Space definition — JSON format reference

Smplrspace engineering · May 2026

Testing before the API is ready

Open any space in the editor, go to the Advanced tab in Space properties, and use Import JSON file. Always import into a fresh blank space — importing the same JSON twice leaves the autoground empty (it only recomputes when walls change). If you re-import, either toggle Automatic ground off then back on, or change any wall point id by one character to force a recompute.

Important context

This schema was built for editor-based, human-driven modeling. Mixed units, UUID-based point identity, and continuous wall polylines are natural in an interactive editor but require deliberate handling in a conversion pipeline. Review sections 11 and 12 before building one.

1. JSON structure overview

A space definition has two top-level keys: space (geometry) and optional spaceMeta (metadata). All geometry lives under space:

space ├── defaults — fallback values for dimensions ├── camerasEnabled — ["orbit"] ├── compassDirection? — optional compass bearing in degrees └── levels[] — one entry per floor (bottom to top) ├── grounds[] — floor slabs ├── walls[] — vertical wall structures ├── roofs[] — not publicly accessible, leave empty ├── stairs[] — staircases connecting levels ├── annotations[] — text labels in 3D └── objects[] — furniture and equipment

All id fields are UUID v4 strings.

2. Coordinate and unit system

Mixed units — the most common mistake

Coordinates use meters; all dimensions use centimeters. A 10 m × 6 m room should span 10 and 6 units in r/t — not 1000 and 600. Using cm for coordinates renders the space 100× too large.

Value categoryUnitExample
r and t 2D coordinatesmeters10 m × 6 m room: r ∈ [0, 10], t ∈ [0, 6]
All dimensions (thickness, height, width, baseHeight, object dims…)cm3 m wall → height: 300; 1.6 m desk → length: 160
Opening position.distancemetersDoor 1.5 m from segment start → distance: 1.5

Coordinate axes

(0, 0) (10, 0) (10, 6) (0, 6) r → east (meters) t → south (meters) 10 m 6 m

A 10 m × 6 m room. Increasing r moves east; increasing t moves south. In 3D: r = x, t = −z.

There is no fixed origin — coordinates are only meaningful relative to one another. A room at r ∈ [0, 10] and one at r ∈ [1000, 1010] are identical in size.

3. Global defaults

The defaults object provides fallback values for all optional dimension fields. Features that omit a dimension inherit the corresponding default.

FieldDefaultUnitNotes
unit"cm-sqm"—Display label only, not used in rendering
groundThickness1cmFloor slab thickness
wallHeight300cm3 m default
wallThickness15cm
roofBaseHeight0cm
roofTopHeight180cm
roofAngle0deg
doorWidth90cm
doorHeight200cm
doorBaseHeight0cmDoors start at floor level
doorOpenAngle90degDefault swing angle
windowWidth90cm
windowHeight110cm
windowBaseHeight90cmSill height from floor
railingHeight110cm
stairsStepSize25cm
annotationHeight120cm
annotationSize14pt

All *Layers fields (wallLayers, groundLayers, doorLayers, windowLayers, stairsLayers, objectLayers, annotationLayers) must be present and set to [].

interface SpaceDefaults {
  unit: string;             // display only — default "cm-sqm"
  groundThickness: number;  // cm — default 1
  wallHeight: number;       // cm — default 300
  wallThickness: number;    // cm — default 15
  roofBaseHeight: number;   // cm — default 0
  roofTopHeight: number;    // cm — default 180
  roofAngle: number;        // degrees — default 0
  doorWidth: number;        // cm — default 90
  doorHeight: number;       // cm — default 200
  doorBaseHeight: number;   // cm — default 0
  doorOpenAngle: number;    // degrees — default 90
  windowWidth: number;      // cm — default 90
  windowHeight: number;     // cm — default 110
  windowBaseHeight: number; // cm — default 90
  railingHeight: number;    // cm — default 110
  stairsStepSize: number;   // cm — default 25
  annotationHeight: number; // cm — default 120
  annotationSize: number;   // pt — default 14
  wallLayers: []; groundLayers: []; doorLayers: [];
  windowLayers: []; stairsLayers: []; objectLayers: []; annotationLayers: [];
}

4. Levels

Levels are ordered bottom-to-top by array index. Index 0 is the lowest level in the definition.

