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/usdGeom/xformCommonAPI.h>
|
||||
#include <pxr/usd/usd/references.h>
|
||||
#include <pxr/usd/usd/editContext.h>
|
||||
#include <pxr/usd/sdf/assetPath.h>
|
||||
#include <pxr/usd/sdf/primSpec.h>
|
||||
#include <imgui.h>
|
||||
@@ -48,6 +49,10 @@ void MaterialPreviewRenderer::EnsureInitialized() {
|
||||
if (m_initialized) return;
|
||||
|
||||
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
|
||||
// rig, dome-pole compensation, and camera framing all assume Y-up.
|
||||
pxr::UsdGeomSetStageUpAxis(m_previewStage, pxr::UsdGeomTokens->y);
|
||||
@@ -112,14 +117,18 @@ void MaterialPreviewRenderer::SetMaterial(const pxr::UsdStageRefPtr& sourceStage
|
||||
// materials can be spread across sublayers. Referencing at the identical
|
||||
// path keeps internal connection paths valid without remapping, and
|
||||
// relative asset paths keep resolving against their original layers.
|
||||
pxr::UsdPrim mirrorPrim = m_previewStage->OverridePrim(materialPath);
|
||||
if (!mirrorPrim) {
|
||||
LOG_ERROR("MaterialPreviewRenderer: failed to create override at " + materialPath.GetString());
|
||||
return;
|
||||
// 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);
|
||||
if (!mirrorPrim) {
|
||||
LOG_ERROR("MaterialPreviewRenderer: failed to create override at " + materialPath.GetString());
|
||||
return;
|
||||
}
|
||||
mirrorPrim.GetReferences().ClearReferences();
|
||||
mirrorPrim.GetReferences().AddReference(
|
||||
sourceStage->GetRootLayer()->GetIdentifier(), materialPath);
|
||||
}
|
||||
mirrorPrim.GetReferences().ClearReferences();
|
||||
mirrorPrim.GetReferences().AddReference(
|
||||
sourceStage->GetRootLayer()->GetIdentifier(), materialPath);
|
||||
|
||||
pxr::UsdPrim spherePrim = m_previewStage->GetPrimAtPath(kSpherePath);
|
||||
pxr::UsdShadeMaterial material(m_previewStage->GetPrimAtPath(materialPath));
|
||||
@@ -131,20 +140,14 @@ void MaterialPreviewRenderer::SetMaterial(const pxr::UsdStageRefPtr& sourceStage
|
||||
pxr::UsdShadeMaterialBindingAPI::Apply(spherePrim).Bind(material);
|
||||
|
||||
// ── Selected-node preview (Hypershade-style) ─────────────────────────
|
||||
// Wipe any override left by a previous selection: the scratch wrapper
|
||||
// shader and the local opinions on the material's surface outputs, all
|
||||
// of which live only in the scratch stage's root layer.
|
||||
pxr::SdfLayerHandle rootLayer = m_previewStage->GetRootLayer();
|
||||
if (pxr::SdfPrimSpecHandle matSpec = rootLayer->GetPrimAtPath(materialPath)) {
|
||||
if (pxr::SdfPrimSpecHandle wrapSpec =
|
||||
rootLayer->GetPrimAtPath(materialPath.AppendChild(kNodePreviewShaderName)))
|
||||
matSpec->RemoveNameChild(wrapSpec);
|
||||
for (const char* outName : {"outputs:surface", "outputs:mtlx:surface"}) {
|
||||
if (pxr::SdfPropertySpecHandle prop =
|
||||
rootLayer->GetPropertyAtPath(materialPath.AppendProperty(pxr::TfToken(outName))))
|
||||
matSpec->RemoveProperty(prop);
|
||||
}
|
||||
}
|
||||
// All node-preview overrides (the scratch wrapper shader and the local
|
||||
// opinions on the material's surface outputs) live exclusively in the
|
||||
// preview stage's session layer, which is stronger than the root layer's
|
||||
// reference. Wiping them is then just clearing that layer — no per-spec
|
||||
// surgery (RemoveNameChild/RemoveProperty) that could interact badly with
|
||||
// Sdf's inert-spec cleanup, and no stale overrides on other materials.
|
||||
pxr::SdfLayerHandle sessionLayer = m_previewStage->GetSessionLayer();
|
||||
sessionLayer->Clear();
|
||||
|
||||
// Route the material's surface through the selected node. Nodes outside
|
||||
// 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)) {
|
||||
pxr::UsdShadeShader nodeShader(m_previewStage->GetPrimAtPath(previewNodePath));
|
||||
if (nodeShader) {
|
||||
pxr::UsdEditContext sessionEdit(m_previewStage,
|
||||
pxr::UsdEditTarget(sessionLayer));
|
||||
pxr::UsdShadeConnectableAPI sourceApi = nodeShader.ConnectableAPI();
|
||||
pxr::TfToken sourceName(previewNodeOutput);
|
||||
if (!previewOutputIsTerminal) {
|
||||
|
||||
Reference in New Issue
Block a user