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:
@@ -0,0 +1,82 @@
|
||||
#include "ConnectShaderAttrsCommand.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 {
|
||||
|
||||
ConnectShaderAttrsCommand::ConnectShaderAttrsCommand(pxr::UsdStageRefPtr stage,
|
||||
const pxr::SdfPath& destNode,
|
||||
const std::string& destInput,
|
||||
const pxr::SdfValueTypeName& destType,
|
||||
const pxr::SdfPath& sourceNode,
|
||||
const std::string& sourceOutput,
|
||||
const pxr::SdfValueTypeName& sourceType)
|
||||
: m_stage(stage)
|
||||
, m_destNode(destNode)
|
||||
, m_destInput(destInput)
|
||||
, m_destType(destType)
|
||||
, m_sourceNode(sourceNode)
|
||||
, m_sourceOutput(sourceOutput)
|
||||
, m_sourceType(sourceType)
|
||||
, m_description("Connect " + sourceNode.GetName() + "." + sourceOutput +
|
||||
" -> " + destNode.GetName() + "." + destInput)
|
||||
{
|
||||
if (!stage) return;
|
||||
|
||||
pxr::UsdShadeShader destShader(stage->GetPrimAtPath(destNode));
|
||||
if (!destShader) return;
|
||||
|
||||
pxr::UsdShadeInput existingInput = destShader.GetInput(pxr::TfToken(destInput));
|
||||
if (!existingInput) return;
|
||||
|
||||
for (const auto& source : pxr::UsdShadeConnectableAPI::GetConnectedSources(existingInput)) {
|
||||
if (!source.IsValid()) continue;
|
||||
m_hadPriorConnection = true;
|
||||
m_priorSourceNode = source.source.GetPrim().GetPath();
|
||||
m_priorSourceOutput = source.sourceName.GetString();
|
||||
break; // shader inputs carry a single upstream connection in this editor's model
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectShaderAttrsCommand::Execute() {
|
||||
if (!m_stage) return;
|
||||
try {
|
||||
pxr::UsdShadeShader destShader(m_stage->GetPrimAtPath(m_destNode));
|
||||
pxr::UsdShadeShader sourceShader(m_stage->GetPrimAtPath(m_sourceNode));
|
||||
if (!destShader || !sourceShader) {
|
||||
LOG_ERROR("ConnectShaderAttrsCommand: invalid shader prim(s)");
|
||||
return;
|
||||
}
|
||||
|
||||
pxr::UsdShadeInput destInput = destShader.CreateInput(pxr::TfToken(m_destInput), m_destType);
|
||||
pxr::UsdShadeOutput sourceOutput = sourceShader.CreateOutput(pxr::TfToken(m_sourceOutput), m_sourceType);
|
||||
pxr::UsdShadeConnectableAPI::ConnectToSource(destInput, sourceOutput);
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR(std::string("ConnectShaderAttrsCommand::Execute error: ") + e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectShaderAttrsCommand::Undo() {
|
||||
if (!m_stage) return;
|
||||
try {
|
||||
pxr::UsdShadeShader destShader(m_stage->GetPrimAtPath(m_destNode));
|
||||
if (!destShader) return;
|
||||
pxr::UsdShadeInput destInput = destShader.GetInput(pxr::TfToken(m_destInput));
|
||||
if (!destInput) return;
|
||||
|
||||
if (m_hadPriorConnection) {
|
||||
pxr::UsdShadeShader priorSourceShader(m_stage->GetPrimAtPath(m_priorSourceNode));
|
||||
pxr::UsdShadeOutput priorSourceOutput = priorSourceShader.GetOutput(pxr::TfToken(m_priorSourceOutput));
|
||||
if (priorSourceShader && priorSourceOutput)
|
||||
pxr::UsdShadeConnectableAPI::ConnectToSource(destInput, priorSourceOutput);
|
||||
} else {
|
||||
pxr::UsdShadeConnectableAPI::DisconnectSource(destInput);
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR(std::string("ConnectShaderAttrsCommand::Undo error: ") + e.what());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace UsdLayerManager
|
||||
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include "../CommandHistory.h"
|
||||
#include <pxr/usd/usd/stage.h>
|
||||
#include <pxr/usd/sdf/path.h>
|
||||
#include <pxr/usd/sdf/valueTypeName.h>
|
||||
#include <string>
|
||||
|
||||
namespace UsdLayerManager {
|
||||
|
||||
/// Connects a shader input to an upstream shader output, sparsely authoring
|
||||
/// both attributes (with the given types) if they don't already exist.
|
||||
/// Captures whatever the input was previously connected to (if anything) so
|
||||
/// Undo can restore it exactly rather than merely disconnecting.
|
||||
class ConnectShaderAttrsCommand : public ICommand {
|
||||
public:
|
||||
ConnectShaderAttrsCommand(pxr::UsdStageRefPtr stage,
|
||||
const pxr::SdfPath& destNode,
|
||||
const std::string& destInput,
|
||||
const pxr::SdfValueTypeName& destType,
|
||||
const pxr::SdfPath& sourceNode,
|
||||
const std::string& sourceOutput,
|
||||
const pxr::SdfValueTypeName& sourceType);
|
||||
|
||||
void Execute() override;
|
||||
void Undo() override;
|
||||
std::string GetDescription() const override { return m_description; }
|
||||
|
||||
private:
|
||||
pxr::UsdStageRefPtr m_stage;
|
||||
pxr::SdfPath m_destNode;
|
||||
std::string m_destInput;
|
||||
pxr::SdfValueTypeName m_destType;
|
||||
pxr::SdfPath m_sourceNode;
|
||||
std::string m_sourceOutput;
|
||||
pxr::SdfValueTypeName m_sourceType;
|
||||
std::string m_description;
|
||||
|
||||
bool m_hadPriorConnection = false;
|
||||
pxr::SdfPath m_priorSourceNode;
|
||||
std::string m_priorSourceOutput;
|
||||
};
|
||||
|
||||
} // namespace UsdLayerManager
|
||||
@@ -0,0 +1,46 @@
|
||||
#include "CreateShaderNodeCommand.h"
|
||||
#include "../../utils/Logger.h"
|
||||
#include <pxr/usd/usdShade/shader.h>
|
||||
#include <pxr/base/tf/token.h>
|
||||
#include <pxr/base/vt/value.h>
|
||||
|
||||
namespace UsdLayerManager {
|
||||
|
||||
CreateShaderNodeCommand::CreateShaderNodeCommand(pxr::UsdStageRefPtr stage,
|
||||
const pxr::SdfPath& path,
|
||||
const std::string& shaderId,
|
||||
const pxr::GfVec2f& uiPosition)
|
||||
: m_stage(stage)
|
||||
, m_path(path)
|
||||
, m_shaderId(shaderId)
|
||||
, m_uiPosition(uiPosition)
|
||||
, m_description("Create Shader " + shaderId + " " + path.GetString())
|
||||
{
|
||||
}
|
||||
|
||||
void CreateShaderNodeCommand::Execute() {
|
||||
if (!m_stage) return;
|
||||
try {
|
||||
pxr::UsdShadeShader shader = pxr::UsdShadeShader::Define(m_stage, m_path);
|
||||
if (!shader) {
|
||||
LOG_ERROR("CreateShaderNodeCommand: failed to define shader " + m_path.GetString());
|
||||
return;
|
||||
}
|
||||
shader.SetShaderId(pxr::TfToken(m_shaderId));
|
||||
shader.GetPrim().SetCustomDataByKey(pxr::TfToken("uiPosition"), pxr::VtValue(m_uiPosition));
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR(std::string("CreateShaderNodeCommand::Execute error: ") + e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void CreateShaderNodeCommand::Undo() {
|
||||
if (!m_stage) return;
|
||||
try {
|
||||
if (!m_stage->RemovePrim(m_path))
|
||||
LOG_ERROR("CreateShaderNodeCommand: failed to remove prim " + m_path.GetString());
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR(std::string("CreateShaderNodeCommand::Undo error: ") + e.what());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace UsdLayerManager
|
||||
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "../CommandHistory.h"
|
||||
#include <pxr/usd/usd/stage.h>
|
||||
#include <pxr/usd/sdf/path.h>
|
||||
#include <pxr/base/gf/vec2f.h>
|
||||
#include <string>
|
||||
|
||||
namespace UsdLayerManager {
|
||||
|
||||
/// Creates a UsdShadeShader prim with the given Sdr shader identifier and
|
||||
/// authors its canvas position as uiPosition custom data (see
|
||||
/// docs/adr — Material Editor node-graph layout is stored as prim
|
||||
/// customData, not scene data, so it never affects composition/renders).
|
||||
class CreateShaderNodeCommand : public ICommand {
|
||||
public:
|
||||
CreateShaderNodeCommand(pxr::UsdStageRefPtr stage,
|
||||
const pxr::SdfPath& path,
|
||||
const std::string& shaderId,
|
||||
const pxr::GfVec2f& uiPosition);
|
||||
|
||||
void Execute() override;
|
||||
void Undo() override;
|
||||
std::string GetDescription() const override { return m_description; }
|
||||
|
||||
private:
|
||||
pxr::UsdStageRefPtr m_stage;
|
||||
pxr::SdfPath m_path;
|
||||
std::string m_shaderId;
|
||||
pxr::GfVec2f m_uiPosition;
|
||||
std::string m_description;
|
||||
};
|
||||
|
||||
} // namespace UsdLayerManager
|
||||
@@ -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
|
||||
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include "../CommandHistory.h"
|
||||
#include <pxr/usd/usd/stage.h>
|
||||
#include <pxr/usd/sdf/path.h>
|
||||
#include <string>
|
||||
|
||||
namespace UsdLayerManager {
|
||||
|
||||
/// Disconnects a shader input from its upstream source, capturing that
|
||||
/// source at construction time so Undo can restore it.
|
||||
class DisconnectShaderAttrCommand : public ICommand {
|
||||
public:
|
||||
DisconnectShaderAttrCommand(pxr::UsdStageRefPtr stage,
|
||||
const pxr::SdfPath& destNode,
|
||||
const std::string& destInput);
|
||||
|
||||
void Execute() override;
|
||||
void Undo() override;
|
||||
std::string GetDescription() const override { return m_description; }
|
||||
|
||||
private:
|
||||
pxr::UsdStageRefPtr m_stage;
|
||||
pxr::SdfPath m_destNode;
|
||||
std::string m_destInput;
|
||||
std::string m_description;
|
||||
|
||||
bool m_hadConnection = false;
|
||||
pxr::SdfPath m_sourceNode;
|
||||
std::string m_sourceOutput;
|
||||
};
|
||||
|
||||
} // namespace UsdLayerManager
|
||||
Reference in New Issue
Block a user