Compare commits

...

3 Commits

Author SHA1 Message Date
indigo d280136ca7 Modify font size to 15 2026-06-18 07:15:55 +08:00
indigo 81d4372bf6 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>
2026-06-18 04:48:18 +08:00
indigo 3ba00761c1 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>
2026-06-18 04:37:16 +08:00
3 changed files with 484 additions and 101 deletions
+1 -1
View File
@@ -106,7 +106,7 @@ bool ImGuiContext::Initialize(const std::string& windowTitle, int width, int hei
const char* tryPaths[] = { fontPath0.c_str(), fontPath1.c_str() };
bool loaded = false;
for (const char* p : tryPaths) {
ImFont* f = io.Fonts->AddFontFromFileTTF(p, 16.0f, &fontConfig);
ImFont* f = io.Fonts->AddFontFromFileTTF(p, 15.0f, &fontConfig);
if (f) {
LOG_INFO(std::string("Loaded Inter font from ") + p);
loaded = true;
+473 -100
View File
@@ -8,7 +8,12 @@
#include <pxr/usd/usd/prim.h>
#include <pxr/usd/usd/primRange.h>
#include <pxr/usd/usd/references.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/sdf/payload.h>
#include <pxr/usd/usdGeom/imageable.h>
#include <pxr/usd/usdGeom/metrics.h>
#include <pxr/usd/usdGeom/tokens.h>
@@ -315,46 +320,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<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);
if (m_commandHistory) {
m_commandHistory->Push(std::make_unique<AddReferenceCommand>(
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();
@@ -363,6 +403,9 @@ void SceneHierarchyPanel::Render() {
// Deferred confirm modal for prim removal (must be opened outside any popup stack).
RenderRemovePrimModal();
// Deferred modal for arc operations (Inherit / Specialize / VariantSet / Variant).
RenderArcModal();
// Deferred file-dialog for reference replacement (must run outside any popup stack).
ProcessPendingReplaceRef();
}
@@ -620,96 +663,283 @@ 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();
if (ImGui::BeginMenu("Replace", 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())) {
m_pendingReplaceRef = oldRef;
m_pendingReplaceRefPrim = prim.GetPath();
m_doReplaceRefPick = true;
}
anyRepl = true;
}
}
if (!anyRepl)
ImGui::TextDisabled("(no direct references)");
ImGui::EndMenu();
}
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();
}
bool hasRefs = prim.HasAuthoredReferences();
// ---- 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;
// ---- 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());
}
anyRepl = true;
}
}
if (!anyRepl) {
ImGui::TextDisabled("(no direct references)");
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();
}
// ---- 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();
}
// ---- 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);
// ---- 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 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());
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();
}
anyListed = true;
}
}
if (anyListed) ImGui::Separator();
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());
}
}
ImGui::EndMenu();
}
@@ -815,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() {
if (!m_doReplaceRefPick) return;
m_doReplaceRefPick = false;
+10
View File
@@ -66,12 +66,15 @@ public:
using StageMetadataChangedCallback = std::function<void()>;
void SetOnStageMetadataChanged(StageMetadataChangedCallback callback) { m_onStageMetadataChanged = callback; }
enum class ArcModalMode { None, Inherit, Specialize, VariantSet, Variant };
private:
void RenderPrimNode(const UsdPrim& prim);
const char* GetPrimTypeIcon(const UsdPrim& prim) const;
Icon GetPrimTypeIconEnum(const UsdPrim& prim) const;
void RenderContextMenu(const UsdPrim& prim);
void RenderRemovePrimModal();
void RenderArcModal();
void ProcessPendingReplaceRef();
PropertyManager* m_propertyManager;
@@ -104,6 +107,13 @@ private:
bool m_doReplaceRefPick = false;
SdfPath m_pendingReplaceRefPrim;
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