diff --git a/pype/api.py b/pype/api.py index 44caa7632f6..37e878580aa 100644 --- a/pype/api.py +++ b/pype/api.py @@ -44,6 +44,9 @@ from . import resources from .plugin import ( + PypeCreatorMixin, + Creator, + Extractor, ValidatePipelineOrder, @@ -86,6 +89,9 @@ # Resources "resources", + # Pype creator mixin + "PypeCreatorMixin", + "Creator", # plugin classes "Extractor", # ordering diff --git a/pype/hosts/aftereffects/plugins/create/create_render.py b/pype/hosts/aftereffects/plugins/create/create_render.py index 6d876e349da..b346bc60d82 100644 --- a/pype/hosts/aftereffects/plugins/create/create_render.py +++ b/pype/hosts/aftereffects/plugins/create/create_render.py @@ -1,4 +1,4 @@ -from avalon import api +import pype.api from avalon.vendor import Qt from avalon import aftereffects @@ -7,7 +7,7 @@ log = logging.getLogger(__name__) -class CreateRender(api.Creator): +class CreateRender(pype.api.Creator): """Render folder for publish.""" name = "renderDefault" diff --git a/pype/hosts/blender/api/plugin.py b/pype/hosts/blender/api/plugin.py index d0b81148c34..f216eb28be7 100644 --- a/pype/hosts/blender/api/plugin.py +++ b/pype/hosts/blender/api/plugin.py @@ -6,6 +6,8 @@ import bpy from avalon import api +import avalon.blender +from pype.api import PypeCreatorMixin VALID_EXTENSIONS = [".blend", ".json"] @@ -100,6 +102,10 @@ def get_local_collection_with_name(name): return None +class Creator(PypeCreatorMixin, avalon.blender.Creator): + pass + + class AssetLoader(api.Loader): """A basic AssetLoader for Blender diff --git a/pype/hosts/blender/plugins/create/create_action.py b/pype/hosts/blender/plugins/create/create_action.py index d4029988435..b8ad24e711e 100644 --- a/pype/hosts/blender/plugins/create/create_action.py +++ b/pype/hosts/blender/plugins/create/create_action.py @@ -3,11 +3,11 @@ import bpy from avalon import api -from avalon.blender import Creator, lib import pype.hosts.blender.api.plugin +from avalon.blender import lib -class CreateAction(Creator): +class CreateAction(pype.hosts.blender.api.plugin.Creator): """Action output for character rigs""" name = "actionMain" diff --git a/pype/hosts/blender/plugins/create/create_animation.py b/pype/hosts/blender/plugins/create/create_animation.py index 9d68768201d..79744ad7e9e 100644 --- a/pype/hosts/blender/plugins/create/create_animation.py +++ b/pype/hosts/blender/plugins/create/create_animation.py @@ -6,7 +6,7 @@ import pype.hosts.blender.api.plugin -class CreateAnimation(blender.Creator): +class CreateAnimation(pype.hosts.blender.api.plugin.Creator): """Animation output for character rigs""" name = "animationMain" diff --git a/pype/hosts/blender/plugins/create/create_camera.py b/pype/hosts/blender/plugins/create/create_camera.py index a40ddc63d47..177d26e08be 100644 --- a/pype/hosts/blender/plugins/create/create_camera.py +++ b/pype/hosts/blender/plugins/create/create_camera.py @@ -3,11 +3,11 @@ import bpy from avalon import api -from avalon.blender import Creator, lib +from avalon.blender import lib import pype.hosts.blender.api.plugin -class CreateCamera(Creator): +class CreateCamera(pype.hosts.blender.api.plugin.Creator): """Polygonal static geometry""" name = "cameraMain" diff --git a/pype/hosts/blender/plugins/create/create_layout.py b/pype/hosts/blender/plugins/create/create_layout.py index ef765d741b3..f45b58d1374 100644 --- a/pype/hosts/blender/plugins/create/create_layout.py +++ b/pype/hosts/blender/plugins/create/create_layout.py @@ -3,11 +3,11 @@ import bpy from avalon import api -from avalon.blender import Creator, lib +from avalon.blender import lib import pype.hosts.blender.api.plugin -class CreateLayout(Creator): +class CreateLayout(pype.hosts.blender.api.plugin.Creator): """Layout output for character rigs""" name = "layoutMain" diff --git a/pype/hosts/blender/plugins/create/create_model.py b/pype/hosts/blender/plugins/create/create_model.py index 2bf8aedb48b..7404b3a1573 100644 --- a/pype/hosts/blender/plugins/create/create_model.py +++ b/pype/hosts/blender/plugins/create/create_model.py @@ -3,11 +3,11 @@ import bpy from avalon import api -from avalon.blender import Creator, lib +from avalon.blender import lib import pype.hosts.blender.api.plugin -class CreateModel(Creator): +class CreateModel(pype.hosts.blender.api.plugin.Creator): """Polygonal static geometry""" name = "modelMain" diff --git a/pype/hosts/blender/plugins/create/create_rig.py b/pype/hosts/blender/plugins/create/create_rig.py index f3efa824f40..d96a88f71dc 100644 --- a/pype/hosts/blender/plugins/create/create_rig.py +++ b/pype/hosts/blender/plugins/create/create_rig.py @@ -3,11 +3,11 @@ import bpy from avalon import api -from avalon.blender import Creator, lib +from avalon.blender import lib import pype.hosts.blender.api.plugin -class CreateRig(Creator): +class CreateRig(pype.hosts.blender.api.plugin.Creator): """Artist-friendly rig with controls to direct motion""" name = "rigMain" diff --git a/pype/hosts/blender/plugins/create/create_setdress.py b/pype/hosts/blender/plugins/create/create_setdress.py index c918949216d..201893b3dfe 100644 --- a/pype/hosts/blender/plugins/create/create_setdress.py +++ b/pype/hosts/blender/plugins/create/create_setdress.py @@ -3,7 +3,8 @@ from avalon import api, blender import pype.hosts.blender.api.plugin -class CreateSetDress(blender.Creator): + +class CreateSetDress(pype.hosts.blender.api.plugin.Creator): """A grouped package of loaded content""" name = "setdressMain" diff --git a/pype/hosts/fusion/plugins/create/create_exr_saver.py b/pype/hosts/fusion/plugins/create/create_exr_saver.py index d5092d1d03a..560f7deb7f9 100644 --- a/pype/hosts/fusion/plugins/create/create_exr_saver.py +++ b/pype/hosts/fusion/plugins/create/create_exr_saver.py @@ -1,10 +1,10 @@ import os -import avalon.api +import pype.api from avalon import fusion -class CreateOpenEXRSaver(avalon.api.Creator): +class CreateOpenEXRSaver(pype.api.Creator): name = "openexrDefault" label = "Create OpenEXR Saver" diff --git a/pype/hosts/harmony/api/plugin.py b/pype/hosts/harmony/api/plugin.py new file mode 100644 index 00000000000..3525ad686d1 --- /dev/null +++ b/pype/hosts/harmony/api/plugin.py @@ -0,0 +1,6 @@ +from avalon import harmony +from pype.api import PypeCreatorMixin + + +class Creator(PypeCreatorMixin, harmony.Creator): + pass diff --git a/pype/hosts/harmony/plugins/create/create_farm_render.py b/pype/hosts/harmony/plugins/create/create_farm_render.py index e134f28f432..a1b198b672d 100644 --- a/pype/hosts/harmony/plugins/create/create_farm_render.py +++ b/pype/hosts/harmony/plugins/create/create_farm_render.py @@ -1,9 +1,10 @@ # -*- coding: utf-8 -*- """Create Composite node for render on farm.""" from avalon import harmony +from pype.hosts.harmony.api import plugin -class CreateFarmRender(harmony.Creator): +class CreateFarmRender(plugin.Creator): """Composite node for publishing renders.""" name = "renderDefault" diff --git a/pype/hosts/harmony/plugins/create/create_render.py b/pype/hosts/harmony/plugins/create/create_render.py index 8e3408d9002..a047fbff773 100644 --- a/pype/hosts/harmony/plugins/create/create_render.py +++ b/pype/hosts/harmony/plugins/create/create_render.py @@ -1,9 +1,10 @@ # -*- coding: utf-8 -*- """Create render node.""" from avalon import harmony +from pype.hosts.harmony.api import plugin -class CreateRender(harmony.Creator): +class CreateRender(plugin.Creator): """Composite node for publishing renders.""" name = "renderDefault" diff --git a/pype/hosts/harmony/plugins/create/create_template.py b/pype/hosts/harmony/plugins/create/create_template.py index babc3fe8d7e..628606c9f42 100644 --- a/pype/hosts/harmony/plugins/create/create_template.py +++ b/pype/hosts/harmony/plugins/create/create_template.py @@ -1,7 +1,7 @@ -from avalon import harmony +from pype.hosts.harmony.api import plugin -class CreateTemplate(harmony.Creator): +class CreateTemplate(plugin.Creator): """Composite node for publishing to templates.""" name = "templateDefault" diff --git a/pype/hosts/hiero/api/plugin.py b/pype/hosts/hiero/api/plugin.py index db6720cbd69..06ee403a5b0 100644 --- a/pype/hosts/hiero/api/plugin.py +++ b/pype/hosts/hiero/api/plugin.py @@ -592,7 +592,7 @@ def load(self): return track_item -class Creator(avalon.Creator): +class Creator(pype.Creator): """Creator class wrapper """ clip_color = "Purple" diff --git a/pype/hosts/houdini/api/plugin.py b/pype/hosts/houdini/api/plugin.py new file mode 100644 index 00000000000..864cc59f098 --- /dev/null +++ b/pype/hosts/houdini/api/plugin.py @@ -0,0 +1,6 @@ +from avalon import houdini +from pype.api import PypeCreatorMixin + + +class Creator(PypeCreatorMixin, houdini.Creator): + pass diff --git a/pype/hosts/houdini/plugins/create/create_alembic_camera.py b/pype/hosts/houdini/plugins/create/create_alembic_camera.py index cf8ac41b62f..2a849ddaa06 100644 --- a/pype/hosts/houdini/plugins/create/create_alembic_camera.py +++ b/pype/hosts/houdini/plugins/create/create_alembic_camera.py @@ -1,7 +1,7 @@ -from avalon import houdini +from pype.hosts.houdini.api import plugin -class CreateAlembicCamera(houdini.Creator): +class CreateAlembicCamera(plugin.Creator): """Single baked camera from Alembic ROP""" name = "camera" diff --git a/pype/hosts/houdini/plugins/create/create_pointcache.py b/pype/hosts/houdini/plugins/create/create_pointcache.py index ae7e8450837..a36ee1c9656 100644 --- a/pype/hosts/houdini/plugins/create/create_pointcache.py +++ b/pype/hosts/houdini/plugins/create/create_pointcache.py @@ -1,7 +1,7 @@ -from avalon import houdini +from pype.hosts.houdini.api import plugin -class CreatePointCache(houdini.Creator): +class CreatePointCache(plugin.Creator): """Alembic ROP to pointcache""" name = "pointcache" diff --git a/pype/hosts/houdini/plugins/create/create_vbd_cache.py b/pype/hosts/houdini/plugins/create/create_vbd_cache.py index e862d5c96d0..01401f0ce4c 100644 --- a/pype/hosts/houdini/plugins/create/create_vbd_cache.py +++ b/pype/hosts/houdini/plugins/create/create_vbd_cache.py @@ -1,7 +1,7 @@ -from avalon import houdini +from pype.hosts.houdini.api import plugin -class CreateVDBCache(houdini.Creator): +class CreateVDBCache(plugin.Creator): """OpenVDB from Geometry ROP""" name = "vbdcache" diff --git a/pype/hosts/maya/api/__init__.py b/pype/hosts/maya/api/__init__.py index 9caca205e69..27e15e0b255 100644 --- a/pype/hosts/maya/api/__init__.py +++ b/pype/hosts/maya/api/__init__.py @@ -151,7 +151,7 @@ def on_open(_): """On scene open let's assume the containers have changed.""" from avalon.vendor.Qt import QtWidgets - from ...widgets import popup + from pype.widgets import popup cmds.evalDeferred( "from pype.hosts.maya.api import lib;" @@ -223,4 +223,4 @@ def on_task_changed(*args): lib.show_message( "Context was changed", ("Context was changed to {}".format(avalon.Session["AVALON_ASSET"])), - ) \ No newline at end of file + ) diff --git a/pype/hosts/maya/api/plugin.py b/pype/hosts/maya/api/plugin.py index c7416920df0..81c89017ff1 100644 --- a/pype/hosts/maya/api/plugin.py +++ b/pype/hosts/maya/api/plugin.py @@ -1,5 +1,7 @@ from avalon import api from avalon.vendor import qargparse +import avalon.maya +from pype.api import PypeCreatorMixin def get_reference_node_parents(ref): @@ -26,6 +28,10 @@ def get_reference_node_parents(ref): return parents +class Creator(PypeCreatorMixin, avalon.maya.Creator): + pass + + class ReferenceLoader(api.Loader): """A basic ReferenceLoader for Maya diff --git a/pype/hosts/maya/plugins/create/create_animation.py b/pype/hosts/maya/plugins/create/create_animation.py index 6894fffb5d5..312357c173c 100644 --- a/pype/hosts/maya/plugins/create/create_animation.py +++ b/pype/hosts/maya/plugins/create/create_animation.py @@ -1,8 +1,10 @@ -import avalon.maya -from pype.hosts.maya.api import lib +from pype.hosts.maya.api import ( + lib, + plugin +) -class CreateAnimation(avalon.maya.Creator): +class CreateAnimation(plugin.Creator): """Animation output for character rigs""" name = "animationDefault" diff --git a/pype/hosts/maya/plugins/create/create_ass.py b/pype/hosts/maya/plugins/create/create_ass.py index b7d5f271143..2d19d20af8c 100644 --- a/pype/hosts/maya/plugins/create/create_ass.py +++ b/pype/hosts/maya/plugins/create/create_ass.py @@ -1,12 +1,14 @@ from collections import OrderedDict -import avalon.maya -from pype.hosts.maya.api import lib +from pype.hosts.maya.api import ( + lib, + plugin +) from maya import cmds -class CreateAss(avalon.maya.Creator): +class CreateAss(plugin.Creator): """Arnold Archive""" name = "ass" diff --git a/pype/hosts/maya/plugins/create/create_assembly.py b/pype/hosts/maya/plugins/create/create_assembly.py index 6d0321b7189..254d3b6670a 100644 --- a/pype/hosts/maya/plugins/create/create_assembly.py +++ b/pype/hosts/maya/plugins/create/create_assembly.py @@ -1,7 +1,7 @@ -import avalon.maya +from pype.hosts.maya.api import plugin -class CreateAssembly(avalon.maya.Creator): +class CreateAssembly(plugin.Creator): """A grouped package of loaded content""" name = "assembly" diff --git a/pype/hosts/maya/plugins/create/create_camera.py b/pype/hosts/maya/plugins/create/create_camera.py index cb438e68bad..4f0865c3c41 100644 --- a/pype/hosts/maya/plugins/create/create_camera.py +++ b/pype/hosts/maya/plugins/create/create_camera.py @@ -1,8 +1,10 @@ -import avalon.maya -from pype.hosts.maya.api import lib +from pype.hosts.maya.api import ( + lib, + plugin +) -class CreateCamera(avalon.maya.Creator): +class CreateCamera(plugin.Creator): """Single baked camera""" name = "cameraMain" @@ -24,7 +26,7 @@ def __init__(self, *args, **kwargs): self.data['bakeToWorldSpace'] = True -class CreateCameraRig(avalon.maya.Creator): +class CreateCameraRig(plugin.Creator): """Complex hierarchy with camera.""" name = "camerarigMain" diff --git a/pype/hosts/maya/plugins/create/create_layout.py b/pype/hosts/maya/plugins/create/create_layout.py index 7f0c82d80ec..bae1bc89b1e 100644 --- a/pype/hosts/maya/plugins/create/create_layout.py +++ b/pype/hosts/maya/plugins/create/create_layout.py @@ -1,7 +1,7 @@ -import avalon.maya +from pype.hosts.maya.api import plugin -class CreateLayout(avalon.maya.Creator): +class CreateLayout(plugin.Creator): """A grouped package of loaded content""" name = "layoutMain" diff --git a/pype/hosts/maya/plugins/create/create_look.py b/pype/hosts/maya/plugins/create/create_look.py index 2d8576a8b43..d81397c1c81 100644 --- a/pype/hosts/maya/plugins/create/create_look.py +++ b/pype/hosts/maya/plugins/create/create_look.py @@ -1,8 +1,10 @@ -import avalon.maya -from pype.hosts.maya.api import lib +from pype.hosts.maya.api import ( + lib, + plugin +) -class CreateLook(avalon.maya.Creator): +class CreateLook(plugin.Creator): """Shader connections defining shape look""" name = "look" diff --git a/pype/hosts/maya/plugins/create/create_mayaascii.py b/pype/hosts/maya/plugins/create/create_mayaascii.py index e7cc40dc24a..0248ebe45ea 100644 --- a/pype/hosts/maya/plugins/create/create_mayaascii.py +++ b/pype/hosts/maya/plugins/create/create_mayaascii.py @@ -1,7 +1,7 @@ -import avalon.maya +from pype.hosts.maya.api import plugin -class CreateMayaAscii(avalon.maya.Creator): +class CreateMayaAscii(plugin.Creator): """Raw Maya Ascii file export""" name = "mayaAscii" diff --git a/pype/hosts/maya/plugins/create/create_model.py b/pype/hosts/maya/plugins/create/create_model.py index 241e2be7f92..f7b5847ae14 100644 --- a/pype/hosts/maya/plugins/create/create_model.py +++ b/pype/hosts/maya/plugins/create/create_model.py @@ -1,7 +1,7 @@ -import avalon.maya +from pype.hosts.maya.api import plugin -class CreateModel(avalon.maya.Creator): +class CreateModel(plugin.Creator): """Polygonal static geometry""" name = "modelMain" diff --git a/pype/hosts/maya/plugins/create/create_pointcache.py b/pype/hosts/maya/plugins/create/create_pointcache.py index 271257c85ec..c179bec9f4f 100644 --- a/pype/hosts/maya/plugins/create/create_pointcache.py +++ b/pype/hosts/maya/plugins/create/create_pointcache.py @@ -1,8 +1,10 @@ -import avalon.maya -from pype.hosts.maya.api import lib +from pype.hosts.maya.api import ( + lib, + plugin +) -class CreatePointCache(avalon.maya.Creator): +class CreatePointCache(plugin.Creator): """Alembic pointcache for animated data""" name = "pointcache" diff --git a/pype/hosts/maya/plugins/create/create_render.py b/pype/hosts/maya/plugins/create/create_render.py index afa30f2d103..726433de925 100644 --- a/pype/hosts/maya/plugins/create/create_render.py +++ b/pype/hosts/maya/plugins/create/create_render.py @@ -8,13 +8,14 @@ from maya import cmds import maya.app.renderSetup.model.renderSetup as renderSetup -from pype.hosts.maya.api import lib +from pype.hosts.maya.api import ( + lib, + plugin +) from pype.api import get_system_settings -import avalon.maya - -class CreateRender(avalon.maya.Creator): +class CreateRender(plugin.Creator): """Create *render* instance. Render instances are not actually published, they hold options for diff --git a/pype/hosts/maya/plugins/create/create_rendersetup.py b/pype/hosts/maya/plugins/create/create_rendersetup.py index bbf46e21699..37eac4aae8a 100644 --- a/pype/hosts/maya/plugins/create/create_rendersetup.py +++ b/pype/hosts/maya/plugins/create/create_rendersetup.py @@ -1,9 +1,11 @@ -import avalon.maya -from pype.hosts.maya.api import lib +from pype.hosts.maya.api import ( + lib, + plugin +) from maya import cmds -class CreateRenderSetup(avalon.maya.Creator): +class CreateRenderSetup(plugin.Creator): """Create rendersetup template json data""" name = "rendersetup" diff --git a/pype/hosts/maya/plugins/create/create_review.py b/pype/hosts/maya/plugins/create/create_review.py index 8619f909641..3f91c8af159 100644 --- a/pype/hosts/maya/plugins/create/create_review.py +++ b/pype/hosts/maya/plugins/create/create_review.py @@ -1,9 +1,11 @@ from collections import OrderedDict -import avalon.maya -from pype.hosts.maya.api import lib +from pype.hosts.maya.api import ( + lib, + plugin +) -class CreateReview(avalon.maya.Creator): +class CreateReview(plugin.Creator): """Single baked camera""" name = "reviewDefault" diff --git a/pype/hosts/maya/plugins/create/create_rig.py b/pype/hosts/maya/plugins/create/create_rig.py index 46929979347..186e8007ee7 100644 --- a/pype/hosts/maya/plugins/create/create_rig.py +++ b/pype/hosts/maya/plugins/create/create_rig.py @@ -1,10 +1,12 @@ from maya import cmds -from pype.hosts.maya.api import lib -import avalon.maya +from pype.hosts.maya.api import ( + lib, + plugin +) -class CreateRig(avalon.maya.Creator): +class CreateRig(plugin.Creator): """Artist-friendly rig with controls to direct motion""" name = "rigDefault" diff --git a/pype/hosts/maya/plugins/create/create_setdress.py b/pype/hosts/maya/plugins/create/create_setdress.py index d5fc0012996..e235d01b202 100644 --- a/pype/hosts/maya/plugins/create/create_setdress.py +++ b/pype/hosts/maya/plugins/create/create_setdress.py @@ -1,7 +1,7 @@ -import avalon.maya +from pype.hosts.maya.api import plugin -class CreateSetDress(avalon.maya.Creator): +class CreateSetDress(plugin.Creator): """A grouped package of loaded content""" name = "setdressMain" diff --git a/pype/hosts/maya/plugins/create/create_unreal_staticmesh.py b/pype/hosts/maya/plugins/create/create_unreal_staticmesh.py index 5a74cb22d55..06cec0f6733 100644 --- a/pype/hosts/maya/plugins/create/create_unreal_staticmesh.py +++ b/pype/hosts/maya/plugins/create/create_unreal_staticmesh.py @@ -1,7 +1,7 @@ -import avalon.maya +from pype.hosts.maya.api import plugin -class CreateUnrealStaticMesh(avalon.maya.Creator): +class CreateUnrealStaticMesh(plugin.Creator): name = "staticMeshMain" label = "Unreal - Static Mesh" family = "unrealStaticMesh" diff --git a/pype/hosts/maya/plugins/create/create_vrayproxy.py b/pype/hosts/maya/plugins/create/create_vrayproxy.py index 010157ca9a7..0af670a0d39 100644 --- a/pype/hosts/maya/plugins/create/create_vrayproxy.py +++ b/pype/hosts/maya/plugins/create/create_vrayproxy.py @@ -1,7 +1,7 @@ -import avalon.maya +from pype.hosts.maya.api import plugin -class CreateVrayProxy(avalon.maya.Creator): +class CreateVrayProxy(plugin.Creator): """Alembic pointcache for animated data""" name = "vrayproxy" diff --git a/pype/hosts/maya/plugins/create/create_vrayscene.py b/pype/hosts/maya/plugins/create/create_vrayscene.py index b2c33175404..5690fb9f519 100644 --- a/pype/hosts/maya/plugins/create/create_vrayscene.py +++ b/pype/hosts/maya/plugins/create/create_vrayscene.py @@ -8,13 +8,14 @@ from maya import cmds import maya.app.renderSetup.model.renderSetup as renderSetup -from pype.hosts.maya.api import lib +from pype.hosts.maya.api import ( + lib, + plugin +) from pype.api import get_system_settings -import avalon.maya - -class CreateVRayScene(avalon.maya.Creator): +class CreateVRayScene(plugin.Creator): """Create Vray Scene.""" label = "VRay Scene" diff --git a/pype/hosts/maya/plugins/create/create_yeti_cache.py b/pype/hosts/maya/plugins/create/create_yeti_cache.py index c9edb65d8bc..cf4f32b2076 100644 --- a/pype/hosts/maya/plugins/create/create_yeti_cache.py +++ b/pype/hosts/maya/plugins/create/create_yeti_cache.py @@ -1,10 +1,12 @@ from collections import OrderedDict -import avalon.maya -from pype.hosts.maya.api import lib +from pype.hosts.maya.api import ( + lib, + plugin +) -class CreateYetiCache(avalon.maya.Creator): +class CreateYetiCache(plugin.Creator): """Output for procedural plugin nodes of Yeti """ name = "yetiDefault" diff --git a/pype/hosts/maya/plugins/create/create_yeti_rig.py b/pype/hosts/maya/plugins/create/create_yeti_rig.py index c385ca662d5..eda51824c14 100644 --- a/pype/hosts/maya/plugins/create/create_yeti_rig.py +++ b/pype/hosts/maya/plugins/create/create_yeti_rig.py @@ -1,10 +1,12 @@ from maya import cmds -from pype.hosts.maya.api import lib -import avalon.maya +from pype.hosts.maya.api import ( + lib, + plugin +) -class CreateYetiRig(avalon.maya.Creator): +class CreateYetiRig(plugin.Creator): """Output for procedural plugin nodes ( Yeti / XGen / etc)""" label = "Yeti Rig" diff --git a/pype/hosts/nuke/api/plugin.py b/pype/hosts/nuke/api/plugin.py index d6799ed6caa..1b3e3419c65 100644 --- a/pype/hosts/nuke/api/plugin.py +++ b/pype/hosts/nuke/api/plugin.py @@ -1,11 +1,13 @@ -import avalon.api import avalon.nuke -from pype.api import get_current_project_settings +from pype.api import ( + get_current_project_settings, + PypeCreatorMixin +) from .lib import check_subsetname_exists import nuke -class PypeCreator(avalon.nuke.pipeline.Creator): +class PypeCreator(PypeCreatorMixin, avalon.nuke.pipeline.Creator): """Pype Nuke Creator class wrapper """ def __init__(self, *args, **kwargs): diff --git a/pype/hosts/nuke/plugins/create/create_backdrop.py b/pype/hosts/nuke/plugins/create/create_backdrop.py index 243b14c2d80..6d8e6a07101 100644 --- a/pype/hosts/nuke/plugins/create/create_backdrop.py +++ b/pype/hosts/nuke/plugins/create/create_backdrop.py @@ -1,9 +1,9 @@ -import avalon.nuke from avalon.nuke import lib as anlib +from pype.hosts.nuke.api import plugin import nuke -class CreateBackdrop(avalon.nuke.Creator): +class CreateBackdrop(plugin.Creator): """Add Publishable Backdrop""" name = "nukenodes" diff --git a/pype/hosts/nuke/plugins/create/create_camera.py b/pype/hosts/nuke/plugins/create/create_camera.py index 919be0ea79d..51278c6f91a 100644 --- a/pype/hosts/nuke/plugins/create/create_camera.py +++ b/pype/hosts/nuke/plugins/create/create_camera.py @@ -1,9 +1,9 @@ -import avalon.nuke from avalon.nuke import lib as anlib +from pype.hosts.nuke.api import plugin import nuke -class CreateCamera(avalon.nuke.Creator): +class CreateCamera(plugin.PypeCreator): """Add Publishable Backdrop""" name = "camera" diff --git a/pype/hosts/nuke/plugins/create/create_gizmo.py b/pype/hosts/nuke/plugins/create/create_gizmo.py index 19b5bead8fb..c3afbe22f29 100644 --- a/pype/hosts/nuke/plugins/create/create_gizmo.py +++ b/pype/hosts/nuke/plugins/create/create_gizmo.py @@ -1,9 +1,9 @@ -import avalon.nuke from avalon.nuke import lib as anlib +from pype.hosts.nuke.api import plugin import nuke -class CreateGizmo(avalon.nuke.Creator): +class CreateGizmo(plugin.PypeCreator): """Add Publishable "gizmo" group The name is symbolically gizmo as presumably diff --git a/pype/hosts/nuke/plugins/create/create_read.py b/pype/hosts/nuke/plugins/create/create_read.py index 591f9c14a21..d0912b46faf 100644 --- a/pype/hosts/nuke/plugins/create/create_read.py +++ b/pype/hosts/nuke/plugins/create/create_read.py @@ -2,11 +2,12 @@ import avalon.api import avalon.nuke from pype import api as pype +from pype.hosts.nuke.api import plugin import nuke -class CrateRead(avalon.nuke.Creator): +class CrateRead(plugin.PypeCreator): # change this to template preset name = "ReadCopy" label = "Create Read Copy" diff --git a/pype/hosts/photoshop/plugins/create/create_image.py b/pype/hosts/photoshop/plugins/create/create_image.py index c1a7d92a2c1..54b6efad291 100644 --- a/pype/hosts/photoshop/plugins/create/create_image.py +++ b/pype/hosts/photoshop/plugins/create/create_image.py @@ -1,9 +1,9 @@ -from avalon import api +import pype.api from avalon.vendor import Qt from avalon import photoshop -class CreateImage(api.Creator): +class CreateImage(pype.api.Creator): """Image folder for publish.""" name = "imageDefault" diff --git a/pype/hosts/resolve/api/plugin.py b/pype/hosts/resolve/api/plugin.py index 2f7c516c8f2..0423f15c2ae 100644 --- a/pype/hosts/resolve/api/plugin.py +++ b/pype/hosts/resolve/api/plugin.py @@ -492,7 +492,7 @@ def remove(self, container): pass -class Creator(api.Creator): +class Creator(pype.PypeCreatorMixin, api.Creator): """Creator class wrapper """ marker_color = "Purple" diff --git a/pype/hosts/tvpaint/api/plugin.py b/pype/hosts/tvpaint/api/plugin.py new file mode 100644 index 00000000000..6f069586a51 --- /dev/null +++ b/pype/hosts/tvpaint/api/plugin.py @@ -0,0 +1,6 @@ +from pype.api import PypeCreatorMixin +from avalon.tvpaint import pipeline + + +class Creator(PypeCreatorMixin, pipeline.Creator): + pass diff --git a/pype/hosts/tvpaint/plugins/create/create_render_layer.py b/pype/hosts/tvpaint/plugins/create/create_render_layer.py index c2921cebbea..ed7c96c9047 100644 --- a/pype/hosts/tvpaint/plugins/create/create_render_layer.py +++ b/pype/hosts/tvpaint/plugins/create/create_render_layer.py @@ -1,7 +1,8 @@ from avalon.tvpaint import pipeline, lib +from pype.hosts.tvpaint.api import plugin -class CreateRenderlayer(pipeline.Creator): +class CreateRenderlayer(plugin.Creator): """Mark layer group as one instance.""" name = "render_layer" label = "RenderLayer" diff --git a/pype/hosts/tvpaint/plugins/create/create_render_pass.py b/pype/hosts/tvpaint/plugins/create/create_render_pass.py index 7e4b2a4e81d..8583f204517 100644 --- a/pype/hosts/tvpaint/plugins/create/create_render_pass.py +++ b/pype/hosts/tvpaint/plugins/create/create_render_pass.py @@ -1,7 +1,8 @@ from avalon.tvpaint import pipeline, lib +from pype.hosts.tvpaint.api import plugin -class CreateRenderPass(pipeline.Creator): +class CreateRenderPass(plugin.Creator): """Render pass is combination of one or more layers from same group. Requirement to create Render Pass is to have already created beauty diff --git a/pype/hosts/tvpaint/plugins/create/create_review.py b/pype/hosts/tvpaint/plugins/create/create_review.py index 9f7ee1396e8..cfc49a8ac6f 100644 --- a/pype/hosts/tvpaint/plugins/create/create_review.py +++ b/pype/hosts/tvpaint/plugins/create/create_review.py @@ -1,7 +1,8 @@ from avalon.tvpaint import pipeline +from pype.hosts.tvpaint.api import plugin -class CreateReview(pipeline.Creator): +class CreateReview(plugin.Creator): """Review for global review of all layers.""" name = "review" label = "Review" diff --git a/pype/hosts/unreal/api/plugin.py b/pype/hosts/unreal/api/plugin.py index 0c00eb77d6f..94ecd3083b5 100644 --- a/pype/hosts/unreal/api/plugin.py +++ b/pype/hosts/unreal/api/plugin.py @@ -1,7 +1,8 @@ from avalon import api +import pype.api -class Creator(api.Creator): +class Creator(pype.api.Creator): """This serves as skeleton for future Pype specific functionality""" pass diff --git a/pype/lib/__init__.py b/pype/lib/__init__.py index 67a31d17373..62cd363d521 100644 --- a/pype/lib/__init__.py +++ b/pype/lib/__init__.py @@ -78,9 +78,13 @@ EnvironmentPrepData, prepare_host_environments, prepare_context_environments, - get_app_environments_for_context + get_app_environments_for_context, + + compile_list_of_regexes ) +from .profiles_filtering import filter_profiles + from .plugin_tools import ( filter_pyblish_plugins, source_hash, @@ -167,6 +171,10 @@ "prepare_context_environments", "get_app_environments_for_context", + "compile_list_of_regexes", + + "filter_profiles", + "filter_pyblish_plugins", "source_hash", "get_unique_layer_name", diff --git a/pype/lib/profiles_filtering.py b/pype/lib/profiles_filtering.py new file mode 100644 index 00000000000..32c17cbd125 --- /dev/null +++ b/pype/lib/profiles_filtering.py @@ -0,0 +1,193 @@ +import re +import logging +from .applications import compile_list_of_regexes + +log = logging.getLogger(__name__) + + +def _profile_exclusion(matching_profiles, logger): + """Find out most matching profile byt host, task and family match. + + Profiles are selectively filtered. Each item in passed argument must + contain tuple of (profile, profile's score) where score is list of + booleans. Each boolean represents existence of filter for specific key. + Profiles are looped in sequence. In each sequence are profiles split into + true_list and false_list. For next sequence loop are used profiles in + true_list if there are any profiles else false_list is used. + + Filtering ends when only one profile left in true_list. Or when all + existence booleans loops passed, in that case first profile from remainded + profiles is returned. + + Args: + matching_profiles (list): Profiles with same scores. Each item is tuple + with (profile, profile values) + + Returns: + dict: Most matching profile. + """ + + logger.info( + "Search for first most matching profile in match order:" + " Host name -> Task name -> Family." + ) + + if not matching_profiles: + return None + + if len(matching_profiles) == 1: + return matching_profiles[0][0] + + scores_len = len(matching_profiles[0][1]) + for idx in range(scores_len): + profiles_true = [] + profiles_false = [] + for profile, score in matching_profiles: + if score[idx]: + profiles_true.append((profile, score)) + else: + profiles_false.append((profile, score)) + + if profiles_true: + matching_profiles = profiles_true + else: + matching_profiles = profiles_false + + if len(matching_profiles) == 1: + return matching_profiles[0][0] + + return matching_profiles[0][0] + + +def validate_value_by_regexes(value, in_list): + """Validates in any regex from list match entered value. + + Args: + value (str): String where regexes is checked. + in_list (list): List with regexes. + + Returns: + int: Returns `0` when list is not set, is empty or contain "*". + Returns `1` when any regex match value and returns `-1` + when none of regexes match entered value. + """ + if not in_list: + return 0 + + if not isinstance(in_list, (list, tuple, set)): + in_list = [in_list] + + if "*" in in_list: + return 0 + + # If value is not set and in list has specific values then resolve value + # as not matching. + if not value: + return -1 + + regexes = compile_list_of_regexes(in_list) + for regex in regexes: + if re.match(regex, value): + return 1 + return -1 + + +def filter_profiles(profiles_data, key_values, keys_order=None, logger=None): + """ Filter profiles by entered key -> values. + + Profile if marked with score for each key/value from `key_values` with + points -1, 0 or 1. + - if profile contain the key and profile's value contain value from + `key_values` then profile gets 1 point + - if profile does not contain the key or profile's value is empty or + contain "*" then got 0 point + - if profile contain the key, profile's value is not empty and does not + contain "*" and value from `key_values` is not available in the value + then got -1 point + + If profile gets -1 point at any time then is skipped and not used for + output. Profile with higher score is returned. If there are multiple + profiles with same score then first in order is used (order of profiles + matter). + + Args: + profiles_data (list): Profile definitions as dictionaries. + key_values (dict): Mapping of Key <-> Value. Key is checked if is + available in profile and if Value is matching it's values. + keys_order (list, tuple): Order of keys from `key_values` which matters + only when multiple profiles have same score. + logger (logging.Logger): Optionally can be passed different logger. + + Returns: + dict/None: Return most matching profile or None if none of profiles + match at least one criteria. + """ + if not profiles_data: + return None + + if not logger: + logger = log + + if not keys_order: + keys_order = tuple(key_values.keys()) + else: + _keys_order = list(keys_order) + # Make all keys from `key_values` are passed + for key in key_values.keys(): + if key not in _keys_order: + _keys_order.append(key) + keys_order = tuple(_keys_order) + + matching_profiles = None + highest_profile_points = -1 + # Each profile get 1 point for each matching filter. Profile with most + # points is returned. For cases when more than one profile will match + # are also stored ordered lists of matching values. + for profile in profiles_data: + profile_points = 0 + profile_scores = [] + + for key in keys_order: + value = key_values[key] + match = validate_value_by_regexes(value, profile.get(key)) + if match == -1: + profile_value = profile.get(key) or [] + logger.debug( + "\"{}\" not found in {}".format(key, profile_value) + ) + profile_points = -1 + break + + profile_points += match + profile_scores.append(bool(match)) + + if ( + profile_points < 0 + or profile_points < highest_profile_points + ): + continue + + if profile_points > highest_profile_points: + matching_profiles = [] + highest_profile_points = profile_points + + if profile_points == highest_profile_points: + matching_profiles.append((profile, profile_scores)) + + log_parts = " | ".join([ + "{}: \"{}\"".format(*item) + for item in key_values.items() + ]) + + if not matching_profiles: + logger.warning( + "None of profiles match your setup. {}".format(log_parts) + ) + return None + + if len(matching_profiles) > 1: + logger.warning( + "More than one profile match your setup. {}".format(log_parts) + ) + + return _profile_exclusion(matching_profiles, logger) diff --git a/pype/plugin.py b/pype/plugin.py index 2e365dbff47..855b3371d3d 100644 --- a/pype/plugin.py +++ b/pype/plugin.py @@ -1,9 +1,9 @@ import tempfile import os import pyblish.api - +import avalon.api from pype.api import get_project_settings -import inspect +from pype.lib import filter_profiles ValidatePipelineOrder = pyblish.api.ValidatorOrder + 0.05 ValidateContentsOrder = pyblish.api.ValidatorOrder + 0.1 @@ -11,6 +11,89 @@ ValidateMeshOrder = pyblish.api.ValidatorOrder + 0.3 +class TaskNotSetError(KeyError): + def __init__(self, msg=None): + if not msg: + msg = "Creator's subset name template requires task name." + super(TaskNotSetError, self).__init__(msg) + + +class PypeCreatorMixin: + """Helper to override avalon's default class methods. + + Mixin class must be used as first in inheritance order to override methods. + """ + default_tempate = "{family}{Variant}" + + @classmethod + def get_subset_name( + cls, variant, task_name, asset_id, project_name, host_name=None + ): + if not cls.family: + return "" + + if not host_name: + host_name = os.environ["AVALON_APP"] + + # Use only last part of class family value split by dot (`.`) + family = cls.family.rsplit(".", 1)[-1] + + # Get settings + tools_settings = get_project_settings(project_name)["global"]["tools"] + profiles = tools_settings["creator"]["subset_name_profiles"] + filtering_criteria = { + "families": family, + "hosts": host_name, + "tasks": task_name + } + + matching_profile = filter_profiles(profiles, filtering_criteria) + template = None + if matching_profile: + template = matching_profile["template"] + + # Make sure template is set (matching may have empty string) + if not template: + template = cls.default_tempate + + # Simple check of task name existence for template with {task} in + # - missing task should be possible only in Standalone publisher + if not task_name and "{task" in template.lower(): + raise TaskNotSetError() + + fill_pairs = ( + ("variant", variant), + ("family", family), + ("task", task_name) + ) + fill_data = {} + for key, value in fill_pairs: + # Handle cases when value is `None` (standalone publisher) + if value is None: + continue + # Keep value as it is + fill_data[key] = value + # Both key and value are with upper case + fill_data[key.upper()] = value.upper() + + # Capitalize only first char of value + # - conditions are because of possible index errors + capitalized = "" + if value: + # Upper first character + capitalized += value[0].upper() + # Append rest of string if there is any + if len(value) > 1: + capitalized += value[1:] + fill_data[key.capitalize()] = capitalized + + return template.format(**fill_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/pype/settings/defaults/project_settings/global.json b/pype/settings/defaults/project_settings/global.json index c74cc9a2c58..92ef9bdb28b 100644 --- a/pype/settings/defaults/project_settings/global.json +++ b/pype/settings/defaults/project_settings/global.json @@ -130,7 +130,23 @@ "rigging", "rig" ] - } + }, + "subset_name_profiles": [ + { + "families": [], + "hosts": [], + "tasks": [], + "template": "{family}{Variant}" + }, + { + "families": [ + "render" + ], + "hosts": [], + "tasks": [], + "template": "{family}{Task}{Variant}" + } + ] }, "Workfiles": { "last_workfile_on_startup": [ diff --git a/pype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json b/pype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json index fa49e12fb79..224389d42ed 100644 --- a/pype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json +++ b/pype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json @@ -12,13 +12,50 @@ "children": [ { "type": "dict-modifiable", - "collapsible": false, + "collapsible": true, "key": "families_smart_select", "label": "Families smart select", "object_type": { "type": "list", "object_type": "text" } + }, + { + "type": "list", + "key": "subset_name_profiles", + "label": "Subset name profiles", + "use_label_wrap": true, + "object_type": { + "type": "dict", + "children": [ + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }, + { + "key": "hosts", + "label": "Hosts", + "type": "list", + "object_type": "text" + }, + { + "key": "tasks", + "label": "Task names", + "type": "list", + "object_type": "text" + }, + { + "type": "separator" + }, + { + "type": "text", + "key": "template", + "label": "Template" + } + ] + } } ] }, diff --git a/pype/tools/standalonepublish/app.py b/pype/tools/standalonepublish/app.py index 920dd32f7cc..54cde9e3229 100644 --- a/pype/tools/standalonepublish/app.py +++ b/pype/tools/standalonepublish/app.py @@ -9,6 +9,7 @@ from .widgets import ( AssetWidget, FamilyWidget, ComponentsWidget, ShadowWidget ) +from .widgets.constants import HOST_NAME from avalon import style from pype.api import resources from avalon.api import AvalonMongoDB @@ -73,6 +74,7 @@ def __init__(self, pyblish_paths, parent=None): # signals widget_assets.selection_changed.connect(self.on_asset_changed) + widget_assets.task_changed.connect(self._on_task_change) widget_assets.project_changed.connect(self.on_project_change) widget_family.stateChanged.connect(self.set_valid_family) @@ -150,6 +152,9 @@ def on_asset_changed(self): self.widget_family.change_asset(None) self.widget_family.on_data_changed() + def _on_task_change(self): + self.widget_family.on_task_change() + def keyPressEvent(self, event): ''' Handling Ctrl+V KeyPress event Can handle: @@ -208,6 +213,8 @@ def collect_data(self): def main(): + os.environ["AVALON_APP"] = HOST_NAME + # Allow to change icon of running process in windows taskbar if os.name == "nt": ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID( diff --git a/pype/tools/standalonepublish/widgets/constants.py b/pype/tools/standalonepublish/widgets/constants.py new file mode 100644 index 00000000000..0ecc8e82e7e --- /dev/null +++ b/pype/tools/standalonepublish/widgets/constants.py @@ -0,0 +1 @@ +HOST_NAME = "standalonepublisher" diff --git a/pype/tools/standalonepublish/widgets/widget_asset.py b/pype/tools/standalonepublish/widgets/widget_asset.py index 91565668cc6..4680e88344c 100644 --- a/pype/tools/standalonepublish/widgets/widget_asset.py +++ b/pype/tools/standalonepublish/widgets/widget_asset.py @@ -125,6 +125,7 @@ class AssetWidget(QtWidgets.QWidget): assets_refreshed = QtCore.Signal() # on model refresh selection_changed = QtCore.Signal() # on view selection change current_changed = QtCore.Signal() # on view current index change + task_changed = QtCore.Signal() def __init__(self, dbcon, parent=None): super(AssetWidget, self).__init__(parent=parent) @@ -190,6 +191,9 @@ def __init__(self, dbcon, parent=None): selection = view.selectionModel() selection.selectionChanged.connect(self.selection_changed) selection.currentChanged.connect(self.current_changed) + task_view.selectionModel().selectionChanged.connect( + self._on_task_change + ) refresh.clicked.connect(self.refresh) self.selection_changed.connect(self._refresh_tasks) @@ -269,7 +273,18 @@ def _refresh_model(self): def refresh(self): self._refresh_model() + def _on_task_change(self): + try: + index = self.task_view.selectedIndexes()[0] + task_name = self.task_model.itemData(index)[0] + except Exception: + task_name = None + + self.dbcon.Session["AVALON_TASK"] = task_name + self.task_changed.emit() + def _refresh_tasks(self): + self.dbcon.Session["AVALON_TASK"] = None tasks = [] selected = self.get_selected_assets() if len(selected) == 1: @@ -279,7 +294,8 @@ def _refresh_tasks(self): if asset: tasks = asset.get('data', {}).get('tasks', []) self.task_model.set_tasks(tasks) - self.task_view.setVisible(len(tasks)>0) + self.task_view.setVisible(len(tasks) > 0) + self.task_changed.emit() def get_active_asset(self): """Return the asset id the current asset.""" diff --git a/pype/tools/standalonepublish/widgets/widget_components.py b/pype/tools/standalonepublish/widgets/widget_components.py index 8d627b7eedd..d4638ea4374 100644 --- a/pype/tools/standalonepublish/widgets/widget_components.py +++ b/pype/tools/standalonepublish/widgets/widget_components.py @@ -7,6 +7,7 @@ from Qt import QtWidgets, QtCore from . import DropDataFrame +from .constants import HOST_NAME from avalon import io from pype.api import execute, Logger from pype.lib import get_pype_execute_args @@ -178,8 +179,8 @@ def set_context(project, asset, task): io.Session["current_dir"] = os.path.normpath(os.getcwd()) - os.environ["AVALON_APP"] = "standalonepublish" - io.Session["AVALON_APP"] = "standalonepublish" + os.environ["AVALON_APP"] = HOST_NAME + io.Session["AVALON_APP"] = HOST_NAME io.uninstall() diff --git a/pype/tools/standalonepublish/widgets/widget_family.py b/pype/tools/standalonepublish/widgets/widget_family.py index ed30dba420d..55215035086 100644 --- a/pype/tools/standalonepublish/widgets/widget_family.py +++ b/pype/tools/standalonepublish/widgets/widget_family.py @@ -1,11 +1,16 @@ import os -from collections import namedtuple +import re from Qt import QtWidgets, QtCore from . import HelpRole, FamilyRole, ExistsRole, PluginRole, PluginKeyRole from . import FamilyDescriptionWidget -from pype.api import get_project_settings +from pype.api import ( + get_project_settings, + Creator +) +from pype.plugin import TaskNotSetError +from avalon.tools.creator.app import SubsetAllowedSymbols class FamilyWidget(QtWidgets.QWidget): @@ -123,6 +128,9 @@ def collect_data(self): } return data + def on_task_change(self): + self.on_data_changed() + def change_asset(self, name): if name is None: name = self.NOT_SELECTED @@ -168,65 +176,113 @@ def _on_action_clicked(self, action): def _on_data_changed(self): asset_name = self.asset_name - subset_name = self.input_subset.text() + user_input_text = self.input_subset.text() item = self.list_families.currentItem() if item is None: return - assets = None + asset_doc = None if asset_name != self.NOT_SELECTED: # Get the assets from the database which match with the name - assets_db = self.dbcon.find( - filter={"type": "asset"}, - projection={"name": 1} + asset_doc = self.dbcon.find_one( + { + "type": "asset", + "name": asset_name + }, + {"_id": 1} ) - assets = [ - asset for asset in assets_db if asset_name in asset["name"] - ] # Get plugin and family plugin = item.data(PluginRole) - if plugin is None: - return - family = plugin.family.rsplit(".", 1)[-1] + # Early exit if no asset name + if not asset_name.strip(): + self._build_menu([]) + item.setData(ExistsRole, False) + print("Asset name is required ..") + self.stateChanged.emit(False) + return - # Update the result - if subset_name: - subset_name = subset_name[0].upper() + subset_name[1:] - self.input_result.setText("{}{}".format(family, subset_name)) + # Get the asset from the database which match with the name + asset_doc = self.dbcon.find_one( + {"name": asset_name, "type": "asset"}, + projection={"_id": 1} + ) + # Get plugin + plugin = item.data(PluginRole) + if asset_doc and plugin: + project_name = self.dbcon.Session["AVALON_PROJECT"] + asset_id = asset_doc["_id"] + task_name = self.dbcon.Session["AVALON_TASK"] + + # Calculate subset name with Creator plugin + try: + subset_name = plugin.get_subset_name( + user_input_text, task_name, asset_id, project_name + ) + # Force replacement of prohibited symbols + # QUESTION should Creator care about this and here should be + # only validated with schema regex? + subset_name = re.sub( + "[^{}]+".format(SubsetAllowedSymbols), + "", + subset_name + ) + self.input_result.setText(subset_name) + + except TaskNotSetError: + subset_name = "" + self.input_result.setText("Select task please") - if assets: # Get all subsets of the current asset - asset_ids = [asset["_id"] for asset in assets] - subsets = self.dbcon.find(filter={"type": "subset", - "name": {"$regex": "{}*".format(family), - "$options": "i"}, - "parent": {"$in": asset_ids}}) or [] - - # Get all subsets' their subset name, "Default", "High", "Low" - existed_subsets = [sub["name"].split(family)[-1] - for sub in subsets] - - if plugin.defaults and isinstance(plugin.defaults, list): - defaults = plugin.defaults[:] + [self.Separator] - lowered = [d.lower() for d in plugin.defaults] - for sub in [s for s in existed_subsets - if s.lower() not in lowered]: - defaults.append(sub) - else: - defaults = existed_subsets - + subset_docs = self.dbcon.find( + { + "type": "subset", + "parent": asset_id + }, + {"name": 1} + ) + existing_subset_names = set(subset_docs.distinct("name")) + + # Defaults to dropdown + defaults = [] + # Check if Creator plugin has set defaults + if ( + plugin.defaults + and isinstance(plugin.defaults, (list, tuple, set)) + ): + defaults = list(plugin.defaults) + + # Replace + compare_regex = re.compile( + subset_name.replace(user_input_text, "(.+)") + ) + subset_hints = set() + if user_input_text: + for _name in existing_subset_names: + _result = compare_regex.search(_name) + if _result: + subset_hints |= set(_result.groups()) + + subset_hints = subset_hints - set(defaults) + if subset_hints: + if defaults: + defaults.append(self.Separator) + defaults.extend(subset_hints) self._build_menu(defaults) item.setData(ExistsRole, True) + else: + subset_name = user_input_text self._build_menu([]) item.setData(ExistsRole, False) - if asset_name != self.NOT_SELECTED: - # TODO add logging into standalone_publish - print("'%s' not found .." % asset_name) + + if not plugin: + print("No registered families ..") + else: + print("Asset '%s' not found .." % asset_name) self.on_version_refresh() @@ -249,30 +305,46 @@ def on_version_refresh(self): subset_name = self.input_result.text() version = 1 + asset_doc = None + subset_doc = None + versions = None if ( asset_name != self.NOT_SELECTED and subset_name.strip() != '' ): - asset = self.dbcon.find_one({ - 'type': 'asset', - 'name': asset_name - }) - subset = self.dbcon.find_one({ - 'type': 'subset', - 'parent': asset['_id'], - 'name': subset_name - }) - if subset: - versions = self.dbcon.find({ + asset_doc = self.dbcon.find_one( + { + 'type': 'asset', + 'name': asset_name + }, + {"_id": 1} + ) + + if asset_doc: + subset_doc = self.dbcon.find_one( + { + 'type': 'subset', + 'parent': asset_doc['_id'], + 'name': subset_name + }, + {"_id": 1} + ) + + if subset_doc: + versions = self.dbcon.find( + { 'type': 'version', - 'parent': subset['_id'] - }) - if versions: - versions = sorted( - [v for v in versions], - key=lambda ver: ver['name'] - ) - version = int(versions[-1]['name']) + 1 + 'parent': subset_doc['_id'] + }, + {"name": 1} + ).distinct("name") + + if versions: + versions = sorted( + [v for v in versions], + key=lambda ver: ver['name'] + ) + version = int(versions[-1]['name']) + 1 self.version_spinbox.setValue(version) @@ -322,11 +394,8 @@ def refresh(self): settings = get_project_settings(project_name) sp_settings = settings.get('standalonepublisher', {}) - for key, creator in sp_settings.get("create", {}).items(): - if key == "__dynamic_keys_labels__": - continue - - creator = namedtuple("Creator", creator.keys())(*creator.values()) + for key, creator_data in sp_settings.get("create", {}).items(): + creator = type(key, (Creator, ), creator_data) label = creator.label or creator.family item = QtWidgets.QListWidgetItem(label)