FieldTypeNotes
namestringDisplayed in the level switcher
initialsstring3 chars max, shown in the level switcher button
autoInitialsbooleanLet the editor auto-generate initials from name
floorplan.urlstring?URL to a tracing image
floorplan.r / .tnumberm — offset of the image origin
floorplan.scalenumberScale factor for the tracing image
roofs[]Not a publicly accessible feature — always leave empty
interface Level {
  name: string;
  initials: string;         // 3 chars max
  autoInitials: boolean;
  grounds: Ground[];
  walls: Wall[];
  roofs: [];                // not publicly accessible — always empty
  stairs: Stair[];
  annotations: Annotation[];
  objects: SpaceObject[];
  floorplan: {
    url?: string;           // tracing image URL
    r: number;              // m — image origin offset
    t: number;              // m — image origin offset
    scale: number;
  };
}

Example

{
  "name": "Ground floor",
  "initials": "GF",
  "autoInitials": false,
  "grounds": [], "walls": [], "roofs": [],
  "stairs": [], "annotations": [], "objects": [],
  "floorplan": { "r": 0, "t": 0, "scale": 1 }
}

5. Grounds

Grounds are floor slabs rendered as 3D extruded polygons.

Options

OptionDefaultNotes
type"normal""normal" | "shell" (outline, no 3D fill) | "norender" (invisible, still counted in area)
style"indoor""indoor" | "grass" | "water" | "darkgrey"
externalfalseMarks the ground as exterior — excluded from automatic facade detection
baseHeight0cm — elevation above the level base
thicknessfrom defaultscm — slab thickness, extruded downward
contour—cm — expands ground to align with wall face; leave unset for most spaces
interface Ground {
  id: string;               // prefix "autoground-" for autoground
  type: "ground";
  name: string;
  options: {
    type: "normal" | "shell" | "norender"; // default "normal"
    style: "indoor" | "grass" | "water" | "darkgrey"; // default "indoor"
    external: boolean;      // default false
    baseHeight?: number;    // cm
    thickness?: number;     // cm
    contour?: number;       // cm — leave unset for most spaces
  };
  segments: {
    // closed polygon: last end.id === first start.id
    start: { id: string; r: number; t: number }; // meters
    end:   { id: string; r: number; t: number }; // meters
  }[];
  holes: {
    id: string; type: "hole"; name: string;
    segments: {
      start: { id: string; r: number; t: number };
      end:   { id: string; r: number; t: number };
    }[];
  }[];
  layers: [];
}

Closed polygon rule

Ground segments form a closed polygon: the end.id of the last segment must equal the start.id of the first. An unclosed polygon renders nothing. Consecutive segments share point IDs: the end.id of segment N is the start.id of segment N+1.

Holes cut openings inside the ground polygon (e.g. stairwell voids) using the same closed polygon structure.

Autoground — recommended for most spaces

Instead of computing a ground polygon manually, use autoground: the viewer automatically traces the outer wall perimeter on first load.

Examples

// Autoground placeholder
{
  "id": "autoground-cc1688b0-b0a0-4a73-8ff7-a51d20a16ac7",
  "type": "ground",
  "name": "Automatic ground",
  "options": { "type": "normal", "thickness": 0.2, "style": "indoor", "external": false },
  "segments": [], "holes": [], "layers": []
}

// Manual ground — 10 m × 6 m rectangle (last end.id = first start.id → closed ✓)
{
  "id": "3f2504e0-4f89-4372-a567-0e02b2c3d479",
  "type": "ground",
  "name": "Main floor",
  "options": { "type": "normal", "style": "indoor", "external": false, "thickness": 15 },
  "segments": [
    { "start": { "id": "pt-sw", "r": 0.0,  "t": 0.0 }, "end": { "id": "pt-se", "r": 10.0, "t": 0.0 } },
    { "start": { "id": "pt-se", "r": 10.0, "t": 0.0 }, "end": { "id": "pt-ne", "r": 10.0, "t": 6.0 } },
    { "start": { "id": "pt-ne", "r": 10.0, "t": 6.0 }, "end": { "id": "pt-nw", "r": 0.0,  "t": 6.0 } },
    { "start": { "id": "pt-nw", "r": 0.0,  "t": 6.0 }, "end": { "id": "pt-sw", "r": 0.0,  "t": 0.0 } }
  ],
  "holes": [], "layers": []
}

6. Walls

Walls are vertical structures rendered from a buffered polyline. Each wall object contains one or more segments.

Options

