Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0640201d5b | |||
| f7a2262e80 |
@@ -115,6 +115,11 @@ bool Application::Initialize(const std::string& windowTitle, int width, int heig
|
|||||||
m_curveEditorPanel->SetSelectedPrimPath(path);
|
m_curveEditorPanel->SetSelectedPrimPath(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
m_sceneHierarchyPanel->SetOnPrimsSelected(
|
||||||
|
[this](const std::vector<std::string>& paths) {
|
||||||
|
m_viewportPanel->SetSelectedPrimPaths(paths);
|
||||||
|
});
|
||||||
|
|
||||||
m_sceneHierarchyPanel->SetOnStageMetadataChanged(
|
m_sceneHierarchyPanel->SetOnStageMetadataChanged(
|
||||||
[this]() {
|
[this]() {
|
||||||
RefreshManagers();
|
RefreshManagers();
|
||||||
|
|||||||
+360
-36
@@ -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
|
||||||
|
|
||||||
@@ -51,7 +59,9 @@ namespace UsdLayerManager {
|
|||||||
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))
|
||||||
|
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("##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;
|
||||||
|
|||||||
@@ -86,9 +86,58 @@ void SceneHierarchyPanel::SetSelectedPathFromClick(const std::string& path) {
|
|||||||
m_primarySelectedPath = path;
|
m_primarySelectedPath = path;
|
||||||
m_primarySdfPath = path.empty() ? SdfPath() : SdfPath(path);
|
m_primarySdfPath = path.empty() ? SdfPath() : SdfPath(path);
|
||||||
if (!path.empty()) m_selectedPaths.insert(path);
|
if (!path.empty()) m_selectedPaths.insert(path);
|
||||||
// No scroll — user clicked the item directly, it's already visible.
|
if (!path.empty()) m_rangeAnchorPath = SdfPath(path);
|
||||||
m_scrollToSelected = false;
|
m_scrollToSelected = false;
|
||||||
if (m_onPrimSelected) m_onPrimSelected(path);
|
if (m_onPrimSelected) m_onPrimSelected(path);
|
||||||
|
if (m_onPrimsSelected) {
|
||||||
|
std::vector<std::string> v;
|
||||||
|
if (!path.empty()) v.push_back(path);
|
||||||
|
m_onPrimsSelected(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneHierarchyPanel::ToggleSelectionFromClick(const std::string& path) {
|
||||||
|
if (m_selectedPaths.count(path)) {
|
||||||
|
m_selectedPaths.erase(path);
|
||||||
|
if (m_primarySelectedPath == path) {
|
||||||
|
m_primarySelectedPath = m_selectedPaths.empty() ? "" : *m_selectedPaths.begin();
|
||||||
|
m_primarySdfPath = m_primarySelectedPath.empty()
|
||||||
|
? SdfPath() : SdfPath(m_primarySelectedPath);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m_selectedPaths.insert(path);
|
||||||
|
m_primarySelectedPath = path;
|
||||||
|
m_primarySdfPath = SdfPath(path);
|
||||||
|
m_rangeAnchorPath = SdfPath(path);
|
||||||
|
}
|
||||||
|
m_scrollToSelected = false;
|
||||||
|
if (m_onPrimSelected) m_onPrimSelected(m_primarySelectedPath);
|
||||||
|
if (m_onPrimsSelected) {
|
||||||
|
std::vector<std::string> v(m_selectedPaths.begin(), m_selectedPaths.end());
|
||||||
|
m_onPrimsSelected(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneHierarchyPanel::RangeSelectToPath(const SdfPath& path) {
|
||||||
|
auto it1 = std::find(m_visiblePrimOrder.begin(), m_visiblePrimOrder.end(), m_rangeAnchorPath);
|
||||||
|
auto it2 = std::find(m_visiblePrimOrder.begin(), m_visiblePrimOrder.end(), path);
|
||||||
|
if (it1 != m_visiblePrimOrder.end() && it2 != m_visiblePrimOrder.end()) {
|
||||||
|
int idx1 = static_cast<int>(it1 - m_visiblePrimOrder.begin());
|
||||||
|
int idx2 = static_cast<int>(it2 - m_visiblePrimOrder.begin());
|
||||||
|
if (idx1 > idx2) std::swap(idx1, idx2);
|
||||||
|
for (int i = idx1; i <= idx2; ++i)
|
||||||
|
m_selectedPaths.insert(m_visiblePrimOrder[i].GetString());
|
||||||
|
} else {
|
||||||
|
m_selectedPaths.insert(path.GetString());
|
||||||
|
}
|
||||||
|
m_primarySelectedPath = path.GetString();
|
||||||
|
m_primarySdfPath = path;
|
||||||
|
m_scrollToSelected = false;
|
||||||
|
if (m_onPrimSelected) m_onPrimSelected(m_primarySelectedPath);
|
||||||
|
if (m_onPrimsSelected) {
|
||||||
|
std::vector<std::string> v(m_selectedPaths.begin(), m_selectedPaths.end());
|
||||||
|
m_onPrimsSelected(v);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* SceneHierarchyPanel::GetPrimTypeIcon(const UsdPrim& prim) const {
|
const char* SceneHierarchyPanel::GetPrimTypeIcon(const UsdPrim& prim) const {
|
||||||
@@ -185,6 +234,26 @@ void SceneHierarchyPanel::HandleKeyboardShortcuts() {
|
|||||||
SetSelectedPathFromClick(groupPath.GetString());
|
SetSelectedPathFromClick(groupPath.GetString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ctrl+H — hide selected prims (visibility = invisible).
|
||||||
|
if (ImGui::GetIO().KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_H, false) &&
|
||||||
|
!m_selectedPaths.empty() && m_stage) {
|
||||||
|
for (const auto& pathStr : m_selectedPaths) {
|
||||||
|
UsdPrim prim = m_stage->GetPrimAtPath(SdfPath(pathStr));
|
||||||
|
if (prim.IsValid() && prim.IsA<UsdGeomImageable>())
|
||||||
|
UsdGeomImageable(prim).GetVisibilityAttr().Set(UsdGeomTokens->invisible);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shift+H — show selected prims (visibility = inherited).
|
||||||
|
if (ImGui::GetIO().KeyShift && ImGui::IsKeyPressed(ImGuiKey_H, false) &&
|
||||||
|
!m_selectedPaths.empty() && m_stage) {
|
||||||
|
for (const auto& pathStr : m_selectedPaths) {
|
||||||
|
UsdPrim prim = m_stage->GetPrimAtPath(SdfPath(pathStr));
|
||||||
|
if (prim.IsValid() && prim.IsA<UsdGeomImageable>())
|
||||||
|
UsdGeomImageable(prim).GetVisibilityAttr().Set(UsdGeomTokens->inherited);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Delete — open the remove-prim confirmation modal.
|
// Delete — open the remove-prim confirmation modal.
|
||||||
if (ImGui::IsKeyPressed(ImGuiKey_Delete, false) && !m_primarySdfPath.IsEmpty()) {
|
if (ImGui::IsKeyPressed(ImGuiKey_Delete, false) && !m_primarySdfPath.IsEmpty()) {
|
||||||
auto rootLayer = m_stage->GetRootLayer();
|
auto rootLayer = m_stage->GetRootLayer();
|
||||||
@@ -266,6 +335,9 @@ void SceneHierarchyPanel::Render() {
|
|||||||
} else {
|
} else {
|
||||||
UsdPrim root = m_stage->GetPseudoRoot();
|
UsdPrim root = m_stage->GetPseudoRoot();
|
||||||
|
|
||||||
|
// Rebuilt each frame as RenderPrimNode visits prims — used for range select.
|
||||||
|
m_visiblePrimOrder.clear();
|
||||||
|
|
||||||
// Rebuild local-layer set once per frame (used by RenderPrimNode to
|
// Rebuild local-layer set once per frame (used by RenderPrimNode to
|
||||||
// detect attribute overrides). GetLayerStack() returns only the stage's
|
// detect attribute overrides). GetLayerStack() returns only the stage's
|
||||||
// own layers — root layer, sublayers, session layer — NOT reference layers.
|
// own layers — root layer, sublayers, session layer — NOT reference layers.
|
||||||
@@ -288,12 +360,14 @@ void SceneHierarchyPanel::Render() {
|
|||||||
ImGuiTableFlags_RowBg |
|
ImGuiTableFlags_RowBg |
|
||||||
ImGuiTableFlags_SizingFixedFit;
|
ImGuiTableFlags_SizingFixedFit;
|
||||||
|
|
||||||
if (ImGui::BeginTable("##primtree", 4, tblFlags)) {
|
const float kSpecW = ImGui::CalcTextSize("over").x + 2.f;
|
||||||
// Col 0 stretches; cols 1-3 are small fixed-width icon columns.
|
if (ImGui::BeginTable("##primtree", 5, tblFlags)) {
|
||||||
|
// Col 0 stretches; cols 1-4 are small fixed-width columns.
|
||||||
ImGui::TableSetupColumn("##prim", ImGuiTableColumnFlags_WidthStretch);
|
ImGui::TableSetupColumn("##prim", ImGuiTableColumnFlags_WidthStretch);
|
||||||
ImGui::TableSetupColumn("##type", ImGuiTableColumnFlags_WidthFixed, kIconW);
|
ImGui::TableSetupColumn("##type", ImGuiTableColumnFlags_WidthFixed, kIconW);
|
||||||
ImGui::TableSetupColumn("##vis", ImGuiTableColumnFlags_WidthFixed, kIconW);
|
ImGui::TableSetupColumn("##vis", ImGuiTableColumnFlags_WidthFixed, kIconW);
|
||||||
ImGui::TableSetupColumn("##ref", ImGuiTableColumnFlags_WidthFixed, kIconW);
|
ImGui::TableSetupColumn("##ref", ImGuiTableColumnFlags_WidthFixed, kIconW);
|
||||||
|
ImGui::TableSetupColumn("##spec", ImGuiTableColumnFlags_WidthFixed, kSpecW);
|
||||||
|
|
||||||
for (const auto& child : root.GetChildren())
|
for (const auto& child : root.GetChildren())
|
||||||
RenderPrimNode(child);
|
RenderPrimNode(child);
|
||||||
@@ -524,6 +598,7 @@ void SceneHierarchyPanel::RenderPrimNode(const UsdPrim& prim) {
|
|||||||
bool isInvisible = false;
|
bool isInvisible = false;
|
||||||
bool hasRefs = prim.HasAuthoredReferences();
|
bool hasRefs = prim.HasAuthoredReferences();
|
||||||
bool hasChildren = !prim.GetChildren().empty();
|
bool hasChildren = !prim.GetChildren().empty();
|
||||||
|
SdfSpecifier spec = prim.GetSpecifier();
|
||||||
|
|
||||||
if (isImageable) {
|
if (isImageable) {
|
||||||
UsdGeomImageable img(prim);
|
UsdGeomImageable img(prim);
|
||||||
@@ -565,6 +640,7 @@ void SceneHierarchyPanel::RenderPrimNode(const UsdPrim& prim) {
|
|||||||
// ────────────────────────────────────────────────────────────────────────
|
// ────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
ImGui::TableNextRow();
|
ImGui::TableNextRow();
|
||||||
|
m_visiblePrimOrder.push_back(primPath); // for Shift+LMB range select
|
||||||
ImGui::TableNextColumn(); // Col 0 — prim name + tree arrow
|
ImGui::TableNextColumn(); // Col 0 — prim name + tree arrow
|
||||||
|
|
||||||
ImGui::PushID(primStr.c_str());
|
ImGui::PushID(primStr.c_str());
|
||||||
@@ -642,6 +718,7 @@ void SceneHierarchyPanel::RenderPrimNode(const UsdPrim& prim) {
|
|||||||
ImGui::TableNextColumn(); // Col 1
|
ImGui::TableNextColumn(); // Col 1
|
||||||
ImGui::TableNextColumn(); // Col 2
|
ImGui::TableNextColumn(); // Col 2
|
||||||
ImGui::TableNextColumn(); // Col 3
|
ImGui::TableNextColumn(); // Col 3
|
||||||
|
ImGui::TableNextColumn(); // Col 4
|
||||||
if (open && hasChildren)
|
if (open && hasChildren)
|
||||||
ImGui::TreePop();
|
ImGui::TreePop();
|
||||||
ImGui::PopID();
|
ImGui::PopID();
|
||||||
@@ -655,9 +732,16 @@ void SceneHierarchyPanel::RenderPrimNode(const UsdPrim& prim) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!isRenaming) {
|
if (!isRenaming) {
|
||||||
// Selection on click (not on toggle arrow).
|
// Selection on LMB click (not on toggle arrow).
|
||||||
if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen())
|
// Shift+LMB: range-select from anchor. Ctrl+LMB: toggle. Plain: replace.
|
||||||
|
if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen()) {
|
||||||
|
if (ImGui::GetIO().KeyShift)
|
||||||
|
RangeSelectToPath(primPath);
|
||||||
|
else if (ImGui::GetIO().KeyCtrl)
|
||||||
|
ToggleSelectionFromClick(primStr);
|
||||||
|
else
|
||||||
SetSelectedPathFromClick(primStr);
|
SetSelectedPathFromClick(primStr);
|
||||||
|
}
|
||||||
|
|
||||||
// Double-click to start inline rename.
|
// Double-click to start inline rename.
|
||||||
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
|
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
|
||||||
@@ -705,7 +789,7 @@ void SceneHierarchyPanel::RenderPrimNode(const UsdPrim& prim) {
|
|||||||
ImGui::EndDragDropTarget();
|
ImGui::EndDragDropTarget();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Context menu (must follow the last widget = the tree node).
|
// Context menu on RMB.
|
||||||
RenderContextMenu(prim);
|
RenderContextMenu(prim);
|
||||||
|
|
||||||
// A context menu op (Unparent, Group, etc.) may have moved this prim,
|
// A context menu op (Unparent, Group, etc.) may have moved this prim,
|
||||||
@@ -714,6 +798,7 @@ void SceneHierarchyPanel::RenderPrimNode(const UsdPrim& prim) {
|
|||||||
ImGui::TableNextColumn(); // Col 1
|
ImGui::TableNextColumn(); // Col 1
|
||||||
ImGui::TableNextColumn(); // Col 2
|
ImGui::TableNextColumn(); // Col 2
|
||||||
ImGui::TableNextColumn(); // Col 3
|
ImGui::TableNextColumn(); // Col 3
|
||||||
|
ImGui::TableNextColumn(); // Col 4
|
||||||
if (open && hasChildren)
|
if (open && hasChildren)
|
||||||
ImGui::TreePop();
|
ImGui::TreePop();
|
||||||
ImGui::PopID();
|
ImGui::PopID();
|
||||||
@@ -740,8 +825,9 @@ void SceneHierarchyPanel::RenderPrimNode(const UsdPrim& prim) {
|
|||||||
const float iconSz = ImGui::GetTextLineHeight();
|
const float iconSz = ImGui::GetTextLineHeight();
|
||||||
const ImVec2 iconVec(iconSz, iconSz);
|
const ImVec2 iconVec(iconSz, iconSz);
|
||||||
Icon visIcon = isInvisible ? Icon::EyeSlash : Icon::Eye;
|
Icon visIcon = isInvisible ? Icon::EyeSlash : Icon::Eye;
|
||||||
ImVec4 visTint = isInvisible ? ImVec4(0.45f, 0.45f, 0.45f, 0.6f)
|
// Invisible: EyeSlash in orange; Visible: Eye at full alpha (dimmer tint).
|
||||||
: ImVec4(0.9f, 0.9f, 0.9f, 1.0f);
|
ImVec4 visTint = isInvisible ? ImVec4(1.00f, 0.60f, 0.15f, 1.0f)
|
||||||
|
: ImVec4(0.60f, 0.60f, 0.60f, 1.0f);
|
||||||
ImTextureID id = m_iconManager ? m_iconManager->Get(visIcon) : ImTextureID_Invalid;
|
ImTextureID id = m_iconManager ? m_iconManager->Get(visIcon) : ImTextureID_Invalid;
|
||||||
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0,0,0,0));
|
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0,0,0,0));
|
||||||
@@ -750,7 +836,8 @@ void SceneHierarchyPanel::RenderPrimNode(const UsdPrim& prim) {
|
|||||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0,0));
|
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0,0));
|
||||||
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 1.f);
|
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 1.f);
|
||||||
|
|
||||||
if (ImGui::ImageButton("##vis", ImTextureRef(id), iconVec,
|
std::string visId = "##vis_" + primStr;
|
||||||
|
if (ImGui::ImageButton(visId.c_str(), ImTextureRef(id), iconVec,
|
||||||
ImVec2(0,0), ImVec2(1,1), ImVec4(0,0,0,0), visTint)) {
|
ImVec2(0,0), ImVec2(1,1), ImVec4(0,0,0,0), visTint)) {
|
||||||
try {
|
try {
|
||||||
UsdGeomImageable img(prim);
|
UsdGeomImageable img(prim);
|
||||||
@@ -789,6 +876,28 @@ void SceneHierarchyPanel::RenderPrimNode(const UsdPrim& prim) {
|
|||||||
ImGui::SetTooltip("Has references");
|
ImGui::SetTooltip("Has references");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Col 4: Specifier (def / over / class) ──────────────────────────────
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
{
|
||||||
|
const char* label = nullptr;
|
||||||
|
ImVec4 color;
|
||||||
|
switch (spec) {
|
||||||
|
case SdfSpecifierOver:
|
||||||
|
label = "over";
|
||||||
|
color = ImVec4(1.00f, 0.60f, 0.10f, isActive ? 1.0f : 0.45f);
|
||||||
|
break;
|
||||||
|
case SdfSpecifierClass:
|
||||||
|
label = "class";
|
||||||
|
color = ImVec4(0.40f, 0.70f, 1.00f, isActive ? 1.0f : 0.45f);
|
||||||
|
break;
|
||||||
|
default: // SdfSpecifierDef
|
||||||
|
label = "def";
|
||||||
|
color = ImVec4(0.45f, 0.45f, 0.45f, isActive ? 0.55f : 0.30f);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ImGui::TextColored(color, "%s", label);
|
||||||
|
}
|
||||||
|
|
||||||
// ── Recurse into children ───────────────────────────────────────────────
|
// ── Recurse into children ───────────────────────────────────────────────
|
||||||
// TreePop must be called in the SAME column as TreeNodeEx (col 0).
|
// TreePop must be called in the SAME column as TreeNodeEx (col 0).
|
||||||
// Since we called TableNextColumn three more times above, we must move
|
// Since we called TableNextColumn three more times above, we must move
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "../core/PropertyManager.h"
|
#include "../core/PropertyManager.h"
|
||||||
#include "../core/CommandHistory.h"
|
#include "../core/CommandHistory.h"
|
||||||
@@ -62,6 +62,9 @@ public:
|
|||||||
using PrimSelectCallback = std::function<void(const std::string& path)>;
|
using PrimSelectCallback = std::function<void(const std::string& path)>;
|
||||||
void SetOnPrimSelected(PrimSelectCallback callback) { m_onPrimSelected = callback; }
|
void SetOnPrimSelected(PrimSelectCallback callback) { m_onPrimSelected = callback; }
|
||||||
|
|
||||||
|
using MultiPrimSelectCallback = std::function<void(const std::vector<std::string>&)>;
|
||||||
|
void SetOnPrimsSelected(MultiPrimSelectCallback cb) { m_onPrimsSelected = cb; }
|
||||||
|
|
||||||
/// Called when stage-level metadata (e.g. up axis) is changed via the hierarchy panel.
|
/// Called when stage-level metadata (e.g. up axis) is changed via the hierarchy panel.
|
||||||
using StageMetadataChangedCallback = std::function<void()>;
|
using StageMetadataChangedCallback = std::function<void()>;
|
||||||
void SetOnStageMetadataChanged(StageMetadataChangedCallback callback) { m_onStageMetadataChanged = callback; }
|
void SetOnStageMetadataChanged(StageMetadataChangedCallback callback) { m_onStageMetadataChanged = callback; }
|
||||||
@@ -96,7 +99,12 @@ private:
|
|||||||
/// NOT layers that came in through references or payloads.
|
/// NOT layers that came in through references or payloads.
|
||||||
std::unordered_set<std::string> m_localLayers;
|
std::unordered_set<std::string> m_localLayers;
|
||||||
|
|
||||||
|
/// Visible prim order rebuilt each frame — used for Shift+RMB range select.
|
||||||
|
std::vector<pxr::SdfPath> m_visiblePrimOrder;
|
||||||
|
pxr::SdfPath m_rangeAnchorPath;
|
||||||
|
|
||||||
PrimSelectCallback m_onPrimSelected;
|
PrimSelectCallback m_onPrimSelected;
|
||||||
|
MultiPrimSelectCallback m_onPrimsSelected;
|
||||||
StageMetadataChangedCallback m_onStageMetadataChanged;
|
StageMetadataChangedCallback m_onStageMetadataChanged;
|
||||||
|
|
||||||
/// Remove-prim confirmation state.
|
/// Remove-prim confirmation state.
|
||||||
@@ -121,6 +129,8 @@ private:
|
|||||||
bool m_renameJustStarted = false;
|
bool m_renameJustStarted = false;
|
||||||
|
|
||||||
void HandleKeyboardShortcuts();
|
void HandleKeyboardShortcuts();
|
||||||
|
void ToggleSelectionFromClick(const std::string& path);
|
||||||
|
void RangeSelectToPath(const pxr::SdfPath& path);
|
||||||
SdfPath FindUniqueChildPath(const SdfPath& parent, const std::string& baseName);
|
SdfPath FindUniqueChildPath(const SdfPath& parent, const std::string& baseName);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -138,6 +138,17 @@ void ViewportPanel::SetSelectedPrimPath(const std::string& path)
|
|||||||
BroadcastSelection();
|
BroadcastSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ViewportPanel::SetSelectedPrimPaths(const std::vector<std::string>& paths)
|
||||||
|
{
|
||||||
|
m_selectedSdfPaths.clear();
|
||||||
|
m_selectedPrimPath.clear();
|
||||||
|
for (const auto& p : paths)
|
||||||
|
m_selectedSdfPaths.push_back(pxr::SdfPath(p));
|
||||||
|
if (!paths.empty())
|
||||||
|
m_selectedPrimPath = paths.front();
|
||||||
|
BroadcastSelection();
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Forwarding accessors
|
// Forwarding accessors
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -384,7 +395,7 @@ void ViewportPanel::RenderGlobalLeftToolbar(ImVec2 contentPos, ImVec2 /*contentS
|
|||||||
const float kIconPad = 5.0f;
|
const float kIconPad = 5.0f;
|
||||||
const float kRounding = 4.0f;
|
const float kRounding = 4.0f;
|
||||||
const float kSpacing = 3.0f;
|
const float kSpacing = 3.0f;
|
||||||
const float kPadX = 9.0f; // left padding inside the strip
|
const float kPadX = 4.0f; // left padding inside the strip
|
||||||
const float kPadY = 10.0f; // top padding
|
const float kPadY = 10.0f; // top padding
|
||||||
const float kSepH = 1.0f; // separator line height
|
const float kSepH = 1.0f; // separator line height
|
||||||
const float kSepGap = 6.0f; // space around separator
|
const float kSepGap = 6.0f; // space around separator
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ public:
|
|||||||
// ── Selection (called by SceneHierarchyPanel) ────────────────────────────
|
// ── Selection (called by SceneHierarchyPanel) ────────────────────────────
|
||||||
/// Set a single selected prim (clears any multi-selection).
|
/// Set a single selected prim (clears any multi-selection).
|
||||||
void SetSelectedPrimPath(const std::string& path);
|
void SetSelectedPrimPath(const std::string& path);
|
||||||
|
/// Set multiple selected prims (from hierarchy Ctrl+click multi-select).
|
||||||
|
void SetSelectedPrimPaths(const std::vector<std::string>& paths);
|
||||||
|
|
||||||
// ── Main render (called from Application::RenderUI) ──────────────────────
|
// ── Main render (called from Application::RenderUI) ──────────────────────
|
||||||
void Render(bool* p_open = nullptr);
|
void Render(bool* p_open = nullptr);
|
||||||
@@ -99,7 +101,7 @@ private:
|
|||||||
bool IsMouseOverDivider(ImVec2 origin, ImVec2 total) const;
|
bool IsMouseOverDivider(ImVec2 origin, ImVec2 total) const;
|
||||||
|
|
||||||
/// Width (px) of the reserved left toolbar strip.
|
/// Width (px) of the reserved left toolbar strip.
|
||||||
static constexpr float kToolbarW = 52.0f;
|
static constexpr float kToolbarW = 40.0f;
|
||||||
|
|
||||||
// ── Selection management ─────────────────────────────────────────────────
|
// ── Selection management ─────────────────────────────────────────────────
|
||||||
/// Push the current shared selection into every tile and the manipulator.
|
/// Push the current shared selection into every tile and the manipulator.
|
||||||
|
|||||||
Reference in New Issue
Block a user