/** Lookups that throw a `CommandError` when the target does not exist. */ import { findNode, type Graph, type GraphEdge, type GraphNode } from '../graph/Graph'; import { type NodeDef, nodeDef } from '../graph/NodeRegistry'; import type { Asset, CompositionAsset, CurveAsset, PatternAsset, SongAsset, TileAsset, BehaviorAsset, StateMachineAsset, TilemapAsset, TilemapLayer, VariableOwnerAsset, VariablesAsset, VectorArtAsset, VectorItem, VectorShape, } from '../model/Asset'; import { findLayer } from '../model/tilemap'; import { findShape } from '../model/vectorart'; import { findVectorItem } from '../model/vectorTree'; import { findObject, type ObjectTree } from '../model/hierarchy'; import { isTemplateContent, objectRef, type ObjectRef } from '../model/instance'; import { findAsset, findAssetOf, findGraphAsset, findObjectHolder, type ObjectHolder, type Project, } from '../model/Project'; import type { SceneObject } from '../model/SceneObject'; import { type PropertyDef, schemaForAsset, schemaForObject } 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 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. */ 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`); } /** A stored object the caller may restructure: template content is rejected before the lookup. */ export function requireStructuralObject(project: Project, holder: ObjectTree, objectId: string): SceneObject { requireStructural(project, holder, objectId); const object = findObject(holder, objectId); if (!object) throw new CommandError(`Scene object ${objectId} not found`); return object; } export function requireBehavior(project: Project, assetId: string): BehaviorAsset { const asset = findAssetOf(project, assetId, 'behavior'); if (!asset) throw new CommandError(`Behavior ${assetId} not found`); return asset; } /** * An asset that declares variables: a behavior, a Variables asset shared by the whole run, or a * Struct asset, whose fields are declared the same way (#253). */ export function requireVariableOwner(project: Project, assetId: string): VariableOwnerAsset { const asset = findAsset(project, assetId); if (asset?.type !== 'behavior' && asset?.type !== 'variables' && asset?.type !== 'struct') throw new CommandError(`Variable owner ${assetId} not found`); return asset; } export function requireTile(project: Project, assetId: string): TileAsset { const asset = findAssetOf(project, assetId, 'tile'); if (!asset) throw new CommandError(`Tile ${assetId} not found`); return asset; } export function requireTilemap(project: Project, assetId: string): TilemapAsset { const asset = findAssetOf(project, assetId, 'tilemap'); if (!asset) throw new CommandError(`Tilemap ${assetId} not found`); return asset; } export function requireTilemapLayer(project: Project, assetId: string, layerId: string): TilemapLayer { const layer = findLayer(requireTilemap(project, assetId), layerId); if (!layer) throw new CommandError(`Layer ${layerId} not found`); return layer; } export function requireVectorArt(project: Project, assetId: string): VectorArtAsset { const asset = findAssetOf(project, assetId, 'vectorart'); if (!asset) throw new CommandError(`Vector art ${assetId} not found`); return asset; } export function requireVectorShape(project: Project, assetId: string, shapeId: string): VectorShape { const shape = findShape(requireVectorArt(project, assetId), shapeId); if (!shape) throw new CommandError(`Shape ${shapeId} not found`); return shape; } /** A shape or a group of the drawing (#190). */ export function requireVectorItem(project: Project, assetId: string, itemId: string): VectorItem { const item = findVectorItem(requireVectorArt(project, assetId), itemId); if (!item) throw new CommandError(`Shape ${itemId} not found`); return item; } export function requireCurve(project: Project, assetId: string): CurveAsset { const asset = findAssetOf(project, assetId, 'curve'); if (!asset) throw new CommandError(`Curve ${assetId} not found`); return asset; } export function requirePattern(project: Project, assetId: string): PatternAsset { const asset = findAssetOf(project, assetId, 'pattern'); if (!asset) throw new CommandError(`Pattern ${assetId} not found`); return asset; } export function requireSong(project: Project, assetId: string): SongAsset { const asset = findAssetOf(project, assetId, 'song'); if (!asset) throw new CommandError(`Song ${assetId} not found`); return asset; } export function requireComposition(project: Project, assetId: string): CompositionAsset { const asset = findAssetOf(project, assetId, 'composition'); if (!asset) throw new CommandError(`Composition ${assetId} not found`); return asset; } export function requireVariablesAsset(project: Project, assetId: string): VariablesAsset { const asset = findAssetOf(project, assetId, 'variables'); if (!asset) throw new CommandError(`Variables ${assetId} not found`); return asset; } export function requireStateMachine(project: Project, assetId: string): StateMachineAsset { const asset = findAssetOf(project, assetId, 'statemachine'); if (!asset) throw new CommandError(`State machine ${assetId} not found`); return asset; } /** The graph of a behavior or of a state machine – what every graph command edits. */ export function requireGraph(project: Project, assetId: string): Graph { const asset = findGraphAsset(project, assetId); if (!asset) throw new CommandError(`Behavior or state machine ${assetId} not found`); return asset.graph; } export function requireNode(project: Project, assetId: string, nodeId: string): GraphNode { const node = findNode(requireGraph(project, assetId), nodeId); if (!node) throw new CommandError(`Node ${nodeId} not found`); return node; } export function requireEdge(project: Project, assetId: string, edgeId: string): GraphEdge { const edge = requireGraph(project, assetId).edges.find((e) => e.id === edgeId); if (!edge) throw new CommandError(`Edge ${edgeId} not found`); return edge; } /** 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; } /** The row `key` names on the asset – or, given the placed object, on that object (its shader's rows). */ export function requirePropDef( project: Project, asset: Asset, key: string, object?: SceneObject, ): PropertyDef { const schema = object ? schemaForObject(project, asset, object) : schemaForAsset(project, asset); const def = schema.find((d) => d.key === key); if (!def) throw new CommandError(`Property "${key}" is not defined for ${asset.type}`); return def; }