Multiselection and container assets: create container from selection, template instances
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
91ee2feadd
commit
0b78382bcc
26 files changed
+897
-114
No files matched your search
@@ -1,13 +1,13 @@
|
||||
import { isPlaceableType } from '../model/AssetTypes';
|
||||
import { descendantsOf, isSelfOrAncestor, localPositionFor, worldPosition } from '../model/hierarchy';
|
||||
import { descendantsOf, isSelfOrAncestor, localPositionFor, type ObjectTree, worldPosition } from '../model/hierarchy';
|
||||
import type { Position, PropertyValue, Vec2 } from '../model/Properties';
|
||||
import { findScene, findSceneObject, type Project } from '../model/Project';
|
||||
import type { SceneAsset } from '../model/Asset';
|
||||
import { createSceneObject, type SceneObject } from '../model/SceneObject';
|
||||
import { coerceValue, schemaForAsset } from '../properties/PropertySchema';
|
||||
import { newId } from '../ids';
|
||||
import { type Command, CommandError } from './Command';
|
||||
import { requireAsset } from './assetCommands';
|
||||
import { instantiateContainer } from '../model/container';
|
||||
|
||||
function requireScene(project: Project, sceneId: string) {
|
||||
const scene = findScene(project, sceneId);
|
||||
@@ -28,35 +28,39 @@ export interface RemovedObject {
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given objects and all their descendants from the scene (a subtree goes as a
|
||||
* whole). Returns what was removed in ascending index order.
|
||||
* Removes the given objects and all their descendants from the object list (a subtree goes
|
||||
* as a whole; ids that are descendants of other ids are simply covered). Returns what was
|
||||
* removed in ascending index order.
|
||||
*/
|
||||
export function removeSubtrees(scene: SceneAsset, rootIds: string[]): RemovedObject[] {
|
||||
export function removeSubtrees(tree: ObjectTree, rootIds: readonly string[]): RemovedObject[] {
|
||||
const ids = new Set<string>();
|
||||
for (const rootId of rootIds) {
|
||||
ids.add(rootId);
|
||||
for (const child of descendantsOf(scene, rootId)) ids.add(child.id);
|
||||
for (const child of descendantsOf(tree, rootId)) ids.add(child.id);
|
||||
}
|
||||
const removed: RemovedObject[] = [];
|
||||
scene.objects.forEach((object, index) => {
|
||||
tree.objects.forEach((object, index) => {
|
||||
if (ids.has(object.id)) removed.push({ object, index });
|
||||
});
|
||||
scene.objects = scene.objects.filter((o) => !ids.has(o.id));
|
||||
tree.objects = tree.objects.filter((o) => !ids.has(o.id));
|
||||
return removed;
|
||||
}
|
||||
|
||||
/** Puts removed objects back at their former indices (ascending order restores them exactly). */
|
||||
export function restoreObjects(scene: SceneAsset, removed: RemovedObject[]): void {
|
||||
for (const entry of [...removed].sort((a, b) => a.index - b.index)) scene.objects.splice(entry.index, 0, entry.object);
|
||||
export function restoreObjects(tree: ObjectTree, removed: RemovedObject[]): void {
|
||||
for (const entry of [...removed].sort((a, b) => a.index - b.index)) tree.objects.splice(entry.index, 0, entry.object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Places an asset into a scene, creating a scene object. Scenes cannot be placed. The
|
||||
* position is a world position; with a parent it is converted to the parent's local space.
|
||||
* Placing a container also creates its content (see `instantiateContainer`).
|
||||
*/
|
||||
export class PlaceAssetCommand implements Command {
|
||||
readonly label = 'Place asset';
|
||||
readonly objectId = newId();
|
||||
/** Every object this command added: the placed one plus a container's content. */
|
||||
private createdIds: string[] = [];
|
||||
|
||||
constructor(
|
||||
private readonly sceneId: string,
|
||||
@@ -71,12 +75,16 @@ export class PlaceAssetCommand implements Command {
|
||||
if (!isPlaceableType(asset.type)) throw new CommandError('A scene cannot be placed into a scene');
|
||||
if (this.parentId) requireObject(project, this.sceneId, this.parentId);
|
||||
const world = { x: this.position.x, y: this.position.y, z: 'z' in this.position ? this.position.z : 0 };
|
||||
scene.objects.push(createSceneObject(this.objectId, this.assetId, localPositionFor(scene, this.parentId, world), this.parentId));
|
||||
const created = [createSceneObject(this.objectId, this.assetId, localPositionFor(scene, this.parentId, world), this.parentId)];
|
||||
if (asset.type === 'container') created.push(...instantiateContainer(project, asset, this.objectId));
|
||||
this.createdIds = created.map((o) => o.id);
|
||||
scene.objects.push(...created);
|
||||
}
|
||||
|
||||
undo(project: Project): void {
|
||||
const scene = requireScene(project, this.sceneId);
|
||||
scene.objects = scene.objects.filter((o) => o.id !== this.objectId);
|
||||
const ids = new Set(this.createdIds);
|
||||
scene.objects = scene.objects.filter((o) => !ids.has(o.id));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +105,23 @@ export class RemoveSceneObjectCommand implements Command {
|
||||
}
|
||||
}
|
||||
|
||||
/** Removes several objects (a multi-selection) with their subtrees as one undo step. */
|
||||
export class RemoveSceneObjectsCommand implements Command {
|
||||
readonly label = 'Remove objects';
|
||||
private removed: RemovedObject[] = [];
|
||||
|
||||
constructor(private readonly sceneId: string, private readonly objectIds: readonly string[]) {}
|
||||
|
||||
execute(project: Project): void {
|
||||
for (const id of this.objectIds) requireObject(project, this.sceneId, id);
|
||||
this.removed = removeSubtrees(requireScene(project, this.sceneId), this.objectIds);
|
||||
}
|
||||
|
||||
undo(project: Project): void {
|
||||
restoreObjects(requireScene(project, this.sceneId), this.removed);
|
||||
}
|
||||
}
|
||||
|
||||
/** Sets the full local position (x, y and draw order z) of a scene object. */
|
||||
export class MoveSceneObjectCommand implements Command {
|
||||
readonly label = 'Move object';
|
||||
|
||||
Reference in new issue
Block a user