OptionDefaultNotes
type"normal""normal" | "structural" (darker) | "shell" (outline) | "norender" (invisible)
cap"flat""flat" | "round" — use "round" only for pillars (same start and end point)
heightfrom defaultscm
thicknessfrom defaultscm
autoFacadetrueSet false for programmatic import — disables the editor's auto-facade detection
partOfFacadefalseSet true for exterior walls
interface Wall {
  id: string;
  type: "wall";
  name: string;
  options: {
    type: "normal" | "structural" | "shell" | "norender"; // default "normal"
    cap: "flat" | "round";  // default "flat"; "round" for pillars only
    height?: number;        // cm
    thickness?: number;     // cm
    autoFacade: boolean;    // set false for programmatic import
    partOfFacade: boolean;  // set true for exterior walls
  };
  segments: {
    // continuous polyline: seg[n].end.id === seg[n+1].start.id
    id: string;
    start: { id: string; r: number; t: number }; // meters
    end:   { id: string; r: number; t: number }; // meters
    openings: Opening[];
  }[];
  layers: [];
}

Continuous polyline rule

Each wall's segments form a continuous chain: the end.id of segment N equals the start.id of segment N+1. A wall whose last end.id equals its first start.id is a closed polygon (building envelope with clean mitre joins at all corners). An open polyline is an interior partition. See modeling guidelines for topology rules.

Example

// L-shaped partition — two segments, shared corner point creates a clean mitre join ✓
{
  "id": "55aa12c1-d027-4f6d-b272-2839c77f1b55",
  "type": "wall",
  "name": "Partition",
  "options": { "type": "normal", "cap": "flat", "autoFacade": false, "partOfFacade": false },
  "segments": [
    {
      "id": "seg-1",
      "start": { "id": "pt-a", "r": 5.0, "t": 0.0 },
      "end":   { "id": "pt-b", "r": 5.0, "t": 4.0 },
      "openings": []
    },
    {
      "id": "seg-2",
      "start": { "id": "pt-b", "r": 5.0, "t": 4.0 },  // same UUID as above ← clean join
      "end":   { "id": "pt-c", "r": 9.0, "t": 4.0 },
      "openings": []
    }
  ],
  "layers": []
}

7. Openings (doors and windows)

Openings are embedded inside wall segments in the segment.openings array.

Dimensions

FieldDoor defaultWindow default
width90 cm90 cm
height200 cm110 cm
baseHeight0 cm (starts at floor)90 cm (sill height)
interface Opening {
  id: string;
  name: string;
  type: "door" | "window";
  dimensions: {
    width?: number;       // cm
    height?: number;      // cm
    baseHeight?: number;  // cm
  };
  position: {
    from: "start" | "end";
    distance: number;     // meters to opening center
  };
  options: DoorOptions | WindowOptions;
  layers: [];
}

Position

start end distance (meters) width (cm)

from: "start" — distance is measured to the center of the opening. Choose the closest end to minimize the value. The opening must satisfy: distance + width/200 ≤ segment_length (widths in cm, lengths in meters). An opening that doesn't fit is silently dropped.

Options

OptionTypeDefaultNotes
stylestring"normal" "normal" (swing) | "sliding" | "revolving" | "roller-shutter" | "railing"
glassbooleanfalseSwing doors only: renders panels in glass material
nbOfPanelsnumber2Sliding doors: total panel count. Otherwise no effect.
twoPanelsbooleanfalse
centerSplitbooleanfalse
swapKnobbooleanfalse
swapInOutbooleanfalse
swapPanelsbooleanfalse
openFromstring"right""left" | "right" | "center"
panelsOpenednumber1Sliding doors: number of panels in open position
openAnglenumber?from defaultsSwing angle in degrees
railingStylestring"horizontal"Only applies when style: "railing"
railingHeightnumber?from defaultscm — only when style: "railing"
OptionTypeDefaultNotes
stylestring"normal" "normal" | "open-air" (no glass) | "multi-panels" | "railing"
nbOfPanelsnumber2style: "multi-panels" only: column count
nbOfPanelsVerticallynumber1style: "multi-panels" only: row count
railingStylestring"horizontal"Only applies when style: "railing"
railingHeightnumber?from defaultscm — only when style: "railing"
interface DoorOptions {
  style: "normal" | "sliding" | "revolving" | "roller-shutter" | "railing";
  glass: boolean;               // default false — swing doors only
  nbOfPanels: number;           // default 2 — sliding: total panel count
  twoPanels: boolean;           // default false
  centerSplit: boolean;         // default false
  swapKnob: boolean;            // default false
  swapInOut: boolean;           // default false
  swapPanels: boolean;          // default false
  openFrom: "left" | "right" | "center"; // default "right"
  panelsOpened: number;         // default 1 — sliding doors
  openAngle?: number;           // degrees
  railingStyle: string;         // default "horizontal"
  railingHeight?: number;       // cm
}

