Skip to content
This repository has been archived by the owner on Sep 20, 2024. It is now read-only.

Commit

Permalink
Merge pull request #1072 from pypeclub/feature/2_0_656-optionally-add…
Browse files Browse the repository at this point in the history
…-task-to-subset-name

Optionally add task to subset name
  • Loading branch information
mkolar authored Mar 3, 2021
2 parents 07aba02 + c762e91 commit ee41f8f
Show file tree
Hide file tree
Showing 59 changed files with 650 additions and 163 deletions.
5 changes: 5 additions & 0 deletions pype/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
from . import resources

from .plugin import (
PypeCreatorMixin,
Creator,

Extractor,

ValidatePipelineOrder,
Expand Down Expand Up @@ -67,6 +70,8 @@
"resources",

# plugin classes
"PypeCreatorMixin",
"Creator",
"Extractor",
# ordering
"ValidatePipelineOrder",
Expand Down
6 changes: 6 additions & 0 deletions pype/hosts/blender/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import bpy

from avalon import api
import avalon.blender
from pype.api import PypeCreatorMixin

VALID_EXTENSIONS = [".blend", ".json"]

Expand Down Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions pype/hosts/harmony/plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from avalon import harmony
from pype.api import PypeCreatorMixin


class Creator(PypeCreatorMixin, harmony.Creator):
pass
6 changes: 6 additions & 0 deletions pype/hosts/houdini/plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from avalon import houdini
from pype.api import PypeCreatorMixin


class Creator(PypeCreatorMixin, houdini.Creator):
pass
6 changes: 6 additions & 0 deletions pype/hosts/maya/plugin.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -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
Expand Down
8 changes: 6 additions & 2 deletions pype/hosts/nuke/plugin.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import re
import avalon.api
import avalon.nuke
from pype.api import config
from pype.api import (
config,
PypeCreatorMixin
)

class PypeCreator(avalon.nuke.pipeline.Creator):

class PypeCreator(PypeCreatorMixin, avalon.nuke.pipeline.Creator):
"""Pype Nuke Creator class wrapper
"""
def __init__(self, *args, **kwargs):
Expand Down
3 changes: 2 additions & 1 deletion pype/hosts/resolve/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from pype.hosts import resolve
from avalon.vendor import qargparse
from pype.api import config
import pype.api

from Qt import QtWidgets, QtCore

Expand Down Expand Up @@ -251,7 +252,7 @@ def remove(self, container):
pass


class Creator(api.Creator):
class Creator(pype.api.Creator):
"""Creator class wrapper
"""
marker_color = "Purple"
Expand Down
6 changes: 6 additions & 0 deletions pype/hosts/tvpaint/plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from pype.api import PypeCreatorMixin
from avalon.tvpaint import pipeline


class Creator(PypeCreatorMixin, pipeline.Creator):
pass
4 changes: 4 additions & 0 deletions pype/lib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
_subprocess
)

from .profiles_filtering import filter_profiles

from .plugin_tools import (
filter_pyblish_plugins,
source_hash,
Expand Down Expand Up @@ -69,6 +71,8 @@
"launch_application",
"ApplicationAction",

"filter_profiles",

"filter_pyblish_plugins",
"get_unique_layer_name",
"get_background_layers",
Expand Down
211 changes: 211 additions & 0 deletions pype/lib/profiles_filtering.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
import re
import logging

log = logging.getLogger(__name__)


def compile_list_of_regexes(in_list):
"""Convert strings in entered list to compiled regex objects."""
regexes = list()
if not in_list:
return regexes

for item in in_list:
if not item:
continue
try:
regexes.append(re.compile(item))
except TypeError:
print((
"Invalid type \"{}\" value \"{}\"."
" Expected string based object. Skipping."
).format(str(type(item)), str(item)))
return regexes


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)
Loading

0 comments on commit ee41f8f

Please sign in to comment.