diff --git a/Dockerfile.centos7 b/Dockerfile.centos7 index 5eb2f478ea..ce1a624a4f 100644 --- a/Dockerfile.centos7 +++ b/Dockerfile.centos7 @@ -52,7 +52,7 @@ RUN yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.n # we need to build our own patchelf WORKDIR /temp-patchelf -RUN git clone https://github.com/NixOS/patchelf.git . \ +RUN git clone -b 0.17.0 --single-branch https://github.com/NixOS/patchelf.git . \ && source scl_source enable devtoolset-7 \ && ./bootstrap.sh \ && ./configure \ diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index f3c079506b..f814187cc1 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2157,17 +2157,23 @@ def set_scene_resolution(width, height, pixelAspect): cmds.setAttr("%s.pixelAspect" % control_node, pixelAspect) -def get_frame_range(): - """Get the current assets frame range and handles.""" +def get_frame_range(include_animation_range=False): + """Get the current assets frame range and handles. + + Args: + include_animation_range (bool, optional): Whether to include + `animationStart` and `animationEnd` keys to define the outer + range of the timeline. It is excluded by default. + + Returns: + dict: Asset's expected frame range values. + + """ # Set frame start/end project_name = get_current_project_name() - task_name = get_current_task_name() asset_name = get_current_asset_name() asset = get_asset_by_name(project_name, asset_name) - settings = get_project_settings(project_name) - include_handles_settings = settings["maya"]["include_handles"] - current_task = asset.get("data").get("tasks").get(task_name) frame_start = asset["data"].get("frameStart") frame_end = asset["data"].get("frameEnd") @@ -2179,32 +2185,39 @@ def get_frame_range(): handle_start = asset["data"].get("handleStart") or 0 handle_end = asset["data"].get("handleEnd") or 0 - animation_start = frame_start - animation_end = frame_end - - include_handles = include_handles_settings["include_handles_default"] - for item in include_handles_settings["per_task_type"]: - if current_task["type"] in item["task_type"]: - include_handles = item["include_handles"] - break - if include_handles: - animation_start -= int(handle_start) - animation_end += int(handle_end) - - cmds.playbackOptions( - minTime=frame_start, - maxTime=frame_end, - animationStartTime=animation_start, - animationEndTime=animation_end - ) - cmds.currentTime(frame_start) - - return { + frame_range = { "frameStart": frame_start, "frameEnd": frame_end, "handleStart": handle_start, "handleEnd": handle_end } + if include_animation_range: + # The animation range values are only included to define whether + # the Maya time slider should include the handles or not. + # Some usages of this function use the full dictionary to define + # instance attributes for which we want to exclude the animation + # keys. That is why these are excluded by default. + task_name = get_current_task_name() + settings = get_project_settings(project_name) + include_handles_settings = settings["maya"]["include_handles"] + current_task = asset.get("data").get("tasks").get(task_name) + + animation_start = frame_start + animation_end = frame_end + + include_handles = include_handles_settings["include_handles_default"] + for item in include_handles_settings["per_task_type"]: + if current_task["type"] in item["task_type"]: + include_handles = item["include_handles"] + break + if include_handles: + animation_start -= int(handle_start) + animation_end += int(handle_end) + + frame_range["animationStart"] = animation_start + frame_range["animationEnd"] = animation_end + + return frame_range def reset_frame_range(playback=True, render=True, fps=True): @@ -2223,18 +2236,23 @@ def reset_frame_range(playback=True, render=True, fps=True): ) set_scene_fps(fps) - frame_range = get_frame_range() + frame_range = get_frame_range(include_animation_range=True) + if not frame_range: + # No frame range data found for asset + return - frame_start = frame_range["frameStart"] - int(frame_range["handleStart"]) - frame_end = frame_range["frameEnd"] + int(frame_range["handleEnd"]) + frame_start = frame_range["frameStart"] + frame_end = frame_range["frameEnd"] + animation_start = frame_range["animationStart"] + animation_end = frame_range["animationEnd"] if playback: - cmds.playbackOptions(minTime=frame_start) - cmds.playbackOptions(maxTime=frame_end) - cmds.playbackOptions(animationStartTime=frame_start) - cmds.playbackOptions(animationEndTime=frame_end) - cmds.playbackOptions(minTime=frame_start) - cmds.playbackOptions(maxTime=frame_end) + cmds.playbackOptions( + minTime=frame_start, + maxTime=frame_end, + animationStartTime=animation_start, + animationEndTime=animation_end + ) cmds.currentTime(frame_start) if render: diff --git a/openpype/hosts/maya/tools/mayalookassigner/alembic.py b/openpype/hosts/maya/tools/mayalookassigner/alembic.py new file mode 100644 index 0000000000..6885e923d3 --- /dev/null +++ b/openpype/hosts/maya/tools/mayalookassigner/alembic.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- +"""Tools for loading looks to vray proxies.""" +import os +from collections import defaultdict +import logging + +import six + +import alembic.Abc + + +log = logging.getLogger(__name__) + + +def get_alembic_paths_by_property(filename, attr, verbose=False): + # type: (str, str, bool) -> dict + """Return attribute value per objects in the Alembic file. + + Reads an Alembic archive hierarchy and retrieves the + value from the `attr` properties on the objects. + + Args: + filename (str): Full path to Alembic archive to read. + attr (str): Id attribute. + verbose (bool): Whether to verbosely log missing attributes. + + Returns: + dict: Mapping of node full path with its id + + """ + # Normalize alembic path + filename = os.path.normpath(filename) + filename = filename.replace("\\", "/") + filename = str(filename) # path must be string + + try: + archive = alembic.Abc.IArchive(filename) + except RuntimeError: + # invalid alembic file - probably vrmesh + log.warning("{} is not an alembic file".format(filename)) + return {} + root = archive.getTop() + + iterator = list(root.children) + obj_ids = {} + + for obj in iterator: + name = obj.getFullName() + + # include children for coming iterations + iterator.extend(obj.children) + + props = obj.getProperties() + if props.getNumProperties() == 0: + # Skip those without properties, e.g. '/materials' in a gpuCache + continue + + # THe custom attribute is under the properties' first container under + # the ".arbGeomParams" + prop = props.getProperty(0) # get base property + + _property = None + try: + geo_params = prop.getProperty('.arbGeomParams') + _property = geo_params.getProperty(attr) + except KeyError: + if verbose: + log.debug("Missing attr on: {0}".format(name)) + continue + + if not _property.isConstant(): + log.warning("Id not constant on: {0}".format(name)) + + # Get first value sample + value = _property.getValue()[0] + + obj_ids[name] = value + + return obj_ids + + +def get_alembic_ids_cache(path): + # type: (str) -> dict + """Build a id to node mapping in Alembic file. + + Nodes without IDs are ignored. + + Returns: + dict: Mapping of id to nodes in the Alembic. + + """ + node_ids = get_alembic_paths_by_property(path, attr="cbId") + id_nodes = defaultdict(list) + for node, _id in six.iteritems(node_ids): + id_nodes[_id].append(node) + + return dict(six.iteritems(id_nodes)) diff --git a/openpype/hosts/maya/tools/mayalookassigner/arnold_standin.py b/openpype/hosts/maya/tools/mayalookassigner/arnold_standin.py index 7eeeb72553..0ce2b21dcd 100644 --- a/openpype/hosts/maya/tools/mayalookassigner/arnold_standin.py +++ b/openpype/hosts/maya/tools/mayalookassigner/arnold_standin.py @@ -9,6 +9,7 @@ from openpype.pipeline import legacy_io from openpype.client import get_last_version_by_subset_name from openpype.hosts.maya import api from . import lib +from .alembic import get_alembic_ids_cache log = logging.getLogger(__name__) @@ -68,6 +69,11 @@ def get_nodes_by_id(standin): (dict): Dictionary with node full name/path and id. """ path = cmds.getAttr(standin + ".dso") + + if path.endswith(".abc"): + # Support alembic files directly + return get_alembic_ids_cache(path) + json_path = None for f in os.listdir(os.path.dirname(path)): if f.endswith(".json"): diff --git a/openpype/hosts/maya/tools/mayalookassigner/vray_proxies.py b/openpype/hosts/maya/tools/mayalookassigner/vray_proxies.py index 1d2ec5fd87..c875fec7f0 100644 --- a/openpype/hosts/maya/tools/mayalookassigner/vray_proxies.py +++ b/openpype/hosts/maya/tools/mayalookassigner/vray_proxies.py @@ -1,108 +1,20 @@ # -*- coding: utf-8 -*- """Tools for loading looks to vray proxies.""" -import os from collections import defaultdict import logging -import six - -import alembic.Abc from maya import cmds from openpype.client import get_last_version_by_subset_name from openpype.pipeline import legacy_io import openpype.hosts.maya.lib as maya_lib from . import lib +from .alembic import get_alembic_ids_cache log = logging.getLogger(__name__) -def get_alembic_paths_by_property(filename, attr, verbose=False): - # type: (str, str, bool) -> dict - """Return attribute value per objects in the Alembic file. - - Reads an Alembic archive hierarchy and retrieves the - value from the `attr` properties on the objects. - - Args: - filename (str): Full path to Alembic archive to read. - attr (str): Id attribute. - verbose (bool): Whether to verbosely log missing attributes. - - Returns: - dict: Mapping of node full path with its id - - """ - # Normalize alembic path - filename = os.path.normpath(filename) - filename = filename.replace("\\", "/") - filename = str(filename) # path must be string - - try: - archive = alembic.Abc.IArchive(filename) - except RuntimeError: - # invalid alembic file - probably vrmesh - log.warning("{} is not an alembic file".format(filename)) - return {} - root = archive.getTop() - - iterator = list(root.children) - obj_ids = {} - - for obj in iterator: - name = obj.getFullName() - - # include children for coming iterations - iterator.extend(obj.children) - - props = obj.getProperties() - if props.getNumProperties() == 0: - # Skip those without properties, e.g. '/materials' in a gpuCache - continue - - # THe custom attribute is under the properties' first container under - # the ".arbGeomParams" - prop = props.getProperty(0) # get base property - - _property = None - try: - geo_params = prop.getProperty('.arbGeomParams') - _property = geo_params.getProperty(attr) - except KeyError: - if verbose: - log.debug("Missing attr on: {0}".format(name)) - continue - - if not _property.isConstant(): - log.warning("Id not constant on: {0}".format(name)) - - # Get first value sample - value = _property.getValue()[0] - - obj_ids[name] = value - - return obj_ids - - -def get_alembic_ids_cache(path): - # type: (str) -> dict - """Build a id to node mapping in Alembic file. - - Nodes without IDs are ignored. - - Returns: - dict: Mapping of id to nodes in the Alembic. - - """ - node_ids = get_alembic_paths_by_property(path, attr="cbId") - id_nodes = defaultdict(list) - for node, _id in six.iteritems(node_ids): - id_nodes[_id].append(node) - - return dict(six.iteritems(id_nodes)) - - def assign_vrayproxy_shaders(vrayproxy, assignments): # type: (str, dict) -> None """Assign shaders to content of Vray Proxy. diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 91712e6672..12223216cd 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -1459,7 +1459,7 @@ ] }, "reference_loader": { - "namespace": "{asset_name}_{subset}_##", + "namespace": "{asset_name}_{subset}_##_", "group_name": "_GRP" } },