/** * "Apply" is the opposite direction of an override: a value changed on a placed object is written * back to the asset, or to the template of the container it belongs to. Reverting drops the * instance's deviation again, which is all it takes for the value to follow the template – the * content of a placed container is resolved from it, never copied (see `model/instance.ts`). */ import type { Asset } from '../model/Asset'; import type { InstanceTarget, ObjectRef } from '../model/instance'; import { type Axis, type Position, type PropertyValue, setBagValue } from '../model/Properties'; import type { Project } from '../model/Project'; import { resolveSceneObjectProps } from '../properties/resolve'; import { CommandError } from './Command'; import { requireAsset, requirePropDef } from './require'; import { ObjectEditCommand } from './sceneObjectCommands'; const ALL_AXES: readonly Axis[] = ['x', 'y', 'z']; function requireTarget(ref: ObjectRef): InstanceTarget { if (!ref.target) throw new CommandError('That object is not part of a placed container'); return ref.target; } /** * Writes the object's value for `key` into its asset and drops the override. For content of a * placed container the template's override goes too, so every instance follows the asset again. */ export class ApplyOverrideToAssetCommand extends ObjectEditCommand { readonly label: string; private asset: Asset | undefined; private previousAssetValue: PropertyValue | undefined; private template: InstanceTarget | undefined; private previousTemplateValue: PropertyValue | undefined; constructor( holderId: string, objectId: string, private readonly key: string, ) { super(holderId, objectId); this.label = `Apply ${key} to asset`; } protected override apply(ref: ObjectRef, project: Project): void { const asset = requireAsset(project, ref.object.assetId); requirePropDef(project, asset, this.key, ref.object); const value = resolveSceneObjectProps(project, asset, ref.object)[this.key]; this.asset = asset; this.previousAssetValue = asset.props[this.key]; asset.props[this.key] = value!; ref.setOverride(this.key, undefined); this.template = ref.target; if (!this.template) return; this.previousTemplateValue = this.template.template.overrides[this.key]; delete this.template.template.overrides[this.key]; } protected override revert(): void { if (this.template) setBagValue(this.template.template.overrides, this.key, this.previousTemplateValue); if (this.asset) setBagValue(this.asset.props, this.key, this.previousAssetValue); } } export class ApplyOverrideToContainerCommand extends ObjectEditCommand { readonly label: string; private previous: PropertyValue | undefined; private target: InstanceTarget | undefined; constructor( holderId: string, objectId: string, private readonly key: string, ) { super(holderId, objectId); this.label = `Apply ${key} to container`; } protected override apply(ref: ObjectRef): void { const target = requireTarget(ref); this.target = target; this.previous = target.template.overrides[this.key]; setBagValue(target.template.overrides, this.key, ref.object.overrides[this.key]); // The deviation is gone the moment the template carries it, so the edit must go with it. ref.setOverride(this.key, target.template.overrides[this.key]); } protected override revert(): void { if (this.target) setBagValue(this.target.template.overrides, this.key, this.previous); } } export class RevertOverrideToTemplateCommand extends ObjectEditCommand { readonly label: string; constructor( holderId: string, objectId: string, private readonly key: string, ) { super(holderId, objectId); this.label = `Revert ${key}`; } protected override apply(ref: ObjectRef): void { ref.setOverride(this.key, requireTarget(ref).base.overrides[this.key]); } } /** Writes the object's local position – one axis, or all – into its container template. */ export class ApplyPositionToContainerCommand extends ObjectEditCommand { readonly label: string; private previous: Position = { x: 0, y: 0, z: 0 }; private target: InstanceTarget | undefined; constructor( holderId: string, objectId: string, private readonly axis?: Axis, ) { super(holderId, objectId); this.label = axis ? `Apply ${axis} to container` : 'Apply position to container'; } protected override apply(ref: ObjectRef): void { const target = requireTarget(ref); this.target = target; this.previous = { ...target.template.position }; for (const axis of this.axis ? [this.axis] : ALL_AXES) target.template.position[axis] = ref.object.position[axis]; ref.setPosition(ref.object.position); } protected override revert(): void { if (this.target) this.target.template.position = { ...this.previous }; } } export class RevertPositionToTemplateCommand extends ObjectEditCommand { readonly label: string; constructor( holderId: string, objectId: string, private readonly axis?: Axis, ) { super(holderId, objectId); this.label = axis ? `Revert ${axis}` : 'Revert position'; } protected override apply(ref: ObjectRef): void { const base = requireTarget(ref).base; const position = { ...ref.object.position }; for (const axis of this.axis ? [this.axis] : ALL_AXES) position[axis] = base.position[axis]; ref.setPosition(position); } }