Show composition arcs and asset info in PropertyPanel
Prims with authored references or payloads get a section below the prim header listing every list-op entry (explicit/add/prepend/append/ delete) with its asset path and target prim; a folder icon button browses (USD/usdz/MaterialX/Alembic filter) and replaces the entry's asset path, authored as one undoable whole-list-op swap into the edit target. Prims with composed assetInfo get a read-only key/value section (resolved paths shown in tooltips). PropertyPanel now receives the IconManager for the browse button. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -111,6 +111,7 @@ bool Application::Initialize(const std::string& windowTitle, int width, int heig
|
||||
m_iconManager = std::make_unique<IconManager>();
|
||||
m_iconManager->Initialize(ResourcePath("resources/icons"), 24);
|
||||
m_sceneHierarchyPanel->SetIconManager(m_iconManager.get());
|
||||
m_propertyPanel->SetIconManager(m_iconManager.get());
|
||||
m_viewportPanel->SetIconManager(m_iconManager.get());
|
||||
m_timelinePanel->SetIconManager(m_iconManager.get());
|
||||
m_stageEditorPanel->SetIconManager(m_iconManager.get());
|
||||
|
||||
+199
-1
@@ -1,8 +1,16 @@
|
||||
#include "PropertyPanel.h"
|
||||
#include "../utils/Logger.h"
|
||||
#include "../utils/FileDialog.h"
|
||||
#include "../core/commands/TransformCommand.h"
|
||||
#include "../core/commands/AttributeSetCommand.h"
|
||||
|
||||
#include <pxr/usd/sdf/listOp.h>
|
||||
#include <pxr/usd/sdf/reference.h>
|
||||
#include <pxr/usd/sdf/payload.h>
|
||||
#include <pxr/usd/sdf/schema.h>
|
||||
#include <pxr/base/vt/dictionary.h>
|
||||
#include <pxr/base/tf/stringUtils.h>
|
||||
|
||||
#include <pxr/usd/usd/prim.h>
|
||||
#include <pxr/usd/usd/attribute.h>
|
||||
#include <pxr/usd/usd/relationship.h>
|
||||
@@ -1116,6 +1124,186 @@ void PropertyPanel::RenderPrimHeader(const pxr::UsdPrim& prim) {
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// References & Payloads – one row per list-op entry; clicking the asset path
|
||||
// browses for a replacement file (undoable whole-list-op swap).
|
||||
// ---------------------------------------------------------------------------
|
||||
namespace {
|
||||
|
||||
pxr::SdfReference WithAssetPath(const pxr::SdfReference& r, const std::string& assetPath) {
|
||||
return pxr::SdfReference(assetPath, r.GetPrimPath(), r.GetLayerOffset(), r.GetCustomData());
|
||||
}
|
||||
pxr::SdfPayload WithAssetPath(const pxr::SdfPayload& p, const std::string& assetPath) {
|
||||
return pxr::SdfPayload(assetPath, p.GetPrimPath(), p.GetLayerOffset());
|
||||
}
|
||||
|
||||
// Shared row renderer for SdfReferenceListOp / SdfPayloadListOp: one row per
|
||||
// item in each list-op bucket, labelled with its op. On replace, the whole
|
||||
// modified list op is authored to the edit target as one undoable command.
|
||||
template <typename ListOpT>
|
||||
void RenderArcListRows(const pxr::UsdPrim& prim, CommandHistory* history,
|
||||
IconManager* icons,
|
||||
const char* tag, const char* displayKind,
|
||||
const pxr::TfToken& fieldKey, const ListOpT& listOp, int& uid)
|
||||
{
|
||||
using ItemVector = typename ListOpT::ItemVector;
|
||||
struct Bucket { const char* opName; ItemVector items; };
|
||||
Bucket buckets[] = {
|
||||
{"explicit", listOp.GetExplicitItems()},
|
||||
{"add", listOp.GetAddedItems()},
|
||||
{"prepend", listOp.GetPrependedItems()},
|
||||
{"append", listOp.GetAppendedItems()},
|
||||
{"delete", listOp.GetDeletedItems()},
|
||||
};
|
||||
const float rowH = ImGui::GetFrameHeight();
|
||||
|
||||
for (size_t b = 0; b < sizeof(buckets) / sizeof(buckets[0]); ++b) {
|
||||
for (size_t i = 0; i < buckets[b].items.size(); ++i) {
|
||||
const auto& item = buckets[b].items[i];
|
||||
std::string display = item.GetAssetPath();
|
||||
if (!item.GetPrimPath().IsEmpty())
|
||||
display += " <" + item.GetPrimPath().GetString() + ">";
|
||||
|
||||
ImGui::TableNextRow(ImGuiTableRowFlags_None, rowH);
|
||||
ImGui::PushID(uid++);
|
||||
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
DrawPropertyMiniButton(tag, /*authored=*/true,
|
||||
item.GetAssetPath().c_str(), displayKind);
|
||||
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextDisabled("%s", buckets[b].opName);
|
||||
|
||||
ImGui::TableSetColumnIndex(2);
|
||||
// Browse icon button first, then the path as plain text.
|
||||
bool clicked;
|
||||
if (icons) {
|
||||
clicked = ImGui::ImageButton("##browseArc",
|
||||
ImTextureRef(icons->Get(Icon::FolderOpen)),
|
||||
ImVec2(14.f, 14.f));
|
||||
} else {
|
||||
clicked = ImGui::SmallButton("...");
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("Browse for a replacement file");
|
||||
ImGui::SameLine();
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted(display.empty() ? "(no asset)" : display.c_str());
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s (%s)\n%s", displayKind, buckets[b].opName,
|
||||
item.GetAssetPath().c_str());
|
||||
if (clicked) {
|
||||
// Anything USD can compose is a valid arc target: native USD
|
||||
// layers plus file formats with layer plugins (MaterialX,
|
||||
// Alembic).
|
||||
static const char* kArcFileFilter =
|
||||
"Referenceable (*.usd;*.usda;*.usdc;*.usdz;*.mtlx;*.abc)\0"
|
||||
"*.usd;*.usda;*.usdc;*.usdz;*.mtlx;*.abc\0"
|
||||
"USD Files (*.usd;*.usda;*.usdc;*.usdz)\0*.usd;*.usda;*.usdc;*.usdz\0"
|
||||
"MaterialX (*.mtlx)\0*.mtlx\0"
|
||||
"Alembic (*.abc)\0*.abc\0"
|
||||
"All Files (*.*)\0*.*\0";
|
||||
std::string picked = FileDialog::OpenFile(kArcFileFilter, "Select Reference File");
|
||||
if (!picked.empty() && picked != item.GetAssetPath()) {
|
||||
ListOpT newOp = listOp;
|
||||
ItemVector items = buckets[b].items;
|
||||
items[i] = WithAssetPath(item, picked);
|
||||
switch (b) {
|
||||
case 0: newOp.SetExplicitItems(items); break;
|
||||
case 1: newOp.SetAddedItems(items); break;
|
||||
case 2: newOp.SetPrependedItems(items); break;
|
||||
case 3: newOp.SetAppendedItems(items); break;
|
||||
case 4: newOp.SetDeletedItems(items); break;
|
||||
}
|
||||
// Authors the whole (possibly multi-layer-composed) list
|
||||
// op into the edit target — the pragmatic single-layer
|
||||
// behaviour; undo restores the previous composed op.
|
||||
if (history) {
|
||||
pxr::UsdPrim p = prim;
|
||||
ListOpT oldOp = listOp;
|
||||
history->Push(std::make_unique<AttributeSetCommand>(
|
||||
std::string("Replace ") + displayKind + " Path",
|
||||
[p, fieldKey, newOp]() mutable { p.SetMetadata(fieldKey, newOp); },
|
||||
[p, fieldKey, oldOp]() mutable { p.SetMetadata(fieldKey, oldOp); }));
|
||||
} else {
|
||||
pxr::UsdPrim(prim).SetMetadata(fieldKey, newOp);
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void PropertyPanel::RenderCompositionArcsSection(const pxr::UsdPrim& prim) {
|
||||
if (!ImGui::CollapsingHeader("References & Payloads", ImGuiTreeNodeFlags_DefaultOpen))
|
||||
return;
|
||||
|
||||
constexpr ImGuiTableFlags kFlags = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg;
|
||||
if (!ImGui::BeginTable("##compArcs", 3, kFlags)) return;
|
||||
ImGui::TableSetupColumn("##dot", ImGuiTableColumnFlags_WidthFixed, 20.f);
|
||||
ImGui::TableSetupColumn("##op", ImGuiTableColumnFlags_WidthFixed, 60.f);
|
||||
ImGui::TableSetupColumn("##val", ImGuiTableColumnFlags_WidthStretch);
|
||||
|
||||
int uid = 0;
|
||||
if (prim.HasAuthoredReferences()) {
|
||||
pxr::SdfReferenceListOp refOp;
|
||||
if (prim.GetMetadata(pxr::SdfFieldKeys->References, &refOp))
|
||||
RenderArcListRows(prim, m_commandHistory, m_iconManager, "(R)", "Reference",
|
||||
pxr::SdfFieldKeys->References, refOp, uid);
|
||||
}
|
||||
if (prim.HasAuthoredPayloads()) {
|
||||
pxr::SdfPayloadListOp payloadOp;
|
||||
if (prim.GetMetadata(pxr::SdfFieldKeys->Payload, &payloadOp))
|
||||
RenderArcListRows(prim, m_commandHistory, m_iconManager, "(P)", "Payload",
|
||||
pxr::SdfFieldKeys->Payload, payloadOp, uid);
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Asset Info – read-only view of the composed assetInfo dictionary
|
||||
// ---------------------------------------------------------------------------
|
||||
void PropertyPanel::RenderAssetInfoSection(const pxr::UsdPrim& prim) {
|
||||
if (!ImGui::CollapsingHeader("Asset Info", ImGuiTreeNodeFlags_DefaultOpen))
|
||||
return;
|
||||
|
||||
constexpr ImGuiTableFlags kFlags = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg;
|
||||
if (!ImGui::BeginTable("##assetInfo", 2, kFlags)) return;
|
||||
ImGui::TableSetupColumn("##key", ImGuiTableColumnFlags_WidthFixed, 110.f);
|
||||
ImGui::TableSetupColumn("##val", ImGuiTableColumnFlags_WidthStretch);
|
||||
|
||||
const pxr::VtDictionary info = prim.GetAssetInfo();
|
||||
std::vector<std::string> keys;
|
||||
keys.reserve(info.size());
|
||||
for (const auto& entry : info)
|
||||
keys.push_back(entry.first);
|
||||
std::sort(keys.begin(), keys.end());
|
||||
|
||||
for (const std::string& key : keys) {
|
||||
const pxr::VtValue& value = info.find(key)->second;
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::TextDisabled("%s", key.c_str());
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
if (value.IsHolding<pxr::SdfAssetPath>()) {
|
||||
const auto& assetPath = value.UncheckedGet<pxr::SdfAssetPath>();
|
||||
ImGui::TextUnformatted(assetPath.GetAssetPath().c_str());
|
||||
if (ImGui::IsItemHovered() && !assetPath.GetResolvedPath().empty())
|
||||
ImGui::SetTooltip("resolved: %s", assetPath.GetResolvedPath().c_str());
|
||||
} else {
|
||||
const std::string text = pxr::TfStringify(value);
|
||||
ImGui::TextUnformatted(text.c_str());
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", text.c_str());
|
||||
}
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Variant Sets – no borders, no header (mirrors DrawVariantSetsCombos in usdtweak)
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -1605,9 +1793,19 @@ void PropertyPanel::Render() {
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Scrollable body – variant sets → transform → all properties
|
||||
// Scrollable body – composition arcs → variant sets → transform → all properties
|
||||
ImGui::BeginChild("##body");
|
||||
|
||||
if (prim.HasAuthoredReferences() || prim.HasAuthoredPayloads()) {
|
||||
RenderCompositionArcsSection(prim);
|
||||
ImGui::Separator();
|
||||
}
|
||||
|
||||
if (!prim.GetAssetInfo().empty()) {
|
||||
RenderAssetInfoSection(prim);
|
||||
ImGui::Separator();
|
||||
}
|
||||
|
||||
if (prim.HasVariantSets()) {
|
||||
RenderVariantSetsSection(prim);
|
||||
ImGui::Separator();
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "../core/PropertyManager.h"
|
||||
#include "../core/CommandHistory.h"
|
||||
#include "../core/commands/TransformCommand.h"
|
||||
#include "IconManager.h"
|
||||
#include <pxr/usd/usd/stage.h>
|
||||
#include <pxr/usd/usd/timeCode.h>
|
||||
#include <pxr/usd/sdf/path.h>
|
||||
@@ -25,6 +26,7 @@ public:
|
||||
|
||||
void SetPropertyManager(PropertyManager* manager);
|
||||
void SetCommandHistory(CommandHistory* history) { m_commandHistory = history; }
|
||||
void SetIconManager(IconManager* icons) { m_iconManager = icons; }
|
||||
void SetStage(pxr::UsdStageRefPtr stage);
|
||||
void SetSelectedPrimPath(const std::string& path);
|
||||
|
||||
@@ -60,12 +62,20 @@ private:
|
||||
void RenderTransformSection();
|
||||
void RenderPropertiesTable(const pxr::UsdPrim& prim); ///< groups attrs by API schema
|
||||
void RenderMaterialBindSection(const pxr::UsdPrim& prim); ///< material:binding info
|
||||
/// References/payloads authored on the prim, one row per list-op entry
|
||||
/// (explicit/add/prepend/append/delete) with its asset path; clicking a
|
||||
/// path browses for a replacement file (undoable).
|
||||
void RenderCompositionArcsSection(const pxr::UsdPrim& prim);
|
||||
/// Read-only view of the prim's composed assetInfo dictionary
|
||||
/// (identifier, name, version, ...).
|
||||
void RenderAssetInfoSection(const pxr::UsdPrim& prim);
|
||||
void RenderAttrRelSubTable(const std::vector<pxr::UsdAttribute>& attrs,
|
||||
const std::vector<pxr::UsdRelationship>& rels,
|
||||
const pxr::UsdEditTarget& editTarget); ///< inner 3-col table
|
||||
|
||||
PropertyManager* m_propertyManager = nullptr;
|
||||
CommandHistory* m_commandHistory = nullptr;
|
||||
IconManager* m_iconManager = nullptr;
|
||||
pxr::UsdStageRefPtr m_stage;
|
||||
std::string m_selectedPrimPath;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user