From 3ba00761c1135c8fb36720f61fc7c4920f479310 Mon Sep 17 00:00:00 2001 From: indigo Date: Thu, 18 Jun 2026 04:37:16 +0800 Subject: [PATCH] Scene hierarchy: group composition arcs into Reference/Payload submenus + add Payload ops MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- src/ui/SceneHierarchyPanel.cpp | 288 ++++++++++++++++++++++----------- 1 file changed, 191 insertions(+), 97 deletions(-) diff --git a/src/ui/SceneHierarchyPanel.cpp b/src/ui/SceneHierarchyPanel.cpp index fa48a0d..22ce16d 100644 --- a/src/ui/SceneHierarchyPanel.cpp +++ b/src/ui/SceneHierarchyPanel.cpp @@ -8,7 +8,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -315,46 +317,81 @@ void SceneHierarchyPanel::Render() { ImGui::Separator(); - // ---- Add Reference ---- - if (ImGui::MenuItem("Add Reference...")) { - std::string filePath = FileDialog::OpenFile( - "USD Files (*.usd;*.usda;*.usdc;*.usdz)\0*.usd;*.usda;*.usdc;*.usdz\0All Files (*.*)\0*.*\0", - "Add Reference File"); - 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 xformName = SanitizeUsdName(stem); - if (xformName.empty()) xformName = "Reference"; + // ---- Reference submenu ---- + if (ImGui::BeginMenu("Reference")) { + 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 Reference File"); + if (!filePath.empty()) { + std::string stem = std::filesystem::path(filePath).stem().string(); + std::string xformName = SanitizeUsdName(stem); + if (xformName.empty()) xformName = "Reference"; - // Avoid name collision: append _N if the path already exists. - std::string finalName = xformName; - int suffix = 1; - while (m_stage->GetPrimAtPath(SdfPath("/" + finalName)).IsValid()) { - finalName = xformName + "_" + std::to_string(suffix++); + std::string finalName = xformName; + int suffix = 1; + while (m_stage->GetPrimAtPath(SdfPath("/" + finalName)).IsValid()) + finalName = xformName + "_" + std::to_string(suffix++); + + SdfPath xformPath("/" + finalName); + if (m_commandHistory) { + m_commandHistory->Push(std::make_unique( + 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); - if (m_commandHistory) { - m_commandHistory->Push(std::make_unique( - m_stage, xformPath, filePath)); - } else { + // ---- 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.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()); - } + 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 reference error: ") + e.what()); + LOG_ERROR(std::string("Add payload error: ") + e.what()); } } } + ImGui::EndMenu(); } ImGui::EndPopup(); @@ -620,94 +657,151 @@ void SceneHierarchyPanel::RenderContextMenu(const UsdPrim& prim) { ImGui::Separator(); - // ---- Reference operations ---- - if (ImGui::MenuItem("Add Reference...")) { - std::string filePath = FileDialog::OpenFile( - "USD Files (*.usd;*.usda;*.usdc;*.usdz)\0*.usd;*.usda;*.usdc;*.usdz\0All Files (*.*)\0*.*\0", - "Add Reference File"); - if (!filePath.empty()) { - try { - 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()); + bool hasRefs = prim.HasAuthoredReferences(); + bool hasPayloads = prim.HasAuthoredPayloads(); + + // ---- Reference submenu ---- + if (ImGui::BeginMenu("Reference")) { + 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 Reference File"); + if (!filePath.empty()) { + try { + 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 ---- - if (ImGui::BeginMenu("Replace Reference", hasRefs)) { - UsdPrimCompositionQuery::Filter replFilter; - replFilter.arcTypeFilter = UsdPrimCompositionQuery::ArcTypeFilter::Reference; - replFilter.dependencyTypeFilter = UsdPrimCompositionQuery::DependencyTypeFilter::Direct; - UsdPrimCompositionQuery replQuery(prim, replFilter); - - bool anyRepl = false; - for (auto& arc : replQuery.GetCompositionArcs()) { - SdfReferenceEditorProxy editor; - SdfReference oldRef; - if (arc.GetIntroducingListEditor(&editor, &oldRef)) { - std::string label = oldRef.GetAssetPath().empty() - ? "(internal reference)" - : 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; + bool anyRepl = false; + for (auto& arc : replQuery.GetCompositionArcs()) { + SdfReferenceEditorProxy editor; + SdfReference oldRef; + if (arc.GetIntroducingListEditor(&editor, &oldRef)) { + std::string label = oldRef.GetAssetPath().empty() + ? "(internal reference)" + : oldRef.GetAssetPath(); + if (ImGui::MenuItem(label.c_str())) { + m_pendingReplaceRef = oldRef; + m_pendingReplaceRefPrim = prim.GetPath(); + m_doReplaceRefPick = true; + } + anyRepl = 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(); } - // ---- Remove Reference ---- - if (ImGui::BeginMenu("Remove Reference", hasRefs)) { - // Collect direct reference arcs via composition query. - 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()); - } + // ---- 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()); } - 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")) { - 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()); + 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();