Add Hypershade-style Material Editor with node graph and shader-ball preview

Browser column lists all scene materials plus a searchable create-node
list; Show Graph (or double-click) loads a material into the node-graph
work area. The graph shows the entire network including MaterialX
(.mtlx) node graphs, resolving connections through NodeGraph boundaries
via UsdShadeUtils::GetValueProducingAttributes, with layered auto-layout
for nodes lacking authored uiPosition. Canvas navigates viewport-style
(Alt+MMB pan / Alt+RMB zoom) and TAB opens a Nuke-style search popup.
Selecting a node shows a typed property editor (live-apply, one undo
command per edit) and previews that node's output on the shader ball.

The preview renders through its own Hydra engine into a scratch stage
that composes the material via a reference to the source root layer (so
referenced .mtlx materials work), re-renders until progressive delegates
(Arnold/Cycles/Embree) converge, and lights with HDR dome presets
(External/Room/Interior/Sunset; CC0 Poly Haven EXRs fetched at CMake
configure). texture:format is authored latlong explicitly - left
automatic, hdArnold falls back to Arnold's angular fisheye default -
and hdCycles gets a -90 X pole rotation to match Storm's +Y-pole
sampling.

Mutations go through new ICommand subclasses (create shader node,
connect/disconnect attrs). imgui-node-editor is vendored (gitignored)
with a local one-line patch: c_ScrollButtonIndex 1->2 for MMB pan.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-04 20:44:54 +08:00
parent 338970b243
commit 09819091c4
20 changed files with 2366 additions and 4 deletions
@@ -0,0 +1,63 @@
#include "DisconnectShaderAttrCommand.h"
#include "../../utils/Logger.h"
#include <pxr/usd/usdShade/shader.h>
#include <pxr/usd/usdShade/connectableAPI.h>
#include <pxr/base/tf/token.h>
namespace UsdLayerManager {
DisconnectShaderAttrCommand::DisconnectShaderAttrCommand(pxr::UsdStageRefPtr stage,
const pxr::SdfPath& destNode,
const std::string& destInput)
: m_stage(stage)
, m_destNode(destNode)
, m_destInput(destInput)
, m_description("Disconnect " + destNode.GetName() + "." + destInput)
{
if (!stage) return;
pxr::UsdShadeShader destShader(stage->GetPrimAtPath(destNode));
if (!destShader) return;
pxr::UsdShadeInput input = destShader.GetInput(pxr::TfToken(destInput));
if (!input) return;
for (const auto& source : pxr::UsdShadeConnectableAPI::GetConnectedSources(input)) {
if (!source.IsValid()) continue;
m_hadConnection = true;
m_sourceNode = source.source.GetPrim().GetPath();
m_sourceOutput = source.sourceName.GetString();
break;
}
}
void DisconnectShaderAttrCommand::Execute() {
if (!m_stage) return;
try {
pxr::UsdShadeShader destShader(m_stage->GetPrimAtPath(m_destNode));
if (!destShader) return;
pxr::UsdShadeInput input = destShader.GetInput(pxr::TfToken(m_destInput));
if (!input) return;
pxr::UsdShadeConnectableAPI::DisconnectSource(input);
} catch (const std::exception& e) {
LOG_ERROR(std::string("DisconnectShaderAttrCommand::Execute error: ") + e.what());
}
}
void DisconnectShaderAttrCommand::Undo() {
if (!m_stage || !m_hadConnection) return;
try {
pxr::UsdShadeShader destShader(m_stage->GetPrimAtPath(m_destNode));
pxr::UsdShadeShader sourceShader(m_stage->GetPrimAtPath(m_sourceNode));
if (!destShader || !sourceShader) return;
pxr::UsdShadeInput input = destShader.GetInput(pxr::TfToken(m_destInput));
pxr::UsdShadeOutput output = sourceShader.GetOutput(pxr::TfToken(m_sourceOutput));
if (input && output)
pxr::UsdShadeConnectableAPI::ConnectToSource(input, output);
} catch (const std::exception& e) {
LOG_ERROR(std::string("DisconnectShaderAttrCommand::Undo error: ") + e.what());
}
}
} // namespace UsdLayerManager