diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml
index 258458e2d4..d9b4d8089c 100644
--- a/.github/workflows/prerelease.yml
+++ b/.github/workflows/prerelease.yml
@@ -43,7 +43,7 @@ jobs:
uses: heinrichreimer/github-changelog-generator-action@v2.2
with:
token: ${{ secrets.ADMIN_TOKEN }}
- addSections: '{"documentation":{"prefix":"### 📖 Documentation","labels":["type: documentation"]},"tests":{"prefix":"### ✅ Testing","labels":["tests"]},"feature":{"prefix":"**🆕 New features**", "labels":["type: feature"]},"breaking":{"prefix":"**💥 Breaking**", "labels":["breaking"]},"enhancements":{"prefix":"**🚀 Enhancements**", "labels":["type: enhancement"]},"bugs":{"prefix":"**🐛 Bug fixes**", "labels":["type: bug"]},"deprecated":{"prefix":"**⚠️ Deprecations**", "labels":["depreciated"]}}'
+ addSections: '{"documentation":{"prefix":"### 📖 Documentation","labels":["type: documentation"]},"tests":{"prefix":"### ✅ Testing","labels":["tests"]},"feature":{"prefix":"**🆕 New features**", "labels":["type: feature"]},"breaking":{"prefix":"**💥 Breaking**", "labels":["breaking"]},"enhancements":{"prefix":"**🚀 Enhancements**", "labels":["type: enhancement"]},"bugs":{"prefix":"**🐛 Bug fixes**", "labels":["type: bug"]},"deprecated":{"prefix":"**⚠️ Deprecations**", "labels":["depreciated"]}, "refactor":{"prefix":"**🔀 Refactored code**", "labels":["refactor"]}}'
issues: false
issuesWoLabels: false
sinceTag: "3.0.0"
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 3f85525c26..917e6c884c 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -39,7 +39,7 @@ jobs:
uses: heinrichreimer/github-changelog-generator-action@v2.2
with:
token: ${{ secrets.ADMIN_TOKEN }}
- addSections: '{"tests":{"prefix":"### ✅ Testing","labels":["tests"]},"feature":{"prefix":"**🆕 New features**", "labels":["type: feature"]},"breaking":{"prefix":"**💥 Breaking**", "labels":["breaking"]},"enhancements":{"prefix":"**🚀 Enhancements**", "labels":["type: enhancement"]},"bugs":{"prefix":"**🐛 Bug fixes**", "labels":["type: bug"]},"deprecated":{"prefix":"**⚠️ Deprecations**", "labels":["depreciated"]},"documentation":{"prefix":"### 📖 Documentation","labels":["type: documentation"]}}'
+ addSections: '{"documentation":{"prefix":"### 📖 Documentation","labels":["type: documentation"]},"tests":{"prefix":"### ✅ Testing","labels":["tests"]},"feature":{"prefix":"**🆕 New features**", "labels":["type: feature"]},"breaking":{"prefix":"**💥 Breaking**", "labels":["breaking"]},"enhancements":{"prefix":"**🚀 Enhancements**", "labels":["type: enhancement"]},"bugs":{"prefix":"**🐛 Bug fixes**", "labels":["type: bug"]},"deprecated":{"prefix":"**⚠️ Deprecations**", "labels":["depreciated"]}, "refactor":{"prefix":"**🔀 Refactored code**", "labels":["refactor"]}}'
issues: false
issuesWoLabels: false
sinceTag: "3.0.0"
@@ -81,7 +81,7 @@ jobs:
uses: heinrichreimer/github-changelog-generator-action@v2.2
with:
token: ${{ secrets.ADMIN_TOKEN }}
- addSections: '{"documentation":{"prefix":"### 📖 Documentation","labels":["type: documentation"]},"tests":{"prefix":"### ✅ Testing","labels":["tests"]},"feature":{"prefix":"**🆕 New features**", "labels":["type: feature"]},"breaking":{"prefix":"**💥 Breaking**", "labels":["breaking"]},"enhancements":{"prefix":"**🚀 Enhancements**", "labels":["type: enhancement"]},"bugs":{"prefix":"**🐛 Bug fixes**", "labels":["type: bug"]},"deprecated":{"prefix":"**⚠️ Deprecations**", "labels":["depreciated"]}}'
+ addSections: '{"documentation":{"prefix":"### 📖 Documentation","labels":["type: documentation"]},"tests":{"prefix":"### ✅ Testing","labels":["tests"]},"feature":{"prefix":"**🆕 New features**", "labels":["type: feature"]},"breaking":{"prefix":"**💥 Breaking**", "labels":["breaking"]},"enhancements":{"prefix":"**🚀 Enhancements**", "labels":["type: enhancement"]},"bugs":{"prefix":"**🐛 Bug fixes**", "labels":["type: bug"]},"deprecated":{"prefix":"**⚠️ Deprecations**", "labels":["depreciated"]}, "refactor":{"prefix":"**🔀 Refactored code**", "labels":["refactor"]}}'
issues: false
issuesWoLabels: false
sinceTag: ${{ steps.version.outputs.last_release }}
diff --git a/openpype/__init__.py b/openpype/__init__.py
index 11b563ebfe..942112835b 100644
--- a/openpype/__init__.py
+++ b/openpype/__init__.py
@@ -5,6 +5,7 @@ import platform
import functools
import logging
+from openpype.pipeline import LegacyCreator
from .settings import get_project_settings
from .lib import (
Anatomy,
@@ -58,10 +59,15 @@ def patched_discover(superclass):
"""
# run original discover and get plugins
plugins = _original_discover(superclass)
+ filtered_plugins = [
+ plugin
+ for plugin in plugins
+ if issubclass(plugin, superclass)
+ ]
- set_plugin_attributes_from_settings(plugins, superclass)
+ set_plugin_attributes_from_settings(filtered_plugins, superclass)
- return plugins
+ return filtered_plugins
@import_wrapper
@@ -113,7 +119,7 @@ def install():
pyblish.register_plugin_path(path)
avalon.register_plugin_path(avalon.Loader, path)
- avalon.register_plugin_path(avalon.Creator, path)
+ avalon.register_plugin_path(LegacyCreator, path)
avalon.register_plugin_path(avalon.InventoryAction, path)
# apply monkey patched discover to original one
diff --git a/openpype/api.py b/openpype/api.py
index 51854492ab..b692b36065 100644
--- a/openpype/api.py
+++ b/openpype/api.py
@@ -45,9 +45,6 @@ from .lib.avalon_context import (
from . import resources
from .plugin import (
- PypeCreatorMixin,
- Creator,
-
Extractor,
ValidatePipelineOrder,
@@ -89,9 +86,6 @@ __all__ = [
# Resources
"resources",
- # Pype creator mixin
- "PypeCreatorMixin",
- "Creator",
# plugin classes
"Extractor",
# ordering
diff --git a/openpype/hosts/blender/api/pipeline.py b/openpype/hosts/blender/api/pipeline.py
index 6da0ba3dcb..1c9820ff22 100644
--- a/openpype/hosts/blender/api/pipeline.py
+++ b/openpype/hosts/blender/api/pipeline.py
@@ -14,6 +14,7 @@ import avalon.api
from avalon import io, schema
from avalon.pipeline import AVALON_CONTAINER_ID
+from openpype.pipeline import LegacyCreator
from openpype.api import Logger
import openpype.hosts.blender
@@ -46,7 +47,7 @@ def install():
pyblish.api.register_plugin_path(str(PUBLISH_PATH))
avalon.api.register_plugin_path(avalon.api.Loader, str(LOAD_PATH))
- avalon.api.register_plugin_path(avalon.api.Creator, str(CREATE_PATH))
+ avalon.api.register_plugin_path(LegacyCreator, str(CREATE_PATH))
lib.append_user_scripts()
@@ -67,7 +68,7 @@ def uninstall():
pyblish.api.deregister_plugin_path(str(PUBLISH_PATH))
avalon.api.deregister_plugin_path(avalon.api.Loader, str(LOAD_PATH))
- avalon.api.deregister_plugin_path(avalon.api.Creator, str(CREATE_PATH))
+ avalon.api.deregister_plugin_path(LegacyCreator, str(CREATE_PATH))
if not IS_HEADLESS:
ops.unregister()
diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py
index 8c9ab9a27f..20d1e4c8db 100644
--- a/openpype/hosts/blender/api/plugin.py
+++ b/openpype/hosts/blender/api/plugin.py
@@ -6,7 +6,7 @@ from typing import Dict, List, Optional
import bpy
import avalon.api
-from openpype.api import PypeCreatorMixin
+from openpype.pipeline import LegacyCreator
from .pipeline import AVALON_CONTAINERS
from .ops import (
MainThreadItem,
@@ -129,7 +129,7 @@ def deselect_all():
bpy.context.view_layer.objects.active = active
-class Creator(PypeCreatorMixin, avalon.api.Creator):
+class Creator(LegacyCreator):
"""Base class for Creator plug-ins."""
defaults = ['Main']
diff --git a/openpype/hosts/blender/plugins/load/load_layout_blend.py b/openpype/hosts/blender/plugins/load/load_layout_blend.py
index 8029c38b4a..7f8ae610c6 100644
--- a/openpype/hosts/blender/plugins/load/load_layout_blend.py
+++ b/openpype/hosts/blender/plugins/load/load_layout_blend.py
@@ -8,6 +8,7 @@ import bpy
from avalon import api
from openpype import lib
+from openpype.pipeline import legacy_create
from openpype.hosts.blender.api import plugin
from openpype.hosts.blender.api.pipeline import (
AVALON_CONTAINERS,
@@ -159,7 +160,7 @@ class BlendLayoutLoader(plugin.AssetLoader):
raise ValueError("Creator plugin \"CreateAnimation\" was "
"not found.")
- api.create(
+ legacy_create(
creator_plugin,
name=local_obj.name.split(':')[-1] + "_animation",
asset=asset,
diff --git a/openpype/hosts/blender/plugins/load/load_layout_json.py b/openpype/hosts/blender/plugins/load/load_layout_json.py
index 0a5bdeecaa..5b5f9ab83d 100644
--- a/openpype/hosts/blender/plugins/load/load_layout_json.py
+++ b/openpype/hosts/blender/plugins/load/load_layout_json.py
@@ -8,7 +8,6 @@ from typing import Dict, Optional
import bpy
from avalon import api
-from openpype import lib
from openpype.hosts.blender.api.pipeline import (
AVALON_INSTANCES,
AVALON_CONTAINERS,
@@ -118,7 +117,7 @@ class JsonLayoutLoader(plugin.AssetLoader):
# raise ValueError("Creator plugin \"CreateCamera\" was "
# "not found.")
- # api.create(
+ # legacy_create(
# creator_plugin,
# name="camera",
# # name=f"{unique_number}_{subset}_animation",
diff --git a/openpype/hosts/blender/plugins/load/load_rig.py b/openpype/hosts/blender/plugins/load/load_rig.py
index eb6d273a51..eacabd3447 100644
--- a/openpype/hosts/blender/plugins/load/load_rig.py
+++ b/openpype/hosts/blender/plugins/load/load_rig.py
@@ -9,6 +9,7 @@ import bpy
from avalon import api
from avalon.blender import lib as avalon_lib
from openpype import lib
+from openpype.pipeline import legacy_create
from openpype.hosts.blender.api import plugin
from openpype.hosts.blender.api.pipeline import (
AVALON_CONTAINERS,
@@ -248,7 +249,7 @@ class BlendRigLoader(plugin.AssetLoader):
animation_asset = options.get('animation_asset')
- api.create(
+ legacy_create(
creator_plugin,
name=namespace + "_animation",
# name=f"{unique_number}_{subset}_animation",
diff --git a/openpype/hosts/celaction/plugins/publish/submit_celaction_deadline.py b/openpype/hosts/celaction/plugins/publish/submit_celaction_deadline.py
index fd958d11a3..ea109e9445 100644
--- a/openpype/hosts/celaction/plugins/publish/submit_celaction_deadline.py
+++ b/openpype/hosts/celaction/plugins/publish/submit_celaction_deadline.py
@@ -1,9 +1,9 @@
import os
+import re
import json
import getpass
-from avalon.vendor import requests
-import re
+import requests
import pyblish.api
diff --git a/openpype/hosts/flame/api/pipeline.py b/openpype/hosts/flame/api/pipeline.py
index af071439ef..f802cf160b 100644
--- a/openpype/hosts/flame/api/pipeline.py
+++ b/openpype/hosts/flame/api/pipeline.py
@@ -7,6 +7,7 @@ from avalon import api as avalon
from avalon.pipeline import AVALON_CONTAINER_ID
from pyblish import api as pyblish
from openpype.api import Logger
+from openpype.pipeline import LegacyCreator
from .lib import (
set_segment_data_marker,
set_publish_attribute,
@@ -33,7 +34,7 @@ def install():
pyblish.register_host("flame")
pyblish.register_plugin_path(PUBLISH_PATH)
avalon.register_plugin_path(avalon.Loader, LOAD_PATH)
- avalon.register_plugin_path(avalon.Creator, CREATE_PATH)
+ avalon.register_plugin_path(LegacyCreator, CREATE_PATH)
avalon.register_plugin_path(avalon.InventoryAction, INVENTORY_PATH)
log.info("OpenPype Flame plug-ins registred ...")
@@ -48,7 +49,7 @@ def uninstall():
log.info("Deregistering Flame plug-ins..")
pyblish.deregister_plugin_path(PUBLISH_PATH)
avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH)
- avalon.deregister_plugin_path(avalon.Creator, CREATE_PATH)
+ avalon.deregister_plugin_path(LegacyCreator, CREATE_PATH)
avalon.deregister_plugin_path(avalon.InventoryAction, INVENTORY_PATH)
# register callback for switching publishable
diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py
index ec49db1601..5221701a2f 100644
--- a/openpype/hosts/flame/api/plugin.py
+++ b/openpype/hosts/flame/api/plugin.py
@@ -2,11 +2,12 @@ import os
import re
import shutil
import sys
-from avalon.vendor import qargparse
from xml.etree import ElementTree as ET
import six
+import qargparse
from Qt import QtWidgets, QtCore
import openpype.api as openpype
+from openpype.pipeline import LegacyCreator
from openpype import style
import avalon.api as avalon
from . import (
@@ -299,7 +300,7 @@ class Spacer(QtWidgets.QWidget):
self.setLayout(layout)
-class Creator(openpype.Creator):
+class Creator(LegacyCreator):
"""Creator class wrapper
"""
clip_color = constants.COLOR_MAP["purple"]
diff --git a/openpype/hosts/fusion/api/pipeline.py b/openpype/hosts/fusion/api/pipeline.py
index 64dda0bc8a..5ac56fcbed 100644
--- a/openpype/hosts/fusion/api/pipeline.py
+++ b/openpype/hosts/fusion/api/pipeline.py
@@ -11,6 +11,7 @@ import avalon.api
from avalon.pipeline import AVALON_CONTAINER_ID
from openpype.api import Logger
+from openpype.pipeline import LegacyCreator
import openpype.hosts.fusion
log = Logger().get_logger(__name__)
@@ -63,7 +64,7 @@ def install():
log.info("Registering Fusion plug-ins..")
avalon.api.register_plugin_path(avalon.api.Loader, LOAD_PATH)
- avalon.api.register_plugin_path(avalon.api.Creator, CREATE_PATH)
+ avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH)
avalon.api.register_plugin_path(avalon.api.InventoryAction, INVENTORY_PATH)
pyblish.api.register_callback(
@@ -87,7 +88,7 @@ def uninstall():
log.info("Deregistering Fusion plug-ins..")
avalon.api.deregister_plugin_path(avalon.api.Loader, LOAD_PATH)
- avalon.api.deregister_plugin_path(avalon.api.Creator, CREATE_PATH)
+ avalon.api.deregister_plugin_path(LegacyCreator, CREATE_PATH)
avalon.api.deregister_plugin_path(
avalon.api.InventoryAction, INVENTORY_PATH
)
diff --git a/openpype/hosts/fusion/plugins/create/create_exr_saver.py b/openpype/hosts/fusion/plugins/create/create_exr_saver.py
index 04717f4746..ff8bdb21ef 100644
--- a/openpype/hosts/fusion/plugins/create/create_exr_saver.py
+++ b/openpype/hosts/fusion/plugins/create/create_exr_saver.py
@@ -1,13 +1,13 @@
import os
-import openpype.api
+from openpype.pipeline import create
from openpype.hosts.fusion.api import (
get_current_comp,
comp_lock_and_undo_chunk
)
-class CreateOpenEXRSaver(openpype.api.Creator):
+class CreateOpenEXRSaver(create.LegacyCreator):
name = "openexrDefault"
label = "Create OpenEXR Saver"
diff --git a/openpype/hosts/fusion/scripts/set_rendermode.py b/openpype/hosts/fusion/scripts/set_rendermode.py
index 77a2d8e945..f0638e4fe3 100644
--- a/openpype/hosts/fusion/scripts/set_rendermode.py
+++ b/openpype/hosts/fusion/scripts/set_rendermode.py
@@ -1,5 +1,5 @@
from Qt import QtWidgets
-from avalon.vendor import qtawesome
+import qtawesome
from openpype.hosts.fusion.api import get_current_comp
diff --git a/openpype/hosts/fusion/utility_scripts/switch_ui.py b/openpype/hosts/fusion/utility_scripts/switch_ui.py
index afb39f7041..d9eeae25ea 100644
--- a/openpype/hosts/fusion/utility_scripts/switch_ui.py
+++ b/openpype/hosts/fusion/utility_scripts/switch_ui.py
@@ -6,7 +6,7 @@ from Qt import QtWidgets, QtCore
import avalon.api
from avalon import io
-from avalon.vendor import qtawesome as qta
+import qtawesome as qta
from openpype import style
from openpype.hosts.fusion import api
diff --git a/openpype/hosts/harmony/api/pipeline.py b/openpype/hosts/harmony/api/pipeline.py
index 17d2870876..6d0f5e9416 100644
--- a/openpype/hosts/harmony/api/pipeline.py
+++ b/openpype/hosts/harmony/api/pipeline.py
@@ -9,6 +9,7 @@ import avalon.api
from avalon.pipeline import AVALON_CONTAINER_ID
from openpype import lib
+from openpype.pipeline import LegacyCreator
import openpype.hosts.harmony
import openpype.hosts.harmony.api as harmony
@@ -179,7 +180,7 @@ def install():
pyblish.api.register_host("harmony")
pyblish.api.register_plugin_path(PUBLISH_PATH)
avalon.api.register_plugin_path(avalon.api.Loader, LOAD_PATH)
- avalon.api.register_plugin_path(avalon.api.Creator, CREATE_PATH)
+ avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH)
log.info(PUBLISH_PATH)
# Register callbacks.
@@ -193,7 +194,7 @@ def install():
def uninstall():
pyblish.api.deregister_plugin_path(PUBLISH_PATH)
avalon.api.deregister_plugin_path(avalon.api.Loader, LOAD_PATH)
- avalon.api.deregister_plugin_path(avalon.api.Creator, CREATE_PATH)
+ avalon.api.deregister_plugin_path(LegacyCreator, CREATE_PATH)
def on_pyblish_instance_toggled(instance, old_value, new_value):
diff --git a/openpype/hosts/harmony/api/plugin.py b/openpype/hosts/harmony/api/plugin.py
index d6d61a547a..c55d200d30 100644
--- a/openpype/hosts/harmony/api/plugin.py
+++ b/openpype/hosts/harmony/api/plugin.py
@@ -1,9 +1,8 @@
-import avalon.api
-from openpype.api import PypeCreatorMixin
+from openpype.pipeline import LegacyCreator
import openpype.hosts.harmony.api as harmony
-class Creator(PypeCreatorMixin, avalon.api.Creator):
+class Creator(LegacyCreator):
"""Creator plugin to create instances in Harmony.
By default a Composite node is created to support any number of nodes in
diff --git a/openpype/hosts/hiero/api/pipeline.py b/openpype/hosts/hiero/api/pipeline.py
index cbcaf23994..5cb23ea355 100644
--- a/openpype/hosts/hiero/api/pipeline.py
+++ b/openpype/hosts/hiero/api/pipeline.py
@@ -9,6 +9,7 @@ from avalon import api as avalon
from avalon import schema
from pyblish import api as pyblish
from openpype.api import Logger
+from openpype.pipeline import LegacyCreator
from openpype.tools.utils import host_tools
from . import lib, menu, events
@@ -45,7 +46,7 @@ def install():
pyblish.register_host("hiero")
pyblish.register_plugin_path(PUBLISH_PATH)
avalon.register_plugin_path(avalon.Loader, LOAD_PATH)
- avalon.register_plugin_path(avalon.Creator, CREATE_PATH)
+ avalon.register_plugin_path(LegacyCreator, CREATE_PATH)
avalon.register_plugin_path(avalon.InventoryAction, INVENTORY_PATH)
# register callback for switching publishable
@@ -67,7 +68,7 @@ def uninstall():
pyblish.deregister_host("hiero")
pyblish.deregister_plugin_path(PUBLISH_PATH)
avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH)
- avalon.deregister_plugin_path(avalon.Creator, CREATE_PATH)
+ avalon.deregister_plugin_path(LegacyCreator, CREATE_PATH)
# register callback for switching publishable
pyblish.deregister_callback("instanceToggled", on_pyblish_instance_toggled)
diff --git a/openpype/hosts/hiero/api/plugin.py b/openpype/hosts/hiero/api/plugin.py
index 3506af2d6a..53928aca41 100644
--- a/openpype/hosts/hiero/api/plugin.py
+++ b/openpype/hosts/hiero/api/plugin.py
@@ -1,12 +1,15 @@
-import re
import os
+import re
+from copy import deepcopy
+
import hiero
+
from Qt import QtWidgets, QtCore
-from avalon.vendor import qargparse
+import qargparse
import avalon.api as avalon
import openpype.api as openpype
+from openpype.pipeline import LegacyCreator
from . import lib
-from copy import deepcopy
log = openpype.Logger().get_logger(__name__)
@@ -589,7 +592,7 @@ class ClipLoader:
return track_item
-class Creator(openpype.Creator):
+class Creator(LegacyCreator):
"""Creator class wrapper
"""
clip_color = "Purple"
diff --git a/openpype/hosts/houdini/api/pipeline.py b/openpype/hosts/houdini/api/pipeline.py
index 1c08e72d65..21027dad2e 100644
--- a/openpype/hosts/houdini/api/pipeline.py
+++ b/openpype/hosts/houdini/api/pipeline.py
@@ -11,6 +11,7 @@ import avalon.api
from avalon.pipeline import AVALON_CONTAINER_ID
from avalon.lib import find_submodule
+from openpype.pipeline import LegacyCreator
import openpype.hosts.houdini
from openpype.hosts.houdini.api import lib
@@ -48,7 +49,7 @@ def install():
pyblish.api.register_plugin_path(PUBLISH_PATH)
avalon.api.register_plugin_path(avalon.api.Loader, LOAD_PATH)
- avalon.api.register_plugin_path(avalon.api.Creator, CREATE_PATH)
+ avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH)
log.info("Installing callbacks ... ")
# avalon.on("init", on_init)
diff --git a/openpype/hosts/houdini/api/plugin.py b/openpype/hosts/houdini/api/plugin.py
index 4967d01d43..2bbb65aa05 100644
--- a/openpype/hosts/houdini/api/plugin.py
+++ b/openpype/hosts/houdini/api/plugin.py
@@ -2,11 +2,12 @@
"""Houdini specific Avalon/Pyblish plugin definitions."""
import sys
import six
-import avalon.api
-from avalon.api import CreatorError
import hou
-from openpype.api import PypeCreatorMixin
+from openpype.pipeline import (
+ CreatorError,
+ LegacyCreator
+)
from .lib import imprint
@@ -14,7 +15,7 @@ class OpenPypeCreatorError(CreatorError):
pass
-class Creator(PypeCreatorMixin, avalon.api.Creator):
+class Creator(LegacyCreator):
"""Creator plugin to create instances in Houdini
To support the wide range of node types for render output (Alembic, VDB,
diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py
index 1b3bb9feb3..8c3669c5d1 100644
--- a/openpype/hosts/maya/api/pipeline.py
+++ b/openpype/hosts/maya/api/pipeline.py
@@ -2,7 +2,6 @@ import os
import sys
import errno
import logging
-import contextlib
from maya import utils, cmds, OpenMaya
import maya.api.OpenMaya as om
@@ -17,6 +16,7 @@ import openpype.hosts.maya
from openpype.tools.utils import host_tools
from openpype.lib import any_outdated
from openpype.lib.path_tools import HostDirmap
+from openpype.pipeline import LegacyCreator
from openpype.hosts.maya.lib import copy_workspace_mel
from . import menu, lib
@@ -50,7 +50,7 @@ def install():
pyblish.api.register_host("maya")
avalon.api.register_plugin_path(avalon.api.Loader, LOAD_PATH)
- avalon.api.register_plugin_path(avalon.api.Creator, CREATE_PATH)
+ avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH)
avalon.api.register_plugin_path(avalon.api.InventoryAction, INVENTORY_PATH)
log.info(PUBLISH_PATH)
@@ -176,7 +176,7 @@ def uninstall():
pyblish.api.deregister_host("maya")
avalon.api.deregister_plugin_path(avalon.api.Loader, LOAD_PATH)
- avalon.api.deregister_plugin_path(avalon.api.Creator, CREATE_PATH)
+ avalon.api.deregister_plugin_path(LegacyCreator, CREATE_PATH)
avalon.api.deregister_plugin_path(
avalon.api.InventoryAction, INVENTORY_PATH
)
diff --git a/openpype/hosts/maya/api/plugin.py b/openpype/hosts/maya/api/plugin.py
index bdb8fcf13a..d99e8d25b7 100644
--- a/openpype/hosts/maya/api/plugin.py
+++ b/openpype/hosts/maya/api/plugin.py
@@ -2,9 +2,10 @@ import os
from maya import cmds
+import qargparse
+
from avalon import api
-from avalon.vendor import qargparse
-from openpype.api import PypeCreatorMixin
+from openpype.pipeline import LegacyCreator
from .pipeline import containerise
from . import lib
@@ -77,7 +78,7 @@ def get_reference_node_parents(ref):
return parents
-class Creator(PypeCreatorMixin, api.Creator):
+class Creator(LegacyCreator):
defaults = ['Main']
def process(self):
diff --git a/openpype/hosts/maya/plugins/create/create_render.py b/openpype/hosts/maya/plugins/create/create_render.py
index 743ec26778..9002ae3876 100644
--- a/openpype/hosts/maya/plugins/create/create_render.py
+++ b/openpype/hosts/maya/plugins/create/create_render.py
@@ -19,9 +19,9 @@ from openpype.api import (
get_project_settings,
get_asset)
from openpype.modules import ModulesManager
+from openpype.pipeline import CreatorError
from avalon.api import Session
-from avalon.api import CreatorError
class CreateRender(plugin.Creator):
diff --git a/openpype/hosts/maya/plugins/create/create_vrayscene.py b/openpype/hosts/maya/plugins/create/create_vrayscene.py
index f2096d902e..fa9c59e016 100644
--- a/openpype/hosts/maya/plugins/create/create_vrayscene.py
+++ b/openpype/hosts/maya/plugins/create/create_vrayscene.py
@@ -19,10 +19,10 @@ from openpype.api import (
get_project_settings
)
+from openpype.pipeline import CreatorError
from openpype.modules import ModulesManager
from avalon.api import Session
-from avalon.api import CreatorError
class CreateVRayScene(plugin.Creator):
diff --git a/openpype/hosts/maya/plugins/load/load_reference.py b/openpype/hosts/maya/plugins/load/load_reference.py
index 0565b0b95c..25db5fb1e5 100644
--- a/openpype/hosts/maya/plugins/load/load_reference.py
+++ b/openpype/hosts/maya/plugins/load/load_reference.py
@@ -3,6 +3,7 @@ from maya import cmds
from avalon import api
from openpype.api import get_project_settings
from openpype.lib import get_creator_by_name
+from openpype.pipeline import legacy_create
import openpype.hosts.maya.api.plugin
from openpype.hosts.maya.api.lib import maintained_selection
@@ -151,7 +152,7 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader):
creator_plugin = get_creator_by_name(self.animation_creator_name)
with maintained_selection():
cmds.select([output, controls] + roots, noExpand=True)
- api.create(
+ legacy_create(
creator_plugin,
name=namespace,
asset=asset,
diff --git a/openpype/hosts/maya/plugins/load/load_vdb_to_vray.py b/openpype/hosts/maya/plugins/load/load_vdb_to_vray.py
index 099c020093..6d5544103d 100644
--- a/openpype/hosts/maya/plugins/load/load_vdb_to_vray.py
+++ b/openpype/hosts/maya/plugins/load/load_vdb_to_vray.py
@@ -174,7 +174,7 @@ class LoadVDBtoVRay(api.Loader):
fname = files[0]
else:
# Sequence
- from avalon.vendor import clique
+ import clique
# todo: check support for negative frames as input
collections, remainder = clique.assemble(files)
assert len(collections) == 1, (
diff --git a/openpype/hosts/nuke/api/pipeline.py b/openpype/hosts/nuke/api/pipeline.py
index 8c6c9ca55b..d98a951491 100644
--- a/openpype/hosts/nuke/api/pipeline.py
+++ b/openpype/hosts/nuke/api/pipeline.py
@@ -14,6 +14,7 @@ from openpype.api import (
BuildWorkfile,
get_current_project_settings
)
+from openpype.pipeline import LegacyCreator
from openpype.tools.utils import host_tools
from .command import viewer_update_and_undo_stop
@@ -98,7 +99,7 @@ def install():
log.info("Registering Nuke plug-ins..")
pyblish.api.register_plugin_path(PUBLISH_PATH)
avalon.api.register_plugin_path(avalon.api.Loader, LOAD_PATH)
- avalon.api.register_plugin_path(avalon.api.Creator, CREATE_PATH)
+ avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH)
avalon.api.register_plugin_path(avalon.api.InventoryAction, INVENTORY_PATH)
# Register Avalon event for workfiles loading.
@@ -124,7 +125,7 @@ def uninstall():
pyblish.deregister_host("nuke")
pyblish.api.deregister_plugin_path(PUBLISH_PATH)
avalon.api.deregister_plugin_path(avalon.api.Loader, LOAD_PATH)
- avalon.api.deregister_plugin_path(avalon.api.Creator, CREATE_PATH)
+ avalon.api.deregister_plugin_path(LegacyCreator, CREATE_PATH)
pyblish.api.deregister_callback(
"instanceToggled", on_pyblish_instance_toggled)
diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py
index 11e30d9fcd..ff186cd685 100644
--- a/openpype/hosts/nuke/api/plugin.py
+++ b/openpype/hosts/nuke/api/plugin.py
@@ -6,10 +6,8 @@ import nuke
import avalon.api
-from openpype.api import (
- get_current_project_settings,
- PypeCreatorMixin
-)
+from openpype.api import get_current_project_settings
+from openpype.pipeline import LegacyCreator
from .lib import (
Knobby,
check_subsetname_exists,
@@ -20,7 +18,7 @@ from .lib import (
)
-class OpenPypeCreator(PypeCreatorMixin, avalon.api.Creator):
+class OpenPypeCreator(LegacyCreator):
"""Pype Nuke Creator class wrapper"""
node_color = "0xdfea5dff"
diff --git a/openpype/hosts/nuke/plugins/load/load_clip.py b/openpype/hosts/nuke/plugins/load/load_clip.py
index 21b7a6a816..a253ba4a9d 100644
--- a/openpype/hosts/nuke/plugins/load/load_clip.py
+++ b/openpype/hosts/nuke/plugins/load/load_clip.py
@@ -1,5 +1,5 @@
import nuke
-from avalon.vendor import qargparse
+import qargparse
from avalon import api, io
from openpype.hosts.nuke.api.lib import (
diff --git a/openpype/hosts/nuke/plugins/load/load_image.py b/openpype/hosts/nuke/plugins/load/load_image.py
index d36226b139..27c634ec57 100644
--- a/openpype/hosts/nuke/plugins/load/load_image.py
+++ b/openpype/hosts/nuke/plugins/load/load_image.py
@@ -1,7 +1,6 @@
-import re
import nuke
-from avalon.vendor import qargparse
+import qargparse
from avalon import api, io
from openpype.hosts.nuke.api.lib import (
diff --git a/openpype/hosts/photoshop/api/__init__.py b/openpype/hosts/photoshop/api/__init__.py
index 4cc2aa2c78..17ea957066 100644
--- a/openpype/hosts/photoshop/api/__init__.py
+++ b/openpype/hosts/photoshop/api/__init__.py
@@ -16,7 +16,6 @@ from .pipeline import (
)
from .plugin import (
PhotoshopLoader,
- Creator,
get_unique_layer_name
)
from .workio import (
@@ -42,11 +41,11 @@ __all__ = [
"list_instances",
"remove_instance",
"install",
+ "uninstall",
"containerise",
# Plugin
"PhotoshopLoader",
- "Creator",
"get_unique_layer_name",
# workfiles
diff --git a/openpype/hosts/photoshop/api/pipeline.py b/openpype/hosts/photoshop/api/pipeline.py
index 25983f2471..662e9dbebc 100644
--- a/openpype/hosts/photoshop/api/pipeline.py
+++ b/openpype/hosts/photoshop/api/pipeline.py
@@ -1,5 +1,4 @@
import os
-import sys
from Qt import QtWidgets
import pyblish.api
@@ -7,6 +6,7 @@ import avalon.api
from avalon import pipeline, io
from openpype.api import Logger
+from openpype.pipeline import LegacyCreator
import openpype.hosts.photoshop
from . import lib
@@ -68,7 +68,7 @@ def install():
pyblish.api.register_plugin_path(PUBLISH_PATH)
avalon.api.register_plugin_path(avalon.api.Loader, LOAD_PATH)
- avalon.api.register_plugin_path(avalon.api.Creator, CREATE_PATH)
+ avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH)
log.info(PUBLISH_PATH)
pyblish.api.register_callback(
@@ -81,7 +81,7 @@ def install():
def uninstall():
pyblish.api.deregister_plugin_path(PUBLISH_PATH)
avalon.api.deregister_plugin_path(avalon.api.Loader, LOAD_PATH)
- avalon.api.deregister_plugin_path(avalon.api.Creator, CREATE_PATH)
+ avalon.api.deregister_plugin_path(LegacyCreator, CREATE_PATH)
def ls():
diff --git a/openpype/hosts/photoshop/api/plugin.py b/openpype/hosts/photoshop/api/plugin.py
index e0db67de2c..c577c67d82 100644
--- a/openpype/hosts/photoshop/api/plugin.py
+++ b/openpype/hosts/photoshop/api/plugin.py
@@ -33,37 +33,3 @@ class PhotoshopLoader(avalon.api.Loader):
@staticmethod
def get_stub():
return stub()
-
-
-class Creator(avalon.api.Creator):
- """Creator plugin to create instances in Photoshop
-
- A LayerSet is created to support any number of layers in an instance. If
- the selection is used, these layers will be added to the LayerSet.
- """
-
- def process(self):
- # Photoshop can have multiple LayerSets with the same name, which does
- # not work with Avalon.
- msg = "Instance with name \"{}\" already exists.".format(self.name)
- stub = lib.stub() # only after Photoshop is up
- for layer in stub.get_layers():
- if self.name.lower() == layer.Name.lower():
- msg = QtWidgets.QMessageBox()
- msg.setIcon(QtWidgets.QMessageBox.Warning)
- msg.setText(msg)
- msg.exec_()
- return False
-
- # Store selection because adding a group will change selection.
- with lib.maintained_selection():
-
- # Add selection to group.
- if (self.options or {}).get("useSelection"):
- group = stub.group_selected_layers(self.name)
- else:
- group = stub.create_group(self.name)
-
- stub.imprint(group, self.data)
-
- return group
diff --git a/openpype/hosts/photoshop/plugins/create/create_image.py b/openpype/hosts/photoshop/plugins/create/create_image.py
index 344a53f47e..a001b5f171 100644
--- a/openpype/hosts/photoshop/plugins/create/create_image.py
+++ b/openpype/hosts/photoshop/plugins/create/create_image.py
@@ -1,9 +1,9 @@
from Qt import QtWidgets
-import openpype.api
+from openpype.pipeline import create
from openpype.hosts.photoshop import api as photoshop
-class CreateImage(openpype.api.Creator):
+class CreateImage(create.LegacyCreator):
"""Image folder for publish."""
name = "imageDefault"
diff --git a/openpype/hosts/photoshop/plugins/load/load_image_from_sequence.py b/openpype/hosts/photoshop/plugins/load/load_image_from_sequence.py
index 6627aded51..12e0503dfc 100644
--- a/openpype/hosts/photoshop/plugins/load/load_image_from_sequence.py
+++ b/openpype/hosts/photoshop/plugins/load/load_image_from_sequence.py
@@ -1,7 +1,7 @@
import os
+import qargparse
from avalon.pipeline import get_representation_path_from_context
-from avalon.vendor import qargparse
from openpype.hosts.photoshop import api as photoshop
from openpype.hosts.photoshop.api import get_unique_layer_name
@@ -92,4 +92,3 @@ class ImageFromSequenceLoader(photoshop.PhotoshopLoader):
def remove(self, container):
"""No update possible, not containerized."""
pass
-
diff --git a/openpype/hosts/resolve/api/pipeline.py b/openpype/hosts/resolve/api/pipeline.py
index 2dc5136c8a..c82545268b 100644
--- a/openpype/hosts/resolve/api/pipeline.py
+++ b/openpype/hosts/resolve/api/pipeline.py
@@ -9,6 +9,7 @@ from avalon import schema
from avalon.pipeline import AVALON_CONTAINER_ID
from pyblish import api as pyblish
from openpype.api import Logger
+from openpype.pipeline import LegacyCreator
from . import lib
from . import PLUGINS_DIR
from openpype.tools.utils import host_tools
@@ -42,7 +43,7 @@ def install():
log.info("Registering DaVinci Resovle plug-ins..")
avalon.register_plugin_path(avalon.Loader, LOAD_PATH)
- avalon.register_plugin_path(avalon.Creator, CREATE_PATH)
+ avalon.register_plugin_path(LegacyCreator, CREATE_PATH)
avalon.register_plugin_path(avalon.InventoryAction, INVENTORY_PATH)
# register callback for switching publishable
@@ -67,7 +68,7 @@ def uninstall():
log.info("Deregistering DaVinci Resovle plug-ins..")
avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH)
- avalon.deregister_plugin_path(avalon.Creator, CREATE_PATH)
+ avalon.deregister_plugin_path(LegacyCreator, CREATE_PATH)
avalon.deregister_plugin_path(avalon.InventoryAction, INVENTORY_PATH)
# register callback for switching publishable
diff --git a/openpype/hosts/resolve/api/plugin.py b/openpype/hosts/resolve/api/plugin.py
index 8612cf82ec..e7793d6e95 100644
--- a/openpype/hosts/resolve/api/plugin.py
+++ b/openpype/hosts/resolve/api/plugin.py
@@ -1,12 +1,15 @@
import re
import uuid
+
+import qargparse
+from Qt import QtWidgets, QtCore
+
from avalon import api
import openpype.api as pype
+from openpype.pipeline import LegacyCreator
from openpype.hosts import resolve
-from avalon.vendor import qargparse
from . import lib
-from Qt import QtWidgets, QtCore
class CreatorWidget(QtWidgets.QDialog):
@@ -493,7 +496,7 @@ class TimelineItemLoader(api.Loader):
pass
-class Creator(pype.PypeCreatorMixin, api.Creator):
+class Creator(LegacyCreator):
"""Creator class wrapper
"""
marker_color = "Purple"
diff --git a/openpype/hosts/testhost/plugins/publish/collect_context.py b/openpype/hosts/testhost/plugins/publish/collect_context.py
index bbb8477cdf..0ab98fb84b 100644
--- a/openpype/hosts/testhost/plugins/publish/collect_context.py
+++ b/openpype/hosts/testhost/plugins/publish/collect_context.py
@@ -19,7 +19,7 @@ class CollectContextDataTestHost(
hosts = ["testhost"]
@classmethod
- def get_instance_attr_defs(cls):
+ def get_attribute_defs(cls):
return [
attribute_definitions.BoolDef(
"test_bool",
diff --git a/openpype/hosts/testhost/plugins/publish/collect_instance_1.py b/openpype/hosts/testhost/plugins/publish/collect_instance_1.py
index 979ab83f11..3c035eccb6 100644
--- a/openpype/hosts/testhost/plugins/publish/collect_instance_1.py
+++ b/openpype/hosts/testhost/plugins/publish/collect_instance_1.py
@@ -20,7 +20,7 @@ class CollectInstanceOneTestHost(
hosts = ["testhost"]
@classmethod
- def get_instance_attr_defs(cls):
+ def get_attribute_defs(cls):
return [
attribute_definitions.NumberDef(
"version",
diff --git a/openpype/hosts/tvpaint/api/pipeline.py b/openpype/hosts/tvpaint/api/pipeline.py
index 74eb41892c..f4599047b4 100644
--- a/openpype/hosts/tvpaint/api/pipeline.py
+++ b/openpype/hosts/tvpaint/api/pipeline.py
@@ -14,6 +14,7 @@ from avalon.pipeline import AVALON_CONTAINER_ID
from openpype.hosts import tvpaint
from openpype.api import get_current_project_settings
+from openpype.pipeline import LegacyCreator
from .lib import (
execute_george,
@@ -76,7 +77,7 @@ def install():
pyblish.api.register_host("tvpaint")
pyblish.api.register_plugin_path(PUBLISH_PATH)
avalon.api.register_plugin_path(avalon.api.Loader, LOAD_PATH)
- avalon.api.register_plugin_path(avalon.api.Creator, CREATE_PATH)
+ avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH)
registered_callbacks = (
pyblish.api.registered_callbacks().get("instanceToggled") or []
@@ -98,7 +99,7 @@ def uninstall():
pyblish.api.deregister_host("tvpaint")
pyblish.api.deregister_plugin_path(PUBLISH_PATH)
avalon.api.deregister_plugin_path(avalon.api.Loader, LOAD_PATH)
- avalon.api.deregister_plugin_path(avalon.api.Creator, CREATE_PATH)
+ avalon.api.deregister_plugin_path(LegacyCreator, CREATE_PATH)
def containerise(
diff --git a/openpype/hosts/tvpaint/api/plugin.py b/openpype/hosts/tvpaint/api/plugin.py
index af80c9eae2..8510794f06 100644
--- a/openpype/hosts/tvpaint/api/plugin.py
+++ b/openpype/hosts/tvpaint/api/plugin.py
@@ -3,14 +3,14 @@ import uuid
import avalon.api
-from openpype.api import PypeCreatorMixin
+from openpype.pipeline import LegacyCreator
from openpype.hosts.tvpaint.api import (
pipeline,
lib
)
-class Creator(PypeCreatorMixin, avalon.api.Creator):
+class Creator(LegacyCreator):
def __init__(self, *args, **kwargs):
super(Creator, self).__init__(*args, **kwargs)
# Add unified identifier created with `uuid` module
diff --git a/openpype/hosts/tvpaint/plugins/create/create_render_layer.py b/openpype/hosts/tvpaint/plugins/create/create_render_layer.py
index 40a7d15990..c1af9632b1 100644
--- a/openpype/hosts/tvpaint/plugins/create/create_render_layer.py
+++ b/openpype/hosts/tvpaint/plugins/create/create_render_layer.py
@@ -1,5 +1,4 @@
-from avalon.api import CreatorError
-
+from openpype.pipeline import CreatorError
from openpype.lib import prepare_template_data
from openpype.hosts.tvpaint.api import (
plugin,
diff --git a/openpype/hosts/tvpaint/plugins/create/create_render_pass.py b/openpype/hosts/tvpaint/plugins/create/create_render_pass.py
index af962052fc..a7f717ccec 100644
--- a/openpype/hosts/tvpaint/plugins/create/create_render_pass.py
+++ b/openpype/hosts/tvpaint/plugins/create/create_render_pass.py
@@ -1,4 +1,4 @@
-from avalon.api import CreatorError
+from openpype.pipeline import CreatorError
from openpype.lib import prepare_template_data
from openpype.hosts.tvpaint.api import (
plugin,
diff --git a/openpype/hosts/tvpaint/plugins/load/load_image.py b/openpype/hosts/tvpaint/plugins/load/load_image.py
index 7dba1e3619..f861d0119e 100644
--- a/openpype/hosts/tvpaint/plugins/load/load_image.py
+++ b/openpype/hosts/tvpaint/plugins/load/load_image.py
@@ -1,4 +1,4 @@
-from avalon.vendor import qargparse
+import qargparse
from openpype.hosts.tvpaint.api import lib, plugin
diff --git a/openpype/hosts/tvpaint/plugins/load/load_reference_image.py b/openpype/hosts/tvpaint/plugins/load/load_reference_image.py
index 0a85e5dc76..5e4e3965d2 100644
--- a/openpype/hosts/tvpaint/plugins/load/load_reference_image.py
+++ b/openpype/hosts/tvpaint/plugins/load/load_reference_image.py
@@ -1,6 +1,6 @@
import collections
+import qargparse
from avalon.pipeline import get_representation_context
-from avalon.vendor import qargparse
from openpype.hosts.tvpaint.api import lib, pipeline, plugin
diff --git a/openpype/hosts/unreal/api/pipeline.py b/openpype/hosts/unreal/api/pipeline.py
index ad64d56e9e..8ab19bd697 100644
--- a/openpype/hosts/unreal/api/pipeline.py
+++ b/openpype/hosts/unreal/api/pipeline.py
@@ -7,6 +7,7 @@ import pyblish.api
from avalon.pipeline import AVALON_CONTAINER_ID
from avalon import api
+from openpype.pipeline import LegacyCreator
from openpype.tools.utils import host_tools
import openpype.hosts.unreal
@@ -44,7 +45,7 @@ def install():
logger.info("installing OpenPype for Unreal")
pyblish.api.register_plugin_path(str(PUBLISH_PATH))
api.register_plugin_path(api.Loader, str(LOAD_PATH))
- api.register_plugin_path(api.Creator, str(CREATE_PATH))
+ api.register_plugin_path(LegacyCreator, str(CREATE_PATH))
_register_callbacks()
_register_events()
@@ -53,7 +54,7 @@ def uninstall():
"""Uninstall Unreal configuration for Avalon."""
pyblish.api.deregister_plugin_path(str(PUBLISH_PATH))
api.deregister_plugin_path(api.Loader, str(LOAD_PATH))
- api.deregister_plugin_path(api.Creator, str(CREATE_PATH))
+ api.deregister_plugin_path(LegacyCreator, str(CREATE_PATH))
def _register_callbacks():
@@ -70,7 +71,7 @@ def _register_events():
pass
-class Creator(api.Creator):
+class Creator(LegacyCreator):
hosts = ["unreal"]
asset_types = []
diff --git a/openpype/hosts/unreal/api/plugin.py b/openpype/hosts/unreal/api/plugin.py
index 2327fc09c8..dd2e7750f0 100644
--- a/openpype/hosts/unreal/api/plugin.py
+++ b/openpype/hosts/unreal/api/plugin.py
@@ -1,11 +1,11 @@
# -*- coding: utf-8 -*-
from abc import ABC
-import openpype.api
+from openpype.pipeline import LegacyCreator
import avalon.api
-class Creator(openpype.api.Creator):
+class Creator(LegacyCreator):
"""This serves as skeleton for future OpenPype specific functionality"""
defaults = ['Main']
diff --git a/openpype/hosts/webpublisher/api/__init__.py b/openpype/hosts/webpublisher/api/__init__.py
index e40d46d662..6ce8a58fc2 100644
--- a/openpype/hosts/webpublisher/api/__init__.py
+++ b/openpype/hosts/webpublisher/api/__init__.py
@@ -5,6 +5,7 @@ from avalon import api as avalon
from avalon import io
from pyblish import api as pyblish
import openpype.hosts.webpublisher
+from openpype.pipeline import LegacyCreator
log = logging.getLogger("openpype.hosts.webpublisher")
@@ -25,7 +26,7 @@ def install():
pyblish.register_plugin_path(PUBLISH_PATH)
avalon.register_plugin_path(avalon.Loader, LOAD_PATH)
- avalon.register_plugin_path(avalon.Creator, CREATE_PATH)
+ avalon.register_plugin_path(LegacyCreator, CREATE_PATH)
log.info(PUBLISH_PATH)
io.install()
@@ -35,7 +36,7 @@ def install():
def uninstall():
pyblish.deregister_plugin_path(PUBLISH_PATH)
avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH)
- avalon.deregister_plugin_path(avalon.Creator, CREATE_PATH)
+ avalon.deregister_plugin_path(LegacyCreator, CREATE_PATH)
# to have required methods for interface
diff --git a/openpype/lib/__init__.py b/openpype/lib/__init__.py
index bbc2504c08..857380ac17 100644
--- a/openpype/lib/__init__.py
+++ b/openpype/lib/__init__.py
@@ -159,6 +159,7 @@ from .plugin_tools import (
)
from .path_tools import (
+ create_hard_link,
version_up,
get_version_from_path,
get_last_version_from_path,
@@ -291,6 +292,7 @@ __all__ = [
"get_unique_layer_name",
"get_background_layers",
+ "create_hard_link",
"version_up",
"get_version_from_path",
"get_last_version_from_path",
diff --git a/openpype/lib/delivery.py b/openpype/lib/delivery.py
index a61603fa05..9fc65aae8e 100644
--- a/openpype/lib/delivery.py
+++ b/openpype/lib/delivery.py
@@ -71,15 +71,14 @@ def path_from_representation(representation, anatomy):
def copy_file(src_path, dst_path):
"""Hardlink file if possible(to save space), copy if not"""
- from avalon.vendor import filelink # safer importing
+ from openpype.lib import create_hard_link # safer importing
if os.path.exists(dst_path):
return
try:
- filelink.create(
+ create_hard_link(
src_path,
- dst_path,
- filelink.HARDLINK
+ dst_path
)
except OSError:
shutil.copyfile(src_path, dst_path)
diff --git a/openpype/lib/path_tools.py b/openpype/lib/path_tools.py
index d6c32ad9e8..3a9f835272 100644
--- a/openpype/lib/path_tools.py
+++ b/openpype/lib/path_tools.py
@@ -4,9 +4,9 @@ import abc
import json
import logging
import six
+import platform
from openpype.settings import get_project_settings
-from openpype.settings.lib import get_site_local_overrides
from .anatomy import Anatomy
from .profiles_filtering import filter_profiles
@@ -14,6 +14,41 @@ from .profiles_filtering import filter_profiles
log = logging.getLogger(__name__)
+def create_hard_link(src_path, dst_path):
+ """Create hardlink of file.
+
+ Args:
+ src_path(str): Full path to a file which is used as source for
+ hardlink.
+ dst_path(str): Full path to a file where a link of source will be
+ added.
+ """
+ # Use `os.link` if is available
+ # - should be for all platforms with newer python versions
+ if hasattr(os, "link"):
+ os.link(src_path, dst_path)
+ return
+
+ # Windows implementation of hardlinks
+ # - used in Python 2
+ if platform.system().lower() == "windows":
+ import ctypes
+ from ctypes.wintypes import BOOL
+ CreateHardLink = ctypes.windll.kernel32.CreateHardLinkW
+ CreateHardLink.argtypes = [
+ ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_void_p
+ ]
+ CreateHardLink.restype = BOOL
+
+ res = CreateHardLink(dst_path, src_path, None)
+ if res == 0:
+ raise ctypes.WinError()
+ # Raises not implemented error if gets here
+ raise NotImplementedError(
+ "Implementation of hardlink for current environment is missing."
+ )
+
+
def _rreplace(s, a, b, n=1):
"""Replace a with b in string s from right side n times."""
return b.join(s.rsplit(a, n))
diff --git a/openpype/lib/plugin_tools.py b/openpype/lib/plugin_tools.py
index 183aad939a..19765a6f4a 100644
--- a/openpype/lib/plugin_tools.py
+++ b/openpype/lib/plugin_tools.py
@@ -293,7 +293,7 @@ def set_plugin_attributes_from_settings(
plugin_type = None
if superclass.__name__.split(".")[-1] in ("Loader", "SubsetLoader"):
plugin_type = "load"
- elif superclass.__name__.split(".")[-1] == "Creator":
+ elif superclass.__name__.split(".")[-1] in ("Creator", "LegacyCreator"):
plugin_type = "create"
if not host_name or not project_name or plugin_type is None:
diff --git a/openpype/modules/base.py b/openpype/modules/base.py
index c7078475df..175957ae39 100644
--- a/openpype/modules/base.py
+++ b/openpype/modules/base.py
@@ -61,6 +61,7 @@ class _ModuleClass(object):
def __init__(self, name):
# Call setattr on super class
super(_ModuleClass, self).__setattr__("name", name)
+ super(_ModuleClass, self).__setattr__("__name__", name)
# Where modules and interfaces are stored
super(_ModuleClass, self).__setattr__("__attributes__", dict())
@@ -72,7 +73,7 @@ class _ModuleClass(object):
if attr_name not in self.__attributes__:
if attr_name in ("__path__", "__file__"):
return None
- raise ImportError("No module named {}.{}".format(
+ raise AttributeError("'{}' has not attribute '{}'".format(
self.name, attr_name
))
return self.__attributes__[attr_name]
diff --git a/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py b/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py
index 82c2494e7a..b4a340a0d0 100644
--- a/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py
+++ b/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py
@@ -84,7 +84,9 @@ def inject_openpype_environment(deadlinePlugin):
with open(export_url) as fp:
contents = json.load(fp)
for key, value in contents.items():
- deadlinePlugin.SetProcessEnvironmentVariable(key, value)
+ print("key:: {}".format(key))
+ if key != 'NUMBER_OF_PROCESSORS':
+ deadlinePlugin.SetProcessEnvironmentVariable(key, value)
print(">>> Removing temporary file")
os.remove(export_url)
diff --git a/openpype/modules/log_viewer/tray/widgets.py b/openpype/modules/log_viewer/tray/widgets.py
index 5a67780413..ff77405de5 100644
--- a/openpype/modules/log_viewer/tray/widgets.py
+++ b/openpype/modules/log_viewer/tray/widgets.py
@@ -1,5 +1,5 @@
from Qt import QtCore, QtWidgets
-from avalon.vendor import qtawesome
+import qtawesome
from .models import LogModel, LogsFilterProxy
diff --git a/openpype/modules/sync_server/tray/models.py b/openpype/modules/sync_server/tray/models.py
index 80f41992cb..7241cc3472 100644
--- a/openpype/modules/sync_server/tray/models.py
+++ b/openpype/modules/sync_server/tray/models.py
@@ -4,9 +4,9 @@ from bson.objectid import ObjectId
from Qt import QtCore
from Qt.QtCore import Qt
+import qtawesome
from openpype.tools.utils.delegates import pretty_timestamp
-from avalon.vendor import qtawesome
from openpype.lib import PypeLogger
from openpype.api import get_local_site_id
diff --git a/openpype/modules/sync_server/tray/widgets.py b/openpype/modules/sync_server/tray/widgets.py
index 18487b3d11..6aae9562cf 100644
--- a/openpype/modules/sync_server/tray/widgets.py
+++ b/openpype/modules/sync_server/tray/widgets.py
@@ -5,6 +5,7 @@ from functools import partial
from Qt import QtWidgets, QtCore, QtGui
from Qt.QtCore import Qt
+import qtawesome
from openpype.tools.settings import style
@@ -12,7 +13,6 @@ from openpype.api import get_local_site_id
from openpype.lib import PypeLogger
from openpype.tools.utils.delegates import pretty_timestamp
-from avalon.vendor import qtawesome
from .models import (
SyncRepresentationSummaryModel,
diff --git a/openpype/pipeline/__init__.py b/openpype/pipeline/__init__.py
index a4fe9e6fb5..1a0e2306c0 100644
--- a/openpype/pipeline/__init__.py
+++ b/openpype/pipeline/__init__.py
@@ -5,7 +5,10 @@ from .create import (
Creator,
AutoCreator,
CreatedInstance,
- CreatorError
+ CreatorError,
+
+ LegacyCreator,
+ legacy_create,
)
from .publish import (
@@ -25,6 +28,12 @@ __all__ = (
"CreatedInstance",
"CreatorError",
+ "CreatorError",
+
+ # Legacy creation
+ "LegacyCreator",
+ "legacy_create",
+
"PublishValidationError",
"PublishXmlValidationError",
"KnownPublishError",
diff --git a/openpype/pipeline/create/__init__.py b/openpype/pipeline/create/__init__.py
index 948b719851..9571f56b8f 100644
--- a/openpype/pipeline/create/__init__.py
+++ b/openpype/pipeline/create/__init__.py
@@ -14,6 +14,11 @@ from .context import (
CreateContext
)
+from .legacy_create import (
+ LegacyCreator,
+ legacy_create,
+)
+
__all__ = (
"SUBSET_NAME_ALLOWED_SYMBOLS",
@@ -25,5 +30,8 @@ __all__ = (
"AutoCreator",
"CreatedInstance",
- "CreateContext"
+ "CreateContext",
+
+ "LegacyCreator",
+ "legacy_create",
)
diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py
index 706279fd72..c2757a4502 100644
--- a/openpype/pipeline/create/context.py
+++ b/openpype/pipeline/create/context.py
@@ -1005,12 +1005,14 @@ class CreateContext:
if not instances:
return
- task_names_by_asset_name = collections.defaultdict(set)
+ task_names_by_asset_name = {}
for instance in instances:
task_name = instance.get("task")
asset_name = instance.get("asset")
- if asset_name and task_name:
- task_names_by_asset_name[asset_name].add(task_name)
+ if asset_name:
+ task_names_by_asset_name[asset_name] = set()
+ if task_name:
+ task_names_by_asset_name[asset_name].add(task_name)
asset_names = [
asset_name
diff --git a/openpype/pipeline/create/legacy_create.py b/openpype/pipeline/create/legacy_create.py
new file mode 100644
index 0000000000..d05cdff689
--- /dev/null
+++ b/openpype/pipeline/create/legacy_create.py
@@ -0,0 +1,156 @@
+"""Create workflow moved from avalon-core repository.
+
+Renamed classes and functions
+- 'Creator' -> 'LegacyCreator'
+- 'create' -> 'legacy_create'
+"""
+
+import logging
+import collections
+
+from openpype.lib import get_subset_name
+
+
+class LegacyCreator(object):
+ """Determine how assets are created"""
+ label = None
+ family = None
+ defaults = None
+ maintain_selection = True
+
+ dynamic_subset_keys = []
+
+ log = logging.getLogger("LegacyCreator")
+
+ def __init__(self, name, asset, options=None, data=None):
+ self.name = name # For backwards compatibility
+ self.options = options
+
+ # Default data
+ self.data = collections.OrderedDict()
+ self.data["id"] = "pyblish.avalon.instance"
+ self.data["family"] = self.family
+ self.data["asset"] = asset
+ self.data["subset"] = name
+ self.data["active"] = True
+
+ self.data.update(data or {})
+
+ def process(self):
+ pass
+
+ @classmethod
+ def get_dynamic_data(
+ cls, variant, task_name, asset_id, project_name, host_name
+ ):
+ """Return dynamic data for current Creator plugin.
+
+ By default return keys from `dynamic_subset_keys` attribute as mapping
+ to keep formatted template unchanged.
+
+ ```
+ dynamic_subset_keys = ["my_key"]
+ ---
+ output = {
+ "my_key": "{my_key}"
+ }
+ ```
+
+ Dynamic keys may override default Creator keys (family, task, asset,
+ ...) but do it wisely if you need.
+
+ All of keys will be converted into 3 variants unchanged, capitalized
+ and all upper letters. Because of that are all keys lowered.
+
+ This method can be modified to prefill some values just keep in mind it
+ is class method.
+
+ Returns:
+ dict: Fill data for subset name template.
+ """
+ dynamic_data = {}
+ for key in cls.dynamic_subset_keys:
+ key = key.lower()
+ dynamic_data[key] = "{" + key + "}"
+ return dynamic_data
+
+ @classmethod
+ def get_subset_name(
+ cls, variant, task_name, asset_id, project_name, host_name=None
+ ):
+ """Return subset name created with entered arguments.
+
+ Logic extracted from Creator tool. This method should give ability
+ to get subset name without the tool.
+
+ TODO: Maybe change `variant` variable.
+
+ By default is output concatenated family with user text.
+
+ Args:
+ variant (str): What is entered by user in creator tool.
+ task_name (str): Context's task name.
+ asset_id (ObjectId): Mongo ID of context's asset.
+ project_name (str): Context's project name.
+ host_name (str): Name of host.
+
+ Returns:
+ str: Formatted subset name with entered arguments. Should match
+ config's logic.
+ """
+
+ dynamic_data = cls.get_dynamic_data(
+ variant, task_name, asset_id, project_name, host_name
+ )
+
+ return get_subset_name(
+ cls.family,
+ variant,
+ task_name,
+ asset_id,
+ project_name,
+ host_name,
+ dynamic_data=dynamic_data
+ )
+
+
+def legacy_create(Creator, name, asset, options=None, data=None):
+ """Create a new instance
+
+ Associate nodes with a subset and family. These nodes are later
+ validated, according to their `family`, and integrated into the
+ shared environment, relative their `subset`.
+
+ Data relative each family, along with default data, are imprinted
+ into the resulting objectSet. This data is later used by extractors
+ and finally asset browsers to help identify the origin of the asset.
+
+ Arguments:
+ Creator (Creator): Class of creator
+ name (str): Name of subset
+ asset (str): Name of asset
+ options (dict, optional): Additional options from GUI
+ data (dict, optional): Additional data from GUI
+
+ Raises:
+ NameError on `subset` already exists
+ KeyError on invalid dynamic property
+ RuntimeError on host error
+
+ Returns:
+ Name of instance
+
+ """
+ from avalon.api import registered_host
+ host = registered_host()
+ plugin = Creator(name, asset, options, data)
+
+ if plugin.maintain_selection is True:
+ with host.maintained_selection():
+ print("Running %s with maintained selection" % plugin)
+ instance = plugin.process()
+ return instance
+
+ print("Running %s" % plugin)
+ instance = plugin.process()
+ return instance
diff --git a/openpype/plugin.py b/openpype/plugin.py
index 45c9a08209..3569936dac 100644
--- a/openpype/plugin.py
+++ b/openpype/plugin.py
@@ -3,79 +3,12 @@ import os
import pyblish.api
import avalon.api
-from openpype.lib import get_subset_name
-
ValidatePipelineOrder = pyblish.api.ValidatorOrder + 0.05
ValidateContentsOrder = pyblish.api.ValidatorOrder + 0.1
ValidateSceneOrder = pyblish.api.ValidatorOrder + 0.2
ValidateMeshOrder = pyblish.api.ValidatorOrder + 0.3
-class PypeCreatorMixin:
- """Helper to override avalon's default class methods.
-
- Mixin class must be used as first in inheritance order to override methods.
- """
- dynamic_subset_keys = []
-
- @classmethod
- def get_dynamic_data(
- cls, variant, task_name, asset_id, project_name, host_name
- ):
- """Return dynamic data for current Creator plugin.
-
- By default return keys from `dynamic_subset_keys` attribute as mapping
- to keep formatted template unchanged.
-
- ```
- dynamic_subset_keys = ["my_key"]
- ---
- output = {
- "my_key": "{my_key}"
- }
- ```
-
- Dynamic keys may override default Creator keys (family, task, asset,
- ...) but do it wisely if you need.
-
- All of keys will be converted into 3 variants unchanged, capitalized
- and all upper letters. Because of that are all keys lowered.
-
- This method can be modified to prefill some values just keep in mind it
- is class method.
-
- Returns:
- dict: Fill data for subset name template.
- """
- dynamic_data = {}
- for key in cls.dynamic_subset_keys:
- key = key.lower()
- dynamic_data[key] = "{" + key + "}"
- return dynamic_data
-
- @classmethod
- def get_subset_name(
- cls, variant, task_name, asset_id, project_name, host_name=None
- ):
- dynamic_data = cls.get_dynamic_data(
- variant, task_name, asset_id, project_name, host_name
- )
-
- return get_subset_name(
- cls.family,
- variant,
- task_name,
- asset_id,
- project_name,
- host_name,
- dynamic_data=dynamic_data
- )
-
-
-class Creator(PypeCreatorMixin, avalon.api.Creator):
- pass
-
-
class ContextPlugin(pyblish.api.ContextPlugin):
def process(cls, *args, **kwargs):
super(ContextPlugin, cls).process(cls, *args, **kwargs)
diff --git a/openpype/plugins/load/delete_old_versions.py b/openpype/plugins/load/delete_old_versions.py
index b2f2c88975..e8612745fb 100644
--- a/openpype/plugins/load/delete_old_versions.py
+++ b/openpype/plugins/load/delete_old_versions.py
@@ -5,10 +5,10 @@ import uuid
import clique
from pymongo import UpdateOne
import ftrack_api
+import qargparse
from Qt import QtWidgets, QtCore
from avalon import api, style
-from avalon.vendor import qargparse
from avalon.api import AvalonMongoDB
import avalon.pipeline
from openpype.api import Anatomy
diff --git a/openpype/plugins/publish/integrate_hero_version.py b/openpype/plugins/publish/integrate_hero_version.py
index ec836954e8..60245314f4 100644
--- a/openpype/plugins/publish/integrate_hero_version.py
+++ b/openpype/plugins/publish/integrate_hero_version.py
@@ -7,7 +7,7 @@ import shutil
from pymongo import InsertOne, ReplaceOne
import pyblish.api
from avalon import api, io, schema
-from avalon.vendor import filelink
+from openpype.lib import create_hard_link
class IntegrateHeroVersion(pyblish.api.InstancePlugin):
@@ -518,7 +518,7 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin):
# First try hardlink and copy if paths are cross drive
try:
- filelink.create(src_path, dst_path, filelink.HARDLINK)
+ create_hard_link(src_path, dst_path)
# Return when successful
return
diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py
index 581902205f..8979e4ac05 100644
--- a/openpype/plugins/publish/integrate_new.py
+++ b/openpype/plugins/publish/integrate_new.py
@@ -13,12 +13,14 @@ from pymongo import DeleteOne, InsertOne
import pyblish.api
from avalon import io
from avalon.api import format_template_with_optional_keys
-from avalon.vendor import filelink
import openpype.api
from datetime import datetime
# from pype.modules import ModulesManager
from openpype.lib.profiles_filtering import filter_profiles
-from openpype.lib import prepare_template_data
+from openpype.lib import (
+ prepare_template_data,
+ create_hard_link
+)
# this is needed until speedcopy for linux is fixed
if sys.platform == "win32":
@@ -196,11 +198,15 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin):
"short": task_code
}
- else:
+ elif "task" in anatomy_data:
# Just set 'task_name' variable to context task
task_name = anatomy_data["task"]["name"]
task_type = anatomy_data["task"]["type"]
+ else:
+ task_name = None
+ task_type = None
+
# Fill family in anatomy data
anatomy_data["family"] = instance.data.get("family")
@@ -737,7 +743,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin):
self.log.critical("An unexpected error occurred.")
six.reraise(*sys.exc_info())
- filelink.create(src, dst, filelink.HARDLINK)
+ create_hard_link(src, dst)
def get_subset(self, asset, instance):
subset_name = instance.data["subset"]
@@ -823,8 +829,12 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin):
# - is there a chance that task name is not filled in anatomy
# data?
# - should we use context task in that case?
- task_name = instance.data["anatomyData"]["task"]["name"]
- task_type = instance.data["anatomyData"]["task"]["type"]
+ anatomy_data = instance.data["anatomyData"]
+ task_name = None
+ task_type = None
+ if "task" in anatomy_data:
+ task_name = anatomy_data["task"]["name"]
+ task_type = anatomy_data["task"]["type"]
filtering_criteria = {
"families": instance.data["family"],
"hosts": instance.context.data["hostName"],
diff --git a/openpype/settings/defaults/project_anatomy/imageio.json b/openpype/settings/defaults/project_anatomy/imageio.json
index 1c86509155..7a3f49452e 100644
--- a/openpype/settings/defaults/project_anatomy/imageio.json
+++ b/openpype/settings/defaults/project_anatomy/imageio.json
@@ -185,8 +185,8 @@
"linux": []
},
"renderSpace": "ACEScg",
- "viewName": "ACES 1.0 SDR-video",
- "displayName": "sRGB"
+ "displayName": "sRGB",
+ "viewName": "ACES 1.0 SDR-video"
},
"colorManagementPreference": {
"configFilePath": {
diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json
index 7aff6d0b30..231c3ec04d 100644
--- a/openpype/settings/defaults/project_settings/deadline.json
+++ b/openpype/settings/defaults/project_settings/deadline.json
@@ -15,33 +15,6 @@
"deadline"
]
},
- "ProcessSubmittedJobOnFarm": {
- "enabled": true,
- "deadline_department": "",
- "deadline_pool": "",
- "deadline_group": "",
- "deadline_chunk_size": 1,
- "deadline_priority": 50,
- "publishing_script": "",
- "skip_integration_repre_list": [],
- "aov_filter": {
- "maya": [
- ".+(?:\\.|_)([Bb]eauty)(?:\\.|_).*"
- ],
- "nuke": [
- ".*"
- ],
- "aftereffects": [
- ".*"
- ],
- "celaction": [
- ".*"
- ],
- "harmony": [
- ".*"
- ]
- }
- },
"MayaSubmitDeadline": {
"enabled": true,
"optional": false,
@@ -95,6 +68,33 @@
"group": "",
"department": "",
"multiprocess": true
+ },
+ "ProcessSubmittedJobOnFarm": {
+ "enabled": true,
+ "deadline_department": "",
+ "deadline_pool": "",
+ "deadline_group": "",
+ "deadline_chunk_size": 1,
+ "deadline_priority": 50,
+ "publishing_script": "",
+ "skip_integration_repre_list": [],
+ "aov_filter": {
+ "maya": [
+ ".+(?:\\.|_)([Bb]eauty)(?:\\.|_).*"
+ ],
+ "nuke": [
+ ".*"
+ ],
+ "aftereffects": [
+ ".*"
+ ],
+ "celaction": [
+ ".*"
+ ],
+ "harmony": [
+ ".*"
+ ]
+ }
}
}
}
\ No newline at end of file
diff --git a/openpype/settings/defaults/project_settings/photoshop.json b/openpype/settings/defaults/project_settings/photoshop.json
index 118b9c721e..4bfc0e04e7 100644
--- a/openpype/settings/defaults/project_settings/photoshop.json
+++ b/openpype/settings/defaults/project_settings/photoshop.json
@@ -44,4 +44,4 @@
"create_first_version": false,
"custom_templates": []
}
-}
+}
\ No newline at end of file
diff --git a/openpype/style/style.css b/openpype/style/style.css
index ba40b780ab..5586cf766d 100644
--- a/openpype/style/style.css
+++ b/openpype/style/style.css
@@ -1266,6 +1266,12 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {
font-size: 15pt;
font-weight: 750;
}
+#ChooseProjectFrame {
+ border-radius: 10px;
+}
+#ChooseProjectView {
+ background: transparent;
+}
/* Globally used names */
#Separator {
diff --git a/openpype/tests/test_avalon_plugin_presets.py b/openpype/tests/test_avalon_plugin_presets.py
index ec21385d23..f1b1a94713 100644
--- a/openpype/tests/test_avalon_plugin_presets.py
+++ b/openpype/tests/test_avalon_plugin_presets.py
@@ -1,8 +1,9 @@
import avalon.api as api
import openpype
+from openpype.pipeline import LegacyCreator
-class MyTestCreator(api.Creator):
+class MyTestCreator(LegacyCreator):
my_test_property = "A"
@@ -26,8 +27,8 @@ def test_avalon_plugin_presets(monkeypatch, printer):
openpype.install()
api.register_host(Test())
- api.register_plugin(api.Creator, MyTestCreator)
- plugins = api.discover(api.Creator)
+ api.register_plugin(LegacyCreator, MyTestCreator)
+ plugins = api.discover(LegacyCreator)
printer("Test if we got our test plugin")
assert MyTestCreator in plugins
for p in plugins:
diff --git a/openpype/tools/creator/model.py b/openpype/tools/creator/model.py
index 6907e8f0aa..ef61c6e0f0 100644
--- a/openpype/tools/creator/model.py
+++ b/openpype/tools/creator/model.py
@@ -2,6 +2,7 @@ import uuid
from Qt import QtGui, QtCore
from avalon import api
+from openpype.pipeline import LegacyCreator
from . constants import (
FAMILY_ROLE,
@@ -21,7 +22,7 @@ class CreatorsModel(QtGui.QStandardItemModel):
self._creators_by_id = {}
items = []
- creators = api.discover(api.Creator)
+ creators = api.discover(LegacyCreator)
for creator in creators:
item_id = str(uuid.uuid4())
self._creators_by_id[item_id] = creator
diff --git a/openpype/tools/creator/widgets.py b/openpype/tools/creator/widgets.py
index 9dd435c1cc..43df08496b 100644
--- a/openpype/tools/creator/widgets.py
+++ b/openpype/tools/creator/widgets.py
@@ -3,9 +3,8 @@ import inspect
from Qt import QtWidgets, QtCore, QtGui
-from avalon.vendor import qtawesome
+import qtawesome
-from openpype import style
from openpype.pipeline.create import SUBSET_NAME_ALLOWED_SYMBOLS
from openpype.tools.utils import ErrorMessageBox
diff --git a/openpype/tools/creator/window.py b/openpype/tools/creator/window.py
index f1d0849dfe..51cc66e715 100644
--- a/openpype/tools/creator/window.py
+++ b/openpype/tools/creator/window.py
@@ -9,7 +9,12 @@ from avalon import api, io
from openpype import style
from openpype.api import get_current_project_settings
from openpype.tools.utils.lib import qt_app_context
-from openpype.pipeline.create import SUBSET_NAME_ALLOWED_SYMBOLS
+from openpype.pipeline.create import (
+ SUBSET_NAME_ALLOWED_SYMBOLS,
+ legacy_create,
+ CreatorError,
+ LegacyCreator,
+)
from .model import CreatorsModel
from .widgets import (
@@ -422,7 +427,7 @@ class CreatorWindow(QtWidgets.QDialog):
error_info = None
try:
- api.create(
+ legacy_create(
creator_plugin,
subset_name,
asset_name,
@@ -430,7 +435,7 @@ class CreatorWindow(QtWidgets.QDialog):
data={"variant": variant}
)
- except api.CreatorError as exc:
+ except CreatorError as exc:
self.echo("Creator error: {}".format(str(exc)))
error_info = (str(exc), None)
@@ -486,7 +491,7 @@ def show(debug=False, parent=None):
if debug:
from avalon import mock
for creator in mock.creators:
- api.register_plugin(api.Creator, creator)
+ api.register_plugin(LegacyCreator, creator)
import traceback
sys.excepthook = lambda typ, val, tb: traceback.print_last()
diff --git a/openpype/tools/launcher/lib.py b/openpype/tools/launcher/lib.py
index b4e6a0c3e9..68c759f295 100644
--- a/openpype/tools/launcher/lib.py
+++ b/openpype/tools/launcher/lib.py
@@ -16,7 +16,7 @@ provides a bridge between the file-based project inventory and configuration.
import os
from Qt import QtGui
-from avalon.vendor import qtawesome
+import qtawesome
from openpype.api import resources
ICON_CACHE = {}
diff --git a/openpype/tools/launcher/models.py b/openpype/tools/launcher/models.py
index effa283318..9036c9cbd5 100644
--- a/openpype/tools/launcher/models.py
+++ b/openpype/tools/launcher/models.py
@@ -7,7 +7,7 @@ import time
import appdirs
from Qt import QtCore, QtGui
-from avalon.vendor import qtawesome
+import qtawesome
from avalon import api
from openpype.lib import JSONSettingRegistry
from openpype.lib.applications import (
diff --git a/openpype/tools/launcher/widgets.py b/openpype/tools/launcher/widgets.py
index 30e6531843..62599664fe 100644
--- a/openpype/tools/launcher/widgets.py
+++ b/openpype/tools/launcher/widgets.py
@@ -2,7 +2,7 @@ import copy
import time
import collections
from Qt import QtWidgets, QtCore, QtGui
-from avalon.vendor import qtawesome
+import qtawesome
from openpype.tools.flickcharm import FlickCharm
from openpype.tools.utils.assets_widget import SingleSelectAssetsWidget
diff --git a/openpype/tools/launcher/window.py b/openpype/tools/launcher/window.py
index b5b6368865..d80b3eabf0 100644
--- a/openpype/tools/launcher/window.py
+++ b/openpype/tools/launcher/window.py
@@ -8,7 +8,7 @@ from avalon.api import AvalonMongoDB
from openpype import style
from openpype.api import resources
-from avalon.vendor import qtawesome
+import qtawesome
from .models import (
LauncherModel,
ProjectModel
diff --git a/openpype/tools/loader/lib.py b/openpype/tools/loader/lib.py
index 180dee3eb5..28e94237ec 100644
--- a/openpype/tools/loader/lib.py
+++ b/openpype/tools/loader/lib.py
@@ -1,7 +1,7 @@
import inspect
from Qt import QtGui
+import qtawesome
-from avalon.vendor import qtawesome
from openpype.tools.utils.widgets import (
OptionalAction,
OptionDialog
diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py
index 10b22d0e17..baee569239 100644
--- a/openpype/tools/loader/model.py
+++ b/openpype/tools/loader/model.py
@@ -8,8 +8,8 @@ from avalon import (
schema
)
from Qt import QtCore, QtGui
+import qtawesome
-from avalon.vendor import qtawesome
from avalon.lib import HeroVersionType
from openpype.tools.utils.models import TreeModel, Item
diff --git a/openpype/tools/mayalookassigner/models.py b/openpype/tools/mayalookassigner/models.py
index 39cab83c61..386b7d7e1e 100644
--- a/openpype/tools/mayalookassigner/models.py
+++ b/openpype/tools/mayalookassigner/models.py
@@ -1,8 +1,8 @@
from collections import defaultdict
from Qt import QtCore
+import qtawesome
-from avalon.vendor import qtawesome
from avalon.style import colors
from openpype.tools.utils import models
diff --git a/openpype/tools/project_manager/project_manager/style.py b/openpype/tools/project_manager/project_manager/style.py
index d24fc7102f..4405d05960 100644
--- a/openpype/tools/project_manager/project_manager/style.py
+++ b/openpype/tools/project_manager/project_manager/style.py
@@ -1,7 +1,7 @@
import os
-from Qt import QtCore, QtGui
+from Qt import QtGui
-from avalon.vendor import qtawesome
+import qtawesome
from openpype.tools.utils import paint_image_with_color
diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py
index 5a84b1d8ca..6707feac9c 100644
--- a/openpype/tools/publisher/control.py
+++ b/openpype/tools/publisher/control.py
@@ -873,8 +873,6 @@ class PublisherController:
"""
for idx, plugin in enumerate(self.publish_plugins):
self._publish_progress = idx
- # Add plugin to publish report
- self._publish_report.add_plugin_iter(plugin, self._publish_context)
# Reset current plugin validations error
self._publish_current_plugin_validation_errors = None
@@ -902,6 +900,9 @@ class PublisherController:
):
yield MainThreadItem(self.stop_publish)
+ # Add plugin to publish report
+ self._publish_report.add_plugin_iter(plugin, self._publish_context)
+
# Trigger callback that new plugin is going to be processed
self._trigger_callbacks(
self._publish_plugin_changed_callback_refs, plugin
diff --git a/openpype/tools/publisher/widgets/create_dialog.py b/openpype/tools/publisher/widgets/create_dialog.py
index c5b77eca8b..6396b77901 100644
--- a/openpype/tools/publisher/widgets/create_dialog.py
+++ b/openpype/tools/publisher/widgets/create_dialog.py
@@ -8,12 +8,14 @@ try:
except Exception:
commonmark = None
from Qt import QtWidgets, QtCore, QtGui
-
+from openpype.lib import TaskNotSetError
from openpype.pipeline.create import (
CreatorError,
SUBSET_NAME_ALLOWED_SYMBOLS
)
+from openpype.tools.utils import ErrorMessageBox
+
from .widgets import IconValuePixmapLabel
from .assets_widget import CreateDialogAssetsWidget
from .tasks_widget import CreateDialogTasksWidget
@@ -27,7 +29,7 @@ from ..constants import (
SEPARATORS = ("---separator---", "---")
-class CreateErrorMessageBox(QtWidgets.QDialog):
+class CreateErrorMessageBox(ErrorMessageBox):
def __init__(
self,
creator_label,
@@ -35,24 +37,38 @@ class CreateErrorMessageBox(QtWidgets.QDialog):
asset_name,
exc_msg,
formatted_traceback,
- parent=None
+ parent
):
- super(CreateErrorMessageBox, self).__init__(parent)
- self.setWindowTitle("Creation failed")
- self.setFocusPolicy(QtCore.Qt.StrongFocus)
- if not parent:
- self.setWindowFlags(
- self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint
- )
+ self._creator_label = creator_label
+ self._subset_name = subset_name
+ self._asset_name = asset_name
+ self._exc_msg = exc_msg
+ self._formatted_traceback = formatted_traceback
+ super(CreateErrorMessageBox, self).__init__("Creation failed", parent)
- body_layout = QtWidgets.QVBoxLayout(self)
-
- main_label = (
+ def _create_top_widget(self, parent_widget):
+ label_widget = QtWidgets.QLabel(parent_widget)
+ label_widget.setText(
"Failed to create"
)
- main_label_widget = QtWidgets.QLabel(main_label, self)
- body_layout.addWidget(main_label_widget)
+ return label_widget
+ def _get_report_data(self):
+ report_message = (
+ "{creator}: Failed to create Subset: \"{subset}\""
+ " in Asset: \"{asset}\""
+ "\n\nError: {message}"
+ ).format(
+ creator=self._creator_label,
+ subset=self._subset_name,
+ asset=self._asset_name,
+ message=self._exc_msg,
+ )
+ if self._formatted_traceback:
+ report_message += "\n\n{}".format(self._formatted_traceback)
+ return [report_message]
+
+ def _create_content(self, content_layout):
item_name_template = (
"Creator: {}
"
"Subset: {}
"
@@ -61,48 +77,29 @@ class CreateErrorMessageBox(QtWidgets.QDialog):
exc_msg_template = "{}"
line = self._create_line()
- body_layout.addWidget(line)
+ content_layout.addWidget(line)
- item_name = item_name_template.format(
- creator_label, subset_name, asset_name
- )
- item_name_widget = QtWidgets.QLabel(
- item_name.replace("\n", "
"), self
- )
- body_layout.addWidget(item_name_widget)
-
- exc_msg = exc_msg_template.format(exc_msg.replace("\n", "
"))
- message_label_widget = QtWidgets.QLabel(exc_msg, self)
- body_layout.addWidget(message_label_widget)
-
- if formatted_traceback:
- tb_widget = QtWidgets.QLabel(
- formatted_traceback.replace("\n", "
"), self
+ item_name_widget = QtWidgets.QLabel(self)
+ item_name_widget.setText(
+ item_name_template.format(
+ self._creator_label, self._subset_name, self._asset_name
)
- tb_widget.setTextInteractionFlags(
- QtCore.Qt.TextBrowserInteraction
- )
- body_layout.addWidget(tb_widget)
-
- footer_widget = QtWidgets.QWidget(self)
- footer_layout = QtWidgets.QHBoxLayout(footer_widget)
- button_box = QtWidgets.QDialogButtonBox(QtCore.Qt.Vertical)
- button_box.setStandardButtons(
- QtWidgets.QDialogButtonBox.StandardButton.Ok
)
- button_box.accepted.connect(self._on_accept)
- footer_layout.addWidget(button_box, alignment=QtCore.Qt.AlignRight)
- body_layout.addWidget(footer_widget)
+ content_layout.addWidget(item_name_widget)
- def _on_accept(self):
- self.close()
+ message_label_widget = QtWidgets.QLabel(self)
+ message_label_widget.setText(
+ exc_msg_template.format(self.convert_text_for_html(self._exc_msg))
+ )
+ content_layout.addWidget(message_label_widget)
- def _create_line(self):
- line = QtWidgets.QFrame(self)
- line.setFixedHeight(2)
- line.setFrameShape(QtWidgets.QFrame.HLine)
- line.setFrameShadow(QtWidgets.QFrame.Sunken)
- return line
+ if self._formatted_traceback:
+ line_widget = self._create_line()
+ tb_widget = self._create_traceback_widget(
+ self._formatted_traceback
+ )
+ content_layout.addWidget(line_widget)
+ content_layout.addWidget(tb_widget)
# TODO add creator identifier/label to details
@@ -201,7 +198,7 @@ class CreateDialog(QtWidgets.QDialog):
self._prereq_available = False
- self.message_dialog = None
+ self._message_dialog = None
name_pattern = "^[{}]*$".format(SUBSET_NAME_ALLOWED_SYMBOLS)
self._name_pattern = name_pattern
@@ -566,10 +563,9 @@ class CreateDialog(QtWidgets.QDialog):
if variant_value is None:
variant_value = self.variant_input.text()
- match = self._compiled_name_pattern.match(variant_value)
- valid = bool(match)
- self.create_btn.setEnabled(valid)
- if not valid:
+ self.create_btn.setEnabled(True)
+ if not self._compiled_name_pattern.match(variant_value):
+ self.create_btn.setEnabled(False)
self._set_variant_state_property("invalid")
self.subset_name_input.setText("< Invalid variant >")
return
@@ -579,9 +575,16 @@ class CreateDialog(QtWidgets.QDialog):
asset_doc = copy.deepcopy(self._asset_doc)
# Calculate subset name with Creator plugin
- subset_name = self._selected_creator.get_subset_name(
- variant_value, task_name, asset_doc, project_name
- )
+ try:
+ subset_name = self._selected_creator.get_subset_name(
+ variant_value, task_name, asset_doc, project_name
+ )
+ except TaskNotSetError:
+ self.create_btn.setEnabled(False)
+ self._set_variant_state_property("invalid")
+ self.subset_name_input.setText("< Missing task >")
+ return
+
self.subset_name_input.setText(subset_name)
self._validate_subset_name(subset_name, variant_value)
@@ -694,14 +697,18 @@ class CreateDialog(QtWidgets.QDialog):
"family": family
}
- error_info = None
+ error_msg = None
+ formatted_traceback = None
try:
self.controller.create(
- creator_identifier, subset_name, instance_data, pre_create_data
+ creator_identifier,
+ subset_name,
+ instance_data,
+ pre_create_data
)
except CreatorError as exc:
- error_info = (str(exc), None)
+ error_msg = str(exc)
# Use bare except because some hosts raise their exceptions that
# do not inherit from python's `BaseException`
@@ -710,12 +717,17 @@ class CreateDialog(QtWidgets.QDialog):
formatted_traceback = "".join(traceback.format_exception(
exc_type, exc_value, exc_traceback
))
- error_info = (str(exc_value), formatted_traceback)
+ error_msg = str(exc_value)
- if error_info:
+ if error_msg is not None:
box = CreateErrorMessageBox(
- creator_label, subset_name, asset_name, *error_info
+ creator_label,
+ subset_name,
+ asset_name,
+ error_msg,
+ formatted_traceback,
+ parent=self
)
box.show()
# Store dialog so is not garbage collected before is shown
- self.message_dialog = box
+ self._message_dialog = box
diff --git a/openpype/tools/publisher/widgets/list_view_widgets.py b/openpype/tools/publisher/widgets/list_view_widgets.py
index 23a86cd070..6bddaf66c8 100644
--- a/openpype/tools/publisher/widgets/list_view_widgets.py
+++ b/openpype/tools/publisher/widgets/list_view_widgets.py
@@ -467,12 +467,22 @@ class InstanceListView(AbstractInstanceView):
else:
active = False
+ group_names = set()
for instance_id in selected_instance_ids:
widget = self._widgets_by_id.get(instance_id)
- if widget is not None:
- widget.set_active(active)
+ if widget is None:
+ continue
+
+ widget.set_active(active)
+ group_name = self._group_by_instance_id.get(instance_id)
+ if group_name is not None:
+ group_names.add(group_name)
+
+ for group_name in group_names:
+ self._update_group_checkstate(group_name)
def _update_group_checkstate(self, group_name):
+ """Update checkstate of one group."""
widget = self._group_widgets.get(group_name)
if widget is None:
return
diff --git a/openpype/tools/publisher/widgets/tasks_widget.py b/openpype/tools/publisher/widgets/tasks_widget.py
index a0b3a340ae..2d1cc017af 100644
--- a/openpype/tools/publisher/widgets/tasks_widget.py
+++ b/openpype/tools/publisher/widgets/tasks_widget.py
@@ -17,9 +17,10 @@ class TasksModel(QtGui.QStandardItemModel):
controller (PublisherController): Controller which handles creation and
publishing.
"""
- def __init__(self, controller):
+ def __init__(self, controller, allow_empty_task=False):
super(TasksModel, self).__init__()
+ self._allow_empty_task = allow_empty_task
self._controller = controller
self._items_by_name = {}
self._asset_names = []
@@ -70,8 +71,14 @@ class TasksModel(QtGui.QStandardItemModel):
task_name (str): Name of task which should be available in asset's
tasks.
"""
- task_names = self._task_names_by_asset_name.get(asset_name)
- if task_names and task_name in task_names:
+ if asset_name not in self._task_names_by_asset_name:
+ return False
+
+ if self._allow_empty_task and not task_name:
+ return True
+
+ task_names = self._task_names_by_asset_name[asset_name]
+ if task_name in task_names:
return True
return False
@@ -92,6 +99,8 @@ class TasksModel(QtGui.QStandardItemModel):
new_task_names = self.get_intersection_of_tasks(
task_names_by_asset_name
)
+ if self._allow_empty_task:
+ new_task_names.add("")
old_task_names = set(self._items_by_name.keys())
if new_task_names == old_task_names:
return
@@ -111,7 +120,9 @@ class TasksModel(QtGui.QStandardItemModel):
item.setData(task_name, TASK_NAME_ROLE)
self._items_by_name[task_name] = item
new_items.append(item)
- root_item.appendRows(new_items)
+
+ if new_items:
+ root_item.appendRows(new_items)
def headerData(self, section, orientation, role=None):
if role is None:
diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py
index fb1f0e54aa..4e55e86491 100644
--- a/openpype/tools/publisher/widgets/widgets.py
+++ b/openpype/tools/publisher/widgets/widgets.py
@@ -4,9 +4,9 @@ import re
import copy
import collections
from Qt import QtWidgets, QtCore, QtGui
+import qtawesome
-from avalon.vendor import qtawesome
-
+from openpype.lib import TaskNotSetError
from openpype.widgets.attribute_defs import create_widget_for_attr_def
from openpype.tools import resources
from openpype.tools.flickcharm import FlickCharm
@@ -471,6 +471,28 @@ class AssetsField(BaseClickableFrame):
self.set_selected_items(self._origin_value)
+class TasksComboboxProxy(QtCore.QSortFilterProxyModel):
+ def __init__(self, *args, **kwargs):
+ super(TasksComboboxProxy, self).__init__(*args, **kwargs)
+ self._filter_empty = False
+
+ def set_filter_empty(self, filter_empty):
+ if self._filter_empty is filter_empty:
+ return
+ self._filter_empty = filter_empty
+ self.invalidate()
+
+ def filterAcceptsRow(self, source_row, parent_index):
+ if self._filter_empty:
+ model = self.sourceModel()
+ source_index = model.index(
+ source_row, self.filterKeyColumn(), parent_index
+ )
+ if not source_index.data(QtCore.Qt.DisplayRole):
+ return False
+ return True
+
+
class TasksCombobox(QtWidgets.QComboBox):
"""Combobox to show tasks for selected instances.
@@ -490,13 +512,16 @@ class TasksCombobox(QtWidgets.QComboBox):
delegate = QtWidgets.QStyledItemDelegate()
self.setItemDelegate(delegate)
- model = TasksModel(controller)
- self.setModel(model)
+ model = TasksModel(controller, True)
+ proxy_model = TasksComboboxProxy()
+ proxy_model.setSourceModel(model)
+ self.setModel(proxy_model)
self.currentIndexChanged.connect(self._on_index_change)
self._delegate = delegate
self._model = model
+ self._proxy_model = proxy_model
self._origin_value = []
self._origin_selection = []
self._selected_items = []
@@ -507,6 +532,14 @@ class TasksCombobox(QtWidgets.QComboBox):
self._text = None
+ def set_invalid_empty_task(self, invalid=True):
+ self._proxy_model.set_filter_empty(invalid)
+ if invalid:
+ self._set_is_valid(False)
+ self.set_text("< One or more subsets require Task selected >")
+ else:
+ self.set_text(None)
+
def set_multiselection_text(self, text):
"""Change text shown when multiple different tasks are in context."""
self._multiselection_text = text
@@ -596,6 +629,8 @@ class TasksCombobox(QtWidgets.QComboBox):
self._ignore_index_change = True
self._model.set_asset_names(asset_names)
+ self._proxy_model.set_filter_empty(False)
+ self._proxy_model.sort(0)
self._ignore_index_change = False
@@ -641,6 +676,9 @@ class TasksCombobox(QtWidgets.QComboBox):
asset_task_combinations (list): List of tuples. Each item in
the list contain asset name and task name.
"""
+ self._proxy_model.set_filter_empty(False)
+ self._proxy_model.sort(0)
+
if asset_task_combinations is None:
asset_task_combinations = []
@@ -932,7 +970,7 @@ class GlobalAttrsWidget(QtWidgets.QWidget):
family_value_widget.set_value()
subset_value_widget.set_value()
- submit_btn = QtWidgets.QPushButton("Submit", self)
+ submit_btn = QtWidgets.QPushButton("Confirm", self)
cancel_btn = QtWidgets.QPushButton("Cancel", self)
submit_btn.setEnabled(False)
cancel_btn.setEnabled(False)
@@ -998,7 +1036,33 @@ class GlobalAttrsWidget(QtWidgets.QWidget):
project_name = self.controller.project_name
subset_names = set()
+ invalid_tasks = False
for instance in self._current_instances:
+ new_variant_value = instance.get("variant")
+ new_asset_name = instance.get("asset")
+ new_task_name = instance.get("task")
+ if variant_value is not None:
+ new_variant_value = variant_value
+
+ if asset_name is not None:
+ new_asset_name = asset_name
+
+ if task_name is not None:
+ new_task_name = task_name
+
+ asset_doc = asset_docs_by_name[new_asset_name]
+
+ try:
+ new_subset_name = instance.creator.get_subset_name(
+ new_variant_value, new_task_name, asset_doc, project_name
+ )
+ except TaskNotSetError:
+ invalid_tasks = True
+ instance.set_task_invalid(True)
+ subset_names.add(instance["subset"])
+ continue
+
+ subset_names.add(new_subset_name)
if variant_value is not None:
instance["variant"] = variant_value
@@ -1007,25 +1071,18 @@ class GlobalAttrsWidget(QtWidgets.QWidget):
instance.set_asset_invalid(False)
if task_name is not None:
- instance["task"] = task_name
+ instance["task"] = task_name or None
instance.set_task_invalid(False)
- new_variant_value = instance.get("variant")
- new_asset_name = instance.get("asset")
- new_task_name = instance.get("task")
-
- asset_doc = asset_docs_by_name[new_asset_name]
-
- new_subset_name = instance.creator.get_subset_name(
- new_variant_value, new_task_name, asset_doc, project_name
- )
- subset_names.add(new_subset_name)
instance["subset"] = new_subset_name
+ if invalid_tasks:
+ self.task_value_widget.set_invalid_empty_task()
+
self.subset_value_widget.set_value(subset_names)
self._set_btns_enabled(False)
- self._set_btns_visible(False)
+ self._set_btns_visible(invalid_tasks)
self.instance_context_changed.emit()
@@ -1098,7 +1155,7 @@ class GlobalAttrsWidget(QtWidgets.QWidget):
variants.add(instance.get("variant") or self.unknown_value)
families.add(instance.get("family") or self.unknown_value)
asset_name = instance.get("asset") or self.unknown_value
- task_name = instance.get("task") or self.unknown_value
+ task_name = instance.get("task") or ""
asset_names.add(asset_name)
asset_task_combinations.append((asset_name, task_name))
subset_names.add(instance.get("subset") or self.unknown_value)
diff --git a/openpype/tools/pyblish_pype/model.py b/openpype/tools/pyblish_pype/model.py
index 0faadb5940..2931a379b3 100644
--- a/openpype/tools/pyblish_pype/model.py
+++ b/openpype/tools/pyblish_pype/model.py
@@ -29,10 +29,9 @@ import pyblish
from . import settings, util
from .awesome import tags as awesome
-import Qt
from Qt import QtCore, QtGui
+import qtawesome
from six import text_type
-from .vendor import qtawesome
from .constants import PluginStates, InstanceStates, GroupStates, Roles
from openpype.api import get_system_settings
diff --git a/openpype/tools/sceneinventory/model.py b/openpype/tools/sceneinventory/model.py
index 6435e5c488..cba60be355 100644
--- a/openpype/tools/sceneinventory/model.py
+++ b/openpype/tools/sceneinventory/model.py
@@ -4,9 +4,9 @@ import logging
from collections import defaultdict
from Qt import QtCore, QtGui
-from avalon import api, io, style, schema
-from avalon.vendor import qtawesome
+import qtawesome
+from avalon import api, io, style, schema
from avalon.lib import HeroVersionType
from openpype.tools.utils.models import TreeModel, Item
diff --git a/openpype/tools/sceneinventory/switch_dialog.py b/openpype/tools/sceneinventory/switch_dialog.py
index 4946c073d4..93ea68beb4 100644
--- a/openpype/tools/sceneinventory/switch_dialog.py
+++ b/openpype/tools/sceneinventory/switch_dialog.py
@@ -1,9 +1,9 @@
import collections
import logging
from Qt import QtWidgets, QtCore
+import qtawesome
from avalon import io, api, pipeline
-from avalon.vendor import qtawesome
from .widgets import (
ButtonWithMenu,
diff --git a/openpype/tools/sceneinventory/view.py b/openpype/tools/sceneinventory/view.py
index ec48b10e47..32c1883de6 100644
--- a/openpype/tools/sceneinventory/view.py
+++ b/openpype/tools/sceneinventory/view.py
@@ -3,9 +3,9 @@ import logging
from functools import partial
from Qt import QtWidgets, QtCore
+import qtawesome
from avalon import io, api, style
-from avalon.vendor import qtawesome
from avalon.lib import HeroVersionType
from openpype.modules import ModulesManager
diff --git a/openpype/tools/sceneinventory/window.py b/openpype/tools/sceneinventory/window.py
index 095d30cac0..83e4435015 100644
--- a/openpype/tools/sceneinventory/window.py
+++ b/openpype/tools/sceneinventory/window.py
@@ -2,7 +2,7 @@ import os
import sys
from Qt import QtWidgets, QtCore
-from avalon.vendor import qtawesome
+import qtawesome
from avalon import io, api
from openpype import style
diff --git a/openpype/tools/settings/settings/categories.py b/openpype/tools/settings/settings/categories.py
index 663d497c36..a5b5cd40f0 100644
--- a/openpype/tools/settings/settings/categories.py
+++ b/openpype/tools/settings/settings/categories.py
@@ -1,9 +1,9 @@
-import os
import sys
import traceback
import contextlib
from enum import Enum
from Qt import QtWidgets, QtCore
+import qtawesome
from openpype.lib import get_openpype_version
from openpype.tools.utils import set_style_property
@@ -63,7 +63,6 @@ from .item_widgets import (
PathInputWidget
)
from .color_widget import ColorWidget
-from avalon.vendor import qtawesome
class CategoryState(Enum):
diff --git a/openpype/tools/settings/settings/widgets.py b/openpype/tools/settings/settings/widgets.py
index f793aab057..577c2630ab 100644
--- a/openpype/tools/settings/settings/widgets.py
+++ b/openpype/tools/settings/settings/widgets.py
@@ -2,7 +2,7 @@ import os
import copy
import uuid
from Qt import QtWidgets, QtCore, QtGui
-from avalon.vendor import qtawesome
+import qtawesome
from avalon.mongodb import (
AvalonMongoConnection,
AvalonMongoDB
diff --git a/openpype/tools/standalonepublish/widgets/model_asset.py b/openpype/tools/standalonepublish/widgets/model_asset.py
index 60afe8f96c..6d764eff9f 100644
--- a/openpype/tools/standalonepublish/widgets/model_asset.py
+++ b/openpype/tools/standalonepublish/widgets/model_asset.py
@@ -1,8 +1,8 @@
import logging
import collections
from Qt import QtCore, QtGui
+import qtawesome
from . import TreeModel, Node
-from avalon.vendor import qtawesome
from avalon import style
diff --git a/openpype/tools/standalonepublish/widgets/model_tasks_template.py b/openpype/tools/standalonepublish/widgets/model_tasks_template.py
index 476f45391d..1f36eaa39d 100644
--- a/openpype/tools/standalonepublish/widgets/model_tasks_template.py
+++ b/openpype/tools/standalonepublish/widgets/model_tasks_template.py
@@ -1,6 +1,6 @@
from Qt import QtCore
+import qtawesome
from . import Node, TreeModel
-from avalon.vendor import qtawesome
from avalon import style
diff --git a/openpype/tools/standalonepublish/widgets/widget_asset.py b/openpype/tools/standalonepublish/widgets/widget_asset.py
index 2886d600bf..d929f227f9 100644
--- a/openpype/tools/standalonepublish/widgets/widget_asset.py
+++ b/openpype/tools/standalonepublish/widgets/widget_asset.py
@@ -1,9 +1,9 @@
import contextlib
from Qt import QtWidgets, QtCore
+import qtawesome
from openpype.tools.utils import PlaceholderLineEdit
-from avalon.vendor import qtawesome
from avalon import style
from . import RecursiveSortFilterProxyModel, AssetModel
diff --git a/openpype/tools/standalonepublish/widgets/widget_family.py b/openpype/tools/standalonepublish/widgets/widget_family.py
index ae44899a89..08cd45bbf2 100644
--- a/openpype/tools/standalonepublish/widgets/widget_family.py
+++ b/openpype/tools/standalonepublish/widgets/widget_family.py
@@ -1,14 +1,11 @@
-import os
import re
from Qt import QtWidgets, QtCore
from . import HelpRole, FamilyRole, ExistsRole, PluginRole, PluginKeyRole
from . import FamilyDescriptionWidget
-from openpype.api import (
- get_project_settings,
- Creator
-)
+from openpype.api import get_project_settings
+from openpype.pipeline import LegacyCreator
from openpype.lib import TaskNotSetError
from openpype.pipeline.create import SUBSET_NAME_ALLOWED_SYMBOLS
@@ -390,7 +387,7 @@ class FamilyWidget(QtWidgets.QWidget):
sp_settings = settings.get('standalonepublisher', {})
for key, creator_data in sp_settings.get("create", {}).items():
- creator = type(key, (Creator, ), creator_data)
+ creator = type(key, (LegacyCreator, ), creator_data)
label = creator.label or creator.family
item = QtWidgets.QListWidgetItem(label)
diff --git a/openpype/tools/standalonepublish/widgets/widget_family_desc.py b/openpype/tools/standalonepublish/widgets/widget_family_desc.py
index 8c95ddf2e4..79681615b9 100644
--- a/openpype/tools/standalonepublish/widgets/widget_family_desc.py
+++ b/openpype/tools/standalonepublish/widgets/widget_family_desc.py
@@ -1,7 +1,7 @@
-from Qt import QtWidgets, QtCore, QtGui
-from . import FamilyRole, PluginRole
-from avalon.vendor import qtawesome
import six
+from Qt import QtWidgets, QtCore, QtGui
+import qtawesome
+from . import FamilyRole, PluginRole
class FamilyDescriptionWidget(QtWidgets.QWidget):
diff --git a/openpype/tools/subsetmanager/window.py b/openpype/tools/subsetmanager/window.py
index b7430d0626..a53af52174 100644
--- a/openpype/tools/subsetmanager/window.py
+++ b/openpype/tools/subsetmanager/window.py
@@ -2,9 +2,9 @@ import os
import sys
from Qt import QtWidgets, QtCore
+import qtawesome
from avalon import api
-from avalon.vendor import qtawesome
from openpype import style
from openpype.tools.utils import PlaceholderLineEdit
diff --git a/openpype/tools/traypublisher/window.py b/openpype/tools/traypublisher/window.py
index 53f8ca450a..d0453c4f23 100644
--- a/openpype/tools/traypublisher/window.py
+++ b/openpype/tools/traypublisher/window.py
@@ -28,38 +28,50 @@ class StandaloneOverlayWidget(QtWidgets.QFrame):
super(StandaloneOverlayWidget, self).__init__(publisher_window)
self.setObjectName("OverlayFrame")
+ middle_frame = QtWidgets.QFrame(self)
+ middle_frame.setObjectName("ChooseProjectFrame")
+
+ content_widget = QtWidgets.QWidget(middle_frame)
+
# Create db connection for projects model
dbcon = AvalonMongoDB()
dbcon.install()
- header_label = QtWidgets.QLabel("Choose project", self)
+ header_label = QtWidgets.QLabel("Choose project", content_widget)
header_label.setObjectName("ChooseProjectLabel")
# Create project models and view
projects_model = ProjectModel(dbcon)
projects_proxy = ProjectSortFilterProxy()
projects_proxy.setSourceModel(projects_model)
- projects_view = QtWidgets.QListView(self)
+ projects_view = QtWidgets.QListView(content_widget)
+ projects_view.setObjectName("ChooseProjectView")
projects_view.setModel(projects_proxy)
projects_view.setEditTriggers(
QtWidgets.QAbstractItemView.NoEditTriggers
)
- confirm_btn = QtWidgets.QPushButton("Choose", self)
+ confirm_btn = QtWidgets.QPushButton("Confirm", content_widget)
btns_layout = QtWidgets.QHBoxLayout()
btns_layout.addStretch(1)
btns_layout.addWidget(confirm_btn, 0)
- layout = QtWidgets.QGridLayout(self)
- layout.addWidget(header_label, 0, 1, alignment=QtCore.Qt.AlignCenter)
- layout.addWidget(projects_view, 1, 1)
- layout.addLayout(btns_layout, 2, 1)
- layout.setColumnStretch(0, 1)
- layout.setColumnStretch(1, 0)
- layout.setColumnStretch(2, 1)
- layout.setRowStretch(0, 0)
- layout.setRowStretch(1, 1)
- layout.setRowStretch(2, 0)
+ content_layout = QtWidgets.QVBoxLayout(content_widget)
+ content_layout.setContentsMargins(0, 0, 0, 0)
+ content_layout.setSpacing(20)
+ content_layout.addWidget(header_label, 0)
+ content_layout.addWidget(projects_view, 1)
+ content_layout.addLayout(btns_layout, 0)
+
+ middle_layout = QtWidgets.QHBoxLayout(middle_frame)
+ middle_layout.setContentsMargins(30, 30, 10, 10)
+ middle_layout.addWidget(content_widget)
+
+ main_layout = QtWidgets.QHBoxLayout(self)
+ main_layout.setContentsMargins(10, 10, 10, 10)
+ main_layout.addStretch(1)
+ main_layout.addWidget(middle_frame, 2)
+ main_layout.addStretch(1)
projects_view.doubleClicked.connect(self._on_double_click)
confirm_btn.clicked.connect(self._on_confirm_click)
diff --git a/openpype/tools/utils/assets_widget.py b/openpype/tools/utils/assets_widget.py
index 17164d9e0f..d410b0f1c3 100644
--- a/openpype/tools/utils/assets_widget.py
+++ b/openpype/tools/utils/assets_widget.py
@@ -3,9 +3,9 @@ import collections
import Qt
from Qt import QtWidgets, QtCore, QtGui
+import qtawesome
from avalon import style
-from avalon.vendor import qtawesome
from openpype.style import get_objected_colors
from openpype.tools.flickcharm import FlickCharm
diff --git a/openpype/tools/utils/lib.py b/openpype/tools/utils/lib.py
index 01b9e25ef3..1cbc632804 100644
--- a/openpype/tools/utils/lib.py
+++ b/openpype/tools/utils/lib.py
@@ -4,10 +4,10 @@ import contextlib
import collections
from Qt import QtWidgets, QtCore, QtGui
+import qtawesome
import avalon.api
from avalon import style
-from avalon.vendor import qtawesome
from openpype.api import (
get_project_settings,
diff --git a/openpype/tools/utils/tasks_widget.py b/openpype/tools/utils/tasks_widget.py
index 2a8a45626c..7619f59974 100644
--- a/openpype/tools/utils/tasks_widget.py
+++ b/openpype/tools/utils/tasks_widget.py
@@ -1,7 +1,7 @@
from Qt import QtWidgets, QtCore, QtGui
+import qtawesome
from avalon import style
-from avalon.vendor import qtawesome
from .views import DeselectableTreeView
@@ -14,6 +14,7 @@ TASK_ASSIGNEE_ROLE = QtCore.Qt.UserRole + 4
class TasksModel(QtGui.QStandardItemModel):
"""A model listing the tasks combined for a list of assets"""
+
def __init__(self, dbcon, parent=None):
super(TasksModel, self).__init__(parent=parent)
self.dbcon = dbcon
diff --git a/openpype/tools/utils/widgets.py b/openpype/tools/utils/widgets.py
index a4e172ea5c..d5ae909be8 100644
--- a/openpype/tools/utils/widgets.py
+++ b/openpype/tools/utils/widgets.py
@@ -1,8 +1,8 @@
import logging
from Qt import QtWidgets, QtCore, QtGui
-
-from avalon.vendor import qtawesome, qargparse
+import qargparse
+import qtawesome
from openpype.style import (
get_objected_colors,
get_style_image_path
diff --git a/openpype/tools/workfiles/model.py b/openpype/tools/workfiles/model.py
index 3425cc3df0..b3cf5063e7 100644
--- a/openpype/tools/workfiles/model.py
+++ b/openpype/tools/workfiles/model.py
@@ -2,9 +2,9 @@ import os
import logging
from Qt import QtCore
+import qtawesome
from avalon import style
-from avalon.vendor import qtawesome
from openpype.tools.utils.models import TreeModel, Item
log = logging.getLogger(__name__)
diff --git a/openpype/vendor/python/common/qargparse.py b/openpype/vendor/python/common/qargparse.py
new file mode 100644
index 0000000000..ebde9ae76d
--- /dev/null
+++ b/openpype/vendor/python/common/qargparse.py
@@ -0,0 +1,817 @@
+"""
+NOTE: The required `Qt` module has changed to use the one that vendorized.
+ Remember to change to relative import when updating this.
+"""
+
+import re
+import logging
+
+from collections import OrderedDict as odict
+from Qt import QtCore, QtWidgets, QtGui
+import qtawesome
+
+__version__ = "0.5.2"
+_log = logging.getLogger(__name__)
+_type = type # used as argument
+
+try:
+ # Python 2
+ _basestring = basestring
+except NameError:
+ _basestring = str
+
+
+class QArgumentParser(QtWidgets.QWidget):
+ """User interface arguments
+
+ Arguments:
+ arguments (list, optional): Instances of QArgument
+ description (str, optional): Long-form text of what this parser is for
+ storage (QSettings, optional): Persistence to disk, providing
+ value() and setValue() methods
+
+ """
+
+ changed = QtCore.Signal(QtCore.QObject) # A QArgument
+
+ def __init__(self,
+ arguments=None,
+ description=None,
+ storage=None,
+ parent=None):
+ super(QArgumentParser, self).__init__(parent)
+ self.setAttribute(QtCore.Qt.WA_StyledBackground)
+
+ # Create internal settings
+ if storage is True:
+ storage = QtCore.QSettings(
+ QtCore.QSettings.IniFormat,
+ QtCore.QSettings.UserScope,
+ __name__, "QArgparse",
+ )
+
+ if storage is not None:
+ _log.info("Storing settings @ %s" % storage.fileName())
+
+ arguments = arguments or []
+
+ assert hasattr(arguments, "__iter__"), "arguments must be iterable"
+ assert isinstance(storage, (type(None), QtCore.QSettings)), (
+ "storage must be of type QSettings"
+ )
+
+ layout = QtWidgets.QGridLayout(self)
+ layout.setRowStretch(999, 1)
+
+ if description:
+ layout.addWidget(QtWidgets.QLabel(description), 0, 0, 1, 2)
+
+ self._row = 1
+ self._storage = storage
+ self._arguments = odict()
+ self._desciption = description
+
+ for arg in arguments or []:
+ self._addArgument(arg)
+
+ self.setStyleSheet(style)
+
+ def setDescription(self, text):
+ self._desciption.setText(text)
+
+ def addArgument(self, name, type=None, default=None, **kwargs):
+ # Infer type from default
+ if type is None and default is not None:
+ type = _type(default)
+
+ # Default to string
+ type = type or str
+
+ Argument = {
+ None: String,
+ int: Integer,
+ float: Float,
+ bool: Boolean,
+ str: String,
+ list: Enum,
+ tuple: Enum,
+ }.get(type, type)
+
+ arg = Argument(name, default=default, **kwargs)
+ self._addArgument(arg)
+ return arg
+
+ def _addArgument(self, arg):
+ if arg["name"] in self._arguments:
+ raise ValueError("Duplicate argument '%s'" % arg["name"])
+
+ if self._storage is not None:
+ default = self._storage.value(arg["name"])
+
+ if default:
+ if isinstance(arg, Boolean):
+ default = bool({
+ None: QtCore.Qt.Unchecked,
+
+ 0: QtCore.Qt.Unchecked,
+ 1: QtCore.Qt.Checked,
+ 2: QtCore.Qt.Checked,
+
+ "0": QtCore.Qt.Unchecked,
+ "1": QtCore.Qt.Checked,
+ "2": QtCore.Qt.Checked,
+
+ # May be stored as string, if used with IniFormat
+ "false": QtCore.Qt.Unchecked,
+ "true": QtCore.Qt.Checked,
+ }.get(default))
+
+ arg["default"] = default
+
+ arg.changed.connect(lambda: self.on_changed(arg))
+
+ label = (
+ QtWidgets.QLabel(arg["label"])
+ if arg.label
+ else QtWidgets.QLabel()
+ )
+ widget = arg.create()
+ icon = qtawesome.icon("fa.refresh", color="white")
+ reset = QtWidgets.QPushButton(icon, "") # default
+ reset.setToolTip("Reset")
+ reset.setProperty("type", "reset")
+ reset.clicked.connect(lambda: self.on_reset(arg))
+
+ # Shown on edit
+ reset.hide()
+
+ for widget in (label, widget):
+ widget.setToolTip(arg["help"])
+ widget.setObjectName(arg["name"]) # useful in CSS
+ widget.setProperty("type", type(arg).__name__)
+ widget.setAttribute(QtCore.Qt.WA_StyledBackground)
+ widget.setEnabled(arg["enabled"])
+
+ # Align label on top of row if widget is over two times heiger
+ height = (lambda w: w.sizeHint().height())
+ label_on_top = height(label) * 2 < height(widget)
+ alignment = (QtCore.Qt.AlignTop,) if label_on_top else ()
+
+ layout = self.layout()
+ layout.addWidget(label, self._row, 0, *alignment)
+ layout.addWidget(widget, self._row, 1)
+ layout.addWidget(reset, self._row, 2, *alignment)
+ layout.setColumnStretch(1, 1)
+
+ def on_changed(*_):
+ reset.setVisible(arg["edited"])
+
+ arg.changed.connect(on_changed)
+
+ self._row += 1
+ self._arguments[arg["name"]] = arg
+
+ def clear(self):
+ assert self._storage, "Cannot clear without persistent storage"
+ self._storage.clear()
+ _log.info("Clearing settings @ %s" % self._storage.fileName())
+
+ def find(self, name):
+ return self._arguments[name]
+
+ def on_reset(self, arg):
+ arg.write(arg["default"])
+
+ def on_changed(self, arg):
+ arg["edited"] = arg.read() != arg["default"]
+ self.changed.emit(arg)
+
+ # Optional PEP08 syntax
+ add_argument = addArgument
+
+
+class QArgument(QtCore.QObject):
+ """Base class of argument user interface
+ """
+ changed = QtCore.Signal()
+
+ # Provide a left-hand side label for this argument
+ label = True
+ # For defining default value for each argument type
+ default = None
+
+ def __init__(self, name, default=None, **kwargs):
+ super(QArgument, self).__init__(kwargs.pop("parent", None))
+
+ kwargs["name"] = name
+ kwargs["label"] = kwargs.get("label", camel_to_title(name))
+ kwargs["default"] = self.default if default is None else default
+ kwargs["help"] = kwargs.get("help", "")
+ kwargs["read"] = kwargs.get("read")
+ kwargs["write"] = kwargs.get("write")
+ kwargs["enabled"] = bool(kwargs.get("enabled", True))
+ kwargs["edited"] = False
+
+ self._data = kwargs
+
+ def __str__(self):
+ return self["name"]
+
+ def __repr__(self):
+ return "%s(\"%s\")" % (type(self).__name__, self["name"])
+
+ def __getitem__(self, key):
+ return self._data[key]
+
+ def __setitem__(self, key, value):
+ self._data[key] = value
+
+ def __eq__(self, other):
+ if isinstance(other, _basestring):
+ return self["name"] == other
+ return super(QArgument, self).__eq__(other)
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def create(self):
+ return QtWidgets.QWidget()
+
+ def read(self):
+ return self._read()
+
+ def write(self, value):
+ self._write(value)
+ self.changed.emit()
+
+
+class Boolean(QArgument):
+ """Boolean type user interface
+
+ Presented by `QtWidgets.QCheckBox`.
+
+ Arguments:
+ name (str): The name of argument
+ label (str, optional): Display name, convert from `name` if not given
+ help (str, optional): Tool tip message of this argument
+ default (bool, optional): Argument's default value, default None
+ enabled (bool, optional): Whether to enable this widget, default True
+
+ """
+ def create(self):
+ widget = QtWidgets.QCheckBox()
+ widget.clicked.connect(self.changed.emit)
+
+ if isinstance(self, Tristate):
+ self._read = lambda: widget.checkState()
+ state = {
+ 0: QtCore.Qt.Unchecked,
+ 1: QtCore.Qt.PartiallyChecked,
+ 2: QtCore.Qt.Checked,
+ "1": QtCore.Qt.PartiallyChecked,
+ "0": QtCore.Qt.Unchecked,
+ "2": QtCore.Qt.Checked,
+ }
+ else:
+ self._read = lambda: bool(widget.checkState())
+ state = {
+ None: QtCore.Qt.Unchecked,
+
+ 0: QtCore.Qt.Unchecked,
+ 1: QtCore.Qt.Checked,
+ 2: QtCore.Qt.Checked,
+
+ "0": QtCore.Qt.Unchecked,
+ "1": QtCore.Qt.Checked,
+ "2": QtCore.Qt.Checked,
+
+ # May be stored as string, if used with QSettings(..IniFormat)
+ "false": QtCore.Qt.Unchecked,
+ "true": QtCore.Qt.Checked,
+ }
+
+ self._write = lambda value: widget.setCheckState(state[value])
+ widget.clicked.connect(self.changed.emit)
+
+ if self["default"] is not None:
+ self._write(self["default"])
+
+ return widget
+
+ def read(self):
+ return self._read()
+
+
+class Tristate(QArgument):
+ """Not implemented"""
+
+
+class Number(QArgument):
+ """Base class of numeric type user interface"""
+ default = 0
+
+ def create(self):
+ if isinstance(self, Float):
+ widget = QtWidgets.QDoubleSpinBox()
+ widget.setMinimum(self._data.get("min", 0.0))
+ widget.setMaximum(self._data.get("max", 99.99))
+ else:
+ widget = QtWidgets.QSpinBox()
+ widget.setMinimum(self._data.get("min", 0))
+ widget.setMaximum(self._data.get("max", 99))
+
+ widget.editingFinished.connect(self.changed.emit)
+ self._read = lambda: widget.value()
+ self._write = lambda value: widget.setValue(value)
+
+ if self["default"] != self.default:
+ self._write(self["default"])
+
+ return widget
+
+
+class Integer(Number):
+ """Integer type user interface
+
+ A subclass of `qargparse.Number`, presented by `QtWidgets.QSpinBox`.
+
+ Arguments:
+ name (str): The name of argument
+ label (str, optional): Display name, convert from `name` if not given
+ help (str, optional): Tool tip message of this argument
+ default (int, optional): Argument's default value, default 0
+ min (int, optional): Argument's minimum value, default 0
+ max (int, optional): Argument's maximum value, default 99
+ enabled (bool, optional): Whether to enable this widget, default True
+
+ """
+
+
+class Float(Number):
+ """Float type user interface
+
+ A subclass of `qargparse.Number`, presented by `QtWidgets.QDoubleSpinBox`.
+
+ Arguments:
+ name (str): The name of argument
+ label (str, optional): Display name, convert from `name` if not given
+ help (str, optional): Tool tip message of this argument
+ default (float, optional): Argument's default value, default 0.0
+ min (float, optional): Argument's minimum value, default 0.0
+ max (float, optional): Argument's maximum value, default 99.99
+ enabled (bool, optional): Whether to enable this widget, default True
+
+ """
+
+
+class Range(Number):
+ """Range type user interface
+
+ A subclass of `qargparse.Number`, not production ready.
+
+ """
+
+
+class Double3(QArgument):
+ """Double3 type user interface
+
+ Presented by three `QtWidgets.QLineEdit` widget with `QDoubleValidator`
+ installed.
+
+ Arguments:
+ name (str): The name of argument
+ label (str, optional): Display name, convert from `name` if not given
+ help (str, optional): Tool tip message of this argument
+ default (tuple or list, optional): Default (0, 0, 0).
+ enabled (bool, optional): Whether to enable this widget, default True
+
+ """
+ default = (0, 0, 0)
+
+ def create(self):
+ widget = QtWidgets.QWidget()
+ layout = QtWidgets.QHBoxLayout(widget)
+ layout.setContentsMargins(0, 0, 0, 0)
+ x, y, z = (self.child_arg(layout, i) for i in range(3))
+
+ self._read = lambda: (
+ float(x.text()), float(y.text()), float(z.text()))
+ self._write = lambda value: [
+ w.setText(str(float(v))) for w, v in zip([x, y, z], value)]
+
+ if self["default"] != self.default:
+ self._write(self["default"])
+
+ return widget
+
+ def child_arg(self, layout, index):
+ widget = QtWidgets.QLineEdit()
+ widget.setValidator(QtGui.QDoubleValidator())
+
+ default = str(float(self["default"][index]))
+ widget.setText(default)
+
+ def focusOutEvent(event):
+ if not widget.text():
+ widget.setText(default) # Ensure value exists for `_read`
+ QtWidgets.QLineEdit.focusOutEvent(widget, event)
+ widget.focusOutEvent = focusOutEvent
+
+ widget.editingFinished.connect(self.changed.emit)
+ widget.returnPressed.connect(widget.editingFinished.emit)
+
+ layout.addWidget(widget)
+
+ return widget
+
+
+class String(QArgument):
+ """String type user interface
+
+ Presented by `QtWidgets.QLineEdit`.
+
+ Arguments:
+ name (str): The name of argument
+ label (str, optional): Display name, convert from `name` if not given
+ help (str, optional): Tool tip message of this argument
+ default (str, optional): Argument's default value, default None
+ placeholder (str, optional): Placeholder message for the widget
+ enabled (bool, optional): Whether to enable this widget, default True
+
+ """
+ def __init__(self, *args, **kwargs):
+ super(String, self).__init__(*args, **kwargs)
+ self._previous = None
+
+ def create(self):
+ widget = QtWidgets.QLineEdit()
+ widget.editingFinished.connect(self.onEditingFinished)
+ widget.returnPressed.connect(widget.editingFinished.emit)
+ self._read = lambda: widget.text()
+ self._write = lambda value: widget.setText(value)
+
+ if isinstance(self, Info):
+ widget.setReadOnly(True)
+ widget.setPlaceholderText(self._data.get("placeholder", ""))
+
+ if self["default"] is not None:
+ self._write(self["default"])
+ self._previous = self["default"]
+
+ return widget
+
+ def onEditingFinished(self):
+ current = self._read()
+
+ if current != self._previous:
+ self.changed.emit()
+ self._previous = current
+
+
+class Info(String):
+ """String type user interface but read-only
+
+ A subclass of `qargparse.String`, presented by `QtWidgets.QLineEdit`.
+
+ Arguments:
+ name (str): The name of argument
+ label (str, optional): Display name, convert from `name` if not given
+ help (str, optional): Tool tip message of this argument
+ default (str, optional): Argument's default value, default None
+ enabled (bool, optional): Whether to enable this widget, default True
+
+ """
+
+
+class Color(String):
+ """Color type user interface
+
+ A subclass of `qargparse.String`, not production ready.
+
+ """
+
+
+class Button(QArgument):
+ """Button type user interface
+
+ Presented by `QtWidgets.QPushButton`.
+
+ Arguments:
+ name (str): The name of argument
+ label (str, optional): Display name, convert from `name` if not given
+ help (str, optional): Tool tip message of this argument
+ default (bool, optional): Argument's default value, default None
+ enabled (bool, optional): Whether to enable this widget, default True
+
+ """
+ label = False
+
+ def create(self):
+ widget = QtWidgets.QPushButton(self["label"])
+ widget.clicked.connect(self.changed.emit)
+
+ state = [
+ QtCore.Qt.Unchecked,
+ QtCore.Qt.Checked,
+ ]
+
+ if isinstance(self, Toggle):
+ widget.setCheckable(True)
+ if hasattr(widget, "isChecked"):
+ self._read = lambda: state[int(widget.isChecked())]
+ self._write = (
+ lambda value: widget.setChecked(value)
+ )
+ else:
+ self._read = lambda: widget.checkState()
+ self._write = (
+ lambda value: widget.setCheckState(state[int(value)])
+ )
+ else:
+ self._read = lambda: "clicked"
+ self._write = lambda value: None
+
+ if self["default"] is not None:
+ self._write(self["default"])
+
+ return widget
+
+
+class Toggle(Button):
+ """Checkable `Button` type user interface
+
+ Presented by `QtWidgets.QPushButton`.
+
+ Arguments:
+ name (str): The name of argument
+ label (str, optional): Display name, convert from `name` if not given
+ help (str, optional): Tool tip message of this argument
+ default (bool, optional): Argument's default value, default None
+ enabled (bool, optional): Whether to enable this widget, default True
+
+ """
+
+
+class InfoList(QArgument):
+ """String list type user interface
+
+ Presented by `QtWidgets.QListView`, not production ready.
+
+ """
+ def __init__(self, name, **kwargs):
+ kwargs["default"] = kwargs.pop("default", ["Empty"])
+ super(InfoList, self).__init__(name, **kwargs)
+
+ def create(self):
+ class Model(QtCore.QStringListModel):
+ def data(self, index, role):
+ return super(Model, self).data(index, role)
+
+ model = QtCore.QStringListModel(self["default"])
+ widget = QtWidgets.QListView()
+ widget.setModel(model)
+ widget.setEditTriggers(widget.NoEditTriggers)
+
+ self._read = lambda: model.stringList()
+ self._write = lambda value: model.setStringList(value)
+
+ return widget
+
+
+class Choice(QArgument):
+ """Argument user interface for selecting one from list
+
+ Presented by `QtWidgets.QListView`.
+
+ Arguments:
+ name (str): The name of argument
+ label (str, optional): Display name, convert from `name` if not given
+ help (str, optional): Tool tip message of this argument
+ items (list, optional): List of strings for select, default `["Empty"]`
+ default (str, optional): Default item in `items`, use first of `items`
+ if not given.
+ enabled (bool, optional): Whether to enable this widget, default True
+
+ """
+ def __init__(self, name, **kwargs):
+ kwargs["items"] = kwargs.get("items", ["Empty"])
+ kwargs["default"] = kwargs.pop("default", kwargs["items"][0])
+ super(Choice, self).__init__(name, **kwargs)
+
+ def index(self, value):
+ """Return numerical equivalent to self.read()"""
+ return self["items"].index(value)
+
+ def create(self):
+ def on_changed(selected, deselected):
+ try:
+ selected = selected.indexes()[0]
+ except IndexError:
+ # At least one item must be selected at all times
+ selected = deselected.indexes()[0]
+
+ value = selected.data(QtCore.Qt.DisplayRole)
+ set_current(value)
+ self.changed.emit()
+
+ def set_current(current):
+ options = model.stringList()
+
+ if current == "Empty":
+ index = 0
+ else:
+ for index, member in enumerate(options):
+ if member == current:
+ break
+ else:
+ raise ValueError(
+ "%s not a member of %s" % (current, options)
+ )
+
+ qindex = model.index(index, 0, QtCore.QModelIndex())
+ smodel.setCurrentIndex(qindex, smodel.ClearAndSelect)
+ self["current"] = options[index]
+
+ def reset(items, default=None):
+ items = items or ["Empty"]
+ model.setStringList(items)
+ set_current(default or items[0])
+
+ model = QtCore.QStringListModel()
+ widget = QtWidgets.QListView()
+ widget.setModel(model)
+ widget.setEditTriggers(widget.NoEditTriggers)
+ widget.setSelectionMode(widget.SingleSelection)
+ smodel = widget.selectionModel()
+ smodel.selectionChanged.connect(on_changed)
+
+ self._read = lambda: self["current"]
+ self._write = lambda value: set_current(value)
+ self.reset = reset
+
+ reset(self["items"], self["default"])
+
+ return widget
+
+
+class Separator(QArgument):
+ """Visual separator
+
+ Example:
+
+ item1
+ item2
+ ------------
+ item3
+ item4
+
+ """
+
+ def create(self):
+ widget = QtWidgets.QWidget()
+
+ self._read = lambda: None
+ self._write = lambda value: None
+
+ return widget
+
+
+class Enum(QArgument):
+ """Argument user interface for selecting one from dropdown list
+
+ Presented by `QtWidgets.QComboBox`.
+
+ Arguments:
+ name (str): The name of argument
+ label (str, optional): Display name, convert from `name` if not given
+ help (str, optional): Tool tip message of this argument
+ items (list, optional): List of strings for select, default `[]`
+ default (int, optional): Index of default item, use first of `items`
+ if not given.
+ enabled (bool, optional): Whether to enable this widget, default True
+
+ """
+ def __init__(self, name, **kwargs):
+ kwargs["default"] = kwargs.pop("default", 0)
+ kwargs["items"] = kwargs.get("items", [])
+
+ assert isinstance(kwargs["items"], (tuple, list)), (
+ "items must be list"
+ )
+
+ super(Enum, self).__init__(name, **kwargs)
+
+ def create(self):
+ widget = QtWidgets.QComboBox()
+ widget.addItems(self["items"])
+ widget.currentIndexChanged.connect(
+ lambda index: self.changed.emit())
+
+ self._read = lambda: widget.currentText()
+ self._write = lambda value: widget.setCurrentIndex(value)
+
+ if self["default"] is not None:
+ self._write(self["default"])
+
+ return widget
+
+
+style = """\
+QWidget {
+ /* Explicitly specify a size, to account for automatic HDPi */
+ font-size: 11px;
+}
+
+*[type="Button"] {
+ text-align:left;
+}
+
+*[type="Info"] {
+ background: transparent;
+ border: none;
+}
+
+QLabel[type="Separator"] {
+ min-height: 20px;
+ text-decoration: underline;
+}
+
+QPushButton[type="reset"] {
+ max-width: 11px;
+ max-height: 11px;
+}
+
+"""
+
+
+def camelToTitle(text):
+ """Convert camelCase `text` to Title Case
+
+ Example:
+ >>> camelToTitle("mixedCase")
+ "Mixed Case"
+ >>> camelToTitle("myName")
+ "My Name"
+ >>> camelToTitle("you")
+ "You"
+ >>> camelToTitle("You")
+ "You"
+ >>> camelToTitle("This is That")
+ "This Is That"
+
+ """
+
+ return re.sub(
+ r"((?<=[a-z])[A-Z]|(?= 3
+ except:
+ import thread as _thread # Python < 3
+ except ImportError:
+ import _dummy_thread as _thread
+
+"""
+# Exports only things specified by thread documentation;
+# skipping obsolete synonyms allocate(), start_new(), exit_thread().
+__all__ = ['error', 'start_new_thread', 'exit', 'get_ident', 'allocate_lock',
+ 'interrupt_main', 'LockType']
+
+# A dummy value
+TIMEOUT_MAX = 2**31
+
+# NOTE: this module can be imported early in the extension building process,
+# and so top level imports of other modules should be avoided. Instead, all
+# imports are done when needed on a function-by-function basis. Since threads
+# are disabled, the import lock should not be an issue anyway (??).
+
+class error(Exception):
+ """Dummy implementation of _thread.error."""
+
+ def __init__(self, *args):
+ self.args = args
+
+def start_new_thread(function, args, kwargs={}):
+ """Dummy implementation of _thread.start_new_thread().
+
+ Compatibility is maintained by making sure that ``args`` is a
+ tuple and ``kwargs`` is a dictionary. If an exception is raised
+ and it is SystemExit (which can be done by _thread.exit()) it is
+ caught and nothing is done; all other exceptions are printed out
+ by using traceback.print_exc().
+
+ If the executed function calls interrupt_main the KeyboardInterrupt will be
+ raised when the function returns.
+
+ """
+ if type(args) != type(tuple()):
+ raise TypeError("2nd arg must be a tuple")
+ if type(kwargs) != type(dict()):
+ raise TypeError("3rd arg must be a dict")
+ global _main
+ _main = False
+ try:
+ function(*args, **kwargs)
+ except SystemExit:
+ pass
+ except:
+ import traceback
+ traceback.print_exc()
+ _main = True
+ global _interrupt
+ if _interrupt:
+ _interrupt = False
+ raise KeyboardInterrupt
+
+def exit():
+ """Dummy implementation of _thread.exit()."""
+ raise SystemExit
+
+def get_ident():
+ """Dummy implementation of _thread.get_ident().
+
+ Since this module should only be used when _threadmodule is not
+ available, it is safe to assume that the current process is the
+ only thread. Thus a constant can be safely returned.
+ """
+ return -1
+
+def allocate_lock():
+ """Dummy implementation of _thread.allocate_lock()."""
+ return LockType()
+
+def stack_size(size=None):
+ """Dummy implementation of _thread.stack_size()."""
+ if size is not None:
+ raise error("setting thread stack size not supported")
+ return 0
+
+class LockType(object):
+ """Class implementing dummy implementation of _thread.LockType.
+
+ Compatibility is maintained by maintaining self.locked_status
+ which is a boolean that stores the state of the lock. Pickling of
+ the lock, though, should not be done since if the _thread module is
+ then used with an unpickled ``lock()`` from here problems could
+ occur from this class not having atomic methods.
+
+ """
+
+ def __init__(self):
+ self.locked_status = False
+
+ def acquire(self, waitflag=None, timeout=-1):
+ """Dummy implementation of acquire().
+
+ For blocking calls, self.locked_status is automatically set to
+ True and returned appropriately based on value of
+ ``waitflag``. If it is non-blocking, then the value is
+ actually checked and not set if it is already acquired. This
+ is all done so that threading.Condition's assert statements
+ aren't triggered and throw a little fit.
+
+ """
+ if waitflag is None or waitflag:
+ self.locked_status = True
+ return True
+ else:
+ if not self.locked_status:
+ self.locked_status = True
+ return True
+ else:
+ if timeout > 0:
+ import time
+ time.sleep(timeout)
+ return False
+
+ __enter__ = acquire
+
+ def __exit__(self, typ, val, tb):
+ self.release()
+
+ def release(self):
+ """Release the dummy lock."""
+ # XXX Perhaps shouldn't actually bother to test? Could lead
+ # to problems for complex, threaded code.
+ if not self.locked_status:
+ raise error
+ self.locked_status = False
+ return True
+
+ def locked(self):
+ return self.locked_status
+
+# Used to signal that interrupt_main was called in a "thread"
+_interrupt = False
+# True when not executing in a "thread"
+_main = True
+
+def interrupt_main():
+ """Set _interrupt flag to True to have start_new_thread raise
+ KeyboardInterrupt upon exiting."""
+ if _main:
+ raise KeyboardInterrupt
+ else:
+ global _interrupt
+ _interrupt = True
diff --git a/openpype/vendor/python/python_2/functools32/functools32.py b/openpype/vendor/python/python_2/functools32/functools32.py
new file mode 100644
index 0000000000..c44551fac0
--- /dev/null
+++ b/openpype/vendor/python/python_2/functools32/functools32.py
@@ -0,0 +1,423 @@
+"""functools.py - Tools for working with functions and callable objects
+"""
+# Python module wrapper for _functools C module
+# to allow utilities written in Python to be added
+# to the functools module.
+# Written by Nick Coghlan
+# and Raymond Hettinger
+# Copyright (C) 2006-2010 Python Software Foundation.
+# See C source code for _functools credits/copyright
+
+__all__ = ['update_wrapper', 'wraps', 'WRAPPER_ASSIGNMENTS', 'WRAPPER_UPDATES',
+ 'total_ordering', 'cmp_to_key', 'lru_cache', 'reduce', 'partial']
+
+from _functools import partial, reduce
+from collections import MutableMapping, namedtuple
+from .reprlib32 import recursive_repr as _recursive_repr
+from weakref import proxy as _proxy
+import sys as _sys
+try:
+ from thread import allocate_lock as Lock
+except ImportError:
+ from ._dummy_thread32 import allocate_lock as Lock
+
+################################################################################
+### OrderedDict
+################################################################################
+
+class _Link(object):
+ __slots__ = 'prev', 'next', 'key', '__weakref__'
+
+class OrderedDict(dict):
+ 'Dictionary that remembers insertion order'
+ # An inherited dict maps keys to values.
+ # The inherited dict provides __getitem__, __len__, __contains__, and get.
+ # The remaining methods are order-aware.
+ # Big-O running times for all methods are the same as regular dictionaries.
+
+ # The internal self.__map dict maps keys to links in a doubly linked list.
+ # The circular doubly linked list starts and ends with a sentinel element.
+ # The sentinel element never gets deleted (this simplifies the algorithm).
+ # The sentinel is in self.__hardroot with a weakref proxy in self.__root.
+ # The prev links are weakref proxies (to prevent circular references).
+ # Individual links are kept alive by the hard reference in self.__map.
+ # Those hard references disappear when a key is deleted from an OrderedDict.
+
+ def __init__(self, *args, **kwds):
+ '''Initialize an ordered dictionary. The signature is the same as
+ regular dictionaries, but keyword arguments are not recommended because
+ their insertion order is arbitrary.
+
+ '''
+ if len(args) > 1:
+ raise TypeError('expected at most 1 arguments, got %d' % len(args))
+ try:
+ self.__root
+ except AttributeError:
+ self.__hardroot = _Link()
+ self.__root = root = _proxy(self.__hardroot)
+ root.prev = root.next = root
+ self.__map = {}
+ self.__update(*args, **kwds)
+
+ def __setitem__(self, key, value,
+ dict_setitem=dict.__setitem__, proxy=_proxy, Link=_Link):
+ 'od.__setitem__(i, y) <==> od[i]=y'
+ # Setting a new item creates a new link at the end of the linked list,
+ # and the inherited dictionary is updated with the new key/value pair.
+ if key not in self:
+ self.__map[key] = link = Link()
+ root = self.__root
+ last = root.prev
+ link.prev, link.next, link.key = last, root, key
+ last.next = link
+ root.prev = proxy(link)
+ dict_setitem(self, key, value)
+
+ def __delitem__(self, key, dict_delitem=dict.__delitem__):
+ 'od.__delitem__(y) <==> del od[y]'
+ # Deleting an existing item uses self.__map to find the link which gets
+ # removed by updating the links in the predecessor and successor nodes.
+ dict_delitem(self, key)
+ link = self.__map.pop(key)
+ link_prev = link.prev
+ link_next = link.next
+ link_prev.next = link_next
+ link_next.prev = link_prev
+
+ def __iter__(self):
+ 'od.__iter__() <==> iter(od)'
+ # Traverse the linked list in order.
+ root = self.__root
+ curr = root.next
+ while curr is not root:
+ yield curr.key
+ curr = curr.next
+
+ def __reversed__(self):
+ 'od.__reversed__() <==> reversed(od)'
+ # Traverse the linked list in reverse order.
+ root = self.__root
+ curr = root.prev
+ while curr is not root:
+ yield curr.key
+ curr = curr.prev
+
+ def clear(self):
+ 'od.clear() -> None. Remove all items from od.'
+ root = self.__root
+ root.prev = root.next = root
+ self.__map.clear()
+ dict.clear(self)
+
+ def popitem(self, last=True):
+ '''od.popitem() -> (k, v), return and remove a (key, value) pair.
+ Pairs are returned in LIFO order if last is true or FIFO order if false.
+
+ '''
+ if not self:
+ raise KeyError('dictionary is empty')
+ root = self.__root
+ if last:
+ link = root.prev
+ link_prev = link.prev
+ link_prev.next = root
+ root.prev = link_prev
+ else:
+ link = root.next
+ link_next = link.next
+ root.next = link_next
+ link_next.prev = root
+ key = link.key
+ del self.__map[key]
+ value = dict.pop(self, key)
+ return key, value
+
+ def move_to_end(self, key, last=True):
+ '''Move an existing element to the end (or beginning if last==False).
+
+ Raises KeyError if the element does not exist.
+ When last=True, acts like a fast version of self[key]=self.pop(key).
+
+ '''
+ link = self.__map[key]
+ link_prev = link.prev
+ link_next = link.next
+ link_prev.next = link_next
+ link_next.prev = link_prev
+ root = self.__root
+ if last:
+ last = root.prev
+ link.prev = last
+ link.next = root
+ last.next = root.prev = link
+ else:
+ first = root.next
+ link.prev = root
+ link.next = first
+ root.next = first.prev = link
+
+ def __sizeof__(self):
+ sizeof = _sys.getsizeof
+ n = len(self) + 1 # number of links including root
+ size = sizeof(self.__dict__) # instance dictionary
+ size += sizeof(self.__map) * 2 # internal dict and inherited dict
+ size += sizeof(self.__hardroot) * n # link objects
+ size += sizeof(self.__root) * n # proxy objects
+ return size
+
+ update = __update = MutableMapping.update
+ keys = MutableMapping.keys
+ values = MutableMapping.values
+ items = MutableMapping.items
+ __ne__ = MutableMapping.__ne__
+
+ __marker = object()
+
+ def pop(self, key, default=__marker):
+ '''od.pop(k[,d]) -> v, remove specified key and return the corresponding
+ value. If key is not found, d is returned if given, otherwise KeyError
+ is raised.
+
+ '''
+ if key in self:
+ result = self[key]
+ del self[key]
+ return result
+ if default is self.__marker:
+ raise KeyError(key)
+ return default
+
+ def setdefault(self, key, default=None):
+ 'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od'
+ if key in self:
+ return self[key]
+ self[key] = default
+ return default
+
+ @_recursive_repr()
+ def __repr__(self):
+ 'od.__repr__() <==> repr(od)'
+ if not self:
+ return '%s()' % (self.__class__.__name__,)
+ return '%s(%r)' % (self.__class__.__name__, list(self.items()))
+
+ def __reduce__(self):
+ 'Return state information for pickling'
+ items = [[k, self[k]] for k in self]
+ inst_dict = vars(self).copy()
+ for k in vars(OrderedDict()):
+ inst_dict.pop(k, None)
+ if inst_dict:
+ return (self.__class__, (items,), inst_dict)
+ return self.__class__, (items,)
+
+ def copy(self):
+ 'od.copy() -> a shallow copy of od'
+ return self.__class__(self)
+
+ @classmethod
+ def fromkeys(cls, iterable, value=None):
+ '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S.
+ If not specified, the value defaults to None.
+
+ '''
+ self = cls()
+ for key in iterable:
+ self[key] = value
+ return self
+
+ def __eq__(self, other):
+ '''od.__eq__(y) <==> od==y. Comparison to another OD is order-sensitive
+ while comparison to a regular mapping is order-insensitive.
+
+ '''
+ if isinstance(other, OrderedDict):
+ return len(self)==len(other) and \
+ all(p==q for p, q in zip(self.items(), other.items()))
+ return dict.__eq__(self, other)
+
+# update_wrapper() and wraps() are tools to help write
+# wrapper functions that can handle naive introspection
+
+WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__')
+WRAPPER_UPDATES = ('__dict__',)
+def update_wrapper(wrapper,
+ wrapped,
+ assigned = WRAPPER_ASSIGNMENTS,
+ updated = WRAPPER_UPDATES):
+ """Update a wrapper function to look like the wrapped function
+
+ wrapper is the function to be updated
+ wrapped is the original function
+ assigned is a tuple naming the attributes assigned directly
+ from the wrapped function to the wrapper function (defaults to
+ functools.WRAPPER_ASSIGNMENTS)
+ updated is a tuple naming the attributes of the wrapper that
+ are updated with the corresponding attribute from the wrapped
+ function (defaults to functools.WRAPPER_UPDATES)
+ """
+ wrapper.__wrapped__ = wrapped
+ for attr in assigned:
+ try:
+ value = getattr(wrapped, attr)
+ except AttributeError:
+ pass
+ else:
+ setattr(wrapper, attr, value)
+ for attr in updated:
+ getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
+ # Return the wrapper so this can be used as a decorator via partial()
+ return wrapper
+
+def wraps(wrapped,
+ assigned = WRAPPER_ASSIGNMENTS,
+ updated = WRAPPER_UPDATES):
+ """Decorator factory to apply update_wrapper() to a wrapper function
+
+ Returns a decorator that invokes update_wrapper() with the decorated
+ function as the wrapper argument and the arguments to wraps() as the
+ remaining arguments. Default arguments are as for update_wrapper().
+ This is a convenience function to simplify applying partial() to
+ update_wrapper().
+ """
+ return partial(update_wrapper, wrapped=wrapped,
+ assigned=assigned, updated=updated)
+
+def total_ordering(cls):
+ """Class decorator that fills in missing ordering methods"""
+ convert = {
+ '__lt__': [('__gt__', lambda self, other: not (self < other or self == other)),
+ ('__le__', lambda self, other: self < other or self == other),
+ ('__ge__', lambda self, other: not self < other)],
+ '__le__': [('__ge__', lambda self, other: not self <= other or self == other),
+ ('__lt__', lambda self, other: self <= other and not self == other),
+ ('__gt__', lambda self, other: not self <= other)],
+ '__gt__': [('__lt__', lambda self, other: not (self > other or self == other)),
+ ('__ge__', lambda self, other: self > other or self == other),
+ ('__le__', lambda self, other: not self > other)],
+ '__ge__': [('__le__', lambda self, other: (not self >= other) or self == other),
+ ('__gt__', lambda self, other: self >= other and not self == other),
+ ('__lt__', lambda self, other: not self >= other)]
+ }
+ roots = set(dir(cls)) & set(convert)
+ if not roots:
+ raise ValueError('must define at least one ordering operation: < > <= >=')
+ root = max(roots) # prefer __lt__ to __le__ to __gt__ to __ge__
+ for opname, opfunc in convert[root]:
+ if opname not in roots:
+ opfunc.__name__ = opname
+ opfunc.__doc__ = getattr(int, opname).__doc__
+ setattr(cls, opname, opfunc)
+ return cls
+
+def cmp_to_key(mycmp):
+ """Convert a cmp= function into a key= function"""
+ class K(object):
+ __slots__ = ['obj']
+ def __init__(self, obj):
+ self.obj = obj
+ def __lt__(self, other):
+ return mycmp(self.obj, other.obj) < 0
+ def __gt__(self, other):
+ return mycmp(self.obj, other.obj) > 0
+ def __eq__(self, other):
+ return mycmp(self.obj, other.obj) == 0
+ def __le__(self, other):
+ return mycmp(self.obj, other.obj) <= 0
+ def __ge__(self, other):
+ return mycmp(self.obj, other.obj) >= 0
+ def __ne__(self, other):
+ return mycmp(self.obj, other.obj) != 0
+ __hash__ = None
+ return K
+
+_CacheInfo = namedtuple("CacheInfo", "hits misses maxsize currsize")
+
+def lru_cache(maxsize=100):
+ """Least-recently-used cache decorator.
+
+ If *maxsize* is set to None, the LRU features are disabled and the cache
+ can grow without bound.
+
+ Arguments to the cached function must be hashable.
+
+ View the cache statistics named tuple (hits, misses, maxsize, currsize) with
+ f.cache_info(). Clear the cache and statistics with f.cache_clear().
+ Access the underlying function with f.__wrapped__.
+
+ See: http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used
+
+ """
+ # Users should only access the lru_cache through its public API:
+ # cache_info, cache_clear, and f.__wrapped__
+ # The internals of the lru_cache are encapsulated for thread safety and
+ # to allow the implementation to change (including a possible C version).
+
+ def decorating_function(user_function,
+ tuple=tuple, sorted=sorted, len=len, KeyError=KeyError):
+
+ hits, misses = [0], [0]
+ kwd_mark = (object(),) # separates positional and keyword args
+ lock = Lock() # needed because OrderedDict isn't threadsafe
+
+ if maxsize is None:
+ cache = dict() # simple cache without ordering or size limit
+
+ @wraps(user_function)
+ def wrapper(*args, **kwds):
+ key = args
+ if kwds:
+ key += kwd_mark + tuple(sorted(kwds.items()))
+ try:
+ result = cache[key]
+ hits[0] += 1
+ return result
+ except KeyError:
+ pass
+ result = user_function(*args, **kwds)
+ cache[key] = result
+ misses[0] += 1
+ return result
+ else:
+ cache = OrderedDict() # ordered least recent to most recent
+ cache_popitem = cache.popitem
+ cache_renew = cache.move_to_end
+
+ @wraps(user_function)
+ def wrapper(*args, **kwds):
+ key = args
+ if kwds:
+ key += kwd_mark + tuple(sorted(kwds.items()))
+ with lock:
+ try:
+ result = cache[key]
+ cache_renew(key) # record recent use of this key
+ hits[0] += 1
+ return result
+ except KeyError:
+ pass
+ result = user_function(*args, **kwds)
+ with lock:
+ cache[key] = result # record recent use of this key
+ misses[0] += 1
+ if len(cache) > maxsize:
+ cache_popitem(0) # purge least recently used cache entry
+ return result
+
+ def cache_info():
+ """Report cache statistics"""
+ with lock:
+ return _CacheInfo(hits[0], misses[0], maxsize, len(cache))
+
+ def cache_clear():
+ """Clear the cache and cache statistics"""
+ with lock:
+ cache.clear()
+ hits[0] = misses[0] = 0
+
+ wrapper.cache_info = cache_info
+ wrapper.cache_clear = cache_clear
+ return wrapper
+
+ return decorating_function
diff --git a/openpype/vendor/python/python_2/functools32/reprlib32.py b/openpype/vendor/python/python_2/functools32/reprlib32.py
new file mode 100644
index 0000000000..af919758ca
--- /dev/null
+++ b/openpype/vendor/python/python_2/functools32/reprlib32.py
@@ -0,0 +1,157 @@
+"""Redo the builtin repr() (representation) but with limits on most sizes."""
+
+__all__ = ["Repr", "repr", "recursive_repr"]
+
+import __builtin__ as builtins
+from itertools import islice
+try:
+ from thread import get_ident
+except ImportError:
+ from _dummy_thread32 import get_ident
+
+def recursive_repr(fillvalue='...'):
+ 'Decorator to make a repr function return fillvalue for a recursive call'
+
+ def decorating_function(user_function):
+ repr_running = set()
+
+ def wrapper(self):
+ key = id(self), get_ident()
+ if key in repr_running:
+ return fillvalue
+ repr_running.add(key)
+ try:
+ result = user_function(self)
+ finally:
+ repr_running.discard(key)
+ return result
+
+ # Can't use functools.wraps() here because of bootstrap issues
+ wrapper.__module__ = getattr(user_function, '__module__')
+ wrapper.__doc__ = getattr(user_function, '__doc__')
+ wrapper.__name__ = getattr(user_function, '__name__')
+ wrapper.__annotations__ = getattr(user_function, '__annotations__', {})
+ return wrapper
+
+ return decorating_function
+
+class Repr:
+
+ def __init__(self):
+ self.maxlevel = 6
+ self.maxtuple = 6
+ self.maxlist = 6
+ self.maxarray = 5
+ self.maxdict = 4
+ self.maxset = 6
+ self.maxfrozenset = 6
+ self.maxdeque = 6
+ self.maxstring = 30
+ self.maxlong = 40
+ self.maxother = 30
+
+ def repr(self, x):
+ return self.repr1(x, self.maxlevel)
+
+ def repr1(self, x, level):
+ typename = type(x).__name__
+ if ' ' in typename:
+ parts = typename.split()
+ typename = '_'.join(parts)
+ if hasattr(self, 'repr_' + typename):
+ return getattr(self, 'repr_' + typename)(x, level)
+ else:
+ return self.repr_instance(x, level)
+
+ def _repr_iterable(self, x, level, left, right, maxiter, trail=''):
+ n = len(x)
+ if level <= 0 and n:
+ s = '...'
+ else:
+ newlevel = level - 1
+ repr1 = self.repr1
+ pieces = [repr1(elem, newlevel) for elem in islice(x, maxiter)]
+ if n > maxiter: pieces.append('...')
+ s = ', '.join(pieces)
+ if n == 1 and trail: right = trail + right
+ return '%s%s%s' % (left, s, right)
+
+ def repr_tuple(self, x, level):
+ return self._repr_iterable(x, level, '(', ')', self.maxtuple, ',')
+
+ def repr_list(self, x, level):
+ return self._repr_iterable(x, level, '[', ']', self.maxlist)
+
+ def repr_array(self, x, level):
+ header = "array('%s', [" % x.typecode
+ return self._repr_iterable(x, level, header, '])', self.maxarray)
+
+ def repr_set(self, x, level):
+ x = _possibly_sorted(x)
+ return self._repr_iterable(x, level, 'set([', '])', self.maxset)
+
+ def repr_frozenset(self, x, level):
+ x = _possibly_sorted(x)
+ return self._repr_iterable(x, level, 'frozenset([', '])',
+ self.maxfrozenset)
+
+ def repr_deque(self, x, level):
+ return self._repr_iterable(x, level, 'deque([', '])', self.maxdeque)
+
+ def repr_dict(self, x, level):
+ n = len(x)
+ if n == 0: return '{}'
+ if level <= 0: return '{...}'
+ newlevel = level - 1
+ repr1 = self.repr1
+ pieces = []
+ for key in islice(_possibly_sorted(x), self.maxdict):
+ keyrepr = repr1(key, newlevel)
+ valrepr = repr1(x[key], newlevel)
+ pieces.append('%s: %s' % (keyrepr, valrepr))
+ if n > self.maxdict: pieces.append('...')
+ s = ', '.join(pieces)
+ return '{%s}' % (s,)
+
+ def repr_str(self, x, level):
+ s = builtins.repr(x[:self.maxstring])
+ if len(s) > self.maxstring:
+ i = max(0, (self.maxstring-3)//2)
+ j = max(0, self.maxstring-3-i)
+ s = builtins.repr(x[:i] + x[len(x)-j:])
+ s = s[:i] + '...' + s[len(s)-j:]
+ return s
+
+ def repr_int(self, x, level):
+ s = builtins.repr(x) # XXX Hope this isn't too slow...
+ if len(s) > self.maxlong:
+ i = max(0, (self.maxlong-3)//2)
+ j = max(0, self.maxlong-3-i)
+ s = s[:i] + '...' + s[len(s)-j:]
+ return s
+
+ def repr_instance(self, x, level):
+ try:
+ s = builtins.repr(x)
+ # Bugs in x.__repr__() can cause arbitrary
+ # exceptions -- then make up something
+ except Exception:
+ return '<%s instance at %x>' % (x.__class__.__name__, id(x))
+ if len(s) > self.maxother:
+ i = max(0, (self.maxother-3)//2)
+ j = max(0, self.maxother-3-i)
+ s = s[:i] + '...' + s[len(s)-j:]
+ return s
+
+
+def _possibly_sorted(x):
+ # Since not all sequences of items can be sorted and comparison
+ # functions may raise arbitrary exceptions, return an unsorted
+ # sequence in that case.
+ try:
+ return sorted(x)
+ except Exception:
+ return list(x)
+
+aRepr = Repr()
+repr = aRepr.repr
diff --git a/poetry.lock b/poetry.lock
index b6eba33e0a..ee7b839b8d 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -674,7 +674,7 @@ ansicon = {version = "*", markers = "platform_system == \"Windows\""}
[[package]]
name = "jsonschema"
-version = "3.2.0"
+version = "2.6.0"
description = "An implementation of JSON Schema validation for Python"
category = "main"
optional = false
@@ -1219,6 +1219,26 @@ category = "main"
optional = false
python-versions = "*"
+[[package]]
+name = "qtawesome"
+version = "0.7.3"
+description = "FontAwesome icons in PyQt and PySide applications"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+qtpy = "*"
+six = "*"
+
+[[package]]
+name = "qtpy"
+version = "1.11.3"
+description = "Provides an abstraction layer on top of the various Qt bindings (PyQt5, PyQt4 and PySide) and additional custom QWidgets."
+category = "main"
+optional = false
+python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*"
+
[[package]]
name = "recommonmark"
version = "0.7.1"
@@ -2101,8 +2121,8 @@ jinxed = [
{file = "jinxed-1.1.0.tar.gz", hash = "sha256:d8f1731f134e9e6b04d95095845ae6c10eb15cb223a5f0cabdea87d4a279c305"},
]
jsonschema = [
- {file = "jsonschema-3.2.0-py2.py3-none-any.whl", hash = "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163"},
- {file = "jsonschema-3.2.0.tar.gz", hash = "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"},
+ {file = "jsonschema-2.6.0-py2.py3-none-any.whl", hash = "sha256:000e68abd33c972a5248544925a0cae7d1125f9bf6c58280d37546b946769a08"},
+ {file = "jsonschema-2.6.0.tar.gz", hash = "sha256:6ff5f3180870836cae40f06fa10419f557208175f13ad7bc26caa77beb1f6e02"},
]
keyring = [
{file = "keyring-22.4.0-py3-none-any.whl", hash = "sha256:d6c531f6d12f3304db6029af1d19894bd446ecbbadd22465fa0f096b3e12d258"},
@@ -2651,6 +2671,14 @@ pywin32-ctypes = [
{file = "Qt.py-1.3.6-py2.py3-none-any.whl", hash = "sha256:7edf6048d07a6924707506b5ba34a6e05d66dde9a3f4e3a62f9996ccab0b91c7"},
{file = "Qt.py-1.3.6.tar.gz", hash = "sha256:0d78656a2f814602eee304521c7bf5da0cec414818b3833712c77524294c404a"},
]
+qtawesome = [
+ {file = "QtAwesome-0.7.3-py2.py3-none-any.whl", hash = "sha256:ddf4530b4af71cec13b24b88a4cdb56ec85b1e44c43c42d0698804c7137b09b0"},
+ {file = "QtAwesome-0.7.3.tar.gz", hash = "sha256:b98b9038d19190e83ab26d91c4d8fc3a36591ee2bc7f5016d4438b8240d097bd"},
+]
+qtpy = [
+ {file = "QtPy-1.11.3-py2.py3-none-any.whl", hash = "sha256:e121fbee8e95645af29c5a4aceba8d657991551fc1aa3b6b6012faf4725a1d20"},
+ {file = "QtPy-1.11.3.tar.gz", hash = "sha256:d427addd37386a8d786db81864a5536700861d95bf085cb31d1bea855d699557"},
+]
recommonmark = [
{file = "recommonmark-0.7.1-py2.py3-none-any.whl", hash = "sha256:1b1db69af0231efce3fa21b94ff627ea33dee7079a01dd0a7f8482c3da148b3f"},
{file = "recommonmark-0.7.1.tar.gz", hash = "sha256:bdb4db649f2222dcd8d2d844f0006b958d627f732415d399791ee436a3686d67"},
diff --git a/pyproject.toml b/pyproject.toml
index 541932bce6..f0d295a44c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -41,7 +41,7 @@ Click = "^7"
dnspython = "^2.1.0"
ftrack-python-api = "2.0.*"
google-api-python-client = "^1.12.8" # sync server google support (should be separate?)
-jsonschema = "^3.2.0"
+jsonschema = "^2.6.0"
keyring = "^22.0.1"
log4mongo = "^1.7"
pathlib2= "^2.3.5" # deadline submit publish job only (single place, maybe not needed?)
@@ -50,6 +50,8 @@ pyblish-base = "^1.8.8"
pynput = "^1.7.2" # idle manager in tray
pymongo = "^3.11.2"
"Qt.py" = "^1.3.3"
+qtpy = "^1.11.3"
+qtawesome = "0.7.3"
speedcopy = "^2.1"
six = "^1.15"
semver = "^2.13.0" # for version resolution