Stage Editor Panel — sublayer editing with ET control, mute, reorder, dirty state

Replace LayerPanel with StageEditorPanel for full sublayer composition workflow:
- Edit target (ET) checkbox on root/session and each sublayer; pen icon header
- Dirty indicator (*) per layer; muted layers remain visible in list
- Drag-drop and Move Up/Down reordering with undo (LayerReorderCommand)
- Add existing sublayer via file dialog; create new sublayer modal
- Fixed LayerReorderCommand::ApplyOrder using full-stack indices on sublayer-local
  list (silent remove failure → duplicate insert → USD _ValidateEdit error)
- Fixed muted sublayer visibility: walk GetSubLayerPaths() + FindOrOpenRelativeToLayer
  + m_layerRefs to keep muted layers alive and visible

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-14 16:37:30 +08:00
parent 9ec9761dd7
commit 9816fb8759
13 changed files with 514 additions and 333 deletions
+48 -20
View File
@@ -148,6 +148,15 @@ void LayerManager::UnmuteLayer(const std::string& layerIdentifier) {
}
}
bool LayerManager::SetEditTarget(const std::string& identifier) {
if (!m_stage) return false;
SdfLayerHandle layer = SdfLayer::Find(identifier);
if (!layer) return false;
m_stage->SetEditTarget(layer);
Refresh();
return true;
}
bool LayerManager::IsLayerMuted(const std::string& layerIdentifier) const {
if (m_stage) {
return m_stage->IsLayerMuted(layerIdentifier);
@@ -181,32 +190,51 @@ std::string LayerManager::ExtractDisplayName(const std::string& identifier) {
void LayerManager::BuildLayerList() {
m_layers.clear();
m_layerRefs.clear();
if (!m_stage) return;
try {
// Get the full layer stack (root + all sublayers)
SdfLayerHandleVector layerStack = m_stage->GetLayerStack();
SdfLayerHandle rootLayer = m_stage->GetRootLayer();
SdfLayerHandle rootLayer = m_stage->GetRootLayer();
SdfLayerHandle sessionLayer = m_stage->GetSessionLayer();
for (const auto& layer : layerStack) {
SdfLayerHandle editTarget = m_stage->GetEditTarget().GetLayer();
auto makeInfo = [&](SdfLayerHandle layer, bool isRoot, bool isSession) {
LayerInfo info;
info.layer = layer;
info.identifier = layer->GetIdentifier();
info.displayName = ExtractDisplayName(info.identifier);
info.realPath = layer->GetRealPath();
info.isMuted = layer->IsMuted();
info.isAnonymous = layer->IsAnonymous();
info.isRootLayer = (layer == rootLayer);
info.isSessionLayer = (layer == sessionLayer);
m_layers.push_back(info);
info.layer = layer;
info.identifier = layer->GetIdentifier();
info.displayName = ExtractDisplayName(info.identifier);
info.realPath = layer->GetRealPath();
info.isMuted = m_stage->IsLayerMuted(info.identifier);
info.isAnonymous = layer->IsAnonymous();
info.isRootLayer = isRoot;
info.isSessionLayer = isSession;
info.isEditTarget = (layer == editTarget);
info.isDirty = layer->IsDirty();
return info;
};
// Root and session layers: stage always holds strong refs, handles are valid.
if (sessionLayer) m_layers.push_back(makeInfo(sessionLayer, false, true));
if (rootLayer) m_layers.push_back(makeInfo(rootLayer, true, false));
// Walk root layer's sublayer paths instead of stage->GetLayerStack().
// GetLayerStack() drops muted layers because they don't contribute to
// composition. FindOrOpenRelativeToLayer locates or reopens the layer
// regardless of mute state. We store the returned RefPtr in m_layerRefs
// so the layer object isn't garbage-collected before the next Refresh().
if (rootLayer) {
for (const auto& subPath : rootLayer->GetSubLayerPaths()) {
SdfLayerRefPtr subRef =
SdfLayer::FindOrOpenRelativeToLayer(rootLayer, subPath);
if (subRef) {
m_layerRefs.push_back(subRef);
m_layers.push_back(makeInfo(SdfLayerHandle(subRef), false, false));
}
}
}
LOG_DEBUG("Built layer list with " + std::to_string(m_layers.size()) + " layers");
} catch (const std::exception& e) {
LOG_ERROR(std::string("Failed to build layer list: ") + e.what());
}
+8 -1
View File
@@ -17,6 +17,8 @@ struct LayerInfo {
bool isAnonymous;
bool isRootLayer;
bool isSessionLayer;
bool isEditTarget;
bool isDirty;
};
class LayerManager {
@@ -39,6 +41,9 @@ public:
bool MoveSublayerUp(int index);
bool MoveSublayerDown(int index);
// Edit target
bool SetEditTarget(const std::string& identifier);
// Muting
void MuteLayer(const std::string& layerIdentifier);
void UnmuteLayer(const std::string& layerIdentifier);
@@ -49,9 +54,11 @@ public:
private:
void BuildLayerList();
pxr::UsdStageRefPtr m_stage;
std::vector<LayerInfo> m_layers;
// Strong references keep sublayers alive even when the stage drops them after muting.
std::vector<pxr::SdfLayerRefPtr> m_layerRefs;
};
} // namespace UsdLayerManager
+10 -18
View File
@@ -46,10 +46,10 @@ LayerRemoveCommand::LayerRemoveCommand(LayerManager* mgr, int index)
, m_description("Remove Layer")
{
if (!mgr) return;
auto layers = mgr->GetLayerStack();
if (index >= 0 && index < static_cast<int>(layers.size())) {
m_savedPath = layers[index].identifier;
m_description = "Remove Layer " + layers[index].displayName;
auto sublayers = mgr->GetSublayers();
if (index >= 0 && index < static_cast<int>(sublayers.size())) {
m_savedPath = sublayers[index].identifier;
m_description = "Remove Layer " + sublayers[index].displayName;
}
}
@@ -81,22 +81,14 @@ void LayerReorderCommand::Undo() { ApplyOrder(m_before); }
void LayerReorderCommand::ApplyOrder(const std::vector<std::string>& order) {
if (!m_mgr) return;
// Remove all sublayers and re-insert in the desired order.
// We only control sublayers — root and session are fixed.
// First gather current sublayer indices (non-root, non-session).
auto layers = m_mgr->GetLayerStack();
// Count sublayers and remove them from highest index down.
std::vector<int> sublayerIndices;
for (int i = 0; i < static_cast<int>(layers.size()); ++i) {
if (!layers[i].isRootLayer && !layers[i].isSessionLayer)
sublayerIndices.push_back(i);
}
// Remove from back to front to preserve indices.
for (int i = static_cast<int>(sublayerIndices.size()) - 1; i >= 0; --i)
m_mgr->RemoveSublayer(sublayerIndices[i]);
// RemoveSublayer() operates on GetSubLayerPaths() — a sublayer-local list
// with indices 0..N-1. GetLayerStack() prepends session and root layers, so
// its indices are always wrong here. Use GetSublayers().size() for the count.
int count = static_cast<int>(m_mgr->GetSublayers().size());
for (int i = count - 1; i >= 0; --i)
m_mgr->RemoveSublayer(i);
// Re-add in desired order.
for (const auto& path : order)
m_mgr->InsertSublayerPath(path, -1);
}