Scene JSON
This page defines the canonical scene content parsed and exported by the current THOTH dev branch. It is intended for backend and integration developers.
The examples use TypeScript-like notation: Record<string, T> is an object keyed by ID, T[] is an array, and ? marks an optional field. Coordinates are model-local and rotations are in radians.
Scene
type Scene = {
models: Record<string, Model>,
collaborative: boolean
}
models is an object, not an array. The minimal scene is:
{
"models": {},
"collaborative": false
}
Model
type Model = {
id: string,
artefact: Artefact,
metadata: Metadata,
transforms: Transforms,
annotations: Annotations,
sensors: Sensor[]
}
The key in Scene.models and the model's id should be identical. Runtime-only fields and models marked trash: true are omitted from export.
Artefact
type Artefact = {
title: string,
gltf_file: string,
description: string,
owner: string,
keywords: string[],
copyright: string,
[additionalField: string]: unknown
}
gltf_file is the canonical resource URL even when it points to a .glb. The parser also recognizes glb_file, url, path, or src as URL aliases and name as a title alias. Additional source fields may be retained.
Metadata
type Metadata = {
schema: {
name: string,
version: string | number,
description: string,
url: string
},
attributes: Record<string, unknown>
}
Legacy metadata may be a flat attributes object with an optional schemaName. THOTH normalizes it to { schema, attributes }. A non-empty flat object without a schema name defaults to puc_schema.
Transforms
type Vector3 = { x: number, y: number, z: number }
type Transforms = {
translation: Vector3,
rotation: Vector3
}
Both vectors default to zero. Input vectors may also be [x, y, z]; position is accepted as an alias for translation, and the legacy singular transform container is accepted. Scale is not canonical and THOTH forces loaded model scale to { x: 1, y: 1, z: 1 }.
Sensors
THOTH preserves sensors as an opaque array and does not validate or edit each entry. Model-data export uses the first entry. A sensor ID may be a primitive or an object field named related_sensor_id, sensor_id, or id; latest_reading defaults to {}.
Annotations
type Annotations = {
selections: Record<string, Selection>,
measurements: Record<string, Measurement>,
semantic_annotations: Record<string, SemanticAnnotation>
}
All three collections are objects keyed by annotation ID. For legacy input, THOTH also accepts these collections directly on the model, but canonical export nests them under annotations.
Shared annotation fields
type BaseAnnotation = {
id: string,
name: string,
description: string,
related_rgb_images: Relation[],
related_multispectral_images: Relation[],
related_artefacts: Relation[],
annotation: Record<string, unknown>,
visible: boolean
}
type Relation = {
id: string,
name: string,
url: string,
[additionalField: string]: unknown
}
Relations retain additional fields. A multispectral relation can therefore include urls: Record<string, string>. The parser derives missing canonical fields from title, image_name, image_url, gltf_file, path, and src. A primitive relation value becomes both its id and name. Visibility defaults to true.
Selection
type Selection = BaseAnnotation & {
annotation: {
selected_faces: Record<string, string>,
selection_color: string
}
}
selected_faces is keyed by mesh ID. Each exported value is a comma-separated list with inclusive ranges:
{
"mesh_0": "1-3,8,11-14",
"mesh_1": "4,9"
}
The parser also accepts arrays or other iterables of non-negative integer face IDs. Legacy top-level selected_faces, selection, selection_color, and highlightColor fields are accepted. Canonical export nests the data in annotation. The color is a hexadecimal string such as #ff8800.
Measurement
type Measurement = BaseAnnotation & {
annotation: {
coordinate_space: "model_local",
distance: number,
distance_type: "euclidean" | "geodesic" | "geodesicExact" | string,
point1: ScenePoint,
point2: ScenePoint
}
}
distance_type defaults to euclidean. Canonical export always writes coordinate_space: "model_local". Runtime and legacy input may use top-level distance, distance_type, distanceType, point1, point2, or points. A legacy nested point without the coordinate-space marker is interpreted as world-space and converted while the model is available.
The rendered geodesic path is runtime-only and is not exported.
Semantic annotation
type SemanticAnnotation = BaseAnnotation & {
annotation: {
coordinate_space: "model_local",
point: ScenePoint
}
}
Canonical export always writes coordinate_space: "model_local". A legacy top-level point, or a nested point without the coordinate-space marker, is accepted and normalized.
Scene point
type ScenePoint = {
x: number,
y: number,
z: number,
face_id: number | null
}
The parser also recognizes faceId, meshId/mesh_id, meshName/mesh_name, and a nested coords vector. Mesh identifiers help runtime geometry resolution but are not part of canonical point export.
Complete example
{
"models": {
"textile_01": {
"id": "textile_01",
"artefact": {
"title": "Textile 01",
"gltf_file": "models/textile_01.glb",
"description": "",
"owner": "",
"keywords": [],
"copyright": ""
},
"metadata": {
"schema": {
"name": "puc_schema",
"version": 1,
"description": "Dedicated TEXTaiLES schema",
"url": ""
},
"attributes": {}
},
"transforms": {
"translation": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0 }
},
"annotations": {
"selections": {
"selection_1": {
"id": "selection_1",
"name": "Example selection",
"description": "Woven border",
"related_rgb_images": [],
"related_multispectral_images": [],
"related_artefacts": [],
"annotation": {
"selected_faces": { "mesh_0": "1-3,8" },
"selection_color": "#ff8800"
},
"visible": true
}
},
"measurements": {},
"semantic_annotations": {}
},
"sensors": []
}
},
"collaborative": false
}
Export rules
- Missing model fields are normalized to empty canonical values.
- Trashed models and annotations are excluded.
- Runtime state such as Three.js nodes, materials, mesh references, cached paths,
model_id, and helper coordinates is excluded. - Additional artefact, relation, annotation-payload, and sensor fields can survive normalization. Consumers should tolerate unknown fields.