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:
+121
-27
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user