Scene hierarchy: add Inherit, Specialize, and VariantSet composition arc menus
Prim right-click context menu now exposes all five USD composition arc types: - Inherit▶ Add... (modal for SdfPath input) | Remove▶ (lists via GetAllDirectInherits()) | Clear All - Specialize▶ Add... | Remove▶ (lists via UsdPrimCompositionQuery + ArcTypeFilter::Specialize) | Clear All - VariantSet▶ Add... | per-set sub-menu: Add Variant... + selectable variant list (SetVariantSelection) All text-input operations share a single RenderArcModal() driven by ArcModalMode enum. VariantSet/Variant names go through SanitizeUsdName(); Inherit/Specialize paths are passed as-is to SdfPath. OK button is disabled until input is non-empty; Enter key also confirms. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,9 @@
|
|||||||
#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/payloads.h>
|
||||||
|
#include <pxr/usd/usd/inherits.h>
|
||||||
|
#include <pxr/usd/usd/specializes.h>
|
||||||
|
#include <pxr/usd/usd/variantSets.h>
|
||||||
#include <pxr/usd/usd/primCompositionQuery.h>
|
#include <pxr/usd/usd/primCompositionQuery.h>
|
||||||
#include <pxr/usd/sdf/payload.h>
|
#include <pxr/usd/sdf/payload.h>
|
||||||
#include <pxr/usd/usdGeom/imageable.h>
|
#include <pxr/usd/usdGeom/imageable.h>
|
||||||
@@ -400,6 +403,9 @@ void SceneHierarchyPanel::Render() {
|
|||||||
// Deferred confirm modal for prim removal (must be opened outside any popup stack).
|
// Deferred confirm modal for prim removal (must be opened outside any popup stack).
|
||||||
RenderRemovePrimModal();
|
RenderRemovePrimModal();
|
||||||
|
|
||||||
|
// Deferred modal for arc operations (Inherit / Specialize / VariantSet / Variant).
|
||||||
|
RenderArcModal();
|
||||||
|
|
||||||
// Deferred file-dialog for reference replacement (must run outside any popup stack).
|
// Deferred file-dialog for reference replacement (must run outside any popup stack).
|
||||||
ProcessPendingReplaceRef();
|
ProcessPendingReplaceRef();
|
||||||
}
|
}
|
||||||
@@ -807,6 +813,136 @@ void SceneHierarchyPanel::RenderContextMenu(const UsdPrim& prim) {
|
|||||||
ImGui::EndMenu();
|
ImGui::EndMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Inherit submenu ----
|
||||||
|
bool hasInherits = prim.HasAuthoredInherits();
|
||||||
|
if (ImGui::BeginMenu("Inherit")) {
|
||||||
|
if (ImGui::MenuItem("Add...")) {
|
||||||
|
m_arcModalMode = ArcModalMode::Inherit;
|
||||||
|
m_arcModalTargetPrim = prim.GetPath();
|
||||||
|
m_arcModalBuf[0] = '\0';
|
||||||
|
m_openArcModal = true;
|
||||||
|
}
|
||||||
|
ImGui::Separator();
|
||||||
|
if (ImGui::BeginMenu("Remove", hasInherits)) {
|
||||||
|
SdfPathVector paths = prim.GetInherits().GetAllDirectInherits();
|
||||||
|
bool any = false;
|
||||||
|
for (const SdfPath& ipath : paths) {
|
||||||
|
if (ImGui::MenuItem(ipath.GetText())) {
|
||||||
|
try {
|
||||||
|
prim.GetInherits().RemoveInherit(ipath);
|
||||||
|
LOG_INFO("Removed inherit '" + ipath.GetString() + "' from: " + prim.GetPath().GetString());
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
LOG_ERROR(std::string("Remove inherit error: ") + e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
any = true;
|
||||||
|
}
|
||||||
|
if (any) ImGui::Separator();
|
||||||
|
if (ImGui::MenuItem("Clear All")) {
|
||||||
|
try {
|
||||||
|
prim.GetInherits().ClearInherits();
|
||||||
|
LOG_INFO("Cleared all inherits on: " + prim.GetPath().GetString());
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
LOG_ERROR(std::string("Clear inherits error: ") + e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Specialize submenu ----
|
||||||
|
bool hasSpecializes = prim.HasAuthoredSpecializes();
|
||||||
|
if (ImGui::BeginMenu("Specialize")) {
|
||||||
|
if (ImGui::MenuItem("Add...")) {
|
||||||
|
m_arcModalMode = ArcModalMode::Specialize;
|
||||||
|
m_arcModalTargetPrim = prim.GetPath();
|
||||||
|
m_arcModalBuf[0] = '\0';
|
||||||
|
m_openArcModal = true;
|
||||||
|
}
|
||||||
|
ImGui::Separator();
|
||||||
|
if (ImGui::BeginMenu("Remove", hasSpecializes)) {
|
||||||
|
UsdPrimCompositionQuery::Filter filter;
|
||||||
|
filter.arcTypeFilter = UsdPrimCompositionQuery::ArcTypeFilter::Specialize;
|
||||||
|
filter.dependencyTypeFilter = UsdPrimCompositionQuery::DependencyTypeFilter::Direct;
|
||||||
|
UsdPrimCompositionQuery query(prim, filter);
|
||||||
|
|
||||||
|
bool any = false;
|
||||||
|
for (auto& arc : query.GetCompositionArcs()) {
|
||||||
|
SdfPathEditorProxy editor;
|
||||||
|
SdfPath specPath;
|
||||||
|
if (arc.GetIntroducingListEditor(&editor, &specPath)) {
|
||||||
|
if (ImGui::MenuItem(specPath.GetText())) {
|
||||||
|
try {
|
||||||
|
prim.GetSpecializes().RemoveSpecialize(specPath);
|
||||||
|
LOG_INFO("Removed specialize '" + specPath.GetString() + "' from: " + prim.GetPath().GetString());
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
LOG_ERROR(std::string("Remove specialize error: ") + e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
any = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (any) ImGui::Separator();
|
||||||
|
if (ImGui::MenuItem("Clear All")) {
|
||||||
|
try {
|
||||||
|
prim.GetSpecializes().ClearSpecializes();
|
||||||
|
LOG_INFO("Cleared all specializes on: " + prim.GetPath().GetString());
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
LOG_ERROR(std::string("Clear specializes error: ") + e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- VariantSet submenu ----
|
||||||
|
if (ImGui::BeginMenu("VariantSet")) {
|
||||||
|
if (ImGui::MenuItem("Add...")) {
|
||||||
|
m_arcModalMode = ArcModalMode::VariantSet;
|
||||||
|
m_arcModalTargetPrim = prim.GetPath();
|
||||||
|
m_arcModalBuf[0] = '\0';
|
||||||
|
m_openArcModal = true;
|
||||||
|
}
|
||||||
|
bool hasVarSets = prim.HasVariantSets();
|
||||||
|
if (hasVarSets) {
|
||||||
|
ImGui::Separator();
|
||||||
|
std::vector<std::string> vsNames;
|
||||||
|
prim.GetVariantSets().GetNames(&vsNames);
|
||||||
|
for (const std::string& vsName : vsNames) {
|
||||||
|
if (ImGui::BeginMenu(vsName.c_str())) {
|
||||||
|
if (ImGui::MenuItem("Add Variant...")) {
|
||||||
|
m_arcModalMode = ArcModalMode::Variant;
|
||||||
|
m_arcModalTargetPrim = prim.GetPath();
|
||||||
|
m_arcModalVarSetName = vsName;
|
||||||
|
m_arcModalBuf[0] = '\0';
|
||||||
|
m_openArcModal = true;
|
||||||
|
}
|
||||||
|
UsdVariantSet vs = prim.GetVariantSet(vsName);
|
||||||
|
std::vector<std::string> varNames = vs.GetVariantNames();
|
||||||
|
if (!varNames.empty()) {
|
||||||
|
ImGui::Separator();
|
||||||
|
std::string currentSel = vs.GetVariantSelection();
|
||||||
|
for (const std::string& varName : varNames) {
|
||||||
|
bool isCurrent = (varName == currentSel);
|
||||||
|
if (ImGui::MenuItem(varName.c_str(), nullptr, isCurrent)) {
|
||||||
|
try {
|
||||||
|
vs.SetVariantSelection(varName);
|
||||||
|
LOG_INFO("Set variant '" + varName + "' on '" + vsName + "' for: " + prim.GetPath().GetString());
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
LOG_ERROR(std::string("Set variant selection error: ") + e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
// ---- Prim removal ----
|
// ---- Prim removal ----
|
||||||
// Only show "Remove Prim" for prims that have a local spec authored in the root
|
// Only show "Remove Prim" for prims that have a local spec authored in the root
|
||||||
// layer. Prims brought in purely via composition from an external referenced
|
// layer. Prims brought in purely via composition from an external referenced
|
||||||
@@ -909,6 +1045,149 @@ void SceneHierarchyPanel::RenderRemovePrimModal() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SceneHierarchyPanel::RenderArcModal() {
|
||||||
|
if (m_openArcModal) {
|
||||||
|
ImGui::OpenPopup("Arc Operation##arc");
|
||||||
|
m_openArcModal = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
|
||||||
|
ImGui::SetNextWindowPos(center, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
|
||||||
|
ImGui::SetNextWindowSize(ImVec2(420, 0), ImGuiCond_Always);
|
||||||
|
|
||||||
|
if (ImGui::BeginPopupModal("Arc Operation##arc", nullptr,
|
||||||
|
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove)) {
|
||||||
|
|
||||||
|
const char* title = "Arc Operation";
|
||||||
|
const char* prompt = "Path:";
|
||||||
|
bool isNameInput = false;
|
||||||
|
|
||||||
|
switch (m_arcModalMode) {
|
||||||
|
case ArcModalMode::Inherit:
|
||||||
|
title = "Add Inherit";
|
||||||
|
prompt = "Inherit path (e.g. /ClassName):";
|
||||||
|
break;
|
||||||
|
case ArcModalMode::Specialize:
|
||||||
|
title = "Add Specialize";
|
||||||
|
prompt = "Specialize path (e.g. /BasePrim):";
|
||||||
|
break;
|
||||||
|
case ArcModalMode::VariantSet:
|
||||||
|
title = "Add VariantSet";
|
||||||
|
prompt = "VariantSet name:";
|
||||||
|
isNameInput = true;
|
||||||
|
break;
|
||||||
|
case ArcModalMode::Variant:
|
||||||
|
title = "Add Variant";
|
||||||
|
prompt = "Variant name:";
|
||||||
|
isNameInput = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
ImGui::EndPopup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::TextUnformatted(title);
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::Spacing();
|
||||||
|
ImGui::TextDisabled("Prim: %s", m_arcModalTargetPrim.GetText());
|
||||||
|
if (m_arcModalMode == ArcModalMode::Variant)
|
||||||
|
ImGui::TextDisabled("VariantSet: %s", m_arcModalVarSetName.c_str());
|
||||||
|
ImGui::Spacing();
|
||||||
|
ImGui::TextUnformatted(prompt);
|
||||||
|
ImGui::SetNextItemWidth(-1.0f);
|
||||||
|
if (ImGui::IsWindowAppearing())
|
||||||
|
ImGui::SetKeyboardFocusHere();
|
||||||
|
bool confirm = ImGui::InputText("##arcbuf", m_arcModalBuf,
|
||||||
|
sizeof(m_arcModalBuf),
|
||||||
|
ImGuiInputTextFlags_EnterReturnsTrue);
|
||||||
|
ImGui::Spacing();
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
bool inputNonEmpty = (m_arcModalBuf[0] != '\0');
|
||||||
|
float buttonWidth = 120.0f;
|
||||||
|
float spacing = ImGui::GetStyle().ItemSpacing.x;
|
||||||
|
float totalW = buttonWidth * 2.0f + spacing;
|
||||||
|
ImGui::SetCursorPosX(
|
||||||
|
(ImGui::GetContentRegionAvail().x - totalW) * 0.5f + ImGui::GetCursorPosX());
|
||||||
|
|
||||||
|
// Capture Enter-confirm before BeginDisabled so it works regardless of button state.
|
||||||
|
bool doConfirm = (confirm && inputNonEmpty);
|
||||||
|
|
||||||
|
if (!inputNonEmpty) ImGui::BeginDisabled();
|
||||||
|
if (ImGui::Button("OK", ImVec2(buttonWidth, 0)))
|
||||||
|
doConfirm = inputNonEmpty;
|
||||||
|
if (!inputNonEmpty) ImGui::EndDisabled();
|
||||||
|
|
||||||
|
ImGui::SameLine();
|
||||||
|
|
||||||
|
if (ImGui::Button("Cancel", ImVec2(buttonWidth, 0))) {
|
||||||
|
m_arcModalMode = ArcModalMode::None;
|
||||||
|
m_arcModalTargetPrim = SdfPath();
|
||||||
|
m_arcModalVarSetName.clear();
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doConfirm) {
|
||||||
|
std::string raw(m_arcModalBuf);
|
||||||
|
std::string val = isNameInput ? SanitizeUsdName(raw) : raw;
|
||||||
|
|
||||||
|
if (m_stage && !m_arcModalTargetPrim.IsEmpty()) {
|
||||||
|
UsdPrim prim = m_stage->GetPrimAtPath(m_arcModalTargetPrim);
|
||||||
|
if (prim.IsValid()) {
|
||||||
|
try {
|
||||||
|
switch (m_arcModalMode) {
|
||||||
|
case ArcModalMode::Inherit: {
|
||||||
|
SdfPath ipath(val);
|
||||||
|
bool ok = prim.GetInherits().AddInherit(ipath);
|
||||||
|
if (ok)
|
||||||
|
LOG_INFO("Added inherit '" + val + "' to: " + m_arcModalTargetPrim.GetString());
|
||||||
|
else
|
||||||
|
LOG_ERROR("Failed to add inherit '" + val + "' to: " + m_arcModalTargetPrim.GetString());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ArcModalMode::Specialize: {
|
||||||
|
SdfPath spath(val);
|
||||||
|
bool ok = prim.GetSpecializes().AddSpecialize(spath);
|
||||||
|
if (ok)
|
||||||
|
LOG_INFO("Added specialize '" + val + "' to: " + m_arcModalTargetPrim.GetString());
|
||||||
|
else
|
||||||
|
LOG_ERROR("Failed to add specialize '" + val + "' to: " + m_arcModalTargetPrim.GetString());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ArcModalMode::VariantSet: {
|
||||||
|
prim.GetVariantSets().AddVariantSet(val);
|
||||||
|
LOG_INFO("Added variantSet '" + val + "' to: " + m_arcModalTargetPrim.GetString());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ArcModalMode::Variant: {
|
||||||
|
UsdVariantSet vs = prim.GetVariantSet(m_arcModalVarSetName);
|
||||||
|
bool ok = vs.AddVariant(val);
|
||||||
|
if (ok)
|
||||||
|
LOG_INFO("Added variant '" + val + "' to variantSet '" + m_arcModalVarSetName + "' on: " + m_arcModalTargetPrim.GetString());
|
||||||
|
else
|
||||||
|
LOG_ERROR("Failed to add variant '" + val + "' to variantSet '" + m_arcModalVarSetName + "'");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
LOG_ERROR(std::string("Arc operation error: ") + e.what());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG_ERROR("Arc modal: prim no longer valid: " + m_arcModalTargetPrim.GetString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_arcModalMode = ArcModalMode::None;
|
||||||
|
m_arcModalTargetPrim = SdfPath();
|
||||||
|
m_arcModalVarSetName.clear();
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void SceneHierarchyPanel::ProcessPendingReplaceRef() {
|
void SceneHierarchyPanel::ProcessPendingReplaceRef() {
|
||||||
if (!m_doReplaceRefPick) return;
|
if (!m_doReplaceRefPick) return;
|
||||||
m_doReplaceRefPick = false;
|
m_doReplaceRefPick = false;
|
||||||
|
|||||||
@@ -66,12 +66,15 @@ public:
|
|||||||
using StageMetadataChangedCallback = std::function<void()>;
|
using StageMetadataChangedCallback = std::function<void()>;
|
||||||
void SetOnStageMetadataChanged(StageMetadataChangedCallback callback) { m_onStageMetadataChanged = callback; }
|
void SetOnStageMetadataChanged(StageMetadataChangedCallback callback) { m_onStageMetadataChanged = callback; }
|
||||||
|
|
||||||
|
enum class ArcModalMode { None, Inherit, Specialize, VariantSet, Variant };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void RenderPrimNode(const UsdPrim& prim);
|
void RenderPrimNode(const UsdPrim& prim);
|
||||||
const char* GetPrimTypeIcon(const UsdPrim& prim) const;
|
const char* GetPrimTypeIcon(const UsdPrim& prim) const;
|
||||||
Icon GetPrimTypeIconEnum(const UsdPrim& prim) const;
|
Icon GetPrimTypeIconEnum(const UsdPrim& prim) const;
|
||||||
void RenderContextMenu(const UsdPrim& prim);
|
void RenderContextMenu(const UsdPrim& prim);
|
||||||
void RenderRemovePrimModal();
|
void RenderRemovePrimModal();
|
||||||
|
void RenderArcModal();
|
||||||
void ProcessPendingReplaceRef();
|
void ProcessPendingReplaceRef();
|
||||||
|
|
||||||
PropertyManager* m_propertyManager;
|
PropertyManager* m_propertyManager;
|
||||||
@@ -104,6 +107,13 @@ private:
|
|||||||
bool m_doReplaceRefPick = false;
|
bool m_doReplaceRefPick = false;
|
||||||
SdfPath m_pendingReplaceRefPrim;
|
SdfPath m_pendingReplaceRefPrim;
|
||||||
pxr::SdfReference m_pendingReplaceRef;
|
pxr::SdfReference m_pendingReplaceRef;
|
||||||
|
|
||||||
|
/// Arc modal state (Inherit / Specialize / VariantSet / Variant).
|
||||||
|
ArcModalMode m_arcModalMode = ArcModalMode::None;
|
||||||
|
SdfPath m_arcModalTargetPrim;
|
||||||
|
std::string m_arcModalVarSetName;
|
||||||
|
char m_arcModalBuf[512] = {};
|
||||||
|
bool m_openArcModal = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace UsdLayerManager
|
} // namespace UsdLayerManager
|
||||||
|
|||||||
Reference in New Issue
Block a user