diff --git a/openpype/cli.py b/openpype/cli.py index f4855e47f9..5c47088a44 100644 --- a/openpype/cli.py +++ b/openpype/cli.py @@ -30,8 +30,14 @@ def main(ctx): It wraps different commands together. """ + if ctx.invoked_subcommand is None: - ctx.invoke(tray) + # Print help if headless mode is used + if os.environ.get("OPENPYPE_HEADLESS_MODE") == "1": + print(ctx.get_help()) + sys.exit(0) + else: + ctx.invoke(tray) @main.command() diff --git a/openpype/hosts/aftereffects/api/launch_logic.py b/openpype/hosts/aftereffects/api/launch_logic.py index 9dcaebd5b9..c428043d99 100644 --- a/openpype/hosts/aftereffects/api/launch_logic.py +++ b/openpype/hosts/aftereffects/api/launch_logic.py @@ -10,7 +10,7 @@ from wsrpc_aiohttp import ( WebSocketAsync ) -from Qt import QtCore +from qtpy import QtCore from openpype.lib import Logger from openpype.pipeline import legacy_io diff --git a/openpype/hosts/aftereffects/api/lib.py b/openpype/hosts/aftereffects/api/lib.py index cfcb76ff81..a39af5c81f 100644 --- a/openpype/hosts/aftereffects/api/lib.py +++ b/openpype/hosts/aftereffects/api/lib.py @@ -7,7 +7,7 @@ import traceback import logging from functools import partial -from Qt import QtWidgets +from qtpy import QtWidgets from openpype.pipeline import install_host from openpype.modules import ModulesManager diff --git a/openpype/hosts/aftereffects/api/pipeline.py b/openpype/hosts/aftereffects/api/pipeline.py index 3f18347d44..95f6f3235b 100644 --- a/openpype/hosts/aftereffects/api/pipeline.py +++ b/openpype/hosts/aftereffects/api/pipeline.py @@ -1,5 +1,6 @@ import os -from Qt import QtWidgets + +from qtpy import QtWidgets import pyblish.api diff --git a/openpype/hosts/blender/api/ops.py b/openpype/hosts/blender/api/ops.py index e0e09277df..481c199db2 100644 --- a/openpype/hosts/blender/api/ops.py +++ b/openpype/hosts/blender/api/ops.py @@ -10,7 +10,7 @@ from pathlib import Path from types import ModuleType from typing import Dict, List, Optional, Union -from Qt import QtWidgets, QtCore +from qtpy import QtWidgets, QtCore import bpy import bpy.utils.previews diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index 1a3c008069..ada512d7ac 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -32,11 +32,6 @@ class CreateCamera(plugin.Creator): subset = self.data["subset"] name = plugin.asset_name(asset, subset) - camera = bpy.data.cameras.new(subset) - camera_obj = bpy.data.objects.new(subset, camera) - - instances.objects.link(camera_obj) - asset_group = bpy.data.objects.new(name=name, object_data=None) asset_group.empty_display_type = 'SINGLE_ARROW' instances.objects.link(asset_group) @@ -53,6 +48,11 @@ class CreateCamera(plugin.Creator): bpy.ops.object.parent_set(keep_transform=True) else: plugin.deselect_all() + camera = bpy.data.cameras.new(subset) + camera_obj = bpy.data.objects.new(subset, camera) + + instances.objects.link(camera_obj) + camera_obj.select_set(True) asset_group.select_set(True) bpy.context.view_layer.objects.active = asset_group diff --git a/openpype/hosts/blender/plugins/load/load_layout_blend.py b/openpype/hosts/blender/plugins/load/load_layout_blend.py index e0124053bf..7d2fd23444 100644 --- a/openpype/hosts/blender/plugins/load/load_layout_blend.py +++ b/openpype/hosts/blender/plugins/load/load_layout_blend.py @@ -48,8 +48,14 @@ class BlendLayoutLoader(plugin.AssetLoader): bpy.data.objects.remove(obj) def _remove_asset_and_library(self, asset_group): + if not asset_group.get(AVALON_PROPERTY): + return + libpath = asset_group.get(AVALON_PROPERTY).get('libpath') + if not libpath: + return + # Check how many assets use the same library count = 0 for obj in bpy.data.collections.get(AVALON_CONTAINERS).all_objects: @@ -63,10 +69,12 @@ class BlendLayoutLoader(plugin.AssetLoader): # If it is the last object to use that library, remove it if count == 1: library = bpy.data.libraries.get(bpy.path.basename(libpath)) - bpy.data.libraries.remove(library) + if library: + bpy.data.libraries.remove(library) def _process( - self, libpath, asset_group, group_name, asset, representation, actions + self, libpath, asset_group, group_name, asset, representation, + actions, anim_instances ): with bpy.data.libraries.load( libpath, link=True, relative=False @@ -140,12 +148,12 @@ class BlendLayoutLoader(plugin.AssetLoader): elif local_obj.type == 'ARMATURE': plugin.prepare_data(local_obj.data) - if action is not None: + if action: if local_obj.animation_data is None: local_obj.animation_data_create() local_obj.animation_data.action = action elif (local_obj.animation_data and - local_obj.animation_data.action is not None): + local_obj.animation_data.action): plugin.prepare_data( local_obj.animation_data.action) @@ -157,19 +165,26 @@ class BlendLayoutLoader(plugin.AssetLoader): t.id = local_obj elif local_obj.type == 'EMPTY': - creator_plugin = get_legacy_creator_by_name("CreateAnimation") - if not creator_plugin: - raise ValueError("Creator plugin \"CreateAnimation\" was " - "not found.") + if (not anim_instances or + (anim_instances and + local_obj.name not in anim_instances.keys())): + avalon = local_obj.get(AVALON_PROPERTY) + if avalon and avalon.get('family') == 'rig': + creator_plugin = get_legacy_creator_by_name( + "CreateAnimation") + if not creator_plugin: + raise ValueError( + "Creator plugin \"CreateAnimation\" was " + "not found.") - legacy_create( - creator_plugin, - name=local_obj.name.split(':')[-1] + "_animation", - asset=asset, - options={"useSelection": False, - "asset_group": local_obj}, - data={"dependencies": representation} - ) + legacy_create( + creator_plugin, + name=local_obj.name.split(':')[-1] + "_animation", + asset=asset, + options={"useSelection": False, + "asset_group": local_obj}, + data={"dependencies": representation} + ) if not local_obj.get(AVALON_PROPERTY): local_obj[AVALON_PROPERTY] = dict() @@ -272,7 +287,8 @@ class BlendLayoutLoader(plugin.AssetLoader): avalon_container.objects.link(asset_group) objects = self._process( - libpath, asset_group, group_name, asset, representation, None) + libpath, asset_group, group_name, asset, representation, + None, None) for child in asset_group.children: if child.get(AVALON_PROPERTY): @@ -352,10 +368,20 @@ class BlendLayoutLoader(plugin.AssetLoader): return actions = {} + anim_instances = {} for obj in asset_group.children: obj_meta = obj.get(AVALON_PROPERTY) if obj_meta.get('family') == 'rig': + # Get animation instance + collections = list(obj.users_collection) + for c in collections: + avalon = c.get(AVALON_PROPERTY) + if avalon and avalon.get('family') == 'animation': + anim_instances[obj.name] = c.name + break + + # Get armature's action rig = None for child in obj.children: if child.type == 'ARMATURE': @@ -384,9 +410,26 @@ class BlendLayoutLoader(plugin.AssetLoader): # If it is the last object to use that library, remove it if count == 1: library = bpy.data.libraries.get(bpy.path.basename(group_libpath)) - bpy.data.libraries.remove(library) + if library: + bpy.data.libraries.remove(library) - self._process(str(libpath), asset_group, object_name, actions) + asset = container.get("asset_name").split("_")[0] + + self._process( + str(libpath), asset_group, object_name, asset, + str(representation.get("_id")), actions, anim_instances + ) + + # Link the new objects to the animation collection + for inst in anim_instances.keys(): + try: + obj = bpy.data.objects[inst] + bpy.data.collections[anim_instances[inst]].objects.link(obj) + except KeyError: + self.log.info(f"Object {inst} does not exist anymore.") + coll = bpy.data.collections.get(anim_instances[inst]) + if (coll): + bpy.data.collections.remove(coll) avalon_container = bpy.data.collections.get(AVALON_CONTAINERS) for child in asset_group.children: diff --git a/openpype/hosts/fusion/api/menu.py b/openpype/hosts/fusion/api/menu.py index 39126935e6..42fbab70a6 100644 --- a/openpype/hosts/fusion/api/menu.py +++ b/openpype/hosts/fusion/api/menu.py @@ -1,6 +1,6 @@ import sys -from Qt import QtWidgets, QtCore, QtGui +from qtpy import QtWidgets, QtCore, QtGui from openpype.tools.utils import host_tools from openpype.style import load_stylesheet diff --git a/openpype/hosts/fusion/api/pipeline.py b/openpype/hosts/fusion/api/pipeline.py index b6092f7c1b..6315fe443d 100644 --- a/openpype/hosts/fusion/api/pipeline.py +++ b/openpype/hosts/fusion/api/pipeline.py @@ -6,7 +6,7 @@ import sys import logging import pyblish.api -from Qt import QtCore +from qtpy import QtCore from openpype.lib import ( Logger, diff --git a/openpype/hosts/fusion/api/pulse.py b/openpype/hosts/fusion/api/pulse.py index eb7ef3785d..762f05ba7e 100644 --- a/openpype/hosts/fusion/api/pulse.py +++ b/openpype/hosts/fusion/api/pulse.py @@ -1,7 +1,7 @@ import os import sys -from Qt import QtCore +from qtpy import QtCore class PulseThread(QtCore.QThread): diff --git a/openpype/hosts/fusion/deploy/MenuScripts/install_pyside2.py b/openpype/hosts/fusion/deploy/MenuScripts/install_pyside2.py index ab9f13ce05..e1240fd677 100644 --- a/openpype/hosts/fusion/deploy/MenuScripts/install_pyside2.py +++ b/openpype/hosts/fusion/deploy/MenuScripts/install_pyside2.py @@ -6,10 +6,10 @@ import importlib try: - from Qt import QtWidgets # noqa: F401 - from Qt import __binding__ - print(f"Qt binding: {__binding__}") - mod = importlib.import_module(__binding__) + from qtpy import API_NAME + + print(f"Qt binding: {API_NAME}") + mod = importlib.import_module(API_NAME) print(f"Qt path: {mod.__file__}") print("Qt library found, nothing to do..") diff --git a/openpype/hosts/fusion/deploy/Scripts/Comp/OpenPype/switch_ui.py b/openpype/hosts/fusion/deploy/Scripts/Comp/OpenPype/switch_ui.py index 93f775b24b..f08dc0bf2c 100644 --- a/openpype/hosts/fusion/deploy/Scripts/Comp/OpenPype/switch_ui.py +++ b/openpype/hosts/fusion/deploy/Scripts/Comp/OpenPype/switch_ui.py @@ -3,7 +3,7 @@ import sys import glob import logging -from Qt import QtWidgets, QtCore +from qtpy import QtWidgets, QtCore import qtawesome as qta diff --git a/openpype/hosts/fusion/plugins/inventory/set_tool_color.py b/openpype/hosts/fusion/plugins/inventory/set_tool_color.py index c7530ce674..a057ad1e89 100644 --- a/openpype/hosts/fusion/plugins/inventory/set_tool_color.py +++ b/openpype/hosts/fusion/plugins/inventory/set_tool_color.py @@ -1,4 +1,4 @@ -from Qt import QtGui, QtWidgets +from qtpy import QtGui, QtWidgets from openpype.pipeline import InventoryAction from openpype import style diff --git a/openpype/hosts/fusion/scripts/set_rendermode.py b/openpype/hosts/fusion/scripts/set_rendermode.py index f0638e4fe3..9d2bfef310 100644 --- a/openpype/hosts/fusion/scripts/set_rendermode.py +++ b/openpype/hosts/fusion/scripts/set_rendermode.py @@ -1,4 +1,4 @@ -from Qt import QtWidgets +from qtpy import QtWidgets import qtawesome from openpype.hosts.fusion.api import get_current_comp diff --git a/openpype/hosts/harmony/api/lib.py b/openpype/hosts/harmony/api/lib.py index e5e7ad1b7e..e1e77bfbee 100644 --- a/openpype/hosts/harmony/api/lib.py +++ b/openpype/hosts/harmony/api/lib.py @@ -14,7 +14,7 @@ import json import signal import time from uuid import uuid4 -from Qt import QtWidgets, QtCore, QtGui +from qtpy import QtWidgets, QtCore, QtGui import collections from .server import Server diff --git a/openpype/hosts/houdini/api/pipeline.py b/openpype/hosts/houdini/api/pipeline.py index b0791fcb6c..f8e2c16d21 100644 --- a/openpype/hosts/houdini/api/pipeline.py +++ b/openpype/hosts/houdini/api/pipeline.py @@ -7,7 +7,7 @@ import contextlib import hou # noqa -from openpype.host import HostBase, IWorkfileHost, ILoadHost, INewPublisher +from openpype.host import HostBase, IWorkfileHost, ILoadHost, IPublishHost import pyblish.api @@ -40,7 +40,7 @@ CREATE_PATH = os.path.join(PLUGINS_DIR, "create") INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory") -class HoudiniHost(HostBase, IWorkfileHost, ILoadHost, INewPublisher): +class HoudiniHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): name = "houdini" def __init__(self): diff --git a/openpype/hosts/maya/api/gltf.py b/openpype/hosts/maya/api/gltf.py new file mode 100644 index 0000000000..2a983f1573 --- /dev/null +++ b/openpype/hosts/maya/api/gltf.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- +"""Tools to work with GLTF.""" +import logging + +from maya import cmds, mel # noqa + +log = logging.getLogger(__name__) + +_gltf_options = { + "of": str, # outputFolder + "cpr": str, # copyright + "sno": bool, # selectedNodeOnly + "sn": str, # sceneName + "glb": bool, # binary + "nbu": bool, # niceBufferURIs + "hbu": bool, # hashBufferURI + "ext": bool, # externalTextures + "ivt": int, # initialValuesTime + "acn": str, # animationClipName + "ast": int, # animationClipStartTime + "aet": int, # animationClipEndTime + "afr": float, # animationClipFrameRate + "dsa": int, # detectStepAnimations + "mpa": str, # meshPrimitiveAttributes + "bpa": str, # blendPrimitiveAttributes + "i32": bool, # force32bitIndices + "ssm": bool, # skipStandardMaterials + "eut": bool, # excludeUnusedTexcoord + "dm": bool, # defaultMaterial + "cm": bool, # colorizeMaterials + "dmy": str, # dumpMaya + "dgl": str, # dumpGLTF + "imd": str, # ignoreMeshDeformers + "ssc": bool, # skipSkinClusters + "sbs": bool, # skipBlendShapes + "rvp": bool, # redrawViewport + "vno": bool # visibleNodesOnly +} + + +def extract_gltf(parent_dir, + filename, + **kwargs): + + """Sets GLTF export options from data in the instance. + + """ + + cmds.loadPlugin('maya2glTF', quiet=True) + # load the UI to run mel command + mel.eval("maya2glTF_UI()") + + parent_dir = parent_dir.replace('\\', '/') + options = { + "dsa": 1, + "glb": True + } + options.update(kwargs) + + for key, value in options.copy().items(): + if key not in _gltf_options: + log.warning("extract_gltf() does not support option '%s'. " + "Flag will be ignored..", key) + options.pop(key) + options.pop(value) + continue + + job_args = list() + default_opt = "maya2glTF -of \"{0}\" -sn \"{1}\"".format(parent_dir, filename) # noqa + job_args.append(default_opt) + + for key, value in options.items(): + if isinstance(value, str): + job_args.append("-{0} \"{1}\"".format(key, value)) + elif isinstance(value, bool): + if value: + job_args.append("-{0}".format(key)) + else: + job_args.append("-{0} {1}".format(key, value)) + + job_str = " ".join(job_args) + log.info("{}".format(job_str)) + mel.eval(job_str) + + # close the gltf export after finish the export + gltf_UI = "maya2glTF_exporter_window" + if cmds.window(gltf_UI, q=True, exists=True): + cmds.deleteUI(gltf_UI) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index b2bbb823aa..ca826946f8 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -128,13 +128,18 @@ def get_main_window(): @contextlib.contextmanager def suspended_refresh(suspend=True): - """Suspend viewport refreshes""" - original_state = cmds.refresh(query=True, suspend=True) + """Suspend viewport refreshes + + cmds.ogs(pause=True) is a toggle so we cant pass False. + """ + original_state = cmds.ogs(query=True, pause=True) try: - cmds.refresh(suspend=suspend) + if suspend and not original_state: + cmds.ogs(pause=True) yield finally: - cmds.refresh(suspend=original_state) + if suspend and not original_state: + cmds.ogs(pause=True) @contextlib.contextmanager @@ -3436,3 +3441,8 @@ def iter_visible_nodes_in_range(nodes, start, end): # If no more nodes to process break the frame iterations.. if not node_dependencies: break + + +def get_attribute_input(attr): + connections = cmds.listConnections(attr, plugs=True, destination=False) + return connections[0] if connections else None diff --git a/openpype/hosts/maya/api/lib_rendersettings.py b/openpype/hosts/maya/api/lib_rendersettings.py index 2b996702c3..5161141ef9 100644 --- a/openpype/hosts/maya/api/lib_rendersettings.py +++ b/openpype/hosts/maya/api/lib_rendersettings.py @@ -95,21 +95,25 @@ class RenderSettings(object): if renderer == "redshift": self._set_redshift_settings(width, height) + mel.eval("redshiftUpdateActiveAovList") def _set_arnold_settings(self, width, height): """Sets settings for Arnold.""" from mtoa.core import createOptions # noqa from mtoa.aovs import AOVInterface # noqa createOptions() - arnold_render_presets = self._project_settings["maya"]["RenderSettings"]["arnold_renderer"] # noqa + render_settings = self._project_settings["maya"]["RenderSettings"] + arnold_render_presets = render_settings["arnold_renderer"] # noqa # Force resetting settings and AOV list to avoid having to deal with # AOV checking logic, for now. # This is a work around because the standard # function to revert render settings does not reset AOVs list in MtoA # Fetch current aovs in case there's any. current_aovs = AOVInterface().getAOVs() + remove_aovs = render_settings["remove_aovs"] + if remove_aovs: # Remove fetched AOVs - AOVInterface().removeAOVs(current_aovs) + AOVInterface().removeAOVs(current_aovs) mel.eval("unifiedRenderGlobalsRevertToDefault") img_ext = arnold_render_presets["image_format"] img_prefix = arnold_render_presets["image_prefix"] @@ -118,6 +122,8 @@ class RenderSettings(object): multi_exr = arnold_render_presets["multilayer_exr"] additional_options = arnold_render_presets["additional_options"] for aov in aovs: + if aov in current_aovs and not remove_aovs: + continue AOVInterface('defaultArnoldRenderOptions').addAOV(aov) cmds.setAttr("defaultResolution.width", width) @@ -141,12 +147,50 @@ class RenderSettings(object): def _set_redshift_settings(self, width, height): """Sets settings for Redshift.""" - redshift_render_presets = ( - self._project_settings - ["maya"] - ["RenderSettings"] - ["redshift_renderer"] - ) + render_settings = self._project_settings["maya"]["RenderSettings"] + redshift_render_presets = render_settings["redshift_renderer"] + + remove_aovs = render_settings["remove_aovs"] + all_rs_aovs = cmds.ls(type='RedshiftAOV') + if remove_aovs: + for aov in all_rs_aovs: + enabled = cmds.getAttr("{}.enabled".format(aov)) + if enabled: + cmds.delete(aov) + + redshift_aovs = redshift_render_presets["aov_list"] + # list all the aovs + all_rs_aovs = cmds.ls(type='RedshiftAOV') + for rs_aov in redshift_aovs: + rs_layername = rs_aov + if " " in rs_aov: + rs_renderlayer = rs_aov.replace(" ", "") + rs_layername = "rsAov_{}".format(rs_renderlayer) + else: + rs_layername = "rsAov_{}".format(rs_aov) + if rs_layername in all_rs_aovs: + continue + cmds.rsCreateAov(type=rs_aov) + # update the AOV list + mel.eval("redshiftUpdateActiveAovList") + + rs_p_engine = redshift_render_presets["primary_gi_engine"] + rs_s_engine = redshift_render_presets["secondary_gi_engine"] + + if int(rs_p_engine) or int(rs_s_engine) != 0: + cmds.setAttr("redshiftOptions.GIEnabled", 1) + if int(rs_p_engine) == 0: + # reset the primary GI Engine as default + cmds.setAttr("redshiftOptions.primaryGIEngine", 4) + if int(rs_s_engine) == 0: + # reset the secondary GI Engine as default + cmds.setAttr("redshiftOptions.secondaryGIEngine", 2) + else: + cmds.setAttr("redshiftOptions.GIEnabled", 0) + + cmds.setAttr("redshiftOptions.primaryGIEngine", int(rs_p_engine)) + cmds.setAttr("redshiftOptions.secondaryGIEngine", int(rs_s_engine)) + additional_options = redshift_render_presets["additional_options"] ext = redshift_render_presets["image_format"] img_exts = ["iff", "exr", "tif", "png", "tga", "jpg"] @@ -163,12 +207,31 @@ class RenderSettings(object): """Sets important settings for Vray.""" settings = cmds.ls(type="VRaySettingsNode") node = settings[0] if settings else cmds.createNode("VRaySettingsNode") - vray_render_presets = ( - self._project_settings - ["maya"] - ["RenderSettings"] - ["vray_renderer"] - ) + render_settings = self._project_settings["maya"]["RenderSettings"] + vray_render_presets = render_settings["vray_renderer"] + # vrayRenderElement + remove_aovs = render_settings["remove_aovs"] + all_vray_aovs = cmds.ls(type='VRayRenderElement') + lightSelect_aovs = cmds.ls(type='VRayRenderElementSet') + if remove_aovs: + for aov in all_vray_aovs: + # remove all aovs except LightSelect + enabled = cmds.getAttr("{}.enabled".format(aov)) + if enabled: + cmds.delete(aov) + # remove LightSelect + for light_aovs in lightSelect_aovs: + light_enabled = cmds.getAttr("{}.enabled".format(light_aovs)) + if light_enabled: + cmds.delete(lightSelect_aovs) + + vray_aovs = vray_render_presets["aov_list"] + for renderlayer in vray_aovs: + renderElement = "vrayAddRenderElement {}".format(renderlayer) + RE_name = mel.eval(renderElement) + # if there is more than one same render element + if RE_name.endswith("1"): + cmds.delete(RE_name) # Set aov separator # First we need to explicitly set the UI items in Render Settings # because that is also what V-Ray updates to when that Render Settings diff --git a/openpype/hosts/maya/api/plugin.py b/openpype/hosts/maya/api/plugin.py index 39d821f620..82df85a8be 100644 --- a/openpype/hosts/maya/api/plugin.py +++ b/openpype/hosts/maya/api/plugin.py @@ -217,7 +217,7 @@ class ReferenceLoader(Loader): # Need to save alembic settings and reapply, cause referencing resets # them to incoming data. - alembic_attrs = ["speed", "offset", "cycleType"] + alembic_attrs = ["speed", "offset", "cycleType", "time"] alembic_data = {} if representation["name"] == "abc": alembic_nodes = cmds.ls( @@ -226,7 +226,12 @@ class ReferenceLoader(Loader): if alembic_nodes: for attr in alembic_attrs: node_attr = "{}.{}".format(alembic_nodes[0], attr) - alembic_data[attr] = cmds.getAttr(node_attr) + data = { + "input": lib.get_attribute_input(node_attr), + "value": cmds.getAttr(node_attr) + } + + alembic_data[attr] = data else: self.log.debug("No alembic nodes found in {}".format(members)) @@ -263,8 +268,19 @@ class ReferenceLoader(Loader): "{}:*".format(namespace), type="AlembicNode" ) if alembic_nodes: - for attr, value in alembic_data.items(): - cmds.setAttr("{}.{}".format(alembic_nodes[0], attr), value) + alembic_node = alembic_nodes[0] # assume single AlembicNode + for attr, data in alembic_data.items(): + node_attr = "{}.{}".format(alembic_node, attr) + input = lib.get_attribute_input(node_attr) + if data["input"]: + if data["input"] != input: + cmds.connectAttr( + data["input"], node_attr, force=True + ) + else: + if input: + cmds.disconnectAttr(input, node_attr) + cmds.setAttr(node_attr, data["value"]) # Fix PLN-40 for older containers created with Avalon that had the # `.verticesOnlySet` set to True. diff --git a/openpype/hosts/maya/plugins/create/create_ass.py b/openpype/hosts/maya/plugins/create/create_ass.py index 39f226900a..935a068ca5 100644 --- a/openpype/hosts/maya/plugins/create/create_ass.py +++ b/openpype/hosts/maya/plugins/create/create_ass.py @@ -1,5 +1,3 @@ -from collections import OrderedDict - from openpype.hosts.maya.api import ( lib, plugin @@ -9,12 +7,26 @@ from maya import cmds class CreateAss(plugin.Creator): - """Arnold Archive""" + """Arnold Scene Source""" name = "ass" - label = "Ass StandIn" + label = "Arnold Scene Source" family = "ass" icon = "cube" + expandProcedurals = False + motionBlur = True + motionBlurKeys = 2 + motionBlurLength = 0.5 + maskOptions = False + maskCamera = False + maskLight = False + maskShape = False + maskShader = False + maskOverride = False + maskDriver = False + maskFilter = False + maskColor_manager = False + maskOperator = False def __init__(self, *args, **kwargs): super(CreateAss, self).__init__(*args, **kwargs) @@ -22,17 +34,27 @@ class CreateAss(plugin.Creator): # Add animation data self.data.update(lib.collect_animation_data()) - # Vertex colors with the geometry - self.data["exportSequence"] = False + self.data["expandProcedurals"] = self.expandProcedurals + self.data["motionBlur"] = self.motionBlur + self.data["motionBlurKeys"] = self.motionBlurKeys + self.data["motionBlurLength"] = self.motionBlurLength + + # Masks + self.data["maskOptions"] = self.maskOptions + self.data["maskCamera"] = self.maskCamera + self.data["maskLight"] = self.maskLight + self.data["maskShape"] = self.maskShape + self.data["maskShader"] = self.maskShader + self.data["maskOverride"] = self.maskOverride + self.data["maskDriver"] = self.maskDriver + self.data["maskFilter"] = self.maskFilter + self.data["maskColor_manager"] = self.maskColor_manager + self.data["maskOperator"] = self.maskOperator def process(self): instance = super(CreateAss, self).process() - # data = OrderedDict(**self.data) - - - - nodes = list() + nodes = [] if (self.options or {}).get("useSelection"): nodes = cmds.ls(selection=True) @@ -42,7 +64,3 @@ class CreateAss(plugin.Creator): assContent = cmds.sets(name="content_SET") assProxy = cmds.sets(name="proxy_SET", empty=True) cmds.sets([assContent, assProxy], forceElement=instance) - - # self.log.info(data) - # - # self.data = data diff --git a/openpype/hosts/maya/plugins/create/create_layout.py b/openpype/hosts/maya/plugins/create/create_layout.py index 6dc87430aa..1768a3d49e 100644 --- a/openpype/hosts/maya/plugins/create/create_layout.py +++ b/openpype/hosts/maya/plugins/create/create_layout.py @@ -8,3 +8,9 @@ class CreateLayout(plugin.Creator): label = "Layout" family = "layout" icon = "cubes" + + def __init__(self, *args, **kwargs): + super(CreateLayout, self).__init__(*args, **kwargs) + # enable this when you want to + # publish group of loaded asset + self.data["groupLoadedAssets"] = False diff --git a/openpype/hosts/maya/plugins/create/create_proxy_abc.py b/openpype/hosts/maya/plugins/create/create_proxy_abc.py new file mode 100644 index 0000000000..2946f7b530 --- /dev/null +++ b/openpype/hosts/maya/plugins/create/create_proxy_abc.py @@ -0,0 +1,35 @@ +from openpype.hosts.maya.api import ( + lib, + plugin +) + + +class CreateProxyAlembic(plugin.Creator): + """Proxy Alembic for animated data""" + + name = "proxyAbcMain" + label = "Proxy Alembic" + family = "proxyAbc" + icon = "gears" + write_color_sets = False + write_face_sets = False + + def __init__(self, *args, **kwargs): + super(CreateProxyAlembic, self).__init__(*args, **kwargs) + + # Add animation data + self.data.update(lib.collect_animation_data()) + + # Vertex colors with the geometry. + self.data["writeColorSets"] = self.write_color_sets + # Vertex colors with the geometry. + self.data["writeFaceSets"] = self.write_face_sets + # Default to exporting world-space + self.data["worldSpace"] = True + + # name suffix for the bounding box + self.data["nameSuffix"] = "_BBox" + + # Add options for custom attributes + self.data["attr"] = "" + self.data["attrPrefix"] = "" diff --git a/openpype/hosts/maya/plugins/create/create_unreal_skeletalmesh.py b/openpype/hosts/maya/plugins/create/create_unreal_skeletalmesh.py index 1a8e84c80d..6e72bf5324 100644 --- a/openpype/hosts/maya/plugins/create/create_unreal_skeletalmesh.py +++ b/openpype/hosts/maya/plugins/create/create_unreal_skeletalmesh.py @@ -48,3 +48,21 @@ class CreateUnrealSkeletalMesh(plugin.Creator): cmds.sets(node, forceElement=joints_set) else: cmds.sets(node, forceElement=geometry_set) + + # Add animation data + self.data.update(lib.collect_animation_data()) + + # Only renderable visible shapes + self.data["renderableOnly"] = False + # only nodes that are visible + self.data["visibleOnly"] = False + # Include parent groups + self.data["includeParentHierarchy"] = False + # Default to exporting world-space + self.data["worldSpace"] = True + # Default to suspend refresh. + self.data["refresh"] = False + + # Add options for custom attributes + self.data["attr"] = "" + self.data["attrPrefix"] = "" diff --git a/openpype/hosts/maya/plugins/load/actions.py b/openpype/hosts/maya/plugins/load/actions.py index eca1b27f34..9cc9180d6e 100644 --- a/openpype/hosts/maya/plugins/load/actions.py +++ b/openpype/hosts/maya/plugins/load/actions.py @@ -14,6 +14,7 @@ class SetFrameRangeLoader(load.LoaderPlugin): families = ["animation", "camera", + "proxyAbc", "pointcache"] representations = ["abc"] @@ -48,6 +49,7 @@ class SetFrameRangeWithHandlesLoader(load.LoaderPlugin): families = ["animation", "camera", + "proxyAbc", "pointcache"] representations = ["abc"] diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index 605a492e4d..70866a3ba6 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -11,7 +11,7 @@ from openpype.settings import get_project_settings class AlembicStandinLoader(load.LoaderPlugin): """Load Alembic as Arnold Standin""" - families = ["animation", "model", "pointcache"] + families = ["animation", "model", "proxyAbc", "pointcache"] representations = ["abc"] label = "Import Alembic as Arnold Standin" diff --git a/openpype/hosts/maya/plugins/load/load_gpucache.py b/openpype/hosts/maya/plugins/load/load_gpucache.py index a09f924c7b..07e5734f43 100644 --- a/openpype/hosts/maya/plugins/load/load_gpucache.py +++ b/openpype/hosts/maya/plugins/load/load_gpucache.py @@ -10,7 +10,7 @@ from openpype.settings import get_project_settings class GpuCacheLoader(load.LoaderPlugin): """Load Alembic as gpuCache""" - families = ["model", "animation", "pointcache"] + families = ["model", "animation", "proxyAbc", "pointcache"] representations = ["abc"] label = "Import Gpu Cache" diff --git a/openpype/hosts/maya/plugins/load/load_reference.py b/openpype/hosts/maya/plugins/load/load_reference.py index c762a29326..c6b07b036d 100644 --- a/openpype/hosts/maya/plugins/load/load_reference.py +++ b/openpype/hosts/maya/plugins/load/load_reference.py @@ -16,6 +16,7 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): families = ["model", "pointcache", + "proxyAbc", "animation", "mayaAscii", "mayaScene", diff --git a/openpype/hosts/maya/plugins/publish/collect_ass.py b/openpype/hosts/maya/plugins/publish/collect_ass.py index 7c9a1b76fb..b5e05d6665 100644 --- a/openpype/hosts/maya/plugins/publish/collect_ass.py +++ b/openpype/hosts/maya/plugins/publish/collect_ass.py @@ -1,4 +1,5 @@ from maya import cmds +from openpype.pipeline.publish import KnownPublishError import pyblish.api @@ -6,6 +7,7 @@ import pyblish.api class CollectAssData(pyblish.api.InstancePlugin): """Collect Ass data.""" + # Offset to be after renderable camera collection. order = pyblish.api.CollectorOrder + 0.2 label = 'Collect Ass' families = ["ass"] @@ -23,8 +25,23 @@ class CollectAssData(pyblish.api.InstancePlugin): instance.data['setMembers'] = members self.log.debug('content members: {}'.format(members)) elif objset.startswith("proxy_SET"): - assert len(members) == 1, "You have multiple proxy meshes, please only use one" + if len(members) != 1: + msg = "You have multiple proxy meshes, please only use one" + raise KnownPublishError(msg) instance.data['proxy'] = members self.log.debug('proxy members: {}'.format(members)) + # Use camera in object set if present else default to render globals + # camera. + cameras = cmds.ls(type="camera", long=True) + renderable = [c for c in cameras if cmds.getAttr("%s.renderable" % c)] + camera = renderable[0] + for node in instance.data["setMembers"]: + camera_shapes = cmds.listRelatives( + node, shapes=True, type="camera" + ) + if camera_shapes: + camera = node + instance.data["camera"] = camera + self.log.debug("data: {}".format(instance.data)) diff --git a/openpype/hosts/maya/plugins/publish/collect_gltf.py b/openpype/hosts/maya/plugins/publish/collect_gltf.py new file mode 100644 index 0000000000..bb37fe3a7e --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/collect_gltf.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +import pyblish.api + + +class CollectGLTF(pyblish.api.InstancePlugin): + """Collect Assets for GLTF/GLB export.""" + + order = pyblish.api.CollectorOrder + 0.2 + label = "Collect Asset for GLTF/GLB export" + families = ["model", "animation", "pointcache"] + + def process(self, instance): + if not instance.data.get("families"): + instance.data["families"] = [] + + if "gltf" not in instance.data["families"]: + instance.data["families"].append("gltf") diff --git a/openpype/hosts/maya/plugins/publish/extract_ass.py b/openpype/hosts/maya/plugins/publish/extract_ass.py index 5c21a4ff08..049f256a7a 100644 --- a/openpype/hosts/maya/plugins/publish/extract_ass.py +++ b/openpype/hosts/maya/plugins/publish/extract_ass.py @@ -1,77 +1,93 @@ import os from maya import cmds +import arnold from openpype.pipeline import publish -from openpype.hosts.maya.api.lib import maintained_selection +from openpype.hosts.maya.api.lib import maintained_selection, attribute_values class ExtractAssStandin(publish.Extractor): - """Extract the content of the instance to a ass file + """Extract the content of the instance to a ass file""" - Things to pay attention to: - - If animation is toggled, are the frames correct - - - """ - - label = "Ass Standin (.ass)" + label = "Arnold Scene Source (.ass)" hosts = ["maya"] families = ["ass"] asciiAss = False def process(self, instance): - - sequence = instance.data.get("exportSequence", False) - staging_dir = self.staging_dir(instance) filename = "{}.ass".format(instance.name) - filenames = list() + filenames = [] file_path = os.path.join(staging_dir, filename) + # Mask + mask = arnold.AI_NODE_ALL + + node_types = { + "options": arnold.AI_NODE_OPTIONS, + "camera": arnold.AI_NODE_CAMERA, + "light": arnold.AI_NODE_LIGHT, + "shape": arnold.AI_NODE_SHAPE, + "shader": arnold.AI_NODE_SHADER, + "override": arnold.AI_NODE_OVERRIDE, + "driver": arnold.AI_NODE_DRIVER, + "filter": arnold.AI_NODE_FILTER, + "color_manager": arnold.AI_NODE_COLOR_MANAGER, + "operator": arnold.AI_NODE_OPERATOR + } + + for key in node_types.keys(): + if instance.data.get("mask" + key.title()): + mask = mask ^ node_types[key] + + # Motion blur + values = { + "defaultArnoldRenderOptions.motion_blur_enable": instance.data.get( + "motionBlur", True + ), + "defaultArnoldRenderOptions.motion_steps": instance.data.get( + "motionBlurKeys", 2 + ), + "defaultArnoldRenderOptions.motion_frames": instance.data.get( + "motionBlurLength", 0.5 + ) + } + # Write out .ass file + kwargs = { + "filename": file_path, + "startFrame": instance.data.get("frameStartHandle", 1), + "endFrame": instance.data.get("frameEndHandle", 1), + "frameStep": instance.data.get("step", 1), + "selected": True, + "asciiAss": self.asciiAss, + "shadowLinks": True, + "lightLinks": True, + "boundingBox": True, + "expandProcedurals": instance.data.get("expandProcedurals", False), + "camera": instance.data["camera"], + "mask": mask + } + self.log.info("Writing: '%s'" % file_path) - with maintained_selection(): - self.log.info("Writing: {}".format(instance.data["setMembers"])) - cmds.select(instance.data["setMembers"], noExpand=True) + with attribute_values(values): + with maintained_selection(): + self.log.info( + "Writing: {}".format(instance.data["setMembers"]) + ) + cmds.select(instance.data["setMembers"], noExpand=True) - if sequence: - self.log.info("Extracting ass sequence") + self.log.info( + "Extracting ass sequence with: {}".format(kwargs) + ) - # Collect the start and end including handles - start = instance.data.get("frameStartHandle", 1) - end = instance.data.get("frameEndHandle", 1) - step = instance.data.get("step", 0) + exported_files = cmds.arnoldExportAss(**kwargs) - exported_files = cmds.arnoldExportAss(filename=file_path, - selected=True, - asciiAss=self.asciiAss, - shadowLinks=True, - lightLinks=True, - boundingBox=True, - startFrame=start, - endFrame=end, - frameStep=step - ) for file in exported_files: filenames.append(os.path.split(file)[1]) + self.log.info("Exported: {}".format(filenames)) - else: - self.log.info("Extracting ass") - cmds.arnoldExportAss(filename=file_path, - selected=True, - asciiAss=False, - shadowLinks=True, - lightLinks=True, - boundingBox=True - ) - self.log.info("Extracted {}".format(filename)) - filenames = filename - optionals = [ - "frameStart", "frameEnd", "step", "handles", - "handleEnd", "handleStart" - ] - for key in optionals: - instance.data.pop(key, None) if "representations" not in instance.data: instance.data["representations"] = [] @@ -79,13 +95,11 @@ class ExtractAssStandin(publish.Extractor): representation = { 'name': 'ass', 'ext': 'ass', - 'files': filenames, - "stagingDir": staging_dir + 'files': filenames if len(filenames) > 1 else filenames[0], + "stagingDir": staging_dir, + 'frameStart': kwargs["startFrame"] } - if sequence: - representation['frameStart'] = start - instance.data["representations"].append(representation) self.log.info("Extracted instance '%s' to: %s" diff --git a/openpype/hosts/maya/plugins/publish/extract_gltf.py b/openpype/hosts/maya/plugins/publish/extract_gltf.py new file mode 100644 index 0000000000..f5ceed5f33 --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/extract_gltf.py @@ -0,0 +1,65 @@ +import os + +from maya import cmds, mel +import pyblish.api + +from openpype.pipeline import publish +from openpype.hosts.maya.api import lib +from openpype.hosts.maya.api.gltf import extract_gltf + + +class ExtractGLB(publish.Extractor): + + order = pyblish.api.ExtractorOrder + hosts = ["maya"] + label = "Extract GLB" + families = ["gltf"] + + def process(self, instance): + staging_dir = self.staging_dir(instance) + filename = "{0}.glb".format(instance.name) + path = os.path.join(staging_dir, filename) + + self.log.info("Extracting GLB to: {}".format(path)) + + nodes = instance[:] + + self.log.info("Instance: {0}".format(nodes)) + + start_frame = instance.data('frameStart') or \ + int(cmds.playbackOptions(query=True, + animationStartTime=True))# noqa + end_frame = instance.data('frameEnd') or \ + int(cmds.playbackOptions(query=True, + animationEndTime=True)) # noqa + fps = mel.eval('currentTimeUnitToFPS()') + + options = { + "sno": True, # selectedNodeOnly + "nbu": True, # .bin instead of .bin0 + "ast": start_frame, + "aet": end_frame, + "afr": fps, + "dsa": 1, + "acn": instance.name, + "glb": True, + "vno": True # visibleNodeOnly + } + with lib.maintained_selection(): + cmds.select(nodes, hi=True, noExpand=True) + extract_gltf(staging_dir, + instance.name, + **options) + + if "representations" not in instance.data: + instance.data["representations"] = [] + + representation = { + 'name': 'glb', + 'ext': 'glb', + 'files': filename, + "stagingDir": staging_dir, + } + instance.data["representations"].append(representation) + + self.log.info("Extract GLB successful to: {0}".format(path)) diff --git a/openpype/hosts/maya/plugins/publish/extract_layout.py b/openpype/hosts/maya/plugins/publish/extract_layout.py index a801d99f42..7921fca069 100644 --- a/openpype/hosts/maya/plugins/publish/extract_layout.py +++ b/openpype/hosts/maya/plugins/publish/extract_layout.py @@ -15,6 +15,7 @@ class ExtractLayout(publish.Extractor): label = "Extract Layout" hosts = ["maya"] families = ["layout"] + project_container = "AVALON_CONTAINERS" optional = True def process(self, instance): @@ -33,12 +34,25 @@ class ExtractLayout(publish.Extractor): for asset in cmds.sets(str(instance), query=True): # Find the container - grp_name = asset.split(':')[0] + project_container = self.project_container + container_list = cmds.ls(project_container) + if len(container_list) == 0: + self.log.warning("Project container is not found!") + self.log.warning("The asset(s) may not be properly loaded after published") # noqa + continue + + grp_loaded_ass = instance.data.get("groupLoadedAssets", False) + if grp_loaded_ass: + asset_list = cmds.listRelatives(asset, children=True) + for asset in asset_list: + grp_name = asset.split(':')[0] + else: + grp_name = asset.split(':')[0] containers = cmds.ls("{}*_CON".format(grp_name)) - - assert len(containers) == 1, \ - "More than one container found for {}".format(asset) - + if len(containers) == 0: + self.log.warning("{} isn't from the loader".format(asset)) + self.log.warning("It may not be properly loaded after published") # noqa + continue container = containers[0] representation_id = cmds.getAttr( diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index 23b76a48c2..7ed73fd5b0 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -86,7 +86,8 @@ class ExtractAlembic(publish.Extractor): start=start, end=end)) - with suspended_refresh(suspend=instance.data.get("refresh", False)): + suspend = not instance.data.get("refresh", False) + with suspended_refresh(suspend=suspend): with maintained_selection(): cmds.select(nodes, noExpand=True) extract_alembic( diff --git a/openpype/hosts/maya/plugins/publish/extract_proxy_abc.py b/openpype/hosts/maya/plugins/publish/extract_proxy_abc.py new file mode 100644 index 0000000000..cf6351fdca --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/extract_proxy_abc.py @@ -0,0 +1,109 @@ +import os + +from maya import cmds + +from openpype.pipeline import publish +from openpype.hosts.maya.api.lib import ( + extract_alembic, + suspended_refresh, + maintained_selection, + iter_visible_nodes_in_range +) + + +class ExtractProxyAlembic(publish.Extractor): + """Produce an alembic for bounding box geometry + """ + + label = "Extract Proxy (Alembic)" + hosts = ["maya"] + families = ["proxyAbc"] + + def process(self, instance): + name_suffix = instance.data.get("nameSuffix") + # Collect the start and end including handles + start = float(instance.data.get("frameStartHandle", 1)) + end = float(instance.data.get("frameEndHandle", 1)) + + attrs = instance.data.get("attr", "").split(";") + attrs = [value for value in attrs if value.strip()] + attrs += ["cbId"] + + attr_prefixes = instance.data.get("attrPrefix", "").split(";") + attr_prefixes = [value for value in attr_prefixes if value.strip()] + + self.log.info("Extracting Proxy Alembic..") + dirname = self.staging_dir(instance) + + filename = "{name}.abc".format(**instance.data) + path = os.path.join(dirname, filename) + + proxy_root = self.create_proxy_geometry(instance, + name_suffix, + start, + end) + + options = { + "step": instance.data.get("step", 1.0), + "attr": attrs, + "attrPrefix": attr_prefixes, + "writeVisibility": True, + "writeCreases": True, + "writeColorSets": instance.data.get("writeColorSets", False), + "writeFaceSets": instance.data.get("writeFaceSets", False), + "uvWrite": True, + "selection": True, + "worldSpace": instance.data.get("worldSpace", True), + "root": proxy_root + } + + if int(cmds.about(version=True)) >= 2017: + # Since Maya 2017 alembic supports multiple uv sets - write them. + options["writeUVSets"] = True + + with suspended_refresh(): + with maintained_selection(): + cmds.select(proxy_root, hi=True, noExpand=True) + extract_alembic(file=path, + startFrame=start, + endFrame=end, + **options) + + if "representations" not in instance.data: + instance.data["representations"] = [] + + representation = { + 'name': 'abc', + 'ext': 'abc', + 'files': filename, + "stagingDir": dirname + } + instance.data["representations"].append(representation) + + instance.context.data["cleanupFullPaths"].append(path) + + self.log.info("Extracted {} to {}".format(instance, dirname)) + # remove the bounding box + bbox_master = cmds.ls("bbox_grp") + cmds.delete(bbox_master) + + def create_proxy_geometry(self, instance, name_suffix, start, end): + nodes = instance[:] + nodes = list(iter_visible_nodes_in_range(nodes, + start=start, + end=end)) + + inst_selection = cmds.ls(nodes, long=True) + cmds.geomToBBox(inst_selection, + nameSuffix=name_suffix, + keepOriginal=True, + single=False, + bakeAnimation=True, + startTime=start, + endTime=end) + # create master group for bounding + # boxes as the main root + master_group = cmds.group(name="bbox_grp") + bbox_sel = cmds.ls(master_group, long=True) + self.log.debug("proxy_root: {}".format(bbox_sel)) + return bbox_sel diff --git a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py index 06244cf003..1edafeb926 100644 --- a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py @@ -105,6 +105,11 @@ class ExtractThumbnail(publish.Extractor): pm.currentTime(refreshFrameInt - 1, edit=True) pm.currentTime(refreshFrameInt, edit=True) + # Override transparency if requested. + transparency = instance.data.get("transparency", 0) + if transparency != 0: + preset["viewport2_options"]["transparencyAlgorithm"] = transparency + # Isolate view is requested by having objects in the set besides a # camera. if preset.pop("isolate_view", False) and instance.data.get("isolate"): diff --git a/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh_abc.py b/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh_abc.py new file mode 100644 index 0000000000..e1f847f31a --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh_abc.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- +"""Create Unreal Skeletal Mesh data to be extracted as FBX.""" +import os +from contextlib import contextmanager + +from maya import cmds # noqa + +from openpype.pipeline import publish +from openpype.hosts.maya.api.lib import ( + extract_alembic, + suspended_refresh, + maintained_selection +) + + +@contextmanager +def renamed(original_name, renamed_name): + # type: (str, str) -> None + try: + cmds.rename(original_name, renamed_name) + yield + finally: + cmds.rename(renamed_name, original_name) + + +class ExtractUnrealSkeletalMeshAbc(publish.Extractor): + """Extract Unreal Skeletal Mesh as FBX from Maya. """ + + label = "Extract Unreal Skeletal Mesh - Alembic" + hosts = ["maya"] + families = ["skeletalMesh"] + optional = True + + def process(self, instance): + self.log.info("Extracting pointcache..") + + geo = cmds.listRelatives( + instance.data.get("geometry"), allDescendents=True, fullPath=True) + joints = cmds.listRelatives( + instance.data.get("joints"), allDescendents=True, fullPath=True) + + nodes = geo + joints + + attrs = instance.data.get("attr", "").split(";") + attrs = [value for value in attrs if value.strip()] + attrs += ["cbId"] + + attr_prefixes = instance.data.get("attrPrefix", "").split(";") + attr_prefixes = [value for value in attr_prefixes if value.strip()] + + # Define output path + staging_dir = self.staging_dir(instance) + filename = "{0}.abc".format(instance.name) + path = os.path.join(staging_dir, filename) + + # The export requires forward slashes because we need + # to format it into a string in a mel expression + path = path.replace('\\', '/') + + self.log.info("Extracting ABC to: {0}".format(path)) + self.log.info("Members: {0}".format(nodes)) + self.log.info("Instance: {0}".format(instance[:])) + + options = { + "step": instance.data.get("step", 1.0), + "attr": attrs, + "attrPrefix": attr_prefixes, + "writeVisibility": True, + "writeCreases": True, + "writeColorSets": instance.data.get("writeColorSets", False), + "writeFaceSets": instance.data.get("writeFaceSets", False), + "uvWrite": True, + "selection": True, + "worldSpace": instance.data.get("worldSpace", True) + } + + self.log.info("Options: {}".format(options)) + + if int(cmds.about(version=True)) >= 2017: + # Since Maya 2017 alembic supports multiple uv sets - write them. + options["writeUVSets"] = True + + if not instance.data.get("includeParentHierarchy", True): + # Set the root nodes if we don't want to include parents + # The roots are to be considered the ones that are the actual + # direct members of the set + options["root"] = instance.data.get("setMembers") + + with suspended_refresh(suspend=instance.data.get("refresh", False)): + with maintained_selection(): + cmds.select(nodes, noExpand=True) + extract_alembic(file=path, + # startFrame=start, + # endFrame=end, + **options) + + if "representations" not in instance.data: + instance.data["representations"] = [] + + representation = { + 'name': 'abc', + 'ext': 'abc', + 'files': filename, + "stagingDir": staging_dir, + } + instance.data["representations"].append(representation) + + self.log.info("Extract ABC successful to: {0}".format(path)) diff --git a/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py b/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh_fbx.py similarity index 95% rename from openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py rename to openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh_fbx.py index 258120db2f..b162ce47f7 100644 --- a/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py +++ b/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh_fbx.py @@ -21,12 +21,13 @@ def renamed(original_name, renamed_name): cmds.rename(renamed_name, original_name) -class ExtractUnrealSkeletalMesh(publish.Extractor): +class ExtractUnrealSkeletalMeshFbx(publish.Extractor): """Extract Unreal Skeletal Mesh as FBX from Maya. """ order = pyblish.api.ExtractorOrder - 0.1 - label = "Extract Unreal Skeletal Mesh" + label = "Extract Unreal Skeletal Mesh - FBX" families = ["skeletalMesh"] + optional = True def process(self, instance): fbx_exporter = fbx.FBXExtractor(log=self.log) diff --git a/openpype/hosts/maya/plugins/publish/validate_animation_out_set_related_node_ids.py b/openpype/hosts/maya/plugins/publish/validate_animation_out_set_related_node_ids.py index 649913fff6..5a527031be 100644 --- a/openpype/hosts/maya/plugins/publish/validate_animation_out_set_related_node_ids.py +++ b/openpype/hosts/maya/plugins/publish/validate_animation_out_set_related_node_ids.py @@ -20,7 +20,7 @@ class ValidateOutRelatedNodeIds(pyblish.api.InstancePlugin): """ order = ValidateContentsOrder - families = ['animation', "pointcache"] + families = ['animation', "pointcache", "proxyAbc"] hosts = ['maya'] label = 'Animation Out Set Related Node Ids' actions = [ diff --git a/openpype/hosts/maya/plugins/publish/validate_frame_range.py b/openpype/hosts/maya/plugins/publish/validate_frame_range.py index b467a7c232..5e50ae72cd 100644 --- a/openpype/hosts/maya/plugins/publish/validate_frame_range.py +++ b/openpype/hosts/maya/plugins/publish/validate_frame_range.py @@ -25,6 +25,7 @@ class ValidateFrameRange(pyblish.api.InstancePlugin): families = ["animation", "pointcache", "camera", + "proxyAbc", "renderlayer", "review", "yeticache"] diff --git a/openpype/hosts/maya/plugins/publish/validate_skeletalmesh_hierarchy.py b/openpype/hosts/maya/plugins/publish/validate_skeletalmesh_hierarchy.py index 8221c18b17..398b6fb7bf 100644 --- a/openpype/hosts/maya/plugins/publish/validate_skeletalmesh_hierarchy.py +++ b/openpype/hosts/maya/plugins/publish/validate_skeletalmesh_hierarchy.py @@ -28,7 +28,9 @@ class ValidateSkeletalMeshHierarchy(pyblish.api.InstancePlugin): parent.split("|")[1] for parent in (joints_parents + geo_parents) } - if len(set(parents_set)) != 1: + self.log.info(parents_set) + + if len(set(parents_set)) > 2: raise PublishXmlValidationError( self, "Multiple roots on geometry or joints." diff --git a/openpype/hosts/maya/plugins/publish/validate_skeletalmesh_triangulated.py b/openpype/hosts/maya/plugins/publish/validate_skeletalmesh_triangulated.py new file mode 100644 index 0000000000..c0a9ddcf69 --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/validate_skeletalmesh_triangulated.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +import pyblish.api + +from openpype.hosts.maya.api.action import ( + SelectInvalidAction, +) +from openpype.pipeline.publish import ( + RepairAction, + ValidateContentsOrder, +) + +from maya import cmds + + +class ValidateSkeletalMeshTriangulated(pyblish.api.InstancePlugin): + """Validates that the geometry has been triangulated.""" + + order = ValidateContentsOrder + hosts = ["maya"] + families = ["skeletalMesh"] + label = "Skeletal Mesh Triangulated" + optional = True + actions = [ + SelectInvalidAction, + RepairAction + ] + + def process(self, instance): + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError( + "The following objects needs to be triangulated: " + "{}".format(invalid)) + + @classmethod + def get_invalid(cls, instance): + geo = instance.data.get("geometry") + + invalid = [] + + for obj in cmds.listRelatives( + cmds.ls(geo), allDescendents=True, fullPath=True): + n_triangles = cmds.polyEvaluate(obj, triangle=True) + n_faces = cmds.polyEvaluate(obj, face=True) + + if not (isinstance(n_triangles, int) and isinstance(n_faces, int)): + continue + + # We check if the number of triangles is equal to the number of + # faces for each transform node. + # If it is, the object is triangulated. + if cmds.objectType(obj, i="transform") and n_triangles != n_faces: + invalid.append(obj) + + return invalid + + @classmethod + def repair(cls, instance): + for node in cls.get_invalid(instance): + cmds.polyTriangulate(node) diff --git a/openpype/hosts/photoshop/api/launch_logic.py b/openpype/hosts/photoshop/api/launch_logic.py index 97a2dd94f6..a4377a9972 100644 --- a/openpype/hosts/photoshop/api/launch_logic.py +++ b/openpype/hosts/photoshop/api/launch_logic.py @@ -8,7 +8,7 @@ from wsrpc_aiohttp import ( WebSocketAsync ) -from Qt import QtCore +from qtpy import QtCore from openpype.lib import Logger from openpype.pipeline import legacy_io diff --git a/openpype/hosts/photoshop/api/lib.py b/openpype/hosts/photoshop/api/lib.py index e0fd0664ef..ff520348f0 100644 --- a/openpype/hosts/photoshop/api/lib.py +++ b/openpype/hosts/photoshop/api/lib.py @@ -3,7 +3,7 @@ import sys import contextlib import traceback -from Qt import QtWidgets +from qtpy import QtWidgets from openpype.lib import env_value_to_bool, Logger from openpype.modules import ModulesManager diff --git a/openpype/hosts/photoshop/api/pipeline.py b/openpype/hosts/photoshop/api/pipeline.py index 5a23a2213c..73dc80260c 100644 --- a/openpype/hosts/photoshop/api/pipeline.py +++ b/openpype/hosts/photoshop/api/pipeline.py @@ -1,5 +1,7 @@ import os -from Qt import QtWidgets + +from qtpy import QtWidgets + import pyblish.api from openpype.lib import register_event_callback, Logger diff --git a/openpype/hosts/tvpaint/api/launch_script.py b/openpype/hosts/tvpaint/api/launch_script.py index c474a10529..614dbe8a6e 100644 --- a/openpype/hosts/tvpaint/api/launch_script.py +++ b/openpype/hosts/tvpaint/api/launch_script.py @@ -6,7 +6,7 @@ import ctypes import platform import logging -from Qt import QtWidgets, QtCore, QtGui +from qtpy import QtWidgets, QtCore, QtGui from openpype import style from openpype.pipeline import install_host diff --git a/openpype/hosts/tvpaint/plugins/create/create_render_layer.py b/openpype/hosts/tvpaint/plugins/create/create_render_layer.py index a085830e96..009b69c4f1 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render_layer.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render_layer.py @@ -207,8 +207,8 @@ class CreateRenderlayer(plugin.Creator): ) def _ask_user_subset_override(self, instance): - from Qt import QtCore - from Qt.QtWidgets import QMessageBox + from qtpy import QtCore + from qtpy.QtWidgets import QMessageBox title = "Subset \"{}\" already exist".format(instance["subset"]) text = ( diff --git a/openpype/hosts/unreal/api/pipeline.py b/openpype/hosts/unreal/api/pipeline.py index d396b64072..ca5a42cd82 100644 --- a/openpype/hosts/unreal/api/pipeline.py +++ b/openpype/hosts/unreal/api/pipeline.py @@ -2,6 +2,7 @@ import os import logging from typing import List +import semver import pyblish.api @@ -21,6 +22,9 @@ import unreal # noqa logger = logging.getLogger("openpype.hosts.unreal") OPENPYPE_CONTAINERS = "OpenPypeContainers" +UNREAL_VERSION = semver.VersionInfo( + *os.getenv("OPENPYPE_UNREAL_VERSION").split(".") +) HOST_DIR = os.path.dirname(os.path.abspath(openpype.hosts.unreal.__file__)) PLUGINS_DIR = os.path.join(HOST_DIR, "plugins") @@ -111,7 +115,9 @@ def ls(): """ ar = unreal.AssetRegistryHelpers.get_asset_registry() - openpype_containers = ar.get_assets_by_class("AssetContainer", True) + # UE 5.1 changed how class name is specified + class_name = ["/Script/OpenPype", "AssetContainer"] if UNREAL_VERSION.major == 5 and UNREAL_VERSION.minor > 0 else "AssetContainer" # noqa + openpype_containers = ar.get_assets_by_class(class_name, True) # get_asset_by_class returns AssetData. To get all metadata we need to # load asset. get_tag_values() work only on metadata registered in diff --git a/openpype/hosts/unreal/api/tools_ui.py b/openpype/hosts/unreal/api/tools_ui.py index 2500f8495f..708e167a65 100644 --- a/openpype/hosts/unreal/api/tools_ui.py +++ b/openpype/hosts/unreal/api/tools_ui.py @@ -1,5 +1,5 @@ import sys -from Qt import QtWidgets, QtCore, QtGui +from qtpy import QtWidgets, QtCore, QtGui from openpype import ( resources, diff --git a/openpype/hosts/unreal/hooks/pre_workfile_preparation.py b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py index 4ae72593e9..2dc6fb9f42 100644 --- a/openpype/hosts/unreal/hooks/pre_workfile_preparation.py +++ b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py @@ -150,6 +150,7 @@ class UnrealPrelaunchHook(PreLaunchHook): engine_path=Path(engine_path) ) + self.launch_context.env["OPENPYPE_UNREAL_VERSION"] = engine_version # Append project file to launch arguments self.launch_context.launch_args.append( f"\"{project_file.as_posix()}\"") diff --git a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/OpenPype.Build.cs b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/OpenPype.Build.cs index fcfd268234..67db648b2a 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/OpenPype.Build.cs +++ b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/OpenPype.Build.cs @@ -6,7 +6,11 @@ public class OpenPype : ModuleRules { public OpenPype(ReadOnlyTargetRules Target) : base(Target) { + DefaultBuildSettings = BuildSettingsVersion.V2; + bLegacyPublicIncludePaths = false; + ShadowVariableWarningLevel = WarningLevel.Error; PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_0; PublicIncludePaths.AddRange( new string[] { diff --git a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/AssetContainer.cpp b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/AssetContainer.cpp index c766f87a8e..61e563f729 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/AssetContainer.cpp +++ b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/AssetContainer.cpp @@ -1,7 +1,7 @@ // Fill out your copyright notice in the Description page of Project Settings. #include "AssetContainer.h" -#include "AssetRegistryModule.h" +#include "AssetRegistry/AssetRegistryModule.h" #include "Misc/PackageName.h" #include "Engine.h" #include "Containers/UnrealString.h" @@ -30,8 +30,8 @@ void UAssetContainer::OnAssetAdded(const FAssetData& AssetData) // get asset path and class FString assetPath = AssetData.GetFullName(); - FString assetFName = AssetData.AssetClass.ToString(); - + FString assetFName = AssetData.AssetClassPath.ToString(); + UE_LOG(LogTemp, Log, TEXT("asset name %s"), *assetFName); // split path assetPath.ParseIntoArray(split, TEXT(" "), true); @@ -60,7 +60,7 @@ void UAssetContainer::OnAssetRemoved(const FAssetData& AssetData) // get asset path and class FString assetPath = AssetData.GetFullName(); - FString assetFName = AssetData.AssetClass.ToString(); + FString assetFName = AssetData.AssetClassPath.ToString(); // split path assetPath.ParseIntoArray(split, TEXT(" "), true); @@ -93,7 +93,7 @@ void UAssetContainer::OnAssetRenamed(const FAssetData& AssetData, const FString& // get asset path and class FString assetPath = AssetData.GetFullName(); - FString assetFName = AssetData.AssetClass.ToString(); + FString assetFName = AssetData.AssetClassPath.ToString(); // split path assetPath.ParseIntoArray(split, TEXT(" "), true); diff --git a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypePublishInstance.cpp b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypePublishInstance.cpp index 322663eeec..f5eb6f9e70 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypePublishInstance.cpp +++ b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypePublishInstance.cpp @@ -1,10 +1,10 @@ #pragma once #include "OpenPypePublishInstance.h" -#include "AssetRegistryModule.h" +#include "AssetRegistry/AssetRegistryModule.h" #include "AssetToolsModule.h" #include "Framework/Notifications/NotificationManager.h" -#include "SNotificationList.h" +#include "Widgets/Notifications/SNotificationList.h" //Moves all the invalid pointers to the end to prepare them for the shrinking #define REMOVE_INVALID_ENTRIES(VAR) VAR.CompactStable(); \ @@ -47,7 +47,7 @@ void UOpenPypePublishInstance::OnAssetCreated(const FAssetData& InAssetData) if (!IsValid(Asset)) { UE_LOG(LogAssetData, Warning, TEXT("Asset \"%s\" is not valid! Skipping the addition."), - *InAssetData.ObjectPath.ToString()); + *InAssetData.GetObjectPathString()); return; } diff --git a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/AssetContainer.h b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/AssetContainer.h index 3c2a360c78..2c06e59d6f 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/AssetContainer.h +++ b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/AssetContainer.h @@ -5,7 +5,7 @@ #include "CoreMinimal.h" #include "UObject/NoExportTypes.h" #include "Engine/AssetUserData.h" -#include "AssetData.h" +#include "AssetRegistry/AssetData.h" #include "AssetContainer.generated.h" /** diff --git a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPypePublishInstance.h b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPypePublishInstance.h index 2f066bd94b..e9d94aecfc 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPypePublishInstance.h +++ b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPypePublishInstance.h @@ -1,6 +1,5 @@ #pragma once -#include "EditorTutorial.h" #include "Engine.h" #include "OpenPypePublishInstance.generated.h" diff --git a/openpype/hosts/unreal/lib.py b/openpype/hosts/unreal/lib.py index d02c6de357..095f5e414b 100644 --- a/openpype/hosts/unreal/lib.py +++ b/openpype/hosts/unreal/lib.py @@ -50,7 +50,10 @@ def get_engine_versions(env=None): # environment variable not set pass except OSError: - # specified directory doesn't exists + # specified directory doesn't exist + pass + except StopIteration: + # specified directory doesn't exist pass # if we've got something, terminate auto-detection process diff --git a/openpype/hosts/unreal/plugins/load/load_alembic_animation.py b/openpype/hosts/unreal/plugins/load/load_alembic_animation.py new file mode 100644 index 0000000000..496b6056ea --- /dev/null +++ b/openpype/hosts/unreal/plugins/load/load_alembic_animation.py @@ -0,0 +1,162 @@ +# -*- coding: utf-8 -*- +"""Load Alembic Animation.""" +import os + +from openpype.pipeline import ( + get_representation_path, + AVALON_CONTAINER_ID +) +from openpype.hosts.unreal.api import plugin +from openpype.hosts.unreal.api import pipeline as unreal_pipeline +import unreal # noqa + + +class AnimationAlembicLoader(plugin.Loader): + """Load Unreal SkeletalMesh from Alembic""" + + families = ["animation"] + label = "Import Alembic Animation" + representations = ["abc"] + icon = "cube" + color = "orange" + + def get_task(self, filename, asset_dir, asset_name, replace): + task = unreal.AssetImportTask() + options = unreal.AbcImportSettings() + sm_settings = unreal.AbcStaticMeshSettings() + conversion_settings = unreal.AbcConversionSettings( + preset=unreal.AbcConversionPreset.CUSTOM, + flip_u=False, flip_v=False, + rotation=[0.0, 0.0, 0.0], + scale=[1.0, 1.0, -1.0]) + + task.set_editor_property('filename', filename) + task.set_editor_property('destination_path', asset_dir) + task.set_editor_property('destination_name', asset_name) + task.set_editor_property('replace_existing', replace) + task.set_editor_property('automated', True) + task.set_editor_property('save', True) + + options.set_editor_property( + 'import_type', unreal.AlembicImportType.SKELETAL) + + options.static_mesh_settings = sm_settings + options.conversion_settings = conversion_settings + task.options = options + + return task + + def load(self, context, name, namespace, data): + """Load and containerise representation into Content Browser. + + This is two step process. First, import FBX to temporary path and + then call `containerise()` on it - this moves all content to new + directory and then it will create AssetContainer there and imprint it + with metadata. This will mark this path as container. + + Args: + context (dict): application context + name (str): subset name + namespace (str): in Unreal this is basically path to container. + This is not passed here, so namespace is set + by `containerise()` because only then we know + real path. + data (dict): Those would be data to be imprinted. This is not used + now, data are imprinted by `containerise()`. + + Returns: + list(str): list of container content + """ + + # Create directory for asset and openpype container + root = "/Game/OpenPype/Assets" + asset = context.get('asset').get('name') + suffix = "_CON" + if asset: + asset_name = "{}_{}".format(asset, name) + else: + asset_name = "{}".format(name) + version = context.get('version').get('name') + + tools = unreal.AssetToolsHelpers().get_asset_tools() + asset_dir, container_name = tools.create_unique_asset_name( + f"{root}/{asset}/{name}_v{version:03d}", suffix="") + + container_name += suffix + + if not unreal.EditorAssetLibrary.does_directory_exist(asset_dir): + unreal.EditorAssetLibrary.make_directory(asset_dir) + + task = self.get_task(self.fname, asset_dir, asset_name, False) + + asset_tools = unreal.AssetToolsHelpers.get_asset_tools() + asset_tools.import_asset_tasks([task]) + + # Create Asset Container + unreal_pipeline.create_container( + container=container_name, path=asset_dir) + + data = { + "schema": "openpype:container-2.0", + "id": AVALON_CONTAINER_ID, + "asset": asset, + "namespace": asset_dir, + "container_name": container_name, + "asset_name": asset_name, + "loader": str(self.__class__.__name__), + "representation": context["representation"]["_id"], + "parent": context["representation"]["parent"], + "family": context["representation"]["context"]["family"] + } + unreal_pipeline.imprint( + "{}/{}".format(asset_dir, container_name), data) + + asset_content = unreal.EditorAssetLibrary.list_assets( + asset_dir, recursive=True, include_folder=True + ) + + for a in asset_content: + unreal.EditorAssetLibrary.save_asset(a) + + return asset_content + + def update(self, container, representation): + name = container["asset_name"] + source_path = get_representation_path(representation) + destination_path = container["namespace"] + + task = self.get_task(source_path, destination_path, name, True) + + # do import fbx and replace existing data + asset_tools = unreal.AssetToolsHelpers.get_asset_tools() + asset_tools.import_asset_tasks([task]) + + container_path = f"{container['namespace']}/{container['objectName']}" + + # update metadata + unreal_pipeline.imprint( + container_path, + { + "representation": str(representation["_id"]), + "parent": str(representation["parent"]) + }) + + asset_content = unreal.EditorAssetLibrary.list_assets( + destination_path, recursive=True, include_folder=True + ) + + for a in asset_content: + unreal.EditorAssetLibrary.save_asset(a) + + def remove(self, container): + path = container["namespace"] + parent_path = os.path.dirname(path) + + unreal.EditorAssetLibrary.delete_directory(path) + + asset_content = unreal.EditorAssetLibrary.list_assets( + parent_path, recursive=False + ) + + if len(asset_content) == 0: + unreal.EditorAssetLibrary.delete_directory(parent_path) diff --git a/openpype/hosts/unreal/plugins/load/load_alembic_geometrycache.py b/openpype/hosts/unreal/plugins/load/load_geometrycache_abc.py similarity index 100% rename from openpype/hosts/unreal/plugins/load/load_alembic_geometrycache.py rename to openpype/hosts/unreal/plugins/load/load_geometrycache_abc.py diff --git a/openpype/hosts/unreal/plugins/load/load_alembic_skeletalmesh.py b/openpype/hosts/unreal/plugins/load/load_skeletalmesh_abc.py similarity index 99% rename from openpype/hosts/unreal/plugins/load/load_alembic_skeletalmesh.py rename to openpype/hosts/unreal/plugins/load/load_skeletalmesh_abc.py index 9fe5f3ab4b..e316d255e9 100644 --- a/openpype/hosts/unreal/plugins/load/load_alembic_skeletalmesh.py +++ b/openpype/hosts/unreal/plugins/load/load_skeletalmesh_abc.py @@ -14,7 +14,7 @@ import unreal # noqa class SkeletalMeshAlembicLoader(plugin.Loader): """Load Unreal SkeletalMesh from Alembic""" - families = ["pointcache"] + families = ["pointcache", "skeletalMesh"] label = "Import Alembic Skeletal Mesh" representations = ["abc"] icon = "cube" diff --git a/openpype/hosts/unreal/plugins/load/load_rig.py b/openpype/hosts/unreal/plugins/load/load_skeletalmesh_fbx.py similarity index 100% rename from openpype/hosts/unreal/plugins/load/load_rig.py rename to openpype/hosts/unreal/plugins/load/load_skeletalmesh_fbx.py diff --git a/openpype/hosts/unreal/plugins/load/load_alembic_staticmesh.py b/openpype/hosts/unreal/plugins/load/load_staticmesh_abc.py similarity index 99% rename from openpype/hosts/unreal/plugins/load/load_alembic_staticmesh.py rename to openpype/hosts/unreal/plugins/load/load_staticmesh_abc.py index a5b9cbd1fc..c7841cef53 100644 --- a/openpype/hosts/unreal/plugins/load/load_alembic_staticmesh.py +++ b/openpype/hosts/unreal/plugins/load/load_staticmesh_abc.py @@ -14,7 +14,7 @@ import unreal # noqa class StaticMeshAlembicLoader(plugin.Loader): """Load Unreal StaticMesh from Alembic""" - families = ["model"] + families = ["model", "staticMesh"] label = "Import Alembic Static Mesh" representations = ["abc"] icon = "cube" diff --git a/openpype/hosts/unreal/plugins/load/load_staticmeshfbx.py b/openpype/hosts/unreal/plugins/load/load_staticmesh_fbx.py similarity index 100% rename from openpype/hosts/unreal/plugins/load/load_staticmeshfbx.py rename to openpype/hosts/unreal/plugins/load/load_staticmesh_fbx.py diff --git a/openpype/hosts/unreal/plugins/publish/collect_instances.py b/openpype/hosts/unreal/plugins/publish/collect_instances.py index 2f604cb322..1f25cbde7d 100644 --- a/openpype/hosts/unreal/plugins/publish/collect_instances.py +++ b/openpype/hosts/unreal/plugins/publish/collect_instances.py @@ -3,6 +3,8 @@ import ast import unreal # noqa import pyblish.api +from openpype.hosts.unreal.api.pipeline import UNREAL_VERSION +from openpype.pipeline.publish import KnownPublishError class CollectInstances(pyblish.api.ContextPlugin): @@ -23,8 +25,10 @@ class CollectInstances(pyblish.api.ContextPlugin): def process(self, context): ar = unreal.AssetRegistryHelpers.get_asset_registry() - instance_containers = ar.get_assets_by_class( - "OpenPypePublishInstance", True) + class_name = ["/Script/OpenPype", + "AssetContainer"] if UNREAL_VERSION.major == 5 and \ + UNREAL_VERSION.minor > 0 else "OpenPypePublishInstance" # noqa + instance_containers = ar.get_assets_by_class(class_name, True) for container_data in instance_containers: asset = container_data.get_asset() @@ -32,9 +36,8 @@ class CollectInstances(pyblish.api.ContextPlugin): data["objectName"] = container_data.asset_name # convert to strings data = {str(key): str(value) for (key, value) in data.items()} - assert data.get("family"), ( - "instance has no family" - ) + if not data.get("family"): + raise KnownPublishError("instance has no family") # content of container members = ast.literal_eval(data.get("members")) diff --git a/openpype/lib/events.py b/openpype/lib/events.py index 747761fb3e..096201312f 100644 --- a/openpype/lib/events.py +++ b/openpype/lib/events.py @@ -74,22 +74,52 @@ class EventCallback(object): "Registered callback is not callable. \"{}\"" ).format(str(func))) - # Collect additional data about function - # - name - # - path - # - if expect argument or not + # Collect function name and path to file for logging func_name = func.__name__ func_path = os.path.abspath(inspect.getfile(func)) + + # Get expected arguments from function spec + # - positional arguments are always preferred + expect_args = False + expect_kwargs = False + fake_event = "fake" if hasattr(inspect, "signature"): + # Python 3 using 'Signature' object where we try to bind arg + # or kwarg. Using signature is recommended approach based on + # documentation. sig = inspect.signature(func) - expect_args = len(sig.parameters) > 0 + try: + sig.bind(fake_event) + expect_args = True + except TypeError: + pass + + try: + sig.bind(event=fake_event) + expect_kwargs = True + except TypeError: + pass + else: - expect_args = len(inspect.getargspec(func)[0]) > 0 + # In Python 2 'signature' is not available so 'getcallargs' is used + # - 'getcallargs' is marked as deprecated since Python 3.0 + try: + inspect.getcallargs(func, fake_event) + expect_args = True + except TypeError: + pass + + try: + inspect.getcallargs(func, event=fake_event) + expect_kwargs = True + except TypeError: + pass self._func_ref = func_ref self._func_name = func_name self._func_path = func_path self._expect_args = expect_args + self._expect_kwargs = expect_kwargs self._ref_valid = func_ref is not None self._enabled = True @@ -157,6 +187,10 @@ class EventCallback(object): try: if self._expect_args: callback(event) + + elif self._expect_kwargs: + callback(event=event) + else: callback() diff --git a/openpype/lib/path_templates.py b/openpype/lib/path_templates.py index b160054e38..0f99efb430 100644 --- a/openpype/lib/path_templates.py +++ b/openpype/lib/path_templates.py @@ -422,7 +422,7 @@ class TemplateResult(str): cls = self.__class__ return cls( - os.path.normpath(self), + os.path.normpath(self.replace("\\", "/")), self.template, self.solved, self.used_values, diff --git a/openpype/modules/avalon_apps/avalon_app.py b/openpype/modules/avalon_apps/avalon_app.py index f9085522b0..a0226ecc5c 100644 --- a/openpype/modules/avalon_apps/avalon_app.py +++ b/openpype/modules/avalon_apps/avalon_app.py @@ -57,7 +57,7 @@ class AvalonModule(OpenPypeModule, ITrayModule): if not self._library_loader_imported: return - from Qt import QtWidgets + from qtpy import QtWidgets # Actions action_library_loader = QtWidgets.QAction( "Loader", tray_menu @@ -75,7 +75,7 @@ class AvalonModule(OpenPypeModule, ITrayModule): def show_library_loader(self): if self._library_loader_window is None: - from Qt import QtCore + from qtpy import QtCore from openpype.tools.libraryloader import LibraryLoaderWindow from openpype.pipeline import install_openpype_plugins diff --git a/openpype/modules/clockify/clockify_module.py b/openpype/modules/clockify/clockify_module.py index 14fcb01f67..300d5576e2 100644 --- a/openpype/modules/clockify/clockify_module.py +++ b/openpype/modules/clockify/clockify_module.py @@ -183,7 +183,7 @@ class ClockifyModule( # Definition of Tray menu def tray_menu(self, parent_menu): # Menu for Tray App - from Qt import QtWidgets + from qtpy import QtWidgets menu = QtWidgets.QMenu("Clockify", parent_menu) menu.setProperty("submenu", "on") diff --git a/openpype/modules/clockify/widgets.py b/openpype/modules/clockify/widgets.py index d58df3c067..122b6212c0 100644 --- a/openpype/modules/clockify/widgets.py +++ b/openpype/modules/clockify/widgets.py @@ -1,4 +1,4 @@ -from Qt import QtCore, QtGui, QtWidgets +from qtpy import QtCore, QtGui, QtWidgets from openpype import resources, style diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 7b597bd478..7e39a644a2 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -127,25 +127,17 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "harmony": [r".*"], # for everything from AE "celaction": [r".*"]} - enviro_filter = [ + environ_job_filter = [ + "OPENPYPE_METADATA_FILE" + ] + + environ_keys = [ "FTRACK_API_USER", "FTRACK_API_KEY", "FTRACK_SERVER", - "OPENPYPE_METADATA_FILE", - "AVALON_PROJECT", - "AVALON_ASSET", - "AVALON_TASK", "AVALON_APP_NAME", - "OPENPYPE_PUBLISH_JOB" - - "OPENPYPE_LOG_NO_COLORS", "OPENPYPE_USERNAME", - "OPENPYPE_RENDER_JOB", - "OPENPYPE_PUBLISH_JOB", - "OPENPYPE_MONGO", - "OPENPYPE_VERSION", - - "IS_TEST" + "OPENPYPE_VERSION" ] # custom deadline attributes @@ -228,30 +220,43 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): instance_version = instance.data.get("version") # take this if exists if instance_version != 1: override_version = instance_version - output_dir = self._get_publish_folder(instance.context.data['anatomy'], - deepcopy( - instance.data["anatomyData"]), - instance.data.get("asset"), - instances[0]["subset"], - 'render', - override_version) + output_dir = self._get_publish_folder( + instance.context.data['anatomy'], + deepcopy(instance.data["anatomyData"]), + instance.data.get("asset"), + instances[0]["subset"], + 'render', + override_version + ) # Transfer the environment from the original job to this dependent # job so they use the same environment metadata_path, roothless_metadata_path = \ self._create_metadata_path(instance) - environment = job["Props"].get("Env", {}) - environment["AVALON_PROJECT"] = legacy_io.Session["AVALON_PROJECT"] - environment["AVALON_ASSET"] = legacy_io.Session["AVALON_ASSET"] - environment["AVALON_TASK"] = legacy_io.Session["AVALON_TASK"] - environment["AVALON_APP_NAME"] = os.environ.get("AVALON_APP_NAME") - environment["OPENPYPE_VERSION"] = os.environ.get("OPENPYPE_VERSION") - environment["OPENPYPE_LOG_NO_COLORS"] = "1" - environment["OPENPYPE_USERNAME"] = instance.context.data["user"] - environment["OPENPYPE_PUBLISH_JOB"] = "1" - environment["OPENPYPE_RENDER_JOB"] = "0" - environment["IS_TEST"] = is_in_tests() + environment = { + "AVALON_PROJECT": legacy_io.Session["AVALON_PROJECT"], + "AVALON_ASSET": legacy_io.Session["AVALON_ASSET"], + "AVALON_TASK": legacy_io.Session["AVALON_TASK"], + "OPENPYPE_USERNAME": instance.context.data["user"], + "OPENPYPE_PUBLISH_JOB": "1", + "OPENPYPE_RENDER_JOB": "0", + "OPENPYPE_REMOTE_JOB": "0", + "OPENPYPE_LOG_NO_COLORS": "1", + "IS_TEST": str(int(is_in_tests())) + } + + # add environments from self.environ_keys + for env_key in self.environ_keys: + if os.getenv(env_key): + environment[env_key] = os.environ[env_key] + + # pass environment keys from self.environ_job_filter + job_environ = job["Props"].get("Env", {}) + for env_j_key in self.environ_job_filter: + if job_environ.get(env_j_key): + environment[env_j_key] = job_environ[env_j_key] + # Add mongo url if it's enabled if instance.context.data.get("deadlinePassMongoUrl"): mongo_url = os.environ.get("OPENPYPE_MONGO") @@ -318,19 +323,15 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): if instance.data.get("suspend_publish"): payload["JobInfo"]["InitialStatus"] = "Suspended" - index = 0 - for key in environment: - if key.upper() in self.enviro_filter: - payload["JobInfo"].update( - { - "EnvironmentKeyValue%d" - % index: "{key}={value}".format( - key=key, value=environment[key] - ) - } - ) - index += 1 - + for index, (key_, value_) in enumerate(environment.items()): + payload["JobInfo"].update( + { + "EnvironmentKeyValue%d" + % index: "{key}={value}".format( + key=key_, value=value_ + ) + } + ) # remove secondary pool payload["JobInfo"].pop("SecondaryPool", None) diff --git a/openpype/modules/example_addons/example_addon/widgets.py b/openpype/modules/example_addons/example_addon/widgets.py index c0a0a7e510..cd0da3ae43 100644 --- a/openpype/modules/example_addons/example_addon/widgets.py +++ b/openpype/modules/example_addons/example_addon/widgets.py @@ -1,4 +1,4 @@ -from Qt import QtWidgets +from qtpy import QtWidgets from openpype.style import load_stylesheet diff --git a/openpype/modules/ftrack/event_handlers_server/event_first_version_status.py b/openpype/modules/ftrack/event_handlers_server/event_first_version_status.py index ecc6c95d90..8ef333effd 100644 --- a/openpype/modules/ftrack/event_handlers_server/event_first_version_status.py +++ b/openpype/modules/ftrack/event_handlers_server/event_first_version_status.py @@ -135,9 +135,9 @@ class FirstVersionStatus(BaseEvent): new_status = asset_version_statuses.get(found_item["status"]) if not new_status: - self.log.warning( + self.log.warning(( "AssetVersion doesn't have status `{}`." - ).format(found_item["status"]) + ).format(found_item["status"])) continue try: diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index 6f061881e8..156b3a86fd 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -3,9 +3,9 @@ import time import datetime import threading -from Qt import QtCore, QtWidgets, QtGui - import ftrack_api +from qtpy import QtCore, QtWidgets, QtGui + from openpype import resources from openpype.lib import Logger from openpype_modules.ftrack import resolve_ftrack_url, FTRACK_MODULE_DIR diff --git a/openpype/modules/ftrack/tray/login_dialog.py b/openpype/modules/ftrack/tray/login_dialog.py index 05d9226ca4..fbb3455775 100644 --- a/openpype/modules/ftrack/tray/login_dialog.py +++ b/openpype/modules/ftrack/tray/login_dialog.py @@ -1,10 +1,13 @@ import os + import requests +from qtpy import QtCore, QtGui, QtWidgets + from openpype import style from openpype_modules.ftrack.lib import credentials -from . import login_tools from openpype import resources -from Qt import QtCore, QtGui, QtWidgets + +from . import login_tools class CredentialsDialog(QtWidgets.QDialog): diff --git a/openpype/modules/interfaces.py b/openpype/modules/interfaces.py index f92ec6bf2d..7cd299df67 100644 --- a/openpype/modules/interfaces.py +++ b/openpype/modules/interfaces.py @@ -222,7 +222,7 @@ class ITrayAction(ITrayModule): pass def tray_menu(self, tray_menu): - from Qt import QtWidgets + from qtpy import QtWidgets if self.admin_action: menu = self.admin_submenu(tray_menu) @@ -247,7 +247,7 @@ class ITrayAction(ITrayModule): @staticmethod def admin_submenu(tray_menu): if ITrayAction._admin_submenu is None: - from Qt import QtWidgets + from qtpy import QtWidgets admin_submenu = QtWidgets.QMenu("Admin", tray_menu) admin_submenu.menuAction().setVisible(False) @@ -279,7 +279,7 @@ class ITrayService(ITrayModule): @staticmethod def services_submenu(tray_menu): if ITrayService._services_submenu is None: - from Qt import QtWidgets + from qtpy import QtWidgets services_submenu = QtWidgets.QMenu("Services", tray_menu) services_submenu.menuAction().setVisible(False) @@ -294,7 +294,7 @@ class ITrayService(ITrayModule): @staticmethod def _load_service_icons(): - from Qt import QtGui + from qtpy import QtGui ITrayService._failed_icon = QtGui.QIcon( resources.get_resource("icons", "circle_red.png") @@ -325,7 +325,7 @@ class ITrayService(ITrayModule): return ITrayService._failed_icon def tray_menu(self, tray_menu): - from Qt import QtWidgets + from qtpy import QtWidgets action = QtWidgets.QAction( self.label, diff --git a/openpype/modules/kitsu/kitsu_widgets.py b/openpype/modules/kitsu/kitsu_widgets.py index 65baed9665..5ff3613583 100644 --- a/openpype/modules/kitsu/kitsu_widgets.py +++ b/openpype/modules/kitsu/kitsu_widgets.py @@ -1,4 +1,4 @@ -from Qt import QtWidgets, QtCore, QtGui +from qtpy import QtWidgets, QtCore, QtGui from openpype import style from openpype.modules.kitsu.utils.credentials import ( diff --git a/openpype/modules/kitsu/utils/sync_service.py b/openpype/modules/kitsu/utils/sync_service.py index 441b95a7ec..237746bea0 100644 --- a/openpype/modules/kitsu/utils/sync_service.py +++ b/openpype/modules/kitsu/utils/sync_service.py @@ -1,12 +1,9 @@ import os +import threading import gazu -from openpype.client import ( - get_project, - get_assets, - get_asset_by_name -) +from openpype.client import get_project, get_assets, get_asset_by_name from openpype.pipeline import AvalonMongoDB from .credentials import validate_credentials from .update_op_with_zou import ( @@ -397,6 +394,13 @@ def start_listeners(login: str, password: str): login (str): Kitsu user login password (str): Kitsu user password """ + # Refresh token every week + def refresh_token_every_week(): + print("Refreshing token...") + gazu.refresh_token() + threading.Timer(7 * 3600 * 24, refresh_token_every_week).start() + + refresh_token_every_week() # Connect to server listener = Listener(login, password) diff --git a/openpype/modules/log_viewer/log_view_module.py b/openpype/modules/log_viewer/log_view_module.py index 31e954fadd..e9dba2041c 100644 --- a/openpype/modules/log_viewer/log_view_module.py +++ b/openpype/modules/log_viewer/log_view_module.py @@ -22,7 +22,7 @@ class LogViewModule(OpenPypeModule, ITrayModule): # Definition of Tray menu def tray_menu(self, tray_menu): - from Qt import QtWidgets + from qtpy import QtWidgets # Menu for Tray App menu = QtWidgets.QMenu('Logging', tray_menu) diff --git a/openpype/modules/log_viewer/tray/app.py b/openpype/modules/log_viewer/tray/app.py index def319e0e3..3c49f337d4 100644 --- a/openpype/modules/log_viewer/tray/app.py +++ b/openpype/modules/log_viewer/tray/app.py @@ -1,4 +1,4 @@ -from Qt import QtWidgets, QtCore +from qtpy import QtWidgets, QtCore from .widgets import LogsWidget, OutputWidget from openpype import style diff --git a/openpype/modules/log_viewer/tray/models.py b/openpype/modules/log_viewer/tray/models.py index d369ffeb64..bc1f54c06c 100644 --- a/openpype/modules/log_viewer/tray/models.py +++ b/openpype/modules/log_viewer/tray/models.py @@ -1,5 +1,5 @@ import collections -from Qt import QtCore, QtGui +from qtpy import QtCore, QtGui from openpype.lib import Logger diff --git a/openpype/modules/log_viewer/tray/widgets.py b/openpype/modules/log_viewer/tray/widgets.py index c7ac64ab70..981152e6e2 100644 --- a/openpype/modules/log_viewer/tray/widgets.py +++ b/openpype/modules/log_viewer/tray/widgets.py @@ -1,5 +1,5 @@ import html -from Qt import QtCore, QtWidgets +from qtpy import QtCore, QtWidgets import qtawesome from .models import LogModel, LogsFilterProxy diff --git a/openpype/modules/muster/muster.py b/openpype/modules/muster/muster.py index 8d395d16e8..77b9214a5a 100644 --- a/openpype/modules/muster/muster.py +++ b/openpype/modules/muster/muster.py @@ -53,7 +53,7 @@ class MusterModule(OpenPypeModule, ITrayModule): # Definition of Tray menu def tray_menu(self, parent): """Add **change credentials** option to tray menu.""" - from Qt import QtWidgets + from qtpy import QtWidgets # Menu for Tray App menu = QtWidgets.QMenu('Muster', parent) diff --git a/openpype/modules/muster/widget_login.py b/openpype/modules/muster/widget_login.py index ae838c6cea..f38f43fb7f 100644 --- a/openpype/modules/muster/widget_login.py +++ b/openpype/modules/muster/widget_login.py @@ -1,5 +1,4 @@ -import os -from Qt import QtCore, QtGui, QtWidgets +from qtpy import QtCore, QtGui, QtWidgets from openpype import resources, style diff --git a/openpype/modules/python_console_interpreter/window/widgets.py b/openpype/modules/python_console_interpreter/window/widgets.py index 36ce1b61a2..b670352f44 100644 --- a/openpype/modules/python_console_interpreter/window/widgets.py +++ b/openpype/modules/python_console_interpreter/window/widgets.py @@ -5,7 +5,7 @@ import collections from code import InteractiveInterpreter import appdirs -from Qt import QtCore, QtWidgets, QtGui +from qtpy import QtCore, QtWidgets, QtGui from openpype import resources from openpype.style import load_stylesheet diff --git a/openpype/modules/shotgrid/tray/credential_dialog.py b/openpype/modules/shotgrid/tray/credential_dialog.py index 9d841d98be..7b839b63c0 100644 --- a/openpype/modules/shotgrid/tray/credential_dialog.py +++ b/openpype/modules/shotgrid/tray/credential_dialog.py @@ -1,5 +1,5 @@ import os -from Qt import QtCore, QtWidgets, QtGui +from qtpy import QtCore, QtWidgets, QtGui from openpype import style from openpype import resources diff --git a/openpype/modules/shotgrid/tray/shotgrid_tray.py b/openpype/modules/shotgrid/tray/shotgrid_tray.py index 4038d77b03..8e363bd318 100644 --- a/openpype/modules/shotgrid/tray/shotgrid_tray.py +++ b/openpype/modules/shotgrid/tray/shotgrid_tray.py @@ -1,7 +1,7 @@ import os import webbrowser -from Qt import QtWidgets +from qtpy import QtWidgets from openpype.modules.shotgrid.lib import credentials from openpype.modules.shotgrid.tray.credential_dialog import ( diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 653ee50541..f9b99da02b 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -1244,7 +1244,7 @@ class SyncServerModule(OpenPypeModule, ITrayModule): if not self.enabled: return - from Qt import QtWidgets + from qtpy import QtWidgets """Add menu or action to Tray(or parent)'s menu""" action = QtWidgets.QAction(self.label, parent_menu) action.triggered.connect(self.show_widget) diff --git a/openpype/modules/sync_server/tray/app.py b/openpype/modules/sync_server/tray/app.py index 9b9768327e..c093835128 100644 --- a/openpype/modules/sync_server/tray/app.py +++ b/openpype/modules/sync_server/tray/app.py @@ -1,4 +1,4 @@ -from Qt import QtWidgets, QtCore, QtGui +from qtpy import QtWidgets, QtCore, QtGui from openpype.tools.settings import style diff --git a/openpype/modules/sync_server/tray/delegates.py b/openpype/modules/sync_server/tray/delegates.py index 988eb40d28..e14b2e2f60 100644 --- a/openpype/modules/sync_server/tray/delegates.py +++ b/openpype/modules/sync_server/tray/delegates.py @@ -1,5 +1,5 @@ import os -from Qt import QtCore, QtWidgets, QtGui +from qtpy import QtCore, QtWidgets, QtGui from openpype.lib import Logger diff --git a/openpype/modules/sync_server/tray/models.py b/openpype/modules/sync_server/tray/models.py index d63d046508..b52f350907 100644 --- a/openpype/modules/sync_server/tray/models.py +++ b/openpype/modules/sync_server/tray/models.py @@ -3,8 +3,7 @@ import attr from bson.objectid import ObjectId import datetime -from Qt import QtCore -from Qt.QtCore import Qt +from qtpy import QtCore import qtawesome from openpype.tools.utils.delegates import pretty_timestamp @@ -79,16 +78,16 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): def columnCount(self, _index=None): return len(self._header) - def headerData(self, section, orientation, role=Qt.DisplayRole): + def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole): if section >= len(self.COLUMN_LABELS): return - if role == Qt.DisplayRole: - if orientation == Qt.Horizontal: + if role == QtCore.Qt.DisplayRole: + if orientation == QtCore.Qt.Horizontal: return self.COLUMN_LABELS[section][1] if role == HEADER_NAME_ROLE: - if orientation == Qt.Horizontal: + if orientation == QtCore.Qt.Horizontal: return self.COLUMN_LABELS[section][0] # return name def data(self, index, role): @@ -123,7 +122,7 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): return item.status == lib.STATUS[2] and \ item.remote_progress < 1 - if role in (Qt.DisplayRole, Qt.EditRole): + if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole): # because of ImageDelegate if header_value in ['remote_site', 'local_site']: return "" @@ -146,7 +145,7 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): if role == STATUS_ROLE: return item.status - if role == Qt.UserRole: + if role == QtCore.Qt.UserRole: return item._id @property @@ -409,7 +408,7 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): """ for i in range(self.rowCount(None)): index = self.index(i, 0) - value = self.data(index, Qt.UserRole) + value = self.data(index, QtCore.Qt.UserRole) if value == id: return index return None @@ -917,7 +916,7 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): if not self.can_edit: return - repre_id = self.data(index, Qt.UserRole) + repre_id = self.data(index, QtCore.Qt.UserRole) representation = get_representation_by_id(self.project, repre_id) if representation: @@ -1353,7 +1352,7 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel): if not self.can_edit: return - file_id = self.data(index, Qt.UserRole) + file_id = self.data(index, QtCore.Qt.UserRole) updated_file = None representation = get_representation_by_id(self.project, self._id) diff --git a/openpype/modules/sync_server/tray/widgets.py b/openpype/modules/sync_server/tray/widgets.py index c40aa98f24..b9ef45727a 100644 --- a/openpype/modules/sync_server/tray/widgets.py +++ b/openpype/modules/sync_server/tray/widgets.py @@ -3,8 +3,7 @@ import subprocess import sys from functools import partial -from Qt import QtWidgets, QtCore, QtGui -from Qt.QtCore import Qt +from qtpy import QtWidgets, QtCore, QtGui import qtawesome from openpype.tools.settings import style @@ -260,7 +259,7 @@ class _SyncRepresentationWidget(QtWidgets.QWidget): self._selected_ids = set() for index in idxs: - self._selected_ids.add(self.model.data(index, Qt.UserRole)) + self._selected_ids.add(self.model.data(index, QtCore.Qt.UserRole)) def _set_selection(self): """ @@ -291,7 +290,7 @@ class _SyncRepresentationWidget(QtWidgets.QWidget): self.table_view.openPersistentEditor(index) return - _id = self.model.data(index, Qt.UserRole) + _id = self.model.data(index, QtCore.Qt.UserRole) detail_window = SyncServerDetailWindow( self.sync_server, _id, self.model.project, parent=self) detail_window.exec() @@ -615,7 +614,7 @@ class SyncRepresentationSummaryWidget(_SyncRepresentationWidget): table_view.setSelectionBehavior( QtWidgets.QAbstractItemView.SelectRows) table_view.horizontalHeader().setSortIndicator( - -1, Qt.AscendingOrder) + -1, QtCore.Qt.AscendingOrder) table_view.setAlternatingRowColors(True) table_view.verticalHeader().hide() table_view.viewport().setAttribute(QtCore.Qt.WA_Hover, True) @@ -773,7 +772,8 @@ class SyncRepresentationDetailWidget(_SyncRepresentationWidget): QtWidgets.QAbstractItemView.ExtendedSelection) table_view.setSelectionBehavior( QtWidgets.QTableView.SelectRows) - table_view.horizontalHeader().setSortIndicator(-1, Qt.AscendingOrder) + table_view.horizontalHeader().setSortIndicator( + -1, QtCore.Qt.AscendingOrder) table_view.horizontalHeader().setSortIndicatorShown(True) table_view.setAlternatingRowColors(True) table_view.verticalHeader().hide() diff --git a/openpype/modules/timers_manager/idle_threads.py b/openpype/modules/timers_manager/idle_threads.py index 7242761143..eb11bbf117 100644 --- a/openpype/modules/timers_manager/idle_threads.py +++ b/openpype/modules/timers_manager/idle_threads.py @@ -1,5 +1,5 @@ import time -from Qt import QtCore +from qtpy import QtCore from pynput import mouse, keyboard from openpype.lib import Logger diff --git a/openpype/modules/timers_manager/widget_user_idle.py b/openpype/modules/timers_manager/widget_user_idle.py index 1ecea74440..9df328e6b2 100644 --- a/openpype/modules/timers_manager/widget_user_idle.py +++ b/openpype/modules/timers_manager/widget_user_idle.py @@ -1,4 +1,4 @@ -from Qt import QtCore, QtGui, QtWidgets +from qtpy import QtCore, QtGui, QtWidgets from openpype import resources, style diff --git a/openpype/modules/webserver/host_console_listener.py b/openpype/modules/webserver/host_console_listener.py index fdfe1ba688..e5c11af9c2 100644 --- a/openpype/modules/webserver/host_console_listener.py +++ b/openpype/modules/webserver/host_console_listener.py @@ -3,7 +3,7 @@ from aiohttp import web import json import logging from concurrent.futures import CancelledError -from Qt import QtWidgets +from qtpy import QtWidgets from openpype.modules import ITrayService diff --git a/openpype/plugins/publish/collect_resources_path.py b/openpype/plugins/publish/collect_resources_path.py index 00f65b8b67..a2d5b95ab2 100644 --- a/openpype/plugins/publish/collect_resources_path.py +++ b/openpype/plugins/publish/collect_resources_path.py @@ -21,6 +21,7 @@ class CollectResourcesPath(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.495 families = ["workfile", "pointcache", + "proxyAbc", "camera", "animation", "model", @@ -50,6 +51,7 @@ class CollectResourcesPath(pyblish.api.InstancePlugin): "source", "assembly", "fbx", + "gltf", "textures", "action", "background", diff --git a/openpype/plugins/publish/extract_burnin.py b/openpype/plugins/publish/extract_burnin.py index fd8dfdece9..f113e61bb0 100644 --- a/openpype/plugins/publish/extract_burnin.py +++ b/openpype/plugins/publish/extract_burnin.py @@ -1,5 +1,4 @@ import os -import re import json import copy import tempfile @@ -21,6 +20,7 @@ from openpype.lib import ( CREATE_NO_WINDOW ) +from openpype.lib.profiles_filtering import filter_profiles class ExtractBurnin(publish.Extractor): @@ -34,6 +34,7 @@ class ExtractBurnin(publish.Extractor): label = "Extract burnins" order = pyblish.api.ExtractorOrder + 0.03 + families = ["review", "burnin"] hosts = [ "nuke", @@ -53,6 +54,7 @@ class ExtractBurnin(publish.Extractor): "flame" # "resolve" ] + optional = True positions = [ @@ -69,11 +71,15 @@ class ExtractBurnin(publish.Extractor): "y_offset": 5 } - # Preset attributes + # Configurable by Settings profiles = None options = None def process(self, instance): + if not self.profiles: + self.log.warning("No profiles present for create burnin") + return + # QUESTION what is this for and should we raise an exception? if "representations" not in instance.data: raise RuntimeError("Burnin needs already created mov to work on.") @@ -137,18 +143,29 @@ class ExtractBurnin(publish.Extractor): return filtered_repres def main_process(self, instance): - # TODO get these data from context host_name = instance.context.data["hostName"] - task_name = os.environ["AVALON_TASK"] - family = self.main_family_from_instance(instance) + family = instance.data["family"] + task_data = instance.data["anatomyData"].get("task", {}) + task_name = task_data.get("name") + task_type = task_data.get("type") + subset = instance.data["subset"] + + filtering_criteria = { + "hosts": host_name, + "families": family, + "task_names": task_name, + "task_types": task_type, + "subset": subset + } + profile = filter_profiles(self.profiles, filtering_criteria, + logger=self.log) - # Find profile most matching current host, task and instance family - profile = self.find_matching_profile(host_name, task_name, family) if not profile: self.log.info(( "Skipped instance. None of profiles in presets are for" - " Host: \"{}\" | Family: \"{}\" | Task \"{}\"" - ).format(host_name, family, task_name)) + " Host: \"{}\" | Families: \"{}\" | Task \"{}\"" + " | Task type \"{}\" | Subset \"{}\" " + ).format(host_name, family, task_name, task_type, subset)) return self.log.debug("profile: {}".format(profile)) @@ -158,7 +175,8 @@ class ExtractBurnin(publish.Extractor): if not burnin_defs: self.log.info(( "Skipped instance. Burnin definitions are not set for profile" - " Host: \"{}\" | Family: \"{}\" | Task \"{}\" | Profile \"{}\"" + " Host: \"{}\" | Families: \"{}\" | Task \"{}\"" + " | Profile \"{}\"" ).format(host_name, family, task_name, profile)) return @@ -693,130 +711,6 @@ class ExtractBurnin(publish.Extractor): ) }) - def find_matching_profile(self, host_name, task_name, family): - """ Filter profiles by Host name, Task name and main Family. - - Filtering keys are "hosts" (list), "tasks" (list), "families" (list). - If key is not find or is empty than it's expected to match. - - Args: - profiles (list): Profiles definition from presets. - host_name (str): Current running host name. - task_name (str): Current context task name. - family (str): Main family of current Instance. - - Returns: - dict/None: Return most matching profile or None if none of profiles - match at least one criteria. - """ - - matching_profiles = None - highest_points = -1 - for profile in self.profiles or tuple(): - profile_points = 0 - profile_value = [] - - # Host filtering - host_names = profile.get("hosts") - match = self.validate_value_by_regexes(host_name, host_names) - if match == -1: - continue - profile_points += match - profile_value.append(bool(match)) - - # Task filtering - task_names = profile.get("tasks") - match = self.validate_value_by_regexes(task_name, task_names) - if match == -1: - continue - profile_points += match - profile_value.append(bool(match)) - - # Family filtering - families = profile.get("families") - match = self.validate_value_by_regexes(family, families) - if match == -1: - continue - profile_points += match - profile_value.append(bool(match)) - - if profile_points > highest_points: - matching_profiles = [] - highest_points = profile_points - - if profile_points == highest_points: - profile["__value__"] = profile_value - matching_profiles.append(profile) - - if not matching_profiles: - return - - if len(matching_profiles) == 1: - return matching_profiles[0] - - return self.profile_exclusion(matching_profiles) - - def profile_exclusion(self, matching_profiles): - """Find out most matching profile by host, task and family match. - - Profiles are selectivelly filtered. Each profile should have - "__value__" key with list of booleans. Each boolean represents - existence of filter for specific key (host, taks, family). - Profiles are looped in sequence. In each sequence are split into - true_list and false_list. For next sequence loop are used profiles in - true_list if there are any profiles else false_list is used. - - Filtering ends when only one profile left in true_list. Or when all - existence booleans loops passed, in that case first profile from left - profiles is returned. - - Args: - matching_profiles (list): Profiles with same values. - - Returns: - dict: Most matching profile. - """ - self.log.info( - "Search for first most matching profile in match order:" - " Host name -> Task name -> Family." - ) - # Filter all profiles with highest points value. First filter profiles - # with matching host if there are any then filter profiles by task - # name if there are any and lastly filter by family. Else use first in - # list. - idx = 0 - final_profile = None - while True: - profiles_true = [] - profiles_false = [] - for profile in matching_profiles: - value = profile["__value__"] - # Just use first profile when idx is greater than values. - if not idx < len(value): - final_profile = profile - break - - if value[idx]: - profiles_true.append(profile) - else: - profiles_false.append(profile) - - if final_profile is not None: - break - - if profiles_true: - matching_profiles = profiles_true - else: - matching_profiles = profiles_false - - if len(matching_profiles) == 1: - final_profile = matching_profiles[0] - break - idx += 1 - - final_profile.pop("__value__") - return final_profile - def filter_burnins_defs(self, profile, instance): """Filter outputs by their values from settings. @@ -909,56 +803,6 @@ class ExtractBurnin(publish.Extractor): return True return False - def compile_list_of_regexes(self, in_list): - """Convert strings in entered list to compiled regex objects.""" - regexes = [] - if not in_list: - return regexes - - for item in in_list: - if not item: - continue - - try: - regexes.append(re.compile(item)) - except TypeError: - self.log.warning(( - "Invalid type \"{}\" value \"{}\"." - " Expected string based object. Skipping." - ).format(str(type(item)), str(item))) - - return regexes - - def validate_value_by_regexes(self, value, in_list): - """Validate in any regexe from list match entered value. - - Args: - in_list (list): List with regexes. - value (str): String where regexes is checked. - - Returns: - int: Returns `0` when list is not set or is empty. Returns `1` when - any regex match value and returns `-1` when none of regexes - match value entered. - """ - if not in_list: - return 0 - - output = -1 - regexes = self.compile_list_of_regexes(in_list) - for regex in regexes: - if re.match(regex, value): - output = 1 - break - return output - - def main_family_from_instance(self, instance): - """Return main family of entered instance.""" - family = instance.data.get("family") - if not family: - family = instance.data["families"][0] - return family - def families_from_instance(self, instance): """Return all families of entered instance.""" families = [] diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 6a85a87129..2ce8037f5f 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -81,6 +81,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): order = pyblish.api.IntegratorOrder families = ["workfile", "pointcache", + "proxyAbc", "camera", "animation", "model", @@ -111,6 +112,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "image", "assembly", "fbx", + "gltf", "textures", "action", "harmony.template", diff --git a/openpype/plugins/publish/integrate_legacy.py b/openpype/plugins/publish/integrate_legacy.py index 670b637faa..8f3b0d4220 100644 --- a/openpype/plugins/publish/integrate_legacy.py +++ b/openpype/plugins/publish/integrate_legacy.py @@ -76,6 +76,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): order = pyblish.api.IntegratorOrder + 0.00001 families = ["workfile", "pointcache", + "proxyAbc", "camera", "animation", "model", @@ -106,6 +107,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "image", "assembly", "fbx", + "gltf", "textures", "action", "harmony.template", diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 2ffc13c84a..da491ea23b 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -209,6 +209,9 @@ { "families": [], "hosts": [], + "task_types": [], + "task_names": [], + "subsets": [], "burnins": { "burnin": { "TOP_LEFT": "{yy}-{mm}-{dd}", diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 988c0e777a..5f40c2a10c 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -59,6 +59,7 @@ "default_render_image_folder": "renders/maya", "enable_all_lights": true, "aov_separator": "underscore", + "remove_aovs": false, "reset_current_frame": false, "arnold_renderer": { "image_prefix": "//_", @@ -149,6 +150,14 @@ "Main" ] }, + "CreateProxyAlembic": { + "enabled": true, + "write_color_sets": false, + "write_face_sets": false, + "defaults": [ + "Main" + ] + }, "CreateMultiverseUsd": { "enabled": true, "defaults": [ @@ -171,7 +180,21 @@ "enabled": true, "defaults": [ "Main" - ] + ], + "expandProcedurals": false, + "motionBlur": true, + "motionBlurKeys": 2, + "motionBlurLength": 0.5, + "maskOptions": false, + "maskCamera": false, + "maskLight": false, + "maskShape": false, + "maskShader": false, + "maskOverride": false, + "maskDriver": false, + "maskFilter": false, + "maskColor_manager": false, + "maskOperator": false }, "CreateAssembly": { "enabled": true, @@ -250,6 +273,9 @@ "CollectFbxCamera": { "enabled": false }, + "CollectGLTF": { + "enabled": false + }, "ValidateInstanceInContext": { "enabled": true, "optional": true, @@ -569,6 +595,12 @@ "optional": false, "active": true }, + "ExtractProxyAlembic": { + "enabled": true, + "families": [ + "proxyAbc" + ] + }, "ExtractAlembic": { "enabled": true, "families": [ @@ -915,7 +947,7 @@ "current_context": [ { "subset_name_filters": [ - "\".+[Mm]ain\"" + ".+[Mm]ain" ], "families": [ "model" @@ -932,7 +964,8 @@ "subset_name_filters": [], "families": [ "animation", - "pointcache" + "pointcache", + "proxyAbc" ], "repre_names": [ "abc" @@ -1007,4 +1040,4 @@ "ValidateNoAnimation": false } } -} \ No newline at end of file +} diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index f2ada5fd8d..5388d04bc9 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -526,11 +526,28 @@ "object_type": "text" }, { - "type": "hosts-enum", "key": "hosts", - "label": "Hosts", + "label": "Host names", + "type": "hosts-enum", "multiselection": true }, + { + "key": "task_types", + "label": "Task types", + "type": "task-types-enum" + }, + { + "key": "task_names", + "label": "Task names", + "type": "list", + "object_type": "text" + }, + { + "key": "subsets", + "label": "Subset names", + "type": "list", + "object_type": "text" + }, { "type": "splitter" }, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json index bc6520474d..e1a3082616 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json @@ -200,7 +200,128 @@ } ] }, - + { + "type": "dict", + "collapsible": true, + "key": "CreateProxyAlembic", + "label": "Create Proxy Alembic", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "write_color_sets", + "label": "Write Color Sets" + }, + { + "type": "boolean", + "key": "write_face_sets", + "label": "Write Face Sets" + }, + { + "type": "list", + "key": "defaults", + "label": "Default Subsets", + "object_type": "text" + } + ] + }, + { + "type": "dict", + "collapsible": true, + "key": "CreateAss", + "label": "Create Ass", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "list", + "key": "defaults", + "label": "Default Subsets", + "object_type": "text" + }, + { + "type": "boolean", + "key": "expandProcedurals", + "label": "Expand Procedurals" + }, + { + "type": "boolean", + "key": "motionBlur", + "label": "Motion Blur" + }, + { + "type": "number", + "key": "motionBlurKeys", + "label": "Motion Blur Keys", + "minimum": 0 + }, + { + "type": "number", + "key": "motionBlurLength", + "label": "Motion Blur Length", + "decimal": 3 + }, + { + "type": "boolean", + "key": "maskOptions", + "label": "Mask Options" + }, + { + "type": "boolean", + "key": "maskCamera", + "label": "Mask Camera" + }, + { + "type": "boolean", + "key": "maskLight", + "label": "Mask Light" + }, + { + "type": "boolean", + "key": "maskShape", + "label": "Mask Shape" + }, + { + "type": "boolean", + "key": "maskShader", + "label": "Mask Shader" + }, + { + "type": "boolean", + "key": "maskOverride", + "label": "Mask Override" + }, + { + "type": "boolean", + "key": "maskDriver", + "label": "Mask Driver" + }, + { + "type": "boolean", + "key": "maskFilter", + "label": "Mask Filter" + }, + { + "type": "boolean", + "key": "maskColor_manager", + "label": "Mask Color Manager" + }, + { + "type": "boolean", + "key": "maskOperator", + "label": "Mask Operator" + } + ] + }, { "type": "schema_template", "name": "template_create_plugin", @@ -217,10 +338,6 @@ "key": "CreateMultiverseUsdOver", "label": "Create Multiverse USD Override" }, - { - "key": "CreateAss", - "label": "Create Ass" - }, { "key": "CreateAssembly", "label": "Create Assembly" diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json index ab8c6b885e..9aaff248ab 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json @@ -35,6 +35,20 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "key": "CollectGLTF", + "label": "Collect Assets for GLTF/GLB export", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + } + ] + }, { "type": "splitter" }, @@ -62,7 +76,7 @@ } ] }, - { + { "type": "dict", "collapsible": true, "key": "ValidateFrameRange", @@ -638,6 +652,26 @@ "type": "label", "label": "Extractors" }, + { + "type": "dict", + "collapsible": true, + "key": "ExtractProxyAlembic", + "label": "Extract Proxy Alembic", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + } + ] + }, { "type": "dict", "collapsible": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_render_settings.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_render_settings.json index 0cbb684fc6..e90495891b 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_render_settings.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_render_settings.json @@ -31,6 +31,11 @@ {"dot": ". (dot)"} ] }, + { + "key": "remove_aovs", + "label": "Remove existing AOVs", + "type": "boolean" + }, { "key": "reset_current_frame", "label": "Reset Current Frame", @@ -204,74 +209,76 @@ "defaults": "empty", "enum_items": [ {"empty": "< empty >"}, - {"atmosphereChannel": "atmosphere"}, - {"backgroundChannel": "background"}, - {"bumpNormalsChannel": "bumpnormals"}, - {"causticsChannel": "caustics"}, - {"coatFilterChannel": "coat_filter"}, - {"coatGlossinessChannel": "coatGloss"}, - {"coatReflectionChannel": "coat_reflection"}, - {"vrayCoatChannel": "coat_specular"}, - {"CoverageChannel": "coverage"}, - {"cryptomatteChannel": "cryptomatte"}, - {"customColor": "custom_color"}, - {"drBucketChannel": "DR"}, - {"denoiserChannel": "denoiser"}, - {"diffuseChannel": "diffuse"}, - {"ExtraTexElement": "extraTex"}, - {"giChannel": "GI"}, - {"LightMixElement": "None"}, - {"lightingChannel": "lighting"}, - {"LightingAnalysisChannel": "LightingAnalysis"}, - {"materialIDChannel": "materialID"}, - {"MaterialSelectElement": "materialSelect"}, - {"matteShadowChannel": "matteShadow"}, - {"MultiMatteElement": "multimatte"}, - {"multimatteIDChannel": "multimatteID"}, - {"normalsChannel": "normals"}, - {"nodeIDChannel": "objectId"}, - {"objectSelectChannel": "objectSelect"}, - {"rawCoatFilterChannel": "raw_coat_filter"}, - {"rawCoatReflectionChannel": "raw_coat_reflection"}, - {"rawDiffuseFilterChannel": "rawDiffuseFilter"}, - {"rawGiChannel": "rawGI"}, - {"rawLightChannel": "rawLight"}, - {"rawReflectionChannel": "rawReflection"}, - {"rawReflectionFilterChannel": "rawReflectionFilter"}, - {"rawRefractionChannel": "rawRefraction"}, - {"rawRefractionFilterChannel": "rawRefractionFilter"}, - {"rawShadowChannel": "rawShadow"}, - {"rawSheenFilterChannel": "raw_sheen_filter"}, - {"rawSheenReflectionChannel": "raw_sheen_reflection"}, - {"rawTotalLightChannel": "rawTotalLight"}, - {"reflectIORChannel": "reflIOR"}, - {"reflectChannel": "reflect"}, - {"reflectionFilterChannel": "reflectionFilter"}, - {"reflectGlossinessChannel": "reflGloss"}, - {"refractChannel": "refract"}, - {"refractionFilterChannel": "refractionFilter"}, - {"refractGlossinessChannel": "refrGloss"}, - {"renderIDChannel": "renderId"}, - {"FastSSS2Channel": "SSS"}, - {"sampleRateChannel": "sampleRate"}, + {"atmosphereChannel": "atmosphereChannel"}, + {"backgroundChannel": "backgroundChannel"}, + {"bumpNormalsChannel": "bumpNormalsChannel"}, + {"causticsChannel": "causticsChannel"}, + {"coatFilterChannel": "coatFilterChannel"}, + {"coatGlossinessChannel": "coatGlossinessChannel"}, + {"coatReflectionChannel": "coatReflectionChannel"}, + {"vrayCoatChannel": "vrayCoatChannel"}, + {"CoverageChannel": "CoverageChannel"}, + {"cryptomatteChannel": "cryptomatteChannel"}, + {"customColor": "customColor"}, + {"drBucketChannel": "drBucketChannel"}, + {"denoiserChannel": "denoiserChannel"}, + {"diffuseChannel": "diffuseChannel"}, + {"ExtraTexElement": "ExtraTexElement"}, + {"giChannel": "giChannel"}, + {"LightMixElement": "LightMixElement"}, + {"LightSelectElement": "LightSelectElement"}, + {"lightingChannel": "lightingChannel"}, + {"LightingAnalysisChannel": "LightingAnalysisChannel"}, + {"materialIDChannel": "materialIDChannel"}, + {"MaterialSelectElement": "MaterialSelectElement"}, + {"matteShadowChannel": "matteShadowChannel"}, + {"metalnessChannel": "metalnessChannel"}, + {"MultiMatteElement": "MultiMatteElement"}, + {"multimatteIDChannel": "multimatteIDChannel"}, + {"noiseLevelChannel": "noiseLevelChannel"}, + {"normalsChannel": "normalsChannel"}, + {"nodeIDChannel": "nodeIDChannel"}, + {"objectSelectChannel": "objectSelectChannel"}, + {"rawCoatFilterChannel": "rawCoatFilterChannel"}, + {"rawCoatReflectionChannel": "rawCoatReflectionChannel"}, + {"rawDiffuseFilterChannel": "rawDiffuseFilterChannel"}, + {"rawGiChannel": "rawGiChannel"}, + {"rawLightChannel": "rawLightChannel"}, + {"rawReflectionChannel": "rawReflectionChannel"}, + {"rawReflectionFilterChannel": "rawReflectionFilterChannel"}, + {"rawRefractionChannel": "rawRefractionChannel"}, + {"rawRefractionFilterChannel": "rawRefractionFilterChannel"}, + {"rawShadowChannel": "rawShadowChannel"}, + {"rawSheenFilterChannel": "rawSheenFilterChannel"}, + {"rawSheenReflectionChannel": "rawSheenReflectionChannel"}, + {"rawTotalLightChannel": "rawTotalLightChannel"}, + {"reflectIORChannel": "reflectIORChannel"}, + {"reflectChannel": "reflectChannel"}, + {"reflectionFilterChannel": "reflectionFilterChannel"}, + {"reflectGlossinessChannel": "reflectGlossinessChannel"}, + {"refractChannel": "refractChannel"}, + {"refractionFilterChannel": "refractionFilterChannel"}, + {"refractGlossinessChannel": "refractGlossinessChannel"}, + {"renderIDChannel": "renderIDChannel"}, + {"FastSSS2Channel": "FastSSS2Channel"}, + {"sampleRateChannel": "sampleRateChannel"}, {"samplerInfo": "samplerInfo"}, - {"selfIllumChannel": "selfIllum"}, - {"shadowChannel": "shadow"}, - {"sheenFilterChannel": "sheen_filter"}, - {"sheenGlossinessChannel": "sheenGloss"}, - {"sheenReflectionChannel": "sheen_reflection"}, - {"vraySheenChannel": "sheen_specular"}, - {"specularChannel": "specular"}, + {"selfIllumChannel": "selfIllumChannel"}, + {"shadowChannel": "shadowChannel"}, + {"sheenFilterChannel": "sheenFilterChannel"}, + {"sheenGlossinessChannel": "sheenGlossinessChannel"}, + {"sheenReflectionChannel": "sheenReflectionChannel"}, + {"vraySheenChannel": "vraySheenChannel"}, + {"specularChannel": "specularChannel"}, {"Toon": "Toon"}, - {"toonLightingChannel": "toonLighting"}, - {"toonSpecularChannel": "toonSpecular"}, - {"totalLightChannel": "totalLight"}, - {"unclampedColorChannel": "unclampedColor"}, - {"VRScansPaintMaskChannel": "VRScansPaintMask"}, - {"VRScansZoneMaskChannel": "VRScansZoneMask"}, - {"velocityChannel": "velocity"}, - {"zdepthChannel": "zDepth"}, - {"LightSelectElement": "lightselect"} + {"toonLightingChannel": "toonLightingChannel"}, + {"toonSpecularChannel": "toonSpecularChannel"}, + {"totalLightChannel": "totalLightChannel"}, + {"unclampedColorChannel": "unclampedColorChannel"}, + {"VRScansPaintMaskChannel": "VRScansPaintMaskChannel"}, + {"VRScansZoneMaskChannel": "VRScansZoneMaskChannel"}, + {"velocityChannel": "velocityChannel"}, + {"zdepthChannel": "zdepthChannel"} ] }, { @@ -310,9 +317,8 @@ "defaults": "0", "enum_items": [ {"0": "None"}, - {"1": "Photon Map"}, - {"2": "Irradiance Cache"}, - {"3": "Brute Force"} + {"3": "Irradiance Cache"}, + {"4": "Brute Force"} ] }, { @@ -323,9 +329,8 @@ "defaults": "0", "enum_items": [ {"0": "None"}, - {"1": "Photon Map"}, - {"2": "Irradiance Cache"}, - {"3": "Brute Force"} + {"2": "Irradiance Point Cloud"}, + {"4": "Brute Force"} ] }, { @@ -361,46 +366,46 @@ "defaults": "empty", "enum_items": [ {"empty": "< none >"}, - {"AO": "Ambient Occlusion"}, + {"Ambient Occlusion": "Ambient Occlusion"}, {"Background": "Background"}, {"Beauty": "Beauty"}, - {"BumpNormals": "Bump Normals"}, + {"Bump Normals": "Bump Normals"}, {"Caustics": "Caustics"}, - {"CausticsRaw": "Caustics Raw"}, + {"Caustics Raw": "Caustics Raw"}, {"Cryptomatte": "Cryptomatte"}, {"Custom": "Custom"}, - {"Z": "Depth"}, - {"DiffuseFilter": "Diffuse Filter"}, - {"DiffuseLighting": "Diffuse Lighting"}, - {"DiffuseLightingRaw": "Diffuse Lighting Raw"}, + {"Depth": "Depth"}, + {"Diffuse Filter": "Diffuse Filter"}, + {"Diffuse Lighting": "Diffuse Lighting"}, + {"Diffuse Lighting Raw": "Diffuse Lighting Raw"}, {"Emission": "Emission"}, - {"GI": "Global Illumination"}, - {"GIRaw": "Global Illumination Raw"}, + {"Global Illumination": "Global Illumination"}, + {"Global Illumination Raw": "Global Illumination Raw"}, {"Matte": "Matte"}, - {"MotionVectors": "Ambient Occlusion"}, - {"N": "Normals"}, - {"ID": "ObjectID"}, - {"ObjectBumpNormal": "Object-Space Bump Normals"}, - {"ObjectPosition": "Object-Space Positions"}, - {"PuzzleMatte": "Puzzle Matte"}, + {"Motion Vectors": "Motion Vectors"}, + {"Normals": "Normals"}, + {"ObjectID": "ObjectID"}, + {"Object-Space Bump Normals": "Object-Space Bump Normals"}, + {"Object-Space Positions": "Object-Space Positions"}, + {"Puzzle Matte": "Puzzle Matte"}, {"Reflections": "Reflections"}, - {"ReflectionsFilter": "Reflections Filter"}, - {"ReflectionsRaw": "Reflections Raw"}, + {"Reflections Filter": "Reflections Filter"}, + {"Reflections Raw": "Reflections Raw"}, {"Refractions": "Refractions"}, - {"RefractionsFilter": "Refractions Filter"}, - {"RefractionsRaw": "Refractions Filter"}, + {"Refractions Filter": "Refractions Filter"}, + {"Refractions Raw": "Refractions Filter"}, {"Shadows": "Shadows"}, {"SpecularLighting": "Specular Lighting"}, - {"SSS": "Sub Surface Scatter"}, - {"SSSRaw": "Sub Surface Scatter Raw"}, - {"TotalDiffuseLightingRaw": "Total Diffuse Lighting Raw"}, - {"TotalTransLightingRaw": "Total Translucency Filter"}, - {"TransTint": "Translucency Filter"}, - {"TransGIRaw": "Translucency Lighting Raw"}, - {"VolumeFogEmission": "Volume Fog Emission"}, - {"VolumeFogTint": "Volume Fog Tint"}, - {"VolumeLighting": "Volume Lighting"}, - {"P": "World Position"} + {"Sub Surface Scatter": "Sub Surface Scatter"}, + {"Sub Surface Scatter Raw": "Sub Surface Scatter Raw"}, + {"Total Diffuse Lighting Raw": "Total Diffuse Lighting Raw"}, + {"Total Translucency Filter": "Total Translucency Filter"}, + {"Translucency Filter": "Translucency Filter"}, + {"Translucency Lighting Raw": "Translucency Lighting Raw"}, + {"Volume Fog Emission": "Volume Fog Emission"}, + {"Volume Fog Tint": "Volume Fog Tint"}, + {"Volume Lighting": "Volume Lighting"}, + {"World Position": "World Position"} ] }, { diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/template_publish_families.json b/openpype/settings/entities/schemas/projects_schema/schemas/template_publish_families.json index f39ad31fbb..43dd74cdf9 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/template_publish_families.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/template_publish_families.json @@ -28,6 +28,7 @@ {"nukenodes": "nukenodes"}, {"plate": "plate"}, {"pointcache": "pointcache"}, + {"proxyAbc": "proxyAbc"}, {"prerender": "prerender"}, {"redshiftproxy": "redshiftproxy"}, {"reference": "reference"}, diff --git a/openpype/tools/attribute_defs/files_widget.py b/openpype/tools/attribute_defs/files_widget.py index 738e50ba07..2c8ed729c2 100644 --- a/openpype/tools/attribute_defs/files_widget.py +++ b/openpype/tools/attribute_defs/files_widget.py @@ -155,7 +155,7 @@ class DropEmpty(QtWidgets.QWidget): extensions_label = " or ".join(allowed_items) else: last_item = allowed_items.pop(-1) - new_last_item = " or ".join(last_item, allowed_items.pop(-1)) + new_last_item = " or ".join([last_item, allowed_items.pop(-1)]) allowed_items.append(new_last_item) extensions_label = ", ".join(allowed_items) diff --git a/openpype/tools/context_dialog/window.py b/openpype/tools/context_dialog/window.py index 3b544bd375..86c53b55c5 100644 --- a/openpype/tools/context_dialog/window.py +++ b/openpype/tools/context_dialog/window.py @@ -1,7 +1,7 @@ import os import json -from Qt import QtWidgets, QtCore, QtGui +from qtpy import QtWidgets, QtCore, QtGui from openpype import style from openpype.pipeline import AvalonMongoDB diff --git a/openpype/tools/launcher/actions.py b/openpype/tools/launcher/actions.py index 34d06f72cc..61660ee9b7 100644 --- a/openpype/tools/launcher/actions.py +++ b/openpype/tools/launcher/actions.py @@ -1,6 +1,6 @@ import os -from Qt import QtWidgets, QtGui +from qtpy import QtWidgets, QtGui from openpype import PLUGINS_DIR from openpype import style diff --git a/openpype/tools/launcher/constants.py b/openpype/tools/launcher/constants.py index 61f631759b..cb0049055c 100644 --- a/openpype/tools/launcher/constants.py +++ b/openpype/tools/launcher/constants.py @@ -1,4 +1,4 @@ -from Qt import QtCore +from qtpy import QtCore ACTION_ROLE = QtCore.Qt.UserRole diff --git a/openpype/tools/launcher/delegates.py b/openpype/tools/launcher/delegates.py index 7b53658727..02a40861d2 100644 --- a/openpype/tools/launcher/delegates.py +++ b/openpype/tools/launcher/delegates.py @@ -1,5 +1,5 @@ import time -from Qt import QtCore, QtWidgets, QtGui +from qtpy import QtCore, QtWidgets, QtGui from .constants import ( ANIMATION_START_ROLE, ANIMATION_STATE_ROLE, diff --git a/openpype/tools/launcher/lib.py b/openpype/tools/launcher/lib.py index 68e57c6b92..2507b6eddc 100644 --- a/openpype/tools/launcher/lib.py +++ b/openpype/tools/launcher/lib.py @@ -1,5 +1,5 @@ import os -from Qt import QtGui +from qtpy import QtGui import qtawesome from openpype import resources diff --git a/openpype/tools/launcher/models.py b/openpype/tools/launcher/models.py index 6e3b531018..6c763544a9 100644 --- a/openpype/tools/launcher/models.py +++ b/openpype/tools/launcher/models.py @@ -6,7 +6,7 @@ import collections import time import appdirs -from Qt import QtCore, QtGui +from qtpy import QtCore, QtGui import qtawesome from openpype.client import ( diff --git a/openpype/tools/launcher/widgets.py b/openpype/tools/launcher/widgets.py index 774ceb659d..3eb641bdb3 100644 --- a/openpype/tools/launcher/widgets.py +++ b/openpype/tools/launcher/widgets.py @@ -1,7 +1,7 @@ import copy import time import collections -from Qt import QtWidgets, QtCore, QtGui +from qtpy import QtWidgets, QtCore, QtGui import qtawesome from openpype.tools.flickcharm import FlickCharm diff --git a/openpype/tools/launcher/window.py b/openpype/tools/launcher/window.py index a9eaa932bb..f68fc4befc 100644 --- a/openpype/tools/launcher/window.py +++ b/openpype/tools/launcher/window.py @@ -1,7 +1,7 @@ import copy import logging -from Qt import QtWidgets, QtCore, QtGui +from qtpy import QtWidgets, QtCore, QtGui from openpype import style from openpype import resources diff --git a/openpype/tools/project_manager/project_manager/__init__.py b/openpype/tools/project_manager/project_manager/__init__.py index 6e44afd841..ac4e3d5f39 100644 --- a/openpype/tools/project_manager/project_manager/__init__.py +++ b/openpype/tools/project_manager/project_manager/__init__.py @@ -44,7 +44,7 @@ from .window import ProjectManagerWindow def main(): import sys - from Qt import QtWidgets + from qtpy import QtWidgets app = QtWidgets.QApplication([]) diff --git a/openpype/tools/project_manager/project_manager/constants.py b/openpype/tools/project_manager/project_manager/constants.py index 7ca4aa9492..72512d797b 100644 --- a/openpype/tools/project_manager/project_manager/constants.py +++ b/openpype/tools/project_manager/project_manager/constants.py @@ -1,5 +1,5 @@ import re -from Qt import QtCore +from qtpy import QtCore # Item identifier (unique ID - uuid4 is used) diff --git a/openpype/tools/project_manager/project_manager/delegates.py b/openpype/tools/project_manager/project_manager/delegates.py index b066bbb159..79e9554b0f 100644 --- a/openpype/tools/project_manager/project_manager/delegates.py +++ b/openpype/tools/project_manager/project_manager/delegates.py @@ -1,4 +1,4 @@ -from Qt import QtWidgets, QtCore +from qtpy import QtWidgets, QtCore from .widgets import ( NameTextEdit, diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 6f40140e5e..29a26f700f 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -5,7 +5,7 @@ from uuid import uuid4 from pymongo import UpdateOne, DeleteOne -from Qt import QtCore, QtGui +from qtpy import QtCore, QtGui from openpype.client import ( get_projects, diff --git a/openpype/tools/project_manager/project_manager/multiselection_combobox.py b/openpype/tools/project_manager/project_manager/multiselection_combobox.py index f776831298..f12f402d1a 100644 --- a/openpype/tools/project_manager/project_manager/multiselection_combobox.py +++ b/openpype/tools/project_manager/project_manager/multiselection_combobox.py @@ -1,4 +1,4 @@ -from Qt import QtCore, QtWidgets +from qtpy import QtCore, QtWidgets class ComboItemDelegate(QtWidgets.QStyledItemDelegate): diff --git a/openpype/tools/project_manager/project_manager/style.py b/openpype/tools/project_manager/project_manager/style.py index 4405d05960..6445bc341d 100644 --- a/openpype/tools/project_manager/project_manager/style.py +++ b/openpype/tools/project_manager/project_manager/style.py @@ -1,5 +1,5 @@ import os -from Qt import QtGui +from qtpy import QtGui import qtawesome from openpype.tools.utils import paint_image_with_color diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 8d1fe54e83..609db30a81 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -1,7 +1,7 @@ import collections from queue import Queue -from Qt import QtWidgets, QtCore, QtGui +from qtpy import QtWidgets, QtCore, QtGui from openpype.client import get_project from .delegates import ( diff --git a/openpype/tools/project_manager/project_manager/widgets.py b/openpype/tools/project_manager/project_manager/widgets.py index 4bc968347a..06ae06e4d2 100644 --- a/openpype/tools/project_manager/project_manager/widgets.py +++ b/openpype/tools/project_manager/project_manager/widgets.py @@ -16,7 +16,7 @@ from openpype.tools.utils import ( get_warning_pixmap ) -from Qt import QtWidgets, QtCore, QtGui +from qtpy import QtWidgets, QtCore, QtGui class NameTextEdit(QtWidgets.QLineEdit): diff --git a/openpype/tools/project_manager/project_manager/window.py b/openpype/tools/project_manager/project_manager/window.py index 3b2dea8ca3..e35922cf36 100644 --- a/openpype/tools/project_manager/project_manager/window.py +++ b/openpype/tools/project_manager/project_manager/window.py @@ -1,4 +1,4 @@ -from Qt import QtWidgets, QtCore, QtGui +from qtpy import QtWidgets, QtCore, QtGui from openpype import resources from openpype.style import load_stylesheet diff --git a/openpype/tools/publisher/constants.py b/openpype/tools/publisher/constants.py index 74337ea1ab..96f74a5a5c 100644 --- a/openpype/tools/publisher/constants.py +++ b/openpype/tools/publisher/constants.py @@ -35,5 +35,8 @@ __all__ = ( "SORT_VALUE_ROLE", "IS_GROUP_ROLE", "CREATOR_IDENTIFIER_ROLE", - "FAMILY_ROLE" + "CREATOR_THUMBNAIL_ENABLED_ROLE", + "FAMILY_ROLE", + "GROUP_ROLE", + "CONVERTER_IDENTIFIER_ROLE", ) diff --git a/openpype/tools/publisher/widgets/card_view_widgets.py b/openpype/tools/publisher/widgets/card_view_widgets.py index 09635d1a15..57336f9304 100644 --- a/openpype/tools/publisher/widgets/card_view_widgets.py +++ b/openpype/tools/publisher/widgets/card_view_widgets.py @@ -43,24 +43,14 @@ from ..constants import ( ) -class SelectionType: - def __init__(self, name): - self.name = name - - def __eq__(self, other): - if isinstance(other, SelectionType): - other = other.name - return self.name == other - - class SelectionTypes: - clear = SelectionType("clear") - extend = SelectionType("extend") - extend_to = SelectionType("extend_to") + clear = "clear" + extend = "extend" + extend_to = "extend_to" class BaseGroupWidget(QtWidgets.QWidget): - selected = QtCore.Signal(str, str, SelectionType) + selected = QtCore.Signal(str, str, str) removed_selected = QtCore.Signal() def __init__(self, group_name, parent): @@ -269,7 +259,7 @@ class InstanceGroupWidget(BaseGroupWidget): class CardWidget(BaseClickableFrame): """Clickable card used as bigger button.""" - selected = QtCore.Signal(str, str, SelectionType) + selected = QtCore.Signal(str, str, str) # Group identifier of card # - this must be set because if send when mouse is released with card id _group_identifier = None @@ -755,11 +745,11 @@ class InstanceCardView(AbstractInstanceView): group_widget = self._widgets_by_group[group_name] new_widget = group_widget.get_widget_by_item_id(instance_id) - if selection_type is SelectionTypes.clear: + if selection_type == SelectionTypes.clear: self._select_item_clear(instance_id, group_name, new_widget) - elif selection_type is SelectionTypes.extend: + elif selection_type == SelectionTypes.extend: self._select_item_extend(instance_id, group_name, new_widget) - elif selection_type is SelectionTypes.extend_to: + elif selection_type == SelectionTypes.extend_to: self._select_item_extend_to(instance_id, group_name, new_widget) self.selection_changed.emit() diff --git a/openpype/tools/settings/__init__.py b/openpype/tools/settings/__init__.py index 3e77a8348a..a5b1ea51a5 100644 --- a/openpype/tools/settings/__init__.py +++ b/openpype/tools/settings/__init__.py @@ -1,5 +1,5 @@ import sys -from Qt import QtWidgets, QtGui +from qtpy import QtWidgets, QtGui from openpype import style from .lib import ( @@ -24,7 +24,9 @@ def main(user_role=None): user_role, ", ".join(allowed_roles) )) - app = QtWidgets.QApplication(sys.argv) + app = QtWidgets.QApplication.instance() + if not app: + app = QtWidgets.QApplication(sys.argv) app.setWindowIcon(QtGui.QIcon(style.app_icon_path())) widget = MainWidget(user_role) diff --git a/openpype/tools/settings/local_settings/apps_widget.py b/openpype/tools/settings/local_settings/apps_widget.py index c1f350fcbc..ce1fc86c32 100644 --- a/openpype/tools/settings/local_settings/apps_widget.py +++ b/openpype/tools/settings/local_settings/apps_widget.py @@ -1,5 +1,5 @@ import platform -from Qt import QtWidgets +from qtpy import QtWidgets from .widgets import ( Separator, ExpandingWidget diff --git a/openpype/tools/settings/local_settings/environments_widget.py b/openpype/tools/settings/local_settings/environments_widget.py index 14ca517851..5008f086d2 100644 --- a/openpype/tools/settings/local_settings/environments_widget.py +++ b/openpype/tools/settings/local_settings/environments_widget.py @@ -1,4 +1,4 @@ -from Qt import QtWidgets +from qtpy import QtWidgets from openpype.tools.utils import PlaceholderLineEdit diff --git a/openpype/tools/settings/local_settings/experimental_widget.py b/openpype/tools/settings/local_settings/experimental_widget.py index 22ef952356..b0d9381663 100644 --- a/openpype/tools/settings/local_settings/experimental_widget.py +++ b/openpype/tools/settings/local_settings/experimental_widget.py @@ -1,4 +1,4 @@ -from Qt import QtWidgets +from qtpy import QtWidgets from openpype.tools.experimental_tools import ( ExperimentalTools, LOCAL_EXPERIMENTAL_KEY diff --git a/openpype/tools/settings/local_settings/general_widget.py b/openpype/tools/settings/local_settings/general_widget.py index 35add7573e..5a75c219dc 100644 --- a/openpype/tools/settings/local_settings/general_widget.py +++ b/openpype/tools/settings/local_settings/general_widget.py @@ -1,6 +1,6 @@ import getpass -from Qt import QtWidgets, QtCore +from qtpy import QtWidgets, QtCore from openpype.lib import is_admin_password_required from openpype.widgets import PasswordDialog from openpype.tools.utils import PlaceholderLineEdit diff --git a/openpype/tools/settings/local_settings/mongo_widget.py b/openpype/tools/settings/local_settings/mongo_widget.py index 600ab79242..9549d6eb17 100644 --- a/openpype/tools/settings/local_settings/mongo_widget.py +++ b/openpype/tools/settings/local_settings/mongo_widget.py @@ -2,7 +2,7 @@ import os import sys import traceback -from Qt import QtWidgets +from qtpy import QtWidgets from pymongo.errors import ServerSelectionTimeoutError from openpype.lib import change_openpype_mongo_url diff --git a/openpype/tools/settings/local_settings/projects_widget.py b/openpype/tools/settings/local_settings/projects_widget.py index 30a0d212f0..b330d54dec 100644 --- a/openpype/tools/settings/local_settings/projects_widget.py +++ b/openpype/tools/settings/local_settings/projects_widget.py @@ -1,6 +1,6 @@ import platform import copy -from Qt import QtWidgets, QtCore, QtGui +from qtpy import QtWidgets, QtCore, QtGui from openpype.tools.settings.settings import ProjectListWidget from openpype.tools.utils import PlaceholderLineEdit from openpype.settings.constants import ( diff --git a/openpype/tools/settings/local_settings/widgets.py b/openpype/tools/settings/local_settings/widgets.py index 2733aef187..f40978a66f 100644 --- a/openpype/tools/settings/local_settings/widgets.py +++ b/openpype/tools/settings/local_settings/widgets.py @@ -1,4 +1,4 @@ -from Qt import QtWidgets, QtCore +from qtpy import QtWidgets, QtCore from openpype.tools.settings.settings.widgets import ( ExpandingWidget ) diff --git a/openpype/tools/settings/local_settings/window.py b/openpype/tools/settings/local_settings/window.py index 76c2d851e9..fdb05e219f 100644 --- a/openpype/tools/settings/local_settings/window.py +++ b/openpype/tools/settings/local_settings/window.py @@ -1,4 +1,4 @@ -from Qt import QtWidgets, QtGui +from qtpy import QtWidgets, QtGui from openpype import style diff --git a/openpype/tools/settings/settings/base.py b/openpype/tools/settings/settings/base.py index 6def284a83..074ecdae90 100644 --- a/openpype/tools/settings/settings/base.py +++ b/openpype/tools/settings/settings/base.py @@ -5,7 +5,7 @@ import traceback import functools import datetime -from Qt import QtWidgets, QtGui, QtCore +from qtpy import QtWidgets, QtGui, QtCore from openpype.settings.entities import ProjectSettings from openpype.tools.settings import CHILD_OFFSET diff --git a/openpype/tools/settings/settings/breadcrumbs_widget.py b/openpype/tools/settings/settings/breadcrumbs_widget.py index 7524bc61f0..2676d2f52d 100644 --- a/openpype/tools/settings/settings/breadcrumbs_widget.py +++ b/openpype/tools/settings/settings/breadcrumbs_widget.py @@ -1,4 +1,4 @@ -from Qt import QtWidgets, QtGui, QtCore +from qtpy import QtWidgets, QtGui, QtCore PREFIX_ROLE = QtCore.Qt.UserRole + 1 LAST_SEGMENT_ROLE = QtCore.Qt.UserRole + 2 diff --git a/openpype/tools/settings/settings/categories.py b/openpype/tools/settings/settings/categories.py index e1b3943317..2e5ce496ed 100644 --- a/openpype/tools/settings/settings/categories.py +++ b/openpype/tools/settings/settings/categories.py @@ -2,7 +2,7 @@ import sys import traceback import contextlib from enum import Enum -from Qt import QtWidgets, QtCore +from qtpy import QtWidgets, QtCore import qtawesome from openpype.lib import get_openpype_version diff --git a/openpype/tools/settings/settings/color_widget.py b/openpype/tools/settings/settings/color_widget.py index b38b46f3cb..819bfb3581 100644 --- a/openpype/tools/settings/settings/color_widget.py +++ b/openpype/tools/settings/settings/color_widget.py @@ -1,4 +1,4 @@ -from Qt import QtWidgets, QtCore, QtGui +from qtpy import QtWidgets, QtCore, QtGui from .item_widgets import InputWidget diff --git a/openpype/tools/settings/settings/constants.py b/openpype/tools/settings/settings/constants.py index 23526e4de9..b2792d885b 100644 --- a/openpype/tools/settings/settings/constants.py +++ b/openpype/tools/settings/settings/constants.py @@ -1,4 +1,4 @@ -from Qt import QtCore +from qtpy import QtCore DEFAULT_PROJECT_LABEL = "< Default >" diff --git a/openpype/tools/settings/settings/dialogs.py b/openpype/tools/settings/settings/dialogs.py index b1b4daa1a0..38da3cf881 100644 --- a/openpype/tools/settings/settings/dialogs.py +++ b/openpype/tools/settings/settings/dialogs.py @@ -1,4 +1,4 @@ -from Qt import QtWidgets, QtCore +from qtpy import QtWidgets, QtCore from openpype.tools.utils.delegates import pretty_date diff --git a/openpype/tools/settings/settings/dict_conditional.py b/openpype/tools/settings/settings/dict_conditional.py index b2a7bb52a2..564603b258 100644 --- a/openpype/tools/settings/settings/dict_conditional.py +++ b/openpype/tools/settings/settings/dict_conditional.py @@ -1,4 +1,4 @@ -from Qt import QtWidgets +from qtpy import QtWidgets from .widgets import ( ExpandingWidget, diff --git a/openpype/tools/settings/settings/dict_mutable_widget.py b/openpype/tools/settings/settings/dict_mutable_widget.py index 1c704b3cd5..27c9392320 100644 --- a/openpype/tools/settings/settings/dict_mutable_widget.py +++ b/openpype/tools/settings/settings/dict_mutable_widget.py @@ -1,6 +1,6 @@ from uuid import uuid4 -from Qt import QtWidgets, QtCore, QtGui +from qtpy import QtWidgets, QtCore from .base import BaseWidget from .lib import ( diff --git a/openpype/tools/settings/settings/images/__init__.py b/openpype/tools/settings/settings/images/__init__.py index 3ad65e114a..0b246349a8 100644 --- a/openpype/tools/settings/settings/images/__init__.py +++ b/openpype/tools/settings/settings/images/__init__.py @@ -1,5 +1,5 @@ import os -from Qt import QtGui +from qtpy import QtGui def get_image_path(image_filename): diff --git a/openpype/tools/settings/settings/item_widgets.py b/openpype/tools/settings/settings/item_widgets.py index 1ddee7efbe..d51f9b9684 100644 --- a/openpype/tools/settings/settings/item_widgets.py +++ b/openpype/tools/settings/settings/item_widgets.py @@ -1,6 +1,6 @@ import json -from Qt import QtWidgets, QtCore, QtGui +from qtpy import QtWidgets, QtCore, QtGui from openpype.widgets.sliders import NiceSlider from openpype.tools.settings import CHILD_OFFSET diff --git a/openpype/tools/settings/settings/lib.py b/openpype/tools/settings/settings/lib.py index eef157812f..0bcf8a4e94 100644 --- a/openpype/tools/settings/settings/lib.py +++ b/openpype/tools/settings/settings/lib.py @@ -1,4 +1,4 @@ -from Qt import QtCore +from qtpy import QtCore from .widgets import SettingsToolBtn diff --git a/openpype/tools/settings/settings/list_item_widget.py b/openpype/tools/settings/settings/list_item_widget.py index cd1fd912ae..67ce4b9dc9 100644 --- a/openpype/tools/settings/settings/list_item_widget.py +++ b/openpype/tools/settings/settings/list_item_widget.py @@ -1,4 +1,4 @@ -from Qt import QtWidgets, QtCore +from qtpy import QtWidgets, QtCore from openpype.tools.settings import ( CHILD_OFFSET diff --git a/openpype/tools/settings/settings/list_strict_widget.py b/openpype/tools/settings/settings/list_strict_widget.py index f0a3022a50..b0b78e5732 100644 --- a/openpype/tools/settings/settings/list_strict_widget.py +++ b/openpype/tools/settings/settings/list_strict_widget.py @@ -1,4 +1,4 @@ -from Qt import QtWidgets, QtCore +from qtpy import QtWidgets, QtCore from .widgets import ( GridLabelWidget, diff --git a/openpype/tools/settings/settings/multiselection_combobox.py b/openpype/tools/settings/settings/multiselection_combobox.py index c2cc2a8fee..4cc81ff56e 100644 --- a/openpype/tools/settings/settings/multiselection_combobox.py +++ b/openpype/tools/settings/settings/multiselection_combobox.py @@ -1,4 +1,4 @@ -from Qt import QtCore, QtGui, QtWidgets +from qtpy import QtCore, QtGui, QtWidgets class ComboItemDelegate(QtWidgets.QStyledItemDelegate): diff --git a/openpype/tools/settings/settings/search_dialog.py b/openpype/tools/settings/settings/search_dialog.py index e6538cfe67..2860e7c943 100644 --- a/openpype/tools/settings/settings/search_dialog.py +++ b/openpype/tools/settings/settings/search_dialog.py @@ -1,7 +1,7 @@ import re import collections -from Qt import QtCore, QtWidgets, QtGui +from qtpy import QtCore, QtWidgets, QtGui ENTITY_LABEL_ROLE = QtCore.Qt.UserRole + 1 ENTITY_PATH_ROLE = QtCore.Qt.UserRole + 2 diff --git a/openpype/tools/settings/settings/tests.py b/openpype/tools/settings/settings/tests.py index fc53e38ad5..772d4618f7 100644 --- a/openpype/tools/settings/settings/tests.py +++ b/openpype/tools/settings/settings/tests.py @@ -1,4 +1,4 @@ -from Qt import QtWidgets, QtCore +from qtpy import QtWidgets, QtCore def indented_print(data, indent=0): diff --git a/openpype/tools/settings/settings/widgets.py b/openpype/tools/settings/settings/widgets.py index b8ad21e7e4..fd04cb0a23 100644 --- a/openpype/tools/settings/settings/widgets.py +++ b/openpype/tools/settings/settings/widgets.py @@ -1,6 +1,6 @@ import copy import uuid -from Qt import QtWidgets, QtCore, QtGui +from qtpy import QtWidgets, QtCore, QtGui import qtawesome from openpype.client import get_projects diff --git a/openpype/tools/settings/settings/window.py b/openpype/tools/settings/settings/window.py index 77a2f64dac..f479908f7b 100644 --- a/openpype/tools/settings/settings/window.py +++ b/openpype/tools/settings/settings/window.py @@ -1,4 +1,4 @@ -from Qt import QtWidgets, QtGui, QtCore +from qtpy import QtWidgets, QtGui, QtCore from openpype import style diff --git a/openpype/tools/settings/settings/wrapper_widgets.py b/openpype/tools/settings/settings/wrapper_widgets.py index b14a226912..0b45a9a01b 100644 --- a/openpype/tools/settings/settings/wrapper_widgets.py +++ b/openpype/tools/settings/settings/wrapper_widgets.py @@ -1,5 +1,5 @@ from uuid import uuid4 -from Qt import QtWidgets +from qtpy import QtWidgets from .widgets import ( ExpandingWidget, diff --git a/openpype/tools/standalonepublish/app.py b/openpype/tools/standalonepublish/app.py index c93c33b2a5..d71c205c3b 100644 --- a/openpype/tools/standalonepublish/app.py +++ b/openpype/tools/standalonepublish/app.py @@ -4,7 +4,7 @@ import ctypes import signal from bson.objectid import ObjectId -from Qt import QtWidgets, QtCore, QtGui +from qtpy import QtWidgets, QtCore, QtGui from openpype.client import get_asset_by_id diff --git a/openpype/tools/standalonepublish/widgets/__init__.py b/openpype/tools/standalonepublish/widgets/__init__.py index e61897f807..d79654498d 100644 --- a/openpype/tools/standalonepublish/widgets/__init__.py +++ b/openpype/tools/standalonepublish/widgets/__init__.py @@ -1,4 +1,4 @@ -from Qt import QtCore +from qtpy import QtCore HelpRole = QtCore.Qt.UserRole + 2 FamilyRole = QtCore.Qt.UserRole + 3 diff --git a/openpype/tools/standalonepublish/widgets/model_asset.py b/openpype/tools/standalonepublish/widgets/model_asset.py index 9fed46b3fe..2f67036e78 100644 --- a/openpype/tools/standalonepublish/widgets/model_asset.py +++ b/openpype/tools/standalonepublish/widgets/model_asset.py @@ -1,7 +1,7 @@ import logging import collections -from Qt import QtCore, QtGui +from qtpy import QtCore, QtGui import qtawesome from openpype.client import get_assets diff --git a/openpype/tools/standalonepublish/widgets/model_filter_proxy_exact_match.py b/openpype/tools/standalonepublish/widgets/model_filter_proxy_exact_match.py index 604ae30934..df9c6fb35f 100644 --- a/openpype/tools/standalonepublish/widgets/model_filter_proxy_exact_match.py +++ b/openpype/tools/standalonepublish/widgets/model_filter_proxy_exact_match.py @@ -1,4 +1,4 @@ -from Qt import QtCore +from qtpy import QtCore class ExactMatchesFilterProxyModel(QtCore.QSortFilterProxyModel): diff --git a/openpype/tools/standalonepublish/widgets/model_filter_proxy_recursive_sort.py b/openpype/tools/standalonepublish/widgets/model_filter_proxy_recursive_sort.py index 71ecdf41dc..727d3a97d7 100644 --- a/openpype/tools/standalonepublish/widgets/model_filter_proxy_recursive_sort.py +++ b/openpype/tools/standalonepublish/widgets/model_filter_proxy_recursive_sort.py @@ -1,5 +1,5 @@ -from Qt import QtCore import re +from qtpy import QtCore class RecursiveSortFilterProxyModel(QtCore.QSortFilterProxyModel): diff --git a/openpype/tools/standalonepublish/widgets/model_tasks_template.py b/openpype/tools/standalonepublish/widgets/model_tasks_template.py index 648f7ed479..e22a4e3bf8 100644 --- a/openpype/tools/standalonepublish/widgets/model_tasks_template.py +++ b/openpype/tools/standalonepublish/widgets/model_tasks_template.py @@ -1,4 +1,4 @@ -from Qt import QtCore +from qtpy import QtCore import qtawesome from openpype.style import get_default_entity_icon_color diff --git a/openpype/tools/standalonepublish/widgets/model_tree.py b/openpype/tools/standalonepublish/widgets/model_tree.py index efac0d6b78..040e95d944 100644 --- a/openpype/tools/standalonepublish/widgets/model_tree.py +++ b/openpype/tools/standalonepublish/widgets/model_tree.py @@ -1,4 +1,4 @@ -from Qt import QtCore +from qtpy import QtCore from . import Node diff --git a/openpype/tools/standalonepublish/widgets/model_tree_view_deselectable.py b/openpype/tools/standalonepublish/widgets/model_tree_view_deselectable.py index 6a15916981..3c8c760eca 100644 --- a/openpype/tools/standalonepublish/widgets/model_tree_view_deselectable.py +++ b/openpype/tools/standalonepublish/widgets/model_tree_view_deselectable.py @@ -1,4 +1,4 @@ -from Qt import QtWidgets, QtCore +from qtpy import QtWidgets, QtCore class DeselectableTreeView(QtWidgets.QTreeView): diff --git a/openpype/tools/standalonepublish/widgets/widget_asset.py b/openpype/tools/standalonepublish/widgets/widget_asset.py index 77d756a606..01f49b79ec 100644 --- a/openpype/tools/standalonepublish/widgets/widget_asset.py +++ b/openpype/tools/standalonepublish/widgets/widget_asset.py @@ -1,5 +1,5 @@ import contextlib -from Qt import QtWidgets, QtCore +from qtpy import QtWidgets, QtCore import qtawesome from openpype.client import ( diff --git a/openpype/tools/standalonepublish/widgets/widget_component_item.py b/openpype/tools/standalonepublish/widgets/widget_component_item.py index de3cde50cd..523c3977e3 100644 --- a/openpype/tools/standalonepublish/widgets/widget_component_item.py +++ b/openpype/tools/standalonepublish/widgets/widget_component_item.py @@ -1,5 +1,5 @@ import os -from Qt import QtCore, QtGui, QtWidgets +from qtpy import QtCore, QtGui, QtWidgets from .resources import get_resource diff --git a/openpype/tools/standalonepublish/widgets/widget_components.py b/openpype/tools/standalonepublish/widgets/widget_components.py index 237e1da583..a86ac845f2 100644 --- a/openpype/tools/standalonepublish/widgets/widget_components.py +++ b/openpype/tools/standalonepublish/widgets/widget_components.py @@ -4,7 +4,7 @@ import tempfile import random import string -from Qt import QtWidgets, QtCore +from qtpy import QtWidgets, QtCore from openpype.pipeline import legacy_io from openpype.lib import ( diff --git a/openpype/tools/standalonepublish/widgets/widget_components_list.py b/openpype/tools/standalonepublish/widgets/widget_components_list.py index 0ee90ae4de..e29ab3c127 100644 --- a/openpype/tools/standalonepublish/widgets/widget_components_list.py +++ b/openpype/tools/standalonepublish/widgets/widget_components_list.py @@ -1,4 +1,4 @@ -from Qt import QtWidgets +from qtpy import QtWidgets class ComponentsList(QtWidgets.QTableWidget): diff --git a/openpype/tools/standalonepublish/widgets/widget_drop_empty.py b/openpype/tools/standalonepublish/widgets/widget_drop_empty.py index a890f38426..110e4d6353 100644 --- a/openpype/tools/standalonepublish/widgets/widget_drop_empty.py +++ b/openpype/tools/standalonepublish/widgets/widget_drop_empty.py @@ -1,4 +1,4 @@ -from Qt import QtWidgets, QtCore, QtGui +from qtpy import QtWidgets, QtCore, QtGui class DropEmpty(QtWidgets.QWidget): diff --git a/openpype/tools/standalonepublish/widgets/widget_drop_frame.py b/openpype/tools/standalonepublish/widgets/widget_drop_frame.py index 18c2b27678..f46e31786c 100644 --- a/openpype/tools/standalonepublish/widgets/widget_drop_frame.py +++ b/openpype/tools/standalonepublish/widgets/widget_drop_frame.py @@ -4,7 +4,7 @@ import json import clique import subprocess import openpype.lib -from Qt import QtWidgets, QtCore +from qtpy import QtWidgets, QtCore from . import DropEmpty, ComponentsList, ComponentItem diff --git a/openpype/tools/standalonepublish/widgets/widget_family.py b/openpype/tools/standalonepublish/widgets/widget_family.py index eab66d75b3..11c5ec33b7 100644 --- a/openpype/tools/standalonepublish/widgets/widget_family.py +++ b/openpype/tools/standalonepublish/widgets/widget_family.py @@ -1,6 +1,6 @@ import re -from Qt import QtWidgets, QtCore +from qtpy import QtWidgets, QtCore from openpype.client import ( get_asset_by_name, @@ -186,19 +186,11 @@ class FamilyWidget(QtWidgets.QWidget): if item is None: return - asset_doc = None - if asset_name != self.NOT_SELECTED: - # Get the assets from the database which match with the name - project_name = self.dbcon.active_project() - asset_doc = get_asset_by_name( - project_name, asset_name, fields=["_id"] - ) - - # Get plugin and family - plugin = item.data(PluginRole) - # Early exit if no asset name - if not asset_name.strip(): + if ( + asset_name == self.NOT_SELECTED + or not asset_name.strip() + ): self._build_menu([]) item.setData(ExistsRole, False) print("Asset name is required ..") @@ -210,8 +202,10 @@ class FamilyWidget(QtWidgets.QWidget): asset_doc = get_asset_by_name( project_name, asset_name, fields=["_id"] ) + # Get plugin plugin = item.data(PluginRole) + if asset_doc and plugin: asset_id = asset_doc["_id"] task_name = self.dbcon.Session["AVALON_TASK"] diff --git a/openpype/tools/standalonepublish/widgets/widget_family_desc.py b/openpype/tools/standalonepublish/widgets/widget_family_desc.py index 2095b332bd..33174a852b 100644 --- a/openpype/tools/standalonepublish/widgets/widget_family_desc.py +++ b/openpype/tools/standalonepublish/widgets/widget_family_desc.py @@ -1,5 +1,5 @@ import six -from Qt import QtWidgets, QtCore, QtGui +from qtpy import QtWidgets, QtCore, QtGui import qtawesome from . import FamilyRole, PluginRole diff --git a/openpype/tools/standalonepublish/widgets/widget_shadow.py b/openpype/tools/standalonepublish/widgets/widget_shadow.py index de5fdf6be0..64cb9544fa 100644 --- a/openpype/tools/standalonepublish/widgets/widget_shadow.py +++ b/openpype/tools/standalonepublish/widgets/widget_shadow.py @@ -1,4 +1,4 @@ -from Qt import QtWidgets, QtCore, QtGui +from qtpy import QtWidgets, QtCore, QtGui class ShadowWidget(QtWidgets.QWidget): diff --git a/openpype/tools/stdout_broker/window.py b/openpype/tools/stdout_broker/window.py index f5720ca05b..5825da73e2 100644 --- a/openpype/tools/stdout_broker/window.py +++ b/openpype/tools/stdout_broker/window.py @@ -1,7 +1,7 @@ import re import collections -from Qt import QtWidgets +from qtpy import QtWidgets from openpype import style diff --git a/openpype/tools/tray/pype_info_widget.py b/openpype/tools/tray/pype_info_widget.py index 232d2024ac..c616ad4dba 100644 --- a/openpype/tools/tray/pype_info_widget.py +++ b/openpype/tools/tray/pype_info_widget.py @@ -2,7 +2,7 @@ import os import json import collections -from Qt import QtCore, QtGui, QtWidgets +from qtpy import QtCore, QtGui, QtWidgets from openpype import style from openpype import resources diff --git a/openpype/tools/tray/pype_tray.py b/openpype/tools/tray/pype_tray.py index fbae4a5eed..2f3b5251f9 100644 --- a/openpype/tools/tray/pype_tray.py +++ b/openpype/tools/tray/pype_tray.py @@ -5,7 +5,7 @@ import atexit import platform -from Qt import QtCore, QtGui, QtWidgets +from qtpy import QtCore, QtGui, QtWidgets import openpype.version from openpype import resources, style diff --git a/openpype/tools/traypublisher/window.py b/openpype/tools/traypublisher/window.py index dfe06d149d..3007fa66a5 100644 --- a/openpype/tools/traypublisher/window.py +++ b/openpype/tools/traypublisher/window.py @@ -8,7 +8,7 @@ publishing plugins. import platform -from Qt import QtWidgets, QtCore +from qtpy import QtWidgets, QtCore import qtawesome import appdirs diff --git a/openpype/version.py b/openpype/version.py index 443c76544b..8d82df563d 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.14.9-nightly.2" +__version__ = "3.14.9-nightly.4" diff --git a/website/docs/assets/deadline_job_version.png b/website/docs/assets/deadline_job_version.png new file mode 100644 index 0000000000..0b78d6a35c Binary files /dev/null and b/website/docs/assets/deadline_job_version.png differ diff --git a/website/docs/dev_deadline.md b/website/docs/dev_deadline.md new file mode 100644 index 0000000000..310b2e0983 --- /dev/null +++ b/website/docs/dev_deadline.md @@ -0,0 +1,38 @@ +--- +id: dev_deadline +title: Deadline integration +sidebar_label: Deadline integration +toc_max_heading_level: 4 +--- + +Deadline is not host as usual, it is missing most of the host features, but it does have +its own set of publishing plugins. + +## How to test OpenPype on Deadline + +### Versions + +Since 3.14 job submitted from OpenPype is bound to OpenPype version used to submit it. So +if you submit job with 3.14.8, Deadline will try to find that particular version and use it +for rendering. This is handled by `OPENPYPE_VERSION` variable on job - you can delete it from +there and then the version set in studio Settings will be used. + +![Deadline Job Version](assets/deadline_job_version.png) + +Deadline needs to bootstrap this version so it will try to look the closest compatible +build. So to use version 3.14.8 on Deadline it is enough to have build 3.14.0 or similar - important +are the first two version numbers - major and minor. If they match, the version +is considered compatible. + +### Testing + +So to test various changes you don't need to build again an again OpenPype and putting +it to directory where Deadline is looking for versions - this needs to be done only on +minor version change. That build will then be used to bootstrap whatever is set on the +job or in the studio Settings. + +So you can either use zip version if it suits you, or better set your sources directory +so it will be find as a version - for example with symlink. + +That way you can only modify `OPENPYPE_VERSION` variable on job to point it to version +you would like to test. \ No newline at end of file diff --git a/website/docs/module_kitsu.md b/website/docs/module_kitsu.md index ec38cce5e1..73e31a280b 100644 --- a/website/docs/module_kitsu.md +++ b/website/docs/module_kitsu.md @@ -26,6 +26,8 @@ openpype_console module kitsu sync-service -l me@domain.ext -p my_password ### Events listening Listening to Kitsu events is the key to automation of many tasks like _project/episode/sequence/shot/asset/task create/update/delete_ and some more. Events listening should run at all times to perform the required processing as it is not possible to catch some of them retrospectively with strong reliability. If such timeout has been encountered, you must relaunch the `sync-service` command to run the synchronization step again. +Connection token is refreshed every week. + ### Push to Kitsu An utility function is provided to help update Kitsu data (a.k.a Zou database) with OpenPype data if the publishing to the production tracker hasn't been possible for some time. Running `push-to-zou` will create the data on behalf of the user. :::caution diff --git a/website/sidebars.js b/website/sidebars.js index af64282d61..f2d9ffee06 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -157,6 +157,7 @@ module.exports = { "dev_host_implementation", "dev_publishing" ] - } + }, + "dev_deadline" ] };