interface WindowOptions {
  style: "normal" | "open-air" | "multi-panels" | "railing";
  nbOfPanels: number;           // default 2 — multi-panels: column count
  nbOfPanelsVertically: number; // default 1 — multi-panels: row count
  railingStyle: string;         // default "horizontal"
  railingHeight?: number;       // cm
}

Examples

// Swing door (style: "normal")
{
  "id": "a3a35b34-e796-419e-81ca-71715800ba06",
  "name": "Office door",
  "type": "door",
  "dimensions": { "width": 90, "height": 210, "baseHeight": 0 },
  "position": { "from": "start", "distance": 2.0 },
  "options": {
    "style": "normal", "glass": false, "openFrom": "right",
    "nbOfPanels": 2, "nbOfPanelsVertically": 1,
    "twoPanels": false, "centerSplit": false,
    "swapKnob": false, "swapInOut": false, "swapPanels": false,
    "panelsOpened": 1, "railingStyle": "horizontal"
  },
  "layers": []
}

// Window — glass fill is always rendered; use style "open-air" to remove it
{
  "id": "60f6466d-3e1c-4dc6-943b-376f4d9a1b82",
  "name": "Office window",
  "type": "window",
  "dimensions": { "width": 120, "height": 110, "baseHeight": 90 },
  "position": { "from": "start", "distance": 3.0 },
  "options": { "style": "normal" },
  "layers": []
}

// Multi-panel window — 3 columns × 2 rows (requires style: "multi-panels")
{
  "id": "e2f42686-93f2-4198-be46-7f1f6489a7f0",
  "name": "Storefront window",
  "type": "window",
  "dimensions": { "width": 240, "height": 150, "baseHeight": 90 },
  "position": { "from": "start", "distance": 2.0 },
  "options": { "style": "multi-panels", "nbOfPanels": 3, "nbOfPanelsVertically": 2 },
  "layers": []
}

// Sliding door
{
  "id": "066ae1a1-bb80-4046-ae63-aa4ec1d7aa8d",
  "name": "Sliding partition door",
  "type": "door",
  "dimensions": { "width": 150, "height": 210, "baseHeight": 0 },
  "position": { "from": "start", "distance": 1.25 },
  "options": {
    "style": "sliding", "nbOfPanels": 2, "panelsOpened": 1, "openFrom": "right",
    "nbOfPanelsVertically": 1, "twoPanels": false, "centerSplit": false,
    "swapKnob": false, "swapInOut": false, "swapPanels": false,
    "glass": false, "railingStyle": "horizontal"
  },
  "layers": []
}

8. Objects (furniture and equipment)

Parametric cube

catalogId: "d04914df-0d0c-4836-aa11-218eeb1786c2" — available in every organization. Set "configuration": {} and any dimensions. Use it as a placeholder for desks, equipment, or zones until real catalog items are sourced.

Position uses 3D axes, not canvas axes

Object position uses (x, z) in meters, not canvas (r, t). The mapping is x = r, z = −t. Canvas point (r: 7.0, t: 1.5) → { "x": 7.0, "z": -1.5, "elevation": 0.0 }.

FieldUnitNotes
catalogId—UUID of the catalog item
position.xmEast/west (= r)
position.zmNorth/south (= −t)
position.elevationmAbove level base
rotation.yawdegRotation around vertical axis
rotation.pitch / .rolldegGenerally set to 0
dimensions.length / .width / .heightcmRendered size
configuration—{} for parametric cube; null routes to glTF path
interface SpaceObject {
  catalogId: string;
  id: string;
  name: string;
  levelIndex: number;
  position: {
    x: number;            // m — equals r
    z: number;            // m — equals -t
    elevation: number;    // m
  };
  rotation: {
    pitch: number;        // degrees
    yaw: number;          // degrees
    roll: number;         // degrees
  };
  dimensions: {
    length: number;       // cm
    width: number;        // cm
    height: number;       // cm
  };
  configuration?: any;    // {} for parametric cube; null for glTF
  layers?: [];
}

Example

{
  "catalogId": "d04914df-0d0c-4836-aa11-218eeb1786c2",
  "id": "9f5ddb23-fd57-4759-8ea6-e85c6eff40e0",
  "name": "Desk placeholder",
  "levelIndex": 0,
  "position": { "x": 7.0, "z": -1.5, "elevation": 0.0 },
  "rotation": { "pitch": 0, "yaw": 0, "roll": 0 },
  "dimensions": { "length": 160, "width": 80, "height": 75 },
  "configuration": {},
  "layers": []
}

9. Annotations

Deprecation notice

Annotations will be deprecated in favor of a dedicated data layer with more rendering control and dynamic data support. Plan to migrate once the data layer is available.

Annotations are text labels rendered in 3D space with occlusion handling.

FieldDefaultNotes
type—"classic" | "entrance"
name—Label text
r / t—m
height120 (from defaults)cm — elevation above floor
size14 (from defaults)pt — font size
interface Annotation {
  id: string;
  type: "classic" | "entrance";
  name: string;
  r: number;            // m
  t: number;            // m
  height?: number;      // cm — default from defaults.annotationHeight
  size?: number;        // pt — default from defaults.annotationSize
  layers: [];
}

10. Stairs

Stairs span between two levels. Each staircase is defined by two edge segments — a bottom edge (lower floor) and a top edge (upper floor) — each with exactly two points in plan view.

FieldNotes
bottomSegment2 points at the lower floor (m)
topSegment2 points at the upper floor (m)
baseHeightcm — elevation of the bottom edge; default 0
heightcm — total vertical rise
style"slope" (ramp) | "thin" | "thick" | "block"
color"white" | "grey" | "darkgrey"
interface Stair {
  id: string;
  name: string;
  bottomSegment: { id: string; r: number; t: number }[]; // 2 points
  topSegment:    { id: string; r: number; t: number }[]; // 2 points
  baseHeight?: number;  // cm — default 0
  height?: number;      // cm
  style: "slope" | "thin" | "thick" | "block";
  color: "white" | "grey" | "darkgrey";
  layers: [];
}

Example

{
  "id": "3f2504e0-4f89-4372-a567-0e02b2c3d479",
  "name": "Main staircase",
  "bottomSegment": [
    { "id": "stair-b1", "r": 2.0, "t": 4.0 },
    { "id": "stair-b2", "r": 4.0, "t": 4.0 }
  ],
  "topSegment": [
    { "id": "stair-t1", "r": 2.0, "t": 0.0 },
    { "id": "stair-t2", "r": 4.0, "t": 0.0 }
  ],
  "baseHeight": 0,
  "height": 300,
  "style": "thin",
  "color": "white",
  "layers": []
}

11. Modeling guidelines

Walls

Rule 1 — A wall object must be a single continuous polyline

Each wall's segments form a continuous chain: end.id of segment N = start.id of segment N+1. This allows the rendering engine to compute clean MITRE joins at corners. Breaking a run into separate Wall objects gives each wall its own end cap at the break instead of a clean join.

✓ Correct
One Wall object A B shared C seg1: A→B seg2: B→C
✗ Wrong
Two Wall objects B₁ B₂ gap / spike at corner ✗

Rule 2 — Draw the facade as one closed-polygon wall

The building envelope should be a single Wall whose chain closes on itself (last end.id = first start.id). Interior partitions are separate Wall objects. Drawing room-by-room with each room as its own closed wall doubles every exterior wall — causing z-fighting, incorrect area calculations, and visual artifacts.

✓ Correct
Wall "Facade" (closed polygon) Wall "Partition" no doubled walls ✓
✗ Wrong
Wall "Room1" + Wall "Room2" doubled exterior wall ✗

Rule 3 — T-junctions: interior walls stop at the exterior wall centerline

When an interior partition meets an exterior wall, its endpoint lands at the centerline of the exterior wall. The buffered shapes overlap at the junction, which the renderer handles correctly. You do not need to account for the exterior wall's thickness.

Rule 4 — Avoid acute angles (< 30°)

The MITRE join style produces sharp spikes at very acute angles. A 5 m maximum extension limit applies — beyond this the join falls back to a bevel. Designs with very acute corners will not render cleanly.

Grounds

Openings

UUIDs

12. Risks and considerations

