Add Maya-style prim editing to SceneHierarchyPanel
- Inline rename: double-click prim label or F2 to edit name in-place; Enter commits, click-away or Esc cancels (undo-able) - Drag-and-drop reparent: drag any prim onto another to make it a child; invisible root drop zone below tree to reparent to stage root (undo-able) - Ctrl+G group: creates Xform group at common parent, reparents selection under it; filters descendant duplication; selects new group (undo-able) - Delete key shortcut: opens existing remove-prim modal when panel focused - Context menu: Rename (F2) and Group (Ctrl+G) items added - Three new undo-able commands using UsdNamespaceEditor (USD 25.05): RenamePrimCommand, ReparentPrimCommand, GroupPrimsCommand Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
#include "GroupPrimsCommand.h"
|
||||
#include "../../utils/Logger.h"
|
||||
#include <pxr/usd/usd/namespaceEditor.h>
|
||||
#include <pxr/usd/usd/prim.h>
|
||||
#include <pxr/base/tf/token.h>
|
||||
|
||||
PXR_NAMESPACE_USING_DIRECTIVE
|
||||
|
||||
namespace UsdLayerManager {
|
||||
|
||||
GroupPrimsCommand::GroupPrimsCommand(UsdStageRefPtr stage,
|
||||
const std::vector<SdfPath>& srcPaths,
|
||||
const SdfPath& groupPath)
|
||||
: m_stage(stage)
|
||||
, m_groupPath(groupPath)
|
||||
, m_srcPaths(srcPaths)
|
||||
, m_description("Group " + std::to_string(srcPaths.size()) +
|
||||
" prim(s) under " + groupPath.GetString())
|
||||
{
|
||||
}
|
||||
|
||||
/// Compute a unique child name under @p parent (collision check against live stage).
|
||||
static std::string UniqueNameUnder(UsdStageRefPtr stage,
|
||||
const SdfPath& parent,
|
||||
const std::string& baseName) {
|
||||
std::string name = baseName;
|
||||
int n = 1;
|
||||
while (stage->GetPrimAtPath(parent.AppendChild(TfToken(name))).IsValid())
|
||||
name = baseName + "_" + std::to_string(n++);
|
||||
return name;
|
||||
}
|
||||
|
||||
static UsdPrim GetParentPrim(UsdStageRefPtr stage, const SdfPath& parentPath) {
|
||||
if (parentPath == SdfPath("/") || parentPath == SdfPath::AbsoluteRootPath())
|
||||
return stage->GetPseudoRoot();
|
||||
return stage->GetPrimAtPath(parentPath);
|
||||
}
|
||||
|
||||
void GroupPrimsCommand::Execute() {
|
||||
// Create the Xform group.
|
||||
UsdPrim groupPrim = m_stage->DefinePrim(m_groupPath, TfToken("Xform"));
|
||||
if (!groupPrim.IsValid()) {
|
||||
LOG_ERROR("GroupPrimsCommand::Execute: failed to create group: " + m_groupPath.GetString());
|
||||
return;
|
||||
}
|
||||
|
||||
m_movedNames.clear();
|
||||
|
||||
for (const SdfPath& srcPath : m_srcPaths) {
|
||||
UsdPrim src = m_stage->GetPrimAtPath(srcPath);
|
||||
if (!src.IsValid()) {
|
||||
LOG_WARNING("GroupPrimsCommand::Execute: prim not found (skipping): " + srcPath.GetString());
|
||||
m_movedNames.emplace_back(srcPath.GetName());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Compute a collision-free name inside the group.
|
||||
std::string name = UniqueNameUnder(m_stage, m_groupPath, srcPath.GetName());
|
||||
m_movedNames.push_back(name);
|
||||
|
||||
UsdPrim gPrim = m_stage->GetPrimAtPath(m_groupPath);
|
||||
UsdNamespaceEditor editor(m_stage);
|
||||
editor.ReparentPrim(src, gPrim, TfToken(name));
|
||||
std::string whyNot;
|
||||
if (!editor.CanApplyEdits(&whyNot)) {
|
||||
LOG_ERROR("GroupPrimsCommand::Execute: cannot reparent " + srcPath.GetString() + ": " + whyNot);
|
||||
continue;
|
||||
}
|
||||
if (!editor.ApplyEdits()) {
|
||||
LOG_ERROR("GroupPrimsCommand::Execute: ApplyEdits failed for " + srcPath.GetString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GroupPrimsCommand::Undo() {
|
||||
if (m_movedNames.size() != m_srcPaths.size()) {
|
||||
LOG_ERROR("GroupPrimsCommand::Undo: state mismatch, cannot undo safely");
|
||||
return;
|
||||
}
|
||||
|
||||
// Reparent children back in reverse order.
|
||||
for (int i = (int)m_srcPaths.size() - 1; i >= 0; --i) {
|
||||
SdfPath movedPath = m_groupPath.AppendChild(TfToken(m_movedNames[i]));
|
||||
UsdPrim moved = m_stage->GetPrimAtPath(movedPath);
|
||||
if (!moved.IsValid()) {
|
||||
LOG_WARNING("GroupPrimsCommand::Undo: prim not in group (skipping): " + movedPath.GetString());
|
||||
continue;
|
||||
}
|
||||
|
||||
SdfPath origParentPath = m_srcPaths[i].GetParentPath();
|
||||
UsdPrim origParent = GetParentPrim(m_stage, origParentPath);
|
||||
if (!origParent.IsValid()) {
|
||||
LOG_ERROR("GroupPrimsCommand::Undo: original parent not found: " + origParentPath.GetString());
|
||||
continue;
|
||||
}
|
||||
|
||||
UsdNamespaceEditor editor(m_stage);
|
||||
editor.ReparentPrim(moved, origParent, TfToken(m_srcPaths[i].GetName()));
|
||||
std::string whyNot;
|
||||
if (!editor.CanApplyEdits(&whyNot)) {
|
||||
LOG_ERROR("GroupPrimsCommand::Undo: cannot reparent back " + movedPath.GetString() + ": " + whyNot);
|
||||
continue;
|
||||
}
|
||||
if (!editor.ApplyEdits()) {
|
||||
LOG_ERROR("GroupPrimsCommand::Undo: ApplyEdits failed for " + movedPath.GetString());
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the (now empty) group prim.
|
||||
m_stage->RemovePrim(m_groupPath);
|
||||
}
|
||||
|
||||
} // namespace UsdLayerManager
|
||||
Reference in New Issue
Block a user