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:
+48
-20
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user