Overview
Nodes are the data primitives that describe the 3D scene in Pascal Editor. All nodes extend BaseNode and are stored in a flat dictionary structure with parent-child relationships defined via parentId.
BaseNode Structure
All nodes in the scene extend the BaseNode schema defined with Zod:
export const BaseNode = z . object ({
object: z . literal ( 'node' ). default ( 'node' ),
id: z . string (), // Auto-generated with type prefix (e.g., "wall_abc123")
type: nodeType ( 'node' ), // Discriminator for type-safe handling
name: z . string (). optional (),
parentId: z . string (). nullable (). default ( null ),
visible: z . boolean (). optional (). default ( true ),
camera: CameraSchema . optional (),
metadata: z . json (). optional (). default ({}),
})
Location: packages/core/src/schema/base.ts:21-30
Core Properties
Auto-generated unique identifier with type prefix (e.g., wall_abc123, item_xyz789)
Node type discriminator used for type-safe handling ('site', 'building', 'level', 'wall', etc.)
parentId
string | null
default: "null"
Reference to parent node ID. Null for root nodes (typically Site nodes).
Controls whether the node is rendered in the 3D scene.
Optional saved camera position for this node (used for viewpoint navigation).
Arbitrary metadata object. Use { isTransient: true } to prevent persistence to IndexedDB.
Node Hierarchy
Nodes form a tree structure representing the building hierarchy:
Site
└── Building
└── Level
├── Wall → Item (doors, windows)
├── Slab
├── Ceiling → Item (lights)
├── Roof
├── Zone
├── Scan (3D reference)
└── Guide (2D reference)
While nodes form a logical tree, they are stored in a flat dictionary (Record<id, Node>), not a nested structure. Parent-child relationships are defined via parentId and children arrays.
Node Types
The SDK provides type-safe node schemas using Zod’s discriminated union:
export const AnyNode = z . discriminatedUnion ( 'type' , [
SiteNode ,
BuildingNode ,
LevelNode ,
WallNode ,
ItemNode ,
ZoneNode ,
SlabNode ,
CeilingNode ,
RoofNode ,
ScanNode ,
GuideNode ,
WindowNode ,
DoorNode ,
])
export type AnyNode = z . infer < typeof AnyNode >
export type AnyNodeType = AnyNode [ 'type' ]
export type AnyNodeId = AnyNode [ 'id' ]
Location: packages/core/src/schema/types.ts:16-35
Structural Nodes
SiteNode
Represents a site/property. Root node of the scene hierarchy.
export const SiteNode = BaseNode . extend ({
id: objectId ( 'site' ),
type: nodeType ( 'site' ),
polygon: PropertyLineData . optional (). default ({
type: 'polygon' ,
points: [
[ - 15 , - 15 ],
[ 15 , - 15 ],
[ 15 , 15 ],
[ - 15 , 15 ],
],
}),
children: z
. array ( z . discriminatedUnion ( 'type' , [ BuildingNode , ItemNode ]))
. default ([ BuildingNode . parse ({})]),
})
Location: packages/core/src/schema/nodes/site.ts:21-39
BuildingNode
Represents a building within a site.
export const BuildingNode = BaseNode . extend ({
id: objectId ( 'building' ),
type: nodeType ( 'building' ),
children: z . array ( LevelNode . shape . id ). default ([]),
position: z . tuple ([ z . number (), z . number (), z . number ()]). default ([ 0 , 0 , 0 ]),
rotation: z . tuple ([ z . number (), z . number (), z . number ()]). default ([ 0 , 0 , 0 ]),
})
Location: packages/core/src/schema/nodes/building.ts:6-12
LevelNode
Represents a floor level in a building.
export const LevelNode = BaseNode . extend ({
id: objectId ( 'level' ),
type: nodeType ( 'level' ),
children: z . array (
z . union ([
WallNode . shape . id ,
ZoneNode . shape . id ,
SlabNode . shape . id ,
CeilingNode . shape . id ,
RoofNode . shape . id ,
ScanNode . shape . id ,
GuideNode . shape . id ,
])
). default ([]),
level: z . number (). default ( 0 ),
})
Location: packages/core/src/schema/nodes/level.ts:12-18
Building Elements
WallNode
Represents a wall defined by start and end points.
export const WallNode = BaseNode . extend ({
id: objectId ( 'wall' ),
type: nodeType ( 'wall' ),
children: z . array ( ItemNode . shape . id ). default ([]),
thickness: z . number (). optional (),
height: z . number (). optional (),
start: z . tuple ([ z . number (), z . number ()]),
end: z . tuple ([ z . number (), z . number ()]),
frontSide: z . enum ([ 'interior' , 'exterior' , 'unknown' ]). default ( 'unknown' ),
backSide: z . enum ([ 'interior' , 'exterior' , 'unknown' ]). default ( 'unknown' ),
})
Start point in level coordinate system (2D: x, z)
End point in level coordinate system (2D: x, z)
Location: packages/core/src/schema/nodes/wall.ts:9-22
SlabNode
Represents a floor slab defined by a polygon.
export const SlabNode = BaseNode . extend ({
id: objectId ( 'slab' ),
type: nodeType ( 'slab' ),
polygon: z . array ( z . tuple ([ z . number (), z . number ()])),
holes: z . array ( z . array ( z . tuple ([ z . number (), z . number ()]))). default ([]),
elevation: z . number (). default ( 0.05 ),
})
polygon
Array<[number, number]>
required
Array of [x, z] points defining the slab boundary
holes
Array<Array<[number, number]>>
default: "[]"
Array of hole polygons (for openings in the slab)
Elevation in meters (thickness of the slab)
Location: packages/core/src/schema/nodes/slab.ts:5-13
ItemNode
Represents furniture, fixtures, or other 3D assets.
export const ItemNode = BaseNode . extend ({
id: objectId ( 'item' ),
type: nodeType ( 'item' ),
position: z . tuple ([ z . number (), z . number (), z . number ()]). default ([ 0 , 0 , 0 ]),
rotation: z . tuple ([ z . number (), z . number (), z . number ()]). default ([ 0 , 0 , 0 ]),
scale: z . tuple ([ z . number (), z . number (), z . number ()]). default ([ 1 , 1 , 1 ]),
side: z . enum ([ 'front' , 'back' ]). optional (),
children: z . array ( objectId ( 'item' )). default ([]),
// Wall attachment properties
wallId: z . string (). optional (),
wallT: z . number (). optional (), // 0-1 parametric position along wall
asset: assetSchema ,
})
Asset data including model source, dimensions, attachment behavior, and corrective transforms
Wall ID when asset is attached to a wall
Parametric position (0-1) along the wall when attached
scale
[number, number, number]
default: "[1, 1, 1]"
Scale factor for the asset (applied to asset.dimensions)
Location: packages/core/src/schema/nodes/item.ts:28-42
Flat Dictionary Storage
Nodes are stored in a flat dictionary rather than a nested tree:
type SceneState = {
// Flat dictionary of all nodes
nodes : Record < AnyNodeId , AnyNode >
// Top-level nodes (typically Site nodes)
rootNodeIds : AnyNodeId []
// Nodes pending system updates
dirtyNodes : Set < AnyNodeId >
}
Location: packages/core/src/store/use-scene.ts:14-23
Benefits of Flat Storage
Fast Lookups Direct access to any node by ID without tree traversal: nodes[id]
Easy Updates Update nodes without cloning entire tree structure
Simple Serialization Easy to persist to IndexedDB or serialize to JSON
Flexible Relationships Supports multiple parent-child patterns (walls, surfaces, attachments)
ID Generation
Node IDs are auto-generated with type prefixes using nanoid:
const customId = customAlphabet ( '0123456789abcdefghijklmnopqrstuvwxyz' , 16 )
export const generateId = < T extends string >( prefix : T ) : ` ${ T } _ ${ string } ` =>
` ${ prefix } _ ${ customId () } ` as ` ${ T } _ ${ string } `
Examples:
site_a1b2c3d4e5f6g7h8
wall_x9y8z7w6v5u4t3s2
item_p0o9i8u7y6t5r4e3
Location: packages/core/src/schema/base.ts:5-13
The type prefix makes it easy to identify node types in logs and debugging without looking up the full node object.