Add keyable attrs, clear override, material binding, and API schema groups to PropertyPanel

Attributes with SdfVariabilityVarying now show a (k) button when the
timeline is at a non-default frame. Orange = exact time sample at the
current frame; gray = no sample here. Clicking keys the current value
via AttributeSetCommand (undoable).

Right-click on any attribute or relationship name opens a context menu
with "Clear Override" (greyed when nothing is authored at the edit
target). Undo restores all previously authored time samples and default
values.

Prims with UsdShadeMaterialBindingAPI now show a "Material Binding"
collapsing section above the transform block, listing Direct / Preview /
Full purpose bindings and the resolved computed material path.

RenderPropertiesTable now groups attributes and relationships under
CollapsingHeader sections named after their USD API schema. Base-type
attributes (e.g. Mesh, Xform) appear first; applied API schema groups
(MaterialBindingAPI, CollectionAPI, etc.) follow alphabetically.
Uncategorised attributes are bucketed by namespace prefix (primvars,
inputs, outputs, …). xformOp attrs are still filtered out (shown in the
TRS section above).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-07-02 07:13:21 +08:00
parent f7a2262e80
commit 0640201d5b
2 changed files with 369 additions and 41 deletions
+364 -40
View File
@@ -1,6 +1,7 @@
#include "PropertyPanel.h" #include "PropertyPanel.h"
#include "../utils/Logger.h" #include "../utils/Logger.h"
#include "../core/commands/TransformCommand.h" #include "../core/commands/TransformCommand.h"
#include "../core/commands/AttributeSetCommand.h"
#include <pxr/usd/usd/prim.h> #include <pxr/usd/usd/prim.h>
#include <pxr/usd/usd/attribute.h> #include <pxr/usd/usd/attribute.h>
@@ -38,8 +39,15 @@
#include <pxr/base/vt/array.h> #include <pxr/base/vt/array.h>
#include <pxr/base/tf/token.h> #include <pxr/base/tf/token.h>
#include <pxr/usd/sdf/assetPath.h> #include <pxr/usd/sdf/assetPath.h>
#include <pxr/usd/usdShade/materialBindingAPI.h>
#include <pxr/usd/usdShade/tokens.h>
#include <pxr/usd/usd/schemaRegistry.h>
#include <pxr/usd/usd/primDefinition.h>
#include <memory> #include <memory>
#include <sstream> #include <sstream>
#include <unordered_map>
#include <vector>
#include <set>
PXR_NAMESPACE_USING_DIRECTIVE PXR_NAMESPACE_USING_DIRECTIVE
@@ -48,10 +56,12 @@ namespace UsdLayerManager {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Colours // Colours
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
static const ImVec4 kColorX { 0.86f, 0.30f, 0.30f, 1.f }; // X axis red static const ImVec4 kColorX { 0.86f, 0.30f, 0.30f, 1.f }; // X axis red
static const ImVec4 kColorY { 0.40f, 0.80f, 0.30f, 1.f }; // Y axis green static const ImVec4 kColorY { 0.40f, 0.80f, 0.30f, 1.f }; // Y axis green
static const ImVec4 kColorZ { 0.28f, 0.56f, 0.96f, 1.f }; // Z axis blue static const ImVec4 kColorZ { 0.28f, 0.56f, 0.96f, 1.f }; // Z axis blue
static const ImVec4 kColorLabel{ 0.80f, 0.80f, 0.80f, 1.f }; // row labels static const ImVec4 kColorLabel { 0.80f, 0.80f, 0.80f, 1.f }; // row labels
static const ImVec4 kColorKeyOn { 1.00f, 0.65f, 0.10f, 1.f }; // key exists at editTime
static const ImVec4 kColorKeyOff { 0.35f, 0.35f, 0.35f, 1.f }; // varying, no sample here
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
PropertyPanel::PropertyPanel() = default; PropertyPanel::PropertyPanel() = default;
@@ -535,6 +545,60 @@ static std::string GetPropDisplayName(const pxr::UsdProperty& prop) {
return ns.empty() ? bn : (ns + ":" + bn); return ns.empty() ? bn : (ns + ":" + bn);
} }
// ---------------------------------------------------------------------------
// Helper: check whether an attribute has an exact time sample at t
// ---------------------------------------------------------------------------
static bool HasExactTimeSample(const pxr::UsdAttribute& attr, double t) {
double lo = 0.0, hi = 0.0;
bool has = false;
attr.GetBracketingTimeSamples(t, &lo, &hi, &has);
return has && (lo == t) && (hi == t);
}
// ---------------------------------------------------------------------------
// Helper: small "(k)" key button for animatable (Varying) attributes.
// Orange = exact time sample at editTime; dim gray = no sample at this frame.
// Click = Set(currentValue, editTime) with undo.
// ---------------------------------------------------------------------------
static void DrawKeyButton(pxr::UsdAttribute attr,
pxr::UsdTimeCode editTime,
UsdLayerManager::CommandHistory* cmdHistory)
{
using namespace pxr;
double t = editTime.GetValue();
bool exactSample = HasExactTimeSample(attr, t);
ImVec4 col = exactSample ? kColorKeyOn : kColorKeyOff;
ImGui::PushStyleColor(ImGuiCol_Text, col);
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.f, 0.f, 0.f, 0.f));
bool clicked = ImGui::SmallButton("(k)");
ImGui::PopStyleColor(2);
if (ImGui::IsItemHovered())
ImGui::SetTooltip("Set key at frame %.3g", t);
if (!clicked || !cmdHistory) return;
VtValue val;
if (!attr.Get(&val, editTime)) return;
std::function<void()> executeFn = [attr, val, editTime]() mutable {
attr.Set(val, editTime);
};
std::function<void()> undoFn;
if (exactSample) {
// overwrite: undo restores the same value that was there before
VtValue prev;
attr.Get(&prev, editTime);
undoFn = [attr, prev, editTime]() mutable { attr.Set(prev, editTime); };
} else {
undoFn = [attr, editTime]() mutable { attr.ClearAtTime(editTime.GetValue()); };
}
cmdHistory->Push(std::make_unique<AttributeSetCommand>(
"Set Key", std::move(executeFn), std::move(undoFn)));
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Helper: mini-button (SmallButton coloured by authored state, like usdtweak) // Helper: mini-button (SmallButton coloured by authored state, like usdtweak)
// Opens a popup context with "Copy path" on left-click. // Opens a popup context with "Copy path" on left-click.
@@ -1109,35 +1173,112 @@ void PropertyPanel::RenderVariantSetsSection(const pxr::UsdPrim& prim) {
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Unified properties table (attributes + relationships) // Material binding section
//
// Mirrors ##DrawPropertyEditorTable in usdtweak DrawUsdPrimProperties:
// • Flags: SizingFixedFit | RowBg — NO borders, NO header row
// • Col 0 (fixed ~20px) : SmallButton mini-button coloured by authored state
// • Col 1 (fixed 140px) : property display name (namespace:basename)
// • Col 2 (stretch) : type-aware value widget, PushItemWidth(-FLT_MIN)
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
void PropertyPanel::RenderPropertiesTable(const pxr::UsdPrim& prim) { void PropertyPanel::RenderMaterialBindSection(const pxr::UsdPrim& prim) {
// Exact flags used by usdtweak no borders, alternating row background using namespace pxr;
constexpr ImGuiTableFlags kFlags =
ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg;
if (!ImGui::BeginTable("##properties", 3, kFlags)) return; if (!prim.HasAPI<UsdShadeMaterialBindingAPI>()) return;
UsdShadeMaterialBindingAPI bindAPI(prim);
ImGui::TableSetupColumn("##dot", ImGuiTableColumnFlags_WidthFixed, 20.f); if (!ImGui::CollapsingHeader("Material Binding", ImGuiTreeNodeFlags_DefaultOpen))
ImGui::TableSetupColumn("##name", ImGuiTableColumnFlags_WidthFixed, 140.f); return;
constexpr ImGuiTableFlags kFlags = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg;
if (!ImGui::BeginTable("##matbind", 2, kFlags)) return;
ImGui::TableSetupColumn("##mf", ImGuiTableColumnFlags_WidthFixed, 80.f);
ImGui::TableSetupColumn("##mv", ImGuiTableColumnFlags_WidthStretch);
// Direct binding (allPurpose)
{
auto direct = bindAPI.GetDirectBinding();
SdfPath matPath = direct.GetMaterialPath();
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0); ImGui::TextDisabled("Direct");
ImGui::TableSetColumnIndex(1);
if (!matPath.IsEmpty()) {
bool authored = direct.GetBindingRel() && direct.GetBindingRel().IsAuthored();
ImVec4 col = authored ? ImVec4(0.6f, 0.9f, 1.f, 1.f)
: ImVec4(0.5f, 0.5f, 0.5f, 1.f);
ImGui::TextColored(col, "%s", matPath.GetText());
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", matPath.GetText());
} else {
ImGui::TextDisabled("(none)");
}
}
// Preview purpose direct binding (only show if different from allPurpose)
{
auto preview = bindAPI.GetDirectBinding(UsdShadeTokens->preview);
SdfPath previewPath = preview.GetMaterialPath();
if (!previewPath.IsEmpty()) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0); ImGui::TextDisabled("Preview");
ImGui::TableSetColumnIndex(1);
ImGui::TextColored(ImVec4(0.6f, 0.9f, 1.f, 1.f), "%s", previewPath.GetText());
}
}
// Full purpose direct binding
{
auto full = bindAPI.GetDirectBinding(UsdShadeTokens->full);
SdfPath fullPath = full.GetMaterialPath();
if (!fullPath.IsEmpty()) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0); ImGui::TextDisabled("Full");
ImGui::TableSetColumnIndex(1);
ImGui::TextColored(ImVec4(0.6f, 0.9f, 1.f, 1.f), "%s", fullPath.GetText());
}
}
// Resolved (computed) bound material
{
UsdShadeMaterial resolved = bindAPI.ComputeBoundMaterial();
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0); ImGui::TextDisabled("Resolved");
ImGui::TableSetColumnIndex(1);
if (resolved) {
ImGui::TextColored(ImVec4(0.7f, 1.f, 0.7f, 1.f),
"%s", resolved.GetPrim().GetPath().GetText());
} else {
ImGui::TextDisabled("(none)");
}
}
ImGui::EndTable();
}
// ---------------------------------------------------------------------------
// Inner attribute + relationship table — one category group
// ---------------------------------------------------------------------------
void PropertyPanel::RenderAttrRelSubTable(
const std::vector<pxr::UsdAttribute>& attrs,
const std::vector<pxr::UsdRelationship>& rels,
const pxr::UsdEditTarget& editTarget)
{
using namespace pxr;
if (attrs.empty() && rels.empty()) return;
constexpr ImGuiTableFlags kFlags = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg;
if (!ImGui::BeginTable("##propSubTable", 3, kFlags)) return;
// Col 0 wider when the key button column is active
float col0W = m_editTime.IsDefault() ? 20.f : 44.f;
ImGui::TableSetupColumn("##dot", ImGuiTableColumnFlags_WidthFixed, col0W);
ImGui::TableSetupColumn("##name", ImGuiTableColumnFlags_WidthFixed, 140.f);
ImGui::TableSetupColumn("##val", ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("##val", ImGuiTableColumnFlags_WidthStretch);
// No TableHeadersRow matches usdtweak "no header" intent
const pxr::UsdEditTarget& editTarget = prim.GetStage()->GetEditTarget();
const float rowH = ImGui::GetFrameHeight(); const float rowH = ImGui::GetFrameHeight();
int uid = 0; int uid = 0;
// ---- Attributes (mirrors usdtweak attribute loop) ---- // ── Attributes ──────────────────────────────────────────────────────────
for (auto& attr : prim.GetAttributes()) { for (auto attr : attrs) {
ImGui::TableNextRow(ImGuiTableRowFlags_None, rowH); ImGui::TableNextRow(ImGuiTableRowFlags_None, rowH);
// Col 0 mini button "(a)" // Col 0 (a) mini-button + optional (k) key button
ImGui::TableSetColumnIndex(0); ImGui::TableSetColumnIndex(0);
ImGui::PushID(uid++); ImGui::PushID(uid++);
DrawPropertyMiniButton("(a)", DrawPropertyMiniButton("(a)",
@@ -1146,29 +1287,75 @@ void PropertyPanel::RenderPropertiesTable(const pxr::UsdPrim& prim) {
attr.GetTypeName().GetAsToken().GetText()); attr.GetTypeName().GetAsToken().GetText());
ImGui::PopID(); ImGui::PopID();
// Col 1 display name (namespace:basename) bool isVarying = (attr.GetVariability() == SdfVariabilityVarying);
if (!m_editTime.IsDefault() && isVarying) {
ImGui::SameLine(0, 2);
ImGui::PushID(uid++);
DrawKeyButton(attr, m_editTime, m_commandHistory);
ImGui::PopID();
}
// Col 1 display name + right-click context menu
ImGui::TableSetColumnIndex(1); ImGui::TableSetColumnIndex(1);
ImGui::PushID(static_cast<int>(attr.GetPath().GetHash()) ^ 0x100);
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted(GetPropDisplayName(attr).c_str()); ImGui::TextUnformatted(GetPropDisplayName(attr).c_str());
if (ImGui::BeginPopupContextItem("##attrCtx")) {
if (ImGui::MenuItem("Copy attribute path"))
ImGui::SetClipboardText(attr.GetPath().GetString().c_str());
ImGui::Separator();
bool authored = attr.IsAuthoredAt(editTarget);
ImGui::BeginDisabled(!authored);
if (ImGui::MenuItem("Clear Override")) {
// Capture current authored opinions so undo can restore them
std::vector<double> times;
attr.GetTimeSamples(&times);
std::vector<std::pair<double, VtValue>> samples;
for (double t : times) {
VtValue v;
if (attr.Get(&v, UsdTimeCode(t)))
samples.emplace_back(t, v);
}
VtValue defVal;
bool hasDefault = attr.Get(&defVal, UsdTimeCode::Default());
// Col 2 value widget with PushItemWidth(-FLT_MIN) (label hidden) if (m_commandHistory) {
m_commandHistory->Push(std::make_unique<AttributeSetCommand>(
"Clear Override",
[attr]() mutable { attr.Clear(); },
[attr, samps = samples, hasDefault, defVal]() mutable {
if (hasDefault) attr.Set(defVal, UsdTimeCode::Default());
for (auto& [t, v] : samps)
attr.Set(v, UsdTimeCode(t));
}
));
} else {
attr.Clear();
}
ImGui::CloseCurrentPopup();
}
ImGui::EndDisabled();
ImGui::EndPopup();
}
ImGui::PopID();
// Col 2 value widget
ImGui::TableSetColumnIndex(2); ImGui::TableSetColumnIndex(2);
ImGui::PushID(static_cast<int>(attr.GetPath().GetHash())); ImGui::PushID(static_cast<int>(attr.GetPath().GetHash()));
ImGui::PushItemWidth(-FLT_MIN); ImGui::PushItemWidth(-FLT_MIN);
// Check for allowedTokens first → Combo (mirrors DrawTfToken in usdtweak) VtValue allowedTokens;
pxr::VtValue allowedTokens; attr.GetMetadata(TfToken("allowedTokens"), &allowedTokens);
attr.GetMetadata(pxr::TfToken("allowedTokens"), &allowedTokens);
pxr::VtValue val; VtValue val;
bool hasVal = attr.Get(&val, m_displayTime); bool hasVal = attr.Get(&val, m_displayTime);
if (!allowedTokens.IsEmpty() && if (!allowedTokens.IsEmpty() &&
allowedTokens.IsHolding<pxr::VtArray<pxr::TfToken>>()) allowedTokens.IsHolding<VtArray<TfToken>>())
{ {
const auto& tokens = allowedTokens.UncheckedGet<pxr::VtArray<pxr::TfToken>>(); const auto& tokens = allowedTokens.UncheckedGet<VtArray<TfToken>>();
std::string cur = (hasVal && val.IsHolding<pxr::TfToken>()) std::string cur = (hasVal && val.IsHolding<TfToken>())
? val.UncheckedGet<pxr::TfToken>().GetString() : ""; ? val.UncheckedGet<TfToken>().GetString() : "";
if (ImGui::BeginCombo("##tok", cur.c_str())) { if (ImGui::BeginCombo("##tok", cur.c_str())) {
for (const auto& tok : tokens) { for (const auto& tok : tokens) {
bool sel = (tok.GetString() == cur); bool sel = (tok.GetString() == cur);
@@ -1178,8 +1365,7 @@ void PropertyPanel::RenderPropertiesTable(const pxr::UsdPrim& prim) {
ImGui::EndCombo(); ImGui::EndCombo();
} }
} else if (hasVal) { } else if (hasVal) {
// Type-aware widget dispatch (mirrors DrawVtValue in usdtweak) VtValue modified = DrawVtValueWidget(attr, val);
pxr::VtValue modified = DrawVtValueWidget(attr, val);
if (!modified.IsEmpty()) if (!modified.IsEmpty())
attr.Set(modified, m_editTime); attr.Set(modified, m_editTime);
} else if (attr.HasAuthoredConnections()) { } else if (attr.HasAuthoredConnections()) {
@@ -1198,11 +1384,11 @@ void PropertyPanel::RenderPropertiesTable(const pxr::UsdPrim& prim) {
ImGui::PopID(); ImGui::PopID();
} }
// ---- Relationships (mirrors usdtweak relationship loop) ---- // ── Relationships ────────────────────────────────────────────────────────
for (const auto& rel : prim.GetRelationships()) { for (const auto& rel : rels) {
ImGui::TableNextRow(ImGuiTableRowFlags_None, rowH); ImGui::TableNextRow(ImGuiTableRowFlags_None, rowH);
// Col 0 mini button "(r)" // Col 0 (r) mini-button (no key button for relationships)
ImGui::TableSetColumnIndex(0); ImGui::TableSetColumnIndex(0);
ImGui::PushID(uid++); ImGui::PushID(uid++);
DrawPropertyMiniButton("(r)", DrawPropertyMiniButton("(r)",
@@ -1210,15 +1396,41 @@ void PropertyPanel::RenderPropertiesTable(const pxr::UsdPrim& prim) {
rel.GetPath().GetString().c_str()); rel.GetPath().GetString().c_str());
ImGui::PopID(); ImGui::PopID();
// Col 1 display name, coloured by authored state (mirrors DrawUsdRelationshipDisplayName) // Col 1 display name + right-click context menu
ImGui::TableSetColumnIndex(1); ImGui::TableSetColumnIndex(1);
ImGui::PushID(static_cast<int>(rel.GetPath().GetHash()) ^ 0x100);
ImVec4 relColor = rel.IsAuthored() ImVec4 relColor = rel.IsAuthored()
? ImVec4(0.6f, 0.9f, 1.f, 1.f) ? ImVec4(0.6f, 0.9f, 1.f, 1.f)
: ImVec4(0.5f, 0.5f, 0.5f, 1.f); : ImVec4(0.5f, 0.5f, 0.5f, 1.f);
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();
ImGui::TextColored(relColor, "%s", GetPropDisplayName(rel).c_str()); ImGui::TextColored(relColor, "%s", GetPropDisplayName(rel).c_str());
if (ImGui::BeginPopupContextItem("##relCtx")) {
if (ImGui::MenuItem("Copy path"))
ImGui::SetClipboardText(rel.GetPath().GetString().c_str());
ImGui::Separator();
ImGui::BeginDisabled(!rel.IsAuthored());
if (ImGui::MenuItem("Clear Override")) {
SdfPathVector oldTargets;
rel.GetTargets(&oldTargets);
if (m_commandHistory) {
m_commandHistory->Push(std::make_unique<AttributeSetCommand>(
"Clear Override",
[rel]() mutable { rel.ClearTargets(false); },
[rel, tgts = oldTargets]() mutable {
for (const auto& p : tgts) rel.AddTarget(p);
}
));
} else {
rel.ClearTargets(false);
}
ImGui::CloseCurrentPopup();
}
ImGui::EndDisabled();
ImGui::EndPopup();
}
ImGui::PopID();
// Col 2 target list: single row when collapsed, expand for multiple // Col 2 target list
ImGui::TableSetColumnIndex(2); ImGui::TableSetColumnIndex(2);
SdfPathVector targets; SdfPathVector targets;
rel.GetTargets(&targets); rel.GetTargets(&targets);
@@ -1253,6 +1465,113 @@ void PropertyPanel::RenderPropertiesTable(const pxr::UsdPrim& prim) {
ImGui::EndTable(); ImGui::EndTable();
} }
// ---------------------------------------------------------------------------
// Unified properties table (attributes + relationships)
//
// Mirrors ##DrawPropertyEditorTable in usdtweak DrawUsdPrimProperties:
// • Flags: SizingFixedFit | RowBg — NO borders, NO header row
// • Col 0 (fixed ~20px) : SmallButton mini-button coloured by authored state
// • Col 1 (fixed 140px) : property display name (namespace:basename)
// • Col 2 (stretch) : type-aware value widget, PushItemWidth(-FLT_MIN)
// ---------------------------------------------------------------------------
void PropertyPanel::RenderPropertiesTable(const pxr::UsdPrim& prim) {
using namespace pxr;
// Build attr-name → category label map from schema registry
std::unordered_map<std::string, std::string> attrToCategory;
const UsdSchemaRegistry& reg = UsdSchemaRegistry::GetInstance();
if (!m_primType.empty()) {
if (const UsdPrimDefinition* typeDef =
reg.FindConcretePrimDefinition(TfToken(m_primType))) {
for (const TfToken& prop : typeDef->GetPropertyNames())
attrToCategory.emplace(prop.GetString(), m_primType);
}
}
for (const auto& schemaToken : prim.GetAppliedSchemas()) {
std::string s = schemaToken.GetString();
// Multi-apply tokens look like "CollectionAPI:name"; strip instance suffix
auto pos = s.find(':');
std::string baseName = (pos != std::string::npos) ? s.substr(0, pos) : s;
if (const UsdPrimDefinition* apiDef =
reg.FindAppliedAPIPrimDefinition(TfToken(baseName))) {
for (const TfToken& prop : apiDef->GetPropertyNames())
attrToCategory.emplace(prop.GetString(), baseName); // first writer wins
}
}
// Bucket attrs and rels into ordered category groups
struct PropGroup {
std::vector<UsdAttribute> attrs;
std::vector<UsdRelationship> rels;
};
std::vector<std::string> orderedCats;
std::set<std::string> seenCats;
std::unordered_map<std::string, PropGroup> groups;
auto addCat = [&](const std::string& cat) {
if (seenCats.insert(cat).second) orderedCats.push_back(cat);
};
if (!m_primType.empty()) addCat(m_primType);
const std::string fallback = m_primType.empty() ? "Properties" : m_primType;
for (auto attr : prim.GetAttributes()) {
const std::string name = attr.GetName().GetString();
if (TfStringStartsWith(name, "xformOp:")) continue; // shown in TRS section
auto it = attrToCategory.find(name);
std::string cat;
if (it != attrToCategory.end()) {
cat = it->second;
} else {
std::string ns = attr.GetNamespace().GetString();
if (ns.empty()) {
cat = fallback;
} else {
auto c = ns.find(':');
cat = (c != std::string::npos) ? ns.substr(0, c) : ns;
}
}
addCat(cat);
groups[cat].attrs.push_back(attr);
}
for (const auto& rel : prim.GetRelationships()) {
std::string ns = rel.GetNamespace().GetString();
std::string cat;
if (ns.empty()) {
cat = fallback;
} else {
auto c = ns.find(':');
cat = (c != std::string::npos) ? ns.substr(0, c) : ns;
}
addCat(cat);
groups[cat].rels.push_back(rel);
}
// Sort: primType first (already inserted), remaining alphabetically
if (orderedCats.size() > 1) {
size_t firstSort = m_primType.empty() ? 0 : 1;
std::sort(orderedCats.begin() + firstSort, orderedCats.end());
}
// Render each category under a collapsing header
const UsdEditTarget& editTarget = prim.GetStage()->GetEditTarget();
for (const auto& cat : orderedCats) {
auto& group = groups[cat];
if (group.attrs.empty() && group.rels.empty()) continue;
ImGui::PushID(cat.c_str());
if (ImGui::CollapsingHeader(cat.c_str(), ImGuiTreeNodeFlags_DefaultOpen))
RenderAttrRelSubTable(group.attrs, group.rels, editTarget);
ImGui::PopID();
}
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Main render entry point // Main render entry point
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -1289,6 +1608,11 @@ void PropertyPanel::Render() {
ImGui::Separator(); ImGui::Separator();
} }
if (prim.HasAPI<pxr::UsdShadeMaterialBindingAPI>()) {
RenderMaterialBindSection(prim);
ImGui::Separator();
}
if (m_hasXform) { if (m_hasXform) {
RenderTransformSection(); RenderTransformSection();
ImGui::Separator(); ImGui::Separator();
+5 -1
View File
@@ -52,7 +52,11 @@ private:
void RenderPrimHeader(const pxr::UsdPrim& prim); ///< fixed-height header child void RenderPrimHeader(const pxr::UsdPrim& prim); ///< fixed-height header child
void RenderVariantSetsSection(const pxr::UsdPrim& prim); void RenderVariantSetsSection(const pxr::UsdPrim& prim);
void RenderTransformSection(); void RenderTransformSection();
void RenderPropertiesTable(const pxr::UsdPrim& prim); ///< unified attr+rel table void RenderPropertiesTable(const pxr::UsdPrim& prim); ///< groups attrs by API schema
void RenderMaterialBindSection(const pxr::UsdPrim& prim); ///< material:binding info
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; PropertyManager* m_propertyManager = nullptr;
CommandHistory* m_commandHistory = nullptr; CommandHistory* m_commandHistory = nullptr;