Timeline Prototype
This commit is contained in:
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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);
|
||||
|
||||
@@ -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) ---
|
||||
|
||||
@@ -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(×)) 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
|
||||
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user