Timeline Prototype

This commit is contained in:
2026-06-13 11:48:56 +08:00
parent 43c70be574
commit c4bf9673a8
24 changed files with 501 additions and 58 deletions
+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path d="M 60 60 L 200 60 L 200 452 L 60 452 Z M 312 60 L 452 60 L 452 452 L 312 452 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 160 B

+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path d="M 40 60 L 40 452 L 472 256 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 111 B

+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path d="M 40 60 L 115 60 L 115 452 L 40 452 Z M 460 60 L 460 452 L 155 256 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 151 B

+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path d="M 40 60 L 362 256 L 40 452 Z M 402 60 L 472 60 L 472 452 L 402 452 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 151 B

+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path d="M 256 60 L 256 452 L 40 256 Z M 472 60 L 472 452 L 256 256 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 143 B

+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path d="M 40 60 L 256 256 L 40 452 Z M 256 60 L 472 256 L 256 452 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 142 B

+5 -5
View File
@@ -356,7 +356,7 @@ pxr::GfRange3d UsdSceneRenderer::ComputeStageBounds()
if (!m_stage) return {};
pxr::TfTokenVector purposes = {
pxr::UsdGeomTokens->default_, pxr::UsdGeomTokens->proxy };
pxr::UsdGeomBBoxCache bboxCache(pxr::UsdTimeCode::Default(), purposes, true);
pxr::UsdGeomBBoxCache bboxCache(m_currentTime, purposes, true);
return bboxCache.ComputeWorldBound(m_stage->GetPseudoRoot()).ComputeAlignedRange();
}
@@ -594,7 +594,7 @@ void UsdSceneRenderer::Render(int width, int height)
// --- Render params (matches stageView.renderSinglePass) ---
m_renderParams = pxr::UsdImagingGLRenderParams();
m_renderParams.frame = pxr::UsdTimeCode::Default();
m_renderParams.frame = m_currentTime;
m_renderParams.complexity = 1.0f;
m_renderParams.drawMode = pxr::UsdImagingGLDrawMode::DRAW_SHADED_SMOOTH;
m_renderParams.showGuides = true;
@@ -1101,7 +1101,7 @@ void UsdSceneRenderer::DrawBoundingBoxes(
pxr::TfTokenVector purposes = {
pxr::UsdGeomTokens->default_, pxr::UsdGeomTokens->proxy };
pxr::UsdGeomBBoxCache bboxCache(
pxr::UsdTimeCode::Default(), purposes, /*useExtentsHint=*/true);
m_currentTime, purposes, /*useExtentsHint=*/true);
// Save/restore GL state
GLboolean prevDepthMask;
@@ -1350,7 +1350,7 @@ void UsdSceneRenderer::DrawCameraWireframes(
if (!prim) continue;
pxr::UsdGeomCamera usdCam(prim);
pxr::GfCamera gfCam = usdCam.GetCamera(pxr::UsdTimeCode::Default());
pxr::GfCamera gfCam = usdCam.GetCamera(m_currentTime);
// Determine colour
bool isSelected = (selectedSet.count(camPath.GetString()) > 0);
@@ -1430,7 +1430,7 @@ bool UsdSceneRenderer::PickCameraAtPoint(
if (!prim) continue;
pxr::UsdGeomCamera usdCam(prim);
pxr::GfCamera gfCam = usdCam.GetCamera(pxr::UsdTimeCode::Default());
pxr::GfCamera gfCam = usdCam.GetCamera(m_currentTime);
std::vector<float> verts;
BuildCameraWireframeLines(gfCam, scale, verts);
+8
View File
@@ -3,6 +3,7 @@
#include <glad/gl.h>
#include <pxr/usd/usd/stage.h>
#include <pxr/usd/usd/timeCode.h>
#include <pxr/imaging/glf/drawTarget.h>
#include <pxr/usdImaging/usdImagingGL/engine.h>
#include <pxr/imaging/glf/simpleLight.h>
@@ -163,6 +164,11 @@ public:
if (val) m_cameraCacheDirty = true;
}
/// Current evaluation time (timeline frame). Drives Hydra rendering,
/// USD camera resolution, and bounding-box computation.
pxr::UsdTimeCode GetCurrentTimeCode() const { return m_currentTime; }
void SetCurrentTimeCode(pxr::UsdTimeCode t) { m_currentTime = t; }
// -----------------------------------------------------------------------
// Bounding box display
// -----------------------------------------------------------------------
@@ -267,6 +273,8 @@ private:
bool m_rendererInitialized;
bool m_forceRefresh;
pxr::UsdTimeCode m_currentTime = pxr::UsdTimeCode::Default();
int m_diagFrameCount;
// --- Grid GL resources (line geometry, reuses m_axisProgram / m_axisUniformMVP) ---
+87 -6
View File
@@ -1,9 +1,52 @@
#include "TransformCommand.h"
#include "../../utils/Logger.h"
#include <pxr/usd/usd/editContext.h>
#include <pxr/usd/usdGeom/xformOp.h>
namespace UsdLayerManager {
// True when attr has an authored time sample exactly at time t.
static bool HasSampleAtTime(const pxr::UsdAttribute& attr, double t)
{
if (!attr) return false;
std::vector<double> times;
if (!attr.GetTimeSamples(&times)) return false;
for (double s : times)
if (s == t) return true;
return false;
}
// Attribute name of the rotate op XformCommonAPI authors for a rotation order
// (e.g. "xformOp:rotateXYZ").
static pxr::TfToken RotateOpName(pxr::UsdGeomXformCommonAPI::RotationOrder ro)
{
return pxr::UsdGeomXformOp::GetOpName(
pxr::UsdGeomXformCommonAPI::ConvertRotationOrderToOpType(ro));
}
TransformCommand::SamplePresence TransformCommand::QuerySamplePresence(
pxr::UsdStageRefPtr stage,
const pxr::SdfPath& primPath,
pxr::UsdGeomXformCommonAPI::RotationOrder rotOrder,
pxr::UsdTimeCode timeCode)
{
SamplePresence presence;
if (timeCode.IsDefault() || !stage) return presence;
pxr::UsdPrim prim = stage->GetPrimAtPath(primPath);
if (!prim) return presence;
double t = timeCode.GetValue();
presence.translate = HasSampleAtTime(
prim.GetAttribute(pxr::UsdGeomXformOp::GetOpName(
pxr::UsdGeomXformOp::TypeTranslate)), t);
presence.rotate = HasSampleAtTime(
prim.GetAttribute(RotateOpName(rotOrder)), t);
presence.scale = HasSampleAtTime(
prim.GetAttribute(pxr::UsdGeomXformOp::GetOpName(
pxr::UsdGeomXformOp::TypeScale)), t);
return presence;
}
TransformCommand::TransformCommand(pxr::UsdStageRefPtr stage,
const pxr::SdfPath& primPath,
pxr::SdfLayerHandle editLayer,
@@ -14,6 +57,8 @@ TransformCommand::TransformCommand(pxr::UsdStageRefPtr stage,
const pxr::GfVec3f& newRotate,
const pxr::GfVec3f& newScale,
pxr::UsdGeomXformCommonAPI::RotationOrder rotOrder,
pxr::UsdTimeCode timeCode,
SamplePresence preEditSamples,
std::string description)
: m_stage(stage)
, m_primPath(primPath)
@@ -25,7 +70,9 @@ TransformCommand::TransformCommand(pxr::UsdStageRefPtr stage,
, m_newRotate(newRotate)
, m_newScale(newScale)
, m_rotOrder(rotOrder)
, m_timeCode(timeCode)
, m_description(std::move(description))
, m_preEditSamples(preEditSamples)
{
}
@@ -35,6 +82,7 @@ void TransformCommand::Execute() {
void TransformCommand::Undo() {
Apply(m_oldTranslate, m_oldRotate, m_oldScale);
ClearCreatedSamples();
}
void TransformCommand::Apply(const pxr::GfVec3d& t,
@@ -51,17 +99,50 @@ void TransformCommand::Apply(const pxr::GfVec3d& t,
try {
if (m_editLayer) {
pxr::UsdEditContext ec(m_stage, m_editLayer);
api.SetTranslate(t, pxr::UsdTimeCode::Default());
api.SetRotate(r, m_rotOrder, pxr::UsdTimeCode::Default());
api.SetScale(s, pxr::UsdTimeCode::Default());
api.SetTranslate(t, m_timeCode);
api.SetRotate(r, m_rotOrder, m_timeCode);
api.SetScale(s, m_timeCode);
} else {
api.SetTranslate(t, pxr::UsdTimeCode::Default());
api.SetRotate(r, m_rotOrder, pxr::UsdTimeCode::Default());
api.SetScale(s, pxr::UsdTimeCode::Default());
api.SetTranslate(t, m_timeCode);
api.SetRotate(r, m_rotOrder, m_timeCode);
api.SetScale(s, m_timeCode);
}
} catch (const std::exception& e) {
LOG_ERROR(std::string("TransformCommand::Apply error: ") + e.what());
}
}
void TransformCommand::ClearCreatedSamples()
{
if (m_timeCode.IsDefault()) return;
if (!m_stage || m_primPath.IsEmpty()) return;
pxr::UsdPrim prim = m_stage->GetPrimAtPath(m_primPath);
if (!prim) return;
auto clearOp = [&](const pxr::TfToken& opName, bool hadSample) {
if (hadSample) return;
pxr::UsdAttribute attr = prim.GetAttribute(opName);
if (attr) attr.ClearAtTime(m_timeCode);
};
auto clearAll = [&]() {
clearOp(pxr::UsdGeomXformOp::GetOpName(
pxr::UsdGeomXformOp::TypeTranslate), m_preEditSamples.translate);
clearOp(RotateOpName(m_rotOrder), m_preEditSamples.rotate);
clearOp(pxr::UsdGeomXformOp::GetOpName(
pxr::UsdGeomXformOp::TypeScale), m_preEditSamples.scale);
};
try {
if (m_editLayer) {
pxr::UsdEditContext ec(m_stage, m_editLayer);
clearAll();
} else {
clearAll();
}
} catch (const std::exception& e) {
LOG_ERROR(std::string("TransformCommand::ClearCreatedSamples error: ") + e.what());
}
}
} // namespace UsdLayerManager
+29
View File
@@ -2,6 +2,7 @@
#include "../CommandHistory.h"
#include <pxr/usd/usd/stage.h>
#include <pxr/usd/usd/timeCode.h>
#include <pxr/usd/sdf/path.h>
#include <pxr/usd/sdf/layer.h>
#include <pxr/base/gf/vec3d.h>
@@ -14,8 +15,26 @@ namespace UsdLayerManager {
/// Reverses a complete TRS edit on a single prim (from gizmo drag or
/// Property panel field commit). Stores the prim path, edit-target layer,
/// and pre/post translate/rotate/scale triples.
///
/// When timeCode is not Default() the edit is a keyframe: values are written
/// as time samples at that frame. Undo removes samples this command created
/// on ops that had no sample at that frame before the edit.
class TransformCommand : public ICommand {
public:
/// Whether each common op had an authored sample at the edit time.
/// Must be queried BEFORE the first live write of an interactive edit
/// (live writes author the sample, so querying later always finds one).
struct SamplePresence {
bool translate = false;
bool rotate = false;
bool scale = false;
};
static SamplePresence QuerySamplePresence(
pxr::UsdStageRefPtr stage,
const pxr::SdfPath& primPath,
pxr::UsdGeomXformCommonAPI::RotationOrder rotOrder,
pxr::UsdTimeCode timeCode);
TransformCommand(pxr::UsdStageRefPtr stage,
const pxr::SdfPath& primPath,
pxr::SdfLayerHandle editLayer,
@@ -26,6 +45,8 @@ public:
const pxr::GfVec3f& newRotate,
const pxr::GfVec3f& newScale,
pxr::UsdGeomXformCommonAPI::RotationOrder rotOrder,
pxr::UsdTimeCode timeCode,
SamplePresence preEditSamples,
std::string description = "Transform");
void Execute() override;
@@ -36,6 +57,9 @@ private:
void Apply(const pxr::GfVec3d& t,
const pxr::GfVec3f& r,
const pxr::GfVec3f& s);
/// Remove the time samples Execute created on ops that previously had
/// no sample at m_timeCode (no-op when m_timeCode is Default).
void ClearCreatedSamples();
pxr::UsdStageRefPtr m_stage;
pxr::SdfPath m_primPath;
@@ -50,7 +74,12 @@ private:
pxr::GfVec3f m_newScale;
pxr::UsdGeomXformCommonAPI::RotationOrder m_rotOrder;
pxr::UsdTimeCode m_timeCode;
std::string m_description;
// Whether each op already had a sample at m_timeCode before the edit
// (only meaningful when m_timeCode is not Default).
SamplePresence m_preEditSamples;
};
} // namespace UsdLayerManager
+17 -1
View File
@@ -69,11 +69,19 @@ bool Application::Initialize(const std::string& windowTitle, int width, int heig
m_propertyPanel->SetPropertyManager(m_propertyManager.get());
m_propertyPanel->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);
};
// Initialize IconManager — must happen after OpenGL context is ready (ImGui init above).
m_iconManager = std::make_unique<IconManager>();
m_iconManager->Initialize(ResourcePath("resources/icons"), 24);
m_sceneHierarchyPanel->SetIconManager(m_iconManager.get());
m_viewportPanel->SetIconManager(m_iconManager.get());
m_timelinePanel->SetIconManager(m_iconManager.get());
m_sceneHierarchyPanel->SetOnPrimSelected(
[this](const std::string& path) {
@@ -155,19 +163,23 @@ void Application::RefreshManagers() {
m_viewportPanel->SetStage(stage);
m_viewportPanel->FrameScene();
m_propertyPanel->SetStage(stage);
m_timelinePanel->SetStage(stage);
} else {
m_layerManager->SetStage(nullptr);
m_propertyManager->SetStage(nullptr);
m_sceneHierarchyPanel->SetStage(nullptr);
m_viewportPanel->SetStage(nullptr);
m_propertyPanel->SetStage(nullptr);
m_timelinePanel->SetStage(nullptr);
}
}
void Application::Update() {
ImGuiIO& io = ImGui::GetIO();
m_timelinePanel->Update(io.DeltaTime);
// Process undo/redo hotkeys (Ctrl+Z / Ctrl+Y / Ctrl+Shift+Z).
// Only fire when no ImGui text-input widget has keyboard focus.
ImGuiIO& io = ImGui::GetIO();
if (!io.WantTextInput) {
if (io.KeyCtrl && !io.KeyShift && ImGui::IsKeyPressed(ImGuiKey_Z, false)) {
m_commandHistory.Undo();
@@ -211,6 +223,10 @@ void Application::RenderUI() {
m_propertyPanel->Render();
ImGui::End();
ImGui::Begin("Timeline", nullptr, ImGuiWindowFlags_NoCollapse);
m_timelinePanel->Render();
ImGui::End();
m_imguiContext->Render();
}
+2
View File
@@ -6,6 +6,7 @@
#include "SceneHierarchyPanel.h"
#include "ViewportPanel.h"
#include "PropertyPanel.h"
#include "TimelinePanel.h"
#include "../core/UsdStageManager.h"
#include "../core/LayerManager.h"
#include "../core/PropertyManager.h"
@@ -52,6 +53,7 @@ private:
std::unique_ptr<SceneHierarchyPanel> m_sceneHierarchyPanel;
std::unique_ptr<ViewportPanel> m_viewportPanel;
std::unique_ptr<PropertyPanel> m_propertyPanel;
std::unique_ptr<TimelinePanel> m_timelinePanel;
bool m_showDemoWindow;
bool m_showStageInfo;
bool m_running;
+7
View File
@@ -48,6 +48,12 @@ static const char* IconFilename(Icon icon) {
case Icon::LayoutHSplit: return "layout-hsplit.svg";
case Icon::LayoutVSplit: return "layout-vsplit.svg";
case Icon::LayoutQuad: return "layout-quad.svg";
case Icon::SkipBack: return "skip-back.svg";
case Icon::StepBack: return "step-back.svg";
case Icon::Play: return "play.svg";
case Icon::Pause: return "pause.svg";
case Icon::StepForward: return "step-forward.svg";
case Icon::SkipEnd: return "skip-end.svg";
default: return nullptr;
}
}
@@ -61,6 +67,7 @@ static constexpr Icon kAllIcons[] = {
Icon::ToolSelect, Icon::ToolMove, Icon::ToolRotate, Icon::ToolScale,
Icon::WorldSpace, Icon::LocalSpace, Icon::Grid, Icon::Antialias,
Icon::LayoutSingle, Icon::LayoutHSplit, Icon::LayoutVSplit, Icon::LayoutQuad,
Icon::SkipBack, Icon::StepBack, Icon::Play, Icon::Pause, Icon::StepForward, Icon::SkipEnd,
};
// ---------------------------------------------------------------------------
+7
View File
@@ -40,6 +40,13 @@ enum class Icon {
LayoutHSplit, // two panels side-by-side
LayoutVSplit, // two panels top/bottom
LayoutQuad, // four panels 2×2
// Timeline transport
SkipBack, // go to first frame (|◀)
StepBack, // previous frame (◀◀)
Play, // play (▶)
Pause, // pause (⏸)
StepForward, // next frame (▶▶)
SkipEnd, // go to last frame (▶|)
};
/// Loads SVG files from disk, rasterizes them with NanoSVG, uploads them as
+52 -26
View File
@@ -74,6 +74,13 @@ void PropertyPanel::SetSelectedPrimPath(const std::string& path) {
}
}
void PropertyPanel::SetTimeCodes(pxr::UsdTimeCode displayTime,
pxr::UsdTimeCode editTime) {
m_displayTime = displayTime;
m_editTime = editTime;
m_needsRead = true;
}
// ---------------------------------------------------------------------------
// USD read
// ---------------------------------------------------------------------------
@@ -101,7 +108,7 @@ void PropertyPanel::ReadTransform() {
if (xformAPI.GetXformVectors(
&translation, &rotation, &scale, &pivot, &rotOrder,
UsdTimeCode::Default()))
m_displayTime))
{
m_translate = GfVec3f(
static_cast<float>(translation[0]),
@@ -121,7 +128,7 @@ void PropertyPanel::ReadTransform() {
GfMatrix4d localMatrix;
bool resetsXformStack = false;
if (!xformable.GetLocalTransformation(
&localMatrix, &resetsXformStack, UsdTimeCode::Default()))
&localMatrix, &resetsXformStack, m_displayTime))
{
localMatrix = GfMatrix4d(1); // identity fallback
}
@@ -232,6 +239,16 @@ void PropertyPanel::EnsureCommonAPILayout() {
m_rotOrder = UsdGeomXformCommonAPI::RotationOrderXYZ;
}
void PropertyPanel::CaptureEditStart() {
m_editStartTranslate = m_translate;
m_editStartRotate = m_rotate;
m_editStartScale = m_scale;
// Keyframe presence must be sampled before the first live write so the
// undo command knows whether this edit created the sample at m_editTime.
m_editStartSamples = TransformCommand::QuerySamplePresence(
m_stage, SdfPath(m_selectedPrimPath), m_rotOrder, m_editTime);
}
void PropertyPanel::WriteTranslate() {
if (!m_stage || m_selectedPrimPath.empty()) return;
UsdPrim prim = m_stage->GetPrimAtPath(SdfPath(m_selectedPrimPath));
@@ -241,7 +258,7 @@ void PropertyPanel::WriteTranslate() {
if (!api) return;
api.SetTranslate(
GfVec3d(m_translate[0], m_translate[1], m_translate[2]),
UsdTimeCode::Default());
m_editTime);
}
void PropertyPanel::WriteRotate() {
@@ -251,7 +268,7 @@ void PropertyPanel::WriteRotate() {
if (m_xformFallback) { EnsureCommonAPILayout(); return; }
UsdGeomXformCommonAPI api(prim);
if (!api) return;
api.SetRotate(m_rotate, m_rotOrder, UsdTimeCode::Default());
api.SetRotate(m_rotate, m_rotOrder, m_editTime);
}
void PropertyPanel::WriteScale() {
@@ -261,7 +278,7 @@ void PropertyPanel::WriteScale() {
if (m_xformFallback) { EnsureCommonAPILayout(); return; }
UsdGeomXformCommonAPI api(prim);
if (!api) return;
api.SetScale(m_scale, UsdTimeCode::Default());
api.SetScale(m_scale, m_editTime);
}
// ---------------------------------------------------------------------------
@@ -323,7 +340,7 @@ void PropertyPanel::RenderTransformSection() {
ImGui::SetNextItemWidth(-FLT_MIN);
if (ImGui::DragFloat("##tx", &m_translate[0], 0.01f, 0.f, 0.f, "%.3f")) WriteTranslate();
bool anyActive = ImGui::IsItemActive();
if (ImGui::IsItemActivated()) { m_editStartTranslate = m_translate; m_editStartRotate = m_rotate; m_editStartScale = m_scale; }
if (ImGui::IsItemActivated()) CaptureEditStart();
if (ImGui::IsItemDeactivatedAfterEdit()) {
if (m_commandHistory && m_stage && !m_selectedPrimPath.empty()) {
auto editLayer = m_stage->GetEditTarget().GetLayer();
@@ -332,14 +349,15 @@ void PropertyPanel::RenderTransformSection() {
pxr::GfVec3d(m_editStartTranslate[0], m_editStartTranslate[1], m_editStartTranslate[2]),
m_editStartRotate, m_editStartScale,
pxr::GfVec3d(m_translate[0], m_translate[1], m_translate[2]),
m_rotate, m_scale, m_rotOrder, "Translate"));
m_rotate, m_scale, m_rotOrder,
m_editTime, m_editStartSamples, "Translate"));
}
}
ImGui::TableSetColumnIndex(2);
ImGui::SetNextItemWidth(-FLT_MIN);
if (ImGui::DragFloat("##ty", &m_translate[1], 0.01f, 0.f, 0.f, "%.3f")) WriteTranslate();
anyActive = anyActive || ImGui::IsItemActive();
if (ImGui::IsItemActivated()) { m_editStartTranslate = m_translate; m_editStartRotate = m_rotate; m_editStartScale = m_scale; }
if (ImGui::IsItemActivated()) CaptureEditStart();
if (ImGui::IsItemDeactivatedAfterEdit()) {
if (m_commandHistory && m_stage && !m_selectedPrimPath.empty()) {
auto editLayer = m_stage->GetEditTarget().GetLayer();
@@ -348,14 +366,15 @@ void PropertyPanel::RenderTransformSection() {
pxr::GfVec3d(m_editStartTranslate[0], m_editStartTranslate[1], m_editStartTranslate[2]),
m_editStartRotate, m_editStartScale,
pxr::GfVec3d(m_translate[0], m_translate[1], m_translate[2]),
m_rotate, m_scale, m_rotOrder, "Translate"));
m_rotate, m_scale, m_rotOrder,
m_editTime, m_editStartSamples, "Translate"));
}
}
ImGui::TableSetColumnIndex(3);
ImGui::SetNextItemWidth(-FLT_MIN);
if (ImGui::DragFloat("##tz", &m_translate[2], 0.01f, 0.f, 0.f, "%.3f")) WriteTranslate();
anyActive = anyActive || ImGui::IsItemActive();
if (ImGui::IsItemActivated()) { m_editStartTranslate = m_translate; m_editStartRotate = m_rotate; m_editStartScale = m_scale; }
if (ImGui::IsItemActivated()) CaptureEditStart();
if (ImGui::IsItemDeactivatedAfterEdit()) {
if (m_commandHistory && m_stage && !m_selectedPrimPath.empty()) {
auto editLayer = m_stage->GetEditTarget().GetLayer();
@@ -364,7 +383,8 @@ void PropertyPanel::RenderTransformSection() {
pxr::GfVec3d(m_editStartTranslate[0], m_editStartTranslate[1], m_editStartTranslate[2]),
m_editStartRotate, m_editStartScale,
pxr::GfVec3d(m_translate[0], m_translate[1], m_translate[2]),
m_rotate, m_scale, m_rotOrder, "Translate"));
m_rotate, m_scale, m_rotOrder,
m_editTime, m_editStartSamples, "Translate"));
}
}
@@ -376,7 +396,7 @@ void PropertyPanel::RenderTransformSection() {
ImGui::SetNextItemWidth(-FLT_MIN);
if (ImGui::DragFloat("##rx", &m_rotate[0], 0.1f, 0.f, 0.f, "%.3f")) WriteRotate();
anyActive = anyActive || ImGui::IsItemActive();
if (ImGui::IsItemActivated()) { m_editStartTranslate = m_translate; m_editStartRotate = m_rotate; m_editStartScale = m_scale; }
if (ImGui::IsItemActivated()) CaptureEditStart();
if (ImGui::IsItemDeactivatedAfterEdit()) {
if (m_commandHistory && m_stage && !m_selectedPrimPath.empty()) {
auto editLayer = m_stage->GetEditTarget().GetLayer();
@@ -385,14 +405,15 @@ void PropertyPanel::RenderTransformSection() {
pxr::GfVec3d(m_editStartTranslate[0], m_editStartTranslate[1], m_editStartTranslate[2]),
m_editStartRotate, m_editStartScale,
pxr::GfVec3d(m_translate[0], m_translate[1], m_translate[2]),
m_rotate, m_scale, m_rotOrder, "Rotate"));
m_rotate, m_scale, m_rotOrder,
m_editTime, m_editStartSamples, "Rotate"));
}
}
ImGui::TableSetColumnIndex(2);
ImGui::SetNextItemWidth(-FLT_MIN);
if (ImGui::DragFloat("##ry", &m_rotate[1], 0.1f, 0.f, 0.f, "%.3f")) WriteRotate();
anyActive = anyActive || ImGui::IsItemActive();
if (ImGui::IsItemActivated()) { m_editStartTranslate = m_translate; m_editStartRotate = m_rotate; m_editStartScale = m_scale; }
if (ImGui::IsItemActivated()) CaptureEditStart();
if (ImGui::IsItemDeactivatedAfterEdit()) {
if (m_commandHistory && m_stage && !m_selectedPrimPath.empty()) {
auto editLayer = m_stage->GetEditTarget().GetLayer();
@@ -401,14 +422,15 @@ void PropertyPanel::RenderTransformSection() {
pxr::GfVec3d(m_editStartTranslate[0], m_editStartTranslate[1], m_editStartTranslate[2]),
m_editStartRotate, m_editStartScale,
pxr::GfVec3d(m_translate[0], m_translate[1], m_translate[2]),
m_rotate, m_scale, m_rotOrder, "Rotate"));
m_rotate, m_scale, m_rotOrder,
m_editTime, m_editStartSamples, "Rotate"));
}
}
ImGui::TableSetColumnIndex(3);
ImGui::SetNextItemWidth(-FLT_MIN);
if (ImGui::DragFloat("##rz", &m_rotate[2], 0.1f, 0.f, 0.f, "%.3f")) WriteRotate();
anyActive = anyActive || ImGui::IsItemActive();
if (ImGui::IsItemActivated()) { m_editStartTranslate = m_translate; m_editStartRotate = m_rotate; m_editStartScale = m_scale; }
if (ImGui::IsItemActivated()) CaptureEditStart();
if (ImGui::IsItemDeactivatedAfterEdit()) {
if (m_commandHistory && m_stage && !m_selectedPrimPath.empty()) {
auto editLayer = m_stage->GetEditTarget().GetLayer();
@@ -417,7 +439,8 @@ void PropertyPanel::RenderTransformSection() {
pxr::GfVec3d(m_editStartTranslate[0], m_editStartTranslate[1], m_editStartTranslate[2]),
m_editStartRotate, m_editStartScale,
pxr::GfVec3d(m_translate[0], m_translate[1], m_translate[2]),
m_rotate, m_scale, m_rotOrder, "Rotate"));
m_rotate, m_scale, m_rotOrder,
m_editTime, m_editStartSamples, "Rotate"));
}
}
@@ -429,7 +452,7 @@ void PropertyPanel::RenderTransformSection() {
ImGui::SetNextItemWidth(-FLT_MIN);
if (ImGui::DragFloat("##sx", &m_scale[0], 0.005f, 0.f, 0.f, "%.3f")) WriteScale();
anyActive = anyActive || ImGui::IsItemActive();
if (ImGui::IsItemActivated()) { m_editStartTranslate = m_translate; m_editStartRotate = m_rotate; m_editStartScale = m_scale; }
if (ImGui::IsItemActivated()) CaptureEditStart();
if (ImGui::IsItemDeactivatedAfterEdit()) {
if (m_commandHistory && m_stage && !m_selectedPrimPath.empty()) {
auto editLayer = m_stage->GetEditTarget().GetLayer();
@@ -438,14 +461,15 @@ void PropertyPanel::RenderTransformSection() {
pxr::GfVec3d(m_editStartTranslate[0], m_editStartTranslate[1], m_editStartTranslate[2]),
m_editStartRotate, m_editStartScale,
pxr::GfVec3d(m_translate[0], m_translate[1], m_translate[2]),
m_rotate, m_scale, m_rotOrder, "Scale"));
m_rotate, m_scale, m_rotOrder,
m_editTime, m_editStartSamples, "Scale"));
}
}
ImGui::TableSetColumnIndex(2);
ImGui::SetNextItemWidth(-FLT_MIN);
if (ImGui::DragFloat("##sy", &m_scale[1], 0.005f, 0.f, 0.f, "%.3f")) WriteScale();
anyActive = anyActive || ImGui::IsItemActive();
if (ImGui::IsItemActivated()) { m_editStartTranslate = m_translate; m_editStartRotate = m_rotate; m_editStartScale = m_scale; }
if (ImGui::IsItemActivated()) CaptureEditStart();
if (ImGui::IsItemDeactivatedAfterEdit()) {
if (m_commandHistory && m_stage && !m_selectedPrimPath.empty()) {
auto editLayer = m_stage->GetEditTarget().GetLayer();
@@ -454,14 +478,15 @@ void PropertyPanel::RenderTransformSection() {
pxr::GfVec3d(m_editStartTranslate[0], m_editStartTranslate[1], m_editStartTranslate[2]),
m_editStartRotate, m_editStartScale,
pxr::GfVec3d(m_translate[0], m_translate[1], m_translate[2]),
m_rotate, m_scale, m_rotOrder, "Scale"));
m_rotate, m_scale, m_rotOrder,
m_editTime, m_editStartSamples, "Scale"));
}
}
ImGui::TableSetColumnIndex(3);
ImGui::SetNextItemWidth(-FLT_MIN);
if (ImGui::DragFloat("##sz", &m_scale[2], 0.005f, 0.f, 0.f, "%.3f")) WriteScale();
anyActive = anyActive || ImGui::IsItemActive();
if (ImGui::IsItemActivated()) { m_editStartTranslate = m_translate; m_editStartRotate = m_rotate; m_editStartScale = m_scale; }
if (ImGui::IsItemActivated()) CaptureEditStart();
if (ImGui::IsItemDeactivatedAfterEdit()) {
if (m_commandHistory && m_stage && !m_selectedPrimPath.empty()) {
auto editLayer = m_stage->GetEditTarget().GetLayer();
@@ -470,7 +495,8 @@ void PropertyPanel::RenderTransformSection() {
pxr::GfVec3d(m_editStartTranslate[0], m_editStartTranslate[1], m_editStartTranslate[2]),
m_editStartRotate, m_editStartScale,
pxr::GfVec3d(m_translate[0], m_translate[1], m_translate[2]),
m_rotate, m_scale, m_rotOrder, "Scale"));
m_rotate, m_scale, m_rotOrder,
m_editTime, m_editStartSamples, "Scale"));
}
}
@@ -946,7 +972,7 @@ void PropertyPanel::RenderPropertiesTable(const pxr::UsdPrim& prim) {
attr.GetMetadata(pxr::TfToken("allowedTokens"), &allowedTokens);
pxr::VtValue val;
bool hasVal = attr.Get(&val, pxr::UsdTimeCode::Default());
bool hasVal = attr.Get(&val, m_displayTime);
if (!allowedTokens.IsEmpty() &&
allowedTokens.IsHolding<pxr::VtArray<pxr::TfToken>>())
@@ -957,7 +983,7 @@ void PropertyPanel::RenderPropertiesTable(const pxr::UsdPrim& prim) {
if (ImGui::BeginCombo("##tok", cur.c_str())) {
for (const auto& tok : tokens) {
bool sel = (tok.GetString() == cur);
if (ImGui::Selectable(tok.GetText(), sel)) attr.Set(tok);
if (ImGui::Selectable(tok.GetText(), sel)) attr.Set(tok, m_editTime);
if (sel) ImGui::SetItemDefaultFocus();
}
ImGui::EndCombo();
@@ -966,7 +992,7 @@ void PropertyPanel::RenderPropertiesTable(const pxr::UsdPrim& prim) {
// Type-aware widget dispatch (mirrors DrawVtValue in usdtweak)
pxr::VtValue modified = DrawVtValueWidget(attr, val);
if (!modified.IsEmpty())
attr.Set(modified, pxr::UsdTimeCode::Default());
attr.Set(modified, m_editTime);
} else if (attr.HasAuthoredConnections()) {
SdfPathVector conns;
attr.GetConnections(&conns);
+15
View File
@@ -2,7 +2,9 @@
#include "../core/PropertyManager.h"
#include "../core/CommandHistory.h"
#include "../core/commands/TransformCommand.h"
#include <pxr/usd/usd/stage.h>
#include <pxr/usd/usd/timeCode.h>
#include <pxr/usd/sdf/path.h>
#include <pxr/base/gf/vec3f.h>
#include <pxr/base/gf/vec3d.h>
@@ -25,6 +27,10 @@ public:
void SetStage(pxr::UsdStageRefPtr stage);
void SetSelectedPrimPath(const std::string& path);
/// displayTime — frame used to read/display attribute values.
/// editTime — frame writes are authored at (Default unless Auto-Key).
void SetTimeCodes(pxr::UsdTimeCode displayTime, pxr::UsdTimeCode editTime);
void Render();
private:
@@ -33,6 +39,10 @@ private:
void WriteRotate();
void WriteScale();
/// Snapshot TRS values + keyframe presence when a DragFloat gains focus;
/// both feed the undo command built on deactivation.
void CaptureEditStart();
/// When XformCommonAPI is not applicable (e.g. referenced prim with
/// xformOp:transform), author standard common-API ops in the current edit
/// layer so that subsequent XformCommonAPI writes succeed.
@@ -67,6 +77,11 @@ private:
pxr::GfVec3f m_editStartTranslate{ 0.f, 0.f, 0.f };
pxr::GfVec3f m_editStartRotate { 0.f, 0.f, 0.f };
pxr::GfVec3f m_editStartScale { 1.f, 1.f, 1.f };
TransformCommand::SamplePresence m_editStartSamples;
// Timeline time codes (synced by Application)
pxr::UsdTimeCode m_displayTime = pxr::UsdTimeCode::Default();
pxr::UsdTimeCode m_editTime = pxr::UsdTimeCode::Default();
std::string m_primType;
};
+132
View File
@@ -0,0 +1,132 @@
#include "TimelinePanel.h"
#include <pxr/usd/usd/editContext.h>
#include <imgui.h>
#include <algorithm>
#include <cmath>
namespace UsdLayerManager {
// ---------------------------------------------------------------------------
void TimelinePanel::SetStage(pxr::UsdStageRefPtr stage)
{
m_stage = stage;
m_playing = false;
m_startFrame = 1.0;
m_endFrame = 100.0;
m_fps = 24.0;
if (stage) {
if (stage->HasAuthoredTimeCodeRange()) {
m_startFrame = stage->GetStartTimeCode();
m_endFrame = stage->GetEndTimeCode();
}
double tps = stage->GetTimeCodesPerSecond();
if (tps > 0.0) m_fps = tps;
}
if (m_endFrame < m_startFrame) m_endFrame = m_startFrame;
// Always notify on stage change so consumers pick up the new range,
// even when the numeric frame happens to be unchanged.
m_currentFrame = m_startFrame;
NotifyTimeChanged();
}
// ---------------------------------------------------------------------------
void TimelinePanel::Update(float deltaTime)
{
if (!m_playing) return;
double next = m_currentFrame + double(deltaTime) * m_fps;
if (next > m_endFrame) {
double span = m_endFrame - m_startFrame;
next = (span > 0.0)
? m_startFrame + std::fmod(next - m_startFrame, span)
: m_startFrame;
}
SetCurrentFrame(next);
}
// ---------------------------------------------------------------------------
void TimelinePanel::Render()
{
// ---- Transport row: icon buttons | start/end range | fps | auto-key ----
auto iconBtn = [&](const char* id, Icon icon, const char* fallback) -> bool {
if (m_iconManager) {
ImTextureID tex = m_iconManager->Get(icon);
return ImGui::ImageButton(id, ImTextureRef(tex), ImVec2(18.f, 18.f));
}
return ImGui::Button(fallback);
};
if (iconBtn("##skipback", Icon::SkipBack, "|<")) { m_playing = false; SetCurrentFrame(m_startFrame); }
ImGui::SameLine();
if (iconBtn("##stepback", Icon::StepBack, "<")) { m_playing = false; SetCurrentFrame(std::floor(m_currentFrame) - 1.0); }
ImGui::SameLine();
if (iconBtn("##playpause", m_playing ? Icon::Pause : Icon::Play, m_playing ? "Pause" : "Play")) m_playing = !m_playing;
ImGui::SameLine();
if (iconBtn("##stepfwd", Icon::StepForward, ">")) { m_playing = false; SetCurrentFrame(std::floor(m_currentFrame) + 1.0); }
ImGui::SameLine();
if (iconBtn("##skipend", Icon::SkipEnd, ">|")) { m_playing = false; SetCurrentFrame(m_endFrame); }
ImGui::SameLine();
int start = int(std::lround(m_startFrame));
int end = int(std::lround(m_endFrame));
ImGui::SetNextItemWidth(90.f);
bool rangeEdited = ImGui::DragInt("##start", &start, 1.0f, 0, 0, "Start %d");
ImGui::SameLine();
ImGui::SetNextItemWidth(90.f);
rangeEdited |= ImGui::DragInt("##end", &end, 1.0f, 0, 0, "End %d");
if (rangeEdited) {
if (end < start) end = start;
m_startFrame = double(start);
m_endFrame = double(end);
if (m_stage) {
// Stage time metadata must live on the root (or session) layer;
// the active edit target may be a sublayer.
pxr::UsdEditContext ec(m_stage, m_stage->GetRootLayer());
m_stage->SetStartTimeCode(m_startFrame);
m_stage->SetEndTimeCode(m_endFrame);
}
SetCurrentFrame(m_currentFrame); // re-clamp into the new range
}
ImGui::SameLine();
ImGui::Text("%.6g fps", m_fps);
ImGui::SameLine();
if (ImGui::Checkbox("Auto-Key", &m_autoKey)) NotifyTimeChanged();
// ---- Frame slider: full width, snaps to whole frames ----
float frame = float(m_currentFrame);
ImGui::SetNextItemWidth(-FLT_MIN);
if (ImGui::SliderFloat("##frame", &frame,
float(m_startFrame), float(m_endFrame), "%.0f")) {
m_playing = false;
SetCurrentFrame(std::round(double(frame)));
}
}
// ---------------------------------------------------------------------------
void TimelinePanel::SetCurrentFrame(double frame)
{
frame = std::min(std::max(frame, m_startFrame), m_endFrame);
if (frame != m_currentFrame) {
m_currentFrame = frame;
NotifyTimeChanged();
}
}
void TimelinePanel::NotifyTimeChanged()
{
if (OnTimeChanged)
OnTimeChanged(pxr::UsdTimeCode(m_currentFrame), EditTime());
}
pxr::UsdTimeCode TimelinePanel::EditTime() const
{
return m_autoKey ? pxr::UsdTimeCode(m_currentFrame)
: pxr::UsdTimeCode::Default();
}
} // namespace UsdLayerManager
+46
View File
@@ -0,0 +1,46 @@
#pragma once
#include <pxr/usd/usd/stage.h>
#include <pxr/usd/usd/timeCode.h>
#include "IconManager.h"
#include <functional>
namespace UsdLayerManager {
/// Bottom-docked playback timeline.
///
/// Owns the current frame, playback state, and the Auto-Key toggle, and fires
/// OnTimeChanged whenever either changes:
/// displayTime — the current frame; used for all reads/evaluation
/// editTime — the current frame when Auto-Key is ON, otherwise Default()
/// (writes at editTime author time samples = keyframes)
class TimelinePanel {
public:
void SetStage(pxr::UsdStageRefPtr stage);
void SetIconManager(IconManager* icons) { m_iconManager = icons; }
/// Advance playback by deltaTime seconds; called once per app frame.
void Update(float deltaTime);
void Render();
std::function<void(pxr::UsdTimeCode displayTime,
pxr::UsdTimeCode editTime)> OnTimeChanged;
private:
/// Clamp to [start, end]; fires OnTimeChanged when the frame changes.
void SetCurrentFrame(double frame);
void NotifyTimeChanged();
pxr::UsdTimeCode EditTime() const;
IconManager* m_iconManager = nullptr;
pxr::UsdStageRefPtr m_stage;
double m_startFrame = 1.0;
double m_endFrame = 100.0;
double m_fps = 24.0;
double m_currentFrame = 1.0;
bool m_playing = false;
bool m_autoKey = false;
};
} // namespace UsdLayerManager
+14 -9
View File
@@ -63,6 +63,7 @@ static constexpr float kCenterCircleRadius = 5.0f;
static bool SetupCompatibleXform(
const pxr::UsdStageRefPtr& stage,
const pxr::SdfPath& primPath,
pxr::UsdTimeCode time,
pxr::GfVec3d& outT,
pxr::GfVec3f& outR,
pxr::GfVec3f& outS,
@@ -75,7 +76,7 @@ static bool SetupCompatibleXform(
pxr::GfVec3f pivot3f;
if (api.GetXformVectors(&outT, &outR, &outS, &pivot3f,
&outRotOrder, pxr::UsdTimeCode::Default()))
&outRotOrder, time))
return true;
// Fallback -----------------------------------------------------------------
@@ -83,7 +84,7 @@ static bool SetupCompatibleXform(
" has an incompatible xform stack. Creating a compatible "
"override in the edit layer.");
pxr::UsdGeomXformCache xc(pxr::UsdTimeCode::Default());
pxr::UsdGeomXformCache xc(time);
bool reset = false;
pxr::GfMatrix4d localMat = xc.GetLocalTransformation(prim, &reset);
@@ -143,7 +144,7 @@ void TransformManipulator::GetGizmoAxes(pxr::GfVec3d axes[3]) const
if (!m_stage || m_primPath.IsEmpty()) return;
pxr::UsdPrim prim = m_stage->GetPrimAtPath(m_primPath);
if (!prim) return;
pxr::UsdGeomXformCache xc(pxr::UsdTimeCode::Default());
pxr::UsdGeomXformCache xc(m_displayTime);
pxr::GfMatrix4d l2w = xc.GetLocalToWorldTransform(prim);
for (int i = 0; i < 3; ++i) {
pxr::GfVec3d row(l2w[i][0], l2w[i][1], l2w[i][2]);
@@ -454,13 +455,17 @@ bool TransformManipulator::HandleInput(const pxr::GfMatrix4d& vp,
pxr::GfVec3d trans; pxr::GfVec3f rot,scale;
pxr::UsdGeomXformCommonAPI::RotationOrder rotOrder;
if (!SetupCompatibleXform(m_stage,m_primPath,trans,rot,scale,rotOrder)) {
if (!SetupCompatibleXform(m_stage,m_primPath,m_displayTime,
trans,rot,scale,rotOrder)) {
m_isDragging=false; return false;
}
m_dragOriginalTranslate=trans; m_dragOriginalRotate=rot;
m_dragOriginalScale=scale; m_dragOriginalRotOrder=rotOrder;
m_dragStartTranslate=trans; m_dragStartRotate=rot;
m_dragStartScale=scale;
// Snapshot keyframe presence before live writes author samples.
m_dragPreEditSamples=TransformCommand::QuerySamplePresence(
m_stage,m_primPath,rotOrder,m_editTime);
pxr::GfRay ray=ComputeMouseRay(vp,mouse,imgPos,vW,vH);
@@ -546,7 +551,7 @@ bool TransformManipulator::HandleInput(const pxr::GfMatrix4d& vp,
pxr::GfVec3d parentDelta=worldDelta;
pxr::UsdPrim prim=m_stage->GetPrimAtPath(m_primPath);
if (prim) {
pxr::UsdGeomXformCache xc(pxr::UsdTimeCode::Default());
pxr::UsdGeomXformCache xc(m_displayTime);
pxr::UsdPrim parent=prim.GetParent();
if (parent && !parent.IsPseudoRoot()) {
pxr::GfMatrix4d p2w=xc.GetLocalToWorldTransform(parent);
@@ -643,7 +648,7 @@ bool TransformManipulator::HandleInput(const pxr::GfMatrix4d& vp,
m_stage->GetEditTarget().GetLayer(),
m_dragOriginalTranslate,m_dragOriginalRotate,m_dragOriginalScale,
m_dragStartTranslate, m_dragStartRotate, m_dragStartScale,
m_dragOriginalRotOrder,
m_dragOriginalRotOrder, m_editTime, m_dragPreEditSamples,
"Transform "+m_primPath.GetName());
m_commandHistory->Push(std::move(cmd));
}
@@ -661,7 +666,7 @@ void TransformManipulator::ApplyTranslate(const pxr::GfVec3d& t)
if (!m_stage||m_primPath.IsEmpty()) return;
pxr::UsdPrim p=m_stage->GetPrimAtPath(m_primPath); if (!p) return;
pxr::UsdEditContext ec(m_stage,m_stage->GetEditTarget());
pxr::UsdGeomXformCommonAPI(p).SetTranslate(t,pxr::UsdTimeCode::Default());
pxr::UsdGeomXformCommonAPI(p).SetTranslate(t,m_editTime);
m_dragStartTranslate=t;
}
@@ -671,7 +676,7 @@ void TransformManipulator::ApplyRotate(const pxr::GfVec3f& r,
if (!m_stage||m_primPath.IsEmpty()) return;
pxr::UsdPrim p=m_stage->GetPrimAtPath(m_primPath); if (!p) return;
pxr::UsdEditContext ec(m_stage,m_stage->GetEditTarget());
pxr::UsdGeomXformCommonAPI(p).SetRotate(r,ro,pxr::UsdTimeCode::Default());
pxr::UsdGeomXformCommonAPI(p).SetRotate(r,ro,m_editTime);
m_dragStartRotate=r;
}
@@ -680,7 +685,7 @@ void TransformManipulator::ApplyScale(const pxr::GfVec3f& s)
if (!m_stage||m_primPath.IsEmpty()) return;
pxr::UsdPrim p=m_stage->GetPrimAtPath(m_primPath); if (!p) return;
pxr::UsdEditContext ec(m_stage,m_stage->GetEditTarget());
pxr::UsdGeomXformCommonAPI(p).SetScale(s,pxr::UsdTimeCode::Default());
pxr::UsdGeomXformCommonAPI(p).SetScale(s,m_editTime);
m_dragStartScale=s;
}
+17
View File
@@ -3,6 +3,7 @@
#include <imgui.h>
#include <pxr/usd/usd/stage.h>
#include <pxr/usd/usd/timeCode.h>
#include <pxr/usd/sdf/path.h>
#include <pxr/base/gf/vec3d.h>
#include <pxr/base/gf/vec3f.h>
@@ -12,6 +13,8 @@
#include <pxr/base/gf/ray.h>
#include <pxr/usd/usdGeom/xformCommonAPI.h>
#include "../core/commands/TransformCommand.h"
namespace UsdLayerManager {
class CommandHistory;
@@ -33,6 +36,13 @@ public:
void SetSelectedPrim(const pxr::SdfPath& path);
void SetCommandHistory(CommandHistory* h) { m_commandHistory = h; }
/// displayTime — frame used to read/evaluate transforms (timeline frame).
/// editTime — frame writes are authored at (Default unless Auto-Key).
void SetTimeCodes(pxr::UsdTimeCode displayTime, pxr::UsdTimeCode editTime) {
m_displayTime = displayTime;
m_editTime = editTime;
}
void Render(ImDrawList* dl, const pxr::GfMatrix4d& viewProj,
const pxr::GfVec3d& pivot, const pxr::GfVec3d& cameraEye,
const ImVec2& imagePos, int viewW, int viewH);
@@ -83,6 +93,8 @@ private:
pxr::UsdStageRefPtr m_stage;
pxr::SdfPath m_primPath;
CommandHistory* m_commandHistory = nullptr;
pxr::UsdTimeCode m_displayTime = pxr::UsdTimeCode::Default();
pxr::UsdTimeCode m_editTime = pxr::UsdTimeCode::Default();
bool m_isDragging = false;
int m_dragAxis = -1;
@@ -127,6 +139,11 @@ private:
pxr::GfVec3d m_dragStartTranslate = {0,0,0};
pxr::GfVec3f m_dragStartRotate = {0,0,0};
pxr::GfVec3f m_dragStartScale = {1,1,1};
// Sample presence at m_editTime, captured at drag start (before the
// first live write) so the end-of-drag TransformCommand can undo
// keyframe creation correctly.
TransformCommand::SamplePresence m_dragPreEditSamples;
};
} // namespace UsdLayerManager
+10
View File
@@ -28,6 +28,7 @@ void ViewportPanel::EnsureTileCount(int count)
if (m_stage) m_tiles.back()->SetStage(m_stage);
if (m_iconManager) m_tiles.back()->SetIconManager(m_iconManager);
if (m_commandHistory) m_tiles.back()->SetCommandHistory(m_commandHistory);
m_tiles.back()->SetTimeCodes(m_displayTime, m_editTime);
m_tiles.back()->SetSelectedPaths(m_selectedSdfPaths, m_selectedPrimPath);
WireCallbacks(idx);
}
@@ -116,6 +117,15 @@ void ViewportPanel::SetIconManager(IconManager* icons)
for (auto& t : m_tiles) t->SetIconManager(icons);
}
void ViewportPanel::SetTimeCodes(pxr::UsdTimeCode displayTime,
pxr::UsdTimeCode editTime)
{
m_displayTime = displayTime;
m_editTime = editTime;
for (auto& t : m_tiles) t->SetTimeCodes(displayTime, editTime);
m_manipulator.SetTimeCodes(displayTime, editTime);
}
void ViewportPanel::SetSelectedPrimPath(const std::string& path)
{
m_selectedPrimPath = path;
+7
View File
@@ -40,6 +40,11 @@ public:
void SetCommandHistory(CommandHistory* history);
void SetIconManager(IconManager* icons);
/// Push timeline time codes to every tile and the shared manipulator.
/// displayTime — frame used for all evaluation (render, cameras, bboxes).
/// editTime — frame writes are authored at (Default unless Auto-Key).
void SetTimeCodes(pxr::UsdTimeCode displayTime, pxr::UsdTimeCode editTime);
// ── Selection (called by SceneHierarchyPanel) ────────────────────────────
/// Set a single selected prim (clears any multi-selection).
void SetSelectedPrimPath(const std::string& path);
@@ -124,6 +129,8 @@ private:
pxr::UsdStageRefPtr m_stage;
IconManager* m_iconManager = nullptr;
CommandHistory* m_commandHistory = nullptr;
pxr::UsdTimeCode m_displayTime = pxr::UsdTimeCode::Default();
pxr::UsdTimeCode m_editTime = pxr::UsdTimeCode::Default();
};
} // namespace UsdLayerManager
+20 -11
View File
@@ -32,6 +32,15 @@ void ViewportTile::SetCommandHistory(CommandHistory* h)
// (ViewportPanel), not by individual tiles.
}
void ViewportTile::SetTimeCodes(pxr::UsdTimeCode displayTime,
pxr::UsdTimeCode editTime)
{
m_displayTime = displayTime;
m_editTime = editTime;
m_renderer.SetCurrentTimeCode(displayTime);
m_renderer.SetForceRefresh(true);
}
void ViewportTile::SetStage(pxr::UsdStageRefPtr stage)
{
m_stage = stage;
@@ -48,7 +57,7 @@ void ViewportTile::SetStage(pxr::UsdStageRefPtr stage)
if (stage) {
pxr::TfTokenVector purposes = {
pxr::UsdGeomTokens->default_, pxr::UsdGeomTokens->proxy };
pxr::UsdGeomBBoxCache bboxCache(pxr::UsdTimeCode::Default(), purposes, true);
pxr::UsdGeomBBoxCache bboxCache(m_displayTime, purposes, true);
pxr::GfBBox3d stageBBox = bboxCache.ComputeWorldBound(stage->GetPseudoRoot());
if (!stageBBox.GetRange().IsEmpty())
m_camera.FrameSelection(stageBBox, 1.1);
@@ -70,7 +79,7 @@ void ViewportTile::FrameScene()
if (!m_stage) return;
pxr::TfTokenVector purposes = {
pxr::UsdGeomTokens->default_, pxr::UsdGeomTokens->proxy };
pxr::UsdGeomBBoxCache bboxCache(pxr::UsdTimeCode::Default(), purposes, true);
pxr::UsdGeomBBoxCache bboxCache(m_displayTime, purposes, true);
pxr::GfBBox3d stageBBox = bboxCache.ComputeWorldBound(m_stage->GetPseudoRoot());
if (!stageBBox.GetRange().IsEmpty()) {
InitCameraNavigation();
@@ -109,7 +118,7 @@ void ViewportTile::InitCameraNavigation()
pxr::UsdPrim prim = m_stage->GetPrimAtPath(camPath);
if (!prim || !prim.IsA<pxr::UsdGeomCamera>()) { TrySwitchToFreeCamera(); return; }
pxr::UsdGeomCamera usdCam(prim);
pxr::GfCamera gfCam = usdCam.GetCamera(pxr::UsdTimeCode::Default());
pxr::GfCamera gfCam = usdCam.GetCamera(m_displayTime);
m_camera.SwitchToFreeCamera(&gfCam);
m_isDrivingUsdCamPrim = true;
m_drivenUsdCamPath = camPath;
@@ -124,13 +133,13 @@ pxr::GfVec3d ViewportTile::ComputeGizmoPivot() const
pxr::TfTokenVector purposes = {
pxr::UsdGeomTokens->default_, pxr::UsdGeomTokens->proxy };
pxr::UsdGeomBBoxCache bboxCache(
pxr::UsdTimeCode::Default(), purposes, true);
m_displayTime, purposes, true);
pxr::GfBBox3d bbox = bboxCache.ComputeWorldBound(prim);
pxr::GfRange3d range = bbox.ComputeAlignedRange();
if (!range.IsEmpty())
return (range.GetMin() + range.GetMax()) * 0.5;
pxr::UsdGeomXformCache xformCache(pxr::UsdTimeCode::Default());
pxr::UsdGeomXformCache xformCache(m_displayTime);
pxr::GfMatrix4d worldXform = xformCache.GetLocalToWorldTransform(prim);
return worldXform.ExtractTranslation();
}
@@ -261,7 +270,7 @@ pxr::GfCamera ViewportTile::ResolveCamera()
pxr::GfVec3d t; pxr::GfVec3f r, s, pv;
pxr::UsdGeomXformCommonAPI::RotationOrder ro;
if (!xformAPI.GetXformVectors(
&t, &r, &s, &pv, &ro, pxr::UsdTimeCode::Default()))
&t, &r, &s, &pv, &ro, m_displayTime))
pxr::UsdGeomXformable(prim).ClearXformOpOrder();
}
pxr::GfMatrix4d camToWorld = gfCamera.GetTransform();
@@ -271,13 +280,13 @@ pxr::GfCamera ViewportTile::ResolveCamera()
pxr::GfVec3d::XAxis(),
pxr::GfVec3d::YAxis(),
pxr::GfVec3d::ZAxis());
xformAPI.SetTranslate(translate, pxr::UsdTimeCode::Default());
xformAPI.SetTranslate(translate, m_editTime);
xformAPI.SetRotate(
pxr::GfVec3f(float(eulerDeg[0]),
float(eulerDeg[1]),
float(eulerDeg[2])),
pxr::UsdGeomXformCommonAPI::RotationOrderXYZ,
pxr::UsdTimeCode::Default());
m_editTime);
}
if (!m_isOrbiting && !m_isPanning && !m_isDollying) {
m_isDrivingUsdCamPrim = false;
@@ -292,7 +301,7 @@ pxr::GfCamera ViewportTile::ResolveCamera()
pxr::UsdPrim camPrim = m_stage->GetPrimAtPath(camPath);
if (camPrim && camPrim.IsA<pxr::UsdGeomCamera>()) {
pxr::UsdGeomCamera usdCam(camPrim);
gfCamera = usdCam.GetCamera(pxr::UsdTimeCode::Default());
gfCamera = usdCam.GetCamera(m_displayTime);
pxr::CameraUtilConformWindow(
&gfCamera, pxr::CameraUtilMatchVertically, aspect);
m_lastComputedGfCamera = gfCamera;
@@ -576,7 +585,7 @@ void ViewportTile::HandleInput(bool isFocused, TransformManipulator& manipulator
pxr::TfTokenVector purposes = {
pxr::UsdGeomTokens->default_, pxr::UsdGeomTokens->proxy };
pxr::UsdGeomBBoxCache bboxCache(
pxr::UsdTimeCode::Default(), purposes, true);
m_displayTime, purposes, true);
pxr::GfRange3d combined;
for (const auto& path : m_selectedSdfPaths) {
pxr::UsdPrim prim = m_stage->GetPrimAtPath(path);
@@ -585,7 +594,7 @@ void ViewportTile::HandleInput(bool isFocused, TransformManipulator& manipulator
combined.UnionWith(bbox.ComputeAlignedRange());
}
if (combined.IsEmpty()) {
pxr::UsdGeomXformCache xformCache(pxr::UsdTimeCode::Default());
pxr::UsdGeomXformCache xformCache(m_displayTime);
for (const auto& path : m_selectedSdfPaths) {
pxr::UsdPrim prim = m_stage->GetPrimAtPath(path);
if (!prim) continue;
+8
View File
@@ -47,6 +47,10 @@ public:
void SetIconManager(IconManager* icons) { m_iconManager = icons; }
void SetCommandHistory(CommandHistory* h);
/// displayTime — frame used for all evaluation (render, cameras, bboxes).
/// editTime — frame writes are authored at (Default unless Auto-Key).
void SetTimeCodes(pxr::UsdTimeCode displayTime, pxr::UsdTimeCode editTime);
// ── Selection sync ───────────────────────────────────────────────────────
/// Called by the container to broadcast the authoritative selection.
/// Updates the local shadow copy and pushes it into the renderer highlight.
@@ -107,6 +111,10 @@ private:
IconManager* m_iconManager = nullptr;
CommandHistory* m_commandHistory = nullptr;
// ── Timeline time codes (synced by container) ────────────────────────────
pxr::UsdTimeCode m_displayTime = pxr::UsdTimeCode::Default();
pxr::UsdTimeCode m_editTime = pxr::UsdTimeCode::Default();
// ── Stage + camera list ──────────────────────────────────────────────────
pxr::UsdStageRefPtr m_stage;
std::vector<pxr::SdfPath> m_cameraPaths;