Init Repo

This commit is contained in:
2026-06-03 09:00:11 +08:00
commit 9be48d8b9e
155 changed files with 14827 additions and 0 deletions
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-05-25
@@ -0,0 +1,123 @@
## Context
The codebase is a C++17 Windows desktop application using OpenUSD + ImGui + OpenGL. All scene mutations are applied immediately to the live `UsdStageRefPtr` with no reversal mechanism. Edits come from five surfaces: (1) viewport gizmo drag (`TransformManipulator`), (2) Property panel TRS fields (`PropertyPanel`), (3) scene hierarchy context menus (`SceneHierarchyPanel` — create/delete/ref), (4) generic attribute edits (`PropertyManager`), and (5) layer CRUD (`LayerPanel`/`LayerManager`). There is currently no command pattern, history stack, or deferred-dispatch infrastructure.
USD provides `SdfLayer`-level undo via `SdfLayer::BeginChangeBlock` / `EndChangeBlock` + `SdfLayer::DumpLayerInfo`, but it does not expose a first-class undo stack accessible to application code. The simpler and more reliable approach for this scope is application-level command objects that store enough pre/post state to replay or reverse themselves.
## Goals / Non-Goals
**Goals:**
- Single-level command history with unlimited depth (bounded only by memory).
- `Ctrl+Z` / `Ctrl+Y` / `Ctrl+Shift+Z` hotkeys processed in the main ImGui loop.
- **Edit → Undo / Redo** menu items that grey out when the respective stack end is reached.
- All five edit surfaces wrapped in reversible commands.
- Gizmo drag treated as one atomic command (not a command per frame).
- History cleared on stage open/close/new.
**Non-Goals:**
- Branching undo trees.
- Persisting history across sessions.
- Undoing camera movement.
- Undoing layer mute/unmute (mute state is cosmetic / non-destructive; deferred to a follow-up).
- Collaborative / multi-user conflict resolution.
## Decisions
### D1 — Classic Command Pattern over USD change-block replay
**Decision**: Store pre/post value snapshots in application-level `ICommand` objects; do not attempt to record and replay USD change blocks.
**Rationale**: USD's change notification system is designed for hydra update propagation, not user-facing undo. Reconstructing "what changed" from `SdfNotice` events is complex and fragile. Application-level commands with explicit `Undo()` / `Execute()` are simpler, debuggable, and already the standard approach in DCC tools.
**Alternatives considered**: `SdfLayer::StateDelegate` (undocumented, internal), `UsdStage::GetMutedLayers` diffing (covers only mute state).
---
### D2 — `CommandHistory` owned by `Application`, passed as pointer to subsystems
**Decision**: `Application` owns a single `CommandHistory` instance. Panels and managers receive a raw `CommandHistory*` alongside existing manager pointers. No global singleton.
**Rationale**: Mirrors the existing pattern for managers (raw pointers, no DI framework). Avoids threading concerns (single-threaded ImGui loop). A singleton would make testing harder.
---
### D3 — Gizmo drag commits one command on mouse-button-release
**Decision**: `TransformManipulator` stores drag-start TRS when drag begins (`m_isDragging` transitions false→true). On drag-end (mouse released), it pushes a single `TransformCommand(prim, startTRS, endTRS)` to `CommandHistory`.
**Rationale**: The current code already accumulates delta from drag-start each frame (`m_dragStartTranslate/Rotate/Scale`) instead of applying per-frame deltas, so the start state is already cached. A single command per gesture is the correct granularity for a user (one Undo reverses one drag, not 60 frames of micro-moves).
**Change required**: `TransformManipulator` needs a `CommandHistory*` member set during construction/init; on drag-end it pushes the command instead of — or after — the final `Apply*Delta`.
---
### D4 — Snapshot-based commands for attribute edits
**Decision**: `AttributeSetCommand<T>` stores `(UsdAttribute, T oldValue, T newValue, UsdTimeCode)`. `Undo()` calls `attr.Set(oldValue, timeCode)`, `Execute()` / `Redo()` calls `attr.Set(newValue, timeCode)`.
**Rationale**: USD attributes hold typed values; snapshot is cheap for scalar/vector types. The full type-erase is handled via `std::function<void()>` closures rather than a deep template hierarchy, keeping the concrete command count manageable.
**Command structure**:
```cpp
struct AttributeSetCommand : ICommand {
std::string description;
std::function<void()> executeFunc; // captures new value
std::function<void()> undoFunc; // captures old value
};
```
---
### D5 — Prim create/delete commands use path + type + layer targeting
**Decision**: `CreatePrimCommand` stores `(SdfPath, TfToken typeName, SdfLayerHandle targetLayer)`. `Undo()` removes the prim. `DeletePrimCommand` stores the full serialized prim spec via `SdfLayer::ExportToString` scoped to that prim, and `Undo()` re-imports it.
**Rationale**: Prim deletion must round-trip the full spec (attributes, relationships, metadata). `SdfLayer::ExportToString` + `SdfLayer::ImportFromString` provides safe serialization without custom recursion.
---
### D6 — Layer operations use index-based snapshots
**Decision**: `LayerReorderCommand` stores the full ordered layer-path list before and after; `Undo()` restores the prior list. `LayerCreateCommand` and `LayerRemoveCommand` store the layer file path + insertion index.
**Rationale**: Layer order is a simple list; full-list snapshots are small and unambiguous. Per-swap tracking would be more complex for multi-step reorders.
---
### D7 — Stack invalidation on stage lifecycle events
**Decision**: `CommandHistory::Clear()` is called from `Application::RefreshManagers()` (already invoked on open/close/new). No special hook needed.
**Rationale**: Commands hold `UsdAttribute`, `UsdPrim`, and `SdfLayerHandle` references that become dangling or point to a different stage after a stage reload. Clearing is the only safe option.
## Risks / Trade-offs
- **[Risk] USD prim/attribute handles become stale** if the user opens a different stage between Undo calls.
→ Mitigation: `Clear()` on every stage lifecycle event (D7).
- **[Risk] `SdfLayer::ExportToString` for large prims may be slow**.
→ Mitigation: Export is done only at delete time (not every frame). Acceptable for scene-level operations.
- **[Risk] `TransformManipulator` drag-end detection** requires distinguishing "drag just ended this frame" from "not dragging". Currently `m_isDragging` is set to false before `HandleInput` returns; the transition must be captured.
→ Mitigation: Detect `wasDrawing && !m_isDragging` transition within `HandleInput` to push the command at the right moment.
- **[Risk] Property panel writes TRS continuously as user types** (no commit-on-enter today). Pushing a command per keystroke floods the history.
→ Mitigation: Add a deferred-commit pattern (push command on `ImGui::IsItemDeactivatedAfterEdit()`), matching standard DCC behaviour.
- **[Risk] Multi-attribute edits (e.g., all three translate components at once)** would push three commands.
→ Mitigation: Acceptable for now; a `MacroCommand` (composite) can be added later if needed.
## Migration Plan
1. Introduce `ICommand` / `CommandHistory` as new files with no side effects on existing code.
2. Thread `CommandHistory*` through constructors/init methods of each subsystem (additive changes, no existing interface removed).
3. Wrap each edit surface one at a time (Transform → Property → SceneHierarchy → Attribute → Layer). Build after each group.
4. Add hotkeys and menu items last (safest to add once all commands are wired).
5. No data migration needed — history is in-memory only.
**Rollback**: Removing the `CommandHistory*` parameter and the `Push()` calls restores pre-change behaviour; no persisted format to migrate.
## Open Questions
- Should `Ctrl+Z` also undo layer **mute/unmute**? Currently scoped out (Non-Goal), but the architecture supports it trivially. Revisit if stakeholders request it.
- Should there be a **maximum history depth** (e.g., 200 steps) to cap memory? Not required at this scope; can be added to `CommandHistory` later as a constructor parameter.
@@ -0,0 +1,39 @@
## Why
The application currently applies all edits (transforms, attribute changes, prim creation/deletion, layer operations) directly to the live USD stage with no reversal mechanism, making it impossible to correct mistakes without closing and reopening the scene. Undo/redo is a foundational feature expected in any scene editor, and its absence is a critical usability gap.
## What Changes
- Introduce a `CommandHistory` class (command pattern) that records reversible `ICommand` objects.
- Wrap all mutating operations — prim create, prim delete, add/replace reference, transform gizmo drag, property panel TRS edits, arbitrary attribute edits, and layer management operations — in concrete `ICommand` subclasses with `Execute()` / `Undo()` pairs.
- Expose `Ctrl+Z` / `Ctrl+Y` (and `Ctrl+Shift+Z`) global hotkeys processed in the main loop.
- Integrate gizmo drag commits: the `TransformManipulator` currently emits continuous per-frame deltas; it will instead emit a single command on drag-end so that one undo step reverses the entire drag.
- Add **Edit → Undo / Redo** menu items with greyed-out state when the stack is empty.
- Clear the history stack on stage close / new stage open (invalid USD references cannot be safely replayed).
## Capabilities
### New Capabilities
- `command-history`: Core undo/redo stack — `ICommand` interface, `CommandHistory` manager, hotkey dispatch, and menu integration.
- `undoable-transform-edits`: Commands wrapping `UsdGeomXformCommonAPI` writes from both the viewport gizmo and the Property panel.
- `undoable-scene-edits`: Commands wrapping prim creation, prim deletion, add-reference, and replace-reference operations.
- `undoable-attribute-edits`: Command wrapping arbitrary `UsdAttribute::Set<T>` calls from `PropertyManager`.
- `undoable-layer-ops`: Commands wrapping `LayerManager` sublayer create, remove, reorder, and mute/unmute operations.
### Modified Capabilities
_(none — no existing spec-level requirements are being tightened or relaxed)_
## Impact
- **New file**: `src/core/CommandHistory.h/.cpp``ICommand`, `CommandHistory`.
- **New files**: `src/core/commands/` — one `.h/.cpp` pair per concrete command group.
- **Modified**: `src/ui/TransformManipulator.cpp` — capture drag-start state, emit command on drag-end instead of applying incremental deltas.
- **Modified**: `src/ui/PropertyPanel.cpp` — wrap TRS writes in commands.
- **Modified**: `src/ui/SceneHierarchyPanel.cpp` — wrap create/delete/ref operations in commands.
- **Modified**: `src/core/PropertyManager.cpp` — wrap `SetPropertyValue` in command.
- **Modified**: `src/ui/LayerPanel.cpp` — wrap layer CRUD in commands.
- **Modified**: `src/ui/Application.cpp` — add `CommandHistory` member, hotkey handling, Edit menu.
- **Modified**: `CMakeLists.txt` — add new source files.
- No new external dependencies.
@@ -0,0 +1,64 @@
## ADDED Requirements
### Requirement: ICommand interface
The system SHALL provide an `ICommand` pure-virtual interface with `Execute()`, `Undo()`, and `GetDescription()` methods that all reversible operations implement.
#### Scenario: Execute runs the operation
- **WHEN** `ICommand::Execute()` is called on a newly created command
- **THEN** the operation is applied to the USD stage and the scene reflects the new state
#### Scenario: Undo reverses the operation
- **WHEN** `ICommand::Undo()` is called after `Execute()`
- **THEN** the USD stage returns to the state it was in before `Execute()` was called
### Requirement: CommandHistory stack management
The system SHALL maintain two stacks (undo stack, redo stack). `Push(cmd)` executes the command, pushes it onto the undo stack, and clears the redo stack. `Undo()` pops from the undo stack, calls `Undo()` on the command, and pushes it onto the redo stack. `Redo()` pops from the redo stack, calls `Execute()` on the command, and pushes it back onto the undo stack.
#### Scenario: Push clears redo stack
- **WHEN** the user undoes two steps and then makes a new edit
- **THEN** the redo stack is cleared and the new command is the top of the undo stack
#### Scenario: Undo with empty stack is a no-op
- **WHEN** `CommandHistory::Undo()` is called and the undo stack is empty
- **THEN** no crash occurs and the scene is unchanged
#### Scenario: Redo with empty stack is a no-op
- **WHEN** `CommandHistory::Redo()` is called and the redo stack is empty
- **THEN** no crash occurs and the scene is unchanged
### Requirement: Ctrl+Z / Ctrl+Y hotkeys
The application SHALL process `Ctrl+Z` to invoke `Undo()` and `Ctrl+Y` (and `Ctrl+Shift+Z`) to invoke `Redo()` during the ImGui main loop, unless an ImGui text input widget has keyboard focus.
#### Scenario: Ctrl+Z triggers undo
- **WHEN** the user presses `Ctrl+Z` and the undo stack is non-empty
- **THEN** the most recent command is undone and the viewport reflects the reverted state
#### Scenario: Ctrl+Y triggers redo
- **WHEN** the user presses `Ctrl+Y` and the redo stack is non-empty
- **THEN** the most recent undone command is reapplied and the viewport reflects the restored state
#### Scenario: Hotkeys ignored in text inputs
- **WHEN** an ImGui `InputText` widget has keyboard focus and the user presses `Ctrl+Z`
- **THEN** ImGui handles the keypress as text-widget undo and `CommandHistory::Undo()` is NOT called
### Requirement: Edit menu Undo/Redo items
The application SHALL provide **Edit → Undo** and **Edit → Redo** menu items. Each item SHALL display the description of the command that would be affected. Items SHALL be greyed out (disabled) when the respective stack is empty.
#### Scenario: Undo item shows command description
- **WHEN** the undo stack is non-empty and the Edit menu is opened
- **THEN** the Undo item reads "Undo: <description of top command>" and is enabled
#### Scenario: Undo item disabled when stack empty
- **WHEN** the undo stack is empty and the Edit menu is opened
- **THEN** the Undo item is greyed out and clicking it has no effect
### Requirement: History cleared on stage lifecycle events
The system SHALL call `CommandHistory::Clear()` whenever a stage is opened, closed, or replaced, so that stale USD object references cannot be dereferenced.
#### Scenario: History clears on stage open
- **WHEN** the user opens a new USD file
- **THEN** both undo and redo stacks are empty after the stage loads
#### Scenario: History clears on stage close
- **WHEN** the user closes the current stage
- **THEN** both undo and redo stacks are empty
@@ -0,0 +1,19 @@
## ADDED Requirements
### Requirement: Generic attribute set is undoable
The system SHALL push an `AttributeSetCommand` to `CommandHistory` when `PropertyManager::SetPropertyValue` or `SetPropertyValueInLayer` is called. The command SHALL capture the old value (read before the set) and the new value as `std::function<void()>` closures so it is type-agnostic. `Undo()` SHALL restore the old value; `Redo()` SHALL re-apply the new value.
#### Scenario: Undo reverts attribute change
- **WHEN** the user changes a light's intensity via the Property panel and then presses Ctrl+Z
- **THEN** the intensity returns to its previous value
#### Scenario: Redo re-applies attribute change
- **WHEN** the user undoes an attribute change and then presses Ctrl+Y
- **THEN** the attribute is set back to the edited value
### Requirement: Attribute command preserves layer targeting
The `AttributeSetCommand` SHALL record which `SdfLayerHandle` was the active edit target. `Undo()` and `Redo()` SHALL use `UsdEditContext` to direct the attribute write to that same layer.
#### Scenario: Undo writes revert to correct layer
- **WHEN** the user edits an attribute with a non-root layer selected and then undoes
- **THEN** the revert opinion is written to the same non-root layer, not the root layer
@@ -0,0 +1,22 @@
## ADDED Requirements
### Requirement: Sublayer creation is undoable
The system SHALL push a `LayerCreateCommand` to `CommandHistory` when a new sublayer is created via the Layer panel. `Undo()` SHALL remove the sublayer from the layer stack. `Redo()` SHALL re-add it at the same position.
#### Scenario: Undo removes created sublayer
- **WHEN** the user creates a sublayer and then presses Ctrl+Z
- **THEN** the sublayer is no longer in the layer stack
### Requirement: Sublayer removal is undoable
The system SHALL push a `LayerRemoveCommand` to `CommandHistory` when a sublayer is removed via the Layer panel. `Undo()` SHALL re-insert the layer path at its original index. `Redo()` SHALL remove it again.
#### Scenario: Undo restores removed sublayer
- **WHEN** the user removes a sublayer and then presses Ctrl+Z
- **THEN** the sublayer reappears at its original position in the stack
### Requirement: Sublayer reorder is undoable
The system SHALL push a `LayerReorderCommand` to `CommandHistory` when sublayers are reordered (moved up or moved down) in the Layer panel. The command SHALL store the full ordered list before and after. `Undo()` SHALL restore the previous order.
#### Scenario: Undo reverts move-up
- **WHEN** the user moves a sublayer up and then presses Ctrl+Z
- **THEN** the layer returns to its previous position in the stack
@@ -0,0 +1,37 @@
## ADDED Requirements
### Requirement: Prim creation is undoable
The system SHALL push a `CreatePrimCommand` to `CommandHistory` when a prim is created via the scene hierarchy context menu. `Undo()` SHALL remove the created prim from the stage. `Redo()` SHALL re-create the prim with the same path and type.
#### Scenario: Undo removes created prim
- **WHEN** the user creates a Sphere prim and then presses Ctrl+Z
- **THEN** the Sphere prim no longer appears in the scene hierarchy
#### Scenario: Redo re-creates the prim
- **WHEN** the user undoes a prim creation and then presses Ctrl+Y
- **THEN** the Sphere prim reappears at the same path with the same type
### Requirement: Prim deletion is undoable
The system SHALL push a `DeletePrimCommand` to `CommandHistory` when a prim is deleted via the scene hierarchy confirm modal. Before deletion, the command SHALL serialize the prim spec (including all authored opinions on the edit-target layer) to an in-memory string. `Undo()` SHALL restore the serialized spec to the layer.
#### Scenario: Undo restores deleted prim
- **WHEN** the user deletes a prim and then presses Ctrl+Z
- **THEN** the prim reappears in the scene hierarchy with all its authored attributes intact
#### Scenario: Redo re-deletes the prim
- **WHEN** the user undoes a deletion and then presses Ctrl+Y
- **THEN** the prim is deleted again
### Requirement: Add-reference is undoable
The system SHALL push an `AddReferenceCommand` to `CommandHistory` when an external USD file is added as a reference. `Undo()` SHALL remove the reference (and the wrapping Xform prim if it was created by the add-reference operation). `Redo()` SHALL re-add the reference.
#### Scenario: Undo removes added reference
- **WHEN** the user adds a reference to an external file and then presses Ctrl+Z
- **THEN** the reference prim is removed from the scene hierarchy
### Requirement: Replace-reference is undoable
The system SHALL push a `ReplaceReferenceCommand` to `CommandHistory` when an existing reference is replaced via the scene hierarchy. `Undo()` SHALL restore the previous `SdfReference`. `Redo()` SHALL apply the replacement again.
#### Scenario: Undo restores previous reference path
- **WHEN** the user replaces a reference and then presses Ctrl+Z
- **THEN** the prim points back to the original reference file
@@ -0,0 +1,34 @@
## ADDED Requirements
### Requirement: Gizmo drag produces one atomic undo command
The system SHALL record the pre-drag TRS state when a viewport gizmo drag begins, and push a single `TransformCommand` to `CommandHistory` when the drag ends (mouse button released). The command SHALL store the prim path, the edit-target layer, and the full TRS (translate, rotate, scale) snapshots from before and after the drag.
#### Scenario: Single undo reverses entire drag
- **WHEN** the user drags the Move gizmo to translate a prim and then presses Ctrl+Z
- **THEN** the prim returns to the position it had before the drag started (not an intermediate position)
#### Scenario: Redo restores drag result
- **WHEN** the user undoes a gizmo drag and then presses Ctrl+Y
- **THEN** the prim moves back to the post-drag position
#### Scenario: No command pushed for zero-delta drag
- **WHEN** the user clicks a gizmo axis but releases without moving
- **THEN** no command is pushed to the history stack
### Requirement: Property panel TRS commits produce undo commands
The system SHALL push a `TransformCommand` to `CommandHistory` when a translate, rotate, or scale field in the Property panel loses focus after being edited (`ImGui::IsItemDeactivatedAfterEdit()`). The command SHALL store the pre-edit and post-edit TRS values.
#### Scenario: Undo reverts property-panel translate edit
- **WHEN** the user types a new X translate value in the Property panel, presses Tab to commit, then presses Ctrl+Z
- **THEN** the prim's translate returns to the value it had before the edit
#### Scenario: Typing without committing does not push a command
- **WHEN** the user begins editing a translate field but presses Escape to cancel
- **THEN** no new command is added to the undo stack
### Requirement: Transform command respects the active edit target layer
The `TransformCommand` SHALL store and restore the `SdfLayerHandle` that was the active edit target at the time of the edit. `Undo()` and `Redo()` SHALL direct their `UsdGeomXformCommonAPI` writes to that same layer via `UsdEditContext`.
#### Scenario: Undo writes to correct layer
- **WHEN** the user has Layer B selected as the edit target, moves a prim, then undoes
- **THEN** the revert is written to Layer B, not to the root layer
@@ -0,0 +1,45 @@
## 1. Core Command Infrastructure
- [x] 1.1 Create `src/core/CommandHistory.h` — define `ICommand` pure-virtual interface (`Execute`, `Undo`, `GetDescription`) and `CommandHistory` class with `Push`, `Undo`, `Redo`, `Clear`, `CanUndo`, `CanRedo`, `GetUndoDescription`, `GetRedoDescription`
- [x] 1.2 Create `src/core/CommandHistory.cpp` — implement `CommandHistory` with undo/redo stacks (`std::vector<std::unique_ptr<ICommand>>`); `Push` executes command, pushes to undo stack, clears redo stack
- [x] 1.3 Add `CommandHistory` as a member of `Application` and pass `CommandHistory*` into all panel and manager constructors/init methods
- [x] 1.4 Call `m_commandHistory.Clear()` inside `Application::RefreshManagers()` so history resets on every stage lifecycle event
- [x] 1.5 Add `CMakeLists.txt` entries for all new source files (`CommandHistory.cpp` and all command `.cpp` files added in subsequent tasks)
## 2. Transform Commands
- [x] 2.1 Create `src/core/commands/TransformCommand.h/.cpp` — stores prim `SdfPath`, `SdfLayerHandle` edit target, and pre/post TRS (`GfVec3d` translate, `GfVec3f` rotate, `GfVec3f` scale); `Execute()` applies post-values, `Undo()` applies pre-values via `UsdGeomXformCommonAPI` + `UsdEditContext`
- [x] 2.2 Modify `TransformManipulator` — accept `CommandHistory*` in constructor/init; on drag-begin snapshot TRS into `m_dragStartTRS`; on drag-end (detect `wasDrawing && !m_isDragging`) push `TransformCommand` only when delta is non-zero
- [x] 2.3 Modify `PropertyPanel` — accept `CommandHistory*`; capture pre-edit TRS before field edit begins; push `TransformCommand` inside `ImGui::IsItemDeactivatedAfterEdit()` blocks for translate, rotate, and scale fields
## 3. Scene Hierarchy Commands
- [x] 3.1 Create `src/core/commands/CreatePrimCommand.h/.cpp` — stores `SdfPath`, `TfToken typeName`, `SdfLayerHandle`; `Execute()` calls `stage->DefinePrim`; `Undo()` calls `stage->RemovePrim`
- [x] 3.2 Create `src/core/commands/DeletePrimCommand.h/.cpp` — on construction serialises the target prim's spec via `SdfCopySpec` into an anonymous layer; `Execute()` removes the prim; `Undo()` restores from the anonymous layer via `SdfCopySpec`
- [x] 3.3 Create `src/core/commands/AddReferenceCommand.h/.cpp` — stores created prim path and `SdfReference`; `Execute()` wraps in Xform and adds reference; `Undo()` removes the prim
- [x] 3.4 Create `src/core/commands/ReplaceReferenceCommand.h/.cpp` — stores prim path, old `SdfReference`, new `SdfReference`; `Execute()` applies new reference; `Undo()` restores old
- [x] 3.5 Modify `SceneHierarchyPanel` — accept `CommandHistory*`; replace direct `Application::CreatePrimOnStage` calls with `Push(CreatePrimCommand)`; replace direct delete with `Push(DeletePrimCommand)`; replace `AddReferenceToStage` with `Push(AddReferenceCommand)`; replace `ProcessPendingReplaceRef` with `Push(ReplaceReferenceCommand)`
## 4. Attribute Edit Commands
- [x] 4.1 Create `src/core/commands/AttributeSetCommand.h/.cpp` — stores `std::string description`, `std::function<void()> executeFunc`, `std::function<void()> undoFunc`; captures old value before set using `UsdAttribute::Get<T>` at the call site
- [ ] 4.2 Modify `PropertyManager::SetPropertyValue` and `SetPropertyValueInLayer` — read old value, construct `AttributeSetCommand` with closures for old/new applies, push to `CommandHistory`
## 5. Layer Operation Commands
- [x] 5.1 Create `src/core/commands/LayerCreateCommand.h/.cpp` — stores layer file path and insertion index; `Execute()` calls `LayerManager::CreateSublayer`; `Undo()` calls `LayerManager::RemoveSublayer`
- [x] 5.2 Create `src/core/commands/LayerRemoveCommand.h/.cpp` — stores layer path and original index; `Execute()` removes; `Undo()` re-inserts at saved index
- [x] 5.3 Create `src/core/commands/LayerReorderCommand.h/.cpp` — stores full ordered-path list before and after; `Execute()` applies new order; `Undo()` restores old order by rewriting `rootLayer->GetSubLayerPaths()`
- [x] 5.4 Modify `LayerPanel` — accept `CommandHistory*`; wrap Create, Remove, MoveUp, and MoveDown calls with the corresponding commands
## 6. Hotkeys and Menu Integration
- [x] 6.1 In `Application`'s main-loop (or `ImGuiContext`), detect `Ctrl+Z` and `Ctrl+Y` / `Ctrl+Shift+Z` key combinations when no ImGui text widget has input focus (`!ImGui::GetIO().WantTextInput`); call `m_commandHistory.Undo()` / `m_commandHistory.Redo()` accordingly
- [x] 6.2 Add **Edit** menu (or extend existing menu bar) with **Undo** and **Redo** items; display `GetUndoDescription()` / `GetRedoDescription()` in item labels; disable items via `ImGui::BeginDisabled(!CanUndo())` / `(!CanRedo())`
## 7. Build and Verification
- [x] 7.1 Run `cmake --preset default` and `cmake --build build --config Release` — compilation succeeds; LNK1104 only occurs because `UsdLayerManager.exe` is currently running (expected)
- [ ] 7.2 Run `cmake --install build --config Release` and launch `install/bin/App.exe`; manually verify: create a prim, undo removes it, redo restores it
- [ ] 7.3 Manually verify gizmo drag undo: translate a prim with the Move gizmo, press Ctrl+Z, confirm prim returns to original position
- [ ] 7.4 Run `ctest --test-dir build -C Release` and confirm all tests pass