-
Notifications
You must be signed in to change notification settings - Fork 203
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
EMSUSD-1114 - Adds a callback for RefreshSystemLock when a layer's lo…
…ck status is changed - Adds a callback system that allow registering for changes coming from either commands or UI interactions. Each callback can receive a context (PXR_NS::VtDictionary) as well as callback data (PXR_NS::VtDictionary). - Added onRefreshSystemLock callback to know when a layer's lock status is changed due to disk write permission checeks. - Added a test case for the callback - Added a new LayerLocking.md document encompassing Layer Locking how-tos to be used through C++ or scripting with the included examples.
- Loading branch information
1 parent
b9d1570
commit a2b565b
Showing
17 changed files
with
427 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
# Layer Locking | ||
|
||
## What is layer locking? | ||
|
||
Layer locking is a collection of commands and UI actions that allows changing | ||
editing and saving permissions on layers of a stage. | ||
|
||
It was designed to provide various levels of controls based on the user's interaction: | ||
- Through the Maya USD Layer Editor | ||
- Through commands (C++, Python and MEL) | ||
|
||
## What are the types of locks? | ||
|
||
There are three types of locks on a layer: | ||
- `Unlocked`: When a layer is allowed to both be edited and saved. | ||
- `Locked` (Edit is Locked): When a layer is allowed to be saved but not allowed to be edited. | ||
- `System-Locked` (Edit and Save are locked): When a layer is not allowed to be saved or edited. | ||
|
||
Changing the lock states will perform the following on a layer: | ||
- Calls OpenUSD's [SetPermissionToSave / SetPermissionToEdit](https://openusd.org/release/api/class_sdf_layer.html#a32ad22bde9522ec46ef46ce2b88dfd14) | ||
- Stores the current state of `Locked` and `System-Locked` in LayerLocking.h | ||
- In case of `Locked`, the list of locked layers are also stored in an attribute on the associated proxy shape. This allows retaining the lock state between Maya sessions if the Maya scene is saved. | ||
|
||
|
||
### USD Layer Editor | ||
- Through action icons on each layer item. Note that the lock button can only be interacted | ||
if the layer isn't already locked as a System-Lock. | ||
|
||
 | ||
|
||
A locked layer appears with a blue lock icon: | ||
|
||
 | ||
|
||
A system-locked layer appears similar to a locked layer but it cannot be unlocked through the user interface: | ||
|
||
 | ||
|
||
However, if there are already changes on the layer that are unsaved, they are indicated with an asterisk (*) | ||
but still are not counted towards saving: | ||
|
||
 | ||
|
||
The lock state can also be changed through context menu items: | ||
|
||
 | ||
|
||
Similar to the lock button, some of the operations aren't allowed for a system locked layer: | ||
|
||
 | ||
