mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-26 13:52:15 +01:00
Merge branch 'develop' into feature/OP-3437_CelAction-conversion-to-op3
This commit is contained in:
commit
29f6e6696e
2 changed files with 520 additions and 251 deletions
|
|
@ -14,6 +14,137 @@ from Deadline.Scripting import (
|
|||
ProcessUtils,
|
||||
)
|
||||
|
||||
VERSION_REGEX = re.compile(
|
||||
r"(?P<major>0|[1-9]\d*)"
|
||||
r"\.(?P<minor>0|[1-9]\d*)"
|
||||
r"\.(?P<patch>0|[1-9]\d*)"
|
||||
r"(?:-(?P<prerelease>[a-zA-Z\d\-.]*))?"
|
||||
r"(?:\+(?P<buildmetadata>[a-zA-Z\d\-.]*))?"
|
||||
)
|
||||
|
||||
|
||||
class OpenPypeVersion:
|
||||
"""Fake semver version class for OpenPype version purposes.
|
||||
|
||||
The version
|
||||
"""
|
||||
def __init__(self, major, minor, patch, prerelease, origin=None):
|
||||
self.major = major
|
||||
self.minor = minor
|
||||
self.patch = patch
|
||||
self.prerelease = prerelease
|
||||
|
||||
is_valid = True
|
||||
if not major or not minor or not patch:
|
||||
is_valid = False
|
||||
self.is_valid = is_valid
|
||||
|
||||
if origin is None:
|
||||
base = "{}.{}.{}".format(str(major), str(minor), str(patch))
|
||||
if not prerelease:
|
||||
origin = base
|
||||
else:
|
||||
origin = "{}-{}".format(base, str(prerelease))
|
||||
|
||||
self.origin = origin
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, version):
|
||||
"""Create an object of version from string.
|
||||
|
||||
Args:
|
||||
version (str): Version as a string.
|
||||
|
||||
Returns:
|
||||
Union[OpenPypeVersion, None]: Version object if input is nonempty
|
||||
string otherwise None.
|
||||
"""
|
||||
|
||||
if not version:
|
||||
return None
|
||||
valid_parts = VERSION_REGEX.findall(version)
|
||||
if len(valid_parts) != 1:
|
||||
# Return invalid version with filled 'origin' attribute
|
||||
return cls(None, None, None, None, origin=str(version))
|
||||
|
||||
# Unpack found version
|
||||
major, minor, patch, pre, post = valid_parts[0]
|
||||
prerelease = pre
|
||||
# Post release is not important anymore and should be considered as
|
||||
# part of prerelease
|
||||
# - comparison is implemented to find suitable build and builds should
|
||||
# never contain prerelease part so "not proper" parsing is
|
||||
# acceptable for this use case.
|
||||
if post:
|
||||
prerelease = "{}+{}".format(pre, post)
|
||||
|
||||
return cls(
|
||||
int(major), int(minor), int(patch), prerelease, origin=version
|
||||
)
|
||||
|
||||
def has_compatible_release(self, other):
|
||||
"""Version has compatible release as other version.
|
||||
|
||||
Both major and minor versions must be exactly the same. In that case
|
||||
a build can be considered as release compatible with any version.
|
||||
|
||||
Args:
|
||||
other (OpenPypeVersion): Other version.
|
||||
|
||||
Returns:
|
||||
bool: Version is release compatible with other version.
|
||||
"""
|
||||
|
||||
if self.is_valid and other.is_valid:
|
||||
return self.major == other.major and self.minor == other.minor
|
||||
return False
|
||||
|
||||
def __bool__(self):
|
||||
return self.is_valid
|
||||
|
||||
def __repr__(self):
|
||||
return "<{} {}>".format(self.__class__.__name__, self.origin)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, self.__class__):
|
||||
return self.origin == other
|
||||
return self.origin == other.origin
|
||||
|
||||
def __lt__(self, other):
|
||||
if not isinstance(other, self.__class__):
|
||||
return None
|
||||
|
||||
if not self.is_valid:
|
||||
return True
|
||||
|
||||
if not other.is_valid:
|
||||
return False
|
||||
|
||||
if self.origin == other.origin:
|
||||
return None
|
||||
|
||||
same_major = self.major == other.major
|
||||
if not same_major:
|
||||
return self.major < other.major
|
||||
|
||||
same_minor = self.minor == other.minor
|
||||
if not same_minor:
|
||||
return self.minor < other.minor
|
||||
|
||||
same_patch = self.patch == other.patch
|
||||
if not same_patch:
|
||||
return self.patch < other.patch
|
||||
|
||||
if not self.prerelease:
|
||||
return False
|
||||
|
||||
if not other.prerelease:
|
||||
return True
|
||||
|
||||
pres = [self.prerelease, other.prerelease]
|
||||
pres.sort()
|
||||
return pres[0] == self.prerelease
|
||||
|
||||
|
||||
def get_openpype_version_from_path(path, build=True):
|
||||
"""Get OpenPype version from provided path.
|
||||
|
|
@ -21,9 +152,9 @@ def get_openpype_version_from_path(path, build=True):
|
|||
build (bool, optional): Get only builds, not sources
|
||||
|
||||
Returns:
|
||||
str or None: version of OpenPype if found.
|
||||
|
||||
Union[OpenPypeVersion, None]: version of OpenPype if found.
|
||||
"""
|
||||
|
||||
# fix path for application bundle on macos
|
||||
if platform.system().lower() == "darwin":
|
||||
path = os.path.join(path, "Contents", "MacOS", "lib", "Python")
|
||||
|
|
@ -46,8 +177,10 @@ def get_openpype_version_from_path(path, build=True):
|
|||
with open(version_file, "r") as vf:
|
||||
exec(vf.read(), version)
|
||||
|
||||
version_match = re.search(r"(\d+\.\d+.\d+).*", version["__version__"])
|
||||
return version_match[1]
|
||||
version_str = version.get("__version__")
|
||||
if version_str:
|
||||
return OpenPypeVersion.from_string(version_str)
|
||||
return None
|
||||
|
||||
|
||||
def get_openpype_executable():
|
||||
|
|
@ -59,6 +192,91 @@ def get_openpype_executable():
|
|||
return exe_list, dir_list
|
||||
|
||||
|
||||
def get_openpype_versions(dir_list):
|
||||
print(">>> Getting OpenPype executable ...")
|
||||
openpype_versions = []
|
||||
|
||||
install_dir = DirectoryUtils.SearchDirectoryList(dir_list)
|
||||
if install_dir:
|
||||
print("--- Looking for OpenPype at: {}".format(install_dir))
|
||||
sub_dirs = [
|
||||
f.path for f in os.scandir(install_dir)
|
||||
if f.is_dir()
|
||||
]
|
||||
for subdir in sub_dirs:
|
||||
version = get_openpype_version_from_path(subdir)
|
||||
if not version:
|
||||
continue
|
||||
print(" - found: {} - {}".format(version, subdir))
|
||||
openpype_versions.append((version, subdir))
|
||||
return openpype_versions
|
||||
|
||||
|
||||
def get_requested_openpype_executable(
|
||||
exe, dir_list, requested_version
|
||||
):
|
||||
requested_version_obj = OpenPypeVersion.from_string(requested_version)
|
||||
if not requested_version_obj:
|
||||
print((
|
||||
">>> Requested version does not match version regex \"{}\""
|
||||
).format(VERSION_REGEX))
|
||||
return None
|
||||
|
||||
print((
|
||||
">>> Scanning for compatible requested version {}"
|
||||
).format(requested_version))
|
||||
openpype_versions = get_openpype_versions(dir_list)
|
||||
if not openpype_versions:
|
||||
return None
|
||||
|
||||
# if looking for requested compatible version,
|
||||
# add the implicitly specified to the list too.
|
||||
if exe:
|
||||
exe_dir = os.path.dirname(exe)
|
||||
print("Looking for OpenPype at: {}".format(exe_dir))
|
||||
version = get_openpype_version_from_path(exe_dir)
|
||||
if version:
|
||||
print(" - found: {} - {}".format(version, exe_dir))
|
||||
openpype_versions.append((version, exe_dir))
|
||||
|
||||
matching_item = None
|
||||
compatible_versions = []
|
||||
for version_item in openpype_versions:
|
||||
version, version_dir = version_item
|
||||
if requested_version_obj.has_compatible_release(version):
|
||||
compatible_versions.append(version_item)
|
||||
if version == requested_version_obj:
|
||||
# Store version item if version match exactly
|
||||
# - break if is found matching version
|
||||
matching_item = version_item
|
||||
break
|
||||
|
||||
if not compatible_versions:
|
||||
return None
|
||||
|
||||
compatible_versions.sort(key=lambda item: item[0])
|
||||
if matching_item:
|
||||
version, version_dir = matching_item
|
||||
print((
|
||||
"*** Found exact match build version {} in {}"
|
||||
).format(version_dir, version))
|
||||
|
||||
else:
|
||||
version, version_dir = compatible_versions[-1]
|
||||
|
||||
print((
|
||||
"*** Latest compatible version found is {} in {}"
|
||||
).format(version_dir, version))
|
||||
|
||||
# create list of executables for different platform and let
|
||||
# Deadline decide.
|
||||
exe_list = [
|
||||
os.path.join(version_dir, "openpype_console.exe"),
|
||||
os.path.join(version_dir, "openpype_console")
|
||||
]
|
||||
return FileUtils.SearchFileList(";".join(exe_list))
|
||||
|
||||
|
||||
def inject_openpype_environment(deadlinePlugin):
|
||||
""" Pull env vars from OpenPype and push them to rendering process.
|
||||
|
||||
|
|
@ -68,93 +286,29 @@ def inject_openpype_environment(deadlinePlugin):
|
|||
|
||||
print(">>> Injecting OpenPype environments ...")
|
||||
try:
|
||||
print(">>> Getting OpenPype executable ...")
|
||||
exe_list, dir_list = get_openpype_executable()
|
||||
openpype_versions = []
|
||||
# if the job requires specific OpenPype version,
|
||||
# lets go over all available and find compatible build.
|
||||
exe = FileUtils.SearchFileList(exe_list)
|
||||
|
||||
requested_version = job.GetJobEnvironmentKeyValue("OPENPYPE_VERSION")
|
||||
if requested_version:
|
||||
print((
|
||||
">>> Scanning for compatible requested version {}"
|
||||
).format(requested_version))
|
||||
install_dir = DirectoryUtils.SearchDirectoryList(dir_list)
|
||||
if install_dir:
|
||||
print("--- Looking for OpenPype at: {}".format(install_dir))
|
||||
sub_dirs = [
|
||||
f.path for f in os.scandir(install_dir)
|
||||
if f.is_dir()
|
||||
]
|
||||
for subdir in sub_dirs:
|
||||
version = get_openpype_version_from_path(subdir)
|
||||
if not version:
|
||||
continue
|
||||
print(" - found: {} - {}".format(version, subdir))
|
||||
openpype_versions.append((version, subdir))
|
||||
exe = get_requested_openpype_executable(
|
||||
exe, dir_list, requested_version
|
||||
)
|
||||
if exe is None:
|
||||
raise RuntimeError((
|
||||
"Cannot find compatible version available for version {}"
|
||||
" requested by the job. Please add it through plugin"
|
||||
" configuration in Deadline or install it to configured"
|
||||
" directory."
|
||||
).format(requested_version))
|
||||
|
||||
exe = FileUtils.SearchFileList(exe_list)
|
||||
if openpype_versions:
|
||||
# if looking for requested compatible version,
|
||||
# add the implicitly specified to the list too.
|
||||
print("Looking for OpenPype at: {}".format(os.path.dirname(exe)))
|
||||
version = get_openpype_version_from_path(
|
||||
os.path.dirname(exe))
|
||||
if version:
|
||||
print(" - found: {} - {}".format(
|
||||
version, os.path.dirname(exe)
|
||||
))
|
||||
openpype_versions.append((version, os.path.dirname(exe)))
|
||||
|
||||
if requested_version:
|
||||
# sort detected versions
|
||||
if openpype_versions:
|
||||
# use natural sorting
|
||||
openpype_versions.sort(
|
||||
key=lambda ver: [
|
||||
int(t) if t.isdigit() else t.lower()
|
||||
for t in re.split(r"(\d+)", ver[0])
|
||||
])
|
||||
print((
|
||||
"*** Latest available version found is {}"
|
||||
).format(openpype_versions[-1][0]))
|
||||
requested_major, requested_minor, _ = requested_version.split(".")[:3] # noqa: E501
|
||||
compatible_versions = []
|
||||
for version in openpype_versions:
|
||||
v = version[0].split(".")[:3]
|
||||
if v[0] == requested_major and v[1] == requested_minor:
|
||||
compatible_versions.append(version)
|
||||
if not compatible_versions:
|
||||
raise RuntimeError(
|
||||
("Cannot find compatible version available "
|
||||
"for version {} requested by the job. "
|
||||
"Please add it through plugin configuration "
|
||||
"in Deadline or install it to configured "
|
||||
"directory.").format(requested_version))
|
||||
# sort compatible versions nad pick the last one
|
||||
compatible_versions.sort(
|
||||
key=lambda ver: [
|
||||
int(t) if t.isdigit() else t.lower()
|
||||
for t in re.split(r"(\d+)", ver[0])
|
||||
])
|
||||
print((
|
||||
"*** Latest compatible version found is {}"
|
||||
).format(compatible_versions[-1][0]))
|
||||
# create list of executables for different platform and let
|
||||
# Deadline decide.
|
||||
exe_list = [
|
||||
os.path.join(
|
||||
compatible_versions[-1][1], "openpype_console.exe"),
|
||||
os.path.join(
|
||||
compatible_versions[-1][1], "openpype_console")
|
||||
]
|
||||
exe = FileUtils.SearchFileList(";".join(exe_list))
|
||||
if exe == "":
|
||||
raise RuntimeError(
|
||||
"OpenPype executable was not found " +
|
||||
"in the semicolon separated list " +
|
||||
"\"" + ";".join(exe_list) + "\". " +
|
||||
"The path to the render executable can be configured " +
|
||||
"from the Plugin Configuration in the Deadline Monitor.")
|
||||
if not exe:
|
||||
raise RuntimeError((
|
||||
"OpenPype executable was not found in the semicolon "
|
||||
"separated list \"{}\"."
|
||||
"The path to the render executable can be configured"
|
||||
" from the Plugin Configuration in the Deadline Monitor."
|
||||
).format(";".join(exe_list)))
|
||||
|
||||
print("--- OpenPype executable: {}".format(exe))
|
||||
|
||||
|
|
@ -172,22 +326,22 @@ def inject_openpype_environment(deadlinePlugin):
|
|||
export_url
|
||||
]
|
||||
|
||||
add_args = {}
|
||||
add_args['project'] = \
|
||||
job.GetJobEnvironmentKeyValue('AVALON_PROJECT')
|
||||
add_args['asset'] = job.GetJobEnvironmentKeyValue('AVALON_ASSET')
|
||||
add_args['task'] = job.GetJobEnvironmentKeyValue('AVALON_TASK')
|
||||
add_args['app'] = job.GetJobEnvironmentKeyValue('AVALON_APP_NAME')
|
||||
add_args["envgroup"] = "farm"
|
||||
add_kwargs = {
|
||||
"project": job.GetJobEnvironmentKeyValue("AVALON_PROJECT"),
|
||||
"asset": job.GetJobEnvironmentKeyValue("AVALON_ASSET"),
|
||||
"task": job.GetJobEnvironmentKeyValue("AVALON_TASK"),
|
||||
"app": job.GetJobEnvironmentKeyValue("AVALON_APP_NAME"),
|
||||
"envgroup": "farm"
|
||||
}
|
||||
if all(add_kwargs.values()):
|
||||
for key, value in add_kwargs.items():
|
||||
args.extend(["--{}".format(key), value])
|
||||
|
||||
if all(add_args.values()):
|
||||
for key, value in add_args.items():
|
||||
args.append("--{}".format(key))
|
||||
args.append(value)
|
||||
else:
|
||||
msg = "Required env vars: AVALON_PROJECT, AVALON_ASSET, " + \
|
||||
"AVALON_TASK, AVALON_APP_NAME"
|
||||
raise RuntimeError(msg)
|
||||
raise RuntimeError((
|
||||
"Missing required env vars: AVALON_PROJECT, AVALON_ASSET,"
|
||||
" AVALON_TASK, AVALON_APP_NAME"
|
||||
))
|
||||
|
||||
if not os.environ.get("OPENPYPE_MONGO"):
|
||||
print(">>> Missing OPENPYPE_MONGO env var, process won't work")
|
||||
|
|
@ -208,12 +362,12 @@ def inject_openpype_environment(deadlinePlugin):
|
|||
print(">>> Loading file ...")
|
||||
with open(export_url) as fp:
|
||||
contents = json.load(fp)
|
||||
for key, value in contents.items():
|
||||
deadlinePlugin.SetProcessEnvironmentVariable(key, value)
|
||||
|
||||
for key, value in contents.items():
|
||||
deadlinePlugin.SetProcessEnvironmentVariable(key, value)
|
||||
|
||||
script_url = job.GetJobPluginInfoKeyValue("ScriptFilename")
|
||||
if script_url:
|
||||
|
||||
script_url = script_url.format(**contents).replace("\\", "/")
|
||||
print(">>> Setting script path {}".format(script_url))
|
||||
job.SetJobPluginInfoKeyValue("ScriptFilename", script_url)
|
||||
|
|
|
|||
|
|
@ -7,10 +7,8 @@ import pyblish.api
|
|||
|
||||
from openpype.client import get_asset_by_id
|
||||
from openpype.lib import filter_profiles
|
||||
from openpype.pipeline import KnownPublishError
|
||||
|
||||
|
||||
# Copy of constant `openpype_modules.ftrack.lib.avalon_sync.CUST_ATTR_AUTO_SYNC`
|
||||
CUST_ATTR_AUTO_SYNC = "avalon_auto_sync"
|
||||
CUST_ATTR_GROUP = "openpype"
|
||||
|
||||
|
||||
|
|
@ -19,7 +17,6 @@ CUST_ATTR_GROUP = "openpype"
|
|||
def get_pype_attr(session, split_hierarchical=True):
|
||||
custom_attributes = []
|
||||
hier_custom_attributes = []
|
||||
# TODO remove deprecated "avalon" group from query
|
||||
cust_attrs_query = (
|
||||
"select id, entity_type, object_type_id, is_hierarchical, default"
|
||||
" from CustomAttributeConfiguration"
|
||||
|
|
@ -79,120 +76,284 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin):
|
|||
create_task_status_profiles = []
|
||||
|
||||
def process(self, context):
|
||||
self.context = context
|
||||
if "hierarchyContext" not in self.context.data:
|
||||
if "hierarchyContext" not in context.data:
|
||||
return
|
||||
|
||||
hierarchy_context = self._get_active_assets(context)
|
||||
self.log.debug("__ hierarchy_context: {}".format(hierarchy_context))
|
||||
|
||||
session = self.context.data["ftrackSession"]
|
||||
project_name = self.context.data["projectEntity"]["name"]
|
||||
query = 'Project where full_name is "{}"'.format(project_name)
|
||||
project = session.query(query).one()
|
||||
auto_sync_state = project["custom_attributes"][CUST_ATTR_AUTO_SYNC]
|
||||
session = context.data["ftrackSession"]
|
||||
project_name = context.data["projectName"]
|
||||
project = session.query(
|
||||
'select id, full_name from Project where full_name is "{}"'.format(
|
||||
project_name
|
||||
)
|
||||
).first()
|
||||
if not project:
|
||||
raise KnownPublishError(
|
||||
"Project \"{}\" was not found on ftrack.".format(project_name)
|
||||
)
|
||||
|
||||
self.context = context
|
||||
self.session = session
|
||||
self.ft_project = project
|
||||
self.task_types = self.get_all_task_types(project)
|
||||
self.task_statuses = self.get_task_statuses(project)
|
||||
|
||||
# disable termporarily ftrack project's autosyncing
|
||||
if auto_sync_state:
|
||||
self.auto_sync_off(project)
|
||||
# import ftrack hierarchy
|
||||
self.import_to_ftrack(project_name, hierarchy_context)
|
||||
|
||||
try:
|
||||
# import ftrack hierarchy
|
||||
self.import_to_ftrack(project_name, hierarchy_context)
|
||||
except Exception:
|
||||
raise
|
||||
finally:
|
||||
if auto_sync_state:
|
||||
self.auto_sync_on(project)
|
||||
def query_ftrack_entitites(self, session, ft_project):
|
||||
project_id = ft_project["id"]
|
||||
entities = session.query((
|
||||
"select id, name, parent_id"
|
||||
" from TypedContext where project_id is \"{}\""
|
||||
).format(project_id)).all()
|
||||
|
||||
def import_to_ftrack(self, project_name, input_data, parent=None):
|
||||
entities_by_id = {}
|
||||
entities_by_parent_id = collections.defaultdict(list)
|
||||
for entity in entities:
|
||||
entities_by_id[entity["id"]] = entity
|
||||
parent_id = entity["parent_id"]
|
||||
entities_by_parent_id[parent_id].append(entity)
|
||||
|
||||
ftrack_hierarchy = []
|
||||
ftrack_id_queue = collections.deque()
|
||||
ftrack_id_queue.append((project_id, ftrack_hierarchy))
|
||||
while ftrack_id_queue:
|
||||
item = ftrack_id_queue.popleft()
|
||||
ftrack_id, parent_list = item
|
||||
if ftrack_id == project_id:
|
||||
entity = ft_project
|
||||
name = entity["full_name"]
|
||||
else:
|
||||
entity = entities_by_id[ftrack_id]
|
||||
name = entity["name"]
|
||||
|
||||
children = []
|
||||
parent_list.append({
|
||||
"name": name,
|
||||
"low_name": name.lower(),
|
||||
"entity": entity,
|
||||
"children": children,
|
||||
})
|
||||
for child in entities_by_parent_id[ftrack_id]:
|
||||
ftrack_id_queue.append((child["id"], children))
|
||||
return ftrack_hierarchy
|
||||
|
||||
def find_matching_ftrack_entities(
|
||||
self, hierarchy_context, ftrack_hierarchy
|
||||
):
|
||||
walk_queue = collections.deque()
|
||||
for entity_name, entity_data in hierarchy_context.items():
|
||||
walk_queue.append(
|
||||
(entity_name, entity_data, ftrack_hierarchy)
|
||||
)
|
||||
|
||||
matching_ftrack_entities = []
|
||||
while walk_queue:
|
||||
item = walk_queue.popleft()
|
||||
entity_name, entity_data, ft_children = item
|
||||
matching_ft_child = None
|
||||
for ft_child in ft_children:
|
||||
if ft_child["low_name"] == entity_name.lower():
|
||||
matching_ft_child = ft_child
|
||||
break
|
||||
|
||||
if matching_ft_child is None:
|
||||
continue
|
||||
|
||||
entity = matching_ft_child["entity"]
|
||||
entity_data["ft_entity"] = entity
|
||||
matching_ftrack_entities.append(entity)
|
||||
|
||||
hierarchy_children = entity_data.get("childs")
|
||||
if not hierarchy_children:
|
||||
continue
|
||||
|
||||
for child_name, child_data in hierarchy_children.items():
|
||||
walk_queue.append(
|
||||
(child_name, child_data, matching_ft_child["children"])
|
||||
)
|
||||
return matching_ftrack_entities
|
||||
|
||||
def query_custom_attribute_values(self, session, entities, hier_attrs):
|
||||
attr_ids = {
|
||||
attr["id"]
|
||||
for attr in hier_attrs
|
||||
}
|
||||
entity_ids = {
|
||||
entity["id"]
|
||||
for entity in entities
|
||||
}
|
||||
output = {
|
||||
entity_id: {}
|
||||
for entity_id in entity_ids
|
||||
}
|
||||
if not attr_ids or not entity_ids:
|
||||
return {}
|
||||
|
||||
joined_attr_ids = ",".join(
|
||||
['"{}"'.format(attr_id) for attr_id in attr_ids]
|
||||
)
|
||||
|
||||
# Query values in chunks
|
||||
chunk_size = int(5000 / len(attr_ids))
|
||||
# Make sure entity_ids is `list` for chunk selection
|
||||
entity_ids = list(entity_ids)
|
||||
results = []
|
||||
for idx in range(0, len(entity_ids), chunk_size):
|
||||
joined_entity_ids = ",".join([
|
||||
'"{}"'.format(entity_id)
|
||||
for entity_id in entity_ids[idx:idx + chunk_size]
|
||||
])
|
||||
results.extend(
|
||||
session.query(
|
||||
(
|
||||
"select value, entity_id, configuration_id"
|
||||
" from CustomAttributeValue"
|
||||
" where entity_id in ({}) and configuration_id in ({})"
|
||||
).format(
|
||||
joined_entity_ids,
|
||||
joined_attr_ids
|
||||
)
|
||||
).all()
|
||||
)
|
||||
|
||||
for result in results:
|
||||
attr_id = result["configuration_id"]
|
||||
entity_id = result["entity_id"]
|
||||
output[entity_id][attr_id] = result["value"]
|
||||
|
||||
return output
|
||||
|
||||
def import_to_ftrack(self, project_name, hierarchy_context):
|
||||
# Prequery hiearchical custom attributes
|
||||
hier_custom_attributes = get_pype_attr(self.session)[1]
|
||||
hier_attrs = get_pype_attr(self.session)[1]
|
||||
hier_attr_by_key = {
|
||||
attr["key"]: attr
|
||||
for attr in hier_custom_attributes
|
||||
for attr in hier_attrs
|
||||
}
|
||||
# Query user entity (for comments)
|
||||
user = self.session.query(
|
||||
"User where username is \"{}\"".format(self.session.api_user)
|
||||
).first()
|
||||
if not user:
|
||||
self.log.warning(
|
||||
"Was not able to query current User {}".format(
|
||||
self.session.api_user
|
||||
)
|
||||
)
|
||||
|
||||
# Query ftrack hierarchy with parenting
|
||||
ftrack_hierarchy = self.query_ftrack_entitites(
|
||||
self.session, self.ft_project)
|
||||
|
||||
# Fill ftrack entities to hierarchy context
|
||||
# - there is no need to query entities again
|
||||
matching_entities = self.find_matching_ftrack_entities(
|
||||
hierarchy_context, ftrack_hierarchy)
|
||||
# Query custom attribute values of each entity
|
||||
custom_attr_values_by_id = self.query_custom_attribute_values(
|
||||
self.session, matching_entities, hier_attrs)
|
||||
|
||||
# Get ftrack api module (as they are different per python version)
|
||||
ftrack_api = self.context.data["ftrackPythonModule"]
|
||||
|
||||
for entity_name in input_data:
|
||||
entity_data = input_data[entity_name]
|
||||
# Use queue of hierarchy items to process
|
||||
import_queue = collections.deque()
|
||||
for entity_name, entity_data in hierarchy_context.items():
|
||||
import_queue.append(
|
||||
(entity_name, entity_data, None)
|
||||
)
|
||||
|
||||
while import_queue:
|
||||
item = import_queue.popleft()
|
||||
entity_name, entity_data, parent = item
|
||||
|
||||
entity_type = entity_data['entity_type']
|
||||
self.log.debug(entity_data)
|
||||
self.log.debug(entity_type)
|
||||
|
||||
if entity_type.lower() == 'project':
|
||||
entity = self.ft_project
|
||||
|
||||
elif self.ft_project is None or parent is None:
|
||||
entity = entity_data.get("ft_entity")
|
||||
if entity is None and entity_type.lower() == "project":
|
||||
raise AssertionError(
|
||||
"Collected items are not in right order!"
|
||||
)
|
||||
|
||||
# try to find if entity already exists
|
||||
else:
|
||||
query = (
|
||||
'TypedContext where name is "{0}" and '
|
||||
'project_id is "{1}"'
|
||||
).format(entity_name, self.ft_project["id"])
|
||||
try:
|
||||
entity = self.session.query(query).one()
|
||||
except Exception:
|
||||
entity = None
|
||||
|
||||
# Create entity if not exists
|
||||
if entity is None:
|
||||
entity = self.create_entity(
|
||||
name=entity_name,
|
||||
type=entity_type,
|
||||
parent=parent
|
||||
)
|
||||
entity = self.session.create(entity_type, {
|
||||
"name": entity_name,
|
||||
"parent": parent
|
||||
})
|
||||
entity_data["ft_entity"] = entity
|
||||
|
||||
# self.log.info('entity: {}'.format(dict(entity)))
|
||||
# CUSTOM ATTRIBUTES
|
||||
custom_attributes = entity_data.get('custom_attributes', [])
|
||||
instances = [
|
||||
instance
|
||||
for instance in self.context
|
||||
if instance.data.get("asset") == entity["name"]
|
||||
]
|
||||
custom_attributes = entity_data.get('custom_attributes', {})
|
||||
instances = []
|
||||
for instance in self.context:
|
||||
instance_asset_name = instance.data.get("asset")
|
||||
if (
|
||||
instance_asset_name
|
||||
and instance_asset_name.lower() == entity["name"].lower()
|
||||
):
|
||||
instances.append(instance)
|
||||
|
||||
for instance in instances:
|
||||
instance.data["ftrackEntity"] = entity
|
||||
|
||||
for key in custom_attributes:
|
||||
for key, cust_attr_value in custom_attributes.items():
|
||||
if cust_attr_value is None:
|
||||
continue
|
||||
|
||||
hier_attr = hier_attr_by_key.get(key)
|
||||
# Use simple method if key is not hierarchical
|
||||
if not hier_attr:
|
||||
assert (key in entity['custom_attributes']), (
|
||||
'Missing custom attribute key: `{0}` in attrs: '
|
||||
'`{1}`'.format(key, entity['custom_attributes'].keys())
|
||||
if key not in entity["custom_attributes"]:
|
||||
raise KnownPublishError((
|
||||
"Missing custom attribute in ftrack with name '{}'"
|
||||
).format(key))
|
||||
|
||||
entity['custom_attributes'][key] = cust_attr_value
|
||||
continue
|
||||
|
||||
attr_id = hier_attr["id"]
|
||||
entity_values = custom_attr_values_by_id.get(entity["id"], {})
|
||||
# New value is defined by having id in values
|
||||
# - it can be set to 'None' (ftrack allows that using API)
|
||||
is_new_value = attr_id not in entity_values
|
||||
attr_value = entity_values.get(attr_id)
|
||||
|
||||
# Use ftrack operations method to set hiearchical
|
||||
# attribute value.
|
||||
# - this is because there may be non hiearchical custom
|
||||
# attributes with different properties
|
||||
entity_key = collections.OrderedDict((
|
||||
("configuration_id", hier_attr["id"]),
|
||||
("entity_id", entity["id"])
|
||||
))
|
||||
op = None
|
||||
if is_new_value:
|
||||
op = ftrack_api.operation.CreateEntityOperation(
|
||||
"CustomAttributeValue",
|
||||
entity_key,
|
||||
{"value": cust_attr_value}
|
||||
)
|
||||
|
||||
entity['custom_attributes'][key] = custom_attributes[key]
|
||||
|
||||
else:
|
||||
# Use ftrack operations method to set hiearchical
|
||||
# attribute value.
|
||||
# - this is because there may be non hiearchical custom
|
||||
# attributes with different properties
|
||||
entity_key = collections.OrderedDict()
|
||||
entity_key["configuration_id"] = hier_attr["id"]
|
||||
entity_key["entity_id"] = entity["id"]
|
||||
self.session.recorded_operations.push(
|
||||
ftrack_api.operation.UpdateEntityOperation(
|
||||
"ContextCustomAttributeValue",
|
||||
entity_key,
|
||||
"value",
|
||||
ftrack_api.symbol.NOT_SET,
|
||||
custom_attributes[key]
|
||||
)
|
||||
elif attr_value != cust_attr_value:
|
||||
op = ftrack_api.operation.UpdateEntityOperation(
|
||||
"CustomAttributeValue",
|
||||
entity_key,
|
||||
"value",
|
||||
attr_value,
|
||||
cust_attr_value
|
||||
)
|
||||
|
||||
if op is not None:
|
||||
self.session.recorded_operations.push(op)
|
||||
|
||||
if self.session.recorded_operations:
|
||||
try:
|
||||
self.session.commit()
|
||||
except Exception:
|
||||
|
|
@ -206,7 +367,7 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin):
|
|||
for instance in instances:
|
||||
task_name = instance.data.get("task")
|
||||
if task_name:
|
||||
instances_by_task_name[task_name].append(instance)
|
||||
instances_by_task_name[task_name.lower()].append(instance)
|
||||
|
||||
tasks = entity_data.get('tasks', [])
|
||||
existing_tasks = []
|
||||
|
|
@ -247,30 +408,28 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin):
|
|||
six.reraise(tp, value, tb)
|
||||
|
||||
# Create notes.
|
||||
user = self.session.query(
|
||||
"User where username is \"{}\"".format(self.session.api_user)
|
||||
).first()
|
||||
if user:
|
||||
for comment in entity_data.get("comments", []):
|
||||
entity_comments = entity_data.get("comments")
|
||||
if user and entity_comments:
|
||||
for comment in entity_comments:
|
||||
entity.create_note(comment, user)
|
||||
else:
|
||||
self.log.warning(
|
||||
"Was not able to query current User {}".format(
|
||||
self.session.api_user
|
||||
)
|
||||
)
|
||||
try:
|
||||
self.session.commit()
|
||||
except Exception:
|
||||
tp, value, tb = sys.exc_info()
|
||||
self.session.rollback()
|
||||
self.session._configure_locations()
|
||||
six.reraise(tp, value, tb)
|
||||
|
||||
try:
|
||||
self.session.commit()
|
||||
except Exception:
|
||||
tp, value, tb = sys.exc_info()
|
||||
self.session.rollback()
|
||||
self.session._configure_locations()
|
||||
six.reraise(tp, value, tb)
|
||||
|
||||
# Import children.
|
||||
if 'childs' in entity_data:
|
||||
self.import_to_ftrack(
|
||||
project_name, entity_data['childs'], entity)
|
||||
children = entity_data.get("childs")
|
||||
if not children:
|
||||
continue
|
||||
|
||||
for entity_name, entity_data in children.items():
|
||||
import_queue.append(
|
||||
(entity_name, entity_data, entity)
|
||||
)
|
||||
|
||||
def create_links(self, project_name, entity_data, entity):
|
||||
# Clear existing links.
|
||||
|
|
@ -366,48 +525,6 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin):
|
|||
|
||||
return task
|
||||
|
||||
def create_entity(self, name, type, parent):
|
||||
entity = self.session.create(type, {
|
||||
'name': name,
|
||||
'parent': parent
|
||||
})
|
||||
try:
|
||||
self.session.commit()
|
||||
except Exception:
|
||||
tp, value, tb = sys.exc_info()
|
||||
self.session.rollback()
|
||||
self.session._configure_locations()
|
||||
six.reraise(tp, value, tb)
|
||||
|
||||
return entity
|
||||
|
||||
def auto_sync_off(self, project):
|
||||
project["custom_attributes"][CUST_ATTR_AUTO_SYNC] = False
|
||||
|
||||
self.log.info("Ftrack autosync swithed off")
|
||||
|
||||
try:
|
||||
self.session.commit()
|
||||
except Exception:
|
||||
tp, value, tb = sys.exc_info()
|
||||
self.session.rollback()
|
||||
self.session._configure_locations()
|
||||
six.reraise(tp, value, tb)
|
||||
|
||||
def auto_sync_on(self, project):
|
||||
|
||||
project["custom_attributes"][CUST_ATTR_AUTO_SYNC] = True
|
||||
|
||||
self.log.info("Ftrack autosync swithed on")
|
||||
|
||||
try:
|
||||
self.session.commit()
|
||||
except Exception:
|
||||
tp, value, tb = sys.exc_info()
|
||||
self.session.rollback()
|
||||
self.session._configure_locations()
|
||||
six.reraise(tp, value, tb)
|
||||
|
||||
def _get_active_assets(self, context):
|
||||
""" Returns only asset dictionary.
|
||||
Usually the last part of deep dictionary which
|
||||
|
|
@ -429,19 +546,17 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin):
|
|||
|
||||
hierarchy_context = context.data["hierarchyContext"]
|
||||
|
||||
active_assets = []
|
||||
active_assets = set()
|
||||
# filter only the active publishing insatnces
|
||||
for instance in context:
|
||||
if instance.data.get("publish") is False:
|
||||
continue
|
||||
|
||||
if not instance.data.get("asset"):
|
||||
continue
|
||||
|
||||
active_assets.append(instance.data["asset"])
|
||||
asset_name = instance.data.get("asset")
|
||||
if asset_name:
|
||||
active_assets.add(asset_name)
|
||||
|
||||
# remove duplicity in list
|
||||
active_assets = list(set(active_assets))
|
||||
self.log.debug("__ active_assets: {}".format(active_assets))
|
||||
self.log.debug("__ active_assets: {}".format(list(active_assets)))
|
||||
|
||||
return get_pure_hierarchy_data(hierarchy_context)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue