import type { SceneGraph } from '@pascal-app/core/clone-scene-graph' import type { AnyNode, AnyNodeId } from '@pascal-app/core/schema' /** * 40 m² studio apartment — a single open room with one window, one front door * and a single "Living/Kitchen" zone. Used as a starting point for small unit * briefs. Deterministic ids (`site_empty`, `building_empty`, `level_0`, etc.) * are regenerated by the MCP tool via `cloneSceneGraph` before applying. */ // Footprint: 8 m × 5 m = 40 m² (centered at origin). // Walls traverse the boundary counter-clockwise (right-handed XZ plane). const W = 4 // half-width const D = 2.5 // half-depth type StudioNodes = { site: AnyNode building: AnyNode level: AnyNode walls: AnyNode[] zone: AnyNode door: AnyNode window: AnyNode } function buildNodes(): StudioNodes { const site: AnyNode = { object: 'node', id: 'site_empty' as AnyNodeId, type: 'site', parentId: null, visible: true, metadata: {}, polygon: { type: 'polygon', points: [ [-15, -15], [15, -15], [15, 15], [-15, 15], ], }, children: ['building_empty' as AnyNodeId], } as unknown as AnyNode const building: AnyNode = { object: 'node', id: 'building_empty' as AnyNodeId, type: 'building', parentId: 'site_empty' as AnyNodeId, visible: true, metadata: {}, position: [0, 0, 0], rotation: [0, 0, 0], children: ['level_0' as AnyNodeId], } as unknown as AnyNode // Walls with deterministic ids; child ids are listed below after doors/windows // are created, so we fill this array after computing them. const wallIds = ['wall_n', 'wall_e', 'wall_s', 'wall_w'] as const // South wall carries the front door; west wall carries the window. const door: AnyNode = { object: 'node', id: 'door_front' as AnyNodeId, type: 'door', parentId: 'wall_s' as AnyNodeId, visible: true, metadata: {}, wallId: 'wall_s', position: [0, 1.05, 0], rotation: [0, 0, 0], width: 0.9, height: 2.1, frameThickness: 0.05, frameDepth: 0.07, threshold: true, thresholdHeight: 0.02, hingesSide: 'left', swingDirection: 'inward', segments: [ { type: 'panel', heightRatio: 0.4, columnRatios: [1], dividerThickness: 0.03, panelDepth: 0.01, panelInset: 0.04, }, { type: 'panel', heightRatio: 0.6, columnRatios: [1], dividerThickness: 0.03, panelDepth: 0.01, panelInset: 0.04, }, ], handle: true, handleHeight: 1.05, handleSide: 'right', contentPadding: [0.04, 0.04], doorCloser: false, panicBar: false, panicBarHeight: 1.0, } as unknown as AnyNode const windowNode: AnyNode = { object: 'node', id: 'window_w' as AnyNodeId, type: 'window', parentId: 'wall_w' as AnyNodeId, visible: true, metadata: {}, wallId: 'wall_w', position: [0, 1.2, 0], rotation: [0, 0, 0], width: 1.5, height: 1.2, frameThickness: 0.05, frameDepth: 0.07, columnRatios: [1], rowRatios: [1], columnDividerThickness: 0.03, rowDividerThickness: 0.03, sill: true, sillDepth: 0.08, sillThickness: 0.03, } as unknown as AnyNode const wallNorth: AnyNode = { object: 'node', id: 'wall_n' as AnyNodeId, type: 'wall', parentId: 'level_0' as AnyNodeId, visible: true, metadata: {}, children: [], thickness: 0.1, height: 2.5, start: [-W, -D], end: [W, -D], frontSide: 'unknown', backSide: 'unknown', } as unknown as AnyNode const wallEast: AnyNode = { object: 'node', id: 'wall_e' as AnyNodeId, type: 'wall', parentId: 'level_0' as AnyNodeId, visible: true, metadata: {}, children: [], thickness: 0.1, height: 2.5, start: [W, -D], end: [W, D], frontSide: 'unknown', backSide: 'unknown', } as unknown as AnyNode const wallSouth: AnyNode = { object: 'node', id: 'wall_s' as AnyNodeId, type: 'wall', parentId: 'level_0' as AnyNodeId, visible: true, metadata: {}, children: ['door_front'], thickness: 0.1, height: 2.5, start: [W, D], end: [-W, D], frontSide: 'unknown', backSide: 'unknown', } as unknown as AnyNode const wallWest: AnyNode = { object: 'node', id: 'wall_w' as AnyNodeId, type: 'wall', parentId: 'level_0' as AnyNodeId, visible: true, metadata: {}, children: ['window_w'], thickness: 0.1, height: 2.5, start: [-W, D], end: [-W, -D], frontSide: 'unknown', backSide: 'unknown', } as unknown as AnyNode const zone: AnyNode = { object: 'node', id: 'zone_living' as AnyNodeId, type: 'zone', parentId: 'level_0' as AnyNodeId, visible: true, metadata: {}, name: 'Living / Kitchen', color: '#60a5fa', polygon: [ [-W, -D], [W, -D], [W, D], [-W, D], ], } as unknown as AnyNode const level: AnyNode = { object: 'node', id: 'level_0' as AnyNodeId, type: 'level', parentId: 'building_empty' as AnyNodeId, visible: true, metadata: {}, level: 0, children: [...wallIds, 'zone_living'] as AnyNodeId[], } as unknown as AnyNode return { site, building, level, walls: [wallNorth, wallEast, wallSouth, wallWest], zone, door, window: windowNode, } } function buildTemplate(): SceneGraph { const n = buildNodes() const nodes: Record = {} for (const node of [n.site, n.building, n.level, ...n.walls, n.zone, n.door, n.window]) { nodes[node.id as AnyNodeId] = node } // SiteNode.children is a discriminatedUnion of BuildingNode/ItemNode objects // (not string ids) — so the site must embed the full building node. The // rest of the tree uses string ids per the BaseNode/LevelNode/WallNode // schemas. We mutate the flat-dict copy of the site here so the nested // representation round-trips through AnyNode.safeParse. const siteInDict = nodes['site_empty' as AnyNodeId] as unknown as { children: unknown[] } siteInDict.children = [nodes['building_empty' as AnyNodeId]] return { nodes, rootNodeIds: ['site_empty'] as AnyNodeId[], } } export const template: SceneGraph = buildTemplate() export const metadata = { id: 'empty-studio', name: 'Empty studio', description: '40 m² single-room studio apartment: 4 walls, 1 living/kitchen zone, 1 window, 1 front door.', } as const