mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge branch 'develop' into feature/igniter-improvements
This commit is contained in:
commit
1ee274ffc5
68 changed files with 708 additions and 213 deletions
|
|
@ -44,6 +44,9 @@ from .lib.avalon_context import (
|
|||
from . import resources
|
||||
|
||||
from .plugin import (
|
||||
PypeCreatorMixin,
|
||||
Creator,
|
||||
|
||||
Extractor,
|
||||
|
||||
ValidatePipelineOrder,
|
||||
|
|
@ -86,6 +89,9 @@ __all__ = [
|
|||
# Resources
|
||||
"resources",
|
||||
|
||||
# Pype creator mixin
|
||||
"PypeCreatorMixin",
|
||||
"Creator",
|
||||
# plugin classes
|
||||
"Extractor",
|
||||
# ordering
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from avalon import api
|
||||
import pype.api
|
||||
from avalon.vendor import Qt
|
||||
from avalon import aftereffects
|
||||
|
||||
|
|
@ -7,7 +7,7 @@ import logging
|
|||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CreateRender(api.Creator):
|
||||
class CreateRender(pype.api.Creator):
|
||||
"""Render folder for publish."""
|
||||
|
||||
name = "renderDefault"
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ from typing import Dict, List, Optional
|
|||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from avalon import api, blender
|
|||
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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ import bpy
|
|||
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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
6
pype/hosts/harmony/api/plugin.py
Normal file
6
pype/hosts/harmony/api/plugin.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
from avalon import harmony
|
||||
from pype.api import PypeCreatorMixin
|
||||
|
||||
|
||||
class Creator(PypeCreatorMixin, harmony.Creator):
|
||||
pass
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -592,7 +592,7 @@ class ClipLoader:
|
|||
return track_item
|
||||
|
||||
|
||||
class Creator(avalon.Creator):
|
||||
class Creator(pype.Creator):
|
||||
"""Creator class wrapper
|
||||
"""
|
||||
clip_color = "Purple"
|
||||
|
|
|
|||
6
pype/hosts/houdini/api/plugin.py
Normal file
6
pype/hosts/houdini/api/plugin.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
from avalon import houdini
|
||||
from pype.api import PypeCreatorMixin
|
||||
|
||||
|
||||
class Creator(PypeCreatorMixin, houdini.Creator):
|
||||
pass
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"])),
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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 @@ class CreateCamera(avalon.maya.Creator):
|
|||
self.data['bakeToWorldSpace'] = True
|
||||
|
||||
|
||||
class CreateCameraRig(avalon.maya.Creator):
|
||||
class CreateCameraRig(plugin.Creator):
|
||||
"""Complex hierarchy with camera."""
|
||||
|
||||
name = "camerarigMain"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -8,13 +8,14 @@ import requests
|
|||
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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -8,13 +8,14 @@ import requests
|
|||
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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -2,11 +2,12 @@ from collections import OrderedDict
|
|||
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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -492,7 +492,7 @@ class TimelineItemLoader(api.Loader):
|
|||
pass
|
||||
|
||||
|
||||
class Creator(api.Creator):
|
||||
class Creator(pype.PypeCreatorMixin, api.Creator):
|
||||
"""Creator class wrapper
|
||||
"""
|
||||
marker_color = "Purple"
|
||||
|
|
|
|||
6
pype/hosts/tvpaint/api/plugin.py
Normal file
6
pype/hosts/tvpaint/api/plugin.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
from pype.api import PypeCreatorMixin
|
||||
from avalon.tvpaint import pipeline
|
||||
|
||||
|
||||
class Creator(PypeCreatorMixin, pipeline.Creator):
|
||||
pass
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -78,9 +78,13 @@ from .applications import (
|
|||
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 @@ __all__ = [
|
|||
"prepare_context_environments",
|
||||
"get_app_environments_for_context",
|
||||
|
||||
"compile_list_of_regexes",
|
||||
|
||||
"filter_profiles",
|
||||
|
||||
"filter_pyblish_plugins",
|
||||
"source_hash",
|
||||
"get_unique_layer_name",
|
||||
|
|
|
|||
|
|
@ -201,8 +201,11 @@ class PypeLogger:
|
|||
# Information about mongo url
|
||||
log_mongo_url = None
|
||||
log_mongo_url_components = None
|
||||
log_database_name = None
|
||||
log_collection_name = None
|
||||
|
||||
# Database name in Mongo
|
||||
log_database_name = "pype"
|
||||
# Collection name under database in Mongo
|
||||
log_collection_name = "logs"
|
||||
|
||||
# PYPE_DEBUG
|
||||
pype_debug = 0
|
||||
|
|
@ -348,25 +351,14 @@ class PypeLogger:
|
|||
cls.pype_debug = int(os.getenv("PYPE_DEBUG") or "0")
|
||||
|
||||
# Mongo URL where logs will be stored
|
||||
cls.log_mongo_url = (
|
||||
os.environ.get("PYPE_LOG_MONGO_URL")
|
||||
or os.environ.get("PYPE_MONGO")
|
||||
)
|
||||
cls.log_mongo_url = os.environ.get("PYPE_MONGO")
|
||||
|
||||
if not cls.log_mongo_url:
|
||||
cls.use_mongo_logging = False
|
||||
else:
|
||||
# Decompose url
|
||||
cls.log_mongo_url_components = decompose_url(cls.log_mongo_url)
|
||||
|
||||
# Database name in Mongo
|
||||
cls.log_database_name = (
|
||||
os.environ.get("PYPE_LOG_MONGO_DB") or "pype"
|
||||
)
|
||||
# Collection name under database in Mongo
|
||||
cls.log_collection_name = (
|
||||
os.environ.get("PYPE_LOG_MONGO_COL") or "logs"
|
||||
)
|
||||
|
||||
# Mark as initialized
|
||||
cls.initialized = True
|
||||
|
||||
|
|
|
|||
193
pype/lib/profiles_filtering.py
Normal file
193
pype/lib/profiles_filtering.py
Normal file
|
|
@ -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)
|
||||
|
|
@ -38,11 +38,6 @@ class AvalonModule(PypeModule, ITrayModule, IRestApi):
|
|||
self.avalon_mongo_url = avalon_mongo_url
|
||||
self.avalon_mongo_timeout = avalon_mongo_timeout
|
||||
|
||||
self.schema_path = os.path.join(
|
||||
os.path.dirname(pype.PACKAGE_DIR),
|
||||
"schema"
|
||||
)
|
||||
|
||||
# Tray attributes
|
||||
self.libraryloader = None
|
||||
self.rest_api_obj = None
|
||||
|
|
@ -50,23 +45,11 @@ class AvalonModule(PypeModule, ITrayModule, IRestApi):
|
|||
def get_global_environments(self):
|
||||
"""Avalon global environments for pype implementation."""
|
||||
return {
|
||||
# 100% hardcoded
|
||||
"AVALON_SCHEMA": self.schema_path,
|
||||
"AVALON_CONFIG": "pype",
|
||||
"AVALON_LABEL": "Pype",
|
||||
|
||||
# Modifiable by settings
|
||||
# - mongo ulr for avalon projects
|
||||
"AVALON_MONGO": self.avalon_mongo_url,
|
||||
# TODO thumbnails root should be multiplafrom
|
||||
# - thumbnails root
|
||||
"AVALON_THUMBNAIL_ROOT": self.thumbnail_root,
|
||||
# - mongo timeout in ms
|
||||
"AVALON_TIMEOUT": str(self.avalon_mongo_timeout),
|
||||
|
||||
# May be modifiable?
|
||||
# - mongo database name where projects are stored
|
||||
"AVALON_DB": "avalon"
|
||||
}
|
||||
|
||||
def tray_init(self):
|
||||
|
|
|
|||
|
|
@ -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 @@ ValidateSceneOrder = pyblish.api.ValidatorOrder + 0.2
|
|||
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)
|
||||
|
|
|
|||
|
|
@ -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": [
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
{
|
||||
"avalon": {
|
||||
"AVALON_MONGO": "",
|
||||
"AVALON_TIMEOUT": 1000,
|
||||
"AVALON_THUMBNAIL_ROOT": {
|
||||
"windows": "",
|
||||
"darwin": "",
|
||||
"linux": ""
|
||||
},
|
||||
"AVALON_DB_DATA": "{PYPE_SETUP_PATH}/../mongo_db_data"
|
||||
}
|
||||
},
|
||||
"ftrack": {
|
||||
"enabled": true,
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -11,12 +11,6 @@
|
|||
"label": "Avalon",
|
||||
"collapsible": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"key": "AVALON_MONGO",
|
||||
"label": "Avalon Mongo URL",
|
||||
"placeholder": "Pype Mongo is used if not filled."
|
||||
},
|
||||
{
|
||||
"type": "number",
|
||||
"key": "AVALON_TIMEOUT",
|
||||
|
|
@ -29,11 +23,6 @@
|
|||
"key": "AVALON_THUMBNAIL_ROOT",
|
||||
"multiplatform": true,
|
||||
"multipath": false
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "AVALON_DB_DATA",
|
||||
"label": "Avalon Mongo Data Location"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from Qt import QtWidgets, QtCore, QtGui
|
|||
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 @@ class Window(QtWidgets.QDialog):
|
|||
|
||||
# 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 @@ class Window(QtWidgets.QDialog):
|
|||
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 @@ class Window(QtWidgets.QDialog):
|
|||
|
||||
|
||||
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(
|
||||
|
|
|
|||
1
pype/tools/standalonepublish/widgets/constants.py
Normal file
1
pype/tools/standalonepublish/widgets/constants.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
HOST_NAME = "standalonepublisher"
|
||||
|
|
@ -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 @@ class AssetWidget(QtWidgets.QWidget):
|
|||
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 @@ class AssetWidget(QtWidgets.QWidget):
|
|||
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 @@ class AssetWidget(QtWidgets.QWidget):
|
|||
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."""
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import string
|
|||
|
||||
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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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 @@ class FamilyWidget(QtWidgets.QWidget):
|
|||
}
|
||||
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 @@ class FamilyWidget(QtWidgets.QWidget):
|
|||
|
||||
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:
|
||||
|
||||
# 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
|
||||
|
||||
family = plugin.family.rsplit(".", 1)[-1]
|
||||
# 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"]
|
||||
|
||||
# Update the result
|
||||
if subset_name:
|
||||
subset_name = subset_name[0].upper() + subset_name[1:]
|
||||
self.input_result.setText("{}{}".format(family, subset_name))
|
||||
# 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 []
|
||||
subset_docs = self.dbcon.find(
|
||||
{
|
||||
"type": "subset",
|
||||
"parent": asset_id
|
||||
},
|
||||
{"name": 1}
|
||||
)
|
||||
existing_subset_names = set(subset_docs.distinct("name"))
|
||||
|
||||
# Get all subsets' their subset name, "Default", "High", "Low"
|
||||
existed_subsets = [sub["name"].split(family)[-1]
|
||||
for sub in subsets]
|
||||
# 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)
|
||||
|
||||
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
|
||||
# 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 @@ class FamilyWidget(QtWidgets.QWidget):
|
|||
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 @@ class FamilyWidget(QtWidgets.QWidget):
|
|||
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)
|
||||
|
|
|
|||
34
start.py
34
start.py
|
|
@ -185,6 +185,38 @@ def run(arguments: list, env: dict = None) -> int:
|
|||
return p.returncode
|
||||
|
||||
|
||||
def set_avalon_environments():
|
||||
"""Set avalon specific environments.
|
||||
|
||||
These are non modifiable environments for avalon workflow that must be set
|
||||
before avalon module is imported because avalon works with globals set with
|
||||
environment variables.
|
||||
"""
|
||||
from pype import PACKAGE_DIR
|
||||
|
||||
# Path to pype's schema
|
||||
schema_path = os.path.join(
|
||||
os.path.dirname(PACKAGE_DIR),
|
||||
"schema"
|
||||
)
|
||||
# Avalon mongo URL
|
||||
avalon_mongo_url = (
|
||||
os.environ.get("AVALON_MONGO")
|
||||
or os.environ["PYPE_MONGO"]
|
||||
)
|
||||
os.environ.update({
|
||||
# Mongo url (use same as pype has)
|
||||
"AVALON_MONGO": avalon_mongo_url,
|
||||
|
||||
"AVALON_SCHEMA": schema_path,
|
||||
# Mongo DB name where avalon docs are stored
|
||||
"AVALON_DB": "avalon",
|
||||
# Name of config
|
||||
"AVALON_CONFIG": "pype",
|
||||
"AVALON_LABEL": "Pype"
|
||||
})
|
||||
|
||||
|
||||
def set_modules_environments():
|
||||
"""Set global environments for pype modules.
|
||||
|
||||
|
|
@ -571,6 +603,8 @@ def boot():
|
|||
from pype.lib import terminal as t
|
||||
from pype.version import __version__
|
||||
print(">>> loading environments ...")
|
||||
# Must happen before `set_modules_environments`
|
||||
set_avalon_environments()
|
||||
set_modules_environments()
|
||||
|
||||
assert version_path, "Version path not defined."
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue