BRZ: Bruce Archive Format


A .brz file is a Nextspace archive format that packages an Assembly. It is a hierarchy of Entities at a single root Entity node for export, import, and interchange between systems. It carries the Entity attribute values, parent-child hierarchy with transforms, and embedded 3D geometry (GLB).

BRZ files are produced by an Export BRZ job and consumed by the Import Assembly endpoint.

Container format

A BRZ file is a standard ZIP archive containing exactly one entry: a SQLite database named model.db. A ".db" file renamed to ".brz" is not considered valid.

archive.brz
└── model.db

Complete schema

The following is the exact creation script and schema explanation to use for new BRZ files. Copy-paste this to initialise a valid v7 database before inserting any data.

PRAGMA application_id = 1112687682;
PRAGMA user_version = 7;

-- Attribute definitions.
-- One row per distinct attribute that Entities in this archive can carry.
CREATE TABLE Attribute (
    id          INTEGER PRIMARY KEY,
    -- Dot-separated grouping path, e.g. "dimensions.physical".
    -- "Bruce" is reserved for internal use. Never create custom attributes under it.
    category    TEXT,
    name        TEXT,
    -- 0=STRING  1=NUMERIC  2=BOOLEAN  3=TIMESTAMP  4=DATE
    dataType    INT,
    -- Optional regex validation pattern. NULL or empty if no validation is needed.
    pattern     TEXT,
    displayName TEXT,
    description TEXT
);

