diff --git a/frontend/apps/artcraft/app/src/pages/PageScene/actions/addCharacter.ts b/frontend/apps/artcraft/app/src/pages/PageScene/actions/addCharacter.ts index 614ac86809..175eee1692 100644 --- a/frontend/apps/artcraft/app/src/pages/PageScene/actions/addCharacter.ts +++ b/frontend/apps/artcraft/app/src/pages/PageScene/actions/addCharacter.ts @@ -17,7 +17,7 @@ export async function addCharacter( obj.userData.isCharacter = true; - await editor.sceneManager?.add_creation_undostack(obj); + editor.history.recordCreate(obj); usePageSceneStore.getState().addCharacter({ id: obj.uuid, diff --git a/frontend/apps/artcraft/app/src/pages/PageScene/actions/addObject.ts b/frontend/apps/artcraft/app/src/pages/PageScene/actions/addObject.ts index da3199418e..e91810b7c1 100644 --- a/frontend/apps/artcraft/app/src/pages/PageScene/actions/addObject.ts +++ b/frontend/apps/artcraft/app/src/pages/PageScene/actions/addObject.ts @@ -15,7 +15,7 @@ export async function addObject( ); if (!obj) return undefined; - await editor.sceneManager?.add_creation_undostack(obj); + editor.history.recordCreate(obj); usePageSceneStore.getState().addObject({ id: obj.uuid, diff --git a/frontend/apps/artcraft/app/src/pages/PageScene/actions/addShape.ts b/frontend/apps/artcraft/app/src/pages/PageScene/actions/addShape.ts index fbd009dc79..fa5f7afde0 100644 --- a/frontend/apps/artcraft/app/src/pages/PageScene/actions/addShape.ts +++ b/frontend/apps/artcraft/app/src/pages/PageScene/actions/addShape.ts @@ -16,7 +16,7 @@ export async function addShape( if (!obj) return undefined; obj.name = item.name ?? "shape"; - await editor.sceneManager?.add_creation_undostack(obj); + editor.history.recordCreate(obj); usePageSceneStore.getState().addShape({ id: obj.uuid, diff --git a/frontend/apps/artcraft/app/src/pages/PageScene/comps/Outliner/Outliner.tsx b/frontend/apps/artcraft/app/src/pages/PageScene/comps/Outliner/Outliner.tsx index 066406f3f5..d30bf94dee 100644 --- a/frontend/apps/artcraft/app/src/pages/PageScene/comps/Outliner/Outliner.tsx +++ b/frontend/apps/artcraft/app/src/pages/PageScene/comps/Outliner/Outliner.tsx @@ -53,8 +53,10 @@ const OutlinerRow = ({ item }: { item: OutlinerItem }) => { const handleToggleVisibility = (e: React.MouseEvent) => { e.stopPropagation(); + const before = item.visible; usePageSceneStore.getState().toggleOutlinerVisibility(item.id); editorEngine?.sceneManager?.hideObject(item.id); + editorEngine?.history.recordSetVisible(item.id, before, !before); }; return ( diff --git a/frontend/apps/artcraft/app/src/pages/PageScene/engine/editor.ts b/frontend/apps/artcraft/app/src/pages/PageScene/engine/editor.ts index a1687f6b09..1204b62f19 100644 --- a/frontend/apps/artcraft/app/src/pages/PageScene/engine/editor.ts +++ b/frontend/apps/artcraft/app/src/pages/PageScene/engine/editor.ts @@ -171,6 +171,8 @@ class Editor { const selected = this.sceneManager?.selected_objects?.[0]; if (selected) this.gizmo.attach(selected); }, + recordLockChange: (uuid, before, after) => + this.history.recordSetLocked(uuid, before, after), }); this.activeScene = new Scene( @@ -732,6 +734,9 @@ class Editor { } deleteObject(uuid: string) { + // Snapshot for the undo stack before the object is gone. + const obj = this.activeScene.scene.getObjectByProperty("uuid", uuid); + if (obj) this.history.recordDelete(obj); this.mouse_controls?.clearFKVisuals(); this.mouse_controls?.removeTransformControls(true); this.utils.deleteObject(uuid); diff --git a/frontend/apps/artcraft/app/src/pages/PageScene/engine/editor/SelectionBridge.ts b/frontend/apps/artcraft/app/src/pages/PageScene/engine/editor/SelectionBridge.ts index 6251b044f7..2526df1a77 100644 --- a/frontend/apps/artcraft/app/src/pages/PageScene/engine/editor/SelectionBridge.ts +++ b/frontend/apps/artcraft/app/src/pages/PageScene/engine/editor/SelectionBridge.ts @@ -29,6 +29,9 @@ export type SelectionBridgeDeps = { isObjectLocked: (uuid: string) => boolean; removeTransformControls: () => void; attachGizmoToCurrentSelection: () => void; + // Push a lock-toggle entry onto the undo stack. Optional so the bridge + // can be constructed before HistoryManager (and to keep tests simple). + recordLockChange?: (uuid: string, before: boolean, after: boolean) => void; }; export class SelectionBridge { @@ -46,6 +49,7 @@ export class SelectionBridge { // the gizmo (lock) or re-attach it to the current selection (unlock). // Returns the new locked state. lockUnlockObject(object_uuid: string): boolean { + const before = this.engine.isObjectLocked(object_uuid); const locked = this.engine.toggleObjectLocked(object_uuid); if (locked) { this.engine.removeTransformControls(); @@ -53,6 +57,7 @@ export class SelectionBridge { this.engine.attachGizmoToCurrentSelection(); } this.updateSelectedUI(); + this.engine.recordLockChange?.(object_uuid, before, locked); return locked; } diff --git a/frontend/apps/artcraft/app/src/pages/PageScene/engine/keymap.ts b/frontend/apps/artcraft/app/src/pages/PageScene/engine/keymap.ts index 3ef85b7dc8..ba8031e3da 100644 --- a/frontend/apps/artcraft/app/src/pages/PageScene/engine/keymap.ts +++ b/frontend/apps/artcraft/app/src/pages/PageScene/engine/keymap.ts @@ -34,8 +34,10 @@ const setGizmoMode = ( const deleteSelected = (editor: Editor) => { const mc = editor.mouse_controls; if (!mc?.selected) return; + // Route through editor.deleteObject so HistoryManager records the + // deletion. mc.deleteObject (the legacy direct path) bypasses history. mc.selected.forEach((sel) => { - mc.deleteObject(sel.uuid); + editor.deleteObject(sel.uuid); }); mc.selected = []; mc.removeTransformControls(); @@ -70,11 +72,11 @@ const openAssetModal = () => { }; const undo = async (editor: Editor) => { - await editor.sceneManager?.undo(); + await editor.history.undo(); }; const redo = async (editor: Editor) => { - await editor.sceneManager?.redo(); + await editor.history.redo(); }; const copy = async (editor: Editor) => { @@ -82,7 +84,8 @@ const copy = async (editor: Editor) => { }; const paste = async (editor: Editor) => { - await editor.sceneManager?.paste(); + const obj = await editor.sceneManager?.paste(); + if (obj) editor.history.recordCreate(obj); }; export const buildKeymap = (): KeyBinding[] => [ diff --git a/frontend/apps/artcraft/app/src/pages/PageScene/engine/scene_manager_api.ts b/frontend/apps/artcraft/app/src/pages/PageScene/engine/scene_manager_api.ts index 4814875ba2..1324bcce96 100644 --- a/frontend/apps/artcraft/app/src/pages/PageScene/engine/scene_manager_api.ts +++ b/frontend/apps/artcraft/app/src/pages/PageScene/engine/scene_manager_api.ts @@ -306,7 +306,7 @@ export class SceneManager implements SceneManagerAPI { } } - public async paste() { + public async paste(): Promise { if (this.copiedObject && this.copiedObject.name != "::CAM::") { const userdata = this.copiedObject.userData; const position = this.copiedObject.position.clone(); @@ -353,8 +353,9 @@ export class SceneManager implements SceneManagerAPI { } await this.copy(); - await this.add_creation_undostack(obj); + return obj; } + return undefined; } public onMouseMove(event: MouseEvent) {