RiskImpactMitigation
Mixed units — cm for r/t Space renders 100× too large Always meters for coordinates, cm for dimensions. Validate r/t ranges visually.
Broken corners — two separate Wall objects at a corner Visual gaps or spikes at every junction Connected wall runs must be one Wall object with shared point IDs at corners.
Unclosed ground — last end.id ≠ first start.id Ground not rendered at all Verify polygon closure in code before ingestion.
Opening out of bounds Opening silently vanishes Pre-validate: distance + width/200 ≤ segment_length.
type: "norender" on ground or wall Feature invisible but still counted in area Only use "norender" intentionally. Default is "normal".
Missing layers: [] Possible parse error at load time Include "layers": [] on every feature, even if unused.
UUID collisions from non-random generation Points unexpectedly merge; walls deform Use a proper UUID v4 library.
Schema not designed for programmatic conversion Constraints feel unnatural (shared point IDs, unit mix) Build explicit validation and topology checks into the conversion pipeline.

13. Full schema reference

All id fields are UUID v4 strings. Fields marked ? are optional.

Show full TypeScript schema
interface SpaceDefinition {
  space: {
    camerasEnabled: ["orbit"];
    compassDirection?: number; // degrees
    defaults: {
      unit: string;             // display only — default "cm-sqm"
      groundThickness: number;  // cm — default 1
      wallHeight: number;       // cm — default 300
      wallThickness: number;    // cm — default 15
      roofBaseHeight: number;   // cm — default 0
      roofTopHeight: number;    // cm — default 180
      roofAngle: number;        // degrees — default 0
      doorWidth: number;        // cm — default 90
      doorHeight: number;       // cm — default 200
      doorBaseHeight: number;   // cm — default 0
      doorOpenAngle: number;    // degrees — default 90
      windowWidth: number;      // cm — default 90
      windowHeight: number;     // cm — default 110
      windowBaseHeight: number; // cm — default 90
      railingHeight: number;    // cm — default 110
      stairsStepSize: number;   // cm — default 25
      annotationHeight: number; // cm — default 120
      annotationSize: number;   // pt — default 14
      wallLayers: []; groundLayers: []; doorLayers: [];
      windowLayers: []; stairsLayers: []; objectLayers: []; annotationLayers: [];
    };
    levels: {
      name: string;
      initials: string;       // 3 chars max
      autoInitials: boolean;
      grounds: {
        id: string;           // prefix "autoground-" for autoground
        type: "ground";
        name: string;
        options: {
          type: "normal" | "shell" | "norender";             // default "normal"
          style: "indoor" | "grass" | "water" | "darkgrey"; // default "indoor"
          external: boolean;    // default false
          baseHeight?: number;  // cm
          thickness?: number;   // cm
          contour?: number;     // cm — leave unset for most spaces
        };
        segments: {
          // closed polygon: last end.id === first start.id
          start: { id: string; r: number; t: number }; // meters
          end:   { id: string; r: number; t: number }; // meters
        }[];
        holes: {
          id: string; type: "hole"; name: string;
          segments: {
            start: { id: string; r: number; t: number };
            end:   { id: string; r: number; t: number };
          }[];
        }[];
        layers: [];
      }[];
      walls: {
        id: string; type: "wall"; name: string;
        options: {
          type: "normal" | "structural" | "shell" | "norender"; // default "normal"
          cap: "flat" | "round";  // default "flat"; "round" for pillars only
          height?: number;        // cm
          thickness?: number;     // cm
          autoFacade: boolean;    // default true; set false for programmatic import
          partOfFacade: boolean;  // default false; set true for exterior walls
        };
        segments: {
          // continuous polyline: seg[n].end.id === seg[n+1].start.id
          id: string;
          start: { id: string; r: number; t: number }; // meters
          end:   { id: string; r: number; t: number }; // meters
          openings: {
            id: string; name: string;
            type: "door" | "window";
            dimensions: {
              width?: number;      // cm
              height?: number;     // cm
              baseHeight?: number; // cm
            };
            position: {
              from: "start" | "end";
              distance: number;    // meters to opening center
            };
            options: {
              // doors:   "normal" (swing) | "sliding" | "revolving" | "roller-shutter" | "railing"
              // windows: "normal" | "open-air" | "multi-panels" | "railing"
              style: string;
              glass: boolean;               // default false — doors (swing) only
              twoPanels: boolean;           // default false — doors only
              centerSplit: boolean;         // default false — doors only
              swapKnob: boolean;            // default false — doors only
              swapInOut: boolean;           // default false — doors only
              swapPanels: boolean;          // default false — doors only
              openFrom: string;             // default "right" — doors only
              panelsOpened: number;         // default 1 — sliding doors
              openAngle?: number;           // degrees — doors only
              nbOfPanels: number;           // default 2; sliding doors: panel count; multi-panels windows: columns
              nbOfPanelsVertically: number; // default 1 — windows multi-panels only
              railingStyle: string;         // default "horizontal"
              railingHeight?: number;       // cm
            };
            layers: [];
          }[];
        }[];
        layers: [];
      }[];
      roofs: [];
      stairs: {
        id: string; name: string;
        bottomSegment: { id: string; r: number; t: number }[];
        topSegment:    { id: string; r: number; t: number }[];
        baseHeight?: number; // cm — default 0
        height?: number;     // cm
        style: "slope" | "thin" | "thick" | "block";
        color: "white" | "grey" | "darkgrey";
        layers: [];
      }[];
      annotations: {
        id: string;
        type: "classic" | "entrance";
        name: string; r: number; t: number; // meters
        height?: number; // cm
        size?: number;   // pt
        layers: [];
      }[];
      objects: {
        catalogId: string; id: string; name: string;
        levelIndex: number;
        position: { x: number; z: number; elevation: number }; // meters; x=r, z=-t
        rotation: { pitch: number; yaw: number; roll: number }; // degrees
        dimensions: { length: number; width: number; height: number }; // cm
        configuration?: any; // {} for parametric cube; null routes to glTF
        layers?: [];
      }[];
      floorplan: { url?: string; r: number; t: number; scale: number };
    }[];
  };
}

