Skip to main content
Nodes are the fundamental building blocks of your 3D scene. This guide shows you how to create walls, slabs, zones, and items programmatically.

Understanding Node Creation

All nodes are created using useScene.getState().createNode(). Each node type has its own schema definition that validates the data structure.
Nodes automatically appear in the 3D scene when created. The SDK handles registering them in the scene graph and making them visible.

Creating Nodes Step-by-Step

1

Import the required schemas

First, import the node schemas and the scene store:
import { useScene, WallNode, SlabNode, ZoneNode, ItemNode } from '@pascal-app/core'
2

Get the parent ID

Most nodes need a parent. Typically, walls, slabs, zones, and items are children of a Level:
// Get the current level ID from the viewer state
const currentLevelId = useViewer.getState().selection.levelId
3

Create the node data

Use the schema’s .parse() method to validate and create the node:
const wall = WallNode.parse({
  start: [0, 0],      // Start point [x, z] in level coordinates
  end: [5, 0],        // End point [x, z]
  thickness: 0.2,     // Wall thickness in meters
  height: 2.7,        // Wall height in meters
  frontSide: 'interior',
  backSide: 'exterior',
})
4

Add the node to the scene

Call createNode() with the node data and parent ID:
useScene.getState().createNode(wall, currentLevelId)
useScene.getState().createNode(slab, currentLevelId)
useScene.getState().createNode(zone, currentLevelId)
useScene.getState().createNode(item, currentLevelId)
The node will immediately appear in the 3D scene and be registered in the scene graph.

Parent-Child Relationships

Nodes form a hierarchy through the parentId property. Understanding this structure is crucial:
// Typical hierarchy:
// Site → Building → Level → [Walls, Slabs, Zones, Items]

// Items can also be children of other items (surface placement)
const table = ItemNode.parse({
  position: [2, 0, 2],
  asset: { /* table asset */ },
})
useScene.getState().createNode(table, currentLevelId)

// Place a lamp on the table
const lamp = ItemNode.parse({
  position: [0, 0.75, 0], // Relative to table surface
  asset: {
    id: 'lamp_01',
    category: 'lighting',
    name: 'Desk Lamp',
    src: '/models/lamp.glb',
    dimensions: [0.2, 0.4, 0.2],
    surface: { height: 0.75 }, // Table surface height
  },
})
useScene.getState().createNode(lamp, table.id) // Parent is the table
When an item is placed on another item’s surface, its position is relative to the parent item’s coordinate system.

Wall-Attached Items

Items can attach to walls using the attachTo property:
// First, create a wall
const wall = WallNode.parse({
  start: [0, 0],
  end: [5, 0],
  thickness: 0.2,
  height: 2.7,
})
useScene.getState().createNode(wall, currentLevelId)

// Create a wall-mounted item (e.g., TV)
const tv = ItemNode.parse({
  position: [2.5, 1.2, 0], // X = distance along wall, Y = height, Z ignored
  side: 'front',           // Which side of the wall
  asset: {
    id: 'tv_01',
    category: 'electronics',
    name: 'Wall TV',
    src: '/models/tv.glb',
    dimensions: [1.2, 0.7, 0.1],
    attachTo: 'wall-side',
  },
})
useScene.getState().createNode(tv, wall.id) // Parent is the wall
For wall-attached items:
  • position[0] is the distance from the wall start point
  • position[1] is the height from the floor
  • The item will snap to the specified wall side

Batch Node Creation

Create multiple nodes efficiently using createNodes():
const operations = [
  {
    node: WallNode.parse({ start: [0, 0], end: [5, 0] }),
    parentId: currentLevelId,
  },
  {
    node: WallNode.parse({ start: [5, 0], end: [5, 4] }),
    parentId: currentLevelId,
  },
  {
    node: WallNode.parse({ start: [5, 4], end: [0, 4] }),
    parentId: currentLevelId,
  },
  {
    node: WallNode.parse({ start: [0, 4], end: [0, 0] }),
    parentId: currentLevelId,
  },
]

useScene.getState().createNodes(operations)

Node Visibility

All nodes have a visible property that controls their visibility in the 3D scene:
const hiddenZone = ZoneNode.parse({
  name: 'Reference Zone',
  polygon: [[0, 0], [2, 0], [2, 2], [0, 2]],
  visible: false, // Won't be rendered in the scene
})
Invisible nodes still participate in spatial queries and collision detection. They’re only hidden visually.

Common Patterns

Creating a Room with Furniture

const createRoom = (levelId: string) => {
  // Define room boundaries
  const roomWidth = 5
  const roomDepth = 4

  // Create walls
  const walls = [
    WallNode.parse({ start: [0, 0], end: [roomWidth, 0] }),
    WallNode.parse({ start: [roomWidth, 0], end: [roomWidth, roomDepth] }),
    WallNode.parse({ start: [roomWidth, roomDepth], end: [0, roomDepth] }),
    WallNode.parse({ start: [0, roomDepth], end: [0, 0] }),
  ]

  walls.forEach(wall => useScene.getState().createNode(wall, levelId))

  // Create floor slab
  const slab = SlabNode.parse({
    polygon: [[0, 0], [roomWidth, 0], [roomWidth, roomDepth], [0, roomDepth]],
    elevation: 0.05,
  })
  useScene.getState().createNode(slab, levelId)

  // Add furniture
  const sofa = ItemNode.parse({
    position: [2.5, 0, 1],
    rotation: [0, 0, 0],
    asset: sofaAsset,
  })
  useScene.getState().createNode(sofa, levelId)
}

Next Steps

Event Handling

Learn how to respond to user interactions with nodes

Spatial Queries

Validate placement and perform collision detection

Build docs developers (and LLMs) love