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,145 @@
|
||||
#include "MaterialManager.h"
|
||||
#include <pxr/usd/sdr/registry.h>
|
||||
#include <pxr/usd/usd/primRange.h>
|
||||
#include <pxr/usd/usdShade/material.h>
|
||||
#include <pxr/usd/usdShade/shader.h>
|
||||
#include <pxr/usd/usdShade/nodeGraph.h>
|
||||
#include <pxr/usd/usdShade/connectableAPI.h>
|
||||
#include <pxr/usd/usdShade/utils.h>
|
||||
#include <pxr/base/tf/token.h>
|
||||
#include <pxr/base/vt/value.h>
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
|
||||
namespace UsdLayerManager {
|
||||
|
||||
const std::vector<ShaderNodeTypeInfo>& MaterialManager::GetAvailableShaderNodes() {
|
||||
if (m_shaderNodeCacheBuilt)
|
||||
return m_shaderNodeCache;
|
||||
|
||||
for (pxr::SdrShaderNodeConstPtr node : pxr::SdrRegistry::GetInstance().GetShaderNodesByFamily()) {
|
||||
if (!node) continue;
|
||||
|
||||
ShaderNodeTypeInfo info;
|
||||
info.identifier = node->GetIdentifier().GetString();
|
||||
info.label = !node->GetLabel().IsEmpty() ? node->GetLabel().GetString() : node->GetName();
|
||||
info.family = node->GetFamily().GetString();
|
||||
m_shaderNodeCache.push_back(std::move(info));
|
||||
}
|
||||
|
||||
std::sort(m_shaderNodeCache.begin(), m_shaderNodeCache.end(),
|
||||
[](const ShaderNodeTypeInfo& a, const ShaderNodeTypeInfo& b) {
|
||||
if (a.family != b.family) return a.family < b.family;
|
||||
return a.label < b.label;
|
||||
});
|
||||
|
||||
m_shaderNodeCacheBuilt = true;
|
||||
return m_shaderNodeCache;
|
||||
}
|
||||
|
||||
std::vector<pxr::SdfPath> MaterialManager::GetAllMaterials() const {
|
||||
std::vector<pxr::SdfPath> result;
|
||||
if (!m_stage) return result;
|
||||
for (const pxr::UsdPrim& prim : m_stage->Traverse()) {
|
||||
if (prim.IsA<pxr::UsdShadeMaterial>())
|
||||
result.push_back(prim.GetPath());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ShaderGraphSnapshot MaterialManager::GetShaderGraph(const pxr::SdfPath& materialPath) const {
|
||||
ShaderGraphSnapshot snapshot;
|
||||
if (!m_stage) return snapshot;
|
||||
|
||||
pxr::UsdPrim materialPrim = m_stage->GetPrimAtPath(materialPath);
|
||||
if (!materialPrim.IsValid()) return snapshot;
|
||||
|
||||
static const pxr::TfToken kUiPositionKey("uiPosition");
|
||||
|
||||
// Seed with every shader under the material, recursing into nested node
|
||||
// graphs (usdMtlx nests a material's nodes inside UsdShadeNodeGraph
|
||||
// scopes rather than authoring them as direct children).
|
||||
std::vector<pxr::UsdPrim> pending;
|
||||
std::vector<pxr::UsdPrim> scopes{materialPrim};
|
||||
while (!scopes.empty()) {
|
||||
pxr::UsdPrim scope = scopes.back();
|
||||
scopes.pop_back();
|
||||
for (const pxr::UsdPrim& child : scope.GetChildren()) {
|
||||
if (child.IsA<pxr::UsdShadeShader>())
|
||||
pending.push_back(child);
|
||||
else if (child.IsA<pxr::UsdShadeNodeGraph>())
|
||||
scopes.push_back(child);
|
||||
}
|
||||
}
|
||||
|
||||
// Worklist walk: resolving each input's true upstream shader output pulls
|
||||
// in nodes living in node graphs outside the material prim too.
|
||||
std::set<pxr::SdfPath> visited;
|
||||
while (!pending.empty()) {
|
||||
pxr::UsdPrim child = pending.back();
|
||||
pending.pop_back();
|
||||
if (!visited.insert(child.GetPath()).second)
|
||||
continue;
|
||||
pxr::UsdShadeShader shader(child);
|
||||
if (!shader) continue;
|
||||
|
||||
ShaderGraphNode node;
|
||||
node.path = child.GetPath();
|
||||
|
||||
pxr::TfToken shaderId;
|
||||
shader.GetIdAttr().Get(&shaderId);
|
||||
node.shaderId = shaderId.GetString();
|
||||
|
||||
pxr::VtValue posValue = child.GetCustomDataByKey(kUiPositionKey);
|
||||
if (posValue.IsHolding<pxr::GfVec2f>()) {
|
||||
node.uiPosition = posValue.UncheckedGet<pxr::GfVec2f>();
|
||||
node.hasAuthoredPosition = true;
|
||||
}
|
||||
|
||||
// Prefer the full Sdr-defined pin set (so unauthored pins can still be
|
||||
// dragged to create a connection); fall back to authored attributes
|
||||
// only for shader types the registry doesn't know about.
|
||||
pxr::SdrShaderNodeConstPtr sdrNode =
|
||||
pxr::SdrRegistry::GetInstance().GetShaderNodeByIdentifier(shaderId);
|
||||
if (sdrNode) {
|
||||
for (const pxr::TfToken& inputName : sdrNode->GetShaderInputNames()) {
|
||||
if (auto* prop = sdrNode->GetShaderInput(inputName))
|
||||
node.inputs.push_back({inputName.GetString(), prop->GetTypeAsSdfType().GetSdfType()});
|
||||
}
|
||||
for (const pxr::TfToken& outputName : sdrNode->GetShaderOutputNames()) {
|
||||
if (auto* prop = sdrNode->GetShaderOutput(outputName))
|
||||
node.outputs.push_back({outputName.GetString(), prop->GetTypeAsSdfType().GetSdfType()});
|
||||
}
|
||||
} else {
|
||||
for (const auto& input : shader.GetInputs())
|
||||
node.inputs.push_back({input.GetBaseName().GetString(), input.GetTypeName()});
|
||||
for (const auto& output : shader.GetOutputs())
|
||||
node.outputs.push_back({output.GetBaseName().GetString(), output.GetTypeName()});
|
||||
}
|
||||
|
||||
// Only authored inputs can carry a connection. Resolve each one
|
||||
// through node-graph boundaries (shaderOutputsOnly) so a MaterialX
|
||||
// connection routed via NodeGraph interface attrs links straight to
|
||||
// the shader output that actually produces the value.
|
||||
for (const auto& input : shader.GetInputs()) {
|
||||
for (const pxr::UsdAttribute& sourceAttr :
|
||||
pxr::UsdShadeUtils::GetValueProducingAttributes(input, /*shaderOutputsOnly=*/true)) {
|
||||
pxr::UsdPrim sourcePrim = sourceAttr.GetPrim();
|
||||
if (!sourcePrim.IsA<pxr::UsdShadeShader>()) continue;
|
||||
ShaderGraphLink link;
|
||||
link.destNode = child.GetPath();
|
||||
link.destInput = input.GetBaseName().GetString();
|
||||
link.sourceNode = sourcePrim.GetPath();
|
||||
link.sourceOutput = pxr::UsdShadeOutput(sourceAttr).GetBaseName().GetString();
|
||||
snapshot.links.push_back(std::move(link));
|
||||
pending.push_back(sourcePrim); // may live outside the material
|
||||
}
|
||||
}
|
||||
|
||||
snapshot.nodes.push_back(std::move(node));
|
||||
}
|
||||
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
} // namespace UsdLayerManager
|
||||
@@ -0,0 +1,84 @@
|
||||
#pragma once
|
||||
|
||||
#include <pxr/usd/usd/stage.h>
|
||||
#include <pxr/usd/sdf/path.h>
|
||||
#include <pxr/usd/sdf/valueTypeName.h>
|
||||
#include <pxr/base/gf/vec2f.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace UsdLayerManager {
|
||||
|
||||
/// One entry in the shader-node creation menu, derived from the Sdr/Ndr
|
||||
/// shader registry (covers UsdPreviewSurface, MaterialX, Arnold, Cycles,
|
||||
/// etc. — whatever shader definitions are discoverable in this build).
|
||||
struct ShaderNodeTypeInfo {
|
||||
std::string identifier; ///< Sdr identifier, authored as info:id
|
||||
std::string label; ///< display label for the create-node menu
|
||||
std::string family; ///< grouping for the create-node menu (may be empty)
|
||||
};
|
||||
|
||||
/// One input or output pin on a shader node, with its USD value type so
|
||||
/// connections/CreateInput/CreateOutput can author the right type.
|
||||
struct ShaderPinInfo {
|
||||
std::string name;
|
||||
pxr::SdfValueTypeName typeName;
|
||||
};
|
||||
|
||||
/// One UsdShadeShader prim read back from an existing material network.
|
||||
/// inputs/outputs list the full Sdr-defined pin set (not just authored
|
||||
/// attributes) so unauthored pins can still be dragged to create a connection.
|
||||
struct ShaderGraphNode {
|
||||
pxr::SdfPath path;
|
||||
std::string shaderId;
|
||||
pxr::GfVec2f uiPosition{0.0f, 0.0f};
|
||||
/// False when no uiPosition custom data is authored (typical for networks
|
||||
/// referenced from .mtlx) — the editor auto-lays such nodes out instead.
|
||||
bool hasAuthoredPosition = false;
|
||||
std::vector<ShaderPinInfo> inputs;
|
||||
std::vector<ShaderPinInfo> outputs;
|
||||
};
|
||||
|
||||
/// One authored connection between two shader nodes in the same network.
|
||||
struct ShaderGraphLink {
|
||||
pxr::SdfPath destNode;
|
||||
std::string destInput;
|
||||
pxr::SdfPath sourceNode;
|
||||
std::string sourceOutput;
|
||||
};
|
||||
|
||||
struct ShaderGraphSnapshot {
|
||||
std::vector<ShaderGraphNode> nodes;
|
||||
std::vector<ShaderGraphLink> links;
|
||||
};
|
||||
|
||||
/// Reads/introspects UsdShade material networks and the Sdr shader registry.
|
||||
/// Mirrors PropertyManager's role: holds read/derive logic only — mutations
|
||||
/// go through ICommand subclasses in src/core/commands/.
|
||||
class MaterialManager {
|
||||
public:
|
||||
void SetStage(pxr::UsdStageRefPtr stage) { m_stage = stage; }
|
||||
|
||||
/// All shader node types registered in the Sdr registry, grouped for a
|
||||
/// categorized create-node menu. Cached after the first (slow) call.
|
||||
const std::vector<ShaderNodeTypeInfo>& GetAvailableShaderNodes();
|
||||
|
||||
/// The entire shader network of the material at materialPath: shaders
|
||||
/// under the material (recursing into nested UsdShadeNodeGraphs) plus
|
||||
/// everything reachable upstream through connections — including nodes in
|
||||
/// node graphs outside the material, as authored by MaterialX (.mtlx)
|
||||
/// imports. Connections are resolved through node-graph boundary attrs to
|
||||
/// the shader outputs that actually produce the values.
|
||||
ShaderGraphSnapshot GetShaderGraph(const pxr::SdfPath& materialPath) const;
|
||||
|
||||
/// Paths of every UsdShadeMaterial prim on the stage, in traversal order.
|
||||
/// Drives the material browser list (Hypershade-style).
|
||||
std::vector<pxr::SdfPath> GetAllMaterials() const;
|
||||
|
||||
private:
|
||||
pxr::UsdStageRefPtr m_stage;
|
||||
std::vector<ShaderNodeTypeInfo> m_shaderNodeCache;
|
||||
bool m_shaderNodeCacheBuilt = false;
|
||||
};
|
||||
|
||||
} // namespace UsdLayerManager
|
||||
@@ -705,7 +705,7 @@ void UsdSceneRenderer::Render(int width, int height)
|
||||
// --- Render params (matches stageView.renderSinglePass) ---
|
||||
m_renderParams = pxr::UsdImagingGLRenderParams();
|
||||
m_renderParams.frame = m_currentTime;
|
||||
m_renderParams.complexity = 1.0f;
|
||||
m_renderParams.complexity = m_complexity;
|
||||
// Apply shading mode → drawMode + enableLighting
|
||||
switch (m_shadingMode) {
|
||||
case ShadingMode::FlatShaded:
|
||||
|
||||
@@ -185,12 +185,23 @@ public:
|
||||
/// unknown or the switch fails. The renderer is re-initialised if needed.
|
||||
bool SetRendererPlugin(const pxr::TfToken& pluginId);
|
||||
|
||||
/// False while a progressive delegate (Arnold/Cycles/Embree…) is still
|
||||
/// accumulating samples for the last-submitted frame; Storm is always
|
||||
/// converged after one pass. Callers that cache the output texture must
|
||||
/// keep calling Render() until this is true.
|
||||
bool IsConverged() const { return !m_renderer || m_renderer->IsConverged(); }
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// View settings
|
||||
// -----------------------------------------------------------------------
|
||||
bool ShowGrid() const { return m_showGrid; }
|
||||
void SetShowGrid(bool show) { m_showGrid = show; }
|
||||
|
||||
/// Hydra refinement complexity (1.0 = base mesh, up to 2.0). The shader
|
||||
/// ball uses a raised value so the sphere tessellates smoothly.
|
||||
float Complexity() const { return m_complexity; }
|
||||
void SetComplexity(float c) { m_complexity = c; }
|
||||
|
||||
bool ShowCameraGuide() const { return m_showCameraGuide; }
|
||||
void SetShowCameraGuide(bool show) { m_showCameraGuide = show; }
|
||||
|
||||
@@ -360,6 +371,7 @@ private:
|
||||
std::vector<pxr::GfVec4d> m_clipPlanes;
|
||||
|
||||
bool m_showGrid;
|
||||
float m_complexity = 1.0f;
|
||||
bool m_showCameraGuide;
|
||||
bool m_showGuides;
|
||||
bool m_showProxy;
|
||||
|
||||
@@ -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