mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-25 21:32:15 +01:00
Merge pull request #49 from BigRoy/master
Implement automatic context switch on Maya scene open and scene save
This commit is contained in:
commit
684fc0cdf0
7 changed files with 246 additions and 3 deletions
|
|
@ -1,6 +1,13 @@
|
|||
import logging
|
||||
|
||||
from .vendor import pather
|
||||
from .vendor.pather.error import ParseError
|
||||
|
||||
import avalon.io as io
|
||||
import avalon.api
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def is_latest(representation):
|
||||
"""Return whether the representation is from latest version
|
||||
|
|
@ -43,4 +50,43 @@ def any_outdated():
|
|||
return True
|
||||
|
||||
checked.add(representation)
|
||||
return False
|
||||
return False
|
||||
|
||||
|
||||
def update_task_from_path(path):
|
||||
"""Update the context using the current scene state.
|
||||
|
||||
When no changes to the context it will not trigger an update.
|
||||
When the context for a file could not be parsed an error is logged but not
|
||||
raised.
|
||||
|
||||
"""
|
||||
if not path:
|
||||
log.warning("Can't update the current task. Scene is not saved.")
|
||||
return
|
||||
|
||||
# Find the current context from the filename
|
||||
project = io.find_one({"type": "project"},
|
||||
projection={"config.template.work": True})
|
||||
template = project['config']['template']['work']
|
||||
# Force to use the registered to root to avoid using wrong paths
|
||||
template = pather.format(template, {"root": avalon.api.registered_root()})
|
||||
try:
|
||||
context = pather.parse(template, path)
|
||||
except ParseError:
|
||||
log.error("Can't update the current task. Unable to parse the "
|
||||
"task for: %s", path)
|
||||
return
|
||||
|
||||
# Find the changes between current Session and the path's context.
|
||||
current = {
|
||||
"asset": avalon.api.Session["AVALON_ASSET"],
|
||||
"task": avalon.api.Session["AVALON_TASK"],
|
||||
"app": avalon.api.Session["AVALON_APP"]
|
||||
}
|
||||
changes = {key: context[key] for key, current_value in current.items()
|
||||
if context[key] != current_value}
|
||||
|
||||
if changes:
|
||||
log.info("Updating work task to: %s", context)
|
||||
avalon.api.update_current_task(**changes)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,10 @@ from maya import cmds
|
|||
from avalon import api as avalon
|
||||
from pyblish import api as pyblish
|
||||
|
||||
from ..lib import (
|
||||
update_task_from_path,
|
||||
any_outdated
|
||||
)
|
||||
from . import menu
|
||||
from . import lib
|
||||
|
||||
|
|
@ -88,6 +92,9 @@ def on_save(_):
|
|||
|
||||
avalon.logger.info("Running callback on save..")
|
||||
|
||||
# Update current task for the current scene
|
||||
update_task_from_path(cmds.file(query=True, sceneName=True))
|
||||
|
||||
# Generate ids of the current context on nodes in the scene
|
||||
nodes = lib.get_id_required_nodes(referenced_nodes=False)
|
||||
for node, new_id in lib.generate_ids(nodes):
|
||||
|
|
@ -97,10 +104,12 @@ def on_save(_):
|
|||
def on_open(_):
|
||||
"""On scene open let's assume the containers have changed."""
|
||||
|
||||
from ..lib import any_outdated
|
||||
from avalon.vendor.Qt import QtWidgets
|
||||
from ..widgets import popup
|
||||
|
||||
# Update current task for the current scene
|
||||
update_task_from_path(cmds.file(query=True, sceneName=True))
|
||||
|
||||
if any_outdated():
|
||||
log.warning("Scene has outdated content.")
|
||||
|
||||
|
|
@ -124,4 +133,4 @@ def on_open(_):
|
|||
dialog.setMessage("There are outdated containers in "
|
||||
"your Maya scene.")
|
||||
dialog.on_show.connect(_on_show_inventory)
|
||||
dialog.show()
|
||||
dialog.show()
|
||||
0
colorbleed/vendor/__init__.py
vendored
Normal file
0
colorbleed/vendor/__init__.py
vendored
Normal file
5
colorbleed/vendor/pather/__init__.py
vendored
Normal file
5
colorbleed/vendor/pather/__init__.py
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
__author__ = 'Roy Nieterau'
|
||||
|
||||
|
||||
from .core import *
|
||||
from .version import *
|
||||
168
colorbleed/vendor/pather/core.py
vendored
Normal file
168
colorbleed/vendor/pather/core.py
vendored
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
|
||||
__all__ = ['parse', 'ls', 'ls_iter', 'format']
|
||||
|
||||
import os
|
||||
import re
|
||||
import string
|
||||
import glob
|
||||
|
||||
from .error import ParseError
|
||||
|
||||
# Regex pattern that matches valid file
|
||||
# TODO: Implement complete pattern if required
|
||||
RE_FILENAME = '[-\w.,; \[\]]'
|
||||
|
||||
|
||||
def format(pattern, data, allow_partial=True):
|
||||
"""Format a pattern with a set of data
|
||||
|
||||
Examples:
|
||||
|
||||
Full formatting
|
||||
>>> format("{a}/{b}/{c}", {"a": "foo", "b": "bar", "c": "nugget"})
|
||||
'foo/bar/nugget'
|
||||
|
||||
Partial formatting
|
||||
>>> format("{asset}/{character}", {"asset": "hero"})
|
||||
'hero/{character}'
|
||||
|
||||
Disallow partial formatting
|
||||
>>> format("{asset}/{character}", {"asset": "hero"},
|
||||
... allow_partial=False)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
KeyError: 'character'
|
||||
|
||||
Args:
|
||||
pattern (str): The pattern to format.
|
||||
data (dict): The key, value pairs used for formatting.
|
||||
allow_partial (bool): Whether to raise error on partial format.
|
||||
|
||||
Returns:
|
||||
str: The formatted result
|
||||
"""
|
||||
|
||||
assert isinstance(data, dict)
|
||||
|
||||
if not all(isinstance(value, basestring) for value in data.values()):
|
||||
raise TypeError("The values in the data "
|
||||
"dictionary must be strings")
|
||||
|
||||
if allow_partial:
|
||||
return _partial_format(pattern, data)
|
||||
else:
|
||||
return pattern.format(**data)
|
||||
|
||||
|
||||
def parse(pattern, path):
|
||||
"""Parse data from a path based on a pattern
|
||||
|
||||
Example:
|
||||
>>> pattern = "root/{task}/{version}/data/"
|
||||
>>> path = "root/modeling/v001/data/"
|
||||
>>> parse(pattern, path)
|
||||
{'task': 'modeling', 'version': 'v001'}
|
||||
|
||||
Returns:
|
||||
dict: The data retrieved from path using pattern.
|
||||
"""
|
||||
|
||||
pattern = os.path.normpath(pattern)
|
||||
path = os.path.normpath(path)
|
||||
|
||||
# Force forward slashes
|
||||
path = path.replace('\\', '/')
|
||||
pattern = pattern.replace('\\', '/')
|
||||
|
||||
# Escape characters in path that are regex patterns so they are
|
||||
# excluded by the regex searches. Exclude '{' and '}' in escaping.
|
||||
pattern = re.escape(pattern)
|
||||
pattern = pattern.replace('\{', '{').replace('\}', '}')
|
||||
|
||||
keys = re.findall(r'{(%s+)}' % RE_FILENAME,
|
||||
pattern)
|
||||
if not keys:
|
||||
return []
|
||||
|
||||
# Find the corresponding values
|
||||
value_pattern = re.sub(r'{(%s+)}' % RE_FILENAME,
|
||||
r'(%s+)' % RE_FILENAME,
|
||||
pattern)
|
||||
match_values = re.match(value_pattern, path)
|
||||
|
||||
if not match_values:
|
||||
raise ParseError("Path doesn't match with pattern. No values parsed")
|
||||
|
||||
values = match_values.groups()
|
||||
|
||||
return dict(zip(keys, values))
|
||||
|
||||
|
||||
def ls_iter(pattern, include=None, with_matches=False):
|
||||
"""Yield all matches for the given pattern.
|
||||
|
||||
If the pattern starts with a relative path (or a dynamic key) the search
|
||||
will start from the current working directory, defined by os.path.realpath.
|
||||
|
||||
Arguments:
|
||||
pattern (str): The pattern to match and search against.
|
||||
include (dict): A dictionary used to target the search with the pattern
|
||||
to include only those key-value pairs within the pattern. With this
|
||||
you can reduce the filesystem query to a specified subset.
|
||||
|
||||
Example:
|
||||
>>> data = {"root": "foobar", "content": "nugget"}
|
||||
>>> for path in ls_iter("{root}/{project}/data/{content}/",
|
||||
... include=data):
|
||||
... print path
|
||||
|
||||
Returns:
|
||||
(str, tuple): The matched paths (and data if `with_matches` is True)
|
||||
|
||||
The returned value changes whether `with_matches` parameter is True or
|
||||
False. If True a 2-tuple is yielded for each match as (path, data) else
|
||||
only the path is returned
|
||||
"""
|
||||
|
||||
# format rule by data already provided to reduce query
|
||||
if include is not None:
|
||||
pattern = format(pattern, include, allow_partial=True)
|
||||
|
||||
pattern = os.path.expandvars(pattern)
|
||||
pattern = os.path.realpath(pattern)
|
||||
|
||||
glob_pattern = re.sub(r'([/\\]{\w+}[/\\])', '/*/', pattern) # folder
|
||||
glob_pattern = re.sub(r'({\w+})', '*', glob_pattern) # filename
|
||||
|
||||
for path in glob.iglob(glob_pattern):
|
||||
path = os.path.realpath(path)
|
||||
if with_matches:
|
||||
data = parse(pattern, path)
|
||||
yield path, data
|
||||
else:
|
||||
yield path
|
||||
|
||||
|
||||
def ls(pattern, include=None, with_matches=False):
|
||||
return list(ls_iter(pattern, include, with_matches=with_matches))
|
||||
|
||||
|
||||
def _partial_format(s, data):
|
||||
"""Return string `s` formatted by `data` allowing a partial format
|
||||
|
||||
Arguments:
|
||||
s (str): The string that will be formatted
|
||||
data (dict): The dictionary used to format with.
|
||||
|
||||
Example:
|
||||
>>> _partial_format("{d} {a} {b} {c} {d}", {'b': "and", 'd': "left"})
|
||||
'left {a} and {c} left'
|
||||
"""
|
||||
|
||||
class FormatDict(dict):
|
||||
def __missing__(self, key):
|
||||
return "{" + key + "}"
|
||||
|
||||
formatter = string.Formatter()
|
||||
mapping = FormatDict(**data)
|
||||
return formatter.vformat(s, (), mapping)
|
||||
5
colorbleed/vendor/pather/error.py
vendored
Normal file
5
colorbleed/vendor/pather/error.py
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
|
||||
|
||||
class ParseError(ValueError):
|
||||
"""Error raised when parsing a path with a pattern fails"""
|
||||
pass
|
||||
10
colorbleed/vendor/pather/version.py
vendored
Normal file
10
colorbleed/vendor/pather/version.py
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
VERSION_MAJOR = 0
|
||||
VERSION_MINOR = 1
|
||||
VERSION_PATCH = 0
|
||||
|
||||
version_info = (VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH)
|
||||
version = '%i.%i.%i' % version_info
|
||||
__version__ = version
|
||||
|
||||
__all__ = ['version', 'version_info', '__version__']
|
||||
Loading…
Add table
Add a link
Reference in a new issue