diff --git a/openpype/hosts/maya/api/customize.py b/openpype/hosts/maya/api/customize.py index 37fd543315..683e6b24b0 100644 --- a/openpype/hosts/maya/api/customize.py +++ b/openpype/hosts/maya/api/customize.py @@ -5,7 +5,7 @@ import logging from functools import partial -import maya.cmds as mc +import maya.cmds as cmds import maya.mel as mel from openpype.api import resources @@ -30,9 +30,9 @@ def override_component_mask_commands(): log.info("Installing override_component_mask_commands..") # Get all object mask buttons - buttons = mc.formLayout("objectMaskIcons", - query=True, - childArray=True) + buttons = cmds.formLayout("objectMaskIcons", + query=True, + childArray=True) # Skip the triangle list item buttons = [btn for btn in buttons if btn != "objPickMenuLayout"] @@ -43,14 +43,14 @@ def override_component_mask_commands(): # toggle the others based on whether any of the buttons # was remaining active after the toggle, if not then # enable all - if mc.getModifiers() == 4: # = CTRL + if cmds.getModifiers() == 4: # = CTRL state = True - active = [mc.iconTextCheckBox(btn, query=True, value=True) for btn - in buttons] + active = [cmds.iconTextCheckBox(btn, query=True, value=True) + for btn in buttons] if any(active): - mc.selectType(allObjects=False) + cmds.selectType(allObjects=False) else: - mc.selectType(allObjects=True) + cmds.selectType(allObjects=True) # Replace #1 with the current button state cmd = raw_command.replace(" #1", " {}".format(int(state))) @@ -63,13 +63,13 @@ def override_component_mask_commands(): # try to implement the fix. (This also allows us to # "uninstall" the behavior later) if btn not in COMPONENT_MASK_ORIGINAL: - original = mc.iconTextCheckBox(btn, query=True, cc=True) + original = cmds.iconTextCheckBox(btn, query=True, cc=True) COMPONENT_MASK_ORIGINAL[btn] = original # Assign the special callback original = COMPONENT_MASK_ORIGINAL[btn] new_fn = partial(on_changed_callback, original) - mc.iconTextCheckBox(btn, edit=True, cc=new_fn) + cmds.iconTextCheckBox(btn, edit=True, cc=new_fn) def override_toolbox_ui(): @@ -78,25 +78,36 @@ def override_toolbox_ui(): parent_widget = get_main_window() # Ensure the maya web icon on toolbox exists - web_button = "ToolBox|MainToolboxLayout|mayaWebButton" - if not mc.iconTextButton(web_button, query=True, exists=True): + button_names = [ + # Maya 2022.1+ with maya.cmds.iconTextStaticLabel + "ToolBox|MainToolboxLayout|mayaHomeToolboxButton", + # Older with maya.cmds.iconTextButton + "ToolBox|MainToolboxLayout|mayaWebButton" + ] + for name in button_names: + if cmds.control(name, query=True, exists=True): + web_button = name + break + else: + # Button does not exist + log.warning("Can't find Maya Home/Web button to override toolbox ui..") return - mc.iconTextButton(web_button, edit=True, visible=False) + cmds.control(web_button, edit=True, visible=False) # real = 32, but 36 with padding - according to toolbox mel script icon_size = 36 parent = web_button.rsplit("|", 1)[0] # Ensure the parent is a formLayout - if not mc.objectTypeUI(parent) == "formLayout": + if not cmds.objectTypeUI(parent) == "formLayout": return # Create our controls controls = [] controls.append( - mc.iconTextButton( + cmds.iconTextButton( "pype_toolbox_lookmanager", annotation="Look Manager", label="Look Manager", @@ -109,7 +120,7 @@ def override_toolbox_ui(): ) controls.append( - mc.iconTextButton( + cmds.iconTextButton( "pype_toolbox_workfiles", annotation="Work Files", label="Work Files", @@ -124,7 +135,7 @@ def override_toolbox_ui(): ) controls.append( - mc.iconTextButton( + cmds.iconTextButton( "pype_toolbox_loader", annotation="Loader", label="Loader", @@ -139,7 +150,7 @@ def override_toolbox_ui(): ) controls.append( - mc.iconTextButton( + cmds.iconTextButton( "pype_toolbox_manager", annotation="Inventory", label="Inventory", @@ -159,7 +170,7 @@ def override_toolbox_ui(): for i, control in enumerate(controls): previous = controls[i - 1] if i > 0 else web_button - mc.formLayout(parent, edit=True, - attachControl=[control, "bottom", 0, previous], - attachForm=([control, "left", 1], - [control, "right", 1])) + cmds.formLayout(parent, edit=True, + attachControl=[control, "bottom", 0, previous], + attachForm=([control, "left", 1], + [control, "right", 1])) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 0b05182bb1..c2a140548a 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -8,7 +8,6 @@ import math import json import logging -import itertools import contextlib from collections import OrderedDict, defaultdict from math import ceil @@ -267,8 +266,10 @@ def float_round(num, places=0, direction=ceil): def pairwise(iterable): """s -> (s0,s1), (s2,s3), (s4, s5), ...""" + from six.moves import zip + a = iter(iterable) - return itertools.izip(a, a) + return zip(a, a) def export_alembic(nodes, @@ -2986,7 +2987,27 @@ def set_colorspace(): """ project_name = os.getenv("AVALON_PROJECT") imageio = get_anatomy_settings(project_name)["imageio"]["maya"] - root_dict = imageio["colorManagementPreference"] + + # Maya 2022+ introduces new OCIO v2 color management settings that + # can override the old color managenement preferences. OpenPype has + # separate settings for both so we fall back when necessary. + use_ocio_v2 = imageio["colorManagementPreference_v2"]["enabled"] + required_maya_version = 2022 + maya_version = int(cmds.about(version=True)) + maya_supports_ocio_v2 = maya_version >= required_maya_version + if use_ocio_v2 and not maya_supports_ocio_v2: + # Fallback to legacy behavior with a warning + log.warning("Color Management Preference v2 is enabled but not " + "supported by current Maya version: {} (< {}). Falling " + "back to legacy settings.".format( + maya_version, required_maya_version) + ) + use_ocio_v2 = False + + if use_ocio_v2: + root_dict = imageio["colorManagementPreference_v2"] + else: + root_dict = imageio["colorManagementPreference"] if not isinstance(root_dict, dict): msg = "set_colorspace(): argument should be dictionary" @@ -2994,11 +3015,12 @@ def set_colorspace(): log.debug(">> root_dict: {}".format(root_dict)) - # first enable color management + # enable color management cmds.colorManagementPrefs(e=True, cmEnabled=True) cmds.colorManagementPrefs(e=True, ocioRulesEnabled=True) - # second set config path + # set config path + custom_ocio_config = False if root_dict.get("configFilePath"): unresolved_path = root_dict["configFilePath"] ocio_paths = unresolved_path[platform.system().lower()] @@ -3015,16 +3037,50 @@ def set_colorspace(): cmds.colorManagementPrefs(e=True, cmConfigFileEnabled=True) log.debug("maya '{}' changed to: {}".format( "configFilePath", resolved_path)) - root_dict.pop("configFilePath") + custom_ocio_config = True else: cmds.colorManagementPrefs(e=True, cmConfigFileEnabled=False) - cmds.colorManagementPrefs(e=True, configFilePath="" ) + cmds.colorManagementPrefs(e=True, configFilePath="") - # third set rendering space and view transform - renderSpace = root_dict["renderSpace"] - cmds.colorManagementPrefs(e=True, renderingSpaceName=renderSpace) - viewTransform = root_dict["viewTransform"] - cmds.colorManagementPrefs(e=True, viewTransformName=viewTransform) + # If no custom OCIO config file was set we make sure that Maya 2022+ + # either chooses between Maya's newer default v2 or legacy config based + # on OpenPype setting to use ocio v2 or not. + if maya_supports_ocio_v2 and not custom_ocio_config: + if use_ocio_v2: + # Use Maya 2022+ default OCIO v2 config + log.info("Setting default Maya OCIO v2 config") + cmds.colorManagementPrefs(edit=True, configFilePath="") + else: + # Set the Maya default config file path + log.info("Setting default Maya OCIO v1 legacy config") + cmds.colorManagementPrefs(edit=True, configFilePath="legacy") + + # set color spaces for rendering space and view transforms + def _colormanage(**kwargs): + """Wrapper around `cmds.colorManagementPrefs`. + + This logs errors instead of raising an error so color management + settings get applied as much as possible. + + """ + assert len(kwargs) == 1, "Must receive one keyword argument" + try: + cmds.colorManagementPrefs(edit=True, **kwargs) + log.debug("Setting Color Management Preference: {}".format(kwargs)) + except RuntimeError as exc: + log.error(exc) + + if use_ocio_v2: + _colormanage(renderingSpaceName=root_dict["renderSpace"]) + _colormanage(displayName=root_dict["displayName"]) + _colormanage(viewName=root_dict["viewName"]) + else: + _colormanage(renderingSpaceName=root_dict["renderSpace"]) + if maya_supports_ocio_v2: + _colormanage(viewName=root_dict["viewTransform"]) + _colormanage(displayName="legacy") + else: + _colormanage(viewTransformName=root_dict["viewTransform"]) @contextlib.contextmanager diff --git a/openpype/hosts/maya/plugins/create/create_render.py b/openpype/hosts/maya/plugins/create/create_render.py index 97a190d57d..743ec26778 100644 --- a/openpype/hosts/maya/plugins/create/create_render.py +++ b/openpype/hosts/maya/plugins/create/create_render.py @@ -253,7 +253,7 @@ class CreateRender(plugin.Creator): # get pools pool_names = [] - self.server_aliases = self.deadline_servers.keys() + self.server_aliases = list(self.deadline_servers.keys()) self.data["deadlineServers"] = self.server_aliases self.data["suspendPublishJob"] = False self.data["review"] = True @@ -286,15 +286,12 @@ class CreateRender(plugin.Creator): raise RuntimeError("Both Deadline and Muster are enabled") if deadline_enabled: - # if default server is not between selected, use first one for - # initial list of pools. try: deadline_url = self.deadline_servers["default"] except KeyError: - deadline_url = [ - self.deadline_servers[k] - for k in self.deadline_servers.keys() - ][0] + # if 'default' server is not between selected, + # use first one for initial list of pools. + deadline_url = next(iter(self.deadline_servers.values())) pool_names = self._get_deadline_pools(deadline_url) diff --git a/openpype/hosts/maya/plugins/publish/collect_look.py b/openpype/hosts/maya/plugins/publish/collect_look.py index d39750e917..b6a76f1e21 100644 --- a/openpype/hosts/maya/plugins/publish/collect_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_look.py @@ -320,7 +320,7 @@ class CollectLook(pyblish.api.InstancePlugin): # Collect file nodes used by shading engines (if we have any) files = [] - look_sets = sets.keys() + look_sets = list(sets.keys()) shader_attrs = [ "surfaceShader", "volumeShader", diff --git a/openpype/hosts/maya/plugins/publish/collect_render.py b/openpype/hosts/maya/plugins/publish/collect_render.py index 13ae1924b9..d99e81573b 100644 --- a/openpype/hosts/maya/plugins/publish/collect_render.py +++ b/openpype/hosts/maya/plugins/publish/collect_render.py @@ -234,13 +234,14 @@ class CollectMayaRender(pyblish.api.ContextPlugin): publish_meta_path = None for aov in exp_files: full_paths = [] - for file in aov[aov.keys()[0]]: + aov_first_key = list(aov.keys())[0] + for file in aov[aov_first_key]: full_path = os.path.join(workspace, default_render_file, file) full_path = full_path.replace("\\", "/") full_paths.append(full_path) publish_meta_path = os.path.dirname(full_path) - aov_dict[aov.keys()[0]] = full_paths + aov_dict[aov_first_key] = full_paths frame_start_render = int(self.get_render_attribute( "startFrame", layer=layer_name)) diff --git a/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py b/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py index 9d25b147de..3015b1ac24 100644 --- a/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py +++ b/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py @@ -43,7 +43,8 @@ def grouper(iterable, n, fillvalue=None): """ args = [iter(iterable)] * n - return itertools.izip_longest(fillvalue=fillvalue, *args) + from six.moves import zip_longest + return zip_longest(fillvalue=fillvalue, *args) def unlock(plug): diff --git a/openpype/hosts/maya/plugins/publish/extract_look.py b/openpype/hosts/maya/plugins/publish/extract_look.py index fe89038a24..a9a2a7b60c 100644 --- a/openpype/hosts/maya/plugins/publish/extract_look.py +++ b/openpype/hosts/maya/plugins/publish/extract_look.py @@ -4,6 +4,7 @@ import os import sys import json import tempfile +import platform import contextlib import subprocess from collections import OrderedDict @@ -62,6 +63,11 @@ def maketx(source, destination, *args): from openpype.lib import get_oiio_tools_path maketx_path = get_oiio_tools_path("maketx") + + if platform.system().lower() == "windows": + # Ensure .exe extension + maketx_path += ".exe" + if not os.path.exists(maketx_path): print( "OIIO tool not found in {}".format(maketx_path)) @@ -216,7 +222,7 @@ class ExtractLook(openpype.api.Extractor): self.log.info("Extract sets (%s) ..." % _scene_type) lookdata = instance.data["lookData"] relationships = lookdata["relationships"] - sets = relationships.keys() + sets = list(relationships.keys()) if not sets: self.log.info("No sets found") return diff --git a/openpype/hosts/maya/plugins/publish/validate_ass_relative_paths.py b/openpype/hosts/maya/plugins/publish/validate_ass_relative_paths.py index 3625d4ab32..5fb9bd98b1 100644 --- a/openpype/hosts/maya/plugins/publish/validate_ass_relative_paths.py +++ b/openpype/hosts/maya/plugins/publish/validate_ass_relative_paths.py @@ -110,9 +110,9 @@ class ValidateAssRelativePaths(pyblish.api.InstancePlugin): Maya API will return a list of values, which need to be properly handled to evaluate properly. """ - if isinstance(attr_val, types.BooleanType): + if isinstance(attr_val, bool): return attr_val - elif isinstance(attr_val, (types.ListType, types.GeneratorType)): + elif isinstance(attr_val, (list, types.GeneratorType)): return any(attr_val) else: return bool(attr_val) diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_overlapping_uvs.py b/openpype/hosts/maya/plugins/publish/validate_mesh_overlapping_uvs.py index 5ce422239d..bf95d8ba09 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_overlapping_uvs.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_overlapping_uvs.py @@ -5,6 +5,8 @@ import math import maya.api.OpenMaya as om import pymel.core as pm +from six.moves import xrange + class GetOverlappingUVs(object): diff --git a/openpype/hosts/maya/plugins/publish/validate_vray_referenced_aovs.py b/openpype/hosts/maya/plugins/publish/validate_vray_referenced_aovs.py index 6cfbd4049b..7a48c29b7d 100644 --- a/openpype/hosts/maya/plugins/publish/validate_vray_referenced_aovs.py +++ b/openpype/hosts/maya/plugins/publish/validate_vray_referenced_aovs.py @@ -82,9 +82,9 @@ class ValidateVrayReferencedAOVs(pyblish.api.InstancePlugin): bool: cast Maya attribute to Pythons boolean value. """ - if isinstance(attr_val, types.BooleanType): + if isinstance(attr_val, bool): return attr_val - elif isinstance(attr_val, (types.ListType, types.GeneratorType)): + elif isinstance(attr_val, (list, types.GeneratorType)): return any(attr_val) else: return bool(attr_val) diff --git a/openpype/settings/defaults/project_anatomy/imageio.json b/openpype/settings/defaults/project_anatomy/imageio.json index 4a1496fe1a..1c86509155 100644 --- a/openpype/settings/defaults/project_anatomy/imageio.json +++ b/openpype/settings/defaults/project_anatomy/imageio.json @@ -177,6 +177,17 @@ } }, "maya": { + "colorManagementPreference_v2": { + "enabled": true, + "configFilePath": { + "windows": [], + "darwin": [], + "linux": [] + }, + "renderSpace": "ACEScg", + "viewName": "ACES 1.0 SDR-video", + "displayName": "sRGB" + }, "colorManagementPreference": { "configFilePath": { "windows": [], diff --git a/openpype/settings/defaults/system_settings/applications.json b/openpype/settings/defaults/system_settings/applications.json index 2f99200a88..0fb99a2608 100644 --- a/openpype/settings/defaults/system_settings/applications.json +++ b/openpype/settings/defaults/system_settings/applications.json @@ -93,7 +93,7 @@ } }, "__dynamic_keys_labels__": { - "2022": "2022 (Testing Only)" + "2022": "2022" } } }, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_imageio.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_imageio.json index e000adacb0..3bec19c3d0 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_imageio.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_imageio.json @@ -377,11 +377,47 @@ "type": "dict", "label": "Maya", "children": [ + { + "key": "colorManagementPreference_v2", + "type": "dict", + "label": "Color Management Preference v2 (Maya 2022+)", + "collapsible": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Use Color Management Preference v2" + }, + { + "type": "path", + "key": "configFilePath", + "label": "OCIO Config File Path", + "multiplatform": true, + "multipath": true + }, + { + "type": "text", + "key": "renderSpace", + "label": "Rendering Space" + }, + { + "type": "text", + "key": "displayName", + "label": "Display" + }, + { + "type": "text", + "key": "viewName", + "label": "View" + } + ] + }, { "key": "colorManagementPreference", "type": "dict", - "label": "Color Managment Preference", - "collapsible": false, + "label": "Color Management Preference (legacy)", + "collapsible": true, "children": [ { "type": "path", @@ -401,7 +437,7 @@ "label": "Viewer Transform" } ] - } + } ] }, { diff --git a/repos/avalon-core b/repos/avalon-core index 159d2f23e4..ffe9e910f1 160000 --- a/repos/avalon-core +++ b/repos/avalon-core @@ -1 +1 @@ -Subproject commit 159d2f23e4c79c04dfac57b68d2ee6ac67adec1b +Subproject commit ffe9e910f1f382e222d457d8e4a8426c41ed43ae