Init Repo
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user