Author shader-ball node-preview overrides in the session layer
Fixes 'No spec at </Materials/...> when trying to set field primChildren' during pin edits: the per-revision wipe used raw spec surgery (RemoveNameChild/RemoveProperty) plus a ClearReferences/ AddReference cycle that briefly leaves the material's over spec empty and inert, which Sdf cleanup can delete out from under later authoring. The reference to the source material is now authored only when the material changes (shared in-memory layers recompose live), and all node-preview overrides live in the preview stage's session layer, wiped wholesale with SdfLayer::Clear — no spec-level removal remains, and switching materials no longer leaves stale overrides behind. The edit target is pinned to the root layer so the rig never lands in the cleared session layer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@
|
|||||||
#include <pxr/usd/usdLux/tokens.h>
|
#include <pxr/usd/usdLux/tokens.h>
|
||||||
#include <pxr/usd/usdGeom/xformCommonAPI.h>
|
#include <pxr/usd/usdGeom/xformCommonAPI.h>
|
||||||
#include <pxr/usd/usd/references.h>
|
#include <pxr/usd/usd/references.h>
|
||||||
|
#include <pxr/usd/usd/editContext.h>
|
||||||
#include <pxr/usd/sdf/assetPath.h>
|
#include <pxr/usd/sdf/assetPath.h>
|
||||||
#include <pxr/usd/sdf/primSpec.h>
|
#include <pxr/usd/sdf/primSpec.h>
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
@@ -48,6 +49,10 @@ void MaterialPreviewRenderer::EnsureInitialized() {
|
|||||||
if (m_initialized) return;
|
if (m_initialized) return;
|
||||||
|
|
||||||
m_previewStage = pxr::UsdStage::CreateInMemory();
|
m_previewStage = pxr::UsdStage::CreateInMemory();
|
||||||
|
// Everything authored here (sphere, lights, reference) must go to the
|
||||||
|
// root layer: the session layer is reserved for node-preview overrides
|
||||||
|
// and is wiped wholesale on every SetMaterial.
|
||||||
|
m_previewStage->SetEditTarget(pxr::UsdEditTarget(m_previewStage->GetRootLayer()));
|
||||||
// Pin the up axis rather than inheriting the site fallback: the light
|
// Pin the up axis rather than inheriting the site fallback: the light
|
||||||
// rig, dome-pole compensation, and camera framing all assume Y-up.
|
// rig, dome-pole compensation, and camera framing all assume Y-up.
|
||||||
pxr::UsdGeomSetStageUpAxis(m_previewStage, pxr::UsdGeomTokens->y);
|
pxr::UsdGeomSetStageUpAxis(m_previewStage, pxr::UsdGeomTokens->y);
|
||||||
@@ -112,6 +117,9 @@ void MaterialPreviewRenderer::SetMaterial(const pxr::UsdStageRefPtr& sourceStage
|
|||||||
// materials can be spread across sublayers. Referencing at the identical
|
// materials can be spread across sublayers. Referencing at the identical
|
||||||
// path keeps internal connection paths valid without remapping, and
|
// path keeps internal connection paths valid without remapping, and
|
||||||
// relative asset paths keep resolving against their original layers.
|
// relative asset paths keep resolving against their original layers.
|
||||||
|
// Authored only when the material changes: the shared in-memory layers
|
||||||
|
// recompose live, so revision bumps need no re-referencing.
|
||||||
|
if (materialPath != m_materialPath) {
|
||||||
pxr::UsdPrim mirrorPrim = m_previewStage->OverridePrim(materialPath);
|
pxr::UsdPrim mirrorPrim = m_previewStage->OverridePrim(materialPath);
|
||||||
if (!mirrorPrim) {
|
if (!mirrorPrim) {
|
||||||
LOG_ERROR("MaterialPreviewRenderer: failed to create override at " + materialPath.GetString());
|
LOG_ERROR("MaterialPreviewRenderer: failed to create override at " + materialPath.GetString());
|
||||||
@@ -120,6 +128,7 @@ void MaterialPreviewRenderer::SetMaterial(const pxr::UsdStageRefPtr& sourceStage
|
|||||||
mirrorPrim.GetReferences().ClearReferences();
|
mirrorPrim.GetReferences().ClearReferences();
|
||||||
mirrorPrim.GetReferences().AddReference(
|
mirrorPrim.GetReferences().AddReference(
|
||||||
sourceStage->GetRootLayer()->GetIdentifier(), materialPath);
|
sourceStage->GetRootLayer()->GetIdentifier(), materialPath);
|
||||||
|
}
|
||||||
|
|
||||||
pxr::UsdPrim spherePrim = m_previewStage->GetPrimAtPath(kSpherePath);
|
pxr::UsdPrim spherePrim = m_previewStage->GetPrimAtPath(kSpherePath);
|
||||||
pxr::UsdShadeMaterial material(m_previewStage->GetPrimAtPath(materialPath));
|
pxr::UsdShadeMaterial material(m_previewStage->GetPrimAtPath(materialPath));
|
||||||
@@ -131,20 +140,14 @@ void MaterialPreviewRenderer::SetMaterial(const pxr::UsdStageRefPtr& sourceStage
|
|||||||
pxr::UsdShadeMaterialBindingAPI::Apply(spherePrim).Bind(material);
|
pxr::UsdShadeMaterialBindingAPI::Apply(spherePrim).Bind(material);
|
||||||
|
|
||||||
// ── Selected-node preview (Hypershade-style) ─────────────────────────
|
// ── Selected-node preview (Hypershade-style) ─────────────────────────
|
||||||
// Wipe any override left by a previous selection: the scratch wrapper
|
// All node-preview overrides (the scratch wrapper shader and the local
|
||||||
// shader and the local opinions on the material's surface outputs, all
|
// opinions on the material's surface outputs) live exclusively in the
|
||||||
// of which live only in the scratch stage's root layer.
|
// preview stage's session layer, which is stronger than the root layer's
|
||||||
pxr::SdfLayerHandle rootLayer = m_previewStage->GetRootLayer();
|
// reference. Wiping them is then just clearing that layer — no per-spec
|
||||||
if (pxr::SdfPrimSpecHandle matSpec = rootLayer->GetPrimAtPath(materialPath)) {
|
// surgery (RemoveNameChild/RemoveProperty) that could interact badly with
|
||||||
if (pxr::SdfPrimSpecHandle wrapSpec =
|
// Sdf's inert-spec cleanup, and no stale overrides on other materials.
|
||||||
rootLayer->GetPrimAtPath(materialPath.AppendChild(kNodePreviewShaderName)))
|
pxr::SdfLayerHandle sessionLayer = m_previewStage->GetSessionLayer();
|
||||||
matSpec->RemoveNameChild(wrapSpec);
|
sessionLayer->Clear();
|
||||||
for (const char* outName : {"outputs:surface", "outputs:mtlx:surface"}) {
|
|
||||||
if (pxr::SdfPropertySpecHandle prop =
|
|
||||||
rootLayer->GetPropertyAtPath(materialPath.AppendProperty(pxr::TfToken(outName))))
|
|
||||||
matSpec->RemoveProperty(prop);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Route the material's surface through the selected node. Nodes outside
|
// Route the material's surface through the selected node. Nodes outside
|
||||||
// the material's subtree (possible in hand-authored cross-scope networks)
|
// the material's subtree (possible in hand-authored cross-scope networks)
|
||||||
@@ -154,6 +157,8 @@ void MaterialPreviewRenderer::SetMaterial(const pxr::UsdStageRefPtr& sourceStage
|
|||||||
previewNodePath.HasPrefix(materialPath)) {
|
previewNodePath.HasPrefix(materialPath)) {
|
||||||
pxr::UsdShadeShader nodeShader(m_previewStage->GetPrimAtPath(previewNodePath));
|
pxr::UsdShadeShader nodeShader(m_previewStage->GetPrimAtPath(previewNodePath));
|
||||||
if (nodeShader) {
|
if (nodeShader) {
|
||||||
|
pxr::UsdEditContext sessionEdit(m_previewStage,
|
||||||
|
pxr::UsdEditTarget(sessionLayer));
|
||||||
pxr::UsdShadeConnectableAPI sourceApi = nodeShader.ConnectableAPI();
|
pxr::UsdShadeConnectableAPI sourceApi = nodeShader.ConnectableAPI();
|
||||||
pxr::TfToken sourceName(previewNodeOutput);
|
pxr::TfToken sourceName(previewNodeOutput);
|
||||||
if (!previewOutputIsTerminal) {
|
if (!previewOutputIsTerminal) {
|
||||||
|
|||||||
Reference in New Issue
Block a user