14. Worked example

A 10 m × 6 m room with a front door on the south wall, a multi-panel window on the east wall, an L-shaped interior partition with a sliding door, and a desk placeholder object.

Points of note
  • Autoground — empty segments, recomputed by the viewer on first load.
  • Facade is a closed polygon: last end.id = first start.id (SW corner b4d9fc51-…719f).
  • All four corner UUIDs (SW, SE, NE, NW) are unique and shared between consecutive segments.
  • East window uses style: "multi-panels", nbOfPanels: 3, nbOfPanelsVertically: 2 → 3×2 panel grid.
  • Partition shares point 809158a0-… at (r:3, t:3) between its two segments — clean MITRE join at the corner.
  • Desk position: canvas (r:7, t:1.5) → { "x": 7.0, "z": -1.5 }.
Show full worked example
{
  "space": {
    "camerasEnabled": ["orbit"],
    "defaults": {
      "unit": "cm-sqm", "groundThickness": 1, "wallHeight": 300, "wallThickness": 15,
      "roofBaseHeight": 0, "roofTopHeight": 180, "roofAngle": 0,
      "doorWidth": 90, "doorHeight": 200, "doorBaseHeight": 0, "doorOpenAngle": 90,
      "windowWidth": 90, "windowHeight": 110, "windowBaseHeight": 90,
      "railingHeight": 110, "stairsStepSize": 25, "annotationHeight": 120, "annotationSize": 14,
      "wallLayers": [], "groundLayers": [], "doorLayers": [],
      "windowLayers": [], "stairsLayers": [], "objectLayers": [], "annotationLayers": []
    },
    "levels": [{
      "name": "Ground floor", "initials": "GF", "autoInitials": false,
      "grounds": [{
        "id": "autoground-cc1688b0-b0a0-4a73-8ff7-a51d20a16ac7",
        "type": "ground", "name": "Automatic ground",
        "options": { "type": "normal", "thickness": 0.2, "style": "indoor", "external": false },
        "segments": [], "holes": [], "layers": []
      }],
      "walls": [
        {
          "id": "55aa12c1-d027-4f6d-b272-2839c77f1b55",
          "type": "wall", "name": "Facade",
          "options": { "type": "normal", "cap": "flat", "autoFacade": false, "partOfFacade": true },
          "segments": [
            {
              "id": "1376fafb-4d78-4729-8a27-4af84933a16b",
              "start": { "id": "b4d9fc51-bfad-4262-ba44-ba00c2e1719f", "r": 0.0, "t": 0.0 },
              "end":   { "id": "a43d6984-62c8-4ddd-85bf-1f3ede4cd7b3", "r": 10.0, "t": 0.0 },
              "openings": [{
                "id": "a3a35b34-e796-419e-81ca-71715800ba06",
                "name": "Front door", "type": "door",
                "dimensions": { "width": 90, "height": 210, "baseHeight": 0 },
                "position": { "from": "start", "distance": 5.0 },
                "options": {
                  "style": "normal", "glass": false, "openFrom": "right",
                  "nbOfPanels": 2, "nbOfPanelsVertically": 1,
                  "twoPanels": false, "centerSplit": false,
                  "swapKnob": false, "swapInOut": false, "swapPanels": false,
                  "panelsOpened": 1, "railingStyle": "horizontal"
                },
                "layers": []
              }]
            },
            {
              "id": "9d648555-2feb-4b4d-9c67-c83b4337ef3c",
              "start": { "id": "a43d6984-62c8-4ddd-85bf-1f3ede4cd7b3", "r": 10.0, "t": 0.0 },
              "end":   { "id": "b7a55b1c-7115-4c88-b5c1-7f0fd7e894b1", "r": 10.0, "t": 6.0 },
              "openings": [{
                "id": "e2f42686-93f2-4198-be46-7f1f6489a7f0",
                "name": "East window", "type": "window",
                "dimensions": { "width": 120, "height": 110, "baseHeight": 90 },
                "position": { "from": "start", "distance": 1.5 },
                "options": { "style": "multi-panels", "nbOfPanels": 3, "nbOfPanelsVertically": 2 },
                "layers": []
              }]
            },
            {
              "id": "60f6466d-3e1c-4dc6-943b-376f4d9a1b82",
              "start": { "id": "b7a55b1c-7115-4c88-b5c1-7f0fd7e894b1", "r": 10.0, "t": 6.0 },
              "end":   { "id": "6c5a302b-71a1-4fa8-8e55-f1e21480bc7a", "r": 0.0, "t": 6.0 },
              "openings": []
            },
            {
              "id": "eda50cd6-2bf8-42b1-aaa1-81e44b56a44f",
              "start": { "id": "6c5a302b-71a1-4fa8-8e55-f1e21480bc7a", "r": 0.0, "t": 6.0 },
              "end":   { "id": "b4d9fc51-bfad-4262-ba44-ba00c2e1719f", "r": 0.0, "t": 0.0 },
              "openings": []
            }
          ],
          "layers": []
        },
        {
          "id": "ec2b9959-94ff-462c-8cf5-24556bf654de",
          "type": "wall", "name": "Partition",
          "options": { "type": "normal", "cap": "flat", "autoFacade": false, "partOfFacade": false },
          "segments": [
            {
              "id": "e6de4a8d-5bc8-4b2d-a579-d94c1c0ff93c",
              "start": { "id": "5cab7af3-92c8-423e-9a6f-f43a4ec8f731", "r": 3.0, "t": 6.0 },
              "end":   { "id": "809158a0-0e32-4dbe-a9c3-bcb8cf3c64f6", "r": 3.0, "t": 3.0 },
              "openings": []
            },
            {
              "id": "d52be4a4-0451-417f-923d-edb00aa22393",
              "start": { "id": "809158a0-0e32-4dbe-a9c3-bcb8cf3c64f6", "r": 3.0, "t": 3.0 },
              "end":   { "id": "aad84bcb-016f-448b-8d52-2e821e73ea92", "r": 10.0, "t": 3.0 },
              "openings": [{
                "id": "066ae1a1-bb80-4046-ae63-aa4ec1d7aa8d",
                "name": "Partition sliding door", "type": "door",
                "dimensions": { "width": 150, "height": 210, "baseHeight": 0 },
                "position": { "from": "start", "distance": 1.25 },
                "options": {
                  "style": "sliding", "nbOfPanels": 2, "panelsOpened": 1, "openFrom": "right",
                  "nbOfPanelsVertically": 1, "twoPanels": false, "centerSplit": false,
                  "swapKnob": false, "swapInOut": false, "swapPanels": false,
                  "glass": false, "railingStyle": "horizontal"
                },
                "layers": []
              }]
            }
          ],
          "layers": []
        }
      ],
      "roofs": [], "stairs": [], "annotations": [],
      "objects": [{
        "catalogId": "d04914df-0d0c-4836-aa11-218eeb1786c2",
        "id": "9f5ddb23-fd57-4759-8ea6-e85c6eff40e0",
        "name": "Desk placeholder", "levelIndex": 0,
        "position": { "x": 7.0, "z": -1.5, "elevation": 0.0 },
        "rotation": { "pitch": 0, "yaw": 0, "roll": 0 },
        "dimensions": { "length": 160, "width": 80, "height": 75 },
        "configuration": {}, "layers": []
      }],
      "floorplan": { "r": 0, "t": 0, "scale": 1 }
    }]
  }
}

Live viewer