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:
+364
-40
@@ -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(×);
|
||||||
|
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();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user