Scene hierarchy: group composition arcs into Reference/Payload submenus + add Payload ops

- Prim context menu: consolidate Add/Replace/Remove Reference under a single Reference▶ submenu
- Prim context menu: add Payload▶ submenu with Add.../Remove▶/Clear All
- Stage blank-area context menu: same grouping (Reference▶ and Payload▶)
- Include pxr/usd/usd/payloads.h and pxr/usd/sdf/payload.h; use SdfPayloadEditorProxy + UsdPrimCompositionQuery::ArcTypeFilter::Payload for per-payload removal

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-18 04:37:16 +08:00
parent f56f4674cc
commit 3ba00761c1
+121 -27
View File
@@ -8,7 +8,9 @@
#include <pxr/usd/usd/prim.h> #include <pxr/usd/usd/prim.h>
#include <pxr/usd/usd/primRange.h> #include <pxr/usd/usd/primRange.h>
#include <pxr/usd/usd/references.h> #include <pxr/usd/usd/references.h>
#include <pxr/usd/usd/payloads.h>
#include <pxr/usd/usd/primCompositionQuery.h> #include <pxr/usd/usd/primCompositionQuery.h>
#include <pxr/usd/sdf/payload.h>
#include <pxr/usd/usdGeom/imageable.h> #include <pxr/usd/usdGeom/imageable.h>
#include <pxr/usd/usdGeom/metrics.h> #include <pxr/usd/usdGeom/metrics.h>
#include <pxr/usd/usdGeom/tokens.h> #include <pxr/usd/usdGeom/tokens.h>
@@ -315,23 +317,21 @@ void SceneHierarchyPanel::Render() {
ImGui::Separator(); ImGui::Separator();
// ---- Add Reference ---- // ---- Reference submenu ----
if (ImGui::MenuItem("Add Reference...")) { if (ImGui::BeginMenu("Reference")) {
if (ImGui::MenuItem("Add...")) {
std::string filePath = FileDialog::OpenFile( std::string filePath = FileDialog::OpenFile(
"USD Files (*.usd;*.usda;*.usdc;*.usdz)\0*.usd;*.usda;*.usdc;*.usdz\0All Files (*.*)\0*.*\0", "USD Files (*.usd;*.usda;*.usdc;*.usdz)\0*.usd;*.usda;*.usdc;*.usdz\0All Files (*.*)\0*.*\0",
"Add Reference File"); "Add Reference File");
if (!filePath.empty()) { if (!filePath.empty()) {
// Derive a valid USD prim name from the file's stem.
std::string stem = std::filesystem::path(filePath).stem().string(); std::string stem = std::filesystem::path(filePath).stem().string();
std::string xformName = SanitizeUsdName(stem); std::string xformName = SanitizeUsdName(stem);
if (xformName.empty()) xformName = "Reference"; if (xformName.empty()) xformName = "Reference";
// Avoid name collision: append _N if the path already exists.
std::string finalName = xformName; std::string finalName = xformName;
int suffix = 1; int suffix = 1;
while (m_stage->GetPrimAtPath(SdfPath("/" + finalName)).IsValid()) { while (m_stage->GetPrimAtPath(SdfPath("/" + finalName)).IsValid())
finalName = xformName + "_" + std::to_string(suffix++); finalName = xformName + "_" + std::to_string(suffix++);
}
SdfPath xformPath("/" + finalName); SdfPath xformPath("/" + finalName);
if (m_commandHistory) { if (m_commandHistory) {
@@ -342,11 +342,10 @@ void SceneHierarchyPanel::Render() {
UsdPrim xformPrim = m_stage->DefinePrim(xformPath, TfToken("Xform")); UsdPrim xformPrim = m_stage->DefinePrim(xformPath, TfToken("Xform"));
if (xformPrim.IsValid()) { if (xformPrim.IsValid()) {
bool ok = xformPrim.GetReferences().AddReference(filePath); bool ok = xformPrim.GetReferences().AddReference(filePath);
if (ok) { if (ok)
LOG_INFO("Added reference '" + filePath + "' under prim: " + xformPath.GetString()); LOG_INFO("Added reference '" + filePath + "' under prim: " + xformPath.GetString());
} else { else
LOG_ERROR("Failed to add reference '" + filePath + "' to: " + xformPath.GetString()); LOG_ERROR("Failed to add reference '" + filePath + "' to: " + xformPath.GetString());
}
} else { } else {
LOG_ERROR("Failed to define Xform prim: " + xformPath.GetString()); LOG_ERROR("Failed to define Xform prim: " + xformPath.GetString());
} }
@@ -356,6 +355,44 @@ void SceneHierarchyPanel::Render() {
} }
} }
} }
ImGui::EndMenu();
}
// ---- Payload submenu ----
if (ImGui::BeginMenu("Payload")) {
if (ImGui::MenuItem("Add...")) {
std::string filePath = FileDialog::OpenFile(
"USD Files (*.usd;*.usda;*.usdc;*.usdz)\0*.usd;*.usda;*.usdc;*.usdz\0All Files (*.*)\0*.*\0",
"Add Payload File");
if (!filePath.empty()) {
std::string stem = std::filesystem::path(filePath).stem().string();
std::string xformName = SanitizeUsdName(stem);
if (xformName.empty()) xformName = "Payload";
std::string finalName = xformName;
int suffix = 1;
while (m_stage->GetPrimAtPath(SdfPath("/" + finalName)).IsValid())
finalName = xformName + "_" + std::to_string(suffix++);
SdfPath xformPath("/" + finalName);
try {
UsdPrim xformPrim = m_stage->DefinePrim(xformPath, TfToken("Xform"));
if (xformPrim.IsValid()) {
bool ok = xformPrim.GetPayloads().AddPayload(filePath);
if (ok)
LOG_INFO("Added payload '" + filePath + "' under prim: " + xformPath.GetString());
else
LOG_ERROR("Failed to add payload '" + filePath + "' to: " + xformPath.GetString());
} else {
LOG_ERROR("Failed to define Xform prim: " + xformPath.GetString());
}
} catch (const std::exception& e) {
LOG_ERROR(std::string("Add payload error: ") + e.what());
}
}
}
ImGui::EndMenu();
}
ImGui::EndPopup(); ImGui::EndPopup();
} }
@@ -620,29 +657,30 @@ void SceneHierarchyPanel::RenderContextMenu(const UsdPrim& prim) {
ImGui::Separator(); ImGui::Separator();
// ---- Reference operations ---- bool hasRefs = prim.HasAuthoredReferences();
if (ImGui::MenuItem("Add Reference...")) { bool hasPayloads = prim.HasAuthoredPayloads();
// ---- Reference submenu ----
if (ImGui::BeginMenu("Reference")) {
if (ImGui::MenuItem("Add...")) {
std::string filePath = FileDialog::OpenFile( std::string filePath = FileDialog::OpenFile(
"USD Files (*.usd;*.usda;*.usdc;*.usdz)\0*.usd;*.usda;*.usdc;*.usdz\0All Files (*.*)\0*.*\0", "USD Files (*.usd;*.usda;*.usdc;*.usdz)\0*.usd;*.usda;*.usdc;*.usdz\0All Files (*.*)\0*.*\0",
"Add Reference File"); "Add Reference File");
if (!filePath.empty()) { if (!filePath.empty()) {
try { try {
bool ok = prim.GetReferences().AddReference(filePath); bool ok = prim.GetReferences().AddReference(filePath);
if (ok) { if (ok)
LOG_INFO("Added reference '" + filePath + "' to prim: " + prim.GetPath().GetString()); LOG_INFO("Added reference '" + filePath + "' to prim: " + prim.GetPath().GetString());
} else { else
LOG_ERROR("Failed to add reference '" + filePath + "' to: " + prim.GetPath().GetString()); LOG_ERROR("Failed to add reference '" + filePath + "' to: " + prim.GetPath().GetString());
}
} catch (const std::exception& e) { } catch (const std::exception& e) {
LOG_ERROR(std::string("Add reference error: ") + e.what()); LOG_ERROR(std::string("Add reference error: ") + e.what());
} }
} }
} }
ImGui::Separator();
bool hasRefs = prim.HasAuthoredReferences(); if (ImGui::BeginMenu("Replace", hasRefs)) {
// ---- Replace Reference ----
if (ImGui::BeginMenu("Replace Reference", hasRefs)) {
UsdPrimCompositionQuery::Filter replFilter; UsdPrimCompositionQuery::Filter replFilter;
replFilter.arcTypeFilter = UsdPrimCompositionQuery::ArcTypeFilter::Reference; replFilter.arcTypeFilter = UsdPrimCompositionQuery::ArcTypeFilter::Reference;
replFilter.dependencyTypeFilter = UsdPrimCompositionQuery::DependencyTypeFilter::Direct; replFilter.dependencyTypeFilter = UsdPrimCompositionQuery::DependencyTypeFilter::Direct;
@@ -657,7 +695,6 @@ void SceneHierarchyPanel::RenderContextMenu(const UsdPrim& prim) {
? "(internal reference)" ? "(internal reference)"
: oldRef.GetAssetPath(); : oldRef.GetAssetPath();
if (ImGui::MenuItem(label.c_str())) { if (ImGui::MenuItem(label.c_str())) {
// NOTE: file dialog is blocking — close popup first via deferred path.
m_pendingReplaceRef = oldRef; m_pendingReplaceRef = oldRef;
m_pendingReplaceRefPrim = prim.GetPath(); m_pendingReplaceRefPrim = prim.GetPath();
m_doReplaceRefPick = true; m_doReplaceRefPick = true;
@@ -665,15 +702,12 @@ void SceneHierarchyPanel::RenderContextMenu(const UsdPrim& prim) {
anyRepl = true; anyRepl = true;
} }
} }
if (!anyRepl) { if (!anyRepl)
ImGui::TextDisabled("(no direct references)"); ImGui::TextDisabled("(no direct references)");
}
ImGui::EndMenu(); ImGui::EndMenu();
} }
// ---- Remove Reference ---- if (ImGui::BeginMenu("Remove", hasRefs)) {
if (ImGui::BeginMenu("Remove Reference", hasRefs)) {
// Collect direct reference arcs via composition query.
UsdPrimCompositionQuery::Filter filter; UsdPrimCompositionQuery::Filter filter;
filter.arcTypeFilter = UsdPrimCompositionQuery::ArcTypeFilter::Reference; filter.arcTypeFilter = UsdPrimCompositionQuery::ArcTypeFilter::Reference;
filter.dependencyTypeFilter = UsdPrimCompositionQuery::DependencyTypeFilter::Direct; filter.dependencyTypeFilter = UsdPrimCompositionQuery::DependencyTypeFilter::Direct;
@@ -698,10 +732,8 @@ void SceneHierarchyPanel::RenderContextMenu(const UsdPrim& prim) {
anyListed = true; anyListed = true;
} }
} }
if (anyListed) ImGui::Separator(); if (anyListed) ImGui::Separator();
if (ImGui::MenuItem("Clear All")) {
if (ImGui::MenuItem("Clear All References")) {
try { try {
prim.GetReferences().ClearReferences(); prim.GetReferences().ClearReferences();
LOG_INFO("Cleared all references on: " + prim.GetPath().GetString()); LOG_INFO("Cleared all references on: " + prim.GetPath().GetString());
@@ -709,6 +741,68 @@ void SceneHierarchyPanel::RenderContextMenu(const UsdPrim& prim) {
LOG_ERROR(std::string("Clear references error: ") + e.what()); LOG_ERROR(std::string("Clear references error: ") + e.what());
} }
} }
ImGui::EndMenu();
}
ImGui::EndMenu();
}
// ---- Payload submenu ----
if (ImGui::BeginMenu("Payload")) {
if (ImGui::MenuItem("Add...")) {
std::string filePath = FileDialog::OpenFile(
"USD Files (*.usd;*.usda;*.usdc;*.usdz)\0*.usd;*.usda;*.usdc;*.usdz\0All Files (*.*)\0*.*\0",
"Add Payload File");
if (!filePath.empty()) {
try {
bool ok = prim.GetPayloads().AddPayload(filePath);
if (ok)
LOG_INFO("Added payload '" + filePath + "' to prim: " + prim.GetPath().GetString());
else
LOG_ERROR("Failed to add payload '" + filePath + "' to: " + prim.GetPath().GetString());
} catch (const std::exception& e) {
LOG_ERROR(std::string("Add payload error: ") + e.what());
}
}
}
ImGui::Separator();
if (ImGui::BeginMenu("Remove", hasPayloads)) {
UsdPrimCompositionQuery::Filter filter;
filter.arcTypeFilter = UsdPrimCompositionQuery::ArcTypeFilter::Payload;
filter.dependencyTypeFilter = UsdPrimCompositionQuery::DependencyTypeFilter::Direct;
UsdPrimCompositionQuery query(prim, filter);
bool anyListed = false;
for (auto& arc : query.GetCompositionArcs()) {
SdfPayloadEditorProxy editor;
SdfPayload payload;
if (arc.GetIntroducingListEditor(&editor, &payload)) {
std::string label = payload.GetAssetPath().empty()
? "(internal payload)"
: payload.GetAssetPath();
if (ImGui::MenuItem(label.c_str())) {
try {
prim.GetPayloads().RemovePayload(payload);
LOG_INFO("Removed payload '" + label + "' from: " + prim.GetPath().GetString());
} catch (const std::exception& e) {
LOG_ERROR(std::string("Remove payload error: ") + e.what());
}
}
anyListed = true;
}
}
if (anyListed) ImGui::Separator();
if (ImGui::MenuItem("Clear All")) {
try {
prim.GetPayloads().ClearPayloads();
LOG_INFO("Cleared all payloads on: " + prim.GetPath().GetString());
} catch (const std::exception& e) {
LOG_ERROR(std::string("Clear payloads error: ") + e.what());
}
}
ImGui::EndMenu();
}
ImGui::EndMenu(); ImGui::EndMenu();
} }