Add preview shape picker to shader ball; drag controls for node properties

Preview geometry dropdown: Sphere / Cube / Cylinder (native prims),
Teapot and Hair (bundled resources/preview/*.usdc - teapot tessellated
from the Newell patch data with the Blinn 1.3x height correction, hair
as 220 procedural B-spline strands), and Cloud Volume (UsdVolVolume
over the OpenVDB bunny_cloud sample, downloaded at CMake configure and
gitignored like the HDRIs). Shapes live under one scope with the
material bound on it; switching activates one shape and reframes the
camera. Missing assets drop out of the dropdown. usdVol added to the
USD link set. The camera starts in a Maya-style 3/4 view.

Node property values switch from Input* fields back to draggable
controls (Ctrl+click to type): live-apply while dragging, one undo
command on release.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-06 08:39:16 +08:00
parent 6b8cb2406e
commit 0f30e080e3
8 changed files with 181 additions and 31 deletions
+20 -23
View File
@@ -293,6 +293,7 @@ void MaterialEditorPanel::RenderPreviewPanel() {
m_preview.RenderRendererDropdown(halfWidth);
ImGui::SameLine();
m_preview.RenderLightingDropdown(halfWidth);
m_preview.RenderShapeDropdown(-1.0f);
if (m_stage && !m_materialPath.IsEmpty()) {
// Hypershade-style: with a node selected, the ball previews that
@@ -454,12 +455,13 @@ void MaterialEditorPanel::RenderInputValueWidget(const ShaderGraphNode& node,
bool authored = false;
pxr::VtValue current = ReadInputValue(prim, node.shaderId, input, &authored);
// Widget vocabulary mirrors PropertyPanel's DrawVtValueWidget (Input*
// fields with %.4f, applied on IsItemDeactivatedAfterEdit; ColorEdit
// applied live) — plus this panel's undo convention: pre-edit state is
// stashed on activation and one undoable command is pushed per edit.
pxr::VtValue newValue; // applied live (colors, checkbox) as it changes
pxr::VtValue finalValue; // applied + committed on deactivate (Input* fields)
// PropertyPanel-style table around draggable controls: Drag* widgets
// (Ctrl+click to type exact values) apply live every frame so the shader
// ball and viewport track the drag, with this panel's undo convention
// pre-edit state stashed on activation, one undoable command pushed when
// the edit ends (IsItemDeactivatedAfterEdit).
pxr::VtValue newValue; // applied live as it changes (drags, colors, checkbox)
pxr::VtValue finalValue; // applied + committed on deactivate (text fields)
auto scalarAsFloat = [&current]() -> float {
if (current.IsHolding<float>()) return current.UncheckedGet<float>();
@@ -482,34 +484,29 @@ void MaterialEditorPanel::RenderInputValueWidget(const ShaderGraphNode& node,
} else if (type == tn->Float3 || type == tn->Vector3f || type == tn->Normal3f || type == tn->Point3f) {
pxr::GfVec3f v = current.IsHolding<pxr::GfVec3f>() ? current.UncheckedGet<pxr::GfVec3f>()
: pxr::GfVec3f(0.0f);
ImGui::InputScalarN(widgetId.c_str(), ImGuiDataType_Float, v.data(), 3, nullptr, nullptr, "%.4f");
if (ImGui::IsItemDeactivatedAfterEdit())
finalValue = v;
if (ImGui::DragFloat3(widgetId.c_str(), v.data(), 0.01f, 0.f, 0.f, "%.4f"))
newValue = v;
} else if (type == tn->Float2 || type == tn->TexCoord2f) {
pxr::GfVec2f v = current.IsHolding<pxr::GfVec2f>() ? current.UncheckedGet<pxr::GfVec2f>()
: pxr::GfVec2f(0.0f);
ImGui::InputScalarN(widgetId.c_str(), ImGuiDataType_Float, v.data(), 2, nullptr, nullptr, "%.4f");
if (ImGui::IsItemDeactivatedAfterEdit())
finalValue = v;
if (ImGui::DragFloat2(widgetId.c_str(), v.data(), 0.01f, 0.f, 0.f, "%.4f"))
newValue = v;
} else if (type == tn->Float4) {
pxr::GfVec4f v = current.IsHolding<pxr::GfVec4f>() ? current.UncheckedGet<pxr::GfVec4f>()
: pxr::GfVec4f(0.0f);
ImGui::InputScalarN(widgetId.c_str(), ImGuiDataType_Float, v.data(), 4, nullptr, nullptr, "%.4f");
if (ImGui::IsItemDeactivatedAfterEdit())
finalValue = v;
if (ImGui::DragFloat4(widgetId.c_str(), v.data(), 0.01f, 0.f, 0.f, "%.4f"))
newValue = v;
} else if (type == tn->Float || type == tn->Double || type == tn->Half) {
float f = scalarAsFloat();
ImGui::InputFloat(widgetId.c_str(), &f, 0.f, 0.f, "%.4f");
if (ImGui::IsItemDeactivatedAfterEdit()) {
if (type == tn->Double) finalValue = static_cast<double>(f);
else if (type == tn->Half) finalValue = pxr::GfHalf(f);
else finalValue = f;
if (ImGui::DragFloat(widgetId.c_str(), &f, 0.01f, 0.f, 0.f, "%.4f")) {
if (type == tn->Double) newValue = static_cast<double>(f);
else if (type == tn->Half) newValue = pxr::GfHalf(f);
else newValue = f;
}
} else if (type == tn->Int) {
int v = current.IsHolding<int>() ? current.UncheckedGet<int>() : 0;
ImGui::InputInt(widgetId.c_str(), &v);
if (ImGui::IsItemDeactivatedAfterEdit())
finalValue = v;
if (ImGui::DragInt(widgetId.c_str(), &v))
newValue = v;
} else if (type == tn->Bool) {
bool v = current.IsHolding<bool>() && current.UncheckedGet<bool>();
if (ImGui::Checkbox(widgetId.c_str(), &v))