-- One row per Entity.
-- Keep hierarchy minimal. Prefer direct geometry-bearing Entities for key parts (e.g. DOOR, WALL_EAST, NAS).
-- Avoid unnecessary wrapper or grouping nodes.
-- It is preferred for leaf-nodes to have geometry, and parent nodes to be mostly organizational.
CREATE TABLE Entity (
    -- Local auto-increment. Used for all FK references within this file only.
    internalID  INTEGER PRIMARY KEY,
    name        TEXT,
    -- 0 = regular Entity instance, 1 = prototype definition (deprecated, don't create new prototypes).
    prototype   BOOLEAN,
    -- UUID from the source system. Used for identity matching after an export from Nextspace.
    -- Leave as NULL for generated files that aren't coming from Nextspace.
    ID          TEXT,
    -- References "EntityType.internalid".
    -- Leave as NULL if no defined Entity Types are being brought in.
    typeid      INTEGER,
    -- Optional geohash string for spatial indexing.
    -- Leave as NULL if not applicable.
    geohash     TEXT,
    -- Optional WGS84 geographic position.
    -- Leave as NULL if not applicable (ie: Entities are relatively positioned within the Assembly).
    longitude   DOUBLE PRECISION,
    latitude    DOUBLE PRECISION,
    altitude    DOUBLE PRECISION
);

-- Attribute values for Entities. One row per attribute value per Entity.
-- Populate only the value column that matches the attribute dataType; leave the others NULL.
-- STRING/BOOLEAN/TIMESTAMP/DATE values under ~240 chars -> StringValue.
-- STRING values over ~240 chars -> LongTextValue.
-- NUMERIC values (and booleans as 0.0/1.0) -> NumericValue.
CREATE TABLE EntityAttribute (
    -- References "Entity.internalID".
    entityID      BIGINT,
    -- References "Attribute.id".
    AttributeID   BIGINT,
    StringValue   TEXT,
    LongTextValue TEXT,
    NumericValue  DOUBLE PRECISION
);

-- One row per GLB geometry asset.
-- The binary data must be zlib-compressed (RFC 1950); the importer always decompresses before use.
-- GLB geometry MUST use Y-up (the glTF standard). ParentRelationship transforms must also be in Y-up to match.
-- See the "GLB requirements" section below for mandatory content rules.
CREATE TABLE Geometry (
    id           INTEGER PRIMARY KEY,
    -- e.g. "model/gltf-binary"
    type         TEXT,
    -- UUID from the source system.
    -- Leave as NULL for generated files that aren't coming from Nextspace.
    persistentID TEXT,
    -- Deprecated field.
    -- Always set this to NULL for generated files as it'll prioritize checking filepath over data.
    filepath     TEXT,
    -- Vertex / face / line counts. Set to 0 if unknown.
    nverts       INT,
    nfaces       INT,
    nlines       INT,
    -- Axis-aligned bounding box of the geometry in its local space.
    minx         DOUBLE PRECISION,
    miny         DOUBLE PRECISION,
    minz         DOUBLE PRECISION,
    maxx         DOUBLE PRECISION,
    maxy         DOUBLE PRECISION,
    maxz         DOUBLE PRECISION,
    -- Uncompressed byte size of the GLB.
    size         DOUBLE PRECISION,
    -- CRC32 of the uncompressed GLB bytes. Used by the server for deduplication.
    hash         INTEGER,
    -- Hash algorithm version. Always 1 (CRC32).
    hashVersion  SMALLINT,
    -- Zlib-compressed GLB bytes (RFC 1950: 2-byte zlib header + DEFLATE payload + 4-byte checksum).
    -- Must not be stored uncompressed or as raw DEFLATE without the zlib wrapper.
    -- In Python use: zlib.compress(glb_bytes).
    -- In Node.js use: zlib.deflateSync(glbBuffer) (use deflateSync, NOT deflateRawSync).
    data         VARBINARY
);

-- Defines the parent-child hierarchy and the local transform of each child.
-- Every non-root Entity must have exactly one row here.
-- Shallow hierarchies are recommended. If a part is in the BOM, attach geometry to that Entity directly.
CREATE TABLE ParentRelationship (
    -- Both reference "Entity.internalID".
    parentID    BIGINT,
    childID     BIGINT,
    -- Zero-based ordering index among siblings.
    childNumber INT,
    -- Local translation in Y-up space (matching the GLB coordinate system).
    -- e.g. px = right, py = up, pz = toward viewer.
    px          DOUBLE PRECISION,
    py          DOUBLE PRECISION,
    pz          DOUBLE PRECISION,
    -- Local rotation as a unit quaternion. Identity = (0, 0, 0, 1).
    qx          DOUBLE PRECISION,
    qy          DOUBLE PRECISION,
    qz          DOUBLE PRECISION,
    qw          DOUBLE PRECISION,
    -- Local scale per axis. Identity = (1, 1, 1).
    sx          DOUBLE PRECISION,
    sy          DOUBLE PRECISION,
    sz          DOUBLE PRECISION
);

-- Links instance Entities to their prototype.
-- Only needed when multiple Entities share the same geometric definition.
-- Deprecated and not needed for newly generated files.
CREATE TABLE PrototypeRelationship (
    -- Both reference "Entity.internalID".
    instanceID  BIGINT,
    prototypeID BIGINT
);

-- Optional material references for a geometry row.
-- Can be left empty for generated files.
-- Simple explicit materials are recommended over vertex colors alone.
-- Window/glazing (and similar) materials should be mostly transparent.
CREATE TABLE GeometryMaterial (
    id                INTEGER PRIMARY KEY,
    -- References "Geometry.id".
    geometryID        BIGINT,
    -- Material slot path within the GLB.
    path              TEXT,
    materialReference TEXT
);

-- Alternative to embedding geometry in "Geometry.data": points to a downloadable URL instead.
-- Only populate this if "Geometry.data" is NULL (e.g. for very large assets).
-- Can be left empty for generated files that embed geometry directly.
CREATE TABLE GeometryRemoteURL (
    -- References "Geometry.id".
    geometryID BIGINT PRIMARY KEY,
    url        TEXT
);

-- Not read by the current importer. Can be left empty.
CREATE TABLE EntityRemoteURL (
    -- References "Entity.internalID".
    entityID BIGINT PRIMARY KEY,
    url      TEXT
);

-- Metadata about the account that exported this file.
-- Leave empty if generating files outside of Nextspace.
CREATE TABLE AccountSettings (
    accountID   TEXT,
    accountName TEXT,
    -- e.g. "prod", "uat", "dev".
    environment TEXT
);

-- Generic key/value store.
-- Leave empty if generating files outside of Nextspace.
CREATE TABLE Parameter (
    name  TEXT PRIMARY KEY,
    value TEXT
);

-- Defines the root Entity of the assembly.
-- At least one row is required; the importer uses this to locate the top of the hierarchy.
CREATE TABLE Component (
    id           INTEGER PRIMARY KEY,
    name         TEXT,
    -- References "Entity.internalID" of the root entity.
    rootEntityID BIGINT
);

-- Associates Entities with geometry at specific levels of detail.
-- Add one row per Entity that has geometry.
-- LODs should be marked with LODCategory = "GLB" and LODGroup = "DEFAULT".
CREATE TABLE EntityLOD (
    id          INTEGER PRIMARY KEY,
    -- References "Entity.internalID".
    entityID    BIGINT,
    -- References "Geometry.id".
    geometryID  BIGINT,
    -- 0 = highest detail.
    level       INT,
    -- "GLB" for primary geometry. "BOUNDS" for a bounding-box proxy mesh.
    LODCategory TEXT,
    -- Optional grouping label. NULL is fine.
    LODGroup    TEXT
);

-- Entity Type definitions used by Entities in this archive.
CREATE TABLE EntityType (
    -- Local auto-increment. Used for FK references within this file only.
    internalid     INTEGER PRIMARY KEY,
    -- UUID from the source system. Used for identity matching after an export from Nextspace.
    -- Leave as NULL for generated files that aren't coming from Nextspace.
    id             TEXT,
    name           TEXT,
    description    TEXT,
    -- 1 = reject attributes not in this schema on import. 0 = allow extras.
    isStrictSchema INTEGER
);

-- Maps attributes to Entity Types, forming a typed schema.
CREATE TABLE SchemaAttribute (
    id              INTEGER PRIMARY KEY,
    -- References "EntityType.internalid".
    typeID          INTEGER,
    -- References "Attribute.id".
    attributeID     INTEGER,
    -- Display ordering index.
    attributeNumber INTEGER
);

-- Required indexes.
CREATE UNIQUE INDEX IDX_Geometry_FilePath   ON Geometry (filepath);
CREATE UNIQUE INDEX UIDX_Geometry_Material  ON GeometryMaterial (geometryID, path);
CREATE        INDEX IDX_Geometry_Hash       ON Geometry (hash);
CREATE UNIQUE INDEX IDX_Entity_ID           ON Entity (ID);

Inserting geometry correctly

The most common mistake when generating BRZ files is populating the Geometry.filepath column. When that column is non-empty the importer tries to read a file from disk instead of reading the embedded blob, which silently drops all geometry. Always set filepath to NULL.

-- Correct: filepath is NULL, data contains zlib-compressed GLB bytes.
INSERT INTO Geometry (
    id, type, persistentID,
    filepath,   -- MUST be NULL
    nverts, nfaces, nlines,
    minx, miny, minz, maxx, maxy, maxz,
    size, hash, hashVersion,
    data
) VALUES (
    1, 'model/gltf-binary', NULL,
    NULL,       -- No filename
    24, 12, 0,
    -1.0, -1.0, 0.0, 1.0, 1.0, 2.5,
    2048, 305419896, 1,
    X'789C...'  -- zlib.compress(glb_bytes)
);

-- Then link the Entity to this Geometry via EntityLOD:
INSERT INTO EntityLOD (id, entityID, geometryID, level, LODCategory, LODGroup)
VALUES (1, 2, 1, 0, 'GLB', 'DEFAULT');

GLB requirements

Each GLB embedded in the Geometry table must satisfy the following rules. Missing any of these will cause import failures, Tileset generation crashes, or models rendering incorrectly.

RequirementDescription
At least one materialEvery GLB must define at least one material using pbrMetallicRoughness. A simple default is fine (e.g. baseColorFactor [0.8, 0.8, 0.8, 1.0]). GLBs with zero materials will crash the Tileset generator.
Material reference on every primitiveThe material index on each mesh primitive must be set. Omitting it produces undefined behaviour in downstream processing tools.
Vertex normals (NORMAL)Every mesh primitive must include a NORMAL accessor alongside POSITION. Without normals, lighting is broken and some processing tools may crash.
Indexed triangles (mode 4)Primitives must use indexed triangle mode. Non-indexed geometry or other modes (lines, points) are not reliably supported.
Y-up orientationGeometry must be authored in Y-up space per the glTF specification. +X = right, +Y = up, −Z = forward. The Nextspace viewer (CesiumJS) expects Y-up and automatically converts to its internal Z-up scene coordinate system. Geometry authored in Z-up will appear sideways.
Single-node structureA single scene containing one root node with one child mesh node is the recommended GLB structure. Multi-node GLBs work but single-node is preferred for stable importer and Tileset generation behaviour.

Minimal valid material example

// Inside the glTF JSON of each GLB:
{
  "materials": [
    {
      "pbrMetallicRoughness": {
        "baseColorFactor": [0.8, 0.8, 0.8, 1.0],
        "metallicFactor": 0.0,
        "roughnessFactor": 0.9
      }
    }
  ],
  "meshes": [
    {
      "primitives": [
        {
          "attributes": { "POSITION": 0, "NORMAL": 1 },
          "indices": 2,
          "material": 0
        }
      ]
    }
  ]
}

Positioning and placement

The ParentRelationship transform (px/py/pz, quaternion, scale) is relative to the parent Entity, not world-absolute. The importer converts each row into a 4×4 matrix stored on the child Entity. At render time the viewer computes the world matrix by chaining transforms up the hierarchy: world = parent.world × child.local.

No collision detection, gravity, or snap-to-surface logic is applied. Objects will float, intersect, or sink if their translations are not computed from their actual geometry bounds.

Flat vs. nested hierarchies

In a flat hierarchy (every Entity is a direct child of the root), the root typically has an identity transform, so each child's px/py/pz values are effectively world-space coordinates.

In a nested hierarchy (e.g. monitor is a child of desk), each child is positioned relative to its parent. A monitor at py = 0.76 under a desk means "0.76 m above the desk's local origin", not 0.76 m above the world floor.

Recommended mesh origin convention

For generated assets, place each mesh origin at the centre of its base contact surface so that miny = 0 for floor-standing objects. This simplifies placement: in a flat hierarchy, py is the world-space Y of the surface the object rests on.

Placement from bounds

To place a child so its base sits on a surface, use the Geometry bounding box:

child.py = surface_y - child.miny

In a flat hierarchy, surface_y is the world-space Y of the support surface. In a nested hierarchy it is the parent-local Y of the support surface (e.g. the parent's maxy). If meshes follow the miny = 0 convention, the formula simplifies to py = surface_y.

Worked example (flat hierarchy)

All Entities below are children of the root Entity (1). Because the root has an identity transform, px/py/pz are world positions.

-- Root Entity (1) has identity transform.
-- All children are parented directly to it.

-- Floor slab: miny = 0, maxy = 0.05.
-- Rests at world origin.
INSERT INTO ParentRelationship VALUES
    (1, 2, 0,   0.0, 0.0, 0.0,   0,0,0,1,   1,1,1);

-- Desk: miny = 0, maxy = 0.76.
-- Rests on the floor surface (floor.maxy = 0.05).
INSERT INTO ParentRelationship VALUES
    (1, 3, 1,   0.0, 0.05, 0.0,   0,0,0,1,   1,1,1);

-- Monitor: miny = 0, maxy = 0.41.
-- Rests on the desk surface (desk.py + desk.maxy = 0.05 + 0.76 = 0.81).
INSERT INTO ParentRelationship VALUES
    (1, 4, 2,   0.0, 0.81, 0.0,   0,0,0,1,   1,1,1);

-- Chair: miny = 0, maxy = 1.0.
-- Rests on the floor surface, offset sideways.
INSERT INTO ParentRelationship VALUES
    (1, 5, 3,   -0.6, 0.05, 0.4,   0,0,0,1,   1,1,1);