Add animation curve editor with Bezier F-curve round-trip
Maya-style Graph Editor panel (View > Curve Editor) that fits Catmull-Rom Bezier curves through USD time samples, allows interactive editing with tangent handles, and bakes the result back to dense linear samples on Apply. Key behaviours: - Left panel lists all animated attributes of the selected prim, decomposed into per-component channels (translate [X/Y/Z], etc.) with colour swatches and visibility toggles - ImDrawList canvas: smooth Bezier polylines, always-visible tangent handle lines/circles, diamond keyframe markers - Pan (MMB/Alt+drag), zoom (scroll / Shift+scroll), Frame All (F) - LMB drag moves keyframes; tangent handle drag reshapes curve with mirrored or broken handles; box-select for multi-selection - Double-click canvas adds a keyframe; Delete removes selected keyframes - RMB context menu: Delete, Flatten, Break/Unify Tangents, Auto Tangents - Simplify toggle (Ramer-Douglas-Peucker) reduces baked sample count - Time cursor draggable to scrub the timeline - UsdNotice::ObjectsChanged listener re-fits clean channels whenever the stage changes externally (Property Panel edits, Auto-Key, undo/redo), while preserving channels with unsaved Bezier edits - Bake is a single undoable AttributeSetCommand (Ctrl+Z restores original sparse samples); Revert discards in-editor edits without touching USD Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+24
-2
@@ -43,6 +43,7 @@ Application::Application()
|
||||
, m_showViewport(true)
|
||||
, m_showPropertyPanel(true)
|
||||
, m_showTimeline(true)
|
||||
, m_showCurveEditor(false)
|
||||
, m_running(false) {
|
||||
}
|
||||
|
||||
@@ -78,11 +79,19 @@ bool Application::Initialize(const std::string& windowTitle, int width, int heig
|
||||
m_propertyPanel->SetPropertyManager(m_propertyManager.get());
|
||||
m_propertyPanel->SetCommandHistory(&m_commandHistory);
|
||||
|
||||
m_curveEditorPanel = std::make_unique<CurveEditorPanel>();
|
||||
m_curveEditorPanel->SetCommandHistory(&m_commandHistory);
|
||||
|
||||
m_timelinePanel = std::make_unique<TimelinePanel>();
|
||||
m_timelinePanel->OnTimeChanged = [this](pxr::UsdTimeCode displayTime,
|
||||
pxr::UsdTimeCode editTime) {
|
||||
m_viewportPanel->SetTimeCodes(displayTime, editTime);
|
||||
m_propertyPanel->SetTimeCodes(displayTime, editTime);
|
||||
m_curveEditorPanel->SetTimeCodes(displayTime, editTime);
|
||||
};
|
||||
|
||||
m_curveEditorPanel->OnScrub = [this](double frame) {
|
||||
m_timelinePanel->SetCurrentFrameExternal(frame);
|
||||
};
|
||||
m_timelinePanel->OnBrowseFolder = [this]() -> std::string {
|
||||
HWND hwnd = m_imguiContext ? m_imguiContext->GetWindowHandle() : nullptr;
|
||||
@@ -101,6 +110,7 @@ bool Application::Initialize(const std::string& windowTitle, int width, int heig
|
||||
[this](const std::string& path) {
|
||||
m_viewportPanel->SetSelectedPrimPath(path);
|
||||
m_propertyPanel->SetSelectedPrimPath(path);
|
||||
m_curveEditorPanel->SetSelectedPrimPath(path);
|
||||
});
|
||||
|
||||
m_sceneHierarchyPanel->SetOnStageMetadataChanged(
|
||||
@@ -108,16 +118,18 @@ bool Application::Initialize(const std::string& windowTitle, int width, int heig
|
||||
RefreshManagers();
|
||||
});
|
||||
|
||||
// Single click in viewport → sync hierarchy + property panel
|
||||
// Single click in viewport → sync hierarchy + property panel + curve editor
|
||||
m_viewportPanel->OnPrimPicked = [this](const std::string& path) {
|
||||
m_sceneHierarchyPanel->SetSelectedPath(path);
|
||||
m_propertyPanel->SetSelectedPrimPath(path);
|
||||
m_curveEditorPanel->SetSelectedPrimPath(path);
|
||||
};
|
||||
|
||||
// Rect drag in viewport → sync hierarchy + property panel (primary path)
|
||||
// Rect drag in viewport → sync hierarchy + property panel + curve editor (primary)
|
||||
m_viewportPanel->OnPrimsPickedRect = [this](const std::vector<std::string>& paths) {
|
||||
m_sceneHierarchyPanel->SetSelectedPaths(paths);
|
||||
m_propertyPanel->SetSelectedPrimPath(paths.empty() ? "" : paths.front());
|
||||
m_curveEditorPanel->SetSelectedPrimPath(paths.empty() ? "" : paths.front());
|
||||
};
|
||||
|
||||
if (!m_stageManager->CreateInMemoryStage()) {
|
||||
@@ -178,6 +190,7 @@ void Application::RefreshManagers() {
|
||||
m_viewportPanel->FrameScene();
|
||||
m_propertyPanel->SetStage(stage);
|
||||
m_timelinePanel->SetStage(stage);
|
||||
m_curveEditorPanel->SetStage(stage);
|
||||
} else {
|
||||
m_layerManager->SetStage(nullptr);
|
||||
m_propertyManager->SetStage(nullptr);
|
||||
@@ -185,6 +198,7 @@ void Application::RefreshManagers() {
|
||||
m_viewportPanel->SetStage(nullptr);
|
||||
m_propertyPanel->SetStage(nullptr);
|
||||
m_timelinePanel->SetStage(nullptr);
|
||||
m_curveEditorPanel->SetStage(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,6 +269,13 @@ void Application::RenderUI() {
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
if (m_showCurveEditor) {
|
||||
ImGui::SetNextWindowSize({960, 320}, ImGuiCond_FirstUseEver);
|
||||
ImGui::Begin("Curve Editor", &m_showCurveEditor, ImGuiWindowFlags_NoCollapse);
|
||||
m_curveEditorPanel->Render();
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
// Capture happens after the timeline so that the "Start" button click in
|
||||
// this frame is detected, and after the viewport has rendered the scene.
|
||||
if (m_timelinePanel->IsPlayblasting()) {
|
||||
@@ -395,6 +416,7 @@ void Application::RenderMenuBar() {
|
||||
ImGui::MenuItem("Viewport", nullptr, &m_showViewport);
|
||||
ImGui::MenuItem("Property Panel", nullptr, &m_showPropertyPanel);
|
||||
ImGui::MenuItem("Timeline", nullptr, &m_showTimeline);
|
||||
ImGui::MenuItem("Curve Editor", nullptr, &m_showCurveEditor);
|
||||
ImGui::Separator();
|
||||
ImGui::MenuItem("Stage Info", nullptr, &m_showStageInfo);
|
||||
ImGui::MenuItem("Demo Window", nullptr, &m_showDemoWindow);
|
||||
|
||||
Reference in New Issue
Block a user