/** Lookups that throw a `CommandError` when the target does not exist. */ import { findNode, type GraphNode } from '../graph/Graph'; import { type NodeDef, nodeDef } from '../graph/NodeRegistry'; import type { Asset, BehaviorAsset, SceneAsset } from '../model/Asset'; import { findObject, type ObjectTree } from '../model/hierarchy'; import { isTemplateContent, objectRef, type ObjectRef } from '../model/instance'; import { findAsset, findObjectHolder, findScene, type ObjectHolder, type Project } from '../model/Project'; import type { SceneObject } from '../model/SceneObject'; import { propDef, type PropertyDef } from '../properties/PropertySchema'; import { CommandError } from './Command'; export function requireAsset(project: Project, assetId: string): Asset { const asset = findAsset(project, assetId); if (!asset) throw new CommandError(`Asset ${assetId} not found`); return asset; } export function requireScene(project: Project, sceneId: string): SceneAsset { const scene = findScene(project, sceneId); if (!scene) throw new CommandError(`Scene ${sceneId} not found`); return scene; } export function requireHolder(project: Project, holderId: string): ObjectHolder { const holder = findObjectHolder(project, holderId); if (!holder) throw new CommandError(`Scene or container ${holderId} not found`); return holder; } export function requireObject(project: Project, holderId: string, objectId: string): SceneObject { const object = findObject(requireHolder(project, holderId), objectId); if (!object) throw new CommandError(`Scene object ${objectId} not found`); return object; } /** * The object a value edit works on, whether it is stored in the holder or resolved from a * container template – the writes then land in the instance's edits. */ export function requireObjectRef(project: Project, holderId: string, objectId: string): ObjectRef { const ref = objectRef(project, requireHolder(project, holderId), objectId); if (!ref) throw new CommandError(`Scene object ${objectId} not found`); return ref; } /** * Rejects an object that only exists because a container template says so. Its structure – what * exists, what it is parented to – is the template's business, not the instance's. */ export function requireStructural(project: Project, holder: ObjectTree, objectId: string): void { if (!isTemplateContent(holder, objectId)) return; // Not resolvable at all is a plain "not found", which the caller's own lookup words better. const object = objectRef(project, holder, objectId)?.object; if (!object) return; const name = object.name || 'That object'; throw new CommandError(`${name} comes from a container template; open the container to change it`); } export function requireBehavior(project: Project, assetId: string): BehaviorAsset { const asset = findAsset(project, assetId); if (!asset || asset.type !== 'behavior') throw new CommandError(`Behavior ${assetId} not found`); return asset; } export function requireNode(project: Project, assetId: string, nodeId: string): GraphNode { const node = findNode(requireBehavior(project, assetId).graph, nodeId); if (!node) throw new CommandError(`Node ${nodeId} not found`); return node; } /** * `NodeRegistry.requireNodeDef` throws a plain `Error`, which the editor does not catch; a command * needs a `CommandError` so an unknown type becomes a notice instead of a crash. */ export function requireNodeDef(type: string): NodeDef { const def = nodeDef(type); if (!def) throw new CommandError(`Unknown node type "${type}"`); return def; } export function requirePropDef(asset: Asset, key: string): PropertyDef { const def = propDef(asset, key); if (!def) throw new CommandError(`Property "${key}" is not defined for ${asset.type}`); return def; }