diff --git a/src/ui/MaterialPreviewRenderer.cpp b/src/ui/MaterialPreviewRenderer.cpp index 00f2cb4..3d0b869 100644 --- a/src/ui/MaterialPreviewRenderer.cpp +++ b/src/ui/MaterialPreviewRenderer.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -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) {