|
||
Note that there are other factors that may prevent a layer from getting saved or edited: | ||
|
||
- If a layer is locked, it cannot be edited | ||
- If a layer is anonymous and its parent layer is locked, the anonymous layer can be edited but cannot be saved. | ||
This is because by saving an anonymous layer, the layer identifier changes which has to be updated on the parent layer. | ||
- If a layer belongs to an Un-shared stage. | ||
|
||
Similarly, layers that are locked cannot have new sublayers as that will require changing the locked parent. | ||
|
||
### Disk Write Permissions | ||
|
||
Each layer is automatically checked for disk write permissions which may apply or remove a System-lock on a layer(s). These are the conditions a disk write check occurs (aka Refresh Sytem Lock): | ||
|
||
- When the user initiates a layer reload (through the context menu in the USD Layer Editor). | ||
- When the user initiates a stage reload (through Attribute Editor). | ||
- When the user loads a stage from a file. | ||
- Anything that may cause a session stage change on the LayerTreeModel (including the case of stage reload). | ||
- Using script with the command `refreshSystemLock`. | ||
|
||
|
||
When evaluating the disk write permissions, files for which the user has no write access will win against user-initiated locks. | ||
For example: | ||
|
||
| Condition | Result | | ||
|:--------- |:------- | | ||
| if a layer has a user-initiated lock and the file on disk is locked | the layer becomes system-locked | | ||
| if a layer is locked and the file on disk is unlocked | The layer will be unlocked | | ||
| if a layer is system-locked and the file on disk is unlocked | The layer will be unlocked | | ||
|
||
Sometimes there may be a need to re-lock the layers after a refreshSystemLock. There is a callback system that will notify through scripting when a system lock refresh occurs and modifies layer permissions. | ||
|
||
## API for Layer Locking | ||
|
||
A layer's lock state can be changed through C++ or Python. | ||
|
||
### C++ | ||
|
||
#### Directly using MayaUsd::lockLayer `(Not Un-doable)`: | ||
|
||
MayaUsd::lockLayer(proxyShapePath, layer, locktype, updateProxyShapeAttr); | ||
|
||
### Using MayaCommandHook::lockLayer `(Un-doable)`: | ||
This uses the underlying `mayaUsdLayerEditor` MEL command. | ||
|
||
MayaCommandHook::lockLayer(usdLayer, lockState, includeSubLayers); | ||
|
||
### Using MEL or Python scripts: | ||
|
||
#### In MEL Script layer editor command `(Un-doable)`: | ||
These commands are un-doable | ||
|
||
// Lock Type: 0 = Unlocked, 1 = Locked and 2 = System-Locked. | ||
// Include Sublayers : 0 = Top Layer Only, 1 : Top and Sublayers | ||
mayaUsdLayerEditor -edit -lockLayer 0 0 "proxyShapePath" "layerIdentifier" | ||
|
||
// example: locks an anonymousLayer1 without changing the lock state of its sublayers | ||
mayaUsdLayerEditor -edit -lockLayer 1 0 "|PathTo|proxyShape" "anon:00000143164533E0:anonymousLayer1" | ||
|
||
// example: locks an exampleLayer.usda as well as its sub-layers | ||
mayaUsdLayerEditor -edit -lockLayer 1 1 "|stage|stageShape1" "d:/Assets/exampleLayer.usda" | ||
|
||
// example: System-locks an exampleLayer.usda | ||
mayaUsdLayerEditor -edit -lockLayer 2 0 "|stage|stageShape1" "d:/Assets/exampleLayer.usda" | ||
|
||
For more info on the syntax please refer to [Layer Editor Command Flags](../lib/mayaUsd/commands/Readme.md#layereditorcommand) | ||
|
||
#### In Python script `(Un-doable)`: | ||
|
||
# example: System-locks an exampleLayer | ||
cmds.mayaUsdLayerEditor(exampleLayer.identifier, edit=True, lockLayer=(2, 0, proxyShapePath)) | ||
|
||
### Disk Write Permission Check (RefreshSystemLock) | ||
|
||
#### In C++ `(Un-doable)`: | ||
This can be done by calling the following function which uses a MEL script to call `mayaUsdLayerEditor refreshSystemLock`. | ||
|
||
MayaCommandHook::refreshLayerSystemLock(usdLayer, refreshSubLayers); | ||
|
||
#### In MEL script `(Un-doable)`: | ||
|
||
// 0 = Only top layer, 1 = Include the sublayers | ||
// example: This will perform a write permission check on a layer: | ||
mayaUsdLayerEditor -edit -refreshSystemLock "|stage|stageShape1" 0 "d:/Assets/exampleLayer.usda" | ||
|
||
// example: This will perform a write permission check on a layer and its sub-layers | ||
mayaUsdLayerEditor -edit -refreshSystemLock "|stage|stageShape1" 1 "d:/Assets/exampleLayer.usda" | ||
|
||
#### In Python script `(Un-doable)`: | ||
|
||
// example: This will perform a write permission check on a layer and its sub-layers | ||
cmds.mayaUsdLayerEditor(topLayer.identifier, edit=True, refreshSystemLock=(proxyShapePath, 1)) | ||
|
||
Note that if there isn't a change in the write permissions, no actions are taken. In order to track the changes due to system-lock refresh, you can use `mayaUsd.lib.registerUICallback` to get notified about the system lock changes due to refreshSystemLock: | ||
|
||
def refreshSystemLockCallback(context, callbackData): | ||
# Get the proxy shape path | ||
proxyShapePath = context.get('proxyShapePath') | ||
# Get the list of affected layers | ||
layerIds = callbackData.get('affectedLayerIds') | ||
print("The layers with a change in lock status are:") | ||
for layerId in layerIds: | ||
print(layerIds) | ||
|
||
mayaUsd.lib.registerUICallback('onRefreshSystemLock', exampleCallback) | ||
|
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+5.86 KB
doc/images/layerlocking/LayerEditor_LockLayerActionButton_SystemLocked.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+5.23 KB
...images/layerlocking/LayerEditor_LockLayerActionButton_SystemLocked_modified.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+6.88 KB
doc/images/layerlocking/LayerEditor_LockLayerMenuItems_SystemLock_Disabled.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
// | ||
// Copyright 2024 Autodesk | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
// | ||
#include <usdUfe/utils/uiCallback.h> | ||
|
||
#include <pxr/base/tf/pyError.h> | ||
#include <pxr/base/tf/pyLock.h> | ||
|
||
#include <boost/python.hpp> | ||
#include <boost/python/def.hpp> | ||
|
||
#include <iostream> | ||
|
||
using namespace boost::python; | ||
|
||
namespace { | ||
|
||
std::string handlePythonException() | ||
{ | ||
PyObject* exc = nullptr; | ||
PyObject* val = nullptr; | ||
PyObject* tb = nullptr; | ||
PyErr_Fetch(&exc, &val, &tb); | ||
handle<> hexc(exc); | ||
handle<> hval(allow_null(val)); | ||
handle<> htb(allow_null(tb)); | ||
object traceback(import("traceback")); | ||
object format_exception_only(traceback.attr("format_exception_only")); | ||
object formatted_list = format_exception_only(hexc, hval); | ||
object formatted = str("\n").join(formatted_list); | ||
return extract<std::string>(formatted); | ||
} | ||
|
||
class PyUICallback : public UsdUfe::UICallback | ||
{ | ||
public: | ||
PyUICallback(PyObject* pyCallable) | ||
: _pyCb(pyCallable) | ||
{ | ||
} | ||
|
||
~PyUICallback() override { } | ||
|
||
void | ||
operator()(const PXR_NS::VtDictionary& context, PXR_NS::VtDictionary& callbackData) override | ||
{ | ||
// Note: necessary to compile the TF_WARN macro as it refers to USD types without using | ||
// the namespace prefix. | ||
PXR_NAMESPACE_USING_DIRECTIVE; | ||
|
||
PXR_NS::TfPyLock pyLock; | ||
if (!PyCallable_Check(_pyCb)) { | ||
return; | ||
} | ||
boost::python::object dictObject(callbackData); | ||
try { | ||
call<void>(_pyCb, context, dictObject); | ||
} catch (const boost::python::error_already_set&) { | ||
const std::string errorMessage = handlePythonException(); | ||
boost::python::handle_exception(); | ||
PyErr_Clear(); | ||
TF_WARN("%s", errorMessage.c_str()); | ||
throw std::runtime_error(errorMessage); | ||
} catch (const std::exception& ex) { | ||
TF_WARN("%s", ex.what()); | ||
throw; | ||
} | ||
boost::python::extract<PXR_NS::VtDictionary> extractedDict(dictObject); | ||
if (extractedDict.check()) { | ||
callbackData = extractedDict; | ||
} | ||
} | ||
|
||
private: | ||
PyObject* _pyCb; | ||
}; | ||
|
||
} // namespace | ||
|
||
void wrapUICallback() | ||
{ | ||
// Making the callbacks accessible from Python | ||
def( | ||
"registerUICallback", +[](const PXR_NS::TfToken& operation, PyObject* uiCallback) { | ||
return UsdUfe::registerUICallback( | ||
operation, std::make_shared<PyUICallback>(uiCallback)); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.