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
+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