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
+191 -97
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,46 +317,81 @@ void SceneHierarchyPanel::Render() {
ImGui::Separator(); ImGui::Separator();
// ---- Add Reference ---- // ---- Reference submenu ----
if (ImGui::MenuItem("Add Reference...")) { if (ImGui::BeginMenu("Reference")) {
std::string filePath = FileDialog::OpenFile( if (ImGui::MenuItem("Add...")) {
"USD Files (*.usd;*.usda;*.usdc;*.usdz)\0*.usd;*.usda;*.usdc;*.usdz\0All Files (*.*)\0*.*\0", std::string filePath = FileDialog::OpenFile(
"Add Reference File"); "USD Files (*.usd;*.usda;*.usdc;*.usdz)\0*.usd;*.usda;*.usdc;*.usdz\0All Files (*.*)\0*.*\0",
if (!filePath.empty()) { "Add Reference File");
// Derive a valid USD prim name from the file's stem. if (!filePath.empty()) {
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);
if (m_commandHistory) {
m_commandHistory->Push(std::make_unique<AddReferenceCommand>(
m_stage, xformPath, filePath));
} else {
try {
UsdPrim xformPrim = m_stage->DefinePrim(xformPath, TfToken("Xform"));
if (xformPrim.IsValid()) {
bool ok = xformPrim.GetReferences().AddReference(filePath);
if (ok)
LOG_INFO("Added reference '" + filePath + "' under prim: " + xformPath.GetString());
else
LOG_ERROR("Failed to add reference '" + filePath + "' to: " + xformPath.GetString());
} else {
LOG_ERROR("Failed to define Xform prim: " + xformPath.GetString());
}
} catch (const std::exception& e) {
LOG_ERROR(std::string("Add reference error: ") + e.what());
}
}
} }
}
ImGui::EndMenu();
}
SdfPath xformPath("/" + finalName); // ---- Payload submenu ----
if (m_commandHistory) { if (ImGui::BeginMenu("Payload")) {
m_commandHistory->Push(std::make_unique<AddReferenceCommand>( if (ImGui::MenuItem("Add...")) {
m_stage, xformPath, filePath)); std::string filePath = FileDialog::OpenFile(
} else { "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 { try {
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.GetPayloads().AddPayload(filePath);
if (ok) { if (ok)
LOG_INFO("Added reference '" + filePath + "' under prim: " + xformPath.GetString()); LOG_INFO("Added payload '" + filePath + "' under prim: " + xformPath.GetString());
} else { else
LOG_ERROR("Failed to add reference '" + filePath + "' to: " + xformPath.GetString()); LOG_ERROR("Failed to add payload '" + filePath + "' to: " + xformPath.GetString());
}
} else { } else {
LOG_ERROR("Failed to define Xform prim: " + xformPath.GetString()); LOG_ERROR("Failed to define Xform prim: " + xformPath.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 payload error: ") + e.what());
} }
} }
} }
ImGui::EndMenu();
} }
ImGui::EndPopup(); ImGui::EndPopup();
@@ -620,94 +657,151 @@ 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();
std::string filePath = FileDialog::OpenFile(
"USD Files (*.usd;*.usda;*.usdc;*.usdz)\0*.usd;*.usda;*.usdc;*.usdz\0All Files (*.*)\0*.*\0", // ---- Reference submenu ----
"Add Reference File"); if (ImGui::BeginMenu("Reference")) {
if (!filePath.empty()) { if (ImGui::MenuItem("Add...")) {
try { std::string filePath = FileDialog::OpenFile(
bool ok = prim.GetReferences().AddReference(filePath); "USD Files (*.usd;*.usda;*.usdc;*.usdz)\0*.usd;*.usda;*.usdc;*.usdz\0All Files (*.*)\0*.*\0",
if (ok) { "Add Reference File");
LOG_INFO("Added reference '" + filePath + "' to prim: " + prim.GetPath().GetString()); if (!filePath.empty()) {
} else { try {
LOG_ERROR("Failed to add reference '" + filePath + "' to: " + prim.GetPath().GetString()); bool ok = prim.GetReferences().AddReference(filePath);
if (ok)
LOG_INFO("Added reference '" + filePath + "' to prim: " + prim.GetPath().GetString());
else
LOG_ERROR("Failed to add reference '" + filePath + "' to: " + prim.GetPath().GetString());
} catch (const std::exception& e) {
LOG_ERROR(std::string("Add reference error: ") + e.what());
} }
} catch (const std::exception& e) {
LOG_ERROR(std::string("Add reference error: ") + e.what());
} }
} }
} ImGui::Separator();
bool hasRefs = prim.HasAuthoredReferences(); if (ImGui::BeginMenu("Replace", hasRefs)) {
UsdPrimCompositionQuery::Filter replFilter;
replFilter.arcTypeFilter = UsdPrimCompositionQuery::ArcTypeFilter::Reference;
replFilter.dependencyTypeFilter = UsdPrimCompositionQuery::DependencyTypeFilter::Direct;
UsdPrimCompositionQuery replQuery(prim, replFilter);
// ---- Replace Reference ---- bool anyRepl = false;
if (ImGui::BeginMenu("Replace Reference", hasRefs)) { for (auto& arc : replQuery.GetCompositionArcs()) {
UsdPrimCompositionQuery::Filter replFilter; SdfReferenceEditorProxy editor;
replFilter.arcTypeFilter = UsdPrimCompositionQuery::ArcTypeFilter::Reference; SdfReference oldRef;
replFilter.dependencyTypeFilter = UsdPrimCompositionQuery::DependencyTypeFilter::Direct; if (arc.GetIntroducingListEditor(&editor, &oldRef)) {
UsdPrimCompositionQuery replQuery(prim, replFilter); std::string label = oldRef.GetAssetPath().empty()
? "(internal reference)"
bool anyRepl = false; : oldRef.GetAssetPath();
for (auto& arc : replQuery.GetCompositionArcs()) { if (ImGui::MenuItem(label.c_str())) {
SdfReferenceEditorProxy editor; m_pendingReplaceRef = oldRef;
SdfReference oldRef; m_pendingReplaceRefPrim = prim.GetPath();
if (arc.GetIntroducingListEditor(&editor, &oldRef)) { m_doReplaceRefPick = true;
std::string label = oldRef.GetAssetPath().empty() }
? "(internal reference)" anyRepl = true;
: oldRef.GetAssetPath();
if (ImGui::MenuItem(label.c_str())) {
// NOTE: file dialog is blocking — close popup first via deferred path.
m_pendingReplaceRef = oldRef;
m_pendingReplaceRefPrim = prim.GetPath();
m_doReplaceRefPick = true;
} }
anyRepl = true;
} }
if (!anyRepl)
ImGui::TextDisabled("(no direct references)");
ImGui::EndMenu();
} }
if (!anyRepl) {
ImGui::TextDisabled("(no direct references)"); if (ImGui::BeginMenu("Remove", hasRefs)) {
UsdPrimCompositionQuery::Filter filter;
filter.arcTypeFilter = UsdPrimCompositionQuery::ArcTypeFilter::Reference;
filter.dependencyTypeFilter = UsdPrimCompositionQuery::DependencyTypeFilter::Direct;
UsdPrimCompositionQuery query(prim, filter);
bool anyListed = false;
for (auto& arc : query.GetCompositionArcs()) {
SdfReferenceEditorProxy editor;
SdfReference ref;
if (arc.GetIntroducingListEditor(&editor, &ref)) {
std::string label = ref.GetAssetPath().empty()
? "(internal reference)"
: ref.GetAssetPath();
if (ImGui::MenuItem(label.c_str())) {
try {
prim.GetReferences().RemoveReference(ref);
LOG_INFO("Removed reference '" + label + "' from: " + prim.GetPath().GetString());
} catch (const std::exception& e) {
LOG_ERROR(std::string("Remove reference error: ") + e.what());
}
}
anyListed = true;
}
}
if (anyListed) ImGui::Separator();
if (ImGui::MenuItem("Clear All")) {
try {
prim.GetReferences().ClearReferences();
LOG_INFO("Cleared all references on: " + prim.GetPath().GetString());
} catch (const std::exception& e) {
LOG_ERROR(std::string("Clear references error: ") + e.what());
}
}
ImGui::EndMenu();
} }
ImGui::EndMenu(); ImGui::EndMenu();
} }
// ---- Remove Reference ---- // ---- Payload submenu ----
if (ImGui::BeginMenu("Remove Reference", hasRefs)) { if (ImGui::BeginMenu("Payload")) {
// Collect direct reference arcs via composition query. if (ImGui::MenuItem("Add...")) {
UsdPrimCompositionQuery::Filter filter; std::string filePath = FileDialog::OpenFile(
filter.arcTypeFilter = UsdPrimCompositionQuery::ArcTypeFilter::Reference; "USD Files (*.usd;*.usda;*.usdc;*.usdz)\0*.usd;*.usda;*.usdc;*.usdz\0All Files (*.*)\0*.*\0",
filter.dependencyTypeFilter = UsdPrimCompositionQuery::DependencyTypeFilter::Direct; "Add Payload File");
UsdPrimCompositionQuery query(prim, filter); if (!filePath.empty()) {
try {
bool anyListed = false; bool ok = prim.GetPayloads().AddPayload(filePath);
for (auto& arc : query.GetCompositionArcs()) { if (ok)
SdfReferenceEditorProxy editor; LOG_INFO("Added payload '" + filePath + "' to prim: " + prim.GetPath().GetString());
SdfReference ref; else
if (arc.GetIntroducingListEditor(&editor, &ref)) { LOG_ERROR("Failed to add payload '" + filePath + "' to: " + prim.GetPath().GetString());
std::string label = ref.GetAssetPath().empty() } catch (const std::exception& e) {
? "(internal reference)" LOG_ERROR(std::string("Add payload error: ") + e.what());
: ref.GetAssetPath();
if (ImGui::MenuItem(label.c_str())) {
try {
prim.GetReferences().RemoveReference(ref);
LOG_INFO("Removed reference '" + label + "' from: " + prim.GetPath().GetString());
} catch (const std::exception& e) {
LOG_ERROR(std::string("Remove reference error: ") + e.what());
}
} }
anyListed = true;
} }
} }
ImGui::Separator();
if (anyListed) ImGui::Separator(); if (ImGui::BeginMenu("Remove", hasPayloads)) {
UsdPrimCompositionQuery::Filter filter;
filter.arcTypeFilter = UsdPrimCompositionQuery::ArcTypeFilter::Payload;
filter.dependencyTypeFilter = UsdPrimCompositionQuery::DependencyTypeFilter::Direct;
UsdPrimCompositionQuery query(prim, filter);
if (ImGui::MenuItem("Clear All References")) { bool anyListed = false;
try { for (auto& arc : query.GetCompositionArcs()) {
prim.GetReferences().ClearReferences(); SdfPayloadEditorProxy editor;
LOG_INFO("Cleared all references on: " + prim.GetPath().GetString()); SdfPayload payload;
} catch (const std::exception& e) { if (arc.GetIntroducingListEditor(&editor, &payload)) {
LOG_ERROR(std::string("Clear references error: ") + e.what()); 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();