Three bugs prevented Apply from working correctly after the first bake: 1. m_needsRefresh spuriously set during bake ClearAllTimeSamples/WriteChannelValue fire OnObjectsChanged synchronously. Since the channel is dirty at that moment, it was skipped, setting anyMatchedChannel=false and triggering m_needsRefresh=true. This caused RefreshFromStage() on the next frame, clearing m_selection and rebuilding m_channels — dropping the user's edit context. Fixed with m_suppressNotice: set true during USD writes so OnObjectsChanged is a no-op during bake. 2. Prim-level metadata notice triggered rebuild SaveBezierToMetadata calls SetCustomDataByKey, which fires a notice with changed==primPath. No attribute channel matched, so m_needsRefresh=true again. Fixed in OnObjectsChanged: skip changed==primPath (metadata-only changes), and mark a path as "known" even if the channel is dirty (so the refresh gate only fires for genuinely new/unknown attributes). 3. Dangling chPtr in undo/redo closures After RefreshFromStage() cleared and rebuilt m_channels, the raw pointer captured in closures pointed to freed memory. Replaced with a findCh lambda that searches m_channels by attr path + component at call time. Falls back to m_needsRefresh=true if the channel no longer exists. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
USD Layer Manager
A C++17 / Windows desktop application providing a Maya Render Layers-style USD scene editor using OpenUSD v25.05, ImGui 1.92.7 (docking branch), and OpenGL 3.3 (via GLAD). Users can open, edit, and save USD files, manage layered stage overrides, inspect and edit prim properties, and interact with 3D scenes through a Hydra viewport.
Features
- Layer Stack Management — Create, reorder, mute/unmute sublayers with full undo support
- Scene Hierarchy — Browse prim tree with per-type SVG icons; create, delete, rename prims; add/replace references
- Property Editor — Maya Channel Box-style attribute editing with type-aware drag inputs, color pickers, and undoable transforms
- 3D Viewport — Hydra rendering via
UsdImagingGLEngineinto an offscreen FBO; switchable render delegates (Storm, Embree, Arnold, Cycles); grid overlay; selection bounding boxes; camera wireframes - Multi-Viewport — Single, horizontal split, vertical split, and quad layouts; per-tile camera and render settings; Space to maximize/restore
- Camera Control — Free orbital camera (tumble/truck/dolly) or drive from USD camera prims; auto near/far clipping; frame selection; correct camera prim orientation for Y-up and Z-up stages
- Transform Gizmo — Pure ImDrawList-based manipulator (translate/rotate/scale) in object or world space
- Undo/Redo — Full command history for prim creation/deletion, attribute edits, transform changes, and layer operations; Ctrl+Z / Ctrl+Y hotkeys; Edit menu integration
- Multi-select — Rectangular selection in viewport with cross-panel syncing
Dependencies
| Dependency | Version | Notes |
|---|---|---|
| OpenUSD | v25.05 | Prebuilt; found via FindOpenUSD.cmake |
| Dear ImGui | v1.92.7 | Docking branch; Win32 + OpenGL3 backends |
| GLAD | — | OpenGL 3.3 core loader |
| Python | 3.12 | Required by OpenUSD runtime (python312.dll) |
Optional Render Delegates
| Delegate | Renderer Version | Variable | Notes |
|---|---|---|---|
| hdEmbree | Embree 4.4.1 | HDEMBREE_USD_ROOT |
USD build with PXR_ENABLE_EMBREE_PLUGIN=ON; set EMBREE_LOCATION if Embree DLLs are not in HDEMBREE_USD_ROOT/bin |
| hdArnold | Arnold 7.4.0 (MtoA 5.5.0) | HDARNOLD_ROOT, ARNOLD_LOCATION |
arnold-usd install dir + MtoA root for ai.dll |
| hdCycles | Cycles 5.2.0 | WITH_CYCLES=ON |
Built from source under third_party/cycles via ExternalProject; requires MSVC |
All delegate paths are pre-configured in CMakePresets.json.
Build
Prerequisites
- Visual Studio 17 2022 (MSVC v143) with C++17 support
- CMake 3.20+
- Python 3.12 installed at
%LOCALAPPDATA%\Programs\Python\Python312\
Steps
cmake --preset default
cmake --build build --config Release
cmake --install build --config Release
The installed executable lives in install/bin/.
Presets — default (Release + tests), debug, release, no-tests.
Building with Cycles
WITH_CYCLES=ON is set in the default preset. The first build compiles Blender Cycles from third_party/cycles via ExternalProject — expect a significantly longer initial build. Subsequent builds are incremental.
Cycles runtime dependencies (embree4.dll, openvdb.dll, OpenImageDenoise.dll, tbb12.dll, etc.) are automatically deployed to the output directory by a post-build step. CRT DLLs bundled inside the Cycles install are excluded to avoid version conflicts with the app's own MSVC runtime.
Tests
cmake --build build --config Release
ctest --test-dir build -C Release
Architecture
src/
├── main.cpp — Entry point (plugin path init, app lifecycle)
├── core/ — USD business logic (no UI dependency)
│ ├── UsdStageManager — Stage open/create/save/close lifecycle
│ ├── LayerManager — Layer stack introspection and mutation
│ ├── PropertyManager — Property read/write via edit target with undo
│ ├── CommandHistory — Undo/redo stack for ICommand instances
│ ├── UsdSceneRenderer — Hydra rendering + picking + overlays
│ ├── ViewportCamera — Free / USD-camera-driven orbital camera
│ └── commands/ — ICommand subclasses for all mutable operations
│ ├── TransformCommand — Undoable translate/rotate/scale
│ ├── CreatePrimCommand — Undoable prim creation (with camera orientation)
│ ├── DeletePrimCommand — Undoable prim deletion (SdfCopySpec snapshot)
│ ├── AttributeSetCommand — Type-agnostic undoable attribute writes
│ ├── AddReferenceCommand — Undoable reference addition
│ ├── ReplaceReferenceCommand — Undoable reference replacement
│ └── LayerCommands — Create, remove, reorder sublayers
├── ui/ — ImGui panels and application shell
│ ├── Application — App shell (owns managers/panels, docking, menus)
│ ├── ImGuiContext — Win32+OpenGL+ImGui initialization and loop
│ ├── ViewportPanel — Viewport container (layout, divider drag, maximize)
│ ├── ViewportTile — Single viewport tile (camera, rendering, overlays, picking)
│ ├── SceneHierarchyPanel — Prim tree browser with context menus
│ ├── PropertyPanel — Attribute/transform editor (channel box-style)
│ ├── LayerPanel — Layer stack editor with modal dialogs
│ ├── TransformManipulator — ImDrawList gizmo (move/rotate/scale)
│ └── IconManager — SVG icon rasterization via NanoSVG
└── utils/ — Cross-cutting utilities
├── Logger — Thread-safe logging (file + console)
├── FileDialog — Win32 file open/save dialogs
├── PathUtils — Exe-relative path resolution
└── GLExt — GLAD extension initialization
Key Design Patterns
- Command Pattern — All mutable USD operations go through
ICommand→CommandHistoryfor undo/redo - Panel-Manager Separation — UI panels hold pointers to core managers but contain no USD layer/prim logic
- Callback Wiring — Cross-panel communication via
std::functioncallbacks (e.g. viewport pick → hierarchy select → property panel update) - Render-Delegate Plugability —
UsdSceneRenderercan switch Hydra render plugins at runtime - Gizmo as 2D Overlay —
TransformManipulatoruses pureImDrawListcalls (no GL resources), same approach as ImGuizmo
Project Configuration
| Config | Purpose |
|---|---|
cmake/modules/FindOpenUSD.cmake |
OpenUSD SDK discovery |
cmake/modules/FindImgui.cmake |
ImGui source integration |
cmake/modules/FindGlad.cmake |
GLAD loader setup |
CMakePresets.json |
Build presets (VS 17 2022, x64) |
AGENTS.md |
AI agent build/architecture guide |
.kilo/ |
Kilo AI configuration (commands, agents, skills) |
openspec/changes/ |
Feature proposals and implementation tracking |
Development Workflow
This project uses OpenSpec for feature development:
- Propose — Create a new change under
openspec/changes/<name>/withproposal.md,design.md,specs/, andtasks.md - Implement — Work through checkbox-tracked tasks in
tasks.md - Archive — Move completed changes to
openspec/archive/when done
Use Kilo's openspec-* skills to streamline this workflow.
Runtime Requirements
- Python 3.12 — OpenUSD requires
python312.dllat runtime. CMake copies it to the output directory viaPOST_BUILDcommands. - USD Plugin Path — The app scans
<exe_dir>/usd/forplugInfo.jsonand registers plugins viapxr::PlugRegistry. - Render delegate DLLs — hdEmbree, hdArnold, and hdCycles each bring their own runtime dependencies (Embree, Arnold SDK, Cycles libs). CMake
POST_BUILDsteps deploy all required DLLs next to the executable automatically.
Contributing
Before writing any OpenUSD API call, verify the API exists in the actual SDK:
findstr /r /s "FunctionName" third_party\OpenUSD_v25.05\include\
Always build and install before running tests:
cmake --build build --config Release
cmake --install build --config Release
ctest --test-dir build -C Release
License
See LICENSE file for details.