From 57b18fcd230e0834e057cf99696f25b0f706230b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 15 Jun 2021 15:08:06 +0200 Subject: [PATCH 001/736] created pipeline folder in openpype root --- openpype/pipeline/__init__.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 openpype/pipeline/__init__.py diff --git a/openpype/pipeline/__init__.py b/openpype/pipeline/__init__.py new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/openpype/pipeline/__init__.py @@ -0,0 +1 @@ + From c6c7c92d0cb78bb4ff3d837af6217eda8e14fe21 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 15 Jun 2021 15:09:17 +0200 Subject: [PATCH 002/736] initial commit of `AbtractAttrDef` the abstract attribute definition --- .../pipeline/lib/attribute_definitions.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 openpype/pipeline/lib/attribute_definitions.py diff --git a/openpype/pipeline/lib/attribute_definitions.py b/openpype/pipeline/lib/attribute_definitions.py new file mode 100644 index 0000000000..b0e9acade2 --- /dev/null +++ b/openpype/pipeline/lib/attribute_definitions.py @@ -0,0 +1,24 @@ +from abc import ABCMeta, abstractmethod +import six + + +@six.add_metaclass(ABCMeta) +class AbtractAttrDef: + """Abstraction of attribute definiton. + + Each attribute definition must have implemented validation and + conversion method. + + Attribute definition should have ability to return "default" value. That + can be based on passed data into `__init__` so is not abstracted to + attribute. + """ + + @abstractmethod + def convert_value(self, value): + """Convert value to a valid one. + + Convert passed value to a valid type. Use default if value can't be + converted. + """ + pass From fd2f7c9a02815e97ec48db099b14a36dcd66a1aa Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 15 Jun 2021 15:11:00 +0200 Subject: [PATCH 003/736] Implemented boolean attribute definition --- openpype/pipeline/lib/attribute_definitions.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/openpype/pipeline/lib/attribute_definitions.py b/openpype/pipeline/lib/attribute_definitions.py index b0e9acade2..780cc2a743 100644 --- a/openpype/pipeline/lib/attribute_definitions.py +++ b/openpype/pipeline/lib/attribute_definitions.py @@ -22,3 +22,21 @@ class AbtractAttrDef: converted. """ pass + + +class BoolDef(AbtractAttrDef): + """Boolean representation. + + Args: + default(bool): Default value. Set to `False` if not defined. + """ + + def __init__(self, default=None): + if default is None: + default = False + self.default = default + + def convert_value(self, value): + if isinstance(value, bool): + return value + return self.default From 55841c16ec9579d4be12b193aaf09b3fc2f401df Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 15 Jun 2021 15:11:19 +0200 Subject: [PATCH 004/736] implemented number attribute definition --- .../pipeline/lib/attribute_definitions.py | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/openpype/pipeline/lib/attribute_definitions.py b/openpype/pipeline/lib/attribute_definitions.py index 780cc2a743..66c680bf21 100644 --- a/openpype/pipeline/lib/attribute_definitions.py +++ b/openpype/pipeline/lib/attribute_definitions.py @@ -24,6 +24,61 @@ class AbtractAttrDef: pass +class NumberDef(AbtractAttrDef): + """Number definition. + + Number can have defined minimum/maximum value and decimal points. Value + is integer if decimals are 0. + + Args: + minimum(int, float): Minimum possible value. + maximum(int, float): Maximum possible value. + decimals(int): Maximum decimal points of value. + default(int, float): Default value for conversion. + """ + + def __init__( + self, minimum=None, maximum=None, decimals=None, default=None + ): + minimum = 0 if minimum is None else minimum + maximum = 999999 if maximum is None else maximum + # Swap min/max when are passed in opposited order + if minimum > maximum: + maximum, minimum = minimum, maximum + + if default is None: + default = 0 + + elif not isinstance(default, (int, float)): + raise TypeError(( + "'default' argument must be 'int' or 'float', not '{}'" + ).format(type(default))) + + # Fix default value by mim/max values + if default < minimum: + default = minimum + + elif default > maximum: + default = maximum + + self.minimum = minimum + self.maximum = maximum + self.default = default + self.decimals = 0 if decimals is None else decimals + + def convert_value(self, value): + if isinstance(value, six.string_types): + try: + value = float(value) + except Exception: + pass + + if not isinstance(value, (int, float)): + return self.default + + if self.decimals == 0: + return int(value) + return round(float(value), self.decimals) class BoolDef(AbtractAttrDef): """Boolean representation. From 9d32a8792d10d494e74ce420f9f09c80d0a7a08a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 15 Jun 2021 15:11:37 +0200 Subject: [PATCH 005/736] implemented text attribute definition --- .../pipeline/lib/attribute_definitions.py | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/openpype/pipeline/lib/attribute_definitions.py b/openpype/pipeline/lib/attribute_definitions.py index 66c680bf21..efe99a7a05 100644 --- a/openpype/pipeline/lib/attribute_definitions.py +++ b/openpype/pipeline/lib/attribute_definitions.py @@ -79,6 +79,48 @@ class NumberDef(AbtractAttrDef): if self.decimals == 0: return int(value) return round(float(value), self.decimals) + + +class TextDef(AbtractAttrDef): + """Text definition. + + Text can have multiline option so endline characters are allowed regex + validation can be applied placeholder for UI purposes and default value. + + Regex validation is not part of attribute implemntentation. + + Args: + multiline(bool): Text has single or multiline support. + regex(str, re.Pattern): Regex validation. + placeholder(str): UI placeholder for attribute. + default(str, None): Default value. Empty string used when not defined. + """ + def __init__( + self, multiline=None, regex=None, placeholder=None, default=None + ): + if multiline is None: + multiline = False + + if default is None: + default = "" + + elif not isinstance(default, six.string_types): + raise TypeError(( + "'default' argument must be a {}, not '{}'" + ).format(six.string_types, type(default))) + + if isinstance(regex, six.string_types): + regex = re.compile(regex) + + self.multiline = multiline + self.placeholder = placeholder + self.regex = regex + self.default = default + + def convert_value(self, value): + if isinstance(value, six.string_types): + return value + return self.default class BoolDef(AbtractAttrDef): """Boolean representation. From f90c769f266690cc72134e54c11700b864a83e5d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 15 Jun 2021 15:11:53 +0200 Subject: [PATCH 006/736] implemented enumerator attribute definition --- .../pipeline/lib/attribute_definitions.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/openpype/pipeline/lib/attribute_definitions.py b/openpype/pipeline/lib/attribute_definitions.py index efe99a7a05..86949f8bf0 100644 --- a/openpype/pipeline/lib/attribute_definitions.py +++ b/openpype/pipeline/lib/attribute_definitions.py @@ -121,6 +121,40 @@ class TextDef(AbtractAttrDef): if isinstance(value, six.string_types): return value return self.default + + +class EnumDef(AbtractAttrDef): + """Enumeration of single item from items. + + Args: + items: Items definition that can be coverted to + `collections.OrderedDict`. Dictionary represent {value: label} + relation. + default: Default value. Must be one key(value) from passed items. + """ + + def __init__(self, items, default=None): + if not items: + raise ValueError(( + "Empty 'items' value. {} must have" + " defined values on initialization." + ).format(self.__class__.__name__)) + + items = collections.OrderedDict(items) + if default not in items: + for key in items.keys(): + default = key + break + + self.items = items + self.default = default + + def convert_value(self, value): + if value in self.items: + return value + return self.default + + class BoolDef(AbtractAttrDef): """Boolean representation. From 63db1730d250d8f16bdfada59b33e2b800224acf Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 15 Jun 2021 15:12:32 +0200 Subject: [PATCH 007/736] added missing imports --- openpype/pipeline/lib/attribute_definitions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/pipeline/lib/attribute_definitions.py b/openpype/pipeline/lib/attribute_definitions.py index 86949f8bf0..abab89e873 100644 --- a/openpype/pipeline/lib/attribute_definitions.py +++ b/openpype/pipeline/lib/attribute_definitions.py @@ -1,3 +1,5 @@ +import re +import collections from abc import ABCMeta, abstractmethod import six From 4c87c0bff81c6f51fdc18152512d51e7964290fb Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 15 Jun 2021 15:12:41 +0200 Subject: [PATCH 008/736] pipeline lib has init file --- openpype/pipeline/lib/__init__.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 openpype/pipeline/lib/__init__.py diff --git a/openpype/pipeline/lib/__init__.py b/openpype/pipeline/lib/__init__.py new file mode 100644 index 0000000000..f706ac0267 --- /dev/null +++ b/openpype/pipeline/lib/__init__.py @@ -0,0 +1,22 @@ +from .attribute_definitions import ( + InvalidValueError, + InvalidValueTypeError, + + AbtractAttrDef, + NumberDef, + TextDef, + EnumDef, + BoolDef +) + + +__all__ = ( + "InvalidValueError", + "InvalidValueTypeError", + + "AbtractAttrDef", + "NumberDef", + "TextDef", + "EnumDef", + "BoolDef" +) From f5abbacf3f1506e808491754ea0c7b2ce7cc5ef0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 15 Jun 2021 15:13:11 +0200 Subject: [PATCH 009/736] initial commit of publish plugins modifications --- openpype/pipeline/publish_plugins.py | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 openpype/pipeline/publish_plugins.py diff --git a/openpype/pipeline/publish_plugins.py b/openpype/pipeline/publish_plugins.py new file mode 100644 index 0000000000..f91dd675d5 --- /dev/null +++ b/openpype/pipeline/publish_plugins.py @@ -0,0 +1,7 @@ +from openpype.api import ( + Logger +) + +import pyblish.api + +log = Logger.get_logger(__name__) From b00a9fa9009ce9ce15ee1a690849d9dbec6220f8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 15 Jun 2021 15:13:54 +0200 Subject: [PATCH 010/736] Initial commit of pyblish plugin mixin --- openpype/pipeline/publish_plugins.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openpype/pipeline/publish_plugins.py b/openpype/pipeline/publish_plugins.py index f91dd675d5..ed9a72100f 100644 --- a/openpype/pipeline/publish_plugins.py +++ b/openpype/pipeline/publish_plugins.py @@ -5,3 +5,10 @@ from openpype.api import ( import pyblish.api log = Logger.get_logger(__name__) + + +class OpenPypePyblishPluginMixin: + + @classmethod + def get_family_attribute_defs(cls, family): + return None From 730ed828d16aaa6127522a77c5679c5cb2ced2cc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 15 Jun 2021 15:14:28 +0200 Subject: [PATCH 011/736] OpenPypePyblishPluginMixin has executable_in_thread attribute --- openpype/pipeline/publish_plugins.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/pipeline/publish_plugins.py b/openpype/pipeline/publish_plugins.py index ed9a72100f..1027a5e20f 100644 --- a/openpype/pipeline/publish_plugins.py +++ b/openpype/pipeline/publish_plugins.py @@ -8,6 +8,7 @@ log = Logger.get_logger(__name__) class OpenPypePyblishPluginMixin: + executable_in_thread = False @classmethod def get_family_attribute_defs(cls, family): From 814889203b0692e2db0d2697cf6a658ca61d190f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 15 Jun 2021 15:15:46 +0200 Subject: [PATCH 012/736] added base of set state implementation --- openpype/pipeline/publish_plugins.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/openpype/pipeline/publish_plugins.py b/openpype/pipeline/publish_plugins.py index 1027a5e20f..45676f50c6 100644 --- a/openpype/pipeline/publish_plugins.py +++ b/openpype/pipeline/publish_plugins.py @@ -10,6 +10,20 @@ log = Logger.get_logger(__name__) class OpenPypePyblishPluginMixin: executable_in_thread = False + state_message = None + state_percent = None + _state_change_callbacks = [] + @classmethod def get_family_attribute_defs(cls, family): return None + + def set_state(self, percent=None, message=None): + if percent is not None: + self.state_percent = percent + + if message: + self.state_message = message + + for callback in self._state_change_callbacks: + callback(self) From 703dd81ecdb1127030a891de4ae23fd3d51cb5bf Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 15 Jun 2021 15:19:18 +0200 Subject: [PATCH 013/736] draft creator structure --- openpype/pipeline/creator_plugins.py | 63 ++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 openpype/pipeline/creator_plugins.py diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py new file mode 100644 index 0000000000..00f30afcfa --- /dev/null +++ b/openpype/pipeline/creator_plugins.py @@ -0,0 +1,63 @@ +import copy +import collections + + +class PublishInstanceData(collections.OrderedDict): + def __init__(self, family, subset_name, data=None): + self["id"] = "pyblish.avalon.instance" + self["family"] = family + self["subset"] = subset_name + self["active"] = True + if data: + self.update(data) + + +class Creator: + # Abstract attributes + label = None + family = None + + # GUI Purposes + # - default_variants may not be used if `get_default_variants` is overriden + default_variants = [] + + def __init__(self, headless=False): + # Creator is running in headless mode (without UI elemets) + # - we may use UI inside processing this attribute should be checked + self.headless = headless + + # Process of creation + # - must expect all data that were passed to init in previous implementation + def create(self, subset_name, instance_data, options=None): + instance = PublishInstanceData( + self.family, subset_name, instance_data + ) + + # Just replacement of class attribute `defaults` + # - gives ability to have some "logic" other than attribute values + # - by default just return `default_variants` value + def get_default_variants(self): + return copy.deepcopy(self.default_variants) + + # Added possibility of returning default variant for default variants + # - UI purposes + # - can be different than `get_default_variants` offers + # - first item from `get_default_variants` should be used if `None` + # is returned + def get_default_variant(self): + return None + + # Subset name for current Creator plugin + # - creator may define it's keys for filling + def get_subset_name( + self, variant, task_name, asset_id, project_name, host_name=None + ): + # Capitalize first letter of user input + if variant: + variant = variant[0].capitalize() + variant[1:] + + family = self.family.rsplit(".", 1)[-1] + return "{}{}".format(family, variant) + + def get_attribute_defs(self): + return [] From 65a4365359e882d21e223a1283cfb32d3b9316b4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 15 Jun 2021 15:44:34 +0200 Subject: [PATCH 014/736] added some dosctrings with usolved issues --- openpype/pipeline/publish_plugins.py | 35 ++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/publish_plugins.py b/openpype/pipeline/publish_plugins.py index 45676f50c6..014eb2d88e 100644 --- a/openpype/pipeline/publish_plugins.py +++ b/openpype/pipeline/publish_plugins.py @@ -15,10 +15,41 @@ class OpenPypePyblishPluginMixin: _state_change_callbacks = [] @classmethod - def get_family_attribute_defs(cls, family): - return None + def get_family_attribute_defs(cls, families): + """Publish attribute definitions per family. + + Questions: + Allow to pass multiple families at one time? + - If yes return intersection of all attributes for all families or all attributes or attributes by family? + - "attributes by family" seems most reasonable so "Main" logic can decide how to handle that. + - also it is time saving if all instances will pass + + Pass instance data (avalon instance) instead of families? + + Args: + families(list): List of families for which should return attribute + definitions. + + Returns: + dict>: Attribute definitions per family. + """ + return {} def set_state(self, percent=None, message=None): + """Inner callback of plugin that would help to show in UI state. + + Plugin have registered callbacks on state change which could trigger + update message and percent in UI and repaint the change. + + This part must be optional and should not be used to display errors + or for logging. + + Message should be short without details. + + Args: + percent(int): Percent of processing in range <1-100>. + message(str): Message which will be shown to user (if in UI). + """ if percent is not None: self.state_percent = percent From 61b6222b7714743f8b89565511ebdbabf2a9487e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 16 Jun 2021 10:41:25 +0200 Subject: [PATCH 015/736] change class name to InstanceData --- openpype/pipeline/creator_plugins.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index 00f30afcfa..e9c9628c2c 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -2,7 +2,7 @@ import copy import collections -class PublishInstanceData(collections.OrderedDict): +class InstanceData(collections.OrderedDict): def __init__(self, family, subset_name, data=None): self["id"] = "pyblish.avalon.instance" self["family"] = family @@ -30,6 +30,7 @@ class Creator: # - must expect all data that were passed to init in previous implementation def create(self, subset_name, instance_data, options=None): instance = PublishInstanceData( + instance = InstanceData( self.family, subset_name, instance_data ) From e3603f10e82ef42fe2c35cd29c1476b519919f0b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 16 Jun 2021 11:18:57 +0200 Subject: [PATCH 016/736] added few docstrings with comments --- openpype/pipeline/creator_plugins.py | 113 ++++++++++++++++++++++----- 1 file changed, 94 insertions(+), 19 deletions(-) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index e9c9628c2c..d032ec9760 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -3,6 +3,24 @@ import collections class InstanceData(collections.OrderedDict): + """Instance data that will be stored to workfile. + + Question: + Add creator metadata key on initialization? + Add plugin metadata key on initialization? + Shouldn't have each instance identifier? + - use current "id" value as "type" and use "id" for identifier + - current "id" value make sence only in few hosts + + Args: + family(str): Name of family that will be created. + subset_name(str): Name of subset that will be created. + data(dict): Data used for filling subset name. + + I think `data` must be required argument containing all minimum information + about instance like "asset" and "task" and all data used for filling subset + name as creators may have custom data for subset name filling. + """ def __init__(self, family, subset_name, data=None): self["id"] = "pyblish.avalon.instance" self["family"] = family @@ -13,9 +31,25 @@ class InstanceData(collections.OrderedDict): class Creator: + """Plugin that create and modify instance data before publishing process. + + We should maybe find better name as creation is only one part of it's logic + and to avoid expectations that it is the same as `avalon.api.Creator`. + + Single object should be used for multiple instances instead. Do not store + temp data or mid-process data to `self` if it's not Plugin specific. + + + """ # Abstract attributes + # Label shown in UI label = None + # Family that plugin represents family = None + # Short description of family + # TODO there should be detailed description for UI purposes + # - using Plugin method + description = None # GUI Purposes # - default_variants may not be used if `get_default_variants` is overriden @@ -26,39 +60,80 @@ class Creator: # - we may use UI inside processing this attribute should be checked self.headless = headless - # Process of creation - # - must expect all data that were passed to init in previous implementation def create(self, subset_name, instance_data, options=None): - instance = PublishInstanceData( + """Create new instance in workfile metadata. + + Replacement of `process` method from avalon implementation. + - must expect all data that were passed to init in previous + implementation + """ + instance = InstanceData( self.family, subset_name, instance_data ) - # Just replacement of class attribute `defaults` - # - gives ability to have some "logic" other than attribute values - # - by default just return `default_variants` value def get_default_variants(self): + """Default variant values for UI tooltips. + + Replacement of `defatults` attribute. Using method gives ability to + have some "logic" other than attribute values. + + By default returns `default_variants` value. + + Returns: + list: Whisper variants for user input. + """ return copy.deepcopy(self.default_variants) - # Added possibility of returning default variant for default variants - # - UI purposes - # - can be different than `get_default_variants` offers - # - first item from `get_default_variants` should be used if `None` - # is returned def get_default_variant(self): + """Default variant value that will be used to prefill variant input. + + This is for user input and value may not be content of result from + `get_default_variants`. + + Can return `None`. In that case first element from + `get_default_variants` should be used. + """ + return None - # Subset name for current Creator plugin - # - creator may define it's keys for filling def get_subset_name( - self, variant, task_name, asset_id, project_name, host_name=None + self, variant, task_name, asset_doc, project_name, host_name=None ): - # Capitalize first letter of user input - if variant: - variant = variant[0].capitalize() + variant[1:] + """Return subset name for passed context. - family = self.family.rsplit(".", 1)[-1] - return "{}{}".format(family, variant) + CHANGES: + Argument `asset_id` was replaced with `asset_doc`. It is easier to + query asset before. In some cases would this method be called multiple + times and it would be too slow to query asset document on each + callback. + + NOTE: + Asset document is not used yet but is required if would like to use + task type in subset templates. + + Args: + variant(str): Subset name variant. In most of cases user input. + task_name(str): For which task subset is created. + asset_doc(dict): Asset document for which subset is created. + project_name(str): Project name. + host_name(str): Which host creates subset. + """ + pass def get_attribute_defs(self): + """Plugin attribute definitions. + + Attribute definitions of plugin that hold data about created instance + and values are stored to metadata for future usage and for publishing + purposes. + + NOTE: + Convert method should be implemented which should care about updating + keys/values when plugin attributes change. + + Returns: + list: Attribute definitions that can be tweaked for + created instance. + """ return [] From 55d9bc108192014faa9427ace14454b02f08be7c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 16 Jun 2021 11:24:54 +0200 Subject: [PATCH 017/736] attribute definitions must have `key` attribute --- .../pipeline/lib/attribute_definitions.py | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/openpype/pipeline/lib/attribute_definitions.py b/openpype/pipeline/lib/attribute_definitions.py index abab89e873..eac236e894 100644 --- a/openpype/pipeline/lib/attribute_definitions.py +++ b/openpype/pipeline/lib/attribute_definitions.py @@ -14,8 +14,17 @@ class AbtractAttrDef: Attribute definition should have ability to return "default" value. That can be based on passed data into `__init__` so is not abstracted to attribute. + + QUESTION: + How to force to set `key` attribute? + + Args: + key(str): Under which key will be attribute value stored. """ + def __init__(self, key): + self.key = key + @abstractmethod def convert_value(self, value): """Convert value to a valid one. @@ -40,8 +49,10 @@ class NumberDef(AbtractAttrDef): """ def __init__( - self, minimum=None, maximum=None, decimals=None, default=None + self, key, minimum=None, maximum=None, decimals=None, default=None ): + super(NumberDef, self).__init__(key) + minimum = 0 if minimum is None else minimum maximum = 999999 if maximum is None else maximum # Swap min/max when are passed in opposited order @@ -98,8 +109,10 @@ class TextDef(AbtractAttrDef): default(str, None): Default value. Empty string used when not defined. """ def __init__( - self, multiline=None, regex=None, placeholder=None, default=None + self, key, multiline=None, regex=None, placeholder=None, default=None ): + super(TextDef, self).__init__(key) + if multiline is None: multiline = False @@ -135,7 +148,9 @@ class EnumDef(AbtractAttrDef): default: Default value. Must be one key(value) from passed items. """ - def __init__(self, items, default=None): + def __init__(self, key, items, default=None): + super(EnumDef, self).__init__(key) + if not items: raise ValueError(( "Empty 'items' value. {} must have" @@ -164,7 +179,9 @@ class BoolDef(AbtractAttrDef): default(bool): Default value. Set to `False` if not defined. """ - def __init__(self, default=None): + def __init__(self, key, default=None): + super(BoolDef, self).__init__(key) + if default is None: default = False self.default = default From aa9d94761421cf80d8e0a2148c11dff140c235c3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 16 Jun 2021 11:33:08 +0200 Subject: [PATCH 018/736] implemented meta class AbstractAttrDefMeta for validation of key attribute --- openpype/pipeline/lib/attribute_definitions.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/lib/attribute_definitions.py b/openpype/pipeline/lib/attribute_definitions.py index eac236e894..e14bc6d8f3 100644 --- a/openpype/pipeline/lib/attribute_definitions.py +++ b/openpype/pipeline/lib/attribute_definitions.py @@ -4,7 +4,21 @@ from abc import ABCMeta, abstractmethod import six -@six.add_metaclass(ABCMeta) +class AbstractAttrDefMeta(ABCMeta): + """Meta class to validate existence of 'key' attribute. + + Each object of `AbtractAttrDef` mus have defined 'key' attribute. + """ + def __call__(self, *args, **kwargs): + obj = super(AbstractAttrDefMeta, self).__call__(*args, **kwargs) + if not hasattr(obj, "key"): + raise TypeError("<{}> must have defined 'key' attribute.".format( + obj.__class__.__name__ + )) + return obj + + +@six.add_metaclass(AbstractAttrDefMeta) class AbtractAttrDef: """Abstraction of attribute definiton. From afc62cdf96d7e6131a3397384598c40aa5cdb881 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 16 Jun 2021 18:17:24 +0200 Subject: [PATCH 019/736] added few more info to InstanceData --- openpype/pipeline/creator_plugins.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index d032ec9760..906ff87097 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -6,11 +6,19 @@ class InstanceData(collections.OrderedDict): """Instance data that will be stored to workfile. Question: + Make sence to have this ordered??? + - not sure how to achieve that when data are loaded from workfile Add creator metadata key on initialization? Add plugin metadata key on initialization? Shouldn't have each instance identifier? - use current "id" value as "type" and use "id" for identifier - current "id" value make sence only in few hosts + Handle changes of instance data here? + - trigger callbacks on value change to update instance data in host + Should have reference to workfile? + - not to store it to metadata!!! To be able tell host if should change + store to currently opened workfile... + - instances can be loaded in one workfile but change can happen in other Args: family(str): Name of family that will be created. @@ -26,9 +34,25 @@ class InstanceData(collections.OrderedDict): self["family"] = family self["subset"] = subset_name self["active"] = True + # Stored family specific attribute values + # {key: value} + self["family_attributes"] = {} + # Stored publish specific attribute values + # {: {key: value}} + self["publish_attributes"] = {} if data: self.update(data) + @staticmethod + def from_existing(instance_data): + """Convert existing instance to InstanceData.""" + instance_data = copy.deepcopy(instance_data) + + family = instance_data.pop("family", None) + subset_name = instance_data.pop("subset", None) + + return InstanceData(family, subset_name, instance_data) + class Creator: """Plugin that create and modify instance data before publishing process. From 2559b5721d2de69fecbb391f7f136b27df39bc26 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 16 Jun 2021 18:35:29 +0200 Subject: [PATCH 020/736] separated creator into 2 classes --- openpype/pipeline/creator_plugins.py | 35 +++++++++++++++++++--------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index 906ff87097..e73db69fc8 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -54,26 +54,19 @@ class InstanceData(collections.OrderedDict): return InstanceData(family, subset_name, instance_data) -class Creator: +class BaseCreator: """Plugin that create and modify instance data before publishing process. We should maybe find better name as creation is only one part of it's logic and to avoid expectations that it is the same as `avalon.api.Creator`. - Single object should be used for multiple instances instead. Do not store - temp data or mid-process data to `self` if it's not Plugin specific. - - + Single object should be used for multiple instances instead of single + instance per one creator object. Do not store temp data or mid-process data + to `self` if it's not Plugin specific. """ # Abstract attributes - # Label shown in UI - label = None # Family that plugin represents family = None - # Short description of family - # TODO there should be detailed description for UI purposes - # - using Plugin method - description = None # GUI Purposes # - default_variants may not be used if `get_default_variants` is overriden @@ -161,3 +154,23 @@ class Creator: created instance. """ return [] + + +class Creator(BaseCreator): + """""" + # Label shown in UI + label = None + + # Short description of family + description = None + + def get_detail_description(self): + """Description of family and plugin. + + Can be detailed with html tags. + + Returns: + str: Detailed description of family for artist. By default returns + short description. + """ + return self.description From 67326a44918b26a15f2cd0c6d7018432862d0a3a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 16 Jun 2021 18:35:42 +0200 Subject: [PATCH 021/736] added comments about creator --- openpype/pipeline/creator_plugins.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index e73db69fc8..a8fac2ab91 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -13,6 +13,11 @@ class InstanceData(collections.OrderedDict): Shouldn't have each instance identifier? - use current "id" value as "type" and use "id" for identifier - current "id" value make sence only in few hosts + - there must be mapping of avalon <> pyblish instance to be able handle + logs and errors + - what if avalon <> pyblish mapping is not set? + - where to show error? on which instance? + - should publisher crash if there is new instance that does not have matching to avalon instance? Handle changes of instance data here? - trigger callbacks on value change to update instance data in host Should have reference to workfile? From 98ce83b63859b491a6df63cbfa7c882ae382b0b8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 16 Jun 2021 19:15:37 +0200 Subject: [PATCH 022/736] pass kwargs to super --- openpype/pipeline/lib/attribute_definitions.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/openpype/pipeline/lib/attribute_definitions.py b/openpype/pipeline/lib/attribute_definitions.py index e14bc6d8f3..038738a194 100644 --- a/openpype/pipeline/lib/attribute_definitions.py +++ b/openpype/pipeline/lib/attribute_definitions.py @@ -63,9 +63,10 @@ class NumberDef(AbtractAttrDef): """ def __init__( - self, key, minimum=None, maximum=None, decimals=None, default=None + self, key, minimum=None, maximum=None, decimals=None, default=None, + **kwargs ): - super(NumberDef, self).__init__(key) + super(NumberDef, self).__init__(key, **kwargs) minimum = 0 if minimum is None else minimum maximum = 999999 if maximum is None else maximum @@ -123,9 +124,10 @@ class TextDef(AbtractAttrDef): default(str, None): Default value. Empty string used when not defined. """ def __init__( - self, key, multiline=None, regex=None, placeholder=None, default=None + self, key, multiline=None, regex=None, placeholder=None, default=None, + **kwargs ): - super(TextDef, self).__init__(key) + super(TextDef, self).__init__(key, **kwargs) if multiline is None: multiline = False @@ -162,8 +164,8 @@ class EnumDef(AbtractAttrDef): default: Default value. Must be one key(value) from passed items. """ - def __init__(self, key, items, default=None): - super(EnumDef, self).__init__(key) + def __init__(self, key, items, default=None, **kwargs): + super(EnumDef, self).__init__(key, **kwargs) if not items: raise ValueError(( @@ -193,8 +195,8 @@ class BoolDef(AbtractAttrDef): default(bool): Default value. Set to `False` if not defined. """ - def __init__(self, key, default=None): - super(BoolDef, self).__init__(key) + def __init__(self, key, default=None, **kwargs): + super(BoolDef, self).__init__(key, **kwargs) if default is None: default = False From 2f7aef8d1c519c7a777b495998013c0c92a65313 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 16 Jun 2021 19:15:57 +0200 Subject: [PATCH 023/736] validate super call --- openpype/pipeline/lib/attribute_definitions.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/openpype/pipeline/lib/attribute_definitions.py b/openpype/pipeline/lib/attribute_definitions.py index 038738a194..55b4445f11 100644 --- a/openpype/pipeline/lib/attribute_definitions.py +++ b/openpype/pipeline/lib/attribute_definitions.py @@ -11,9 +11,10 @@ class AbstractAttrDefMeta(ABCMeta): """ def __call__(self, *args, **kwargs): obj = super(AbstractAttrDefMeta, self).__call__(*args, **kwargs) - if not hasattr(obj, "key"): - raise TypeError("<{}> must have defined 'key' attribute.".format( - obj.__class__.__name__ + init_class = getattr(obj, "__init__class__", None) + if init_class is not AbtractAttrDef: + raise TypeError("{} super was not called in __init__.".format( + type(obj) )) return obj @@ -34,10 +35,17 @@ class AbtractAttrDef: Args: key(str): Under which key will be attribute value stored. + label(str): Attribute label. + tooltip(str): Attribute tooltip. """ - def __init__(self, key): + def __init__(self, key, label=None, tooltip=None): self.key = key + self.label = label + self.tooltip = tooltip + + self.__init__class__ = AbtractAttrDef + @abstractmethod def convert_value(self, value): From ccd90bf770d0fbab2a57204c3bee9a8c90c9c964 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 16 Jun 2021 19:24:27 +0200 Subject: [PATCH 024/736] small changes --- openpype/pipeline/__init__.py | 1 - openpype/pipeline/creator_plugins.py | 9 ++++----- openpype/pipeline/publish_plugins.py | 2 -- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/openpype/pipeline/__init__.py b/openpype/pipeline/__init__.py index 8b13789179..e69de29bb2 100644 --- a/openpype/pipeline/__init__.py +++ b/openpype/pipeline/__init__.py @@ -1 +0,0 @@ - diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index a8fac2ab91..5eb6294afc 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -8,8 +8,6 @@ class InstanceData(collections.OrderedDict): Question: Make sence to have this ordered??? - not sure how to achieve that when data are loaded from workfile - Add creator metadata key on initialization? - Add plugin metadata key on initialization? Shouldn't have each instance identifier? - use current "id" value as "type" and use "id" for identifier - current "id" value make sence only in few hosts @@ -90,9 +88,10 @@ class BaseCreator: implementation """ - instance = InstanceData( - self.family, subset_name, instance_data - ) + # instance = InstanceData( + # self.family, subset_name, instance_data + # ) + pass def get_default_variants(self): """Default variant values for UI tooltips. diff --git a/openpype/pipeline/publish_plugins.py b/openpype/pipeline/publish_plugins.py index 014eb2d88e..4b7868ae99 100644 --- a/openpype/pipeline/publish_plugins.py +++ b/openpype/pipeline/publish_plugins.py @@ -2,8 +2,6 @@ from openpype.api import ( Logger ) -import pyblish.api - log = Logger.get_logger(__name__) From b9fa9258d12e56b15675707a3f102f67c94e203b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 17 Jun 2021 19:32:43 +0200 Subject: [PATCH 025/736] new publisher ui beginning --- openpype/tools/new_publisher/__init__.py | 0 openpype/tools/new_publisher/image_file.png | Bin 0 -> 5118 bytes openpype/tools/new_publisher/widgets.py | 98 ++++++++++++++++++++ openpype/tools/new_publisher/window.py | 97 +++++++++++++++++++ 4 files changed, 195 insertions(+) create mode 100644 openpype/tools/new_publisher/__init__.py create mode 100644 openpype/tools/new_publisher/image_file.png create mode 100644 openpype/tools/new_publisher/widgets.py create mode 100644 openpype/tools/new_publisher/window.py diff --git a/openpype/tools/new_publisher/__init__.py b/openpype/tools/new_publisher/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openpype/tools/new_publisher/image_file.png b/openpype/tools/new_publisher/image_file.png new file mode 100644 index 0000000000000000000000000000000000000000..adea862e5b7169f8279bd398226a2fe6283e4925 GIT binary patch literal 5118 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") + family_value_widget.setText("") + asset_value_widget.setText("") + task_value_widget.setText("") + + global_attrs_layout = QtWidgets.QFormLayout(global_attrs_widget) + global_attrs_layout.addRow("Name", variant_input) + global_attrs_layout.addRow("Family", family_value_widget) + global_attrs_layout.addRow("Asset", asset_value_widget) + global_attrs_layout.addRow("Task", task_value_widget) + global_attrs_layout.addRow("Subset", subset_value_widget) + + thumbnail_widget = ThumbnailWidget(top_widget) + + top_layout = QtWidgets.QHBoxLayout(top_widget) + top_layout.setContentsMargins(0, 0, 0, 0) + top_layout.addWidget(global_attrs_widget, 7) + top_layout.addWidget(thumbnail_widget, 3) + + # BOTTOM PART + bottom_widget = QtWidgets.QWidget(self) + # TODO they should be scrollable + family_attrs_widget = QtWidgets.QWidget(bottom_widget) + publish_attrs_widget = QtWidgets.QWidget(bottom_widget) + + bottom_layout = QtWidgets.QHBoxLayout(bottom_widget) + bottom_layout.setContentsMargins(0, 0, 0, 0) + bottom_layout.addWidget(family_attrs_widget, 1) + bottom_layout.addWidget(publish_attrs_widget, 1) + + layout = QtWidgets.QVBoxLayout(self) + layout.addWidget(top_widget, 0) + layout.addWidget(bottom_widget, 1) + + self.global_attrs_widget = global_attrs_widget + self.thumbnail_widget = thumbnail_widget + + +class ThumbnailWidget(QtWidgets.QWidget): + def __init__(self, parent): + super(ThumbnailWidget, self).__init__(parent) + + default_pix = QtGui.QPixmap(get_default_thumbnail_image_path()) + + thumbnail_label = QtWidgets.QLabel(self) + thumbnail_label.setPixmap( + default_pix.scaled( + 200, 100, + QtCore.Qt.KeepAspectRatio, + QtCore.Qt.SmoothTransformation + ) + ) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(thumbnail_label, alignment=QtCore.Qt.AlignCenter) + + self.thumbnail_label = thumbnail_label + self.default_pix = default_pix + self.current_pix = None diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py new file mode 100644 index 0000000000..f1d753e83c --- /dev/null +++ b/openpype/tools/new_publisher/window.py @@ -0,0 +1,97 @@ +import sys +sys.path.append(r"C:\Users\iLLiCiT\PycharmProjects\pype3\.venv\Lib\site-packages") +from Qt import QtWidgets, QtCore + +from widgets import SubsetAttributesWidget + + +class PublisherWindow(QtWidgets.QWidget): + def __init__(self, parent=None): + super(PublisherWindow, self).__init__(parent) + + # TODO Title, Icon, Stylesheet + + main_frame = QtWidgets.QWidget(self) + + # Header + context_label = QtWidgets.QLabel(main_frame) + + # Content + content_widget = QtWidgets.QWidget(main_frame) + + # Subset widget + subset_widget = QtWidgets.QWidget(content_widget) + + subset_view = QtWidgets.QTreeView(subset_widget) + subset_attributes = SubsetAttributesWidget(subset_widget) + + subset_layout = QtWidgets.QHBoxLayout(subset_widget) + subset_layout.addWidget(subset_view, 0) + subset_layout.addWidget(subset_attributes, 1) + + content_layout = QtWidgets.QVBoxLayout(content_widget) + content_layout.setContentsMargins(0, 0, 0, 0) + content_layout.addWidget(subset_widget) + + # Footer + footer_widget = QtWidgets.QWidget(self) + + message_input = QtWidgets.QLineEdit(footer_widget) + validate_btn = QtWidgets.QPushButton("Validate", footer_widget) + publish_btn = QtWidgets.QPushButton("Publish", footer_widget) + + footer_layout = QtWidgets.QHBoxLayout(footer_widget) + footer_layout.setContentsMargins(0, 0, 0, 0) + footer_layout.addWidget(message_input, 1) + footer_layout.addWidget(validate_btn, 0) + footer_layout.addWidget(publish_btn, 0) + + # Main frame + main_frame_layout = QtWidgets.QVBoxLayout(main_frame) + main_frame_layout.addWidget(context_label, 0) + main_frame_layout.addWidget(content_widget, 1) + main_frame_layout.addWidget(footer_widget, 0) + + # Add main frame to this window + main_layout = QtWidgets.QHBoxLayout(self) + main_layout.addWidget(main_frame) + + validate_btn.clicked.connect(self._on_validate_clicked) + publish_btn.clicked.connect(self._on_publish_clicked) + + self.main_frame = main_frame + + self.context_label = context_label + + self.footer_widget = footer_widget + self.message_input = message_input + self.validate_btn = validate_btn + self.publish_btn = publish_btn + + # DEBUGING + self.set_context_label( + "////" + ) + # self.setStyleSheet("border: 1px solid black;") + + def set_context_label(self, label): + self.context_label.setText(label) + + def _on_validate_clicked(self): + print("Validation!!!") + + def _on_publish_clicked(self): + print("Publishing!!!") + + +def main(): + """Main function for testing purposes.""" + + app = QtWidgets.QApplication([]) + window = PublisherWindow() + window.show() + app.exec_() + + +if __name__ == "__main__": + main() From 10b0cda6db63740275a5574d2ef50f06ca5b0669 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 23 Jun 2021 10:11:55 +0200 Subject: [PATCH 026/736] expect passed settings to creator --- openpype/pipeline/creator_plugins.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index 5eb6294afc..b5ad21a180 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -70,12 +70,13 @@ class BaseCreator: # Abstract attributes # Family that plugin represents family = None + enabled = True # GUI Purposes # - default_variants may not be used if `get_default_variants` is overriden default_variants = [] - def __init__(self, headless=False): + def __init__(self, system_settings, project_settings, headless=False): # Creator is running in headless mode (without UI elemets) # - we may use UI inside processing this attribute should be checked self.headless = headless From 17c1956b29d29bcd17c8bfcfd6a22fb3676da5d2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 23 Jun 2021 10:12:23 +0200 Subject: [PATCH 027/736] conversion method for creator attributes --- openpype/pipeline/creator_plugins.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index b5ad21a180..5faff999d7 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -160,6 +160,20 @@ class BaseCreator: """ return [] + def convert_family_attribute_values(self, attribute_values): + """Convert values loaded from workfile metadata. + + If passed values match current creator version just return the value + back. Update of changes in workfile must not happen in this method. + + Args: + attribute_values(dict): Values from instance metadata. + + Returns: + dict: Converted values. + """ + return attribute_values + class Creator(BaseCreator): """""" From fc032958178dccb2cd0f03cd2e01bac1ffcb6323 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 23 Jun 2021 10:12:57 +0200 Subject: [PATCH 028/736] get_family_attribute_defs wil return all definitions for all families --- openpype/pipeline/publish_plugins.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/openpype/pipeline/publish_plugins.py b/openpype/pipeline/publish_plugins.py index 4b7868ae99..0afbbf66e0 100644 --- a/openpype/pipeline/publish_plugins.py +++ b/openpype/pipeline/publish_plugins.py @@ -13,17 +13,9 @@ class OpenPypePyblishPluginMixin: _state_change_callbacks = [] @classmethod - def get_family_attribute_defs(cls, families): + def get_family_attribute_defs(cls): """Publish attribute definitions per family. - Questions: - Allow to pass multiple families at one time? - - If yes return intersection of all attributes for all families or all attributes or attributes by family? - - "attributes by family" seems most reasonable so "Main" logic can decide how to handle that. - - also it is time saving if all instances will pass - - Pass instance data (avalon instance) instead of families? - Args: families(list): List of families for which should return attribute definitions. From 6979b4955a70c8194b22fc438cf5306d81cceb41 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 23 Jun 2021 10:14:06 +0200 Subject: [PATCH 029/736] auto creator idea --- openpype/pipeline/creator_plugins.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index 5faff999d7..afcd14741e 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -193,3 +193,10 @@ class Creator(BaseCreator): short description. """ return self.description + + +class AutoCreator(BaseCreator): + """Creator which is automatically triggered without user interaction. + + Can be used e.g. for `workfile`. + """ From 91c3f31322637b174cf40d289af7bb717a4c25fd Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 30 Jun 2021 17:05:37 +0200 Subject: [PATCH 030/736] changed get_family_attribute_defs to get_attribute_defs --- openpype/pipeline/publish_plugins.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/openpype/pipeline/publish_plugins.py b/openpype/pipeline/publish_plugins.py index 0afbbf66e0..5354718bfa 100644 --- a/openpype/pipeline/publish_plugins.py +++ b/openpype/pipeline/publish_plugins.py @@ -13,17 +13,14 @@ class OpenPypePyblishPluginMixin: _state_change_callbacks = [] @classmethod - def get_family_attribute_defs(cls): - """Publish attribute definitions per family. - - Args: - families(list): List of families for which should return attribute - definitions. + def get_attribute_defs(cls): + """Publish attribute definitions. + Attributes available for all families in plugin's `families` attribute. Returns: - dict>: Attribute definitions per family. + list: Attribute definitions for plugin. """ - return {} + return [] def set_state(self, percent=None, message=None): """Inner callback of plugin that would help to show in UI state. From df25a943dd280ed93fd68d940df8032e10257d79 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 30 Jun 2021 17:06:20 +0200 Subject: [PATCH 031/736] removed unused log --- openpype/pipeline/publish_plugins.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/openpype/pipeline/publish_plugins.py b/openpype/pipeline/publish_plugins.py index 5354718bfa..d43c49427e 100644 --- a/openpype/pipeline/publish_plugins.py +++ b/openpype/pipeline/publish_plugins.py @@ -1,10 +1,3 @@ -from openpype.api import ( - Logger -) - -log = Logger.get_logger(__name__) - - class OpenPypePyblishPluginMixin: executable_in_thread = False From 5ddb646925f61263c4112e05c192cc685c37b237 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 30 Jun 2021 17:09:38 +0200 Subject: [PATCH 032/736] changed InstanceData to AvalonInstance class --- openpype/pipeline/creator_plugins.py | 60 ++++++++++++---------------- 1 file changed, 26 insertions(+), 34 deletions(-) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index afcd14741e..91c9bf3cd6 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -1,60 +1,52 @@ import copy import collections +from uuid import uuid4 -class InstanceData(collections.OrderedDict): - """Instance data that will be stored to workfile. - - Question: - Make sence to have this ordered??? - - not sure how to achieve that when data are loaded from workfile - Shouldn't have each instance identifier? - - use current "id" value as "type" and use "id" for identifier - - current "id" value make sence only in few hosts - - there must be mapping of avalon <> pyblish instance to be able handle - logs and errors - - what if avalon <> pyblish mapping is not set? - - where to show error? on which instance? - - should publisher crash if there is new instance that does not have matching to avalon instance? - Handle changes of instance data here? - - trigger callbacks on value change to update instance data in host - Should have reference to workfile? - - not to store it to metadata!!! To be able tell host if should change - store to currently opened workfile... - - instances can be loaded in one workfile but change can happen in other - - Args: - family(str): Name of family that will be created. - subset_name(str): Name of subset that will be created. - data(dict): Data used for filling subset name. +class AvalonInstance: + """Instance entity with data that will be stored to workfile. I think `data` must be required argument containing all minimum information about instance like "asset" and "task" and all data used for filling subset name as creators may have custom data for subset name filling. + + Args: + family(str): Name of family that will be created. + subset_name(str): Name of subset that will be created. + data(dict): Data used for filling subset name or override data from + already existing instance. """ def __init__(self, family, subset_name, data=None): - self["id"] = "pyblish.avalon.instance" - self["family"] = family - self["subset"] = subset_name - self["active"] = True + + self.family = family + self.subset_name = subset_name + + self.data = collections.OrderedDict() + self.data["id"] = "pyblish.avalon.instance" + self.data["family"] = family + self.data["subset"] = subset_name + self.data["active"] = True # Stored family specific attribute values # {key: value} - self["family_attributes"] = {} + self.data["family_attributes"] = {} # Stored publish specific attribute values # {: {key: value}} - self["publish_attributes"] = {} + self.data["publish_attributes"] = {} if data: - self.update(data) + self.data.update(data) + + if not self.data.get("uuid"): + self.data["uuid"] = str(uuid4()) @staticmethod def from_existing(instance_data): - """Convert existing instance to InstanceData.""" + """Convert instance data from workfile to AvalonInstance.""" instance_data = copy.deepcopy(instance_data) family = instance_data.pop("family", None) subset_name = instance_data.pop("subset", None) - return InstanceData(family, subset_name, instance_data) + return AvalonInstance(family, subset_name, instance_data) class BaseCreator: From fa11fd6899600e59d99c40230d99e8e03d7d4b60 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 30 Jun 2021 17:55:55 +0200 Subject: [PATCH 033/736] added version to AvalonInstance data --- openpype/pipeline/creator_plugins.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index 91c9bf3cd6..40b4657d96 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -16,9 +16,10 @@ class AvalonInstance: data(dict): Data used for filling subset name or override data from already existing instance. """ - def __init__(self, family, subset_name, data=None): - + def __init__(self, family, subset_name, data=None, new=True): + # Family of instance self.family = family + # Subset name self.subset_name = subset_name self.data = collections.OrderedDict() @@ -26,6 +27,9 @@ class AvalonInstance: self.data["family"] = family self.data["subset"] = subset_name self.data["active"] = True + # Schema or version? + if new: + self.data["version"] = 1 # Stored family specific attribute values # {key: value} self.data["family_attributes"] = {} @@ -38,6 +42,9 @@ class AvalonInstance: if not self.data.get("uuid"): self.data["uuid"] = str(uuid4()) + if not new and "version" not in self.data: + self.data["version"] = None + @staticmethod def from_existing(instance_data): """Convert instance data from workfile to AvalonInstance.""" From 75757e3d5cb419428d8dbd4466def6e95d366f2b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 30 Jun 2021 20:06:47 +0200 Subject: [PATCH 034/736] cleanups --- openpype/pipeline/lib/__init__.py | 6 ------ openpype/pipeline/lib/attribute_definitions.py | 1 - 2 files changed, 7 deletions(-) diff --git a/openpype/pipeline/lib/__init__.py b/openpype/pipeline/lib/__init__.py index f706ac0267..c1f8d14f33 100644 --- a/openpype/pipeline/lib/__init__.py +++ b/openpype/pipeline/lib/__init__.py @@ -1,7 +1,4 @@ from .attribute_definitions import ( - InvalidValueError, - InvalidValueTypeError, - AbtractAttrDef, NumberDef, TextDef, @@ -11,9 +8,6 @@ from .attribute_definitions import ( __all__ = ( - "InvalidValueError", - "InvalidValueTypeError", - "AbtractAttrDef", "NumberDef", "TextDef", diff --git a/openpype/pipeline/lib/attribute_definitions.py b/openpype/pipeline/lib/attribute_definitions.py index 55b4445f11..62b5dd3b47 100644 --- a/openpype/pipeline/lib/attribute_definitions.py +++ b/openpype/pipeline/lib/attribute_definitions.py @@ -46,7 +46,6 @@ class AbtractAttrDef: self.__init__class__ = AbtractAttrDef - @abstractmethod def convert_value(self, value): """Convert value to a valid one. From a08aa60902454c2aae1dcdbedf2bcf4f2419b412 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 30 Jun 2021 20:07:46 +0200 Subject: [PATCH 035/736] using ABC to define abstract methods and properties of Creator plugin --- openpype/pipeline/creator_plugins.py | 43 ++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index 40b4657d96..3361bb5013 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -2,6 +2,13 @@ import copy import collections from uuid import uuid4 +from abc import ( + ABCMeta, + abstractmethod, + abstractproperty +) +import six + class AvalonInstance: """Instance entity with data that will be stored to workfile. @@ -56,6 +63,7 @@ class AvalonInstance: return AvalonInstance(family, subset_name, instance_data) +@six.add_metaclass(ABCMeta) class BaseCreator: """Plugin that create and modify instance data before publishing process. @@ -66,9 +74,8 @@ class BaseCreator: instance per one creator object. Do not store temp data or mid-process data to `self` if it's not Plugin specific. """ - # Abstract attributes - # Family that plugin represents - family = None + + # Creator is enabled (Probably does not have reason of existence?) enabled = True # GUI Purposes @@ -80,17 +87,19 @@ class BaseCreator: # - we may use UI inside processing this attribute should be checked self.headless = headless - def create(self, subset_name, instance_data, options=None): - """Create new instance in workfile metadata. + @abstractproperty + def family(self): + """Family that plugin represents.""" + pass + + @abstractmethod + def create(self, options=None): + """Create new instance. Replacement of `process` method from avalon implementation. - must expect all data that were passed to init in previous implementation """ - - # instance = InstanceData( - # self.family, subset_name, instance_data - # ) pass def get_default_variants(self): @@ -182,6 +191,22 @@ class Creator(BaseCreator): # Short description of family description = None + @abstractmethod + def create(self, subset_name, instance_data, options=None): + """Create new instance and store it. + + Ideally should be stored to workfile using host implementation. + + Args: + subset_name(str): Subset name of created instance. + instance_data(dict): + """ + + # instance = AvalonInstance( + # self.family, subset_name, instance_data + # ) + pass + def get_detail_description(self): """Description of family and plugin. From 233210025d874a4614a2e4b2e7dd6aeb45063704 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 30 Jun 2021 20:09:52 +0200 Subject: [PATCH 036/736] added change_order method to AvalonInstance class --- openpype/pipeline/creator_plugins.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index 3361bb5013..9c2d349806 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -52,6 +52,16 @@ class AvalonInstance: if not new and "version" not in self.data: self.data["version"] = None + def change_order(self, keys_order): + data = collections.OrderedDict() + for key in keys_order: + if key in self.data: + data[key] = self.data.pop(key) + + for key in tuple(self.data.keys()): + data[key] = self.data.pop(key) + self.data = data + @staticmethod def from_existing(instance_data): """Convert instance data from workfile to AvalonInstance.""" From 52d72e4b244bbd7f19c7a1b1d3163dfac86fd3f1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 30 Jun 2021 20:10:25 +0200 Subject: [PATCH 037/736] from_existing is using `new` argument --- openpype/pipeline/creator_plugins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index 9c2d349806..6696951b31 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -70,7 +70,7 @@ class AvalonInstance: family = instance_data.pop("family", None) subset_name = instance_data.pop("subset", None) - return AvalonInstance(family, subset_name, instance_data) + return AvalonInstance(family, subset_name, instance_data, new=False) @six.add_metaclass(ABCMeta) From 0658fe6be008faa4386fba396d16785f3bfc2e23 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 30 Jun 2021 20:10:50 +0200 Subject: [PATCH 038/736] pipeline init has imported stuff --- openpype/pipeline/__init__.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/openpype/pipeline/__init__.py b/openpype/pipeline/__init__.py index e69de29bb2..7316753785 100644 --- a/openpype/pipeline/__init__.py +++ b/openpype/pipeline/__init__.py @@ -0,0 +1,15 @@ +from .creator_plugins import ( + BaseCreator, + Creator, + AutoCreator +) + +from .publish_plugins import OpenPypePyblishPluginMixin + +__all__ = ( + "BaseCreator", + "Creator", + "AutoCreator", + + "OpenPypePyblishPluginMixin" +) From 9eb2d3ff1fe721965003a282e1cf8f494d4135ae Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 30 Jun 2021 20:11:32 +0200 Subject: [PATCH 039/736] init commit of PublisherController --- openpype/tools/new_publisher/control.py | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 openpype/tools/new_publisher/control.py diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py new file mode 100644 index 0000000000..53d3e9ed46 --- /dev/null +++ b/openpype/tools/new_publisher/control.py @@ -0,0 +1,8 @@ +import logging +import avalon.api + + +class PublisherController: + def __init__(self): + self.log = logging.getLogger("PublisherController") + self.host = avalon.api.registered_host() From 3df0d2023cc0262d0a72781062e9bbf314e1b772 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 30 Jun 2021 20:12:25 +0200 Subject: [PATCH 040/736] initial commit of reset method --- openpype/tools/new_publisher/control.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 53d3e9ed46..2344081a28 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -1,8 +1,30 @@ import logging +import inspect import avalon.api +from openpype.pipeline import BaseCreator class PublisherController: def __init__(self): self.log = logging.getLogger("PublisherController") self.host = avalon.api.registered_host() + + self.creators = [] + self.publish_plugins = [] + self.instances = [] + + def reset(self): + """Reset to initial state.""" + creators = [] + for creator in avalon.api.discover(BaseCreator): + if inspect.isabstract(creator): + self.log.info( + "Skipping abstract Creator {}".format(str(creator)) + ) + continue + creators.append(creator) + + self.creators = creators + self.publish_plugins = [] + + self.instances = self.host.list_instances() From e78c9c2765a1c0ead4df9eb104dfe0a232744a39 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 30 Jun 2021 20:13:13 +0200 Subject: [PATCH 041/736] use controller in main window --- openpype/tools/new_publisher/window.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index f1d753e83c..2b72009286 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -2,6 +2,7 @@ import sys sys.path.append(r"C:\Users\iLLiCiT\PycharmProjects\pype3\.venv\Lib\site-packages") from Qt import QtWidgets, QtCore +from control import PublisherController from widgets import SubsetAttributesWidget @@ -68,12 +69,19 @@ class PublisherWindow(QtWidgets.QWidget): self.validate_btn = validate_btn self.publish_btn = publish_btn + controller = PublisherController() + + self.controller = controller + # DEBUGING self.set_context_label( "////" ) # self.setStyleSheet("border: 1px solid black;") + def reset(self): + self.controller.reset() + def set_context_label(self, label): self.context_label.setText(label) From f4dddfa2b8420692fa3e8d21550c526c7d5cb08b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 30 Jun 2021 20:13:44 +0200 Subject: [PATCH 042/736] added mockup create button --- openpype/tools/new_publisher/window.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 2b72009286..1f163a3507 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -37,12 +37,14 @@ class PublisherWindow(QtWidgets.QWidget): # Footer footer_widget = QtWidgets.QWidget(self) + create_btn = QtWidgets.QPushButton("Create", footer_widget) message_input = QtWidgets.QLineEdit(footer_widget) validate_btn = QtWidgets.QPushButton("Validate", footer_widget) publish_btn = QtWidgets.QPushButton("Publish", footer_widget) footer_layout = QtWidgets.QHBoxLayout(footer_widget) footer_layout.setContentsMargins(0, 0, 0, 0) + footer_layout.addWidget(create_btn, 0) footer_layout.addWidget(message_input, 1) footer_layout.addWidget(validate_btn, 0) footer_layout.addWidget(publish_btn, 0) @@ -57,6 +59,7 @@ class PublisherWindow(QtWidgets.QWidget): main_layout = QtWidgets.QHBoxLayout(self) main_layout.addWidget(main_frame) + create_btn.clicked.connect(self._on_create_clicked) validate_btn.clicked.connect(self._on_validate_clicked) publish_btn.clicked.connect(self._on_publish_clicked) @@ -85,6 +88,9 @@ class PublisherWindow(QtWidgets.QWidget): def set_context_label(self, label): self.context_label.setText(label) + def _on_create_clicked(self): + print("Creation!!!") + def _on_validate_clicked(self): print("Validation!!!") From c54608a751d80aa5e0a17d34227e373790b6ea05 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 30 Jun 2021 20:14:02 +0200 Subject: [PATCH 043/736] trigger reset on first show --- openpype/tools/new_publisher/window.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 1f163a3507..4b594ac3e1 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -10,6 +10,7 @@ class PublisherWindow(QtWidgets.QWidget): def __init__(self, parent=None): super(PublisherWindow, self).__init__(parent) + self._first_show = True # TODO Title, Icon, Stylesheet main_frame = QtWidgets.QWidget(self) @@ -82,6 +83,12 @@ class PublisherWindow(QtWidgets.QWidget): ) # self.setStyleSheet("border: 1px solid black;") + def showEvent(self, event): + super(PublisherWindow, self).showEvent(event) + if self._first_show: + self._first_show = False + self.reset() + def reset(self): self.controller.reset() From 0e2ff510f2dce60417066ead177bd71dd6672e72 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 30 Jun 2021 20:14:21 +0200 Subject: [PATCH 044/736] added reset button --- openpype/tools/new_publisher/window.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 4b594ac3e1..4e830c5eb5 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -16,7 +16,14 @@ class PublisherWindow(QtWidgets.QWidget): main_frame = QtWidgets.QWidget(self) # Header - context_label = QtWidgets.QLabel(main_frame) + header_widget = QtWidgets.QWidget(main_frame) + context_label = QtWidgets.QLabel(header_widget) + reset_btn = QtWidgets.QPushButton("Reset", header_widget) + + header_layout = QtWidgets.QHBoxLayout(header_widget) + header_layout.setContentsMargins(0, 0, 0, 0) + header_layout.addWidget(context_label, 1) + header_layout.addWidget(reset_btn, 0) # Content content_widget = QtWidgets.QWidget(main_frame) @@ -52,7 +59,7 @@ class PublisherWindow(QtWidgets.QWidget): # Main frame main_frame_layout = QtWidgets.QVBoxLayout(main_frame) - main_frame_layout.addWidget(context_label, 0) + main_frame_layout.addWidget(header_widget, 0) main_frame_layout.addWidget(content_widget, 1) main_frame_layout.addWidget(footer_widget, 0) @@ -60,6 +67,8 @@ class PublisherWindow(QtWidgets.QWidget): main_layout = QtWidgets.QHBoxLayout(self) main_layout.addWidget(main_frame) + reset_btn.clicked.connect(self._on_reset_clicked) + create_btn.clicked.connect(self._on_create_clicked) validate_btn.clicked.connect(self._on_validate_clicked) publish_btn.clicked.connect(self._on_publish_clicked) @@ -95,6 +104,9 @@ class PublisherWindow(QtWidgets.QWidget): def set_context_label(self, label): self.context_label.setText(label) + def _on_reset_clicked(self): + self.reset() + def _on_create_clicked(self): print("Creation!!!") From b574bf7589d6e4970c77a11c1ca0ab1627a5fc9c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 30 Jun 2021 20:26:36 +0200 Subject: [PATCH 045/736] make sure reset is not called twice in row --- openpype/tools/new_publisher/control.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 2344081a28..a1e806c10c 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -13,7 +13,17 @@ class PublisherController: self.publish_plugins = [] self.instances = [] + self._in_reset = False + def reset(self): + if self._in_reset: + return + + self._in_reset = True + self._reset() + self._in_reset = False + + def _reset(self): """Reset to initial state.""" creators = [] for creator in avalon.api.discover(BaseCreator): From b65fc7f76eb176fdcde77fc085b3bf4ecfcdede4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 30 Jun 2021 20:27:09 +0200 Subject: [PATCH 046/736] discover pyblish plugins --- openpype/tools/new_publisher/control.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index a1e806c10c..4655649b74 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -2,6 +2,7 @@ import logging import inspect import avalon.api from openpype.pipeline import BaseCreator +import pyblish.api class PublisherController: @@ -26,6 +27,9 @@ class PublisherController: def _reset(self): """Reset to initial state.""" creators = [] + publish_plugins = pyblish.api.discover() + self.publish_plugins = publish_plugins + for creator in avalon.api.discover(BaseCreator): if inspect.isabstract(creator): self.log.info( From 4dfc0b7fa7d5609e8cba973e764c855f8694a4f9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 30 Jun 2021 20:27:26 +0200 Subject: [PATCH 047/736] store creators by family --- openpype/tools/new_publisher/control.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 4655649b74..11c4702ce8 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -10,7 +10,7 @@ class PublisherController: self.log = logging.getLogger("PublisherController") self.host = avalon.api.registered_host() - self.creators = [] + self.creators = {} self.publish_plugins = [] self.instances = [] @@ -26,17 +26,17 @@ class PublisherController: def _reset(self): """Reset to initial state.""" - creators = [] publish_plugins = pyblish.api.discover() self.publish_plugins = publish_plugins + creators = {} for creator in avalon.api.discover(BaseCreator): if inspect.isabstract(creator): self.log.info( "Skipping abstract Creator {}".format(str(creator)) ) continue - creators.append(creator) + creators[creator.family] = creator self.creators = creators self.publish_plugins = [] From ed2b8b9f0b08789bba483cdeb628411c94a0ca24 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 30 Jun 2021 20:28:02 +0200 Subject: [PATCH 048/736] convert loaded instances into AvalonInstance object on list_instances --- openpype/tools/new_publisher/control.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 11c4702ce8..e7de5d4361 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -1,8 +1,11 @@ import logging import inspect import avalon.api -from openpype.pipeline import BaseCreator import pyblish.api +from openpype.pipeline import ( + BaseCreator, + AvalonInstance +) class PublisherController: @@ -39,6 +42,17 @@ class PublisherController: creators[creator.family] = creator self.creators = creators - self.publish_plugins = [] - self.instances = self.host.list_instances() + host_instances = self.host.list_instances() + instances = [] + for instance_data in host_instances: + family = instance_data["family"] + creator = creators.get(family) + if creator is not None: + instance_data = creator.convert_family_attribute_values( + instance_data + ) + instance = AvalonInstance.from_existing(instance_data) + instances.append(instance) + + self.instances = instances From d8fc151bc290118060cecfa132832bc6f1cb5c2b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 30 Jun 2021 20:31:31 +0200 Subject: [PATCH 049/736] added headless argument to controller --- openpype/tools/new_publisher/control.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index e7de5d4361..45cdef41dd 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -9,9 +9,10 @@ from openpype.pipeline import ( class PublisherController: - def __init__(self): + def __init__(self, headless=False): self.log = logging.getLogger("PublisherController") self.host = avalon.api.registered_host() + self.headless = headless self.creators = {} self.publish_plugins = [] From 1ea491fceb95efc1fe60ae2a4b37f21e64d8b085 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 30 Jun 2021 20:32:23 +0200 Subject: [PATCH 050/736] create object of creators on reset --- openpype/tools/new_publisher/control.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 45cdef41dd..788126a206 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -2,6 +2,10 @@ import logging import inspect import avalon.api import pyblish.api +from openpype.api import ( + get_system_settings, + get_project_settings +) from openpype.pipeline import ( BaseCreator, AvalonInstance @@ -33,6 +37,10 @@ class PublisherController: publish_plugins = pyblish.api.discover() self.publish_plugins = publish_plugins + project_name = avalon.api.Session["AVALON_PROJECT"] + system_settings = get_system_settings() + project_settings = get_project_settings(project_name) + creators = {} for creator in avalon.api.discover(BaseCreator): if inspect.isabstract(creator): @@ -40,7 +48,11 @@ class PublisherController: "Skipping abstract Creator {}".format(str(creator)) ) continue - creators[creator.family] = creator + creators[creator.family] = creator( + system_settings, + project_settings, + self.headless + ) self.creators = creators From 2754c042c2d30842835538be5b97f30f841ec463 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 30 Jun 2021 20:33:53 +0200 Subject: [PATCH 051/736] added debugging code to window --- openpype/tools/new_publisher/window.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 4e830c5eb5..696716f1a2 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -1,5 +1,24 @@ +import os import sys -sys.path.append(r"C:\Users\iLLiCiT\PycharmProjects\pype3\.venv\Lib\site-packages") + +openpype_dir = "" +mongo_url = "" +project_name = "" + +os.environ["OPENPYPE_MONGO"] = mongo_url +os.environ["AVALON_MONGO"] = mongo_url +os.environ["AVALON_PROJECT"] = project_name +os.environ["OPENPYPE_DATABASE_NAME"] = "openpype" +os.environ["AVALON_CONFIG"] = "openpype" +os.environ["AVALON_TIMEOUT"] = "1000" +os.environ["AVALON_DB"] = "avalon" +for path in [ + openpype_dir, + r"{}\repos\avalon-core".format(openpype_dir), + r"{}\.venv\Lib\site-packages".format(openpype_dir) +]: + sys.path.append(path) + from Qt import QtWidgets, QtCore from control import PublisherController From fca803aae42fd94d1f5c4b0df88f1d918ae6bba6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 30 Jun 2021 20:53:25 +0200 Subject: [PATCH 052/736] implemented base of create dialog --- openpype/tools/new_publisher/widgets.py | 46 +++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 1f38f5a8e1..fb0164eb81 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -96,3 +96,49 @@ class ThumbnailWidget(QtWidgets.QWidget): self.thumbnail_label = thumbnail_label self.default_pix = default_pix self.current_pix = None + + +class CreateDialog(QtWidgets.QDialog): + def __init__(self, controller, parent=None): + super(CreateDialog, self).__init__(parent) + + self.controller = controller + + family_list = QtWidgets.QListView(self) + variant_input = QtWidgets.QLineEdit(self) + + checkbox_inputs = QtWidgets.QWidget(self) + auto_close_checkbox = QtWidgets.QCheckBox( + "Auto-close", checkbox_inputs + ) + use_selection_checkbox = QtWidgets.QCheckBox( + "Use selection", checkbox_inputs + ) + + checkbox_layout = QtWidgets.QHBoxLayout(checkbox_inputs) + checkbox_layout.setContentsMargins(0, 0, 0, 0) + checkbox_layout.addWidget(auto_close_checkbox) + checkbox_layout.addWidget(use_selection_checkbox) + + create_btn = QtWidgets.QPushButton("Create", self) + + layout = QtWidgets.QVBoxLayout(self) + layout.addWidget(QtWidgets.QLabel("Family:", self)) + layout.addWidget(family_list, 1) + layout.addWidget(QtWidgets.QLabel("Name:", self)) + layout.addWidget(variant_input, 0) + layout.addWidget(checkbox_inputs, 0) + layout.addWidget(create_btn, 0) + + create_btn.clicked.connect(self._on_create) + + self.variant_input = variant_input + self.family_list = family_list + self.auto_close_checkbox = auto_close_checkbox + self.use_selection_checkbox = auto_close_checkbox + self.create_btn = create_btn + + def _on_create(self): + # TODO do some stuff + if self.auto_close_checkbox.isChecked(): + self.hide() From 622c924cb162a3cfa6dfc44a95ea4d29e89aa030 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 30 Jun 2021 20:53:57 +0200 Subject: [PATCH 053/736] use CreateDialog in main window --- openpype/tools/new_publisher/window.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 696716f1a2..1330fe54c3 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -22,7 +22,10 @@ for path in [ from Qt import QtWidgets, QtCore from control import PublisherController -from widgets import SubsetAttributesWidget +from widgets import ( + SubsetAttributesWidget, + CreateDialog +) class PublisherWindow(QtWidgets.QWidget): @@ -30,8 +33,10 @@ class PublisherWindow(QtWidgets.QWidget): super(PublisherWindow, self).__init__(parent) self._first_show = True - # TODO Title, Icon, Stylesheet + controller = PublisherController() + + # TODO Title, Icon, Stylesheet main_frame = QtWidgets.QWidget(self) # Header @@ -86,6 +91,8 @@ class PublisherWindow(QtWidgets.QWidget): main_layout = QtWidgets.QHBoxLayout(self) main_layout.addWidget(main_frame) + creator_window = CreateDialog(controller, self) + reset_btn.clicked.connect(self._on_reset_clicked) create_btn.clicked.connect(self._on_create_clicked) @@ -101,10 +108,10 @@ class PublisherWindow(QtWidgets.QWidget): self.validate_btn = validate_btn self.publish_btn = publish_btn - controller = PublisherController() - self.controller = controller + self.creator_window = creator_window + # DEBUGING self.set_context_label( "////" @@ -127,7 +134,7 @@ class PublisherWindow(QtWidgets.QWidget): self.reset() def _on_create_clicked(self): - print("Creation!!!") + self.creator_window.show() def _on_validate_clicked(self): print("Validation!!!") From 6bac12e763a37784856cf1093a212b7927fe4ab4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 30 Jun 2021 20:54:14 +0200 Subject: [PATCH 054/736] store last position of CreateDialog --- openpype/tools/new_publisher/widgets.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index fb0164eb81..7070e74daa 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -104,6 +104,8 @@ class CreateDialog(QtWidgets.QDialog): self.controller = controller + self._last_pos = None + family_list = QtWidgets.QListView(self) variant_input = QtWidgets.QLineEdit(self) @@ -138,6 +140,15 @@ class CreateDialog(QtWidgets.QDialog): self.use_selection_checkbox = auto_close_checkbox self.create_btn = create_btn + def moveEvent(self, event): + super(CreateDialog, self).moveEvent(event) + self._last_pos = self.pos() + + def showEvent(self, event): + super(CreateDialog, self).showEvent(event) + if self._last_pos is not None: + self.move(self._last_pos) + def _on_create(self): # TODO do some stuff if self.auto_close_checkbox.isChecked(): From 483b4f161c16f76e61aee97ba2bab43ae5f46e85 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 1 Jul 2021 10:05:36 +0200 Subject: [PATCH 055/736] use openpype styles --- openpype/tools/new_publisher/window.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 1330fe54c3..813b667c04 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -21,6 +21,7 @@ for path in [ from Qt import QtWidgets, QtCore +from openpype import style from control import PublisherController from widgets import ( SubsetAttributesWidget, @@ -116,7 +117,7 @@ class PublisherWindow(QtWidgets.QWidget): self.set_context_label( "////" ) - # self.setStyleSheet("border: 1px solid black;") + self.setStyleSheet(style.load_stylesheet()) def showEvent(self, event): super(PublisherWindow, self).showEvent(event) From 902656ec30f14ae4ea64eece276f48c1e8e6effe Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 1 Jul 2021 19:56:12 +0200 Subject: [PATCH 056/736] added base implementation of get_subset_name --- openpype/pipeline/creator_plugins.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index 6696951b31..daa5a1d094 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -9,6 +9,8 @@ from abc import ( ) import six +from openpype.lib import get_subset_name + class AvalonInstance: """Instance entity with data that will be stored to workfile. @@ -137,6 +139,11 @@ class BaseCreator: return None + def get_dynamic_data( + self, variant, task_name, asset_doc, project_name, host_name + ): + return {} + def get_subset_name( self, variant, task_name, asset_doc, project_name, host_name=None ): @@ -159,7 +166,19 @@ class BaseCreator: project_name(str): Project name. host_name(str): Which host creates subset. """ - pass + dynamic_data = self.get_dynamic_data( + variant, task_name, asset_doc, project_name, host_name + ) + + return get_subset_name( + self.family, + variant, + task_name, + asset_doc, + project_name, + host_name, + dynamic_data=dynamic_data + ) def get_attribute_defs(self): """Plugin attribute definitions. From c643ac5f6255f106e531586bf1025c3d3aa77d01 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 1 Jul 2021 19:56:40 +0200 Subject: [PATCH 057/736] controller can have defined AvalonMongoDB --- openpype/tools/new_publisher/control.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 788126a206..50b2e755b5 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -13,11 +13,17 @@ from openpype.pipeline import ( class PublisherController: - def __init__(self, headless=False): + def __init__(self, dbcon=None, headless=False): self.log = logging.getLogger("PublisherController") self.host = avalon.api.registered_host() self.headless = headless + if dbcon is None: + session = avalon.api.session_data_from_environment(True) + dbcon = avalon.api.AvalonMongoDB(session) + dbcon.install() + self.dbcon = dbcon + self.creators = {} self.publish_plugins = [] self.instances = [] From ffe752e8a360d45943e7d637d40410f6f7cbb22d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 1 Jul 2021 19:56:56 +0200 Subject: [PATCH 058/736] controller handle reset callbacks --- openpype/tools/new_publisher/control.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 50b2e755b5..76ecf6e2df 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -1,3 +1,4 @@ +import weakref import logging import inspect import avalon.api @@ -24,18 +25,36 @@ class PublisherController: dbcon.install() self.dbcon = dbcon + self._reset_callback_refs = set() + self.creators = {} self.publish_plugins = [] self.instances = [] self._in_reset = False + def add_reset_callback(self, callback): + ref = weakref.WeakMethod(callback) + self._reset_callback_refs.add(ref) + def reset(self): if self._in_reset: return self._in_reset = True self._reset() + + # Trigger reset callbacks + to_remove = set() + for ref in self._reset_callback_refs: + callback = ref() + if callback: + callback() + else: + to_remove.add(ref) + for ref in to_remove: + self._reset_callback_refs.remove(ref) + self._in_reset = False def _reset(self): From b51556f056053a37b0f81dcde2cbf97d57e608c5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 1 Jul 2021 19:57:44 +0200 Subject: [PATCH 059/736] use self.dbcon.Session instead of avalon.api.Session --- openpype/tools/new_publisher/control.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 76ecf6e2df..b390693147 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -62,7 +62,7 @@ class PublisherController: publish_plugins = pyblish.api.discover() self.publish_plugins = publish_plugins - project_name = avalon.api.Session["AVALON_PROJECT"] + project_name = self.dbcon.Session["AVALON_PROJECT"] system_settings = get_system_settings() project_settings = get_project_settings(project_name) From 40d774b03a247cc85469be333d9f8f68fcfde983 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 1 Jul 2021 19:57:59 +0200 Subject: [PATCH 060/736] added create method without implementation and argument defintiions --- openpype/tools/new_publisher/control.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index b390693147..8e3d297a85 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -94,3 +94,6 @@ class PublisherController: instances.append(instance) self.instances = instances + + def create(self, family, variant=None, options=None): + print("TODO implement create") From 91b9d11d2d4580f4fe55946daec4db21e3799690 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 1 Jul 2021 19:58:36 +0200 Subject: [PATCH 061/736] added more context variables --- openpype/tools/new_publisher/window.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 813b667c04..7f9bcb7e63 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -4,10 +4,16 @@ import sys openpype_dir = "" mongo_url = "" project_name = "" +asset_name = "" +task_name = "" +host_name = "" os.environ["OPENPYPE_MONGO"] = mongo_url os.environ["AVALON_MONGO"] = mongo_url os.environ["AVALON_PROJECT"] = project_name +os.environ["AVALON_ASSET"] = asset_name +os.environ["AVALON_TASK"] = task_name +os.environ["AVALON_APP"] = host_name os.environ["OPENPYPE_DATABASE_NAME"] = "openpype" os.environ["AVALON_CONFIG"] = "openpype" os.environ["AVALON_TIMEOUT"] = "1000" From d36505b95f8b3da4ae4065cbb486d6b1cb87fa84 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 1 Jul 2021 19:58:52 +0200 Subject: [PATCH 062/736] moved image out of init method --- openpype/tools/new_publisher/widgets.py | 26 ++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 7070e74daa..106bbacd3b 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -8,22 +8,22 @@ def get_default_thumbnail_image_path(): class SubsetAttributesWidget(QtWidgets.QWidget): + """Widget where attributes of instance/s are modified. + _____________________________ + | | | + | Global | Thumbnail | + | attributes | | TOP + |_________________|___________| + | | | + | | Publish | + | Family | plugin | + | attributes | attributes | BOTTOM + |______________|______________| + """ + def __init__(self, parent): super(SubsetAttributesWidget, self).__init__(parent) - """ - _____________________________ - | | | - | Global | Thumbnail | - | attributes | | TOP - |_________________|___________| - | | | - | | Publish | - | Family | plugin | - | attributes | attributes | BOTTOM - |______________|______________| - """ - # TOP PART top_widget = QtWidgets.QWidget(self) From c20f779cbcc7a0ad19080045716ab883015712ce Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 1 Jul 2021 19:59:30 +0200 Subject: [PATCH 063/736] renamed family_list to family_view --- openpype/tools/new_publisher/widgets.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 106bbacd3b..c365f93507 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -105,8 +105,8 @@ class CreateDialog(QtWidgets.QDialog): self.controller = controller self._last_pos = None + family_view = QtWidgets.QListView(self) - family_list = QtWidgets.QListView(self) variant_input = QtWidgets.QLineEdit(self) checkbox_inputs = QtWidgets.QWidget(self) @@ -126,7 +126,7 @@ class CreateDialog(QtWidgets.QDialog): layout = QtWidgets.QVBoxLayout(self) layout.addWidget(QtWidgets.QLabel("Family:", self)) - layout.addWidget(family_list, 1) + layout.addWidget(family_view, 1) layout.addWidget(QtWidgets.QLabel("Name:", self)) layout.addWidget(variant_input, 0) layout.addWidget(checkbox_inputs, 0) @@ -135,7 +135,7 @@ class CreateDialog(QtWidgets.QDialog): create_btn.clicked.connect(self._on_create) self.variant_input = variant_input - self.family_list = family_list + self.family_view = family_view self.auto_close_checkbox = auto_close_checkbox self.use_selection_checkbox = auto_close_checkbox self.create_btn = create_btn From dbb97bd4ce13354a5ab24aaa5e272018ccc9d84f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 1 Jul 2021 20:00:01 +0200 Subject: [PATCH 064/736] family view has model --- openpype/tools/new_publisher/widgets.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index c365f93507..662523cc21 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -106,6 +106,8 @@ class CreateDialog(QtWidgets.QDialog): self._last_pos = None family_view = QtWidgets.QListView(self) + family_model = QtGui.QStandardItemModel() + family_view.setModel(family_model) variant_input = QtWidgets.QLineEdit(self) @@ -135,6 +137,7 @@ class CreateDialog(QtWidgets.QDialog): create_btn.clicked.connect(self._on_create) self.variant_input = variant_input + self.family_model = family_model self.family_view = family_view self.auto_close_checkbox = auto_close_checkbox self.use_selection_checkbox = auto_close_checkbox From e3a3534cd0048258663168bf450fa164b793c7c9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 1 Jul 2021 20:00:44 +0200 Subject: [PATCH 065/736] added subset name input --- openpype/tools/new_publisher/widgets.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 662523cc21..b6f9d16f8d 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -111,6 +111,9 @@ class CreateDialog(QtWidgets.QDialog): variant_input = QtWidgets.QLineEdit(self) + subset_name_input = QtWidgets.QLineEdit(self) + subset_name_input.setEnabled(False) + checkbox_inputs = QtWidgets.QWidget(self) auto_close_checkbox = QtWidgets.QCheckBox( "Auto-close", checkbox_inputs @@ -131,10 +134,13 @@ class CreateDialog(QtWidgets.QDialog): layout.addWidget(family_view, 1) layout.addWidget(QtWidgets.QLabel("Name:", self)) layout.addWidget(variant_input, 0) + layout.addWidget(QtWidgets.QLabel("Subset:", self)) + layout.addWidget(subset_name_input, 0) layout.addWidget(checkbox_inputs, 0) layout.addWidget(create_btn, 0) create_btn.clicked.connect(self._on_create) + self.subset_name_input = subset_name_input self.variant_input = variant_input self.family_model = family_model From 5c67832eab02b201b1f5534ebce070adc080f2f4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 1 Jul 2021 20:01:18 +0200 Subject: [PATCH 066/736] added asset name input --- openpype/tools/new_publisher/widgets.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index b6f9d16f8d..771a4cf04a 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -111,6 +111,9 @@ class CreateDialog(QtWidgets.QDialog): variant_input = QtWidgets.QLineEdit(self) + asset_name_input = QtWidgets.QLineEdit(self) + asset_name_input.setEnabled(False) + subset_name_input = QtWidgets.QLineEdit(self) subset_name_input.setEnabled(False) @@ -132,6 +135,8 @@ class CreateDialog(QtWidgets.QDialog): layout = QtWidgets.QVBoxLayout(self) layout.addWidget(QtWidgets.QLabel("Family:", self)) layout.addWidget(family_view, 1) + layout.addWidget(QtWidgets.QLabel("Asset:", self)) + layout.addWidget(asset_name_input, 0) layout.addWidget(QtWidgets.QLabel("Name:", self)) layout.addWidget(variant_input, 0) layout.addWidget(QtWidgets.QLabel("Subset:", self)) @@ -140,6 +145,8 @@ class CreateDialog(QtWidgets.QDialog): layout.addWidget(create_btn, 0) create_btn.clicked.connect(self._on_create) + + self.asset_name_input = asset_name_input self.subset_name_input = subset_name_input self.variant_input = variant_input From de9086f72aaa8953d5dccb7a7892e2d195e47b25 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 1 Jul 2021 20:02:34 +0200 Subject: [PATCH 067/736] added variant hints button --- openpype/tools/new_publisher/widgets.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 771a4cf04a..317ff078fd 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -111,6 +111,15 @@ class CreateDialog(QtWidgets.QDialog): variant_input = QtWidgets.QLineEdit(self) + variant_hints_btn = QtWidgets.QPushButton(self) + variant_hints_btn.setFixedWidth(18) + + variant_layout = QtWidgets.QHBoxLayout() + variant_layout.setContentsMargins(0, 0, 0, 0) + variant_layout.setSpacing(0) + variant_layout.addWidget(variant_input, 1) + variant_layout.addWidget(variant_hints_btn, 0) + asset_name_input = QtWidgets.QLineEdit(self) asset_name_input.setEnabled(False) @@ -138,7 +147,7 @@ class CreateDialog(QtWidgets.QDialog): layout.addWidget(QtWidgets.QLabel("Asset:", self)) layout.addWidget(asset_name_input, 0) layout.addWidget(QtWidgets.QLabel("Name:", self)) - layout.addWidget(variant_input, 0) + layout.addLayout(variant_layout, 0) layout.addWidget(QtWidgets.QLabel("Subset:", self)) layout.addWidget(subset_name_input, 0) layout.addWidget(checkbox_inputs, 0) @@ -150,6 +159,8 @@ class CreateDialog(QtWidgets.QDialog): self.subset_name_input = subset_name_input self.variant_input = variant_input + self.variant_hints_btn = variant_hints_btn + self.family_model = family_model self.family_view = family_view self.auto_close_checkbox = auto_close_checkbox From ae8278deaed1184c889d4246848bc4dbd093c7a4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 1 Jul 2021 20:03:06 +0200 Subject: [PATCH 068/736] added dbcon property to create dialog --- openpype/tools/new_publisher/widgets.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 317ff078fd..68d18c060c 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -167,6 +167,10 @@ class CreateDialog(QtWidgets.QDialog): self.use_selection_checkbox = auto_close_checkbox self.create_btn = create_btn + @property + def dbcon(self): + return self.controller.dbcon + def moveEvent(self, event): super(CreateDialog, self).moveEvent(event) self._last_pos = self.pos() From 1fcd5260566e954437453db24c7ab7810da5b19a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 1 Jul 2021 20:03:35 +0200 Subject: [PATCH 069/736] added variant menu and group for hints --- openpype/tools/new_publisher/widgets.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 68d18c060c..0f9b13ceb0 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -114,6 +114,10 @@ class CreateDialog(QtWidgets.QDialog): variant_hints_btn = QtWidgets.QPushButton(self) variant_hints_btn.setFixedWidth(18) + variant_hints_menu = QtWidgets.QMenu(variant_hints_btn) + variant_hints_group = QtWidgets.QActionGroup(variant_hints_menu) + variant_hints_btn.setMenu(variant_hints_menu) + variant_layout = QtWidgets.QHBoxLayout() variant_layout.setContentsMargins(0, 0, 0, 0) variant_layout.setSpacing(0) @@ -160,6 +164,8 @@ class CreateDialog(QtWidgets.QDialog): self.variant_input = variant_input self.variant_hints_btn = variant_hints_btn + self.variant_hints_menu = variant_hints_menu + self.variant_hints_group = variant_hints_group self.family_model = family_model self.family_view = family_view From f27e22a33fc98172359223f37817046ce958fb9d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 1 Jul 2021 20:03:48 +0200 Subject: [PATCH 070/736] define separators values --- openpype/tools/new_publisher/widgets.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 0f9b13ceb0..37e8ac1ef5 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -1,6 +1,8 @@ import os from Qt import QtWidgets, QtCore, QtGui +SEPARATORS = ("---separator---", "---") + def get_default_thumbnail_image_path(): dirpath = os.path.dirname(os.path.abspath(__file__)) From a859339387defce9ee803adbbdeb707c901ad3bc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 1 Jul 2021 20:06:33 +0200 Subject: [PATCH 071/736] added base of asset refresh and cache it's document --- openpype/tools/new_publisher/widgets.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 37e8ac1ef5..dc41d97c37 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -107,6 +107,7 @@ class CreateDialog(QtWidgets.QDialog): self.controller = controller self._last_pos = None + self._asset_doc = None family_view = QtWidgets.QListView(self) family_model = QtGui.QStandardItemModel() family_view.setModel(family_model) @@ -179,6 +180,30 @@ class CreateDialog(QtWidgets.QDialog): def dbcon(self): return self.controller.dbcon + def refresh(self): + self._refresh_asset() + + def _refresh_asset(self): + asset_name = self.dbcon.Session.get("AVALON_ASSET") + + # Skip if asset did not change + if self._asset_doc and self._asset_doc["name"] == asset_name: + return + + # Make sure `_asset_doc` and `_subset_names` variables are reset + self._asset_doc = None + if asset_name is None: + return + + asset_doc = self.dbcon.find_one({ + "type": "asset", + "name": asset_name + }) + self._asset_doc = asset_doc + + if asset_doc: + self.asset_name_input.setText(asset_doc["name"]) + def moveEvent(self, event): super(CreateDialog, self).moveEvent(event) self._last_pos = self.pos() From 92156f29a62eaefbf8a22350613881b4853b14af Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 1 Jul 2021 20:07:02 +0200 Subject: [PATCH 072/736] also cache subset names of current asset on refresh --- openpype/tools/new_publisher/widgets.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index dc41d97c37..66961040b7 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -108,6 +108,7 @@ class CreateDialog(QtWidgets.QDialog): self._last_pos = None self._asset_doc = None + self._subset_names = None family_view = QtWidgets.QListView(self) family_model = QtGui.QStandardItemModel() family_view.setModel(family_model) @@ -192,6 +193,7 @@ class CreateDialog(QtWidgets.QDialog): # Make sure `_asset_doc` and `_subset_names` variables are reset self._asset_doc = None + self._subset_names = None if asset_name is None: return @@ -203,6 +205,14 @@ class CreateDialog(QtWidgets.QDialog): if asset_doc: self.asset_name_input.setText(asset_doc["name"]) + subset_docs = self.dbcon.find( + { + "type": "subset", + "parent": asset_doc["_id"] + }, + {"name": 1} + ) + self._subset_names = set(subset_docs.distinct("name")) def moveEvent(self, event): super(CreateDialog, self).moveEvent(event) From a635fc4b9ad362c67fa12139a3f5434c93530a59 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 1 Jul 2021 20:07:50 +0200 Subject: [PATCH 073/736] added callback on controller reset --- openpype/tools/new_publisher/widgets.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 66961040b7..c7721bc965 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -163,6 +163,8 @@ class CreateDialog(QtWidgets.QDialog): create_btn.clicked.connect(self._on_create) + controller.add_reset_callback(self._on_control_reset) + self.asset_name_input = asset_name_input self.subset_name_input = subset_name_input @@ -214,6 +216,9 @@ class CreateDialog(QtWidgets.QDialog): ) self._subset_names = set(subset_docs.distinct("name")) + def _on_control_reset(self): + self.refresh() + def moveEvent(self, event): super(CreateDialog, self).moveEvent(event) self._last_pos = self.pos() From e2187daeaf93975717071319723a6acd1bfa3b53 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 1 Jul 2021 20:08:43 +0200 Subject: [PATCH 074/736] added method for refreshing creators --- openpype/tools/new_publisher/widgets.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index c7721bc965..37554f5e05 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -185,6 +185,7 @@ class CreateDialog(QtWidgets.QDialog): def refresh(self): self._refresh_asset() + self._refresh_creators() def _refresh_asset(self): asset_name = self.dbcon.Session.get("AVALON_ASSET") @@ -216,6 +217,30 @@ class CreateDialog(QtWidgets.QDialog): ) self._subset_names = set(subset_docs.distinct("name")) + def _refresh_creators(self): + # Refresh creators and add their families to list + existing_items = {} + old_families = set() + for row in range(self.family_model.rowCount()): + item = self.family_model.item(row, 0) + family = item.data(QtCore.Qt.DisplayRole) + existing_items[family] = item + old_families.add(family) + + # Add new families + new_families = set() + for family, creator in self.controller.creators.items(): + # TODO add details about creator + new_families.add(family) + if family not in existing_items: + item = QtGui.QStandardItem(family) + self.family_model.appendRow(item) + + # Remove families that are no more available + for family in (old_families - new_families): + item = existing_items[family] + self.family_model.takeRow(item.row()) + def _on_control_reset(self): self.refresh() From 1e1874772e53e43f507dba64a15e9558bce793c3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 1 Jul 2021 20:09:47 +0200 Subject: [PATCH 075/736] set family if nothing is selected --- openpype/tools/new_publisher/widgets.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 37554f5e05..7ad13ae45d 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -241,6 +241,15 @@ class CreateDialog(QtWidgets.QDialog): item = existing_items[family] self.family_model.takeRow(item.row()) + if self.family_model.rowCount() < 1: + return + + # Make sure there is a selection + indexes = self.family_view.selectedIndexes() + if not indexes: + index = self.family_model.index(0, 0) + self.family_view.setCurrentIndex(index) + def _on_control_reset(self): self.refresh() From fb0a4a79252da7bf84601fa65d2e3c0a6dae8912 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 1 Jul 2021 20:10:04 +0200 Subject: [PATCH 076/736] create button is disabled by default --- openpype/tools/new_publisher/widgets.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 7ad13ae45d..d005d5a2c6 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -148,6 +148,7 @@ class CreateDialog(QtWidgets.QDialog): checkbox_layout.addWidget(use_selection_checkbox) create_btn = QtWidgets.QPushButton("Create", self) + create_btn.setEnabled(False) layout = QtWidgets.QVBoxLayout(self) layout.addWidget(QtWidgets.QLabel("Family:", self)) From 446687b31f15c226ecead60049ccd53851304eba Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 1 Jul 2021 20:11:07 +0200 Subject: [PATCH 077/736] both asset and families must be available to do anything in creator otherwise disable all widgets --- openpype/tools/new_publisher/widgets.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index d005d5a2c6..718cfa1a51 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -109,6 +109,8 @@ class CreateDialog(QtWidgets.QDialog): self._last_pos = None self._asset_doc = None self._subset_names = None + + self._prereq_available = False family_view = QtWidgets.QListView(self) family_model = QtGui.QStandardItemModel() family_view.setModel(family_model) @@ -185,9 +187,23 @@ class CreateDialog(QtWidgets.QDialog): return self.controller.dbcon def refresh(self): + self._prereq_available = True + self._refresh_asset() self._refresh_creators() + if self._asset_doc is None: + self.asset_name_input.setText("< Asset is not set >") + self._prereq_available = False + + if self.family_model.rowCount() < 1: + self._prereq_available = False + + self.create_btn.setEnabled(self._prereq_available) + self.family_view.setEnabled(self._prereq_available) + self.variant_input.setEnabled(self._prereq_available) + self.variant_hints_btn.setEnabled(self._prereq_available) + def _refresh_asset(self): asset_name = self.dbcon.Session.get("AVALON_ASSET") From ad3a10e74fcbf75cf6521895b2a64f854831d48a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 1 Jul 2021 20:11:43 +0200 Subject: [PATCH 078/736] added comments --- openpype/tools/new_publisher/widgets.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 718cfa1a51..9ba13bb58e 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -189,7 +189,10 @@ class CreateDialog(QtWidgets.QDialog): def refresh(self): self._prereq_available = True + # Refresh data before update of creators self._refresh_asset() + # Then refresh creators which may trigger callbacks using refreshed + # data self._refresh_creators() if self._asset_doc is None: From 38251d7021bb97563ae882b16c62ca8f475a77e9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 1 Jul 2021 20:12:24 +0200 Subject: [PATCH 079/736] change variant input on variant action click --- openpype/tools/new_publisher/widgets.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 9ba13bb58e..f3d0b461de 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -165,6 +165,7 @@ class CreateDialog(QtWidgets.QDialog): layout.addWidget(create_btn, 0) create_btn.clicked.connect(self._on_create) + variant_hints_menu.triggered.connect(self._on_variant_action) controller.add_reset_callback(self._on_control_reset) @@ -273,6 +274,11 @@ class CreateDialog(QtWidgets.QDialog): def _on_control_reset(self): self.refresh() + def _on_variant_action(self, action): + value = action.text() + if self.variant_input.text() != value: + self.variant_input.setText(value) + def moveEvent(self, event): super(CreateDialog, self).moveEvent(event) self._last_pos = self.pos() From 2427add56244cae285f8bb2addbb8d4480b3fa55 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 1 Jul 2021 20:14:20 +0200 Subject: [PATCH 080/736] added callback on family change and cache selected creator --- openpype/tools/new_publisher/widgets.py | 34 +++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index f3d0b461de..fd9cff17d4 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -109,6 +109,7 @@ class CreateDialog(QtWidgets.QDialog): self._last_pos = None self._asset_doc = None self._subset_names = None + self._selected_creator = None self._prereq_available = False family_view = QtWidgets.QListView(self) @@ -165,6 +166,9 @@ class CreateDialog(QtWidgets.QDialog): layout.addWidget(create_btn, 0) create_btn.clicked.connect(self._on_create) + family_view.selectionModel().currentChanged.connect( + self._on_family_change + ) variant_hints_menu.triggered.connect(self._on_variant_action) controller.add_reset_callback(self._on_control_reset) @@ -274,6 +278,36 @@ class CreateDialog(QtWidgets.QDialog): def _on_control_reset(self): self.refresh() + def _on_family_change(self, new_index, old_index): + family = None + if new_index.isValid(): + family = new_index.data(QtCore.Qt.DisplayRole) + + creator = self.controller.creators.get(family) + self._selected_creator = creator + if not creator: + return + + default_variants = creator.get_default_variants() + if not default_variants: + default_variants = ["Main"] + + default_variant = creator.get_default_variant() + if not default_variant: + default_variant = default_variants[0] + + for action in tuple(self.variant_hints_menu.actions()): + self.variant_hints_menu.removeAction(action) + action.deleteLater() + + for variant in default_variants: + if variant in SEPARATORS: + self.variant_hints_menu.addSeparator() + elif variant: + self.variant_hints_menu.addAction(variant) + + self.variant_input.setText(default_variant or "Main") + def _on_variant_action(self, action): value = action.text() if self.variant_input.text() != value: From 5de4fa2cf4a32d7ff0740d07f5f090464a05edcf Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 1 Jul 2021 20:14:35 +0200 Subject: [PATCH 081/736] trigger creation on enter press --- openpype/tools/new_publisher/widgets.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index fd9cff17d4..0f1f66fc99 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -166,6 +166,7 @@ class CreateDialog(QtWidgets.QDialog): layout.addWidget(create_btn, 0) create_btn.clicked.connect(self._on_create) + variant_input.returnPressed.connect(self._on_create) family_view.selectionModel().currentChanged.connect( self._on_family_change ) From 5860de67707b575e8327601e49c6f390a167c979 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 1 Jul 2021 20:15:10 +0200 Subject: [PATCH 082/736] set subset name on variant change --- openpype/tools/new_publisher/widgets.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 0f1f66fc99..469505056c 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -1,4 +1,5 @@ import os +import copy from Qt import QtWidgets, QtCore, QtGui SEPARATORS = ("---separator---", "---") @@ -314,6 +315,22 @@ class CreateDialog(QtWidgets.QDialog): if self.variant_input.text() != value: self.variant_input.setText(value) + def _on_variant_change(self, variant_value): + if not self._prereq_available or not self._selected_creator: + if self.subset_name_input.text(): + self.subset_name_input.setText("") + return + + project_name = self.dbcon.Session["AVALON_PROJECT"] + task_name = self.dbcon.Session.get("AVALON_TASK") + + asset_doc = copy.deepcopy(self._asset_doc) + # Calculate subset name with Creator plugin + subset_name = self._selected_creator.get_subset_name( + variant_value, task_name, asset_doc, project_name + ) + self.subset_name_input.setText(subset_name) + def moveEvent(self, event): super(CreateDialog, self).moveEvent(event) self._last_pos = self.pos() From 6ce0ddde070791e7aae446393e147558f74a22d4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 1 Jul 2021 20:15:38 +0200 Subject: [PATCH 083/736] base of on create method --- openpype/tools/new_publisher/widgets.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 469505056c..dc9ee17ab9 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -341,6 +341,21 @@ class CreateDialog(QtWidgets.QDialog): self.move(self._last_pos) def _on_create(self): - # TODO do some stuff + indexes = self.family_view.selectedIndexes() + if not indexes or len(indexes) > 1: + return + + if not self.create_btn.isEnabled(): + return + + index = indexes[0] + family = index.data(QtCore.Qt.DisplayRole) + variant = self.variant_input.text() + options = { + "useSelection": self.use_selection_checkbox.isChecked() + } + + self.controller.create(family, variant, options) + if self.auto_close_checkbox.isChecked(): self.hide() From 3b57e9f5514e64522d94d8d04ace46380578d692 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 1 Jul 2021 20:15:58 +0200 Subject: [PATCH 084/736] trigger variant change callback on text change --- openpype/tools/new_publisher/widgets.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index dc9ee17ab9..21f29af993 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -168,6 +168,7 @@ class CreateDialog(QtWidgets.QDialog): create_btn.clicked.connect(self._on_create) variant_input.returnPressed.connect(self._on_create) + variant_input.textChanged.connect(self._on_variant_change) family_view.selectionModel().currentChanged.connect( self._on_family_change ) From 4e830bd45c7a5079532adf01846b706fcc126c4f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 1 Jul 2021 20:16:52 +0200 Subject: [PATCH 085/736] added validation of subset name on variant change --- openpype/tools/new_publisher/widgets.py | 40 +++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 21f29af993..c29d35fd6c 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -332,6 +332,46 @@ class CreateDialog(QtWidgets.QDialog): ) self.subset_name_input.setText(subset_name) + self._validate_subset_name(subset_name, variant_value) + + def _validate_subset_name(self, subset_name, variant_value): + # Get all subsets of the current asset + existing_subset_names = set(self._subset_names) + existing_subset_names_low = set( + _name.lower() + for _name in existing_subset_names + ) + + # Replace + compare_regex = re.compile(re.sub( + variant_value, "(.+)", subset_name, flags=re.IGNORECASE + )) + variant_hints = set() + if variant_value: + for _name in existing_subset_names: + _result = compare_regex.search(_name) + if _result: + variant_hints |= set(_result.groups()) + + # Remove previous hints from menu + for action in tuple(self.variant_hints_group.actions()): + self.variant_hints_group.removeAction(action) + self.variant_hints_menu.removeAction(action) + action.deleteLater() + + # Add separator if there are hints and menu already has actions + if variant_hints and self.variant_hints_menu.actions(): + self.variant_hints_menu.addSeparator() + + # Add hints to actions + for variant_hint in variant_hints: + action = self.variant_hints_menu.addAction(variant_hint) + self.variant_hints_group.addAction(action) + + variant_is_valid = variant_value.strip() != "" + if variant_is_valid != self.create_btn.isEnabled(): + self.create_btn.setEnabled(variant_is_valid) + def moveEvent(self, event): super(CreateDialog, self).moveEvent(event) self._last_pos = self.pos() From 51ba573eaf5b2d61fde42c4f07c8b47feca9f7f5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 1 Jul 2021 20:17:06 +0200 Subject: [PATCH 086/736] refresh on show --- openpype/tools/new_publisher/widgets.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index c29d35fd6c..85d80c41ce 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -381,6 +381,8 @@ class CreateDialog(QtWidgets.QDialog): if self._last_pos is not None: self.move(self._last_pos) + self.refresh() + def _on_create(self): indexes = self.family_view.selectedIndexes() if not indexes or len(indexes) > 1: From 55b3468c268c5d89ea775f26ac63220f4a93b3aa Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 1 Jul 2021 20:18:26 +0200 Subject: [PATCH 087/736] trigger refresh only if dialog is visible --- openpype/tools/new_publisher/widgets.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 85d80c41ce..35f3ebe64d 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -279,7 +279,9 @@ class CreateDialog(QtWidgets.QDialog): self.family_view.setCurrentIndex(index) def _on_control_reset(self): - self.refresh() + # Trigger refresh only if is visible + if self.isVisible(): + self.refresh() def _on_family_change(self, new_index, old_index): family = None From 6c67bf3381d38fe1ccf6f6fca55bf05d074b4849 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 1 Jul 2021 20:18:43 +0200 Subject: [PATCH 088/736] keep old_index as unused argument --- openpype/tools/new_publisher/widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 35f3ebe64d..d4872d02b9 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -283,7 +283,7 @@ class CreateDialog(QtWidgets.QDialog): if self.isVisible(): self.refresh() - def _on_family_change(self, new_index, old_index): + def _on_family_change(self, new_index, _old_index): family = None if new_index.isValid(): family = new_index.data(QtCore.Qt.DisplayRole) From d0399e798f6615f36b9381107bb142f5c1054a75 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 1 Jul 2021 20:19:10 +0200 Subject: [PATCH 089/736] added style changes of variant input border --- openpype/style/style.css | 12 ++++++++++++ openpype/tools/new_publisher/widgets.py | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/openpype/style/style.css b/openpype/style/style.css index c57b9a8da6..da7335d5c4 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -614,3 +614,15 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { border: 1px solid {color:border}; border-radius: 0.1em; } + +#VariantInput[state="new"], #VariantInput[state="new"]:focus, #VariantInput[state="new"]:hover { + border-color: #7AAB8F; +} + +#VariantInput[state="empty"], #VariantInput[state="empty"]:focus, #VariantInput[state="empty"]:hover { + border-color: {color:bg-inputs}; +} + +#VariantInput[state="exists"], #VariantInput[state="exists"]:focus, #VariantInput[state="exists"]:hover { + border-color: #4E76BB; +} diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index d4872d02b9..ce33cfbb13 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -118,6 +118,7 @@ class CreateDialog(QtWidgets.QDialog): family_view.setModel(family_model) variant_input = QtWidgets.QLineEdit(self) + variant_input.setObjectName("VariantInput") variant_hints_btn = QtWidgets.QPushButton(self) variant_hints_btn.setFixedWidth(18) @@ -370,6 +371,23 @@ class CreateDialog(QtWidgets.QDialog): action = self.variant_hints_menu.addAction(variant_hint) self.variant_hints_group.addAction(action) + # Indicate subset existence + if not variant_value: + property_value = "empty" + + elif subset_name.lower() in existing_subset_names_low: + # validate existence of subset name with lowered text + # - "renderMain" vs. "rendermain" mean same path item for + # windows + property_value = "exists" + else: + property_value = "new" + + current_value = self.variant_input.property("state") + if current_value != property_value: + self.variant_input.setProperty("state", property_value) + self.variant_input.style().polish(self.variant_input) + variant_is_valid = variant_value.strip() != "" if variant_is_valid != self.create_btn.isEnabled(): self.create_btn.setEnabled(variant_is_valid) From d02dfc3e346ba65142a31e08b44c1ef09a6a470f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 1 Jul 2021 20:23:52 +0200 Subject: [PATCH 090/736] added missing import --- openpype/tools/new_publisher/widgets.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index ce33cfbb13..b453af0851 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -1,4 +1,5 @@ import os +import re import copy from Qt import QtWidgets, QtCore, QtGui From a7123e3cc1fd3c469940e52ede9847031ceda0f0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 7 Jul 2021 18:46:27 +0200 Subject: [PATCH 091/736] more data are passet to create method --- openpype/tools/new_publisher/control.py | 5 +++-- openpype/tools/new_publisher/widgets.py | 14 ++++++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 8e3d297a85..962c712b12 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -95,5 +95,6 @@ class PublisherController: self.instances = instances - def create(self, family, variant=None, options=None): - print("TODO implement create") + def create(self, family, subset_name, instance_data, options): + creator = self.creators[family] + return creator.create(subset_name, instance_data, options) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index b453af0851..ffa461890d 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -414,12 +414,22 @@ class CreateDialog(QtWidgets.QDialog): index = indexes[0] family = index.data(QtCore.Qt.DisplayRole) + subset_name = self.subset_name_input.text() variant = self.variant_input.text() + asset_name = self._asset_doc["name"] + task_name = self.dbcon.Session.get("AVALON_TASK") options = { "useSelection": self.use_selection_checkbox.isChecked() } - - self.controller.create(family, variant, options) + # Where to define these data? + # - what data show be stored? + instance_data = { + "asset": asset_name, + "task": task_name, + "variant": variant, + "family": family + } + self.controller.create(family, subset_name, instance_data, options) if self.auto_close_checkbox.isChecked(): self.hide() From d87b1328c23c5d16de25500dcbf5f8b13d6315cf Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 7 Jul 2021 18:58:11 +0200 Subject: [PATCH 092/736] catch create errors --- openpype/tools/new_publisher/widgets.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index ffa461890d..5a0b08b8f8 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -429,7 +429,15 @@ class CreateDialog(QtWidgets.QDialog): "variant": variant, "family": family } - self.controller.create(family, subset_name, instance_data, options) + + error_info = None + try: + self.controller.create(family, subset_name, instance_data, options) + + except Exception as exc: + # TODO better handling + print(str(exc)) + if self.auto_close_checkbox.isChecked(): self.hide() From 0a4de5accb49c9b16e395e46280191dc7ee41891 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Jul 2021 10:06:14 +0200 Subject: [PATCH 093/736] added method that handle for triggering callbacks --- openpype/tools/new_publisher/control.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 962c712b12..ce0f2a30db 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -37,6 +37,20 @@ class PublisherController: ref = weakref.WeakMethod(callback) self._reset_callback_refs.add(ref) + + def _trigger_callbacks(self, callbacks, *args, **kwargs): + # Trigger reset callbacks + to_remove = set() + for ref in callbacks: + callback = ref() + if callback: + callback() + else: + to_remove.add(ref) + + for ref in to_remove: + callbacks.remove(ref) + def reset(self): if self._in_reset: return @@ -45,15 +59,7 @@ class PublisherController: self._reset() # Trigger reset callbacks - to_remove = set() - for ref in self._reset_callback_refs: - callback = ref() - if callback: - callback() - else: - to_remove.add(ref) - for ref in to_remove: - self._reset_callback_refs.remove(ref) + self._trigger_callbacks(self._reset_callback_refs) self._in_reset = False From 214bba3880db26eb2194c367989b8b82283d0de5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Jul 2021 10:06:55 +0200 Subject: [PATCH 094/736] renamed method `add_reset_callback` to `add_on_reset_callback` --- openpype/tools/new_publisher/control.py | 2 +- openpype/tools/new_publisher/widgets.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index ce0f2a30db..3d9fdeb517 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -33,7 +33,7 @@ class PublisherController: self._in_reset = False - def add_reset_callback(self, callback): + def add_on_reset_callback(self, callback): ref = weakref.WeakMethod(callback) self._reset_callback_refs.add(ref) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 5a0b08b8f8..71709be2e1 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -176,7 +176,7 @@ class CreateDialog(QtWidgets.QDialog): ) variant_hints_menu.triggered.connect(self._on_variant_action) - controller.add_reset_callback(self._on_control_reset) + controller.add_on_reset_callback(self._on_control_reset) self.asset_name_input = asset_name_input self.subset_name_input = subset_name_input From 259b7c2433d74b6001e30f288f8ae50a83166ea3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Jul 2021 10:17:12 +0200 Subject: [PATCH 095/736] added ability to register callbacks on create --- openpype/tools/new_publisher/control.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 3d9fdeb517..2a74b218b5 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -26,6 +26,7 @@ class PublisherController: self.dbcon = dbcon self._reset_callback_refs = set() + self._on_create_callback_refs = set() self.creators = {} self.publish_plugins = [] @@ -37,6 +38,9 @@ class PublisherController: ref = weakref.WeakMethod(callback) self._reset_callback_refs.add(ref) + def add_on_create_callback(self, callback): + ref = weakref.WeakMethod(callback) + self._on_create_callback_refs.add(ref) def _trigger_callbacks(self, callbacks, *args, **kwargs): # Trigger reset callbacks @@ -102,5 +106,16 @@ class PublisherController: self.instances = instances def create(self, family, subset_name, instance_data, options): + # QUESTION Force to return instances or call `list_instances` on each + # creation? (`list_instances` may slow down...) creator = self.creators[family] - return creator.create(subset_name, instance_data, options) + result = creator.create(subset_name, instance_data, options) + if result and not isinstance(result, (list, tuple)): + result = [result] + + for instance in result: + self.instances.append(instance) + + self._trigger_callbacks(self._on_create_callback_refs) + + return result From 94bb9f38a37147bba205dcad2c854dc9d1365b22 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Jul 2021 10:18:01 +0200 Subject: [PATCH 096/736] subset view has model --- openpype/tools/new_publisher/window.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 7f9bcb7e63..aeab40ccfa 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -63,6 +63,9 @@ class PublisherWindow(QtWidgets.QWidget): subset_widget = QtWidgets.QWidget(content_widget) subset_view = QtWidgets.QTreeView(subset_widget) + subset_model = QtGui.QStandardItemModel() + subset_view.setModel(subset_model) + subset_attributes = SubsetAttributesWidget(subset_widget) subset_layout = QtWidgets.QHBoxLayout(subset_widget) @@ -110,6 +113,9 @@ class PublisherWindow(QtWidgets.QWidget): self.context_label = context_label + self.subset_view = subset_view + self.subset_model = subset_model + self.footer_widget = footer_widget self.message_input = message_input self.validate_btn = validate_btn From d0e9080b3b065bb0ce9a7c9902c17892f1001715 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Jul 2021 10:57:46 +0200 Subject: [PATCH 097/736] creators have logger --- openpype/pipeline/creator_plugins.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index daa5a1d094..5bc5bd5703 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -1,4 +1,5 @@ import copy +import logging import collections from uuid import uuid4 @@ -87,6 +88,9 @@ class BaseCreator: to `self` if it's not Plugin specific. """ + # Variable to store logger + _log = None + # Creator is enabled (Probably does not have reason of existence?) enabled = True @@ -104,6 +108,12 @@ class BaseCreator: """Family that plugin represents.""" pass + @property + def log(self): + if self._log is None: + self._log = logging.getLogger(self.__class__.__name__) + return self._log + @abstractmethod def create(self, options=None): """Create new instance. From 73bb892ac339e6ad11d73c0ea70449336693c534 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Jul 2021 11:03:48 +0200 Subject: [PATCH 098/736] added missing import --- openpype/tools/new_publisher/window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index aeab40ccfa..d3e9839cdc 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -25,7 +25,7 @@ for path in [ ]: sys.path.append(path) -from Qt import QtWidgets, QtCore +from Qt import QtWidgets, QtCore, QtGui from openpype import style from control import PublisherController From 6c3c541bcbe6c059c989dd1375cf839ee95f2ac6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Jul 2021 11:04:08 +0200 Subject: [PATCH 099/736] refresh instances on create and on reset --- openpype/tools/new_publisher/window.py | 40 ++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index d3e9839cdc..72021911e1 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -103,6 +103,9 @@ class PublisherWindow(QtWidgets.QWidget): creator_window = CreateDialog(controller, self) + controller.add_on_reset_callback(self._on_control_reset) + controller.add_on_create_callback(self._on_control_create) + reset_btn.clicked.connect(self._on_reset_clicked) create_btn.clicked.connect(self._on_create_clicked) @@ -155,6 +158,43 @@ class PublisherWindow(QtWidgets.QWidget): def _on_publish_clicked(self): print("Publishing!!!") + def _refresh_instances(self): + to_remove = set() + existing_mapping = {} + + for idx in range(self.subset_model.rowCount()): + index = self.subset_model.index(idx, 0) + uuid = index.data(QtCore.Qt.UserRole) + to_remove.add(uuid) + existing_mapping[uuid] = idx + + new_items = [] + for instance in self.controller.instances: + uuid = instance.data["uuid"] + if uuid in to_remove: + to_remove.remove(uuid) + continue + + item = QtGui.QStandardItem(instance.data["subset"]) + item.setData(instance.data["uuid"], QtCore.Qt.UserRole) + new_items.append(item) + + idx_to_remove = [] + for uuid in to_remove: + idx_to_remove.append(existing_mapping[uuid]) + + for idx in reversed(sorted(idx_to_remove)): + self.subset_model.removeRows(idx, 1) + + if new_items: + self.subset_model.invisibleRootItem().appendRows(new_items) + + def _on_control_create(self): + self._refresh_instances() + + def _on_control_reset(self): + self._refresh_instances() + def main(): """Main function for testing purposes.""" From 2873d908f2ad51625b3ba88d0a6bb9dbc5ed188d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Jul 2021 11:50:13 +0200 Subject: [PATCH 100/736] store subset widget --- openpype/tools/new_publisher/window.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 72021911e1..f97bf32453 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -66,11 +66,11 @@ class PublisherWindow(QtWidgets.QWidget): subset_model = QtGui.QStandardItemModel() subset_view.setModel(subset_model) - subset_attributes = SubsetAttributesWidget(subset_widget) + subset_attributes_widget = SubsetAttributesWidget(subset_widget) subset_layout = QtWidgets.QHBoxLayout(subset_widget) subset_layout.addWidget(subset_view, 0) - subset_layout.addWidget(subset_attributes, 1) + subset_layout.addWidget(subset_attributes_widget, 1) content_layout = QtWidgets.QVBoxLayout(content_widget) content_layout.setContentsMargins(0, 0, 0, 0) @@ -119,6 +119,7 @@ class PublisherWindow(QtWidgets.QWidget): self.subset_view = subset_view self.subset_model = subset_model + self.subset_attributes_widget = subset_attributes_widget self.footer_widget = footer_widget self.message_input = message_input self.validate_btn = validate_btn From 75a0f76530ae70c08ea44b00e43ee3877ca9e66c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Jul 2021 11:50:56 +0200 Subject: [PATCH 101/736] gloat attributes is separated widget GlobalAttrsWidget --- openpype/tools/new_publisher/widgets.py | 50 +++++++++++++++---------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 71709be2e1..1fe5f4f20e 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -11,6 +11,35 @@ def get_default_thumbnail_image_path(): return os.path.join(dirpath, "image_file.png") +class GlobalAttrsWidget(QtWidgets.QWidget): + def __init__(self, parent): + super(GlobalAttrsWidget, self).__init__(parent) + + variant_input = QtWidgets.QLineEdit(self) + family_value_widget = QtWidgets.QLabel(self) + asset_value_widget = QtWidgets.QLabel(self) + task_value_widget = QtWidgets.QLabel(self) + subset_value_widget = QtWidgets.QLabel(self) + + subset_value_widget.setText("") + family_value_widget.setText("") + asset_value_widget.setText("") + task_value_widget.setText("") + + main_layout = QtWidgets.QFormLayout(self) + main_layout.addRow("Name", variant_input) + main_layout.addRow("Family", family_value_widget) + main_layout.addRow("Asset", asset_value_widget) + main_layout.addRow("Task", task_value_widget) + main_layout.addRow("Subset", subset_value_widget) + + self.variant_input = variant_input + self.family_value_widget = family_value_widget + self.asset_value_widget = asset_value_widget + self.task_value_widget = task_value_widget + self.subset_value_widget = subset_value_widget + + class SubsetAttributesWidget(QtWidgets.QWidget): """Widget where attributes of instance/s are modified. _____________________________ @@ -32,26 +61,7 @@ class SubsetAttributesWidget(QtWidgets.QWidget): top_widget = QtWidgets.QWidget(self) # Global attributes - global_attrs_widget = QtWidgets.QWidget(top_widget) - - variant_input = QtWidgets.QLineEdit(global_attrs_widget) - subset_value_widget = QtWidgets.QLabel(global_attrs_widget) - family_value_widget = QtWidgets.QLabel(global_attrs_widget) - asset_value_widget = QtWidgets.QLabel(global_attrs_widget) - task_value_widget = QtWidgets.QLabel(global_attrs_widget) - - subset_value_widget.setText("") - family_value_widget.setText("") - asset_value_widget.setText("") - task_value_widget.setText("") - - global_attrs_layout = QtWidgets.QFormLayout(global_attrs_widget) - global_attrs_layout.addRow("Name", variant_input) - global_attrs_layout.addRow("Family", family_value_widget) - global_attrs_layout.addRow("Asset", asset_value_widget) - global_attrs_layout.addRow("Task", task_value_widget) - global_attrs_layout.addRow("Subset", subset_value_widget) - + global_attrs_widget = GlobalAttrsWidget(top_widget) thumbnail_widget = ThumbnailWidget(top_widget) top_layout = QtWidgets.QHBoxLayout(top_widget) From 076c79c4bd978c0667e205eae20b183eda0aa4c0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Jul 2021 11:52:14 +0200 Subject: [PATCH 102/736] implemented set_current_instances for global attributes widget --- openpype/tools/new_publisher/widgets.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 1fe5f4f20e..3aae0dce53 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -39,6 +39,25 @@ class GlobalAttrsWidget(QtWidgets.QWidget): self.task_value_widget = task_value_widget self.subset_value_widget = subset_value_widget + def set_current_instances(self, instances): + if len(instances) == 1: + instance = instances[0] + unknown = "N/A" + + variant = instance.data.get("variant") or unknown + family = instance.data.get("family") or unknown + asset_name = instance.data.get("asset") or unknown + task_name = instance.data.get("task") or unknown + subset_name = instance.data.get("subset") or unknown + + self.variant_input.setText(variant) + self.family_value_widget.setText(family) + self.asset_value_widget.setText(asset_name) + self.task_value_widget.setText(task_name) + self.subset_value_widget.setText(subset_name) + return + # TODO what to do when mulsiselection? + class SubsetAttributesWidget(QtWidgets.QWidget): """Widget where attributes of instance/s are modified. From 14ceda2fac8df530de148070da3edd681193f1c2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Jul 2021 11:52:50 +0200 Subject: [PATCH 103/736] subset attribte widget will pass instances to global attributes widget --- openpype/tools/new_publisher/widgets.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 3aae0dce53..3c0a0f8bb8 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -106,6 +106,9 @@ class SubsetAttributesWidget(QtWidgets.QWidget): self.global_attrs_widget = global_attrs_widget self.thumbnail_widget = thumbnail_widget + def set_current_instances(self, instances): + self.global_attrs_widget.set_current_instances(instances) + class ThumbnailWidget(QtWidgets.QWidget): def __init__(self, parent): From 8886117d08a5de9e03958ee54d8d4bc2ba5d1e1e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Jul 2021 11:53:15 +0200 Subject: [PATCH 104/736] catch subset changes and pass selection to attributes widget --- openpype/tools/new_publisher/window.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index f97bf32453..02d4dc7563 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -112,6 +112,10 @@ class PublisherWindow(QtWidgets.QWidget): validate_btn.clicked.connect(self._on_validate_clicked) publish_btn.clicked.connect(self._on_publish_clicked) + subset_view.selectionModel().selectionChanged.connect( + self._on_subset_change + ) + self.main_frame = main_frame self.context_label = context_label @@ -196,6 +200,21 @@ class PublisherWindow(QtWidgets.QWidget): def _on_control_reset(self): self._refresh_instances() + def _on_subset_change(self, *_args): + instances = [] + instances_by_id = {} + for instance in self.controller.instances: + instance_id = instance.data["uuid"] + instances_by_id[instance_id] = instance + + for index in self.subset_view.selectionModel().selectedIndexes(): + instance_id = index.data(QtCore.Qt.UserRole) + instance = instances_by_id.get(instance_id) + if instance: + instances.append(instance) + + self.subset_attributes_widget.set_current_instances(instances) + def main(): """Main function for testing purposes.""" From d112f32f5bac0ecefbfedd2e7da4009adf455b22 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Jul 2021 11:55:25 +0200 Subject: [PATCH 105/736] subset view modification --- openpype/tools/new_publisher/window.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 02d4dc7563..cfc14542a9 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -63,6 +63,9 @@ class PublisherWindow(QtWidgets.QWidget): subset_widget = QtWidgets.QWidget(content_widget) subset_view = QtWidgets.QTreeView(subset_widget) + subset_view.setHeaderHidden(True) + subset_view.setIndentation(0) + subset_model = QtGui.QStandardItemModel() subset_view.setModel(subset_model) From 65e88f92bfeb675d6fea3216336364b457ccdd30 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Jul 2021 14:55:02 +0200 Subject: [PATCH 106/736] implemented __eq__ for attribute defs --- .../pipeline/lib/attribute_definitions.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/openpype/pipeline/lib/attribute_definitions.py b/openpype/pipeline/lib/attribute_definitions.py index 62b5dd3b47..7a310c824e 100644 --- a/openpype/pipeline/lib/attribute_definitions.py +++ b/openpype/pipeline/lib/attribute_definitions.py @@ -46,6 +46,11 @@ class AbtractAttrDef: self.__init__class__ = AbtractAttrDef + def __eq__(self, other): + if not isinstance(other, self.__class__): + return False + return self.key == other.key + @abstractmethod def convert_value(self, value): """Convert value to a valid one. @@ -101,6 +106,16 @@ class NumberDef(AbtractAttrDef): self.default = default self.decimals = 0 if decimals is None else decimals + def __eq__(self, other): + if not super(NumberDef, self).__eq__(other): + return False + + return ( + self.decimals == other.decimals + and self.maximum == other.maximum + and self.maximum == other.maximum + ) + def convert_value(self, value): if isinstance(value, six.string_types): try: @@ -155,6 +170,15 @@ class TextDef(AbtractAttrDef): self.regex = regex self.default = default + def __eq__(self, other): + if not super(TextDef, self).__eq__(other): + return False + + return ( + self.multiline == other.multiline + and self.regex == other.regex + ) + def convert_value(self, value): if isinstance(value, six.string_types): return value @@ -189,6 +213,18 @@ class EnumDef(AbtractAttrDef): self.items = items self.default = default + def __eq__(self, other): + if not super(EnumDef, self).__eq__(other): + return False + + if set(self.items.keys()) != set(other.items.keys()): + return False + + for key, label in self.items.items(): + if other.items[key] != label: + return False + return True + def convert_value(self, value): if value in self.items: return value From 7f69d96eb771b631c4dfe0d5e6932279776aa215 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Jul 2021 16:12:19 +0200 Subject: [PATCH 107/736] controller have method to return attributes for passed instances --- openpype/tools/new_publisher/control.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 2a74b218b5..3a2edd5c9d 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -105,6 +105,24 @@ class PublisherController: self.instances = instances + def get_family_attribute_definitions(self, instances): + attr_defs = [] + if len(instances) == 1: + instance = instances[0] + family = instance.data["family"] + creator = self.creators.get(family) + if not creator: + # TODO handle when creator is not available + return + + attr_defs = creator.get_attribute_defs() + + else: + # TODO mulsiselection + pass + + return attr_defs + def create(self, family, subset_name, instance_data, options): # QUESTION Force to return instances or call `list_instances` on each # creation? (`list_instances` may slow down...) From 4f000d0331a3ef660db42ca7306b8532b92ffe5c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Jul 2021 16:12:47 +0200 Subject: [PATCH 108/736] controller has method to return attribute definitions for passed instances --- openpype/tools/new_publisher/control.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 3a2edd5c9d..8f452919d2 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -8,6 +8,7 @@ from openpype.api import ( get_project_settings ) from openpype.pipeline import ( + OpenPypePyblishPluginMixin, BaseCreator, AvalonInstance ) @@ -30,6 +31,7 @@ class PublisherController: self.creators = {} self.publish_plugins = [] + self.instances = [] self._in_reset = False @@ -123,6 +125,27 @@ class PublisherController: return attr_defs + def get_publish_attribute_definitions(self, instances): + families = set() + for instance in instances: + family = instance.data["family"] + families.add(family) + + plugins_with_defs = [] + for plugin in self.publish_plugins: + if OpenPypePyblishPluginMixin in inspect.getmro(plugin): + plugins_with_defs.append(plugin) + + filtered_plugins = pyblish.logic.plugins_by_families( + plugins_with_defs, families + ) + output = [] + for plugin in filtered_plugins: + attr_defs = plugin.get_attribute_defs() + if attr_defs: + output.append((plugin.__name__, attr_defs)) + return output + def create(self, family, subset_name, instance_data, options): # QUESTION Force to return instances or call `list_instances` on each # creation? (`list_instances` may slow down...) From 0b686ab4bd8e99f7e309a2967708cc1e9db48bc7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Jul 2021 16:13:24 +0200 Subject: [PATCH 109/736] pass controller to subset attribute widget --- openpype/tools/new_publisher/widgets.py | 3 ++- openpype/tools/new_publisher/window.py | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 3c0a0f8bb8..eb4067779e 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -73,7 +73,7 @@ class SubsetAttributesWidget(QtWidgets.QWidget): |______________|______________| """ - def __init__(self, parent): + def __init__(self, controller, parent): super(SubsetAttributesWidget, self).__init__(parent) # TOP PART @@ -103,6 +103,7 @@ class SubsetAttributesWidget(QtWidgets.QWidget): layout.addWidget(top_widget, 0) layout.addWidget(bottom_widget, 1) + self.controller = controller self.global_attrs_widget = global_attrs_widget self.thumbnail_widget = thumbnail_widget diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index cfc14542a9..7188d83d44 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -69,7 +69,9 @@ class PublisherWindow(QtWidgets.QWidget): subset_model = QtGui.QStandardItemModel() subset_view.setModel(subset_model) - subset_attributes_widget = SubsetAttributesWidget(subset_widget) + subset_attributes_widget = SubsetAttributesWidget( + controller, subset_widget + ) subset_layout = QtWidgets.QHBoxLayout(subset_widget) subset_layout.addWidget(subset_view, 0) From d248cf301965f118941038413d6f36882082e1fe Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Jul 2021 16:13:46 +0200 Subject: [PATCH 110/736] created widgets for attribute definitions --- openpype/widgets/attribute_defs/__init__.py | 6 + openpype/widgets/attribute_defs/widgets.py | 185 ++++++++++++++++++++ 2 files changed, 191 insertions(+) create mode 100644 openpype/widgets/attribute_defs/__init__.py create mode 100644 openpype/widgets/attribute_defs/widgets.py diff --git a/openpype/widgets/attribute_defs/__init__.py b/openpype/widgets/attribute_defs/__init__.py new file mode 100644 index 0000000000..147efeb3d6 --- /dev/null +++ b/openpype/widgets/attribute_defs/__init__.py @@ -0,0 +1,6 @@ +from .widgets import create_widget_for_attr_def + + +__all__ = ( + "create_widget_for_attr_def", +) diff --git a/openpype/widgets/attribute_defs/widgets.py b/openpype/widgets/attribute_defs/widgets.py new file mode 100644 index 0000000000..562a76a221 --- /dev/null +++ b/openpype/widgets/attribute_defs/widgets.py @@ -0,0 +1,185 @@ +from openpype.pipeline.lib import ( + AbtractAttrDef, + NumberDef, + TextDef, + EnumDef, + BoolDef +) +from Qt import QtWidgets, QtCore, QtGui + + +def create_widget_for_attr_def(attr_def, parent=None): + if not isinstance(attr_def, AbtractAttrDef): + raise TypeError("Unexpected type \"{}\" expected \"{}\"".format( + str(type(attr_def)), AbtractAttrDef + )) + + if isinstance(attr_def, NumberDef): + return NumberAttrWidget(attr_def, parent) + + if isinstance(attr_def, TextDef): + return TextAttrWidget(attr_def, parent) + + if isinstance(attr_def, EnumDef): + return EnumAttrWidget(attr_def, parent) + + if isinstance(attr_def, BoolDef): + return BoolAttrWidget(attr_def, parent) + + raise ValueError("Unknown attribute definition \"{}\"".format( + str(type(attr_def)) + )) + + +class _BaseAttrDefWidget(QtWidgets.QWidget): + value_changed = QtCore.Signal(object, object) + + def __init__(self, attr_def, parent): + super(_BaseAttrDefWidget, self).__init__(parent) + + self.attr_def = attr_def + + main_layout = QtWidgets.QHBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + + self.main_layout = main_layout + + self._ui_init() + + def _ui_init(self): + raise NotImplementedError( + "Method '_ui_init' is not implemented. {}".format( + self.__class__.__name__ + ) + ) + + def current_value(self): + raise NotImplementedError( + "Method 'current_value' is not implemented. {}".format( + self.__class__.__name__ + ) + ) + + +class NumberAttrWidget(_BaseAttrDefWidget): + def _ui_init(self): + decimals = self.attr_def.decimals + if decimals > 0: + input_widget = QtWidgets.QDoubleSpinBox(self) + input_widget.setDecimals(decimals) + else: + input_widget = QtWidgets.QSpinBox(self) + + input_widget.setMinimum(self.attr_def.minimum) + input_widget.setMaximum(self.attr_def.maximum) + input_widget.setValue(self.attr_def.default) + + self._last_value = input_widget.value() + + input_widget.valueChanged.connect(self._on_value_change) + + self._input_widget = input_widget + + self.main_layout.addWidget(input_widget, 0) + + def _on_value_change(self, new_value): + old_value = self._last_value + self._last_value = new_value + self.value_changed.emit(old_value, new_value) + + def current_value(self): + return self._input_widget.value() + + +class TextAttrWidget(_BaseAttrDefWidget): + def _ui_init(self): + # TODO Solve how to handle regex + # self.attr_def.regex + + self.multiline = self.attr_def.multiline + if self.multiline: + input_widget = QtWidgets.QPlainTextEdit(self) + else: + input_widget = QtWidgets.QLineEdit(self) + + if ( + self.attr_def.placeholder + and hasattr(input_widget, "setPlaceholderText") + ): + input_widget.setPlaceholderText(self.attr_def.placeholder) + + if self.attr_def.default: + if self.multiline: + input_widget.setPlainText(self.attr_def.default) + else: + input_widget.setText(self.attr_def.default) + + self._last_value = self.current_value() + + input_widget.textChanged.connect(self._on_value_change) + + self._input_widget = input_widget + + self.main_layout.addWidget(input_widget, 0) + + def _on_value_change(self): + new_value = self._input_widget.toPlainText() + old_value = self._last_value + self._last_value = new_value + self.value_changed.emit(old_value, new_value) + + def current_value(self): + if self.multiline: + return self._input_widget.toPlainText() + return self._input_widget.text() + + +class BoolAttrWidget(_BaseAttrDefWidget): + def _ui_init(self): + input_widget = QtWidgets.QCheckBox(self) + input_widget.setChecked(self.attr_def.default) + + input_widget.stateChanged.connect(self._on_value_change) + + self._last_value = input_widget.isChecked() + self.input_widget = input_widget + + def _on_value_change(self): + new_value = self.input_widget.isChecked() + old_value = self._last_value + self._last_value = new_value + self.value_changed.emit(old_value, new_value) + + def current_value(self): + return self._input_widget.isChecked() + + +class EnumAttrWidget(_BaseAttrDefWidget): + def _ui_init(self): + input_widget = QtWidgets.QComboBox(self) + combo_delegate = QtWidgets.QStyledItemDelegate(input_widget) + input_widget.setItemDelegate(combo_delegate) + + items = self.attr_def.items + for key, label in items.items(): + input_widget.addItem(label, key) + + idx = input_widget.findData(self.attr_def.default) + if idx >= 0: + input_widget.setCurrentIndex(idx) + + input_widget.currentIndexChanged.connect(self._on_value_change) + + self._combo_delegate = combo_delegate + self._input_widget = input_widget + self._last_value = self.current_value() + + def _on_value_change(self): + new_value = self.current_value() + old_value = self._last_value + self._last_value = new_value + self.value_changed.emit(old_value, new_value) + + def current_value(self): + idx = self._input_widget.currentIndex() + return self._input_widget.itemData(idx) From e5c1fa59267a55864d0c0b906343155d7ea73834 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Jul 2021 16:14:39 +0200 Subject: [PATCH 111/736] created widgets that can show attribute definition widgets --- openpype/tools/new_publisher/widgets.py | 106 +++++++++++++++++++++++- 1 file changed, 102 insertions(+), 4 deletions(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index eb4067779e..4f41ac8035 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -3,6 +3,8 @@ import re import copy from Qt import QtWidgets, QtCore, QtGui +from openpype.widgets.attribute_defs import create_widget_for_attr_def + SEPARATORS = ("---separator---", "---") @@ -56,7 +58,96 @@ class GlobalAttrsWidget(QtWidgets.QWidget): self.task_value_widget.setText(task_name) self.subset_value_widget.setText(subset_name) return - # TODO what to do when mulsiselection? + # TODO mulsiselection + + +class FamilyAttrsWidget(QtWidgets.QWidget): + def __init__(self, controller, parent): + super(FamilyAttrsWidget, self).__init__(parent) + + scroll_area = QtWidgets.QScrollArea(self) + scroll_area.setWidgetResizable(True) + + main_layout = QtWidgets.QHBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + main_layout.addWidget(scroll_area, 1) + + self._main_layout = main_layout + + self.controller = controller + self._scroll_area = scroll_area + # Store content of scroll area to prevend garbage collection + self._content_widget = None + + def set_current_instances(self, instances): + prev_content_widget = self._scroll_area.widget() + if prev_content_widget: + self._scroll_area.takeWidget() + prev_content_widget.hide() + prev_content_widget.deleteLater() + + self._content_widget = None + + attr_defs = self.controller.get_family_attribute_definitions( + instances + ) + + content_widget = QtWidgets.QWidget(self._scroll_area) + content_layout = QtWidgets.QFormLayout(content_widget) + for attr_def in attr_defs: + widget = create_widget_for_attr_def(attr_def, content_widget) + label = attr_def.label or attr_def.key + content_layout.addRow(label, widget) + + self._scroll_area.setWidget(content_widget) + self._content_widget = content_widget + + +class PublishPluginAttrsWidget(QtWidgets.QWidget): + def __init__(self, controller, parent): + super(PublishPluginAttrsWidget, self).__init__(parent) + + scroll_area = QtWidgets.QScrollArea(self) + scroll_area.setWidgetResizable(True) + + main_layout = QtWidgets.QHBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + main_layout.addWidget(scroll_area, 1) + + self._main_layout = main_layout + + self.controller = controller + self._scroll_area = scroll_area + # Store content of scroll area to prevend garbage collection + self._content_widget = None + + def set_current_instances(self, instances): + prev_content_widget = self._scroll_area.widget() + if prev_content_widget: + self._scroll_area.takeWidget() + prev_content_widget.hide() + prev_content_widget.deleteLater() + + self._content_widget = None + + attr_defs = self.controller.get_publish_attribute_definitions( + instances + ) + + content_widget = QtWidgets.QWidget(self._scroll_area) + content_layout = QtWidgets.QFormLayout(content_widget) + for plugin_name, plugin_attr_defs in attr_defs: + for attr_def in plugin_attr_defs: + widget = create_widget_for_attr_def( + attr_def, content_widget + ) + label = attr_def.label or attr_def.key + content_layout.addRow(label, widget) + + self._scroll_area.setWidget(content_widget) + self._content_widget = content_widget class SubsetAttributesWidget(QtWidgets.QWidget): @@ -90,9 +181,12 @@ class SubsetAttributesWidget(QtWidgets.QWidget): # BOTTOM PART bottom_widget = QtWidgets.QWidget(self) - # TODO they should be scrollable - family_attrs_widget = QtWidgets.QWidget(bottom_widget) - publish_attrs_widget = QtWidgets.QWidget(bottom_widget) + family_attrs_widget = FamilyAttrsWidget( + controller, bottom_widget + ) + publish_attrs_widget = PublishPluginAttrsWidget( + controller, bottom_widget + ) bottom_layout = QtWidgets.QHBoxLayout(bottom_widget) bottom_layout.setContentsMargins(0, 0, 0, 0) @@ -105,10 +199,14 @@ class SubsetAttributesWidget(QtWidgets.QWidget): self.controller = controller self.global_attrs_widget = global_attrs_widget + self.family_attrs_widget = family_attrs_widget + self.publish_attrs_widget = publish_attrs_widget self.thumbnail_widget = thumbnail_widget def set_current_instances(self, instances): self.global_attrs_widget.set_current_instances(instances) + self.family_attrs_widget.set_current_instances(instances) + self.publish_attrs_widget.set_current_instances(instances) class ThumbnailWidget(QtWidgets.QWidget): From 0b9dc0d713c04721dbd48a30b917ee722d356642 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Jul 2021 16:26:33 +0200 Subject: [PATCH 112/736] fix adding to layout --- openpype/widgets/attribute_defs/widgets.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/widgets/attribute_defs/widgets.py b/openpype/widgets/attribute_defs/widgets.py index 562a76a221..f6b963f3ab 100644 --- a/openpype/widgets/attribute_defs/widgets.py +++ b/openpype/widgets/attribute_defs/widgets.py @@ -144,6 +144,8 @@ class BoolAttrWidget(_BaseAttrDefWidget): self._last_value = input_widget.isChecked() self.input_widget = input_widget + self.main_layout.addWidget(input_widget, 0) + def _on_value_change(self): new_value = self.input_widget.isChecked() old_value = self._last_value @@ -174,6 +176,8 @@ class EnumAttrWidget(_BaseAttrDefWidget): self._input_widget = input_widget self._last_value = self.current_value() + self.main_layout.addWidget(input_widget, 0) + def _on_value_change(self): new_value = self.current_value() old_value = self._last_value From 15604e8611ac26d066cd77a7c12af31560fc3a57 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Jul 2021 16:26:42 +0200 Subject: [PATCH 113/736] remove boxes from spin boxes --- openpype/widgets/attribute_defs/widgets.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/widgets/attribute_defs/widgets.py b/openpype/widgets/attribute_defs/widgets.py index f6b963f3ab..6069e39704 100644 --- a/openpype/widgets/attribute_defs/widgets.py +++ b/openpype/widgets/attribute_defs/widgets.py @@ -74,6 +74,10 @@ class NumberAttrWidget(_BaseAttrDefWidget): input_widget.setMaximum(self.attr_def.maximum) input_widget.setValue(self.attr_def.default) + input_widget.setButtonSymbols( + QtWidgets.QAbstractSpinBox.ButtonSymbols.NoButtons + ) + self._last_value = input_widget.value() input_widget.valueChanged.connect(self._on_value_change) From 324bbc09d8edee63d8b20763a6ff71a095d37536 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 12 Jul 2021 13:59:16 +0200 Subject: [PATCH 114/736] from_existing is class method --- openpype/pipeline/creator_plugins.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index 5bc5bd5703..57774cff70 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -65,15 +65,15 @@ class AvalonInstance: data[key] = self.data.pop(key) self.data = data - @staticmethod - def from_existing(instance_data): + @classmethod + def from_existing(cls, instance_data): """Convert instance data from workfile to AvalonInstance.""" instance_data = copy.deepcopy(instance_data) family = instance_data.pop("family", None) subset_name = instance_data.pop("subset", None) - return AvalonInstance(family, subset_name, instance_data, new=False) + return cls(family, subset_name, instance_data, new=False) @six.add_metaclass(ABCMeta) From 4385663bb8849bd74e30328518270b842fe31a2d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 12 Jul 2021 18:34:45 +0200 Subject: [PATCH 115/736] added separators --- openpype/tools/new_publisher/widgets.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 4f41ac8035..e3f993ae59 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -188,13 +188,23 @@ class SubsetAttributesWidget(QtWidgets.QWidget): controller, bottom_widget ) + bottom_separator = QtWidgets.QWidget(bottom_widget) + bottom_separator.setObjectName("Separator") + bottom_separator.setMinimumWidth(1) + bottom_layout = QtWidgets.QHBoxLayout(bottom_widget) bottom_layout.setContentsMargins(0, 0, 0, 0) bottom_layout.addWidget(family_attrs_widget, 1) + bottom_layout.addWidget(bottom_separator, 0) bottom_layout.addWidget(publish_attrs_widget, 1) + top_bottom = QtWidgets.QWidget(self) + top_bottom.setObjectName("Separator") + top_bottom.setMinimumHeight(1) + layout = QtWidgets.QVBoxLayout(self) layout.addWidget(top_widget, 0) + layout.addWidget(top_bottom, 0) layout.addWidget(bottom_widget, 1) self.controller = controller From 8e28004450435a32f76938e0d6ae4c3984b76093 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Jul 2021 10:33:42 +0200 Subject: [PATCH 116/736] attribute definitions have id --- openpype/pipeline/lib/attribute_definitions.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/pipeline/lib/attribute_definitions.py b/openpype/pipeline/lib/attribute_definitions.py index 7a310c824e..efcb8f6d2b 100644 --- a/openpype/pipeline/lib/attribute_definitions.py +++ b/openpype/pipeline/lib/attribute_definitions.py @@ -1,5 +1,6 @@ import re import collections +import uuid from abc import ABCMeta, abstractmethod import six @@ -43,9 +44,14 @@ class AbtractAttrDef: self.key = key self.label = label self.tooltip = tooltip + self._id = uuid.uuid4() self.__init__class__ = AbtractAttrDef + @property + def id(self): + return self._id + def __eq__(self, other): if not isinstance(other, self.__class__): return False From 1b870a7497590809ab5eca68d9df8921d995bf2e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Jul 2021 10:35:51 +0200 Subject: [PATCH 117/736] abstract def handle default value --- .../pipeline/lib/attribute_definitions.py | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/openpype/pipeline/lib/attribute_definitions.py b/openpype/pipeline/lib/attribute_definitions.py index efcb8f6d2b..2262aeab44 100644 --- a/openpype/pipeline/lib/attribute_definitions.py +++ b/openpype/pipeline/lib/attribute_definitions.py @@ -40,10 +40,11 @@ class AbtractAttrDef: tooltip(str): Attribute tooltip. """ - def __init__(self, key, label=None, tooltip=None): + def __init__(self, key, default, label=None, tooltip=None): self.key = key self.label = label self.tooltip = tooltip + self.default = default self._id = uuid.uuid4() self.__init__class__ = AbtractAttrDef @@ -84,8 +85,6 @@ class NumberDef(AbtractAttrDef): self, key, minimum=None, maximum=None, decimals=None, default=None, **kwargs ): - super(NumberDef, self).__init__(key, **kwargs) - minimum = 0 if minimum is None else minimum maximum = 999999 if maximum is None else maximum # Swap min/max when are passed in opposited order @@ -107,9 +106,10 @@ class NumberDef(AbtractAttrDef): elif default > maximum: default = maximum + super(NumberDef, self).__init__(key, default=default, **kwargs) + self.minimum = minimum self.maximum = maximum - self.default = default self.decimals = 0 if decimals is None else decimals def __eq__(self, other): @@ -155,14 +155,14 @@ class TextDef(AbtractAttrDef): self, key, multiline=None, regex=None, placeholder=None, default=None, **kwargs ): - super(TextDef, self).__init__(key, **kwargs) + if default is None: + default = "" + + super(TextDef, self).__init__(key, default=default, **kwargs) if multiline is None: multiline = False - if default is None: - default = "" - elif not isinstance(default, six.string_types): raise TypeError(( "'default' argument must be a {}, not '{}'" @@ -174,7 +174,6 @@ class TextDef(AbtractAttrDef): self.multiline = multiline self.placeholder = placeholder self.regex = regex - self.default = default def __eq__(self, other): if not super(TextDef, self).__eq__(other): @@ -202,8 +201,6 @@ class EnumDef(AbtractAttrDef): """ def __init__(self, key, items, default=None, **kwargs): - super(EnumDef, self).__init__(key, **kwargs) - if not items: raise ValueError(( "Empty 'items' value. {} must have" @@ -212,12 +209,13 @@ class EnumDef(AbtractAttrDef): items = collections.OrderedDict(items) if default not in items: - for key in items.keys(): - default = key + for _key in items.keys(): + default = _key break + super(EnumDef, self).__init__(key, default=default, **kwargs) + self.items = items - self.default = default def __eq__(self, other): if not super(EnumDef, self).__eq__(other): @@ -245,11 +243,9 @@ class BoolDef(AbtractAttrDef): """ def __init__(self, key, default=None, **kwargs): - super(BoolDef, self).__init__(key, **kwargs) - if default is None: default = False - self.default = default + super(BoolDef, self).__init__(key, default=default, **kwargs) def convert_value(self, value): if isinstance(value, bool): From 28a834e47a5579cc30b7ca2112250b39767627bf Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Jul 2021 10:36:57 +0200 Subject: [PATCH 118/736] added unknown definition --- openpype/pipeline/lib/__init__.py | 2 ++ openpype/pipeline/lib/attribute_definitions.py | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/openpype/pipeline/lib/__init__.py b/openpype/pipeline/lib/__init__.py index c1f8d14f33..1bb65be79b 100644 --- a/openpype/pipeline/lib/__init__.py +++ b/openpype/pipeline/lib/__init__.py @@ -1,5 +1,6 @@ from .attribute_definitions import ( AbtractAttrDef, + UnknownDef, NumberDef, TextDef, EnumDef, @@ -9,6 +10,7 @@ from .attribute_definitions import ( __all__ = ( "AbtractAttrDef", + "UnknownDef", "NumberDef", "TextDef", "EnumDef", diff --git a/openpype/pipeline/lib/attribute_definitions.py b/openpype/pipeline/lib/attribute_definitions.py index 2262aeab44..5e25356366 100644 --- a/openpype/pipeline/lib/attribute_definitions.py +++ b/openpype/pipeline/lib/attribute_definitions.py @@ -68,6 +68,15 @@ class AbtractAttrDef: pass +class UnknownDef(AbtractAttrDef): + """Definition is not known because definition is not available.""" + def __init__(self, key): + super(UnknownDef, self).__init__(key, None) + + def convert_value(self, value): + return value + + class NumberDef(AbtractAttrDef): """Number definition. From 7c2bb13e684eb4e416dbbefd86c39ba22219ba7c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Jul 2021 10:39:08 +0200 Subject: [PATCH 119/736] AvalonInstance expect host and creator as arguments --- openpype/pipeline/creator_plugins.py | 13 ++++++++++--- openpype/tools/new_publisher/control.py | 4 +++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index 57774cff70..aaee8efb5f 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -26,7 +26,12 @@ class AvalonInstance: data(dict): Data used for filling subset name or override data from already existing instance. """ - def __init__(self, family, subset_name, data=None, new=True): + def __init__( + self, host, creator, family, subset_name, data=None, new=True + ): + self.host = host + self.creator = creator + # Family of instance self.family = family # Subset name @@ -66,14 +71,16 @@ class AvalonInstance: self.data = data @classmethod - def from_existing(cls, instance_data): + def from_existing(cls, host, creator, instance_data): """Convert instance data from workfile to AvalonInstance.""" instance_data = copy.deepcopy(instance_data) family = instance_data.pop("family", None) subset_name = instance_data.pop("subset", None) + return cls( + host, creator, family, subset_name, instance_data, new=False + ) - return cls(family, subset_name, instance_data, new=False) @six.add_metaclass(ABCMeta) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 8f452919d2..8b37b5d757 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -102,7 +102,9 @@ class PublisherController: instance_data = creator.convert_family_attribute_values( instance_data ) - instance = AvalonInstance.from_existing(instance_data) + instance = AvalonInstance.from_existing( + self.host, creator, instance_data + ) instances.append(instance) self.instances = instances From 42870fa1592f9ef8a6e1237907ff8f55b06191e2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Jul 2021 10:48:38 +0200 Subject: [PATCH 120/736] value_changed of def widgets returns only value and attribute id --- openpype/widgets/attribute_defs/widgets.py | 25 ++++++---------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/openpype/widgets/attribute_defs/widgets.py b/openpype/widgets/attribute_defs/widgets.py index 6069e39704..3c4d2a216e 100644 --- a/openpype/widgets/attribute_defs/widgets.py +++ b/openpype/widgets/attribute_defs/widgets.py @@ -1,3 +1,4 @@ +import uuid from openpype.pipeline.lib import ( AbtractAttrDef, NumberDef, @@ -32,7 +33,7 @@ def create_widget_for_attr_def(attr_def, parent=None): class _BaseAttrDefWidget(QtWidgets.QWidget): - value_changed = QtCore.Signal(object, object) + value_changed = QtCore.Signal(object, uuid.UUID) def __init__(self, attr_def, parent): super(_BaseAttrDefWidget, self).__init__(parent) @@ -78,8 +79,6 @@ class NumberAttrWidget(_BaseAttrDefWidget): QtWidgets.QAbstractSpinBox.ButtonSymbols.NoButtons ) - self._last_value = input_widget.value() - input_widget.valueChanged.connect(self._on_value_change) self._input_widget = input_widget @@ -87,9 +86,7 @@ class NumberAttrWidget(_BaseAttrDefWidget): self.main_layout.addWidget(input_widget, 0) def _on_value_change(self, new_value): - old_value = self._last_value - self._last_value = new_value - self.value_changed.emit(old_value, new_value) + self.value_changed.emit(new_value, self.attr_def.id) def current_value(self): return self._input_widget.value() @@ -118,8 +115,6 @@ class TextAttrWidget(_BaseAttrDefWidget): else: input_widget.setText(self.attr_def.default) - self._last_value = self.current_value() - input_widget.textChanged.connect(self._on_value_change) self._input_widget = input_widget @@ -128,9 +123,7 @@ class TextAttrWidget(_BaseAttrDefWidget): def _on_value_change(self): new_value = self._input_widget.toPlainText() - old_value = self._last_value - self._last_value = new_value - self.value_changed.emit(old_value, new_value) + self.value_changed.emit(new_value, self.attr_def.id) def current_value(self): if self.multiline: @@ -145,16 +138,13 @@ class BoolAttrWidget(_BaseAttrDefWidget): input_widget.stateChanged.connect(self._on_value_change) - self._last_value = input_widget.isChecked() self.input_widget = input_widget self.main_layout.addWidget(input_widget, 0) def _on_value_change(self): new_value = self.input_widget.isChecked() - old_value = self._last_value - self._last_value = new_value - self.value_changed.emit(old_value, new_value) + self.value_changed.emit(new_value, self.attr_def.id) def current_value(self): return self._input_widget.isChecked() @@ -178,15 +168,12 @@ class EnumAttrWidget(_BaseAttrDefWidget): self._combo_delegate = combo_delegate self._input_widget = input_widget - self._last_value = self.current_value() self.main_layout.addWidget(input_widget, 0) def _on_value_change(self): new_value = self.current_value() - old_value = self._last_value - self._last_value = new_value - self.value_changed.emit(old_value, new_value) + self.value_changed.emit(new_value, self.attr_def.id) def current_value(self): idx = self._input_widget.currentIndex() From ecf85225bb38df07944377c9b9098c98117c591c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Jul 2021 10:48:51 +0200 Subject: [PATCH 121/736] implemented set_value for each attribute def widget --- openpype/widgets/attribute_defs/widgets.py | 31 ++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/openpype/widgets/attribute_defs/widgets.py b/openpype/widgets/attribute_defs/widgets.py index 3c4d2a216e..47590c21c1 100644 --- a/openpype/widgets/attribute_defs/widgets.py +++ b/openpype/widgets/attribute_defs/widgets.py @@ -61,6 +61,13 @@ class _BaseAttrDefWidget(QtWidgets.QWidget): ) ) + def set_value(self, value): + raise NotImplementedError( + "Method 'current_value' is not implemented. {}".format( + self.__class__.__name__ + ) + ) + class NumberAttrWidget(_BaseAttrDefWidget): def _ui_init(self): @@ -91,6 +98,10 @@ class NumberAttrWidget(_BaseAttrDefWidget): def current_value(self): return self._input_widget.value() + def set_value(self, value): + if self.current_value != value: + self._input_widget.setValue(value) + class TextAttrWidget(_BaseAttrDefWidget): def _ui_init(self): @@ -130,6 +141,14 @@ class TextAttrWidget(_BaseAttrDefWidget): return self._input_widget.toPlainText() return self._input_widget.text() + def set_value(self, value): + if value == self.current_value(): + return + if self.multiline: + self._input_widget.setPlainText(value) + else: + self._input_widget.setText(value) + class BoolAttrWidget(_BaseAttrDefWidget): def _ui_init(self): @@ -149,6 +168,10 @@ class BoolAttrWidget(_BaseAttrDefWidget): def current_value(self): return self._input_widget.isChecked() + def set_value(self, value): + if value != self.current_value(): + self._input_widget.setChecked(value) + class EnumAttrWidget(_BaseAttrDefWidget): def _ui_init(self): @@ -178,3 +201,11 @@ class EnumAttrWidget(_BaseAttrDefWidget): def current_value(self): idx = self._input_widget.currentIndex() return self._input_widget.itemData(idx) + + def set_value(self, value): + idx = self._input_widget.findData(value) + cur_idx = self._input_widget.currentIndex() + if idx == cur_idx: + return + if idx >= 0: + self._input_widget.setCurrentIndex(idx) From 3c6f9f73564b1c1166b0f1097f84a6e6e5822dad Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Jul 2021 10:49:50 +0200 Subject: [PATCH 122/736] create copy of passed data and store original value --- openpype/pipeline/creator_plugins.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index aaee8efb5f..69b0df11bb 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -37,6 +37,9 @@ class AvalonInstance: # Subset name self.subset_name = subset_name + data = copy.deepcopy(data or {}) + self._orig_data = copy.deepcopy(data) + self.data = collections.OrderedDict() self.data["id"] = "pyblish.avalon.instance" self.data["family"] = family From 9a16db0136cff1fa2f9fb73127ec8722ea6c2667 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Jul 2021 10:53:15 +0200 Subject: [PATCH 123/736] data are private attribute of AvalonInstance --- openpype/pipeline/creator_plugins.py | 30 ++++++++++++++++------------ 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index 69b0df11bb..583a5f9d0f 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -40,28 +40,32 @@ class AvalonInstance: data = copy.deepcopy(data or {}) self._orig_data = copy.deepcopy(data) - self.data = collections.OrderedDict() - self.data["id"] = "pyblish.avalon.instance" - self.data["family"] = family - self.data["subset"] = subset_name - self.data["active"] = True + self._data = collections.OrderedDict() + self._data["id"] = "pyblish.avalon.instance" + self._data["family"] = family + self._data["subset"] = subset_name + self._data["active"] = data.get("active", True) + # Schema or version? if new: - self.data["version"] = 1 + self._data["version"] = 1 + else: + self._data["version"] = data.get("version") # Stored family specific attribute values # {key: value} - self.data["family_attributes"] = {} + self._data["family_attributes"] = {} # Stored publish specific attribute values # {: {key: value}} - self.data["publish_attributes"] = {} + self._data["publish_attributes"] = {} if data: - self.data.update(data) + self._data.update(data) - if not self.data.get("uuid"): - self.data["uuid"] = str(uuid4()) + if not self._data.get("uuid"): + self._data["uuid"] = str(uuid4()) - if not new and "version" not in self.data: - self.data["version"] = None + @property + def data(self): + return self._data def change_order(self, keys_order): data = collections.OrderedDict() From a45690641f2a66b523881fabedb203e0aebd5d2c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Jul 2021 11:04:59 +0200 Subject: [PATCH 124/736] created class for family attribute values --- openpype/pipeline/creator_plugins.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index 583a5f9d0f..6d43c9da21 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -13,6 +13,10 @@ import six from openpype.lib import get_subset_name +class FamilyAttributeValues(dict): + pass + + class AvalonInstance: """Instance entity with data that will be stored to workfile. @@ -53,7 +57,7 @@ class AvalonInstance: self._data["version"] = data.get("version") # Stored family specific attribute values # {key: value} - self._data["family_attributes"] = {} + self._data["family_attributes"] = FamilyAttributeValues() # Stored publish specific attribute values # {: {key: value}} self._data["publish_attributes"] = {} From aaafbf8eeb5afc40e78c6d86b7871f19a066a76b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Jul 2021 11:14:05 +0200 Subject: [PATCH 125/736] FamilyAttributeValues expect instance and origin values --- openpype/pipeline/creator_plugins.py | 30 ++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index 6d43c9da21..c258306063 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -14,7 +14,27 @@ from openpype.lib import get_subset_name class FamilyAttributeValues(dict): - pass + def __init__(self, instance, values): + self.instance = instance + creator = self.instance.creator + + if creator is not None: + attr_defs = creator.get_attribute_defs() + else: + attr_defs = [ + UnknownDef(key, label=key, default=value) + for key, value in values.items() + ] + + self._attr_defs = attr_defs + self._attr_defs_by_key = {} + self._data = {} + for attr_def in attr_defs: + key = attr_def.key + self._attr_defs_by_key[key] = attr_def + self._data[key] = values.get(key) + + self._last_data = copy.deepcopy(values) class AvalonInstance: @@ -44,6 +64,9 @@ class AvalonInstance: data = copy.deepcopy(data or {}) self._orig_data = copy.deepcopy(data) + + orig_family_attributes = data.pop("family_attributes") or {} + self._data = collections.OrderedDict() self._data["id"] = "pyblish.avalon.instance" self._data["family"] = family @@ -57,7 +80,10 @@ class AvalonInstance: self._data["version"] = data.get("version") # Stored family specific attribute values # {key: value} - self._data["family_attributes"] = FamilyAttributeValues() + self._data["family_attributes"] = FamilyAttributeValues( + self, orig_family_attributes + ) + # Stored publish specific attribute values # {: {key: value}} self._data["publish_attributes"] = {} From 5f64741b6d8eaac186c2b4d82b1d88adef49dd2e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Jul 2021 11:14:45 +0200 Subject: [PATCH 126/736] gave access to attr definitions --- openpype/pipeline/creator_plugins.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index c258306063..95408bec1f 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -36,6 +36,10 @@ class FamilyAttributeValues(dict): self._last_data = copy.deepcopy(values) + @property + def attr_defs(self): + return self._attr_defs + class AvalonInstance: """Instance entity with data that will be stored to workfile. @@ -97,6 +101,10 @@ class AvalonInstance: def data(self): return self._data + @property + def family_attribute_defs(self): + return self._data["family_attributes"].attr_defs + def change_order(self, keys_order): data = collections.OrderedDict() for key in keys_order: From 21ea868db3e0f6eaae4104c256cc827e421b6baa Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Jul 2021 11:15:44 +0200 Subject: [PATCH 127/736] added method to calculate changes --- openpype/pipeline/creator_plugins.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index 95408bec1f..e11c688c2a 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -40,6 +40,14 @@ class FamilyAttributeValues(dict): def attr_defs(self): return self._attr_defs + def changes(self): + changes = {} + for key, new_value in self._data.items(): + old_value = self._last_data.get(key) + if old_value != new_value: + changes[key] = (old_value, new_value) + return changes + class AvalonInstance: """Instance entity with data that will be stored to workfile. From b1ba5ddede70641c9663a6c559526a7ae0ca6e8f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Jul 2021 11:16:30 +0200 Subject: [PATCH 128/736] added family attribute changes callback --- openpype/pipeline/creator_plugins.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index e11c688c2a..942d3142a2 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -113,6 +113,9 @@ class AvalonInstance: def family_attribute_defs(self): return self._data["family_attributes"].attr_defs + def on_family_attribute_change(self, changes): + print(changes) + def change_order(self, keys_order): data = collections.OrderedDict() for key in keys_order: From 5292e3b3e3cb7e3c76d403907b7aef9cb3c32084 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Jul 2021 11:17:05 +0200 Subject: [PATCH 129/736] added progapate changes to trigger changes callback --- openpype/pipeline/creator_plugins.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index 942d3142a2..0236ab8975 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -48,6 +48,17 @@ class FamilyAttributeValues(dict): changes[key] = (old_value, new_value) return changes + def _propagate_changes(self, changes=None): + if changes is None: + changes = self.changes() + + if not changes: + return + + self.instance.on_family_attribute_change(changes) + for key, values in changes.items(): + self._last_data[key] = values[1] + class AvalonInstance: """Instance entity with data that will be stored to workfile. From 5e2615e18ace462a7c507047497a4497ce2f8cf4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Jul 2021 11:21:43 +0200 Subject: [PATCH 130/736] implemented iterators --- openpype/pipeline/creator_plugins.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index 0236ab8975..7141967890 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -36,6 +36,17 @@ class FamilyAttributeValues(dict): self._last_data = copy.deepcopy(values) + def keys(self): + return self._attr_defs_by_key.keys() + + def values(self): + for key in self._attr_defs_by_key.keys(): + yield self._data.get(key) + + def items(self): + for key in self._attr_defs_by_key.keys(): + yield key, self._data.get(key) + @property def attr_defs(self): return self._attr_defs From 3ba089382d11898c99cb78fc96abbec4e6dd308d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Jul 2021 11:22:02 +0200 Subject: [PATCH 131/736] implemented getters and setters --- openpype/pipeline/creator_plugins.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index 7141967890..cc0562d5c3 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -36,6 +36,32 @@ class FamilyAttributeValues(dict): self._last_data = copy.deepcopy(values) + def __setitem__(self, key, value): + if key not in self._attr_defs_by_key: + raise KeyError("Key \"{}\" was not found.".format(key)) + + old_value = self._data.get(key) + if old_value == value: + return + self._data[key] = value + + self._propagate_changes({ + key: (old_value, value) + }) + + def __getitem__(self, key): + if key not in self._attr_defs_by_key: + return self._data[key] + return self._data.get(key, self._attr_defs_by_key[key].default) + + def __contains__(self, key): + return key in self._attr_defs_by_key + + def get(self, key, default=None): + if key in self._attr_defs_by_key: + return self[key] + return default + def keys(self): return self._attr_defs_by_key.keys() From 90d51629afcb275e699148ec54ce456b7dff80fc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Jul 2021 11:24:17 +0200 Subject: [PATCH 132/736] gave ability to pop value --- openpype/pipeline/creator_plugins.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index cc0562d5c3..22151110db 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -73,6 +73,16 @@ class FamilyAttributeValues(dict): for key in self._attr_defs_by_key.keys(): yield key, self._data.get(key) + def pop(self, key, default=None): + if key not in self._data: + return default + + result = self._data.pop(key) + self._propagate_changes({ + key: (result, None) + }) + return result + @property def attr_defs(self): return self._attr_defs From 997a6d14d4cc7c74ac22890361e6a118a6215a2d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Jul 2021 11:24:35 +0200 Subject: [PATCH 133/736] changes can be propagated in chunks --- openpype/pipeline/creator_plugins.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index 22151110db..ed8976b81d 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -36,6 +36,8 @@ class FamilyAttributeValues(dict): self._last_data = copy.deepcopy(values) + self._chunk_value = 0 + def __setitem__(self, key, value): if key not in self._attr_defs_by_key: raise KeyError("Key \"{}\" was not found.".format(key)) @@ -73,6 +75,11 @@ class FamilyAttributeValues(dict): for key in self._attr_defs_by_key.keys(): yield key, self._data.get(key) + def update(self, value): + with self.chunk_changes(): + for _key, _value in dict(value): + self[_key] = _value + def pop(self, key, default=None): if key not in self._data: return default @@ -96,6 +103,9 @@ class FamilyAttributeValues(dict): return changes def _propagate_changes(self, changes=None): + if self._chunk_value > 0: + return + if changes is None: changes = self.changes() @@ -106,6 +116,17 @@ class FamilyAttributeValues(dict): for key, values in changes.items(): self._last_data[key] = values[1] + @contextlib.contextmanager + def chunk_changes(self): + try: + self._chunk_value += 1 + yield + finally: + self._chunk_value -= 1 + + if self._chunk_value == 0: + self._propagate_changes() + class AvalonInstance: """Instance entity with data that will be stored to workfile. From cd4172e47574645d1823110efa83b1684f976b77 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Jul 2021 11:24:51 +0200 Subject: [PATCH 134/736] FamilyAttributeValues does not inherit from dict --- openpype/pipeline/creator_plugins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index ed8976b81d..4927fbc20a 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -13,7 +13,7 @@ import six from openpype.lib import get_subset_name -class FamilyAttributeValues(dict): +class FamilyAttributeValues: def __init__(self, instance, values): self.instance = instance creator = self.instance.creator From fd64709eefc95eff6f8e53c794de56aaaa55e753 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Jul 2021 11:25:48 +0200 Subject: [PATCH 135/736] added missing import --- openpype/pipeline/creator_plugins.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index 4927fbc20a..4fe4ce4a7c 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -1,5 +1,6 @@ import copy import logging +import contextlib import collections from uuid import uuid4 @@ -10,6 +11,7 @@ from abc import ( ) import six +from .lib import UnknownDef from openpype.lib import get_subset_name From aa8998a72d79d90e67e3dae9f876f497cd46d536 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Jul 2021 11:27:21 +0200 Subject: [PATCH 136/736] pop few keys from passed data --- openpype/pipeline/creator_plugins.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index 4fe4ce4a7c..34cf1e845f 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -154,11 +154,19 @@ class AvalonInstance: # Subset name self.subset_name = subset_name + # Create a copy of passed data to avoid changing them on the fly data = copy.deepcopy(data or {}) + # Store original value of passed data self._orig_data = copy.deepcopy(data) + # Pop family and subset to preved unexpected changes + data.pop("family", None) + data.pop("subset", None) + # Pop dictionary values that will be converted to objects to be able + # catch changes orig_family_attributes = data.pop("family_attributes") or {} + orig_publish_attributes = data.pop("publish_attributes") or {} self._data = collections.OrderedDict() self._data["id"] = "pyblish.avalon.instance" @@ -171,6 +179,7 @@ class AvalonInstance: self._data["version"] = 1 else: self._data["version"] = data.get("version") + # Stored family specific attribute values # {key: value} self._data["family_attributes"] = FamilyAttributeValues( @@ -212,8 +221,9 @@ class AvalonInstance: """Convert instance data from workfile to AvalonInstance.""" instance_data = copy.deepcopy(instance_data) - family = instance_data.pop("family", None) - subset_name = instance_data.pop("subset", None) + family = instance_data.get("family", None) + subset_name = instance_data.get("subset", None) + return cls( host, creator, family, subset_name, instance_data, new=False ) From 16d16509e8d6fe80743c4028ddd7fbe2a1442662 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Jul 2021 11:29:10 +0200 Subject: [PATCH 137/736] changed how family attributes are created --- openpype/tools/new_publisher/control.py | 23 ++++++----------------- openpype/tools/new_publisher/widgets.py | 14 +++++++++++--- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 8b37b5d757..cfe39cd9e7 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -98,10 +98,6 @@ class PublisherController: for instance_data in host_instances: family = instance_data["family"] creator = creators.get(family) - if creator is not None: - instance_data = creator.convert_family_attribute_values( - instance_data - ) instance = AvalonInstance.from_existing( self.host, creator, instance_data ) @@ -110,22 +106,15 @@ class PublisherController: self.instances = instances def get_family_attribute_definitions(self, instances): - attr_defs = [] if len(instances) == 1: instance = instances[0] - family = instance.data["family"] - creator = self.creators.get(family) - if not creator: - # TODO handle when creator is not available - return + output = [] + for attr_def in instance.family_attribute_defs: + output.append((attr_def, [instance])) + return output - attr_defs = creator.get_attribute_defs() - - else: - # TODO mulsiselection - pass - - return attr_defs + # TODO mulsiselection + return ([], []) def get_publish_attribute_definitions(self, instances): families = set() diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index e3f993ae59..71dc8680b1 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -77,7 +77,10 @@ class FamilyAttrsWidget(QtWidgets.QWidget): self.controller = controller self._scroll_area = scroll_area - # Store content of scroll area to prevend garbage collection + + self._attr_def_id_to_instances = {} + self._attr_def_id_to_attr_def = {} + # To store content of scroll area to prevend garbage collection self._content_widget = None def set_current_instances(self, instances): @@ -88,18 +91,23 @@ class FamilyAttrsWidget(QtWidgets.QWidget): prev_content_widget.deleteLater() self._content_widget = None + self._attr_def_id_to_instances = {} + self._attr_def_id_to_attr_def = {} - attr_defs = self.controller.get_family_attribute_definitions( + result = self.controller.get_family_attribute_definitions( instances ) content_widget = QtWidgets.QWidget(self._scroll_area) content_layout = QtWidgets.QFormLayout(content_widget) - for attr_def in attr_defs: + for attr_def, _instances in result: widget = create_widget_for_attr_def(attr_def, content_widget) label = attr_def.label or attr_def.key content_layout.addRow(label, widget) + self._attr_def_id_to_instances[attr_def.id] = _instances + self._attr_def_id_to_attr_def[attr_def.id] = attr_def + self._scroll_area.setWidget(content_widget) self._content_widget = content_widget From b01570fa702d75fda8c7b0e3d9ca468f327c96bc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Jul 2021 11:29:35 +0200 Subject: [PATCH 138/736] trigger instance data change on input value change --- openpype/tools/new_publisher/widgets.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 71dc8680b1..9384236dd4 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -104,6 +104,7 @@ class FamilyAttrsWidget(QtWidgets.QWidget): widget = create_widget_for_attr_def(attr_def, content_widget) label = attr_def.label or attr_def.key content_layout.addRow(label, widget) + widget.value_changed.connect(self._input_value_changed) self._attr_def_id_to_instances[attr_def.id] = _instances self._attr_def_id_to_attr_def[attr_def.id] = attr_def @@ -111,6 +112,17 @@ class FamilyAttrsWidget(QtWidgets.QWidget): self._scroll_area.setWidget(content_widget) self._content_widget = content_widget + def _input_value_changed(self, value, attr_id): + instances = self._attr_def_id_to_instances.get(attr_id) + attr_def = self._attr_def_id_to_attr_def.get(attr_id) + if not instances or not attr_def: + return + + for instance in instances: + family_attributes = instance.data["family_attributes"] + if attr_def.key in family_attributes: + family_attributes[attr_def.key] = value + class PublishPluginAttrsWidget(QtWidgets.QWidget): def __init__(self, controller, parent): From d4e35eb3558601c0a64b320c98fbe23943ce4640 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Jul 2021 11:55:38 +0200 Subject: [PATCH 139/736] UnknownDef has more abilities with using kwargs --- openpype/pipeline/lib/attribute_definitions.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/lib/attribute_definitions.py b/openpype/pipeline/lib/attribute_definitions.py index 5e25356366..2b34e15bc4 100644 --- a/openpype/pipeline/lib/attribute_definitions.py +++ b/openpype/pipeline/lib/attribute_definitions.py @@ -70,8 +70,9 @@ class AbtractAttrDef: class UnknownDef(AbtractAttrDef): """Definition is not known because definition is not available.""" - def __init__(self, key): - super(UnknownDef, self).__init__(key, None) + def __init__(self, key, default=None, **kwargs): + kwargs["default"] = default + super(UnknownDef, self).__init__(key, **kwargs) def convert_value(self, value): return value From d5889a0cecae1dfa6190fbe71ab946ee109cd24c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Jul 2021 11:56:07 +0200 Subject: [PATCH 140/736] implemented UnknownAttrWidget --- openpype/widgets/attribute_defs/widgets.py | 28 ++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/openpype/widgets/attribute_defs/widgets.py b/openpype/widgets/attribute_defs/widgets.py index 47590c21c1..cadfa29bb8 100644 --- a/openpype/widgets/attribute_defs/widgets.py +++ b/openpype/widgets/attribute_defs/widgets.py @@ -1,6 +1,7 @@ import uuid from openpype.pipeline.lib import ( AbtractAttrDef, + UnknownDef, NumberDef, TextDef, EnumDef, @@ -27,6 +28,9 @@ def create_widget_for_attr_def(attr_def, parent=None): if isinstance(attr_def, BoolDef): return BoolAttrWidget(attr_def, parent) + if isinstance(attr_def, UnknownDef): + return UnknownAttrWidget(attr_def, parent) + raise ValueError("Unknown attribute definition \"{}\"".format( str(type(attr_def)) )) @@ -157,12 +161,12 @@ class BoolAttrWidget(_BaseAttrDefWidget): input_widget.stateChanged.connect(self._on_value_change) - self.input_widget = input_widget + self._input_widget = input_widget self.main_layout.addWidget(input_widget, 0) def _on_value_change(self): - new_value = self.input_widget.isChecked() + new_value = self._input_widget.isChecked() self.value_changed.emit(new_value, self.attr_def.id) def current_value(self): @@ -209,3 +213,23 @@ class EnumAttrWidget(_BaseAttrDefWidget): return if idx >= 0: self._input_widget.setCurrentIndex(idx) + + +class UnknownAttrWidget(_BaseAttrDefWidget): + def _ui_init(self): + input_widget = QtWidgets.QLabel(self) + self._value = self.attr_def.default + input_widget.setText(str(self._value)) + + self._input_widget = input_widget + + self.main_layout.addWidget(input_widget, 0) + + def current_value(self): + return self._input_widget.text() + + def set_value(self, value): + str_value = str(value) + if str_value != self._value: + self._value = str_value + self._input_widget.setText(str_value) From 333e7c37ae1b4cf0aa5d78edf9ab3da90c58009f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Jul 2021 11:59:21 +0200 Subject: [PATCH 141/736] loads values from instances --- openpype/tools/new_publisher/control.py | 5 +++-- openpype/tools/new_publisher/widgets.py | 12 ++++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index cfe39cd9e7..aa5db39b68 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -110,11 +110,12 @@ class PublisherController: instance = instances[0] output = [] for attr_def in instance.family_attribute_defs: - output.append((attr_def, [instance])) + value = instance.data["family_attributes"][attr_def.key] + output.append((attr_def, [instance], [value])) return output # TODO mulsiselection - return ([], []) + return ([], [], []) def get_publish_attribute_definitions(self, instances): families = set() diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 9384236dd4..cace227ee8 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -100,13 +100,21 @@ class FamilyAttrsWidget(QtWidgets.QWidget): content_widget = QtWidgets.QWidget(self._scroll_area) content_layout = QtWidgets.QFormLayout(content_widget) - for attr_def, _instances in result: + for attr_def, attr_instances, values in result: widget = create_widget_for_attr_def(attr_def, content_widget) + if len(values) == 1: + value = values[0] + if value is not None: + widget.set_value(values[0]) + else: + # TODO multiselection + pass + label = attr_def.label or attr_def.key content_layout.addRow(label, widget) widget.value_changed.connect(self._input_value_changed) - self._attr_def_id_to_instances[attr_def.id] = _instances + self._attr_def_id_to_instances[attr_def.id] = attr_instances self._attr_def_id_to_attr_def[attr_def.id] = attr_def self._scroll_area.setWidget(content_widget) From 9cf725d4d9c5fad2cdcb12e3215316ada022eef9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Jul 2021 12:51:59 +0200 Subject: [PATCH 142/736] added static method calculate changes --- openpype/pipeline/creator_plugins.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index 34cf1e845f..ce1a0a3946 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -96,14 +96,18 @@ class FamilyAttributeValues: def attr_defs(self): return self._attr_defs - def changes(self): + @staticmethod + def calculate_changes(new_data, old_data): changes = {} - for key, new_value in self._data.items(): - old_value = self._last_data.get(key) + for key, new_value in new_data.items(): + old_value = old_data.get(key) if old_value != new_value: changes[key] = (old_value, new_value) return changes + def changes(self): + return self.calculate_changes(self._data, self._last_data) + def _propagate_changes(self, changes=None): if self._chunk_value > 0: return From 088d7e203db643fc0354610b6a558fdc946c8913 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Jul 2021 12:54:10 +0200 Subject: [PATCH 143/736] keep keys that are not known from creator definitions --- openpype/pipeline/creator_plugins.py | 32 +++++++++++++++++++--------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index ce1a0a3946..9d671df5f8 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -20,21 +20,33 @@ class FamilyAttributeValues: self.instance = instance creator = self.instance.creator - if creator is not None: - attr_defs = creator.get_attribute_defs() + if creator is None: + attr_defs = [] else: - attr_defs = [ - UnknownDef(key, label=key, default=value) - for key, value in values.items() - ] + new_values = creator.convert_family_attribute_values(values) + attr_defs = creator.get_attribute_defs() + if values != new_values: + self._propagate_changes( + self.calculate_changes(new_values, values) + ) + values = new_values + + attr_defs_by_key = { + attr_def.key: attr_def + for attr_def in attr_defs + } + for key, value in values.items(): + if key not in attr_defs_by_key: + new_def = UnknownDef(key, label=key, default=value) + attr_defs.append(new_def) + attr_defs_by_key[key] = new_def self._attr_defs = attr_defs - self._attr_defs_by_key = {} + self._attr_defs_by_key = attr_defs_by_key + self._data = {} for attr_def in attr_defs: - key = attr_def.key - self._attr_defs_by_key[key] = attr_def - self._data[key] = values.get(key) + self._data[attr_def.key] = values.get(attr_def.key) self._last_data = copy.deepcopy(values) From 2156c5d919413f9226d3dfa1f318b6f835a37f6b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Jul 2021 12:54:46 +0200 Subject: [PATCH 144/736] added data_to_store methods --- openpype/pipeline/creator_plugins.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index 9d671df5f8..c7f1574081 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -120,6 +120,12 @@ class FamilyAttributeValues: def changes(self): return self.calculate_changes(self._data, self._last_data) + def data_to_store(self): + output = {} + for key in self._data: + output[key] = self[key] + return output + def _propagate_changes(self, changes=None): if self._chunk_value > 0: return @@ -219,6 +225,20 @@ class AvalonInstance: def family_attribute_defs(self): return self._data["family_attributes"].attr_defs + def data_to_store(self): + output = collections.OrderedDict() + for key, value in self._data.items(): + if key in ("family_attributes", "publish_attributes"): + continue + output[key] = value + + family_attributes = self._data["family_attributes"] + output["family_attributes"] = family_attributes.data_to_store() + + output["publish_attributes"] = self._data["publish_attributes"] + + return output + def on_family_attribute_change(self, changes): print(changes) From 931f637beea86635c0590a7bd141f59dbe08ac28 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Jul 2021 12:55:17 +0200 Subject: [PATCH 145/736] trigger update instance on family attribute change --- openpype/pipeline/creator_plugins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index c7f1574081..c9f4428408 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -240,7 +240,7 @@ class AvalonInstance: return output def on_family_attribute_change(self, changes): - print(changes) + self.host.update_instance(self, changes) def change_order(self, keys_order): data = collections.OrderedDict() From 8d58b41480d9b94a2e8082cc4708914339645797 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Jul 2021 12:55:47 +0200 Subject: [PATCH 146/736] convert values to match attribute definitions --- openpype/pipeline/creator_plugins.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index c9f4428408..402e5a2f14 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -403,12 +403,23 @@ class BaseCreator: If passed values match current creator version just return the value back. Update of changes in workfile must not happen in this method. + Default implementation only convert passed values to right types. But + implementation can be changed to do more stuff (update instance + to newer version etc.). + Args: attribute_values(dict): Values from instance metadata. Returns: dict: Converted values. """ + attr_defs = self.get_attribute_defs() + for attr_def in attr_defs: + key = attr_def.key + if key in attribute_values: + attribute_values[key] = attr_def.convert_value( + attribute_values[key] + ) return attribute_values From 4e6e6e2cbc7670e56c50451c7b8b22f3245b179e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Jul 2021 12:56:10 +0200 Subject: [PATCH 147/736] added few import to init --- openpype/pipeline/__init__.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/__init__.py b/openpype/pipeline/__init__.py index 7316753785..88c6d696f1 100644 --- a/openpype/pipeline/__init__.py +++ b/openpype/pipeline/__init__.py @@ -1,15 +1,22 @@ +from .lib import attribute_definitions + from .creator_plugins import ( BaseCreator, Creator, - AutoCreator + AutoCreator, + AvalonInstance ) from .publish_plugins import OpenPypePyblishPluginMixin + __all__ = ( + "attribute_definitions", + "BaseCreator", "Creator", "AutoCreator", + "AvalonInstance", "OpenPypePyblishPluginMixin" ) From 2fc3c7d4f1328ae932306486a1426314b0e60dd1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Jul 2021 18:54:54 +0200 Subject: [PATCH 148/736] changes are not propagated immidiatelly --- openpype/pipeline/creator_plugins.py | 66 ++++++---------------------- 1 file changed, 13 insertions(+), 53 deletions(-) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index 402e5a2f14..e7a71f0d96 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -46,11 +46,9 @@ class FamilyAttributeValues: self._data = {} for attr_def in attr_defs: - self._data[attr_def.key] = values.get(attr_def.key) - - self._last_data = copy.deepcopy(values) - - self._chunk_value = 0 + value = values.get(attr_def.key) + if value is not None: + self._data[attr_def.key] = value def __setitem__(self, key, value): if key not in self._attr_defs_by_key: @@ -61,10 +59,6 @@ class FamilyAttributeValues: return self._data[key] = value - self._propagate_changes({ - key: (old_value, value) - }) - def __getitem__(self, key): if key not in self._attr_defs_by_key: return self._data[key] @@ -90,24 +84,22 @@ class FamilyAttributeValues: yield key, self._data.get(key) def update(self, value): - with self.chunk_changes(): - for _key, _value in dict(value): - self[_key] = _value + for _key, _value in dict(value): + self[_key] = _value def pop(self, key, default=None): - if key not in self._data: - return default - - result = self._data.pop(key) - self._propagate_changes({ - key: (result, None) - }) - return result + return self._data.pop(key, default) @property def attr_defs(self): return self._attr_defs + def data_to_store(self): + output = {} + for key in self._data: + output[key] = self[key] + return output + @staticmethod def calculate_changes(new_data, old_data): changes = {} @@ -118,38 +110,9 @@ class FamilyAttributeValues: return changes def changes(self): - return self.calculate_changes(self._data, self._last_data) + return self.calculate_changes(self._data, self._origin_data) - def data_to_store(self): - output = {} - for key in self._data: - output[key] = self[key] - return output - def _propagate_changes(self, changes=None): - if self._chunk_value > 0: - return - - if changes is None: - changes = self.changes() - - if not changes: - return - - self.instance.on_family_attribute_change(changes) - for key, values in changes.items(): - self._last_data[key] = values[1] - - @contextlib.contextmanager - def chunk_changes(self): - try: - self._chunk_value += 1 - yield - finally: - self._chunk_value -= 1 - - if self._chunk_value == 0: - self._propagate_changes() class AvalonInstance: @@ -239,9 +202,6 @@ class AvalonInstance: return output - def on_family_attribute_change(self, changes): - self.host.update_instance(self, changes) - def change_order(self, keys_order): data = collections.OrderedDict() for key in keys_order: From cd825942e136d59f305507989dca703634ad674c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Jul 2021 18:56:21 +0200 Subject: [PATCH 149/736] created AttributeValues that cares about attribute values --- openpype/pipeline/creator_plugins.py | 37 +++++++++++++++++----------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index e7a71f0d96..928a460e1e 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -15,21 +15,11 @@ from .lib import UnknownDef from openpype.lib import get_subset_name -class FamilyAttributeValues: - def __init__(self, instance, values): - self.instance = instance - creator = self.instance.creator - - if creator is None: - attr_defs = [] - else: - new_values = creator.convert_family_attribute_values(values) - attr_defs = creator.get_attribute_defs() - if values != new_values: - self._propagate_changes( - self.calculate_changes(new_values, values) - ) - values = new_values +class AttributeValues: + def __init__(self, attr_defs, values, origin_data=None): + if origin_data is None: + origin_data = copy.deepcopy(values) + self._origin_data = origin_data attr_defs_by_key = { attr_def.key: attr_def @@ -113,6 +103,23 @@ class FamilyAttributeValues: return self.calculate_changes(self._data, self._origin_data) +class FamilyAttributeValues(AttributeValues): + def __init__(self, instance, values, origin_values=None): + self.instance = instance + creator = self.instance.creator + + origin_values = copy.deepcopy(values) + if creator is None: + attr_defs = [] + else: + values = creator.convert_family_attribute_values(values) + attr_defs = creator.get_attribute_defs() + + super(FamilyAttributeValues, self).__init__( + attr_defs, values, origin_values + ) + + class AvalonInstance: From b0e9e35567cab4896e3577e5068f1dda98de19bf Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Jul 2021 18:56:43 +0200 Subject: [PATCH 150/736] implemented basic changes method for AvalonInstance --- openpype/pipeline/creator_plugins.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index 928a460e1e..83f669b7a6 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -191,6 +191,28 @@ class AvalonInstance: def data(self): return self._data + def changes(self): + changes = {} + new_keys = set() + for key, new_value in self._data.items(): + new_keys.add(key) + if key in ("family_attributes", "publish_attributes"): + continue + + old_value = self._orig_data.get(key) + if old_value != new_value: + changes[key] = (old_value, new_value) + + family_attributes = self.data["family_attributes"] + family_attr_changes = family_attributes.changes() + if family_attr_changes: + changes["family_attributes"] = family_attr_changes + + for key, old_value in self._orig_data.items(): + if key not in new_keys: + changes[key] = (old_value, None) + return changes + @property def family_attribute_defs(self): return self._data["family_attributes"].attr_defs From 865846330e8b089b3257e79a0e35c1918223fae6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Jul 2021 18:57:22 +0200 Subject: [PATCH 151/736] added save button to save changes --- openpype/tools/new_publisher/window.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 7188d83d44..6927f7bb8e 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -85,6 +85,7 @@ class PublisherWindow(QtWidgets.QWidget): footer_widget = QtWidgets.QWidget(self) create_btn = QtWidgets.QPushButton("Create", footer_widget) + save_btn = QtWidgets.QPushButton("Save", footer_widget) message_input = QtWidgets.QLineEdit(footer_widget) validate_btn = QtWidgets.QPushButton("Validate", footer_widget) publish_btn = QtWidgets.QPushButton("Publish", footer_widget) @@ -92,6 +93,7 @@ class PublisherWindow(QtWidgets.QWidget): footer_layout = QtWidgets.QHBoxLayout(footer_widget) footer_layout.setContentsMargins(0, 0, 0, 0) footer_layout.addWidget(create_btn, 0) + footer_layout.addWidget(save_btn, 0) footer_layout.addWidget(message_input, 1) footer_layout.addWidget(validate_btn, 0) footer_layout.addWidget(publish_btn, 0) @@ -114,6 +116,7 @@ class PublisherWindow(QtWidgets.QWidget): reset_btn.clicked.connect(self._on_reset_clicked) create_btn.clicked.connect(self._on_create_clicked) + save_btn.clicked.connect(self._on_save_clicked) validate_btn.clicked.connect(self._on_validate_clicked) publish_btn.clicked.connect(self._on_publish_clicked) @@ -162,6 +165,9 @@ class PublisherWindow(QtWidgets.QWidget): def _on_create_clicked(self): self.creator_window.show() + def _on_save_clicked(self): + self.controller.save_instance_changes() + def _on_validate_clicked(self): print("Validation!!!") From 74850047e2da3b58a2310f9cbb18d906182cf1f7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Jul 2021 18:57:42 +0200 Subject: [PATCH 152/736] controller can save changes of instances --- openpype/tools/new_publisher/control.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index aa5db39b68..9c67486547 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -105,6 +105,16 @@ class PublisherController: self.instances = instances + def save_instance_changes(self): + update_list = [] + for instance in self.instances: + instance_changes = instance.changes() + if instance_changes: + update_list.append((instance, instance_changes)) + + if update_list: + self.host.update_instances(update_list) + def get_family_attribute_definitions(self, instances): if len(instances) == 1: instance = instances[0] From d61a1f9d977b86da6b3af42660802dcdd081eed0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Jul 2021 19:00:11 +0200 Subject: [PATCH 153/736] removed unused argument --- openpype/pipeline/creator_plugins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index 83f669b7a6..fd09b9a6e3 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -104,7 +104,7 @@ class AttributeValues: class FamilyAttributeValues(AttributeValues): - def __init__(self, instance, values, origin_values=None): + def __init__(self, instance, values): self.instance = instance creator = self.instance.creator From d20b42797e80a9f3431d80e1eaa3908552df1f91 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 14 Jul 2021 11:16:28 +0200 Subject: [PATCH 154/736] FamilyAttributeValues does not touch instance's creator --- openpype/pipeline/creator_plugins.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index fd09b9a6e3..8f101a941a 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -104,20 +104,11 @@ class AttributeValues: class FamilyAttributeValues(AttributeValues): - def __init__(self, instance, values): + def __init__(self, instance, *args, **kwargs): self.instance = instance - creator = self.instance.creator + super(FamilyAttributeValues, self).__init__(*args, **kwargs) - origin_values = copy.deepcopy(values) - if creator is None: - attr_defs = [] - else: - values = creator.convert_family_attribute_values(values) - attr_defs = creator.get_attribute_defs() - super(FamilyAttributeValues, self).__init__( - attr_defs, values, origin_values - ) @@ -157,8 +148,8 @@ class AvalonInstance: # Pop dictionary values that will be converted to objects to be able # catch changes - orig_family_attributes = data.pop("family_attributes") or {} - orig_publish_attributes = data.pop("publish_attributes") or {} + orig_family_attributes = data.pop("family_attributes", None) or {} + orig_publish_attributes = data.pop("publish_attributes", None) or {} self._data = collections.OrderedDict() self._data["id"] = "pyblish.avalon.instance" @@ -174,8 +165,16 @@ class AvalonInstance: # Stored family specific attribute values # {key: value} + new_family_values = copy.deepcopy(orig_family_attributes) + family_attr_defs = [] + if creator is not None: + new_family_values = creator.convert_family_attribute_values( + new_family_values + ) + family_attr_defs = creator.get_attribute_defs() + self._data["family_attributes"] = FamilyAttributeValues( - self, orig_family_attributes + self, family_attr_defs, new_family_values, orig_family_attributes ) # Stored publish specific attribute values From 7afca818a6c6baa8362415f4172f6530b51e847b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 14 Jul 2021 11:17:53 +0200 Subject: [PATCH 155/736] publish plugins can be passed to avaloninstance --- openpype/pipeline/creator_plugins.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index 8f101a941a..a81911075e 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -127,7 +127,8 @@ class AvalonInstance: already existing instance. """ def __init__( - self, host, creator, family, subset_name, data=None, new=True + self, host, creator, family, subset_name, data=None, + attr_plugins=None, new=True ): self.host = host self.creator = creator @@ -241,7 +242,7 @@ class AvalonInstance: self.data = data @classmethod - def from_existing(cls, host, creator, instance_data): + def from_existing(cls, host, creator, instance_data, attr_plugins=None): """Convert instance data from workfile to AvalonInstance.""" instance_data = copy.deepcopy(instance_data) @@ -249,7 +250,8 @@ class AvalonInstance: subset_name = instance_data.get("subset", None) return cls( - host, creator, family, subset_name, instance_data, new=False + host, creator, family, subset_name, instance_data, + attr_plugins, new=False ) From 2559ea14c07a9d0e5ead06958c88a0e458e9d5ed Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 14 Jul 2021 11:19:41 +0200 Subject: [PATCH 156/736] added PublishAttributes to handle publish attributes --- openpype/pipeline/creator_plugins.py | 64 ++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index a81911075e..bd9d5643e3 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -109,8 +109,72 @@ class FamilyAttributeValues(AttributeValues): super(FamilyAttributeValues, self).__init__(*args, **kwargs) +class PublishAttributeValues(AttributeValues): + def __init__(self, publish_attributes, *args, **kwargs): + self.publish_attributes = publish_attributes + super(PublishAttributeValues, self).__init__(*args, **kwargs) + + @property + def instance(self): + self.publish_attributes.instance +class PublishAttributes: + def __init__(self, instance, origin_data, attr_plugins=None): + self.instance = instance + self._origin_data = copy.deepcopy(origin_data) + + attr_plugins = attr_plugins or [] + self.attr_plugins = attr_plugins + + self._data = {} + self._plugin_names_order = [] + data = copy.deepcopy(origin_data) + for plugin in attr_plugins: + data = plugin.convert_attribute_values(data) + attr_defs = plugin.get_attribute_defs() + if not attr_defs: + continue + + key = plugin.__name__ + self._plugin_names_order.append(key) + + value = data.get(key) or {} + orig_value = copy.deepcopy(origin_data.get(key) or {}) + self._data[key] = PublishAttributeValues( + self, attr_defs, value, orig_value + ) + + for key, value in origin_data.items(): + if key not in self._data: + orig_value = copy.deepcopy(value) + self._data[key] = PublishAttributeValues( + self, attr_defs, value, orig_value + ) + + def __getitem__(self, key): + self._data[key] + + def __contains__(self, key): + return key in self._data + + def pop(self, key, default=None): + # TODO implement + if key not in self._data: + return default + + # if key not in self._plugin_keys: + + def plugin_names_order(self): + for name in self._plugin_names_order: + yield name + + def set_publish_plugins(self, attr_plugins): + self.attr_plugins = attr_plugins or [] + for plugin in attr_plugins: + attr_defs = plugin.get_attribute_defs() + if not attr_defs: + continue class AvalonInstance: From 3fd37de800e0e04bb588569a62cc1d26ec3eddac Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 14 Jul 2021 11:21:24 +0200 Subject: [PATCH 157/736] pass attribute definitions to AvalonInstance --- openpype/tools/new_publisher/control.py | 32 ++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 9c67486547..faa162b299 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -30,7 +30,10 @@ class PublisherController: self._on_create_callback_refs = set() self.creators = {} + self.publish_plugins = [] + self.plugins_with_defs = [] + self._attr_plugins_by_family = {} self.instances = [] @@ -69,15 +72,36 @@ class PublisherController: self._in_reset = False + def _get_publish_plugins_with_attr_for_family(self, family): + if family not in self._attr_plugins_by_family: + filtered_plugins = pyblish.logic.plugins_by_families( + self.plugins_with_defs, [family] + ) + self._attr_plugins_by_family[family] = filtered_plugins + + return self._attr_plugins_by_family[family] + def _reset(self): """Reset to initial state.""" + # Reset publish plugins + self._attr_plugins_by_family = {} + publish_plugins = pyblish.api.discover() self.publish_plugins = publish_plugins + # Collect plugins that can have attribute definitions + plugins_with_defs = [] + for plugin in publish_plugins: + if OpenPypePyblishPluginMixin in inspect.getmro(plugin): + plugins_with_defs.append(plugin) + self.plugins_with_defs = plugins_with_defs + + # Prepare settings project_name = self.dbcon.Session["AVALON_PROJECT"] system_settings = get_system_settings() project_settings = get_project_settings(project_name) + # Discover and prepare creators creators = {} for creator in avalon.api.discover(BaseCreator): if inspect.isabstract(creator): @@ -93,13 +117,19 @@ class PublisherController: self.creators = creators + # Collect instances host_instances = self.host.list_instances() instances = [] for instance_data in host_instances: family = instance_data["family"] + # Prepare publish plugins with attribute definitions + creator = creators.get(family) + attr_plugins = self._get_publish_plugins_with_attr_for_family( + family + ) instance = AvalonInstance.from_existing( - self.host, creator, instance_data + self.host, creator, instance_data, attr_plugins ) instances.append(instance) From dee5b66292fd40f27dfdc55696a53c376c823e26 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 14 Jul 2021 11:21:56 +0200 Subject: [PATCH 158/736] renamed AvalonInstance to CreatedInstance --- openpype/pipeline/__init__.py | 4 ++-- openpype/pipeline/creator_plugins.py | 6 +++--- openpype/tools/new_publisher/control.py | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/openpype/pipeline/__init__.py b/openpype/pipeline/__init__.py index 88c6d696f1..795681a8b4 100644 --- a/openpype/pipeline/__init__.py +++ b/openpype/pipeline/__init__.py @@ -4,7 +4,7 @@ from .creator_plugins import ( BaseCreator, Creator, AutoCreator, - AvalonInstance + CreatedInstance ) from .publish_plugins import OpenPypePyblishPluginMixin @@ -16,7 +16,7 @@ __all__ = ( "BaseCreator", "Creator", "AutoCreator", - "AvalonInstance", + "CreatedInstance", "OpenPypePyblishPluginMixin" ) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index bd9d5643e3..568c44e1d4 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -177,7 +177,7 @@ class PublishAttributes: continue -class AvalonInstance: +class CreatedInstance: """Instance entity with data that will be stored to workfile. I think `data` must be required argument containing all minimum information @@ -307,7 +307,7 @@ class AvalonInstance: @classmethod def from_existing(cls, host, creator, instance_data, attr_plugins=None): - """Convert instance data from workfile to AvalonInstance.""" + """Convert instance data from workfile to CreatedInstance.""" instance_data = copy.deepcopy(instance_data) family = instance_data.get("family", None) @@ -496,7 +496,7 @@ class Creator(BaseCreator): instance_data(dict): """ - # instance = AvalonInstance( + # instance = CreatedInstance( # self.family, subset_name, instance_data # ) pass diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index faa162b299..2ce47f61d7 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -10,7 +10,7 @@ from openpype.api import ( from openpype.pipeline import ( OpenPypePyblishPluginMixin, BaseCreator, - AvalonInstance + CreatedInstance ) @@ -128,7 +128,7 @@ class PublisherController: attr_plugins = self._get_publish_plugins_with_attr_for_family( family ) - instance = AvalonInstance.from_existing( + instance = CreatedInstance.from_existing( self.host, creator, instance_data, attr_plugins ) instances.append(instance) From 9375daac073f53dba44e4dc32e1dfd8a31d80dd0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 14 Jul 2021 11:23:44 +0200 Subject: [PATCH 159/736] use PublishAttributes in CreatedInstance --- openpype/pipeline/creator_plugins.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index 568c44e1d4..c1001a50d4 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -244,7 +244,9 @@ class CreatedInstance: # Stored publish specific attribute values # {: {key: value}} - self._data["publish_attributes"] = {} + self._data["publish_attributes"] = PublishAttributes( + self, orig_publish_attributes, attr_plugins + ) if data: self._data.update(data) @@ -281,6 +283,10 @@ class CreatedInstance: def family_attribute_defs(self): return self._data["family_attributes"].attr_defs + @property + def publish_attributes(self): + return self._data["publish_attributes"] + def data_to_store(self): output = collections.OrderedDict() for key, value in self._data.items(): @@ -318,6 +324,9 @@ class CreatedInstance: attr_plugins, new=False ) + def set_publish_plugins(self, attr_plugins): + self._data["publish_attributes"].set_publish_plugins(attr_plugins) + @six.add_metaclass(ABCMeta) From 4c269ba7ddd3224e7b00046fed9c3ba364ce5a70 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 14 Jul 2021 11:25:02 +0200 Subject: [PATCH 160/736] added ability to store publish attributes --- openpype/pipeline/creator_plugins.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index c1001a50d4..f82a30e37c 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -169,6 +169,12 @@ class PublishAttributes: for name in self._plugin_names_order: yield name + def data_to_store(self): + output = {} + for key, attr_value in self._data.items(): + output[key] = attr_value.data_to_store() + return output + def set_publish_plugins(self, attr_plugins): self.attr_plugins = attr_plugins or [] for plugin in attr_plugins: @@ -297,7 +303,8 @@ class CreatedInstance: family_attributes = self._data["family_attributes"] output["family_attributes"] = family_attributes.data_to_store() - output["publish_attributes"] = self._data["publish_attributes"] + publish_attributes = self._data["publish_attributes"] + output["publish_attributes"] = publish_attributes.data_to_store() return output From 4262f59dbe12a483292e069652aebdbb81dfba77 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 14 Jul 2021 12:06:16 +0200 Subject: [PATCH 161/736] added basic methods for PublishAttributes --- openpype/pipeline/creator_plugins.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index f82a30e37c..f09c632041 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -153,11 +153,20 @@ class PublishAttributes: ) def __getitem__(self, key): - self._data[key] + return self._data[key] def __contains__(self, key): return key in self._data + def keys(self): + return self._data.keys() + + def values(self): + return self._data.values() + + def items(self): + return self._data.items() + def pop(self, key, default=None): # TODO implement if key not in self._data: From 37e43c6edfe911aabb7578828210f293ba2a374e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 14 Jul 2021 12:06:28 +0200 Subject: [PATCH 162/736] added conversion method for publish plugin --- openpype/pipeline/publish_plugins.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/openpype/pipeline/publish_plugins.py b/openpype/pipeline/publish_plugins.py index d43c49427e..c896d5ef32 100644 --- a/openpype/pipeline/publish_plugins.py +++ b/openpype/pipeline/publish_plugins.py @@ -15,6 +15,22 @@ class OpenPypePyblishPluginMixin: """ return [] + @classmethod + def convert_attribute_values(cls, attribute_values): + if cls.__name__ not in attribute_values: + return attribute_values + + plugin_values = attribute_values[cls.__name__] + + attr_defs = cls.get_attribute_defs() + for attr_def in attr_defs: + key = attr_def.key + if key in plugin_values: + plugin_values[key] = attr_def.convert_value( + plugin_values[key] + ) + return attribute_values + def set_state(self, percent=None, message=None): """Inner callback of plugin that would help to show in UI state. From f572f2c6518bd44a9637624f1caaae32cb009bd2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 14 Jul 2021 12:07:09 +0200 Subject: [PATCH 163/736] plugin attributes and values are used based on instance --- openpype/tools/new_publisher/control.py | 44 +++++++++++++++-------- openpype/tools/new_publisher/widgets.py | 47 +++++++++++++++++++++++-- 2 files changed, 74 insertions(+), 17 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 2ce47f61d7..75356bb5a7 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -158,24 +158,40 @@ class PublisherController: return ([], [], []) def get_publish_attribute_definitions(self, instances): - families = set() + all_defs_by_plugin_name = {} + all_plugin_values = {} for instance in instances: - family = instance.data["family"] - families.add(family) + for plugin_name, attr_val in instance.publish_attributes.items(): + attr_defs = attr_val.attr_defs + if not attr_defs: + continue - plugins_with_defs = [] - for plugin in self.publish_plugins: - if OpenPypePyblishPluginMixin in inspect.getmro(plugin): - plugins_with_defs.append(plugin) + if plugin_name not in all_defs_by_plugin_name: + all_defs_by_plugin_name[plugin_name] = attr_val.attr_defs + + if plugin_name not in all_plugin_values: + all_plugin_values[plugin_name] = {} + + plugin_values = all_plugin_values[plugin_name] + + for attr_def in attr_defs: + if attr_def.key not in plugin_values: + plugin_values[attr_def.key] = [] + attr_values = plugin_values[attr_def.key] + + value = attr_val[attr_def.key] + attr_values.append((instance, value)) - filtered_plugins = pyblish.logic.plugins_by_families( - plugins_with_defs, families - ) output = [] - for plugin in filtered_plugins: - attr_defs = plugin.get_attribute_defs() - if attr_defs: - output.append((plugin.__name__, attr_defs)) + for plugin in self.plugins_with_defs: + plugin_name = plugin.__name__ + if plugin_name not in all_defs_by_plugin_name: + continue + output.append(( + plugin_name, + all_defs_by_plugin_name[plugin_name], + all_plugin_values + )) return output def create(self, family, subset_name, instance_data, options): diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index cace227ee8..6b3580bf0a 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -80,6 +80,7 @@ class FamilyAttrsWidget(QtWidgets.QWidget): self._attr_def_id_to_instances = {} self._attr_def_id_to_attr_def = {} + # To store content of scroll area to prevend garbage collection self._content_widget = None @@ -148,6 +149,11 @@ class PublishPluginAttrsWidget(QtWidgets.QWidget): self.controller = controller self._scroll_area = scroll_area + + self._attr_def_id_to_instances = {} + self._attr_def_id_to_attr_def = {} + self._attr_def_id_to_plugin_name = {} + # Store content of scroll area to prevend garbage collection self._content_widget = None @@ -160,23 +166,58 @@ class PublishPluginAttrsWidget(QtWidgets.QWidget): self._content_widget = None - attr_defs = self.controller.get_publish_attribute_definitions( + self._attr_def_id_to_instances = {} + self._attr_def_id_to_attr_def = {} + self._attr_def_id_to_plugin_name = {} + + result = self.controller.get_publish_attribute_definitions( instances ) content_widget = QtWidgets.QWidget(self._scroll_area) content_layout = QtWidgets.QFormLayout(content_widget) - for plugin_name, plugin_attr_defs in attr_defs: - for attr_def in plugin_attr_defs: + for plugin_name, attr_defs, all_plugin_values in result: + plugin_values = all_plugin_values[plugin_name] + + for attr_def in attr_defs: widget = create_widget_for_attr_def( attr_def, content_widget ) label = attr_def.label or attr_def.key content_layout.addRow(label, widget) + widget.value_changed.connect(self._input_value_changed) + + attr_values = plugin_values[attr_def.key] + mutlivalue = len(attr_values) > 1 + values = set() + instances = [] + for instance, value in attr_values: + values.add(value) + instances.append(instance) + + values = list(values) + + self._attr_def_id_to_attr_def[attr_def.id] = attr_def + self._attr_def_id_to_instances[attr_def.id] = instances + self._attr_def_id_to_plugin_name[attr_def.id] = plugin_name + + widget.set_value(values[0]) + self._scroll_area.setWidget(content_widget) self._content_widget = content_widget + def _input_value_changed(self, value, attr_id): + instances = self._attr_def_id_to_instances.get(attr_id) + attr_def = self._attr_def_id_to_attr_def.get(attr_id) + plugin_name = self._attr_def_id_to_plugin_name.get(attr_id) + if not instances or not attr_def or not plugin_name: + return + + for instance in instances: + plugin_val = instance.publish_attributes[plugin_name] + plugin_val[attr_def.key] = value + class SubsetAttributesWidget(QtWidgets.QWidget): """Widget where attributes of instance/s are modified. From 42d63642a0be4055b3961eafc75afdb36cef693c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 14 Jul 2021 12:11:56 +0200 Subject: [PATCH 164/736] implemented changes for publish attributes --- openpype/pipeline/creator_plugins.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index f09c632041..bda4155744 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -184,7 +184,22 @@ class PublishAttributes: output[key] = attr_value.data_to_store() return output + def changes(self): + changes = {} + for key, attr_val in self._data.items(): + attr_changes = attr_val.changes() + if attr_changes: + if key not in changes: + changes[key] = {} + changes[key].update(attr_val) + + for key, value in self._origin_data.items(): + if key not in self._data: + changes[key] = (value, None) + return changes + def set_publish_plugins(self, attr_plugins): + # TODO implement self.attr_plugins = attr_plugins or [] for plugin in attr_plugins: attr_defs = plugin.get_attribute_defs() @@ -289,6 +304,10 @@ class CreatedInstance: if family_attr_changes: changes["family_attributes"] = family_attr_changes + publish_attr_changes = self.publish_attributes.changes() + if publish_attr_changes: + changes["publish_attributes"] = publish_attr_changes + for key, old_value in self._orig_data.items(): if key not in new_keys: changes[key] = (old_value, None) From 16f65326d8e38e595310e2440eee1f7eca0223bb Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 14 Jul 2021 17:51:00 +0200 Subject: [PATCH 165/736] added multiselection --- openpype/tools/new_publisher/window.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 6927f7bb8e..59d0a30319 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -65,6 +65,9 @@ class PublisherWindow(QtWidgets.QWidget): subset_view = QtWidgets.QTreeView(subset_widget) subset_view.setHeaderHidden(True) subset_view.setIndentation(0) + subset_view.setSelectionMode( + QtWidgets.QAbstractItemView.ExtendedSelection + ) subset_model = QtGui.QStandardItemModel() subset_view.setModel(subset_model) From ea831982c1f5dad098a6439af8b0889b4361c91a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 14 Jul 2021 17:52:12 +0200 Subject: [PATCH 166/736] changed args order on CreatedInstance --- openpype/pipeline/creator_plugins.py | 11 ++++++++--- openpype/tools/new_publisher/control.py | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index bda4155744..66eff35c5a 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -12,6 +12,7 @@ from abc import ( import six from .lib import UnknownDef +import avalon.api from openpype.lib import get_subset_name @@ -221,9 +222,11 @@ class CreatedInstance: already existing instance. """ def __init__( - self, host, creator, family, subset_name, data=None, + self, family, subset_name, data=None, creator=None, host=None, attr_plugins=None, new=True ): + if not host: + host = avalon.api.registered_host() self.host = host self.creator = creator @@ -347,7 +350,9 @@ class CreatedInstance: self.data = data @classmethod - def from_existing(cls, host, creator, instance_data, attr_plugins=None): + def from_existing( + cls, instance_data, creator=None, host=None, attr_plugins=None + ): """Convert instance data from workfile to CreatedInstance.""" instance_data = copy.deepcopy(instance_data) @@ -355,7 +360,7 @@ class CreatedInstance: subset_name = instance_data.get("subset", None) return cls( - host, creator, family, subset_name, instance_data, + family, subset_name, instance_data, creator, host, attr_plugins, new=False ) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 75356bb5a7..acd56fde92 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -129,7 +129,7 @@ class PublisherController: family ) instance = CreatedInstance.from_existing( - self.host, creator, instance_data, attr_plugins + instance_data, creator, self.host, attr_plugins ) instances.append(instance) From 1be17f398fe944fe914bc16e662f638681828bbf Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 14 Jul 2021 18:04:10 +0200 Subject: [PATCH 167/736] get_family_attribute_definitions returns defs for multiselection --- openpype/tools/new_publisher/control.py | 26 +++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index acd56fde92..929071f107 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -146,16 +146,26 @@ class PublisherController: self.host.update_instances(update_list) def get_family_attribute_definitions(self, instances): - if len(instances) == 1: - instance = instances[0] - output = [] + output = [] + _attr_defs = {} + for instance in instances: for attr_def in instance.family_attribute_defs: - value = instance.data["family_attributes"][attr_def.key] - output.append((attr_def, [instance], [value])) - return output + found_idx = None + for idx, _attr_def in _attr_defs.items(): + if attr_def == _attr_def: + found_idx = idx + break - # TODO mulsiselection - return ([], [], []) + value = instance.data["family_attributes"][attr_def.key] + if found_idx is None: + idx = len(output) + output.append((attr_def, [instance], [value])) + _attr_defs[idx] = attr_def + else: + item = output[found_idx] + item[1].append(instance) + item[2].append(value) + return output def get_publish_attribute_definitions(self, instances): all_defs_by_plugin_name = {} From 6487775b8047d5c9dc11b82adeea7ed034ccd885 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 14 Jul 2021 19:24:28 +0200 Subject: [PATCH 168/736] GlobalAttrsWidget handle more cases --- openpype/tools/new_publisher/widgets.py | 52 +++++++++++++++++++++---- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 6b3580bf0a..a9d78e73e0 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -42,8 +42,19 @@ class GlobalAttrsWidget(QtWidgets.QWidget): self.subset_value_widget = subset_value_widget def set_current_instances(self, instances): - if len(instances) == 1: + editable = False + if len(instances) == 0: + variant = "" + family = "" + asset_name = "" + task_name = "" + subset_name = "" + + elif len(instances) == 1: instance = instances[0] + if instance.creator is not None: + editable = True + unknown = "N/A" variant = instance.data.get("variant") or unknown @@ -52,13 +63,38 @@ class GlobalAttrsWidget(QtWidgets.QWidget): task_name = instance.data.get("task") or unknown subset_name = instance.data.get("subset") or unknown - self.variant_input.setText(variant) - self.family_value_widget.setText(family) - self.asset_value_widget.setText(asset_name) - self.task_value_widget.setText(task_name) - self.subset_value_widget.setText(subset_name) - return - # TODO mulsiselection + else: + families = set() + asset_names = set() + task_names = set() + for instance in instances: + families.add(instance.data.get("family") or unknown) + asset_names.add(instance.data.get("asset") or unknown) + task_names.add(instance.data.get("task") or unknown) + + multiselection_text = "< Multiselection >" + + variant = multiselection_text + family = multiselection_text + asset_name = multiselection_text + task_name = multiselection_text + subset_name = multiselection_text + if len(families) < 4: + family = " / ".join(families) + + if len(asset_names) < 4: + asset_name = " / ".join(asset_names) + + if len(task_names) < 4: + task_name = " / ".join(task_names) + + self.variant_input.set_editable(editable) + + self.variant_input.setText(variant) + self.family_value_widget.setText(family) + self.asset_value_widget.setText(asset_name) + self.task_value_widget.setText(task_name) + self.subset_value_widget.setText(subset_name) class FamilyAttrsWidget(QtWidgets.QWidget): From 7e8e51138735988c9088d49c121140e575b03cfc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 14 Jul 2021 19:24:53 +0200 Subject: [PATCH 169/736] replace variant input with readable/editable widget --- openpype/tools/new_publisher/widgets.py | 56 ++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index a9d78e73e0..a4b86527d7 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -13,11 +13,65 @@ def get_default_thumbnail_image_path(): return os.path.join(dirpath, "image_file.png") +class ReadWriteLineEdit(QtWidgets.QFrame): + textChanged = QtCore.Signal(str) + + def __init__(self, parent): + super(ReadWriteLineEdit, self).__init__(parent) + + read_widget = QtWidgets.QLabel(self) + edit_widget = QtWidgets.QLineEdit(self) + + self._editable = False + edit_widget.setVisible(self._editable) + read_widget.setVisible(not self._editable) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + layout.addWidget(read_widget) + layout.addWidget(edit_widget) + + edit_widget.textChanged.connect(self.textChanged) + + self.read_widget = read_widget + self.edit_widget = edit_widget + + def set_editable(self, editable): + if self._editable == editable: + return + self._editable = editable + self.set_edit(False) + + def set_edit(self, edit=None): + if edit is None: + edit = not self.edit_widget.isVisible() + + if not self._editable and edit: + return + + if self.edit_widget.isVisible() == edit: + return + + self.read_widget.setVisible(not edit) + self.edit_widget.setVisible(edit) + + def setText(self, text): + self.read_widget.setText(text) + if self.edit_widget.text() != text: + self.edit_widget.setText(text) + + def text(self): + if self.edit_widget.isVisible(): + return self.edit_widget.text() + return self.read_widget.text() + + class GlobalAttrsWidget(QtWidgets.QWidget): def __init__(self, parent): super(GlobalAttrsWidget, self).__init__(parent) - variant_input = QtWidgets.QLineEdit(self) + variant_input = ReadWriteLineEdit(self) family_value_widget = QtWidgets.QLabel(self) asset_value_widget = QtWidgets.QLabel(self) task_value_widget = QtWidgets.QLabel(self) From 9f4a2630b8b0897e589c0b0ebe97f4951c3e63f1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 15 Jul 2021 09:28:20 +0200 Subject: [PATCH 170/736] changed layout of buttons --- openpype/tools/new_publisher/window.py | 31 +++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 59d0a30319..e255cfee8f 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -72,12 +72,37 @@ class PublisherWindow(QtWidgets.QWidget): subset_model = QtGui.QStandardItemModel() subset_view.setModel(subset_model) + # Buttons at the bottom of subset view + create_btn = QtWidgets.QPushButton("Create", subset_widget) + delete_btn = QtWidgets.QPushButton("Delete", subset_widget) + save_btn = QtWidgets.QPushButton("Save", subset_widget) + change_view_btn = QtWidgets.QPushButton("=", subset_widget) + + # Subset details widget subset_attributes_widget = SubsetAttributesWidget( controller, subset_widget ) + # Layout of buttons at the bottom of subset view + subset_view_btns_layout = QtWidgets.QHBoxLayout() + subset_view_btns_layout.setContentsMargins(0, 0, 0, 0) + subset_view_btns_layout.setSpacing(5) + subset_view_btns_layout.addWidget(create_btn) + subset_view_btns_layout.addWidget(delete_btn) + subset_view_btns_layout.addWidget(save_btn) + subset_view_btns_layout.addStretch(1) + subset_view_btns_layout.addWidget(change_view_btn) + + # Layout of view and buttons + subset_view_layout = QtWidgets.QVBoxLayout() + subset_view_layout.setContentsMargins(0, 0, 0, 0) + subset_view_layout.addWidget(subset_view, 1) + subset_view_layout.addLayout(subset_view_btns_layout, 0) + + # Whole subset layout with attributes and details subset_layout = QtWidgets.QHBoxLayout(subset_widget) - subset_layout.addWidget(subset_view, 0) + subset_layout.setContentsMargins(0, 0, 0, 0) + subset_layout.addLayout(subset_view_layout, 0) subset_layout.addWidget(subset_attributes_widget, 1) content_layout = QtWidgets.QVBoxLayout(content_widget) @@ -87,16 +112,12 @@ class PublisherWindow(QtWidgets.QWidget): # Footer footer_widget = QtWidgets.QWidget(self) - create_btn = QtWidgets.QPushButton("Create", footer_widget) - save_btn = QtWidgets.QPushButton("Save", footer_widget) message_input = QtWidgets.QLineEdit(footer_widget) validate_btn = QtWidgets.QPushButton("Validate", footer_widget) publish_btn = QtWidgets.QPushButton("Publish", footer_widget) footer_layout = QtWidgets.QHBoxLayout(footer_widget) footer_layout.setContentsMargins(0, 0, 0, 0) - footer_layout.addWidget(create_btn, 0) - footer_layout.addWidget(save_btn, 0) footer_layout.addWidget(message_input, 1) footer_layout.addWidget(validate_btn, 0) footer_layout.addWidget(publish_btn, 0) From 1290312f4ed35d5787a46ca61cfcaa918a89a40f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 15 Jul 2021 09:29:04 +0200 Subject: [PATCH 171/736] added `get_selected_instances` method to be able use it in multiple places --- openpype/tools/new_publisher/window.py | 27 ++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index e255cfee8f..4433798732 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -183,6 +183,21 @@ class PublisherWindow(QtWidgets.QWidget): def set_context_label(self, label): self.context_label.setText(label) + def get_selected_instances(self): + instances = [] + instances_by_id = {} + for instance in self.controller.instances: + instance_id = instance.data["uuid"] + instances_by_id[instance_id] = instance + + for index in self.subset_view.selectionModel().selectedIndexes(): + instance_id = index.data(QtCore.Qt.UserRole) + instance = instances_by_id.get(instance_id) + if instance: + instances.append(instance) + + return instances + def _on_reset_clicked(self): self.reset() @@ -236,17 +251,9 @@ class PublisherWindow(QtWidgets.QWidget): self._refresh_instances() def _on_subset_change(self, *_args): - instances = [] - instances_by_id = {} - for instance in self.controller.instances: - instance_id = instance.data["uuid"] - instances_by_id[instance_id] = instance + instances = self.get_selected_instances() - for index in self.subset_view.selectionModel().selectedIndexes(): - instance_id = index.data(QtCore.Qt.UserRole) - instance = instances_by_id.get(instance_id) - if instance: - instances.append(instance) + self.delete_btn.setEnabled(len(instances) >= 0) self.subset_attributes_widget.set_current_instances(instances) From f61c07b02607dd9ed59e5499f8f52135b2107a08 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 15 Jul 2021 09:30:43 +0200 Subject: [PATCH 172/736] ignore changes during instance refreshing --- openpype/tools/new_publisher/window.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 4433798732..3237225ead 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -40,6 +40,7 @@ class PublisherWindow(QtWidgets.QWidget): super(PublisherWindow, self).__init__(parent) self._first_show = True + self._refreshing_instances = False controller = PublisherController() @@ -214,6 +215,11 @@ class PublisherWindow(QtWidgets.QWidget): print("Publishing!!!") def _refresh_instances(self): + if self._refreshing_instances: + return + + self._refreshing_instances = True + to_remove = set() existing_mapping = {} @@ -244,6 +250,11 @@ class PublisherWindow(QtWidgets.QWidget): if new_items: self.subset_model.invisibleRootItem().appendRows(new_items) + self._refreshing_instances = False + + # Force to change instance and refresh details + self._on_subset_change() + def _on_control_create(self): self._refresh_instances() @@ -251,8 +262,13 @@ class PublisherWindow(QtWidgets.QWidget): self._refresh_instances() def _on_subset_change(self, *_args): + # Ignore changes if in middle of refreshing + if self._refreshing_instances: + return + instances = self.get_selected_instances() + # Disable delete button if nothing is selected self.delete_btn.setEnabled(len(instances) >= 0) self.subset_attributes_widget.set_current_instances(instances) From 696bdb94c620060e31059e9a6ea046a6a2172702 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 15 Jul 2021 09:31:03 +0200 Subject: [PATCH 173/736] added delete and change view buttons --- openpype/tools/new_publisher/window.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 3237225ead..d7db52c892 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -141,7 +141,10 @@ class PublisherWindow(QtWidgets.QWidget): reset_btn.clicked.connect(self._on_reset_clicked) create_btn.clicked.connect(self._on_create_clicked) + delete_btn.clicked.connect(self._on_delete_clicked) save_btn.clicked.connect(self._on_save_clicked) + change_view_btn.clicked.connect(self._on_change_view_clicked) + validate_btn.clicked.connect(self._on_validate_clicked) publish_btn.clicked.connect(self._on_publish_clicked) @@ -156,6 +159,8 @@ class PublisherWindow(QtWidgets.QWidget): self.subset_view = subset_view self.subset_model = subset_model + self.delete_btn = delete_btn + self.subset_attributes_widget = subset_attributes_widget self.footer_widget = footer_widget self.message_input = message_input @@ -205,6 +210,12 @@ class PublisherWindow(QtWidgets.QWidget): def _on_create_clicked(self): self.creator_window.show() + def _on_delete_clicked(self): + instances = self.get_selected_instances() + + def _on_change_view_clicked(self): + print("change view") + def _on_save_clicked(self): self.controller.save_instance_changes() From 672f124252a406d2b2bbbc71f9d2470905f607b2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 15 Jul 2021 10:14:09 +0200 Subject: [PATCH 174/736] attr defs widget can have multivalue --- openpype/widgets/attribute_defs/widgets.py | 86 +++++++++++++++++----- 1 file changed, 66 insertions(+), 20 deletions(-) diff --git a/openpype/widgets/attribute_defs/widgets.py b/openpype/widgets/attribute_defs/widgets.py index cadfa29bb8..ecfa4b3afc 100644 --- a/openpype/widgets/attribute_defs/widgets.py +++ b/openpype/widgets/attribute_defs/widgets.py @@ -65,7 +65,7 @@ class _BaseAttrDefWidget(QtWidgets.QWidget): ) ) - def set_value(self, value): + def set_value(self, value, multivalue=False): raise NotImplementedError( "Method 'current_value' is not implemented. {}".format( self.__class__.__name__ @@ -102,7 +102,18 @@ class NumberAttrWidget(_BaseAttrDefWidget): def current_value(self): return self._input_widget.value() - def set_value(self, value): + def set_value(self, value, multivalue=False): + if multivalue: + set_value = set(value) + if None in set_value: + set_value.remove(None) + set_value.add(self.attr_def.default) + + if len(set_value) > 1: + self._input_widget.setSpecialValueText("Multiselection") + return + value = tuple(set_value)[0] + if self.current_value != value: self._input_widget.setValue(value) @@ -137,7 +148,10 @@ class TextAttrWidget(_BaseAttrDefWidget): self.main_layout.addWidget(input_widget, 0) def _on_value_change(self): - new_value = self._input_widget.toPlainText() + if self.multiline: + new_value = self._input_widget.toPlainText() + else: + new_value = self._input_widget.text() self.value_changed.emit(new_value, self.attr_def.id) def current_value(self): @@ -145,13 +159,23 @@ class TextAttrWidget(_BaseAttrDefWidget): return self._input_widget.toPlainText() return self._input_widget.text() - def set_value(self, value): - if value == self.current_value(): - return - if self.multiline: - self._input_widget.setPlainText(value) - else: - self._input_widget.setText(value) + def set_value(self, value, multivalue=False): + if multivalue: + set_value = set(value) + if None in set_value: + set_value.remove(None) + set_value.add(self.attr_def.default) + + if len(set_value) == 1: + value = tuple(set_value)[0] + else: + value = "< Multiselection >" + + if value != self.current_value(): + if self.multiline: + self._input_widget.setPlainText(value) + else: + self._input_widget.setText(value) class BoolAttrWidget(_BaseAttrDefWidget): @@ -172,7 +196,18 @@ class BoolAttrWidget(_BaseAttrDefWidget): def current_value(self): return self._input_widget.isChecked() - def set_value(self, value): + def set_value(self, value, multivalue=False): + if multivalue: + set_value = set(value) + if None in set_value: + set_value.remove(None) + set_value.add(self.attr_def.default) + + if len(set_value) > 1: + self._input_widget.setCheckState(QtCore.Qt.PartiallyChecked) + return + value = tuple(set_value)[0] + if value != self.current_value(): self._input_widget.setChecked(value) @@ -206,13 +241,15 @@ class EnumAttrWidget(_BaseAttrDefWidget): idx = self._input_widget.currentIndex() return self._input_widget.itemData(idx) - def set_value(self, value): - idx = self._input_widget.findData(value) - cur_idx = self._input_widget.currentIndex() - if idx == cur_idx: - return - if idx >= 0: - self._input_widget.setCurrentIndex(idx) + def set_value(self, value, multivalue=False): + if not multivalue: + idx = self._input_widget.findData(value) + cur_idx = self._input_widget.currentIndex() + if idx != cur_idx and idx >= 0: + self._input_widget.setCurrentIndex(idx) + + else: + self._input_widget.lineEdit().setText("Multiselection") class UnknownAttrWidget(_BaseAttrDefWidget): @@ -226,9 +263,18 @@ class UnknownAttrWidget(_BaseAttrDefWidget): self.main_layout.addWidget(input_widget, 0) def current_value(self): - return self._input_widget.text() + raise ValueError( + "{} can't hold real value.".format(self.__class__.__name__) + ) + + def set_value(self, value, multivalue=False): + if multivalue: + set_value = set(value) + if len(set_value) == 1: + value = tuple(set_value)[0] + else: + value = "< Multiselection >" - def set_value(self, value): str_value = str(value) if str_value != self._value: self._value = str_value From 17a3e40bbf8a6bb45e889a9b240d204f5cc083ea Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 15 Jul 2021 10:15:27 +0200 Subject: [PATCH 175/736] multivalue is sent to widgets --- openpype/tools/new_publisher/widgets.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index a4b86527d7..513040d024 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -198,8 +198,7 @@ class FamilyAttrsWidget(QtWidgets.QWidget): if value is not None: widget.set_value(values[0]) else: - # TODO multiselection - pass + widget.set_value(values, True) label = attr_def.label or attr_def.key content_layout.addRow(label, widget) @@ -279,20 +278,21 @@ class PublishPluginAttrsWidget(QtWidgets.QWidget): widget.value_changed.connect(self._input_value_changed) attr_values = plugin_values[attr_def.key] - mutlivalue = len(attr_values) > 1 - values = set() + multivalue = len(attr_values) > 1 + values = [] instances = [] for instance, value in attr_values: - values.add(value) + values.append(value) instances.append(instance) - values = list(values) - self._attr_def_id_to_attr_def[attr_def.id] = attr_def self._attr_def_id_to_instances[attr_def.id] = instances self._attr_def_id_to_plugin_name[attr_def.id] = plugin_name - widget.set_value(values[0]) + if multivalue: + widget.set_value(values, multivalue) + else: + widget.set_value(values[0]) self._scroll_area.setWidget(content_widget) self._content_widget = content_widget From 562638474982d03ddee0dec645e10dadae6ee14c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 15 Jul 2021 11:06:45 +0200 Subject: [PATCH 176/736] added ask dialog if user want to remove instances --- openpype/tools/new_publisher/window.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index d7db52c892..2747688f21 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -213,6 +213,25 @@ class PublisherWindow(QtWidgets.QWidget): def _on_delete_clicked(self): instances = self.get_selected_instances() + # Ask user if he really wants to remove instances + dialog = QtWidgets.QMessageBox(self) + dialog.setIcon(QtWidgets.QMessageBox.Question) + dialog.setWindowTitle("Are you sure?") + if len(instances) > 1: + msg = ( + "Do you really want to remove {} instances?" + ).format(len(instances)) + else: + msg = ( + "Do you really want to remove the instance?" + ) + dialog.setText(msg) + dialog.setStandardButtons( + QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel + ) + dialog.setDefaultButton(QtWidgets.QMessageBox.Ok) + dialog.setEscapeButton(QtWidgets.QMessageBox.Cancel) + dialog.exec_() def _on_change_view_clicked(self): print("change view") From 7c101b7e02630b3fc8b24ef90c6c72f043c75470 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 15 Jul 2021 11:09:06 +0200 Subject: [PATCH 177/736] added remove instances callback --- openpype/tools/new_publisher/control.py | 23 +++++++++++++---------- openpype/tools/new_publisher/window.py | 4 ++++ 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 929071f107..f183f662d2 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -135,16 +135,6 @@ class PublisherController: self.instances = instances - def save_instance_changes(self): - update_list = [] - for instance in self.instances: - instance_changes = instance.changes() - if instance_changes: - update_list.append((instance, instance_changes)) - - if update_list: - self.host.update_instances(update_list) - def get_family_attribute_definitions(self, instances): output = [] _attr_defs = {} @@ -218,3 +208,16 @@ class PublisherController: self._trigger_callbacks(self._on_create_callback_refs) return result + + def save_instance_changes(self): + update_list = [] + for instance in self.instances: + instance_changes = instance.changes() + if instance_changes: + update_list.append((instance, instance_changes)) + + if update_list: + self.host.update_instances(update_list) + + def remove_instances(self, instances): + self.host.remove_instances(instances) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 2747688f21..7a6aff79bd 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -232,6 +232,10 @@ class PublisherWindow(QtWidgets.QWidget): dialog.setDefaultButton(QtWidgets.QMessageBox.Ok) dialog.setEscapeButton(QtWidgets.QMessageBox.Cancel) dialog.exec_() + # Skip if OK was not clicked + if dialog.result() == QtWidgets.QMessageBox.Ok: + self.controller.remove_instances(instances) + def _on_change_view_clicked(self): print("change view") From 30c36283ac7aba05b98a59a950ecac6dff27394d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 15 Jul 2021 11:27:11 +0200 Subject: [PATCH 178/736] changed how reseting of plugins and instances work --- openpype/tools/new_publisher/control.py | 63 ++++++++++++++----------- openpype/tools/new_publisher/widgets.py | 4 +- openpype/tools/new_publisher/window.py | 8 +--- 3 files changed, 39 insertions(+), 36 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index f183f662d2..fb44951083 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -26,8 +26,8 @@ class PublisherController: dbcon.install() self.dbcon = dbcon - self._reset_callback_refs = set() - self._on_create_callback_refs = set() + self._instances_refresh_callback_refs = set() + self._plugins_refresh_callback_refs = set() self.creators = {} @@ -37,15 +37,16 @@ class PublisherController: self.instances = [] - self._in_reset = False + self._resetting_plugins = False + self._resetting_instances = False - def add_on_reset_callback(self, callback): + def add_instances_refresh_callback(self, callback): ref = weakref.WeakMethod(callback) - self._reset_callback_refs.add(ref) + self._instances_refresh_callback_refs.add(ref) - def add_on_create_callback(self, callback): + def add_plugins_refresh_callback(self, callback): ref = weakref.WeakMethod(callback) - self._on_create_callback_refs.add(ref) + self._plugins_refresh_callback_refs.add(ref) def _trigger_callbacks(self, callbacks, *args, **kwargs): # Trigger reset callbacks @@ -61,16 +62,8 @@ class PublisherController: callbacks.remove(ref) def reset(self): - if self._in_reset: - return - - self._in_reset = True - self._reset() - - # Trigger reset callbacks - self._trigger_callbacks(self._reset_callback_refs) - - self._in_reset = False + self._reset_plugin() + self._reset_instances() def _get_publish_plugins_with_attr_for_family(self, family): if family not in self._attr_plugins_by_family: @@ -81,8 +74,13 @@ class PublisherController: return self._attr_plugins_by_family[family] - def _reset(self): + def _reset_plugin(self): """Reset to initial state.""" + if self._resetting_plugins: + return + + self._resetting_plugins = True + # Reset publish plugins self._attr_plugins_by_family = {} @@ -117,6 +115,16 @@ class PublisherController: self.creators = creators + self._resetting_plugins = False + + self._trigger_callbacks(self._plugins_refresh_callback_refs) + + def _reset_instances(self): + if self._resetting_instances: + return + + self._resetting_instances = True + # Collect instances host_instances = self.host.list_instances() instances = [] @@ -124,7 +132,7 @@ class PublisherController: family = instance_data["family"] # Prepare publish plugins with attribute definitions - creator = creators.get(family) + creator = self.creators.get(family) attr_plugins = self._get_publish_plugins_with_attr_for_family( family ) @@ -135,6 +143,10 @@ class PublisherController: self.instances = instances + self._resetting_instances = False + + self._trigger_callbacks(self._instances_refresh_callback_refs) + def get_family_attribute_definitions(self, instances): output = [] _attr_defs = {} @@ -198,16 +210,9 @@ class PublisherController: # QUESTION Force to return instances or call `list_instances` on each # creation? (`list_instances` may slow down...) creator = self.creators[family] - result = creator.create(subset_name, instance_data, options) - if result and not isinstance(result, (list, tuple)): - result = [result] + creator.create(subset_name, instance_data, options) - for instance in result: - self.instances.append(instance) - - self._trigger_callbacks(self._on_create_callback_refs) - - return result + self._reset_instances() def save_instance_changes(self): update_list = [] @@ -221,3 +226,5 @@ class PublisherController: def remove_instances(self, instances): self.host.remove_instances(instances) + + self._reset_instances() diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 513040d024..b8a56619ac 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -476,7 +476,7 @@ class CreateDialog(QtWidgets.QDialog): ) variant_hints_menu.triggered.connect(self._on_variant_action) - controller.add_on_reset_callback(self._on_control_reset) + controller.add_plugins_refresh_callback(self._on_plugins_refresh) self.asset_name_input = asset_name_input self.subset_name_input = subset_name_input @@ -580,7 +580,7 @@ class CreateDialog(QtWidgets.QDialog): index = self.family_model.index(0, 0) self.family_view.setCurrentIndex(index) - def _on_control_reset(self): + def _on_plugins_refresh(self): # Trigger refresh only if is visible if self.isVisible(): self.refresh() diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 7a6aff79bd..505efa4771 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -135,8 +135,7 @@ class PublisherWindow(QtWidgets.QWidget): creator_window = CreateDialog(controller, self) - controller.add_on_reset_callback(self._on_control_reset) - controller.add_on_create_callback(self._on_control_create) + controller.add_instances_refresh_callback(self._on_instances_refresh) reset_btn.clicked.connect(self._on_reset_clicked) @@ -289,10 +288,7 @@ class PublisherWindow(QtWidgets.QWidget): # Force to change instance and refresh details self._on_subset_change() - def _on_control_create(self): - self._refresh_instances() - - def _on_control_reset(self): + def _on_instances_refresh(self): self._refresh_instances() def _on_subset_change(self, *_args): From 9ce2eedde6bcc9cb18abc0a9362d9ef0e0dec964 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 15 Jul 2021 16:01:59 +0200 Subject: [PATCH 179/736] implemented card view of instances --- openpype/tools/new_publisher/widgets.py | 171 ++++++++++++++++++++++++ 1 file changed, 171 insertions(+) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index b8a56619ac..648cbac697 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -741,3 +741,174 @@ class CreateDialog(QtWidgets.QDialog): if self.auto_close_checkbox.isChecked(): self.hide() + + +class InstanceCardWidget(QtWidgets.QWidget): + active_changed = QtCore.Signal(str, bool) + + def __init__(self, instance, item, parent): + super(InstanceCardWidget, self).__init__(parent) + + self.instance = instance + self.item = item + + subset_name_label = QtWidgets.QLabel(instance.data["subset"], self) + active_checkbox = QtWidgets.QCheckBox(self) + active_checkbox.setStyleSheet("background: transparent;") + active_checkbox.setChecked(instance.data["active"]) + + expand_btn = QtWidgets.QToolButton(self) + expand_btn.setArrowType(QtCore.Qt.DownArrow) + expand_btn.setMaximumWidth(14) + expand_btn.setEnabled(False) + + detail_widget = QtWidgets.QWidget(self) + detail_widget.setVisible(False) + self.detail_widget = detail_widget + + top_layout = QtWidgets.QHBoxLayout() + top_layout.addWidget(subset_name_label) + top_layout.addStretch(1) + top_layout.addWidget(active_checkbox) + top_layout.addWidget(expand_btn) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(2, 2, 2, 2) + layout.addLayout(top_layout) + layout.addWidget(detail_widget) + + self.setAttribute(QtCore.Qt.WA_TranslucentBackground) + subset_name_label.setAttribute(QtCore.Qt.WA_TranslucentBackground) + active_checkbox.setAttribute(QtCore.Qt.WA_TranslucentBackground) + expand_btn.setAttribute(QtCore.Qt.WA_TranslucentBackground) + + active_checkbox.stateChanged.connect(self._on_active_change) + expand_btn.clicked.connect(self._on_expend_clicked) + + self.subset_name_label = subset_name_label + self.active_checkbox = active_checkbox + self.expand_btn = expand_btn + + def set_active(self, new_value): + checkbox_value = self.active_checkbox.isChecked() + instance_value = self.instance.data["active"] + if instance_value == checkbox_value == new_value: + return + + # First change instance value and them change checkbox + # - prevent to trigger `active_changed` signal + self.instance.data["active"] = new_value + self.active_checkbox.setChecked(new_value) + + def update_instance(self, instance): + self.instance = instance + self.set_active(instance.data["active"]) + + def _set_expanded(self, expanded=None): + if expanded is None: + expanded = not self.detail_widget.isVisible() + self.detail_widget.setVisible(expanded) + self.item.setSizeHint(self.sizeHint()) + + def showEvent(self, event): + super(InstanceCardWidget, self).showEvent(event) + self.item.setSizeHint(self.sizeHint()) + + def _on_active_change(self): + new_value = self.active_checkbox.isChecked() + old_value = self.instance.data["active"] + if new_value == old_value: + return + + self.instance.data["active"] = new_value + self.active_changed.emit(self.instance.data["uuid"], new_value) + + def _on_expend_clicked(self): + self._set_expanded() + + +class InstanceCardView(QtWidgets.QWidget): + selection_changed = QtCore.Signal() + + def __init__(self, controller, parent): + super(InstanceCardView, self).__init__(parent) + + self.controller = controller + + list_widget = QtWidgets.QListWidget(self) + list_widget.setSelectionMode( + QtWidgets.QAbstractItemView.ExtendedSelection + ) + list_widget.setResizeMode(QtWidgets.QListView.Adjust) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(list_widget, 1) + + list_widget.selectionModel().selectionChanged.connect( + self._on_selection_change + ) + + self._items_by_id = {} + self._widgets_by_id = {} + + self.list_widget = list_widget + + def refresh(self): + instances_by_id = {} + for instance in self.controller.instances: + instance_id = instance.data["uuid"] + instances_by_id[instance_id] = instance + + for instance_id in tuple(self._items_by_id.keys()): + if instance_id not in instances_by_id: + item = self._items_by_id.pop(instance_id) + self.list_widget.removeItemWidget(item) + widget = self._widgets_by_id.pop(instance_id) + widget.deleteLater() + row = self.list_widget.row(item) + self.list_widget.takeItem(row) + + for instance_id, instance in instances_by_id.items(): + if instance_id in self._items_by_id: + widget = self._widgets_by_id[instance_id] + widget.update_instance(instance) + + else: + item = QtWidgets.QListWidgetItem(self.list_widget) + widget = InstanceCardWidget(instance, item, self) + widget.active_changed.connect(self._on_active_changed) + item.setData(QtCore.Qt.UserRole, instance_id) + self.list_widget.addItem(item) + self.list_widget.setItemWidget(item, widget) + self._items_by_id[instance_id] = item + self._widgets_by_id[instance_id] = widget + + def _on_active_changed(self, changed_instance_id, new_value): + selected_ids = set() + found = False + for item in self.list_widget.selectedItems(): + instance_id = item.data(QtCore.Qt.UserRole) + selected_ids.add(instance_id) + if not found and instance_id == changed_instance_id: + found = True + + if not found: + return + + for instance_id in selected_ids: + widget = self._widgets_by_id.get(instance_id) + if widget: + widget.set_active(new_value) + + def _on_selection_change(self, *_args): + self.selection_changed.emit() + + def get_selected_instances(self): + instances = [] + for item in self.list_widget.selectedItems(): + instance_id = item.data(QtCore.Qt.UserRole) + widget = self._widgets_by_id.get(instance_id) + if widget: + instances.append(widget.instance) + return instances From ce5126092f6e3cf8219201aad51855560357c6dc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 15 Jul 2021 16:25:55 +0200 Subject: [PATCH 180/736] reorganized few widgets and layouts --- openpype/tools/new_publisher/window.py | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 505efa4771..88dabb5846 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -58,10 +58,8 @@ class PublisherWindow(QtWidgets.QWidget): header_layout.addWidget(reset_btn, 0) # Content - content_widget = QtWidgets.QWidget(main_frame) - # Subset widget - subset_widget = QtWidgets.QWidget(content_widget) + subset_widget = QtWidgets.QWidget(main_frame) subset_view = QtWidgets.QTreeView(subset_widget) subset_view.setHeaderHidden(True) @@ -106,18 +104,16 @@ class PublisherWindow(QtWidgets.QWidget): subset_layout.addLayout(subset_view_layout, 0) subset_layout.addWidget(subset_attributes_widget, 1) - content_layout = QtWidgets.QVBoxLayout(content_widget) + content_layout = QtWidgets.QVBoxLayout() content_layout.setContentsMargins(0, 0, 0, 0) content_layout.addWidget(subset_widget) # Footer - footer_widget = QtWidgets.QWidget(self) + message_input = QtWidgets.QLineEdit(main_frame) + validate_btn = QtWidgets.QPushButton("Validate", main_frame) + publish_btn = QtWidgets.QPushButton("Publish", main_frame) - message_input = QtWidgets.QLineEdit(footer_widget) - validate_btn = QtWidgets.QPushButton("Validate", footer_widget) - publish_btn = QtWidgets.QPushButton("Publish", footer_widget) - - footer_layout = QtWidgets.QHBoxLayout(footer_widget) + footer_layout = QtWidgets.QHBoxLayout() footer_layout.setContentsMargins(0, 0, 0, 0) footer_layout.addWidget(message_input, 1) footer_layout.addWidget(validate_btn, 0) @@ -126,8 +122,8 @@ class PublisherWindow(QtWidgets.QWidget): # Main frame main_frame_layout = QtWidgets.QVBoxLayout(main_frame) main_frame_layout.addWidget(header_widget, 0) - main_frame_layout.addWidget(content_widget, 1) - main_frame_layout.addWidget(footer_widget, 0) + main_frame_layout.addLayout(content_layout, 1) + main_frame_layout.addLayout(footer_layout, 0) # Add main frame to this window main_layout = QtWidgets.QHBoxLayout(self) @@ -161,7 +157,6 @@ class PublisherWindow(QtWidgets.QWidget): self.delete_btn = delete_btn self.subset_attributes_widget = subset_attributes_widget - self.footer_widget = footer_widget self.message_input = message_input self.validate_btn = validate_btn self.publish_btn = publish_btn From 65671f49d3f070e122f109b7988f07b806a02461 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 15 Jul 2021 17:23:07 +0200 Subject: [PATCH 181/736] implemented basic concept of 2 views --- openpype/tools/new_publisher/widgets.py | 141 +++++++++++++++++++++++- openpype/tools/new_publisher/window.py | 113 ++++++++++--------- 2 files changed, 200 insertions(+), 54 deletions(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 648cbac697..0ec839c314 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -827,9 +827,26 @@ class InstanceCardWidget(QtWidgets.QWidget): self._set_expanded() -class InstanceCardView(QtWidgets.QWidget): +class _AbstractInstanceView(QtWidgets.QWidget): selection_changed = QtCore.Signal() + def refresh(self): + raise NotImplementedError(( + "{} Method 'refresh' is not implemented." + ).format(self.__class__.__name__)) + + def get_selected_instances(self): + raise NotImplementedError(( + "{} Method 'get_selected_instances' is not implemented." + ).format(self.__class__.__name__)) + + def set_selected_instances(self, instances): + raise NotImplementedError(( + "{} Method 'set_selected_instances' is not implemented." + ).format(self.__class__.__name__)) + + +class InstanceCardView(_AbstractInstanceView): def __init__(self, controller, parent): super(InstanceCardView, self).__init__(parent) @@ -912,3 +929,125 @@ class InstanceCardView(QtWidgets.QWidget): if widget: instances.append(widget.instance) return instances + + def set_selected_instances(self, instances): + indexes = [] + model = self.list_widget.model() + for instance in instances: + instance_id = instance.data["uuid"] + item = self._items_by_id.get(instance_id) + if item: + row = self.list_widget.row(item) + index = model.index(row, 0) + indexes.append(index) + + selection_model = self.list_widget.selectionModel() + first_item = True + for index in indexes: + if first_item: + first_item = False + select_type = QtCore.QItemSelectionModel.SelectCurrent + else: + select_type = QtCore.QItemSelectionModel.Select + selection_model.select(index, select_type) + + +class InstanceListView(_AbstractInstanceView): + def __init__(self, controller, parent): + super(InstanceListView, self).__init__(parent) + + self.controller = controller + + instance_view = QtWidgets.QTreeView(self) + instance_view.setHeaderHidden(True) + instance_view.setIndentation(0) + instance_view.setSelectionMode( + QtWidgets.QAbstractItemView.ExtendedSelection + ) + + instance_model = QtGui.QStandardItemModel() + instance_view.setModel(instance_model) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(instance_view) + + instance_view.selectionModel().selectionChanged.connect( + self._on_selection_change + ) + + self.instance_view = instance_view + self.instance_model = instance_model + + def refresh(self): + to_remove = set() + existing_mapping = {} + + for idx in range(self.instance_model.rowCount()): + index = self.instance_model.index(idx, 0) + uuid = index.data(QtCore.Qt.UserRole) + to_remove.add(uuid) + existing_mapping[uuid] = idx + + new_items = [] + for instance in self.controller.instances: + uuid = instance.data["uuid"] + if uuid in to_remove: + to_remove.remove(uuid) + continue + + item = QtGui.QStandardItem(instance.data["subset"]) + item.setData(instance.data["uuid"], QtCore.Qt.UserRole) + new_items.append(item) + + idx_to_remove = [] + for uuid in to_remove: + idx_to_remove.append(existing_mapping[uuid]) + + for idx in reversed(sorted(idx_to_remove)): + self.instance_model.removeRows(idx, 1) + + if new_items: + self.instance_model.invisibleRootItem().appendRows(new_items) + + def get_selected_instances(self): + instances = [] + instances_by_id = {} + for instance in self.controller.instances: + instance_id = instance.data["uuid"] + instances_by_id[instance_id] = instance + + for index in self.instance_view.selectionModel().selectedIndexes(): + instance_id = index.data(QtCore.Qt.UserRole) + instance = instances_by_id.get(instance_id) + if instance: + instances.append(instance) + + return instances + + def set_selected_instances(self, instances): + model = self.instance_view.model() + selected_ids = set() + for instance in instances: + instance_id = instance.data["uuid"] + selected_ids.add(instance_id) + + indexes = [] + for row in range(model.rowCount()): + index = model.index(row, 0) + instance_id = index.data(QtCore.Qt.UserRole) + if instance_id in selected_ids: + indexes.append(index) + + selection_model = self.instance_view.selectionModel() + first_item = True + for index in indexes: + if first_item: + first_item = False + select_type = QtCore.QItemSelectionModel.SelectCurrent + else: + select_type = QtCore.QItemSelectionModel.Select + selection_model.select(index, select_type) + + def _on_selection_change(self, *_args): + self.selection_changed.emit() diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 88dabb5846..0e8edb9525 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -31,6 +31,8 @@ from openpype import style from control import PublisherController from widgets import ( SubsetAttributesWidget, + InstanceCardView, + InstanceListView, CreateDialog ) @@ -42,6 +44,10 @@ class PublisherWindow(QtWidgets.QWidget): self._first_show = True self._refreshing_instances = False + self._view_type_order = ["card", "list"] + self._view_type = self._view_type_order[0] + self._views_refreshed = {} + controller = PublisherController() # TODO Title, Icon, Stylesheet @@ -61,15 +67,11 @@ class PublisherWindow(QtWidgets.QWidget): # Subset widget subset_widget = QtWidgets.QWidget(main_frame) - subset_view = QtWidgets.QTreeView(subset_widget) - subset_view.setHeaderHidden(True) - subset_view.setIndentation(0) - subset_view.setSelectionMode( - QtWidgets.QAbstractItemView.ExtendedSelection - ) + subset_view_cards = InstanceCardView(controller, subset_widget) + subset_list_view = InstanceListView(controller, subset_widget) - subset_model = QtGui.QStandardItemModel() - subset_view.setModel(subset_model) + subset_view_cards.setVisible(False) + subset_list_view.setVisible(False) # Buttons at the bottom of subset view create_btn = QtWidgets.QPushButton("Create", subset_widget) @@ -95,7 +97,8 @@ class PublisherWindow(QtWidgets.QWidget): # Layout of view and buttons subset_view_layout = QtWidgets.QVBoxLayout() subset_view_layout.setContentsMargins(0, 0, 0, 0) - subset_view_layout.addWidget(subset_view, 1) + subset_view_layout.addWidget(subset_view_cards, 1) + subset_view_layout.addWidget(subset_list_view, 1) subset_view_layout.addLayout(subset_view_btns_layout, 0) # Whole subset layout with attributes and details @@ -143,7 +146,10 @@ class PublisherWindow(QtWidgets.QWidget): validate_btn.clicked.connect(self._on_validate_clicked) publish_btn.clicked.connect(self._on_publish_clicked) - subset_view.selectionModel().selectionChanged.connect( + subset_list_view.selection_changed.connect( + self._on_subset_change + ) + subset_view_cards.selection_changed.connect( self._on_subset_change ) @@ -151,8 +157,8 @@ class PublisherWindow(QtWidgets.QWidget): self.context_label = context_label - self.subset_view = subset_view - self.subset_model = subset_model + self.subset_view_cards = subset_view_cards + self.subset_list_view = subset_list_view self.delete_btn = delete_btn @@ -165,11 +171,19 @@ class PublisherWindow(QtWidgets.QWidget): self.creator_window = creator_window + self.views_by_type = { + "card": subset_view_cards, + "list": subset_list_view + } + + self._change_view_type(self._view_type) + + self.setStyleSheet(style.load_stylesheet()) + # DEBUGING self.set_context_label( "////" ) - self.setStyleSheet(style.load_stylesheet()) def showEvent(self, event): super(PublisherWindow, self).showEvent(event) @@ -184,19 +198,37 @@ class PublisherWindow(QtWidgets.QWidget): self.context_label.setText(label) def get_selected_instances(self): - instances = [] - instances_by_id = {} - for instance in self.controller.instances: - instance_id = instance.data["uuid"] - instances_by_id[instance_id] = instance + view = self.views_by_type[self._view_type] + return view.get_selected_instances() - for index in self.subset_view.selectionModel().selectedIndexes(): - instance_id = index.data(QtCore.Qt.UserRole) - instance = instances_by_id.get(instance_id) - if instance: - instances.append(instance) + def _change_view_type(self, view_type=None): + if view_type is None: + next_type = False + for _view_type in self._view_type_order: + if next_type: + view_type = _view_type + break - return instances + if _view_type == self._view_type: + next_type = True + + if view_type is None: + view_type = self._view_type_order[0] + + old_view = self.views_by_type[self._view_type] + old_view.setVisible(False) + + self._view_type = view_type + refreshed = self._views_refreshed.get(view_type, False) + new_view = self.views_by_type[view_type] + new_view.setVisible(True) + if not refreshed: + new_view.refresh() + self._views_refreshed[view_type] = True + + if new_view is not old_view: + selected_instances = old_view.get_selected_instances() + new_view.set_selected_instances(selected_instances) def _on_reset_clicked(self): self.reset() @@ -231,7 +263,7 @@ class PublisherWindow(QtWidgets.QWidget): self.controller.remove_instances(instances) def _on_change_view_clicked(self): - print("change view") + self._change_view_type() def _on_save_clicked(self): self.controller.save_instance_changes() @@ -248,35 +280,10 @@ class PublisherWindow(QtWidgets.QWidget): self._refreshing_instances = True - to_remove = set() - existing_mapping = {} + view = self.views_by_type[self._view_type] + view.refresh() - for idx in range(self.subset_model.rowCount()): - index = self.subset_model.index(idx, 0) - uuid = index.data(QtCore.Qt.UserRole) - to_remove.add(uuid) - existing_mapping[uuid] = idx - - new_items = [] - for instance in self.controller.instances: - uuid = instance.data["uuid"] - if uuid in to_remove: - to_remove.remove(uuid) - continue - - item = QtGui.QStandardItem(instance.data["subset"]) - item.setData(instance.data["uuid"], QtCore.Qt.UserRole) - new_items.append(item) - - idx_to_remove = [] - for uuid in to_remove: - idx_to_remove.append(existing_mapping[uuid]) - - for idx in reversed(sorted(idx_to_remove)): - self.subset_model.removeRows(idx, 1) - - if new_items: - self.subset_model.invisibleRootItem().appendRows(new_items) + self._views_refreshed = {self._view_type: True} self._refreshing_instances = False From 2e5e1014d8dce557cb4a5781848ad1140e9761d7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 15 Jul 2021 17:56:05 +0200 Subject: [PATCH 182/736] list view has grouping by family --- openpype/tools/new_publisher/widgets.py | 108 ++++++++++++++++-------- 1 file changed, 75 insertions(+), 33 deletions(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 0ec839c314..6899f774a8 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -976,39 +976,68 @@ class InstanceListView(_AbstractInstanceView): self._on_selection_change ) + self._group_items = {} self.instance_view = instance_view self.instance_model = instance_model def refresh(self): - to_remove = set() - existing_mapping = {} - - for idx in range(self.instance_model.rowCount()): - index = self.instance_model.index(idx, 0) - uuid = index.data(QtCore.Qt.UserRole) - to_remove.add(uuid) - existing_mapping[uuid] = idx - - new_items = [] + instances_by_family = collections.defaultdict(list) + families = set() for instance in self.controller.instances: - uuid = instance.data["uuid"] - if uuid in to_remove: - to_remove.remove(uuid) + family = instance.data["family"] + families.add(family) + instances_by_family[family].append(instance) + + new_group_items = [] + for family in families: + if family in self._group_items: continue - item = QtGui.QStandardItem(instance.data["subset"]) - item.setData(instance.data["uuid"], QtCore.Qt.UserRole) - new_items.append(item) + group_item = QtGui.QStandardItem(family) + self._group_items[family] = group_item + new_group_items.append(group_item) - idx_to_remove = [] - for uuid in to_remove: - idx_to_remove.append(existing_mapping[uuid]) + root_item = self.instance_model.invisibleRootItem() + if new_group_items: + root_item.appendRows(new_group_items) - for idx in reversed(sorted(idx_to_remove)): - self.instance_model.removeRows(idx, 1) + for family in tuple(self._group_items.keys()): + if family in families: + continue - if new_items: - self.instance_model.invisibleRootItem().appendRows(new_items) + group_item = self._group_items.pop(family) + root_item.removeRow(group_item.row()) + + for family, group_item in self._group_items.items(): + to_remove = set() + existing_mapping = {} + + for idx in range(group_item.rowCount()): + index = group_item.index(idx, 0) + instance_id = index.data(QtCore.Qt.UserRole) + to_remove.add(instance_id) + existing_mapping[instance_id] = idx + + new_items = [] + for instance in instances_by_family[family]: + instance_id = instance.data["uuid"] + if instance_id in to_remove: + to_remove.remove(instance_id) + continue + + item = QtGui.QStandardItem(instance.data["subset"]) + item.setData(instance.data["uuid"], QtCore.Qt.UserRole) + new_items.append(item) + + idx_to_remove = [] + for instance_id in to_remove: + idx_to_remove.append(existing_mapping[instance_id]) + + for idx in reversed(sorted(idx_to_remove)): + group_item.removeRows(idx, 1) + + if new_items: + group_item.appendRows(new_items) def get_selected_instances(self): instances = [] @@ -1019,25 +1048,38 @@ class InstanceListView(_AbstractInstanceView): for index in self.instance_view.selectionModel().selectedIndexes(): instance_id = index.data(QtCore.Qt.UserRole) - instance = instances_by_id.get(instance_id) - if instance: - instances.append(instance) + if instance_id is not None: + instance = instances_by_id.get(instance_id) + if instance: + instances.append(instance) return instances def set_selected_instances(self, instances): model = self.instance_view.model() - selected_ids = set() + instance_ids_by_family = collections.defaultdict(set) for instance in instances: + family = instance.data["family"] instance_id = instance.data["uuid"] - selected_ids.add(instance_id) + instance_ids_by_family[family].add(instance_id) indexes = [] - for row in range(model.rowCount()): - index = model.index(row, 0) - instance_id = index.data(QtCore.Qt.UserRole) - if instance_id in selected_ids: - indexes.append(index) + for family, group_item in self._group_items.items(): + selected_ids = instance_ids_by_family[family] + if not selected_ids: + continue + + group_index = model.index(group_item.row(), group_item.column()) + has_indexes = False + for row in range(group_item.rowCount()): + index = model.index(row, 0, group_index) + instance_id = index.data(QtCore.Qt.UserRole) + if instance_id in selected_ids: + indexes.append(index) + has_indexes = True + + if has_indexes: + self.instance_view.setExpanded(group_index, True) selection_model = self.instance_view.selectionModel() first_item = True From 0775d4ded0b4622654c27077a995df1e333247b1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 15 Jul 2021 18:00:41 +0200 Subject: [PATCH 183/736] added constant role for instance id --- openpype/tools/new_publisher/constants.py | 9 +++++++++ openpype/tools/new_publisher/widgets.py | 16 +++++++++------- 2 files changed, 18 insertions(+), 7 deletions(-) create mode 100644 openpype/tools/new_publisher/constants.py diff --git a/openpype/tools/new_publisher/constants.py b/openpype/tools/new_publisher/constants.py new file mode 100644 index 0000000000..9bf4764454 --- /dev/null +++ b/openpype/tools/new_publisher/constants.py @@ -0,0 +1,9 @@ +from Qt import QtCore + + +INSTANCE_ID_ROLE = QtCore.Qt.UserRole + 1 + + +__all__ = ( + "INSTANCE_ID_ROLE", +) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 6899f774a8..aad082c74b 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -1,9 +1,11 @@ import os import re import copy +import collections from Qt import QtWidgets, QtCore, QtGui from openpype.widgets.attribute_defs import create_widget_for_attr_def +from constants import INSTANCE_ID_ROLE SEPARATORS = ("---separator---", "---") @@ -895,7 +897,7 @@ class InstanceCardView(_AbstractInstanceView): item = QtWidgets.QListWidgetItem(self.list_widget) widget = InstanceCardWidget(instance, item, self) widget.active_changed.connect(self._on_active_changed) - item.setData(QtCore.Qt.UserRole, instance_id) + item.setData(INSTANCE_ID_ROLE, instance_id) self.list_widget.addItem(item) self.list_widget.setItemWidget(item, widget) self._items_by_id[instance_id] = item @@ -905,7 +907,7 @@ class InstanceCardView(_AbstractInstanceView): selected_ids = set() found = False for item in self.list_widget.selectedItems(): - instance_id = item.data(QtCore.Qt.UserRole) + instance_id = item.data(INSTANCE_ID_ROLE) selected_ids.add(instance_id) if not found and instance_id == changed_instance_id: found = True @@ -924,7 +926,7 @@ class InstanceCardView(_AbstractInstanceView): def get_selected_instances(self): instances = [] for item in self.list_widget.selectedItems(): - instance_id = item.data(QtCore.Qt.UserRole) + instance_id = item.data(INSTANCE_ID_ROLE) widget = self._widgets_by_id.get(instance_id) if widget: instances.append(widget.instance) @@ -1014,7 +1016,7 @@ class InstanceListView(_AbstractInstanceView): for idx in range(group_item.rowCount()): index = group_item.index(idx, 0) - instance_id = index.data(QtCore.Qt.UserRole) + instance_id = index.data(INSTANCE_ID_ROLE) to_remove.add(instance_id) existing_mapping[instance_id] = idx @@ -1026,7 +1028,7 @@ class InstanceListView(_AbstractInstanceView): continue item = QtGui.QStandardItem(instance.data["subset"]) - item.setData(instance.data["uuid"], QtCore.Qt.UserRole) + item.setData(instance.data["uuid"], INSTANCE_ID_ROLE) new_items.append(item) idx_to_remove = [] @@ -1047,7 +1049,7 @@ class InstanceListView(_AbstractInstanceView): instances_by_id[instance_id] = instance for index in self.instance_view.selectionModel().selectedIndexes(): - instance_id = index.data(QtCore.Qt.UserRole) + instance_id = index.data(INSTANCE_ID_ROLE) if instance_id is not None: instance = instances_by_id.get(instance_id) if instance: @@ -1073,7 +1075,7 @@ class InstanceListView(_AbstractInstanceView): has_indexes = False for row in range(group_item.rowCount()): index = model.index(row, 0, group_index) - instance_id = index.data(QtCore.Qt.UserRole) + instance_id = index.data(INSTANCE_ID_ROLE) if instance_id in selected_ids: indexes.append(index) has_indexes = True From 9dceb6b9463a584e56ff47c4c048df4f35d8b326 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 16 Jul 2021 10:00:38 +0200 Subject: [PATCH 184/736] minor pep fixes --- openpype/pipeline/creator_plugins.py | 2 -- openpype/tools/new_publisher/widgets.py | 1 - openpype/tools/new_publisher/window.py | 2 +- openpype/widgets/attribute_defs/widgets.py | 2 +- 4 files changed, 2 insertions(+), 5 deletions(-) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/creator_plugins.py index 66eff35c5a..cd4bea58fe 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/creator_plugins.py @@ -1,6 +1,5 @@ import copy import logging -import contextlib import collections from uuid import uuid4 @@ -368,7 +367,6 @@ class CreatedInstance: self._data["publish_attributes"].set_publish_plugins(attr_plugins) - @six.add_metaclass(ABCMeta) class BaseCreator: """Plugin that create and modify instance data before publishing process. diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index aad082c74b..8d295f41d3 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -740,7 +740,6 @@ class CreateDialog(QtWidgets.QDialog): # TODO better handling print(str(exc)) - if self.auto_close_checkbox.isChecked(): self.hide() diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 0e8edb9525..485c227b5b 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -25,7 +25,7 @@ for path in [ ]: sys.path.append(path) -from Qt import QtWidgets, QtCore, QtGui +from Qt import QtWidgets from openpype import style from control import PublisherController diff --git a/openpype/widgets/attribute_defs/widgets.py b/openpype/widgets/attribute_defs/widgets.py index ecfa4b3afc..b2781dabfe 100644 --- a/openpype/widgets/attribute_defs/widgets.py +++ b/openpype/widgets/attribute_defs/widgets.py @@ -7,7 +7,7 @@ from openpype.pipeline.lib import ( EnumDef, BoolDef ) -from Qt import QtWidgets, QtCore, QtGui +from Qt import QtWidgets, QtCore def create_widget_for_attr_def(attr_def, parent=None): From 5d0836de082c67fcffca791b6ec79181066fed7d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 16 Jul 2021 11:29:39 +0200 Subject: [PATCH 185/736] added sorting to list view --- openpype/tools/new_publisher/widgets.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 8d295f41d3..a42fe5a1bc 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -967,7 +967,15 @@ class InstanceListView(_AbstractInstanceView): ) instance_model = QtGui.QStandardItemModel() - instance_view.setModel(instance_model) + + proxy_model = QtCore.QSortFilterProxyModel() + proxy_model.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) + proxy_model.setFilterKeyColumn(0) + proxy_model.setDynamicSortFilter(True) + proxy_model.setSortRole(QtCore.Qt.DisplayRole) + proxy_model.setSourceModel(instance_model) + + instance_view.setModel(proxy_model) layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) @@ -980,6 +988,7 @@ class InstanceListView(_AbstractInstanceView): self._group_items = {} self.instance_view = instance_view self.instance_model = instance_model + self.proxy_model = proxy_model def refresh(self): instances_by_family = collections.defaultdict(list) From f73beb50aec0e62b33d87ed7afe123f644fc3b47 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 16 Jul 2021 11:30:14 +0200 Subject: [PATCH 186/736] simplified set active --- openpype/tools/new_publisher/widgets.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index a42fe5a1bc..f32dc9fc0b 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -793,13 +793,14 @@ class InstanceCardWidget(QtWidgets.QWidget): def set_active(self, new_value): checkbox_value = self.active_checkbox.isChecked() instance_value = self.instance.data["active"] - if instance_value == checkbox_value == new_value: - return # First change instance value and them change checkbox # - prevent to trigger `active_changed` signal - self.instance.data["active"] = new_value - self.active_checkbox.setChecked(new_value) + if instance_value != new_value: + self.instance.data["active"] = new_value + + if checkbox_value != new_value: + self.active_checkbox.setChecked(new_value) def update_instance(self, instance): self.instance = instance From 5dd18b4610859c0a36d340b7ef0265c64f2c9490 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 16 Jul 2021 11:32:08 +0200 Subject: [PATCH 187/736] added refresh active state --- openpype/tools/new_publisher/widgets.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index f32dc9fc0b..ac196764af 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -804,7 +804,10 @@ class InstanceCardWidget(QtWidgets.QWidget): def update_instance(self, instance): self.instance = instance - self.set_active(instance.data["active"]) + self.update_instance_values() + + def update_instance_values(self): + self.set_active(self.instance.data["active"]) def _set_expanded(self, expanded=None): if expanded is None: @@ -903,6 +906,10 @@ class InstanceCardView(_AbstractInstanceView): self._items_by_id[instance_id] = item self._widgets_by_id[instance_id] = widget + def refresh_active_state(self): + for widget in self._widgets_by_id.values(): + widget.update_instance_values() + def _on_active_changed(self, changed_instance_id, new_value): selected_ids = set() found = False From f20289f8fa5a5551c34004e27afcc81b055158d4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 16 Jul 2021 11:33:13 +0200 Subject: [PATCH 188/736] fix indexes on refresh --- openpype/tools/new_publisher/widgets.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index ac196764af..4ed740b9a3 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -1030,8 +1030,12 @@ class InstanceListView(_AbstractInstanceView): to_remove = set() existing_mapping = {} + group_index = self.instance_model.index( + group_item.row(), group_item.column() + ) + for idx in range(group_item.rowCount()): - index = group_item.index(idx, 0) + index = self.instance_model.index(idx, 0, group_index) instance_id = index.data(INSTANCE_ID_ROLE) to_remove.add(instance_id) existing_mapping[instance_id] = idx From a3ae09808cc4ace4dd7ced4643e9013a85311ee7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 16 Jul 2021 11:34:09 +0200 Subject: [PATCH 189/736] list view has also widget --- openpype/tools/new_publisher/widgets.py | 85 ++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 3 deletions(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 4ed740b9a3..dafc6026f7 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -961,6 +961,63 @@ class InstanceCardView(_AbstractInstanceView): selection_model.select(index, select_type) +class InstanceListItemWidget(QtWidgets.QWidget): + active_changed = QtCore.Signal(str, bool) + + def __init__(self, instance, parent): + super(InstanceListItemWidget, self).__init__(parent) + + self.instance = instance + + subset_name_label = QtWidgets.QLabel(instance.data["subset"], self) + active_checkbox = QtWidgets.QCheckBox(self) + active_checkbox.setStyleSheet("background: transparent;") + active_checkbox.setChecked(instance.data["active"]) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(subset_name_label) + layout.addStretch(1) + layout.addWidget(active_checkbox) + + self.setAttribute(QtCore.Qt.WA_TranslucentBackground) + subset_name_label.setAttribute(QtCore.Qt.WA_TranslucentBackground) + active_checkbox.setAttribute(QtCore.Qt.WA_TranslucentBackground) + + active_checkbox.stateChanged.connect(self._on_active_change) + + self.subset_name_label = subset_name_label + self.active_checkbox = active_checkbox + + def set_active(self, new_value): + checkbox_value = self.active_checkbox.isChecked() + instance_value = self.instance.data["active"] + + # First change instance value and them change checkbox + # - prevent to trigger `active_changed` signal + if instance_value != new_value: + self.instance.data["active"] = new_value + + if checkbox_value != new_value: + self.active_checkbox.setChecked(new_value) + + def update_instance(self, instance): + self.instance = instance + self.update_instance_values() + + def update_instance_values(self): + self.set_active(self.instance.data["active"]) + + def _on_active_change(self): + new_value = self.active_checkbox.isChecked() + old_value = self.instance.data["active"] + if new_value == old_value: + return + + self.instance.data["active"] = new_value + self.active_changed.emit(self.instance.data["uuid"], new_value) + + class InstanceListView(_AbstractInstanceView): def __init__(self, controller, parent): super(InstanceListView, self).__init__(parent) @@ -994,6 +1051,7 @@ class InstanceListView(_AbstractInstanceView): ) self._group_items = {} + self._widgets_by_id = {} self.instance_view = instance_view self.instance_model = instance_model self.proxy_model = proxy_model @@ -1041,15 +1099,19 @@ class InstanceListView(_AbstractInstanceView): existing_mapping[instance_id] = idx new_items = [] + items_with_instance = [] for instance in instances_by_family[family]: instance_id = instance.data["uuid"] if instance_id in to_remove: to_remove.remove(instance_id) + widget = self._widgets_by_id[instance_id] + widget.update_instance(instance) continue - item = QtGui.QStandardItem(instance.data["subset"]) + item = QtGui.QStandardItem() item.setData(instance.data["uuid"], INSTANCE_ID_ROLE) new_items.append(item) + items_with_instance.append((item, instance)) idx_to_remove = [] for instance_id in to_remove: @@ -1058,8 +1120,25 @@ class InstanceListView(_AbstractInstanceView): for idx in reversed(sorted(idx_to_remove)): group_item.removeRows(idx, 1) - if new_items: - group_item.appendRows(new_items) + for instance_id in to_remove: + widget = self._widgets_by_id.pop(instance.data["uuid"]) + widget.deleteLater() + + if not new_items: + continue + + group_item.appendRows(new_items) + + for item, instance in items_with_instance: + item_index = self.instance_model.index( + item.row(), + item.column(), + group_index + ) + proxy_index = self.proxy_model.mapFromSource(item_index) + widget = InstanceListItemWidget(instance, self) + self.instance_view.setIndexWidget(proxy_index, widget) + self._widgets_by_id[instance.data["uuid"]] = widget def get_selected_instances(self): instances = [] From 6f26f7c026ab19b9fc5222f4078b89b547c74d1a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 16 Jul 2021 11:34:37 +0200 Subject: [PATCH 190/736] changed first item selection type --- openpype/tools/new_publisher/widgets.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index dafc6026f7..de87fc63d6 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -955,7 +955,7 @@ class InstanceCardView(_AbstractInstanceView): for index in indexes: if first_item: first_item = False - select_type = QtCore.QItemSelectionModel.SelectCurrent + select_type = QtCore.QItemSelectionModel.ClearAndSelect else: select_type = QtCore.QItemSelectionModel.Select selection_model.select(index, select_type) @@ -1187,7 +1187,7 @@ class InstanceListView(_AbstractInstanceView): for index in indexes: if first_item: first_item = False - select_type = QtCore.QItemSelectionModel.SelectCurrent + select_type = QtCore.QItemSelectionModel.ClearAndSelect else: select_type = QtCore.QItemSelectionModel.Select selection_model.select(index, select_type) From 66e561dace736e38f3855519581cbc92078e2b74 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 16 Jul 2021 11:34:48 +0200 Subject: [PATCH 191/736] group items are not enabled --- openpype/tools/new_publisher/widgets.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index de87fc63d6..8c03e20553 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -1070,6 +1070,7 @@ class InstanceListView(_AbstractInstanceView): continue group_item = QtGui.QStandardItem(family) + group_item.setFlags(QtCore.Qt.ItemIsEnabled) self._group_items[family] = group_item new_group_items.append(group_item) From 1df123a86338b269c39e729028e2ac3f7fb7e810 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 16 Jul 2021 11:35:02 +0200 Subject: [PATCH 192/736] added refresh_active_state for list view --- openpype/tools/new_publisher/widgets.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 8c03e20553..220d57da8c 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -1141,6 +1141,10 @@ class InstanceListView(_AbstractInstanceView): self.instance_view.setIndexWidget(proxy_index, widget) self._widgets_by_id[instance.data["uuid"]] = widget + def refresh_active_state(self): + for widget in self._widgets_by_id.values(): + widget.update_instance_values() + def get_selected_instances(self): instances = [] instances_by_id = {} From 7c6e2e52df12519e5f05066f42b0c07e51cd02ff Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 16 Jul 2021 11:35:17 +0200 Subject: [PATCH 193/736] refresh active state on view change --- openpype/tools/new_publisher/window.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 485c227b5b..dd72b6515e 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -225,6 +225,8 @@ class PublisherWindow(QtWidgets.QWidget): if not refreshed: new_view.refresh() self._views_refreshed[view_type] = True + else: + new_view.refresh_active_state() if new_view is not old_view: selected_instances = old_view.get_selected_instances() From a7033e024b0d965b7f6ca0a5f5f6b35f2adc2910 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 16 Jul 2021 11:58:54 +0200 Subject: [PATCH 194/736] added role for sort value --- openpype/tools/new_publisher/constants.py | 2 ++ openpype/tools/new_publisher/widgets.py | 11 ++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/openpype/tools/new_publisher/constants.py b/openpype/tools/new_publisher/constants.py index 9bf4764454..2b7b6eaa62 100644 --- a/openpype/tools/new_publisher/constants.py +++ b/openpype/tools/new_publisher/constants.py @@ -2,8 +2,10 @@ from Qt import QtCore INSTANCE_ID_ROLE = QtCore.Qt.UserRole + 1 +SORT_VALUE_ROLE = QtCore.Qt.UserRole + 2 __all__ = ( "INSTANCE_ID_ROLE", + "SORT_VALUE_ROLE" ) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 220d57da8c..463db3f421 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -5,7 +5,10 @@ import collections from Qt import QtWidgets, QtCore, QtGui from openpype.widgets.attribute_defs import create_widget_for_attr_def -from constants import INSTANCE_ID_ROLE +from constants import ( + INSTANCE_ID_ROLE, + SORT_VALUE_ROLE +) SEPARATORS = ("---separator---", "---") @@ -1034,11 +1037,11 @@ class InstanceListView(_AbstractInstanceView): instance_model = QtGui.QStandardItemModel() proxy_model = QtCore.QSortFilterProxyModel() + proxy_model.setSourceModel(instance_model) proxy_model.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) + proxy_model.setSortRole(SORT_VALUE_ROLE) proxy_model.setFilterKeyColumn(0) proxy_model.setDynamicSortFilter(True) - proxy_model.setSortRole(QtCore.Qt.DisplayRole) - proxy_model.setSourceModel(instance_model) instance_view.setModel(proxy_model) @@ -1070,6 +1073,7 @@ class InstanceListView(_AbstractInstanceView): continue group_item = QtGui.QStandardItem(family) + group_item.setData(family, SORT_VALUE_ROLE) group_item.setFlags(QtCore.Qt.ItemIsEnabled) self._group_items[family] = group_item new_group_items.append(group_item) @@ -1110,6 +1114,7 @@ class InstanceListView(_AbstractInstanceView): continue item = QtGui.QStandardItem() + item.setData(instance.data["subset"], SORT_VALUE_ROLE) item.setData(instance.data["uuid"], INSTANCE_ID_ROLE) new_items.append(item) items_with_instance.append((item, instance)) From bd7a49437e4552f934d4331790a6dd8e669613ad Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 16 Jul 2021 11:59:36 +0200 Subject: [PATCH 195/736] few minor changes --- openpype/tools/new_publisher/widgets.py | 30 ++++++++++++------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 463db3f421..39483cdb3b 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -1104,7 +1104,7 @@ class InstanceListView(_AbstractInstanceView): existing_mapping[instance_id] = idx new_items = [] - items_with_instance = [] + new_items_with_instance = [] for instance in instances_by_family[family]: instance_id = instance.data["uuid"] if instance_id in to_remove: @@ -1117,7 +1117,7 @@ class InstanceListView(_AbstractInstanceView): item.setData(instance.data["subset"], SORT_VALUE_ROLE) item.setData(instance.data["uuid"], INSTANCE_ID_ROLE) new_items.append(item) - items_with_instance.append((item, instance)) + new_items_with_instance.append((item, instance)) idx_to_remove = [] for instance_id in to_remove: @@ -1130,21 +1130,21 @@ class InstanceListView(_AbstractInstanceView): widget = self._widgets_by_id.pop(instance.data["uuid"]) widget.deleteLater() - if not new_items: - continue + if new_items: + sort_at_the_end = True - group_item.appendRows(new_items) + group_item.appendRows(new_items) - for item, instance in items_with_instance: - item_index = self.instance_model.index( - item.row(), - item.column(), - group_index - ) - proxy_index = self.proxy_model.mapFromSource(item_index) - widget = InstanceListItemWidget(instance, self) - self.instance_view.setIndexWidget(proxy_index, widget) - self._widgets_by_id[instance.data["uuid"]] = widget + for item, instance in new_items_with_instance: + item_index = self.instance_model.index( + item.row(), + item.column(), + group_index + ) + proxy_index = self.proxy_model.mapFromSource(item_index) + widget = InstanceListItemWidget(instance, self) + self.instance_view.setIndexWidget(proxy_index, widget) + self._widgets_by_id[instance.data["uuid"]] = widget def refresh_active_state(self): for widget in self._widgets_by_id.values(): From 84529c0b90c5a21f9c163e16e65ced1e4875d8b2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 16 Jul 2021 12:04:16 +0200 Subject: [PATCH 196/736] trigger sort on proxy model when new items were added --- openpype/tools/new_publisher/widgets.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 39483cdb3b..c963164514 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -1078,8 +1078,10 @@ class InstanceListView(_AbstractInstanceView): self._group_items[family] = group_item new_group_items.append(group_item) + sort_at_the_end = False root_item = self.instance_model.invisibleRootItem() if new_group_items: + sort_at_the_end = True root_item.appendRows(new_group_items) for family in tuple(self._group_items.keys()): @@ -1146,6 +1148,10 @@ class InstanceListView(_AbstractInstanceView): self.instance_view.setIndexWidget(proxy_index, widget) self._widgets_by_id[instance.data["uuid"]] = widget + # Trigger sort at the end of refresh + if sort_at_the_end: + self.proxy_model.sort(0) + def refresh_active_state(self): for widget in self._widgets_by_id.values(): widget.update_instance_values() From 1ad25f83731c7ea393d7ce52c96743da3fe2c151 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 16 Jul 2021 12:04:45 +0200 Subject: [PATCH 197/736] fixed widget pop --- openpype/tools/new_publisher/widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index c963164514..ea7534b28a 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -1129,7 +1129,7 @@ class InstanceListView(_AbstractInstanceView): group_item.removeRows(idx, 1) for instance_id in to_remove: - widget = self._widgets_by_id.pop(instance.data["uuid"]) + widget = self._widgets_by_id.pop(instance_id) widget.deleteLater() if new_items: From edc9eee884fcdb68011326339d97e2dee06bfbef Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 16 Jul 2021 12:05:13 +0200 Subject: [PATCH 198/736] fixed set instance selection --- openpype/tools/new_publisher/widgets.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index ea7534b28a..790b9254f2 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -1186,17 +1186,20 @@ class InstanceListView(_AbstractInstanceView): if not selected_ids: continue - group_index = model.index(group_item.row(), group_item.column()) + group_index = self.instance_model.index( + group_item.row(), group_item.column() + ) + proxy_group_index = self.proxy_model.mapFromSource(group_index) has_indexes = False for row in range(group_item.rowCount()): - index = model.index(row, 0, group_index) + index = model.index(row, 0, proxy_group_index) instance_id = index.data(INSTANCE_ID_ROLE) if instance_id in selected_ids: indexes.append(index) has_indexes = True if has_indexes: - self.instance_view.setExpanded(group_index, True) + self.instance_view.setExpanded(proxy_group_index, True) selection_model = self.instance_view.selectionModel() first_item = True From 959fd511cfe7404dd15f76af7008ddec69a4930e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 16 Jul 2021 16:21:31 +0200 Subject: [PATCH 199/736] added group widget for groups --- openpype/tools/new_publisher/widgets.py | 66 ++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 2 deletions(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 790b9254f2..7c041b9fc3 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -1021,6 +1021,53 @@ class InstanceListItemWidget(QtWidgets.QWidget): self.active_changed.emit(self.instance.data["uuid"], new_value) +class InstanceListGroupWidget(QtWidgets.QFrame): + expand_changed = QtCore.Signal(str, bool) + + def __init__(self, family, parent): + super(InstanceListGroupWidget, self).__init__(parent) + self.setObjectName("InstanceListGroupWidget") + + self.family = family + self._expanded = False + + subset_name_label = QtWidgets.QLabel(family, self) + + expand_btn = QtWidgets.QToolButton(self) + expand_btn.setStyleSheet("background: transparent;") + expand_btn.setArrowType(QtCore.Qt.RightArrow) + expand_btn.setMaximumWidth(14) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(5, 0, 2, 0) + layout.addWidget( + subset_name_label, 1, QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter + ) + layout.addWidget(expand_btn) + + # self.setAttribute(QtCore.Qt.WA_TranslucentBackground) + subset_name_label.setAttribute(QtCore.Qt.WA_TranslucentBackground) + expand_btn.setAttribute(QtCore.Qt.WA_TranslucentBackground) + + expand_btn.clicked.connect(self._on_expand_clicked) + + self.subset_name_label = subset_name_label + self.expand_btn = expand_btn + + def _on_expand_clicked(self): + self.expand_changed.emit(self.family, not self._expanded) + + def set_expanded(self, expanded): + if self._expanded == expanded: + return + + self._expanded = expanded + if expanded: + self.expand_btn.setArrowType(QtCore.Qt.DownArrow) + else: + self.expand_btn.setArrowType(QtCore.Qt.RightArrow) + + class InstanceListView(_AbstractInstanceView): def __init__(self, controller, parent): super(InstanceListView, self).__init__(parent) @@ -1054,6 +1101,7 @@ class InstanceListView(_AbstractInstanceView): ) self._group_items = {} + self._group_widgets = {} self._widgets_by_id = {} self.instance_view = instance_view self.instance_model = instance_model @@ -1072,7 +1120,7 @@ class InstanceListView(_AbstractInstanceView): if family in self._group_items: continue - group_item = QtGui.QStandardItem(family) + group_item = QtGui.QStandardItem() group_item.setData(family, SORT_VALUE_ROLE) group_item.setFlags(QtCore.Qt.ItemIsEnabled) self._group_items[family] = group_item @@ -1084,12 +1132,24 @@ class InstanceListView(_AbstractInstanceView): sort_at_the_end = True root_item.appendRows(new_group_items) + for group_item in new_group_items: + index = self.instance_model.index( + group_item.row(), group_item.column() + ) + proxy_index = self.proxy_model.mapFromSource(index) + widget = InstanceListGroupWidget(family, self.instance_view) + family = group_item.data(SORT_VALUE_ROLE) + self._group_widgets[family] = widget + self.instance_view.setIndexWidget(proxy_index, widget) + for family in tuple(self._group_items.keys()): if family in families: continue group_item = self._group_items.pop(family) root_item.removeRow(group_item.row()) + widget = self._group_widgets.pop(family) + widget.deleteLater() for family, group_item in self._group_items.items(): to_remove = set() @@ -1144,7 +1204,9 @@ class InstanceListView(_AbstractInstanceView): group_index ) proxy_index = self.proxy_model.mapFromSource(item_index) - widget = InstanceListItemWidget(instance, self) + widget = InstanceListItemWidget( + instance, self.instance_view + ) self.instance_view.setIndexWidget(proxy_index, widget) self._widgets_by_id[instance.data["uuid"]] = widget From c36bf869d315a5cc304ddcb44e897333e5ac7040 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 16 Jul 2021 16:24:34 +0200 Subject: [PATCH 200/736] change arrow based on expanded/collapsed state --- openpype/tools/new_publisher/widgets.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 7c041b9fc3..68050e6cbf 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -1099,6 +1099,8 @@ class InstanceListView(_AbstractInstanceView): instance_view.selectionModel().selectionChanged.connect( self._on_selection_change ) + instance_view.collapsed.connect(self._on_collapse) + instance_view.expanded.connect(self._on_expand) self._group_items = {} self._group_widgets = {} @@ -1107,6 +1109,18 @@ class InstanceListView(_AbstractInstanceView): self.instance_model = instance_model self.proxy_model = proxy_model + def _on_expand(self, index): + family = index.data(SORT_VALUE_ROLE) + group_widget = self._group_widgets.get(family) + if group_widget: + group_widget.set_expanded(True) + + def _on_collapse(self, index): + family = index.data(SORT_VALUE_ROLE) + group_widget = self._group_widgets.get(family) + if group_widget: + group_widget.set_expanded(False) + def refresh(self): instances_by_family = collections.defaultdict(list) families = set() From 33c4fee06c94bdce996e1891c5faefd090196092 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 16 Jul 2021 16:36:08 +0200 Subject: [PATCH 201/736] catch expand requests --- openpype/tools/new_publisher/widgets.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 68050e6cbf..691bbc96f3 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -1151,8 +1151,9 @@ class InstanceListView(_AbstractInstanceView): group_item.row(), group_item.column() ) proxy_index = self.proxy_model.mapFromSource(index) - widget = InstanceListGroupWidget(family, self.instance_view) family = group_item.data(SORT_VALUE_ROLE) + widget = InstanceListGroupWidget(family, self.instance_view) + widget.expand_changed.connect(self._on_group_expand_request) self._group_widgets[family] = widget self.instance_view.setIndexWidget(proxy_index, widget) @@ -1289,3 +1290,14 @@ class InstanceListView(_AbstractInstanceView): def _on_selection_change(self, *_args): self.selection_changed.emit() + + def _on_group_expand_request(self, family, expanded): + group_item = self._group_items.get(family) + if not group_item: + return + + group_index = self.instance_model.index( + group_item.row(), group_item.column() + ) + proxy_index = self.proxy_model.mapFromSource(group_index) + self.instance_view.setExpanded(proxy_index, expanded) From 3c448429b0be11957a288a12ee810002daed0851 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 19 Jul 2021 13:54:21 +0200 Subject: [PATCH 202/736] added create folder to pipeline --- openpype/pipeline/__init__.py | 2 +- openpype/pipeline/create/__init__.py | 14 ++++++++++++++ openpype/pipeline/{ => create}/creator_plugins.py | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 openpype/pipeline/create/__init__.py rename openpype/pipeline/{ => create}/creator_plugins.py (99%) diff --git a/openpype/pipeline/__init__.py b/openpype/pipeline/__init__.py index 795681a8b4..8da4a01148 100644 --- a/openpype/pipeline/__init__.py +++ b/openpype/pipeline/__init__.py @@ -1,6 +1,6 @@ from .lib import attribute_definitions -from .creator_plugins import ( +from .create import ( BaseCreator, Creator, AutoCreator, diff --git a/openpype/pipeline/create/__init__.py b/openpype/pipeline/create/__init__.py new file mode 100644 index 0000000000..1061f1bca0 --- /dev/null +++ b/openpype/pipeline/create/__init__.py @@ -0,0 +1,14 @@ +from .creator_plugins import ( + BaseCreator, + Creator, + AutoCreator, + CreatedInstance +) + + +__all__ = ( + "BaseCreator", + "Creator", + "AutoCreator", + "CreatedInstance" +) diff --git a/openpype/pipeline/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py similarity index 99% rename from openpype/pipeline/creator_plugins.py rename to openpype/pipeline/create/creator_plugins.py index cd4bea58fe..cf21b188cb 100644 --- a/openpype/pipeline/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -10,7 +10,7 @@ from abc import ( ) import six -from .lib import UnknownDef +from ..lib import UnknownDef import avalon.api from openpype.lib import get_subset_name From c07dc2da863a097a6395b40a11d754ba0de055fa Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 19 Jul 2021 14:00:29 +0200 Subject: [PATCH 203/736] added id attribute to CreateInstance --- openpype/pipeline/create/creator_plugins.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index cf21b188cb..3604d9cd12 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -285,6 +285,10 @@ class CreatedInstance: if not self._data.get("uuid"): self._data["uuid"] = str(uuid4()) + @property + def id(self): + return self._data["uuid"] + @property def data(self): return self._data From 7a59d443d421bae2f02e2bbc7b0fa135fa29dfb7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 19 Jul 2021 14:02:17 +0200 Subject: [PATCH 204/736] added base concept of instance members --- openpype/pipeline/create/creator_plugins.py | 25 ++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 3604d9cd12..ee12ca295c 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -15,6 +15,22 @@ import avalon.api from openpype.lib import get_subset_name +class InstanceMember: + def __init__(self, instance, name): + self.instance = instance + + instance.add_members(self) + + self.name = name + self._actions = [] + + def add_action(self, label, callback): + self._actions.append({ + "label": label, + "callback": callback + }) + + class AttributeValues: def __init__(self, attr_defs, values, origin_data=None): if origin_data is None: @@ -233,6 +249,8 @@ class CreatedInstance: self.family = family # Subset name self.subset_name = subset_name + # Instance members may have actions on them + self._members = [] # Create a copy of passed data to avoid changing them on the fly data = copy.deepcopy(data or {}) @@ -254,7 +272,7 @@ class CreatedInstance: self._data["subset"] = subset_name self._data["active"] = data.get("active", True) - # Schema or version? + # QUESTION handle version of instance here or in creator? if new: self._data["version"] = 1 else: @@ -370,6 +388,11 @@ class CreatedInstance: def set_publish_plugins(self, attr_plugins): self._data["publish_attributes"].set_publish_plugins(attr_plugins) + def add_members(self, members): + for member in members: + if member not in self._members: + self._members.append(member) + @six.add_metaclass(ABCMeta) class BaseCreator: From d9224fcd9a932636a239b63a23b993bb19312a38 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 19 Jul 2021 14:02:42 +0200 Subject: [PATCH 205/736] removed unused change_order --- openpype/pipeline/create/creator_plugins.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index ee12ca295c..06d09f3108 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -360,15 +360,6 @@ class CreatedInstance: return output - def change_order(self, keys_order): - data = collections.OrderedDict() - for key in keys_order: - if key in self.data: - data[key] = self.data.pop(key) - - for key in tuple(self.data.keys()): - data[key] = self.data.pop(key) - self.data = data @classmethod def from_existing( From 41f0f6b1e8c0fe9a69744027f8d2dcaf152335fd Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 19 Jul 2021 14:07:58 +0200 Subject: [PATCH 206/736] moved create context classes to different file --- openpype/pipeline/create/context.py | 375 ++++++++++++++++++++ openpype/pipeline/create/creator_plugins.py | 374 ------------------- 2 files changed, 375 insertions(+), 374 deletions(-) create mode 100644 openpype/pipeline/create/context.py diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py new file mode 100644 index 0000000000..13424f239d --- /dev/null +++ b/openpype/pipeline/create/context.py @@ -0,0 +1,375 @@ +import copy +import collections +from uuid import uuid4 + +from ..lib import UnknownDef +import avalon.api + + +class InstanceMember: + def __init__(self, instance, name): + self.instance = instance + + instance.add_members(self) + + self.name = name + self._actions = [] + + def add_action(self, label, callback): + self._actions.append({ + "label": label, + "callback": callback + }) + + +class AttributeValues: + def __init__(self, attr_defs, values, origin_data=None): + if origin_data is None: + origin_data = copy.deepcopy(values) + self._origin_data = origin_data + + attr_defs_by_key = { + attr_def.key: attr_def + for attr_def in attr_defs + } + for key, value in values.items(): + if key not in attr_defs_by_key: + new_def = UnknownDef(key, label=key, default=value) + attr_defs.append(new_def) + attr_defs_by_key[key] = new_def + + self._attr_defs = attr_defs + self._attr_defs_by_key = attr_defs_by_key + + self._data = {} + for attr_def in attr_defs: + value = values.get(attr_def.key) + if value is not None: + self._data[attr_def.key] = value + + def __setitem__(self, key, value): + if key not in self._attr_defs_by_key: + raise KeyError("Key \"{}\" was not found.".format(key)) + + old_value = self._data.get(key) + if old_value == value: + return + self._data[key] = value + + def __getitem__(self, key): + if key not in self._attr_defs_by_key: + return self._data[key] + return self._data.get(key, self._attr_defs_by_key[key].default) + + def __contains__(self, key): + return key in self._attr_defs_by_key + + def get(self, key, default=None): + if key in self._attr_defs_by_key: + return self[key] + return default + + def keys(self): + return self._attr_defs_by_key.keys() + + def values(self): + for key in self._attr_defs_by_key.keys(): + yield self._data.get(key) + + def items(self): + for key in self._attr_defs_by_key.keys(): + yield key, self._data.get(key) + + def update(self, value): + for _key, _value in dict(value): + self[_key] = _value + + def pop(self, key, default=None): + return self._data.pop(key, default) + + @property + def attr_defs(self): + return self._attr_defs + + def data_to_store(self): + output = {} + for key in self._data: + output[key] = self[key] + return output + + @staticmethod + def calculate_changes(new_data, old_data): + changes = {} + for key, new_value in new_data.items(): + old_value = old_data.get(key) + if old_value != new_value: + changes[key] = (old_value, new_value) + return changes + + def changes(self): + return self.calculate_changes(self._data, self._origin_data) + + +class FamilyAttributeValues(AttributeValues): + def __init__(self, instance, *args, **kwargs): + self.instance = instance + super(FamilyAttributeValues, self).__init__(*args, **kwargs) + + +class PublishAttributeValues(AttributeValues): + def __init__(self, publish_attributes, *args, **kwargs): + self.publish_attributes = publish_attributes + super(PublishAttributeValues, self).__init__(*args, **kwargs) + + @property + def instance(self): + self.publish_attributes.instance + + +class PublishAttributes: + def __init__(self, instance, origin_data, attr_plugins=None): + self.instance = instance + self._origin_data = copy.deepcopy(origin_data) + + attr_plugins = attr_plugins or [] + self.attr_plugins = attr_plugins + + self._data = {} + self._plugin_names_order = [] + data = copy.deepcopy(origin_data) + for plugin in attr_plugins: + data = plugin.convert_attribute_values(data) + attr_defs = plugin.get_attribute_defs() + if not attr_defs: + continue + + key = plugin.__name__ + self._plugin_names_order.append(key) + + value = data.get(key) or {} + orig_value = copy.deepcopy(origin_data.get(key) or {}) + self._data[key] = PublishAttributeValues( + self, attr_defs, value, orig_value + ) + + for key, value in origin_data.items(): + if key not in self._data: + orig_value = copy.deepcopy(value) + self._data[key] = PublishAttributeValues( + self, attr_defs, value, orig_value + ) + + def __getitem__(self, key): + return self._data[key] + + def __contains__(self, key): + return key in self._data + + def keys(self): + return self._data.keys() + + def values(self): + return self._data.values() + + def items(self): + return self._data.items() + + def pop(self, key, default=None): + # TODO implement + if key not in self._data: + return default + + # if key not in self._plugin_keys: + + def plugin_names_order(self): + for name in self._plugin_names_order: + yield name + + def data_to_store(self): + output = {} + for key, attr_value in self._data.items(): + output[key] = attr_value.data_to_store() + return output + + def changes(self): + changes = {} + for key, attr_val in self._data.items(): + attr_changes = attr_val.changes() + if attr_changes: + if key not in changes: + changes[key] = {} + changes[key].update(attr_val) + + for key, value in self._origin_data.items(): + if key not in self._data: + changes[key] = (value, None) + return changes + + def set_publish_plugins(self, attr_plugins): + # TODO implement + self.attr_plugins = attr_plugins or [] + for plugin in attr_plugins: + attr_defs = plugin.get_attribute_defs() + if not attr_defs: + continue + + +class CreatedInstance: + """Instance entity with data that will be stored to workfile. + + I think `data` must be required argument containing all minimum information + about instance like "asset" and "task" and all data used for filling subset + name as creators may have custom data for subset name filling. + + Args: + family(str): Name of family that will be created. + subset_name(str): Name of subset that will be created. + data(dict): Data used for filling subset name or override data from + already existing instance. + """ + def __init__( + self, family, subset_name, data=None, creator=None, host=None, + attr_plugins=None, new=True + ): + if not host: + host = avalon.api.registered_host() + self.host = host + self.creator = creator + + # Family of instance + self.family = family + # Subset name + self.subset_name = subset_name + # Instance members may have actions on them + self._members = [] + + # Create a copy of passed data to avoid changing them on the fly + data = copy.deepcopy(data or {}) + # Store original value of passed data + self._orig_data = copy.deepcopy(data) + + # Pop family and subset to preved unexpected changes + data.pop("family", None) + data.pop("subset", None) + + # Pop dictionary values that will be converted to objects to be able + # catch changes + orig_family_attributes = data.pop("family_attributes", None) or {} + orig_publish_attributes = data.pop("publish_attributes", None) or {} + + self._data = collections.OrderedDict() + self._data["id"] = "pyblish.avalon.instance" + self._data["family"] = family + self._data["subset"] = subset_name + self._data["active"] = data.get("active", True) + + # QUESTION handle version of instance here or in creator? + if new: + self._data["version"] = 1 + else: + self._data["version"] = data.get("version") + + # Stored family specific attribute values + # {key: value} + new_family_values = copy.deepcopy(orig_family_attributes) + family_attr_defs = [] + if creator is not None: + new_family_values = creator.convert_family_attribute_values( + new_family_values + ) + family_attr_defs = creator.get_attribute_defs() + + self._data["family_attributes"] = FamilyAttributeValues( + self, family_attr_defs, new_family_values, orig_family_attributes + ) + + # Stored publish specific attribute values + # {: {key: value}} + self._data["publish_attributes"] = PublishAttributes( + self, orig_publish_attributes, attr_plugins + ) + if data: + self._data.update(data) + + if not self._data.get("uuid"): + self._data["uuid"] = str(uuid4()) + + @property + def id(self): + return self._data["uuid"] + + @property + def data(self): + return self._data + + def changes(self): + changes = {} + new_keys = set() + for key, new_value in self._data.items(): + new_keys.add(key) + if key in ("family_attributes", "publish_attributes"): + continue + + old_value = self._orig_data.get(key) + if old_value != new_value: + changes[key] = (old_value, new_value) + + family_attributes = self.data["family_attributes"] + family_attr_changes = family_attributes.changes() + if family_attr_changes: + changes["family_attributes"] = family_attr_changes + + publish_attr_changes = self.publish_attributes.changes() + if publish_attr_changes: + changes["publish_attributes"] = publish_attr_changes + + for key, old_value in self._orig_data.items(): + if key not in new_keys: + changes[key] = (old_value, None) + return changes + + @property + def family_attribute_defs(self): + return self._data["family_attributes"].attr_defs + + @property + def publish_attributes(self): + return self._data["publish_attributes"] + + def data_to_store(self): + output = collections.OrderedDict() + for key, value in self._data.items(): + if key in ("family_attributes", "publish_attributes"): + continue + output[key] = value + + family_attributes = self._data["family_attributes"] + output["family_attributes"] = family_attributes.data_to_store() + + publish_attributes = self._data["publish_attributes"] + output["publish_attributes"] = publish_attributes.data_to_store() + + return output + + @classmethod + def from_existing( + cls, instance_data, creator=None, host=None, attr_plugins=None + ): + """Convert instance data from workfile to CreatedInstance.""" + instance_data = copy.deepcopy(instance_data) + + family = instance_data.get("family", None) + subset_name = instance_data.get("subset", None) + + return cls( + family, subset_name, instance_data, creator, host, + attr_plugins, new=False + ) + + def set_publish_plugins(self, attr_plugins): + self._data["publish_attributes"].set_publish_plugins(attr_plugins) + + def add_members(self, members): + for member in members: + if member not in self._members: + self._members.append(member) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 06d09f3108..33ad35d9d3 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -1,7 +1,5 @@ import copy import logging -import collections -from uuid import uuid4 from abc import ( ABCMeta, @@ -10,381 +8,9 @@ from abc import ( ) import six -from ..lib import UnknownDef -import avalon.api from openpype.lib import get_subset_name -class InstanceMember: - def __init__(self, instance, name): - self.instance = instance - - instance.add_members(self) - - self.name = name - self._actions = [] - - def add_action(self, label, callback): - self._actions.append({ - "label": label, - "callback": callback - }) - - -class AttributeValues: - def __init__(self, attr_defs, values, origin_data=None): - if origin_data is None: - origin_data = copy.deepcopy(values) - self._origin_data = origin_data - - attr_defs_by_key = { - attr_def.key: attr_def - for attr_def in attr_defs - } - for key, value in values.items(): - if key not in attr_defs_by_key: - new_def = UnknownDef(key, label=key, default=value) - attr_defs.append(new_def) - attr_defs_by_key[key] = new_def - - self._attr_defs = attr_defs - self._attr_defs_by_key = attr_defs_by_key - - self._data = {} - for attr_def in attr_defs: - value = values.get(attr_def.key) - if value is not None: - self._data[attr_def.key] = value - - def __setitem__(self, key, value): - if key not in self._attr_defs_by_key: - raise KeyError("Key \"{}\" was not found.".format(key)) - - old_value = self._data.get(key) - if old_value == value: - return - self._data[key] = value - - def __getitem__(self, key): - if key not in self._attr_defs_by_key: - return self._data[key] - return self._data.get(key, self._attr_defs_by_key[key].default) - - def __contains__(self, key): - return key in self._attr_defs_by_key - - def get(self, key, default=None): - if key in self._attr_defs_by_key: - return self[key] - return default - - def keys(self): - return self._attr_defs_by_key.keys() - - def values(self): - for key in self._attr_defs_by_key.keys(): - yield self._data.get(key) - - def items(self): - for key in self._attr_defs_by_key.keys(): - yield key, self._data.get(key) - - def update(self, value): - for _key, _value in dict(value): - self[_key] = _value - - def pop(self, key, default=None): - return self._data.pop(key, default) - - @property - def attr_defs(self): - return self._attr_defs - - def data_to_store(self): - output = {} - for key in self._data: - output[key] = self[key] - return output - - @staticmethod - def calculate_changes(new_data, old_data): - changes = {} - for key, new_value in new_data.items(): - old_value = old_data.get(key) - if old_value != new_value: - changes[key] = (old_value, new_value) - return changes - - def changes(self): - return self.calculate_changes(self._data, self._origin_data) - - -class FamilyAttributeValues(AttributeValues): - def __init__(self, instance, *args, **kwargs): - self.instance = instance - super(FamilyAttributeValues, self).__init__(*args, **kwargs) - - -class PublishAttributeValues(AttributeValues): - def __init__(self, publish_attributes, *args, **kwargs): - self.publish_attributes = publish_attributes - super(PublishAttributeValues, self).__init__(*args, **kwargs) - - @property - def instance(self): - self.publish_attributes.instance - - -class PublishAttributes: - def __init__(self, instance, origin_data, attr_plugins=None): - self.instance = instance - self._origin_data = copy.deepcopy(origin_data) - - attr_plugins = attr_plugins or [] - self.attr_plugins = attr_plugins - - self._data = {} - self._plugin_names_order = [] - data = copy.deepcopy(origin_data) - for plugin in attr_plugins: - data = plugin.convert_attribute_values(data) - attr_defs = plugin.get_attribute_defs() - if not attr_defs: - continue - - key = plugin.__name__ - self._plugin_names_order.append(key) - - value = data.get(key) or {} - orig_value = copy.deepcopy(origin_data.get(key) or {}) - self._data[key] = PublishAttributeValues( - self, attr_defs, value, orig_value - ) - - for key, value in origin_data.items(): - if key not in self._data: - orig_value = copy.deepcopy(value) - self._data[key] = PublishAttributeValues( - self, attr_defs, value, orig_value - ) - - def __getitem__(self, key): - return self._data[key] - - def __contains__(self, key): - return key in self._data - - def keys(self): - return self._data.keys() - - def values(self): - return self._data.values() - - def items(self): - return self._data.items() - - def pop(self, key, default=None): - # TODO implement - if key not in self._data: - return default - - # if key not in self._plugin_keys: - - def plugin_names_order(self): - for name in self._plugin_names_order: - yield name - - def data_to_store(self): - output = {} - for key, attr_value in self._data.items(): - output[key] = attr_value.data_to_store() - return output - - def changes(self): - changes = {} - for key, attr_val in self._data.items(): - attr_changes = attr_val.changes() - if attr_changes: - if key not in changes: - changes[key] = {} - changes[key].update(attr_val) - - for key, value in self._origin_data.items(): - if key not in self._data: - changes[key] = (value, None) - return changes - - def set_publish_plugins(self, attr_plugins): - # TODO implement - self.attr_plugins = attr_plugins or [] - for plugin in attr_plugins: - attr_defs = plugin.get_attribute_defs() - if not attr_defs: - continue - - -class CreatedInstance: - """Instance entity with data that will be stored to workfile. - - I think `data` must be required argument containing all minimum information - about instance like "asset" and "task" and all data used for filling subset - name as creators may have custom data for subset name filling. - - Args: - family(str): Name of family that will be created. - subset_name(str): Name of subset that will be created. - data(dict): Data used for filling subset name or override data from - already existing instance. - """ - def __init__( - self, family, subset_name, data=None, creator=None, host=None, - attr_plugins=None, new=True - ): - if not host: - host = avalon.api.registered_host() - self.host = host - self.creator = creator - - # Family of instance - self.family = family - # Subset name - self.subset_name = subset_name - # Instance members may have actions on them - self._members = [] - - # Create a copy of passed data to avoid changing them on the fly - data = copy.deepcopy(data or {}) - # Store original value of passed data - self._orig_data = copy.deepcopy(data) - - # Pop family and subset to preved unexpected changes - data.pop("family", None) - data.pop("subset", None) - - # Pop dictionary values that will be converted to objects to be able - # catch changes - orig_family_attributes = data.pop("family_attributes", None) or {} - orig_publish_attributes = data.pop("publish_attributes", None) or {} - - self._data = collections.OrderedDict() - self._data["id"] = "pyblish.avalon.instance" - self._data["family"] = family - self._data["subset"] = subset_name - self._data["active"] = data.get("active", True) - - # QUESTION handle version of instance here or in creator? - if new: - self._data["version"] = 1 - else: - self._data["version"] = data.get("version") - - # Stored family specific attribute values - # {key: value} - new_family_values = copy.deepcopy(orig_family_attributes) - family_attr_defs = [] - if creator is not None: - new_family_values = creator.convert_family_attribute_values( - new_family_values - ) - family_attr_defs = creator.get_attribute_defs() - - self._data["family_attributes"] = FamilyAttributeValues( - self, family_attr_defs, new_family_values, orig_family_attributes - ) - - # Stored publish specific attribute values - # {: {key: value}} - self._data["publish_attributes"] = PublishAttributes( - self, orig_publish_attributes, attr_plugins - ) - if data: - self._data.update(data) - - if not self._data.get("uuid"): - self._data["uuid"] = str(uuid4()) - - @property - def id(self): - return self._data["uuid"] - - @property - def data(self): - return self._data - - def changes(self): - changes = {} - new_keys = set() - for key, new_value in self._data.items(): - new_keys.add(key) - if key in ("family_attributes", "publish_attributes"): - continue - - old_value = self._orig_data.get(key) - if old_value != new_value: - changes[key] = (old_value, new_value) - - family_attributes = self.data["family_attributes"] - family_attr_changes = family_attributes.changes() - if family_attr_changes: - changes["family_attributes"] = family_attr_changes - - publish_attr_changes = self.publish_attributes.changes() - if publish_attr_changes: - changes["publish_attributes"] = publish_attr_changes - - for key, old_value in self._orig_data.items(): - if key not in new_keys: - changes[key] = (old_value, None) - return changes - - @property - def family_attribute_defs(self): - return self._data["family_attributes"].attr_defs - - @property - def publish_attributes(self): - return self._data["publish_attributes"] - - def data_to_store(self): - output = collections.OrderedDict() - for key, value in self._data.items(): - if key in ("family_attributes", "publish_attributes"): - continue - output[key] = value - - family_attributes = self._data["family_attributes"] - output["family_attributes"] = family_attributes.data_to_store() - - publish_attributes = self._data["publish_attributes"] - output["publish_attributes"] = publish_attributes.data_to_store() - - return output - - - @classmethod - def from_existing( - cls, instance_data, creator=None, host=None, attr_plugins=None - ): - """Convert instance data from workfile to CreatedInstance.""" - instance_data = copy.deepcopy(instance_data) - - family = instance_data.get("family", None) - subset_name = instance_data.get("subset", None) - - return cls( - family, subset_name, instance_data, creator, host, - attr_plugins, new=False - ) - - def set_publish_plugins(self, attr_plugins): - self._data["publish_attributes"].set_publish_plugins(attr_plugins) - - def add_members(self, members): - for member in members: - if member not in self._members: - self._members.append(member) - - @six.add_metaclass(ABCMeta) class BaseCreator: """Plugin that create and modify instance data before publishing process. From 21fc9ba0867a8eeac925aa171980ac87e588f1e8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 19 Jul 2021 15:21:44 +0200 Subject: [PATCH 207/736] added base of CreateContext --- openpype/pipeline/create/__init__.py | 12 ++- openpype/pipeline/create/context.py | 118 ++++++++++++++++++++++++++- 2 files changed, 126 insertions(+), 4 deletions(-) diff --git a/openpype/pipeline/create/__init__.py b/openpype/pipeline/create/__init__.py index 1061f1bca0..981f4917a2 100644 --- a/openpype/pipeline/create/__init__.py +++ b/openpype/pipeline/create/__init__.py @@ -1,8 +1,12 @@ from .creator_plugins import ( BaseCreator, Creator, - AutoCreator, - CreatedInstance + AutoCreator +) + +from .context import ( + CreatedInstance, + CreateContext ) @@ -10,5 +14,7 @@ __all__ = ( "BaseCreator", "Creator", "AutoCreator", - "CreatedInstance" + + "CreatedInstance", + "CreateContext" ) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 13424f239d..8b2f4573a6 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1,9 +1,16 @@ import copy +import logging import collections +import inspect from uuid import uuid4 from ..lib import UnknownDef -import avalon.api +from .creator_plugins import BaseCreator + +from openpype.api import ( + get_system_settings, + get_project_settings +) class InstanceMember: @@ -232,6 +239,8 @@ class CreatedInstance: attr_plugins=None, new=True ): if not host: + import avalon.api + host = avalon.api.registered_host() self.host = host self.creator = creator @@ -373,3 +382,110 @@ class CreatedInstance: for member in members: if member not in self._members: self._members.append(member) + + +class CreateContext: + def __init__(self, host, dbcon=None, headless=False, reset=True): + if dbcon is None: + import avalon.api + + session = avalon.api.session_data_from_environment(True) + dbcon = avalon.api.AvalonMongoDB(session) + dbcon.install() + + self.dbcon = dbcon + + self.host = host + self.headless = headless + + self.instances = [] + + self.creators = {} + self.publish_plugins = [] + self.plugins_with_defs = [] + self.attr_plugins_by_family = {} + + self._log = None + + if reset: + self.reset() + + @property + def log(self): + if self._log is None: + self._log = logging.getLogger(self.__class__.__name__) + return self._log + + def reset(self): + self.reset_plugins() + self.reset_instances() + + def reset_plugins(self): + import avalon.api + import pyblish.api + + from openpype.pipeline import OpenPypePyblishPluginMixin + + # Reset publish plugins + self.attr_plugins_by_family = {} + + publish_plugins = pyblish.api.discover() + self.publish_plugins = publish_plugins + + # Collect plugins that can have attribute definitions + plugins_with_defs = [] + for plugin in publish_plugins: + if OpenPypePyblishPluginMixin in inspect.getmro(plugin): + plugins_with_defs.append(plugin) + self.plugins_with_defs = plugins_with_defs + + # Prepare settings + project_name = self.dbcon.Session["AVALON_PROJECT"] + system_settings = get_system_settings() + project_settings = get_project_settings(project_name) + + # Discover and prepare creators + creators = {} + for creator in avalon.api.discover(BaseCreator): + if inspect.isabstract(creator): + self.log.info( + "Skipping abstract Creator {}".format(str(creator)) + ) + continue + creators[creator.family] = creator( + self, + system_settings, + project_settings, + self.headless + ) + + self.creators = creators + + def reset_instances(self): + # Collect instances + host_instances = self.host.list_instances() + instances = [] + for instance_data in host_instances: + family = instance_data["family"] + # Prepare publish plugins with attribute definitions + creator = self.creators.get(family) + attr_plugins = self._get_publish_plugins_with_attr_for_family( + family + ) + instance = CreatedInstance.from_existing( + instance_data, creator, self.host, attr_plugins + ) + instances.append(instance) + + self.instances = instances + + def _get_publish_plugins_with_attr_for_family(self, family): + if family not in self.attr_plugins_by_family: + import pyblish.logic + + filtered_plugins = pyblish.logic.plugins_by_families( + self.plugins_with_defs, [family] + ) + self.attr_plugins_by_family[family] = filtered_plugins + + return self.attr_plugins_by_family[family] From 24a94a46b995bbecea23a5b7e86a8518d65ab191 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 19 Jul 2021 15:30:26 +0200 Subject: [PATCH 208/736] use createcontext in controller --- openpype/pipeline/create/creator_plugins.py | 7 +- openpype/tools/new_publisher/control.py | 103 ++++++-------------- 2 files changed, 35 insertions(+), 75 deletions(-) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 33ad35d9d3..a5c98e45df 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -33,7 +33,12 @@ class BaseCreator: # - default_variants may not be used if `get_default_variants` is overriden default_variants = [] - def __init__(self, system_settings, project_settings, headless=False): + def __init__( + self, create_context, system_settings, project_settings, headless=False + ): + # Reference to CreateContext + self.create_context = create_context + # Creator is running in headless mode (without UI elemets) # - we may use UI inside processing this attribute should be checked self.headless = headless diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index fb44951083..e42f3d3d27 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -8,9 +8,11 @@ from openpype.api import ( get_project_settings ) from openpype.pipeline import ( - OpenPypePyblishPluginMixin, + OpenPypePyblishPluginMixin +) +from openpype.pipeline.create import ( BaseCreator, - CreatedInstance + CreateContext ) @@ -20,26 +22,36 @@ class PublisherController: self.host = avalon.api.registered_host() self.headless = headless - if dbcon is None: - session = avalon.api.session_data_from_environment(True) - dbcon = avalon.api.AvalonMongoDB(session) - dbcon.install() - self.dbcon = dbcon + self.create_context = CreateContext( + self.host, dbcon, headless=False, reset=False + ) self._instances_refresh_callback_refs = set() self._plugins_refresh_callback_refs = set() - self.creators = {} - - self.publish_plugins = [] - self.plugins_with_defs = [] - self._attr_plugins_by_family = {} - - self.instances = [] - self._resetting_plugins = False self._resetting_instances = False + @property + def dbcon(self): + return self.create_context.dbcon + + @property + def instances(self): + return self.create_context.instances + + @property + def creators(self): + return self.create_context.creators + + @property + def publish_plugins(self): + return self.create_context.publish_plugins + + @property + def plugins_with_defs(self): + return self.create_context.plugins_with_defs + def add_instances_refresh_callback(self, callback): ref = weakref.WeakMethod(callback) self._instances_refresh_callback_refs.add(ref) @@ -65,15 +77,6 @@ class PublisherController: self._reset_plugin() self._reset_instances() - def _get_publish_plugins_with_attr_for_family(self, family): - if family not in self._attr_plugins_by_family: - filtered_plugins = pyblish.logic.plugins_by_families( - self.plugins_with_defs, [family] - ) - self._attr_plugins_by_family[family] = filtered_plugins - - return self._attr_plugins_by_family[family] - def _reset_plugin(self): """Reset to initial state.""" if self._resetting_plugins: @@ -81,39 +84,7 @@ class PublisherController: self._resetting_plugins = True - # Reset publish plugins - self._attr_plugins_by_family = {} - - publish_plugins = pyblish.api.discover() - self.publish_plugins = publish_plugins - - # Collect plugins that can have attribute definitions - plugins_with_defs = [] - for plugin in publish_plugins: - if OpenPypePyblishPluginMixin in inspect.getmro(plugin): - plugins_with_defs.append(plugin) - self.plugins_with_defs = plugins_with_defs - - # Prepare settings - project_name = self.dbcon.Session["AVALON_PROJECT"] - system_settings = get_system_settings() - project_settings = get_project_settings(project_name) - - # Discover and prepare creators - creators = {} - for creator in avalon.api.discover(BaseCreator): - if inspect.isabstract(creator): - self.log.info( - "Skipping abstract Creator {}".format(str(creator)) - ) - continue - creators[creator.family] = creator( - system_settings, - project_settings, - self.headless - ) - - self.creators = creators + self.create_context.reset_plugins() self._resetting_plugins = False @@ -125,23 +96,7 @@ class PublisherController: self._resetting_instances = True - # Collect instances - host_instances = self.host.list_instances() - instances = [] - for instance_data in host_instances: - family = instance_data["family"] - # Prepare publish plugins with attribute definitions - - creator = self.creators.get(family) - attr_plugins = self._get_publish_plugins_with_attr_for_family( - family - ) - instance = CreatedInstance.from_existing( - instance_data, creator, self.host, attr_plugins - ) - instances.append(instance) - - self.instances = instances + self.create_context.reset_instances() self._resetting_instances = False From 233b1e0b4878ced9d331e0ffffffbe1f74f9bf8d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 22 Jul 2021 12:13:10 +0200 Subject: [PATCH 209/736] changed attr_plugins_by_family attribute to private --- openpype/pipeline/create/context.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 8b2f4573a6..910221dbf9 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -403,7 +403,7 @@ class CreateContext: self.creators = {} self.publish_plugins = [] self.plugins_with_defs = [] - self.attr_plugins_by_family = {} + self._attr_plugins_by_family = {} self._log = None @@ -427,7 +427,7 @@ class CreateContext: from openpype.pipeline import OpenPypePyblishPluginMixin # Reset publish plugins - self.attr_plugins_by_family = {} + self._attr_plugins_by_family = {} publish_plugins = pyblish.api.discover() self.publish_plugins = publish_plugins @@ -480,12 +480,12 @@ class CreateContext: self.instances = instances def _get_publish_plugins_with_attr_for_family(self, family): - if family not in self.attr_plugins_by_family: + if family not in self._attr_plugins_by_family: import pyblish.logic filtered_plugins = pyblish.logic.plugins_by_families( self.plugins_with_defs, [family] ) - self.attr_plugins_by_family[family] = filtered_plugins + self._attr_plugins_by_family[family] = filtered_plugins - return self.attr_plugins_by_family[family] + return self._attr_plugins_by_family[family] From 6101c095cf401235a4a672d41dbda39677c8aeef Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 22 Jul 2021 12:13:35 +0200 Subject: [PATCH 210/736] Changed labels of buttons --- openpype/tools/new_publisher/window.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index dd72b6515e..1f28cfb47e 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -74,8 +74,8 @@ class PublisherWindow(QtWidgets.QWidget): subset_list_view.setVisible(False) # Buttons at the bottom of subset view - create_btn = QtWidgets.QPushButton("Create", subset_widget) - delete_btn = QtWidgets.QPushButton("Delete", subset_widget) + create_btn = QtWidgets.QPushButton("+", subset_widget) + delete_btn = QtWidgets.QPushButton("-", subset_widget) save_btn = QtWidgets.QPushButton("Save", subset_widget) change_view_btn = QtWidgets.QPushButton("=", subset_widget) From c6f95ee5604c70c8395b618e78c3d137d0f7a4e9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 22 Jul 2021 12:13:47 +0200 Subject: [PATCH 211/736] added spaces between imports --- openpype/tools/new_publisher/control.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index e42f3d3d27..5ca8da5540 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -7,9 +7,11 @@ from openpype.api import ( get_system_settings, get_project_settings ) + from openpype.pipeline import ( OpenPypePyblishPluginMixin ) + from openpype.pipeline.create import ( BaseCreator, CreateContext From 9b0339246bb3a95204d7521ae0d3a53a4869c413 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 22 Jul 2021 14:31:23 +0200 Subject: [PATCH 212/736] implemented base of publishing --- openpype/tools/new_publisher/control.py | 152 +++++++++++++++++++++++- openpype/tools/new_publisher/window.py | 4 +- 2 files changed, 152 insertions(+), 4 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 5ca8da5540..3922360010 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -1,6 +1,7 @@ import weakref import logging import inspect +import collections import avalon.api import pyblish.api from openpype.api import ( @@ -17,6 +18,57 @@ from openpype.pipeline.create import ( CreateContext ) +from Qt import QtCore + + +PLUGIN_ORDER_OFFSET = 0.5 + + +class MainThreadItem: + def __init__(self, callback, *args, **kwargs): + self.callback = callback + self.args = args + self.kwargs = kwargs + + def process(self): + self.callback(*self.args, **self.kwargs) + + +class MainThreadProcess(QtCore.QObject): + def __init__(self): + super(MainThreadProcess, self).__init__() + self._items_to_process = collections.deque() + + timer = QtCore.QTimer() + timer.setInterval(50) + + timer.timeout.connect(self._execute) + + self._timer = timer + + def add_item(self, item): + self._items_to_process.append(item) + + def _execute(self): + if not self._items_to_process: + return + + item = self._items_to_process.popleft() + item.process() + + def start(self): + if not self._timer.isActive(): + self._timer.start() + + def stop(self): + if self._timer.isActive(): + self._timer.stop() + + def clear(self): + if self._timer.isActive(): + self._timer.stop() + self._items_to_process = collections.deque() + class PublisherController: def __init__(self, dbcon=None, headless=False): @@ -28,6 +80,15 @@ class PublisherController: self.host, dbcon, headless=False, reset=False ) + self._publish_context = None + self._publish_validated = False + self._publish_up_validation = False + self._validation_order = ( + pyblish.api.ValidatorOrder + PLUGIN_ORDER_OFFSET + ) + self._main_thread_processor = MainThreadProcess() + self._main_thread_iter = None + self._instances_refresh_callback_refs = set() self._plugins_refresh_callback_refs = set() @@ -76,10 +137,12 @@ class PublisherController: callbacks.remove(ref) def reset(self): - self._reset_plugin() + self._reset_plugins() + # Publish part must be resetted after plugins + self._reset_publish() self._reset_instances() - def _reset_plugin(self): + def _reset_plugins(self): """Reset to initial state.""" if self._resetting_plugins: return @@ -185,3 +248,88 @@ class PublisherController: self.host.remove_instances(instances) self._reset_instances() + + def _reset_publish(self): + self._publish_validated = False + self._publish_up_validation = False + self._main_thread_processor.clear() + self._main_thread_iter = self._publish_iterator() + self._publish_context = pyblish.api.Context() + + def validate(self): + if self._publish_validated: + return + self._publish_up_validation = True + self._start_publish() + + def publish(self): + self._publish_up_validation = False + self._start_publish() + + def _start_publish(self): + self._main_thread_processor.start() + self._publish_next_process() + + def _stop_publish(self): + self._main_thread_processor.stop() + + def _publish_next_process(self): + item = next(self._main_thread_iter) + self._main_thread_processor.add_item(item) + + def _publish_iterator(self): + for plugin in self.publish_plugins: + if ( + self._publish_up_validation + and plugin.order >= self._validation_order + ): + yield MainThreadItem(self._stop_publish) + + if plugin.__instanceEnabled__: + instances = pyblish.logic.instances_by_plugin( + self._publish_context, plugin + ) + if not instances: + continue + + for instance in instances: + if instance.data.get("publish") is False: + continue + + yield MainThreadItem( + self._process_and_continue, plugin, instance + ) + else: + families = collect_families_from_instances( + self._publish_context, only_active=True + ) + plugins = pyblish.logic.plugins_by_families( + [plugin], families + ) + if plugins: + yield MainThreadItem( + self._process_and_continue, plugin, None + ) + yield MainThreadItem(self._stop_publish) + + def _process_and_continue(self, plugin, instance): + # TODO execute plugin + print(plugin, instance) + self._publish_next_process() + + +def collect_families_from_instances(instances, only_active=False): + all_families = set() + for instance in instances: + if only_active: + if instance.data.get("publish") is False: + continue + family = instance.data.get("family") + if family: + all_families.add(family) + + families = instance.data.get("families") or tuple() + for family in families: + all_families.add(family) + + return list(all_families) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 1f28cfb47e..49eb2a67b1 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -271,10 +271,10 @@ class PublisherWindow(QtWidgets.QWidget): self.controller.save_instance_changes() def _on_validate_clicked(self): - print("Validation!!!") + self.controller.validate() def _on_publish_clicked(self): - print("Publishing!!!") + self.controller.publish() def _refresh_instances(self): if self._refreshing_instances: From 3178af7cadcd488eb6321b5bf18f6f8a39405373 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 22 Jul 2021 16:55:51 +0200 Subject: [PATCH 213/736] added first version of status overlay --- openpype/tools/new_publisher/widgets.py | 66 +++++++++++++++++++++++++ openpype/tools/new_publisher/window.py | 32 ++++++++++++ 2 files changed, 98 insertions(+) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 691bbc96f3..81cfc7f8e8 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -1301,3 +1301,69 @@ class InstanceListView(_AbstractInstanceView): ) proxy_index = self.proxy_model.mapFromSource(group_index) self.instance_view.setExpanded(proxy_index, expanded) + + +class PublishOverlayFrame(QtWidgets.QWidget): + def __init__(self, parent): + super(PublishOverlayFrame, self).__init__(parent) + + info_frame = QtWidgets.QFrame(self) + info_frame.setObjectName("PublishOverlay") + + content_widget = QtWidgets.QWidget(info_frame) + content_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + + info_layout = QtWidgets.QVBoxLayout(info_frame) + info_layout.setContentsMargins(0, 0, 0, 0) + info_layout.addWidget(content_widget) + + main_label = QtWidgets.QLabel("Publishing...", content_widget) + main_label.setAlignment(QtCore.Qt.AlignCenter) + + instance_label = QtWidgets.QLabel("", content_widget) + instance_label.setAlignment( + QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter + ) + plugin_label = QtWidgets.QLabel("", content_widget) + plugin_label.setAlignment( + QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter + ) + instance_plugin_layout = QtWidgets.QHBoxLayout() + instance_plugin_layout.addWidget(instance_label, 1) + instance_plugin_layout.addWidget(plugin_label, 1) + + progress_widget = QtWidgets.QProgressBar(content_widget) + + content_layout = QtWidgets.QVBoxLayout(content_widget) + content_layout.setAlignment(QtCore.Qt.AlignCenter) + content_layout.addStretch(1) + content_layout.addWidget(main_label) + content_layout.addStretch(1) + content_layout.addLayout(instance_plugin_layout) + content_layout.addWidget(progress_widget) + content_layout.addStretch(1) + + main_layout = QtWidgets.QVBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.addStretch(1) + main_layout.addWidget(info_frame, 2) + main_layout.addStretch(1) + + self.main_label = main_label + self.info_frame = info_frame + + self.instance_label = instance_label + self.plugin_label = plugin_label + self.progress_widget = progress_widget + + def set_instance(self, instance_name): + self.instance_label.setText(instance_name) + + def set_plugin(self, plugin_name): + self.plugin_label.setText(plugin_name) + + def set_progress_range(self, max_value): + self.progress_widget.setMaximum(max_value) + + def set_progress(self, value): + self.progress_widget.setValue(value) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 49eb2a67b1..327e957774 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -30,6 +30,7 @@ from Qt import QtWidgets from openpype import style from control import PublisherController from widgets import ( + PublishOverlayFrame, SubsetAttributesWidget, InstanceCardView, InstanceListView, @@ -52,6 +53,15 @@ class PublisherWindow(QtWidgets.QWidget): # TODO Title, Icon, Stylesheet main_frame = QtWidgets.QWidget(self) + # Overlay MUST be created after Main to be painted on top of it + overlay_frame = PublishOverlayFrame(self) + overlay_frame.setVisible(False) + + blur_effect = QtWidgets.QGraphicsBlurEffect(main_frame) + blur_effect.setBlurRadius(3) + blur_effect.setEnabled(False) + + main_frame.setGraphicsEffect(blur_effect) # Header header_widget = QtWidgets.QWidget(main_frame) @@ -154,6 +164,9 @@ class PublisherWindow(QtWidgets.QWidget): ) self.main_frame = main_frame + self.overlay_frame = overlay_frame + + self.blur_effect = blur_effect self.context_label = context_label @@ -185,6 +198,15 @@ class PublisherWindow(QtWidgets.QWidget): "////" ) + def resizeEvent(self, event): + super(PublisherWindow, self).resizeEvent(event) + + self.overlay_frame.resize(self.main_frame.size()) + + def moveEvent(self, event): + super(PublisherWindow, self).moveEvent(event) + self.overlay_frame.move(self.main_frame.pos()) + def showEvent(self, event): super(PublisherWindow, self).showEvent(event) if self._first_show: @@ -270,10 +292,20 @@ class PublisherWindow(QtWidgets.QWidget): def _on_save_clicked(self): self.controller.save_instance_changes() + def _show_overlay(self): + if self.overlay_frame.isVisible(): + return + + self.overlay_frame.setVisible(True) + + self.blur_effect.setEnabled(True) + def _on_validate_clicked(self): + self._show_overlay() self.controller.validate() def _on_publish_clicked(self): + self._show_overlay() self.controller.publish() def _refresh_instances(self): From 30decaecb71c56a6490d56ec39a007621b4219c4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 22 Jul 2021 17:24:29 +0200 Subject: [PATCH 214/736] added callbacks to be able change labels by current processing item --- openpype/tools/new_publisher/control.py | 31 ++++++++++++++++++++++++- openpype/tools/new_publisher/window.py | 30 ++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 3922360010..1e42aacbf3 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -91,6 +91,9 @@ class PublisherController: self._instances_refresh_callback_refs = set() self._plugins_refresh_callback_refs = set() + self._publish_instance_changed_callback_refs = set() + self._publish_plugin_changed_callback_refs = set() + self._publishing_stopped_callback_refs = set() self._resetting_plugins = False self._resetting_instances = False @@ -123,13 +126,25 @@ class PublisherController: ref = weakref.WeakMethod(callback) self._plugins_refresh_callback_refs.add(ref) + def add_instance_change_callback(self, callback): + ref = weakref.WeakMethod(callback) + self._publish_instance_changed_callback_refs.add(ref) + + def add_plugin_change_callback(self, callback): + ref = weakref.WeakMethod(callback) + self._publish_plugin_changed_callback_refs.add(ref) + + def add_publish_stopped_callback(self, callback): + ref = weakref.WeakMethod(callback) + self._publishing_stopped_callback_refs.add(ref) + def _trigger_callbacks(self, callbacks, *args, **kwargs): # Trigger reset callbacks to_remove = set() for ref in callbacks: callback = ref() if callback: - callback() + callback(*args, **kwargs) else: to_remove.add(ref) @@ -272,6 +287,7 @@ class PublisherController: def _stop_publish(self): self._main_thread_processor.stop() + self._trigger_callbacks(self._publishing_stopped_callback_refs) def _publish_next_process(self): item = next(self._main_thread_iter) @@ -285,6 +301,9 @@ class PublisherController: ): yield MainThreadItem(self._stop_publish) + self._trigger_callbacks( + self._publish_plugin_changed_callback_refs, plugin + ) if plugin.__instanceEnabled__: instances = pyblish.logic.instances_by_plugin( self._publish_context, plugin @@ -296,6 +315,11 @@ class PublisherController: if instance.data.get("publish") is False: continue + self._trigger_callbacks( + self._publish_instance_changed_callback_refs, + self._publish_context, + instance + ) yield MainThreadItem( self._process_and_continue, plugin, instance ) @@ -307,6 +331,11 @@ class PublisherController: [plugin], families ) if plugins: + self._trigger_callbacks( + self._publish_instance_changed_callback_refs, + self._publish_context, + None + ) yield MainThreadItem( self._process_and_continue, plugin, None ) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 327e957774..ce79ed3609 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -163,6 +163,10 @@ class PublisherWindow(QtWidgets.QWidget): self._on_subset_change ) + controller.add_instance_change_callback(self._on_instance_change) + controller.add_plugin_change_callback(self._on_plugin_change) + controller.add_publish_stopped_callback(self._on_publish_stop) + self.main_frame = main_frame self.overlay_frame = overlay_frame @@ -339,6 +343,32 @@ class PublisherWindow(QtWidgets.QWidget): self.subset_attributes_widget.set_current_instances(instances) + def _on_plugin_change(self, plugin): + plugin_name = plugin.__name__ + if hasattr(plugin, "label") and plugin.label: + plugin_name = plugin.label + self.overlay_frame.set_plugin(plugin_name) + + def _on_instance_change(self, context, instance): + if instance is None: + new_name = ( + context.data.get("label") + or getattr(context, "label", None) + or context.data.get("name") + or "Context" + ) + else: + new_name = ( + instance.data.get("label") + or getattr(instance, "label", None) + or instance.data["name"] + ) + + self.overlay_frame.set_instance(new_name) + + def _on_publish_stop(self): + pass + def main(): """Main function for testing purposes.""" From 39fecfbf13da8da08de752c53bd81a18a2ee6718 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 27 Jul 2021 17:08:35 +0200 Subject: [PATCH 215/736] replaced blur effect with possible black shadow --- openpype/tools/new_publisher/widgets.py | 4 +++- openpype/tools/new_publisher/window.py | 10 ---------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 81cfc7f8e8..43a838df84 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -1303,10 +1303,12 @@ class InstanceListView(_AbstractInstanceView): self.instance_view.setExpanded(proxy_index, expanded) -class PublishOverlayFrame(QtWidgets.QWidget): +class PublishOverlayFrame(QtWidgets.QFrame): def __init__(self, parent): super(PublishOverlayFrame, self).__init__(parent) + self.setObjectName("PublishOverlayFrame") + info_frame = QtWidgets.QFrame(self) info_frame.setObjectName("PublishOverlay") diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index ce79ed3609..9a10b0c9e4 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -57,12 +57,6 @@ class PublisherWindow(QtWidgets.QWidget): overlay_frame = PublishOverlayFrame(self) overlay_frame.setVisible(False) - blur_effect = QtWidgets.QGraphicsBlurEffect(main_frame) - blur_effect.setBlurRadius(3) - blur_effect.setEnabled(False) - - main_frame.setGraphicsEffect(blur_effect) - # Header header_widget = QtWidgets.QWidget(main_frame) context_label = QtWidgets.QLabel(header_widget) @@ -170,8 +164,6 @@ class PublisherWindow(QtWidgets.QWidget): self.main_frame = main_frame self.overlay_frame = overlay_frame - self.blur_effect = blur_effect - self.context_label = context_label self.subset_view_cards = subset_view_cards @@ -302,8 +294,6 @@ class PublisherWindow(QtWidgets.QWidget): self.overlay_frame.setVisible(True) - self.blur_effect.setEnabled(True) - def _on_validate_clicked(self): self._show_overlay() self.controller.validate() From 281c1fabdcaad70ae74dd6df888ed47eb8ce0887 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 27 Jul 2021 17:08:48 +0200 Subject: [PATCH 216/736] resize overlay to full size of main frame --- openpype/tools/new_publisher/window.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 9a10b0c9e4..9563ae49dd 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -197,11 +197,11 @@ class PublisherWindow(QtWidgets.QWidget): def resizeEvent(self, event): super(PublisherWindow, self).resizeEvent(event) - self.overlay_frame.resize(self.main_frame.size()) + self.overlay_frame.resize(self.size()) def moveEvent(self, event): super(PublisherWindow, self).moveEvent(event) - self.overlay_frame.move(self.main_frame.pos()) + self.overlay_frame.move(0, 0) def showEvent(self, event): super(PublisherWindow, self).showEvent(event) From 88abb05a19f829c7b81f6e91d559a06588a2f29d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 13 Aug 2021 11:37:17 +0200 Subject: [PATCH 217/736] added testing host --- openpype/hosts/testhost/__init__.py | 0 openpype/hosts/testhost/api/__init__.py | 27 ++++++ openpype/hosts/testhost/api/instances.json | 73 ++++++++++++++++ openpype/hosts/testhost/api/pipeline.py | 85 +++++++++++++++++++ .../testhost/plugins/create/test_creator_1.py | 29 +++++++ .../testhost/plugins/create/test_creator_2.py | 23 +++++ 6 files changed, 237 insertions(+) create mode 100644 openpype/hosts/testhost/__init__.py create mode 100644 openpype/hosts/testhost/api/__init__.py create mode 100644 openpype/hosts/testhost/api/instances.json create mode 100644 openpype/hosts/testhost/api/pipeline.py create mode 100644 openpype/hosts/testhost/plugins/create/test_creator_1.py create mode 100644 openpype/hosts/testhost/plugins/create/test_creator_2.py diff --git a/openpype/hosts/testhost/__init__.py b/openpype/hosts/testhost/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openpype/hosts/testhost/api/__init__.py b/openpype/hosts/testhost/api/__init__.py new file mode 100644 index 0000000000..9febac248f --- /dev/null +++ b/openpype/hosts/testhost/api/__init__.py @@ -0,0 +1,27 @@ +import os +import logging +import pyblish.api +import avalon.api +from openpype.pipeline import BaseCreator + +from .pipeline import ( + ls, + list_instances, + update_instances, + remove_instances +) + + +HOST_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +PLUGINS_DIR = os.path.join(HOST_DIR, "plugins") +PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") +CREATE_PATH = os.path.join(PLUGINS_DIR, "create") + +log = logging.getLogger(__name__) + + +def install(): + log.info("OpenPype - Installing TestHost integration") + pyblish.api.register_host("testhost") + pyblish.api.register_plugin_path(PUBLISH_PATH) + avalon.api.register_plugin_path(BaseCreator, CREATE_PATH) diff --git a/openpype/hosts/testhost/api/instances.json b/openpype/hosts/testhost/api/instances.json new file mode 100644 index 0000000000..0745be3b6b --- /dev/null +++ b/openpype/hosts/testhost/api/instances.json @@ -0,0 +1,73 @@ +[ + { + "id": "pyblish.avalon.instance", + "family": "test_one", + "subset": "test_oneMyVariant", + "active": true, + "version": 1, + "asset": "ep01sh0010", + "task": "Compositing", + "variant": "myVariant", + "uuid": "a485f148-9121-46a5-8157-aa64df0fb449", + "family_attributes": { + "number_key": 10, + "ha": 10 + }, + "publish_attributes": { + "CollectFtrackApi": { + "add_ftrack_family": false + } + } + }, + { + "id": "pyblish.avalon.instance", + "family": "test_one", + "subset": "test_oneMyVariant2", + "active": true, + "version": 1, + "asset": "ep01sh0010", + "task": "Compositing", + "variant": "myVariant2", + "uuid": "a485f148-9121-46a5-8157-aa64df0fb444", + "family_attributes": {}, + "publish_attributes": { + "CollectFtrackApi": { + "add_ftrack_family": true + } + } + }, + { + "id": "pyblish.avalon.instance", + "family": "test_two", + "subset": "test_twoMain", + "active": true, + "version": 1, + "asset": "ep01sh0010", + "task": "Compositing", + "variant": "Main", + "uuid": "3607bc95-75f6-4648-a58d-e699f413d09f", + "family_attributes": {}, + "publish_attributes": { + "CollectFtrackApi": { + "add_ftrack_family": true + } + } + }, + { + "id": "pyblish.avalon.instance", + "family": "test_two", + "subset": "test_twoMain2", + "active": true, + "version": 1, + "asset": "ep01sh0010", + "task": "Compositing", + "variant": "Main2", + "uuid": "4ccf56f6-9982-4837-967c-a49695dbe8eb", + "family_attributes": {}, + "publish_attributes": { + "CollectFtrackApi": { + "add_ftrack_family": true + } + } + } +] \ No newline at end of file diff --git a/openpype/hosts/testhost/api/pipeline.py b/openpype/hosts/testhost/api/pipeline.py new file mode 100644 index 0000000000..fb47f1aa77 --- /dev/null +++ b/openpype/hosts/testhost/api/pipeline.py @@ -0,0 +1,85 @@ +import os +import json +import collections + + +class HostContext: + json_path = None + + @classmethod + def add_instance(cls, instance): + instances = cls.get_instances() + instances.append(instance) + cls.save_instances(instances) + + @classmethod + def save_instances(cls, instances): + json_path = cls.get_json_path() + with open(json_path, "w") as json_stream: + json.dump(instances, json_stream, indent=4) + + @classmethod + def get_json_path(cls): + if cls.json_path is None: + cls.json_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "instances.json" + ) + return cls.json_path + + @classmethod + def get_instances(cls): + json_path = cls.get_json_path() + if not os.path.exists(json_path): + instances = [] + with open(json_path, "w") as json_stream: + json.dump(json_stream, instances) + else: + with open(json_path, "r") as json_stream: + instances = json.load(json_stream) + return instances + + +def ls(): + return [] + + +def list_instances(): + return HostContext.get_instances() + + +def update_instances(update_list): + current_instances = HostContext.get_instances() + + for instance, _changes in update_list: + instance_id = instance.data["uuid"] + + found_idx = None + for idx, current_instance in enumerate(current_instances): + if instance_id == current_instance["uuid"]: + found_idx = idx + break + + if found_idx is None: + return + + current_instances[found_idx] = instance.data_to_store() + HostContext.save_instances(current_instances) + + +def remove_instances(instances): + if not isinstance(instances, (tuple, list)): + instances = [instances] + + current_instances = HostContext.get_instances() + for instance in instances: + instance_id = instance.data["uuid"] + found_idx = None + for idx, _instance in enumerate(current_instances): + if instance_id == _instance["uuid"]: + found_idx = idx + break + + if found_idx is not None: + current_instances.pop(found_idx) + HostContext.save_instances(current_instances) diff --git a/openpype/hosts/testhost/plugins/create/test_creator_1.py b/openpype/hosts/testhost/plugins/create/test_creator_1.py new file mode 100644 index 0000000000..65d94d8dd7 --- /dev/null +++ b/openpype/hosts/testhost/plugins/create/test_creator_1.py @@ -0,0 +1,29 @@ +from openpype.hosts.testhost import api +from openpype.pipeline import ( + Creator, + CreatedInstance, + lib +) + + +class TestCreatorOne(Creator): + family = "test_one" + + def create(self, subset_name, data, options=None): + avalon_instance = CreatedInstance(self.family, subset_name, data, self) + api.pipeline.HostContext.add_instance(avalon_instance.data_to_store()) + self.log.info(avalon_instance.data) + return avalon_instance + + def get_default_variants(self): + return [ + "myVariant", + "variantTwo", + "different_variant" + ] + + def get_attribute_defs(self): + output = [ + lib.NumberDef("number_key", label="Number") + ] + return output diff --git a/openpype/hosts/testhost/plugins/create/test_creator_2.py b/openpype/hosts/testhost/plugins/create/test_creator_2.py new file mode 100644 index 0000000000..2acc3273cd --- /dev/null +++ b/openpype/hosts/testhost/plugins/create/test_creator_2.py @@ -0,0 +1,23 @@ +from openpype.hosts.testhost import api +from openpype.pipeline import ( + Creator, + CreatedInstance, + lib +) + + +class TestCreatorTwo(Creator): + family = "test_two" + + def create(self, subset_name, data, options=None): + avalon_instance = CreatedInstance(self.family, subset_name, data, self) + api.pipeline.HostContext.add_instance(avalon_instance.data_to_store()) + self.log.info(avalon_instance.data) + return avalon_instance + + def get_attribute_defs(self): + output = [ + lib.NumberDef("number_key"), + lib.TextDef("text_key") + ] + return output From 271b02ece32a703ed89dcde84fbe7a0f55fc30b4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 13 Aug 2021 11:43:15 +0200 Subject: [PATCH 218/736] added basic create error exception --- openpype/pipeline/create/__init__.py | 2 ++ openpype/pipeline/create/creator_plugins.py | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/openpype/pipeline/create/__init__.py b/openpype/pipeline/create/__init__.py index 981f4917a2..2f4e8ef5b9 100644 --- a/openpype/pipeline/create/__init__.py +++ b/openpype/pipeline/create/__init__.py @@ -1,4 +1,5 @@ from .creator_plugins import ( + CreatorError, BaseCreator, Creator, AutoCreator @@ -11,6 +12,7 @@ from .context import ( __all__ = ( + "CreatorError", "BaseCreator", "Creator", "AutoCreator", diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index a5c98e45df..d273f4cdb1 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -11,6 +11,16 @@ import six from openpype.lib import get_subset_name +class CreatorError(Exception): + """Should be raised when creator failed because of known issue. + + Message of error should be user readable. + """ + + def __init__(self, message): + super(CreatorError, self).__init__(message) + + @six.add_metaclass(ABCMeta) class BaseCreator: """Plugin that create and modify instance data before publishing process. From f6442923ce2aa3c43fcd87c46dcb1f1c7ad611e5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 13 Aug 2021 11:44:31 +0200 Subject: [PATCH 219/736] base handling of create error --- openpype/tools/new_publisher/widgets.py | 100 +++++++++++++++++++++++- 1 file changed, 98 insertions(+), 2 deletions(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 43a838df84..5c40e55b8f 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -1,9 +1,12 @@ import os +import sys import re import copy import collections +import traceback from Qt import QtWidgets, QtCore, QtGui +from openpype.pipeline.create import CreatorError from openpype.widgets.attribute_defs import create_widget_for_attr_def from constants import ( INSTANCE_ID_ROLE, @@ -419,6 +422,9 @@ class CreateDialog(QtWidgets.QDialog): self._selected_creator = None self._prereq_available = False + + self.message_dialog = None + family_view = QtWidgets.QListView(self) family_model = QtGui.QStandardItemModel() family_view.setModel(family_model) @@ -739,9 +745,23 @@ class CreateDialog(QtWidgets.QDialog): try: self.controller.create(family, subset_name, instance_data, options) + except CreatorError as exc: + error_info = (str(exc), None) + except Exception as exc: - # TODO better handling - print(str(exc)) + exc_type, exc_value, exc_traceback = sys.exc_info() + formatted_traceback = "".join(traceback.format_exception( + exc_type, exc_value, exc_traceback + )) + error_info = (str(exc), formatted_traceback) + + if error_info: + box = CreateErrorMessageBox( + family, subset_name, asset_name, *error_info + ) + box.show() + # Store dialog so is not garbage collected before is shown + self.message_dialog = box if self.auto_close_checkbox.isChecked(): self.hide() @@ -1369,3 +1389,79 @@ class PublishOverlayFrame(QtWidgets.QFrame): def set_progress(self, value): self.progress_widget.setValue(value) + + +class CreateErrorMessageBox(QtWidgets.QDialog): + def __init__( + self, + family, + subset_name, + asset_name, + exc_msg, + formatted_traceback, + parent=None + ): + super(CreateErrorMessageBox, self).__init__(parent) + self.setWindowTitle("Creation failed") + self.setFocusPolicy(QtCore.Qt.StrongFocus) + if not parent: + self.setWindowFlags( + self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint + ) + + body_layout = QtWidgets.QVBoxLayout(self) + + main_label = ( + "Failed to create" + ) + main_label_widget = QtWidgets.QLabel(main_label, self) + body_layout.addWidget(main_label_widget) + + item_name_template = ( + "Family: {}
" + "Subset: {}
" + "Asset: {}
" + ) + exc_msg_template = "{}" + + line = self._create_line() + body_layout.addWidget(line) + + item_name = item_name_template.format(family, subset_name, asset_name) + item_name_widget = QtWidgets.QLabel( + item_name.replace("\n", "
"), self + ) + body_layout.addWidget(item_name_widget) + + exc_msg = exc_msg_template.format(exc_msg.replace("\n", "
")) + message_label_widget = QtWidgets.QLabel(exc_msg, self) + body_layout.addWidget(message_label_widget) + + if formatted_traceback: + tb_widget = QtWidgets.QLabel( + formatted_traceback.replace("\n", "
"), self + ) + tb_widget.setTextInteractionFlags( + QtCore.Qt.TextBrowserInteraction + ) + body_layout.addWidget(tb_widget) + + footer_widget = QtWidgets.QWidget(self) + footer_layout = QtWidgets.QHBoxLayout(footer_widget) + button_box = QtWidgets.QDialogButtonBox(QtCore.Qt.Vertical) + button_box.setStandardButtons( + QtWidgets.QDialogButtonBox.StandardButton.Ok + ) + button_box.accepted.connect(self._on_accept) + footer_layout.addWidget(button_box, alignment=QtCore.Qt.AlignRight) + body_layout.addWidget(footer_widget) + + def _on_accept(self): + self.close() + + def _create_line(self): + line = QtWidgets.QFrame(self) + line.setFixedHeight(2) + line.setFrameShape(QtWidgets.QFrame.HLine) + line.setFrameShadow(QtWidgets.QFrame.Sunken) + return line From f68cf132a58dac377ceb3a08830edc0dc81e7d9a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 13 Aug 2021 15:53:11 +0200 Subject: [PATCH 220/736] fixed context --- openpype/pipeline/create/context.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 910221dbf9..fc93cce51f 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -159,13 +159,6 @@ class PublishAttributes: self, attr_defs, value, orig_value ) - for key, value in origin_data.items(): - if key not in self._data: - orig_value = copy.deepcopy(value) - self._data[key] = PublishAttributeValues( - self, attr_defs, value, orig_value - ) - def __getitem__(self, key): return self._data[key] From 16ded37ea1911a54e1f7d2bf3b276013e397b361 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 13 Aug 2021 16:56:59 +0200 Subject: [PATCH 221/736] safer subset names when asset is not set --- openpype/tools/new_publisher/widgets.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 5c40e55b8f..b0c79a07cd 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -651,7 +651,10 @@ class CreateDialog(QtWidgets.QDialog): def _validate_subset_name(self, subset_name, variant_value): # Get all subsets of the current asset - existing_subset_names = set(self._subset_names) + if self._subset_names: + existing_subset_names = set(self._subset_names) + else: + existing_subset_names = set() existing_subset_names_low = set( _name.lower() for _name in existing_subset_names From 7cd9aaaa5bea20c1e8dc79a488891a8d85fc9f3c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 13 Aug 2021 16:58:43 +0200 Subject: [PATCH 222/736] added footer buttons --- openpype/tools/new_publisher/widgets.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index b0c79a07cd..f7b798eede 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -1359,7 +1359,20 @@ class PublishOverlayFrame(QtWidgets.QFrame): progress_widget = QtWidgets.QProgressBar(content_widget) + copy_log_btn = QtWidgets.QPushButton("Copy log", content_widget) + stop_btn = QtWidgets.QPushButton("Stop", content_widget) + refresh_btn = QtWidgets.QPushButton("Refresh", content_widget) + publish_btn = QtWidgets.QPushButton("Publish", content_widget) + + footer_layout = QtWidgets.QHBoxLayout() + footer_layout.addWidget(copy_log_btn, 0) + footer_layout.addStretch(1) + footer_layout.addWidget(refresh_btn, 0) + footer_layout.addWidget(stop_btn, 0) + footer_layout.addWidget(publish_btn, 0) + content_layout = QtWidgets.QVBoxLayout(content_widget) + content_layout.setSpacing(5) content_layout.setAlignment(QtCore.Qt.AlignCenter) content_layout.addStretch(1) content_layout.addWidget(main_label) @@ -1367,6 +1380,7 @@ class PublishOverlayFrame(QtWidgets.QFrame): content_layout.addLayout(instance_plugin_layout) content_layout.addWidget(progress_widget) content_layout.addStretch(1) + content_layout.addLayout(footer_layout) main_layout = QtWidgets.QVBoxLayout(self) main_layout.setContentsMargins(0, 0, 0, 0) @@ -1381,6 +1395,11 @@ class PublishOverlayFrame(QtWidgets.QFrame): self.plugin_label = plugin_label self.progress_widget = progress_widget + self.copy_log_btn = copy_log_btn + self.stop_btn = stop_btn + self.refresh_btn = refresh_btn + self.publish_btn = publish_btn + def set_instance(self, instance_name): self.instance_label.setText(instance_name) From 5cd95d248cceeeae12f7df0b15011ed30b2f5e03 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 13 Aug 2021 16:59:01 +0200 Subject: [PATCH 223/736] added hide button --- openpype/tools/new_publisher/widgets.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index f7b798eede..a8b2d0a790 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -1342,6 +1342,13 @@ class PublishOverlayFrame(QtWidgets.QFrame): info_layout.setContentsMargins(0, 0, 0, 0) info_layout.addWidget(content_widget) + hide_btn = QtWidgets.QPushButton("Hide", content_widget) + + top_layout = QtWidgets.QHBoxLayout() + top_layout.setContentsMargins(0, 0, 0, 0) + top_layout.addStretch(1) + top_layout.addWidget(hide_btn) + main_label = QtWidgets.QLabel("Publishing...", content_widget) main_label.setAlignment(QtCore.Qt.AlignCenter) @@ -1374,7 +1381,8 @@ class PublishOverlayFrame(QtWidgets.QFrame): content_layout = QtWidgets.QVBoxLayout(content_widget) content_layout.setSpacing(5) content_layout.setAlignment(QtCore.Qt.AlignCenter) - content_layout.addStretch(1) + + content_layout.addLayout(top_layout) content_layout.addWidget(main_label) content_layout.addStretch(1) content_layout.addLayout(instance_plugin_layout) @@ -1388,6 +1396,10 @@ class PublishOverlayFrame(QtWidgets.QFrame): main_layout.addWidget(info_frame, 2) main_layout.addStretch(1) + hide_btn.clicked.connect(self.hide_requested) + + self.hide_btn = hide_btn + self.main_label = main_label self.info_frame = info_frame From f894843204c413af33620a921660c67fae6de73b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 13 Aug 2021 17:00:07 +0200 Subject: [PATCH 224/736] listen to hide overlay request --- openpype/tools/new_publisher/widgets.py | 2 ++ openpype/tools/new_publisher/window.py | 18 +++++++++++------- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index a8b2d0a790..8899107e49 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -1327,6 +1327,8 @@ class InstanceListView(_AbstractInstanceView): class PublishOverlayFrame(QtWidgets.QFrame): + hide_requested = QtCore.Signal() + def __init__(self, parent): super(PublishOverlayFrame, self).__init__(parent) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 9563ae49dd..17b66b66ac 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -156,6 +156,7 @@ class PublisherWindow(QtWidgets.QWidget): subset_view_cards.selection_changed.connect( self._on_subset_change ) + overlay_frame.hide_requested.connect(self._on_overlay_hide_request) controller.add_instance_change_callback(self._on_instance_change) controller.add_plugin_change_callback(self._on_plugin_change) @@ -288,19 +289,19 @@ class PublisherWindow(QtWidgets.QWidget): def _on_save_clicked(self): self.controller.save_instance_changes() - def _show_overlay(self): - if self.overlay_frame.isVisible(): + def _set_overlay_visibility(self, visible): + if self.overlay_frame.isVisible() == visible: return - self.overlay_frame.setVisible(True) + self.overlay_frame.setVisible(visible) def _on_validate_clicked(self): - self._show_overlay() - self.controller.validate() + self._set_overlay_visibility(True) + # self.controller.validate() def _on_publish_clicked(self): - self._show_overlay() - self.controller.publish() + self._set_overlay_visibility(True) + # self.controller.publish() def _refresh_instances(self): if self._refreshing_instances: @@ -359,6 +360,9 @@ class PublisherWindow(QtWidgets.QWidget): def _on_publish_stop(self): pass + def _on_overlay_hide_request(self): + self._set_overlay_visibility(False) + def main(): """Main function for testing purposes.""" From 1361000211136e1894a7baafbaa84baff3a869af Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 13 Aug 2021 17:00:23 +0200 Subject: [PATCH 225/736] moved info panel to bottom --- openpype/tools/new_publisher/widgets.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 8899107e49..f62b8c0274 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -1393,10 +1393,8 @@ class PublishOverlayFrame(QtWidgets.QFrame): content_layout.addLayout(footer_layout) main_layout = QtWidgets.QVBoxLayout(self) - main_layout.setContentsMargins(0, 0, 0, 0) - main_layout.addStretch(1) - main_layout.addWidget(info_frame, 2) main_layout.addStretch(1) + main_layout.addWidget(info_frame, 0) hide_btn.clicked.connect(self.hide_requested) From 027000c4efd2b9a15d1d10e39591e0d531bb368f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 13 Aug 2021 17:09:32 +0200 Subject: [PATCH 226/736] named instance view --- openpype/tools/new_publisher/widgets.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index f62b8c0274..35c1990a61 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -1098,6 +1098,7 @@ class InstanceListView(_AbstractInstanceView): self.controller = controller instance_view = QtWidgets.QTreeView(self) + instance_view.setObjectName("InstanceListView") instance_view.setHeaderHidden(True) instance_view.setIndentation(0) instance_view.setSelectionMode( From c7206daa93a4bc508bea1dace3471548ddd49a23 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 13 Aug 2021 17:12:53 +0200 Subject: [PATCH 227/736] styled few widgets --- openpype/style/style.css | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/openpype/style/style.css b/openpype/style/style.css index 6997c73f27..f851399653 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -635,3 +635,27 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { #VariantInput[state="exists"], #VariantInput[state="exists"]:focus, #VariantInput[state="exists"]:hover { border-color: #4E76BB; } + +#InstanceListView::item { + border-radius: 0.3em; + margin: 1px; +} +#InstanceListGroupWidget { + border: 1px solid white; + border-radius: 0.3em; + background: transparent; +} + +#PublishOverlayFrame { + background: rgba(0, 0, 0, 127) +} + +#PublishOverlay { + background: rgb(194, 226, 236); + border: 2px solid black; + border-radius: 0.3em; +} + +#PublishOverlay QLabel { + color: black; +} From c760a476651903085b46e7643c7275ac77b4a9e3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 13 Aug 2021 18:00:27 +0200 Subject: [PATCH 228/736] fixed styles of arrow buttons nad checkbox --- openpype/style/style.css | 9 +++++++++ openpype/tools/new_publisher/widgets.py | 5 ++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/openpype/style/style.css b/openpype/style/style.css index f851399653..1fd9110a59 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -57,6 +57,11 @@ QAbstractSpinBox:focus, QLineEdit:focus, QPlainTextEdit:focus, QTextEdit:focus{ border-color: {color:border-focus}; } +/* Checkbox */ +QCheckBox { + background: transparent; +} + /* Buttons */ QPushButton { text-align:center center; @@ -659,3 +664,7 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { #PublishOverlay QLabel { color: black; } + +#ArrowBtn, #ArrowBtn:disabled, #ArrowBtn:hover { + background: transparent; +} diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 35c1990a61..5164f0de9c 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -781,10 +781,10 @@ class InstanceCardWidget(QtWidgets.QWidget): subset_name_label = QtWidgets.QLabel(instance.data["subset"], self) active_checkbox = QtWidgets.QCheckBox(self) - active_checkbox.setStyleSheet("background: transparent;") active_checkbox.setChecked(instance.data["active"]) expand_btn = QtWidgets.QToolButton(self) + expand_btn.setObjectName("ArrowBtn") expand_btn.setArrowType(QtCore.Qt.DownArrow) expand_btn.setMaximumWidth(14) expand_btn.setEnabled(False) @@ -997,7 +997,6 @@ class InstanceListItemWidget(QtWidgets.QWidget): subset_name_label = QtWidgets.QLabel(instance.data["subset"], self) active_checkbox = QtWidgets.QCheckBox(self) - active_checkbox.setStyleSheet("background: transparent;") active_checkbox.setChecked(instance.data["active"]) layout = QtWidgets.QHBoxLayout(self) @@ -1057,7 +1056,7 @@ class InstanceListGroupWidget(QtWidgets.QFrame): subset_name_label = QtWidgets.QLabel(family, self) expand_btn = QtWidgets.QToolButton(self) - expand_btn.setStyleSheet("background: transparent;") + expand_btn.setObjectName("ArrowBtn") expand_btn.setArrowType(QtCore.Qt.RightArrow) expand_btn.setMaximumWidth(14) From 31e5f0ba9f03137ca845f5c26bcac524451619ca Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 13 Aug 2021 19:24:57 +0200 Subject: [PATCH 229/736] added ability to get asset hierarchy from controller --- openpype/tools/new_publisher/control.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 1e42aacbf3..7b83375cc9 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -1,6 +1,5 @@ import weakref import logging -import inspect import collections import avalon.api import pyblish.api @@ -346,6 +345,23 @@ class PublisherController: print(plugin, instance) self._publish_next_process() + def get_asset_hierarchy(self): + _queue = collections.deque((self.dbcon.find( + {"type": "asset"}, + { + "_id": True, + "name": True, + "data.visualParent": True + } + ))) + + output = collections.defaultdict(list) + while _queue: + asset_doc = _queue.popleft() + parent_id = asset_doc["data"]["visualParent"] + output[parent_id].append(asset_doc) + return output + def collect_families_from_instances(instances, only_active=False): all_families = set() From 034d580ed50abef0bf2140b6e505dc24c6f5b19f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 13 Aug 2021 19:25:34 +0200 Subject: [PATCH 230/736] added asset combobox --- openpype/tools/new_publisher/widgets.py | 143 ++++++++++++++++++++++-- 1 file changed, 136 insertions(+), 7 deletions(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 5164f0de9c..887e3a20e2 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -75,26 +75,155 @@ class ReadWriteLineEdit(QtWidgets.QFrame): return self.read_widget.text() -class GlobalAttrsWidget(QtWidgets.QWidget): +class AssetHierarchyModel(QtGui.QStandardItemModel): + def __init__(self, controller): + super(AssetHierarchyModel, self).__init__() + self._controller = controller + + self._items_by_name = {} + + def reset(self): + self.clear() + + self._items_by_name = {} + assets_by_parent_id = self._controller.get_asset_hierarchy() + + items_by_name = {} + _queue = collections.deque() + _queue.append((self.invisibleRootItem(), None)) + while _queue: + parent_item, parent_id = _queue.popleft() + children = assets_by_parent_id.get(parent_id) + if not children: + continue + + children_by_name = { + child["name"]: child + for child in children + } + items = [] + for name in sorted(children_by_name.keys()): + child = children_by_name[name] + item = QtGui.QStandardItem(name) + items_by_name[name] = item + items.append(item) + _queue.append((item, child["_id"])) + + parent_item.appendRows(items) + + self._items_by_name = items_by_name + + def get_index_by_name(self, item_name): + item = self._items_by_name.get(item_name) + if item: + return item.index() + return QtCore.QModelIndex() + + +class TreeComboBoxView(QtWidgets.QTreeView): + visible_rows = 12 + def __init__(self, parent): + super(TreeComboBoxView, self).__init__(parent) + + self.setHeaderHidden(True) + self.setFrameShape(QtWidgets.QFrame.NoFrame) + self.setEditTriggers(QtWidgets.QTreeView.NoEditTriggers) + self.setAlternatingRowColors(True) + self.setSelectionBehavior(QtWidgets.QTreeView.SelectRows) + self.setWordWrap(True) + self.setAllColumnsShowFocus(True) + + def showEvent(self, event): + super(TreeComboBoxView, self).showEvent(event) + + row_sh = self.sizeHintForRow(0) + current_height = self.height() + height = (self.visible_rows * row_sh) + (current_height % row_sh) + self.setMinimumHeight(height) + + +class TreeComboBox(QtWidgets.QComboBox): + def __init__(self, model, parent): + super(TreeComboBox, self).__init__(parent) + + tree_view = TreeComboBoxView(self) + self.setView(tree_view) + + tree_view.viewport().installEventFilter(self) + + self._tree_view = tree_view + self._model = None + self._skip_next_hide = False + + if model: + self.setModel(model) + + def setModel(self, model): + self._model = model + super(TreeComboBox, self).setModel(model) + + def showPopup(self): + self.setRootModelIndex(QtCore.QModelIndex()) + super(TreeComboBox, self).showPopup() + + def hidePopup(self): + # NOTE This would hide everything except parents + # self.setRootModelIndex(self._tree_view.currentIndex().parent()) + # self.setCurrentIndex(self._tree_view.currentIndex().row()) + if self._skip_next_hide: + self._skip_next_hide = False + else: + super(TreeComboBox, self).hidePopup() + + def selectIndex(self, index): + self.setRootModelIndex(index.parent()) + self.setCurrentIndex(index.row()) + + def eventFilter(self, obj, event): + if ( + event.type() == QtCore.QEvent.MouseButtonPress + and obj is self._tree_view.viewport() + ): + index = self._tree_view.indexAt(event.pos()) + self._skip_next_hide = not ( + self._tree_view.visualRect(index).contains(event.pos()) + ) + return False + + def set_selected_item(self, item_name): + index = self._model.get_index_by_name(item_name) + if index.isValid(): + self._tree_view.selectionModel().setCurrentIndex( + index, QtCore.QItemSelectionModel.SelectCurrent + ) + self.selectIndex(index) + + +class GlobalAttrsWidget(QtWidgets.QWidget): + def __init__(self, controller, parent): super(GlobalAttrsWidget, self).__init__(parent) + self.controller = controller + variant_input = ReadWriteLineEdit(self) - family_value_widget = QtWidgets.QLabel(self) - asset_value_widget = QtWidgets.QLabel(self) + asset_model = AssetHierarchyModel(controller) + asset_value_widget = TreeComboBox(asset_model, self) + asset_model.reset() task_value_widget = QtWidgets.QLabel(self) + family_value_widget = QtWidgets.QLabel(self) subset_value_widget = QtWidgets.QLabel(self) subset_value_widget.setText("") family_value_widget.setText("") - asset_value_widget.setText("") + # asset_value_widget.setText("") task_value_widget.setText("") main_layout = QtWidgets.QFormLayout(self) main_layout.addRow("Name", variant_input) - main_layout.addRow("Family", family_value_widget) main_layout.addRow("Asset", asset_value_widget) main_layout.addRow("Task", task_value_widget) + main_layout.addRow("Family", family_value_widget) main_layout.addRow("Subset", subset_value_widget) self.variant_input = variant_input @@ -154,7 +283,7 @@ class GlobalAttrsWidget(QtWidgets.QWidget): self.variant_input.setText(variant) self.family_value_widget.setText(family) - self.asset_value_widget.setText(asset_name) + self.asset_value_widget.set_selected_item(asset_name) self.task_value_widget.setText(task_name) self.subset_value_widget.setText(subset_name) @@ -338,7 +467,7 @@ class SubsetAttributesWidget(QtWidgets.QWidget): top_widget = QtWidgets.QWidget(self) # Global attributes - global_attrs_widget = GlobalAttrsWidget(top_widget) + global_attrs_widget = GlobalAttrsWidget(controller, top_widget) thumbnail_widget = ThumbnailWidget(top_widget) top_layout = QtWidgets.QHBoxLayout(top_widget) From 4ad543755b7f62a76abda17ce3315a4fe056ce46 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 13 Aug 2021 19:29:02 +0200 Subject: [PATCH 231/736] added better margins to list item widget --- openpype/tools/new_publisher/widgets.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 887e3a20e2..bc1e045f8e 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -1129,7 +1129,8 @@ class InstanceListItemWidget(QtWidgets.QWidget): active_checkbox.setChecked(instance.data["active"]) layout = QtWidgets.QHBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) + content_margins = layout.contentsMargins() + layout.setContentsMargins(content_margins.left() + 2, 0, 2, 0) layout.addWidget(subset_name_label) layout.addStretch(1) layout.addWidget(active_checkbox) From aaee9d81c1be86eb5ba69af7531f875214d0c552 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 16 Aug 2021 11:30:47 +0200 Subject: [PATCH 232/736] use asset doc cacher --- openpype/tools/new_publisher/control.py | 69 ++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 8 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 7b83375cc9..1edc764510 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -1,3 +1,4 @@ +import copy import weakref import logging import collections @@ -69,6 +70,50 @@ class MainThreadProcess(QtCore.QObject): self._items_to_process = collections.deque() +class AssetDocsCache: + projection = { + "_id": True, + "name": True, + "data.visualParent": True, + "data.tasks": True + } + + def __init__(self, controller): + self._controller = controller + self._asset_docs = None + self._task_names_by_asset_name = {} + + @property + def dbcon(self): + return self._controller.dbcon + + def reset(self): + self._asset_docs = None + self._task_names_by_asset_name = {} + + def _query(self): + if self._asset_docs is None: + asset_docs = list(self.dbcon.find( + {"type": "asset"}, + self.projection + )) + task_names_by_asset_name = {} + for asset_doc in asset_docs: + asset_name = asset_doc["name"] + asset_tasks = asset_doc.get("data", {}).get("tasks") or {} + task_names_by_asset_name[asset_name] = list(asset_tasks.keys()) + self._asset_docs = asset_docs + self._task_names_by_asset_name = task_names_by_asset_name + + def get_asset_docs(self): + self._query() + return copy.deepcopy(self._asset_docs) + + def get_task_names_by_asset_name(self): + self._query() + return copy.deepcopy(self._task_names_by_asset_name) + + class PublisherController: def __init__(self, dbcon=None, headless=False): self.log = logging.getLogger("PublisherController") @@ -97,6 +142,8 @@ class PublisherController: self._resetting_plugins = False self._resetting_instances = False + self._asset_docs_cache = AssetDocsCache(self) + @property def dbcon(self): return self.create_context.dbcon @@ -346,14 +393,9 @@ class PublisherController: self._publish_next_process() def get_asset_hierarchy(self): - _queue = collections.deque((self.dbcon.find( - {"type": "asset"}, - { - "_id": True, - "name": True, - "data.visualParent": True - } - ))) + _queue = collections.deque( + self._asset_docs_cache.get_asset_docs() + ) output = collections.defaultdict(list) while _queue: @@ -362,6 +404,17 @@ class PublisherController: output[parent_id].append(asset_doc) return output + def get_task_names_for_asset_names(self, asset_names): + task_names_by_asset_name = ( + self._asset_docs_cache.get_task_names_by_asset_name() + ) + tasks = set() + for asset_name in asset_names: + task_names = task_names_by_asset_name.get(asset_name) + if task_names: + tasks |= set(task_names) + return tasks + def collect_families_from_instances(instances, only_active=False): all_families = set() From b8d8121a8db2a2f58cf0f272f7edb1cafcedafb2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 16 Aug 2021 11:32:57 +0200 Subject: [PATCH 233/736] use full function of asset tree combobox --- openpype/tools/new_publisher/widgets.py | 85 +++++++++++++++++++------ 1 file changed, 67 insertions(+), 18 deletions(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index bc1e045f8e..14d8a5075a 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -75,9 +75,9 @@ class ReadWriteLineEdit(QtWidgets.QFrame): return self.read_widget.text() -class AssetHierarchyModel(QtGui.QStandardItemModel): +class AssetsHierarchyModel(QtGui.QStandardItemModel): def __init__(self, controller): - super(AssetHierarchyModel, self).__init__() + super(AssetsHierarchyModel, self).__init__() self._controller = controller self._items_by_name = {} @@ -159,6 +159,12 @@ class TreeComboBox(QtWidgets.QComboBox): if model: self.setModel(model) + # Create `lineEdit` to be able set asset names that are not available + # or for multiselection. + self.setEditable(True) + # Set `lineEdit` to read only + self.lineEdit().setReadOnly(True) + def setModel(self, model): self._model = model super(TreeComboBox, self).setModel(model) @@ -199,6 +205,54 @@ class TreeComboBox(QtWidgets.QComboBox): ) self.selectIndex(index) + else: + self.lineEdit().setText(item_name) + + +class AssetsTreeComboBox(TreeComboBox): + selection_changed = QtCore.Signal() + + def __init__(self, controller, parent): + model = AssetsHierarchyModel(controller) + + super(AssetsTreeComboBox, self).__init__(model, parent) + + self.currentIndexChanged.connect(self._on_index_change) + + self._ignore_index_change = False + self._selected_items = [] + self._model = model + + model.reset() + + def _on_index_change(self): + if self._ignore_index_change: + return + + self._selected_items = [self.currentText()] + self.selection_changed.emit() + + def get_selected_items(self): + return list(self._selected_items) + + def set_selected_items(self, asset_names=None, multiselection_text=None): + if asset_names is None: + asset_names = [] + + self._selected_items = asset_names + self._ignore_index_change = True + if not asset_names: + self.set_selected_item("") + + elif len(asset_names) == 1: + self.set_selected_item(tuple(asset_names)[0]) + else: + if multiselection_text is None: + multiselection_text = "|".join(asset_names) + self.set_selected_item(multiselection_text) + + self._ignore_index_change = False + class GlobalAttrsWidget(QtWidgets.QWidget): def __init__(self, controller, parent): @@ -207,17 +261,16 @@ class GlobalAttrsWidget(QtWidgets.QWidget): self.controller = controller variant_input = ReadWriteLineEdit(self) - asset_model = AssetHierarchyModel(controller) - asset_value_widget = TreeComboBox(asset_model, self) - asset_model.reset() task_value_widget = QtWidgets.QLabel(self) + + asset_value_widget = AssetsTreeComboBox(controller, self) family_value_widget = QtWidgets.QLabel(self) subset_value_widget = QtWidgets.QLabel(self) subset_value_widget.setText("") family_value_widget.setText("") - # asset_value_widget.setText("") task_value_widget.setText("") + asset_value_widget.set_selected_items() main_layout = QtWidgets.QFormLayout(self) main_layout.addRow("Name", variant_input) @@ -234,10 +287,12 @@ class GlobalAttrsWidget(QtWidgets.QWidget): def set_current_instances(self, instances): editable = False + multiselection_text = "< Multiselection >" + unknown = "N/A" + asset_names = set() if len(instances) == 0: variant = "" family = "" - asset_name = "" task_name = "" subset_name = "" @@ -246,36 +301,27 @@ class GlobalAttrsWidget(QtWidgets.QWidget): if instance.creator is not None: editable = True - unknown = "N/A" - variant = instance.data.get("variant") or unknown family = instance.data.get("family") or unknown - asset_name = instance.data.get("asset") or unknown task_name = instance.data.get("task") or unknown + asset_names.add(instance.data.get("asset") or unknown) subset_name = instance.data.get("subset") or unknown else: families = set() - asset_names = set() task_names = set() for instance in instances: families.add(instance.data.get("family") or unknown) asset_names.add(instance.data.get("asset") or unknown) task_names.add(instance.data.get("task") or unknown) - multiselection_text = "< Multiselection >" - variant = multiselection_text family = multiselection_text - asset_name = multiselection_text task_name = multiselection_text subset_name = multiselection_text if len(families) < 4: family = " / ".join(families) - if len(asset_names) < 4: - asset_name = " / ".join(asset_names) - if len(task_names) < 4: task_name = " / ".join(task_names) @@ -283,8 +329,11 @@ class GlobalAttrsWidget(QtWidgets.QWidget): self.variant_input.setText(variant) self.family_value_widget.setText(family) - self.asset_value_widget.set_selected_item(asset_name) self.task_value_widget.setText(task_name) + # Set context of asset widget + self.asset_value_widget.set_selected_items( + asset_names, multiselection_text + ) self.subset_value_widget.setText(subset_name) From b680d4851652e27a9dc03aecb764d893e8ec3fe0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 16 Aug 2021 11:33:21 +0200 Subject: [PATCH 234/736] added task combobox reacting to changes in asset combobox --- openpype/tools/new_publisher/widgets.py | 85 ++++++++++++++++++++++--- 1 file changed, 75 insertions(+), 10 deletions(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 14d8a5075a..0b0c40abba 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -120,6 +120,47 @@ class AssetsHierarchyModel(QtGui.QStandardItemModel): return QtCore.QModelIndex() +class TasksModel(QtGui.QStandardItemModel): + def __init__(self, controller): + super(TasksModel, self).__init__() + self._controller = controller + self._items_by_name = {} + self._asset_names = [] + + def set_asset_names(self, asset_names): + self._asset_names = asset_names + self.reset() + + def reset(self): + if not self._asset_names: + self._items_by_name = {} + self.clear() + return + + new_task_names = self._controller.get_task_names_for_asset_names( + self._asset_names + ) + old_task_names = set(self._items_by_name.keys()) + if new_task_names == old_task_names: + return + + root_item = self.invisibleRootItem() + for task_name in old_task_names: + if task_name not in new_task_names: + item = self._items_by_name.pop(task_name) + root_item.removeRow(item.row()) + + new_items = [] + for task_name in new_task_names: + if task_name in self._items_by_name: + continue + + item = QtGui.QStandardItem(task_name) + self._items_by_name[task_name] = item + new_items.append(item) + root_item.appendRows(new_items) + + class TreeComboBoxView(QtWidgets.QTreeView): visible_rows = 12 @@ -254,6 +295,25 @@ class AssetsTreeComboBox(TreeComboBox): self._ignore_index_change = False +class TasksCombobox(QtWidgets.QComboBox): + def __init__(self, controller, parent): + super(TasksCombobox, self).__init__(parent) + + self.setEditable(True) + self.lineEdit().setReadOnly(True) + + model = TasksModel(controller) + self.setModel(model) + + self._model = model + + def set_asset_names(self, asset_names): + self._model.set_asset_names(asset_names) + + def set_selected_items(self, task_names=None, multiselection_text=None): + pass + + class GlobalAttrsWidget(QtWidgets.QWidget): def __init__(self, controller, parent): super(GlobalAttrsWidget, self).__init__(parent) @@ -261,16 +321,16 @@ class GlobalAttrsWidget(QtWidgets.QWidget): self.controller = controller variant_input = ReadWriteLineEdit(self) - task_value_widget = QtWidgets.QLabel(self) asset_value_widget = AssetsTreeComboBox(controller, self) + task_value_widget = TasksCombobox(controller, self) family_value_widget = QtWidgets.QLabel(self) subset_value_widget = QtWidgets.QLabel(self) subset_value_widget.setText("") family_value_widget.setText("") - task_value_widget.setText("") asset_value_widget.set_selected_items() + task_value_widget.set_selected_items() main_layout = QtWidgets.QFormLayout(self) main_layout.addRow("Name", variant_input) @@ -279,21 +339,27 @@ class GlobalAttrsWidget(QtWidgets.QWidget): main_layout.addRow("Family", family_value_widget) main_layout.addRow("Subset", subset_value_widget) + asset_value_widget.selection_changed.connect(self._on_asset_change) + self.variant_input = variant_input self.family_value_widget = family_value_widget self.asset_value_widget = asset_value_widget self.task_value_widget = task_value_widget self.subset_value_widget = subset_value_widget + def _on_asset_change(self): + asset_names = self.asset_value_widget.get_selected_items() + self.task_value_widget.set_asset_names(asset_names) + def set_current_instances(self, instances): editable = False multiselection_text = "< Multiselection >" unknown = "N/A" asset_names = set() + task_names = set() if len(instances) == 0: variant = "" family = "" - task_name = "" subset_name = "" elif len(instances) == 1: @@ -303,13 +369,12 @@ class GlobalAttrsWidget(QtWidgets.QWidget): variant = instance.data.get("variant") or unknown family = instance.data.get("family") or unknown - task_name = instance.data.get("task") or unknown asset_names.add(instance.data.get("asset") or unknown) + task_names.add(instance.data.get("task") or unknown) subset_name = instance.data.get("subset") or unknown else: families = set() - task_names = set() for instance in instances: families.add(instance.data.get("family") or unknown) asset_names.add(instance.data.get("asset") or unknown) @@ -317,23 +382,23 @@ class GlobalAttrsWidget(QtWidgets.QWidget): variant = multiselection_text family = multiselection_text - task_name = multiselection_text subset_name = multiselection_text if len(families) < 4: family = " / ".join(families) - if len(task_names) < 4: - task_name = " / ".join(task_names) - self.variant_input.set_editable(editable) self.variant_input.setText(variant) self.family_value_widget.setText(family) - self.task_value_widget.setText(task_name) # Set context of asset widget self.asset_value_widget.set_selected_items( asset_names, multiselection_text ) + # Set context of task widget + self.task_value_widget.set_asset_names(asset_names) + self.task_value_widget.set_selected_items( + task_names, multiselection_text + ) self.subset_value_widget.setText(subset_name) From 720310cfff687e9f668d4ade8251438c38dbfcfb Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 16 Aug 2021 11:42:37 +0200 Subject: [PATCH 235/736] fixed intersection of task names --- openpype/tools/new_publisher/control.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 1edc764510..45f12e854e 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -408,11 +408,16 @@ class PublisherController: task_names_by_asset_name = ( self._asset_docs_cache.get_task_names_by_asset_name() ) - tasks = set() + tasks = None for asset_name in asset_names: - task_names = task_names_by_asset_name.get(asset_name) - if task_names: - tasks |= set(task_names) + task_names = set(task_names_by_asset_name.get(asset_name, [])) + if tasks is None: + tasks = task_names + else: + tasks &= task_names + + if not tasks: + break return tasks From e86b71b5a07093df27966af5f21ea6be6ab1715a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 16 Aug 2021 11:42:57 +0200 Subject: [PATCH 236/736] task combobox also have selection --- openpype/tools/new_publisher/widgets.py | 40 ++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 0b0c40abba..cd987d8d09 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -296,6 +296,8 @@ class AssetsTreeComboBox(TreeComboBox): class TasksCombobox(QtWidgets.QComboBox): + selection_changed = QtCore.Signal() + def __init__(self, controller, parent): super(TasksCombobox, self).__init__(parent) @@ -305,13 +307,49 @@ class TasksCombobox(QtWidgets.QComboBox): model = TasksModel(controller) self.setModel(model) + self.currentIndexChanged.connect(self._on_index_change) + self._model = model + self._selected_items = [] + self._ignore_index_change = False + + def _on_index_change(self): + if self._ignore_index_change: + return + + self._selected_items = [self.currentText()] + self.selection_changed.emit() + + def get_selected_items(self): + return list(self._selected_items) def set_asset_names(self, asset_names): self._model.set_asset_names(asset_names) def set_selected_items(self, task_names=None, multiselection_text=None): - pass + if task_names is None: + task_names = [] + + self._ignore_index_change = True + self._selected_items = task_names + if not task_names: + self.set_selected_item("") + + elif len(task_names) == 1: + self.set_selected_item(tuple(task_names)[0]) + + else: + if multiselection_text is None: + multiselection_text = "|".join(task_names) + self.set_selected_item(multiselection_text) + self._ignore_index_change = False + + def set_selected_item(self, item_name): + idx = self.findText(item_name) + if idx < 0: + self.lineEdit().setText(item_name) + else: + self.setCurrentIndex(idx) class GlobalAttrsWidget(QtWidgets.QWidget): From 15b8943934a6cf5dc79918984cb285a4d0c4be70 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 16 Aug 2021 11:55:26 +0200 Subject: [PATCH 237/736] care if selection of tasks changed --- openpype/tools/new_publisher/widgets.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index cd987d8d09..b7767fa6e9 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -310,7 +310,9 @@ class TasksCombobox(QtWidgets.QComboBox): self.currentIndexChanged.connect(self._on_index_change) self._model = model + self._origin_selection = [] self._selected_items = [] + self._has_selection_changed = False self._ignore_index_change = False def _on_index_change(self): @@ -318,6 +320,10 @@ class TasksCombobox(QtWidgets.QComboBox): return self._selected_items = [self.currentText()] + self._has_selection_changed = ( + self._origin_selection != self._selected_items + ) + self.selection_changed.emit() def get_selected_items(self): @@ -331,7 +337,11 @@ class TasksCombobox(QtWidgets.QComboBox): task_names = [] self._ignore_index_change = True - self._selected_items = task_names + self._has_selection_changed = False + self._origin_selection = list(task_names) + self._selected_items = list(task_names) + # Reset current index + self.setCurrentIndex(-1) if not task_names: self.set_selected_item("") @@ -342,6 +352,7 @@ class TasksCombobox(QtWidgets.QComboBox): if multiselection_text is None: multiselection_text = "|".join(task_names) self.set_selected_item(multiselection_text) + self._ignore_index_change = False def set_selected_item(self, item_name): From 35a63fce8f9aaead688b4bc2c8be16978ca3b634 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 16 Aug 2021 12:05:22 +0200 Subject: [PATCH 238/736] care about selection changes in asset combobox --- openpype/tools/new_publisher/widgets.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index b7767fa6e9..9c33617da7 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -262,6 +262,7 @@ class AssetsTreeComboBox(TreeComboBox): self._ignore_index_change = False self._selected_items = [] + self._origin_selection = [] self._model = model model.reset() @@ -271,8 +272,14 @@ class AssetsTreeComboBox(TreeComboBox): return self._selected_items = [self.currentText()] + self._has_selection_changed = ( + self._origin_selection != self._selected_items + ) self.selection_changed.emit() + def has_selection_changed(self): + return self._has_selection_changed + def get_selected_items(self): return list(self._selected_items) @@ -280,8 +287,11 @@ class AssetsTreeComboBox(TreeComboBox): if asset_names is None: asset_names = [] - self._selected_items = asset_names self._ignore_index_change = True + + self._has_selection_changed = False + self._origin_selection = list(asset_names) + self._selected_items = list(asset_names) if not asset_names: self.set_selected_item("") @@ -326,6 +336,9 @@ class TasksCombobox(QtWidgets.QComboBox): self.selection_changed.emit() + def has_selection_changed(self): + return self._has_selection_changed + def get_selected_items(self): return list(self._selected_items) @@ -337,6 +350,7 @@ class TasksCombobox(QtWidgets.QComboBox): task_names = [] self._ignore_index_change = True + self._has_selection_changed = False self._origin_selection = list(task_names) self._selected_items = list(task_names) From dc1b8f45bc988f9bdaf870f800a93b8dd6455863 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 16 Aug 2021 13:28:29 +0200 Subject: [PATCH 239/736] window has default size --- openpype/tools/new_publisher/window.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 17b66b66ac..797821b48d 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -39,6 +39,9 @@ from widgets import ( class PublisherWindow(QtWidgets.QWidget): + default_width = 1000 + default_height = 600 + def __init__(self, parent=None): super(PublisherWindow, self).__init__(parent) @@ -190,6 +193,8 @@ class PublisherWindow(QtWidgets.QWidget): self.setStyleSheet(style.load_stylesheet()) + self.resize(self.default_width, self.default_height) + # DEBUGING self.set_context_label( "////" From 8c43636fdc3b6ed2751fea1b5206c06db0197625 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 16 Aug 2021 13:28:59 +0200 Subject: [PATCH 240/736] few attribute name changes --- openpype/tools/new_publisher/widgets.py | 35 ++++++++++++++++++------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 9c33617da7..7d28e3012f 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -263,6 +263,7 @@ class AssetsTreeComboBox(TreeComboBox): self._ignore_index_change = False self._selected_items = [] self._origin_selection = [] + self._has_value_changed = False self._model = model model.reset() @@ -272,13 +273,13 @@ class AssetsTreeComboBox(TreeComboBox): return self._selected_items = [self.currentText()] - self._has_selection_changed = ( + self._has_value_changed = ( self._origin_selection != self._selected_items ) self.selection_changed.emit() - def has_selection_changed(self): - return self._has_selection_changed + def has_value_changed(self): + return self._has_value_changed def get_selected_items(self): return list(self._selected_items) @@ -289,7 +290,7 @@ class AssetsTreeComboBox(TreeComboBox): self._ignore_index_change = True - self._has_selection_changed = False + self._has_value_changed = False self._origin_selection = list(asset_names) self._selected_items = list(asset_names) if not asset_names: @@ -322,22 +323,24 @@ class TasksCombobox(QtWidgets.QComboBox): self._model = model self._origin_selection = [] self._selected_items = [] - self._has_selection_changed = False + self._has_value_changed = False self._ignore_index_change = False + self._value_is_valid = True def _on_index_change(self): if self._ignore_index_change: return self._selected_items = [self.currentText()] - self._has_selection_changed = ( + self._has_value_changed = ( self._origin_selection != self._selected_items ) + self._value_is_valid = True self.selection_changed.emit() - def has_selection_changed(self): - return self._has_selection_changed + def has_value_changed(self): + return self._has_value_changed def get_selected_items(self): return list(self._selected_items) @@ -351,18 +354,30 @@ class TasksCombobox(QtWidgets.QComboBox): self._ignore_index_change = True - self._has_selection_changed = False + self._has_value_changed = False self._origin_selection = list(task_names) self._selected_items = list(task_names) # Reset current index self.setCurrentIndex(-1) if not task_names: + self._value_is_valid = False self.set_selected_item("") elif len(task_names) == 1: - self.set_selected_item(tuple(task_names)[0]) + task_name = tuple(task_names)[0] + idx = self.findText(task_name) + self._value_is_valid = not idx < 0 + self.set_selected_item(task_name) else: + valid_value = True + for task_name in task_names: + idx = self.findText(task_name) + valid_value = not idx < 0 + if not valid_value: + break + + self._value_is_valid = valid_value if multiselection_text is None: multiselection_text = "|".join(task_names) self.set_selected_item(multiselection_text) From 73e6993434b1a5fb27a5eabb69725f4c16372683 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 16 Aug 2021 13:29:29 +0200 Subject: [PATCH 241/736] use editable variant input --- openpype/tools/new_publisher/widgets.py | 116 +++++++++++------------- 1 file changed, 54 insertions(+), 62 deletions(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 7d28e3012f..1976e2a903 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -21,60 +21,6 @@ def get_default_thumbnail_image_path(): return os.path.join(dirpath, "image_file.png") -class ReadWriteLineEdit(QtWidgets.QFrame): - textChanged = QtCore.Signal(str) - - def __init__(self, parent): - super(ReadWriteLineEdit, self).__init__(parent) - - read_widget = QtWidgets.QLabel(self) - edit_widget = QtWidgets.QLineEdit(self) - - self._editable = False - edit_widget.setVisible(self._editable) - read_widget.setVisible(not self._editable) - - layout = QtWidgets.QHBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) - layout.addWidget(read_widget) - layout.addWidget(edit_widget) - - edit_widget.textChanged.connect(self.textChanged) - - self.read_widget = read_widget - self.edit_widget = edit_widget - - def set_editable(self, editable): - if self._editable == editable: - return - self._editable = editable - self.set_edit(False) - - def set_edit(self, edit=None): - if edit is None: - edit = not self.edit_widget.isVisible() - - if not self._editable and edit: - return - - if self.edit_widget.isVisible() == edit: - return - - self.read_widget.setVisible(not edit) - self.edit_widget.setVisible(edit) - - def setText(self, text): - self.read_widget.setText(text) - if self.edit_widget.text() != text: - self.edit_widget.setText(text) - - def text(self): - if self.edit_widget.isVisible(): - return self.edit_widget.text() - return self.read_widget.text() - - class AssetsHierarchyModel(QtGui.QStandardItemModel): def __init__(self, controller): super(AssetsHierarchyModel, self).__init__() @@ -392,19 +338,67 @@ class TasksCombobox(QtWidgets.QComboBox): self.setCurrentIndex(idx) +class VariantInputWidget(QtWidgets.QLineEdit): + value_changed = QtCore.Signal() + + def __init__(self, parent): + super(VariantInputWidget, self).__init__(parent) + + self._origin_value = [] + self._current_value = [] + + self._ignore_value_change = False + self._has_value_changed = False + + self.textChanged.connect(self._on_text_change) + + def has_value_changed(self): + return self._has_value_changed + + def _on_text_change(self): + if self._ignore_value_change: + return + + self._current_value = [self.text()] + self._has_value_changed = self._current_value != self._origin_value + + def set_value(self, variants=None, multiselection_text=None): + if variants is None: + variants = [] + + self._ignore_value_change = True + + self._origin_value = list(variants) + self._current_value = list(variants) + + if not variants: + self.setText("") + + elif len(variants) == 1: + self.setText(self._current_value[0]) + + else: + if multiselection_text is None: + multiselection_text = "|".join(variants) + self.setText("") + self.setPlaceholderText(multiselection_text) + + self._ignore_value_change = False + + class GlobalAttrsWidget(QtWidgets.QWidget): def __init__(self, controller, parent): super(GlobalAttrsWidget, self).__init__(parent) self.controller = controller - variant_input = ReadWriteLineEdit(self) - + variant_input = VariantInputWidget(self) asset_value_widget = AssetsTreeComboBox(controller, self) task_value_widget = TasksCombobox(controller, self) family_value_widget = QtWidgets.QLabel(self) subset_value_widget = QtWidgets.QLabel(self) + variant_input.set_value() subset_value_widget.setText("") family_value_widget.setText("") asset_value_widget.set_selected_items() @@ -435,8 +429,8 @@ class GlobalAttrsWidget(QtWidgets.QWidget): unknown = "N/A" asset_names = set() task_names = set() + variants = set() if len(instances) == 0: - variant = "" family = "" subset_name = "" @@ -445,7 +439,7 @@ class GlobalAttrsWidget(QtWidgets.QWidget): if instance.creator is not None: editable = True - variant = instance.data.get("variant") or unknown + variants.add(instance.data.get("variant") or unknown) family = instance.data.get("family") or unknown asset_names.add(instance.data.get("asset") or unknown) task_names.add(instance.data.get("task") or unknown) @@ -454,19 +448,17 @@ class GlobalAttrsWidget(QtWidgets.QWidget): else: families = set() for instance in instances: + variants.add(instance.data.get("variant") or unknown) families.add(instance.data.get("family") or unknown) asset_names.add(instance.data.get("asset") or unknown) task_names.add(instance.data.get("task") or unknown) - variant = multiselection_text family = multiselection_text subset_name = multiselection_text if len(families) < 4: family = " / ".join(families) - self.variant_input.set_editable(editable) - - self.variant_input.setText(variant) + self.variant_input.set_value(variants, multiselection_text) self.family_value_widget.setText(family) # Set context of asset widget self.asset_value_widget.set_selected_items( From c14f969dc2f6199ae5877149507ecf23184fbd00 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 17 Aug 2021 10:30:06 +0200 Subject: [PATCH 242/736] not code related commit --- openpype/hosts/testhost/api/instances.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/testhost/api/instances.json b/openpype/hosts/testhost/api/instances.json index 0745be3b6b..53b5356ceb 100644 --- a/openpype/hosts/testhost/api/instances.json +++ b/openpype/hosts/testhost/api/instances.json @@ -5,7 +5,7 @@ "subset": "test_oneMyVariant", "active": true, "version": 1, - "asset": "ep01sh0010", + "asset": "sq01_sh0010", "task": "Compositing", "variant": "myVariant", "uuid": "a485f148-9121-46a5-8157-aa64df0fb449", @@ -25,7 +25,7 @@ "subset": "test_oneMyVariant2", "active": true, "version": 1, - "asset": "ep01sh0010", + "asset": "sq01_sh0010", "task": "Compositing", "variant": "myVariant2", "uuid": "a485f148-9121-46a5-8157-aa64df0fb444", @@ -42,7 +42,7 @@ "subset": "test_twoMain", "active": true, "version": 1, - "asset": "ep01sh0010", + "asset": "sq01_sh0010", "task": "Compositing", "variant": "Main", "uuid": "3607bc95-75f6-4648-a58d-e699f413d09f", @@ -59,7 +59,7 @@ "subset": "test_twoMain2", "active": true, "version": 1, - "asset": "ep01sh0010", + "asset": "sq01_sh0020", "task": "Compositing", "variant": "Main2", "uuid": "4ccf56f6-9982-4837-967c-a49695dbe8eb", From 30e83a397400ffec456f2cae19983404d0d94459 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 17 Aug 2021 12:00:42 +0200 Subject: [PATCH 243/736] added view for families and subset names --- openpype/style/style.css | 5 ++ openpype/tools/new_publisher/widgets.py | 87 ++++++++++++++++++++----- 2 files changed, 74 insertions(+), 18 deletions(-) diff --git a/openpype/style/style.css b/openpype/style/style.css index 1fd9110a59..d912e79c91 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -641,6 +641,11 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { border-color: #4E76BB; } +#MultipleItemView:item { + background: {color:bg-view-selection}; + border-radius: 0.4em; +} + #InstanceListView::item { border-radius: 0.3em; margin: 1px; diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 1976e2a903..348c1bbd21 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -386,6 +386,58 @@ class VariantInputWidget(QtWidgets.QLineEdit): self._ignore_value_change = False +class MultipleItemWidget(QtWidgets.QWidget): + def __init__(self, parent): + super(MultipleItemWidget, self).__init__(parent) + + model = QtGui.QStandardItemModel() + + view = QtWidgets.QListView(self) + view.setObjectName("MultipleItemView") + view.setLayoutMode(QtWidgets.QListView.Batched) + view.setViewMode(QtWidgets.QListView.IconMode) + view.setResizeMode(QtWidgets.QListView.Adjust) + view.setWrapping(False) + view.setSpacing(2) + view.setModel(model) + view.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + view.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(view) + + self._view = view + self._model = model + + self._value = [] + + def showEvent(self, event): + super(MultipleItemWidget, self).showEvent(event) + tmp_item = None + if not self._value: + tmp_item = QtGui.QStandardItem("tmp") + self._model.appendRow(tmp_item) + + height = self._view.sizeHintForRow(0) + self.setMaximumHeight(height + (2 * self._view.spacing())) + + if tmp_item is not None: + self._model.clear() + + def set_value(self, value=None): + if value is None: + value = [] + self._value = value + + self._model.clear() + for item_text in value: + item = QtGui.QStandardItem(item_text) + item.setEditable(False) + item.setSelectable(False) + self._model.appendRow(item) + + class GlobalAttrsWidget(QtWidgets.QWidget): def __init__(self, controller, parent): super(GlobalAttrsWidget, self).__init__(parent) @@ -395,14 +447,14 @@ class GlobalAttrsWidget(QtWidgets.QWidget): variant_input = VariantInputWidget(self) asset_value_widget = AssetsTreeComboBox(controller, self) task_value_widget = TasksCombobox(controller, self) - family_value_widget = QtWidgets.QLabel(self) - subset_value_widget = QtWidgets.QLabel(self) + family_value_widget = MultipleItemWidget(self) + subset_value_widget = MultipleItemWidget(self) variant_input.set_value() - subset_value_widget.setText("") - family_value_widget.setText("") asset_value_widget.set_selected_items() task_value_widget.set_selected_items() + family_value_widget.set_value() + subset_value_widget.set_value() main_layout = QtWidgets.QFormLayout(self) main_layout.addRow("Name", variant_input) @@ -414,9 +466,9 @@ class GlobalAttrsWidget(QtWidgets.QWidget): asset_value_widget.selection_changed.connect(self._on_asset_change) self.variant_input = variant_input - self.family_value_widget = family_value_widget self.asset_value_widget = asset_value_widget self.task_value_widget = task_value_widget + self.family_value_widget = family_value_widget self.subset_value_widget = subset_value_widget def _on_asset_change(self): @@ -430,9 +482,10 @@ class GlobalAttrsWidget(QtWidgets.QWidget): asset_names = set() task_names = set() variants = set() + families = set() + subset_names = set() if len(instances) == 0: - family = "" - subset_name = "" + pass elif len(instances) == 1: instance = instances[0] @@ -440,26 +493,23 @@ class GlobalAttrsWidget(QtWidgets.QWidget): editable = True variants.add(instance.data.get("variant") or unknown) - family = instance.data.get("family") or unknown + families.add(instance.data.get("family") or unknown) asset_names.add(instance.data.get("asset") or unknown) task_names.add(instance.data.get("task") or unknown) - subset_name = instance.data.get("subset") or unknown + subset_names.add(instance.data.get("subset") or unknown) else: - families = set() + for instance in instances: variants.add(instance.data.get("variant") or unknown) families.add(instance.data.get("family") or unknown) asset_names.add(instance.data.get("asset") or unknown) task_names.add(instance.data.get("task") or unknown) + subset_names.add(instance.data.get("subset") or unknown) - family = multiselection_text - subset_name = multiselection_text - if len(families) < 4: - family = " / ".join(families) - - self.variant_input.set_value(variants, multiselection_text) - self.family_value_widget.setText(family) + self.variant_input.set_value( + variants, multiselection_text + ) # Set context of asset widget self.asset_value_widget.set_selected_items( asset_names, multiselection_text @@ -469,7 +519,8 @@ class GlobalAttrsWidget(QtWidgets.QWidget): self.task_value_widget.set_selected_items( task_names, multiselection_text ) - self.subset_value_widget.setText(subset_name) + self.family_value_widget.set_value(families) + self.subset_value_widget.set_value(subset_names) class FamilyAttrsWidget(QtWidgets.QWidget): From ad5ddb21ca2472ce28c82f306ecd8dcd9a541cd3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 17 Aug 2021 12:26:37 +0200 Subject: [PATCH 244/736] moved flickcharm to tools folder root --- openpype/tools/{launcher => }/flickcharm.py | 0 openpype/tools/launcher/widgets.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename openpype/tools/{launcher => }/flickcharm.py (100%) diff --git a/openpype/tools/launcher/flickcharm.py b/openpype/tools/flickcharm.py similarity index 100% rename from openpype/tools/launcher/flickcharm.py rename to openpype/tools/flickcharm.py diff --git a/openpype/tools/launcher/widgets.py b/openpype/tools/launcher/widgets.py index 048210115c..c67003c26e 100644 --- a/openpype/tools/launcher/widgets.py +++ b/openpype/tools/launcher/widgets.py @@ -7,7 +7,7 @@ from avalon.vendor import qtawesome from .delegates import ActionDelegate from . import lib from .models import TaskModel, ActionModel, ProjectModel -from .flickcharm import FlickCharm +from openpype.tools.flickcharm import FlickCharm from .constants import ( ACTION_ROLE, GROUP_ROLE, From 29ca8688bca84c173dc22028bbc31073a74fadd5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 17 Aug 2021 12:26:57 +0200 Subject: [PATCH 245/736] use FlickCharm on MultipleItemWidget --- openpype/tools/new_publisher/widgets.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 348c1bbd21..772a55dbb5 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -13,6 +13,8 @@ from constants import ( SORT_VALUE_ROLE ) +from openpype.tools.flickcharm import FlickCharm + SEPARATORS = ("---separator---", "---") @@ -403,6 +405,9 @@ class MultipleItemWidget(QtWidgets.QWidget): view.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) view.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + flick = FlickCharm(parent=view) + flick.activateOn(view) + layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(view) From 9ca94263da4dc7f281ce6da45eab7de5aa5a1186 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 17 Aug 2021 14:38:30 +0200 Subject: [PATCH 246/736] added submit and cancel buttons --- openpype/tools/new_publisher/widgets.py | 166 +++++++++++++++++------- 1 file changed, 117 insertions(+), 49 deletions(-) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets.py index 772a55dbb5..f19acd947b 100644 --- a/openpype/tools/new_publisher/widgets.py +++ b/openpype/tools/new_publisher/widgets.py @@ -199,7 +199,7 @@ class TreeComboBox(QtWidgets.QComboBox): class AssetsTreeComboBox(TreeComboBox): - selection_changed = QtCore.Signal() + value_changed = QtCore.Signal() def __init__(self, controller, parent): model = AssetsHierarchyModel(controller) @@ -210,21 +210,26 @@ class AssetsTreeComboBox(TreeComboBox): self._ignore_index_change = False self._selected_items = [] - self._origin_selection = [] + self._origin_value = [] self._has_value_changed = False self._model = model + self._multiselection_text = None + model.reset() + def set_multiselection_text(self, text): + self._multiselection_text = text + def _on_index_change(self): if self._ignore_index_change: return self._selected_items = [self.currentText()] self._has_value_changed = ( - self._origin_selection != self._selected_items + self._origin_value != self._selected_items ) - self.selection_changed.emit() + self.value_changed.emit() def has_value_changed(self): return self._has_value_changed @@ -232,14 +237,14 @@ class AssetsTreeComboBox(TreeComboBox): def get_selected_items(self): return list(self._selected_items) - def set_selected_items(self, asset_names=None, multiselection_text=None): + def set_selected_items(self, asset_names=None): if asset_names is None: asset_names = [] self._ignore_index_change = True self._has_value_changed = False - self._origin_selection = list(asset_names) + self._origin_value = list(asset_names) self._selected_items = list(asset_names) if not asset_names: self.set_selected_item("") @@ -247,15 +252,19 @@ class AssetsTreeComboBox(TreeComboBox): elif len(asset_names) == 1: self.set_selected_item(tuple(asset_names)[0]) else: + multiselection_text = self._multiselection_text if multiselection_text is None: multiselection_text = "|".join(asset_names) self.set_selected_item(multiselection_text) self._ignore_index_change = False + def reset_to_origin(self): + self.set_selected_items(self._origin_value) + class TasksCombobox(QtWidgets.QComboBox): - selection_changed = QtCore.Signal() + value_changed = QtCore.Signal() def __init__(self, controller, parent): super(TasksCombobox, self).__init__(parent) @@ -269,11 +278,14 @@ class TasksCombobox(QtWidgets.QComboBox): self.currentIndexChanged.connect(self._on_index_change) self._model = model - self._origin_selection = [] + self._origin_value = [] self._selected_items = [] self._has_value_changed = False self._ignore_index_change = False - self._value_is_valid = True + self._multiselection_text = None + + def set_multiselection_text(self, text): + self._multiselection_text = text def _on_index_change(self): if self._ignore_index_change: @@ -281,11 +293,10 @@ class TasksCombobox(QtWidgets.QComboBox): self._selected_items = [self.currentText()] self._has_value_changed = ( - self._origin_selection != self._selected_items + self._origin_value != self._selected_items ) - self._value_is_valid = True - self.selection_changed.emit() + self.value_changed.emit() def has_value_changed(self): return self._has_value_changed @@ -296,25 +307,23 @@ class TasksCombobox(QtWidgets.QComboBox): def set_asset_names(self, asset_names): self._model.set_asset_names(asset_names) - def set_selected_items(self, task_names=None, multiselection_text=None): + def set_selected_items(self, task_names=None): if task_names is None: task_names = [] self._ignore_index_change = True self._has_value_changed = False - self._origin_selection = list(task_names) + self._origin_value = list(task_names) self._selected_items = list(task_names) # Reset current index self.setCurrentIndex(-1) if not task_names: - self._value_is_valid = False self.set_selected_item("") elif len(task_names) == 1: task_name = tuple(task_names)[0] idx = self.findText(task_name) - self._value_is_valid = not idx < 0 self.set_selected_item(task_name) else: @@ -325,13 +334,15 @@ class TasksCombobox(QtWidgets.QComboBox): if not valid_value: break - self._value_is_valid = valid_value + multiselection_text = self._multiselection_text if multiselection_text is None: multiselection_text = "|".join(task_names) self.set_selected_item(multiselection_text) self._ignore_index_change = False + self.value_changed.emit() + def set_selected_item(self, item_name): idx = self.findText(item_name) if idx < 0: @@ -339,6 +350,9 @@ class TasksCombobox(QtWidgets.QComboBox): else: self.setCurrentIndex(idx) + def reset_to_origin(self): + self.set_selected_items(self._origin_value) + class VariantInputWidget(QtWidgets.QLineEdit): value_changed = QtCore.Signal() @@ -351,9 +365,13 @@ class VariantInputWidget(QtWidgets.QLineEdit): self._ignore_value_change = False self._has_value_changed = False + self._multiselection_text = None self.textChanged.connect(self._on_text_change) + def set_multiselection_text(self, text): + self._multiselection_text = text + def has_value_changed(self): return self._has_value_changed @@ -364,7 +382,10 @@ class VariantInputWidget(QtWidgets.QLineEdit): self._current_value = [self.text()] self._has_value_changed = self._current_value != self._origin_value - def set_value(self, variants=None, multiselection_text=None): + def reset_to_origin(self): + self.set_value(self._origin_value) + + def set_value(self, variants=None): if variants is None: variants = [] @@ -373,6 +394,7 @@ class VariantInputWidget(QtWidgets.QLineEdit): self._origin_value = list(variants) self._current_value = list(variants) + self.setPlaceholderText("") if not variants: self.setText("") @@ -380,6 +402,7 @@ class VariantInputWidget(QtWidgets.QLineEdit): self.setText(self._current_value[0]) else: + multiselection_text = self._multiselection_text if multiselection_text is None: multiselection_text = "|".join(variants) self.setText("") @@ -444,6 +467,9 @@ class MultipleItemWidget(QtWidgets.QWidget): class GlobalAttrsWidget(QtWidgets.QWidget): + multiselection_text = "< Multiselection >" + unknown_value = "N/A" + def __init__(self, controller, parent): super(GlobalAttrsWidget, self).__init__(parent) @@ -455,78 +481,120 @@ class GlobalAttrsWidget(QtWidgets.QWidget): family_value_widget = MultipleItemWidget(self) subset_value_widget = MultipleItemWidget(self) + variant_input.set_multiselection_text(self.multiselection_text) + asset_value_widget.set_multiselection_text(self.multiselection_text) + task_value_widget.set_multiselection_text(self.multiselection_text) + variant_input.set_value() asset_value_widget.set_selected_items() task_value_widget.set_selected_items() family_value_widget.set_value() subset_value_widget.set_value() + submit_btn = QtWidgets.QPushButton("Submit", self) + cancel_btn = QtWidgets.QPushButton("Cancel", self) + submit_btn.setEnabled(False) + cancel_btn.setEnabled(False) + + btns_layout = QtWidgets.QHBoxLayout() + btns_layout.setContentsMargins(0, 0, 0, 0) + btns_layout.addStretch(1) + btns_layout.addWidget(submit_btn) + btns_layout.addWidget(cancel_btn) + main_layout = QtWidgets.QFormLayout(self) main_layout.addRow("Name", variant_input) main_layout.addRow("Asset", asset_value_widget) main_layout.addRow("Task", task_value_widget) main_layout.addRow("Family", family_value_widget) main_layout.addRow("Subset", subset_value_widget) + main_layout.addRow(btns_layout) - asset_value_widget.selection_changed.connect(self._on_asset_change) + variant_input.value_changed.connect(self._on_variant_change) + asset_value_widget.value_changed.connect(self._on_asset_change) + task_value_widget.value_changed.connect(self._on_task_change) + submit_btn.clicked.connect(self._on_submit) + cancel_btn.clicked.connect(self._on_cancel) self.variant_input = variant_input self.asset_value_widget = asset_value_widget self.task_value_widget = task_value_widget self.family_value_widget = family_value_widget self.subset_value_widget = subset_value_widget + self.submit_btn = submit_btn + self.cancel_btn = cancel_btn + + def _on_submit(self): + print("submit") + self.cancel_btn.setEnabled(False) + self.submit_btn.setEnabled(False) + + def _on_cancel(self): + self.variant_input.reset_to_origin() + self.asset_value_widget.reset_to_origin() + self.task_value_widget.reset_to_origin() + self.cancel_btn.setEnabled(False) + self.submit_btn.setEnabled(False) + + def _on_value_change(self): + any_changed = ( + self.variant_input.has_value_changed() + or self.asset_value_widget.has_value_changed() + or self.task_value_widget.has_value_changed() + ) + + self.cancel_btn.setEnabled(any_changed) + self.submit_btn.setEnabled(any_changed) + + def _on_variant_change(self): + self._on_value_change() def _on_asset_change(self): asset_names = self.asset_value_widget.get_selected_items() self.task_value_widget.set_asset_names(asset_names) + self._on_value_change() + + def _on_task_change(self): + self._on_value_change() def set_current_instances(self, instances): - editable = False - multiselection_text = "< Multiselection >" - unknown = "N/A" + self.cancel_btn.setEnabled(False) + self.submit_btn.setEnabled(False) + asset_names = set() task_names = set() variants = set() families = set() subset_names = set() + + editable = True if len(instances) == 0: - pass + editable = False - elif len(instances) == 1: - instance = instances[0] - if instance.creator is not None: - editable = True + for instance in instances: + if instance.creator is None: + editable = False - variants.add(instance.data.get("variant") or unknown) - families.add(instance.data.get("family") or unknown) - asset_names.add(instance.data.get("asset") or unknown) - task_names.add(instance.data.get("task") or unknown) - subset_names.add(instance.data.get("subset") or unknown) + variants.add(instance.data.get("variant") or self.unknown_value) + families.add(instance.data.get("family") or self.unknown_value) + asset_names.add(instance.data.get("asset") or self.unknown_value) + task_names.add(instance.data.get("task") or self.unknown_value) + subset_names.add(instance.data.get("subset") or self.unknown_value) - else: + self.variant_input.set_value(variants) - for instance in instances: - variants.add(instance.data.get("variant") or unknown) - families.add(instance.data.get("family") or unknown) - asset_names.add(instance.data.get("asset") or unknown) - task_names.add(instance.data.get("task") or unknown) - subset_names.add(instance.data.get("subset") or unknown) - - self.variant_input.set_value( - variants, multiselection_text - ) # Set context of asset widget - self.asset_value_widget.set_selected_items( - asset_names, multiselection_text - ) + self.asset_value_widget.set_selected_items(asset_names) # Set context of task widget self.task_value_widget.set_asset_names(asset_names) - self.task_value_widget.set_selected_items( - task_names, multiselection_text - ) + self.task_value_widget.set_selected_items(task_names) self.family_value_widget.set_value(families) self.subset_value_widget.set_value(subset_names) + self.variant_input.setEnabled(editable) + self.asset_value_widget.setEnabled(editable) + self.task_value_widget.setEnabled(editable) + class FamilyAttrsWidget(QtWidgets.QWidget): def __init__(self, controller, parent): From b93ac1598e5379778e470a25c8803c384dddc7c2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 18 Aug 2021 10:49:04 +0200 Subject: [PATCH 247/736] added project name property to controller --- openpype/tools/new_publisher/control.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 45f12e854e..53084909fc 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -144,6 +144,10 @@ class PublisherController: self._asset_docs_cache = AssetDocsCache(self) + @property + def project_name(self): + return self.dbcon.Session["AVALON_PROJECT"] + @property def dbcon(self): return self.create_context.dbcon @@ -392,10 +396,11 @@ class PublisherController: print(plugin, instance) self._publish_next_process() + def get_asset_docs(self): + return self._asset_docs_cache.get_asset_docs() + def get_asset_hierarchy(self): - _queue = collections.deque( - self._asset_docs_cache.get_asset_docs() - ) + _queue = collections.deque(self.get_asset_docs()) output = collections.defaultdict(list) while _queue: From fa66aa7033c048d0610f8860caa26b78b286811a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 18 Aug 2021 10:49:22 +0200 Subject: [PATCH 248/736] added validation attributes to created instance --- openpype/pipeline/create/context.py | 33 +++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index fc93cce51f..1c870afb66 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -296,6 +296,39 @@ class CreatedInstance: if not self._data.get("uuid"): self._data["uuid"] = str(uuid4()) + self._asset_is_valid = self.has_set_asset + self._task_is_valid = self.has_set_task + + @property + def has_set_asset(self): + return "asset" in self._data + + @property + def has_set_task(self): + return "task" in self._data + + @property + def has_valid_context(self): + return self.has_valid_asset and self.has_valid_task + + @property + def has_valid_asset(self): + if not self.has_set_asset: + return False + return self._asset_is_valid + + @property + def has_valid_task(self): + if not self.has_set_task: + return False + return self._task_is_valid + + def set_asset_invalid(self, invalid): + self._asset_is_valid = not invalid + + def set_task_invalid(self, invalid): + self._task_is_valid = not invalid + @property def id(self): return self._data["uuid"] From 107b8841b53c9f0fa6f8cf2203305904a0c397b8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 18 Aug 2021 10:54:13 +0200 Subject: [PATCH 249/736] added widgets to subfolder --- openpype/tools/new_publisher/widgets/__init__.py | 16 ++++++++++++++++ .../tools/new_publisher/{ => widgets}/widgets.py | 0 2 files changed, 16 insertions(+) create mode 100644 openpype/tools/new_publisher/widgets/__init__.py rename openpype/tools/new_publisher/{ => widgets}/widgets.py (100%) diff --git a/openpype/tools/new_publisher/widgets/__init__.py b/openpype/tools/new_publisher/widgets/__init__.py new file mode 100644 index 0000000000..82953be090 --- /dev/null +++ b/openpype/tools/new_publisher/widgets/__init__.py @@ -0,0 +1,16 @@ +from .widgets import ( + PublishOverlayFrame, + SubsetAttributesWidget, + InstanceCardView, + InstanceListView, + CreateDialog +) + + +__all__ = ( + "PublishOverlayFrame", + "SubsetAttributesWidget", + "InstanceCardView", + "InstanceListView", + "CreateDialog" +) diff --git a/openpype/tools/new_publisher/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py similarity index 100% rename from openpype/tools/new_publisher/widgets.py rename to openpype/tools/new_publisher/widgets/widgets.py From 163f88b553d415f8cdc96a5ac4ce4e38eb65e9e8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 18 Aug 2021 10:56:18 +0200 Subject: [PATCH 250/736] moved create dialog to separated file --- .../tools/new_publisher/widgets/__init__.py | 5 +- .../new_publisher/widgets/create_dialog.py | 446 ++++++++++++++++++ .../tools/new_publisher/widgets/widgets.py | 442 ----------------- 3 files changed, 450 insertions(+), 443 deletions(-) create mode 100644 openpype/tools/new_publisher/widgets/create_dialog.py diff --git a/openpype/tools/new_publisher/widgets/__init__.py b/openpype/tools/new_publisher/widgets/__init__.py index 82953be090..11e8f1f10e 100644 --- a/openpype/tools/new_publisher/widgets/__init__.py +++ b/openpype/tools/new_publisher/widgets/__init__.py @@ -2,7 +2,9 @@ from .widgets import ( PublishOverlayFrame, SubsetAttributesWidget, InstanceCardView, - InstanceListView, + InstanceListView +) +from .create_dialog import ( CreateDialog ) @@ -12,5 +14,6 @@ __all__ = ( "SubsetAttributesWidget", "InstanceCardView", "InstanceListView", + "CreateDialog" ) diff --git a/openpype/tools/new_publisher/widgets/create_dialog.py b/openpype/tools/new_publisher/widgets/create_dialog.py new file mode 100644 index 0000000000..fe11c89100 --- /dev/null +++ b/openpype/tools/new_publisher/widgets/create_dialog.py @@ -0,0 +1,446 @@ +import sys +import re +import traceback +import copy + +from Qt import QtWidgets, QtCore, QtGui + +from openpype.pipeline.create import CreatorError + +SEPARATORS = ("---separator---", "---") + + +class CreateErrorMessageBox(QtWidgets.QDialog): + def __init__( + self, + family, + subset_name, + asset_name, + exc_msg, + formatted_traceback, + parent=None + ): + super(CreateErrorMessageBox, self).__init__(parent) + self.setWindowTitle("Creation failed") + self.setFocusPolicy(QtCore.Qt.StrongFocus) + if not parent: + self.setWindowFlags( + self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint + ) + + body_layout = QtWidgets.QVBoxLayout(self) + + main_label = ( + "Failed to create" + ) + main_label_widget = QtWidgets.QLabel(main_label, self) + body_layout.addWidget(main_label_widget) + + item_name_template = ( + "Family: {}
" + "Subset: {}
" + "Asset: {}
" + ) + exc_msg_template = "{}" + + line = self._create_line() + body_layout.addWidget(line) + + item_name = item_name_template.format(family, subset_name, asset_name) + item_name_widget = QtWidgets.QLabel( + item_name.replace("\n", "
"), self + ) + body_layout.addWidget(item_name_widget) + + exc_msg = exc_msg_template.format(exc_msg.replace("\n", "
")) + message_label_widget = QtWidgets.QLabel(exc_msg, self) + body_layout.addWidget(message_label_widget) + + if formatted_traceback: + tb_widget = QtWidgets.QLabel( + formatted_traceback.replace("\n", "
"), self + ) + tb_widget.setTextInteractionFlags( + QtCore.Qt.TextBrowserInteraction + ) + body_layout.addWidget(tb_widget) + + footer_widget = QtWidgets.QWidget(self) + footer_layout = QtWidgets.QHBoxLayout(footer_widget) + button_box = QtWidgets.QDialogButtonBox(QtCore.Qt.Vertical) + button_box.setStandardButtons( + QtWidgets.QDialogButtonBox.StandardButton.Ok + ) + button_box.accepted.connect(self._on_accept) + footer_layout.addWidget(button_box, alignment=QtCore.Qt.AlignRight) + body_layout.addWidget(footer_widget) + + def _on_accept(self): + self.close() + + def _create_line(self): + line = QtWidgets.QFrame(self) + line.setFixedHeight(2) + line.setFrameShape(QtWidgets.QFrame.HLine) + line.setFrameShadow(QtWidgets.QFrame.Sunken) + return line + + +class CreateDialog(QtWidgets.QDialog): + def __init__(self, controller, parent=None): + super(CreateDialog, self).__init__(parent) + + self.controller = controller + + self._last_pos = None + self._asset_doc = None + self._subset_names = None + self._selected_creator = None + + self._prereq_available = False + + self.message_dialog = None + + family_view = QtWidgets.QListView(self) + family_model = QtGui.QStandardItemModel() + family_view.setModel(family_model) + + variant_input = QtWidgets.QLineEdit(self) + variant_input.setObjectName("VariantInput") + + variant_hints_btn = QtWidgets.QPushButton(self) + variant_hints_btn.setFixedWidth(18) + + variant_hints_menu = QtWidgets.QMenu(variant_hints_btn) + variant_hints_group = QtWidgets.QActionGroup(variant_hints_menu) + variant_hints_btn.setMenu(variant_hints_menu) + + variant_layout = QtWidgets.QHBoxLayout() + variant_layout.setContentsMargins(0, 0, 0, 0) + variant_layout.setSpacing(0) + variant_layout.addWidget(variant_input, 1) + variant_layout.addWidget(variant_hints_btn, 0) + + asset_name_input = QtWidgets.QLineEdit(self) + asset_name_input.setEnabled(False) + + subset_name_input = QtWidgets.QLineEdit(self) + subset_name_input.setEnabled(False) + + checkbox_inputs = QtWidgets.QWidget(self) + auto_close_checkbox = QtWidgets.QCheckBox( + "Auto-close", checkbox_inputs + ) + use_selection_checkbox = QtWidgets.QCheckBox( + "Use selection", checkbox_inputs + ) + + checkbox_layout = QtWidgets.QHBoxLayout(checkbox_inputs) + checkbox_layout.setContentsMargins(0, 0, 0, 0) + checkbox_layout.addWidget(auto_close_checkbox) + checkbox_layout.addWidget(use_selection_checkbox) + + create_btn = QtWidgets.QPushButton("Create", self) + create_btn.setEnabled(False) + + layout = QtWidgets.QVBoxLayout(self) + layout.addWidget(QtWidgets.QLabel("Family:", self)) + layout.addWidget(family_view, 1) + layout.addWidget(QtWidgets.QLabel("Asset:", self)) + layout.addWidget(asset_name_input, 0) + layout.addWidget(QtWidgets.QLabel("Name:", self)) + layout.addLayout(variant_layout, 0) + layout.addWidget(QtWidgets.QLabel("Subset:", self)) + layout.addWidget(subset_name_input, 0) + layout.addWidget(checkbox_inputs, 0) + layout.addWidget(create_btn, 0) + + create_btn.clicked.connect(self._on_create) + variant_input.returnPressed.connect(self._on_create) + variant_input.textChanged.connect(self._on_variant_change) + family_view.selectionModel().currentChanged.connect( + self._on_family_change + ) + variant_hints_menu.triggered.connect(self._on_variant_action) + + controller.add_plugins_refresh_callback(self._on_plugins_refresh) + + self.asset_name_input = asset_name_input + self.subset_name_input = subset_name_input + + self.variant_input = variant_input + self.variant_hints_btn = variant_hints_btn + self.variant_hints_menu = variant_hints_menu + self.variant_hints_group = variant_hints_group + + self.family_model = family_model + self.family_view = family_view + self.auto_close_checkbox = auto_close_checkbox + self.use_selection_checkbox = auto_close_checkbox + self.create_btn = create_btn + + @property + def dbcon(self): + return self.controller.dbcon + + def refresh(self): + self._prereq_available = True + + # Refresh data before update of creators + self._refresh_asset() + # Then refresh creators which may trigger callbacks using refreshed + # data + self._refresh_creators() + + if self._asset_doc is None: + self.asset_name_input.setText("< Asset is not set >") + self._prereq_available = False + + if self.family_model.rowCount() < 1: + self._prereq_available = False + + self.create_btn.setEnabled(self._prereq_available) + self.family_view.setEnabled(self._prereq_available) + self.variant_input.setEnabled(self._prereq_available) + self.variant_hints_btn.setEnabled(self._prereq_available) + + def _refresh_asset(self): + asset_name = self.dbcon.Session.get("AVALON_ASSET") + + # Skip if asset did not change + if self._asset_doc and self._asset_doc["name"] == asset_name: + return + + # Make sure `_asset_doc` and `_subset_names` variables are reset + self._asset_doc = None + self._subset_names = None + if asset_name is None: + return + + asset_doc = self.dbcon.find_one({ + "type": "asset", + "name": asset_name + }) + self._asset_doc = asset_doc + + if asset_doc: + self.asset_name_input.setText(asset_doc["name"]) + subset_docs = self.dbcon.find( + { + "type": "subset", + "parent": asset_doc["_id"] + }, + {"name": 1} + ) + self._subset_names = set(subset_docs.distinct("name")) + + def _refresh_creators(self): + # Refresh creators and add their families to list + existing_items = {} + old_families = set() + for row in range(self.family_model.rowCount()): + item = self.family_model.item(row, 0) + family = item.data(QtCore.Qt.DisplayRole) + existing_items[family] = item + old_families.add(family) + + # Add new families + new_families = set() + for family, creator in self.controller.creators.items(): + # TODO add details about creator + new_families.add(family) + if family not in existing_items: + item = QtGui.QStandardItem(family) + self.family_model.appendRow(item) + + # Remove families that are no more available + for family in (old_families - new_families): + item = existing_items[family] + self.family_model.takeRow(item.row()) + + if self.family_model.rowCount() < 1: + return + + # Make sure there is a selection + indexes = self.family_view.selectedIndexes() + if not indexes: + index = self.family_model.index(0, 0) + self.family_view.setCurrentIndex(index) + + def _on_plugins_refresh(self): + # Trigger refresh only if is visible + if self.isVisible(): + self.refresh() + + def _on_family_change(self, new_index, _old_index): + family = None + if new_index.isValid(): + family = new_index.data(QtCore.Qt.DisplayRole) + + creator = self.controller.creators.get(family) + self._selected_creator = creator + if not creator: + return + + default_variants = creator.get_default_variants() + if not default_variants: + default_variants = ["Main"] + + default_variant = creator.get_default_variant() + if not default_variant: + default_variant = default_variants[0] + + for action in tuple(self.variant_hints_menu.actions()): + self.variant_hints_menu.removeAction(action) + action.deleteLater() + + for variant in default_variants: + if variant in SEPARATORS: + self.variant_hints_menu.addSeparator() + elif variant: + self.variant_hints_menu.addAction(variant) + + self.variant_input.setText(default_variant or "Main") + + def _on_variant_action(self, action): + value = action.text() + if self.variant_input.text() != value: + self.variant_input.setText(value) + + def _on_variant_change(self, variant_value): + if not self._prereq_available or not self._selected_creator: + if self.subset_name_input.text(): + self.subset_name_input.setText("") + return + + project_name = self.dbcon.Session["AVALON_PROJECT"] + task_name = self.dbcon.Session.get("AVALON_TASK") + + asset_doc = copy.deepcopy(self._asset_doc) + # Calculate subset name with Creator plugin + subset_name = self._selected_creator.get_subset_name( + variant_value, task_name, asset_doc, project_name + ) + self.subset_name_input.setText(subset_name) + + self._validate_subset_name(subset_name, variant_value) + + def _validate_subset_name(self, subset_name, variant_value): + # Get all subsets of the current asset + if self._subset_names: + existing_subset_names = set(self._subset_names) + else: + existing_subset_names = set() + existing_subset_names_low = set( + _name.lower() + for _name in existing_subset_names + ) + + # Replace + compare_regex = re.compile(re.sub( + variant_value, "(.+)", subset_name, flags=re.IGNORECASE + )) + variant_hints = set() + if variant_value: + for _name in existing_subset_names: + _result = compare_regex.search(_name) + if _result: + variant_hints |= set(_result.groups()) + + # Remove previous hints from menu + for action in tuple(self.variant_hints_group.actions()): + self.variant_hints_group.removeAction(action) + self.variant_hints_menu.removeAction(action) + action.deleteLater() + + # Add separator if there are hints and menu already has actions + if variant_hints and self.variant_hints_menu.actions(): + self.variant_hints_menu.addSeparator() + + # Add hints to actions + for variant_hint in variant_hints: + action = self.variant_hints_menu.addAction(variant_hint) + self.variant_hints_group.addAction(action) + + # Indicate subset existence + if not variant_value: + property_value = "empty" + + elif subset_name.lower() in existing_subset_names_low: + # validate existence of subset name with lowered text + # - "renderMain" vs. "rendermain" mean same path item for + # windows + property_value = "exists" + else: + property_value = "new" + + current_value = self.variant_input.property("state") + if current_value != property_value: + self.variant_input.setProperty("state", property_value) + self.variant_input.style().polish(self.variant_input) + + variant_is_valid = variant_value.strip() != "" + if variant_is_valid != self.create_btn.isEnabled(): + self.create_btn.setEnabled(variant_is_valid) + + def moveEvent(self, event): + super(CreateDialog, self).moveEvent(event) + self._last_pos = self.pos() + + def showEvent(self, event): + super(CreateDialog, self).showEvent(event) + if self._last_pos is not None: + self.move(self._last_pos) + + self.refresh() + + def _on_create(self): + indexes = self.family_view.selectedIndexes() + if not indexes or len(indexes) > 1: + return + + if not self.create_btn.isEnabled(): + return + + index = indexes[0] + family = index.data(QtCore.Qt.DisplayRole) + subset_name = self.subset_name_input.text() + variant = self.variant_input.text() + asset_name = self._asset_doc["name"] + task_name = self.dbcon.Session.get("AVALON_TASK") + options = { + "useSelection": self.use_selection_checkbox.isChecked() + } + # Where to define these data? + # - what data show be stored? + instance_data = { + "asset": asset_name, + "task": task_name, + "variant": variant, + "family": family + } + + error_info = None + try: + self.controller.create(family, subset_name, instance_data, options) + + except CreatorError as exc: + error_info = (str(exc), None) + + except Exception as exc: + exc_type, exc_value, exc_traceback = sys.exc_info() + formatted_traceback = "".join(traceback.format_exception( + exc_type, exc_value, exc_traceback + )) + error_info = (str(exc), formatted_traceback) + + if error_info: + box = CreateErrorMessageBox( + family, subset_name, asset_name, *error_info + ) + box.show() + # Store dialog so is not garbage collected before is shown + self.message_dialog = box + + if self.auto_close_checkbox.isChecked(): + self.hide() diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index f19acd947b..9b1456426f 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -1,12 +1,8 @@ import os -import sys -import re import copy import collections -import traceback from Qt import QtWidgets, QtCore, QtGui -from openpype.pipeline.create import CreatorError from openpype.widgets.attribute_defs import create_widget_for_attr_def from constants import ( INSTANCE_ID_ROLE, @@ -15,8 +11,6 @@ from constants import ( from openpype.tools.flickcharm import FlickCharm -SEPARATORS = ("---separator---", "---") - def get_default_thumbnail_image_path(): dirpath = os.path.dirname(os.path.abspath(__file__)) @@ -847,366 +841,6 @@ class ThumbnailWidget(QtWidgets.QWidget): self.current_pix = None -class CreateDialog(QtWidgets.QDialog): - def __init__(self, controller, parent=None): - super(CreateDialog, self).__init__(parent) - - self.controller = controller - - self._last_pos = None - self._asset_doc = None - self._subset_names = None - self._selected_creator = None - - self._prereq_available = False - - self.message_dialog = None - - family_view = QtWidgets.QListView(self) - family_model = QtGui.QStandardItemModel() - family_view.setModel(family_model) - - variant_input = QtWidgets.QLineEdit(self) - variant_input.setObjectName("VariantInput") - - variant_hints_btn = QtWidgets.QPushButton(self) - variant_hints_btn.setFixedWidth(18) - - variant_hints_menu = QtWidgets.QMenu(variant_hints_btn) - variant_hints_group = QtWidgets.QActionGroup(variant_hints_menu) - variant_hints_btn.setMenu(variant_hints_menu) - - variant_layout = QtWidgets.QHBoxLayout() - variant_layout.setContentsMargins(0, 0, 0, 0) - variant_layout.setSpacing(0) - variant_layout.addWidget(variant_input, 1) - variant_layout.addWidget(variant_hints_btn, 0) - - asset_name_input = QtWidgets.QLineEdit(self) - asset_name_input.setEnabled(False) - - subset_name_input = QtWidgets.QLineEdit(self) - subset_name_input.setEnabled(False) - - checkbox_inputs = QtWidgets.QWidget(self) - auto_close_checkbox = QtWidgets.QCheckBox( - "Auto-close", checkbox_inputs - ) - use_selection_checkbox = QtWidgets.QCheckBox( - "Use selection", checkbox_inputs - ) - - checkbox_layout = QtWidgets.QHBoxLayout(checkbox_inputs) - checkbox_layout.setContentsMargins(0, 0, 0, 0) - checkbox_layout.addWidget(auto_close_checkbox) - checkbox_layout.addWidget(use_selection_checkbox) - - create_btn = QtWidgets.QPushButton("Create", self) - create_btn.setEnabled(False) - - layout = QtWidgets.QVBoxLayout(self) - layout.addWidget(QtWidgets.QLabel("Family:", self)) - layout.addWidget(family_view, 1) - layout.addWidget(QtWidgets.QLabel("Asset:", self)) - layout.addWidget(asset_name_input, 0) - layout.addWidget(QtWidgets.QLabel("Name:", self)) - layout.addLayout(variant_layout, 0) - layout.addWidget(QtWidgets.QLabel("Subset:", self)) - layout.addWidget(subset_name_input, 0) - layout.addWidget(checkbox_inputs, 0) - layout.addWidget(create_btn, 0) - - create_btn.clicked.connect(self._on_create) - variant_input.returnPressed.connect(self._on_create) - variant_input.textChanged.connect(self._on_variant_change) - family_view.selectionModel().currentChanged.connect( - self._on_family_change - ) - variant_hints_menu.triggered.connect(self._on_variant_action) - - controller.add_plugins_refresh_callback(self._on_plugins_refresh) - - self.asset_name_input = asset_name_input - self.subset_name_input = subset_name_input - - self.variant_input = variant_input - self.variant_hints_btn = variant_hints_btn - self.variant_hints_menu = variant_hints_menu - self.variant_hints_group = variant_hints_group - - self.family_model = family_model - self.family_view = family_view - self.auto_close_checkbox = auto_close_checkbox - self.use_selection_checkbox = auto_close_checkbox - self.create_btn = create_btn - - @property - def dbcon(self): - return self.controller.dbcon - - def refresh(self): - self._prereq_available = True - - # Refresh data before update of creators - self._refresh_asset() - # Then refresh creators which may trigger callbacks using refreshed - # data - self._refresh_creators() - - if self._asset_doc is None: - self.asset_name_input.setText("< Asset is not set >") - self._prereq_available = False - - if self.family_model.rowCount() < 1: - self._prereq_available = False - - self.create_btn.setEnabled(self._prereq_available) - self.family_view.setEnabled(self._prereq_available) - self.variant_input.setEnabled(self._prereq_available) - self.variant_hints_btn.setEnabled(self._prereq_available) - - def _refresh_asset(self): - asset_name = self.dbcon.Session.get("AVALON_ASSET") - - # Skip if asset did not change - if self._asset_doc and self._asset_doc["name"] == asset_name: - return - - # Make sure `_asset_doc` and `_subset_names` variables are reset - self._asset_doc = None - self._subset_names = None - if asset_name is None: - return - - asset_doc = self.dbcon.find_one({ - "type": "asset", - "name": asset_name - }) - self._asset_doc = asset_doc - - if asset_doc: - self.asset_name_input.setText(asset_doc["name"]) - subset_docs = self.dbcon.find( - { - "type": "subset", - "parent": asset_doc["_id"] - }, - {"name": 1} - ) - self._subset_names = set(subset_docs.distinct("name")) - - def _refresh_creators(self): - # Refresh creators and add their families to list - existing_items = {} - old_families = set() - for row in range(self.family_model.rowCount()): - item = self.family_model.item(row, 0) - family = item.data(QtCore.Qt.DisplayRole) - existing_items[family] = item - old_families.add(family) - - # Add new families - new_families = set() - for family, creator in self.controller.creators.items(): - # TODO add details about creator - new_families.add(family) - if family not in existing_items: - item = QtGui.QStandardItem(family) - self.family_model.appendRow(item) - - # Remove families that are no more available - for family in (old_families - new_families): - item = existing_items[family] - self.family_model.takeRow(item.row()) - - if self.family_model.rowCount() < 1: - return - - # Make sure there is a selection - indexes = self.family_view.selectedIndexes() - if not indexes: - index = self.family_model.index(0, 0) - self.family_view.setCurrentIndex(index) - - def _on_plugins_refresh(self): - # Trigger refresh only if is visible - if self.isVisible(): - self.refresh() - - def _on_family_change(self, new_index, _old_index): - family = None - if new_index.isValid(): - family = new_index.data(QtCore.Qt.DisplayRole) - - creator = self.controller.creators.get(family) - self._selected_creator = creator - if not creator: - return - - default_variants = creator.get_default_variants() - if not default_variants: - default_variants = ["Main"] - - default_variant = creator.get_default_variant() - if not default_variant: - default_variant = default_variants[0] - - for action in tuple(self.variant_hints_menu.actions()): - self.variant_hints_menu.removeAction(action) - action.deleteLater() - - for variant in default_variants: - if variant in SEPARATORS: - self.variant_hints_menu.addSeparator() - elif variant: - self.variant_hints_menu.addAction(variant) - - self.variant_input.setText(default_variant or "Main") - - def _on_variant_action(self, action): - value = action.text() - if self.variant_input.text() != value: - self.variant_input.setText(value) - - def _on_variant_change(self, variant_value): - if not self._prereq_available or not self._selected_creator: - if self.subset_name_input.text(): - self.subset_name_input.setText("") - return - - project_name = self.dbcon.Session["AVALON_PROJECT"] - task_name = self.dbcon.Session.get("AVALON_TASK") - - asset_doc = copy.deepcopy(self._asset_doc) - # Calculate subset name with Creator plugin - subset_name = self._selected_creator.get_subset_name( - variant_value, task_name, asset_doc, project_name - ) - self.subset_name_input.setText(subset_name) - - self._validate_subset_name(subset_name, variant_value) - - def _validate_subset_name(self, subset_name, variant_value): - # Get all subsets of the current asset - if self._subset_names: - existing_subset_names = set(self._subset_names) - else: - existing_subset_names = set() - existing_subset_names_low = set( - _name.lower() - for _name in existing_subset_names - ) - - # Replace - compare_regex = re.compile(re.sub( - variant_value, "(.+)", subset_name, flags=re.IGNORECASE - )) - variant_hints = set() - if variant_value: - for _name in existing_subset_names: - _result = compare_regex.search(_name) - if _result: - variant_hints |= set(_result.groups()) - - # Remove previous hints from menu - for action in tuple(self.variant_hints_group.actions()): - self.variant_hints_group.removeAction(action) - self.variant_hints_menu.removeAction(action) - action.deleteLater() - - # Add separator if there are hints and menu already has actions - if variant_hints and self.variant_hints_menu.actions(): - self.variant_hints_menu.addSeparator() - - # Add hints to actions - for variant_hint in variant_hints: - action = self.variant_hints_menu.addAction(variant_hint) - self.variant_hints_group.addAction(action) - - # Indicate subset existence - if not variant_value: - property_value = "empty" - - elif subset_name.lower() in existing_subset_names_low: - # validate existence of subset name with lowered text - # - "renderMain" vs. "rendermain" mean same path item for - # windows - property_value = "exists" - else: - property_value = "new" - - current_value = self.variant_input.property("state") - if current_value != property_value: - self.variant_input.setProperty("state", property_value) - self.variant_input.style().polish(self.variant_input) - - variant_is_valid = variant_value.strip() != "" - if variant_is_valid != self.create_btn.isEnabled(): - self.create_btn.setEnabled(variant_is_valid) - - def moveEvent(self, event): - super(CreateDialog, self).moveEvent(event) - self._last_pos = self.pos() - - def showEvent(self, event): - super(CreateDialog, self).showEvent(event) - if self._last_pos is not None: - self.move(self._last_pos) - - self.refresh() - - def _on_create(self): - indexes = self.family_view.selectedIndexes() - if not indexes or len(indexes) > 1: - return - - if not self.create_btn.isEnabled(): - return - - index = indexes[0] - family = index.data(QtCore.Qt.DisplayRole) - subset_name = self.subset_name_input.text() - variant = self.variant_input.text() - asset_name = self._asset_doc["name"] - task_name = self.dbcon.Session.get("AVALON_TASK") - options = { - "useSelection": self.use_selection_checkbox.isChecked() - } - # Where to define these data? - # - what data show be stored? - instance_data = { - "asset": asset_name, - "task": task_name, - "variant": variant, - "family": family - } - - error_info = None - try: - self.controller.create(family, subset_name, instance_data, options) - - except CreatorError as exc: - error_info = (str(exc), None) - - except Exception as exc: - exc_type, exc_value, exc_traceback = sys.exc_info() - formatted_traceback = "".join(traceback.format_exception( - exc_type, exc_value, exc_traceback - )) - error_info = (str(exc), formatted_traceback) - - if error_info: - box = CreateErrorMessageBox( - family, subset_name, asset_name, *error_info - ) - box.show() - # Store dialog so is not garbage collected before is shown - self.message_dialog = box - - if self.auto_close_checkbox.isChecked(): - self.hide() - - class InstanceCardWidget(QtWidgets.QWidget): active_changed = QtCore.Signal(str, bool) @@ -1861,79 +1495,3 @@ class PublishOverlayFrame(QtWidgets.QFrame): def set_progress(self, value): self.progress_widget.setValue(value) - - -class CreateErrorMessageBox(QtWidgets.QDialog): - def __init__( - self, - family, - subset_name, - asset_name, - exc_msg, - formatted_traceback, - parent=None - ): - super(CreateErrorMessageBox, self).__init__(parent) - self.setWindowTitle("Creation failed") - self.setFocusPolicy(QtCore.Qt.StrongFocus) - if not parent: - self.setWindowFlags( - self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint - ) - - body_layout = QtWidgets.QVBoxLayout(self) - - main_label = ( - "Failed to create" - ) - main_label_widget = QtWidgets.QLabel(main_label, self) - body_layout.addWidget(main_label_widget) - - item_name_template = ( - "Family: {}
" - "Subset: {}
" - "Asset: {}
" - ) - exc_msg_template = "{}" - - line = self._create_line() - body_layout.addWidget(line) - - item_name = item_name_template.format(family, subset_name, asset_name) - item_name_widget = QtWidgets.QLabel( - item_name.replace("\n", "
"), self - ) - body_layout.addWidget(item_name_widget) - - exc_msg = exc_msg_template.format(exc_msg.replace("\n", "
")) - message_label_widget = QtWidgets.QLabel(exc_msg, self) - body_layout.addWidget(message_label_widget) - - if formatted_traceback: - tb_widget = QtWidgets.QLabel( - formatted_traceback.replace("\n", "
"), self - ) - tb_widget.setTextInteractionFlags( - QtCore.Qt.TextBrowserInteraction - ) - body_layout.addWidget(tb_widget) - - footer_widget = QtWidgets.QWidget(self) - footer_layout = QtWidgets.QHBoxLayout(footer_widget) - button_box = QtWidgets.QDialogButtonBox(QtCore.Qt.Vertical) - button_box.setStandardButtons( - QtWidgets.QDialogButtonBox.StandardButton.Ok - ) - button_box.accepted.connect(self._on_accept) - footer_layout.addWidget(button_box, alignment=QtCore.Qt.AlignRight) - body_layout.addWidget(footer_widget) - - def _on_accept(self): - self.close() - - def _create_line(self): - line = QtWidgets.QFrame(self) - line.setFixedHeight(2) - line.setFrameShape(QtWidgets.QFrame.HLine) - line.setFrameShadow(QtWidgets.QFrame.Sunken) - return line From e2f6653fced5bc607d475c308e8d1a625070e8dd Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 18 Aug 2021 10:56:48 +0200 Subject: [PATCH 251/736] added delegate to combobox --- openpype/tools/new_publisher/widgets/widgets.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 9b1456426f..6a6d3da415 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -266,11 +266,15 @@ class TasksCombobox(QtWidgets.QComboBox): self.setEditable(True) self.lineEdit().setReadOnly(True) + delegate = QtWidgets.QStyledItemDelegate() + self.setItemDelegate(delegate) + model = TasksModel(controller) self.setModel(model) self.currentIndexChanged.connect(self._on_index_change) + self._delegate = delegate self._model = model self._origin_value = [] self._selected_items = [] From 9d43aa9931a62bd09fcc98196bc61bc86347bd41 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 18 Aug 2021 10:57:14 +0200 Subject: [PATCH 252/736] variant input is triggering value changed signal --- openpype/tools/new_publisher/widgets/widgets.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 6a6d3da415..4038ad43c2 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -380,9 +380,14 @@ class VariantInputWidget(QtWidgets.QLineEdit): self._current_value = [self.text()] self._has_value_changed = self._current_value != self._origin_value + self.value_changed.emit() + def reset_to_origin(self): self.set_value(self._origin_value) + def get_value(self): + return copy.deepcopy(self._current_value) + def set_value(self, variants=None): if variants is None: variants = [] From 0523f869ea633fb9561adbd4fe18d6e4057a19be Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 18 Aug 2021 10:57:55 +0200 Subject: [PATCH 253/736] GlobalAttrsWidget is storing current instances --- openpype/tools/new_publisher/widgets/widgets.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 4038ad43c2..2a538b8cee 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -477,6 +477,7 @@ class GlobalAttrsWidget(QtWidgets.QWidget): super(GlobalAttrsWidget, self).__init__(parent) self.controller = controller + self._current_instances = [] variant_input = VariantInputWidget(self) asset_value_widget = AssetsTreeComboBox(controller, self) @@ -564,6 +565,8 @@ class GlobalAttrsWidget(QtWidgets.QWidget): self.cancel_btn.setEnabled(False) self.submit_btn.setEnabled(False) + self._current_instances = instances + asset_names = set() task_names = set() variants = set() From a925cc455416206b98dd0a196881dac8aae24752 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 18 Aug 2021 10:58:35 +0200 Subject: [PATCH 254/736] base of changes submit is working --- .../tools/new_publisher/widgets/widgets.py | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 2a538b8cee..0d4dbe8f85 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -529,7 +529,57 @@ class GlobalAttrsWidget(QtWidgets.QWidget): self.cancel_btn = cancel_btn def _on_submit(self): - print("submit") + variant_value = None + asset_name = None + task_name = None + if self.variant_input.has_value_changed(): + variant_value = self.variant_input.get_value()[0] + + if self.asset_value_widget.has_value_changed(): + asset_name = self.asset_value_widget.get_selected_items()[0] + + if self.task_value_widget.has_value_changed(): + task_name = self.task_value_widget.get_selected_items()[0] + + asset_docs_by_name = {} + asset_names = set() + if asset_name is None: + for instance in self._current_instances: + asset_names.add(instance.data.get("asset")) + else: + asset_names.add(asset_name) + + for asset_doc in self.controller.get_asset_docs(): + _asset_name = asset_doc["name"] + if _asset_name in asset_names: + asset_names.remove(_asset_name) + asset_docs_by_name[_asset_name] = asset_doc + + if not asset_names: + break + + project_name = self.controller.project_name + for instance in self._current_instances: + if variant_value is not None: + instance.data["variant"] = variant_value + + if asset_name is not None: + instance.data["asset"] = asset_name + + if task_name is not None: + instance.data["task"] = task_name + + new_variant_value = instance.data.get("variant") + new_asset_name = instance.data.get("asset") + new_task_name = instance.data.get("task") + + asset_doc = asset_docs_by_name[new_asset_name] + + new_subset_name = instance.creator.get_subset_name( + new_variant_value, new_task_name, asset_doc, project_name + ) + instance.data["subset"] = new_subset_name + self.cancel_btn.setEnabled(False) self.submit_btn.setEnabled(False) From c7ce6a21fb605bd3a4cb8afb7dbbc0fca6ff285f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 18 Aug 2021 11:11:54 +0200 Subject: [PATCH 255/736] moved instance view widgets to specific file --- .../tools/new_publisher/widgets/__init__.py | 16 +- .../widgets/instance_views_widgets.py | 565 +++++++++++++++++ .../tools/new_publisher/widgets/widgets.py | 576 +----------------- 3 files changed, 585 insertions(+), 572 deletions(-) create mode 100644 openpype/tools/new_publisher/widgets/instance_views_widgets.py diff --git a/openpype/tools/new_publisher/widgets/__init__.py b/openpype/tools/new_publisher/widgets/__init__.py index 11e8f1f10e..b5639926dc 100644 --- a/openpype/tools/new_publisher/widgets/__init__.py +++ b/openpype/tools/new_publisher/widgets/__init__.py @@ -1,19 +1,23 @@ from .widgets import ( - PublishOverlayFrame, SubsetAttributesWidget, - InstanceCardView, - InstanceListView + PublishOverlayFrame ) from .create_dialog import ( CreateDialog ) +from .instance_views_widgets import ( + InstanceCardView, + InstanceListView +) + __all__ = ( - "PublishOverlayFrame", "SubsetAttributesWidget", + "PublishOverlayFrame", + + "CreateDialog", + "InstanceCardView", "InstanceListView", - - "CreateDialog" ) diff --git a/openpype/tools/new_publisher/widgets/instance_views_widgets.py b/openpype/tools/new_publisher/widgets/instance_views_widgets.py new file mode 100644 index 0000000000..70746062d6 --- /dev/null +++ b/openpype/tools/new_publisher/widgets/instance_views_widgets.py @@ -0,0 +1,565 @@ +import collections + +from Qt import QtWidgets, QtCore, QtGui + +from constants import ( + INSTANCE_ID_ROLE, + SORT_VALUE_ROLE +) + + +class InstanceCardWidget(QtWidgets.QWidget): + active_changed = QtCore.Signal(str, bool) + + def __init__(self, instance, item, parent): + super(InstanceCardWidget, self).__init__(parent) + + self.instance = instance + self.item = item + + subset_name_label = QtWidgets.QLabel(instance.data["subset"], self) + active_checkbox = QtWidgets.QCheckBox(self) + active_checkbox.setChecked(instance.data["active"]) + + expand_btn = QtWidgets.QToolButton(self) + expand_btn.setObjectName("ArrowBtn") + expand_btn.setArrowType(QtCore.Qt.DownArrow) + expand_btn.setMaximumWidth(14) + expand_btn.setEnabled(False) + + detail_widget = QtWidgets.QWidget(self) + detail_widget.setVisible(False) + self.detail_widget = detail_widget + + top_layout = QtWidgets.QHBoxLayout() + top_layout.addWidget(subset_name_label) + top_layout.addStretch(1) + top_layout.addWidget(active_checkbox) + top_layout.addWidget(expand_btn) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(2, 2, 2, 2) + layout.addLayout(top_layout) + layout.addWidget(detail_widget) + + self.setAttribute(QtCore.Qt.WA_TranslucentBackground) + subset_name_label.setAttribute(QtCore.Qt.WA_TranslucentBackground) + active_checkbox.setAttribute(QtCore.Qt.WA_TranslucentBackground) + expand_btn.setAttribute(QtCore.Qt.WA_TranslucentBackground) + + active_checkbox.stateChanged.connect(self._on_active_change) + expand_btn.clicked.connect(self._on_expend_clicked) + + self.subset_name_label = subset_name_label + self.active_checkbox = active_checkbox + self.expand_btn = expand_btn + + def set_active(self, new_value): + checkbox_value = self.active_checkbox.isChecked() + instance_value = self.instance.data["active"] + + # First change instance value and them change checkbox + # - prevent to trigger `active_changed` signal + if instance_value != new_value: + self.instance.data["active"] = new_value + + if checkbox_value != new_value: + self.active_checkbox.setChecked(new_value) + + def update_instance(self, instance): + self.instance = instance + self.update_instance_values() + + def update_instance_values(self): + self.set_active(self.instance.data["active"]) + + def _set_expanded(self, expanded=None): + if expanded is None: + expanded = not self.detail_widget.isVisible() + self.detail_widget.setVisible(expanded) + self.item.setSizeHint(self.sizeHint()) + + def showEvent(self, event): + super(InstanceCardWidget, self).showEvent(event) + self.item.setSizeHint(self.sizeHint()) + + def _on_active_change(self): + new_value = self.active_checkbox.isChecked() + old_value = self.instance.data["active"] + if new_value == old_value: + return + + self.instance.data["active"] = new_value + self.active_changed.emit(self.instance.data["uuid"], new_value) + + def _on_expend_clicked(self): + self._set_expanded() + + +class _AbstractInstanceView(QtWidgets.QWidget): + selection_changed = QtCore.Signal() + + def refresh(self): + raise NotImplementedError(( + "{} Method 'refresh' is not implemented." + ).format(self.__class__.__name__)) + + def get_selected_instances(self): + raise NotImplementedError(( + "{} Method 'get_selected_instances' is not implemented." + ).format(self.__class__.__name__)) + + def set_selected_instances(self, instances): + raise NotImplementedError(( + "{} Method 'set_selected_instances' is not implemented." + ).format(self.__class__.__name__)) + + +class InstanceCardView(_AbstractInstanceView): + def __init__(self, controller, parent): + super(InstanceCardView, self).__init__(parent) + + self.controller = controller + + list_widget = QtWidgets.QListWidget(self) + list_widget.setSelectionMode( + QtWidgets.QAbstractItemView.ExtendedSelection + ) + list_widget.setResizeMode(QtWidgets.QListView.Adjust) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(list_widget, 1) + + list_widget.selectionModel().selectionChanged.connect( + self._on_selection_change + ) + + self._items_by_id = {} + self._widgets_by_id = {} + + self.list_widget = list_widget + + def refresh(self): + instances_by_id = {} + for instance in self.controller.instances: + instance_id = instance.data["uuid"] + instances_by_id[instance_id] = instance + + for instance_id in tuple(self._items_by_id.keys()): + if instance_id not in instances_by_id: + item = self._items_by_id.pop(instance_id) + self.list_widget.removeItemWidget(item) + widget = self._widgets_by_id.pop(instance_id) + widget.deleteLater() + row = self.list_widget.row(item) + self.list_widget.takeItem(row) + + for instance_id, instance in instances_by_id.items(): + if instance_id in self._items_by_id: + widget = self._widgets_by_id[instance_id] + widget.update_instance(instance) + + else: + item = QtWidgets.QListWidgetItem(self.list_widget) + widget = InstanceCardWidget(instance, item, self) + widget.active_changed.connect(self._on_active_changed) + item.setData(INSTANCE_ID_ROLE, instance_id) + self.list_widget.addItem(item) + self.list_widget.setItemWidget(item, widget) + self._items_by_id[instance_id] = item + self._widgets_by_id[instance_id] = widget + + def refresh_active_state(self): + for widget in self._widgets_by_id.values(): + widget.update_instance_values() + + def _on_active_changed(self, changed_instance_id, new_value): + selected_ids = set() + found = False + for item in self.list_widget.selectedItems(): + instance_id = item.data(INSTANCE_ID_ROLE) + selected_ids.add(instance_id) + if not found and instance_id == changed_instance_id: + found = True + + if not found: + return + + for instance_id in selected_ids: + widget = self._widgets_by_id.get(instance_id) + if widget: + widget.set_active(new_value) + + def _on_selection_change(self, *_args): + self.selection_changed.emit() + + def get_selected_instances(self): + instances = [] + for item in self.list_widget.selectedItems(): + instance_id = item.data(INSTANCE_ID_ROLE) + widget = self._widgets_by_id.get(instance_id) + if widget: + instances.append(widget.instance) + return instances + + def set_selected_instances(self, instances): + indexes = [] + model = self.list_widget.model() + for instance in instances: + instance_id = instance.data["uuid"] + item = self._items_by_id.get(instance_id) + if item: + row = self.list_widget.row(item) + index = model.index(row, 0) + indexes.append(index) + + selection_model = self.list_widget.selectionModel() + first_item = True + for index in indexes: + if first_item: + first_item = False + select_type = QtCore.QItemSelectionModel.ClearAndSelect + else: + select_type = QtCore.QItemSelectionModel.Select + selection_model.select(index, select_type) + + +class InstanceListItemWidget(QtWidgets.QWidget): + active_changed = QtCore.Signal(str, bool) + + def __init__(self, instance, parent): + super(InstanceListItemWidget, self).__init__(parent) + + self.instance = instance + + subset_name_label = QtWidgets.QLabel(instance.data["subset"], self) + active_checkbox = QtWidgets.QCheckBox(self) + active_checkbox.setChecked(instance.data["active"]) + + layout = QtWidgets.QHBoxLayout(self) + content_margins = layout.contentsMargins() + layout.setContentsMargins(content_margins.left() + 2, 0, 2, 0) + layout.addWidget(subset_name_label) + layout.addStretch(1) + layout.addWidget(active_checkbox) + + self.setAttribute(QtCore.Qt.WA_TranslucentBackground) + subset_name_label.setAttribute(QtCore.Qt.WA_TranslucentBackground) + active_checkbox.setAttribute(QtCore.Qt.WA_TranslucentBackground) + + active_checkbox.stateChanged.connect(self._on_active_change) + + self.subset_name_label = subset_name_label + self.active_checkbox = active_checkbox + + def set_active(self, new_value): + checkbox_value = self.active_checkbox.isChecked() + instance_value = self.instance.data["active"] + + # First change instance value and them change checkbox + # - prevent to trigger `active_changed` signal + if instance_value != new_value: + self.instance.data["active"] = new_value + + if checkbox_value != new_value: + self.active_checkbox.setChecked(new_value) + + def update_instance(self, instance): + self.instance = instance + self.update_instance_values() + + def update_instance_values(self): + self.set_active(self.instance.data["active"]) + + def _on_active_change(self): + new_value = self.active_checkbox.isChecked() + old_value = self.instance.data["active"] + if new_value == old_value: + return + + self.instance.data["active"] = new_value + self.active_changed.emit(self.instance.data["uuid"], new_value) + + +class InstanceListGroupWidget(QtWidgets.QFrame): + expand_changed = QtCore.Signal(str, bool) + + def __init__(self, family, parent): + super(InstanceListGroupWidget, self).__init__(parent) + self.setObjectName("InstanceListGroupWidget") + + self.family = family + self._expanded = False + + subset_name_label = QtWidgets.QLabel(family, self) + + expand_btn = QtWidgets.QToolButton(self) + expand_btn.setObjectName("ArrowBtn") + expand_btn.setArrowType(QtCore.Qt.RightArrow) + expand_btn.setMaximumWidth(14) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(5, 0, 2, 0) + layout.addWidget( + subset_name_label, 1, QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter + ) + layout.addWidget(expand_btn) + + # self.setAttribute(QtCore.Qt.WA_TranslucentBackground) + subset_name_label.setAttribute(QtCore.Qt.WA_TranslucentBackground) + expand_btn.setAttribute(QtCore.Qt.WA_TranslucentBackground) + + expand_btn.clicked.connect(self._on_expand_clicked) + + self.subset_name_label = subset_name_label + self.expand_btn = expand_btn + + def _on_expand_clicked(self): + self.expand_changed.emit(self.family, not self._expanded) + + def set_expanded(self, expanded): + if self._expanded == expanded: + return + + self._expanded = expanded + if expanded: + self.expand_btn.setArrowType(QtCore.Qt.DownArrow) + else: + self.expand_btn.setArrowType(QtCore.Qt.RightArrow) + + +class InstanceListView(_AbstractInstanceView): + def __init__(self, controller, parent): + super(InstanceListView, self).__init__(parent) + + self.controller = controller + + instance_view = QtWidgets.QTreeView(self) + instance_view.setObjectName("InstanceListView") + instance_view.setHeaderHidden(True) + instance_view.setIndentation(0) + instance_view.setSelectionMode( + QtWidgets.QAbstractItemView.ExtendedSelection + ) + + instance_model = QtGui.QStandardItemModel() + + proxy_model = QtCore.QSortFilterProxyModel() + proxy_model.setSourceModel(instance_model) + proxy_model.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) + proxy_model.setSortRole(SORT_VALUE_ROLE) + proxy_model.setFilterKeyColumn(0) + proxy_model.setDynamicSortFilter(True) + + instance_view.setModel(proxy_model) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(instance_view) + + instance_view.selectionModel().selectionChanged.connect( + self._on_selection_change + ) + instance_view.collapsed.connect(self._on_collapse) + instance_view.expanded.connect(self._on_expand) + + self._group_items = {} + self._group_widgets = {} + self._widgets_by_id = {} + self.instance_view = instance_view + self.instance_model = instance_model + self.proxy_model = proxy_model + + def _on_expand(self, index): + family = index.data(SORT_VALUE_ROLE) + group_widget = self._group_widgets.get(family) + if group_widget: + group_widget.set_expanded(True) + + def _on_collapse(self, index): + family = index.data(SORT_VALUE_ROLE) + group_widget = self._group_widgets.get(family) + if group_widget: + group_widget.set_expanded(False) + + def refresh(self): + instances_by_family = collections.defaultdict(list) + families = set() + for instance in self.controller.instances: + family = instance.data["family"] + families.add(family) + instances_by_family[family].append(instance) + + new_group_items = [] + for family in families: + if family in self._group_items: + continue + + group_item = QtGui.QStandardItem() + group_item.setData(family, SORT_VALUE_ROLE) + group_item.setFlags(QtCore.Qt.ItemIsEnabled) + self._group_items[family] = group_item + new_group_items.append(group_item) + + sort_at_the_end = False + root_item = self.instance_model.invisibleRootItem() + if new_group_items: + sort_at_the_end = True + root_item.appendRows(new_group_items) + + for group_item in new_group_items: + index = self.instance_model.index( + group_item.row(), group_item.column() + ) + proxy_index = self.proxy_model.mapFromSource(index) + family = group_item.data(SORT_VALUE_ROLE) + widget = InstanceListGroupWidget(family, self.instance_view) + widget.expand_changed.connect(self._on_group_expand_request) + self._group_widgets[family] = widget + self.instance_view.setIndexWidget(proxy_index, widget) + + for family in tuple(self._group_items.keys()): + if family in families: + continue + + group_item = self._group_items.pop(family) + root_item.removeRow(group_item.row()) + widget = self._group_widgets.pop(family) + widget.deleteLater() + + for family, group_item in self._group_items.items(): + to_remove = set() + existing_mapping = {} + + group_index = self.instance_model.index( + group_item.row(), group_item.column() + ) + + for idx in range(group_item.rowCount()): + index = self.instance_model.index(idx, 0, group_index) + instance_id = index.data(INSTANCE_ID_ROLE) + to_remove.add(instance_id) + existing_mapping[instance_id] = idx + + new_items = [] + new_items_with_instance = [] + for instance in instances_by_family[family]: + instance_id = instance.data["uuid"] + if instance_id in to_remove: + to_remove.remove(instance_id) + widget = self._widgets_by_id[instance_id] + widget.update_instance(instance) + continue + + item = QtGui.QStandardItem() + item.setData(instance.data["subset"], SORT_VALUE_ROLE) + item.setData(instance.data["uuid"], INSTANCE_ID_ROLE) + new_items.append(item) + new_items_with_instance.append((item, instance)) + + idx_to_remove = [] + for instance_id in to_remove: + idx_to_remove.append(existing_mapping[instance_id]) + + for idx in reversed(sorted(idx_to_remove)): + group_item.removeRows(idx, 1) + + for instance_id in to_remove: + widget = self._widgets_by_id.pop(instance_id) + widget.deleteLater() + + if new_items: + sort_at_the_end = True + + group_item.appendRows(new_items) + + for item, instance in new_items_with_instance: + item_index = self.instance_model.index( + item.row(), + item.column(), + group_index + ) + proxy_index = self.proxy_model.mapFromSource(item_index) + widget = InstanceListItemWidget( + instance, self.instance_view + ) + self.instance_view.setIndexWidget(proxy_index, widget) + self._widgets_by_id[instance.data["uuid"]] = widget + + # Trigger sort at the end of refresh + if sort_at_the_end: + self.proxy_model.sort(0) + + def refresh_active_state(self): + for widget in self._widgets_by_id.values(): + widget.update_instance_values() + + def get_selected_instances(self): + instances = [] + instances_by_id = {} + for instance in self.controller.instances: + instance_id = instance.data["uuid"] + instances_by_id[instance_id] = instance + + for index in self.instance_view.selectionModel().selectedIndexes(): + instance_id = index.data(INSTANCE_ID_ROLE) + if instance_id is not None: + instance = instances_by_id.get(instance_id) + if instance: + instances.append(instance) + + return instances + + def set_selected_instances(self, instances): + model = self.instance_view.model() + instance_ids_by_family = collections.defaultdict(set) + for instance in instances: + family = instance.data["family"] + instance_id = instance.data["uuid"] + instance_ids_by_family[family].add(instance_id) + + indexes = [] + for family, group_item in self._group_items.items(): + selected_ids = instance_ids_by_family[family] + if not selected_ids: + continue + + group_index = self.instance_model.index( + group_item.row(), group_item.column() + ) + proxy_group_index = self.proxy_model.mapFromSource(group_index) + has_indexes = False + for row in range(group_item.rowCount()): + index = model.index(row, 0, proxy_group_index) + instance_id = index.data(INSTANCE_ID_ROLE) + if instance_id in selected_ids: + indexes.append(index) + has_indexes = True + + if has_indexes: + self.instance_view.setExpanded(proxy_group_index, True) + + selection_model = self.instance_view.selectionModel() + first_item = True + for index in indexes: + if first_item: + first_item = False + select_type = QtCore.QItemSelectionModel.ClearAndSelect + else: + select_type = QtCore.QItemSelectionModel.Select + selection_model.select(index, select_type) + + def _on_selection_change(self, *_args): + self.selection_changed.emit() + + def _on_group_expand_request(self, family, expanded): + group_item = self._group_items.get(family) + if not group_item: + return + + group_index = self.instance_model.index( + group_item.row(), group_item.column() + ) + proxy_index = self.proxy_model.mapFromSource(group_index) + self.instance_view.setExpanded(proxy_index, expanded) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 0d4dbe8f85..fe88ab3f21 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -4,10 +4,6 @@ import collections from Qt import QtWidgets, QtCore, QtGui from openpype.widgets.attribute_defs import create_widget_for_attr_def -from constants import ( - INSTANCE_ID_ROLE, - SORT_VALUE_ROLE -) from openpype.tools.flickcharm import FlickCharm @@ -596,9 +592,7 @@ class GlobalAttrsWidget(QtWidgets.QWidget): or self.asset_value_widget.has_value_changed() or self.task_value_widget.has_value_changed() ) - - self.cancel_btn.setEnabled(any_changed) - self.submit_btn.setEnabled(any_changed) + self.set_btns_visible(any_changed) def _on_variant_change(self): self._on_value_change() @@ -611,9 +605,16 @@ class GlobalAttrsWidget(QtWidgets.QWidget): def _on_task_change(self): self._on_value_change() + def set_btns_visible(self, visible): + self.cancel_btn.setVisible(visible) + self.submit_btn.setVisible(visible) + + def set_btns_enabled(self, enabled): + self.cancel_btn.setEnabled(enabled) + self.submit_btn.setEnabled(enabled) + def set_current_instances(self, instances): - self.cancel_btn.setEnabled(False) - self.submit_btn.setEnabled(False) + self.set_btns_visible(False) self._current_instances = instances @@ -903,563 +904,6 @@ class ThumbnailWidget(QtWidgets.QWidget): self.current_pix = None -class InstanceCardWidget(QtWidgets.QWidget): - active_changed = QtCore.Signal(str, bool) - - def __init__(self, instance, item, parent): - super(InstanceCardWidget, self).__init__(parent) - - self.instance = instance - self.item = item - - subset_name_label = QtWidgets.QLabel(instance.data["subset"], self) - active_checkbox = QtWidgets.QCheckBox(self) - active_checkbox.setChecked(instance.data["active"]) - - expand_btn = QtWidgets.QToolButton(self) - expand_btn.setObjectName("ArrowBtn") - expand_btn.setArrowType(QtCore.Qt.DownArrow) - expand_btn.setMaximumWidth(14) - expand_btn.setEnabled(False) - - detail_widget = QtWidgets.QWidget(self) - detail_widget.setVisible(False) - self.detail_widget = detail_widget - - top_layout = QtWidgets.QHBoxLayout() - top_layout.addWidget(subset_name_label) - top_layout.addStretch(1) - top_layout.addWidget(active_checkbox) - top_layout.addWidget(expand_btn) - - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(2, 2, 2, 2) - layout.addLayout(top_layout) - layout.addWidget(detail_widget) - - self.setAttribute(QtCore.Qt.WA_TranslucentBackground) - subset_name_label.setAttribute(QtCore.Qt.WA_TranslucentBackground) - active_checkbox.setAttribute(QtCore.Qt.WA_TranslucentBackground) - expand_btn.setAttribute(QtCore.Qt.WA_TranslucentBackground) - - active_checkbox.stateChanged.connect(self._on_active_change) - expand_btn.clicked.connect(self._on_expend_clicked) - - self.subset_name_label = subset_name_label - self.active_checkbox = active_checkbox - self.expand_btn = expand_btn - - def set_active(self, new_value): - checkbox_value = self.active_checkbox.isChecked() - instance_value = self.instance.data["active"] - - # First change instance value and them change checkbox - # - prevent to trigger `active_changed` signal - if instance_value != new_value: - self.instance.data["active"] = new_value - - if checkbox_value != new_value: - self.active_checkbox.setChecked(new_value) - - def update_instance(self, instance): - self.instance = instance - self.update_instance_values() - - def update_instance_values(self): - self.set_active(self.instance.data["active"]) - - def _set_expanded(self, expanded=None): - if expanded is None: - expanded = not self.detail_widget.isVisible() - self.detail_widget.setVisible(expanded) - self.item.setSizeHint(self.sizeHint()) - - def showEvent(self, event): - super(InstanceCardWidget, self).showEvent(event) - self.item.setSizeHint(self.sizeHint()) - - def _on_active_change(self): - new_value = self.active_checkbox.isChecked() - old_value = self.instance.data["active"] - if new_value == old_value: - return - - self.instance.data["active"] = new_value - self.active_changed.emit(self.instance.data["uuid"], new_value) - - def _on_expend_clicked(self): - self._set_expanded() - - -class _AbstractInstanceView(QtWidgets.QWidget): - selection_changed = QtCore.Signal() - - def refresh(self): - raise NotImplementedError(( - "{} Method 'refresh' is not implemented." - ).format(self.__class__.__name__)) - - def get_selected_instances(self): - raise NotImplementedError(( - "{} Method 'get_selected_instances' is not implemented." - ).format(self.__class__.__name__)) - - def set_selected_instances(self, instances): - raise NotImplementedError(( - "{} Method 'set_selected_instances' is not implemented." - ).format(self.__class__.__name__)) - - -class InstanceCardView(_AbstractInstanceView): - def __init__(self, controller, parent): - super(InstanceCardView, self).__init__(parent) - - self.controller = controller - - list_widget = QtWidgets.QListWidget(self) - list_widget.setSelectionMode( - QtWidgets.QAbstractItemView.ExtendedSelection - ) - list_widget.setResizeMode(QtWidgets.QListView.Adjust) - - layout = QtWidgets.QHBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.addWidget(list_widget, 1) - - list_widget.selectionModel().selectionChanged.connect( - self._on_selection_change - ) - - self._items_by_id = {} - self._widgets_by_id = {} - - self.list_widget = list_widget - - def refresh(self): - instances_by_id = {} - for instance in self.controller.instances: - instance_id = instance.data["uuid"] - instances_by_id[instance_id] = instance - - for instance_id in tuple(self._items_by_id.keys()): - if instance_id not in instances_by_id: - item = self._items_by_id.pop(instance_id) - self.list_widget.removeItemWidget(item) - widget = self._widgets_by_id.pop(instance_id) - widget.deleteLater() - row = self.list_widget.row(item) - self.list_widget.takeItem(row) - - for instance_id, instance in instances_by_id.items(): - if instance_id in self._items_by_id: - widget = self._widgets_by_id[instance_id] - widget.update_instance(instance) - - else: - item = QtWidgets.QListWidgetItem(self.list_widget) - widget = InstanceCardWidget(instance, item, self) - widget.active_changed.connect(self._on_active_changed) - item.setData(INSTANCE_ID_ROLE, instance_id) - self.list_widget.addItem(item) - self.list_widget.setItemWidget(item, widget) - self._items_by_id[instance_id] = item - self._widgets_by_id[instance_id] = widget - - def refresh_active_state(self): - for widget in self._widgets_by_id.values(): - widget.update_instance_values() - - def _on_active_changed(self, changed_instance_id, new_value): - selected_ids = set() - found = False - for item in self.list_widget.selectedItems(): - instance_id = item.data(INSTANCE_ID_ROLE) - selected_ids.add(instance_id) - if not found and instance_id == changed_instance_id: - found = True - - if not found: - return - - for instance_id in selected_ids: - widget = self._widgets_by_id.get(instance_id) - if widget: - widget.set_active(new_value) - - def _on_selection_change(self, *_args): - self.selection_changed.emit() - - def get_selected_instances(self): - instances = [] - for item in self.list_widget.selectedItems(): - instance_id = item.data(INSTANCE_ID_ROLE) - widget = self._widgets_by_id.get(instance_id) - if widget: - instances.append(widget.instance) - return instances - - def set_selected_instances(self, instances): - indexes = [] - model = self.list_widget.model() - for instance in instances: - instance_id = instance.data["uuid"] - item = self._items_by_id.get(instance_id) - if item: - row = self.list_widget.row(item) - index = model.index(row, 0) - indexes.append(index) - - selection_model = self.list_widget.selectionModel() - first_item = True - for index in indexes: - if first_item: - first_item = False - select_type = QtCore.QItemSelectionModel.ClearAndSelect - else: - select_type = QtCore.QItemSelectionModel.Select - selection_model.select(index, select_type) - - -class InstanceListItemWidget(QtWidgets.QWidget): - active_changed = QtCore.Signal(str, bool) - - def __init__(self, instance, parent): - super(InstanceListItemWidget, self).__init__(parent) - - self.instance = instance - - subset_name_label = QtWidgets.QLabel(instance.data["subset"], self) - active_checkbox = QtWidgets.QCheckBox(self) - active_checkbox.setChecked(instance.data["active"]) - - layout = QtWidgets.QHBoxLayout(self) - content_margins = layout.contentsMargins() - layout.setContentsMargins(content_margins.left() + 2, 0, 2, 0) - layout.addWidget(subset_name_label) - layout.addStretch(1) - layout.addWidget(active_checkbox) - - self.setAttribute(QtCore.Qt.WA_TranslucentBackground) - subset_name_label.setAttribute(QtCore.Qt.WA_TranslucentBackground) - active_checkbox.setAttribute(QtCore.Qt.WA_TranslucentBackground) - - active_checkbox.stateChanged.connect(self._on_active_change) - - self.subset_name_label = subset_name_label - self.active_checkbox = active_checkbox - - def set_active(self, new_value): - checkbox_value = self.active_checkbox.isChecked() - instance_value = self.instance.data["active"] - - # First change instance value and them change checkbox - # - prevent to trigger `active_changed` signal - if instance_value != new_value: - self.instance.data["active"] = new_value - - if checkbox_value != new_value: - self.active_checkbox.setChecked(new_value) - - def update_instance(self, instance): - self.instance = instance - self.update_instance_values() - - def update_instance_values(self): - self.set_active(self.instance.data["active"]) - - def _on_active_change(self): - new_value = self.active_checkbox.isChecked() - old_value = self.instance.data["active"] - if new_value == old_value: - return - - self.instance.data["active"] = new_value - self.active_changed.emit(self.instance.data["uuid"], new_value) - - -class InstanceListGroupWidget(QtWidgets.QFrame): - expand_changed = QtCore.Signal(str, bool) - - def __init__(self, family, parent): - super(InstanceListGroupWidget, self).__init__(parent) - self.setObjectName("InstanceListGroupWidget") - - self.family = family - self._expanded = False - - subset_name_label = QtWidgets.QLabel(family, self) - - expand_btn = QtWidgets.QToolButton(self) - expand_btn.setObjectName("ArrowBtn") - expand_btn.setArrowType(QtCore.Qt.RightArrow) - expand_btn.setMaximumWidth(14) - - layout = QtWidgets.QHBoxLayout(self) - layout.setContentsMargins(5, 0, 2, 0) - layout.addWidget( - subset_name_label, 1, QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter - ) - layout.addWidget(expand_btn) - - # self.setAttribute(QtCore.Qt.WA_TranslucentBackground) - subset_name_label.setAttribute(QtCore.Qt.WA_TranslucentBackground) - expand_btn.setAttribute(QtCore.Qt.WA_TranslucentBackground) - - expand_btn.clicked.connect(self._on_expand_clicked) - - self.subset_name_label = subset_name_label - self.expand_btn = expand_btn - - def _on_expand_clicked(self): - self.expand_changed.emit(self.family, not self._expanded) - - def set_expanded(self, expanded): - if self._expanded == expanded: - return - - self._expanded = expanded - if expanded: - self.expand_btn.setArrowType(QtCore.Qt.DownArrow) - else: - self.expand_btn.setArrowType(QtCore.Qt.RightArrow) - - -class InstanceListView(_AbstractInstanceView): - def __init__(self, controller, parent): - super(InstanceListView, self).__init__(parent) - - self.controller = controller - - instance_view = QtWidgets.QTreeView(self) - instance_view.setObjectName("InstanceListView") - instance_view.setHeaderHidden(True) - instance_view.setIndentation(0) - instance_view.setSelectionMode( - QtWidgets.QAbstractItemView.ExtendedSelection - ) - - instance_model = QtGui.QStandardItemModel() - - proxy_model = QtCore.QSortFilterProxyModel() - proxy_model.setSourceModel(instance_model) - proxy_model.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) - proxy_model.setSortRole(SORT_VALUE_ROLE) - proxy_model.setFilterKeyColumn(0) - proxy_model.setDynamicSortFilter(True) - - instance_view.setModel(proxy_model) - - layout = QtWidgets.QHBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.addWidget(instance_view) - - instance_view.selectionModel().selectionChanged.connect( - self._on_selection_change - ) - instance_view.collapsed.connect(self._on_collapse) - instance_view.expanded.connect(self._on_expand) - - self._group_items = {} - self._group_widgets = {} - self._widgets_by_id = {} - self.instance_view = instance_view - self.instance_model = instance_model - self.proxy_model = proxy_model - - def _on_expand(self, index): - family = index.data(SORT_VALUE_ROLE) - group_widget = self._group_widgets.get(family) - if group_widget: - group_widget.set_expanded(True) - - def _on_collapse(self, index): - family = index.data(SORT_VALUE_ROLE) - group_widget = self._group_widgets.get(family) - if group_widget: - group_widget.set_expanded(False) - - def refresh(self): - instances_by_family = collections.defaultdict(list) - families = set() - for instance in self.controller.instances: - family = instance.data["family"] - families.add(family) - instances_by_family[family].append(instance) - - new_group_items = [] - for family in families: - if family in self._group_items: - continue - - group_item = QtGui.QStandardItem() - group_item.setData(family, SORT_VALUE_ROLE) - group_item.setFlags(QtCore.Qt.ItemIsEnabled) - self._group_items[family] = group_item - new_group_items.append(group_item) - - sort_at_the_end = False - root_item = self.instance_model.invisibleRootItem() - if new_group_items: - sort_at_the_end = True - root_item.appendRows(new_group_items) - - for group_item in new_group_items: - index = self.instance_model.index( - group_item.row(), group_item.column() - ) - proxy_index = self.proxy_model.mapFromSource(index) - family = group_item.data(SORT_VALUE_ROLE) - widget = InstanceListGroupWidget(family, self.instance_view) - widget.expand_changed.connect(self._on_group_expand_request) - self._group_widgets[family] = widget - self.instance_view.setIndexWidget(proxy_index, widget) - - for family in tuple(self._group_items.keys()): - if family in families: - continue - - group_item = self._group_items.pop(family) - root_item.removeRow(group_item.row()) - widget = self._group_widgets.pop(family) - widget.deleteLater() - - for family, group_item in self._group_items.items(): - to_remove = set() - existing_mapping = {} - - group_index = self.instance_model.index( - group_item.row(), group_item.column() - ) - - for idx in range(group_item.rowCount()): - index = self.instance_model.index(idx, 0, group_index) - instance_id = index.data(INSTANCE_ID_ROLE) - to_remove.add(instance_id) - existing_mapping[instance_id] = idx - - new_items = [] - new_items_with_instance = [] - for instance in instances_by_family[family]: - instance_id = instance.data["uuid"] - if instance_id in to_remove: - to_remove.remove(instance_id) - widget = self._widgets_by_id[instance_id] - widget.update_instance(instance) - continue - - item = QtGui.QStandardItem() - item.setData(instance.data["subset"], SORT_VALUE_ROLE) - item.setData(instance.data["uuid"], INSTANCE_ID_ROLE) - new_items.append(item) - new_items_with_instance.append((item, instance)) - - idx_to_remove = [] - for instance_id in to_remove: - idx_to_remove.append(existing_mapping[instance_id]) - - for idx in reversed(sorted(idx_to_remove)): - group_item.removeRows(idx, 1) - - for instance_id in to_remove: - widget = self._widgets_by_id.pop(instance_id) - widget.deleteLater() - - if new_items: - sort_at_the_end = True - - group_item.appendRows(new_items) - - for item, instance in new_items_with_instance: - item_index = self.instance_model.index( - item.row(), - item.column(), - group_index - ) - proxy_index = self.proxy_model.mapFromSource(item_index) - widget = InstanceListItemWidget( - instance, self.instance_view - ) - self.instance_view.setIndexWidget(proxy_index, widget) - self._widgets_by_id[instance.data["uuid"]] = widget - - # Trigger sort at the end of refresh - if sort_at_the_end: - self.proxy_model.sort(0) - - def refresh_active_state(self): - for widget in self._widgets_by_id.values(): - widget.update_instance_values() - - def get_selected_instances(self): - instances = [] - instances_by_id = {} - for instance in self.controller.instances: - instance_id = instance.data["uuid"] - instances_by_id[instance_id] = instance - - for index in self.instance_view.selectionModel().selectedIndexes(): - instance_id = index.data(INSTANCE_ID_ROLE) - if instance_id is not None: - instance = instances_by_id.get(instance_id) - if instance: - instances.append(instance) - - return instances - - def set_selected_instances(self, instances): - model = self.instance_view.model() - instance_ids_by_family = collections.defaultdict(set) - for instance in instances: - family = instance.data["family"] - instance_id = instance.data["uuid"] - instance_ids_by_family[family].add(instance_id) - - indexes = [] - for family, group_item in self._group_items.items(): - selected_ids = instance_ids_by_family[family] - if not selected_ids: - continue - - group_index = self.instance_model.index( - group_item.row(), group_item.column() - ) - proxy_group_index = self.proxy_model.mapFromSource(group_index) - has_indexes = False - for row in range(group_item.rowCount()): - index = model.index(row, 0, proxy_group_index) - instance_id = index.data(INSTANCE_ID_ROLE) - if instance_id in selected_ids: - indexes.append(index) - has_indexes = True - - if has_indexes: - self.instance_view.setExpanded(proxy_group_index, True) - - selection_model = self.instance_view.selectionModel() - first_item = True - for index in indexes: - if first_item: - first_item = False - select_type = QtCore.QItemSelectionModel.ClearAndSelect - else: - select_type = QtCore.QItemSelectionModel.Select - selection_model.select(index, select_type) - - def _on_selection_change(self, *_args): - self.selection_changed.emit() - - def _on_group_expand_request(self, family, expanded): - group_item = self._group_items.get(family) - if not group_item: - return - - group_index = self.instance_model.index( - group_item.row(), group_item.column() - ) - proxy_index = self.proxy_model.mapFromSource(group_index) - self.instance_view.setExpanded(proxy_index, expanded) - - class PublishOverlayFrame(QtWidgets.QFrame): hide_requested = QtCore.Signal() From d972534db6703de0efc9ccd014042404a5cc5561 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 18 Aug 2021 12:01:37 +0200 Subject: [PATCH 256/736] added invalidation of instances on instance refresh --- openpype/pipeline/create/context.py | 44 +++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 1c870afb66..a3c059e617 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -491,6 +491,7 @@ class CreateContext: # Collect instances host_instances = self.host.list_instances() instances = [] + task_names_by_asset_name = collections.defaultdict(set) for instance_data in host_instances: family = instance_data["family"] # Prepare publish plugins with attribute definitions @@ -503,6 +504,49 @@ class CreateContext: ) instances.append(instance) + task_name = instance_data.get("task") + asset_name = instance_data.get("asset") + if asset_name and task_name: + task_names_by_asset_name[asset_name].add(task_name) + + asset_names = [ + asset_name + for asset_name in task_names_by_asset_name.keys() + if asset_name is not None + ] + asset_docs = list(self.dbcon.find( + { + "type": "asset", + "name": {"$in": asset_names} + }, + { + "name": True, + "data.tasks": True + } + )) + + task_names_by_asset_name = {} + for asset_doc in asset_docs: + asset_name = asset_doc["name"] + tasks = asset_doc.get("data", {}).get("tasks") or {} + task_names_by_asset_name[asset_name] = set(tasks.keys()) + + for instance in instances: + if not instance.has_valid_asset or not instance.has_valid_task: + continue + + asset_name = instance.data["asset"] + if asset_name not in task_names_by_asset_name: + instance.set_asset_invalid(True) + continue + + task_name = instance.data["task"] + if not task_name: + continue + + if task_name not in task_names_by_asset_name[asset_name]: + instance.set_task_invalid(True) + self.instances = instances def _get_publish_plugins_with_attr_for_family(self, family): From 0af3f774161fe2f75c8a28461a057848478b9f3e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 18 Aug 2021 12:07:51 +0200 Subject: [PATCH 257/736] create publish subfolder --- openpype/pipeline/__init__.py | 2 +- openpype/pipeline/publish/__init__.py | 7 +++++++ openpype/pipeline/{ => publish}/publish_plugins.py | 0 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 openpype/pipeline/publish/__init__.py rename openpype/pipeline/{ => publish}/publish_plugins.py (100%) diff --git a/openpype/pipeline/__init__.py b/openpype/pipeline/__init__.py index 8da4a01148..b27759d232 100644 --- a/openpype/pipeline/__init__.py +++ b/openpype/pipeline/__init__.py @@ -7,7 +7,7 @@ from .create import ( CreatedInstance ) -from .publish_plugins import OpenPypePyblishPluginMixin +from .publish import OpenPypePyblishPluginMixin __all__ = ( diff --git a/openpype/pipeline/publish/__init__.py b/openpype/pipeline/publish/__init__.py new file mode 100644 index 0000000000..10a18cd384 --- /dev/null +++ b/openpype/pipeline/publish/__init__.py @@ -0,0 +1,7 @@ +from .publish_plugins import ( + OpenPypePyblishPluginMixin +) + +__all__ = ( + "OpenPypePyblishPluginMixin", +) diff --git a/openpype/pipeline/publish_plugins.py b/openpype/pipeline/publish/publish_plugins.py similarity index 100% rename from openpype/pipeline/publish_plugins.py rename to openpype/pipeline/publish/publish_plugins.py From bbb6840b130d839adf9f246d946c3f573d33b727 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 18 Aug 2021 12:12:35 +0200 Subject: [PATCH 258/736] added base of exception classes --- openpype/pipeline/publish/__init__.py | 6 +++++- openpype/pipeline/publish/publish_plugins.py | 8 ++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/publish/__init__.py b/openpype/pipeline/publish/__init__.py index 10a18cd384..4a18c1a110 100644 --- a/openpype/pipeline/publish/__init__.py +++ b/openpype/pipeline/publish/__init__.py @@ -1,7 +1,11 @@ from .publish_plugins import ( + PublishValidationError, + KnownPublishError, OpenPypePyblishPluginMixin ) __all__ = ( - "OpenPypePyblishPluginMixin", + "PublishValidationError", + "KnownPublishError", + "OpenPypePyblishPluginMixin" ) diff --git a/openpype/pipeline/publish/publish_plugins.py b/openpype/pipeline/publish/publish_plugins.py index c896d5ef32..31bc715b62 100644 --- a/openpype/pipeline/publish/publish_plugins.py +++ b/openpype/pipeline/publish/publish_plugins.py @@ -1,3 +1,11 @@ +class PublishValidationError(Exception): + pass + + +class KnownPublishError(Exception): + pass + + class OpenPypePyblishPluginMixin: executable_in_thread = False From 058d801bbcd336f3131bb84746df7297ebea89ed Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 18 Aug 2021 14:27:01 +0200 Subject: [PATCH 259/736] moved plugin/instance change callbacks to overlay --- .../tools/new_publisher/widgets/widgets.py | 33 ++++++++++++++++--- openpype/tools/new_publisher/window.py | 31 ++--------------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index fe88ab3f21..8b6fd97359 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -907,7 +907,7 @@ class ThumbnailWidget(QtWidgets.QWidget): class PublishOverlayFrame(QtWidgets.QFrame): hide_requested = QtCore.Signal() - def __init__(self, parent): + def __init__(self, controller, parent): super(PublishOverlayFrame, self).__init__(parent) self.setObjectName("PublishOverlayFrame") @@ -976,6 +976,11 @@ class PublishOverlayFrame(QtWidgets.QFrame): hide_btn.clicked.connect(self.hide_requested) + controller.add_instance_change_callback(self._on_instance_change) + controller.add_plugin_change_callback(self._on_plugin_change) + + self.controller = controller + self.hide_btn = hide_btn self.main_label = main_label @@ -990,11 +995,31 @@ class PublishOverlayFrame(QtWidgets.QFrame): self.refresh_btn = refresh_btn self.publish_btn = publish_btn - def set_instance(self, instance_name): - self.instance_label.setText(instance_name) + def _on_instance_change(self, context, instance): + if instance is None: + new_name = ( + context.data.get("label") + or getattr(context, "label", None) + or context.data.get("name") + or "Context" + ) + else: + new_name = ( + instance.data.get("label") + or getattr(instance, "label", None) + or instance.data["name"] + ) + + self.instance_label.setText(new_name) + QtWidgets.QApplication.processEvents() + + def _on_plugin_change(self, plugin): + plugin_name = plugin.__name__ + if hasattr(plugin, "label") and plugin.label: + plugin_name = plugin.label - def set_plugin(self, plugin_name): self.plugin_label.setText(plugin_name) + QtWidgets.QApplication.processEvents() def set_progress_range(self, max_value): self.progress_widget.setMaximum(max_value) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 797821b48d..9e2a267394 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -57,7 +57,7 @@ class PublisherWindow(QtWidgets.QWidget): # TODO Title, Icon, Stylesheet main_frame = QtWidgets.QWidget(self) # Overlay MUST be created after Main to be painted on top of it - overlay_frame = PublishOverlayFrame(self) + overlay_frame = PublishOverlayFrame(controller, self) overlay_frame.setVisible(False) # Header @@ -161,8 +161,6 @@ class PublisherWindow(QtWidgets.QWidget): ) overlay_frame.hide_requested.connect(self._on_overlay_hide_request) - controller.add_instance_change_callback(self._on_instance_change) - controller.add_plugin_change_callback(self._on_plugin_change) controller.add_publish_stopped_callback(self._on_publish_stop) self.main_frame = main_frame @@ -302,11 +300,11 @@ class PublisherWindow(QtWidgets.QWidget): def _on_validate_clicked(self): self._set_overlay_visibility(True) - # self.controller.validate() + self.controller.validate() def _on_publish_clicked(self): self._set_overlay_visibility(True) - # self.controller.publish() + self.controller.publish() def _refresh_instances(self): if self._refreshing_instances: @@ -339,29 +337,6 @@ class PublisherWindow(QtWidgets.QWidget): self.subset_attributes_widget.set_current_instances(instances) - def _on_plugin_change(self, plugin): - plugin_name = plugin.__name__ - if hasattr(plugin, "label") and plugin.label: - plugin_name = plugin.label - self.overlay_frame.set_plugin(plugin_name) - - def _on_instance_change(self, context, instance): - if instance is None: - new_name = ( - context.data.get("label") - or getattr(context, "label", None) - or context.data.get("name") - or "Context" - ) - else: - new_name = ( - instance.data.get("label") - or getattr(instance, "label", None) - or instance.data["name"] - ) - - self.overlay_frame.set_instance(new_name) - def _on_publish_stop(self): pass From 99797a08b3e5598181d326203fa425d99d120c65 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 18 Aug 2021 15:36:50 +0200 Subject: [PATCH 260/736] added few more attributes to controller --- openpype/tools/new_publisher/control.py | 26 +++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 53084909fc..7a5cac2c82 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -124,24 +124,45 @@ class PublisherController: self.host, dbcon, headless=False, reset=False ) + # pyblish.api.Context self._publish_context = None + # Pyblish logs + self._publish_logs = [] + # Store exceptions of validation error + self._publish_validation_errors = [] + # Any other exception that happened during publishing + self._publish_error = None + # Publishing is over validation order self._publish_validated = False + # Publishing should stop at validation stage self._publish_up_validation = False + # All publish plugins are processed + self._publish_finished = False + + # Validation order + # - plugin with order same or higher than this value is extractor or + # higher self._validation_order = ( pyblish.api.ValidatorOrder + PLUGIN_ORDER_OFFSET ) + + # Qt based main thread processor self._main_thread_processor = MainThreadProcess() + # Plugin iterator self._main_thread_iter = None + # Varianbles where callbacks are stored self._instances_refresh_callback_refs = set() self._plugins_refresh_callback_refs = set() self._publish_instance_changed_callback_refs = set() self._publish_plugin_changed_callback_refs = set() self._publishing_stopped_callback_refs = set() + # State flags to prevent executing method which is already in progress self._resetting_plugins = False self._resetting_instances = False + # Cacher of avalon documents self._asset_docs_cache = AssetDocsCache(self) @property @@ -317,10 +338,15 @@ class PublisherController: def _reset_publish(self): self._publish_validated = False self._publish_up_validation = False + self._publish_finished = False self._main_thread_processor.clear() self._main_thread_iter = self._publish_iterator() self._publish_context = pyblish.api.Context() + self._publish_logs = [] + self._publish_validation_errors = [] + self._publish_error = None + def validate(self): if self._publish_validated: return From d913cb34474c3a06a7eec0f9b91f02bd53aa3c3f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 18 Aug 2021 15:38:43 +0200 Subject: [PATCH 261/736] implemented _process_and_continue --- openpype/tools/new_publisher/control.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 7a5cac2c82..4786166b5f 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -10,6 +10,8 @@ from openpype.api import ( ) from openpype.pipeline import ( + PublishValidationError, + KnownPublishError, OpenPypePyblishPluginMixin ) @@ -419,7 +421,20 @@ class PublisherController: def _process_and_continue(self, plugin, instance): # TODO execute plugin - print(plugin, instance) + result = pyblish.plugin.process( + plugin, self._publish_context, instance + ) + exception = result.get("error") + if exception: + if ( + isinstance(exception, PublishValidationError) + and not self._publish_validated + ): + self._publish_validation_errors.append(exception) + + else: + self._publish_error = exception + self._publish_next_process() def get_asset_docs(self): From 74496f899ac08beb360adbab3b80bd8aa957fe56 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 18 Aug 2021 15:39:10 +0200 Subject: [PATCH 262/736] change publish finished if all plugins are processed --- openpype/tools/new_publisher/control.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 4786166b5f..cf6e3cabbd 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -417,6 +417,7 @@ class PublisherController: yield MainThreadItem( self._process_and_continue, plugin, None ) + self._publish_finished = True yield MainThreadItem(self._stop_publish) def _process_and_continue(self, plugin, instance): From 0b396c2d1bbacf63e78f56e1cc26591f200ce7aa Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 18 Aug 2021 15:45:43 +0200 Subject: [PATCH 263/736] do prevalidations in _publish_next_process --- openpype/tools/new_publisher/control.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index cf6e3cabbd..2658a80e59 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -368,7 +368,27 @@ class PublisherController: self._trigger_callbacks(self._publishing_stopped_callback_refs) def _publish_next_process(self): - item = next(self._main_thread_iter) + # Validations of progress before using iterator + # - same conditions may be inside iterator but they may be used + # only in specific cases (e.g. when it happens for a first time) + + # There are validation errors and validation is passed + # - can't do any progree + if ( + self._publish_validated + and self._publish_validation_errors + ): + item = MainThreadItem(self._stop_publish) + + # Any unexpected error happened + # - everything should stop + elif self._publish_error: + item = MainThreadItem(self._stop_publish) + + # Everything is ok so try to get new processing item + else: + item = next(self._main_thread_iter) + self._main_thread_processor.add_item(item) def _publish_iterator(self): From 58de88b69535df101f70ea2f11f968e212974a1e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 18 Aug 2021 15:45:58 +0200 Subject: [PATCH 264/736] do validations inside interator too --- openpype/tools/new_publisher/control.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 2658a80e59..4909906518 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -393,15 +393,29 @@ class PublisherController: def _publish_iterator(self): for plugin in self.publish_plugins: + # Check if plugin is over validation order + if not self._publish_validated: + self._publish_validated = ( + plugin.order >= self._validation_order + ) + + # Stop if plugin is over validation order and process + # should process up to validation. + if self._publish_up_validation and self._publish_validated: + yield MainThreadItem(self._stop_publish) + + # Stop if validation is over and validation errors happened if ( - self._publish_up_validation - and plugin.order >= self._validation_order + self._publish_validated + and self._publish_validation_errors ): yield MainThreadItem(self._stop_publish) + # Trigger callback that new plugin is going to be processed self._trigger_callbacks( self._publish_plugin_changed_callback_refs, plugin ) + # Plugin is instance plugin if plugin.__instanceEnabled__: instances = pyblish.logic.instances_by_plugin( self._publish_context, plugin From 4e5817759dc372e949febb9fef2e46f52bbaa5cc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 18 Aug 2021 15:46:42 +0200 Subject: [PATCH 265/736] hide background and borders of multiview --- openpype/style/style.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/style/style.css b/openpype/style/style.css index d912e79c91..bb826e879e 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -641,6 +641,11 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { border-color: #4E76BB; } +#MultipleItemView { + background: transparent; + border: none; +} + #MultipleItemView:item { background: {color:bg-view-selection}; border-radius: 0.4em; From 22357390a18498d1a768d76a7253036e602ba00e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 18 Aug 2021 17:15:05 +0200 Subject: [PATCH 266/736] added targets filtering --- openpype/pipeline/__init__.py | 8 +++++++- openpype/pipeline/create/context.py | 6 +++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/__init__.py b/openpype/pipeline/__init__.py index b27759d232..e968df4011 100644 --- a/openpype/pipeline/__init__.py +++ b/openpype/pipeline/__init__.py @@ -7,7 +7,11 @@ from .create import ( CreatedInstance ) -from .publish import OpenPypePyblishPluginMixin +from .publish import ( + PublishValidationError, + KnownPublishError, + OpenPypePyblishPluginMixin +) __all__ = ( @@ -18,5 +22,7 @@ __all__ = ( "AutoCreator", "CreatedInstance", + "PublishValidationError", + "KnownPublishError", "OpenPypePyblishPluginMixin" ) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index a3c059e617..960f5ab6df 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -456,7 +456,11 @@ class CreateContext: self._attr_plugins_by_family = {} publish_plugins = pyblish.api.discover() - self.publish_plugins = publish_plugins + targets = pyblish.logic.registered_targets() or ["default"] + plugins_by_targets = pyblish.logic.plugins_by_targets( + publish_plugins, targets + ) + self.publish_plugins = plugins_by_targets # Collect plugins that can have attribute definitions plugins_with_defs = [] From 68b1d0b16c59b66896d4dbca085cf0b1aa57be58 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 18 Aug 2021 17:16:55 +0200 Subject: [PATCH 267/736] added publish started callback --- openpype/tools/new_publisher/control.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 4909906518..3c02ec5e45 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -156,6 +156,7 @@ class PublisherController: # Varianbles where callbacks are stored self._instances_refresh_callback_refs = set() self._plugins_refresh_callback_refs = set() + self._publishing_started_callback_refs = set() self._publish_instance_changed_callback_refs = set() self._publish_plugin_changed_callback_refs = set() self._publishing_stopped_callback_refs = set() @@ -199,6 +200,10 @@ class PublisherController: ref = weakref.WeakMethod(callback) self._plugins_refresh_callback_refs.add(ref) + def add_publish_started_callback(self, callback): + ref = weakref.WeakMethod(callback) + self._publishing_started_callback_refs.add(ref) + def add_instance_change_callback(self, callback): ref = weakref.WeakMethod(callback) self._publish_instance_changed_callback_refs.add(ref) @@ -360,6 +365,8 @@ class PublisherController: self._start_publish() def _start_publish(self): + """Start or continue in publishing.""" + self._trigger_callbacks(self._publishing_started_callback_refs) self._main_thread_processor.start() self._publish_next_process() From 44802371bcfd8e6ab40da2a11377a6d8cbc69f78 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 18 Aug 2021 17:17:28 +0200 Subject: [PATCH 268/736] move avalon mongo methods in code --- openpype/tools/new_publisher/control.py | 58 ++++++++++++------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 3c02ec5e45..dd85f6b5b1 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -216,6 +216,35 @@ class PublisherController: ref = weakref.WeakMethod(callback) self._publishing_stopped_callback_refs.add(ref) + def get_asset_docs(self): + return self._asset_docs_cache.get_asset_docs() + + def get_asset_hierarchy(self): + _queue = collections.deque(self.get_asset_docs()) + + output = collections.defaultdict(list) + while _queue: + asset_doc = _queue.popleft() + parent_id = asset_doc["data"]["visualParent"] + output[parent_id].append(asset_doc) + return output + + def get_task_names_for_asset_names(self, asset_names): + task_names_by_asset_name = ( + self._asset_docs_cache.get_task_names_by_asset_name() + ) + tasks = None + for asset_name in asset_names: + task_names = set(task_names_by_asset_name.get(asset_name, [])) + if tasks is None: + tasks = task_names + else: + tasks &= task_names + + if not tasks: + break + return tasks + def _trigger_callbacks(self, callbacks, *args, **kwargs): # Trigger reset callbacks to_remove = set() @@ -479,35 +508,6 @@ class PublisherController: self._publish_next_process() - def get_asset_docs(self): - return self._asset_docs_cache.get_asset_docs() - - def get_asset_hierarchy(self): - _queue = collections.deque(self.get_asset_docs()) - - output = collections.defaultdict(list) - while _queue: - asset_doc = _queue.popleft() - parent_id = asset_doc["data"]["visualParent"] - output[parent_id].append(asset_doc) - return output - - def get_task_names_for_asset_names(self, asset_names): - task_names_by_asset_name = ( - self._asset_docs_cache.get_task_names_by_asset_name() - ) - tasks = None - for asset_name in asset_names: - task_names = set(task_names_by_asset_name.get(asset_name, [])) - if tasks is None: - tasks = task_names - else: - tasks &= task_names - - if not tasks: - break - return tasks - def collect_families_from_instances(instances, only_active=False): all_families = set() From c91a2fb435da598e7be43d3130206b2e34e1a3b9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 18 Aug 2021 17:17:59 +0200 Subject: [PATCH 269/736] added log collecting --- openpype/tools/new_publisher/control.py | 50 +++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index dd85f6b5b1..6706b225a7 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -383,16 +383,18 @@ class PublisherController: self._publish_validation_errors = [] self._publish_error = None + def publish(self): + """Run publishing.""" + self._publish_up_validation = False + self._start_publish() + def validate(self): + """Run publishing and stop after Validation.""" if self._publish_validated: return self._publish_up_validation = True self._start_publish() - def publish(self): - self._publish_up_validation = False - self._start_publish() - def _start_publish(self): """Start or continue in publishing.""" self._trigger_callbacks(self._publishing_started_callback_refs) @@ -400,6 +402,7 @@ class PublisherController: self._publish_next_process() def _stop_publish(self): + """Stop or pause publishing.""" self._main_thread_processor.stop() self._trigger_callbacks(self._publishing_stopped_callback_refs) @@ -490,11 +493,50 @@ class PublisherController: self._publish_finished = True yield MainThreadItem(self._stop_publish) + def _extract_log_items(self, result): + output = [] + records = result.get("records") + if not records: + return output + + instance_name = None + instance = result["instance"] + if instance is not None: + instance_name = instance.data["name"] + + for record in result.get("records") or []: + if isinstance(record, dict): + print("is dict") + record_item = record + else: + record_item = { + "instance": instance_name, + "type": "record", + "label": str(record.msg), + "msg": str(record.msg), + "name": record.name, + "lineno": record.lineno, + "levelno": record.levelno, + "levelname": record.levelname, + "threadName": record.threadName, + "filename": record.filename, + "pathname": record.pathname, + "msecs": record.msecs + } + + record_item["instance"] = instance_name + output.append(record_item) + + return output + def _process_and_continue(self, plugin, instance): # TODO execute plugin result = pyblish.plugin.process( plugin, self._publish_context, instance ) + + self._publish_logs.extend(self._extract_log_items(result)) + exception = result.get("error") if exception: if ( From 8c0392e34b19e12762aed299d795873c1e6d70de Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 18 Aug 2021 17:18:13 +0200 Subject: [PATCH 270/736] added helper methods --- openpype/tools/new_publisher/control.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 6706b225a7..f82b464e80 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -11,7 +11,6 @@ from openpype.api import ( from openpype.pipeline import ( PublishValidationError, - KnownPublishError, OpenPypePyblishPluginMixin ) @@ -246,6 +245,7 @@ class PublisherController: return tasks def _trigger_callbacks(self, callbacks, *args, **kwargs): + """Helper method to trigger callbacks stored by their rerence.""" # Trigger reset callbacks to_remove = set() for ref in callbacks: @@ -371,6 +371,20 @@ class PublisherController: self._reset_instances() + # --- Publish specific implementations --- + @property + def publish_has_finished(self): + return self._publish_finished + + def get_publish_crash_error(self): + return self._publish_error + + def get_publish_logs(self): + return self._publish_logs + + def get_validation_errors(self): + return self._publish_validation_errors + def _reset_publish(self): self._publish_validated = False self._publish_up_validation = False From 08703fbae35353493af6d2b1be4d1f3ab5b8d718 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 18 Aug 2021 17:43:23 +0200 Subject: [PATCH 271/736] moved progress methos to top --- openpype/tools/new_publisher/widgets/widgets.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 8b6fd97359..c4afa69f96 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -995,6 +995,14 @@ class PublishOverlayFrame(QtWidgets.QFrame): self.refresh_btn = refresh_btn self.publish_btn = publish_btn + def set_progress_range(self, max_value): + # TODO implement triggers for this method + self.progress_widget.setMaximum(max_value) + + def set_progress(self, value): + # TODO implement triggers for this method + self.progress_widget.setValue(value) + def _on_instance_change(self, context, instance): if instance is None: new_name = ( @@ -1021,8 +1029,4 @@ class PublishOverlayFrame(QtWidgets.QFrame): self.plugin_label.setText(plugin_name) QtWidgets.QApplication.processEvents() - def set_progress_range(self, max_value): - self.progress_widget.setMaximum(max_value) - def set_progress(self, value): - self.progress_widget.setValue(value) From 24e8768419ae2067b56c3cac8c7dbdbe02098bcc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 18 Aug 2021 17:44:10 +0200 Subject: [PATCH 272/736] added _set_success_property to able change style of background --- openpype/tools/new_publisher/widgets/widgets.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index c4afa69f96..95d304a3db 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -1030,3 +1030,6 @@ class PublishOverlayFrame(QtWidgets.QFrame): QtWidgets.QApplication.processEvents() + def _set_success_property(self, success): + self.info_frame.setProperty("success", str(success)) + self.info_frame.style().polish(self.info_frame) From d89aebcd5b5efabda43d32ffe4f0ac116740f665 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 18 Aug 2021 17:49:29 +0200 Subject: [PATCH 273/736] added message label --- openpype/tools/new_publisher/widgets/widgets.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 95d304a3db..70b68188fe 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -929,9 +929,12 @@ class PublishOverlayFrame(QtWidgets.QFrame): top_layout.addStretch(1) top_layout.addWidget(hide_btn) - main_label = QtWidgets.QLabel("Publishing...", content_widget) + main_label = QtWidgets.QLabel(content_widget) main_label.setAlignment(QtCore.Qt.AlignCenter) + message_label = QtWidgets.QLabel(content_widget) + message_label.setAlignment(QtCore.Qt.AlignCenter) + instance_label = QtWidgets.QLabel("", content_widget) instance_label.setAlignment( QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter @@ -947,6 +950,7 @@ class PublishOverlayFrame(QtWidgets.QFrame): progress_widget = QtWidgets.QProgressBar(content_widget) copy_log_btn = QtWidgets.QPushButton("Copy log", content_widget) + copy_log_btn.setVisible(False) stop_btn = QtWidgets.QPushButton("Stop", content_widget) refresh_btn = QtWidgets.QPushButton("Refresh", content_widget) publish_btn = QtWidgets.QPushButton("Publish", content_widget) @@ -964,6 +968,7 @@ class PublishOverlayFrame(QtWidgets.QFrame): content_layout.addLayout(top_layout) content_layout.addWidget(main_label) + content_layout.addWidget(message_label) content_layout.addStretch(1) content_layout.addLayout(instance_plugin_layout) content_layout.addWidget(progress_widget) @@ -984,6 +989,7 @@ class PublishOverlayFrame(QtWidgets.QFrame): self.hide_btn = hide_btn self.main_label = main_label + self.message_label = message_label self.info_frame = info_frame self.instance_label = instance_label From 7a0e33297e4dc1db8bf87f2b086604b707b3bf87 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 18 Aug 2021 17:51:24 +0200 Subject: [PATCH 274/736] added publish callbacks to overlay --- .../tools/new_publisher/widgets/widgets.py | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 70b68188fe..1229acd92c 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -3,6 +3,8 @@ import copy import collections from Qt import QtWidgets, QtCore, QtGui +from openpype.pipeline import KnownPublishError + from openpype.widgets.attribute_defs import create_widget_for_attr_def from openpype.tools.flickcharm import FlickCharm @@ -983,6 +985,9 @@ class PublishOverlayFrame(QtWidgets.QFrame): controller.add_instance_change_callback(self._on_instance_change) controller.add_plugin_change_callback(self._on_plugin_change) + controller.add_plugins_refresh_callback(self._on_publish_reset) + controller.add_publish_started_callback(self._on_publish_start) + controller.add_publish_stopped_callback(self._on_publish_stop) self.controller = controller @@ -1009,7 +1014,18 @@ class PublishOverlayFrame(QtWidgets.QFrame): # TODO implement triggers for this method self.progress_widget.setValue(value) + def _on_publish_reset(self): + self._set_success_property("") + self.main_label.setText("Hit publish! (if you want)") + self.message_label.setText("") + self.copy_log_btn.setVisible(False) + + def _on_publish_start(self): + self._set_success_property(-1) + self.main_label.setText("Publishing...") + def _on_instance_change(self, context, instance): + """Change instance label when instance is going to be processed.""" if instance is None: new_name = ( context.data.get("label") @@ -1028,6 +1044,7 @@ class PublishOverlayFrame(QtWidgets.QFrame): QtWidgets.QApplication.processEvents() def _on_plugin_change(self, plugin): + """Change plugin label when instance is going to be processed.""" plugin_name = plugin.__name__ if hasattr(plugin, "label") and plugin.label: plugin_name = plugin.label @@ -1035,6 +1052,40 @@ class PublishOverlayFrame(QtWidgets.QFrame): self.plugin_label.setText(plugin_name) QtWidgets.QApplication.processEvents() + def _on_publish_stop(self): + error = self.controller.get_publish_crash_error() + if error: + self._set_error(error) + return + + validation_errors = self.controller.get_validation_errors() + if validation_errors: + self._set_validation_errors(validation_errors) + return + + if self.controller.has_finished: + self._set_finished() + + def _set_error(self, error): + self.main_label.setText("Error happened") + if isinstance(error, KnownPublishError): + msg = str(error) + else: + msg = ( + "Something went wrong. Send report" + " to your supervisor or OpenPype." + ) + self.message_label.setText(msg) + self._set_success_property(0) + self.copy_log_btn.setVisible(True) + + def _set_validation_errors(self, validation_errors): + # TODO implement + pass + + def _set_finished(self): + self.main_label.setText("Finished") + self._set_success_property(1) def _set_success_property(self, success): self.info_frame.setProperty("success", str(success)) From 6e0819bdd26f0535f59b97e6139a4876353211af Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 18 Aug 2021 17:51:35 +0200 Subject: [PATCH 275/736] added first version of log copy --- openpype/tools/new_publisher/widgets/widgets.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 1229acd92c..33d2b08e61 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -1,5 +1,6 @@ import os import copy +import json import collections from Qt import QtWidgets, QtCore, QtGui @@ -982,6 +983,7 @@ class PublishOverlayFrame(QtWidgets.QFrame): main_layout.addWidget(info_frame, 0) hide_btn.clicked.connect(self.hide_requested) + copy_log_btn.clicked.connect(self._on_copy_log) controller.add_instance_change_callback(self._on_instance_change) controller.add_plugin_change_callback(self._on_plugin_change) @@ -1090,3 +1092,13 @@ class PublishOverlayFrame(QtWidgets.QFrame): def _set_success_property(self, success): self.info_frame.setProperty("success", str(success)) self.info_frame.style().polish(self.info_frame) + + def _on_copy_log(self): + logs = self.controller.get_publish_logs() + logs_string = json.dumps(logs, indent=4) + + mime_data = QtCore.QMimeData() + mime_data.setText(logs_string) + QtWidgets.QApplication.instance().clipboard().setMimeData( + mime_data + ) From 44966f95e6b7b78dd74e416b3fc6551773bea326 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 18 Aug 2021 17:51:54 +0200 Subject: [PATCH 276/736] simplified records --- openpype/tools/new_publisher/control.py | 33 ++++++++++--------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index f82b464e80..3cba6fcb03 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -519,26 +519,19 @@ class PublisherController: instance_name = instance.data["name"] for record in result.get("records") or []: - if isinstance(record, dict): - print("is dict") - record_item = record - else: - record_item = { - "instance": instance_name, - "type": "record", - "label": str(record.msg), - "msg": str(record.msg), - "name": record.name, - "lineno": record.lineno, - "levelno": record.levelno, - "levelname": record.levelname, - "threadName": record.threadName, - "filename": record.filename, - "pathname": record.pathname, - "msecs": record.msecs - } - - record_item["instance"] = instance_name + record_item = { + "instance": instance_name, + "type": "record", + "msg": str(record.msg), + "name": record.name, + "lineno": record.lineno, + "levelno": record.levelno, + "levelname": record.levelname, + "threadName": record.threadName, + "filename": record.filename, + "pathname": record.pathname, + "msecs": record.msecs + } output.append(record_item) return output From 5a4983961ecc16d1de1eba1b18175a0f216f12b0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 18 Aug 2021 18:05:43 +0200 Subject: [PATCH 277/736] added errors to log output --- openpype/tools/new_publisher/control.py | 33 +++++++++++++++++++------ 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 3cba6fcb03..9f4f8a8ed5 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -1,6 +1,7 @@ import copy import weakref import logging +import traceback import collections import avalon.api import pyblish.api @@ -509,20 +510,23 @@ class PublisherController: def _extract_log_items(self, result): output = [] - records = result.get("records") - if not records: - return output - + records = result.get("records") or [] instance_name = None instance = result["instance"] if instance is not None: instance_name = instance.data["name"] - for record in result.get("records") or []: + for record in records: + record_exc_info = record.exc_info + if record_exc_info is not None: + record_exc_info = "".join( + traceback.format_exception(*record_exc_info) + ) + record_item = { "instance": instance_name, "type": "record", - "msg": str(record.msg), + "msg": record.getMessage(), "name": record.name, "lineno": record.lineno, "levelno": record.levelno, @@ -530,10 +534,25 @@ class PublisherController: "threadName": record.threadName, "filename": record.filename, "pathname": record.pathname, - "msecs": record.msecs + "msecs": record.msecs, + "exc_info": record_exc_info } output.append(record_item) + exception = result.get("error") + if exception: + fname, line_no, func, exc = exception.traceback + error_item = { + "type": "error", + "msg": str(exception), + "filename": str(fname), + "lineno": str(line_no), + "func": str(func), + "traceback": exception.formatted_traceback, + "instance": instance_name + } + output.append(error_item) + return output def _process_and_continue(self, plugin, instance): From 4e6dedb88109fae89f6541aae1369b9cf191b3c6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 18 Aug 2021 18:06:03 +0200 Subject: [PATCH 278/736] added different styles for overlay window --- openpype/style/style.css | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/openpype/style/style.css b/openpype/style/style.css index bb826e879e..7675f7b147 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -666,11 +666,23 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { } #PublishOverlay { - background: rgb(194, 226, 236); + background: {color:bg}; border: 2px solid black; border-radius: 0.3em; } +#PublishOverlay[success="-1"] { + background: rgb(194, 226, 236); +} + +#PublishOverlay[success="1"] { + background: #458056 +} + +#PublishOverlay[success="0"] { + background: #AA5050 +} + #PublishOverlay QLabel { color: black; } From 779a65521e3a8757e67c4455720f56cba2625bb2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 19 Aug 2021 15:46:46 +0200 Subject: [PATCH 279/736] made bigger font on main label --- openpype/style/style.css | 4 ++++ openpype/tools/new_publisher/widgets/widgets.py | 1 + 2 files changed, 5 insertions(+) diff --git a/openpype/style/style.css b/openpype/style/style.css index 7675f7b147..1692644e93 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -687,6 +687,10 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { color: black; } +#PublishOverlayMainLabel { + font-size: 12pt; +} + #ArrowBtn, #ArrowBtn:disabled, #ArrowBtn:hover { background: transparent; } diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 33d2b08e61..453f11b9ce 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -933,6 +933,7 @@ class PublishOverlayFrame(QtWidgets.QFrame): top_layout.addWidget(hide_btn) main_label = QtWidgets.QLabel(content_widget) + main_label.setObjectName("PublishOverlayMainLabel") main_label.setAlignment(QtCore.Qt.AlignCenter) message_label = QtWidgets.QLabel(content_widget) From 0fca397ae2d788c3b71bae7ad75d83aabd08037d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 19 Aug 2021 15:47:05 +0200 Subject: [PATCH 280/736] added more images --- .../tools/new_publisher/widgets/__init__.py | 9 +++++ openpype/tools/new_publisher/widgets/icons.py | 35 ++++++++++++++++++ .../new_publisher/widgets/images/play.png | Bin 0 -> 1020 bytes .../new_publisher/widgets/images/refresh.png | Bin 0 -> 7656 bytes .../new_publisher/widgets/images/stop.png | Bin 0 -> 463 bytes .../images/thumbnail.png} | Bin .../new_publisher/widgets/images/validate.png | Bin 0 -> 46158 bytes 7 files changed, 44 insertions(+) create mode 100644 openpype/tools/new_publisher/widgets/icons.py create mode 100644 openpype/tools/new_publisher/widgets/images/play.png create mode 100644 openpype/tools/new_publisher/widgets/images/refresh.png create mode 100644 openpype/tools/new_publisher/widgets/images/stop.png rename openpype/tools/new_publisher/{image_file.png => widgets/images/thumbnail.png} (100%) create mode 100644 openpype/tools/new_publisher/widgets/images/validate.png diff --git a/openpype/tools/new_publisher/widgets/__init__.py b/openpype/tools/new_publisher/widgets/__init__.py index b5639926dc..95e0213b1c 100644 --- a/openpype/tools/new_publisher/widgets/__init__.py +++ b/openpype/tools/new_publisher/widgets/__init__.py @@ -1,3 +1,8 @@ +from .icons import ( + get_icon_path, + get_pixmap, + get_icon +) from .widgets import ( SubsetAttributesWidget, PublishOverlayFrame @@ -13,6 +18,10 @@ from .instance_views_widgets import ( __all__ = ( + "get_icon_path", + "get_pixmap", + "get_icon", + "SubsetAttributesWidget", "PublishOverlayFrame", diff --git a/openpype/tools/new_publisher/widgets/icons.py b/openpype/tools/new_publisher/widgets/icons.py new file mode 100644 index 0000000000..7f7084bccd --- /dev/null +++ b/openpype/tools/new_publisher/widgets/icons.py @@ -0,0 +1,35 @@ +import os + +from Qt import QtGui + + +def get_icon_path(icon_name=None, filename=None): + if icon_name is None and filename is None: + return None + + if filename is None: + filename = "{}.png".format(icon_name) + + path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "images", + filename + ) + if os.path.exists(path): + return path + return None + + +def get_pixmap(icon_name=None, filename=None): + path = get_icon_path(icon_name, filename) + if not path: + return None + + return QtGui.QPixmap(path) + + +def get_icon(icon_name=None, filename=None): + pix = get_pixmap(icon_name, filename) + if not pix: + return None + return QtGui.QIcon(pix) diff --git a/openpype/tools/new_publisher/widgets/images/play.png b/openpype/tools/new_publisher/widgets/images/play.png new file mode 100644 index 0000000000000000000000000000000000000000..6e0fefab28e3b8a774725670d7a414bc61780490 GIT binary patch literal 1020 zcmeAS@N?(olHy`uVBq!ia0vp^3qY8I4M=vMPuB%foCO|{#S9F5M?jcysy3fA0|WCq zPZ!6KiaBqu`{oHd3bZ|xYH2Cr+4NNFWN3r5O7^))p=Ux9Udl9Y&=R}o(Wj{{cfO?j z@1FCFZhL=(tk-1%nu-K2G_3j;aCP>@_YQ%pel5^1TUBNI%U0uALc^+6=T}v&UlqRi zeydU+o5k(PPiyRY>JBi?cTI7hR@`ZF;AU^fAGOmN&s>^xQ!ICMh<^}> zLWfyI@TZDWxzeX@o#PWT8;m!}cpPIqccO@4CcB2yw7aM64oF6ptvUr%d~;?NvqAHUi8wj?>r^8`e*JdQM_NuTJ?P27l$#vIYmfPkh=Y z@!;o)B4(Zf$xV9<*(!jfAlnYU$hc0%b?(!`6B=$So)!m^%BQ&v4w#+zG*coWI_0M< z&j$WYHB8KJ+I6HQ66#ZaGBC@u>u@v6tgV?Qxqv@)^BIO(&FQIZFZ@^P7(49uN}0*{ zYr55G9)q?mk!cMZmz*|Y?$Pv}CYcbUx#yl1RvnEzVXv zXv%wXZ7X-g`3)c;*~1{ApF2T9GiI*_3Hj-Pgm&&Y2NG)k4HByHSNc>sYuWt8@IRRAvB7PQ7XhHnTBB0Q}C1*iG-mgGH{Hh?K#8Y7XBJp zCh-EjzA?PUNcdd@wD#Ey-Eu zn8*H*Y0~Y5TKp64SOpj!d~$7KmN<|ZoZ9f@+R8VaYTM7H?rV5r*3iSOvb{0I%U~z3 z#%WfS?T)VU3OjlAvYJlj3JK~f>~xI=W{v;1SHC*@V*A2|#f<*~uUd!hmHMkEQfP3% wJ#??!U%4%hznVXYbIJSJ%!rh^fXoN??af;D9qfqaeF~EBboFyt=akR{0I7(vRR910 literal 0 HcmV?d00001 diff --git a/openpype/tools/new_publisher/widgets/images/refresh.png b/openpype/tools/new_publisher/widgets/images/refresh.png new file mode 100644 index 0000000000000000000000000000000000000000..fe04ab9a8d49dfa103fc2101471866b01b188854 GIT binary patch literal 7656 zcmeHLX;f2J*SIAy1wuI^?BFIz31$+_p|qY&OUqP z-YlOr9vZ6pssI3JczL?50{{d(L;xyC_;Kkc(`)c#B-uZRzm6`za1#@v*l{cje+!p| zVF}q$03dw)ws6A_hKtnQp)Hhd30%NJ6-2oV)HQn9KRD^ zR o;%z29qbX-yTdw`i9~w$u<6&)bXwf-8izCY~UR9$g-5{R9vFhsl9!>sQ8(W_G zlpfGlYj!+ock|>zjm}tA*Naaxm@C?K_5#eVxkqKKzWGePY1Ym16YB@FQRpp&$KQJU zccbSeuk1}tR(8Kf*CIHX+dO;g=lJS<&#wJ1>`RVyl^742*X`M5dS`2PA3ZzuR^{dN zf}eQnI{Y8%Qr>wsoTj7OxJygo_h-KVf38DpK^`|cw$6M`;j(@D^YrfrZa9g(eC@CX zj;C~j%_H6pWvT7aRn@S&evg`a3{fS$A+S6r@9VA1yR^yV19STVI%-$&Hjj_~G1(2~ zS#}a$M!k@unohjlu73eYzjFQuzS;Pap{ncasXl>+jC3uZq6DLKBKY|g{pNcFNm>;GFiURC`C2huxO_MgIUkX%&YK381}z1U}OhG1i0KH z*8t1SvOw=bM8JF6rHQixK`Y$dD^8i)Zf+0BJ9?0GjLBY@+IG48q?rxxWni{h_MG^DXqUtp^=F{>*|-uI+;&Q#Y*k@oqcmnc4cv_pI9>jElj>Y%+>df#4K<;tJ&B&wP;L= zyF9-3={Q!;ym1Hv-_~B+*7HYtNRh;DbAGi-dptS65nXFcnU|C@%o`cClfM-oxVvh= z?1N4+`a*j6awsQPUB2boc>c6Ofp!HO6H?d#0AMU3CoV&a%7UHzQjErblE2?d9~BMd6`oa`&IE+mdR=CcbEGw>&^+w z9pCrnmzxR2H#OCqY$AKC(Jxfa2EC&<>N=i%mtyZ!(zG)j`{7Bl;GjFQu#0_ij6@}rFQR?wXy^b*N zE~J$}+`Tx=+SFs@x4GY&$)>VfP6Fu(X=%1~b+=vC7>f-zpHD#=8X{h@ZiJ5s1=V$R zqC5PFNl+D?xGlrb(G>1d3)pZs3i4h}VI;&`(U}R6EGuC=7w%R7;N&Xg(iyQVJ|>bC z&E_~`hngf<44dhU4Yc(pcym{*}KFB&5Iq@hm=-wzR8ymOvYz>?&id}nRJ*8JS&bB53BNEuf)H(^zicb z`D~$}AetS|ow0(+{+lMB9rYJke~V4AGLz1ifxzaUasQ_MXYMn~u$H$s#VvucSrML> zn=@A7pTbOFu$h#Zp?#DclR%Fo<7^0#);LlWISNN7+LLh1NCuf;M<9}kEbA|zyf{2Q zox@-$pkQz-HjEQxZ)Znl+DGD|YzPVn5)(&fkyyCMNV+}QK9b0!(+OWdtW9LYRY{Ng zGAacW6Na+3w~n;2iL%AfNs;zA5|hBdMUousa1Qo1_ACax=r9`%lR;UTkQh&gx04-D zk7nVyoah-J3c@MNe7u~o)>faq&PaUX==>`C_a)0vW1K#1izk~0Yiv=}{A|_xu{M30eL4hj?3pDDW~Oi{bjBwqJbDs~ zIpYYn`?SP}p>v{H@ClzSsXyiHf7mWtGToL)Vlr?98|z3Mi5vy*IJ|{8qJtfsPO>Fi z+mSw{@fn?$5XBeJ6Isim;Y{Id;1Zh224gWx=68ROMi9eNFh#H?;s|6I*PlqD5bP*K zYb<^?S-j#*|Cy~5{(tb{G^6lE6M*eL$>8P!_bUA7W;M%~g2MmeHM-CirR!fY@UN7=R@Ya${uKlNO8IMb{h!gL`q%3Yiv#}y62LDrxtiNj000U2toC<@ zUx*+Gnt3t$di%=2|2qbvlLi@Z(jb4`8b5&A_Xz%434oESJludxB_mIGg64V#^8i3| zkKzX!Uha*7h01&{Z+GQh6Xm}D0RT{`@^V||4}a)<=3DrLtghsn2o;q)m#@AT zzh|yk5Q{YTo#%_&usF7@TGYDysNg&8D&%XH^n!Z59yqpy7J=%C6{mbtjYo{wN?Ya( zbO4k3vD=9RQ|uN@s+r=(~7u+6)#B1bz~JSS3zH4_5&b3gP%# zy!gW)YJuy1>Yn09E`HSfSk(v7Is=K7!zo9_R(11Dk9d{dY&W|UtvWE$uOx|9jTuok zZ`h?1Gm@FsxkBlK@QT};lc;1TR?mkZ5Qm__ccYe=()XbVqZamy5gMq0puzX16o6E+ zS|%5$%MVzUrU2>-akM}!v7|z2@HJR1uuFRZ5(M?)Fh`(EQ?EK!S^D@DVxzLO^A#fL z6|lfDOj>COg!Sv5Fa!eo)gEOaX|GejCNAILxoEW<2l9U9^aHKoBukif4HH-f{w z3UQbnko4HeqbCB8tBFP6Fdmpzl1hMaJpe+;6hc}xbnDPZEy-y!Wtq^?fGVw3y1R+| zj$eh!Js$>=4rY2R)H|*-cDW`V`Az=tld+pMS?8Zur*-QF4QVf=)=!0N+VTy!58 zNO&N<=3PbUQKE{fP*>sw4@|xTCYsC$8!LXcLS#OSAg2+xhK_z~3`})K5mldw_?u zD3lCAumbhO!zh$?7XWpm?*~urUIui7=cd+Hj2j=M?su0Hzzy{acpQY29Bspo?@qFsu+dK zx!9$o8CmS!i#li(j8j@irSsr}7}=RsT0D0hZK@D~H*!9@-mCOZ>kgAOkBc42Cdb;( z?l1|MD5j9hdow0A^Ol%#p~@X)-)lUTyrLcVN94S7;7YxZBbDzoHEg@n>UDNbh>^YU z>FozEckbSZ4t3_lSR9bH#cCi&_3H}{{TjGhN`l+P|KcKFoJRh3BBu95(JJE|RXxTv zyneso%qe72Wya23A6z%~heQi~AYKwDeB*HG^zok1=7+LiXm5RI;9di#QxIq`fS>Q6 z4YmjFD4vcsby|i_L(o$E0WHJgVJN5Y&6pus2Jx0oPJ1hBjzd8=rfifwtT!(Kqpg+imXzxm?uWPK;}|kxn3G86a%J& z>!Nmm1lxojN__?+4=s~-=^X5nBmw$l*U8tQC&mRp8yaof1(;S}oVvQWS}FIL82Cl^ z5h8O9O%3A+f=v5rCGEr1!^_^`K+uzx2R#ItratWLO&ElV7A+aE0q8^*9D3TNQUMRhVI@KPxlvV)I!8ovZ;X&XRJn#VdNN&8aBS4CQ5L|G9o)kVv@*@WOfxdBL zeQ6)mQ;H&7kpQ6pd>Ald|JmMyxes@uBl?O~1U=0~5n7=hVBFQw6jUE}bpWQdw6dT$ z2r)1uN(AJ_#yU{@sFtSG6*6GFkTgJL@Db+4t{aS=jJRL-hwEaoZF&-e;qnA3mz;$_ zP&hgHw%cK7BQL8MS7VUNE~G6R;F7UV>hIxQzo&UG1x`Lp@e!D=80$BQGh#lg4Zh;DABqqX~{@`4pq6 z%h{_&#JSpp3)T%rWTp~YP}~%9+2XE}*1<)VVDX`X4PDN8!A;?a%qa9fwrzz_I>)C2u1TuO{Yc4=EmI+Eo$Y@6(rbX|5EouR@pROaZS{sFgZWz*9B4G6xFy zLxrx)z5$Ga*K8@^%6wgn4F$wj7{~~X;6$D-&8h*sou^y6v;izHH;`F0fJxg8O3fS) zF569Hi|Ro{o(bXOQ`K9J_6Wx9c4-{LhH&SuraHkUqxhs;IqC7?*9R_i`zd;pm-`yG J%H_1}{{dRE*dG7@ literal 0 HcmV?d00001 diff --git a/openpype/tools/new_publisher/widgets/images/stop.png b/openpype/tools/new_publisher/widgets/images/stop.png new file mode 100644 index 0000000000000000000000000000000000000000..319e014e3166001e5c4a329c8b2433763fccbbe4 GIT binary patch literal 463 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
f^)tEn-=sh?&BR2R;6j84wcC#$k#5bK!P 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 Q0R$bicR5h}gKyG*0jvjrKL7v# literal 0 HcmV?d00001 From efb0dbc1b966f4bf0fab0db97311cb8b5c298395 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 19 Aug 2021 15:48:08 +0200 Subject: [PATCH 281/736] use new icons for buttons in overlay widget --- .../tools/new_publisher/widgets/widgets.py | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 453f11b9ce..9bef411183 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -10,10 +10,10 @@ from openpype.widgets.attribute_defs import create_widget_for_attr_def from openpype.tools.flickcharm import FlickCharm - -def get_default_thumbnail_image_path(): - dirpath = os.path.dirname(os.path.abspath(__file__)) - return os.path.join(dirpath, "image_file.png") +from .icons import ( + get_icon, + get_pixmap +) class AssetsHierarchyModel(QtGui.QStandardItemModel): @@ -887,7 +887,7 @@ class ThumbnailWidget(QtWidgets.QWidget): def __init__(self, parent): super(ThumbnailWidget, self).__init__(parent) - default_pix = QtGui.QPixmap(get_default_thumbnail_image_path()) + default_pix = get_pixmap("thumbnail") thumbnail_label = QtWidgets.QLabel(self) thumbnail_label.setPixmap( @@ -955,15 +955,25 @@ class PublishOverlayFrame(QtWidgets.QFrame): copy_log_btn = QtWidgets.QPushButton("Copy log", content_widget) copy_log_btn.setVisible(False) - stop_btn = QtWidgets.QPushButton("Stop", content_widget) - refresh_btn = QtWidgets.QPushButton("Refresh", content_widget) - publish_btn = QtWidgets.QPushButton("Publish", content_widget) + + stop_btn = QtWidgets.QPushButton(content_widget) + stop_btn.setIcon(get_icon("stop")) + + refresh_btn = QtWidgets.QPushButton(content_widget) + refresh_btn.setIcon(get_icon("refresh")) + + validate_btn = QtWidgets.QPushButton(content_widget) + validate_btn.setIcon(get_icon("validate")) + + publish_btn = QtWidgets.QPushButton(content_widget) + publish_btn.setIcon(get_icon("play")) footer_layout = QtWidgets.QHBoxLayout() footer_layout.addWidget(copy_log_btn, 0) footer_layout.addStretch(1) footer_layout.addWidget(refresh_btn, 0) footer_layout.addWidget(stop_btn, 0) + footer_layout.addWidget(validate_btn, 0) footer_layout.addWidget(publish_btn, 0) content_layout = QtWidgets.QVBoxLayout(content_widget) @@ -972,6 +982,7 @@ class PublishOverlayFrame(QtWidgets.QFrame): content_layout.addLayout(top_layout) content_layout.addWidget(main_label) + content_layout.addStretch(1) content_layout.addWidget(message_label) content_layout.addStretch(1) content_layout.addLayout(instance_plugin_layout) @@ -1007,6 +1018,7 @@ class PublishOverlayFrame(QtWidgets.QFrame): self.copy_log_btn = copy_log_btn self.stop_btn = stop_btn self.refresh_btn = refresh_btn + self.validate_btn = validate_btn self.publish_btn = publish_btn def set_progress_range(self, max_value): From b5b6d42d6b50dff08a9a3697b4529c674e6da55a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 19 Aug 2021 15:48:19 +0200 Subject: [PATCH 282/736] fixed attribute name --- openpype/tools/new_publisher/widgets/widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 9bef411183..ef4d9dc958 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -1078,7 +1078,7 @@ class PublishOverlayFrame(QtWidgets.QFrame): self._set_validation_errors(validation_errors) return - if self.controller.has_finished: + if self.controller.publish_has_finished: self._set_finished() def _set_error(self, error): From 6427cb7314b75a6fa822edb2e9883bd242768c56 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 19 Aug 2021 15:49:31 +0200 Subject: [PATCH 283/736] added more callbacks to controller --- openpype/tools/new_publisher/control.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 9f4f8a8ed5..0a1667c03e 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -156,10 +156,11 @@ class PublisherController: # Varianbles where callbacks are stored self._instances_refresh_callback_refs = set() self._plugins_refresh_callback_refs = set() - self._publishing_started_callback_refs = set() + self._publish_started_callback_refs = set() + self._publish_validated_callback_refs = set() self._publish_instance_changed_callback_refs = set() self._publish_plugin_changed_callback_refs = set() - self._publishing_stopped_callback_refs = set() + self._publish_stopped_callback_refs = set() # State flags to prevent executing method which is already in progress self._resetting_plugins = False @@ -202,7 +203,11 @@ class PublisherController: def add_publish_started_callback(self, callback): ref = weakref.WeakMethod(callback) - self._publishing_started_callback_refs.add(ref) + self._publish_started_callback_refs.add(ref) + + def add_publish_validated_callback(self, callback): + ref = weakref.WeakMethod(callback) + self._publish_validated_callback_refs.add(ref) def add_instance_change_callback(self, callback): ref = weakref.WeakMethod(callback) @@ -214,7 +219,7 @@ class PublisherController: def add_publish_stopped_callback(self, callback): ref = weakref.WeakMethod(callback) - self._publishing_stopped_callback_refs.add(ref) + self._publish_stopped_callback_refs.add(ref) def get_asset_docs(self): return self._asset_docs_cache.get_asset_docs() @@ -412,14 +417,14 @@ class PublisherController: def _start_publish(self): """Start or continue in publishing.""" - self._trigger_callbacks(self._publishing_started_callback_refs) + self._trigger_callbacks(self._publish_started_callback_refs) self._main_thread_processor.start() self._publish_next_process() def _stop_publish(self): """Stop or pause publishing.""" self._main_thread_processor.stop() - self._trigger_callbacks(self._publishing_stopped_callback_refs) + self._trigger_callbacks(self._publish_stopped_callback_refs) def _publish_next_process(self): # Validations of progress before using iterator @@ -452,6 +457,11 @@ class PublisherController: self._publish_validated = ( plugin.order >= self._validation_order ) + # Trigger callbacks when validation stage is passed + if self._publish_validated: + self._trigger_callbacks( + self._publish_validated_callback_refs + ) # Stop if plugin is over validation order and process # should process up to validation. From 4f846246fffac15fa469329d3ec3a91c3d5cb619 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 19 Aug 2021 15:49:58 +0200 Subject: [PATCH 284/736] added more publish attributes --- openpype/tools/new_publisher/control.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 0a1667c03e..ae7789ad56 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -201,6 +201,7 @@ class PublisherController: ref = weakref.WeakMethod(callback) self._plugins_refresh_callback_refs.add(ref) + # --- Publish specific callbacks --- def add_publish_started_callback(self, callback): ref = weakref.WeakMethod(callback) self._publish_started_callback_refs.add(ref) @@ -382,6 +383,18 @@ class PublisherController: def publish_has_finished(self): return self._publish_finished + @property + def publish_is_running(self): + return self._publish_is_running + + @property + def publish_has_validated(self): + return self._publish_validated + + @property + def publish_has_crashed(self): + return bool(self._publish_error) + def get_publish_crash_error(self): return self._publish_error @@ -392,6 +405,7 @@ class PublisherController: return self._publish_validation_errors def _reset_publish(self): + self._publish_is_running = False self._publish_validated = False self._publish_up_validation = False self._publish_finished = False @@ -417,12 +431,14 @@ class PublisherController: def _start_publish(self): """Start or continue in publishing.""" + self._publish_is_running = True self._trigger_callbacks(self._publish_started_callback_refs) self._main_thread_processor.start() self._publish_next_process() def _stop_publish(self): """Stop or pause publishing.""" + self._publish_is_running = False self._main_thread_processor.stop() self._trigger_callbacks(self._publish_stopped_callback_refs) From e11e6c7c1575c5e0785056ee969b1bd2507c2ab5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 19 Aug 2021 15:50:18 +0200 Subject: [PATCH 285/736] stop publish has wrapper function --- openpype/tools/new_publisher/control.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index ae7789ad56..6b29987854 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -442,6 +442,10 @@ class PublisherController: self._main_thread_processor.stop() self._trigger_callbacks(self._publish_stopped_callback_refs) + def stop_publish(self): + if self._publish_is_running: + self._stop_publish() + def _publish_next_process(self): # Validations of progress before using iterator # - same conditions may be inside iterator but they may be used @@ -453,12 +457,12 @@ class PublisherController: self._publish_validated and self._publish_validation_errors ): - item = MainThreadItem(self._stop_publish) + item = MainThreadItem(self.stop_publish) # Any unexpected error happened # - everything should stop elif self._publish_error: - item = MainThreadItem(self._stop_publish) + item = MainThreadItem(self.stop_publish) # Everything is ok so try to get new processing item else: @@ -482,14 +486,14 @@ class PublisherController: # Stop if plugin is over validation order and process # should process up to validation. if self._publish_up_validation and self._publish_validated: - yield MainThreadItem(self._stop_publish) + yield MainThreadItem(self.stop_publish) # Stop if validation is over and validation errors happened if ( self._publish_validated and self._publish_validation_errors ): - yield MainThreadItem(self._stop_publish) + yield MainThreadItem(self.stop_publish) # Trigger callback that new plugin is going to be processed self._trigger_callbacks( @@ -532,7 +536,7 @@ class PublisherController: self._process_and_continue, plugin, None ) self._publish_finished = True - yield MainThreadItem(self._stop_publish) + yield MainThreadItem(self.stop_publish) def _extract_log_items(self, result): output = [] From 75f5b4011de986d29fcbfff7f0e9477ad3f96d12 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 19 Aug 2021 15:50:30 +0200 Subject: [PATCH 286/736] added reset publish method --- openpype/tools/new_publisher/control.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 6b29987854..d9dfb529b3 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -446,6 +446,10 @@ class PublisherController: if self._publish_is_running: self._stop_publish() + def reset_publish(self): + self.stop_publish() + self._reset_publish() + def _publish_next_process(self): # Validations of progress before using iterator # - same conditions may be inside iterator but they may be used From 17d4c3532600961848be346d47358e999d8cf2e7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 19 Aug 2021 15:51:25 +0200 Subject: [PATCH 287/736] added all buttons to main window --- openpype/tools/new_publisher/window.py | 34 +++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 9e2a267394..1697182b1f 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -34,7 +34,8 @@ from widgets import ( SubsetAttributesWidget, InstanceCardView, InstanceListView, - CreateDialog + CreateDialog, + get_icon ) @@ -120,12 +121,28 @@ class PublisherWindow(QtWidgets.QWidget): # Footer message_input = QtWidgets.QLineEdit(main_frame) - validate_btn = QtWidgets.QPushButton("Validate", main_frame) - publish_btn = QtWidgets.QPushButton("Publish", main_frame) + + refresh_btn = QtWidgets.QPushButton(main_frame) + refresh_btn.setIcon(get_icon("refresh")) + refresh_btn.setToolTip("Refresh publishing") + + stop_btn = QtWidgets.QPushButton(main_frame) + stop_btn.setIcon(get_icon("stop")) + stop_btn.setToolTip("Stop/Pause publishing") + + validate_btn = QtWidgets.QPushButton(main_frame) + validate_btn.setIcon(get_icon("validate")) + validate_btn.setToolTip("Validate") + + publish_btn = QtWidgets.QPushButton(main_frame) + publish_btn.setIcon(get_icon("play")) + publish_btn.setToolTip("Publish") footer_layout = QtWidgets.QHBoxLayout() footer_layout.setContentsMargins(0, 0, 0, 0) footer_layout.addWidget(message_input, 1) + footer_layout.addWidget(refresh_btn, 0) + footer_layout.addWidget(stop_btn, 0) footer_layout.addWidget(validate_btn, 0) footer_layout.addWidget(publish_btn, 0) @@ -150,6 +167,8 @@ class PublisherWindow(QtWidgets.QWidget): save_btn.clicked.connect(self._on_save_clicked) change_view_btn.clicked.connect(self._on_change_view_clicked) + stop_btn.clicked.connect(self._on_stop_clicked) + refresh_btn.clicked.connect(self._on_refresh_clicked) validate_btn.clicked.connect(self._on_validate_clicked) publish_btn.clicked.connect(self._on_publish_clicked) @@ -175,6 +194,9 @@ class PublisherWindow(QtWidgets.QWidget): self.subset_attributes_widget = subset_attributes_widget self.message_input = message_input + + self.stop_btn = stop_btn + self.refresh_btn = refresh_btn self.validate_btn = validate_btn self.publish_btn = publish_btn @@ -298,6 +320,12 @@ class PublisherWindow(QtWidgets.QWidget): self.overlay_frame.setVisible(visible) + def _on_refresh_clicked(self): + self.controller.stop_publish() + + def _on_stop_clicked(self): + self.controller.stop_publish() + def _on_validate_clicked(self): self._set_overlay_visibility(True) self.controller.validate() From 590c5c314d4697fb41469f50316f5b03a749c609 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 19 Aug 2021 15:51:37 +0200 Subject: [PATCH 288/736] added and implemented controller callbacks --- openpype/tools/new_publisher/window.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 1697182b1f..c4b383ed3e 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -180,6 +180,8 @@ class PublisherWindow(QtWidgets.QWidget): ) overlay_frame.hide_requested.connect(self._on_overlay_hide_request) + controller.add_publish_started_callback(self._on_publish_start) + controller.add_publish_validated_callback(self._on_publish_validated) controller.add_publish_stopped_callback(self._on_publish_stop) self.main_frame = main_frame @@ -365,8 +367,28 @@ class PublisherWindow(QtWidgets.QWidget): self.subset_attributes_widget.set_current_instances(instances) + def _on_publish_start(self): + self.refresh_btn.setEnabled(False) + self.stop_btn.setEnabled(True) + self.validate_btn.setEnabled(False) + self.publish_btn.setEnabled(False) + + def _on_publish_validated(self): + self.validate_btn.setEnabled(False) + def _on_publish_stop(self): - pass + self.refresh_btn.setEnabled(True) + self.stop_btn.setEnabled(False) + validate_enabled = self.controller.publish_has_crashed + publish_enabled = self.controller.publish_has_crashed + if validate_enabled: + + self.validate_btn.setEnabled( + not self.controller.publish_has_validated + ) + self.publish_btn.setEnabled( + not self.controller.publish_has_finished + ) def _on_overlay_hide_request(self): self._set_overlay_visibility(False) From 114423e5f5d340d6e1d1cd0d4812cf510e58b98d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 19 Aug 2021 16:03:28 +0200 Subject: [PATCH 289/736] added same controller callbacks to overlay widget --- .../tools/new_publisher/widgets/widgets.py | 33 +++++++++++++++++-- openpype/tools/new_publisher/window.py | 28 +++++++++------- 2 files changed, 47 insertions(+), 14 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index ef4d9dc958..5b65316109 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -997,11 +997,13 @@ class PublishOverlayFrame(QtWidgets.QFrame): hide_btn.clicked.connect(self.hide_requested) copy_log_btn.clicked.connect(self._on_copy_log) + controller.add_publish_refresh_callback(self._on_publish_reset) + controller.add_publish_started_callback(self._on_publish_start) + controller.add_publish_validated_callback(self._on_publish_validated) + controller.add_publish_stopped_callback(self._on_publish_stop) + controller.add_instance_change_callback(self._on_instance_change) controller.add_plugin_change_callback(self._on_plugin_change) - controller.add_plugins_refresh_callback(self._on_publish_reset) - controller.add_publish_started_callback(self._on_publish_start) - controller.add_publish_stopped_callback(self._on_publish_stop) self.controller = controller @@ -1035,10 +1037,23 @@ class PublishOverlayFrame(QtWidgets.QFrame): self.message_label.setText("") self.copy_log_btn.setVisible(False) + self.refresh_btn.setEnabled(True) + self.stop_btn.setEnabled(False) + self.validate_btn.setEnabled(True) + self.publish_btn.setEnabled(True) + def _on_publish_start(self): self._set_success_property(-1) self.main_label.setText("Publishing...") + self.refresh_btn.setEnabled(False) + self.stop_btn.setEnabled(True) + self.validate_btn.setEnabled(False) + self.publish_btn.setEnabled(False) + + def _on_publish_validated(self): + self.validate_btn.setEnabled(False) + def _on_instance_change(self, context, instance): """Change instance label when instance is going to be processed.""" if instance is None: @@ -1068,6 +1083,18 @@ class PublishOverlayFrame(QtWidgets.QFrame): QtWidgets.QApplication.processEvents() def _on_publish_stop(self): + self.refresh_btn.setEnabled(True) + self.stop_btn.setEnabled(False) + validate_enabled = not self.controller.publish_has_crashed + publish_enabled = not self.controller.publish_has_crashed + if validate_enabled: + validate_enabled = not self.controller.publish_has_validated + if publish_enabled: + publish_enabled = not self.controller.publish_has_finished + + self.validate_btn.setEnabled(validate_enabled) + self.publish_btn.setEnabled(publish_enabled) + error = self.controller.get_publish_crash_error() if error: self._set_error(error) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index c4b383ed3e..fc38e4784c 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -158,8 +158,6 @@ class PublisherWindow(QtWidgets.QWidget): creator_window = CreateDialog(controller, self) - controller.add_instances_refresh_callback(self._on_instances_refresh) - reset_btn.clicked.connect(self._on_reset_clicked) create_btn.clicked.connect(self._on_create_clicked) @@ -180,6 +178,9 @@ class PublisherWindow(QtWidgets.QWidget): ) overlay_frame.hide_requested.connect(self._on_overlay_hide_request) + controller.add_instances_refresh_callback(self._on_instances_refresh) + + controller.add_publish_refresh_callback(self._on_publish_reset) controller.add_publish_started_callback(self._on_publish_start) controller.add_publish_validated_callback(self._on_publish_validated) controller.add_publish_stopped_callback(self._on_publish_stop) @@ -367,6 +368,12 @@ class PublisherWindow(QtWidgets.QWidget): self.subset_attributes_widget.set_current_instances(instances) + def _on_publish_reset(self): + self.refresh_btn.setEnabled(True) + self.stop_btn.setEnabled(False) + self.validate_btn.setEnabled(True) + self.publish_btn.setEnabled(True) + def _on_publish_start(self): self.refresh_btn.setEnabled(False) self.stop_btn.setEnabled(True) @@ -379,16 +386,15 @@ class PublisherWindow(QtWidgets.QWidget): def _on_publish_stop(self): self.refresh_btn.setEnabled(True) self.stop_btn.setEnabled(False) - validate_enabled = self.controller.publish_has_crashed - publish_enabled = self.controller.publish_has_crashed + validate_enabled = not self.controller.publish_has_crashed + publish_enabled = not self.controller.publish_has_crashed if validate_enabled: - - self.validate_btn.setEnabled( - not self.controller.publish_has_validated - ) - self.publish_btn.setEnabled( - not self.controller.publish_has_finished - ) + validate_enabled = not self.controller.publish_has_validated + if publish_enabled: + publish_enabled = not self.controller.publish_has_finished + + self.validate_btn.setEnabled(validate_enabled) + self.publish_btn.setEnabled(publish_enabled) def _on_overlay_hide_request(self): self._set_overlay_visibility(False) From 321458a9f68aefa5a1b837d13b311cfaf6b7248f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 19 Aug 2021 16:06:54 +0200 Subject: [PATCH 290/736] added callbacks for buttons --- .../tools/new_publisher/widgets/widgets.py | 25 ++++++++++++++++--- openpype/tools/new_publisher/window.py | 2 +- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 5b65316109..22cd1dafb8 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -956,12 +956,12 @@ class PublishOverlayFrame(QtWidgets.QFrame): copy_log_btn = QtWidgets.QPushButton("Copy log", content_widget) copy_log_btn.setVisible(False) - stop_btn = QtWidgets.QPushButton(content_widget) - stop_btn.setIcon(get_icon("stop")) - refresh_btn = QtWidgets.QPushButton(content_widget) refresh_btn.setIcon(get_icon("refresh")) + stop_btn = QtWidgets.QPushButton(content_widget) + stop_btn.setIcon(get_icon("stop")) + validate_btn = QtWidgets.QPushButton(content_widget) validate_btn.setIcon(get_icon("validate")) @@ -997,6 +997,11 @@ class PublishOverlayFrame(QtWidgets.QFrame): hide_btn.clicked.connect(self.hide_requested) copy_log_btn.clicked.connect(self._on_copy_log) + refresh_btn.clicked.connect(self._on_refresh_clicked) + stop_btn.clicked.connect(self._on_stop_clicked) + validate_btn.clicked.connect(self._on_validate_clicked) + publish_btn.clicked.connect(self._on_publish_clicked) + controller.add_publish_refresh_callback(self._on_publish_reset) controller.add_publish_started_callback(self._on_publish_start) controller.add_publish_validated_callback(self._on_publish_validated) @@ -1018,8 +1023,8 @@ class PublishOverlayFrame(QtWidgets.QFrame): self.progress_widget = progress_widget self.copy_log_btn = copy_log_btn - self.stop_btn = stop_btn self.refresh_btn = refresh_btn + self.stop_btn = stop_btn self.validate_btn = validate_btn self.publish_btn = publish_btn @@ -1142,3 +1147,15 @@ class PublishOverlayFrame(QtWidgets.QFrame): QtWidgets.QApplication.instance().clipboard().setMimeData( mime_data ) + + def _on_refresh_clicked(self): + self.controller.stop_publish() + + def _on_stop_clicked(self): + self.controller.stop_publish() + + def _on_validate_clicked(self): + self.controller.validate() + + def _on_publish_clicked(self): + self.controller.publish() diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index fc38e4784c..cf394a4afc 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -165,8 +165,8 @@ class PublisherWindow(QtWidgets.QWidget): save_btn.clicked.connect(self._on_save_clicked) change_view_btn.clicked.connect(self._on_change_view_clicked) - stop_btn.clicked.connect(self._on_stop_clicked) refresh_btn.clicked.connect(self._on_refresh_clicked) + stop_btn.clicked.connect(self._on_stop_clicked) validate_btn.clicked.connect(self._on_validate_clicked) publish_btn.clicked.connect(self._on_publish_clicked) From 4e87a8934ef0e980f6cdbf57a45112a4d7d7d15a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 19 Aug 2021 16:10:05 +0200 Subject: [PATCH 291/736] fixed publish reset logic --- openpype/tools/new_publisher/control.py | 11 ++++++++- .../tools/new_publisher/widgets/widgets.py | 22 ++++++++--------- openpype/tools/new_publisher/window.py | 24 +++++++++---------- 3 files changed, 33 insertions(+), 24 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index d9dfb529b3..618cfe98ce 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -156,11 +156,14 @@ class PublisherController: # Varianbles where callbacks are stored self._instances_refresh_callback_refs = set() self._plugins_refresh_callback_refs = set() + + self._publish_reset_callback_refs = set() self._publish_started_callback_refs = set() self._publish_validated_callback_refs = set() + self._publish_stopped_callback_refs = set() + self._publish_instance_changed_callback_refs = set() self._publish_plugin_changed_callback_refs = set() - self._publish_stopped_callback_refs = set() # State flags to prevent executing method which is already in progress self._resetting_plugins = False @@ -202,6 +205,10 @@ class PublisherController: self._plugins_refresh_callback_refs.add(ref) # --- Publish specific callbacks --- + def add_publish_reset_callback(self, callback): + ref = weakref.WeakMethod(callback) + self._publish_reset_callback_refs.add(ref) + def add_publish_started_callback(self, callback): ref = weakref.WeakMethod(callback) self._publish_started_callback_refs.add(ref) @@ -417,6 +424,8 @@ class PublisherController: self._publish_validation_errors = [] self._publish_error = None + self._trigger_callbacks(self._publish_reset_callback_refs) + def publish(self): """Run publishing.""" self._publish_up_validation = False diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 22cd1dafb8..4f6b246776 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -956,8 +956,8 @@ class PublishOverlayFrame(QtWidgets.QFrame): copy_log_btn = QtWidgets.QPushButton("Copy log", content_widget) copy_log_btn.setVisible(False) - refresh_btn = QtWidgets.QPushButton(content_widget) - refresh_btn.setIcon(get_icon("refresh")) + reset_btn = QtWidgets.QPushButton(content_widget) + reset_btn.setIcon(get_icon("refresh")) stop_btn = QtWidgets.QPushButton(content_widget) stop_btn.setIcon(get_icon("stop")) @@ -971,7 +971,7 @@ class PublishOverlayFrame(QtWidgets.QFrame): footer_layout = QtWidgets.QHBoxLayout() footer_layout.addWidget(copy_log_btn, 0) footer_layout.addStretch(1) - footer_layout.addWidget(refresh_btn, 0) + footer_layout.addWidget(reset_btn, 0) footer_layout.addWidget(stop_btn, 0) footer_layout.addWidget(validate_btn, 0) footer_layout.addWidget(publish_btn, 0) @@ -997,12 +997,12 @@ class PublishOverlayFrame(QtWidgets.QFrame): hide_btn.clicked.connect(self.hide_requested) copy_log_btn.clicked.connect(self._on_copy_log) - refresh_btn.clicked.connect(self._on_refresh_clicked) + reset_btn.clicked.connect(self._on_reset_clicked) stop_btn.clicked.connect(self._on_stop_clicked) validate_btn.clicked.connect(self._on_validate_clicked) publish_btn.clicked.connect(self._on_publish_clicked) - controller.add_publish_refresh_callback(self._on_publish_reset) + controller.add_publish_reset_callback(self._on_publish_reset) controller.add_publish_started_callback(self._on_publish_start) controller.add_publish_validated_callback(self._on_publish_validated) controller.add_publish_stopped_callback(self._on_publish_stop) @@ -1023,7 +1023,7 @@ class PublishOverlayFrame(QtWidgets.QFrame): self.progress_widget = progress_widget self.copy_log_btn = copy_log_btn - self.refresh_btn = refresh_btn + self.reset_btn = reset_btn self.stop_btn = stop_btn self.validate_btn = validate_btn self.publish_btn = publish_btn @@ -1042,7 +1042,7 @@ class PublishOverlayFrame(QtWidgets.QFrame): self.message_label.setText("") self.copy_log_btn.setVisible(False) - self.refresh_btn.setEnabled(True) + self.reset_btn.setEnabled(True) self.stop_btn.setEnabled(False) self.validate_btn.setEnabled(True) self.publish_btn.setEnabled(True) @@ -1051,7 +1051,7 @@ class PublishOverlayFrame(QtWidgets.QFrame): self._set_success_property(-1) self.main_label.setText("Publishing...") - self.refresh_btn.setEnabled(False) + self.reset_btn.setEnabled(False) self.stop_btn.setEnabled(True) self.validate_btn.setEnabled(False) self.publish_btn.setEnabled(False) @@ -1088,7 +1088,7 @@ class PublishOverlayFrame(QtWidgets.QFrame): QtWidgets.QApplication.processEvents() def _on_publish_stop(self): - self.refresh_btn.setEnabled(True) + self.reset_btn.setEnabled(True) self.stop_btn.setEnabled(False) validate_enabled = not self.controller.publish_has_crashed publish_enabled = not self.controller.publish_has_crashed @@ -1148,8 +1148,8 @@ class PublishOverlayFrame(QtWidgets.QFrame): mime_data ) - def _on_refresh_clicked(self): - self.controller.stop_publish() + def _on_reset_clicked(self): + self.controller.reset_publish() def _on_stop_clicked(self): self.controller.stop_publish() diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index cf394a4afc..9890cb1da0 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -122,9 +122,9 @@ class PublisherWindow(QtWidgets.QWidget): # Footer message_input = QtWidgets.QLineEdit(main_frame) - refresh_btn = QtWidgets.QPushButton(main_frame) - refresh_btn.setIcon(get_icon("refresh")) - refresh_btn.setToolTip("Refresh publishing") + reset_btn = QtWidgets.QPushButton(main_frame) + reset_btn.setIcon(get_icon("refresh")) + reset_btn.setToolTip("Refresh publishing") stop_btn = QtWidgets.QPushButton(main_frame) stop_btn.setIcon(get_icon("stop")) @@ -141,7 +141,7 @@ class PublisherWindow(QtWidgets.QWidget): footer_layout = QtWidgets.QHBoxLayout() footer_layout.setContentsMargins(0, 0, 0, 0) footer_layout.addWidget(message_input, 1) - footer_layout.addWidget(refresh_btn, 0) + footer_layout.addWidget(reset_btn, 0) footer_layout.addWidget(stop_btn, 0) footer_layout.addWidget(validate_btn, 0) footer_layout.addWidget(publish_btn, 0) @@ -165,7 +165,7 @@ class PublisherWindow(QtWidgets.QWidget): save_btn.clicked.connect(self._on_save_clicked) change_view_btn.clicked.connect(self._on_change_view_clicked) - refresh_btn.clicked.connect(self._on_refresh_clicked) + reset_btn.clicked.connect(self._on_reset_clicked) stop_btn.clicked.connect(self._on_stop_clicked) validate_btn.clicked.connect(self._on_validate_clicked) publish_btn.clicked.connect(self._on_publish_clicked) @@ -180,7 +180,7 @@ class PublisherWindow(QtWidgets.QWidget): controller.add_instances_refresh_callback(self._on_instances_refresh) - controller.add_publish_refresh_callback(self._on_publish_reset) + controller.add_publish_reset_callback(self._on_publish_reset) controller.add_publish_started_callback(self._on_publish_start) controller.add_publish_validated_callback(self._on_publish_validated) controller.add_publish_stopped_callback(self._on_publish_stop) @@ -199,7 +199,7 @@ class PublisherWindow(QtWidgets.QWidget): self.message_input = message_input self.stop_btn = stop_btn - self.refresh_btn = refresh_btn + self.reset_btn = reset_btn self.validate_btn = validate_btn self.publish_btn = publish_btn @@ -323,8 +323,8 @@ class PublisherWindow(QtWidgets.QWidget): self.overlay_frame.setVisible(visible) - def _on_refresh_clicked(self): - self.controller.stop_publish() + def _on_reset_clicked(self): + self.controller.reset_publish() def _on_stop_clicked(self): self.controller.stop_publish() @@ -369,13 +369,13 @@ class PublisherWindow(QtWidgets.QWidget): self.subset_attributes_widget.set_current_instances(instances) def _on_publish_reset(self): - self.refresh_btn.setEnabled(True) + self.reset_btn.setEnabled(True) self.stop_btn.setEnabled(False) self.validate_btn.setEnabled(True) self.publish_btn.setEnabled(True) def _on_publish_start(self): - self.refresh_btn.setEnabled(False) + self.reset_btn.setEnabled(False) self.stop_btn.setEnabled(True) self.validate_btn.setEnabled(False) self.publish_btn.setEnabled(False) @@ -384,7 +384,7 @@ class PublisherWindow(QtWidgets.QWidget): self.validate_btn.setEnabled(False) def _on_publish_stop(self): - self.refresh_btn.setEnabled(True) + self.reset_btn.setEnabled(True) self.stop_btn.setEnabled(False) validate_enabled = not self.controller.publish_has_crashed publish_enabled = not self.controller.publish_has_crashed From 6de233346a5ffe8aa741bf7cd2bc3f1e1e68f81e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 19 Aug 2021 16:29:35 +0200 Subject: [PATCH 292/736] added context warning icon --- .../new_publisher/widgets/images/warning.png | Bin 0 -> 9748 bytes .../widgets/instance_views_widgets.py | 53 ++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 openpype/tools/new_publisher/widgets/images/warning.png diff --git a/openpype/tools/new_publisher/widgets/images/warning.png b/openpype/tools/new_publisher/widgets/images/warning.png new file mode 100644 index 0000000000000000000000000000000000000000..76d1e34b6ccf951b166db53cd59d8183cded7586 GIT binary patch literal 9748 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 E3l)7W-~a#s literal 0 HcmV?d00001 diff --git a/openpype/tools/new_publisher/widgets/instance_views_widgets.py b/openpype/tools/new_publisher/widgets/instance_views_widgets.py index 70746062d6..fefec30349 100644 --- a/openpype/tools/new_publisher/widgets/instance_views_widgets.py +++ b/openpype/tools/new_publisher/widgets/instance_views_widgets.py @@ -6,6 +6,53 @@ from constants import ( INSTANCE_ID_ROLE, SORT_VALUE_ROLE ) +from .icons import get_pixmap + + +class ContextWarningLabel(QtWidgets.QLabel): + cached_images_by_size = {} + source_image = None + + def __init__(self, parent): + super(ContextWarningLabel, self).__init__(parent) + self.setToolTip( + "Contain invalid context. Please check details." + ) + + self._image = None + if self.__class__.source_image is None: + self.__class__.source_image = get_pixmap("warning") + + @classmethod + def _get_pixmap_by_size(cls, size): + image = cls.cached_images_by_size.get(size) + if image is not None: + return image + + margins = int(size / 8) + margins_double = margins * 2 + pix = QtGui.QPixmap(size, size) + pix.fill(QtCore.Qt.transparent) + + scaled_image = cls.source_image.scaled( + size - margins_double, size - margins_double, + QtCore.Qt.KeepAspectRatio, + QtCore.Qt.SmoothTransformation + ) + painter = QtGui.QPainter(pix) + painter.setRenderHints( + painter.Antialiasing | painter.SmoothPixmapTransform + ) + painter.drawPixmap(margins, margins, scaled_image) + painter.end() + + return pix + + def showEvent(self, event): + super(ContextWarningLabel, self).showEvent(event) + if self._image is None: + self._image = self._get_pixmap_by_size(self.height()) + self.setPixmap(self._image) class InstanceCardWidget(QtWidgets.QWidget): @@ -21,6 +68,10 @@ class InstanceCardWidget(QtWidgets.QWidget): active_checkbox = QtWidgets.QCheckBox(self) active_checkbox.setChecked(instance.data["active"]) + context_warning = ContextWarningLabel(self) + if instance.has_valid_context: + context_warning.setVisible(False) + expand_btn = QtWidgets.QToolButton(self) expand_btn.setObjectName("ArrowBtn") expand_btn.setArrowType(QtCore.Qt.DownArrow) @@ -34,6 +85,7 @@ class InstanceCardWidget(QtWidgets.QWidget): top_layout = QtWidgets.QHBoxLayout() top_layout.addWidget(subset_name_label) top_layout.addStretch(1) + top_layout.addWidget(context_warning) top_layout.addWidget(active_checkbox) top_layout.addWidget(expand_btn) @@ -51,6 +103,7 @@ class InstanceCardWidget(QtWidgets.QWidget): expand_btn.clicked.connect(self._on_expend_clicked) self.subset_name_label = subset_name_label + self.context_warning = context_warning self.active_checkbox = active_checkbox self.expand_btn = expand_btn From ec3d236d4ae698d707d0df4516160f55e73accba Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 19 Aug 2021 16:37:36 +0200 Subject: [PATCH 293/736] moved overlay widget to separated file --- .../tools/new_publisher/widgets/__init__.py | 5 +- .../new_publisher/widgets/overlay_widget.py | 261 +++++++++++++++++ .../tools/new_publisher/widgets/widgets.py | 263 +----------------- 3 files changed, 266 insertions(+), 263 deletions(-) create mode 100644 openpype/tools/new_publisher/widgets/overlay_widget.py diff --git a/openpype/tools/new_publisher/widgets/__init__.py b/openpype/tools/new_publisher/widgets/__init__.py index 95e0213b1c..759160dcde 100644 --- a/openpype/tools/new_publisher/widgets/__init__.py +++ b/openpype/tools/new_publisher/widgets/__init__.py @@ -4,7 +4,9 @@ from .icons import ( get_icon ) from .widgets import ( - SubsetAttributesWidget, + SubsetAttributesWidget +) +from .overlay_widget import ( PublishOverlayFrame ) from .create_dialog import ( @@ -23,6 +25,7 @@ __all__ = ( "get_icon", "SubsetAttributesWidget", + "PublishOverlayFrame", "CreateDialog", diff --git a/openpype/tools/new_publisher/widgets/overlay_widget.py b/openpype/tools/new_publisher/widgets/overlay_widget.py new file mode 100644 index 0000000000..8f41d6c132 --- /dev/null +++ b/openpype/tools/new_publisher/widgets/overlay_widget.py @@ -0,0 +1,261 @@ +import json + +from Qt import QtWidgets, QtCore + +from openpype.pipeline import KnownPublishError + +from .icons import get_icon + + +class PublishOverlayFrame(QtWidgets.QFrame): + hide_requested = QtCore.Signal() + + def __init__(self, controller, parent): + super(PublishOverlayFrame, self).__init__(parent) + + self.setObjectName("PublishOverlayFrame") + + info_frame = QtWidgets.QFrame(self) + info_frame.setObjectName("PublishOverlay") + + content_widget = QtWidgets.QWidget(info_frame) + content_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + + info_layout = QtWidgets.QVBoxLayout(info_frame) + info_layout.setContentsMargins(0, 0, 0, 0) + info_layout.addWidget(content_widget) + + hide_btn = QtWidgets.QPushButton("Hide", content_widget) + + top_layout = QtWidgets.QHBoxLayout() + top_layout.setContentsMargins(0, 0, 0, 0) + top_layout.addStretch(1) + top_layout.addWidget(hide_btn) + + main_label = QtWidgets.QLabel(content_widget) + main_label.setObjectName("PublishOverlayMainLabel") + main_label.setAlignment(QtCore.Qt.AlignCenter) + + message_label = QtWidgets.QLabel(content_widget) + message_label.setAlignment(QtCore.Qt.AlignCenter) + + instance_label = QtWidgets.QLabel("", content_widget) + instance_label.setAlignment( + QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter + ) + plugin_label = QtWidgets.QLabel("", content_widget) + plugin_label.setAlignment( + QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter + ) + instance_plugin_layout = QtWidgets.QHBoxLayout() + instance_plugin_layout.addWidget(instance_label, 1) + instance_plugin_layout.addWidget(plugin_label, 1) + + progress_widget = QtWidgets.QProgressBar(content_widget) + + copy_log_btn = QtWidgets.QPushButton("Copy log", content_widget) + copy_log_btn.setVisible(False) + + reset_btn = QtWidgets.QPushButton(content_widget) + reset_btn.setIcon(get_icon("refresh")) + + stop_btn = QtWidgets.QPushButton(content_widget) + stop_btn.setIcon(get_icon("stop")) + + validate_btn = QtWidgets.QPushButton(content_widget) + validate_btn.setIcon(get_icon("validate")) + + publish_btn = QtWidgets.QPushButton(content_widget) + publish_btn.setIcon(get_icon("play")) + + footer_layout = QtWidgets.QHBoxLayout() + footer_layout.addWidget(copy_log_btn, 0) + footer_layout.addStretch(1) + footer_layout.addWidget(reset_btn, 0) + footer_layout.addWidget(stop_btn, 0) + footer_layout.addWidget(validate_btn, 0) + footer_layout.addWidget(publish_btn, 0) + + content_layout = QtWidgets.QVBoxLayout(content_widget) + content_layout.setSpacing(5) + content_layout.setAlignment(QtCore.Qt.AlignCenter) + + content_layout.addLayout(top_layout) + content_layout.addWidget(main_label) + content_layout.addStretch(1) + content_layout.addWidget(message_label) + content_layout.addStretch(1) + content_layout.addLayout(instance_plugin_layout) + content_layout.addWidget(progress_widget) + content_layout.addStretch(1) + content_layout.addLayout(footer_layout) + + main_layout = QtWidgets.QVBoxLayout(self) + main_layout.addStretch(1) + main_layout.addWidget(info_frame, 0) + + hide_btn.clicked.connect(self.hide_requested) + copy_log_btn.clicked.connect(self._on_copy_log) + + reset_btn.clicked.connect(self._on_reset_clicked) + stop_btn.clicked.connect(self._on_stop_clicked) + validate_btn.clicked.connect(self._on_validate_clicked) + publish_btn.clicked.connect(self._on_publish_clicked) + + controller.add_publish_reset_callback(self._on_publish_reset) + controller.add_publish_started_callback(self._on_publish_start) + controller.add_publish_validated_callback(self._on_publish_validated) + controller.add_publish_stopped_callback(self._on_publish_stop) + + controller.add_instance_change_callback(self._on_instance_change) + controller.add_plugin_change_callback(self._on_plugin_change) + + self.controller = controller + + self.hide_btn = hide_btn + + self.main_label = main_label + self.message_label = message_label + self.info_frame = info_frame + + self.instance_label = instance_label + self.plugin_label = plugin_label + self.progress_widget = progress_widget + + self.copy_log_btn = copy_log_btn + self.reset_btn = reset_btn + self.stop_btn = stop_btn + self.validate_btn = validate_btn + self.publish_btn = publish_btn + + def set_progress_range(self, max_value): + # TODO implement triggers for this method + self.progress_widget.setMaximum(max_value) + + def set_progress(self, value): + # TODO implement triggers for this method + self.progress_widget.setValue(value) + + def _on_publish_reset(self): + self._set_success_property("") + self.main_label.setText("Hit publish! (if you want)") + self.message_label.setText("") + self.copy_log_btn.setVisible(False) + + self.reset_btn.setEnabled(True) + self.stop_btn.setEnabled(False) + self.validate_btn.setEnabled(True) + self.publish_btn.setEnabled(True) + + def _on_publish_start(self): + self._set_success_property(-1) + self.main_label.setText("Publishing...") + + self.reset_btn.setEnabled(False) + self.stop_btn.setEnabled(True) + self.validate_btn.setEnabled(False) + self.publish_btn.setEnabled(False) + + def _on_publish_validated(self): + self.validate_btn.setEnabled(False) + + def _on_instance_change(self, context, instance): + """Change instance label when instance is going to be processed.""" + if instance is None: + new_name = ( + context.data.get("label") + or getattr(context, "label", None) + or context.data.get("name") + or "Context" + ) + else: + new_name = ( + instance.data.get("label") + or getattr(instance, "label", None) + or instance.data["name"] + ) + + self.instance_label.setText(new_name) + QtWidgets.QApplication.processEvents() + + def _on_plugin_change(self, plugin): + """Change plugin label when instance is going to be processed.""" + plugin_name = plugin.__name__ + if hasattr(plugin, "label") and plugin.label: + plugin_name = plugin.label + + self.plugin_label.setText(plugin_name) + QtWidgets.QApplication.processEvents() + + def _on_publish_stop(self): + self.reset_btn.setEnabled(True) + self.stop_btn.setEnabled(False) + validate_enabled = not self.controller.publish_has_crashed + publish_enabled = not self.controller.publish_has_crashed + if validate_enabled: + validate_enabled = not self.controller.publish_has_validated + if publish_enabled: + publish_enabled = not self.controller.publish_has_finished + + self.validate_btn.setEnabled(validate_enabled) + self.publish_btn.setEnabled(publish_enabled) + + error = self.controller.get_publish_crash_error() + if error: + self._set_error(error) + return + + validation_errors = self.controller.get_validation_errors() + if validation_errors: + self._set_validation_errors(validation_errors) + return + + if self.controller.publish_has_finished: + self._set_finished() + + def _set_error(self, error): + self.main_label.setText("Error happened") + if isinstance(error, KnownPublishError): + msg = str(error) + else: + msg = ( + "Something went wrong. Send report" + " to your supervisor or OpenPype." + ) + self.message_label.setText(msg) + self._set_success_property(0) + self.copy_log_btn.setVisible(True) + + def _set_validation_errors(self, validation_errors): + # TODO implement + pass + + def _set_finished(self): + self.main_label.setText("Finished") + self._set_success_property(1) + + def _set_success_property(self, success): + self.info_frame.setProperty("success", str(success)) + self.info_frame.style().polish(self.info_frame) + + def _on_copy_log(self): + logs = self.controller.get_publish_logs() + logs_string = json.dumps(logs, indent=4) + + mime_data = QtCore.QMimeData() + mime_data.setText(logs_string) + QtWidgets.QApplication.instance().clipboard().setMimeData( + mime_data + ) + + def _on_reset_clicked(self): + self.controller.reset_publish() + + def _on_stop_clicked(self): + self.controller.stop_publish() + + def _on_validate_clicked(self): + self.controller.validate() + + def _on_publish_clicked(self): + self.controller.publish() diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 4f6b246776..af689e5a67 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -1,19 +1,12 @@ import os import copy -import json import collections from Qt import QtWidgets, QtCore, QtGui -from openpype.pipeline import KnownPublishError - from openpype.widgets.attribute_defs import create_widget_for_attr_def - from openpype.tools.flickcharm import FlickCharm -from .icons import ( - get_icon, - get_pixmap -) +from .icons import get_pixmap class AssetsHierarchyModel(QtGui.QStandardItemModel): @@ -905,257 +898,3 @@ class ThumbnailWidget(QtWidgets.QWidget): self.thumbnail_label = thumbnail_label self.default_pix = default_pix self.current_pix = None - - -class PublishOverlayFrame(QtWidgets.QFrame): - hide_requested = QtCore.Signal() - - def __init__(self, controller, parent): - super(PublishOverlayFrame, self).__init__(parent) - - self.setObjectName("PublishOverlayFrame") - - info_frame = QtWidgets.QFrame(self) - info_frame.setObjectName("PublishOverlay") - - content_widget = QtWidgets.QWidget(info_frame) - content_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) - - info_layout = QtWidgets.QVBoxLayout(info_frame) - info_layout.setContentsMargins(0, 0, 0, 0) - info_layout.addWidget(content_widget) - - hide_btn = QtWidgets.QPushButton("Hide", content_widget) - - top_layout = QtWidgets.QHBoxLayout() - top_layout.setContentsMargins(0, 0, 0, 0) - top_layout.addStretch(1) - top_layout.addWidget(hide_btn) - - main_label = QtWidgets.QLabel(content_widget) - main_label.setObjectName("PublishOverlayMainLabel") - main_label.setAlignment(QtCore.Qt.AlignCenter) - - message_label = QtWidgets.QLabel(content_widget) - message_label.setAlignment(QtCore.Qt.AlignCenter) - - instance_label = QtWidgets.QLabel("", content_widget) - instance_label.setAlignment( - QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter - ) - plugin_label = QtWidgets.QLabel("", content_widget) - plugin_label.setAlignment( - QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter - ) - instance_plugin_layout = QtWidgets.QHBoxLayout() - instance_plugin_layout.addWidget(instance_label, 1) - instance_plugin_layout.addWidget(plugin_label, 1) - - progress_widget = QtWidgets.QProgressBar(content_widget) - - copy_log_btn = QtWidgets.QPushButton("Copy log", content_widget) - copy_log_btn.setVisible(False) - - reset_btn = QtWidgets.QPushButton(content_widget) - reset_btn.setIcon(get_icon("refresh")) - - stop_btn = QtWidgets.QPushButton(content_widget) - stop_btn.setIcon(get_icon("stop")) - - validate_btn = QtWidgets.QPushButton(content_widget) - validate_btn.setIcon(get_icon("validate")) - - publish_btn = QtWidgets.QPushButton(content_widget) - publish_btn.setIcon(get_icon("play")) - - footer_layout = QtWidgets.QHBoxLayout() - footer_layout.addWidget(copy_log_btn, 0) - footer_layout.addStretch(1) - footer_layout.addWidget(reset_btn, 0) - footer_layout.addWidget(stop_btn, 0) - footer_layout.addWidget(validate_btn, 0) - footer_layout.addWidget(publish_btn, 0) - - content_layout = QtWidgets.QVBoxLayout(content_widget) - content_layout.setSpacing(5) - content_layout.setAlignment(QtCore.Qt.AlignCenter) - - content_layout.addLayout(top_layout) - content_layout.addWidget(main_label) - content_layout.addStretch(1) - content_layout.addWidget(message_label) - content_layout.addStretch(1) - content_layout.addLayout(instance_plugin_layout) - content_layout.addWidget(progress_widget) - content_layout.addStretch(1) - content_layout.addLayout(footer_layout) - - main_layout = QtWidgets.QVBoxLayout(self) - main_layout.addStretch(1) - main_layout.addWidget(info_frame, 0) - - hide_btn.clicked.connect(self.hide_requested) - copy_log_btn.clicked.connect(self._on_copy_log) - - reset_btn.clicked.connect(self._on_reset_clicked) - stop_btn.clicked.connect(self._on_stop_clicked) - validate_btn.clicked.connect(self._on_validate_clicked) - publish_btn.clicked.connect(self._on_publish_clicked) - - controller.add_publish_reset_callback(self._on_publish_reset) - controller.add_publish_started_callback(self._on_publish_start) - controller.add_publish_validated_callback(self._on_publish_validated) - controller.add_publish_stopped_callback(self._on_publish_stop) - - controller.add_instance_change_callback(self._on_instance_change) - controller.add_plugin_change_callback(self._on_plugin_change) - - self.controller = controller - - self.hide_btn = hide_btn - - self.main_label = main_label - self.message_label = message_label - self.info_frame = info_frame - - self.instance_label = instance_label - self.plugin_label = plugin_label - self.progress_widget = progress_widget - - self.copy_log_btn = copy_log_btn - self.reset_btn = reset_btn - self.stop_btn = stop_btn - self.validate_btn = validate_btn - self.publish_btn = publish_btn - - def set_progress_range(self, max_value): - # TODO implement triggers for this method - self.progress_widget.setMaximum(max_value) - - def set_progress(self, value): - # TODO implement triggers for this method - self.progress_widget.setValue(value) - - def _on_publish_reset(self): - self._set_success_property("") - self.main_label.setText("Hit publish! (if you want)") - self.message_label.setText("") - self.copy_log_btn.setVisible(False) - - self.reset_btn.setEnabled(True) - self.stop_btn.setEnabled(False) - self.validate_btn.setEnabled(True) - self.publish_btn.setEnabled(True) - - def _on_publish_start(self): - self._set_success_property(-1) - self.main_label.setText("Publishing...") - - self.reset_btn.setEnabled(False) - self.stop_btn.setEnabled(True) - self.validate_btn.setEnabled(False) - self.publish_btn.setEnabled(False) - - def _on_publish_validated(self): - self.validate_btn.setEnabled(False) - - def _on_instance_change(self, context, instance): - """Change instance label when instance is going to be processed.""" - if instance is None: - new_name = ( - context.data.get("label") - or getattr(context, "label", None) - or context.data.get("name") - or "Context" - ) - else: - new_name = ( - instance.data.get("label") - or getattr(instance, "label", None) - or instance.data["name"] - ) - - self.instance_label.setText(new_name) - QtWidgets.QApplication.processEvents() - - def _on_plugin_change(self, plugin): - """Change plugin label when instance is going to be processed.""" - plugin_name = plugin.__name__ - if hasattr(plugin, "label") and plugin.label: - plugin_name = plugin.label - - self.plugin_label.setText(plugin_name) - QtWidgets.QApplication.processEvents() - - def _on_publish_stop(self): - self.reset_btn.setEnabled(True) - self.stop_btn.setEnabled(False) - validate_enabled = not self.controller.publish_has_crashed - publish_enabled = not self.controller.publish_has_crashed - if validate_enabled: - validate_enabled = not self.controller.publish_has_validated - if publish_enabled: - publish_enabled = not self.controller.publish_has_finished - - self.validate_btn.setEnabled(validate_enabled) - self.publish_btn.setEnabled(publish_enabled) - - error = self.controller.get_publish_crash_error() - if error: - self._set_error(error) - return - - validation_errors = self.controller.get_validation_errors() - if validation_errors: - self._set_validation_errors(validation_errors) - return - - if self.controller.publish_has_finished: - self._set_finished() - - def _set_error(self, error): - self.main_label.setText("Error happened") - if isinstance(error, KnownPublishError): - msg = str(error) - else: - msg = ( - "Something went wrong. Send report" - " to your supervisor or OpenPype." - ) - self.message_label.setText(msg) - self._set_success_property(0) - self.copy_log_btn.setVisible(True) - - def _set_validation_errors(self, validation_errors): - # TODO implement - pass - - def _set_finished(self): - self.main_label.setText("Finished") - self._set_success_property(1) - - def _set_success_property(self, success): - self.info_frame.setProperty("success", str(success)) - self.info_frame.style().polish(self.info_frame) - - def _on_copy_log(self): - logs = self.controller.get_publish_logs() - logs_string = json.dumps(logs, indent=4) - - mime_data = QtCore.QMimeData() - mime_data.setText(logs_string) - QtWidgets.QApplication.instance().clipboard().setMimeData( - mime_data - ) - - def _on_reset_clicked(self): - self.controller.reset_publish() - - def _on_stop_clicked(self): - self.controller.stop_publish() - - def _on_validate_clicked(self): - self.controller.validate() - - def _on_publish_clicked(self): - self.controller.publish() From faaab65a461ad7b49443d6f253e442c0355e757a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 19 Aug 2021 16:40:09 +0200 Subject: [PATCH 294/736] fixed debug reset button --- openpype/tools/new_publisher/window.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 9890cb1da0..db14bf7173 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -64,12 +64,12 @@ class PublisherWindow(QtWidgets.QWidget): # Header header_widget = QtWidgets.QWidget(main_frame) context_label = QtWidgets.QLabel(header_widget) - reset_btn = QtWidgets.QPushButton("Reset", header_widget) + debug_reset_btn = QtWidgets.QPushButton("Reset", header_widget) header_layout = QtWidgets.QHBoxLayout(header_widget) header_layout.setContentsMargins(0, 0, 0, 0) header_layout.addWidget(context_label, 1) - header_layout.addWidget(reset_btn, 0) + header_layout.addWidget(debug_reset_btn, 0) # Content # Subset widget @@ -158,7 +158,7 @@ class PublisherWindow(QtWidgets.QWidget): creator_window = CreateDialog(controller, self) - reset_btn.clicked.connect(self._on_reset_clicked) + debug_reset_btn.clicked.connect(self._on_debug_reset_clicked) create_btn.clicked.connect(self._on_create_clicked) delete_btn.clicked.connect(self._on_delete_clicked) @@ -279,7 +279,7 @@ class PublisherWindow(QtWidgets.QWidget): selected_instances = old_view.get_selected_instances() new_view.set_selected_instances(selected_instances) - def _on_reset_clicked(self): + def _on_debug_reset_clicked(self): self.reset() def _on_create_clicked(self): From 786c60c83f34f04196ac73492bae46645d24f689 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 19 Aug 2021 17:05:22 +0200 Subject: [PATCH 295/736] added collector to testhost --- .../plugins/publish/collect_context.py | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 openpype/hosts/testhost/plugins/publish/collect_context.py diff --git a/openpype/hosts/testhost/plugins/publish/collect_context.py b/openpype/hosts/testhost/plugins/publish/collect_context.py new file mode 100644 index 0000000000..67d81073ea --- /dev/null +++ b/openpype/hosts/testhost/plugins/publish/collect_context.py @@ -0,0 +1,64 @@ +""" +Requires: + environment -> SAPUBLISH_INPATH + environment -> SAPUBLISH_OUTPATH + +Provides: + context -> returnJsonPath (str) + context -> project + context -> asset + instance -> destination_list (list) + instance -> representations (list) + instance -> source (list) + instance -> representations +""" + +import os +import json +import copy + +import pyblish.api +from avalon import io + +from openpype.hosts.testhost import api + + +class CollectContextDataTestHost(pyblish.api.ContextPlugin): + """ + Collecting temp json data sent from a host context + and path for returning json data back to hostself. + """ + + label = "Collect Context - Test Host" + order = pyblish.api.CollectorOrder - 0.49 + hosts = ["testhost"] + + def process(self, context): + # get json paths from os and load them + io.install() + + for instance_data in api.list_instances(): + # create instance + self.create_instance(context, instance_data) + + def create_instance(self, context, in_data): + subset = in_data["subset"] + # If instance data already contain families then use it + instance_families = in_data.get("families") or [] + + instance = context.create_instance(subset) + instance.data.update( + { + "subset": subset, + "asset": in_data["asset"], + "label": subset, + "name": subset, + "family": in_data["family"], + "families": instance_families + } + ) + self.log.info("collected instance: {}".format(instance.data)) + self.log.info("parsing data: {}".format(in_data)) + + instance.data["representations"] = list() + instance.data["source"] = "testhost" From 9f23cd02306b50c2ccc5afd462034b3ade7c6f7c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 19 Aug 2021 17:19:18 +0200 Subject: [PATCH 296/736] kept only one reset button --- .../tools/new_publisher/widgets/overlay_widget.py | 2 +- openpype/tools/new_publisher/window.py | 11 ++--------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/overlay_widget.py b/openpype/tools/new_publisher/widgets/overlay_widget.py index 8f41d6c132..aab8d714cc 100644 --- a/openpype/tools/new_publisher/widgets/overlay_widget.py +++ b/openpype/tools/new_publisher/widgets/overlay_widget.py @@ -249,7 +249,7 @@ class PublishOverlayFrame(QtWidgets.QFrame): ) def _on_reset_clicked(self): - self.controller.reset_publish() + self.controller.reset() def _on_stop_clicked(self): self.controller.stop_publish() diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index db14bf7173..6a02f78d62 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -64,12 +64,10 @@ class PublisherWindow(QtWidgets.QWidget): # Header header_widget = QtWidgets.QWidget(main_frame) context_label = QtWidgets.QLabel(header_widget) - debug_reset_btn = QtWidgets.QPushButton("Reset", header_widget) header_layout = QtWidgets.QHBoxLayout(header_widget) header_layout.setContentsMargins(0, 0, 0, 0) header_layout.addWidget(context_label, 1) - header_layout.addWidget(debug_reset_btn, 0) # Content # Subset widget @@ -158,8 +156,6 @@ class PublisherWindow(QtWidgets.QWidget): creator_window = CreateDialog(controller, self) - debug_reset_btn.clicked.connect(self._on_debug_reset_clicked) - create_btn.clicked.connect(self._on_create_clicked) delete_btn.clicked.connect(self._on_delete_clicked) save_btn.clicked.connect(self._on_save_clicked) @@ -236,7 +232,7 @@ class PublisherWindow(QtWidgets.QWidget): super(PublisherWindow, self).showEvent(event) if self._first_show: self._first_show = False - self.reset() + self.controller.reset() def reset(self): self.controller.reset() @@ -279,9 +275,6 @@ class PublisherWindow(QtWidgets.QWidget): selected_instances = old_view.get_selected_instances() new_view.set_selected_instances(selected_instances) - def _on_debug_reset_clicked(self): - self.reset() - def _on_create_clicked(self): self.creator_window.show() @@ -324,7 +317,7 @@ class PublisherWindow(QtWidgets.QWidget): self.overlay_frame.setVisible(visible) def _on_reset_clicked(self): - self.controller.reset_publish() + self.controller.reset() def _on_stop_clicked(self): self.controller.stop_publish() From 0b3eba2a94ab647dfa65db15973dcfbe46dea7df Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 19 Aug 2021 17:20:19 +0200 Subject: [PATCH 297/736] fixed resetting in controller --- openpype/tools/new_publisher/control.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 618cfe98ce..7e53f693ca 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -134,6 +134,8 @@ class PublisherController: self._publish_validation_errors = [] # Any other exception that happened during publishing self._publish_error = None + # Publishing is in progress + self._publish_is_running = False # Publishing is over validation order self._publish_validated = False # Publishing should stop at validation stage @@ -273,6 +275,7 @@ class PublisherController: callbacks.remove(ref) def reset(self): + self.stop_publish() self._reset_plugins() # Publish part must be resetted after plugins self._reset_publish() @@ -455,10 +458,6 @@ class PublisherController: if self._publish_is_running: self._stop_publish() - def reset_publish(self): - self.stop_publish() - self._reset_publish() - def _publish_next_process(self): # Validations of progress before using iterator # - same conditions may be inside iterator but they may be used From c83bad891c95432cdc57eb26ca3270366c19ceca Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 19 Aug 2021 17:21:52 +0200 Subject: [PATCH 298/736] renamed publish log to publish report --- openpype/tools/new_publisher/control.py | 12 ++++++------ .../tools/new_publisher/widgets/overlay_widget.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 7e53f693ca..2fa3e0bb8a 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -128,8 +128,8 @@ class PublisherController: # pyblish.api.Context self._publish_context = None - # Pyblish logs - self._publish_logs = [] + # Pyblish report + self._publish_report = [] # Store exceptions of validation error self._publish_validation_errors = [] # Any other exception that happened during publishing @@ -408,8 +408,8 @@ class PublisherController: def get_publish_crash_error(self): return self._publish_error - def get_publish_logs(self): - return self._publish_logs + def get_publish_report(self): + return self._publish_report def get_validation_errors(self): return self._publish_validation_errors @@ -423,7 +423,7 @@ class PublisherController: self._main_thread_iter = self._publish_iterator() self._publish_context = pyblish.api.Context() - self._publish_logs = [] + self._publish_report = [] self._publish_validation_errors = [] self._publish_error = None @@ -603,7 +603,7 @@ class PublisherController: plugin, self._publish_context, instance ) - self._publish_logs.extend(self._extract_log_items(result)) + self._publish_report.extend(self._extract_log_items(result)) exception = result.get("error") if exception: diff --git a/openpype/tools/new_publisher/widgets/overlay_widget.py b/openpype/tools/new_publisher/widgets/overlay_widget.py index aab8d714cc..0d8292fd80 100644 --- a/openpype/tools/new_publisher/widgets/overlay_widget.py +++ b/openpype/tools/new_publisher/widgets/overlay_widget.py @@ -239,7 +239,7 @@ class PublishOverlayFrame(QtWidgets.QFrame): self.info_frame.style().polish(self.info_frame) def _on_copy_log(self): - logs = self.controller.get_publish_logs() + logs = self.controller.get_publish_report() logs_string = json.dumps(logs, indent=4) mime_data = QtCore.QMimeData() From 8573e2d8c878993fb258dc4a01c2d726b77817cf Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 19 Aug 2021 18:07:59 +0200 Subject: [PATCH 299/736] added new reporting object --- openpype/tools/new_publisher/control.py | 177 +++++++++++++++++------- 1 file changed, 126 insertions(+), 51 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 2fa3e0bb8a..ca7fbb5486 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -116,6 +116,122 @@ class AssetDocsCache: return copy.deepcopy(self._task_names_by_asset_name) +class PublishReport: + def __init__(self, controller): + self.controller = controller + self._plugin_data = [] + self._current_plugin_data = [] + self._all_instances_by_id = {} + self._current_context = None + + def reset(self, context): + self._plugin_data = [] + self._current_plugin_data = {} + self._all_instances_by_id = {} + self._current_context = context + + def add_plugin_iter(self, plugin, context): + for instance in context: + self._all_instances_by_id[instance.id] = instance + + self._current_plugin_data = { + "name": plugin.__name__, + "label": getattr(plugin, "label"), + "order": plugin.order, + "instances_data": [], + "skipped": False + } + + self._plugin_data.append(self._current_plugin_data) + + def set_plugin_skipped(self): + self._current_plugin_data["skipped"] = True + + def add_result(self, result): + instance = result["instance"] + instance_id = None + if instance: + instance_id = instance.id + self._current_plugin_data["instances_data"].append({ + "id": instance_id, + "logs": self._extract_log_items(result) + }) + + def get_report(self): + instances_details = {} + for instance in self._all_instances_by_id.values(): + instances_details[instance.id] = self._extract_instance_data( + instance, instance in self._current_context + ) + + return { + "plugins_data": copy.deepcopy(self._plugin_data), + "instances": instances_details, + "context": self._extract_context_data(self._current_context) + } + + def _extract_context_data(self, context): + return { + "label": context.data.get("label") + } + + def _extract_instance_data(self, instance, exists): + return { + "name": instance.data.get("name"), + "label": instance.data.get("label"), + "family": instance.data["family"], + "families": instance.data.get("families") or [], + "exists": exists + } + + def _extract_log_items(self, result): + output = [] + records = result.get("records") or [] + instance = result["instance"] + instance_id = None + if instance_id: + instance_name = instance.id + + for record in records: + record_exc_info = record.exc_info + if record_exc_info is not None: + record_exc_info = "".join( + traceback.format_exception(*record_exc_info) + ) + + record_item = { + "instance_id": instance_id, + "type": "record", + "msg": record.getMessage(), + "name": record.name, + "lineno": record.lineno, + "levelno": record.levelno, + "levelname": record.levelname, + "threadName": record.threadName, + "filename": record.filename, + "pathname": record.pathname, + "msecs": record.msecs, + "exc_info": record_exc_info + } + output.append(record_item) + + exception = result.get("error") + if exception: + fname, line_no, func, exc = exception.traceback + error_item = { + "type": "error", + "msg": str(exception), + "filename": str(fname), + "lineno": str(line_no), + "func": str(func), + "traceback": exception.formatted_traceback, + "instance": instance_name + } + output.append(error_item) + + return output + + class PublisherController: def __init__(self, dbcon=None, headless=False): self.log = logging.getLogger("PublisherController") @@ -129,7 +245,7 @@ class PublisherController: # pyblish.api.Context self._publish_context = None # Pyblish report - self._publish_report = [] + self._publish_report = PublishReport(self) # Store exceptions of validation error self._publish_validation_errors = [] # Any other exception that happened during publishing @@ -409,7 +525,7 @@ class PublisherController: return self._publish_error def get_publish_report(self): - return self._publish_report + return self._publish_report.get_report() def get_validation_errors(self): return self._publish_validation_errors @@ -423,7 +539,7 @@ class PublisherController: self._main_thread_iter = self._publish_iterator() self._publish_context = pyblish.api.Context() - self._publish_report = [] + self._publish_report.reset(self._publish_context) self._publish_validation_errors = [] self._publish_error = None @@ -484,6 +600,8 @@ class PublisherController: def _publish_iterator(self): for plugin in self.publish_plugins: + self._publish_report.add_plugin_iter(plugin, self._publish_context) + # Check if plugin is over validation order if not self._publish_validated: self._publish_validated = ( @@ -517,6 +635,7 @@ class PublisherController: self._publish_context, plugin ) if not instances: + self._publish_report.set_plugin_skipped() continue for instance in instances: @@ -547,63 +666,19 @@ class PublisherController: yield MainThreadItem( self._process_and_continue, plugin, None ) + else: + self._publish_report.set_plugin_skipped() + self._publish_finished = True yield MainThreadItem(self.stop_publish) - def _extract_log_items(self, result): - output = [] - records = result.get("records") or [] - instance_name = None - instance = result["instance"] - if instance is not None: - instance_name = instance.data["name"] - - for record in records: - record_exc_info = record.exc_info - if record_exc_info is not None: - record_exc_info = "".join( - traceback.format_exception(*record_exc_info) - ) - - record_item = { - "instance": instance_name, - "type": "record", - "msg": record.getMessage(), - "name": record.name, - "lineno": record.lineno, - "levelno": record.levelno, - "levelname": record.levelname, - "threadName": record.threadName, - "filename": record.filename, - "pathname": record.pathname, - "msecs": record.msecs, - "exc_info": record_exc_info - } - output.append(record_item) - - exception = result.get("error") - if exception: - fname, line_no, func, exc = exception.traceback - error_item = { - "type": "error", - "msg": str(exception), - "filename": str(fname), - "lineno": str(line_no), - "func": str(func), - "traceback": exception.formatted_traceback, - "instance": instance_name - } - output.append(error_item) - - return output - def _process_and_continue(self, plugin, instance): # TODO execute plugin result = pyblish.plugin.process( plugin, self._publish_context, instance ) - self._publish_report.extend(self._extract_log_items(result)) + self._publish_report.add_result(result) exception = result.get("error") if exception: From 8aaf54e317f55ca15ba1436567a0f6e86669b850 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 19 Aug 2021 18:14:07 +0200 Subject: [PATCH 300/736] added dummy validator --- .../plugins/publish/validate_with_error.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 openpype/hosts/testhost/plugins/publish/validate_with_error.py diff --git a/openpype/hosts/testhost/plugins/publish/validate_with_error.py b/openpype/hosts/testhost/plugins/publish/validate_with_error.py new file mode 100644 index 0000000000..48519cd996 --- /dev/null +++ b/openpype/hosts/testhost/plugins/publish/validate_with_error.py @@ -0,0 +1,33 @@ +import pyblish.api +from openpype.pipeline import PublishValidationError + + +class ValidateInstanceAssetRepair(pyblish.api.Action): + """Repair the instance asset.""" + + label = "Repair" + icon = "wrench" + on = "failed" + + def process(self, context, plugin): + pass + + +class ValidateInstanceAsset(pyblish.api.InstancePlugin): + """Validate the instance asset is the current selected context asset. + + As it might happen that multiple worfiles are opened, switching + between them would mess with selected context. + In that case outputs might be output under wrong asset! + + Repair action will use Context asset value (from Workfiles or Launcher) + Closing and reopening with Workfiles will refresh Context value. + """ + + label = "Validate With Error" + hosts = ["testhost"] + actions = [ValidateInstanceAssetRepair] + order = pyblish.api.ValidatorOrder + + def process(self, instance): + raise PublishValidationError("Crashing") From 3342bf892f8edb9d08427bfaf0195983e9684ad0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 19 Aug 2021 18:16:41 +0200 Subject: [PATCH 301/736] fixed asset ids in logs reports --- openpype/tools/new_publisher/control.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index ca7fbb5486..a73abd3ab4 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -189,8 +189,8 @@ class PublishReport: records = result.get("records") or [] instance = result["instance"] instance_id = None - if instance_id: - instance_name = instance.id + if instance: + instance_id = instance.id for record in records: record_exc_info = record.exc_info @@ -199,7 +199,7 @@ class PublishReport: traceback.format_exception(*record_exc_info) ) - record_item = { + output.append({ "instance_id": instance_id, "type": "record", "msg": record.getMessage(), @@ -212,22 +212,20 @@ class PublishReport: "pathname": record.pathname, "msecs": record.msecs, "exc_info": record_exc_info - } - output.append(record_item) + }) exception = result.get("error") if exception: fname, line_no, func, exc = exception.traceback - error_item = { + output.append({ + "instance_id": instance_id, "type": "error", "msg": str(exception), "filename": str(fname), "lineno": str(line_no), "func": str(func), - "traceback": exception.formatted_traceback, - "instance": instance_name - } - output.append(error_item) + "traceback": exception.formatted_traceback + }) return output From 1313a7c197b639f75824ab7a33f7d9de3abb48df Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 19 Aug 2021 18:18:14 +0200 Subject: [PATCH 302/736] simplified overlay method --- openpype/tools/new_publisher/window.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 6a02f78d62..53fbff756f 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -311,10 +311,8 @@ class PublisherWindow(QtWidgets.QWidget): self.controller.save_instance_changes() def _set_overlay_visibility(self, visible): - if self.overlay_frame.isVisible() == visible: - return - - self.overlay_frame.setVisible(visible) + if self.overlay_frame.isVisible() != visible: + self.overlay_frame.setVisible(visible) def _on_reset_clicked(self): self.controller.reset() From 82bcc9cb05253fb975e05a0b1e8c346fef853ac2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 20 Aug 2021 12:00:02 +0200 Subject: [PATCH 303/736] publish validation error has more attributes --- openpype/pipeline/publish/publish_plugins.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/publish/publish_plugins.py b/openpype/pipeline/publish/publish_plugins.py index 31bc715b62..bb1a87976b 100644 --- a/openpype/pipeline/publish/publish_plugins.py +++ b/openpype/pipeline/publish/publish_plugins.py @@ -1,5 +1,22 @@ class PublishValidationError(Exception): - pass + """Validation error happened during publishing. + + This exception should be used when validation publishing failed. + + Has additional UI specific attributes that may be handy for artist. + + Args: + message(str): Message of error. Short explanation an issue. + title(str): Title showed in UI. All instances are grouped under + single title. + description(str): Detailed description of an error. It is possible + to use Markdown syntax. + """ + def __init__(self, message, title=None, description=None): + self.message = message + self.title = title or "< Missing title >" + self.description = description or message + super(PublishValidationError, self).__init__(message) class KnownPublishError(Exception): From 64cb28451dfe477507b89885feb2f7cc9b7d9531 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 20 Aug 2021 12:01:05 +0200 Subject: [PATCH 304/736] changed publish overlay states --- openpype/style/style.css | 14 +++++++++----- .../tools/new_publisher/widgets/overlay_widget.py | 4 ++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/openpype/style/style.css b/openpype/style/style.css index 1692644e93..b2b5289ebb 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -671,16 +671,20 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { border-radius: 0.3em; } -#PublishOverlay[success="-1"] { +#PublishOverlay[state="-1"] { background: rgb(194, 226, 236); } -#PublishOverlay[success="1"] { - background: #458056 +#PublishOverlay[state="0"] { + background: #AA5050; } -#PublishOverlay[success="0"] { - background: #AA5050 +#PublishOverlay[state="1"] { + background: #458056; +} + +#PublishOverlay[state="2"] { + background: #ff9900; } #PublishOverlay QLabel { diff --git a/openpype/tools/new_publisher/widgets/overlay_widget.py b/openpype/tools/new_publisher/widgets/overlay_widget.py index 0d8292fd80..1a76ec9279 100644 --- a/openpype/tools/new_publisher/widgets/overlay_widget.py +++ b/openpype/tools/new_publisher/widgets/overlay_widget.py @@ -234,8 +234,8 @@ class PublishOverlayFrame(QtWidgets.QFrame): self.main_label.setText("Finished") self._set_success_property(1) - def _set_success_property(self, success): - self.info_frame.setProperty("success", str(success)) + def _set_success_property(self, state): + self.info_frame.setProperty("state", str(state)) self.info_frame.style().polish(self.info_frame) def _on_copy_log(self): From 6446dca30d7de0ca5a720b64d833f6449268ac61 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 20 Aug 2021 12:01:30 +0200 Subject: [PATCH 305/736] initial version of validation errors widget --- .../widgets/validations_widget.py | 188 ++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 openpype/tools/new_publisher/widgets/validations_widget.py diff --git a/openpype/tools/new_publisher/widgets/validations_widget.py b/openpype/tools/new_publisher/widgets/validations_widget.py new file mode 100644 index 0000000000..fa353c4d2e --- /dev/null +++ b/openpype/tools/new_publisher/widgets/validations_widget.py @@ -0,0 +1,188 @@ +from Qt import QtWidgets, QtCore + + +class ValidationErrorInfo: + def __init__(self, title, detail, actions): + self.title = title + self.detail = detail + self.actions = actions + +global_msg = """ +## Publish plugins + +### Validate Scene Settings + +#### Skip Resolution Check for Tasks + +Set regex pattern(s) to look for in a Task name to skip resolution check against values from DB. + +#### Skip Timeline Check for Tasks + +Set regex pattern(s) to look for in a Task name to skip `frameStart`, `frameEnd` check against values from DB. + +### AfterEffects Submit to Deadline + +* `Use Published scene` - Set to True (green) when Deadline should take published scene as a source instead of uploaded local one. +* `Priority` - priority of job on farm +* `Primary Pool` - here is list of pool fetched from server you can select from. +* `Secondary Pool` +* `Frames Per Task` - number of sequence division between individual tasks (chunks) +making one job on farm. +""" + + +class ValidationErrorTitleWidget(QtWidgets.QFrame): + checked = QtCore.Signal(int) + + def __init__(self, index, error_info, parent): + super(ValidationErrorTitleWidget, self).__init__(parent) + + self.setObjectName("ValidationErrorTitleWidget") + + label_widget = QtWidgets.QLabel(error_info.title, self) + + layout = QtWidgets.QHBoxLayout(self) + layout.addWidget(label_widget) + + self._index = index + self._error_info = error_info + self._checked = False + + self._mouse_pressed = False + + @property + def is_checked(self): + return self._checked + + @property + def index(self): + return self._index + + def set_index(self, index): + self._index = index + + def _change_style_property(self, checked): + value = "1" if checked else "" + self.setProperty("checked", value) + self.style().polish(self) + + def set_checked(self, checked=None): + if checked is None: + checked = not self._checked + + elif checked == self._checked: + return + + self._checked = checked + self._change_style_property(checked) + if checked: + self.checked.emit(self._index) + + def mousePressEvent(self, event): + if event.button() == QtCore.Qt.LeftButton: + self._mouse_pressed = True + super(ValidationErrorTitleWidget, self).mousePressEvent(event) + + def mouseReleaseEvent(self, event): + if self._mouse_pressed: + self._mouse_pressed = False + if self.rect().contains(event.pos()): + self.set_checked(True) + + super(ValidationErrorTitleWidget, self).mouseReleaseEvent(event) + + +class ValidationsWidget(QtWidgets.QWidget): + def __init__(self, parent): + super(ValidationsWidget, self).__init__(parent) + + self.setAttribute(QtCore.Qt.WA_TranslucentBackground) + + errors_widget = QtWidgets.QWidget(self) + errors_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + errors_layout = QtWidgets.QVBoxLayout(errors_widget) + errors_layout.setContentsMargins(0, 0, 0, 0) + + error_details_widget = QtWidgets.QWidget(self) + error_details_input = QtWidgets.QTextEdit(error_details_widget) + error_details_input.setTextInteractionFlags( + QtCore.Qt.TextBrowserInteraction + ) + + error_details_layout = QtWidgets.QVBoxLayout(error_details_widget) + error_details_layout.addWidget(error_details_input) + + actions_widget = QtWidgets.QWidget(self) + actions_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + actions_layout = QtWidgets.QVBoxLayout(actions_widget) + + layout = QtWidgets.QHBoxLayout(self) + layout.setSpacing(0) + layout.setContentsMargins(0, 0, 0, 0) + + layout.addWidget(errors_widget, 0) + layout.addWidget(error_details_widget, 1) + layout.addWidget(actions_widget, 0) + + self._errors_widget = errors_widget + self._errors_layout = errors_layout + self._error_details_input = error_details_input + self._actions_layout = actions_layout + + self._title_widgets = {} + self._error_info = {} + self._previous_checked = None + + self.set_errors([ + { + "title": "Test 1", + "detail": global_msg, + "actions": [] + }, + { + "title": "Test 2", + "detail": "Detaile message about error 2", + "actions": [] + }, + { + "title": "Test 3", + "detail": "Detaile message about error 3", + "actions": [] + } + ]) + + def set_errors(self, errors): + _old_title_widget = self._title_widgets + self._title_widgets = {} + self._error_info = {} + self._previous_checked = None + while self._errors_layout.count(): + self._errors_layout.takeAt(0) + + while self._actions_layout.count(): + self._actions_layout.takeAt(0) + + for idx, error in enumerate(errors): + item = ValidationErrorInfo( + error["title"], error["detail"], error["actions"] + ) + widget = ValidationErrorTitleWidget(idx, item, self) + widget.checked.connect(self._on_checked) + self._errors_layout.addWidget(widget) + self._title_widgets[idx] = widget + self._error_info[idx] = item + + if self._title_widgets: + self._title_widgets[0].set_checked(True) + + self._errors_layout.addStretch(1) + + def _on_checked(self, index): + if self._previous_checked: + if self._previous_checked.index == index: + return + self._previous_checked.set_checked(False) + + self._previous_checked = self._title_widgets[index] + error_item = self._error_info[index] + self._error_details_input.setMarkdown(error_item.detail) From 821ba297699a8470d6737ac425f21b3a92d2122f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 20 Aug 2021 12:01:58 +0200 Subject: [PATCH 306/736] use validation widget in overlay --- openpype/tools/new_publisher/widgets/overlay_widget.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/widgets/overlay_widget.py b/openpype/tools/new_publisher/widgets/overlay_widget.py index 1a76ec9279..01044a126e 100644 --- a/openpype/tools/new_publisher/widgets/overlay_widget.py +++ b/openpype/tools/new_publisher/widgets/overlay_widget.py @@ -5,6 +5,7 @@ from Qt import QtWidgets, QtCore from openpype.pipeline import KnownPublishError from .icons import get_icon +from .validations_widget import ValidationsWidget class PublishOverlayFrame(QtWidgets.QFrame): @@ -18,6 +19,8 @@ class PublishOverlayFrame(QtWidgets.QFrame): info_frame = QtWidgets.QFrame(self) info_frame.setObjectName("PublishOverlay") + validation_errors_widget = ValidationsWidget(self) + content_widget = QtWidgets.QWidget(info_frame) content_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) @@ -91,7 +94,7 @@ class PublishOverlayFrame(QtWidgets.QFrame): content_layout.addLayout(footer_layout) main_layout = QtWidgets.QVBoxLayout(self) - main_layout.addStretch(1) + main_layout.addWidget(validation_errors_widget, 1) main_layout.addWidget(info_frame, 0) hide_btn.clicked.connect(self.hide_requested) From bacf1a41d91cb0f2c70dd399788ffb813c193727 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 20 Aug 2021 12:34:06 +0200 Subject: [PATCH 307/736] changed how data are expected --- .../widgets/validations_widget.py | 67 ++++++++++--------- 1 file changed, 34 insertions(+), 33 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/validations_widget.py b/openpype/tools/new_publisher/widgets/validations_widget.py index fa353c4d2e..55d4160842 100644 --- a/openpype/tools/new_publisher/widgets/validations_widget.py +++ b/openpype/tools/new_publisher/widgets/validations_widget.py @@ -1,12 +1,6 @@ from Qt import QtWidgets, QtCore -class ValidationErrorInfo: - def __init__(self, title, detail, actions): - self.title = title - self.detail = detail - self.actions = actions - global_msg = """ ## Publish plugins @@ -39,7 +33,8 @@ class ValidationErrorTitleWidget(QtWidgets.QFrame): self.setObjectName("ValidationErrorTitleWidget") - label_widget = QtWidgets.QLabel(error_info.title, self) + exception = error_info["exception"] + label_widget = QtWidgets.QLabel(exception.title, self) layout = QtWidgets.QHBoxLayout(self) layout.addWidget(label_widget) @@ -133,50 +128,54 @@ class ValidationsWidget(QtWidgets.QWidget): self._error_info = {} self._previous_checked = None - self.set_errors([ - { - "title": "Test 1", - "detail": global_msg, - "actions": [] - }, - { - "title": "Test 2", - "detail": "Detaile message about error 2", - "actions": [] - }, - { - "title": "Test 3", - "detail": "Detaile message about error 3", - "actions": [] - } - ]) - def set_errors(self, errors): _old_title_widget = self._title_widgets self._title_widgets = {} self._error_info = {} self._previous_checked = None while self._errors_layout.count(): - self._errors_layout.takeAt(0) + item = self._errors_layout.takeAt(0) + widget = item.widget() + if widget: + widget.deleteLater() while self._actions_layout.count(): self._actions_layout.takeAt(0) - for idx, error in enumerate(errors): - item = ValidationErrorInfo( - error["title"], error["detail"], error["actions"] - ) + errors_by_title = [] + for plugin_info in errors: + titles = [] + exception_by_title = {} + instances_by_title = {} + + for error_info in plugin_info["errors"]: + exception = error_info["exception"] + title = exception.title + if title not in titles: + titles.append(title) + instances_by_title[title] = [] + exception_by_title[title] = exception + instances_by_title[title].append(error_info["instance"]) + + for title in titles: + errors_by_title.append({ + "plugin": plugin_info["plugin"], + "exception": exception_by_title[title], + "instances": instances_by_title[title] + }) + + for idx, item in enumerate(errors_by_title): widget = ValidationErrorTitleWidget(idx, item, self) widget.checked.connect(self._on_checked) self._errors_layout.addWidget(widget) self._title_widgets[idx] = widget self._error_info[idx] = item + self._errors_layout.addStretch(1) + if self._title_widgets: self._title_widgets[0].set_checked(True) - self._errors_layout.addStretch(1) - def _on_checked(self, index): if self._previous_checked: if self._previous_checked.index == index: @@ -185,4 +184,6 @@ class ValidationsWidget(QtWidgets.QWidget): self._previous_checked = self._title_widgets[index] error_item = self._error_info[index] - self._error_details_input.setMarkdown(error_item.detail) + self._error_details_input.setMarkdown( + error_item["exception"].description + ) From 659514b3654e9e82e3665c128bf3bc245325b173 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 20 Aug 2021 12:34:29 +0200 Subject: [PATCH 308/736] added better validation error storing --- openpype/tools/new_publisher/control.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index a73abd3ab4..403f48bbe1 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -246,6 +246,8 @@ class PublisherController: self._publish_report = PublishReport(self) # Store exceptions of validation error self._publish_validation_errors = [] + # Currently processing plugin errors + self._publish_current_plugin_validation_errors = None # Any other exception that happened during publishing self._publish_error = None # Publishing is in progress @@ -539,6 +541,7 @@ class PublisherController: self._publish_report.reset(self._publish_context) self._publish_validation_errors = [] + self._publish_current_plugin_validation_errors = None self._publish_error = None self._trigger_callbacks(self._publish_reset_callback_refs) @@ -598,8 +601,12 @@ class PublisherController: def _publish_iterator(self): for plugin in self.publish_plugins: + # Add plugin to publish report self._publish_report.add_plugin_iter(plugin, self._publish_context) + # Reset current plugin validations error + self._publish_current_plugin_validation_errors = None + # Check if plugin is over validation order if not self._publish_validated: self._publish_validated = ( @@ -670,6 +677,21 @@ class PublisherController: self._publish_finished = True yield MainThreadItem(self.stop_publish) + def _add_validation_error(self, result): + if self._publish_current_plugin_validation_errors is None: + self._publish_current_plugin_validation_errors = { + "plugin": result["plugin"], + "errors": [] + } + self._publish_validation_errors.append( + self._publish_current_plugin_validation_errors + ) + + self._publish_current_plugin_validation_errors["errors"].append({ + "exception": result["error"], + "instance": result["instance"] + }) + def _process_and_continue(self, plugin, instance): # TODO execute plugin result = pyblish.plugin.process( @@ -684,7 +706,7 @@ class PublisherController: isinstance(exception, PublishValidationError) and not self._publish_validated ): - self._publish_validation_errors.append(exception) + self._add_validation_error(result) else: self._publish_error = exception From c565e709f0004fdc48e39a63047ca7b23b0e3945 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 20 Aug 2021 12:34:54 +0200 Subject: [PATCH 309/736] implemented set errors on validation crash --- .../tools/new_publisher/widgets/overlay_widget.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/overlay_widget.py b/openpype/tools/new_publisher/widgets/overlay_widget.py index 01044a126e..e7b327904b 100644 --- a/openpype/tools/new_publisher/widgets/overlay_widget.py +++ b/openpype/tools/new_publisher/widgets/overlay_widget.py @@ -117,9 +117,11 @@ class PublishOverlayFrame(QtWidgets.QFrame): self.hide_btn = hide_btn + self.validation_errors_widget = validation_errors_widget + + self.info_frame = info_frame self.main_label = main_label self.message_label = message_label - self.info_frame = info_frame self.instance_label = instance_label self.plugin_label = plugin_label @@ -230,8 +232,11 @@ class PublishOverlayFrame(QtWidgets.QFrame): self.copy_log_btn.setVisible(True) def _set_validation_errors(self, validation_errors): - # TODO implement - pass + self.main_label.setText("Your publish didn't pass studio validations") + self.message_label.setText("Check publish results please") + self._set_success_property(2) + + self.validation_errors_widget.set_errors(validation_errors) def _set_finished(self): self.main_label.setText("Finished") From 26152999d96d1ea2bd052a89aa51a803dec8cf00 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 20 Aug 2021 12:37:16 +0200 Subject: [PATCH 310/736] moved description to testing plugin --- .../plugins/publish/validate_with_error.py | 28 +++++++++++++++++-- .../widgets/validations_widget.py | 24 ---------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/openpype/hosts/testhost/plugins/publish/validate_with_error.py b/openpype/hosts/testhost/plugins/publish/validate_with_error.py index 48519cd996..a830629cd8 100644 --- a/openpype/hosts/testhost/plugins/publish/validate_with_error.py +++ b/openpype/hosts/testhost/plugins/publish/validate_with_error.py @@ -13,7 +13,31 @@ class ValidateInstanceAssetRepair(pyblish.api.Action): pass -class ValidateInstanceAsset(pyblish.api.InstancePlugin): +description = """ +## Publish plugins + +### Validate Scene Settings + +#### Skip Resolution Check for Tasks + +Set regex pattern(s) to look for in a Task name to skip resolution check against values from DB. + +#### Skip Timeline Check for Tasks + +Set regex pattern(s) to look for in a Task name to skip `frameStart`, `frameEnd` check against values from DB. + +### AfterEffects Submit to Deadline + +* `Use Published scene` - Set to True (green) when Deadline should take published scene as a source instead of uploaded local one. +* `Priority` - priority of job on farm +* `Primary Pool` - here is list of pool fetched from server you can select from. +* `Secondary Pool` +* `Frames Per Task` - number of sequence division between individual tasks (chunks) +making one job on farm. +""" + + +class ValidateWithError(pyblish.api.InstancePlugin): """Validate the instance asset is the current selected context asset. As it might happen that multiple worfiles are opened, switching @@ -30,4 +54,4 @@ class ValidateInstanceAsset(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder def process(self, instance): - raise PublishValidationError("Crashing") + raise PublishValidationError("Crashing", "Errored out", description) diff --git a/openpype/tools/new_publisher/widgets/validations_widget.py b/openpype/tools/new_publisher/widgets/validations_widget.py index 55d4160842..8cd9f3ddc0 100644 --- a/openpype/tools/new_publisher/widgets/validations_widget.py +++ b/openpype/tools/new_publisher/widgets/validations_widget.py @@ -1,30 +1,6 @@ from Qt import QtWidgets, QtCore -global_msg = """ -## Publish plugins - -### Validate Scene Settings - -#### Skip Resolution Check for Tasks - -Set regex pattern(s) to look for in a Task name to skip resolution check against values from DB. - -#### Skip Timeline Check for Tasks - -Set regex pattern(s) to look for in a Task name to skip `frameStart`, `frameEnd` check against values from DB. - -### AfterEffects Submit to Deadline - -* `Use Published scene` - Set to True (green) when Deadline should take published scene as a source instead of uploaded local one. -* `Priority` - priority of job on farm -* `Primary Pool` - here is list of pool fetched from server you can select from. -* `Secondary Pool` -* `Frames Per Task` - number of sequence division between individual tasks (chunks) -making one job on farm. -""" - - class ValidationErrorTitleWidget(QtWidgets.QFrame): checked = QtCore.Signal(int) From c24397f7b4ad0909c655815f4ffe24f7d463faeb Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 20 Aug 2021 12:37:33 +0200 Subject: [PATCH 311/736] addded style for validation title widget --- openpype/style/style.css | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openpype/style/style.css b/openpype/style/style.css index b2b5289ebb..5ced6581f1 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -695,6 +695,13 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { font-size: 12pt; } +#ValidationErrorTitleWidget { + background: {color:bg-inputs}; +} +#ValidationErrorTitleWidget[checked="1"] { + background: {color:bg}; +} + #ArrowBtn, #ArrowBtn:disabled, #ArrowBtn:hover { background: transparent; } From 7b9f202c794d22c487e66ea7527d3663a016164a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 20 Aug 2021 12:44:21 +0200 Subject: [PATCH 312/736] hide validation widgets when publishing starts --- .../new_publisher/widgets/overlay_widget.py | 2 ++ .../new_publisher/widgets/validations_widget.py | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/widgets/overlay_widget.py b/openpype/tools/new_publisher/widgets/overlay_widget.py index e7b327904b..f90141df7c 100644 --- a/openpype/tools/new_publisher/widgets/overlay_widget.py +++ b/openpype/tools/new_publisher/widgets/overlay_widget.py @@ -153,6 +153,8 @@ class PublishOverlayFrame(QtWidgets.QFrame): self.publish_btn.setEnabled(True) def _on_publish_start(self): + self.validation_errors_widget.set_errors([]) + self._set_success_property(-1) self.main_label.setText("Publishing...") diff --git a/openpype/tools/new_publisher/widgets/validations_widget.py b/openpype/tools/new_publisher/widgets/validations_widget.py index 8cd9f3ddc0..e15b472b23 100644 --- a/openpype/tools/new_publisher/widgets/validations_widget.py +++ b/openpype/tools/new_publisher/widgets/validations_widget.py @@ -97,14 +97,16 @@ class ValidationsWidget(QtWidgets.QWidget): self._errors_widget = errors_widget self._errors_layout = errors_layout + self._error_details_widget = error_details_widget self._error_details_input = error_details_input + self._actions_widget = actions_widget self._actions_layout = actions_layout self._title_widgets = {} self._error_info = {} self._previous_checked = None - def set_errors(self, errors): + def clear(self): _old_title_widget = self._title_widgets self._title_widgets = {} self._error_info = {} @@ -118,6 +120,18 @@ class ValidationsWidget(QtWidgets.QWidget): while self._actions_layout.count(): self._actions_layout.takeAt(0) + self._error_details_widget.setVisible(False) + self._errors_widget.setVisible(False) + self._actions_widget.setVisible(False) + + def set_errors(self, errors): + self.clear() + if not errors: + return + + self._error_details_widget.setVisible(True) + self._errors_widget.setVisible(True) + errors_by_title = [] for plugin_info in errors: titles = [] From dda59a174db6c68e81ecef9331457079f95a4f06 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 20 Aug 2021 14:04:18 +0200 Subject: [PATCH 313/736] use blur effect --- openpype/tools/new_publisher/widgets/overlay_widget.py | 2 +- openpype/tools/new_publisher/window.py | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/overlay_widget.py b/openpype/tools/new_publisher/widgets/overlay_widget.py index f90141df7c..5678a95401 100644 --- a/openpype/tools/new_publisher/widgets/overlay_widget.py +++ b/openpype/tools/new_publisher/widgets/overlay_widget.py @@ -235,7 +235,7 @@ class PublishOverlayFrame(QtWidgets.QFrame): def _set_validation_errors(self, validation_errors): self.main_label.setText("Your publish didn't pass studio validations") - self.message_label.setText("Check publish results please") + self.message_label.setText("Check results above please") self._set_success_property(2) self.validation_errors_widget.set_errors(validation_errors) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 53fbff756f..742383a918 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -55,8 +55,10 @@ class PublisherWindow(QtWidgets.QWidget): controller = PublisherController() - # TODO Title, Icon, Stylesheet main_frame = QtWidgets.QWidget(self) + blur_effect = QtWidgets.QGraphicsBlurEffect() + blur_effect.setBlurRadius(5) + # Overlay MUST be created after Main to be painted on top of it overlay_frame = PublishOverlayFrame(controller, self) overlay_frame.setVisible(False) @@ -181,6 +183,7 @@ class PublisherWindow(QtWidgets.QWidget): controller.add_publish_validated_callback(self._on_publish_validated) controller.add_publish_stopped_callback(self._on_publish_stop) + self.blur_effect = blur_effect self.main_frame = main_frame self.overlay_frame = overlay_frame @@ -313,6 +316,10 @@ class PublisherWindow(QtWidgets.QWidget): def _set_overlay_visibility(self, visible): if self.overlay_frame.isVisible() != visible: self.overlay_frame.setVisible(visible) + effect = None + if visible: + effect = self.blur_effect + self.main_frame.setGraphicsEffect(effect) def _on_reset_clicked(self): self.controller.reset() From c8b03161b18fef352d5304acafa5ea43a568c9a6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 20 Aug 2021 16:05:46 +0200 Subject: [PATCH 314/736] error detail has UI friendly style --- openpype/style/style.css | 14 ++++++++++++++ .../new_publisher/widgets/validations_widget.py | 1 + 2 files changed, 15 insertions(+) diff --git a/openpype/style/style.css b/openpype/style/style.css index 5ced6581f1..d288eba3e7 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -570,6 +570,20 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { padding-right: 3px; } +#InfoText { + padding: 5px; + background: transparent; + border: 1px solid {color:border}; +} + +#InfoText:hover { + border-color: {color:border}; +} + +#InfoText:focus { + border-color: {color:border}; +} + #TypeEditor, #ToolEditor, #NameEditor, #NumberEditor { background: transparent; border-radius: 0.3em; diff --git a/openpype/tools/new_publisher/widgets/validations_widget.py b/openpype/tools/new_publisher/widgets/validations_widget.py index e15b472b23..076f66ce11 100644 --- a/openpype/tools/new_publisher/widgets/validations_widget.py +++ b/openpype/tools/new_publisher/widgets/validations_widget.py @@ -76,6 +76,7 @@ class ValidationsWidget(QtWidgets.QWidget): error_details_widget = QtWidgets.QWidget(self) error_details_input = QtWidgets.QTextEdit(error_details_widget) + error_details_input.setObjectName("InfoText") error_details_input.setTextInteractionFlags( QtCore.Qt.TextBrowserInteraction ) From ab683ed1b6f2bde9c202436a4eb9fe7d8ccdf934 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 20 Aug 2021 16:33:37 +0200 Subject: [PATCH 315/736] use clear method instead of setting empty list --- openpype/tools/new_publisher/widgets/overlay_widget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/widgets/overlay_widget.py b/openpype/tools/new_publisher/widgets/overlay_widget.py index 5678a95401..8882c85afe 100644 --- a/openpype/tools/new_publisher/widgets/overlay_widget.py +++ b/openpype/tools/new_publisher/widgets/overlay_widget.py @@ -153,7 +153,7 @@ class PublishOverlayFrame(QtWidgets.QFrame): self.publish_btn.setEnabled(True) def _on_publish_start(self): - self.validation_errors_widget.set_errors([]) + self.validation_errors_widget.clear() self._set_success_property(-1) self.main_label.setText("Publishing...") From d7303262b473cf070740ef6a917bfccf805ab6c3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 20 Aug 2021 16:45:29 +0200 Subject: [PATCH 316/736] added base of validation actions --- openpype/tools/new_publisher/control.py | 6 + .../new_publisher/widgets/overlay_widget.py | 2 +- .../widgets/validations_widget.py | 131 +++++++++++++++--- 3 files changed, 119 insertions(+), 20 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 403f48bbe1..d87c4124a7 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -575,6 +575,12 @@ class PublisherController: if self._publish_is_running: self._stop_publish() + def run_action(self, plugin, action): + # TODO handle result + result = pyblish.plugin.process( + plugin, self._publish_context, None, action.id + ) + def _publish_next_process(self): # Validations of progress before using iterator # - same conditions may be inside iterator but they may be used diff --git a/openpype/tools/new_publisher/widgets/overlay_widget.py b/openpype/tools/new_publisher/widgets/overlay_widget.py index 8882c85afe..da2b461e36 100644 --- a/openpype/tools/new_publisher/widgets/overlay_widget.py +++ b/openpype/tools/new_publisher/widgets/overlay_widget.py @@ -19,7 +19,7 @@ class PublishOverlayFrame(QtWidgets.QFrame): info_frame = QtWidgets.QFrame(self) info_frame.setObjectName("PublishOverlay") - validation_errors_widget = ValidationsWidget(self) + validation_errors_widget = ValidationsWidget(controller, self) content_widget = QtWidgets.QWidget(info_frame) content_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) diff --git a/openpype/tools/new_publisher/widgets/validations_widget.py b/openpype/tools/new_publisher/widgets/validations_widget.py index 076f66ce11..7f6b11d2ec 100644 --- a/openpype/tools/new_publisher/widgets/validations_widget.py +++ b/openpype/tools/new_publisher/widgets/validations_widget.py @@ -1,7 +1,30 @@ from Qt import QtWidgets, QtCore -class ValidationErrorTitleWidget(QtWidgets.QFrame): +class _ClickableFrame(QtWidgets.QFrame): + def __init__(self, parent): + super(_ClickableFrame, self).__init__(parent) + + self._mouse_pressed = False + + def _mouse_release_callback(self): + pass + + def mousePressEvent(self, event): + if event.button() == QtCore.Qt.LeftButton: + self._mouse_pressed = True + super(_ClickableFrame, self).mousePressEvent(event) + + def mouseReleaseEvent(self, event): + if self._mouse_pressed: + self._mouse_pressed = False + if self.rect().contains(event.pos()): + self._mouse_release_callback() + + super(_ClickableFrame, self).mouseReleaseEvent(event) + + +class ValidationErrorTitleWidget(_ClickableFrame): checked = QtCore.Signal(int) def __init__(self, index, error_info, parent): @@ -49,28 +72,100 @@ class ValidationErrorTitleWidget(QtWidgets.QFrame): if checked: self.checked.emit(self._index) - def mousePressEvent(self, event): - if event.button() == QtCore.Qt.LeftButton: - self._mouse_pressed = True - super(ValidationErrorTitleWidget, self).mousePressEvent(event) + def _mouse_release_callback(self): + self.set_checked(True) - def mouseReleaseEvent(self, event): - if self._mouse_pressed: - self._mouse_pressed = False - if self.rect().contains(event.pos()): - self.set_checked(True) - super(ValidationErrorTitleWidget, self).mouseReleaseEvent(event) +class ActionWidget(_ClickableFrame): + action_clicked = QtCore.Signal(str) + + def __init__(self, action, parent): + super(ActionWidget, self).__init__(parent) + + self.setObjectName("PublishPluginActionWidget") + + self._action_id = action.id + + action_label = action.label or action.__name__ + # TODO handle icons + # action.icon + + lable_widget = QtWidgets.QLabel(action_label, self) + layout = QtWidgets.QHBoxLayout(self) + layout.addWidget(lable_widget) + + def _mouse_release_callback(self): + self.action_clicked.emit(self._action_id) + + +class ValidateActionsWidget(QtWidgets.QFrame): + def __init__(self, controller, parent): + super(ValidateActionsWidget, self).__init__(parent) + + self.setAttribute(QtCore.Qt.WA_TranslucentBackground) + + content_widget = QtWidgets.QWidget(self) + content_layout = QtWidgets.QVBoxLayout(content_widget) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(content_widget) + + self.controller = controller + self._content_widget = content_widget + self._content_layout = content_layout + self._plugin = None + self._actions_mapping = {} + + def clear(self): + while self._content_layout.count(): + item = self._content_layout.takeAt(0) + widget = item.widget() + if widget: + widget.deleteLater() + self._actions_mapping = {} + + def set_plugin(self, plugin): + self.clear() + self._plugin = plugin + if not plugin: + self.setVisible(False) + return + + actions = getattr(plugin, "actions", []) + for action in actions: + if not action.active: + continue + + if action.on not in ("failed", "all"): + continue + + self._actions_mapping[action.id] = action + + action_widget = ActionWidget(action, self._content_widget) + action_widget.action_clicked.connect(self._on_action_click) + self._content_layout.addWidget(action_widget) + + if self._content_layout.count() > 0: + self.setVisible(True) + self._content_layout.addStretch(1) + else: + self.setVisible(False) + + def _on_action_click(self, action_id): + action = self._actions_mapping[action_id] + self.controller.run_action(self._plugin, action) class ValidationsWidget(QtWidgets.QWidget): - def __init__(self, parent): + def __init__(self, controller, parent): super(ValidationsWidget, self).__init__(parent) self.setAttribute(QtCore.Qt.WA_TranslucentBackground) errors_widget = QtWidgets.QWidget(self) errors_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + errors_widget.setFixedWidth(200) errors_layout = QtWidgets.QVBoxLayout(errors_widget) errors_layout.setContentsMargins(0, 0, 0, 0) @@ -84,9 +179,8 @@ class ValidationsWidget(QtWidgets.QWidget): error_details_layout = QtWidgets.QVBoxLayout(error_details_widget) error_details_layout.addWidget(error_details_input) - actions_widget = QtWidgets.QWidget(self) - actions_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) - actions_layout = QtWidgets.QVBoxLayout(actions_widget) + actions_widget = ValidateActionsWidget(controller, self) + actions_widget.setFixedWidth(140) layout = QtWidgets.QHBoxLayout(self) layout.setSpacing(0) @@ -101,7 +195,6 @@ class ValidationsWidget(QtWidgets.QWidget): self._error_details_widget = error_details_widget self._error_details_input = error_details_input self._actions_widget = actions_widget - self._actions_layout = actions_layout self._title_widgets = {} self._error_info = {} @@ -118,9 +211,6 @@ class ValidationsWidget(QtWidgets.QWidget): if widget: widget.deleteLater() - while self._actions_layout.count(): - self._actions_layout.takeAt(0) - self._error_details_widget.setVisible(False) self._errors_widget.setVisible(False) self._actions_widget.setVisible(False) @@ -174,7 +264,10 @@ class ValidationsWidget(QtWidgets.QWidget): self._previous_checked.set_checked(False) self._previous_checked = self._title_widgets[index] + error_item = self._error_info[index] + self._error_details_input.setMarkdown( error_item["exception"].description ) + self._actions_widget.set_plugin(error_item["plugin"]) From 647d897fdce4623207f74e7252f94c87e97add42 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 20 Aug 2021 16:46:05 +0200 Subject: [PATCH 317/736] removed blur effect --- openpype/tools/new_publisher/window.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 742383a918..6fcf9e9e0c 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -56,9 +56,6 @@ class PublisherWindow(QtWidgets.QWidget): controller = PublisherController() main_frame = QtWidgets.QWidget(self) - blur_effect = QtWidgets.QGraphicsBlurEffect() - blur_effect.setBlurRadius(5) - # Overlay MUST be created after Main to be painted on top of it overlay_frame = PublishOverlayFrame(controller, self) overlay_frame.setVisible(False) @@ -183,7 +180,6 @@ class PublisherWindow(QtWidgets.QWidget): controller.add_publish_validated_callback(self._on_publish_validated) controller.add_publish_stopped_callback(self._on_publish_stop) - self.blur_effect = blur_effect self.main_frame = main_frame self.overlay_frame = overlay_frame @@ -316,10 +312,6 @@ class PublisherWindow(QtWidgets.QWidget): def _set_overlay_visibility(self, visible): if self.overlay_frame.isVisible() != visible: self.overlay_frame.setVisible(visible) - effect = None - if visible: - effect = self.blur_effect - self.main_frame.setGraphicsEffect(effect) def _on_reset_clicked(self): self.controller.reset() From 6491cd6fda01a863ecd013439edd0fb26831a34d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 20 Aug 2021 17:13:18 +0200 Subject: [PATCH 318/736] used stack layout for views --- .../widgets/instance_views_widgets.py | 4 ++ openpype/tools/new_publisher/window.py | 60 +++++++------------ 2 files changed, 24 insertions(+), 40 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/instance_views_widgets.py b/openpype/tools/new_publisher/widgets/instance_views_widgets.py index fefec30349..fde83c3a44 100644 --- a/openpype/tools/new_publisher/widgets/instance_views_widgets.py +++ b/openpype/tools/new_publisher/widgets/instance_views_widgets.py @@ -151,6 +151,10 @@ class InstanceCardWidget(QtWidgets.QWidget): class _AbstractInstanceView(QtWidgets.QWidget): selection_changed = QtCore.Signal() + refreshed = False + + def set_refreshed(self, refreshed): + self.refreshed = refreshed def refresh(self): raise NotImplementedError(( diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 6fcf9e9e0c..af9e7cd6a6 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -49,10 +49,6 @@ class PublisherWindow(QtWidgets.QWidget): self._first_show = True self._refreshing_instances = False - self._view_type_order = ["card", "list"] - self._view_type = self._view_type_order[0] - self._views_refreshed = {} - controller = PublisherController() main_frame = QtWidgets.QWidget(self) @@ -75,8 +71,9 @@ class PublisherWindow(QtWidgets.QWidget): subset_view_cards = InstanceCardView(controller, subset_widget) subset_list_view = InstanceListView(controller, subset_widget) - subset_view_cards.setVisible(False) - subset_list_view.setVisible(False) + subset_views_layout = QtWidgets.QStackedLayout() + subset_views_layout.addWidget(subset_view_cards) + subset_views_layout.addWidget(subset_list_view) # Buttons at the bottom of subset view create_btn = QtWidgets.QPushButton("+", subset_widget) @@ -102,8 +99,7 @@ class PublisherWindow(QtWidgets.QWidget): # Layout of view and buttons subset_view_layout = QtWidgets.QVBoxLayout() subset_view_layout.setContentsMargins(0, 0, 0, 0) - subset_view_layout.addWidget(subset_view_cards, 1) - subset_view_layout.addWidget(subset_list_view, 1) + subset_view_layout.addLayout(subset_views_layout, 1) subset_view_layout.addLayout(subset_view_btns_layout, 0) # Whole subset layout with attributes and details @@ -187,6 +183,7 @@ class PublisherWindow(QtWidgets.QWidget): self.subset_view_cards = subset_view_cards self.subset_list_view = subset_list_view + self.subset_views_layout = subset_views_layout self.delete_btn = delete_btn @@ -202,13 +199,6 @@ class PublisherWindow(QtWidgets.QWidget): self.creator_window = creator_window - self.views_by_type = { - "card": subset_view_cards, - "list": subset_list_view - } - - self._change_view_type(self._view_type) - self.setStyleSheet(style.load_stylesheet()) self.resize(self.default_width, self.default_height) @@ -240,33 +230,20 @@ class PublisherWindow(QtWidgets.QWidget): self.context_label.setText(label) def get_selected_instances(self): - view = self.views_by_type[self._view_type] + view = self.subset_views_layout.currentWidget() return view.get_selected_instances() - def _change_view_type(self, view_type=None): - if view_type is None: - next_type = False - for _view_type in self._view_type_order: - if next_type: - view_type = _view_type - break + def _change_view_type(self): + old_view = self.subset_views_layout.currentWidget() - if _view_type == self._view_type: - next_type = True + idx = self.subset_views_layout.currentIndex() + new_idx = (idx + 1) % self.subset_views_layout.count() + self.subset_views_layout.setCurrentIndex(new_idx) - if view_type is None: - view_type = self._view_type_order[0] - - old_view = self.views_by_type[self._view_type] - old_view.setVisible(False) - - self._view_type = view_type - refreshed = self._views_refreshed.get(view_type, False) - new_view = self.views_by_type[view_type] - new_view.setVisible(True) - if not refreshed: + new_view = self.subset_views_layout.currentWidget() + if not new_view.refreshed: new_view.refresh() - self._views_refreshed[view_type] = True + new_view.set_refreshed(True) else: new_view.refresh_active_state() @@ -333,10 +310,13 @@ class PublisherWindow(QtWidgets.QWidget): self._refreshing_instances = True - view = self.views_by_type[self._view_type] - view.refresh() + for idx in range(self.subset_views_layout.count()): + widget = self.subset_views_layout.widget(idx) + widget.set_refreshed(False) - self._views_refreshed = {self._view_type: True} + view = self.subset_views_layout.currentWidget() + view.refresh() + view.set_refreshed(True) self._refreshing_instances = False From e06f2d42f3687aa3a6aab8d51dcc47c69d5106e8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 20 Aug 2021 17:34:04 +0200 Subject: [PATCH 319/736] added different bg properties for overlay frame --- openpype/style/style.css | 5 ++++- .../tools/new_publisher/widgets/overlay_widget.py | 14 +++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/openpype/style/style.css b/openpype/style/style.css index d288eba3e7..f21ccf3389 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -676,7 +676,10 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { } #PublishOverlayFrame { - background: rgba(0, 0, 0, 127) + background: rgba(0, 0, 0, 127); +} +#PublishOverlayFrame[state="1"] { + background: rgb(22, 25, 29); } #PublishOverlay { diff --git a/openpype/tools/new_publisher/widgets/overlay_widget.py b/openpype/tools/new_publisher/widgets/overlay_widget.py index da2b461e36..c4d53db3d8 100644 --- a/openpype/tools/new_publisher/widgets/overlay_widget.py +++ b/openpype/tools/new_publisher/widgets/overlay_widget.py @@ -142,7 +142,9 @@ class PublishOverlayFrame(QtWidgets.QFrame): self.progress_widget.setValue(value) def _on_publish_reset(self): - self._set_success_property("") + self._set_success_property() + self._change_bg_property() + self.main_label.setText("Hit publish! (if you want)") self.message_label.setText("") self.copy_log_btn.setVisible(False) @@ -156,6 +158,7 @@ class PublishOverlayFrame(QtWidgets.QFrame): self.validation_errors_widget.clear() self._set_success_property(-1) + self._change_bg_property() self.main_label.setText("Publishing...") self.reset_btn.setEnabled(False) @@ -214,6 +217,7 @@ class PublishOverlayFrame(QtWidgets.QFrame): validation_errors = self.controller.get_validation_errors() if validation_errors: + self._change_bg_property(1) self._set_validation_errors(validation_errors) return @@ -244,8 +248,12 @@ class PublishOverlayFrame(QtWidgets.QFrame): self.main_label.setText("Finished") self._set_success_property(1) - def _set_success_property(self, state): - self.info_frame.setProperty("state", str(state)) + def _change_bg_property(self, state=None): + self.setProperty("state", str(state or "")) + self.style().polish(self) + + def _set_success_property(self, state=None): + self.info_frame.setProperty("state", str(state or "")) self.info_frame.style().polish(self.info_frame) def _on_copy_log(self): From 0b071225486f9f9e4a453e2ccea7967c1d1cf0d3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 20 Aug 2021 17:34:48 +0200 Subject: [PATCH 320/736] simplified stacking of widgets with using stack layout --- openpype/tools/new_publisher/window.py | 101 +++++++++++++------------ 1 file changed, 52 insertions(+), 49 deletions(-) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index af9e7cd6a6..90dfddaa25 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -51,39 +51,41 @@ class PublisherWindow(QtWidgets.QWidget): controller = PublisherController() - main_frame = QtWidgets.QWidget(self) - # Overlay MUST be created after Main to be painted on top of it - overlay_frame = PublishOverlayFrame(controller, self) - overlay_frame.setVisible(False) - # Header - header_widget = QtWidgets.QWidget(main_frame) + header_widget = QtWidgets.QWidget(self) context_label = QtWidgets.QLabel(header_widget) + context_label.setObjectName("PublishContextLabel") header_layout = QtWidgets.QHBoxLayout(header_widget) - header_layout.setContentsMargins(0, 0, 0, 0) header_layout.addWidget(context_label, 1) + line_widget = QtWidgets.QWidget(self) + line_widget.setObjectName("Separator") + line_widget.setMinimumHeight(2) + + # Overlay MUST be created after Main to be painted on top of it + publish_frame = PublishOverlayFrame(controller, self) + # Content # Subset widget - subset_widget = QtWidgets.QWidget(main_frame) + subset_frame = QtWidgets.QWidget(self) - subset_view_cards = InstanceCardView(controller, subset_widget) - subset_list_view = InstanceListView(controller, subset_widget) + subset_view_cards = InstanceCardView(controller, subset_frame) + subset_list_view = InstanceListView(controller, subset_frame) subset_views_layout = QtWidgets.QStackedLayout() subset_views_layout.addWidget(subset_view_cards) subset_views_layout.addWidget(subset_list_view) # Buttons at the bottom of subset view - create_btn = QtWidgets.QPushButton("+", subset_widget) - delete_btn = QtWidgets.QPushButton("-", subset_widget) - save_btn = QtWidgets.QPushButton("Save", subset_widget) - change_view_btn = QtWidgets.QPushButton("=", subset_widget) + create_btn = QtWidgets.QPushButton("+", subset_frame) + delete_btn = QtWidgets.QPushButton("-", subset_frame) + save_btn = QtWidgets.QPushButton("Save", subset_frame) + change_view_btn = QtWidgets.QPushButton("=", subset_frame) # Subset details widget subset_attributes_widget = SubsetAttributesWidget( - controller, subset_widget + controller, subset_frame ) # Layout of buttons at the bottom of subset view @@ -103,31 +105,27 @@ class PublisherWindow(QtWidgets.QWidget): subset_view_layout.addLayout(subset_view_btns_layout, 0) # Whole subset layout with attributes and details - subset_layout = QtWidgets.QHBoxLayout(subset_widget) - subset_layout.setContentsMargins(0, 0, 0, 0) - subset_layout.addLayout(subset_view_layout, 0) - subset_layout.addWidget(subset_attributes_widget, 1) - - content_layout = QtWidgets.QVBoxLayout() - content_layout.setContentsMargins(0, 0, 0, 0) - content_layout.addWidget(subset_widget) + subset_content_layout = QtWidgets.QHBoxLayout() + subset_content_layout.setContentsMargins(0, 0, 0, 0) + subset_content_layout.addLayout(subset_view_layout, 0) + subset_content_layout.addWidget(subset_attributes_widget, 1) # Footer - message_input = QtWidgets.QLineEdit(main_frame) + message_input = QtWidgets.QLineEdit(subset_frame) - reset_btn = QtWidgets.QPushButton(main_frame) + reset_btn = QtWidgets.QPushButton(subset_frame) reset_btn.setIcon(get_icon("refresh")) reset_btn.setToolTip("Refresh publishing") - stop_btn = QtWidgets.QPushButton(main_frame) + stop_btn = QtWidgets.QPushButton(subset_frame) stop_btn.setIcon(get_icon("stop")) stop_btn.setToolTip("Stop/Pause publishing") - validate_btn = QtWidgets.QPushButton(main_frame) + validate_btn = QtWidgets.QPushButton(subset_frame) validate_btn.setIcon(get_icon("validate")) validate_btn.setToolTip("Validate") - publish_btn = QtWidgets.QPushButton(main_frame) + publish_btn = QtWidgets.QPushButton(subset_frame) publish_btn.setIcon(get_icon("play")) publish_btn.setToolTip("Publish") @@ -139,15 +137,25 @@ class PublisherWindow(QtWidgets.QWidget): footer_layout.addWidget(validate_btn, 0) footer_layout.addWidget(publish_btn, 0) - # Main frame - main_frame_layout = QtWidgets.QVBoxLayout(main_frame) - main_frame_layout.addWidget(header_widget, 0) - main_frame_layout.addLayout(content_layout, 1) - main_frame_layout.addLayout(footer_layout, 0) + # Subset frame layout + subset_layout = QtWidgets.QVBoxLayout(subset_frame) + subset_layout.addLayout(subset_content_layout, 1) + subset_layout.addLayout(footer_layout, 0) + + content_stacked_layout = QtWidgets.QStackedLayout() + content_stacked_layout.setStackingMode( + QtWidgets.QStackedLayout.StackAll + ) + content_stacked_layout.addWidget(subset_frame) + content_stacked_layout.addWidget(publish_frame) # Add main frame to this window - main_layout = QtWidgets.QHBoxLayout(self) - main_layout.addWidget(main_frame) + main_layout = QtWidgets.QVBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + main_layout.addWidget(header_widget, 0) + main_layout.addWidget(line_widget, 0) + main_layout.addLayout(content_stacked_layout, 1) creator_window = CreateDialog(controller, self) @@ -167,7 +175,7 @@ class PublisherWindow(QtWidgets.QWidget): subset_view_cards.selection_changed.connect( self._on_subset_change ) - overlay_frame.hide_requested.connect(self._on_overlay_hide_request) + publish_frame.hide_requested.connect(self._on_overlay_hide_request) controller.add_instances_refresh_callback(self._on_instances_refresh) @@ -176,8 +184,9 @@ class PublisherWindow(QtWidgets.QWidget): controller.add_publish_validated_callback(self._on_publish_validated) controller.add_publish_stopped_callback(self._on_publish_stop) - self.main_frame = main_frame - self.overlay_frame = overlay_frame + self.content_stacked_layout = content_stacked_layout + self.publish_frame = publish_frame + self.subset_frame = subset_frame self.context_label = context_label @@ -208,15 +217,6 @@ class PublisherWindow(QtWidgets.QWidget): "////" ) - def resizeEvent(self, event): - super(PublisherWindow, self).resizeEvent(event) - - self.overlay_frame.resize(self.size()) - - def moveEvent(self, event): - super(PublisherWindow, self).moveEvent(event) - self.overlay_frame.move(0, 0) - def showEvent(self, event): super(PublisherWindow, self).showEvent(event) if self._first_show: @@ -287,8 +287,11 @@ class PublisherWindow(QtWidgets.QWidget): self.controller.save_instance_changes() def _set_overlay_visibility(self, visible): - if self.overlay_frame.isVisible() != visible: - self.overlay_frame.setVisible(visible) + if visible: + widget = self.publish_frame + else: + widget = self.subset_frame + self.content_stacked_layout.setCurrentWidget(widget) def _on_reset_clicked(self): self.controller.reset() From 9638d1db48597fffad832b1b02379fd105472a74 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 20 Aug 2021 17:35:07 +0200 Subject: [PATCH 321/736] added bigger font to context label --- openpype/style/style.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/style/style.css b/openpype/style/style.css index f21ccf3389..06c3c2b555 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -712,6 +712,10 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { font-size: 12pt; } +#PublishContextLabel { + font-size: 13pt; +} + #ValidationErrorTitleWidget { background: {color:bg-inputs}; } From c3987a54502c840922fb76b42a9302164300c146 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 20 Aug 2021 17:38:10 +0200 Subject: [PATCH 322/736] removed overlay from naming --- openpype/style/style.css | 18 +++++++++--------- .../tools/new_publisher/widgets/__init__.py | 4 ++-- .../{overlay_widget.py => publish_widget.py} | 10 +++++----- openpype/tools/new_publisher/window.py | 4 ++-- 4 files changed, 18 insertions(+), 18 deletions(-) rename openpype/tools/new_publisher/widgets/{overlay_widget.py => publish_widget.py} (97%) diff --git a/openpype/style/style.css b/openpype/style/style.css index 06c3c2b555..76a054a292 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -675,40 +675,40 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background: transparent; } -#PublishOverlayFrame { +#PublishFrame { background: rgba(0, 0, 0, 127); } -#PublishOverlayFrame[state="1"] { +#PublishFrame[state="1"] { background: rgb(22, 25, 29); } -#PublishOverlay { +#PublishInfoFrame { background: {color:bg}; border: 2px solid black; border-radius: 0.3em; } -#PublishOverlay[state="-1"] { +#PublishInfoFrame[state="-1"] { background: rgb(194, 226, 236); } -#PublishOverlay[state="0"] { +#PublishInfoFrame[state="0"] { background: #AA5050; } -#PublishOverlay[state="1"] { +#PublishInfoFrame[state="1"] { background: #458056; } -#PublishOverlay[state="2"] { +#PublishInfoFrame[state="2"] { background: #ff9900; } -#PublishOverlay QLabel { +#PublishInfoFrame QLabel { color: black; } -#PublishOverlayMainLabel { +#PublishInfoMainLabel { font-size: 12pt; } diff --git a/openpype/tools/new_publisher/widgets/__init__.py b/openpype/tools/new_publisher/widgets/__init__.py index 759160dcde..57434c38c4 100644 --- a/openpype/tools/new_publisher/widgets/__init__.py +++ b/openpype/tools/new_publisher/widgets/__init__.py @@ -6,8 +6,8 @@ from .icons import ( from .widgets import ( SubsetAttributesWidget ) -from .overlay_widget import ( - PublishOverlayFrame +from .publish_widget import ( + PublishFrame ) from .create_dialog import ( CreateDialog diff --git a/openpype/tools/new_publisher/widgets/overlay_widget.py b/openpype/tools/new_publisher/widgets/publish_widget.py similarity index 97% rename from openpype/tools/new_publisher/widgets/overlay_widget.py rename to openpype/tools/new_publisher/widgets/publish_widget.py index c4d53db3d8..0b20da2c24 100644 --- a/openpype/tools/new_publisher/widgets/overlay_widget.py +++ b/openpype/tools/new_publisher/widgets/publish_widget.py @@ -8,16 +8,16 @@ from .icons import get_icon from .validations_widget import ValidationsWidget -class PublishOverlayFrame(QtWidgets.QFrame): +class PublishFrame(QtWidgets.QFrame): hide_requested = QtCore.Signal() def __init__(self, controller, parent): - super(PublishOverlayFrame, self).__init__(parent) + super(PublishFrame, self).__init__(parent) - self.setObjectName("PublishOverlayFrame") + self.setObjectName("PublishFrame") info_frame = QtWidgets.QFrame(self) - info_frame.setObjectName("PublishOverlay") + info_frame.setObjectName("PublishInfoFrame") validation_errors_widget = ValidationsWidget(controller, self) @@ -36,7 +36,7 @@ class PublishOverlayFrame(QtWidgets.QFrame): top_layout.addWidget(hide_btn) main_label = QtWidgets.QLabel(content_widget) - main_label.setObjectName("PublishOverlayMainLabel") + main_label.setObjectName("PublishInfoMainLabel") main_label.setAlignment(QtCore.Qt.AlignCenter) message_label = QtWidgets.QLabel(content_widget) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 90dfddaa25..ecdfe7960b 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -30,7 +30,7 @@ from Qt import QtWidgets from openpype import style from control import PublisherController from widgets import ( - PublishOverlayFrame, + PublishFrame, SubsetAttributesWidget, InstanceCardView, InstanceListView, @@ -64,7 +64,7 @@ class PublisherWindow(QtWidgets.QWidget): line_widget.setMinimumHeight(2) # Overlay MUST be created after Main to be painted on top of it - publish_frame = PublishOverlayFrame(controller, self) + publish_frame = PublishFrame(controller, self) # Content # Subset widget From 8a49ffd9f1f7880de46e023a5d9bf46128808f07 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 20 Aug 2021 17:45:17 +0200 Subject: [PATCH 323/736] added progress to publish widget --- openpype/tools/new_publisher/control.py | 17 ++++++++++++++++- .../new_publisher/widgets/publish_widget.py | 14 ++++++-------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index d87c4124a7..2a7d0e4c55 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -258,6 +258,8 @@ class PublisherController: self._publish_up_validation = False # All publish plugins are processed self._publish_finished = False + self._publish_max_progress = 0 + self._publish_progress = 0 # Validation order # - plugin with order same or higher than this value is extractor or @@ -521,6 +523,14 @@ class PublisherController: def publish_has_crashed(self): return bool(self._publish_error) + @property + def publish_max_progress(self): + return self._publish_max_progress + + @property + def publish_progress(self): + return self._publish_progress + def get_publish_crash_error(self): return self._publish_error @@ -544,6 +554,9 @@ class PublisherController: self._publish_current_plugin_validation_errors = None self._publish_error = None + self._publish_max_progress = len(self.publish_plugins) + self._publish_progress = 0 + self._trigger_callbacks(self._publish_reset_callback_refs) def publish(self): @@ -606,7 +619,8 @@ class PublisherController: self._main_thread_processor.add_item(item) def _publish_iterator(self): - for plugin in self.publish_plugins: + for idx, plugin in enumerate(self.publish_plugins): + self._publish_progress = idx # Add plugin to publish report self._publish_report.add_plugin_iter(plugin, self._publish_context) @@ -681,6 +695,7 @@ class PublisherController: self._publish_report.set_plugin_skipped() self._publish_finished = True + self._publish_progress = self._publish_max_progress yield MainThreadItem(self.stop_publish) def _add_validation_error(self, result): diff --git a/openpype/tools/new_publisher/widgets/publish_widget.py b/openpype/tools/new_publisher/widgets/publish_widget.py index 0b20da2c24..c7e3d3a6a8 100644 --- a/openpype/tools/new_publisher/widgets/publish_widget.py +++ b/openpype/tools/new_publisher/widgets/publish_widget.py @@ -133,14 +133,6 @@ class PublishFrame(QtWidgets.QFrame): self.validate_btn = validate_btn self.publish_btn = publish_btn - def set_progress_range(self, max_value): - # TODO implement triggers for this method - self.progress_widget.setMaximum(max_value) - - def set_progress(self, value): - # TODO implement triggers for this method - self.progress_widget.setValue(value) - def _on_publish_reset(self): self._set_success_property() self._change_bg_property() @@ -154,6 +146,9 @@ class PublishFrame(QtWidgets.QFrame): self.validate_btn.setEnabled(True) self.publish_btn.setEnabled(True) + self.progress_widget.setValue(self.controller.publish_progress) + self.progress_widget.setMaximum(self.controller.publish_max_progress) + def _on_publish_start(self): self.validation_errors_widget.clear() @@ -194,10 +189,13 @@ class PublishFrame(QtWidgets.QFrame): if hasattr(plugin, "label") and plugin.label: plugin_name = plugin.label + self.progress_widget.setValue(self.controller.publish_progress) self.plugin_label.setText(plugin_name) QtWidgets.QApplication.processEvents() def _on_publish_stop(self): + self.progress_widget.setValue(self.controller.publish_progress) + self.reset_btn.setEnabled(True) self.stop_btn.setEnabled(False) validate_enabled = not self.controller.publish_has_crashed From e088c8b3173491658329231912c2b9645b416597 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 20 Aug 2021 18:58:46 +0200 Subject: [PATCH 324/736] changed checked to selected --- .../widgets/validations_widget.py | 51 ++++++++++--------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/validations_widget.py b/openpype/tools/new_publisher/widgets/validations_widget.py index 7f6b11d2ec..60791693f1 100644 --- a/openpype/tools/new_publisher/widgets/validations_widget.py +++ b/openpype/tools/new_publisher/widgets/validations_widget.py @@ -25,7 +25,7 @@ class _ClickableFrame(QtWidgets.QFrame): class ValidationErrorTitleWidget(_ClickableFrame): - checked = QtCore.Signal(int) + selected = QtCore.Signal(int) def __init__(self, index, error_info, parent): super(ValidationErrorTitleWidget, self).__init__(parent) @@ -40,13 +40,13 @@ class ValidationErrorTitleWidget(_ClickableFrame): self._index = index self._error_info = error_info - self._checked = False + self._selected = False self._mouse_pressed = False @property - def is_checked(self): - return self._checked + def is_selected(self): + return self._selected @property def index(self): @@ -55,25 +55,25 @@ class ValidationErrorTitleWidget(_ClickableFrame): def set_index(self, index): self._index = index - def _change_style_property(self, checked): - value = "1" if checked else "" - self.setProperty("checked", value) + def _change_style_property(self, selected): + value = "1" if selected else "" + self.setProperty("selected", value) self.style().polish(self) - def set_checked(self, checked=None): - if checked is None: - checked = not self._checked + def set_selected(self, selected=None): + if selected is None: + selected = not self._selected - elif checked == self._checked: + elif selected == self._selected: return - self._checked = checked - self._change_style_property(checked) - if checked: - self.checked.emit(self._index) + self._selected = selected + self._change_style_property(selected) + if selected: + self.selected.emit(self._index) def _mouse_release_callback(self): - self.set_checked(True) + self.set_selected(True) class ActionWidget(_ClickableFrame): @@ -91,6 +91,7 @@ class ActionWidget(_ClickableFrame): # action.icon lable_widget = QtWidgets.QLabel(action_label, self) + layout = QtWidgets.QHBoxLayout(self) layout.addWidget(lable_widget) @@ -198,13 +199,13 @@ class ValidationsWidget(QtWidgets.QWidget): self._title_widgets = {} self._error_info = {} - self._previous_checked = None + self._previous_select = None def clear(self): _old_title_widget = self._title_widgets self._title_widgets = {} self._error_info = {} - self._previous_checked = None + self._previous_select = None while self._errors_layout.count(): item = self._errors_layout.takeAt(0) widget = item.widget() @@ -247,7 +248,7 @@ class ValidationsWidget(QtWidgets.QWidget): for idx, item in enumerate(errors_by_title): widget = ValidationErrorTitleWidget(idx, item, self) - widget.checked.connect(self._on_checked) + widget.selected.connect(self._on_select) self._errors_layout.addWidget(widget) self._title_widgets[idx] = widget self._error_info[idx] = item @@ -255,15 +256,15 @@ class ValidationsWidget(QtWidgets.QWidget): self._errors_layout.addStretch(1) if self._title_widgets: - self._title_widgets[0].set_checked(True) + self._title_widgets[0].set_selected(True) - def _on_checked(self, index): - if self._previous_checked: - if self._previous_checked.index == index: + def _on_select(self, index): + if self._previous_select: + if self._previous_select.index == index: return - self._previous_checked.set_checked(False) + self._previous_select.set_selected(False) - self._previous_checked = self._title_widgets[index] + self._previous_select = self._title_widgets[index] error_item = self._error_info[index] From 8c2a00acde539644e0ba9a805955e50d81c1fbc5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 20 Aug 2021 19:00:03 +0200 Subject: [PATCH 325/736] use html if markdown converted is available --- .../new_publisher/widgets/validations_widget.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/validations_widget.py b/openpype/tools/new_publisher/widgets/validations_widget.py index 60791693f1..5b1f404833 100644 --- a/openpype/tools/new_publisher/widgets/validations_widget.py +++ b/openpype/tools/new_publisher/widgets/validations_widget.py @@ -1,3 +1,8 @@ +try: + import commonmark +except Exception: + commonmark = None + from Qt import QtWidgets, QtCore @@ -268,7 +273,10 @@ class ValidationsWidget(QtWidgets.QWidget): error_item = self._error_info[index] - self._error_details_input.setMarkdown( - error_item["exception"].description - ) + dsc = error_item["exception"].description + if commonmark: + html = commonmark.commonmark(dsc) + self._error_details_input.setHtml(html) + else: + self._error_details_input.setMarkdown(dsc) self._actions_widget.set_plugin(error_item["plugin"]) From 2a61810f461fff5d7c8dd195b5ed0b552ff7e24f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 20 Aug 2021 19:01:43 +0200 Subject: [PATCH 326/736] rename widget to frame --- openpype/style/style.css | 22 +++++++++---------- .../widgets/validations_widget.py | 8 +++---- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/openpype/style/style.css b/openpype/style/style.css index 76a054a292..72569e457b 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -571,19 +571,12 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { } #InfoText { - padding: 5px; + padding-left: 30px; + padding-top: 20px; background: transparent; border: 1px solid {color:border}; } -#InfoText:hover { - border-color: {color:border}; -} - -#InfoText:focus { - border-color: {color:border}; -} - #TypeEditor, #ToolEditor, #NameEditor, #NumberEditor { background: transparent; border-radius: 0.3em; @@ -716,11 +709,18 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { font-size: 13pt; } -#ValidationErrorTitleWidget { +#ValidationErrorTitleFrame { background: {color:bg-inputs}; + border-left: 4px solid transparent; } -#ValidationErrorTitleWidget[checked="1"] { + +#ValidationErrorTitleFrame:hover { + border-left-color: {color:border}; +} + +#ValidationErrorTitleFrame[selected="1"] { background: {color:bg}; + border-left-color: {palette:blue-light}; } #ArrowBtn, #ArrowBtn:disabled, #ArrowBtn:hover { diff --git a/openpype/tools/new_publisher/widgets/validations_widget.py b/openpype/tools/new_publisher/widgets/validations_widget.py index 5b1f404833..aaacbc2414 100644 --- a/openpype/tools/new_publisher/widgets/validations_widget.py +++ b/openpype/tools/new_publisher/widgets/validations_widget.py @@ -29,13 +29,13 @@ class _ClickableFrame(QtWidgets.QFrame): super(_ClickableFrame, self).mouseReleaseEvent(event) -class ValidationErrorTitleWidget(_ClickableFrame): +class ValidationErrorTitleFrame(_ClickableFrame): selected = QtCore.Signal(int) def __init__(self, index, error_info, parent): - super(ValidationErrorTitleWidget, self).__init__(parent) + super(ValidationErrorTitleFrame, self).__init__(parent) - self.setObjectName("ValidationErrorTitleWidget") + self.setObjectName("ValidationErrorTitleFrame") exception = error_info["exception"] label_widget = QtWidgets.QLabel(exception.title, self) @@ -252,7 +252,7 @@ class ValidationsWidget(QtWidgets.QWidget): }) for idx, item in enumerate(errors_by_title): - widget = ValidationErrorTitleWidget(idx, item, self) + widget = ValidationErrorTitleFrame(idx, item, self) widget.selected.connect(self._on_select) self._errors_layout.addWidget(widget) self._title_widgets[idx] = widget From f4a1071f852b08e54d5d884574be538942ad9d64 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 20 Aug 2021 19:07:33 +0200 Subject: [PATCH 327/736] added action button --- .../widgets/validations_widget.py | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/validations_widget.py b/openpype/tools/new_publisher/widgets/validations_widget.py index aaacbc2414..1a7cefddc6 100644 --- a/openpype/tools/new_publisher/widgets/validations_widget.py +++ b/openpype/tools/new_publisher/widgets/validations_widget.py @@ -81,27 +81,23 @@ class ValidationErrorTitleFrame(_ClickableFrame): self.set_selected(True) -class ActionWidget(_ClickableFrame): +class ActionButton(QtWidgets.QPushButton): action_clicked = QtCore.Signal(str) def __init__(self, action, parent): - super(ActionWidget, self).__init__(parent) - - self.setObjectName("PublishPluginActionWidget") - - self._action_id = action.id + super(ActionButton, self).__init__(parent) action_label = action.label or action.__name__ + self.setText(action_label) + # TODO handle icons # action.icon - lable_widget = QtWidgets.QLabel(action_label, self) + self.action = action + self.clicked.connect(self._on_click) - layout = QtWidgets.QHBoxLayout(self) - layout.addWidget(lable_widget) - - def _mouse_release_callback(self): - self.action_clicked.emit(self._action_id) + def _on_click(self): + self.action_clicked.emit(self.action.id) class ValidateActionsWidget(QtWidgets.QFrame): @@ -148,9 +144,9 @@ class ValidateActionsWidget(QtWidgets.QFrame): self._actions_mapping[action.id] = action - action_widget = ActionWidget(action, self._content_widget) - action_widget.action_clicked.connect(self._on_action_click) - self._content_layout.addWidget(action_widget) + action_btn = ActionButton(action, self._content_widget) + action_btn.action_clicked.connect(self._on_action_click) + self._content_layout.addWidget(action_btn) if self._content_layout.count() > 0: self.setVisible(True) From 4a585cf8306fa1f3ec97c14713a8e9b6f113e08e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 20 Aug 2021 19:13:15 +0200 Subject: [PATCH 328/736] added label on top --- .../widgets/validations_widget.py | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/validations_widget.py b/openpype/tools/new_publisher/widgets/validations_widget.py index 1a7cefddc6..9dcf3032d8 100644 --- a/openpype/tools/new_publisher/widgets/validations_widget.py +++ b/openpype/tools/new_publisher/widgets/validations_widget.py @@ -178,20 +178,30 @@ class ValidationsWidget(QtWidgets.QWidget): QtCore.Qt.TextBrowserInteraction ) - error_details_layout = QtWidgets.QVBoxLayout(error_details_widget) - error_details_layout.addWidget(error_details_input) - actions_widget = ValidateActionsWidget(controller, self) actions_widget.setFixedWidth(140) - layout = QtWidgets.QHBoxLayout(self) - layout.setSpacing(0) + error_details_layout = QtWidgets.QHBoxLayout(error_details_widget) + error_details_layout.addWidget(error_details_input, 1) + error_details_layout.addWidget(actions_widget, 0) + + content_layout = QtWidgets.QHBoxLayout() + content_layout.setSpacing(0) + content_layout.setContentsMargins(0, 0, 0, 0) + + content_layout.addWidget(errors_widget, 0) + content_layout.addWidget(error_details_widget, 1) + + top_label = QtWidgets.QLabel("Publish validation report", self) + top_label.setObjectName("PublishInfoMainLabel") + top_label.setAlignment(QtCore.Qt.AlignCenter) + + layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(top_label) + layout.addLayout(content_layout) - layout.addWidget(errors_widget, 0) - layout.addWidget(error_details_widget, 1) - layout.addWidget(actions_widget, 0) - + self._top_label = top_label self._errors_widget = errors_widget self._errors_layout = errors_layout self._error_details_widget = error_details_widget @@ -213,6 +223,7 @@ class ValidationsWidget(QtWidgets.QWidget): if widget: widget.deleteLater() + self._top_label.setVisible(False) self._error_details_widget.setVisible(False) self._errors_widget.setVisible(False) self._actions_widget.setVisible(False) @@ -222,6 +233,7 @@ class ValidationsWidget(QtWidgets.QWidget): if not errors: return + self._top_label.setVisible(True) self._error_details_widget.setVisible(True) self._errors_widget.setVisible(True) From f1a572141876f60e4470f338e32ae560aa8b761e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 20 Aug 2021 19:23:33 +0200 Subject: [PATCH 329/736] fixed buttons disabled/enabled states --- openpype/tools/new_publisher/control.py | 4 ++++ openpype/tools/new_publisher/widgets/publish_widget.py | 9 ++++++++- openpype/tools/new_publisher/window.py | 9 ++++++++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 2a7d0e4c55..05072b5861 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -523,6 +523,10 @@ class PublisherController: def publish_has_crashed(self): return bool(self._publish_error) + @property + def publish_has_validation_errors(self): + return bool(self._publish_validation_errors) + @property def publish_max_progress(self): return self._publish_max_progress diff --git a/openpype/tools/new_publisher/widgets/publish_widget.py b/openpype/tools/new_publisher/widgets/publish_widget.py index c7e3d3a6a8..9dca3733cf 100644 --- a/openpype/tools/new_publisher/widgets/publish_widget.py +++ b/openpype/tools/new_publisher/widgets/publish_widget.py @@ -203,7 +203,14 @@ class PublishFrame(QtWidgets.QFrame): if validate_enabled: validate_enabled = not self.controller.publish_has_validated if publish_enabled: - publish_enabled = not self.controller.publish_has_finished + if ( + self.controller.publish_has_validated + and self.controller.publish_has_validation_errors + ): + publish_enabled = False + + else: + publish_enabled = not self.controller.publish_has_finished self.validate_btn.setEnabled(validate_enabled) self.publish_btn.setEnabled(publish_enabled) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index ecdfe7960b..7983061b3d 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -364,7 +364,14 @@ class PublisherWindow(QtWidgets.QWidget): if validate_enabled: validate_enabled = not self.controller.publish_has_validated if publish_enabled: - publish_enabled = not self.controller.publish_has_finished + if ( + self.controller.publish_has_validated + and self.controller.publish_has_validation_errors + ): + publish_enabled = False + + else: + publish_enabled = not self.controller.publish_has_finished self.validate_btn.setEnabled(validate_enabled) self.publish_btn.setEnabled(publish_enabled) From 6a111e5f8e01d7eebb23decca6a033df6cd44c1e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 20 Aug 2021 19:23:44 +0200 Subject: [PATCH 330/736] fixed margins --- openpype/tools/new_publisher/window.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 7983061b3d..11d07cd10a 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -63,9 +63,6 @@ class PublisherWindow(QtWidgets.QWidget): line_widget.setObjectName("Separator") line_widget.setMinimumHeight(2) - # Overlay MUST be created after Main to be painted on top of it - publish_frame = PublishFrame(controller, self) - # Content # Subset widget subset_frame = QtWidgets.QWidget(self) @@ -130,7 +127,6 @@ class PublisherWindow(QtWidgets.QWidget): publish_btn.setToolTip("Publish") footer_layout = QtWidgets.QHBoxLayout() - footer_layout.setContentsMargins(0, 0, 0, 0) footer_layout.addWidget(message_input, 1) footer_layout.addWidget(reset_btn, 0) footer_layout.addWidget(stop_btn, 0) @@ -139,9 +135,18 @@ class PublisherWindow(QtWidgets.QWidget): # Subset frame layout subset_layout = QtWidgets.QVBoxLayout(subset_frame) + marings = subset_layout.contentsMargins() + marings.setLeft(marings.left() * 2) + marings.setRight(marings.right() * 2) + marings.setTop(marings.top() * 2) + marings.setBottom(marings.bottom() * 2) + subset_layout.setContentsMargins(marings) subset_layout.addLayout(subset_content_layout, 1) subset_layout.addLayout(footer_layout, 0) + # Create publish frame + publish_frame = PublishFrame(controller, self) + content_stacked_layout = QtWidgets.QStackedLayout() content_stacked_layout.setStackingMode( QtWidgets.QStackedLayout.StackAll From 8705eaaf4c519e724a9e99f821573df03ada4a23 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 20 Aug 2021 19:26:10 +0200 Subject: [PATCH 331/736] refresh will hide publish window --- openpype/tools/new_publisher/window.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 11d07cd10a..c48225b0f9 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -291,7 +291,7 @@ class PublisherWindow(QtWidgets.QWidget): def _on_save_clicked(self): self.controller.save_instance_changes() - def _set_overlay_visibility(self, visible): + def _set_publish_visibility(self, visible): if visible: widget = self.publish_frame else: @@ -305,11 +305,11 @@ class PublisherWindow(QtWidgets.QWidget): self.controller.stop_publish() def _on_validate_clicked(self): - self._set_overlay_visibility(True) + self._set_publish_visibility(True) self.controller.validate() def _on_publish_clicked(self): - self._set_overlay_visibility(True) + self._set_publish_visibility(True) self.controller.publish() def _refresh_instances(self): @@ -352,6 +352,8 @@ class PublisherWindow(QtWidgets.QWidget): self.validate_btn.setEnabled(True) self.publish_btn.setEnabled(True) + self._set_publish_visibility(False) + def _on_publish_start(self): self.reset_btn.setEnabled(False) self.stop_btn.setEnabled(True) @@ -382,7 +384,7 @@ class PublisherWindow(QtWidgets.QWidget): self.publish_btn.setEnabled(publish_enabled) def _on_overlay_hide_request(self): - self._set_overlay_visibility(False) + self._set_publish_visibility(False) def main(): From 767e53328a40710fc8173d6ec4e2e599990363ee Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 20 Aug 2021 19:33:45 +0200 Subject: [PATCH 332/736] final touches --- .../new_publisher/widgets/publish_widget.py | 39 ++++++++++--------- openpype/tools/new_publisher/window.py | 4 -- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/publish_widget.py b/openpype/tools/new_publisher/widgets/publish_widget.py index 9dca3733cf..d781b79d37 100644 --- a/openpype/tools/new_publisher/widgets/publish_widget.py +++ b/openpype/tools/new_publisher/widgets/publish_widget.py @@ -9,8 +9,6 @@ from .validations_widget import ValidationsWidget class PublishFrame(QtWidgets.QFrame): - hide_requested = QtCore.Signal() - def __init__(self, controller, parent): super(PublishFrame, self).__init__(parent) @@ -28,13 +26,6 @@ class PublishFrame(QtWidgets.QFrame): info_layout.setContentsMargins(0, 0, 0, 0) info_layout.addWidget(content_widget) - hide_btn = QtWidgets.QPushButton("Hide", content_widget) - - top_layout = QtWidgets.QHBoxLayout() - top_layout.setContentsMargins(0, 0, 0, 0) - top_layout.addStretch(1) - top_layout.addWidget(hide_btn) - main_label = QtWidgets.QLabel(content_widget) main_label.setObjectName("PublishInfoMainLabel") main_label.setAlignment(QtCore.Qt.AlignCenter) @@ -42,6 +33,9 @@ class PublishFrame(QtWidgets.QFrame): message_label = QtWidgets.QLabel(content_widget) message_label.setAlignment(QtCore.Qt.AlignCenter) + message_label_bottom = QtWidgets.QLabel(content_widget) + message_label_bottom.setAlignment(QtCore.Qt.AlignCenter) + instance_label = QtWidgets.QLabel("", content_widget) instance_label.setAlignment( QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter @@ -57,7 +51,7 @@ class PublishFrame(QtWidgets.QFrame): progress_widget = QtWidgets.QProgressBar(content_widget) copy_log_btn = QtWidgets.QPushButton("Copy log", content_widget) - copy_log_btn.setVisible(False) + # copy_log_btn.setVisible(False) reset_btn = QtWidgets.QPushButton(content_widget) reset_btn.setIcon(get_icon("refresh")) @@ -73,7 +67,7 @@ class PublishFrame(QtWidgets.QFrame): footer_layout = QtWidgets.QHBoxLayout() footer_layout.addWidget(copy_log_btn, 0) - footer_layout.addStretch(1) + footer_layout.addWidget(message_label_bottom, 1) footer_layout.addWidget(reset_btn, 0) footer_layout.addWidget(stop_btn, 0) footer_layout.addWidget(validate_btn, 0) @@ -83,7 +77,6 @@ class PublishFrame(QtWidgets.QFrame): content_layout.setSpacing(5) content_layout.setAlignment(QtCore.Qt.AlignCenter) - content_layout.addLayout(top_layout) content_layout.addWidget(main_label) content_layout.addStretch(1) content_layout.addWidget(message_label) @@ -97,7 +90,6 @@ class PublishFrame(QtWidgets.QFrame): main_layout.addWidget(validation_errors_widget, 1) main_layout.addWidget(info_frame, 0) - hide_btn.clicked.connect(self.hide_requested) copy_log_btn.clicked.connect(self._on_copy_log) reset_btn.clicked.connect(self._on_reset_clicked) @@ -115,8 +107,6 @@ class PublishFrame(QtWidgets.QFrame): self.controller = controller - self.hide_btn = hide_btn - self.validation_errors_widget = validation_errors_widget self.info_frame = info_frame @@ -128,6 +118,7 @@ class PublishFrame(QtWidgets.QFrame): self.progress_widget = progress_widget self.copy_log_btn = copy_log_btn + self.message_label_bottom = message_label_bottom self.reset_btn = reset_btn self.stop_btn = stop_btn self.validate_btn = validate_btn @@ -136,10 +127,12 @@ class PublishFrame(QtWidgets.QFrame): def _on_publish_reset(self): self._set_success_property() self._change_bg_property() + self._set_progress_visibility(True) self.main_label.setText("Hit publish! (if you want)") self.message_label.setText("") - self.copy_log_btn.setVisible(False) + self.message_label_bottom.setText("") + # self.copy_log_btn.setVisible(False) self.reset_btn.setEnabled(True) self.stop_btn.setEnabled(False) @@ -154,6 +147,7 @@ class PublishFrame(QtWidgets.QFrame): self._set_success_property(-1) self._change_bg_property() + self._set_progress_visibility(True) self.main_label.setText("Publishing...") self.reset_btn.setEnabled(False) @@ -222,6 +216,7 @@ class PublishFrame(QtWidgets.QFrame): validation_errors = self.controller.get_validation_errors() if validation_errors: + self._set_progress_visibility(False) self._change_bg_property(1) self._set_validation_errors(validation_errors) return @@ -239,12 +234,14 @@ class PublishFrame(QtWidgets.QFrame): " to your supervisor or OpenPype." ) self.message_label.setText(msg) + self.message_label_bottom.setText("") self._set_success_property(0) - self.copy_log_btn.setVisible(True) + # self.copy_log_btn.setVisible(True) def _set_validation_errors(self, validation_errors): self.main_label.setText("Your publish didn't pass studio validations") - self.message_label.setText("Check results above please") + self.message_label.setText("") + self.message_label_bottom.setText("Check results above please") self._set_success_property(2) self.validation_errors_widget.set_errors(validation_errors) @@ -257,6 +254,12 @@ class PublishFrame(QtWidgets.QFrame): self.setProperty("state", str(state or "")) self.style().polish(self) + def _set_progress_visibility(self, visible): + self.instance_label.setVisible(visible) + self.plugin_label.setVisible(visible) + self.progress_widget.setVisible(visible) + self.message_label.setVisible(visible) + def _set_success_property(self, state=None): self.info_frame.setProperty("state", str(state or "")) self.info_frame.style().polish(self.info_frame) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index c48225b0f9..e574b47c37 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -180,7 +180,6 @@ class PublisherWindow(QtWidgets.QWidget): subset_view_cards.selection_changed.connect( self._on_subset_change ) - publish_frame.hide_requested.connect(self._on_overlay_hide_request) controller.add_instances_refresh_callback(self._on_instances_refresh) @@ -383,9 +382,6 @@ class PublisherWindow(QtWidgets.QWidget): self.validate_btn.setEnabled(validate_enabled) self.publish_btn.setEnabled(publish_enabled) - def _on_overlay_hide_request(self): - self._set_publish_visibility(False) - def main(): """Main function for testing purposes.""" From 188357a9d2d269e630543ecf7f8b0efed518086e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 20 Aug 2021 19:34:38 +0200 Subject: [PATCH 333/736] testing log viewer widgets --- .../publish_log_viewer/logs.json | 845 ++++++++++++++++++ .../publish_log_viewer/window.py | 124 +++ 2 files changed, 969 insertions(+) create mode 100644 openpype/tools/new_publisher/publish_log_viewer/logs.json create mode 100644 openpype/tools/new_publisher/publish_log_viewer/window.py diff --git a/openpype/tools/new_publisher/publish_log_viewer/logs.json b/openpype/tools/new_publisher/publish_log_viewer/logs.json new file mode 100644 index 0000000000..ede8f2c290 --- /dev/null +++ b/openpype/tools/new_publisher/publish_log_viewer/logs.json @@ -0,0 +1,845 @@ +{ + "plugins_data": [ + { + "name": "CollectSettings", + "label": "Collect Settings", + "order": -0.491, + "instances_data": [ + { + "id": null, + "logs": [] + } + ], + "skipped": false + }, + { + "name": "CollectContextDataTestHost", + "label": "Collect Context - Test Host", + "order": -0.49, + "instances_data": [ + { + "id": null, + "logs": [ + { + "instance_id": null, + "type": "record", + "msg": "collected instance: {'family': 'test_one', 'name': 'test_oneMyVariant', 'subset': 'test_oneMyVariant', 'asset': 'sq01_sh0010', 'label': 'test_oneMyVariant', 'families': []}", + "name": "pyblish.CollectContextDataTestHost", + "lineno": 60, + "levelno": 20, + "levelname": "INFO", + "threadName": "MainThread", + "filename": "", + "pathname": "", + "msecs": 421.083927154541, + "exc_info": null + }, + { + "instance_id": null, + "type": "record", + "msg": "parsing data: {'id': 'pyblish.avalon.instance', 'family': 'test_one', 'subset': 'test_oneMyVariant', 'active': True, 'version': 1, 'asset': 'sq01_sh0010', 'task': 'Compositing', 'variant': 'myVariant', 'uuid': 'a485f148-9121-46a5-8157-aa64df0fb449', 'family_attributes': {'number_key': 10, 'ha': 10}, 'publish_attributes': {'CollectFtrackApi': {'add_ftrack_family': False}}}", + "name": "pyblish.CollectContextDataTestHost", + "lineno": 61, + "levelno": 20, + "levelname": "INFO", + "threadName": "MainThread", + "filename": "", + "pathname": "", + "msecs": 421.083927154541, + "exc_info": null + }, + { + "instance_id": null, + "type": "record", + "msg": "collected instance: {'family': 'test_one', 'name': 'test_oneMyVariant2', 'subset': 'test_oneMyVariant2', 'asset': 'sq01_sh0010', 'label': 'test_oneMyVariant2', 'families': []}", + "name": "pyblish.CollectContextDataTestHost", + "lineno": 60, + "levelno": 20, + "levelname": "INFO", + "threadName": "MainThread", + "filename": "", + "pathname": "", + "msecs": 423.0847358703613, + "exc_info": null + }, + { + "instance_id": null, + "type": "record", + "msg": "parsing data: {'id': 'pyblish.avalon.instance', 'family': 'test_one', 'subset': 'test_oneMyVariant2', 'active': True, 'version': 1, 'asset': 'sq01_sh0010', 'task': 'Compositing', 'variant': 'myVariant2', 'uuid': 'a485f148-9121-46a5-8157-aa64df0fb444', 'family_attributes': {}, 'publish_attributes': {'CollectFtrackApi': {'add_ftrack_family': True}}}", + "name": "pyblish.CollectContextDataTestHost", + "lineno": 61, + "levelno": 20, + "levelname": "INFO", + "threadName": "MainThread", + "filename": "", + "pathname": "", + "msecs": 423.0847358703613, + "exc_info": null + }, + { + "instance_id": null, + "type": "record", + "msg": "collected instance: {'family': 'test_two', 'name': 'test_twoMain', 'subset': 'test_twoMain', 'asset': 'sq01_sh0010', 'label': 'test_twoMain', 'families': []}", + "name": "pyblish.CollectContextDataTestHost", + "lineno": 60, + "levelno": 20, + "levelname": "INFO", + "threadName": "MainThread", + "filename": "", + "pathname": "", + "msecs": 423.0847358703613, + "exc_info": null + }, + { + "instance_id": null, + "type": "record", + "msg": "parsing data: {'id': 'pyblish.avalon.instance', 'family': 'test_two', 'subset': 'test_twoMain', 'active': True, 'version': 1, 'asset': 'sq01_sh0010', 'task': 'Compositing', 'variant': 'Main', 'uuid': '3607bc95-75f6-4648-a58d-e699f413d09f', 'family_attributes': {}, 'publish_attributes': {'CollectFtrackApi': {'add_ftrack_family': True}}}", + "name": "pyblish.CollectContextDataTestHost", + "lineno": 61, + "levelno": 20, + "levelname": "INFO", + "threadName": "MainThread", + "filename": "", + "pathname": "", + "msecs": 424.0849018096924, + "exc_info": null + }, + { + "instance_id": null, + "type": "record", + "msg": "collected instance: {'family': 'test_two', 'name': 'test_twoMain2', 'subset': 'test_twoMain2', 'asset': 'sq01_sh0020', 'label': 'test_twoMain2', 'families': []}", + "name": "pyblish.CollectContextDataTestHost", + "lineno": 60, + "levelno": 20, + "levelname": "INFO", + "threadName": "MainThread", + "filename": "", + "pathname": "", + "msecs": 424.0849018096924, + "exc_info": null + }, + { + "instance_id": null, + "type": "record", + "msg": "parsing data: {'id': 'pyblish.avalon.instance', 'family': 'test_two', 'subset': 'test_twoMain2', 'active': True, 'version': 1, 'asset': 'sq01_sh0020', 'task': 'Compositing', 'variant': 'Main2', 'uuid': '4ccf56f6-9982-4837-967c-a49695dbe8eb', 'family_attributes': {}, 'publish_attributes': {'CollectFtrackApi': {'add_ftrack_family': True}}}", + "name": "pyblish.CollectContextDataTestHost", + "lineno": 61, + "levelno": 20, + "levelname": "INFO", + "threadName": "MainThread", + "filename": "", + "pathname": "", + "msecs": 424.0849018096924, + "exc_info": null + } + ] + } + ], + "skipped": false + }, + { + "name": "CollectAnatomyObject", + "label": "Collect Anatomy Object", + "order": -0.4, + "instances_data": [ + { + "id": null, + "logs": [ + { + "instance_id": null, + "type": "record", + "msg": "Crashed", + "name": "pyblish.CollectAnatomyObject", + "lineno": 24, + "levelno": 20, + "levelname": "INFO", + "threadName": "MainThread", + "filename": "", + "pathname": "", + "msecs": 483.0970764160156, + "exc_info": "Traceback (most recent call last):\n File \"\", line 22, in process\nValueError: test\n" + }, + { + "instance_id": null, + "type": "record", + "msg": "Anatomy object collected for project \"kuba_each_case\".", + "name": "pyblish.CollectAnatomyObject", + "lineno": 35, + "levelno": 20, + "levelname": "INFO", + "threadName": "MainThread", + "filename": "", + "pathname": "", + "msecs": 491.09888076782227, + "exc_info": null + } + ] + } + ], + "skipped": false + }, + { + "name": "CollectAvalonEntities", + "label": "Collect Avalon Entities", + "order": -0.1, + "instances_data": [ + { + "id": null, + "logs": [ + { + "instance_id": null, + "type": "record", + "msg": "Collected Project \"{'_id': ObjectId('5eb950f9e3c2c3183455f266'), 'name': 'kuba_each_case', 'data': {'ftrackId': '38c5fec4-0aed-11ea-a454-3e41ec9bc0d6', 'entityType': 'Project', 'library_project': False, 'clipIn': 1001, 'resolutionWidth': 1920, 'handleEnd': 10, 'frameEnd': 1100, 'resolutionHeight': 1080, 'frameStart': 1001, 'pixelAspect': 1.0, 'fps': 25.0, 'handleStart': 10, 'clipOut': 1100, 'tools_env': [], 'code': 'kuba_each_case'}, 'type': 'project', 'config': {'tasks': {'Layout': {'short_name': 'lay'}, 'Setdress': {'short_name': 'dress'}, 'Previz': {'short_name': ''}, 'Generic': {'short_name': 'gener'}, 'Animation': {'short_name': 'anim'}, 'Modeling': {'short_name': 'mdl'}, 'Lookdev': {'short_name': 'look'}, 'FX': {'short_name': 'fx'}, 'Lighting': {'short_name': 'lgt'}, 'Compositing': {'short_name': 'comp'}, 'Tracking': {'short_name': ''}, 'Rigging': {'short_name': 'rig'}, 'Paint': {'short_name': 'paint'}, 'Art': {'short_name': 'art'}, 'Texture': {'short_name': 'tex'}, 'Edit': {'short_name': 'edit'}}, 'apps': [{'name': 'blender/2-91'}, {'name': 'hiero/11-3'}, {'name': 'houdini/18-5'}, {'name': 'maya/2020'}, {'name': 'nuke/11-3'}, {'name': 'nukestudio/11-3'}], 'imageio': {'hiero': {'workfile': {'ocioConfigName': 'nuke-default', 'ocioconfigpath': {'windows': [], 'darwin': [], 'linux': []}, 'workingSpace': 'linear', 'sixteenBitLut': 'sRGB', 'eightBitLut': 'sRGB', 'floatLut': 'linear', 'logLut': 'Cineon', 'viewerLut': 'sRGB', 'thumbnailLut': 'sRGB'}, 'regexInputs': {'inputs': [{'regex': '[^-a-zA-Z0-9](plateRef).*(?=mp4)', 'colorspace': 'sRGB'}]}}, 'nuke': {'viewer': {'viewerProcess': 'sRGB'}, 'workfile': {'colorManagement': 'Nuke', 'OCIO_config': 'nuke-default', 'customOCIOConfigPath': {'windows': [], 'darwin': [], 'linux': []}, 'workingSpaceLUT': 'linear', 'monitorLut': 'sRGB', 'int8Lut': 'sRGB', 'int16Lut': 'sRGB', 'logLut': 'Cineon', 'floatLut': 'linear'}, 'nodes': {'requiredNodes': [{'plugins': ['CreateWriteRender'], 'nukeNodeClass': 'Write', 'knobs': [{'name': 'file_type', 'value': 'exr'}, {'name': 'datatype', 'value': '16 bit half'}, {'name': 'compression', 'value': 'Zip (1 scanline)'}, {'name': 'autocrop', 'value': 'True'}, {'name': 'tile_color', 'value': '0xff0000ff'}, {'name': 'channels', 'value': 'rgb'}, {'name': 'colorspace', 'value': 'linear'}, {'name': 'create_directories', 'value': 'True'}]}, {'plugins': ['CreateWritePrerender'], 'nukeNodeClass': 'Write', 'knobs': [{'name': 'file_type', 'value': 'exr'}, {'name': 'datatype', 'value': '16 bit half'}, {'name': 'compression', 'value': 'Zip (1 scanline)'}, {'name': 'autocrop', 'value': 'False'}, {'name': 'tile_color', 'value': '0xadab1dff'}, {'name': 'channels', 'value': 'rgb'}, {'name': 'colorspace', 'value': 'linear'}, {'name': 'create_directories', 'value': 'True'}]}], 'customNodes': []}, 'regexInputs': {'inputs': [{'regex': '[^-a-zA-Z0-9]beauty[^-a-zA-Z0-9]', 'colorspace': 'linear'}]}}}, 'roots': {'work': {'windows': 'C:/projects', 'darwin': '/Volumes/path', 'linux': '/mnt/share/projects'}}, 'templates': {'defaults': {'version_padding': 3, 'version': 'v{version:0>{@version_padding}}', 'frame_padding': 4, 'frame': '{frame:0>{@frame_padding}}'}, 'work': {'folder': '{root[work]}/{project[name]}/{hierarchy}/{asset}/work/{task}', 'file': '{project[code]}_{asset}_{task}_{@version}<_{comment}>.{ext}', 'path': '{@folder}/{@file}'}, 'render': {'folder': '{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/{@version}', 'file': '{project[code]}_{asset}_{subset}_{@version}<_{output}><.{@frame}>.{ext}', 'path': '{@folder}/{@file}'}, 'publish': {'folder': '{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/{@version}', 'file': '{project[code]}_{asset}_{subset}_{@version}<_{output}><.{@frame}><_{udim}>.{ext}', 'path': '{@folder}/{@file}', 'thumbnail': '{thumbnail_root}/{project[name]}/{_id}_{thumbnail_type}.{ext}'}, 'hero': {'folder': '{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/hero', 'file': '{project[code]}_{asset}_{subset}_hero<_{output}><.{frame}>.{ext}', 'path': '{@folder}/{@file}'}, 'delivery': {}, 'others': {}}, 'schema': 'openpype:config-2.0'}, 'parent': None, 'schema': 'openpype:project-3.0'}\"", + "name": "pyblish.CollectAvalonEntities", + "lineno": 33, + "levelno": 10, + "levelname": "DEBUG", + "threadName": "MainThread", + "filename": "", + "pathname": "", + "msecs": 546.1108684539795, + "exc_info": null + }, + { + "instance_id": null, + "type": "record", + "msg": "Collected Asset \"{'_id': ObjectId('5dd50967e3c2c32b004b4817'), 'name': 'Alpaca_01', 'data': {'ftrackId': 'fc02ea60-9786-11eb-8ae9-fa8d1b9899e1', 'entityType': 'AssetBuild', 'clipIn': 1001, 'resolutionWidth': 1920, 'handleEnd': 10, 'frameEnd': 1010, 'resolutionHeight': 1080, 'frameStart': 1001, 'pixelAspect': 1.0, 'fps': 25.0, 'handleStart': 10, 'clipOut': 1100, 'tools_env': ['mtoa/3-1'], 'avalon_mongo_id': '5dd50967e3c2c32b004b4817', 'parents': ['Assets'], 'tasks': {'modeling': {'type': 'Modeling'}, 'animation': {'type': 'Animation'}, 'edit': {'type': 'Edit'}}, 'visualParent': ObjectId('5dd50967e3c2c32b004b4812')}, 'type': 'asset', 'parent': ObjectId('5eb950f9e3c2c3183455f266'), 'schema': 'openpype:asset-3.0'}\"", + "name": "pyblish.CollectAvalonEntities", + "lineno": 44, + "levelno": 10, + "levelname": "DEBUG", + "threadName": "MainThread", + "filename": "", + "pathname": "", + "msecs": 548.1112003326416, + "exc_info": null + } + ] + } + ], + "skipped": false + }, + { + "name": "CollectCurrentDate", + "label": "Current date", + "order": 0, + "instances_data": [ + { + "id": null, + "logs": [] + } + ], + "skipped": false + }, + { + "name": "CollectCurrentUser", + "label": "Current user", + "order": 0, + "instances_data": [ + { + "id": null, + "logs": [] + } + ], + "skipped": false + }, + { + "name": "CollectCurrentWorkingDirectory", + "label": "Current working directory", + "order": 0, + "instances_data": [ + { + "id": null, + "logs": [] + } + ], + "skipped": false + }, + { + "name": "CollectComment", + "label": "Collect Comment", + "order": 0, + "instances_data": [ + { + "id": null, + "logs": [] + } + ], + "skipped": false + }, + { + "name": "CollectDateTimeData", + "label": "Collect DateTime data", + "order": 0, + "instances_data": [ + { + "id": null, + "logs": [] + } + ], + "skipped": false + }, + { + "name": "CollectHostName", + "label": "Collect Host Name", + "order": 0, + "instances_data": [ + { + "id": null, + "logs": [] + } + ], + "skipped": false + }, + { + "name": "CollectMachineName", + "label": "Local Machine Name", + "order": 0, + "instances_data": [ + { + "id": null, + "logs": [ + { + "instance_id": null, + "type": "record", + "msg": "Machine name: 014-BAILEYS", + "name": "pyblish.CollectMachineName", + "lineno": 21, + "levelno": 20, + "levelname": "INFO", + "threadName": "MainThread", + "filename": "", + "pathname": "", + "msecs": 985.015869140625, + "exc_info": null + } + ] + } + ], + "skipped": false + }, + { + "name": "CollectTime", + "label": "Collect Current Time", + "order": 0, + "instances_data": [ + { + "id": null, + "logs": [] + } + ], + "skipped": false + }, + { + "name": "RepairUnicodeStrings", + "label": "Unicode Strings", + "order": 0, + "instances_data": [ + { + "id": null, + "logs": [] + } + ], + "skipped": false + }, + { + "name": "CollectCurrentUserPype", + "label": "Collect Pype User", + "order": 0.001, + "instances_data": [ + { + "id": null, + "logs": [ + { + "instance_id": null, + "type": "record", + "msg": "Collected user \"jakub.trllo\"", + "name": "pyblish.CollectCurrentUserPype", + "lineno": 17, + "levelno": 10, + "levelname": "DEBUG", + "threadName": "MainThread", + "filename": "", + "pathname": "", + "msecs": 170.26805877685547, + "exc_info": null + } + ] + } + ], + "skipped": false + }, + { + "name": "CollectAnatomyContextData", + "label": "Collect Anatomy Context Data", + "order": 0.002, + "instances_data": [ + { + "id": null, + "logs": [ + { + "instance_id": null, + "type": "record", + "msg": "Global anatomy Data collected", + "name": "pyblish.CollectAnatomyContextData", + "lineno": 87, + "levelno": 20, + "levelname": "INFO", + "threadName": "MainThread", + "filename": "", + "pathname": "", + "msecs": 234.85493659973145, + "exc_info": null + }, + { + "instance_id": null, + "type": "record", + "msg": "{\n \"project\": {\n \"name\": \"kuba_each_case\",\n \"code\": \"kuba_each_case\"\n },\n \"asset\": \"Alpaca_01\",\n \"hierarchy\": \"Assets\",\n \"task\": \"modeling\",\n \"username\": \"jakub.trllo\",\n \"app\": \"testhost\",\n \"d\": \"19\",\n \"dd\": \"19\",\n \"ddd\": \"Thu\",\n \"dddd\": \"Thursday\",\n \"m\": \"8\",\n \"mm\": \"08\",\n \"mmm\": \"Aug\",\n \"mmmm\": \"August\",\n \"yy\": \"21\",\n \"yyyy\": \"2021\",\n \"H\": \"18\",\n \"HH\": \"18\",\n \"h\": \"6\",\n \"hh\": \"06\",\n \"ht\": \"PM\",\n \"M\": \"7\",\n \"MM\": \"07\",\n \"S\": \"1\",\n \"SS\": \"01\"\n}", + "name": "pyblish.CollectAnatomyContextData", + "lineno": 88, + "levelno": 10, + "levelname": "DEBUG", + "threadName": "MainThread", + "filename": "", + "pathname": "", + "msecs": 234.85493659973145, + "exc_info": null + } + ] + } + ], + "skipped": false + }, + { + "name": "CollectContextLabel", + "label": "Context Label", + "order": 0.25, + "instances_data": [ + { + "id": null, + "logs": [] + } + ], + "skipped": false + }, + { + "name": "CollectAnatomyInstanceData", + "label": "Collect Anatomy Instance data", + "order": 0.49, + "instances_data": [ + { + "id": null, + "logs": [ + { + "instance_id": null, + "type": "record", + "msg": "Collecting anatomy data for all instances.", + "name": "pyblish.CollectAnatomyInstanceData", + "lineno": 42, + "levelno": 20, + "levelname": "INFO", + "threadName": "MainThread", + "filename": "", + "pathname": "", + "msecs": 358.84571075439453, + "exc_info": null + }, + { + "instance_id": null, + "type": "record", + "msg": "Qeurying asset documents for instances.", + "name": "pyblish.CollectAnatomyInstanceData", + "lineno": 51, + "levelno": 10, + "levelname": "DEBUG", + "threadName": "MainThread", + "filename": "", + "pathname": "", + "msecs": 358.84571075439453, + "exc_info": null + }, + { + "instance_id": null, + "type": "record", + "msg": "Querying asset documents with names: \"sq01_sh0010\", \"sq01_sh0020\"", + "name": "pyblish.CollectAnatomyInstanceData", + "lineno": 82, + "levelno": 10, + "levelname": "DEBUG", + "threadName": "MainThread", + "filename": "", + "pathname": "", + "msecs": 359.8446846008301, + "exc_info": null + }, + { + "instance_id": null, + "type": "record", + "msg": "Qeurying latest versions for instances.", + "name": "pyblish.CollectAnatomyInstanceData", + "lineno": 123, + "levelno": 10, + "levelname": "DEBUG", + "threadName": "MainThread", + "filename": "", + "pathname": "", + "msecs": 360.8419895172119, + "exc_info": null + }, + { + "instance_id": null, + "type": "record", + "msg": "Storing anatomy data to instance data.", + "name": "pyblish.CollectAnatomyInstanceData", + "lineno": 210, + "levelno": 10, + "levelname": "DEBUG", + "threadName": "MainThread", + "filename": "", + "pathname": "", + "msecs": 361.8345260620117, + "exc_info": null + }, + { + "instance_id": null, + "type": "record", + "msg": "Anatomy data for instance test_oneMyVariant(test_oneMyVariant): {\n \"project\": {\n \"name\": \"kuba_each_case\",\n \"code\": \"kuba_each_case\"\n },\n \"asset\": \"sq01_sh0010\",\n \"hierarchy\": \"Shots/sq01\",\n \"task\": \"modeling\",\n \"username\": \"jakub.trllo\",\n \"app\": \"testhost\",\n \"d\": \"19\",\n \"dd\": \"19\",\n \"ddd\": \"Thu\",\n \"dddd\": \"Thursday\",\n \"m\": \"8\",\n \"mm\": \"08\",\n \"mmm\": \"Aug\",\n \"mmmm\": \"August\",\n \"yy\": \"21\",\n \"yyyy\": \"2021\",\n \"H\": \"18\",\n \"HH\": \"18\",\n \"h\": \"6\",\n \"hh\": \"06\",\n \"ht\": \"PM\",\n \"M\": \"7\",\n \"MM\": \"07\",\n \"S\": \"1\",\n \"SS\": \"01\",\n \"family\": \"test_one\",\n \"subset\": \"test_oneMyVariant\",\n \"version\": 1\n}", + "name": "pyblish.CollectAnatomyInstanceData", + "lineno": 279, + "levelno": 10, + "levelname": "DEBUG", + "threadName": "MainThread", + "filename": "", + "pathname": "", + "msecs": 362.8430366516113, + "exc_info": null + }, + { + "instance_id": null, + "type": "record", + "msg": "Anatomy data for instance test_oneMyVariant2(test_oneMyVariant2): {\n \"project\": {\n \"name\": \"kuba_each_case\",\n \"code\": \"kuba_each_case\"\n },\n \"asset\": \"sq01_sh0010\",\n \"hierarchy\": \"Shots/sq01\",\n \"task\": \"modeling\",\n \"username\": \"jakub.trllo\",\n \"app\": \"testhost\",\n \"d\": \"19\",\n \"dd\": \"19\",\n \"ddd\": \"Thu\",\n \"dddd\": \"Thursday\",\n \"m\": \"8\",\n \"mm\": \"08\",\n \"mmm\": \"Aug\",\n \"mmmm\": \"August\",\n \"yy\": \"21\",\n \"yyyy\": \"2021\",\n \"H\": \"18\",\n \"HH\": \"18\",\n \"h\": \"6\",\n \"hh\": \"06\",\n \"ht\": \"PM\",\n \"M\": \"7\",\n \"MM\": \"07\",\n \"S\": \"1\",\n \"SS\": \"01\",\n \"family\": \"test_one\",\n \"subset\": \"test_oneMyVariant2\",\n \"version\": 1\n}", + "name": "pyblish.CollectAnatomyInstanceData", + "lineno": 279, + "levelno": 10, + "levelname": "DEBUG", + "threadName": "MainThread", + "filename": "", + "pathname": "", + "msecs": 362.8430366516113, + "exc_info": null + }, + { + "instance_id": null, + "type": "record", + "msg": "Anatomy data for instance test_twoMain(test_twoMain): {\n \"project\": {\n \"name\": \"kuba_each_case\",\n \"code\": \"kuba_each_case\"\n },\n \"asset\": \"sq01_sh0010\",\n \"hierarchy\": \"Shots/sq01\",\n \"task\": \"modeling\",\n \"username\": \"jakub.trllo\",\n \"app\": \"testhost\",\n \"d\": \"19\",\n \"dd\": \"19\",\n \"ddd\": \"Thu\",\n \"dddd\": \"Thursday\",\n \"m\": \"8\",\n \"mm\": \"08\",\n \"mmm\": \"Aug\",\n \"mmmm\": \"August\",\n \"yy\": \"21\",\n \"yyyy\": \"2021\",\n \"H\": \"18\",\n \"HH\": \"18\",\n \"h\": \"6\",\n \"hh\": \"06\",\n \"ht\": \"PM\",\n \"M\": \"7\",\n \"MM\": \"07\",\n \"S\": \"1\",\n \"SS\": \"01\",\n \"family\": \"test_two\",\n \"subset\": \"test_twoMain\",\n \"version\": 1\n}", + "name": "pyblish.CollectAnatomyInstanceData", + "lineno": 279, + "levelno": 10, + "levelname": "DEBUG", + "threadName": "MainThread", + "filename": "", + "pathname": "", + "msecs": 363.8427257537842, + "exc_info": null + }, + { + "instance_id": null, + "type": "record", + "msg": "Anatomy data for instance test_twoMain2(test_twoMain2): {\n \"project\": {\n \"name\": \"kuba_each_case\",\n \"code\": \"kuba_each_case\"\n },\n \"asset\": \"sq01_sh0020\",\n \"hierarchy\": \"Shots/sq01\",\n \"task\": \"modeling\",\n \"username\": \"jakub.trllo\",\n \"app\": \"testhost\",\n \"d\": \"19\",\n \"dd\": \"19\",\n \"ddd\": \"Thu\",\n \"dddd\": \"Thursday\",\n \"m\": \"8\",\n \"mm\": \"08\",\n \"mmm\": \"Aug\",\n \"mmmm\": \"August\",\n \"yy\": \"21\",\n \"yyyy\": \"2021\",\n \"H\": \"18\",\n \"HH\": \"18\",\n \"h\": \"6\",\n \"hh\": \"06\",\n \"ht\": \"PM\",\n \"M\": \"7\",\n \"MM\": \"07\",\n \"S\": \"1\",\n \"SS\": \"01\",\n \"family\": \"test_two\",\n \"subset\": \"test_twoMain2\",\n \"version\": 1\n}", + "name": "pyblish.CollectAnatomyInstanceData", + "lineno": 279, + "levelno": 10, + "levelname": "DEBUG", + "threadName": "MainThread", + "filename": "", + "pathname": "", + "msecs": 364.84289169311523, + "exc_info": null + }, + { + "instance_id": null, + "type": "record", + "msg": "Anatomy Data collection finished.", + "name": "pyblish.CollectAnatomyInstanceData", + "lineno": 48, + "levelno": 20, + "levelname": "INFO", + "threadName": "MainThread", + "filename": "", + "pathname": "", + "msecs": 365.83518981933594, + "exc_info": null + } + ] + } + ], + "skipped": false + }, + { + "name": "CollectResourcesPath", + "label": "Collect Resources Path", + "order": 0.495, + "instances_data": [], + "skipped": true + }, + { + "name": "ValidateEditorialAssetName", + "label": "Validate Editorial Asset Name", + "order": 1, + "instances_data": [ + { + "id": null, + "logs": [ + { + "instance_id": null, + "type": "record", + "msg": "__ asset_and_parents: {}", + "name": "pyblish.ValidateEditorialAssetName", + "lineno": 19, + "levelno": 10, + "levelname": "DEBUG", + "threadName": "MainThread", + "filename": "", + "pathname": "", + "msecs": 420.8390712738037, + "exc_info": null + }, + { + "instance_id": null, + "type": "record", + "msg": "__ db_assets: [{'_id': ObjectId('5e11ec3e0002fc29f0728009'), 'name': 'MyFolder', 'data': {'parents': []}}, {'_id': ObjectId('5dd50967e3c2c32b004b4811'), 'name': 'Shots', 'data': {'parents': []}}, {'_id': ObjectId('5dd50967e3c2c32b004b4812'), 'name': 'Assets', 'data': {'parents': []}}, {'_id': ObjectId('606d813fda091ca18ad3c1cb'), 'name': 'sq01', 'data': {'parents': ['Shots']}}, {'_id': ObjectId('60ffcef3ed7b98218feec87d'), 'name': 'editorial', 'data': {'parents': ['Shots']}}, {'_id': ObjectId('5eb40b96e3c2c3361ce67c05'), 'name': 'Buddie_01', 'data': {'parents': ['Assets']}}, {'_id': ObjectId('60ffcef3ed7b98218feec87e'), 'name': 'characters', 'data': {'parents': ['Assets']}}, {'_id': ObjectId('60ffcef3ed7b98218feec87f'), 'name': 'locations', 'data': {'parents': ['Assets']}}, {'_id': ObjectId('5dd50967e3c2c32b004b4817'), 'name': 'Alpaca_01', 'data': {'parents': ['Assets']}}, {'_id': ObjectId('60d9a8e8a5016c2a23f88534'), 'name': 'sq01_sh0020', 'data': {'parents': ['Shots', 'sq01']}}, {'_id': ObjectId('60d9a8e8a5016c2a23f88535'), 'name': 'sq01_sh0030', 'data': {'parents': ['Shots', 'sq01']}}, {'_id': ObjectId('606d813fda091ca18ad3c1cd'), 'name': 'sq01_sh0010', 'data': {'parents': ['Shots', 'sq01']}}]", + "name": "pyblish.ValidateEditorialAssetName", + "lineno": 26, + "levelno": 10, + "levelname": "DEBUG", + "threadName": "MainThread", + "filename": "", + "pathname": "", + "msecs": 421.8316078186035, + "exc_info": null + }, + { + "instance_id": null, + "type": "record", + "msg": "__ project_entities: {'Alpaca_01': ['Assets'],\n 'Assets': [],\n 'Buddie_01': ['Assets'],\n 'MyFolder': [],\n 'Shots': [],\n 'characters': ['Assets'],\n 'editorial': ['Shots'],\n 'locations': ['Assets'],\n 'sq01': ['Shots'],\n 'sq01_sh0010': ['Shots', 'sq01'],\n 'sq01_sh0020': ['Shots', 'sq01'],\n 'sq01_sh0030': ['Shots', 'sq01']}", + "name": "pyblish.ValidateEditorialAssetName", + "lineno": 33, + "levelno": 10, + "levelname": "DEBUG", + "threadName": "MainThread", + "filename": "", + "pathname": "", + "msecs": 421.8316078186035, + "exc_info": null + } + ] + } + ], + "skipped": false + }, + { + "name": "ValidateFFmpegInstalled", + "label": "Validate ffmpeg installation", + "order": 1, + "instances_data": [ + { + "id": null, + "logs": [ + { + "instance_id": null, + "type": "record", + "msg": "ffmpeg path: `C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\vendor\\bin\\ffmpeg\\windows\\bin\\ffmpeg`", + "name": "pyblish.ValidateFFmpegInstalled", + "lineno": 31, + "levelno": 20, + "levelname": "INFO", + "threadName": "MainThread", + "filename": "", + "pathname": "", + "msecs": 482.6626777648926, + "exc_info": null + } + ] + } + ], + "skipped": false + }, + { + "name": "ValidateResources", + "label": "Resources", + "order": 1.1, + "instances_data": [ + { + "id": null, + "logs": [] + }, + { + "id": null, + "logs": [] + }, + { + "id": null, + "logs": [] + }, + { + "id": null, + "logs": [] + } + ], + "skipped": false + }, + { + "name": "ExtractHierarchyToAvalon", + "label": "Extract Hierarchy To Avalon", + "order": 1.99, + "instances_data": [], + "skipped": true + }, + { + "name": "IntegrateResourcesPath", + "label": "Integrate Resources Path", + "order": 2.95, + "instances_data": [], + "skipped": true + }, + { + "name": "IntegrateAssetNew", + "label": "Integrate Asset New", + "order": 3, + "instances_data": [], + "skipped": true + }, + { + "name": "IntegrateThumbnails", + "label": "Integrate Thumbnails", + "order": 3.01, + "instances_data": [], + "skipped": true + }, + { + "name": "IntegrateHeroVersion", + "label": "Integrate Hero Version", + "order": 3.1, + "instances_data": [], + "skipped": true + }, + { + "name": "CleanUp", + "label": "Clean Up", + "order": 13, + "instances_data": [ + { + "id": null, + "logs": [ + { + "instance_id": null, + "type": "record", + "msg": "Staging dir not set.", + "name": "pyblish.CleanUp", + "lineno": 60, + "levelno": 20, + "levelname": "INFO", + "threadName": "MainThread", + "filename": "", + "pathname": "", + "msecs": 794.025182723999, + "exc_info": null + } + ] + }, + { + "id": null, + "logs": [ + { + "instance_id": null, + "type": "record", + "msg": "Staging dir not set.", + "name": "pyblish.CleanUp", + "lineno": 60, + "levelno": 20, + "levelname": "INFO", + "threadName": "MainThread", + "filename": "", + "pathname": "", + "msecs": 856.0423851013184, + "exc_info": null + } + ] + }, + { + "id": null, + "logs": [ + { + "instance_id": null, + "type": "record", + "msg": "Staging dir not set.", + "name": "pyblish.CleanUp", + "lineno": 60, + "levelno": 20, + "levelname": "INFO", + "threadName": "MainThread", + "filename": "", + "pathname": "", + "msecs": 917.0691967010498, + "exc_info": null + } + ] + }, + { + "id": null, + "logs": [ + { + "instance_id": null, + "type": "record", + "msg": "Staging dir not set.", + "name": "pyblish.CleanUp", + "lineno": 60, + "levelno": 20, + "levelname": "INFO", + "threadName": "MainThread", + "filename": "", + "pathname": "", + "msecs": 978.832483291626, + "exc_info": null + } + ] + } + ], + "skipped": false + } + ], + "instances": { + "fad07a7e-0d74-4468-8c82-d5ef3b21c625": { + "name": "test_oneMyVariant", + "label": "test_oneMyVariant", + "family": "test_one", + "families": [], + "exists": true + }, + "3690bd36-4ea5-450d-9deb-e5ba4caa456b": { + "name": "test_oneMyVariant2", + "label": "test_oneMyVariant2", + "family": "test_one", + "families": [], + "exists": true + }, + "c6ad1345-a447-4b0a-82a7-827a957a0424": { + "name": "test_twoMain", + "label": "test_twoMain", + "family": "test_two", + "families": [], + "exists": true + }, + "d01c9aab-a7fc-4f61-bd38-05c2d999e740": { + "name": "test_twoMain2", + "label": "test_twoMain2", + "family": "test_two", + "families": [], + "exists": true + } + }, + "context": { + "name": null, + "label": "Testhost - " + } +} diff --git a/openpype/tools/new_publisher/publish_log_viewer/window.py b/openpype/tools/new_publisher/publish_log_viewer/window.py new file mode 100644 index 0000000000..01d8b853a8 --- /dev/null +++ b/openpype/tools/new_publisher/publish_log_viewer/window.py @@ -0,0 +1,124 @@ +import os +import sys +import json +import collections + +openpype_dir = r"C:\Users\jakub.trllo\Desktop\pype\pype3" +mongo_url = "mongodb://localhost:2707" + +os.environ["OPENPYPE_MONGO"] = mongo_url +os.environ["AVALON_MONGO"] = mongo_url +os.environ["OPENPYPE_DATABASE_NAME"] = "openpype" +os.environ["AVALON_CONFIG"] = "openpype" +os.environ["AVALON_TIMEOUT"] = "1000" +os.environ["AVALON_DB"] = "avalon" +for path in [ + openpype_dir, + r"{}\repos\avalon-core".format(openpype_dir), + r"{}\.venv\Lib\site-packages".format(openpype_dir) +]: + sys.path.append(path) + +from Qt import QtWidgets, QtGui + + +class InstancesModel(QtGui.QStandardItemModel): + def set_report(self, report_data): + self.clear() + + root_item = self.invisibleRootItem() + + context_data = report_data["context"] + context_label = context_data["label"] or "Context" + + context_item = QtGui.QStandardItem(context_label) + + items = [context_item] + families = [] + instances_by_family = collections.defaultdict(list) + instances_by_id = {} + for instance_id, instance_detail in report_data["instances"].items(): + family = instance_detail["family"] + if family not in families: + families.append(family) + + label = instance_detail["label"] or instance_detail["name"] + + instance_item = QtGui.QStandardItem(label) + instances_by_id[instance_id] = instance_item + instances_by_family[family].append(instance_item) + + for family in families: + instance_items = instances_by_family[family] + family_item = QtGui.QStandardItem(family) + family_item.appendRows(instance_items) + items.append(family_item) + + root_item.appendRows(items) + + +class PluginsModel(QtGui.QStandardItemModel): + def set_report(self, report_data): + self.clear() + + plugins_data = report_data["plugins_data"] + + root_item = self.invisibleRootItem() + + items = [] + for plugin_detail in plugins_data: + item = QtGui.QStandardItem(plugin_detail["label"]) + items.append(item) + + root_item.appendRows(items) + + +class PublishLogViewerWindow(QtWidgets.QWidget): + default_width = 1000 + default_height = 600 + + def __init__(self, parent=None): + super(PublishLogViewerWindow, self).__init__(parent) + + instances_model = InstancesModel() + plugins_model = PluginsModel() + + instances_view = QtWidgets.QTreeView(self) + instances_view.setModel(instances_model) + + plugins_view = QtWidgets.QTreeView(self) + plugins_view.setModel(plugins_model) + + views_layout = QtWidgets.QHBoxLayout() + views_layout.addWidget(instances_view) + views_layout.addWidget(plugins_view) + + layout = QtWidgets.QHBoxLayout(self) + layout.addLayout(views_layout) + + self.resize(self.default_width, self.default_height) + + self._instances_view = instances_view + self._plugins_view = plugins_view + + self._instances_model = instances_model + self._plugins_model = plugins_model + + log_path = os.path.join(os.path.dirname(__file__), "logs.json") + with open(log_path, "r") as file_stream: + report_data = json.load(file_stream) + + plugins_model.set_report(report_data) + instances_model.set_report(report_data) + + +def main(): + """Main function for testing purposes.""" + app = QtWidgets.QApplication([]) + window = PublishLogViewerWindow() + window.show() + app.exec_() + + +if __name__ == "__main__": + main() From fcecf2cc6c5367a3b88fc50bccd104c55af61b47 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 23 Aug 2021 10:33:58 +0200 Subject: [PATCH 334/736] added instances list under warning title frame --- .../widgets/validations_widget.py | 108 +++++++++++++++--- 1 file changed, 91 insertions(+), 17 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/validations_widget.py b/openpype/tools/new_publisher/widgets/validations_widget.py index 9dcf3032d8..c0ff7b163a 100644 --- a/openpype/tools/new_publisher/widgets/validations_widget.py +++ b/openpype/tools/new_publisher/widgets/validations_widget.py @@ -3,7 +3,7 @@ try: except Exception: commonmark = None -from Qt import QtWidgets, QtCore +from Qt import QtWidgets, QtCore, QtGui class _ClickableFrame(QtWidgets.QFrame): @@ -29,25 +29,94 @@ class _ClickableFrame(QtWidgets.QFrame): super(_ClickableFrame, self).mouseReleaseEvent(event) -class ValidationErrorTitleFrame(_ClickableFrame): +class ValidationErrorInstanceList(QtWidgets.QListView): + def __init__(self, *args, **kwargs): + super(ValidationErrorInstanceList, self).__init__(*args, **kwargs) + + self.setObjectName("ValidationErrorInstanceList") + + self.setSelectionMode(QtWidgets.QListView.ExtendedSelection) + + def minimumSizeHint(self): + result = super(ValidationErrorInstanceList, self).minimumSizeHint() + result.setHeight(0) + return result + + def sizeHint(self): + row_count = self.model().rowCount() + height = 0 + if row_count > 0: + height = self.sizeHintForRow(0) * row_count + return QtCore.QSize(self.width(), height) + + +class ValidationErrorTitleWidget(QtWidgets.QWidget): selected = QtCore.Signal(int) def __init__(self, index, error_info, parent): - super(ValidationErrorTitleFrame, self).__init__(parent) - - self.setObjectName("ValidationErrorTitleFrame") - - exception = error_info["exception"] - label_widget = QtWidgets.QLabel(exception.title, self) - - layout = QtWidgets.QHBoxLayout(self) - layout.addWidget(label_widget) + super(ValidationErrorTitleWidget, self).__init__(parent) self._index = index self._error_info = error_info self._selected = False - self._mouse_pressed = False + title_frame = _ClickableFrame(self) + title_frame.setObjectName("ValidationErrorTitleFrame") + title_frame._mouse_release_callback = self._mouse_release_callback + + toggle_instance_btn = QtWidgets.QToolButton(title_frame) + toggle_instance_btn.setObjectName("ArrowBtn") + toggle_instance_btn.setArrowType(QtCore.Qt.RightArrow) + toggle_instance_btn.setMaximumWidth(14) + + exception = error_info["exception"] + label_widget = QtWidgets.QLabel(exception.title, title_frame) + + title_frame_layout = QtWidgets.QHBoxLayout(title_frame) + title_frame_layout.addWidget(toggle_instance_btn) + title_frame_layout.addWidget(label_widget) + + instances_model = QtGui.QStandardItemModel() + instances = error_info["instances"] + context_validation = False + if ( + not instances + or (len(instances) == 1 and instances[0] is None) + ): + context_validation = True + toggle_instance_btn.setArrowType(QtCore.Qt.NoArrow) + else: + items = [] + for instance in instances: + label = instance.data.get("label") or instance.data.get("name") + item = QtGui.QStandardItem(label) + item.setData(instance.id) + items.append(item) + break + instances_model.invisibleRootItem().appendRows(items) + + instances_view = ValidationErrorInstanceList(self) + instances_view.setModel(instances_model) + instances_view.setVisible(False) + + layout = QtWidgets.QVBoxLayout(self) + layout.setSpacing(0) + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(title_frame) + layout.addWidget(instances_view) + + if not context_validation: + toggle_instance_btn.clicked.connect(self._on_toggle_btn_click) + + self._title_frame = title_frame + + self._toggle_instance_btn = toggle_instance_btn + + self._instances_model = instances_model + self._instances_view = instances_view + + def _mouse_release_callback(self): + self.set_selected(True) @property def is_selected(self): @@ -62,8 +131,8 @@ class ValidationErrorTitleFrame(_ClickableFrame): def _change_style_property(self, selected): value = "1" if selected else "" - self.setProperty("selected", value) - self.style().polish(self) + self._title_frame.setProperty("selected", value) + self._title_frame.style().polish(self._title_frame) def set_selected(self, selected=None): if selected is None: @@ -77,8 +146,13 @@ class ValidationErrorTitleFrame(_ClickableFrame): if selected: self.selected.emit(self._index) - def _mouse_release_callback(self): - self.set_selected(True) + def _on_toggle_btn_click(self): + new_visible = not self._instances_view.isVisible() + self._instances_view.setVisible(new_visible) + if new_visible: + self._toggle_instance_btn.setArrowType(QtCore.Qt.DownArrow) + else: + self._toggle_instance_btn.setArrowType(QtCore.Qt.RightArrow) class ActionButton(QtWidgets.QPushButton): @@ -260,7 +334,7 @@ class ValidationsWidget(QtWidgets.QWidget): }) for idx, item in enumerate(errors_by_title): - widget = ValidationErrorTitleFrame(idx, item, self) + widget = ValidationErrorTitleWidget(idx, item, self) widget.selected.connect(self._on_select) self._errors_layout.addWidget(widget) self._title_widgets[idx] = widget From 4a76cfe1c51d1ba16062443e143196b2f2f3b1f0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 23 Aug 2021 10:48:22 +0200 Subject: [PATCH 335/736] instance view is offset --- openpype/style/style.css | 9 +++++++++ .../tools/new_publisher/widgets/validations_widget.py | 8 +++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/openpype/style/style.css b/openpype/style/style.css index 72569e457b..acca138c5a 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -723,6 +723,15 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { border-left-color: {palette:blue-light}; } +#ValidationErrorInstanceList { + border-radius: 0; +} + +#ValidationErrorInstanceList::item { + border-bottom: 1px solid {color:border}; + border-left: 1px solid {color:border}; +} + #ArrowBtn, #ArrowBtn:disabled, #ArrowBtn:hover { background: transparent; } diff --git a/openpype/tools/new_publisher/widgets/validations_widget.py b/openpype/tools/new_publisher/widgets/validations_widget.py index c0ff7b163a..9ce7ec0d4b 100644 --- a/openpype/tools/new_publisher/widgets/validations_widget.py +++ b/openpype/tools/new_publisher/widgets/validations_widget.py @@ -99,11 +99,17 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget): instances_view.setModel(instances_model) instances_view.setVisible(False) + view_layout = QtWidgets.QHBoxLayout() + view_layout.setContentsMargins(0, 0, 0, 0) + view_layout.setSpacing(0) + view_layout.addSpacing(14) + view_layout.addWidget(instances_view) + layout = QtWidgets.QVBoxLayout(self) layout.setSpacing(0) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(title_frame) - layout.addWidget(instances_view) + layout.addLayout(view_layout) if not context_validation: toggle_instance_btn.clicked.connect(self._on_toggle_btn_click) From f6e18fafa2c5c87f09a2e8aa8c316ce1c63f6b85 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 23 Aug 2021 10:50:23 +0200 Subject: [PATCH 336/736] added context validator --- .../publish/validate_context_with_error.py | 57 +++++++++++++++++++ .../plugins/publish/validate_with_error.py | 2 +- 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 openpype/hosts/testhost/plugins/publish/validate_context_with_error.py diff --git a/openpype/hosts/testhost/plugins/publish/validate_context_with_error.py b/openpype/hosts/testhost/plugins/publish/validate_context_with_error.py new file mode 100644 index 0000000000..46e996a569 --- /dev/null +++ b/openpype/hosts/testhost/plugins/publish/validate_context_with_error.py @@ -0,0 +1,57 @@ +import pyblish.api +from openpype.pipeline import PublishValidationError + + +class ValidateInstanceAssetRepair(pyblish.api.Action): + """Repair the instance asset.""" + + label = "Repair" + icon = "wrench" + on = "failed" + + def process(self, context, plugin): + pass + + +description = """ +## Publish plugins + +### Validate Scene Settings + +#### Skip Resolution Check for Tasks + +Set regex pattern(s) to look for in a Task name to skip resolution check against values from DB. + +#### Skip Timeline Check for Tasks + +Set regex pattern(s) to look for in a Task name to skip `frameStart`, `frameEnd` check against values from DB. + +### AfterEffects Submit to Deadline + +* `Use Published scene` - Set to True (green) when Deadline should take published scene as a source instead of uploaded local one. +* `Priority` - priority of job on farm +* `Primary Pool` - here is list of pool fetched from server you can select from. +* `Secondary Pool` +* `Frames Per Task` - number of sequence division between individual tasks (chunks) +making one job on farm. +""" + + +class ValidateContextWithError(pyblish.api.ContextPlugin): + """Validate the instance asset is the current selected context asset. + + As it might happen that multiple worfiles are opened, switching + between them would mess with selected context. + In that case outputs might be output under wrong asset! + + Repair action will use Context asset value (from Workfiles or Launcher) + Closing and reopening with Workfiles will refresh Context value. + """ + + label = "Validate Context With Error" + hosts = ["testhost"] + actions = [ValidateInstanceAssetRepair] + order = pyblish.api.ValidatorOrder + + def process(self, context): + raise PublishValidationError("Crashing", "Context error", description) diff --git a/openpype/hosts/testhost/plugins/publish/validate_with_error.py b/openpype/hosts/testhost/plugins/publish/validate_with_error.py index a830629cd8..5a2888a8b0 100644 --- a/openpype/hosts/testhost/plugins/publish/validate_with_error.py +++ b/openpype/hosts/testhost/plugins/publish/validate_with_error.py @@ -54,4 +54,4 @@ class ValidateWithError(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder def process(self, instance): - raise PublishValidationError("Crashing", "Errored out", description) + raise PublishValidationError("Crashing", "Instance error", description) From 802f8926df83564955231a631004240b8eb39ff7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 23 Aug 2021 11:06:32 +0200 Subject: [PATCH 337/736] use less orange --- openpype/style/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/style/style.css b/openpype/style/style.css index acca138c5a..776af2ef1b 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -694,7 +694,7 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { } #PublishInfoFrame[state="2"] { - background: #ff9900; + background: #ffc671; } #PublishInfoFrame QLabel { From e1c2645cc8fdfb3110b2087d4ec971f90a1416d8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 23 Aug 2021 12:30:45 +0200 Subject: [PATCH 338/736] added basic log viewer for publishing --- .../publish_log_viewer/window.py | 272 +++++++++++++++--- 1 file changed, 232 insertions(+), 40 deletions(-) diff --git a/openpype/tools/new_publisher/publish_log_viewer/window.py b/openpype/tools/new_publisher/publish_log_viewer/window.py index 01d8b853a8..96de03b299 100644 --- a/openpype/tools/new_publisher/publish_log_viewer/window.py +++ b/openpype/tools/new_publisher/publish_log_viewer/window.py @@ -1,6 +1,8 @@ import os import sys import json +import copy +import uuid import collections openpype_dir = r"C:\Users\jakub.trllo\Desktop\pype\pype3" @@ -19,62 +21,175 @@ for path in [ ]: sys.path.append(path) -from Qt import QtWidgets, QtGui +from Qt import QtWidgets, QtCore, QtGui + +from openpype import style + +ITEM_ID_ROLE = QtCore.Qt.UserRole + 1 + + +class PluginItem: + def __init__(self, plugin_data): + self._id = uuid.uuid4() + + self.name = plugin_data["name"] + self.label = plugin_data["label"] + self.order = plugin_data["order"] + self.skipped = plugin_data["skipped"] + + logs = [] + for instance_data in plugin_data["instances_data"]: + logs.extend(copy.deepcopy(instance_data["logs"])) + + self.logs = logs + + @property + def id(self): + return self._id + + +class InstanceItem: + def __init__(self, instance_id, instance_data, report_data): + self._id = instance_id + self.label = instance_data.get("label") or instance_data.get("name") + self.family = instance_data.get("family") + + logs = [] + for plugin_data in report_data["plugins_data"]: + for instance_data_item in plugin_data["instances_data"]: + if instance_data_item["id"] == self._id: + logs.extend(copy.deepcopy(instance_data_item["logs"])) + + self.logs = logs + + @property + def id(self): + return self._id + + +class PublishReport: + def __init__(self, report_data): + data = copy.deepcopy(report_data) + + context_data = data["context"] + context_data["name"] = "context" + context_data["label"] = context_data["label"] or "Context" + + instance_items_by_id = {} + instance_items_by_family = {} + context_item = InstanceItem(None, context_data, data) + instance_items_by_id[context_item.id] = context_item + instance_items_by_family[context_item.family] = [context_item] + + for instance_id, instance_data in data["instances"].items(): + item = InstanceItem(instance_id, instance_data, data) + instance_items_by_id[item.id] = item + if item.family not in instance_items_by_family: + instance_items_by_family[item.family] = [] + instance_items_by_family[item.family].append(item) + + all_logs = [] + plugins_items_by_id = {} + plugins_id_order = [] + for plugin_data in data["plugins_data"]: + item = PluginItem(plugin_data) + plugins_id_order.append(item.id) + plugins_items_by_id[item.id] = item + all_logs.extend(copy.deepcopy(item.logs)) + + self.instance_items_by_id = instance_items_by_id + self.instance_items_by_family = instance_items_by_family + + self.plugins_id_order = plugins_id_order + self.plugins_items_by_id = plugins_items_by_id + + self.logs = all_logs class InstancesModel(QtGui.QStandardItemModel): - def set_report(self, report_data): + def set_report(self, report_item): self.clear() root_item = self.invisibleRootItem() - context_data = report_data["context"] - context_label = context_data["label"] or "Context" + families = set(report_item.instance_items_by_family.keys()) + families.remove(None) + all_families = list(sorted(families)) + all_families.insert(0, None) - context_item = QtGui.QStandardItem(context_label) + family_items = [] + for family in all_families: + items = [] + instance_items = report_item.instance_items_by_family[family] + for instance_item in instance_items: + item = QtGui.QStandardItem(instance_item.label) + item.setData(instance_item.id, ITEM_ID_ROLE) + items.append(item) - items = [context_item] - families = [] - instances_by_family = collections.defaultdict(list) - instances_by_id = {} - for instance_id, instance_detail in report_data["instances"].items(): - family = instance_detail["family"] - if family not in families: - families.append(family) + if family is None: + family_items.extend(items) + continue - label = instance_detail["label"] or instance_detail["name"] - - instance_item = QtGui.QStandardItem(label) - instances_by_id[instance_id] = instance_item - instances_by_family[family].append(instance_item) - - for family in families: - instance_items = instances_by_family[family] family_item = QtGui.QStandardItem(family) - family_item.appendRows(instance_items) - items.append(family_item) + family_item.appendRows(items) + family_items.append(family_item) - root_item.appendRows(items) + root_item.appendRows(family_items) class PluginsModel(QtGui.QStandardItemModel): - def set_report(self, report_data): + def set_report(self, report_item): self.clear() - plugins_data = report_data["plugins_data"] - root_item = self.invisibleRootItem() items = [] - for plugin_detail in plugins_data: - item = QtGui.QStandardItem(plugin_detail["label"]) + for plugin_id in report_item.plugins_id_order: + plugin_item = report_item.plugins_items_by_id[plugin_id] + item = QtGui.QStandardItem(plugin_item.label) + item.setData(plugin_item.id, ITEM_ID_ROLE) items.append(item) root_item.appendRows(items) +class DetailsWidget(QtWidgets.QWidget): + def __init__(self, parent): + super(DetailsWidget, self).__init__(parent) + + output_widget = QtWidgets.QPlainTextEdit(self) + output_widget.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) + output_widget.setObjectName("PublishLogConsole") + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(output_widget) + + self._output_widget = output_widget + + def clear(self): + self._output_widget.setPlainText("") + + def set_logs(self, logs): + lines = [] + for log in logs: + if log["type"] == "record": + message = "{}: {}".format(log["levelname"], log["msg"]) + + lines.append(message) + exc_info = log["exc_info"] + if exc_info: + lines.append(exc_info) + + else: + print(log["type"]) + + text = "\n".join(lines) + self._output_widget.setPlainText(text) + + class PublishLogViewerWindow(QtWidgets.QWidget): - default_width = 1000 + default_width = 1200 default_height = 600 def __init__(self, parent=None): @@ -85,18 +200,33 @@ class PublishLogViewerWindow(QtWidgets.QWidget): instances_view = QtWidgets.QTreeView(self) instances_view.setModel(instances_model) + # instances_view.setIndentation(0) + instances_view.setHeaderHidden(True) + instances_view.setEditTriggers(QtWidgets.QTreeView.NoEditTriggers) plugins_view = QtWidgets.QTreeView(self) plugins_view.setModel(plugins_model) + # plugins_view.setIndentation(0) + plugins_view.setHeaderHidden(True) + plugins_view.setEditTriggers(QtWidgets.QTreeView.NoEditTriggers) - views_layout = QtWidgets.QHBoxLayout() - views_layout.addWidget(instances_view) - views_layout.addWidget(plugins_view) + details_widget = DetailsWidget(self) layout = QtWidgets.QHBoxLayout(self) - layout.addLayout(views_layout) + layout.addWidget(instances_view) + layout.addWidget(plugins_view) + layout.addWidget(details_widget, 1) - self.resize(self.default_width, self.default_height) + instances_view.selectionModel().selectionChanged.connect( + self._on_instance_change + ) + plugins_view.selectionModel().selectionChanged.connect( + self._on_plugin_change + ) + + self._ignore_selection_changes = False + self._report_item = None + self._details_widget = details_widget self._instances_view = instances_view self._plugins_view = plugins_view @@ -104,18 +234,80 @@ class PublishLogViewerWindow(QtWidgets.QWidget): self._instances_model = instances_model self._plugins_model = plugins_model - log_path = os.path.join(os.path.dirname(__file__), "logs.json") - with open(log_path, "r") as file_stream: - report_data = json.load(file_stream) + self.resize(self.default_width, self.default_height) + self.setStyleSheet(style.load_stylesheet()) - plugins_model.set_report(report_data) - instances_model.set_report(report_data) + def _on_instance_change(self, *_args): + if self._ignore_selection_changes: + return + + valid_index = None + for index in self._instances_view.selectedIndexes(): + if index.isValid(): + valid_index = index + break + + if valid_index is None: + print("NOT INSTANCE") + return + + if self._plugins_view.selectedIndexes(): + self._ignore_selection_changes = True + self._plugins_view.selectionModel().clearSelection() + self._ignore_selection_changes = False + + plugin_id = valid_index.data(ITEM_ID_ROLE) + instance_item = self._report_item.instance_items_by_id[plugin_id] + self._details_widget.set_logs(instance_item.logs) + + def _on_plugin_change(self, *_args): + if self._ignore_selection_changes: + return + + valid_index = None + for index in self._plugins_view.selectedIndexes(): + if index.isValid(): + valid_index = index + break + + if valid_index is None: + self._details_widget.set_logs(self._report_item.logs) + return + + if self._instances_view.selectedIndexes(): + self._ignore_selection_changes = True + self._instances_view.selectionModel().clearSelection() + self._ignore_selection_changes = False + + plugin_id = valid_index.data(ITEM_ID_ROLE) + plugin_item = self._report_item.plugins_items_by_id[plugin_id] + self._details_widget.set_logs(plugin_item.logs) + + def set_report(self, report_data): + self._ignore_selection_changes = True + + report_item = PublishReport(report_data) + self._report_item = report_item + + self._instances_model.set_report(report_item) + self._plugins_model.set_report(report_item) + + self._details_widget.set_logs(report_item.logs) + + self._ignore_selection_changes = False def main(): """Main function for testing purposes.""" app = QtWidgets.QApplication([]) window = PublishLogViewerWindow() + + log_path = os.path.join(os.path.dirname(__file__), "logs.json") + with open(log_path, "r") as file_stream: + report_data = json.load(file_stream) + + window.set_report(report_data) + window.show() app.exec_() From 53ab72c77200ef321655cb096b8c3c21862f5571 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 23 Aug 2021 12:34:45 +0200 Subject: [PATCH 339/736] fixed removed lines during resolving --- openpype/style/style.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/style/style.css b/openpype/style/style.css index 3c34c37e1a..b5cf507596 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -637,6 +637,11 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { border-radius: 0.1em; } +/* Python console interpreter */ +#PythonInterpreterOutput, #PythonCodeEditor { + font-family: "Roboto Mono"; +} + #VariantInput[state="new"], #VariantInput[state="new"]:focus, #VariantInput[state="new"]:hover { border-color: #7AAB8F; } From b12d44a390cd87518483c468904a94aa40cb8a5a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 23 Aug 2021 12:36:55 +0200 Subject: [PATCH 340/736] changed fornt for publish log console --- openpype/style/style.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/style/style.css b/openpype/style/style.css index b5cf507596..e37303f501 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -642,6 +642,11 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { font-family: "Roboto Mono"; } +/* New Create/Publish UI */ +#PublishLogConsole { + font-family: "Roboto Mono"; +} + #VariantInput[state="new"], #VariantInput[state="new"]:focus, #VariantInput[state="new"]:hover { border-color: #7AAB8F; } From 7800de0def70b77c700953cbb5338e2ed061c930 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 23 Aug 2021 13:39:11 +0200 Subject: [PATCH 341/736] separated log viewer into widgets --- .../publish_log_viewer/__init__.py | 7 + .../publish_log_viewer/widgets.py | 269 ++++++++++++++++++ .../publish_log_viewer/window.py | 259 +---------------- 3 files changed, 281 insertions(+), 254 deletions(-) create mode 100644 openpype/tools/new_publisher/publish_log_viewer/__init__.py create mode 100644 openpype/tools/new_publisher/publish_log_viewer/widgets.py diff --git a/openpype/tools/new_publisher/publish_log_viewer/__init__.py b/openpype/tools/new_publisher/publish_log_viewer/__init__.py new file mode 100644 index 0000000000..156bd05305 --- /dev/null +++ b/openpype/tools/new_publisher/publish_log_viewer/__init__.py @@ -0,0 +1,7 @@ +from .widgets import ( + PublishLogViewerWindow +) + +__all__ = ( + "PublishLogViewerWindow", +) diff --git a/openpype/tools/new_publisher/publish_log_viewer/widgets.py b/openpype/tools/new_publisher/publish_log_viewer/widgets.py new file mode 100644 index 0000000000..4ba8d3f6a7 --- /dev/null +++ b/openpype/tools/new_publisher/publish_log_viewer/widgets.py @@ -0,0 +1,269 @@ +import copy +import uuid + +from Qt import QtWidgets, QtCore, QtGui + + +ITEM_ID_ROLE = QtCore.Qt.UserRole + 1 + + +class PluginItem: + def __init__(self, plugin_data): + self._id = uuid.uuid4() + + self.name = plugin_data["name"] + self.label = plugin_data["label"] + self.order = plugin_data["order"] + self.skipped = plugin_data["skipped"] + + logs = [] + for instance_data in plugin_data["instances_data"]: + logs.extend(copy.deepcopy(instance_data["logs"])) + + self.logs = logs + + @property + def id(self): + return self._id + + +class InstanceItem: + def __init__(self, instance_id, instance_data, report_data): + self._id = instance_id + self.label = instance_data.get("label") or instance_data.get("name") + self.family = instance_data.get("family") + + logs = [] + for plugin_data in report_data["plugins_data"]: + for instance_data_item in plugin_data["instances_data"]: + if instance_data_item["id"] == self._id: + logs.extend(copy.deepcopy(instance_data_item["logs"])) + + self.logs = logs + + @property + def id(self): + return self._id + + +class PublishReport: + def __init__(self, report_data): + data = copy.deepcopy(report_data) + + context_data = data["context"] + context_data["name"] = "context" + context_data["label"] = context_data["label"] or "Context" + + instance_items_by_id = {} + instance_items_by_family = {} + context_item = InstanceItem(None, context_data, data) + instance_items_by_id[context_item.id] = context_item + instance_items_by_family[context_item.family] = [context_item] + + for instance_id, instance_data in data["instances"].items(): + item = InstanceItem(instance_id, instance_data, data) + instance_items_by_id[item.id] = item + if item.family not in instance_items_by_family: + instance_items_by_family[item.family] = [] + instance_items_by_family[item.family].append(item) + + all_logs = [] + plugins_items_by_id = {} + plugins_id_order = [] + for plugin_data in data["plugins_data"]: + item = PluginItem(plugin_data) + plugins_id_order.append(item.id) + plugins_items_by_id[item.id] = item + all_logs.extend(copy.deepcopy(item.logs)) + + self.instance_items_by_id = instance_items_by_id + self.instance_items_by_family = instance_items_by_family + + self.plugins_id_order = plugins_id_order + self.plugins_items_by_id = plugins_items_by_id + + self.logs = all_logs + + +class InstancesModel(QtGui.QStandardItemModel): + def set_report(self, report_item): + self.clear() + + root_item = self.invisibleRootItem() + + families = set(report_item.instance_items_by_family.keys()) + families.remove(None) + all_families = list(sorted(families)) + all_families.insert(0, None) + + family_items = [] + for family in all_families: + items = [] + instance_items = report_item.instance_items_by_family[family] + for instance_item in instance_items: + item = QtGui.QStandardItem(instance_item.label) + item.setData(instance_item.id, ITEM_ID_ROLE) + items.append(item) + + if family is None: + family_items.extend(items) + continue + + family_item = QtGui.QStandardItem(family) + family_item.appendRows(items) + family_items.append(family_item) + + root_item.appendRows(family_items) + + +class PluginsModel(QtGui.QStandardItemModel): + def set_report(self, report_item): + self.clear() + + root_item = self.invisibleRootItem() + + items = [] + for plugin_id in report_item.plugins_id_order: + plugin_item = report_item.plugins_items_by_id[plugin_id] + item = QtGui.QStandardItem(plugin_item.label) + item.setData(plugin_item.id, ITEM_ID_ROLE) + items.append(item) + + root_item.appendRows(items) + + +class DetailsWidget(QtWidgets.QWidget): + def __init__(self, parent): + super(DetailsWidget, self).__init__(parent) + + output_widget = QtWidgets.QPlainTextEdit(self) + output_widget.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) + output_widget.setObjectName("PublishLogConsole") + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(output_widget) + + self._output_widget = output_widget + + def clear(self): + self._output_widget.setPlainText("") + + def set_logs(self, logs): + lines = [] + for log in logs: + if log["type"] == "record": + message = "{}: {}".format(log["levelname"], log["msg"]) + + lines.append(message) + exc_info = log["exc_info"] + if exc_info: + lines.append(exc_info) + + else: + print(log["type"]) + + text = "\n".join(lines) + self._output_widget.setPlainText(text) + + +class PublishLogViewerWidget(QtWidgets.QWidget): + def __init__(self, parent=None): + super(PublishLogViewerWidget, self).__init__(parent) + + instances_model = InstancesModel() + plugins_model = PluginsModel() + + instances_view = QtWidgets.QTreeView(self) + instances_view.setModel(instances_model) + # instances_view.setIndentation(0) + instances_view.setHeaderHidden(True) + instances_view.setEditTriggers(QtWidgets.QTreeView.NoEditTriggers) + + plugins_view = QtWidgets.QTreeView(self) + plugins_view.setModel(plugins_model) + # plugins_view.setIndentation(0) + plugins_view.setHeaderHidden(True) + plugins_view.setEditTriggers(QtWidgets.QTreeView.NoEditTriggers) + + details_widget = DetailsWidget(self) + + layout = QtWidgets.QHBoxLayout(self) + layout.addWidget(instances_view) + layout.addWidget(plugins_view) + layout.addWidget(details_widget, 1) + + instances_view.selectionModel().selectionChanged.connect( + self._on_instance_change + ) + plugins_view.selectionModel().selectionChanged.connect( + self._on_plugin_change + ) + + self._ignore_selection_changes = False + self._report_item = None + self._details_widget = details_widget + + self._instances_view = instances_view + self._plugins_view = plugins_view + + self._instances_model = instances_model + self._plugins_model = plugins_model + + def _on_instance_change(self, *_args): + if self._ignore_selection_changes: + return + + valid_index = None + for index in self._instances_view.selectedIndexes(): + if index.isValid(): + valid_index = index + break + + if valid_index is None: + return + + if self._plugins_view.selectedIndexes(): + self._ignore_selection_changes = True + self._plugins_view.selectionModel().clearSelection() + self._ignore_selection_changes = False + + plugin_id = valid_index.data(ITEM_ID_ROLE) + instance_item = self._report_item.instance_items_by_id[plugin_id] + self._details_widget.set_logs(instance_item.logs) + + def _on_plugin_change(self, *_args): + if self._ignore_selection_changes: + return + + valid_index = None + for index in self._plugins_view.selectedIndexes(): + if index.isValid(): + valid_index = index + break + + if valid_index is None: + self._details_widget.set_logs(self._report_item.logs) + return + + if self._instances_view.selectedIndexes(): + self._ignore_selection_changes = True + self._instances_view.selectionModel().clearSelection() + self._ignore_selection_changes = False + + plugin_id = valid_index.data(ITEM_ID_ROLE) + plugin_item = self._report_item.plugins_items_by_id[plugin_id] + self._details_widget.set_logs(plugin_item.logs) + + def set_report(self, report_data): + self._ignore_selection_changes = True + + report_item = PublishReport(report_data) + self._report_item = report_item + + self._instances_model.set_report(report_item) + self._plugins_model.set_report(report_item) + + self._details_widget.set_logs(report_item.logs) + + self._ignore_selection_changes = False diff --git a/openpype/tools/new_publisher/publish_log_viewer/window.py b/openpype/tools/new_publisher/publish_log_viewer/window.py index 96de03b299..914215210d 100644 --- a/openpype/tools/new_publisher/publish_log_viewer/window.py +++ b/openpype/tools/new_publisher/publish_log_viewer/window.py @@ -24,168 +24,7 @@ for path in [ from Qt import QtWidgets, QtCore, QtGui from openpype import style - -ITEM_ID_ROLE = QtCore.Qt.UserRole + 1 - - -class PluginItem: - def __init__(self, plugin_data): - self._id = uuid.uuid4() - - self.name = plugin_data["name"] - self.label = plugin_data["label"] - self.order = plugin_data["order"] - self.skipped = plugin_data["skipped"] - - logs = [] - for instance_data in plugin_data["instances_data"]: - logs.extend(copy.deepcopy(instance_data["logs"])) - - self.logs = logs - - @property - def id(self): - return self._id - - -class InstanceItem: - def __init__(self, instance_id, instance_data, report_data): - self._id = instance_id - self.label = instance_data.get("label") or instance_data.get("name") - self.family = instance_data.get("family") - - logs = [] - for plugin_data in report_data["plugins_data"]: - for instance_data_item in plugin_data["instances_data"]: - if instance_data_item["id"] == self._id: - logs.extend(copy.deepcopy(instance_data_item["logs"])) - - self.logs = logs - - @property - def id(self): - return self._id - - -class PublishReport: - def __init__(self, report_data): - data = copy.deepcopy(report_data) - - context_data = data["context"] - context_data["name"] = "context" - context_data["label"] = context_data["label"] or "Context" - - instance_items_by_id = {} - instance_items_by_family = {} - context_item = InstanceItem(None, context_data, data) - instance_items_by_id[context_item.id] = context_item - instance_items_by_family[context_item.family] = [context_item] - - for instance_id, instance_data in data["instances"].items(): - item = InstanceItem(instance_id, instance_data, data) - instance_items_by_id[item.id] = item - if item.family not in instance_items_by_family: - instance_items_by_family[item.family] = [] - instance_items_by_family[item.family].append(item) - - all_logs = [] - plugins_items_by_id = {} - plugins_id_order = [] - for plugin_data in data["plugins_data"]: - item = PluginItem(plugin_data) - plugins_id_order.append(item.id) - plugins_items_by_id[item.id] = item - all_logs.extend(copy.deepcopy(item.logs)) - - self.instance_items_by_id = instance_items_by_id - self.instance_items_by_family = instance_items_by_family - - self.plugins_id_order = plugins_id_order - self.plugins_items_by_id = plugins_items_by_id - - self.logs = all_logs - - -class InstancesModel(QtGui.QStandardItemModel): - def set_report(self, report_item): - self.clear() - - root_item = self.invisibleRootItem() - - families = set(report_item.instance_items_by_family.keys()) - families.remove(None) - all_families = list(sorted(families)) - all_families.insert(0, None) - - family_items = [] - for family in all_families: - items = [] - instance_items = report_item.instance_items_by_family[family] - for instance_item in instance_items: - item = QtGui.QStandardItem(instance_item.label) - item.setData(instance_item.id, ITEM_ID_ROLE) - items.append(item) - - if family is None: - family_items.extend(items) - continue - - family_item = QtGui.QStandardItem(family) - family_item.appendRows(items) - family_items.append(family_item) - - root_item.appendRows(family_items) - - -class PluginsModel(QtGui.QStandardItemModel): - def set_report(self, report_item): - self.clear() - - root_item = self.invisibleRootItem() - - items = [] - for plugin_id in report_item.plugins_id_order: - plugin_item = report_item.plugins_items_by_id[plugin_id] - item = QtGui.QStandardItem(plugin_item.label) - item.setData(plugin_item.id, ITEM_ID_ROLE) - items.append(item) - - root_item.appendRows(items) - - -class DetailsWidget(QtWidgets.QWidget): - def __init__(self, parent): - super(DetailsWidget, self).__init__(parent) - - output_widget = QtWidgets.QPlainTextEdit(self) - output_widget.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) - output_widget.setObjectName("PublishLogConsole") - - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.addWidget(output_widget) - - self._output_widget = output_widget - - def clear(self): - self._output_widget.setPlainText("") - - def set_logs(self, logs): - lines = [] - for log in logs: - if log["type"] == "record": - message = "{}: {}".format(log["levelname"], log["msg"]) - - lines.append(message) - exc_info = log["exc_info"] - if exc_info: - lines.append(exc_info) - - else: - print(log["type"]) - - text = "\n".join(lines) - self._output_widget.setPlainText(text) +from widgets import PublishLogViewerWidget class PublishLogViewerWindow(QtWidgets.QWidget): @@ -195,106 +34,18 @@ class PublishLogViewerWindow(QtWidgets.QWidget): def __init__(self, parent=None): super(PublishLogViewerWindow, self).__init__(parent) - instances_model = InstancesModel() - plugins_model = PluginsModel() - - instances_view = QtWidgets.QTreeView(self) - instances_view.setModel(instances_model) - # instances_view.setIndentation(0) - instances_view.setHeaderHidden(True) - instances_view.setEditTriggers(QtWidgets.QTreeView.NoEditTriggers) - - plugins_view = QtWidgets.QTreeView(self) - plugins_view.setModel(plugins_model) - # plugins_view.setIndentation(0) - plugins_view.setHeaderHidden(True) - plugins_view.setEditTriggers(QtWidgets.QTreeView.NoEditTriggers) - - details_widget = DetailsWidget(self) + main_widget = PublishLogViewerWidget(self) layout = QtWidgets.QHBoxLayout(self) - layout.addWidget(instances_view) - layout.addWidget(plugins_view) - layout.addWidget(details_widget, 1) + layout.addWidget(main_widget) - instances_view.selectionModel().selectionChanged.connect( - self._on_instance_change - ) - plugins_view.selectionModel().selectionChanged.connect( - self._on_plugin_change - ) - - self._ignore_selection_changes = False - self._report_item = None - self._details_widget = details_widget - - self._instances_view = instances_view - self._plugins_view = plugins_view - - self._instances_model = instances_model - self._plugins_model = plugins_model + self._main_widget = main_widget self.resize(self.default_width, self.default_height) self.setStyleSheet(style.load_stylesheet()) - def _on_instance_change(self, *_args): - if self._ignore_selection_changes: - return - - valid_index = None - for index in self._instances_view.selectedIndexes(): - if index.isValid(): - valid_index = index - break - - if valid_index is None: - print("NOT INSTANCE") - return - - if self._plugins_view.selectedIndexes(): - self._ignore_selection_changes = True - self._plugins_view.selectionModel().clearSelection() - self._ignore_selection_changes = False - - plugin_id = valid_index.data(ITEM_ID_ROLE) - instance_item = self._report_item.instance_items_by_id[plugin_id] - self._details_widget.set_logs(instance_item.logs) - - def _on_plugin_change(self, *_args): - if self._ignore_selection_changes: - return - - valid_index = None - for index in self._plugins_view.selectedIndexes(): - if index.isValid(): - valid_index = index - break - - if valid_index is None: - self._details_widget.set_logs(self._report_item.logs) - return - - if self._instances_view.selectedIndexes(): - self._ignore_selection_changes = True - self._instances_view.selectionModel().clearSelection() - self._ignore_selection_changes = False - - plugin_id = valid_index.data(ITEM_ID_ROLE) - plugin_item = self._report_item.plugins_items_by_id[plugin_id] - self._details_widget.set_logs(plugin_item.logs) - def set_report(self, report_data): - self._ignore_selection_changes = True - - report_item = PublishReport(report_data) - self._report_item = report_item - - self._instances_model.set_report(report_item) - self._plugins_model.set_report(report_item) - - self._details_widget.set_logs(report_item.logs) - - self._ignore_selection_changes = False + self._main_widget.set_report(report_data) def main(): From 4935791ecf86c4787a22a17ead6a51fc68893acc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 23 Aug 2021 13:43:32 +0200 Subject: [PATCH 342/736] added todo --- openpype/tools/new_publisher/publish_log_viewer/window.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/publish_log_viewer/window.py b/openpype/tools/new_publisher/publish_log_viewer/window.py index 914215210d..517b0aadee 100644 --- a/openpype/tools/new_publisher/publish_log_viewer/window.py +++ b/openpype/tools/new_publisher/publish_log_viewer/window.py @@ -24,10 +24,14 @@ for path in [ from Qt import QtWidgets, QtCore, QtGui from openpype import style -from widgets import PublishLogViewerWidget +if __package__: + from .widgets import PublishLogViewerWidget +else: + from widgets import PublishLogViewerWidget class PublishLogViewerWindow(QtWidgets.QWidget): + # TODO add buttons to be able load report file or paste content of report default_width = 1200 default_height = 600 From 60e658afad47e7e776136ab6f3ba14d233ab09e1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 23 Aug 2021 14:34:09 +0200 Subject: [PATCH 343/736] added show details button --- .../new_publisher/widgets/publish_widget.py | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/publish_widget.py b/openpype/tools/new_publisher/widgets/publish_widget.py index d781b79d37..2dfdd6e6d6 100644 --- a/openpype/tools/new_publisher/widgets/publish_widget.py +++ b/openpype/tools/new_publisher/widgets/publish_widget.py @@ -51,7 +51,12 @@ class PublishFrame(QtWidgets.QFrame): progress_widget = QtWidgets.QProgressBar(content_widget) copy_log_btn = QtWidgets.QPushButton("Copy log", content_widget) - # copy_log_btn.setVisible(False) + copy_log_btn.setVisible(False) + + show_details_btn = QtWidgets.QPushButton( + "Show details", content_widget + ) + show_details_btn.setVisible(False) reset_btn = QtWidgets.QPushButton(content_widget) reset_btn.setIcon(get_icon("refresh")) @@ -67,6 +72,7 @@ class PublishFrame(QtWidgets.QFrame): footer_layout = QtWidgets.QHBoxLayout() footer_layout.addWidget(copy_log_btn, 0) + footer_layout.addWidget(show_details_btn, 0) footer_layout.addWidget(message_label_bottom, 1) footer_layout.addWidget(reset_btn, 0) footer_layout.addWidget(stop_btn, 0) @@ -91,6 +97,7 @@ class PublishFrame(QtWidgets.QFrame): main_layout.addWidget(info_frame, 0) copy_log_btn.clicked.connect(self._on_copy_log) + show_details_btn.clicked.connect(self._on_show_details) reset_btn.clicked.connect(self._on_reset_clicked) stop_btn.clicked.connect(self._on_stop_clicked) @@ -118,6 +125,7 @@ class PublishFrame(QtWidgets.QFrame): self.progress_widget = progress_widget self.copy_log_btn = copy_log_btn + self.show_details_btn = show_details_btn self.message_label_bottom = message_label_bottom self.reset_btn = reset_btn self.stop_btn = stop_btn @@ -132,7 +140,8 @@ class PublishFrame(QtWidgets.QFrame): self.main_label.setText("Hit publish! (if you want)") self.message_label.setText("") self.message_label_bottom.setText("") - # self.copy_log_btn.setVisible(False) + self.copy_log_btn.setVisible(False) + self.show_details_btn.setVisible(False) self.reset_btn.setEnabled(True) self.stop_btn.setEnabled(False) @@ -149,6 +158,8 @@ class PublishFrame(QtWidgets.QFrame): self._change_bg_property() self._set_progress_visibility(True) self.main_label.setText("Publishing...") + self.copy_log_btn.setVisible(False) + self.show_details_btn.setVisible(False) self.reset_btn.setEnabled(False) self.stop_btn.setEnabled(True) @@ -189,6 +200,8 @@ class PublishFrame(QtWidgets.QFrame): def _on_publish_stop(self): self.progress_widget.setValue(self.controller.publish_progress) + self.copy_log_btn.setVisible(True) + self.show_details_btn.setVisible(True) self.reset_btn.setEnabled(True) self.stop_btn.setEnabled(False) @@ -236,7 +249,6 @@ class PublishFrame(QtWidgets.QFrame): self.message_label.setText(msg) self.message_label_bottom.setText("") self._set_success_property(0) - # self.copy_log_btn.setVisible(True) def _set_validation_errors(self, validation_errors): self.main_label.setText("Your publish didn't pass studio validations") @@ -274,6 +286,9 @@ class PublishFrame(QtWidgets.QFrame): mime_data ) + def _on_show_details(self): + pass + def _on_reset_clicked(self): self.controller.reset() From dcda171dbc410f196a8f21e8ae37ad210b3bf705 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 24 Aug 2021 18:12:48 +0200 Subject: [PATCH 344/736] use custom publish plugins `discover` --- openpype/pipeline/create/context.py | 9 +- openpype/pipeline/publish/__init__.py | 9 +- openpype/pipeline/publish/lib.py | 126 ++++++++++++++++++++++++++ 3 files changed, 141 insertions(+), 3 deletions(-) create mode 100644 openpype/pipeline/publish/lib.py diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 960f5ab6df..1692c91d51 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -427,6 +427,7 @@ class CreateContext: self.instances = [] self.creators = {} + self.publish_discover_result = None self.publish_plugins = [] self.plugins_with_defs = [] self._attr_plugins_by_family = {} @@ -448,18 +449,22 @@ class CreateContext: def reset_plugins(self): import avalon.api - import pyblish.api + import pyblish.logic from openpype.pipeline import OpenPypePyblishPluginMixin + from openpype.pipeline.publish import publish_plugins_discover # Reset publish plugins self._attr_plugins_by_family = {} - publish_plugins = pyblish.api.discover() + discover_result = publish_plugins_discover() + publish_plugins = discover_result.plugins + targets = pyblish.logic.registered_targets() or ["default"] plugins_by_targets = pyblish.logic.plugins_by_targets( publish_plugins, targets ) + self.publish_discover_result = discover_result self.publish_plugins = plugins_by_targets # Collect plugins that can have attribute definitions diff --git a/openpype/pipeline/publish/__init__.py b/openpype/pipeline/publish/__init__.py index 4a18c1a110..7eed98e73a 100644 --- a/openpype/pipeline/publish/__init__.py +++ b/openpype/pipeline/publish/__init__.py @@ -4,8 +4,15 @@ from .publish_plugins import ( OpenPypePyblishPluginMixin ) +from .lib import ( + publish_plugins_discover +) + + __all__ = ( "PublishValidationError", "KnownPublishError", - "OpenPypePyblishPluginMixin" + "OpenPypePyblishPluginMixin", + + "publish_plugins_discover" ) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py new file mode 100644 index 0000000000..ba44066484 --- /dev/null +++ b/openpype/pipeline/publish/lib.py @@ -0,0 +1,126 @@ +import os +import sys +import types + +import six +import pyblish.logic + + +class DiscoverResult: + """Hold result of publish plugins discovery. + + Stores discovered plugins duplicated plugins and file paths which + crashed on execution of file. + """ + def __init__(self): + self.plugins = [] + self.crashed_file_paths = {} + self.duplicated_plugins = [] + + def __iter__(self): + for plugin in self.plugins: + yield plugin + + def __getitem__(self, item): + return self.plugins[item] + + def __setitem__(self, item, value): + self.plugins[item] = value + + +def publish_plugins_discover(paths=None): + """Find and return available pyblish plug-ins + + Overriden function from `pyblish` module to be able collect crashed files + and reason of their crash. + + Arguments: + paths (list, optional): Paths to discover plug-ins from. + If no paths are provided, all paths are searched. + + """ + + # The only difference with `pyblish.api.discover` + result = DiscoverResult() + + plugins = dict() + plugin_names = [] + + allow_duplicates = pyblish.logic.ALLOW_DUPLICATES + log = pyblish.logic.log + + # Include plug-ins from registered paths + if not paths: + paths = pyblish.logic.plugin_paths() + + for path in paths: + path = os.path.normpath(path) + if not os.path.isdir(path): + continue + + for fname in os.listdir(path): + if fname.startswith("_"): + continue + + abspath = os.path.join(path, fname) + + if not os.path.isfile(abspath): + continue + + mod_name, mod_ext = os.path.splitext(fname) + + if not mod_ext == ".py": + continue + + module = types.ModuleType(mod_name) + module.__file__ = abspath + + try: + with open(abspath, "rb") as f: + six.exec_(f.read(), module.__dict__) + + # Store reference to original module, to avoid + # garbage collection from collecting it's global + # imports, such as `import os`. + sys.modules[abspath] = module + + except Exception as err: + result.crashed_file_paths[abspath] = sys.exc_info() + + log.debug("Skipped: \"%s\" (%s)", mod_name, err) + continue + + for plugin in pyblish.logic.plugins_from_module(module): + if not allow_duplicates and plugin.__name__ in plugin_names: + result.duplicated_plugins.append(plugin) + log.debug("Duplicate plug-in found: %s", plugin) + continue + + plugin_names.append(plugin.__name__) + + plugin.__module__ = module.__file__ + key = "{0}.{1}".format(plugin.__module__, plugin.__name__) + plugins[key] = plugin + + # Include plug-ins from registration. + # Directly registered plug-ins take precedence. + for plugin in pyblish.logic.registered_plugins(): + if not allow_duplicates and plugin.__name__ in plugin_names: + result.duplicated_plugins.append(plugin) + log.debug("Duplicate plug-in found: %s", plugin) + continue + + plugin_names.append(plugin.__name__) + + plugins[plugin.__name__] = plugin + + plugins = list(plugins.values()) + pyblish.logic.sort(plugins) # In-place + + # In-place user-defined filter + for filter_ in pyblish.logic._registered_plugin_filters: + filter_(plugins) + + result.plugins = plugins + + return result From 510b13a3b12615f87c0cda6050521705f5412340 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 24 Aug 2021 18:56:18 +0200 Subject: [PATCH 345/736] fixed launcher --- openpype/tools/launcher/widgets.py | 2 +- openpype/tools/launcher/window.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/launcher/widgets.py b/openpype/tools/launcher/widgets.py index c67003c26e..16870d9b6f 100644 --- a/openpype/tools/launcher/widgets.py +++ b/openpype/tools/launcher/widgets.py @@ -6,7 +6,7 @@ from avalon.vendor import qtawesome from .delegates import ActionDelegate from . import lib -from .models import TaskModel, ActionModel, ProjectModel +from .models import TaskModel, ActionModel from openpype.tools.flickcharm import FlickCharm from .constants import ( ACTION_ROLE, diff --git a/openpype/tools/launcher/window.py b/openpype/tools/launcher/window.py index 979aab42cf..b2f86e5f56 100644 --- a/openpype/tools/launcher/window.py +++ b/openpype/tools/launcher/window.py @@ -21,7 +21,7 @@ from .widgets import ( SlidePageWidget ) -from .flickcharm import FlickCharm +from openpype.tools.flickcharm import FlickCharm class ProjectIconView(QtWidgets.QListView): From dff5cd9a79ff41e8726993dfac9ed44d8ab1e13b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 24 Aug 2021 18:56:33 +0200 Subject: [PATCH 346/736] cleanup imports --- openpype/tools/new_publisher/control.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 05072b5861..032c7332a5 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -5,20 +5,9 @@ import traceback import collections import avalon.api import pyblish.api -from openpype.api import ( - get_system_settings, - get_project_settings -) -from openpype.pipeline import ( - PublishValidationError, - OpenPypePyblishPluginMixin -) - -from openpype.pipeline.create import ( - BaseCreator, - CreateContext -) +from openpype.pipeline import PublishValidationError +from openpype.pipeline.create import CreateContext from Qt import QtCore From 2b512e7cfc8333daeca2727c79ee742455d7974b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 25 Aug 2021 09:56:39 +0200 Subject: [PATCH 347/736] fixed discover function --- openpype/pipeline/publish/lib.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index ba44066484..0fa712a301 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -3,7 +3,7 @@ import sys import types import six -import pyblish.logic +import pyblish.plugin class DiscoverResult: @@ -46,12 +46,12 @@ def publish_plugins_discover(paths=None): plugins = dict() plugin_names = [] - allow_duplicates = pyblish.logic.ALLOW_DUPLICATES - log = pyblish.logic.log + allow_duplicates = pyblish.plugin.ALLOW_DUPLICATES + log = pyblish.plugin.log # Include plug-ins from registered paths if not paths: - paths = pyblish.logic.plugin_paths() + paths = pyblish.plugin.plugin_paths() for path in paths: path = os.path.normpath(path) @@ -90,7 +90,7 @@ def publish_plugins_discover(paths=None): log.debug("Skipped: \"%s\" (%s)", mod_name, err) continue - for plugin in pyblish.logic.plugins_from_module(module): + for plugin in pyblish.plugin.plugins_from_module(module): if not allow_duplicates and plugin.__name__ in plugin_names: result.duplicated_plugins.append(plugin) log.debug("Duplicate plug-in found: %s", plugin) @@ -104,7 +104,7 @@ def publish_plugins_discover(paths=None): # Include plug-ins from registration. # Directly registered plug-ins take precedence. - for plugin in pyblish.logic.registered_plugins(): + for plugin in pyblish.plugin.registered_plugins(): if not allow_duplicates and plugin.__name__ in plugin_names: result.duplicated_plugins.append(plugin) log.debug("Duplicate plug-in found: %s", plugin) @@ -115,10 +115,10 @@ def publish_plugins_discover(paths=None): plugins[plugin.__name__] = plugin plugins = list(plugins.values()) - pyblish.logic.sort(plugins) # In-place + pyblish.plugin.sort(plugins) # In-place # In-place user-defined filter - for filter_ in pyblish.logic._registered_plugin_filters: + for filter_ in pyblish.plugin._registered_plugin_filters: filter_(plugins) result.plugins = plugins From 869965a4b1c6f838825e822f68de98bce041271b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 25 Aug 2021 09:57:14 +0200 Subject: [PATCH 348/736] added WeakMethod for python2 --- openpype/tools/new_publisher/_python2_comp.py | 41 +++++++++++++++++++ openpype/tools/new_publisher/control.py | 23 +++++++---- 2 files changed, 55 insertions(+), 9 deletions(-) create mode 100644 openpype/tools/new_publisher/_python2_comp.py diff --git a/openpype/tools/new_publisher/_python2_comp.py b/openpype/tools/new_publisher/_python2_comp.py new file mode 100644 index 0000000000..d7137dbe9c --- /dev/null +++ b/openpype/tools/new_publisher/_python2_comp.py @@ -0,0 +1,41 @@ +import weakref + + +class _weak_callable: + def __init__(self, obj, func): + self.im_self = obj + self.im_func = func + + def __call__(self, *args, **kws): + if self.im_self is None: + return self.im_func(*args, **kws) + else: + return self.im_func(self.im_self, *args, **kws) + + +class WeakMethod: + """ Wraps a function or, more importantly, a bound method in + a way that allows a bound method's object to be GCed, while + providing the same interface as a normal weak reference. """ + + def __init__(self, fn): + try: + self._obj = weakref.ref(fn.im_self) + self._meth = fn.im_func + except AttributeError: + # It's not a bound method + self._obj = None + self._meth = fn + + def __call__(self): + if self._dead(): + return None + return _weak_callable(self._getobj(), self._meth) + + def _dead(self): + return self._obj is not None and self._obj() is None + + def _getobj(self): + if self._obj is None: + return None + return self._obj() diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 032c7332a5..d366f3d198 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -1,8 +1,13 @@ import copy -import weakref import logging import traceback import collections + +try: + from weakref import WeakMethod +except Exception: + from ._python2_comp import WeakMethod + import avalon.api import pyblish.api @@ -306,36 +311,36 @@ class PublisherController: return self.create_context.plugins_with_defs def add_instances_refresh_callback(self, callback): - ref = weakref.WeakMethod(callback) + ref = WeakMethod(callback) self._instances_refresh_callback_refs.add(ref) def add_plugins_refresh_callback(self, callback): - ref = weakref.WeakMethod(callback) + ref = WeakMethod(callback) self._plugins_refresh_callback_refs.add(ref) # --- Publish specific callbacks --- def add_publish_reset_callback(self, callback): - ref = weakref.WeakMethod(callback) + ref = WeakMethod(callback) self._publish_reset_callback_refs.add(ref) def add_publish_started_callback(self, callback): - ref = weakref.WeakMethod(callback) + ref = WeakMethod(callback) self._publish_started_callback_refs.add(ref) def add_publish_validated_callback(self, callback): - ref = weakref.WeakMethod(callback) + ref = WeakMethod(callback) self._publish_validated_callback_refs.add(ref) def add_instance_change_callback(self, callback): - ref = weakref.WeakMethod(callback) + ref = WeakMethod(callback) self._publish_instance_changed_callback_refs.add(ref) def add_plugin_change_callback(self, callback): - ref = weakref.WeakMethod(callback) + ref = WeakMethod(callback) self._publish_plugin_changed_callback_refs.add(ref) def add_publish_stopped_callback(self, callback): - ref = weakref.WeakMethod(callback) + ref = WeakMethod(callback) self._publish_stopped_callback_refs.add(ref) def get_asset_docs(self): From 866fc2146dbebcda65a1258a91abb9fa3d3ef7bd Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 25 Aug 2021 09:58:13 +0200 Subject: [PATCH 349/736] fixed imports in new publisher --- openpype/tools/new_publisher/__init__.py | 5 +++ openpype/tools/new_publisher/app.py | 17 +++++++ .../tools/new_publisher/widgets/__init__.py | 2 +- .../widgets/instance_views_widgets.py | 2 +- openpype/tools/new_publisher/window.py | 45 ++----------------- 5 files changed, 27 insertions(+), 44 deletions(-) create mode 100644 openpype/tools/new_publisher/app.py diff --git a/openpype/tools/new_publisher/__init__.py b/openpype/tools/new_publisher/__init__.py index e69de29bb2..2d81de8fee 100644 --- a/openpype/tools/new_publisher/__init__.py +++ b/openpype/tools/new_publisher/__init__.py @@ -0,0 +1,5 @@ +from .app import show + +__all__ = ( + "show", +) diff --git a/openpype/tools/new_publisher/app.py b/openpype/tools/new_publisher/app.py new file mode 100644 index 0000000000..2176d6facb --- /dev/null +++ b/openpype/tools/new_publisher/app.py @@ -0,0 +1,17 @@ +from .window import PublisherWindow + + +class _WindowCache: + window = None + + +def show(parent=None): + window = _WindowCache.window + if window is None: + window = PublisherWindow() + _WindowCache.window = window + + window.show() + window.activateWindow() + + return window diff --git a/openpype/tools/new_publisher/widgets/__init__.py b/openpype/tools/new_publisher/widgets/__init__.py index 57434c38c4..9980606248 100644 --- a/openpype/tools/new_publisher/widgets/__init__.py +++ b/openpype/tools/new_publisher/widgets/__init__.py @@ -26,7 +26,7 @@ __all__ = ( "SubsetAttributesWidget", - "PublishOverlayFrame", + "PublishFrame", "CreateDialog", diff --git a/openpype/tools/new_publisher/widgets/instance_views_widgets.py b/openpype/tools/new_publisher/widgets/instance_views_widgets.py index fde83c3a44..989dcf3aaf 100644 --- a/openpype/tools/new_publisher/widgets/instance_views_widgets.py +++ b/openpype/tools/new_publisher/widgets/instance_views_widgets.py @@ -2,7 +2,7 @@ import collections from Qt import QtWidgets, QtCore, QtGui -from constants import ( +from ..constants import ( INSTANCE_ID_ROLE, SORT_VALUE_ROLE ) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index e574b47c37..928724e217 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -1,35 +1,9 @@ -import os -import sys - -openpype_dir = "" -mongo_url = "" -project_name = "" -asset_name = "" -task_name = "" -host_name = "" - -os.environ["OPENPYPE_MONGO"] = mongo_url -os.environ["AVALON_MONGO"] = mongo_url -os.environ["AVALON_PROJECT"] = project_name -os.environ["AVALON_ASSET"] = asset_name -os.environ["AVALON_TASK"] = task_name -os.environ["AVALON_APP"] = host_name -os.environ["OPENPYPE_DATABASE_NAME"] = "openpype" -os.environ["AVALON_CONFIG"] = "openpype" -os.environ["AVALON_TIMEOUT"] = "1000" -os.environ["AVALON_DB"] = "avalon" -for path in [ - openpype_dir, - r"{}\repos\avalon-core".format(openpype_dir), - r"{}\.venv\Lib\site-packages".format(openpype_dir) -]: - sys.path.append(path) - from Qt import QtWidgets from openpype import style -from control import PublisherController -from widgets import ( + +from .control import PublisherController +from .widgets import ( PublishFrame, SubsetAttributesWidget, InstanceCardView, @@ -381,16 +355,3 @@ class PublisherWindow(QtWidgets.QWidget): self.validate_btn.setEnabled(validate_enabled) self.publish_btn.setEnabled(publish_enabled) - - -def main(): - """Main function for testing purposes.""" - - app = QtWidgets.QApplication([]) - window = PublisherWindow() - window.show() - app.exec_() - - -if __name__ == "__main__": - main() From f17e2a4f2878aa51eb8496dccb46e35cd895df20 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 25 Aug 2021 10:14:52 +0200 Subject: [PATCH 350/736] added ensurence that it is not possible to run publishing twice --- openpype/tools/new_publisher/control.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index d366f3d198..f8ef70225f 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -571,6 +571,8 @@ class PublisherController: def _start_publish(self): """Start or continue in publishing.""" + if self._publish_is_running: + return self._publish_is_running = True self._trigger_callbacks(self._publish_started_callback_refs) self._main_thread_processor.start() From 9bf233ac928ed0280c4745e00b575a7e84aba9a7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 25 Aug 2021 10:21:26 +0200 Subject: [PATCH 351/736] change labels on stop --- openpype/style/style.css | 1 + .../new_publisher/widgets/publish_widget.py | 25 ++++++++++++++----- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/openpype/style/style.css b/openpype/style/style.css index e37303f501..4305fd68cc 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -710,6 +710,7 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { #PublishInfoFrame QLabel { color: black; + font-style: bold; } #PublishInfoMainLabel { diff --git a/openpype/tools/new_publisher/widgets/publish_widget.py b/openpype/tools/new_publisher/widgets/publish_widget.py index 2dfdd6e6d6..242cbd23d1 100644 --- a/openpype/tools/new_publisher/widgets/publish_widget.py +++ b/openpype/tools/new_publisher/widgets/publish_widget.py @@ -137,7 +137,7 @@ class PublishFrame(QtWidgets.QFrame): self._change_bg_property() self._set_progress_visibility(True) - self.main_label.setText("Hit publish! (if you want)") + self.main_label.setText("Hit publish (play button)! If you want") self.message_label.setText("") self.message_label_bottom.setText("") self.copy_log_btn.setVisible(False) @@ -223,20 +223,33 @@ class PublishFrame(QtWidgets.QFrame): self.publish_btn.setEnabled(publish_enabled) error = self.controller.get_publish_crash_error() + validation_errors = self.controller.get_validation_errors() if error: self._set_error(error) - return - validation_errors = self.controller.get_validation_errors() - if validation_errors: + elif validation_errors: self._set_progress_visibility(False) self._change_bg_property(1) self._set_validation_errors(validation_errors) - return - if self.controller.publish_has_finished: + elif self.controller.publish_has_finished: self._set_finished() + else: + self._set_stopped() + + def _set_stopped(self): + main_label = "Publish paused" + if self.controller.publish_has_validated: + main_label += " - Validation passed" + + self.main_label.setText(main_label) + self.message_label.setText( + "Hit publish (play button) to continue." + ) + + self._set_success_property(-1) + def _set_error(self, error): self.main_label.setText("Error happened") if isinstance(error, KnownPublishError): From a650052587752ad88b6b397ec7397b32140558cc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 25 Aug 2021 10:30:01 +0200 Subject: [PATCH 352/736] added logs from maya --- .../publish_log_viewer/logs.json | 5360 ++++++++++++++--- 1 file changed, 4524 insertions(+), 836 deletions(-) diff --git a/openpype/tools/new_publisher/publish_log_viewer/logs.json b/openpype/tools/new_publisher/publish_log_viewer/logs.json index ede8f2c290..e734e4ee7b 100644 --- a/openpype/tools/new_publisher/publish_log_viewer/logs.json +++ b/openpype/tools/new_publisher/publish_log_viewer/logs.json @@ -1,845 +1,4533 @@ { - "plugins_data": [ - { - "name": "CollectSettings", - "label": "Collect Settings", - "order": -0.491, - "instances_data": [ - { - "id": null, - "logs": [] - } - ], - "skipped": false - }, - { - "name": "CollectContextDataTestHost", - "label": "Collect Context - Test Host", - "order": -0.49, - "instances_data": [ - { - "id": null, - "logs": [ - { - "instance_id": null, - "type": "record", - "msg": "collected instance: {'family': 'test_one', 'name': 'test_oneMyVariant', 'subset': 'test_oneMyVariant', 'asset': 'sq01_sh0010', 'label': 'test_oneMyVariant', 'families': []}", - "name": "pyblish.CollectContextDataTestHost", - "lineno": 60, - "levelno": 20, - "levelname": "INFO", - "threadName": "MainThread", - "filename": "", - "pathname": "", - "msecs": 421.083927154541, - "exc_info": null - }, - { - "instance_id": null, - "type": "record", - "msg": "parsing data: {'id': 'pyblish.avalon.instance', 'family': 'test_one', 'subset': 'test_oneMyVariant', 'active': True, 'version': 1, 'asset': 'sq01_sh0010', 'task': 'Compositing', 'variant': 'myVariant', 'uuid': 'a485f148-9121-46a5-8157-aa64df0fb449', 'family_attributes': {'number_key': 10, 'ha': 10}, 'publish_attributes': {'CollectFtrackApi': {'add_ftrack_family': False}}}", - "name": "pyblish.CollectContextDataTestHost", - "lineno": 61, - "levelno": 20, - "levelname": "INFO", - "threadName": "MainThread", - "filename": "", - "pathname": "", - "msecs": 421.083927154541, - "exc_info": null - }, - { - "instance_id": null, - "type": "record", - "msg": "collected instance: {'family': 'test_one', 'name': 'test_oneMyVariant2', 'subset': 'test_oneMyVariant2', 'asset': 'sq01_sh0010', 'label': 'test_oneMyVariant2', 'families': []}", - "name": "pyblish.CollectContextDataTestHost", - "lineno": 60, - "levelno": 20, - "levelname": "INFO", - "threadName": "MainThread", - "filename": "", - "pathname": "", - "msecs": 423.0847358703613, - "exc_info": null - }, - { - "instance_id": null, - "type": "record", - "msg": "parsing data: {'id': 'pyblish.avalon.instance', 'family': 'test_one', 'subset': 'test_oneMyVariant2', 'active': True, 'version': 1, 'asset': 'sq01_sh0010', 'task': 'Compositing', 'variant': 'myVariant2', 'uuid': 'a485f148-9121-46a5-8157-aa64df0fb444', 'family_attributes': {}, 'publish_attributes': {'CollectFtrackApi': {'add_ftrack_family': True}}}", - "name": "pyblish.CollectContextDataTestHost", - "lineno": 61, - "levelno": 20, - "levelname": "INFO", - "threadName": "MainThread", - "filename": "", - "pathname": "", - "msecs": 423.0847358703613, - "exc_info": null - }, - { - "instance_id": null, - "type": "record", - "msg": "collected instance: {'family': 'test_two', 'name': 'test_twoMain', 'subset': 'test_twoMain', 'asset': 'sq01_sh0010', 'label': 'test_twoMain', 'families': []}", - "name": "pyblish.CollectContextDataTestHost", - "lineno": 60, - "levelno": 20, - "levelname": "INFO", - "threadName": "MainThread", - "filename": "", - "pathname": "", - "msecs": 423.0847358703613, - "exc_info": null - }, - { - "instance_id": null, - "type": "record", - "msg": "parsing data: {'id': 'pyblish.avalon.instance', 'family': 'test_two', 'subset': 'test_twoMain', 'active': True, 'version': 1, 'asset': 'sq01_sh0010', 'task': 'Compositing', 'variant': 'Main', 'uuid': '3607bc95-75f6-4648-a58d-e699f413d09f', 'family_attributes': {}, 'publish_attributes': {'CollectFtrackApi': {'add_ftrack_family': True}}}", - "name": "pyblish.CollectContextDataTestHost", - "lineno": 61, - "levelno": 20, - "levelname": "INFO", - "threadName": "MainThread", - "filename": "", - "pathname": "", - "msecs": 424.0849018096924, - "exc_info": null - }, - { - "instance_id": null, - "type": "record", - "msg": "collected instance: {'family': 'test_two', 'name': 'test_twoMain2', 'subset': 'test_twoMain2', 'asset': 'sq01_sh0020', 'label': 'test_twoMain2', 'families': []}", - "name": "pyblish.CollectContextDataTestHost", - "lineno": 60, - "levelno": 20, - "levelname": "INFO", - "threadName": "MainThread", - "filename": "", - "pathname": "", - "msecs": 424.0849018096924, - "exc_info": null - }, - { - "instance_id": null, - "type": "record", - "msg": "parsing data: {'id': 'pyblish.avalon.instance', 'family': 'test_two', 'subset': 'test_twoMain2', 'active': True, 'version': 1, 'asset': 'sq01_sh0020', 'task': 'Compositing', 'variant': 'Main2', 'uuid': '4ccf56f6-9982-4837-967c-a49695dbe8eb', 'family_attributes': {}, 'publish_attributes': {'CollectFtrackApi': {'add_ftrack_family': True}}}", - "name": "pyblish.CollectContextDataTestHost", - "lineno": 61, - "levelno": 20, - "levelname": "INFO", - "threadName": "MainThread", - "filename": "", - "pathname": "", - "msecs": 424.0849018096924, - "exc_info": null - } - ] - } - ], - "skipped": false - }, - { - "name": "CollectAnatomyObject", - "label": "Collect Anatomy Object", - "order": -0.4, - "instances_data": [ - { - "id": null, - "logs": [ - { - "instance_id": null, - "type": "record", - "msg": "Crashed", - "name": "pyblish.CollectAnatomyObject", - "lineno": 24, - "levelno": 20, - "levelname": "INFO", - "threadName": "MainThread", - "filename": "", - "pathname": "", - "msecs": 483.0970764160156, - "exc_info": "Traceback (most recent call last):\n File \"\", line 22, in process\nValueError: test\n" - }, - { - "instance_id": null, - "type": "record", - "msg": "Anatomy object collected for project \"kuba_each_case\".", - "name": "pyblish.CollectAnatomyObject", - "lineno": 35, - "levelno": 20, - "levelname": "INFO", - "threadName": "MainThread", - "filename": "", - "pathname": "", - "msecs": 491.09888076782227, - "exc_info": null - } - ] - } - ], - "skipped": false - }, - { - "name": "CollectAvalonEntities", - "label": "Collect Avalon Entities", - "order": -0.1, - "instances_data": [ - { - "id": null, - "logs": [ - { - "instance_id": null, - "type": "record", - "msg": "Collected Project \"{'_id': ObjectId('5eb950f9e3c2c3183455f266'), 'name': 'kuba_each_case', 'data': {'ftrackId': '38c5fec4-0aed-11ea-a454-3e41ec9bc0d6', 'entityType': 'Project', 'library_project': False, 'clipIn': 1001, 'resolutionWidth': 1920, 'handleEnd': 10, 'frameEnd': 1100, 'resolutionHeight': 1080, 'frameStart': 1001, 'pixelAspect': 1.0, 'fps': 25.0, 'handleStart': 10, 'clipOut': 1100, 'tools_env': [], 'code': 'kuba_each_case'}, 'type': 'project', 'config': {'tasks': {'Layout': {'short_name': 'lay'}, 'Setdress': {'short_name': 'dress'}, 'Previz': {'short_name': ''}, 'Generic': {'short_name': 'gener'}, 'Animation': {'short_name': 'anim'}, 'Modeling': {'short_name': 'mdl'}, 'Lookdev': {'short_name': 'look'}, 'FX': {'short_name': 'fx'}, 'Lighting': {'short_name': 'lgt'}, 'Compositing': {'short_name': 'comp'}, 'Tracking': {'short_name': ''}, 'Rigging': {'short_name': 'rig'}, 'Paint': {'short_name': 'paint'}, 'Art': {'short_name': 'art'}, 'Texture': {'short_name': 'tex'}, 'Edit': {'short_name': 'edit'}}, 'apps': [{'name': 'blender/2-91'}, {'name': 'hiero/11-3'}, {'name': 'houdini/18-5'}, {'name': 'maya/2020'}, {'name': 'nuke/11-3'}, {'name': 'nukestudio/11-3'}], 'imageio': {'hiero': {'workfile': {'ocioConfigName': 'nuke-default', 'ocioconfigpath': {'windows': [], 'darwin': [], 'linux': []}, 'workingSpace': 'linear', 'sixteenBitLut': 'sRGB', 'eightBitLut': 'sRGB', 'floatLut': 'linear', 'logLut': 'Cineon', 'viewerLut': 'sRGB', 'thumbnailLut': 'sRGB'}, 'regexInputs': {'inputs': [{'regex': '[^-a-zA-Z0-9](plateRef).*(?=mp4)', 'colorspace': 'sRGB'}]}}, 'nuke': {'viewer': {'viewerProcess': 'sRGB'}, 'workfile': {'colorManagement': 'Nuke', 'OCIO_config': 'nuke-default', 'customOCIOConfigPath': {'windows': [], 'darwin': [], 'linux': []}, 'workingSpaceLUT': 'linear', 'monitorLut': 'sRGB', 'int8Lut': 'sRGB', 'int16Lut': 'sRGB', 'logLut': 'Cineon', 'floatLut': 'linear'}, 'nodes': {'requiredNodes': [{'plugins': ['CreateWriteRender'], 'nukeNodeClass': 'Write', 'knobs': [{'name': 'file_type', 'value': 'exr'}, {'name': 'datatype', 'value': '16 bit half'}, {'name': 'compression', 'value': 'Zip (1 scanline)'}, {'name': 'autocrop', 'value': 'True'}, {'name': 'tile_color', 'value': '0xff0000ff'}, {'name': 'channels', 'value': 'rgb'}, {'name': 'colorspace', 'value': 'linear'}, {'name': 'create_directories', 'value': 'True'}]}, {'plugins': ['CreateWritePrerender'], 'nukeNodeClass': 'Write', 'knobs': [{'name': 'file_type', 'value': 'exr'}, {'name': 'datatype', 'value': '16 bit half'}, {'name': 'compression', 'value': 'Zip (1 scanline)'}, {'name': 'autocrop', 'value': 'False'}, {'name': 'tile_color', 'value': '0xadab1dff'}, {'name': 'channels', 'value': 'rgb'}, {'name': 'colorspace', 'value': 'linear'}, {'name': 'create_directories', 'value': 'True'}]}], 'customNodes': []}, 'regexInputs': {'inputs': [{'regex': '[^-a-zA-Z0-9]beauty[^-a-zA-Z0-9]', 'colorspace': 'linear'}]}}}, 'roots': {'work': {'windows': 'C:/projects', 'darwin': '/Volumes/path', 'linux': '/mnt/share/projects'}}, 'templates': {'defaults': {'version_padding': 3, 'version': 'v{version:0>{@version_padding}}', 'frame_padding': 4, 'frame': '{frame:0>{@frame_padding}}'}, 'work': {'folder': '{root[work]}/{project[name]}/{hierarchy}/{asset}/work/{task}', 'file': '{project[code]}_{asset}_{task}_{@version}<_{comment}>.{ext}', 'path': '{@folder}/{@file}'}, 'render': {'folder': '{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/{@version}', 'file': '{project[code]}_{asset}_{subset}_{@version}<_{output}><.{@frame}>.{ext}', 'path': '{@folder}/{@file}'}, 'publish': {'folder': '{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/{@version}', 'file': '{project[code]}_{asset}_{subset}_{@version}<_{output}><.{@frame}><_{udim}>.{ext}', 'path': '{@folder}/{@file}', 'thumbnail': '{thumbnail_root}/{project[name]}/{_id}_{thumbnail_type}.{ext}'}, 'hero': {'folder': '{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/hero', 'file': '{project[code]}_{asset}_{subset}_hero<_{output}><.{frame}>.{ext}', 'path': '{@folder}/{@file}'}, 'delivery': {}, 'others': {}}, 'schema': 'openpype:config-2.0'}, 'parent': None, 'schema': 'openpype:project-3.0'}\"", - "name": "pyblish.CollectAvalonEntities", - "lineno": 33, - "levelno": 10, - "levelname": "DEBUG", - "threadName": "MainThread", - "filename": "", - "pathname": "", - "msecs": 546.1108684539795, - "exc_info": null - }, - { - "instance_id": null, - "type": "record", - "msg": "Collected Asset \"{'_id': ObjectId('5dd50967e3c2c32b004b4817'), 'name': 'Alpaca_01', 'data': {'ftrackId': 'fc02ea60-9786-11eb-8ae9-fa8d1b9899e1', 'entityType': 'AssetBuild', 'clipIn': 1001, 'resolutionWidth': 1920, 'handleEnd': 10, 'frameEnd': 1010, 'resolutionHeight': 1080, 'frameStart': 1001, 'pixelAspect': 1.0, 'fps': 25.0, 'handleStart': 10, 'clipOut': 1100, 'tools_env': ['mtoa/3-1'], 'avalon_mongo_id': '5dd50967e3c2c32b004b4817', 'parents': ['Assets'], 'tasks': {'modeling': {'type': 'Modeling'}, 'animation': {'type': 'Animation'}, 'edit': {'type': 'Edit'}}, 'visualParent': ObjectId('5dd50967e3c2c32b004b4812')}, 'type': 'asset', 'parent': ObjectId('5eb950f9e3c2c3183455f266'), 'schema': 'openpype:asset-3.0'}\"", - "name": "pyblish.CollectAvalonEntities", - "lineno": 44, - "levelno": 10, - "levelname": "DEBUG", - "threadName": "MainThread", - "filename": "", - "pathname": "", - "msecs": 548.1112003326416, - "exc_info": null - } - ] - } - ], - "skipped": false - }, - { - "name": "CollectCurrentDate", - "label": "Current date", - "order": 0, - "instances_data": [ - { - "id": null, - "logs": [] - } - ], - "skipped": false - }, - { - "name": "CollectCurrentUser", - "label": "Current user", - "order": 0, - "instances_data": [ - { - "id": null, - "logs": [] - } - ], - "skipped": false - }, - { - "name": "CollectCurrentWorkingDirectory", - "label": "Current working directory", - "order": 0, - "instances_data": [ - { - "id": null, - "logs": [] - } - ], - "skipped": false - }, - { - "name": "CollectComment", - "label": "Collect Comment", - "order": 0, - "instances_data": [ - { - "id": null, - "logs": [] - } - ], - "skipped": false - }, - { - "name": "CollectDateTimeData", - "label": "Collect DateTime data", - "order": 0, - "instances_data": [ - { - "id": null, - "logs": [] - } - ], - "skipped": false - }, - { - "name": "CollectHostName", - "label": "Collect Host Name", - "order": 0, - "instances_data": [ - { - "id": null, - "logs": [] - } - ], - "skipped": false - }, - { - "name": "CollectMachineName", - "label": "Local Machine Name", - "order": 0, - "instances_data": [ - { - "id": null, - "logs": [ - { - "instance_id": null, - "type": "record", - "msg": "Machine name: 014-BAILEYS", - "name": "pyblish.CollectMachineName", - "lineno": 21, - "levelno": 20, - "levelname": "INFO", - "threadName": "MainThread", - "filename": "", - "pathname": "", - "msecs": 985.015869140625, - "exc_info": null - } - ] - } - ], - "skipped": false - }, - { - "name": "CollectTime", - "label": "Collect Current Time", - "order": 0, - "instances_data": [ - { - "id": null, - "logs": [] - } - ], - "skipped": false - }, - { - "name": "RepairUnicodeStrings", - "label": "Unicode Strings", - "order": 0, - "instances_data": [ - { - "id": null, - "logs": [] - } - ], - "skipped": false - }, - { - "name": "CollectCurrentUserPype", - "label": "Collect Pype User", - "order": 0.001, - "instances_data": [ - { - "id": null, - "logs": [ - { - "instance_id": null, - "type": "record", - "msg": "Collected user \"jakub.trllo\"", - "name": "pyblish.CollectCurrentUserPype", - "lineno": 17, - "levelno": 10, - "levelname": "DEBUG", - "threadName": "MainThread", - "filename": "", - "pathname": "", - "msecs": 170.26805877685547, - "exc_info": null - } - ] - } - ], - "skipped": false - }, - { - "name": "CollectAnatomyContextData", - "label": "Collect Anatomy Context Data", - "order": 0.002, - "instances_data": [ - { - "id": null, - "logs": [ - { - "instance_id": null, - "type": "record", - "msg": "Global anatomy Data collected", - "name": "pyblish.CollectAnatomyContextData", - "lineno": 87, - "levelno": 20, - "levelname": "INFO", - "threadName": "MainThread", - "filename": "", - "pathname": "", - "msecs": 234.85493659973145, - "exc_info": null - }, - { - "instance_id": null, - "type": "record", - "msg": "{\n \"project\": {\n \"name\": \"kuba_each_case\",\n \"code\": \"kuba_each_case\"\n },\n \"asset\": \"Alpaca_01\",\n \"hierarchy\": \"Assets\",\n \"task\": \"modeling\",\n \"username\": \"jakub.trllo\",\n \"app\": \"testhost\",\n \"d\": \"19\",\n \"dd\": \"19\",\n \"ddd\": \"Thu\",\n \"dddd\": \"Thursday\",\n \"m\": \"8\",\n \"mm\": \"08\",\n \"mmm\": \"Aug\",\n \"mmmm\": \"August\",\n \"yy\": \"21\",\n \"yyyy\": \"2021\",\n \"H\": \"18\",\n \"HH\": \"18\",\n \"h\": \"6\",\n \"hh\": \"06\",\n \"ht\": \"PM\",\n \"M\": \"7\",\n \"MM\": \"07\",\n \"S\": \"1\",\n \"SS\": \"01\"\n}", - "name": "pyblish.CollectAnatomyContextData", - "lineno": 88, - "levelno": 10, - "levelname": "DEBUG", - "threadName": "MainThread", - "filename": "", - "pathname": "", - "msecs": 234.85493659973145, - "exc_info": null - } - ] - } - ], - "skipped": false - }, - { - "name": "CollectContextLabel", - "label": "Context Label", - "order": 0.25, - "instances_data": [ - { - "id": null, - "logs": [] - } - ], - "skipped": false - }, - { - "name": "CollectAnatomyInstanceData", - "label": "Collect Anatomy Instance data", - "order": 0.49, - "instances_data": [ - { - "id": null, - "logs": [ - { - "instance_id": null, - "type": "record", - "msg": "Collecting anatomy data for all instances.", - "name": "pyblish.CollectAnatomyInstanceData", - "lineno": 42, - "levelno": 20, - "levelname": "INFO", - "threadName": "MainThread", - "filename": "", - "pathname": "", - "msecs": 358.84571075439453, - "exc_info": null - }, - { - "instance_id": null, - "type": "record", - "msg": "Qeurying asset documents for instances.", - "name": "pyblish.CollectAnatomyInstanceData", - "lineno": 51, - "levelno": 10, - "levelname": "DEBUG", - "threadName": "MainThread", - "filename": "", - "pathname": "", - "msecs": 358.84571075439453, - "exc_info": null - }, - { - "instance_id": null, - "type": "record", - "msg": "Querying asset documents with names: \"sq01_sh0010\", \"sq01_sh0020\"", - "name": "pyblish.CollectAnatomyInstanceData", - "lineno": 82, - "levelno": 10, - "levelname": "DEBUG", - "threadName": "MainThread", - "filename": "", - "pathname": "", - "msecs": 359.8446846008301, - "exc_info": null - }, - { - "instance_id": null, - "type": "record", - "msg": "Qeurying latest versions for instances.", - "name": "pyblish.CollectAnatomyInstanceData", - "lineno": 123, - "levelno": 10, - "levelname": "DEBUG", - "threadName": "MainThread", - "filename": "", - "pathname": "", - "msecs": 360.8419895172119, - "exc_info": null - }, - { - "instance_id": null, - "type": "record", - "msg": "Storing anatomy data to instance data.", - "name": "pyblish.CollectAnatomyInstanceData", - "lineno": 210, - "levelno": 10, - "levelname": "DEBUG", - "threadName": "MainThread", - "filename": "", - "pathname": "", - "msecs": 361.8345260620117, - "exc_info": null - }, - { - "instance_id": null, - "type": "record", - "msg": "Anatomy data for instance test_oneMyVariant(test_oneMyVariant): {\n \"project\": {\n \"name\": \"kuba_each_case\",\n \"code\": \"kuba_each_case\"\n },\n \"asset\": \"sq01_sh0010\",\n \"hierarchy\": \"Shots/sq01\",\n \"task\": \"modeling\",\n \"username\": \"jakub.trllo\",\n \"app\": \"testhost\",\n \"d\": \"19\",\n \"dd\": \"19\",\n \"ddd\": \"Thu\",\n \"dddd\": \"Thursday\",\n \"m\": \"8\",\n \"mm\": \"08\",\n \"mmm\": \"Aug\",\n \"mmmm\": \"August\",\n \"yy\": \"21\",\n \"yyyy\": \"2021\",\n \"H\": \"18\",\n \"HH\": \"18\",\n \"h\": \"6\",\n \"hh\": \"06\",\n \"ht\": \"PM\",\n \"M\": \"7\",\n \"MM\": \"07\",\n \"S\": \"1\",\n \"SS\": \"01\",\n \"family\": \"test_one\",\n \"subset\": \"test_oneMyVariant\",\n \"version\": 1\n}", - "name": "pyblish.CollectAnatomyInstanceData", - "lineno": 279, - "levelno": 10, - "levelname": "DEBUG", - "threadName": "MainThread", - "filename": "", - "pathname": "", - "msecs": 362.8430366516113, - "exc_info": null - }, - { - "instance_id": null, - "type": "record", - "msg": "Anatomy data for instance test_oneMyVariant2(test_oneMyVariant2): {\n \"project\": {\n \"name\": \"kuba_each_case\",\n \"code\": \"kuba_each_case\"\n },\n \"asset\": \"sq01_sh0010\",\n \"hierarchy\": \"Shots/sq01\",\n \"task\": \"modeling\",\n \"username\": \"jakub.trllo\",\n \"app\": \"testhost\",\n \"d\": \"19\",\n \"dd\": \"19\",\n \"ddd\": \"Thu\",\n \"dddd\": \"Thursday\",\n \"m\": \"8\",\n \"mm\": \"08\",\n \"mmm\": \"Aug\",\n \"mmmm\": \"August\",\n \"yy\": \"21\",\n \"yyyy\": \"2021\",\n \"H\": \"18\",\n \"HH\": \"18\",\n \"h\": \"6\",\n \"hh\": \"06\",\n \"ht\": \"PM\",\n \"M\": \"7\",\n \"MM\": \"07\",\n \"S\": \"1\",\n \"SS\": \"01\",\n \"family\": \"test_one\",\n \"subset\": \"test_oneMyVariant2\",\n \"version\": 1\n}", - "name": "pyblish.CollectAnatomyInstanceData", - "lineno": 279, - "levelno": 10, - "levelname": "DEBUG", - "threadName": "MainThread", - "filename": "", - "pathname": "", - "msecs": 362.8430366516113, - "exc_info": null - }, - { - "instance_id": null, - "type": "record", - "msg": "Anatomy data for instance test_twoMain(test_twoMain): {\n \"project\": {\n \"name\": \"kuba_each_case\",\n \"code\": \"kuba_each_case\"\n },\n \"asset\": \"sq01_sh0010\",\n \"hierarchy\": \"Shots/sq01\",\n \"task\": \"modeling\",\n \"username\": \"jakub.trllo\",\n \"app\": \"testhost\",\n \"d\": \"19\",\n \"dd\": \"19\",\n \"ddd\": \"Thu\",\n \"dddd\": \"Thursday\",\n \"m\": \"8\",\n \"mm\": \"08\",\n \"mmm\": \"Aug\",\n \"mmmm\": \"August\",\n \"yy\": \"21\",\n \"yyyy\": \"2021\",\n \"H\": \"18\",\n \"HH\": \"18\",\n \"h\": \"6\",\n \"hh\": \"06\",\n \"ht\": \"PM\",\n \"M\": \"7\",\n \"MM\": \"07\",\n \"S\": \"1\",\n \"SS\": \"01\",\n \"family\": \"test_two\",\n \"subset\": \"test_twoMain\",\n \"version\": 1\n}", - "name": "pyblish.CollectAnatomyInstanceData", - "lineno": 279, - "levelno": 10, - "levelname": "DEBUG", - "threadName": "MainThread", - "filename": "", - "pathname": "", - "msecs": 363.8427257537842, - "exc_info": null - }, - { - "instance_id": null, - "type": "record", - "msg": "Anatomy data for instance test_twoMain2(test_twoMain2): {\n \"project\": {\n \"name\": \"kuba_each_case\",\n \"code\": \"kuba_each_case\"\n },\n \"asset\": \"sq01_sh0020\",\n \"hierarchy\": \"Shots/sq01\",\n \"task\": \"modeling\",\n \"username\": \"jakub.trllo\",\n \"app\": \"testhost\",\n \"d\": \"19\",\n \"dd\": \"19\",\n \"ddd\": \"Thu\",\n \"dddd\": \"Thursday\",\n \"m\": \"8\",\n \"mm\": \"08\",\n \"mmm\": \"Aug\",\n \"mmmm\": \"August\",\n \"yy\": \"21\",\n \"yyyy\": \"2021\",\n \"H\": \"18\",\n \"HH\": \"18\",\n \"h\": \"6\",\n \"hh\": \"06\",\n \"ht\": \"PM\",\n \"M\": \"7\",\n \"MM\": \"07\",\n \"S\": \"1\",\n \"SS\": \"01\",\n \"family\": \"test_two\",\n \"subset\": \"test_twoMain2\",\n \"version\": 1\n}", - "name": "pyblish.CollectAnatomyInstanceData", - "lineno": 279, - "levelno": 10, - "levelname": "DEBUG", - "threadName": "MainThread", - "filename": "", - "pathname": "", - "msecs": 364.84289169311523, - "exc_info": null - }, - { - "instance_id": null, - "type": "record", - "msg": "Anatomy Data collection finished.", - "name": "pyblish.CollectAnatomyInstanceData", - "lineno": 48, - "levelno": 20, - "levelname": "INFO", - "threadName": "MainThread", - "filename": "", - "pathname": "", - "msecs": 365.83518981933594, - "exc_info": null - } - ] - } - ], - "skipped": false - }, - { - "name": "CollectResourcesPath", - "label": "Collect Resources Path", - "order": 0.495, - "instances_data": [], - "skipped": true - }, - { - "name": "ValidateEditorialAssetName", - "label": "Validate Editorial Asset Name", - "order": 1, - "instances_data": [ - { - "id": null, - "logs": [ - { - "instance_id": null, - "type": "record", - "msg": "__ asset_and_parents: {}", - "name": "pyblish.ValidateEditorialAssetName", - "lineno": 19, - "levelno": 10, - "levelname": "DEBUG", - "threadName": "MainThread", - "filename": "", - "pathname": "", - "msecs": 420.8390712738037, - "exc_info": null - }, - { - "instance_id": null, - "type": "record", - "msg": "__ db_assets: [{'_id': ObjectId('5e11ec3e0002fc29f0728009'), 'name': 'MyFolder', 'data': {'parents': []}}, {'_id': ObjectId('5dd50967e3c2c32b004b4811'), 'name': 'Shots', 'data': {'parents': []}}, {'_id': ObjectId('5dd50967e3c2c32b004b4812'), 'name': 'Assets', 'data': {'parents': []}}, {'_id': ObjectId('606d813fda091ca18ad3c1cb'), 'name': 'sq01', 'data': {'parents': ['Shots']}}, {'_id': ObjectId('60ffcef3ed7b98218feec87d'), 'name': 'editorial', 'data': {'parents': ['Shots']}}, {'_id': ObjectId('5eb40b96e3c2c3361ce67c05'), 'name': 'Buddie_01', 'data': {'parents': ['Assets']}}, {'_id': ObjectId('60ffcef3ed7b98218feec87e'), 'name': 'characters', 'data': {'parents': ['Assets']}}, {'_id': ObjectId('60ffcef3ed7b98218feec87f'), 'name': 'locations', 'data': {'parents': ['Assets']}}, {'_id': ObjectId('5dd50967e3c2c32b004b4817'), 'name': 'Alpaca_01', 'data': {'parents': ['Assets']}}, {'_id': ObjectId('60d9a8e8a5016c2a23f88534'), 'name': 'sq01_sh0020', 'data': {'parents': ['Shots', 'sq01']}}, {'_id': ObjectId('60d9a8e8a5016c2a23f88535'), 'name': 'sq01_sh0030', 'data': {'parents': ['Shots', 'sq01']}}, {'_id': ObjectId('606d813fda091ca18ad3c1cd'), 'name': 'sq01_sh0010', 'data': {'parents': ['Shots', 'sq01']}}]", - "name": "pyblish.ValidateEditorialAssetName", - "lineno": 26, - "levelno": 10, - "levelname": "DEBUG", - "threadName": "MainThread", - "filename": "", - "pathname": "", - "msecs": 421.8316078186035, - "exc_info": null - }, - { - "instance_id": null, - "type": "record", - "msg": "__ project_entities: {'Alpaca_01': ['Assets'],\n 'Assets': [],\n 'Buddie_01': ['Assets'],\n 'MyFolder': [],\n 'Shots': [],\n 'characters': ['Assets'],\n 'editorial': ['Shots'],\n 'locations': ['Assets'],\n 'sq01': ['Shots'],\n 'sq01_sh0010': ['Shots', 'sq01'],\n 'sq01_sh0020': ['Shots', 'sq01'],\n 'sq01_sh0030': ['Shots', 'sq01']}", - "name": "pyblish.ValidateEditorialAssetName", - "lineno": 33, - "levelno": 10, - "levelname": "DEBUG", - "threadName": "MainThread", - "filename": "", - "pathname": "", - "msecs": 421.8316078186035, - "exc_info": null - } - ] - } - ], - "skipped": false - }, - { - "name": "ValidateFFmpegInstalled", - "label": "Validate ffmpeg installation", - "order": 1, - "instances_data": [ - { - "id": null, - "logs": [ - { - "instance_id": null, - "type": "record", - "msg": "ffmpeg path: `C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\vendor\\bin\\ffmpeg\\windows\\bin\\ffmpeg`", - "name": "pyblish.ValidateFFmpegInstalled", - "lineno": 31, - "levelno": 20, - "levelname": "INFO", - "threadName": "MainThread", - "filename": "", - "pathname": "", - "msecs": 482.6626777648926, - "exc_info": null - } - ] - } - ], - "skipped": false - }, - { - "name": "ValidateResources", - "label": "Resources", - "order": 1.1, - "instances_data": [ - { - "id": null, - "logs": [] - }, - { - "id": null, - "logs": [] - }, - { - "id": null, - "logs": [] - }, - { - "id": null, - "logs": [] - } - ], - "skipped": false - }, - { - "name": "ExtractHierarchyToAvalon", - "label": "Extract Hierarchy To Avalon", - "order": 1.99, - "instances_data": [], - "skipped": true - }, - { - "name": "IntegrateResourcesPath", - "label": "Integrate Resources Path", - "order": 2.95, - "instances_data": [], - "skipped": true - }, - { - "name": "IntegrateAssetNew", - "label": "Integrate Asset New", - "order": 3, - "instances_data": [], - "skipped": true - }, - { - "name": "IntegrateThumbnails", - "label": "Integrate Thumbnails", - "order": 3.01, - "instances_data": [], - "skipped": true - }, - { - "name": "IntegrateHeroVersion", - "label": "Integrate Hero Version", - "order": 3.1, - "instances_data": [], - "skipped": true - }, - { - "name": "CleanUp", - "label": "Clean Up", - "order": 13, - "instances_data": [ - { - "id": null, - "logs": [ - { - "instance_id": null, - "type": "record", - "msg": "Staging dir not set.", - "name": "pyblish.CleanUp", - "lineno": 60, - "levelno": 20, - "levelname": "INFO", - "threadName": "MainThread", - "filename": "", - "pathname": "", - "msecs": 794.025182723999, - "exc_info": null - } - ] - }, - { - "id": null, - "logs": [ - { - "instance_id": null, - "type": "record", - "msg": "Staging dir not set.", - "name": "pyblish.CleanUp", - "lineno": 60, - "levelno": 20, - "levelname": "INFO", - "threadName": "MainThread", - "filename": "", - "pathname": "", - "msecs": 856.0423851013184, - "exc_info": null - } - ] - }, - { - "id": null, - "logs": [ - { - "instance_id": null, - "type": "record", - "msg": "Staging dir not set.", - "name": "pyblish.CleanUp", - "lineno": 60, - "levelno": 20, - "levelname": "INFO", - "threadName": "MainThread", - "filename": "", - "pathname": "", - "msecs": 917.0691967010498, - "exc_info": null - } - ] - }, - { - "id": null, - "logs": [ - { - "instance_id": null, - "type": "record", - "msg": "Staging dir not set.", - "name": "pyblish.CleanUp", - "lineno": 60, - "levelno": 20, - "levelname": "INFO", - "threadName": "MainThread", - "filename": "", - "pathname": "", - "msecs": 978.832483291626, - "exc_info": null - } - ] - } - ], - "skipped": false - } - ], "instances": { - "fad07a7e-0d74-4468-8c82-d5ef3b21c625": { - "name": "test_oneMyVariant", - "label": "test_oneMyVariant", - "family": "test_one", - "families": [], - "exists": true + "6d2ee587-9843-47a6-a6e2-10b6d2d8e495": { + "exists": true, + "families": [ + "model", + "review" + ], + "name": "modelMain", + "family": "model", + "label": "modelMain (Alpaca_01)" }, - "3690bd36-4ea5-450d-9deb-e5ba4caa456b": { - "name": "test_oneMyVariant2", - "label": "test_oneMyVariant2", - "family": "test_one", - "families": [], - "exists": true + "8276e1a3-418c-46fa-8a95-8a9bf5c1ef30": { + "exists": true, + "families": [ + "workfile" + ], + "name": "kuba_each_case_Alpaca_01_animation_v007", + "family": "workfile", + "label": "workfileAnimation" }, - "c6ad1345-a447-4b0a-82a7-827a957a0424": { - "name": "test_twoMain", - "label": "test_twoMain", - "family": "test_two", - "families": [], - "exists": true - }, - "d01c9aab-a7fc-4f61-bd38-05c2d999e740": { - "name": "test_twoMain2", - "label": "test_twoMain2", - "family": "test_two", - "families": [], - "exists": true + "b2d211bd-217a-4cfb-b989-45b3dfc4edc9": { + "exists": false, + "families": [ + "review" + ], + "name": "reviewMain", + "family": "review", + "label": "reviewMain (Alpaca_01) [991-1020]" } }, + "plugins_data": [ + { + "instances_data": [ + { + "id": null, + "logs": [] + } + ], + "label": "Maya Workspace", + "skipped": false, + "order": -0.5, + "name": "CollectMayaWorkspace" + }, + { + "instances_data": [ + { + "id": null, + "logs": [] + } + ], + "label": "Collect Settings", + "skipped": false, + "order": -0.491, + "name": "CollectSettings" + }, + { + "instances_data": [ + { + "id": null, + "logs": [ + { + "instance_id": null, + "name": "pyblish.CollectFileDependencies", + "threadName": "MainThread", + "msecs": 523.9999294281006, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 28, + "msg": "[]", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + } + ] + } + ], + "label": "Collect File Dependencies", + "skipped": false, + "order": -0.49, + "name": "CollectFileDependencies" + }, + { + "instances_data": [ + { + "id": null, + "logs": [ + { + "instance_id": null, + "name": "pyblish.CollectAnatomyObject", + "threadName": "MainThread", + "msecs": 585.0000381469727, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 31, + "msg": "Anatomy object collected for project \"kuba_each_case\".", + "exc_info": null, + "type": "record", + "levelname": "INFO" + } + ] + } + ], + "label": "Collect Anatomy Object", + "skipped": false, + "order": -0.4, + "name": "CollectAnatomyObject" + }, + { + "instances_data": [ + { + "id": null, + "logs": [ + { + "instance_id": null, + "name": "pyblish.CollectAvalonEntities", + "threadName": "MainThread", + "msecs": 638.0000114440918, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 33, + "msg": "Collected Project \"{u'name': u'kuba_each_case', u'parent': None, u'type': u'project', u'data': {u'code': u'kuba_each_case', u'resolutionHeight': 1080, u'library_project': False, u'clipIn': 1001, u'frameStart': 1001, u'handleStart': 10, u'entityType': u'Project', u'frameEnd': 1100, u'tools_env': [], u'pixelAspect': 1.0, u'ftrackId': u'38c5fec4-0aed-11ea-a454-3e41ec9bc0d6', u'resolutionWidth': 1920, u'fps': 25.0, u'handleEnd': 10, u'clipOut': 1100}, u'_id': ObjectId('5eb950f9e3c2c3183455f266'), u'config': {u'templates': {u'hero': {u'path': u'{@folder}/{@file}', u'folder': u'{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/hero', u'file': u'{project[code]}_{asset}_{subset}_hero<_{output}><.{frame}>.{ext}'}, u'render': {u'path': u'{@folder}/{@file}', u'folder': u'{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/{@version}', u'file': u'{project[code]}_{asset}_{subset}_{@version}<_{output}><.{@frame}>.{ext}'}, u'work': {u'path': u'{@folder}/{@file}', u'folder': u'{root[work]}/{project[name]}/{hierarchy}/{asset}/work/{task}', u'file': u'{project[code]}_{asset}_{task}_{@version}<_{comment}>.{ext}'}, u'publish': {u'path': u'{@folder}/{@file}', u'folder': u'{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/{@version}', u'thumbnail': u'{thumbnail_root}/{project[name]}/{_id}_{thumbnail_type}.{ext}', u'file': u'{project[code]}_{asset}_{subset}_{@version}<_{output}><.{@frame}><_{udim}>.{ext}'}, u'delivery': {}, u'defaults': {u'frame_padding': 4, u'version_padding': 3, u'frame': u'{frame:0>{@frame_padding}}', u'version': u'v{version:0>{@version_padding}}'}, u'others': {}}, u'tasks': {u'Previz': {u'short_name': u''}, u'Lookdev': {u'short_name': u'look'}, u'Edit': {u'short_name': u'edit'}, u'Tracking': {u'short_name': u''}, u'Layout': {u'short_name': u'lay'}, u'Art': {u'short_name': u'art'}, u'Paint': {u'short_name': u'paint'}, u'Generic': {u'short_name': u'gener'}, u'Texture': {u'short_name': u'tex'}, u'Setdress': {u'short_name': u'dress'}, u'Animation': {u'short_name': u'anim'}, u'Lighting': {u'short_name': u'lgt'}, u'Compositing': {u'short_name': u'comp'}, u'Modeling': {u'short_name': u'mdl'}, u'Rigging': {u'short_name': u'rig'}, u'FX': {u'short_name': u'fx'}}, u'imageio': {u'hiero': {u'workfile': {u'eightBitLut': u'sRGB', u'viewerLut': u'sRGB', u'workingSpace': u'linear', u'ocioconfigpath': {u'windows': [], u'darwin': [], u'linux': []}, u'ocioConfigName': u'nuke-default', u'thumbnailLut': u'sRGB', u'floatLut': u'linear', u'sixteenBitLut': u'sRGB', u'logLut': u'Cineon'}, u'regexInputs': {u'inputs': [{u'regex': u'[^-a-zA-Z0-9](plateRef).*(?=mp4)', u'colorspace': u'sRGB'}]}}, u'nuke': {u'viewer': {u'viewerProcess': u'sRGB'}, u'workfile': {u'floatLut': u'linear', u'workingSpaceLUT': u'linear', u'customOCIOConfigPath': {u'windows': [], u'darwin': [], u'linux': []}, u'int16Lut': u'sRGB', u'logLut': u'Cineon', u'int8Lut': u'sRGB', u'colorManagement': u'Nuke', u'OCIO_config': u'nuke-default', u'monitorLut': u'sRGB'}, u'nodes': {u'customNodes': [], u'requiredNodes': [{u'nukeNodeClass': u'Write', u'knobs': [{u'name': u'file_type', u'value': u'exr'}, {u'name': u'datatype', u'value': u'16 bit half'}, {u'name': u'compression', u'value': u'Zip (1 scanline)'}, {u'name': u'autocrop', u'value': u'True'}, {u'name': u'tile_color', u'value': u'0xff0000ff'}, {u'name': u'channels', u'value': u'rgb'}, {u'name': u'colorspace', u'value': u'linear'}, {u'name': u'create_directories', u'value': u'True'}], u'plugins': [u'CreateWriteRender']}, {u'nukeNodeClass': u'Write', u'knobs': [{u'name': u'file_type', u'value': u'exr'}, {u'name': u'datatype', u'value': u'16 bit half'}, {u'name': u'compression', u'value': u'Zip (1 scanline)'}, {u'name': u'autocrop', u'value': u'False'}, {u'name': u'tile_color', u'value': u'0xadab1dff'}, {u'name': u'channels', u'value': u'rgb'}, {u'name': u'colorspace', u'value': u'linear'}, {u'name': u'create_directories', u'value': u'True'}], u'plugins': [u'CreateWritePrerender']}]}, u'regexInputs': {u'inputs': [{u'regex': u'[^-a-zA-Z0-9]beauty[^-a-zA-Z0-9]', u'colorspace': u'linear'}]}}}, u'apps': [{u'name': u'blender/2-91'}, {u'name': u'hiero/11-3'}, {u'name': u'houdini/18-5'}, {u'name': u'maya/2020'}, {u'name': u'nuke/11-3'}, {u'name': u'nukestudio/11-3'}], u'roots': {u'work': {u'windows': u'C:/projects', u'darwin': u'/Volumes/path', u'linux': u'/mnt/share/projects'}}, u'schema': u'openpype:config-2.0'}, u'schema': u'openpype:project-3.0'}\"", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "pyblish.CollectAvalonEntities", + "threadName": "MainThread", + "msecs": 647.0000743865967, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 44, + "msg": "Collected Asset \"{u'name': u'Alpaca_01', u'parent': ObjectId('5eb950f9e3c2c3183455f266'), u'data': {u'visualParent': ObjectId('5dd50967e3c2c32b004b4812'), u'resolutionHeight': 1080, u'clipIn': 1001, u'frameStart': 1001, u'tasks': {u'edit': {u'type': u'Edit'}, u'modeling': {u'type': u'Modeling'}, u'animation': {u'type': u'Animation'}}, u'handleStart': 10, u'entityType': u'AssetBuild', u'frameEnd': 1010, u'parents': [u'Assets'], u'tools_env': [u'mtoa/3-1'], u'pixelAspect': 1.0, u'ftrackId': u'fc02ea60-9786-11eb-8ae9-fa8d1b9899e1', u'resolutionWidth': 1920, u'fps': 25.0, u'handleEnd': 10, u'clipOut': 1100, u'avalon_mongo_id': u'5dd50967e3c2c32b004b4817'}, u'_id': ObjectId('5dd50967e3c2c32b004b4817'), u'type': u'asset', u'schema': u'openpype:asset-3.0'}\"", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + } + ] + } + ], + "label": "Collect Avalon Entities", + "skipped": false, + "order": -0.1, + "name": "CollectAvalonEntities" + }, + { + "instances_data": [ + { + "id": null, + "logs": [ + { + "instance_id": null, + "name": "pyblish.CollectMayaScene", + "threadName": "MainThread", + "msecs": 700.9999752044678, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 53, + "msg": "Collected instance: kuba_each_case_Alpaca_01_animation_v007.ma", + "exc_info": null, + "type": "record", + "levelname": "INFO" + }, + { + "instance_id": null, + "name": "pyblish.CollectMayaScene", + "threadName": "MainThread", + "msecs": 700.9999752044678, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 54, + "msg": "Scene path: C:/projects/kuba_each_case/Assets/Alpaca_01/work/animation/kuba_each_case_Alpaca_01_animation_v007.ma", + "exc_info": null, + "type": "record", + "levelname": "INFO" + }, + { + "instance_id": null, + "name": "pyblish.CollectMayaScene", + "threadName": "MainThread", + "msecs": 701.9999027252197, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 55, + "msg": "staging Dir: C:/projects/kuba_each_case/Assets/Alpaca_01/work/animation", + "exc_info": null, + "type": "record", + "levelname": "INFO" + }, + { + "instance_id": null, + "name": "pyblish.CollectMayaScene", + "threadName": "MainThread", + "msecs": 703.0000686645508, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 56, + "msg": "subset: workfileAnimation", + "exc_info": null, + "type": "record", + "levelname": "INFO" + } + ] + } + ], + "label": "Maya Workfile", + "skipped": false, + "order": -0.01, + "name": "CollectMayaScene" + }, + { + "instances_data": [ + { + "id": null, + "logs": [ + { + "instance_id": null, + "name": "pyblish.CollectSceneVersion", + "threadName": "MainThread", + "msecs": 765.0001049041748, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 41, + "msg": "", + "exc_info": null, + "type": "record", + "levelname": "INFO" + }, + { + "instance_id": null, + "name": "pyblish.CollectSceneVersion", + "threadName": "MainThread", + "msecs": 765.0001049041748, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 42, + "msg": "Scene Version: 7", + "exc_info": null, + "type": "record", + "levelname": "INFO" + } + ] + } + ], + "label": "Collect Version", + "skipped": false, + "order": 0, + "name": "CollectSceneVersion" + }, + { + "instances_data": [ + { + "id": null, + "logs": [ + { + "instance_id": null, + "name": "pyblish.CollectWorksceneFPS", + "threadName": "MainThread", + "msecs": 832.9999446868896, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 14, + "msg": "Workscene FPS: 25.0", + "exc_info": null, + "type": "record", + "levelname": "INFO" + } + ] + } + ], + "label": "Workscene FPS", + "skipped": false, + "order": 0, + "name": "CollectWorksceneFPS" + }, + { + "instances_data": [ + { + "id": null, + "logs": [] + } + ], + "label": "Current user", + "skipped": false, + "order": 0, + "name": "CollectCurrentUser" + }, + { + "instances_data": [ + { + "id": null, + "logs": [] + } + ], + "label": "Collect Host Name", + "skipped": false, + "order": 0, + "name": "CollectHostName" + }, + { + "instances_data": [ + { + "id": null, + "logs": [ + { + "instance_id": null, + "name": "ModulesManager", + "threadName": "MainThread", + "msecs": 22.00007438659668, + "filename": "base.py", + "levelno": 10, + "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\modules\\base.py", + "lineno": 360, + "msg": "*** Pype modules initialization.", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "ModulesManager", + "threadName": "MainThread", + "msecs": 36.00001335144043, + "filename": "base.py", + "levelno": 10, + "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\modules\\base.py", + "lineno": 407, + "msg": "[X] AvalonModule", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "ModulesManager", + "threadName": "MainThread", + "msecs": 36.99994087219238, + "filename": "base.py", + "levelno": 10, + "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\modules\\base.py", + "lineno": 407, + "msg": "[ ] ClockifyModule", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "ModulesManager", + "threadName": "MainThread", + "msecs": 39.00003433227539, + "filename": "base.py", + "levelno": 10, + "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\modules\\base.py", + "lineno": 407, + "msg": "[X] DeadlineModule", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "ModulesManager", + "threadName": "MainThread", + "msecs": 39.999961853027344, + "filename": "base.py", + "levelno": 10, + "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\modules\\base.py", + "lineno": 407, + "msg": "[ ] FtrackModule", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "ModulesManager", + "threadName": "MainThread", + "msecs": 40.9998893737793, + "filename": "base.py", + "levelno": 10, + "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\modules\\base.py", + "lineno": 407, + "msg": "[X] IdleManager", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "ModulesManager", + "threadName": "MainThread", + "msecs": 42.999982833862305, + "filename": "base.py", + "levelno": 10, + "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\modules\\base.py", + "lineno": 407, + "msg": "[X] LauncherAction", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "ModulesManager", + "threadName": "MainThread", + "msecs": 43.99991035461426, + "filename": "base.py", + "levelno": 10, + "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\modules\\base.py", + "lineno": 407, + "msg": "[X] LocalSettingsAction", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "ModulesManager", + "threadName": "MainThread", + "msecs": 45.00007629394531, + "filename": "base.py", + "levelno": 10, + "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\modules\\base.py", + "lineno": 407, + "msg": "[X] LogViewModule", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "ModulesManager", + "threadName": "MainThread", + "msecs": 46.000003814697266, + "filename": "base.py", + "levelno": 10, + "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\modules\\base.py", + "lineno": 407, + "msg": "[ ] MusterModule", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "ModulesManager", + "threadName": "MainThread", + "msecs": 46.99993133544922, + "filename": "base.py", + "levelno": 10, + "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\modules\\base.py", + "lineno": 407, + "msg": "[X] ProjectManagerAction", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "ModulesManager", + "threadName": "MainThread", + "msecs": 48.00009727478027, + "filename": "base.py", + "levelno": 10, + "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\modules\\base.py", + "lineno": 407, + "msg": "[X] PythonInterpreterAction", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "ModulesManager", + "threadName": "MainThread", + "msecs": 49.00002479553223, + "filename": "base.py", + "levelno": 10, + "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\modules\\base.py", + "lineno": 407, + "msg": "[X] SettingsAction", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "ModulesManager", + "threadName": "MainThread", + "msecs": 49.99995231628418, + "filename": "base.py", + "levelno": 10, + "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\modules\\base.py", + "lineno": 407, + "msg": "[ ] SlackIntegrationModule", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "ModulesManager", + "threadName": "MainThread", + "msecs": 51.000118255615234, + "filename": "base.py", + "levelno": 10, + "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\modules\\base.py", + "lineno": 407, + "msg": "[X] StandAlonePublishAction", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "ModulesManager", + "threadName": "MainThread", + "msecs": 52.00004577636719, + "filename": "base.py", + "levelno": 10, + "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\modules\\base.py", + "lineno": 407, + "msg": "[ ] SyncServerModule", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "ModulesManager", + "threadName": "MainThread", + "msecs": 52.00004577636719, + "filename": "base.py", + "levelno": 10, + "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\modules\\base.py", + "lineno": 407, + "msg": "[ ] TimersManager", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "ModulesManager", + "threadName": "MainThread", + "msecs": 55.00006675720215, + "filename": "base.py", + "levelno": 10, + "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\modules\\base.py", + "lineno": 407, + "msg": "[X] WebServerModule", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "ModulesManager", + "threadName": "MainThread", + "msecs": 55.9999942779541, + "filename": "base.py", + "levelno": 10, + "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\modules\\base.py", + "lineno": 432, + "msg": "Has 11 enabled modules.", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + } + ] + } + ], + "label": "OpenPype Modules", + "skipped": false, + "order": 0, + "name": "CollectModules" + }, + { + "instances_data": [ + { + "id": null, + "logs": [ + { + "instance_id": null, + "name": "pyblish.CollectInstances", + "threadName": "MainThread", + "msecs": 85.00003814697266, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 65, + "msg": "Creating instance for reviewMain", + "exc_info": null, + "type": "record", + "levelname": "INFO" + }, + { + "instance_id": null, + "name": "pyblish.CollectInstances", + "threadName": "MainThread", + "msecs": 88.00005912780762, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 159, + "msg": "Found: \"reviewMain\" ", + "exc_info": null, + "type": "record", + "levelname": "INFO" + }, + { + "instance_id": null, + "name": "pyblish.CollectInstances", + "threadName": "MainThread", + "msecs": 88.00005912780762, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 161, + "msg": "DATA: {\n \"subset\": \"reviewMain\", \n \"families\": [\n \"review\"\n ], \n \"family\": \"review\", \n \"frameStart\": 1001, \n \"variant\": \"Main\", \n \"active\": true, \n \"step\": 1.0, \n \"frameStartHandle\": 991, \n \"frameEnd\": 1010, \n \"cbId\": \"5dd50967e3c2c32b004b4817:3a06e704e27a\", \n \"keepImages\": false, \n \"id\": \"pyblish.avalon.instance\", \n \"name\": \"reviewMain\", \n \"handleStart\": 10, \n \"frameEndHandle\": 1020, \n \"isolate\": false, \n \"publish\": true, \n \"label\": \"reviewMain (Alpaca_01) [991-1020]\", \n \"asset\": \"Alpaca_01\", \n \"fps\": 25.0, \n \"setMembers\": [\n \"|persp_CAM\", \n \"modelMain\"\n ], \n \"handleEnd\": 10, \n \"imagePlane\": true\n} ", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "pyblish.CollectInstances", + "threadName": "MainThread", + "msecs": 89.99991416931152, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 65, + "msg": "Creating instance for modelMain", + "exc_info": null, + "type": "record", + "levelname": "INFO" + }, + { + "instance_id": null, + "name": "pyblish.CollectInstances", + "threadName": "MainThread", + "msecs": 91.00008010864258, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 159, + "msg": "Found: \"modelMain\" ", + "exc_info": null, + "type": "record", + "levelname": "INFO" + }, + { + "instance_id": null, + "name": "pyblish.CollectInstances", + "threadName": "MainThread", + "msecs": 91.00008010864258, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 161, + "msg": "DATA: {\n \"subset\": \"modelMain\", \n \"families\": [\n \"model\"\n ], \n \"family\": \"model\", \n \"variant\": \"Main\", \n \"active\": true, \n \"attrPrefix\": \"\", \n \"cbId\": \"5dd50967e3c2c32b004b4817:bc3d17f26cde\", \n \"writeColorSets\": false, \n \"id\": \"pyblish.avalon.instance\", \n \"attr\": \"\", \n \"includeParentHierarchy\": false, \n \"name\": \"modelMain\", \n \"publish\": true, \n \"label\": \"modelMain (Alpaca_01)\", \n \"asset\": \"Alpaca_01\", \n \"setMembers\": [\n \"|pTorus_GEO\"\n ]\n} ", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + } + ] + } + ], + "label": "Collect Instances", + "skipped": false, + "order": 0, + "name": "CollectInstances" + }, + { + "instances_data": [], + "label": "Deadline Webservice from the Instance", + "skipped": true, + "order": 0, + "name": "CollectDeadlineServerFromInstance" + }, + { + "instances_data": [ + { + "id": null, + "logs": [] + } + ], + "label": "Collect DateTime data", + "skipped": false, + "order": 0, + "name": "CollectDateTimeData" + }, + { + "instances_data": [ + { + "id": null, + "logs": [] + } + ], + "label": "Collect Comment", + "skipped": false, + "order": 0, + "name": "CollectComment" + }, + { + "instances_data": [ + { + "id": null, + "logs": [] + } + ], + "label": "Maya Units", + "skipped": false, + "order": 0, + "name": "CollectMayaUnits" + }, + { + "instances_data": [ + { + "id": null, + "logs": [] + } + ], + "label": "Current working directory", + "skipped": false, + "order": 0, + "name": "CollectCurrentWorkingDirectory" + }, + { + "instances_data": [ + { + "id": null, + "logs": [ + { + "instance_id": null, + "name": "pyblish.CollectMachineName", + "threadName": "MainThread", + "msecs": 394.9999809265137, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 21, + "msg": "Machine name: 014-BAILEYS", + "exc_info": null, + "type": "record", + "levelname": "INFO" + } + ] + } + ], + "label": "Local Machine Name", + "skipped": false, + "order": 0, + "name": "CollectMachineName" + }, + { + "instances_data": [ + { + "id": null, + "logs": [] + } + ], + "label": "Current date", + "skipped": false, + "order": 0, + "name": "CollectCurrentDate" + }, + { + "instances_data": [ + { + "id": null, + "logs": [] + } + ], + "label": "Collect Current Time", + "skipped": false, + "order": 0, + "name": "CollectTime" + }, + { + "instances_data": [ + { + "id": null, + "logs": [] + } + ], + "label": "Unicode Strings", + "skipped": false, + "order": 0, + "name": "RepairUnicodeStrings" + }, + { + "instances_data": [ + { + "id": null, + "logs": [ + { + "instance_id": null, + "name": "pyblish.CollectCurrentUserPype", + "threadName": "MainThread", + "msecs": 648.0000019073486, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 17, + "msg": "Collected user \"jakub.trllo\"", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + } + ] + } + ], + "label": "Collect Pype User", + "skipped": false, + "order": 0.001, + "name": "CollectCurrentUserPype" + }, + { + "instances_data": [ + { + "id": null, + "logs": [ + { + "instance_id": null, + "name": "pyblish.CollectAnatomyContextData", + "threadName": "MainThread", + "msecs": 710.0000381469727, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 87, + "msg": "Global anatomy Data collected", + "exc_info": null, + "type": "record", + "levelname": "INFO" + }, + { + "instance_id": null, + "name": "pyblish.CollectAnatomyContextData", + "threadName": "MainThread", + "msecs": 710.0000381469727, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 88, + "msg": "{\n \"username\": \"jakub.trllo\", \n \"SS\": \"14\", \n \"hierarchy\": \"Assets\", \n \"H\": \"10\", \n \"app\": \"maya\", \n \"M\": \"23\", \n \"ht\": \"AM\", \n \"mmmm\": \"August\", \n \"S\": \"14\", \n \"dddd\": \"Wednesday\", \n \"HH\": \"10\", \n \"yyyy\": \"2021\", \n \"mmm\": \"Aug\", \n \"task\": \"animation\", \n \"d\": \"25\", \n \"mm\": \"08\", \n \"h\": \"10\", \n \"m\": \"8\", \n \"hh\": \"10\", \n \"project\": {\n \"code\": \"kuba_each_case\", \n \"name\": \"kuba_each_case\"\n }, \n \"MM\": \"23\", \n \"asset\": \"Alpaca_01\", \n \"yy\": \"21\", \n \"dd\": \"25\", \n \"ddd\": \"Wed\"\n}", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + } + ] + } + ], + "label": "Collect Anatomy Context Data", + "skipped": false, + "order": 0.002, + "name": "CollectAnatomyContextData" + }, + { + "instances_data": [], + "label": "Collect Vray Proxy", + "skipped": true, + "order": 0.01, + "name": "CollectVrayProxy" + }, + { + "instances_data": [], + "label": "Collect Vray Scene", + "skipped": true, + "order": 0.01, + "name": "CollectVrayScene" + }, + { + "instances_data": [ + { + "id": null, + "logs": [ + { + "instance_id": null, + "name": "pyblish.CollectDefaultDeadlineServer", + "threadName": "MainThread", + "msecs": 770.9999084472656, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 20, + "msg": "{u'default': u'http://127.0.0.1:8082'}", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + } + ] + } + ], + "label": "Default Deadline Webservice", + "skipped": false, + "order": 0.01, + "name": "CollectDefaultDeadlineServer" + }, + { + "instances_data": [ + { + "id": null, + "logs": [ + { + "instance_id": null, + "name": "pyblish.CollectMayaRender", + "threadName": "MainThread", + "msecs": 834.0001106262207, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 80, + "msg": "No render instance found, skipping render layer collection.", + "exc_info": null, + "type": "record", + "levelname": "INFO" + } + ] + } + ], + "label": "Collect Render Layers", + "skipped": false, + "order": 0.01, + "name": "CollectMayaRender" + }, + { + "instances_data": [], + "label": "Collect Renderable Camera(s)", + "skipped": true, + "order": 0.02, + "name": "CollectRenderableCamera" + }, + { + "instances_data": [], + "label": "Render Elements / AOVs", + "skipped": true, + "order": 0.02, + "name": "CollectRenderLayerAOVS" + }, + { + "instances_data": [], + "label": "Maya History", + "skipped": true, + "order": 0.1, + "name": "CollectMayaHistory" + }, + { + "instances_data": [], + "label": "Collect Ass", + "skipped": true, + "order": 0.2, + "name": "CollectAssData" + }, + { + "instances_data": [], + "label": "Collect Model Data", + "skipped": true, + "order": 0.2, + "name": "CollectMayaAscii" + }, + { + "instances_data": [], + "label": "Collect Rig Data", + "skipped": true, + "order": 0.2, + "name": "CollectRigData" + }, + { + "instances_data": [ + { + "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "logs": [] + } + ], + "label": "Collect Model Data", + "skipped": false, + "order": 0.2, + "name": "CollectModelData" + }, + { + "instances_data": [], + "label": "Collect Look", + "skipped": true, + "order": 0.2, + "name": "CollectLook" + }, + { + "instances_data": [], + "label": "Collect Model Data", + "skipped": true, + "order": 0.2, + "name": "CollectUnrealStaticMesh" + }, + { + "instances_data": [ + { + "id": null, + "logs": [] + } + ], + "label": "Context Label", + "skipped": false, + "order": 0.25, + "name": "CollectContextLabel" + }, + { + "instances_data": [ + { + "id": "b2d211bd-217a-4cfb-b989-45b3dfc4edc9", + "logs": [ + { + "instance_id": "b2d211bd-217a-4cfb-b989-45b3dfc4edc9", + "name": "pyblish.CollectReview", + "threadName": "MainThread", + "msecs": 22.00007438659668, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 20, + "msg": "instance: reviewMain", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "b2d211bd-217a-4cfb-b989-45b3dfc4edc9", + "name": "pyblish.CollectReview", + "threadName": "MainThread", + "msecs": 23.000001907348633, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 28, + "msg": "members: [u'|persp_CAM', u'modelMain']", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "b2d211bd-217a-4cfb-b989-45b3dfc4edc9", + "name": "pyblish.CollectReview", + "threadName": "MainThread", + "msecs": 23.000001907348633, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 33, + "msg": "camera: |persp_CAM|persp_CAMShape", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "b2d211bd-217a-4cfb-b989-45b3dfc4edc9", + "name": "pyblish.CollectReview", + "threadName": "MainThread", + "msecs": 23.999929428100586, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 41, + "msg": "subset for review: [u'modelMain']", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "b2d211bd-217a-4cfb-b989-45b3dfc4edc9", + "name": "pyblish.CollectReview", + "threadName": "MainThread", + "msecs": 23.999929428100586, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 46, + "msg": "filtering modelMain", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "b2d211bd-217a-4cfb-b989-45b3dfc4edc9", + "name": "pyblish.CollectReview", + "threadName": "MainThread", + "msecs": 25.00009536743164, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 60, + "msg": "adding review family to [u'modelMain']", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "b2d211bd-217a-4cfb-b989-45b3dfc4edc9", + "name": "pyblish.CollectReview", + "threadName": "MainThread", + "msecs": 25.00009536743164, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 74, + "msg": "data {u'subset': u'modelMain', 'families': [u'model', 'review'], 'fps': 25.0, 'family': u'model', 'frameStart': 1001, u'variant': u'Main', u'active': True, 'step': 1.0, 'review_camera': u'|persp_CAM|persp_CAMShape', 'frameEnd': 1010, u'attrPrefix': u'', u'cbId': u'5dd50967e3c2c32b004b4817:bc3d17f26cde', u'writeColorSets': False, u'id': u'pyblish.avalon.instance', u'attr': u'', u'includeParentHierarchy': False, 'name': u'modelMain', 'frameEndHandle': 1020, 'isolate': False, 'publish': True, 'label': 'modelMain (Alpaca_01)', 'handles': None, 'frameStartHandle': 991, u'asset': u'Alpaca_01', 'frameStartFtrack': 991, 'setMembers': [u'|pTorus_GEO'], 'frameEndFtrack': 1020}", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "b2d211bd-217a-4cfb-b989-45b3dfc4edc9", + "name": "pyblish.CollectReview", + "threadName": "MainThread", + "msecs": 26.999950408935547, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 77, + "msg": "isntance data {u'subset': u'reviewMain', 'families': [u'review'], 'family': u'review', u'frameStart': 1001, u'variant': u'Main', 'remove': True, u'active': True, u'step': 1.0, 'frameStartHandle': 991, u'frameEnd': 1010, u'cbId': u'5dd50967e3c2c32b004b4817:3a06e704e27a', u'keepImages': False, u'id': u'pyblish.avalon.instance', 'name': u'reviewMain', 'handleStart': 10, 'frameEndHandle': 1020, u'isolate': False, 'publish': True, 'label': 'reviewMain (Alpaca_01) [991-1020]', u'asset': u'Alpaca_01', u'fps': 25.0, 'setMembers': [u'|persp_CAM', u'modelMain'], 'handleEnd': 10, u'imagePlane': True}", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "b2d211bd-217a-4cfb-b989-45b3dfc4edc9", + "name": "pyblish.CollectReview", + "threadName": "MainThread", + "msecs": 28.0001163482666, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 46, + "msg": "filtering reviewMain", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "b2d211bd-217a-4cfb-b989-45b3dfc4edc9", + "name": "pyblish.CollectReview", + "threadName": "MainThread", + "msecs": 28.0001163482666, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 51, + "msg": "subset name does not match modelMain", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "b2d211bd-217a-4cfb-b989-45b3dfc4edc9", + "name": "pyblish.CollectReview", + "threadName": "MainThread", + "msecs": 29.000043869018555, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 46, + "msg": "filtering kuba_each_case_Alpaca_01_animation_v007", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "b2d211bd-217a-4cfb-b989-45b3dfc4edc9", + "name": "pyblish.CollectReview", + "threadName": "MainThread", + "msecs": 29.000043869018555, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 51, + "msg": "subset name does not match modelMain", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + } + ] + } + ], + "label": "Collect Review Data", + "skipped": false, + "order": 0.3, + "name": "CollectReview" + }, + { + "instances_data": [], + "label": "Collect Yeti Rig", + "skipped": true, + "order": 0.4, + "name": "CollectYetiRig" + }, + { + "instances_data": [], + "label": "Collect Animation Output Geometry", + "skipped": true, + "order": 0.4, + "name": "CollectAnimationOutputGeometry" + }, + { + "instances_data": [], + "label": "Collect Yeti Cache", + "skipped": true, + "order": 0.45, + "name": "CollectYetiCache" + }, + { + "instances_data": [ + { + "id": null, + "logs": [ + { + "instance_id": null, + "name": "pyblish.CollectAnatomyInstanceData", + "threadName": "MainThread", + "msecs": 84.0001106262207, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 42, + "msg": "Collecting anatomy data for all instances.", + "exc_info": null, + "type": "record", + "levelname": "INFO" + }, + { + "instance_id": null, + "name": "pyblish.CollectAnatomyInstanceData", + "threadName": "MainThread", + "msecs": 85.00003814697266, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 51, + "msg": "Qeurying asset documents for instances.", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "pyblish.CollectAnatomyInstanceData", + "threadName": "MainThread", + "msecs": 85.99996566772461, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 77, + "msg": "All instances already had right asset document.", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "pyblish.CollectAnatomyInstanceData", + "threadName": "MainThread", + "msecs": 85.99996566772461, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 123, + "msg": "Qeurying latest versions for instances.", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "pyblish.CollectAnatomyInstanceData", + "threadName": "MainThread", + "msecs": 98.00004959106445, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 210, + "msg": "Storing anatomy data to instance data.", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "pyblish.CollectAnatomyInstanceData", + "threadName": "MainThread", + "msecs": 98.9999771118164, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 279, + "msg": "Anatomy data for instance modelMain(modelMain (Alpaca_01)): {\n \"subset\": \"modelMain\", \n \"family\": \"model\", \n \"hierarchy\": \"Assets\", \n \"app\": \"maya\", \n \"yyyy\": \"2021\", \n \"HH\": \"10\", \n \"version\": 7, \n \"asset\": \"Alpaca_01\", \n \"fps\": 25.0, \n \"ddd\": \"Wed\", \n \"username\": \"jakub.trllo\", \n \"mm\": \"08\", \n \"H\": \"10\", \n \"dd\": \"25\", \n \"M\": \"23\", \n \"ht\": \"AM\", \n \"mmmm\": \"August\", \n \"S\": \"14\", \n \"dddd\": \"Wednesday\", \n \"mmm\": \"Aug\", \n \"task\": \"animation\", \n \"d\": \"25\", \n \"SS\": \"14\", \n \"h\": \"10\", \n \"m\": \"8\", \n \"hh\": \"10\", \n \"project\": {\n \"code\": \"kuba_each_case\", \n \"name\": \"kuba_each_case\"\n }, \n \"MM\": \"23\", \n \"yy\": \"21\"\n}", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "pyblish.CollectAnatomyInstanceData", + "threadName": "MainThread", + "msecs": 101.00007057189941, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 279, + "msg": "Anatomy data for instance reviewMain(reviewMain (Alpaca_01) [991-1020]): {\n \"subset\": \"reviewMain\", \n \"family\": \"review\", \n \"hierarchy\": \"Assets\", \n \"app\": \"maya\", \n \"yyyy\": \"2021\", \n \"HH\": \"10\", \n \"version\": 1, \n \"asset\": \"Alpaca_01\", \n \"fps\": 25.0, \n \"ddd\": \"Wed\", \n \"username\": \"jakub.trllo\", \n \"mm\": \"08\", \n \"H\": \"10\", \n \"dd\": \"25\", \n \"M\": \"23\", \n \"ht\": \"AM\", \n \"mmmm\": \"August\", \n \"S\": \"14\", \n \"dddd\": \"Wednesday\", \n \"mmm\": \"Aug\", \n \"task\": \"animation\", \n \"d\": \"25\", \n \"SS\": \"14\", \n \"h\": \"10\", \n \"m\": \"8\", \n \"hh\": \"10\", \n \"project\": {\n \"code\": \"kuba_each_case\", \n \"name\": \"kuba_each_case\"\n }, \n \"MM\": \"23\", \n \"yy\": \"21\"\n}", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "pyblish.CollectAnatomyInstanceData", + "threadName": "MainThread", + "msecs": 102.99992561340332, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 279, + "msg": "Anatomy data for instance kuba_each_case_Alpaca_01_animation_v007(workfileAnimation): {\n \"subset\": \"workfileAnimation\", \n \"family\": \"workfile\", \n \"hierarchy\": \"Assets\", \n \"app\": \"maya\", \n \"yyyy\": \"2021\", \n \"HH\": \"10\", \n \"version\": 7, \n \"asset\": \"Alpaca_01\", \n \"ddd\": \"Wed\", \n \"username\": \"jakub.trllo\", \n \"mm\": \"08\", \n \"H\": \"10\", \n \"dd\": \"25\", \n \"M\": \"23\", \n \"ht\": \"AM\", \n \"mmmm\": \"August\", \n \"S\": \"14\", \n \"dddd\": \"Wednesday\", \n \"mmm\": \"Aug\", \n \"task\": \"animation\", \n \"d\": \"25\", \n \"SS\": \"14\", \n \"h\": \"10\", \n \"m\": \"8\", \n \"hh\": \"10\", \n \"project\": {\n \"code\": \"kuba_each_case\", \n \"name\": \"kuba_each_case\"\n }, \n \"MM\": \"23\", \n \"yy\": \"21\"\n}", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "pyblish.CollectAnatomyInstanceData", + "threadName": "MainThread", + "msecs": 105.00001907348633, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 48, + "msg": "Anatomy Data collection finished.", + "exc_info": null, + "type": "record", + "levelname": "INFO" + } + ] + } + ], + "label": "Collect Anatomy Instance data", + "skipped": false, + "order": 0.49, + "name": "CollectAnatomyInstanceData" + }, + { + "instances_data": [], + "label": "Assemby", + "skipped": true, + "order": 0.49, + "name": "CollectAssembly" + }, + { + "instances_data": [ + { + "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "logs": [ + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.CollectResourcesPath", + "threadName": "MainThread", + "msecs": 164.99996185302734, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 93, + "msg": "publishDir: \"C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\"", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.CollectResourcesPath", + "threadName": "MainThread", + "msecs": 165.9998893737793, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 94, + "msg": "resourcesDir: \"C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\resources\"", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + } + ] + }, + { + "id": null, + "logs": [ + { + "instance_id": null, + "name": "pyblish.CollectResourcesPath", + "threadName": "MainThread", + "msecs": 224.99990463256836, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 93, + "msg": "publishDir: \"C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\workfile\\workfileAnimation\\v007\"", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "pyblish.CollectResourcesPath", + "threadName": "MainThread", + "msecs": 226.0000705718994, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 94, + "msg": "resourcesDir: \"C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\workfile\\workfileAnimation\\v007\\resources\"", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + } + ] + } + ], + "label": "Collect Resources Path", + "skipped": false, + "order": 0.495, + "name": "CollectResourcesPath" + }, + { + "instances_data": [ + { + "id": null, + "logs": [ + { + "instance_id": null, + "name": "pyblish.CollectRemoveMarked", + "threadName": "MainThread", + "msecs": 269.0000534057617, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 16, + "msg": "[pyblish.plugin.Instance(\"modelMain\"), pyblish.plugin.Instance(\"reviewMain\"), pyblish.plugin.Instance(\"kuba_each_case_Alpaca_01_animation_v007\")]", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + } + ] + } + ], + "label": "Remove Marked Instances", + "skipped": false, + "order": 0.499, + "name": "CollectRemoveMarked" + }, + { + "instances_data": [ + { + "id": null, + "logs": [] + } + ], + "label": "Validate File Saved", + "skipped": false, + "order": 0.9, + "name": "ValidateCurrentSaveFile" + }, + { + "instances_data": [], + "label": "Validate Assembly Namespaces", + "skipped": true, + "order": 1, + "name": "ValidateAssemblyNamespaces" + }, + { + "instances_data": [], + "label": "Validate Assembly Name", + "skipped": true, + "order": 1, + "name": "ValidateAssemblyName" + }, + { + "instances_data": [], + "label": "VRay Proxy Members", + "skipped": true, + "order": 1, + "name": "ValidateVrayProxyMembers" + }, + { + "instances_data": [ + { + "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "logs": [] + }, + { + "id": null, + "logs": [] + } + ], + "label": "Validate Version", + "skipped": false, + "order": 1, + "name": "ValidateVersion" + }, + { + "instances_data": [ + { + "id": null, + "logs": [ + { + "instance_id": null, + "name": "pyblish.ValidateEditorialAssetName", + "threadName": "MainThread", + "msecs": 517.9998874664307, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 19, + "msg": "__ asset_and_parents: {}", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "pyblish.ValidateEditorialAssetName", + "threadName": "MainThread", + "msecs": 520.9999084472656, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 26, + "msg": "__ db_assets: [{u'_id': ObjectId('5e11ec3e0002fc29f0728009'), u'data': {u'parents': []}, u'name': u'MyFolder'}, {u'_id': ObjectId('5dd50967e3c2c32b004b4811'), u'data': {u'parents': []}, u'name': u'Shots'}, {u'_id': ObjectId('5dd50967e3c2c32b004b4812'), u'data': {u'parents': []}, u'name': u'Assets'}, {u'_id': ObjectId('606d813fda091ca18ad3c1cb'), u'data': {u'parents': [u'Shots']}, u'name': u'sq01'}, {u'_id': ObjectId('60ffcef3ed7b98218feec87d'), u'data': {u'parents': [u'Shots']}, u'name': u'editorial'}, {u'_id': ObjectId('5eb40b96e3c2c3361ce67c05'), u'data': {u'parents': [u'Assets']}, u'name': u'Buddie_01'}, {u'_id': ObjectId('60ffcef3ed7b98218feec87e'), u'data': {u'parents': [u'Assets']}, u'name': u'characters'}, {u'_id': ObjectId('60ffcef3ed7b98218feec87f'), u'data': {u'parents': [u'Assets']}, u'name': u'locations'}, {u'_id': ObjectId('5dd50967e3c2c32b004b4817'), u'data': {u'parents': [u'Assets']}, u'name': u'Alpaca_01'}, {u'_id': ObjectId('60d9a8e8a5016c2a23f88534'), u'data': {u'parents': [u'Shots', u'sq01']}, u'name': u'sq01_sh0020'}, {u'_id': ObjectId('60d9a8e8a5016c2a23f88535'), u'data': {u'parents': [u'Shots', u'sq01']}, u'name': u'sq01_sh0030'}, {u'_id': ObjectId('606d813fda091ca18ad3c1cd'), u'data': {u'parents': [u'Shots', u'sq01']}, u'name': u'sq01_sh0010'}]", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "pyblish.ValidateEditorialAssetName", + "threadName": "MainThread", + "msecs": 523.0000019073486, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 33, + "msg": "__ project_entities: {'Alpaca_01': [u'Assets'],\n 'Assets': [],\n 'Buddie_01': [u'Assets'],\n 'MyFolder': [],\n 'Shots': [],\n 'characters': [u'Assets'],\n 'editorial': [u'Shots'],\n 'locations': [u'Assets'],\n 'sq01': [u'Shots'],\n 'sq01_sh0010': [u'Shots', u'sq01'],\n 'sq01_sh0020': [u'Shots', u'sq01'],\n 'sq01_sh0030': [u'Shots', u'sq01']}", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + } + ] + } + ], + "label": "Validate Editorial Asset Name", + "skipped": false, + "order": 1, + "name": "ValidateEditorialAssetName" + }, + { + "instances_data": [], + "label": "Yeti Rig Settings", + "skipped": true, + "order": 1, + "name": "ValidateYetiRigSettings" + }, + { + "instances_data": [ + { + "id": null, + "logs": [ + { + "instance_id": null, + "name": "pyblish.ValidateFFmpegInstalled", + "threadName": "MainThread", + "msecs": 579.9999237060547, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 31, + "msg": "ffmpeg path: `C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\vendor\\bin\\ffmpeg\\windows\\bin\\ffmpeg`", + "exc_info": null, + "type": "record", + "levelname": "INFO" + } + ] + } + ], + "label": "Validate ffmpeg installation", + "skipped": false, + "order": 1, + "name": "ValidateFFmpegInstalled" + }, + { + "instances_data": [], + "label": "Current Render Layer Has Renderable Camera", + "skipped": true, + "order": 1, + "name": "ValidateCurrentRenderLayerIsRenderable" + }, + { + "instances_data": [], + "label": "No V-Ray Proxies (VRayMesh)", + "skipped": true, + "order": 1, + "name": "ValidateNoVRayMesh" + }, + { + "instances_data": [], + "label": "Yeti Rig Cache State", + "skipped": true, + "order": 1, + "name": "ValidateYetiRigCacheState" + }, + { + "instances_data": [], + "label": "VRay Proxy Settings", + "skipped": true, + "order": 1, + "name": "ValidateVrayProxy" + }, + { + "instances_data": [ + { + "id": null, + "logs": [] + } + ], + "label": "Validate Containers", + "skipped": false, + "order": 1, + "name": "ValidateContainers" + }, + { + "instances_data": [], + "label": "VRay Referenced AOVs", + "skipped": true, + "order": 1, + "name": "ValidateVrayReferencedAOVs" + }, + { + "instances_data": [], + "label": "Validate Deadline Web Service", + "skipped": true, + "order": 1, + "name": "ValidateDeadlineConnection" + }, + { + "instances_data": [], + "label": "Validate Muster REST API Service", + "skipped": true, + "order": 1, + "name": "ValidateMusterConnection" + }, + { + "instances_data": [], + "label": "Instancer Content", + "skipped": true, + "order": 1, + "name": "ValidateInstancerContent" + }, + { + "instances_data": [], + "label": "Instancer Cache Frame Ranges", + "skipped": true, + "order": 1, + "name": "ValidateInstancerFrameRanges" + }, + { + "instances_data": [ + { + "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "logs": [] + } + ], + "label": "Node Ids Related (ID)", + "skipped": false, + "order": 1.05, + "name": "ValidateNodeIDsRelated" + }, + { + "instances_data": [ + { + "id": null, + "logs": [] + } + ], + "label": "Maya Workspace Set", + "skipped": false, + "order": 1.05, + "name": "ValidateSceneSetWorkspace" + }, + { + "instances_data": [ + { + "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "logs": [] + } + ], + "label": "Instance Nodes Have ID", + "skipped": false, + "order": 1.05, + "name": "ValidateNodeIDs" + }, + { + "instances_data": [], + "label": "Look members unique", + "skipped": true, + "order": 1.05, + "name": "ValidateUniqueRelationshipMembers" + }, + { + "instances_data": [ + { + "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "logs": [] + } + ], + "label": "Non Duplicate Instance Members (ID)", + "skipped": false, + "order": 1.05, + "name": "ValidateNodeIdsUnique" + }, + { + "instances_data": [ + { + "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "logs": [] + }, + { + "id": null, + "logs": [] + } + ], + "label": "Node Ids in Database", + "skipped": false, + "order": 1.05, + "name": "ValidateNodeIdsInDatabase" + }, + { + "instances_data": [], + "label": "Look Shading Engine Naming", + "skipped": true, + "order": 1.1, + "name": "ValidateShadingEngine" + }, + { + "instances_data": [], + "label": "Joints Hidden", + "skipped": true, + "order": 1.1, + "name": "ValidateRigJointsHidden" + }, + { + "instances_data": [], + "label": "Animation Content", + "skipped": true, + "order": 1.1, + "name": "ValidateAnimationContent" + }, + { + "instances_data": [], + "label": "Step size", + "skipped": true, + "order": 1.1, + "name": "ValidateStepSize" + }, + { + "instances_data": [], + "label": "Single Assembly", + "skipped": true, + "order": 1.1, + "name": "ValidateSingleAssembly" + }, + { + "instances_data": [], + "label": "Render Settings", + "skipped": true, + "order": 1.1, + "name": "ValidateRenderSettings" + }, + { + "instances_data": [], + "label": "No Default Cameras", + "skipped": true, + "order": 1.1, + "name": "ValidateNoDefaultCameras" + }, + { + "instances_data": [ + { + "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "logs": [] + }, + { + "id": null, + "logs": [] + } + ], + "label": "Instance in same Context", + "skipped": false, + "order": 1.1, + "name": "ValidateInstanceInContext" + }, + { + "instances_data": [], + "label": "VRay Distributed Rendering", + "skipped": true, + "order": 1.1, + "name": "ValidateVRayDistributedRendering" + }, + { + "instances_data": [], + "label": "No Default Cameras Renderable", + "skipped": true, + "order": 1.1, + "name": "ValidateRenderNoDefaultCameras" + }, + { + "instances_data": [], + "label": "Look Default Shader Connections", + "skipped": true, + "order": 1.1, + "name": "ValidateLookDefaultShadersConnections" + }, + { + "instances_data": [], + "label": "Yeti Render Script Callbacks", + "skipped": true, + "order": 1.1, + "name": "ValidateYetiRenderScriptCallbacks" + }, + { + "instances_data": [ + { + "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "logs": [] + } + ], + "label": "Validate Frame Range", + "skipped": false, + "order": 1.1, + "name": "ValidateFrameRange" + }, + { + "instances_data": [], + "label": "Render Passes / AOVs Are Registered", + "skipped": true, + "order": 1.1, + "name": "ValidateRenderLayerAOVs" + }, + { + "instances_data": [], + "label": "Camera Contents", + "skipped": true, + "order": 1.1, + "name": "ValidateCameraContents" + }, + { + "instances_data": [], + "label": "Render Single Camera", + "skipped": true, + "order": 1.1, + "name": "ValidateRenderSingleCamera" + }, + { + "instances_data": [ + { + "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "logs": [] + } + ], + "label": "Suffix Naming Conventions", + "skipped": false, + "order": 1.1, + "name": "ValidateTransformNamingSuffix" + }, + { + "instances_data": [ + { + "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "logs": [ + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ValidateResources", + "threadName": "MainThread", + "msecs": 13.99993896484375, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 27, + "msg": "No resources to validate..", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + } + ] + }, + { + "id": null, + "logs": [ + { + "instance_id": null, + "name": "pyblish.ValidateResources", + "threadName": "MainThread", + "msecs": 75.99997520446777, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 27, + "msg": "No resources to validate..", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + } + ] + } + ], + "label": "Resources Unique", + "skipped": false, + "order": 1.1, + "name": "ValidateResources" + }, + { + "instances_data": [], + "label": "Look Sets", + "skipped": true, + "order": 1.1, + "name": "ValidateLookSets" + }, + { + "instances_data": [], + "label": "Look Data Contents", + "skipped": true, + "order": 1.1, + "name": "ValidateLookContents" + }, + { + "instances_data": [], + "label": "Look Single Shader Per Shape", + "skipped": true, + "order": 1.1, + "name": "ValidateSingleShader" + }, + { + "instances_data": [ + { + "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "logs": [] + }, + { + "id": null, + "logs": [] + } + ], + "label": "Subset Name", + "skipped": false, + "order": 1.1, + "name": "ValidateSubsetName" + }, + { + "instances_data": [], + "label": "Skincluster Deformer Relationships", + "skipped": true, + "order": 1.1, + "name": "ValidateSkinclusterDeformerSet" + }, + { + "instances_data": [], + "label": "Images File Rule (Workspace)", + "skipped": true, + "order": 1.1, + "name": "ValidateRenderImageRule" + }, + { + "instances_data": [], + "label": "Yeti Rig Input Shapes In Instance", + "skipped": true, + "order": 1.1, + "name": "ValidateYetiRigInputShapesInInstance" + }, + { + "instances_data": [], + "label": "Animation Out Set Related Node Ids", + "skipped": true, + "order": 1.1, + "name": "ValidateOutRelatedNodeIds" + }, + { + "instances_data": [ + { + "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "logs": [] + } + ], + "label": "Model Content", + "skipped": false, + "order": 1.1, + "name": "ValidateModelContent" + }, + { + "instances_data": [], + "label": "Rig Out Set Node Ids", + "skipped": true, + "order": 1.1, + "name": "ValidateRigOutSetNodeIds" + }, + { + "instances_data": [], + "label": "VRay Translator Settings", + "skipped": true, + "order": 1.1, + "name": "ValidateVRayTranslatorEnabled" + }, + { + "instances_data": [ + { + "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "logs": [] + } + ], + "label": "No Namespaces", + "skipped": false, + "order": 1.1, + "name": "ValidateNoNamespace" + }, + { + "instances_data": [], + "label": "Unreal StaticMesh Name", + "skipped": true, + "order": 1.1, + "name": "ValidateUnrealStaticmeshName" + }, + { + "instances_data": [ + { + "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "logs": [] + } + ], + "label": "Unknown Nodes", + "skipped": false, + "order": 1.1, + "name": "ValidateNoUnknownNodes" + }, + { + "instances_data": [], + "label": "ASS has relative texture paths", + "skipped": true, + "order": 1.1, + "name": "ValidateAssRelativePaths" + }, + { + "instances_data": [], + "label": "Unreal Up-Axis check", + "skipped": true, + "order": 1.1, + "name": "ValidateUnrealUpAxis" + }, + { + "instances_data": [], + "label": "Look Id Reference Edits", + "skipped": true, + "order": 1.1, + "name": "ValidateLookIdReferenceEdits" + }, + { + "instances_data": [ + { + "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "logs": [] + }, + { + "id": null, + "logs": [] + } + ], + "label": "Instance has members", + "skipped": false, + "order": 1.1, + "name": "ValidateInstanceHasMembers" + }, + { + "instances_data": [ + { + "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "logs": [] + } + ], + "label": "No Empty/Null Transforms", + "skipped": false, + "order": 1.1, + "name": "ValidateNoNullTransforms" + }, + { + "instances_data": [], + "label": "Deformed shape ids", + "skipped": true, + "order": 1.1, + "name": "ValidateNodeIdsDeformedShape" + }, + { + "instances_data": [], + "label": "Rig Contents", + "skipped": true, + "order": 1.1, + "name": "ValidateRigContents" + }, + { + "instances_data": [], + "label": "Look No Default Shaders", + "skipped": true, + "order": 1.11, + "name": "ValidateLookNoDefaultShaders" + }, + { + "instances_data": [], + "label": "Rig Controllers (Arnold Attributes)", + "skipped": true, + "order": 1.1500000000000001, + "name": "ValidateRigControllersArnoldAttributes" + }, + { + "instances_data": [], + "label": "Rig Controllers", + "skipped": true, + "order": 1.1500000000000001, + "name": "ValidateRigControllers" + }, + { + "instances_data": [], + "label": "Rig Output Ids", + "skipped": true, + "order": 1.1500000000000001, + "name": "ValidateRigOutputIds" + }, + { + "instances_data": [ + { + "id": null, + "logs": [ + { + "instance_id": null, + "name": "pyblish.ValidateMayaUnits", + "threadName": "MainThread", + "msecs": 769.9999809265137, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 36, + "msg": "Units (linear): cm", + "exc_info": null, + "type": "record", + "levelname": "INFO" + }, + { + "instance_id": null, + "name": "pyblish.ValidateMayaUnits", + "threadName": "MainThread", + "msecs": 770.9999084472656, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 37, + "msg": "Units (angular): deg", + "exc_info": null, + "type": "record", + "levelname": "INFO" + }, + { + "instance_id": null, + "name": "pyblish.ValidateMayaUnits", + "threadName": "MainThread", + "msecs": 772.0000743865967, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 38, + "msg": "Units (time): 25.0 FPS", + "exc_info": null, + "type": "record", + "levelname": "INFO" + } + ] + } + ], + "label": "Maya Units", + "skipped": false, + "order": 1.2, + "name": "ValidateMayaUnits" + }, + { + "instances_data": [ + { + "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "logs": [] + } + ], + "label": "Mesh Shader Connections", + "skipped": false, + "order": 1.3, + "name": "ValidateMeshShaderConnections" + }, + { + "instances_data": [ + { + "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "logs": [] + } + ], + "label": "Mesh Vertices Have Edges", + "skipped": false, + "order": 1.3, + "name": "ValidateMeshVerticesHaveEdges" + }, + { + "instances_data": [ + { + "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "logs": [] + } + ], + "label": "Mesh Edge Length Non Zero", + "skipped": false, + "order": 1.3, + "name": "ValidateMeshNonZeroEdgeLength" + }, + { + "instances_data": [], + "label": "Mesh is Triangulated", + "skipped": true, + "order": 1.3, + "name": "ValidateUnrealMeshTriangulated" + }, + { + "instances_data": [ + { + "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "logs": [] + } + ], + "label": "Mesh Has UVs", + "skipped": false, + "order": 1.3, + "name": "ValidateMeshHasUVs" + }, + { + "instances_data": [ + { + "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "logs": [] + } + ], + "label": "Mesh No Negative Scale", + "skipped": false, + "order": 1.3, + "name": "ValidateMeshNoNegativeScale" + }, + { + "instances_data": [], + "label": "Assembly Model Transforms", + "skipped": true, + "order": 1.49, + "name": "ValidateAssemblyModelTransforms" + }, + { + "instances_data": [], + "label": "Extract Hierarchy To Avalon", + "skipped": true, + "order": 1.99, + "name": "ExtractHierarchyToAvalon" + }, + { + "instances_data": [], + "label": "Extract Yeti Rig", + "skipped": true, + "order": 2.0, + "name": "ExtractYetiRig" + }, + { + "instances_data": [], + "label": "VRay Proxy (.vrmesh)", + "skipped": true, + "order": 2.0, + "name": "ExtractVRayProxy" + }, + { + "instances_data": [], + "label": "Extract Animation", + "skipped": true, + "order": 2.0, + "name": "ExtractAnimation" + }, + { + "instances_data": [ + { + "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "logs": [ + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractThumbnail", + "threadName": "MainThread", + "msecs": 141.9999599456787, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 27, + "msg": "Extracting capture..", + "exc_info": null, + "type": "record", + "levelname": "INFO" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractThumbnail", + "threadName": "MainThread", + "msecs": 144.00005340576172, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 41, + "msg": "Using viewport preset: {'sound': None, 'display_options': {u'backgroundTop': [0.49019607843137253, 0.49019607843137253, 0.49019607843137253], 'displayGradient': True, u'backgroundBottom': [0.49019607843137253, 0.49019607843137253, 0.49019607843137253], u'background': [0.49019607843137253, 0.49019607843137253, 0.49019607843137253]}, 'compression': u'jpg', 'format': u'image', 'off_screen': True, 'viewport2_options': {'multiSampleEnable': True, 'textureMaxResolution': 1024, 'enableTextureMaxRes': True, 'textureMaxResMode': 1, 'multiSampleCount': 4, 'ssaoEnable': True}, 'height': 1080, 'width': 1920, 'quality': 95, 'isolate_view': True, 'viewport_options': {'hud': False, 'particleInstancers': False, 'manipulators': False, 'joints': False, 'nParticles': False, 'dynamicConstraints': False, 'rendererName': u'vp2Renderer', 'pluginShapes': False, 'deformers': False, 'motionTrails': False, 'dynamics': False, 'pivots': False, 'dimensions': False, 'cameras': False, 'ikHandles': False, 'fluids': False, 'lights': False, 'handles': False, 'controlVertices': False, 'greasePencils': False, 'locators': False, 'displayLights': u'default', 'nurbsCurves': False, 'textures': True, 'nCloths': False, 'subdivSurfaces': False, 'strokes': False, 'follicles': False, 'polymeshes': True, 'grid': False, 'imagePlane': True, 'nurbsSurfaces': False, 'shadows': True, 'clipGhosts': False, 'hairSystems': True, 'hulls': False, 'twoSidedLighting': True, 'planes': False, 'nRigids': False}}", + "exc_info": null, + "type": "record", + "levelname": "INFO" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractThumbnail", + "threadName": "MainThread", + "msecs": 150.00009536743164, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 65, + "msg": "Outputting images to c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr\\modelMain", + "exc_info": null, + "type": "record", + "levelname": "INFO" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractThumbnail", + "threadName": "MainThread", + "msecs": 519.0000534057617, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 94, + "msg": "file list modelMain.1001.jpg", + "exc_info": null, + "type": "record", + "levelname": "INFO" + } + ] + } + ], + "label": "Thumbnail", + "skipped": false, + "order": 2.0, + "name": "ExtractThumbnail" + }, + { + "instances_data": [ + { + "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "logs": [ + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractAlembic", + "threadName": "MainThread", + "msecs": 576.9999027252197, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 43, + "msg": "Extracting pointcache..", + "exc_info": null, + "type": "record", + "levelname": "INFO" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractAlembic", + "threadName": "MainThread", + "msecs": 628.9999485015869, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 92, + "msg": "Extracted modelMain to c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr", + "exc_info": null, + "type": "record", + "levelname": "INFO" + } + ] + } + ], + "label": "Extract Pointcache (Alembic)", + "skipped": false, + "order": 2.0, + "name": "ExtractAlembic" + }, + { + "instances_data": [], + "label": "Ass Standin (.ass)", + "skipped": true, + "order": 2.0, + "name": "ExtractAssStandin" + }, + { + "instances_data": [ + { + "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "logs": [ + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractPlayblast", + "threadName": "MainThread", + "msecs": 644.0000534057617, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 29, + "msg": "Extracting capture..", + "exc_info": null, + "type": "record", + "levelname": "INFO" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractPlayblast", + "threadName": "MainThread", + "msecs": 644.9999809265137, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 43, + "msg": "start: 991, end: 1020", + "exc_info": null, + "type": "record", + "levelname": "INFO" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractPlayblast", + "threadName": "MainThread", + "msecs": 644.9999809265137, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 62, + "msg": "Outputting images to c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr\\modelMain", + "exc_info": null, + "type": "record", + "levelname": "INFO" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractPlayblast", + "threadName": "MainThread", + "msecs": 657.0000648498535, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 93, + "msg": "using viewport preset: {'sound': None, 'display_options': {u'backgroundTop': [0.49019607843137253, 0.49019607843137253, 0.49019607843137253], 'displayGradient': True, u'backgroundBottom': [0.49019607843137253, 0.49019607843137253, 0.49019607843137253], u'background': [0.49019607843137253, 0.49019607843137253, 0.49019607843137253]}, 'compression': u'jpg', 'format': u'image', 'viewer': False, 'end_frame': 1020, 'start_frame': 991, 'off_screen': True, 'viewport2_options': {'multiSampleEnable': True, 'textureMaxResolution': 1024, 'enableTextureMaxRes': True, 'textureMaxResMode': 1, 'multiSampleCount': 4, 'ssaoEnable': True}, 'height': 1080, 'width': 1920, 'camera': u'|persp_CAM|persp_CAMShape', 'filename': 'c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr\\\\modelMain', 'quality': 95, 'overwrite': True, 'viewport_options': {'hud': False, 'particleInstancers': False, 'manipulators': False, 'joints': False, 'nParticles': False, 'dynamicConstraints': False, 'rendererName': u'vp2Renderer', 'pluginShapes': False, 'deformers': False, 'motionTrails': False, 'dynamics': False, 'pivots': False, 'dimensions': False, 'cameras': False, 'ikHandles': False, 'fluids': False, 'lights': False, 'handles': False, 'controlVertices': False, 'greasePencils': False, 'locators': False, 'displayLights': u'default', 'nurbsCurves': False, 'textures': True, 'nCloths': False, 'subdivSurfaces': False, 'strokes': False, 'follicles': False, 'polymeshes': True, 'grid': False, 'imagePlane': True, 'nurbsSurfaces': False, 'shadows': True, 'clipGhosts': False, 'hairSystems': True, 'hulls': False, 'twoSidedLighting': True, 'planes': False, 'nRigids': False}}", + "exc_info": null, + "type": "record", + "levelname": "INFO" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractPlayblast", + "threadName": "MainThread", + "msecs": 125.99992752075195, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 97, + "msg": "playblast path c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr\\modelMain.####.jpg", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractPlayblast", + "threadName": "MainThread", + "msecs": 128.00002098083496, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 102, + "msg": "filename c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr\\modelMain", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractPlayblast", + "threadName": "MainThread", + "msecs": 128.9999485015869, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 106, + "msg": "collection head modelMain", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractPlayblast", + "threadName": "MainThread", + "msecs": 128.9999485015869, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 111, + "msg": "we found collection of interest modelMain.%04d.jpg [991-1020]", + "exc_info": null, + "type": "record", + "levelname": "INFO" + } + ] + } + ], + "label": "Extract Playblast", + "skipped": false, + "order": 2.0, + "name": "ExtractPlayblast" + }, + { + "instances_data": [], + "label": "Extract Yeti Cache", + "skipped": true, + "order": 2.0, + "name": "ExtractYetiCache" + }, + { + "instances_data": [], + "label": "Redshift Proxy (.rs)", + "skipped": true, + "order": 2.0, + "name": "ExtractRedshiftProxy" + }, + { + "instances_data": [], + "label": "Extract RenderSetup", + "skipped": true, + "order": 2.0, + "name": "ExtractRenderSetup" + }, + { + "instances_data": [], + "label": "Camera (Alembic)", + "skipped": true, + "order": 2.0, + "name": "ExtractCameraAlembic" + }, + { + "instances_data": [], + "label": "Camera (Maya Scene)", + "skipped": true, + "order": 2.0, + "name": "ExtractCameraMayaScene" + }, + { + "instances_data": [], + "label": "Maya Scene (Raw)", + "skipped": true, + "order": 2.0, + "name": "ExtractMayaSceneRaw" + }, + { + "instances_data": [], + "label": "Extract Rig (Maya Scene)", + "skipped": true, + "order": 2.0, + "name": "ExtractRig" + }, + { + "instances_data": [], + "label": "VRay Scene (.vrscene)", + "skipped": true, + "order": 2.0, + "name": "ExtractVrayscene" + }, + { + "instances_data": [], + "label": "Extract Assembly", + "skipped": true, + "order": 2.0, + "name": "ExtractAssembly" + }, + { + "instances_data": [ + { + "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "logs": [ + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractModel", + "threadName": "MainThread", + "msecs": 282.99999237060547, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 39, + "msg": "Looking in settings for scene type ...", + "exc_info": null, + "type": "record", + "levelname": "INFO" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractModel", + "threadName": "MainThread", + "msecs": 289.0000343322754, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 45, + "msg": "Using ma as scene type", + "exc_info": null, + "type": "record", + "levelname": "INFO" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractModel", + "threadName": "MainThread", + "msecs": 289.0000343322754, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 56, + "msg": "Performing extraction ...", + "exc_info": null, + "type": "record", + "levelname": "INFO" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractModel", + "threadName": "MainThread", + "msecs": 329.9999237060547, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 102, + "msg": "Extracted instance 'modelMain' to: c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr\\modelMain.ma", + "exc_info": null, + "type": "record", + "levelname": "INFO" + } + ] + } + ], + "label": "Model (Maya Scene)", + "skipped": false, + "order": 2.0, + "name": "ExtractModel" + }, + { + "instances_data": [], + "label": "Extract Xgen ABC Cache", + "skipped": true, + "order": 2.0, + "name": "ExtractXgenCache" + }, + { + "instances_data": [], + "label": "Extract FBX", + "skipped": true, + "order": 2, + "name": "ExtractFBX" + }, + { + "instances_data": [ + { + "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "logs": [ + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractReview", + "threadName": "MainThread", + "msecs": 403.0001163482666, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 65, + "msg": "[{'files': u'modelMain.1001.jpg', 'ext': 'jpg', 'stagingDir': 'c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr', 'name': 'thumbnail', 'thumbnail': True}, {'files': 'modelMain.abc', 'ext': 'abc', 'stagingDir': 'c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr', 'name': 'abc'}, {'files': ['modelMain.0991.jpg', 'modelMain.0992.jpg', 'modelMain.0993.jpg', 'modelMain.0994.jpg', 'modelMain.0995.jpg', 'modelMain.0996.jpg', 'modelMain.0997.jpg', 'modelMain.0998.jpg', 'modelMain.0999.jpg', 'modelMain.1000.jpg', 'modelMain.1001.jpg', 'modelMain.1002.jpg', 'modelMain.1003.jpg', 'modelMain.1004.jpg', 'modelMain.1005.jpg', 'modelMain.1006.jpg', 'modelMain.1007.jpg', 'modelMain.1008.jpg', 'modelMain.1009.jpg', 'modelMain.1010.jpg', 'modelMain.1011.jpg', 'modelMain.1012.jpg', 'modelMain.1013.jpg', 'modelMain.1014.jpg', 'modelMain.1015.jpg', 'modelMain.1016.jpg', 'modelMain.1017.jpg', 'modelMain.1018.jpg', 'modelMain.1019.jpg', 'modelMain.1020.jpg'], 'ext': 'png', 'tags': ['review', 'delete'], 'name': 'png', 'fps': 25.0, 'frameStart': 991, 'camera_name': u'persp_CAM', 'preview': True, 'stagingDir': 'c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr', 'frameEnd': 1020}, {'files': 'modelMain.ma', 'ext': u'ma', 'stagingDir': 'c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr', 'name': u'ma'}]", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractReview", + "threadName": "MainThread", + "msecs": 405.99989891052246, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 96, + "msg": "Host: \"maya\"", + "exc_info": null, + "type": "record", + "levelname": "INFO" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractReview", + "threadName": "MainThread", + "msecs": 405.99989891052246, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 97, + "msg": "Task: \"animation\"", + "exc_info": null, + "type": "record", + "levelname": "INFO" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractReview", + "threadName": "MainThread", + "msecs": 407.0000648498535, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 98, + "msg": "Family: \"model\"", + "exc_info": null, + "type": "record", + "levelname": "INFO" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractReview", + "threadName": "MainThread", + "msecs": 407.0000648498535, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 110, + "msg": "Matching profile: \"{\"families\": [], \"hosts\": [], \"outputs\": {\"h264\": {\"tags\": [\"burnin\", \"ftrackreview\"], \"height\": 0, \"filter\": {\"families\": [\"render\", \"review\", \"ftrack\"]}, \"width\": 0, \"ext\": \"mp4\", \"overscan_color\": [0, 0, 0, 255], \"letter_box\": {\"line_color\": [255, 0, 0, 255], \"ratio\": 0.0, \"fill_color\": [0, 0, 0, 255], \"enabled\": false, \"state\": \"letterbox\", \"line_thickness\": 0}, \"bg_color\": [0, 0, 0, 0], \"overscan_crop\": \"\", \"ffmpeg_args\": {\"video_filters\": [], \"input\": [\"-apply_trc gamma22\"], \"audio_filters\": [], \"output\": [\"-pix_fmt yuv420p\", \"-crf 18\", \"-intra\"]}}}}\"", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractReview", + "threadName": "MainThread", + "msecs": 408.9999198913574, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 136, + "msg": "Repre: thumbnail - Didn't found \"review\" in tags. Skipping", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractReview", + "threadName": "MainThread", + "msecs": 410.0000858306885, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 136, + "msg": "Repre: abc - Didn't found \"review\" in tags. Skipping", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractReview", + "threadName": "MainThread", + "msecs": 410.0000858306885, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 192, + "msg": "New representation tags: `['review', u'burnin', u'ftrackreview']`", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractReview", + "threadName": "MainThread", + "msecs": 411.00001335144043, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 199, + "msg": "Filling gaps in sequence.", + "exc_info": null, + "type": "record", + "levelname": "INFO" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "openpype.lib.plugin_tools", + "threadName": "MainThread", + "msecs": 411.9999408721924, + "filename": "plugin_tools.py", + "levelno": 10, + "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\lib\\plugin_tools.py", + "lineno": 360, + "msg": "OIIOTool is not configured or not present at C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\vendor\\bin\\oiio\\windows\\oiiotool", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractReview", + "threadName": "MainThread", + "msecs": 413.00010681152344, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 761, + "msg": "New representation ext: `mp4`", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractReview", + "threadName": "MainThread", + "msecs": 413.00010681152344, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 817, + "msg": "Input path c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr\\modelMain.%04d.jpg", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractReview", + "threadName": "MainThread", + "msecs": 414.0000343322754, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 818, + "msg": "Output path c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr\\modelMain_h264.mp4", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractReview", + "threadName": "MainThread", + "msecs": 414.0000343322754, + "filename": "vendor_bin_utils.py", + "levelno": 20, + "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\lib\\vendor_bin_utils.py", + "lineno": 71, + "msg": "Getting information about input \"c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr\\modelMain.0991.jpg\".", + "exc_info": null, + "type": "record", + "levelname": "INFO" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractReview", + "threadName": "MainThread", + "msecs": 414.99996185302734, + "filename": "vendor_bin_utils.py", + "levelno": 10, + "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\lib\\vendor_bin_utils.py", + "lineno": 82, + "msg": "FFprobe command: \"\"C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\vendor\\bin\\ffmpeg\\windows\\bin\\ffprobe\" -v quiet -print_format json -show_format -show_streams \"c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr\\modelMain.0991.jpg\"\"", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractReview", + "threadName": "MainThread", + "msecs": 779.0000438690186, + "filename": "vendor_bin_utils.py", + "levelno": 10, + "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\lib\\vendor_bin_utils.py", + "lineno": 93, + "msg": "FFprobe stdout:\n{\r\n \"streams\": [\r\n {\r\n \"index\": 0,\r\n \"codec_name\": \"mjpeg\",\r\n \"codec_long_name\": \"Motion JPEG\",\r\n \"profile\": \"Baseline\",\r\n \"codec_type\": \"video\",\r\n \"codec_tag_string\": \"[0][0][0][0]\",\r\n \"codec_tag\": \"0x0000\",\r\n \"width\": 1920,\r\n \"height\": 1080,\r\n \"coded_width\": 1920,\r\n \"coded_height\": 1080,\r\n \"closed_captions\": 0,\r\n \"has_b_frames\": 0,\r\n \"pix_fmt\": \"yuvj420p\",\r\n \"level\": -99,\r\n \"color_range\": \"pc\",\r\n \"color_space\": \"bt470bg\",\r\n \"chroma_location\": \"center\",\r\n \"refs\": 1,\r\n \"r_frame_rate\": \"25/1\",\r\n \"avg_frame_rate\": \"25/1\",\r\n \"time_base\": \"1/25\",\r\n \"start_pts\": 0,\r\n \"start_time\": \"0.000000\",\r\n \"duration_ts\": 1,\r\n \"duration\": \"0.040000\",\r\n \"bits_per_raw_sample\": \"8\",\r\n \"disposition\": {\r\n \"default\": 0,\r\n \"dub\": 0,\r\n \"original\": 0,\r\n \"comment\": 0,\r\n \"lyrics\": 0,\r\n \"karaoke\": 0,\r\n \"forced\": 0,\r\n \"hearing_impaired\": 0,\r\n \"visual_impaired\": 0,\r\n \"clean_effects\": 0,\r\n \"attached_pic\": 0,\r\n \"timed_thumbnails\": 0\r\n }\r\n }\r\n ],\r\n \"format\": {\r\n \"filename\": \"c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr\\\\modelMain.0991.jpg\",\r\n \"nb_streams\": 1,\r\n \"nb_programs\": 0,\r\n \"format_name\": \"image2\",\r\n \"format_long_name\": \"image2 sequence\",\r\n \"start_time\": \"0.000000\",\r\n \"duration\": \"0.040000\",\r\n \"size\": \"36385\",\r\n \"bit_rate\": \"7277000\",\r\n \"probe_score\": 50\r\n }\r\n}\r\n", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractReview", + "threadName": "MainThread", + "msecs": 783.9999198913574, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 1018, + "msg": "Overscan color: `#000000`", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractReview", + "threadName": "MainThread", + "msecs": 783.9999198913574, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 1064, + "msg": "pixel_aspect: `1`", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractReview", + "threadName": "MainThread", + "msecs": 783.9999198913574, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 1065, + "msg": "input_width: `1920`", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractReview", + "threadName": "MainThread", + "msecs": 785.0000858306885, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 1066, + "msg": "input_height: `1080`", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractReview", + "threadName": "MainThread", + "msecs": 785.0000858306885, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 1075, + "msg": "Using resolution from input.", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractReview", + "threadName": "MainThread", + "msecs": 786.0000133514404, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 1099, + "msg": "Output resolution is 1920x1080", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractReview", + "threadName": "MainThread", + "msecs": 786.0000133514404, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 1111, + "msg": "Output resolution is same as input's and \"letter_box\" key is not set. Skipping reformat part.", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractReview", + "threadName": "MainThread", + "msecs": 786.9999408721924, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 220, + "msg": "Executing: \"C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\vendor\\bin\\ffmpeg\\windows\\bin\\ffmpeg\" -apply_trc gamma22 -start_number 991 -framerate 25.0 -to 1.2000000000 -i \"c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr\\modelMain.%04d.jpg\" -pix_fmt yuv420p -crf 18 -intra -y \"c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr\\modelMain_h264.mp4\"", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractReview", + "threadName": "MainThread", + "msecs": 203.00006866455078, + "filename": "execute.py", + "levelno": 30, + "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\lib\\execute.py", + "lineno": 126, + "msg": "ffmpeg version 4.4-full_build-www.gyan.dev Copyright (c) 2000-2021 the FFmpeg developers\r\n built with gcc 10.2.0 (Rev6, Built by MSYS2 project)\r\n configuration: --enable-gpl --enable-version3 --enable-static --disable-w32threads --disable-autodetect --enable-fontconfig --enable-iconv --enable-gnutls --enable-libxml2 --enable-gmp --enable-lzma --enable-libsnappy --enable-zlib --enable-librist --enable-libsrt --enable-libssh --enable-libzmq --enable-avisynth --enable-libbluray --enable-libcaca --enable-sdl2 --enable-libdav1d --enable-libzvbi --enable-librav1e --enable-libsvtav1 --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxvid --enable-libaom --enable-libopenjpeg --enable-libvpx --enable-libass --enable-frei0r --enable-libfreetype --enable-libfribidi --enable-libvidstab --enable-libvmaf --enable-libzimg --enable-amf --enable-cuda-llvm --enable-cuvid --enable-ffnvcodec --enable-nvdec --enable-nvenc --enable-d3d11va --enable-dxva2 --enable-libmfx --enable-libglslang --enable-vulkan --enable-opencl --enable-libcdio --enable-libgme --enable-libmodplug --enable-libopenmpt --enable-libopencore-amrwb --enable-libmp3lame --enable-libshine --enable-libtheora --enable-libtwolame --enable-libvo-amrwbenc --enable-libilbc --enable-libgsm --enable-libopencore-amrnb --enable-libopus --enable-libspeex --enable-libvorbis --enable-ladspa --enable-libbs2b --enable-libflite --enable-libmysofa --enable-librubberband --enable-libsoxr --enable-chromaprint\r\n libavutil 56. 70.100 / 56. 70.100\r\n libavcodec 58.134.100 / 58.134.100\r\n libavformat 58. 76.100 / 58. 76.100\r\n libavdevice 58. 13.100 / 58. 13.100\r\n libavfilter 7.110.100 / 7.110.100\r\n libswscale 5. 9.100 / 5. 9.100\r\n libswresample 3. 9.100 / 3. 9.100\r\n libpostproc 55. 9.100 / 55. 9.100\r\nInput #0, image2, from 'c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr\\modelMain.%04d.jpg':\r\n Duration: 00:00:01.20, start: 0.000000, bitrate: N/A\r\n Stream #0:0: Video: mjpeg (Baseline), yuvj420p(pc, bt470bg/unknown/unknown), 1920x1080, 25 fps, 25 tbr, 25 tbn, 25 tbc\r\nCodec AVOption apply_trc (color transfer characteristics to apply to EXR linear input) specified for input file #0 (c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr\\modelMain.%04d.jpg) has not been used for any stream. The most likely reason is either wrong type (e.g. a video option with no video streams) or that it is a private option of some decoder which was not actually used for any stream.\r\nStream mapping:\r\n Stream #0:0 -> #0:0 (mjpeg (native) -> h264 (libx264))\r\nPress [q] to stop, [?] for help\r\n[swscaler @ 00000244426a5380] deprecated pixel format used, make sure you did set range correctly\r\n[libx264 @ 000002444205e140] using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX FMA3 BMI2 AVX2\r\n[libx264 @ 000002444205e140] profile High, level 4.0, 4:2:0, 8-bit\r\n[libx264 @ 000002444205e140] 264 - core 161 r3048 b86ae3c - H.264/MPEG-4 AVC codec - Copyleft 2003-2021 - http://www.videolan.org/x264.html - options: cabac=1 ref=1 deblock=1:0:0 analyse=0x3:0x113 me=hex subme=7 psy=1 psy_rd=1.00:0.00 mixed_ref=0 me_range=16 chroma_me=1 trellis=1 8x8dct=1 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=-2 threads=24 lookahead_threads=4 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=0 weightp=0 keyint=1 keyint_min=1 scenecut=40 intra_refresh=0 rc=crf mbtree=0 crf=18.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 ip_ratio=1.40 aq=1:1.00\r\nOutput #0, mp4, to 'c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr\\modelMain_h264.mp4':\r\n Metadata:\r\n encoder : Lavf58.76.100\r\n Stream #0:0: Video: h264 (avc1 / 0x31637661), yuv420p(tv, bt470bg/unknown/unknown, progressive), 1920x1080, q=2-31, 25 fps, 12800 tbn\r\n Metadata:\r\n encoder : Lavc58.134.100 libx264\r\n Side data:\r\n cpb: bitrate max/min/avg: 0/0/0 buffer size: 0 vbv_delay: N/A\r\nframe= 1 fps=0.0 q=0.0 size= 0kB time=00:00:00.00 bitrate=N/A speed= 0x \rframe= 30 fps=0.0 q=-1.0 Lsize= 47kB time=00:00:01.16 bitrate= 332.6kbits/s speed=3.74x \r\nvideo:46kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 1.998477%\r\n[libx264 @ 000002444205e140] frame I:30 Avg QP: 1.05 size: 1556\r\n[libx264 @ 000002444205e140] mb I I16..4: 99.5% 0.2% 0.4%\r\n[libx264 @ 000002444205e140] 8x8 transform intra:0.2%\r\n[libx264 @ 000002444205e140] coded y,uvDC,uvAC intra: 0.4% 0.0% 0.0%\r\n[libx264 @ 000002444205e140] i16 v,h,dc,p: 98% 0% 1% 0%\r\n[libx264 @ 000002444205e140] i8 v,h,dc,ddl,ddr,vr,hd,vl,hu: 4% 40% 4% 10% 15% 6% 8% 6% 8%\r\n[libx264 @ 000002444205e140] i4 v,h,dc,ddl,ddr,vr,hd,vl,hu: 9% 31% 18% 5% 4% 5% 14% 5% 8%\r\n[libx264 @ 000002444205e140] i8c dc,h,v,p: 100% 0% 0% 0%\r\n[libx264 @ 000002444205e140] kb/s:311.17\r\n", + "exc_info": null, + "type": "record", + "levelname": "WARNING" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractReview", + "threadName": "MainThread", + "msecs": 217.00000762939453, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 251, + "msg": "Adding new representation: {'files': 'modelMain_h264.mp4', 'outputName': u'h264', 'resolutionHeight': 1080, 'name': u'h264', 'frameStart': 991, 'outputDef': {u'bg_color': [0, 0, 0, 0], u'tags': [u'burnin', u'ftrackreview'], u'height': 0, u'filter': {u'families': [u'render', u'review', u'ftrack']}, u'width': 0, u'ext': u'mp4', u'overscan_color': [0, 0, 0, 255], u'letter_box': {u'line_color': [255, 0, 0, 255], u'ratio': 0.0, u'fill_color': [0, 0, 0, 255], u'enabled': False, u'state': u'letterbox', u'line_thickness': 0}, 'filename_suffix': u'h264', u'overscan_crop': u'', u'ffmpeg_args': {u'video_filters': [], u'input': [u'-apply_trc gamma22'], u'output': [u'-pix_fmt yuv420p', u'-crf 18', u'-intra'], u'audio_filters': []}}, 'camera_name': u'persp_CAM', 'tags': ['review', u'burnin', u'ftrackreview'], 'stagingDir': 'c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr', 'frameEnd': 1020, 'ext': u'mp4', 'resolutionWidth': 1920, 'fps': 25.0, 'frameStartFtrack': 991, 'frameEndFtrack': 1020}", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.ExtractReview", + "threadName": "MainThread", + "msecs": 219.00010108947754, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 136, + "msg": "Repre: ma - Didn't found \"review\" in tags. Skipping", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + } + ] + } + ], + "label": "Extract Review", + "skipped": false, + "order": 2.02, + "name": "ExtractReview" + }, + { + "instances_data": [], + "label": "Review with Slate frame", + "skipped": true, + "order": 2.031, + "name": "ExtractReviewSlate" + }, + { + "instances_data": [], + "label": "Ass Proxy (Maya ASCII)", + "skipped": true, + "order": 2.2, + "name": "ExtractAssProxy" + }, + { + "instances_data": [], + "label": "Extract Look (Maya ASCII + JSON)", + "skipped": true, + "order": 2.2, + "name": "ExtractLook" + }, + { + "instances_data": [], + "label": "Save current file", + "skipped": true, + "order": 2.51, + "name": "SaveCurrentScene" + }, + { + "instances_data": [], + "label": "Integrate Resources Path", + "skipped": true, + "order": 2.95, + "name": "IntegrateResourcesPath" + }, + { + "instances_data": [ + { + "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "logs": [ + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 318.00007820129395, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 183, + "msg": "Establishing staging directory @ c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "openpype.lib.profiles_filtering", + "threadName": "MainThread", + "msecs": 319.99993324279785, + "filename": "profiles_filtering.py", + "levelno": 20, + "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\lib\\profiles_filtering.py", + "lineno": 31, + "msg": "Search for first most matching profile in match order: Host name -> Task name -> Family.", + "exc_info": null, + "type": "record", + "levelname": "INFO" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 322.00002670288086, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 199, + "msg": "Next version: v7", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 322.9999542236328, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 846, + "msg": "Source: {root[work]}/kuba_each_case/Assets/Alpaca_01/work/animation/kuba_each_case_Alpaca_01_animation_v007.ma", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 323.99988174438477, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 215, + "msg": "Creating version ...", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 332.0000171661377, + "filename": "profiles_filtering.py", + "levelno": 10, + "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\lib\\profiles_filtering.py", + "lineno": 168, + "msg": "\"families\" not found in [u'review', u'render', u'prerender']", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 332.99994468688965, + "filename": "profiles_filtering.py", + "levelno": 20, + "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\lib\\profiles_filtering.py", + "lineno": 31, + "msg": "Search for first most matching profile in match order: Host name -> Task name -> Family.", + "exc_info": null, + "type": "record", + "levelname": "INFO" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 351.99999809265137, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 506, + "msg": "__ dst: C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007.jpg", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 352.9999256134033, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 563, + "msg": "Integrating source files to destination ...", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 354.0000915527344, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 652, + "msg": "Copying file ... c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr\\modelMain.1001.jpg -> C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007.jpg.tmp", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 355.9999465942383, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 566, + "msg": "Integrated files {'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg.tmp': 36385L}", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 357.00011253356934, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 569, + "msg": "Preparing files information ...", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 358.0000400543213, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 930, + "msg": "get_resource_files_info.resources:[[u'c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr\\\\modelMain.1001.jpg', 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg']]", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 359.9998950958252, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 574, + "msg": "__ representation: {'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/v007/kuba_each_case_Alpaca_01_modelMain_v007.jpg', '_id': ObjectId('6125fdf9839e8042d68dbd30'), 'hash': 'kuba_each_case_Alpaca_01_modelMain_v007,jpg|1629879799,25|36385', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 360000)}], 'size': 36385L}], 'context': {'subset': u'modelMain', 'username': 'jakub.trllo', 'task': 'animation', 'family': u'model', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'jpg', 'version': 7, 'asset': u'Alpaca_01', 'representation': 'jpg', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': 'thumbnail', 'parent': ObjectId('6125fdf9839e8042d68dbd2d'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd2e'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 361.9999885559082, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 576, + "msg": "__ destination_list: ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg']", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 362.99991607666016, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 584, + "msg": "__ representations: [{'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/v007/kuba_each_case_Alpaca_01_modelMain_v007.jpg', '_id': ObjectId('6125fdf9839e8042d68dbd30'), 'hash': 'kuba_each_case_Alpaca_01_modelMain_v007,jpg|1629879799,25|36385', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 360000)}], 'size': 36385L}], 'context': {'subset': u'modelMain', 'username': 'jakub.trllo', 'task': 'animation', 'family': u'model', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'jpg', 'version': 7, 'asset': u'Alpaca_01', 'representation': 'jpg', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': 'thumbnail', 'parent': ObjectId('6125fdf9839e8042d68dbd2d'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd2e'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}]", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 384.0000629425049, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 506, + "msg": "__ dst: C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007.abc", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 384.99999046325684, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 563, + "msg": "Integrating source files to destination ...", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 385.9999179840088, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 652, + "msg": "Copying file ... c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr\\modelMain.abc -> C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007.abc.tmp", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 388.0000114440918, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 566, + "msg": "Integrated files {'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.abc.tmp': 25170L, 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg.tmp': 36385L}", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 388.99993896484375, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 569, + "msg": "Preparing files information ...", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 388.99993896484375, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 930, + "msg": "get_resource_files_info.resources:[['c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr\\\\modelMain.abc', 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.abc']]", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 391.00003242492676, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 574, + "msg": "__ representation: {'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/v007/kuba_each_case_Alpaca_01_modelMain_v007.abc', '_id': ObjectId('6125fdf9839e8042d68dbd33'), 'hash': 'kuba_each_case_Alpaca_01_modelMain_v007,abc|1629879798,63|25170', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 391000)}], 'size': 25170L}], 'context': {'subset': u'modelMain', 'username': 'jakub.trllo', 'task': 'animation', 'family': u'model', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'abc', 'version': 7, 'asset': u'Alpaca_01', 'representation': 'abc', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': 'abc', 'parent': ObjectId('6125fdf9839e8042d68dbd2d'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.abc', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd31'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 394.0000534057617, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 576, + "msg": "__ destination_list: ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg', 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.abc']", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 394.9999809265137, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 584, + "msg": "__ representations: [{'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/v007/kuba_each_case_Alpaca_01_modelMain_v007.jpg', '_id': ObjectId('6125fdf9839e8042d68dbd30'), 'hash': 'kuba_each_case_Alpaca_01_modelMain_v007,jpg|1629879799,25|36385', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 360000)}], 'size': 36385L}], 'context': {'subset': u'modelMain', 'username': 'jakub.trllo', 'task': 'animation', 'family': u'model', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'jpg', 'version': 7, 'asset': u'Alpaca_01', 'representation': 'jpg', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': 'thumbnail', 'parent': ObjectId('6125fdf9839e8042d68dbd2d'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd2e'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}, {'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/v007/kuba_each_case_Alpaca_01_modelMain_v007.abc', '_id': ObjectId('6125fdf9839e8042d68dbd33'), 'hash': 'kuba_each_case_Alpaca_01_modelMain_v007,abc|1629879798,63|25170', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 391000)}], 'size': 25170L}], 'context': {'subset': u'modelMain', 'username': 'jakub.trllo', 'task': 'animation', 'family': u'model', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'abc', 'version': 7, 'asset': u'Alpaca_01', 'representation': 'abc', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': 'abc', 'parent': ObjectId('6125fdf9839e8042d68dbd2d'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.abc', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd31'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}]", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 418.99991035461426, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 506, + "msg": "__ dst: C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007.ma", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 420.0000762939453, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 563, + "msg": "Integrating source files to destination ...", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 420.0000762939453, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 652, + "msg": "Copying file ... c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr\\modelMain.ma -> C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007.ma.tmp", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 423.0000972747803, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 566, + "msg": "Integrated files {'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.ma.tmp': 52747L, 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.abc.tmp': 25170L, 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg.tmp': 36385L}", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 424.0000247955322, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 569, + "msg": "Preparing files information ...", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 424.0000247955322, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 930, + "msg": "get_resource_files_info.resources:[['c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr\\\\modelMain.ma', 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.ma']]", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 427.0000457763672, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 574, + "msg": "__ representation: {'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/v007/kuba_each_case_Alpaca_01_modelMain_v007.ma', '_id': ObjectId('6125fdf9839e8042d68dbd36'), 'hash': 'kuba_each_case_Alpaca_01_modelMain_v007,ma|1629879800,33|52747', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 426000)}], 'size': 52747L}], 'context': {'subset': u'modelMain', 'username': 'jakub.trllo', 'task': 'animation', 'family': u'model', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'ma', 'version': 7, 'asset': u'Alpaca_01', 'representation': u'ma', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': u'ma', 'parent': ObjectId('6125fdf9839e8042d68dbd2d'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.ma', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd34'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 428.9999008178711, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 576, + "msg": "__ destination_list: ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg', 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.abc', 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.ma']", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 430.9999942779541, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 584, + "msg": "__ representations: [{'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/v007/kuba_each_case_Alpaca_01_modelMain_v007.jpg', '_id': ObjectId('6125fdf9839e8042d68dbd30'), 'hash': 'kuba_each_case_Alpaca_01_modelMain_v007,jpg|1629879799,25|36385', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 360000)}], 'size': 36385L}], 'context': {'subset': u'modelMain', 'username': 'jakub.trllo', 'task': 'animation', 'family': u'model', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'jpg', 'version': 7, 'asset': u'Alpaca_01', 'representation': 'jpg', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': 'thumbnail', 'parent': ObjectId('6125fdf9839e8042d68dbd2d'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd2e'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}, {'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/v007/kuba_each_case_Alpaca_01_modelMain_v007.abc', '_id': ObjectId('6125fdf9839e8042d68dbd33'), 'hash': 'kuba_each_case_Alpaca_01_modelMain_v007,abc|1629879798,63|25170', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 391000)}], 'size': 25170L}], 'context': {'subset': u'modelMain', 'username': 'jakub.trllo', 'task': 'animation', 'family': u'model', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'abc', 'version': 7, 'asset': u'Alpaca_01', 'representation': 'abc', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': 'abc', 'parent': ObjectId('6125fdf9839e8042d68dbd2d'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.abc', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd31'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}, {'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/v007/kuba_each_case_Alpaca_01_modelMain_v007.ma', '_id': ObjectId('6125fdf9839e8042d68dbd36'), 'hash': 'kuba_each_case_Alpaca_01_modelMain_v007,ma|1629879800,33|52747', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 426000)}], 'size': 52747L}], 'context': {'subset': u'modelMain', 'username': 'jakub.trllo', 'task': 'animation', 'family': u'model', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'ma', 'version': 7, 'asset': u'Alpaca_01', 'representation': u'ma', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': u'ma', 'parent': ObjectId('6125fdf9839e8042d68dbd2d'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.ma', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd34'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}]", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 456.00008964538574, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 506, + "msg": "__ dst: C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 457.0000171661377, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 563, + "msg": "Integrating source files to destination ...", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 457.99994468688965, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 652, + "msg": "Copying file ... c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr\\modelMain_h264.mp4 -> C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4.tmp", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 460.00003814697266, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 566, + "msg": "Integrated files {'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4.tmp': 48231L, 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.ma.tmp': 52747L, 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.abc.tmp': 25170L, 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg.tmp': 36385L}", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 460.9999656677246, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 569, + "msg": "Preparing files information ...", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 461.99989318847656, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 930, + "msg": "get_resource_files_info.resources:[['c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr\\\\modelMain_h264.mp4', 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4']]", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 463.99998664855957, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 574, + "msg": "__ representation: {'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/v007/kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4', '_id': ObjectId('6125fdf9839e8042d68dbd39'), 'hash': 'kuba_each_case_Alpaca_01_modelMain_v007_h264,mp4|1629879801,18|48231', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 464000)}], 'size': 48231L}], 'context': {'subset': u'modelMain', 'username': 'jakub.trllo', 'task': 'animation', 'family': u'model', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'mp4', 'version': 7, 'asset': u'Alpaca_01', 'representation': u'mp4', 'output': u'h264', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': u'h264', 'parent': ObjectId('6125fdf9839e8042d68dbd2d'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd37'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 470.99995613098145, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 576, + "msg": "__ destination_list: ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg', 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.abc', 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.ma', 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4']", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 471.9998836517334, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 584, + "msg": "__ representations: [{'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/v007/kuba_each_case_Alpaca_01_modelMain_v007.jpg', '_id': ObjectId('6125fdf9839e8042d68dbd30'), 'hash': 'kuba_each_case_Alpaca_01_modelMain_v007,jpg|1629879799,25|36385', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 360000)}], 'size': 36385L}], 'context': {'subset': u'modelMain', 'username': 'jakub.trllo', 'task': 'animation', 'family': u'model', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'jpg', 'version': 7, 'asset': u'Alpaca_01', 'representation': 'jpg', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': 'thumbnail', 'parent': ObjectId('6125fdf9839e8042d68dbd2d'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd2e'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}, {'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/v007/kuba_each_case_Alpaca_01_modelMain_v007.abc', '_id': ObjectId('6125fdf9839e8042d68dbd33'), 'hash': 'kuba_each_case_Alpaca_01_modelMain_v007,abc|1629879798,63|25170', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 391000)}], 'size': 25170L}], 'context': {'subset': u'modelMain', 'username': 'jakub.trllo', 'task': 'animation', 'family': u'model', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'abc', 'version': 7, 'asset': u'Alpaca_01', 'representation': 'abc', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': 'abc', 'parent': ObjectId('6125fdf9839e8042d68dbd2d'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.abc', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd31'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}, {'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/v007/kuba_each_case_Alpaca_01_modelMain_v007.ma', '_id': ObjectId('6125fdf9839e8042d68dbd36'), 'hash': 'kuba_each_case_Alpaca_01_modelMain_v007,ma|1629879800,33|52747', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 426000)}], 'size': 52747L}], 'context': {'subset': u'modelMain', 'username': 'jakub.trllo', 'task': 'animation', 'family': u'model', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'ma', 'version': 7, 'asset': u'Alpaca_01', 'representation': u'ma', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': u'ma', 'parent': ObjectId('6125fdf9839e8042d68dbd2d'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.ma', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd34'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}, {'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/v007/kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4', '_id': ObjectId('6125fdf9839e8042d68dbd39'), 'hash': 'kuba_each_case_Alpaca_01_modelMain_v007_h264,mp4|1629879801,18|48231', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 464000)}], 'size': 48231L}], 'context': {'subset': u'modelMain', 'username': 'jakub.trllo', 'task': 'animation', 'family': u'model', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'mp4', 'version': 7, 'asset': u'Alpaca_01', 'representation': u'mp4', 'output': u'h264', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': u'h264', 'parent': ObjectId('6125fdf9839e8042d68dbd2d'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd37'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}]", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 480.9999465942383, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 594, + "msg": "__ rep: {'files': u'modelMain.1001.jpg', 'publishedFiles': ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg'], 'name': 'thumbnail', 'published_path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg', 'stagingDir': 'c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr', 'ext': 'jpg', 'thumbnail': True}", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 483.0000400543213, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 594, + "msg": "__ rep: {'files': 'modelMain.abc', 'publishedFiles': ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.abc'], 'name': 'abc', 'published_path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.abc', 'stagingDir': 'c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr', 'ext': 'abc'}", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 483.99996757507324, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 594, + "msg": "__ rep: {'files': 'modelMain.ma', 'publishedFiles': ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.ma'], 'name': u'ma', 'published_path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.ma', 'stagingDir': 'c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr', 'ext': u'ma'}", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 484.9998950958252, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 594, + "msg": "__ rep: {'files': 'modelMain_h264.mp4', 'outputName': u'h264', 'resolutionHeight': 1080, 'name': u'h264', 'published_path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4', 'frameStart': 991, 'outputDef': {u'bg_color': [0, 0, 0, 0], u'tags': [u'burnin', u'ftrackreview'], u'height': 0, u'filter': {u'families': [u'render', u'review', u'ftrack']}, u'width': 0, u'ext': u'mp4', u'overscan_color': [0, 0, 0, 255], u'letter_box': {u'line_color': [255, 0, 0, 255], u'ratio': 0.0, u'fill_color': [0, 0, 0, 255], u'enabled': False, u'state': u'letterbox', u'line_thickness': 0}, 'filename_suffix': u'h264', u'overscan_crop': u'', u'ffmpeg_args': {u'video_filters': [], u'input': [u'-apply_trc gamma22'], u'output': [u'-pix_fmt yuv420p', u'-crf 18', u'-intra'], u'audio_filters': []}}, 'camera_name': u'persp_CAM', 'tags': ['review', u'burnin', u'ftrackreview'], 'stagingDir': 'c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr', 'frameEnd': 1020, 'ext': u'mp4', 'resolutionWidth': 1920, 'fps': 25.0, 'publishedFiles': ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4'], 'frameStartFtrack': 991, 'frameEndFtrack': 1020}", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 509.0000629425049, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 601, + "msg": "Registered 4 items", + "exc_info": null, + "type": "record", + "levelname": "INFO" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 509.99999046325684, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 124, + "msg": "Integrated Asset in to the database ...", + "exc_info": null, + "type": "record", + "levelname": "INFO" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 510.9999179840088, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 125, + "msg": "instance.data: {u'subset': u'modelMain', 'versionEntity': {u'name': 7, u'parent': ObjectId('610124ffb5db38f84962c00e'), u'type': u'version', u'_id': ObjectId('6125fdf9839e8042d68dbd2d'), u'data': {u'comment': u'', u'families': [u'model', u'model', u'review'], u'frameStart': 1001, u'author': u'jakub.trllo', u'step': 1.0, u'frameEnd': 1010, u'machine': u'014-BAILEYS', u'source': u'{root[work]}/kuba_each_case/Assets/Alpaca_01/work/animation/kuba_each_case_Alpaca_01_animation_v007.ma', u'handles': None, u'fps': 25.0, u'time': u'20210825T102314Z'}, u'schema': u'openpype:version-3.0'}, 'representations': [{'files': u'modelMain.1001.jpg', 'publishedFiles': ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg'], 'name': 'thumbnail', 'published_path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg', 'stagingDir': 'c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr', 'ext': 'jpg', 'thumbnail': True}, {'files': 'modelMain.abc', 'publishedFiles': ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.abc'], 'name': 'abc', 'published_path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.abc', 'stagingDir': 'c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr', 'ext': 'abc'}, {'files': 'modelMain.ma', 'publishedFiles': ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.ma'], 'name': u'ma', 'published_path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.ma', 'stagingDir': 'c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr', 'ext': u'ma'}, {'files': 'modelMain_h264.mp4', 'outputName': u'h264', 'resolutionHeight': 1080, 'name': u'h264', 'published_path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4', 'frameStart': 991, 'outputDef': {u'bg_color': [0, 0, 0, 0], u'tags': [u'burnin', u'ftrackreview'], u'height': 0, u'filter': {u'families': [u'render', u'review', u'ftrack']}, u'width': 0, u'ext': u'mp4', u'overscan_color': [0, 0, 0, 255], u'letter_box': {u'line_color': [255, 0, 0, 255], u'ratio': 0.0, u'fill_color': [0, 0, 0, 255], u'enabled': False, u'state': u'letterbox', u'line_thickness': 0}, 'filename_suffix': u'h264', u'overscan_crop': u'', u'ffmpeg_args': {u'video_filters': [], u'input': [u'-apply_trc gamma22'], u'output': [u'-pix_fmt yuv420p', u'-crf 18', u'-intra'], u'audio_filters': []}}, 'camera_name': u'persp_CAM', 'tags': ['review', u'burnin', u'ftrackreview'], 'stagingDir': 'c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr', 'frameEnd': 1020, 'ext': u'mp4', 'resolutionWidth': 1920, 'fps': 25.0, 'publishedFiles': ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4'], 'frameStartFtrack': 991, 'frameEndFtrack': 1020}], 'stagingDir': 'c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr', 'families': [u'model', 'review'], 'fps': 25.0, 'family': u'model', 'frameStart': 1001, 'published_representations': {ObjectId('6125fdf9839e8042d68dbd34'): {'representation': {'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/v007/kuba_each_case_Alpaca_01_modelMain_v007.ma', '_id': ObjectId('6125fdf9839e8042d68dbd36'), 'hash': 'kuba_each_case_Alpaca_01_modelMain_v007,ma|1629879800,33|52747', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 426000)}], 'size': 52747L}], 'context': {'subset': u'modelMain', 'username': 'jakub.trllo', 'task': 'animation', 'family': u'model', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'ma', 'version': 7, 'asset': u'Alpaca_01', 'representation': u'ma', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': u'ma', 'parent': ObjectId('6125fdf9839e8042d68dbd2d'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.ma', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd34'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}, 'published_files': ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.ma'], 'anatomy_data': {'subset': u'modelMain', 'family': u'model', 'hierarchy': u'Assets', 'app': 'maya', 'yyyy': '2021', 'HH': '10', 'version': 7, 'asset': u'Alpaca_01', 'fps': 25.0, 'ddd': 'Wed', 'username': 'jakub.trllo', 'H': '10', 'dd': '25', 'M': '23', 'ht': 'AM', 'mmmm': 'August', 'S': '14', 'dddd': 'Wednesday', 'mmm': 'Aug', 'task': 'animation', 'd': '25', 'mm': '08', 'h': '10', 'm': '8', 'hh': '10', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'SS': '14', 'ext': u'ma', 'yy': '21', 'representation': u'ma', 'MM': '23'}}, ObjectId('6125fdf9839e8042d68dbd37'): {'representation': {'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/v007/kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4', '_id': ObjectId('6125fdf9839e8042d68dbd39'), 'hash': 'kuba_each_case_Alpaca_01_modelMain_v007_h264,mp4|1629879801,18|48231', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 464000)}], 'size': 48231L}], 'context': {'subset': u'modelMain', 'username': 'jakub.trllo', 'task': 'animation', 'family': u'model', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'mp4', 'version': 7, 'asset': u'Alpaca_01', 'representation': u'mp4', 'output': u'h264', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': u'h264', 'parent': ObjectId('6125fdf9839e8042d68dbd2d'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd37'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}, 'published_files': ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4'], 'anatomy_data': {'subset': u'modelMain', 'resolution_width': 1920, 'family': u'model', 'hierarchy': u'Assets', 'app': 'maya', 'yyyy': '2021', 'HH': '10', 'version': 7, 'asset': u'Alpaca_01', 'fps': 25.0, 'ddd': 'Wed', 'username': 'jakub.trllo', 'H': '10', 'dd': '25', 'M': '23', 'ht': 'AM', 'mmmm': 'August', 'S': '14', 'dddd': 'Wednesday', 'representation': u'mp4', 'mmm': 'Aug', 'task': 'animation', 'd': '25', 'mm': '08', 'h': '10', 'm': '8', 'hh': '10', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'SS': '14', 'ext': u'mp4', 'resolution_height': 1080, 'yy': '21', 'output': u'h264', 'MM': '23'}}, ObjectId('6125fdf9839e8042d68dbd2e'): {'representation': {'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/v007/kuba_each_case_Alpaca_01_modelMain_v007.jpg', '_id': ObjectId('6125fdf9839e8042d68dbd30'), 'hash': 'kuba_each_case_Alpaca_01_modelMain_v007,jpg|1629879799,25|36385', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 360000)}], 'size': 36385L}], 'context': {'subset': u'modelMain', 'username': 'jakub.trllo', 'task': 'animation', 'family': u'model', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'jpg', 'version': 7, 'asset': u'Alpaca_01', 'representation': 'jpg', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': 'thumbnail', 'parent': ObjectId('6125fdf9839e8042d68dbd2d'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd2e'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}, 'published_files': ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg'], 'anatomy_data': {'subset': u'modelMain', 'family': u'model', 'hierarchy': u'Assets', 'app': 'maya', 'yyyy': '2021', 'HH': '10', 'version': 7, 'asset': u'Alpaca_01', 'fps': 25.0, 'ddd': 'Wed', 'username': 'jakub.trllo', 'H': '10', 'dd': '25', 'M': '23', 'ht': 'AM', 'mmmm': 'August', 'S': '14', 'dddd': 'Wednesday', 'mmm': 'Aug', 'task': 'animation', 'd': '25', 'mm': '08', 'h': '10', 'm': '8', 'hh': '10', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'SS': '14', 'ext': 'jpg', 'yy': '21', 'representation': 'jpg', 'MM': '23'}}, ObjectId('6125fdf9839e8042d68dbd31'): {'representation': {'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/v007/kuba_each_case_Alpaca_01_modelMain_v007.abc', '_id': ObjectId('6125fdf9839e8042d68dbd33'), 'hash': 'kuba_each_case_Alpaca_01_modelMain_v007,abc|1629879798,63|25170', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 391000)}], 'size': 25170L}], 'context': {'subset': u'modelMain', 'username': 'jakub.trllo', 'task': 'animation', 'family': u'model', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'abc', 'version': 7, 'asset': u'Alpaca_01', 'representation': 'abc', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': 'abc', 'parent': ObjectId('6125fdf9839e8042d68dbd2d'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.abc', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd31'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}, 'published_files': ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.abc'], 'anatomy_data': {'subset': u'modelMain', 'family': u'model', 'hierarchy': u'Assets', 'app': 'maya', 'yyyy': '2021', 'HH': '10', 'version': 7, 'asset': u'Alpaca_01', 'fps': 25.0, 'ddd': 'Wed', 'username': 'jakub.trllo', 'H': '10', 'dd': '25', 'M': '23', 'ht': 'AM', 'mmmm': 'August', 'S': '14', 'dddd': 'Wednesday', 'mmm': 'Aug', 'task': 'animation', 'd': '25', 'mm': '08', 'h': '10', 'm': '8', 'hh': '10', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'SS': '14', 'ext': 'abc', 'yy': '21', 'representation': 'abc', 'MM': '23'}}}, 'projectEntity': {u'name': u'kuba_each_case', u'parent': None, u'type': u'project', u'data': {u'code': u'kuba_each_case', u'resolutionHeight': 1080, u'library_project': False, u'clipIn': 1001, u'frameStart': 1001, u'handleStart': 10, u'entityType': u'Project', u'frameEnd': 1100, u'tools_env': [], u'pixelAspect': 1.0, u'ftrackId': u'38c5fec4-0aed-11ea-a454-3e41ec9bc0d6', u'resolutionWidth': 1920, u'fps': 25.0, u'handleEnd': 10, u'clipOut': 1100}, u'_id': ObjectId('5eb950f9e3c2c3183455f266'), u'config': {u'templates': {u'hero': {u'path': u'{@folder}/{@file}', u'folder': u'{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/hero', u'file': u'{project[code]}_{asset}_{subset}_hero<_{output}><.{frame}>.{ext}'}, u'render': {u'path': u'{@folder}/{@file}', u'folder': u'{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/{@version}', u'file': u'{project[code]}_{asset}_{subset}_{@version}<_{output}><.{@frame}>.{ext}'}, u'work': {u'path': u'{@folder}/{@file}', u'folder': u'{root[work]}/{project[name]}/{hierarchy}/{asset}/work/{task}', u'file': u'{project[code]}_{asset}_{task}_{@version}<_{comment}>.{ext}'}, u'publish': {u'path': u'{@folder}/{@file}', u'folder': u'{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/{@version}', u'thumbnail': u'{thumbnail_root}/{project[name]}/{_id}_{thumbnail_type}.{ext}', u'file': u'{project[code]}_{asset}_{subset}_{@version}<_{output}><.{@frame}><_{udim}>.{ext}'}, u'delivery': {}, u'defaults': {u'frame_padding': 4, u'version_padding': 3, u'frame': u'{frame:0>{@frame_padding}}', u'version': u'v{version:0>{@version_padding}}'}, u'others': {}}, u'tasks': {u'Previz': {u'short_name': u''}, u'Lookdev': {u'short_name': u'look'}, u'Edit': {u'short_name': u'edit'}, u'Tracking': {u'short_name': u''}, u'Layout': {u'short_name': u'lay'}, u'Art': {u'short_name': u'art'}, u'Paint': {u'short_name': u'paint'}, u'Generic': {u'short_name': u'gener'}, u'Texture': {u'short_name': u'tex'}, u'Setdress': {u'short_name': u'dress'}, u'Animation': {u'short_name': u'anim'}, u'Lighting': {u'short_name': u'lgt'}, u'Compositing': {u'short_name': u'comp'}, u'Modeling': {u'short_name': u'mdl'}, u'Rigging': {u'short_name': u'rig'}, u'FX': {u'short_name': u'fx'}}, u'imageio': {u'hiero': {u'workfile': {u'eightBitLut': u'sRGB', u'viewerLut': u'sRGB', u'workingSpace': u'linear', u'ocioconfigpath': {u'windows': [], u'darwin': [], u'linux': []}, u'ocioConfigName': u'nuke-default', u'thumbnailLut': u'sRGB', u'floatLut': u'linear', u'sixteenBitLut': u'sRGB', u'logLut': u'Cineon'}, u'regexInputs': {u'inputs': [{u'regex': u'[^-a-zA-Z0-9](plateRef).*(?=mp4)', u'colorspace': u'sRGB'}]}}, u'nuke': {u'viewer': {u'viewerProcess': u'sRGB'}, u'workfile': {u'floatLut': u'linear', u'workingSpaceLUT': u'linear', u'customOCIOConfigPath': {u'windows': [], u'darwin': [], u'linux': []}, u'int16Lut': u'sRGB', u'logLut': u'Cineon', u'int8Lut': u'sRGB', u'colorManagement': u'Nuke', u'OCIO_config': u'nuke-default', u'monitorLut': u'sRGB'}, u'nodes': {u'customNodes': [], u'requiredNodes': [{u'nukeNodeClass': u'Write', u'knobs': [{u'name': u'file_type', u'value': u'exr'}, {u'name': u'datatype', u'value': u'16 bit half'}, {u'name': u'compression', u'value': u'Zip (1 scanline)'}, {u'name': u'autocrop', u'value': u'True'}, {u'name': u'tile_color', u'value': u'0xff0000ff'}, {u'name': u'channels', u'value': u'rgb'}, {u'name': u'colorspace', u'value': u'linear'}, {u'name': u'create_directories', u'value': u'True'}], u'plugins': [u'CreateWriteRender']}, {u'nukeNodeClass': u'Write', u'knobs': [{u'name': u'file_type', u'value': u'exr'}, {u'name': u'datatype', u'value': u'16 bit half'}, {u'name': u'compression', u'value': u'Zip (1 scanline)'}, {u'name': u'autocrop', u'value': u'False'}, {u'name': u'tile_color', u'value': u'0xadab1dff'}, {u'name': u'channels', u'value': u'rgb'}, {u'name': u'colorspace', u'value': u'linear'}, {u'name': u'create_directories', u'value': u'True'}], u'plugins': [u'CreateWritePrerender']}]}, u'regexInputs': {u'inputs': [{u'regex': u'[^-a-zA-Z0-9]beauty[^-a-zA-Z0-9]', u'colorspace': u'linear'}]}}}, u'apps': [{u'name': u'blender/2-91'}, {u'name': u'hiero/11-3'}, {u'name': u'houdini/18-5'}, {u'name': u'maya/2020'}, {u'name': u'nuke/11-3'}, {u'name': u'nukestudio/11-3'}], u'roots': {u'work': {u'windows': u'C:/projects', u'darwin': u'/Volumes/path', u'linux': u'/mnt/share/projects'}}, u'schema': u'openpype:config-2.0'}, u'schema': u'openpype:project-3.0'}, 'version': 7, u'variant': u'Main', u'active': True, 'step': 1.0, 'review_camera': u'|persp_CAM|persp_CAMShape', 'frameEnd': 1010, u'attrPrefix': u'', u'cbId': u'5dd50967e3c2c32b004b4817:bc3d17f26cde', 'publishDir': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007', 'latestVersion': 6, u'writeColorSets': False, 'anatomyData': {'subset': u'modelMain', 'family': u'model', 'hierarchy': u'Assets', 'app': 'maya', 'yyyy': '2021', 'HH': '10', 'version': 7, 'asset': u'Alpaca_01', 'fps': 25.0, 'ddd': 'Wed', 'username': 'jakub.trllo', 'mm': '08', 'H': '10', 'dd': '25', 'M': '23', 'ht': 'AM', 'mmmm': 'August', 'S': '14', 'dddd': 'Wednesday', 'mmm': 'Aug', 'task': 'animation', 'd': '25', 'SS': '14', 'h': '10', 'm': '8', 'hh': '10', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'MM': '23', 'yy': '21'}, u'id': u'pyblish.avalon.instance', u'attr': u'', 'resourcesDir': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\resources', u'includeParentHierarchy': False, 'name': u'modelMain', 'frameEndHandle': 1020, 'isolate': False, 'assetEntity': {u'name': u'Alpaca_01', u'parent': ObjectId('5eb950f9e3c2c3183455f266'), u'data': {u'visualParent': ObjectId('5dd50967e3c2c32b004b4812'), u'resolutionHeight': 1080, u'clipIn': 1001, u'frameStart': 1001, u'tasks': {u'edit': {u'type': u'Edit'}, u'modeling': {u'type': u'Modeling'}, u'animation': {u'type': u'Animation'}}, u'handleStart': 10, u'entityType': u'AssetBuild', u'frameEnd': 1010, u'parents': [u'Assets'], u'tools_env': [u'mtoa/3-1'], u'pixelAspect': 1.0, u'ftrackId': u'fc02ea60-9786-11eb-8ae9-fa8d1b9899e1', u'resolutionWidth': 1920, u'fps': 25.0, u'handleEnd': 10, u'clipOut': 1100, u'avalon_mongo_id': u'5dd50967e3c2c32b004b4817'}, u'_id': ObjectId('5dd50967e3c2c32b004b4817'), u'type': u'asset', u'schema': u'openpype:asset-3.0'}, 'publish': True, 'label': 'modelMain (Alpaca_01)', 'handles': None, 'frameStartHandle': 991, u'asset': u'Alpaca_01', 'frameStartFtrack': 991, 'subsetEntity': {u'name': u'modelMain', u'parent': ObjectId('5dd50967e3c2c32b004b4817'), u'type': u'subset', u'_id': ObjectId('610124ffb5db38f84962c00e'), u'data': {u'families': [u'model', u'model', u'review']}, u'schema': u'openpype:subset-3.0'}, 'destination_list': ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg', 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.abc', 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.ma', 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4'], 'transfers': [['c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr\\\\modelMain_h264.mp4', 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4']], 'setMembers': [u'|pTorus_GEO'], 'frameEndFtrack': 1020}", + "exc_info": null, + "type": "record", + "levelname": "INFO" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 545.0000762939453, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 1072, + "msg": "Renaming file C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4.tmp to C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 546.9999313354492, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 1072, + "msg": "Renaming file C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007.ma.tmp to C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007.ma", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 548.0000972747803, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 1072, + "msg": "Renaming file C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007.abc.tmp to C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007.abc", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 549.9999523162842, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 1072, + "msg": "Renaming file C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007.jpg.tmp to C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007.jpg", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + } + ] + }, + { + "id": null, + "logs": [ + { + "instance_id": null, + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 607.0001125335693, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 179, + "msg": "kuba_each_case_Alpaca_01_animation_v007 is missing reference to staging directory. Will try to get it from representation.", + "exc_info": null, + "type": "record", + "levelname": "INFO" + }, + { + "instance_id": null, + "name": "openpype.lib.profiles_filtering", + "threadName": "MainThread", + "msecs": 608.9999675750732, + "filename": "profiles_filtering.py", + "levelno": 20, + "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\lib\\profiles_filtering.py", + "lineno": 31, + "msg": "Search for first most matching profile in match order: Host name -> Task name -> Family.", + "exc_info": null, + "type": "record", + "levelname": "INFO" + }, + { + "instance_id": null, + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 611.0000610351562, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 199, + "msg": "Next version: v7", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 611.9999885559082, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 846, + "msg": "Source: {root[work]}/kuba_each_case/Assets/Alpaca_01/work/animation/kuba_each_case_Alpaca_01_animation_v007.ma", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 612.9999160766602, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 215, + "msg": "Creating version ...", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 619.999885559082, + "filename": "profiles_filtering.py", + "levelno": 10, + "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\lib\\profiles_filtering.py", + "lineno": 168, + "msg": "\"families\" not found in [u'review', u'render', u'prerender']", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 619.999885559082, + "filename": "profiles_filtering.py", + "levelno": 20, + "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\lib\\profiles_filtering.py", + "lineno": 31, + "msg": "Search for first most matching profile in match order: Host name -> Task name -> Family.", + "exc_info": null, + "type": "record", + "levelname": "INFO" + }, + { + "instance_id": null, + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 640.0001049041748, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 506, + "msg": "__ dst: C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\workfile\\workfileAnimation\\v007\\kuba_each_case_Alpaca_01_workfileAnimation_v007.ma", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 641.0000324249268, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 563, + "msg": "Integrating source files to destination ...", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 641.0000324249268, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 652, + "msg": "Copying file ... C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\work\\animation\\kuba_each_case_Alpaca_01_animation_v007.ma -> C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\workfile\\workfileAnimation\\v007\\kuba_each_case_Alpaca_01_workfileAnimation_v007.ma.tmp", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 644.0000534057617, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 566, + "msg": "Integrated files {'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\workfile\\\\workfileAnimation\\\\v007\\\\kuba_each_case_Alpaca_01_workfileAnimation_v007.ma.tmp': 56463L}", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 644.9999809265137, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 569, + "msg": "Preparing files information ...", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 644.9999809265137, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 930, + "msg": "get_resource_files_info.resources:[[u'C:/projects/kuba_each_case/Assets/Alpaca_01/work/animation\\\\kuba_each_case_Alpaca_01_animation_v007.ma', 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\workfile\\\\workfileAnimation\\\\v007\\\\kuba_each_case_Alpaca_01_workfileAnimation_v007.ma']]", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 647.0000743865967, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 574, + "msg": "__ representation: {'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/workfile/workfileAnimation/v007/kuba_each_case_Alpaca_01_workfileAnimation_v007.ma', '_id': ObjectId('6125fdf9839e8042d68dbd3e'), 'hash': 'kuba_each_case_Alpaca_01_workfileAnimation_v007,ma|1629816033,49|56463', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 647000)}], 'size': 56463L}], 'context': {'subset': 'workfileAnimation', 'username': 'jakub.trllo', 'task': 'animation', 'family': 'workfile', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'ma', 'version': 7, 'asset': 'Alpaca_01', 'representation': u'ma', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': u'ma', 'parent': ObjectId('6125fdf9839e8042d68dbd3b'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\workfile\\\\workfileAnimation\\\\v007\\\\kuba_each_case_Alpaca_01_workfileAnimation_v007.ma', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd3c'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 650.0000953674316, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 576, + "msg": "__ destination_list: ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\workfile\\\\workfileAnimation\\\\v007\\\\kuba_each_case_Alpaca_01_workfileAnimation_v007.ma']", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 651.0000228881836, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 584, + "msg": "__ representations: [{'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/workfile/workfileAnimation/v007/kuba_each_case_Alpaca_01_workfileAnimation_v007.ma', '_id': ObjectId('6125fdf9839e8042d68dbd3e'), 'hash': 'kuba_each_case_Alpaca_01_workfileAnimation_v007,ma|1629816033,49|56463', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 647000)}], 'size': 56463L}], 'context': {'subset': 'workfileAnimation', 'username': 'jakub.trllo', 'task': 'animation', 'family': 'workfile', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'ma', 'version': 7, 'asset': 'Alpaca_01', 'representation': u'ma', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': u'ma', 'parent': ObjectId('6125fdf9839e8042d68dbd3b'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\workfile\\\\workfileAnimation\\\\v007\\\\kuba_each_case_Alpaca_01_workfileAnimation_v007.ma', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd3c'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}]", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 654.0000438690186, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 594, + "msg": "__ rep: {'files': u'kuba_each_case_Alpaca_01_animation_v007.ma', 'publishedFiles': ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\workfile\\\\workfileAnimation\\\\v007\\\\kuba_each_case_Alpaca_01_workfileAnimation_v007.ma'], 'name': u'ma', 'published_path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\workfile\\\\workfileAnimation\\\\v007\\\\kuba_each_case_Alpaca_01_workfileAnimation_v007.ma', 'stagingDir': u'C:/projects/kuba_each_case/Assets/Alpaca_01/work/animation', 'ext': u'ma'}", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": null, + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 661.0000133514404, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 601, + "msg": "Registered 1 items", + "exc_info": null, + "type": "record", + "levelname": "INFO" + }, + { + "instance_id": null, + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 661.0000133514404, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 124, + "msg": "Integrated Asset in to the database ...", + "exc_info": null, + "type": "record", + "levelname": "INFO" + }, + { + "instance_id": null, + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 661.9999408721924, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 125, + "msg": "instance.data: {'subset': 'workfileAnimation', 'publishDir': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\workfile\\\\workfileAnimation\\\\v007', 'version': 7, 'family': 'workfile', 'frameStart': 1001, 'published_representations': {ObjectId('6125fdf9839e8042d68dbd3c'): {'representation': {'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/workfile/workfileAnimation/v007/kuba_each_case_Alpaca_01_workfileAnimation_v007.ma', '_id': ObjectId('6125fdf9839e8042d68dbd3e'), 'hash': 'kuba_each_case_Alpaca_01_workfileAnimation_v007,ma|1629816033,49|56463', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 647000)}], 'size': 56463L}], 'context': {'subset': 'workfileAnimation', 'username': 'jakub.trllo', 'task': 'animation', 'family': 'workfile', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'ma', 'version': 7, 'asset': 'Alpaca_01', 'representation': u'ma', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': u'ma', 'parent': ObjectId('6125fdf9839e8042d68dbd3b'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\workfile\\\\workfileAnimation\\\\v007\\\\kuba_each_case_Alpaca_01_workfileAnimation_v007.ma', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd3c'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}, 'published_files': ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\workfile\\\\workfileAnimation\\\\v007\\\\kuba_each_case_Alpaca_01_workfileAnimation_v007.ma'], 'anatomy_data': {'subset': 'workfileAnimation', 'family': 'workfile', 'hierarchy': u'Assets', 'app': 'maya', 'yyyy': '2021', 'HH': '10', 'version': 7, 'asset': 'Alpaca_01', 'ddd': 'Wed', 'username': 'jakub.trllo', 'H': '10', 'dd': '25', 'M': '23', 'ht': 'AM', 'mmmm': 'August', 'S': '14', 'dddd': 'Wednesday', 'mmm': 'Aug', 'task': 'animation', 'd': '25', 'SS': '14', 'h': '10', 'm': '8', 'hh': '10', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'mm': '08', 'ext': u'ma', 'yy': '21', 'representation': u'ma', 'MM': '23'}}}, 'destination_list': ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\workfile\\\\workfileAnimation\\\\v007\\\\kuba_each_case_Alpaca_01_workfileAnimation_v007.ma'], 'frameEnd': 1010, 'transfers': [[u'C:/projects/kuba_each_case/Assets/Alpaca_01/work/animation\\\\kuba_each_case_Alpaca_01_animation_v007.ma', 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\workfile\\\\workfileAnimation\\\\v007\\\\kuba_each_case_Alpaca_01_workfileAnimation_v007.ma']], 'latestVersion': 6, 'resourcesDir': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\workfile\\\\workfileAnimation\\\\v007\\\\resources', 'handleStart': 10, 'publish': True, 'label': 'workfileAnimation', 'projectEntity': {u'name': u'kuba_each_case', u'parent': None, u'type': u'project', u'data': {u'code': u'kuba_each_case', u'resolutionHeight': 1080, u'library_project': False, u'clipIn': 1001, u'frameStart': 1001, u'handleStart': 10, u'entityType': u'Project', u'frameEnd': 1100, u'tools_env': [], u'pixelAspect': 1.0, u'ftrackId': u'38c5fec4-0aed-11ea-a454-3e41ec9bc0d6', u'resolutionWidth': 1920, u'fps': 25.0, u'handleEnd': 10, u'clipOut': 1100}, u'_id': ObjectId('5eb950f9e3c2c3183455f266'), u'config': {u'templates': {u'hero': {u'path': u'{@folder}/{@file}', u'folder': u'{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/hero', u'file': u'{project[code]}_{asset}_{subset}_hero<_{output}><.{frame}>.{ext}'}, u'render': {u'path': u'{@folder}/{@file}', u'folder': u'{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/{@version}', u'file': u'{project[code]}_{asset}_{subset}_{@version}<_{output}><.{@frame}>.{ext}'}, u'work': {u'path': u'{@folder}/{@file}', u'folder': u'{root[work]}/{project[name]}/{hierarchy}/{asset}/work/{task}', u'file': u'{project[code]}_{asset}_{task}_{@version}<_{comment}>.{ext}'}, u'publish': {u'path': u'{@folder}/{@file}', u'folder': u'{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/{@version}', u'thumbnail': u'{thumbnail_root}/{project[name]}/{_id}_{thumbnail_type}.{ext}', u'file': u'{project[code]}_{asset}_{subset}_{@version}<_{output}><.{@frame}><_{udim}>.{ext}'}, u'delivery': {}, u'defaults': {u'frame_padding': 4, u'version_padding': 3, u'frame': u'{frame:0>{@frame_padding}}', u'version': u'v{version:0>{@version_padding}}'}, u'others': {}}, u'tasks': {u'Previz': {u'short_name': u''}, u'Lookdev': {u'short_name': u'look'}, u'Edit': {u'short_name': u'edit'}, u'Tracking': {u'short_name': u''}, u'Layout': {u'short_name': u'lay'}, u'Art': {u'short_name': u'art'}, u'Paint': {u'short_name': u'paint'}, u'Generic': {u'short_name': u'gener'}, u'Texture': {u'short_name': u'tex'}, u'Setdress': {u'short_name': u'dress'}, u'Animation': {u'short_name': u'anim'}, u'Lighting': {u'short_name': u'lgt'}, u'Compositing': {u'short_name': u'comp'}, u'Modeling': {u'short_name': u'mdl'}, u'Rigging': {u'short_name': u'rig'}, u'FX': {u'short_name': u'fx'}}, u'imageio': {u'hiero': {u'workfile': {u'eightBitLut': u'sRGB', u'viewerLut': u'sRGB', u'workingSpace': u'linear', u'ocioconfigpath': {u'windows': [], u'darwin': [], u'linux': []}, u'ocioConfigName': u'nuke-default', u'thumbnailLut': u'sRGB', u'floatLut': u'linear', u'sixteenBitLut': u'sRGB', u'logLut': u'Cineon'}, u'regexInputs': {u'inputs': [{u'regex': u'[^-a-zA-Z0-9](plateRef).*(?=mp4)', u'colorspace': u'sRGB'}]}}, u'nuke': {u'viewer': {u'viewerProcess': u'sRGB'}, u'workfile': {u'floatLut': u'linear', u'workingSpaceLUT': u'linear', u'customOCIOConfigPath': {u'windows': [], u'darwin': [], u'linux': []}, u'int16Lut': u'sRGB', u'logLut': u'Cineon', u'int8Lut': u'sRGB', u'colorManagement': u'Nuke', u'OCIO_config': u'nuke-default', u'monitorLut': u'sRGB'}, u'nodes': {u'customNodes': [], u'requiredNodes': [{u'nukeNodeClass': u'Write', u'knobs': [{u'name': u'file_type', u'value': u'exr'}, {u'name': u'datatype', u'value': u'16 bit half'}, {u'name': u'compression', u'value': u'Zip (1 scanline)'}, {u'name': u'autocrop', u'value': u'True'}, {u'name': u'tile_color', u'value': u'0xff0000ff'}, {u'name': u'channels', u'value': u'rgb'}, {u'name': u'colorspace', u'value': u'linear'}, {u'name': u'create_directories', u'value': u'True'}], u'plugins': [u'CreateWriteRender']}, {u'nukeNodeClass': u'Write', u'knobs': [{u'name': u'file_type', u'value': u'exr'}, {u'name': u'datatype', u'value': u'16 bit half'}, {u'name': u'compression', u'value': u'Zip (1 scanline)'}, {u'name': u'autocrop', u'value': u'False'}, {u'name': u'tile_color', u'value': u'0xadab1dff'}, {u'name': u'channels', u'value': u'rgb'}, {u'name': u'colorspace', u'value': u'linear'}, {u'name': u'create_directories', u'value': u'True'}], u'plugins': [u'CreateWritePrerender']}]}, u'regexInputs': {u'inputs': [{u'regex': u'[^-a-zA-Z0-9]beauty[^-a-zA-Z0-9]', u'colorspace': u'linear'}]}}}, u'apps': [{u'name': u'blender/2-91'}, {u'name': u'hiero/11-3'}, {u'name': u'houdini/18-5'}, {u'name': u'maya/2020'}, {u'name': u'nuke/11-3'}, {u'name': u'nukestudio/11-3'}], u'roots': {u'work': {u'windows': u'C:/projects', u'darwin': u'/Volumes/path', u'linux': u'/mnt/share/projects'}}, u'schema': u'openpype:config-2.0'}, u'schema': u'openpype:project-3.0'}, 'asset': 'Alpaca_01', 'setMembers': [u'C:/projects/kuba_each_case/Assets/Alpaca_01/work/animation/kuba_each_case_Alpaca_01_animation_v007.ma'], 'families': ['workfile'], 'handleEnd': 10, 'representations': [{'files': u'kuba_each_case_Alpaca_01_animation_v007.ma', 'publishedFiles': ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\workfile\\\\workfileAnimation\\\\v007\\\\kuba_each_case_Alpaca_01_workfileAnimation_v007.ma'], 'name': u'ma', 'published_path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\workfile\\\\workfileAnimation\\\\v007\\\\kuba_each_case_Alpaca_01_workfileAnimation_v007.ma', 'stagingDir': u'C:/projects/kuba_each_case/Assets/Alpaca_01/work/animation', 'ext': u'ma'}], 'anatomyData': {'subset': 'workfileAnimation', 'family': 'workfile', 'hierarchy': u'Assets', 'app': 'maya', 'yyyy': '2021', 'HH': '10', 'version': 7, 'asset': 'Alpaca_01', 'ddd': 'Wed', 'username': 'jakub.trllo', 'mm': '08', 'H': '10', 'dd': '25', 'M': '23', 'ht': 'AM', 'mmmm': 'August', 'S': '14', 'dddd': 'Wednesday', 'mmm': 'Aug', 'task': 'animation', 'd': '25', 'SS': '14', 'h': '10', 'm': '8', 'hh': '10', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'MM': '23', 'yy': '21'}, 'versionEntity': {u'name': 7, u'parent': ObjectId('61012500b5db38f84962c01d'), u'type': u'version', u'_id': ObjectId('6125fdf9839e8042d68dbd3b'), u'data': {u'comment': u'', u'families': [u'workfile', u'workfile'], u'frameStart': 1001, u'handleStart': 10, u'author': u'jakub.trllo', u'frameEnd': 1010, u'machine': u'014-BAILEYS', u'source': u'{root[work]}/kuba_each_case/Assets/Alpaca_01/work/animation/kuba_each_case_Alpaca_01_animation_v007.ma', u'fps': 25.0, u'time': u'20210825T102314Z', u'handleEnd': 10}, u'schema': u'openpype:version-3.0'}, 'name': u'kuba_each_case_Alpaca_01_animation_v007', 'assetEntity': {u'name': u'Alpaca_01', u'parent': ObjectId('5eb950f9e3c2c3183455f266'), u'data': {u'visualParent': ObjectId('5dd50967e3c2c32b004b4812'), u'resolutionHeight': 1080, u'clipIn': 1001, u'frameStart': 1001, u'tasks': {u'edit': {u'type': u'Edit'}, u'modeling': {u'type': u'Modeling'}, u'animation': {u'type': u'Animation'}}, u'handleStart': 10, u'entityType': u'AssetBuild', u'frameEnd': 1010, u'parents': [u'Assets'], u'tools_env': [u'mtoa/3-1'], u'pixelAspect': 1.0, u'ftrackId': u'fc02ea60-9786-11eb-8ae9-fa8d1b9899e1', u'resolutionWidth': 1920, u'fps': 25.0, u'handleEnd': 10, u'clipOut': 1100, u'avalon_mongo_id': u'5dd50967e3c2c32b004b4817'}, u'_id': ObjectId('5dd50967e3c2c32b004b4817'), u'type': u'asset', u'schema': u'openpype:asset-3.0'}, 'subsetEntity': {u'name': u'workfileAnimation', u'parent': ObjectId('5dd50967e3c2c32b004b4817'), u'type': u'subset', u'_id': ObjectId('61012500b5db38f84962c01d'), u'data': {u'families': [u'workfile', u'workfile']}, u'schema': u'openpype:subset-3.0'}}", + "exc_info": null, + "type": "record", + "levelname": "INFO" + }, + { + "instance_id": null, + "name": "pyblish.IntegrateAssetNew", + "threadName": "MainThread", + "msecs": 680.0000667572021, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 1072, + "msg": "Renaming file C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\workfile\\workfileAnimation\\v007\\kuba_each_case_Alpaca_01_workfileAnimation_v007.ma.tmp to C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\workfile\\workfileAnimation\\v007\\kuba_each_case_Alpaca_01_workfileAnimation_v007.ma", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + } + ] + } + ], + "label": "Integrate Asset New", + "skipped": false, + "order": 3, + "name": "IntegrateAssetNew" + }, + { + "instances_data": [], + "label": "Determine Subset Version", + "skipped": true, + "order": 3, + "name": "DetermineFutureVersion" + }, + { + "instances_data": [ + { + "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "logs": [ + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateThumbnails", + "threadName": "MainThread", + "msecs": 730.0000190734863, + "filename": "", + "levelno": 30, + "pathname": "", + "lineno": 29, + "msg": "AVALON_THUMBNAIL_ROOT is not set. Skipping thumbnail integration.", + "exc_info": null, + "type": "record", + "levelname": "WARNING" + } + ] + } + ], + "label": "Integrate Thumbnails", + "skipped": false, + "order": 3.01, + "name": "IntegrateThumbnails" + }, + { + "instances_data": [], + "label": "Submit to Muster", + "skipped": true, + "order": 3.1, + "name": "MayaSubmitMuster" + }, + { + "instances_data": [], + "label": "Submit to Deadline", + "skipped": true, + "order": 3.1, + "name": "MayaSubmitDeadline" + }, + { + "instances_data": [ + { + "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "logs": [ + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateHeroVersion", + "threadName": "MainThread", + "msecs": 796.9999313354492, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 43, + "msg": "--- Integration of Hero version for subset `modelMain` begins.", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateHeroVersion", + "threadName": "MainThread", + "msecs": 798.0000972747803, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 70, + "msg": "`hero` template check was successful. `{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/hero/{project[code]}_{asset}_{subset}_hero<_{output}><.{frame}>.{ext}`", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateHeroVersion", + "threadName": "MainThread", + "msecs": 816.9999122619629, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 497, + "msg": "hero publish dir: \"C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\hero\"", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateHeroVersion", + "threadName": "MainThread", + "msecs": 821.0000991821289, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 179, + "msg": "Replacing old hero version.", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateHeroVersion", + "threadName": "MainThread", + "msecs": 822.0000267028809, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 254, + "msg": "Backup folder path is \"C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\hero.BACKUP\"", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateHeroVersion", + "threadName": "MainThread", + "msecs": 914.9999618530273, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 507, + "msg": "Folder(s) created: \"C:/projects/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/hero\"", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateHeroVersion", + "threadName": "MainThread", + "msecs": 915.9998893737793, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 516, + "msg": "Copying file \"C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007.ma\" to \"C:/projects/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/hero/kuba_each_case_Alpaca_01_modelMain_hero.ma\"", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateHeroVersion", + "threadName": "MainThread", + "msecs": 917.9999828338623, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 513, + "msg": "Folder already exists: \"C:/projects/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/hero\"", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateHeroVersion", + "threadName": "MainThread", + "msecs": 917.9999828338623, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 516, + "msg": "Copying file \"C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4\" to \"C:/projects/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/hero/kuba_each_case_Alpaca_01_modelMain_hero_h264.mp4\"", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateHeroVersion", + "threadName": "MainThread", + "msecs": 920.0000762939453, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 513, + "msg": "Folder already exists: \"C:/projects/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/hero\"", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateHeroVersion", + "threadName": "MainThread", + "msecs": 921.0000038146973, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 516, + "msg": "Copying file \"C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007.jpg\" to \"C:/projects/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/hero/kuba_each_case_Alpaca_01_modelMain_hero.jpg\"", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateHeroVersion", + "threadName": "MainThread", + "msecs": 921.9999313354492, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 513, + "msg": "Folder already exists: \"C:/projects/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/hero\"", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateHeroVersion", + "threadName": "MainThread", + "msecs": 923.0000972747803, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 516, + "msg": "Copying file \"C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007.abc\" to \"C:/projects/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/hero/kuba_each_case_Alpaca_01_modelMain_hero.abc\"", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + }, + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.IntegrateHeroVersion", + "threadName": "MainThread", + "msecs": 928.9999008178711, + "filename": "", + "levelno": 10, + "pathname": "", + "lineno": 457, + "msg": "--- hero version integration for subset `modelMain` seems to be successful.", + "exc_info": null, + "type": "record", + "levelname": "DEBUG" + } + ] + } + ], + "label": "Integrate Hero Version", + "skipped": false, + "order": 3.1, + "name": "IntegrateHeroVersion" + }, + { + "instances_data": [], + "label": "Submit image sequence jobs to Deadline or Muster", + "skipped": true, + "order": 3.2, + "name": "ProcessSubmittedJobOnFarm" + }, + { + "instances_data": [ + { + "id": null, + "logs": [ + { + "instance_id": null, + "name": "openpype.lib.path_tools", + "threadName": "MainThread", + "msecs": 993.0000305175781, + "filename": "path_tools.py", + "levelno": 20, + "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\lib\\path_tools.py", + "lineno": 64, + "msg": "New version _v008", + "exc_info": null, + "type": "record", + "levelname": "INFO" + } + ] + } + ], + "label": "Increment current file", + "skipped": false, + "order": 12.0, + "name": "IncrementCurrentFileDeadline" + }, + { + "instances_data": [ + { + "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "logs": [ + { + "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", + "name": "pyblish.CleanUp", + "threadName": "MainThread", + "msecs": 107.00011253356934, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 72, + "msg": "Removing staging directory c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr", + "exc_info": null, + "type": "record", + "levelname": "INFO" + } + ] + }, + { + "id": null, + "logs": [ + { + "instance_id": null, + "name": "pyblish.CleanUp", + "threadName": "MainThread", + "msecs": 184.00001525878906, + "filename": "", + "levelno": 20, + "pathname": "", + "lineno": 60, + "msg": "Staging dir not set.", + "exc_info": null, + "type": "record", + "levelname": "INFO" + } + ] + } + ], + "label": "Clean Up", + "skipped": false, + "order": 13, + "name": "CleanUp" + } + ], "context": { - "name": null, - "label": "Testhost - " + "label": "Maya - kuba_each_case_Alpaca_01_animation_v007.ma" } } From 97ba15ca22ead6c2e4bbafdfeddd62f15445b8e9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 25 Aug 2021 11:34:32 +0200 Subject: [PATCH 353/736] added few enhancements for plugins view --- .../publish_log_viewer/widgets.py | 151 +++++++++++++++--- 1 file changed, 126 insertions(+), 25 deletions(-) diff --git a/openpype/tools/new_publisher/publish_log_viewer/widgets.py b/openpype/tools/new_publisher/publish_log_viewer/widgets.py index 4ba8d3f6a7..d555543b5c 100644 --- a/openpype/tools/new_publisher/publish_log_viewer/widgets.py +++ b/openpype/tools/new_publisher/publish_log_viewer/widgets.py @@ -3,8 +3,12 @@ import uuid from Qt import QtWidgets, QtCore, QtGui +import pyblish.api ITEM_ID_ROLE = QtCore.Qt.UserRole + 1 +ITEM_IS_GROUP_ROLE = QtCore.Qt.UserRole + 2 +PLUGIN_SKIPPED_ROLE = QtCore.Qt.UserRole + 3 +PLUGIN_ERRORED_ROLE = QtCore.Qt.UserRole + 4 class PluginItem: @@ -17,9 +21,14 @@ class PluginItem: self.skipped = plugin_data["skipped"] logs = [] + errored = False for instance_data in plugin_data["instances_data"]: - logs.extend(copy.deepcopy(instance_data["logs"])) + for log_item in instance_data["logs"]: + if not errored: + errored = log_item["type"] == "error" + logs.append(copy.deepcopy(log_item)) + self.errored = errored self.logs = logs @property @@ -103,6 +112,7 @@ class InstancesModel(QtGui.QStandardItemModel): for instance_item in instance_items: item = QtGui.QStandardItem(instance_item.label) item.setData(instance_item.id, ITEM_ID_ROLE) + item.setData(False, ITEM_IS_GROUP_ROLE) items.append(item) if family is None: @@ -110,6 +120,8 @@ class InstancesModel(QtGui.QStandardItemModel): continue family_item = QtGui.QStandardItem(family) + family_item.setFlags(QtCore.Qt.ItemIsEnabled) + family_item.setData(True, ITEM_IS_GROUP_ROLE) family_item.appendRows(items) family_items.append(family_item) @@ -117,19 +129,86 @@ class InstancesModel(QtGui.QStandardItemModel): class PluginsModel(QtGui.QStandardItemModel): + order_label_mapping = ( + (pyblish.api.CollectorOrder + 0.5, "Collect"), + (pyblish.api.ValidatorOrder + 0.5, "Validate"), + (pyblish.api.ExtractorOrder + 0.5, "Extract"), + (pyblish.api.IntegratorOrder + 0.5, "Integrate"), + (None, "Other") + ) + def set_report(self, report_item): self.clear() root_item = self.invisibleRootItem() - items = [] + labels_iter = iter(self.order_label_mapping) + cur_order, cur_label = next(labels_iter) + cur_plugin_items = [] + + plugin_items_by_group_labels = [] + plugin_items_by_group_labels.append((cur_label, cur_plugin_items)) for plugin_id in report_item.plugins_id_order: plugin_item = report_item.plugins_items_by_id[plugin_id] - item = QtGui.QStandardItem(plugin_item.label) - item.setData(plugin_item.id, ITEM_ID_ROLE) - items.append(item) + if cur_order is not None and plugin_item.order >= cur_order: + cur_order, cur_label = next(labels_iter) + cur_plugin_items = [] + plugin_items_by_group_labels.append( + (cur_label, cur_plugin_items) + ) - root_item.appendRows(items) + cur_plugin_items.append(plugin_item) + + group_items = [] + for group_label, plugin_items in plugin_items_by_group_labels: + group_item = QtGui.QStandardItem(group_label) + group_item.setData(True, ITEM_IS_GROUP_ROLE) + group_item.setFlags(QtCore.Qt.ItemIsEnabled) + group_items.append(group_item) + + if not plugin_items: + continue + + items = [] + for plugin_item in plugin_items: + item = QtGui.QStandardItem(plugin_item.label) + item.setData(False, ITEM_IS_GROUP_ROLE) + item.setData(plugin_item.id, ITEM_ID_ROLE) + item.setData(plugin_item.skipped, PLUGIN_SKIPPED_ROLE) + item.setData(plugin_item.errored, PLUGIN_ERRORED_ROLE) + items.append(item) + group_item.appendRows(items) + + root_item.appendRows(group_items) + + +class PluginProxyModel(QtCore.QSortFilterProxyModel): + def __init__(self, *args, **kwargs): + super(PluginProxyModel, self).__init__(*args, **kwargs) + + self._ignore_skipped = True + + @property + def ignore_skipped(self): + return self._ignore_skipped + + def set_ignore_skipped(self, value): + if value == self._ignore_skipped: + return + self._ignore_skipped = value + + if self.sourceModel(): + self.invalidateFilter() + + def filterAcceptsRow(self, row, parent): + model = self.sourceModel() + source_index = model.index(row, 0, parent) + if source_index.data(ITEM_IS_GROUP_ROLE): + return model.rowCount(source_index) > 0 + + if self._ignore_skipped and source_index.data(PLUGIN_SKIPPED_ROLE): + return False + return True class DetailsWidget(QtWidgets.QWidget): @@ -172,7 +251,10 @@ class PublishLogViewerWidget(QtWidgets.QWidget): super(PublishLogViewerWidget, self).__init__(parent) instances_model = InstancesModel() + plugins_model = PluginsModel() + plugins_proxy = PluginProxyModel() + plugins_proxy.setSourceModel(plugins_model) instances_view = QtWidgets.QTreeView(self) instances_view.setModel(instances_model) @@ -180,18 +262,26 @@ class PublishLogViewerWidget(QtWidgets.QWidget): instances_view.setHeaderHidden(True) instances_view.setEditTriggers(QtWidgets.QTreeView.NoEditTriggers) + skipped_plugins_check = QtWidgets.QCheckBox( + "Ignore skipped plugins", self + ) + skipped_plugins_check.setChecked( + plugins_proxy.ignore_skipped + ) + plugins_view = QtWidgets.QTreeView(self) - plugins_view.setModel(plugins_model) + plugins_view.setModel(plugins_proxy) # plugins_view.setIndentation(0) plugins_view.setHeaderHidden(True) plugins_view.setEditTriggers(QtWidgets.QTreeView.NoEditTriggers) details_widget = DetailsWidget(self) - layout = QtWidgets.QHBoxLayout(self) - layout.addWidget(instances_view) - layout.addWidget(plugins_view) - layout.addWidget(details_widget, 1) + layout = QtWidgets.QGridLayout(self) + layout.addWidget(instances_view, 1, 0) + layout.addWidget(skipped_plugins_check, 0, 1) + layout.addWidget(plugins_view, 1, 1) + layout.addWidget(details_widget, 1, 2) instances_view.selectionModel().selectionChanged.connect( self._on_instance_change @@ -200,15 +290,34 @@ class PublishLogViewerWidget(QtWidgets.QWidget): self._on_plugin_change ) + skipped_plugins_check.stateChanged.connect( + self._on_skipped_plugin_check + ) + self._ignore_selection_changes = False self._report_item = None self._details_widget = details_widget self._instances_view = instances_view - self._plugins_view = plugins_view - self._instances_model = instances_model + + self._skipped_plugins_check = skipped_plugins_check + self._plugins_view = plugins_view self._plugins_model = plugins_model + self._plugins_proxy = plugins_proxy + + def set_report(self, report_data): + self._ignore_selection_changes = True + + report_item = PublishReport(report_data) + self._report_item = report_item + + self._instances_model.set_report(report_item) + self._plugins_model.set_report(report_item) + + self._details_widget.set_logs(report_item.logs) + + self._ignore_selection_changes = False def _on_instance_change(self, *_args): if self._ignore_selection_changes: @@ -255,15 +364,7 @@ class PublishLogViewerWidget(QtWidgets.QWidget): plugin_item = self._report_item.plugins_items_by_id[plugin_id] self._details_widget.set_logs(plugin_item.logs) - def set_report(self, report_data): - self._ignore_selection_changes = True - - report_item = PublishReport(report_data) - self._report_item = report_item - - self._instances_model.set_report(report_item) - self._plugins_model.set_report(report_item) - - self._details_widget.set_logs(report_item.logs) - - self._ignore_selection_changes = False + def _on_skipped_plugin_check(self): + self._plugins_proxy.set_ignore_skipped( + self._skipped_plugins_check.isChecked() + ) From 08b00f3414f537f64d0410a7aec6ad20148b9c17 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 25 Aug 2021 11:43:52 +0200 Subject: [PATCH 354/736] added filtering of remove instances --- .../publish_log_viewer/widgets.py | 59 +++++++++++++++++-- 1 file changed, 54 insertions(+), 5 deletions(-) diff --git a/openpype/tools/new_publisher/publish_log_viewer/widgets.py b/openpype/tools/new_publisher/publish_log_viewer/widgets.py index d555543b5c..6043344bda 100644 --- a/openpype/tools/new_publisher/publish_log_viewer/widgets.py +++ b/openpype/tools/new_publisher/publish_log_viewer/widgets.py @@ -9,6 +9,7 @@ ITEM_ID_ROLE = QtCore.Qt.UserRole + 1 ITEM_IS_GROUP_ROLE = QtCore.Qt.UserRole + 2 PLUGIN_SKIPPED_ROLE = QtCore.Qt.UserRole + 3 PLUGIN_ERRORED_ROLE = QtCore.Qt.UserRole + 4 +INSTANCE_REMOVED_ROLE = QtCore.Qt.UserRole + 5 class PluginItem: @@ -41,6 +42,7 @@ class InstanceItem: self._id = instance_id self.label = instance_data.get("label") or instance_data.get("name") self.family = instance_data.get("family") + self.removed = not instance_data.get("exists", True) logs = [] for plugin_data in report_data["plugins_data"]: @@ -112,6 +114,7 @@ class InstancesModel(QtGui.QStandardItemModel): for instance_item in instance_items: item = QtGui.QStandardItem(instance_item.label) item.setData(instance_item.id, ITEM_ID_ROLE) + item.setData(instance_item.removed, INSTANCE_REMOVED_ROLE) item.setData(False, ITEM_IS_GROUP_ROLE) items.append(item) @@ -128,6 +131,35 @@ class InstancesModel(QtGui.QStandardItemModel): root_item.appendRows(family_items) +class InstanceProxyModel(QtCore.QSortFilterProxyModel): + def __init__(self, *args, **kwargs): + super(InstanceProxyModel, self).__init__(*args, **kwargs) + + self._ignore_removed = True + + @property + def ignore_removed(self): + return self._ignore_removed + + def set_ignore_removed(self, value): + if value == self._ignore_removed: + return + self._ignore_removed = value + + if self.sourceModel(): + self.invalidateFilter() + + def filterAcceptsRow(self, row, parent): + model = self.sourceModel() + source_index = model.index(row, 0, parent) + if source_index.data(ITEM_IS_GROUP_ROLE): + return model.rowCount(source_index) > 0 + + if self._ignore_removed and source_index.data(PLUGIN_SKIPPED_ROLE): + return False + return True + + class PluginsModel(QtGui.QStandardItemModel): order_label_mapping = ( (pyblish.api.CollectorOrder + 0.5, "Collect"), @@ -173,6 +205,7 @@ class PluginsModel(QtGui.QStandardItemModel): for plugin_item in plugin_items: item = QtGui.QStandardItem(plugin_item.label) item.setData(False, ITEM_IS_GROUP_ROLE) + item.setData(False, ITEM_IS_GROUP_ROLE) item.setData(plugin_item.id, ITEM_ID_ROLE) item.setData(plugin_item.skipped, PLUGIN_SKIPPED_ROLE) item.setData(plugin_item.errored, PLUGIN_ERRORED_ROLE) @@ -251,23 +284,28 @@ class PublishLogViewerWidget(QtWidgets.QWidget): super(PublishLogViewerWidget, self).__init__(parent) instances_model = InstancesModel() + instances_proxy = InstanceProxyModel() + instances_proxy.setSourceModel(instances_model) plugins_model = PluginsModel() plugins_proxy = PluginProxyModel() plugins_proxy.setSourceModel(plugins_model) + removed_instances_check = QtWidgets.QCheckBox( + "Hide removed instances", self + ) + removed_instances_check.setChecked(instances_proxy.ignore_removed) + instances_view = QtWidgets.QTreeView(self) - instances_view.setModel(instances_model) + instances_view.setModel(instances_proxy) # instances_view.setIndentation(0) instances_view.setHeaderHidden(True) instances_view.setEditTriggers(QtWidgets.QTreeView.NoEditTriggers) skipped_plugins_check = QtWidgets.QCheckBox( - "Ignore skipped plugins", self - ) - skipped_plugins_check.setChecked( - plugins_proxy.ignore_skipped + "Hide skipped plugins", self ) + skipped_plugins_check.setChecked(plugins_proxy.ignore_skipped) plugins_view = QtWidgets.QTreeView(self) plugins_view.setModel(plugins_proxy) @@ -278,6 +316,7 @@ class PublishLogViewerWidget(QtWidgets.QWidget): details_widget = DetailsWidget(self) layout = QtWidgets.QGridLayout(self) + layout.addWidget(removed_instances_check, 0, 0) layout.addWidget(instances_view, 1, 0) layout.addWidget(skipped_plugins_check, 0, 1) layout.addWidget(plugins_view, 1, 1) @@ -293,13 +332,18 @@ class PublishLogViewerWidget(QtWidgets.QWidget): skipped_plugins_check.stateChanged.connect( self._on_skipped_plugin_check ) + removed_instances_check.stateChanged.connect( + self._on_removed_instances_check + ) self._ignore_selection_changes = False self._report_item = None self._details_widget = details_widget + self._removed_instances_check = removed_instances_check self._instances_view = instances_view self._instances_model = instances_model + self._instances_proxy = instances_proxy self._skipped_plugins_check = skipped_plugins_check self._plugins_view = plugins_view @@ -368,3 +412,8 @@ class PublishLogViewerWidget(QtWidgets.QWidget): self._plugins_proxy.set_ignore_skipped( self._skipped_plugins_check.isChecked() ) + + def _on_removed_instances_check(self): + self._instances_proxy.set_ignore_removed( + self._removed_instances_check.isChecked() + ) From 649dbf44a278acbddc82debd8233f1c6064c19a6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 31 Aug 2021 14:28:19 +0200 Subject: [PATCH 355/736] modified log viewer --- .../publish_log_viewer/__init__.py | 2 +- .../publish_log_viewer/constants.py | 19 ++ .../publish_log_viewer/delegates.py | 188 +++++++++++++++++ .../new_publisher/publish_log_viewer/model.py | 197 ++++++++++++++++++ .../publish_log_viewer/widgets.py | 183 +++------------- .../publish_log_viewer/window.py | 44 +--- 6 files changed, 432 insertions(+), 201 deletions(-) create mode 100644 openpype/tools/new_publisher/publish_log_viewer/constants.py create mode 100644 openpype/tools/new_publisher/publish_log_viewer/delegates.py create mode 100644 openpype/tools/new_publisher/publish_log_viewer/model.py diff --git a/openpype/tools/new_publisher/publish_log_viewer/__init__.py b/openpype/tools/new_publisher/publish_log_viewer/__init__.py index 156bd05305..eef88102cc 100644 --- a/openpype/tools/new_publisher/publish_log_viewer/__init__.py +++ b/openpype/tools/new_publisher/publish_log_viewer/__init__.py @@ -1,4 +1,4 @@ -from .widgets import ( +from .window import ( PublishLogViewerWindow ) diff --git a/openpype/tools/new_publisher/publish_log_viewer/constants.py b/openpype/tools/new_publisher/publish_log_viewer/constants.py new file mode 100644 index 0000000000..42a4d7be74 --- /dev/null +++ b/openpype/tools/new_publisher/publish_log_viewer/constants.py @@ -0,0 +1,19 @@ +from Qt import QtCore + + +ITEM_ID_ROLE = QtCore.Qt.UserRole + 1 +ITEM_IS_GROUP_ROLE = QtCore.Qt.UserRole + 2 +ITEM_LABEL_ROLE = QtCore.Qt.UserRole + 3 +PLUGIN_SKIPPED_ROLE = QtCore.Qt.UserRole + 4 +PLUGIN_ERRORED_ROLE = QtCore.Qt.UserRole + 5 +INSTANCE_REMOVED_ROLE = QtCore.Qt.UserRole + 6 + + +__all__ = ( + "ITEM_ID_ROLE", + "ITEM_IS_GROUP_ROLE", + "ITEM_LABEL_ROLE", + "PLUGIN_SKIPPED_ROLE", + "PLUGIN_ERRORED_ROLE", + "INSTANCE_REMOVED_ROLE" +) diff --git a/openpype/tools/new_publisher/publish_log_viewer/delegates.py b/openpype/tools/new_publisher/publish_log_viewer/delegates.py new file mode 100644 index 0000000000..ec4555e4b3 --- /dev/null +++ b/openpype/tools/new_publisher/publish_log_viewer/delegates.py @@ -0,0 +1,188 @@ +import platform +from Qt import QtWidgets, QtCore, QtGui +from constants import ( + ITEM_ID_ROLE, + ITEM_IS_GROUP_ROLE, + ITEM_LABEL_ROLE, + PLUGIN_SKIPPED_ROLE, + PLUGIN_ERRORED_ROLE, + INSTANCE_REMOVED_ROLE +) + +colors = { + "error": QtGui.QColor("#ff4a4a"), + "warning": QtGui.QColor("#ff9900"), + "ok": QtGui.QColor("#77AE24"), + "active": QtGui.QColor("#99CEEE"), + "idle": QtCore.Qt.white, + "inactive": QtGui.QColor("#888"), + "hover": QtGui.QColor(255, 255, 255, 5), + "selected": QtGui.QColor(255, 255, 255, 10), + "outline": QtGui.QColor("#333"), + "group": QtGui.QColor("#21252B"), + "group-hover": QtGui.QColor("#3c3c3c"), + "group-selected-hover": QtGui.QColor("#555555") +} + + +class ItemDelegate(QtWidgets.QStyledItemDelegate): + pass + + +class GroupItemDelegate(QtWidgets.QStyledItemDelegate): + """Generic delegate for instance header""" + + def __init__(self, parent): + super(GroupItemDelegate, self).__init__(parent) + self.item_delegate = ItemDelegate(parent) + + self._minus_pixmaps = {} + self._plus_pixmaps = {} + self._pix_offset_ratio = 1 / 3 + self._pix_stroke_size_ratio = 1 / 7 + + path_stroker = QtGui.QPainterPathStroker() + path_stroker.setCapStyle(QtCore.Qt.RoundCap) + path_stroker.setJoinStyle(QtCore.Qt.RoundJoin) + + self._path_stroker = path_stroker + + def _get_plus_pixmap(self, size): + pix = self._minus_pixmaps.get(size) + if pix is not None: + return pix + + pix = QtGui.QPixmap(size, size) + pix.fill(QtCore.Qt.transparent) + + offset = int(size * self._pix_offset_ratio) + pnt_1 = QtCore.QPoint(offset, int(size / 2)) + pnt_2 = QtCore.QPoint(size - offset, int(size / 2)) + pnt_3 = QtCore.QPoint(int(size / 2), offset) + pnt_4 = QtCore.QPoint(int(size / 2), size - offset) + path_1 = QtGui.QPainterPath(pnt_1) + path_1.lineTo(pnt_2) + path_2 = QtGui.QPainterPath(pnt_3) + path_2.lineTo(pnt_4) + + self._path_stroker.setWidth(size * self._pix_stroke_size_ratio) + stroked_path_1 = self._path_stroker.createStroke(path_1) + stroked_path_2 = self._path_stroker.createStroke(path_2) + + pix = QtGui.QPixmap(size, size) + pix.fill(QtCore.Qt.transparent) + + painter = QtGui.QPainter(pix) + painter.setRenderHint(QtGui.QPainter.Antialiasing) + painter.setPen(QtCore.Qt.transparent) + painter.setBrush(QtCore.Qt.white) + painter.drawPath(stroked_path_1) + painter.drawPath(stroked_path_2) + painter.end() + + self._minus_pixmaps[size] = pix + + return pix + + def _get_minus_pixmap(self, size): + pix = self._plus_pixmaps.get(size) + if pix is not None: + return pix + + offset = int(size / self._pix_offset_ratio) + pnt_1 = QtCore.QPoint(offset, int(size / 2)) + pnt_2 = QtCore.QPoint(size - offset, int(size / 2)) + path = QtGui.QPainterPath(pnt_1) + path.lineTo(pnt_2) + self._path_stroker.setWidth(size / self._pix_stroke_size_ratio) + stroked_path = self._path_stroker.createStroke(path) + + pix = QtGui.QPixmap(size, size) + pix.fill(QtCore.Qt.transparent) + + painter = QtGui.QPainter(pix) + painter.setRenderHint(QtGui.QPainter.Antialiasing) + painter.setPen(QtCore.Qt.transparent) + painter.setBrush(QtCore.Qt.white) + painter.drawPath(stroked_path) + painter.end() + + self._plus_pixmaps[size] = pix + + return pix + + def paint(self, painter, option, index): + if index.data(ITEM_IS_GROUP_ROLE): + self.group_item_paint(painter, option, index) + else: + self.item_delegate.paint(painter, option, index) + + def group_item_paint(self, painter, option, index): + """Paint text + _ + My label + """ + self.initStyleOption(option, index) + + widget = option.widget + if widget: + style = widget.style() + else: + style = QtWidgets.QApplicaion.style() + _rect = style.proxy().subElementRect( + style.SE_ItemViewItemText, option, widget + ) + + bg_rect = QtCore.QRectF(option.rect) + bg_rect.setY(_rect.y()) + bg_rect.setHeight(_rect.height()) + + expander_rect = QtCore.QRectF(bg_rect) + expander_rect.setWidth(expander_rect.height() + 5) + + label_rect = QtCore.QRectF( + expander_rect.x() + expander_rect.width(), + expander_rect.y(), + bg_rect.width() - expander_rect.width(), + expander_rect.height() + ) + + bg_path = QtGui.QPainterPath() + radius = (bg_rect.height() / 2) - 0.01 + bg_path.addRoundedRect(bg_rect, radius, radius) + + painter.fillPath(bg_path, colors["group"]) + + selected = option.state & QtWidgets.QStyle.State_Selected + hovered = option.state & QtWidgets.QStyle.State_MouseOver + + if selected and hovered: + painter.fillPath(bg_path, colors["selected"]) + elif hovered: + painter.fillPath(bg_path, colors["hover"]) + + expanded = self.parent().isExpanded(index) + if expanded: + expander_icon = self._get_minus_pixmap(expander_rect.height()) + else: + expander_icon = self._get_plus_pixmap(expander_rect.height()) + + label = index.data(QtCore.Qt.DisplayRole) + label = option.fontMetrics.elidedText( + label, QtCore.Qt.ElideRight, label_rect.width() + ) + + # Maintain reference to state, so we can restore it once we're done + painter.save() + pix_point = QtCore.QPoint( + expander_rect.center().x() - int(expander_icon.width() / 2), + expander_rect.top() + ) + painter.drawPixmap(pix_point, expander_icon) + + # Draw label + painter.setFont(option.font) + painter.drawText(label_rect, QtCore.Qt.AlignVCenter, label) + + # Ok, we're done, tidy up. + painter.restore() diff --git a/openpype/tools/new_publisher/publish_log_viewer/model.py b/openpype/tools/new_publisher/publish_log_viewer/model.py new file mode 100644 index 0000000000..ccc6cb4946 --- /dev/null +++ b/openpype/tools/new_publisher/publish_log_viewer/model.py @@ -0,0 +1,197 @@ +import uuid +from Qt import QtCore, QtGui + +import pyblish.api + +from .constants import ( + ITEM_ID_ROLE, + ITEM_IS_GROUP_ROLE, + ITEM_LABEL_ROLE, + PLUGIN_SKIPPED_ROLE, + PLUGIN_ERRORED_ROLE, + INSTANCE_REMOVED_ROLE +) + + +class InstancesModel(QtGui.QStandardItemModel): + def __init__(self, *args, **kwargs): + super(InstancesModel, self).__init__(*args, **kwargs) + + self._items_by_id = {} + self._plugin_items_by_id = {} + + def get_items_by_id(self): + return self._items_by_id + + def set_report(self, report_item): + self.clear() + self._items_by_id.clear() + self._plugin_items_by_id.clear() + + root_item = self.invisibleRootItem() + + families = set(report_item.instance_items_by_family.keys()) + families.remove(None) + all_families = list(sorted(families)) + all_families.insert(0, None) + + family_items = [] + for family in all_families: + items = [] + instance_items = report_item.instance_items_by_family[family] + for instance_item in instance_items: + item = QtGui.QStandardItem(instance_item.label) + item.setData(instance_item.label, ITEM_LABEL_ROLE) + item.setData(instance_item.id, ITEM_ID_ROLE) + item.setData(instance_item.removed, INSTANCE_REMOVED_ROLE) + item.setData(False, ITEM_IS_GROUP_ROLE) + items.append(item) + self._items_by_id[instance_item.id] = item + self._plugin_items_by_id[instance_item.id] = item + + if family is None: + family_items.extend(items) + continue + + family_item = QtGui.QStandardItem(family) + family_item.setData(family, ITEM_LABEL_ROLE) + family_item.setFlags(QtCore.Qt.ItemIsEnabled) + family_id = uuid.uuid4() + family_item.setData(family_id, ITEM_ID_ROLE) + family_item.setData(True, ITEM_IS_GROUP_ROLE) + family_item.appendRows(items) + family_items.append(family_item) + self._items_by_id[family_id] = family_item + + root_item.appendRows(family_items) + + +class InstanceProxyModel(QtCore.QSortFilterProxyModel): + def __init__(self, *args, **kwargs): + super(InstanceProxyModel, self).__init__(*args, **kwargs) + + self._ignore_removed = True + + @property + def ignore_removed(self): + return self._ignore_removed + + def set_ignore_removed(self, value): + if value == self._ignore_removed: + return + self._ignore_removed = value + + if self.sourceModel(): + self.invalidateFilter() + + def filterAcceptsRow(self, row, parent): + model = self.sourceModel() + source_index = model.index(row, 0, parent) + if source_index.data(ITEM_IS_GROUP_ROLE): + return model.rowCount(source_index) > 0 + + if self._ignore_removed and source_index.data(PLUGIN_SKIPPED_ROLE): + return False + return True + + +class PluginsModel(QtGui.QStandardItemModel): + order_label_mapping = ( + (pyblish.api.CollectorOrder + 0.5, "Collect"), + (pyblish.api.ValidatorOrder + 0.5, "Validate"), + (pyblish.api.ExtractorOrder + 0.5, "Extract"), + (pyblish.api.IntegratorOrder + 0.5, "Integrate"), + (None, "Other") + ) + + def __init__(self, *args, **kwargs): + super(PluginsModel, self).__init__(*args, **kwargs) + + self._items_by_id = {} + self._plugin_items_by_id = {} + + def get_items_by_id(self): + return self._items_by_id + + def set_report(self, report_item): + self.clear() + self._items_by_id.clear() + self._plugin_items_by_id.clear() + + root_item = self.invisibleRootItem() + + labels_iter = iter(self.order_label_mapping) + cur_order, cur_label = next(labels_iter) + cur_plugin_items = [] + + plugin_items_by_group_labels = [] + plugin_items_by_group_labels.append((cur_label, cur_plugin_items)) + for plugin_id in report_item.plugins_id_order: + plugin_item = report_item.plugins_items_by_id[plugin_id] + if cur_order is not None and plugin_item.order >= cur_order: + cur_order, cur_label = next(labels_iter) + cur_plugin_items = [] + plugin_items_by_group_labels.append( + (cur_label, cur_plugin_items) + ) + + cur_plugin_items.append(plugin_item) + + group_items = [] + for group_label, plugin_items in plugin_items_by_group_labels: + group_id = uuid.uuid4() + group_item = QtGui.QStandardItem(group_label) + group_item.setData(group_label, ITEM_LABEL_ROLE) + group_item.setData(group_id, ITEM_ID_ROLE) + group_item.setData(True, ITEM_IS_GROUP_ROLE) + group_item.setFlags(QtCore.Qt.ItemIsEnabled) + group_items.append(group_item) + + self._items_by_id[group_id] = group_item + + if not plugin_items: + continue + + items = [] + for plugin_item in plugin_items: + item = QtGui.QStandardItem(plugin_item.label) + item.setData(False, ITEM_IS_GROUP_ROLE) + item.setData(plugin_item.label, ITEM_LABEL_ROLE) + item.setData(plugin_item.id, ITEM_ID_ROLE) + item.setData(plugin_item.skipped, PLUGIN_SKIPPED_ROLE) + item.setData(plugin_item.errored, PLUGIN_ERRORED_ROLE) + items.append(item) + self._items_by_id[plugin_item.id] = item + self._plugin_items_by_id[plugin_item.id] = item + group_item.appendRows(items) + + root_item.appendRows(group_items) + + +class PluginProxyModel(QtCore.QSortFilterProxyModel): + def __init__(self, *args, **kwargs): + super(PluginProxyModel, self).__init__(*args, **kwargs) + + self._ignore_skipped = True + + @property + def ignore_skipped(self): + return self._ignore_skipped + + def set_ignore_skipped(self, value): + if value == self._ignore_skipped: + return + self._ignore_skipped = value + + if self.sourceModel(): + self.invalidateFilter() + + def filterAcceptsRow(self, row, parent): + model = self.sourceModel() + source_index = model.index(row, 0, parent) + if source_index.data(ITEM_IS_GROUP_ROLE): + return model.rowCount(source_index) > 0 + + if self._ignore_skipped and source_index.data(PLUGIN_SKIPPED_ROLE): + return False + return True diff --git a/openpype/tools/new_publisher/publish_log_viewer/widgets.py b/openpype/tools/new_publisher/publish_log_viewer/widgets.py index 6043344bda..d45f88dfb6 100644 --- a/openpype/tools/new_publisher/publish_log_viewer/widgets.py +++ b/openpype/tools/new_publisher/publish_log_viewer/widgets.py @@ -5,11 +5,16 @@ from Qt import QtWidgets, QtCore, QtGui import pyblish.api -ITEM_ID_ROLE = QtCore.Qt.UserRole + 1 -ITEM_IS_GROUP_ROLE = QtCore.Qt.UserRole + 2 -PLUGIN_SKIPPED_ROLE = QtCore.Qt.UserRole + 3 -PLUGIN_ERRORED_ROLE = QtCore.Qt.UserRole + 4 -INSTANCE_REMOVED_ROLE = QtCore.Qt.UserRole + 5 +from .constants import ( + ITEM_ID_ROLE +) +from .delegates import GroupItemDelegate +from .model import ( + InstancesModel, + InstanceProxyModel, + PluginsModel, + PluginProxyModel +) class PluginItem: @@ -96,154 +101,6 @@ class PublishReport: self.logs = all_logs -class InstancesModel(QtGui.QStandardItemModel): - def set_report(self, report_item): - self.clear() - - root_item = self.invisibleRootItem() - - families = set(report_item.instance_items_by_family.keys()) - families.remove(None) - all_families = list(sorted(families)) - all_families.insert(0, None) - - family_items = [] - for family in all_families: - items = [] - instance_items = report_item.instance_items_by_family[family] - for instance_item in instance_items: - item = QtGui.QStandardItem(instance_item.label) - item.setData(instance_item.id, ITEM_ID_ROLE) - item.setData(instance_item.removed, INSTANCE_REMOVED_ROLE) - item.setData(False, ITEM_IS_GROUP_ROLE) - items.append(item) - - if family is None: - family_items.extend(items) - continue - - family_item = QtGui.QStandardItem(family) - family_item.setFlags(QtCore.Qt.ItemIsEnabled) - family_item.setData(True, ITEM_IS_GROUP_ROLE) - family_item.appendRows(items) - family_items.append(family_item) - - root_item.appendRows(family_items) - - -class InstanceProxyModel(QtCore.QSortFilterProxyModel): - def __init__(self, *args, **kwargs): - super(InstanceProxyModel, self).__init__(*args, **kwargs) - - self._ignore_removed = True - - @property - def ignore_removed(self): - return self._ignore_removed - - def set_ignore_removed(self, value): - if value == self._ignore_removed: - return - self._ignore_removed = value - - if self.sourceModel(): - self.invalidateFilter() - - def filterAcceptsRow(self, row, parent): - model = self.sourceModel() - source_index = model.index(row, 0, parent) - if source_index.data(ITEM_IS_GROUP_ROLE): - return model.rowCount(source_index) > 0 - - if self._ignore_removed and source_index.data(PLUGIN_SKIPPED_ROLE): - return False - return True - - -class PluginsModel(QtGui.QStandardItemModel): - order_label_mapping = ( - (pyblish.api.CollectorOrder + 0.5, "Collect"), - (pyblish.api.ValidatorOrder + 0.5, "Validate"), - (pyblish.api.ExtractorOrder + 0.5, "Extract"), - (pyblish.api.IntegratorOrder + 0.5, "Integrate"), - (None, "Other") - ) - - def set_report(self, report_item): - self.clear() - - root_item = self.invisibleRootItem() - - labels_iter = iter(self.order_label_mapping) - cur_order, cur_label = next(labels_iter) - cur_plugin_items = [] - - plugin_items_by_group_labels = [] - plugin_items_by_group_labels.append((cur_label, cur_plugin_items)) - for plugin_id in report_item.plugins_id_order: - plugin_item = report_item.plugins_items_by_id[plugin_id] - if cur_order is not None and plugin_item.order >= cur_order: - cur_order, cur_label = next(labels_iter) - cur_plugin_items = [] - plugin_items_by_group_labels.append( - (cur_label, cur_plugin_items) - ) - - cur_plugin_items.append(plugin_item) - - group_items = [] - for group_label, plugin_items in plugin_items_by_group_labels: - group_item = QtGui.QStandardItem(group_label) - group_item.setData(True, ITEM_IS_GROUP_ROLE) - group_item.setFlags(QtCore.Qt.ItemIsEnabled) - group_items.append(group_item) - - if not plugin_items: - continue - - items = [] - for plugin_item in plugin_items: - item = QtGui.QStandardItem(plugin_item.label) - item.setData(False, ITEM_IS_GROUP_ROLE) - item.setData(False, ITEM_IS_GROUP_ROLE) - item.setData(plugin_item.id, ITEM_ID_ROLE) - item.setData(plugin_item.skipped, PLUGIN_SKIPPED_ROLE) - item.setData(plugin_item.errored, PLUGIN_ERRORED_ROLE) - items.append(item) - group_item.appendRows(items) - - root_item.appendRows(group_items) - - -class PluginProxyModel(QtCore.QSortFilterProxyModel): - def __init__(self, *args, **kwargs): - super(PluginProxyModel, self).__init__(*args, **kwargs) - - self._ignore_skipped = True - - @property - def ignore_skipped(self): - return self._ignore_skipped - - def set_ignore_skipped(self, value): - if value == self._ignore_skipped: - return - self._ignore_skipped = value - - if self.sourceModel(): - self.invalidateFilter() - - def filterAcceptsRow(self, row, parent): - model = self.sourceModel() - source_index = model.index(row, 0, parent) - if source_index.data(ITEM_IS_GROUP_ROLE): - return model.rowCount(source_index) > 0 - - if self._ignore_skipped and source_index.data(PLUGIN_SKIPPED_ROLE): - return False - return True - - class DetailsWidget(QtWidgets.QWidget): def __init__(self, parent): super(DetailsWidget, self).__init__(parent) @@ -297,28 +154,38 @@ class PublishLogViewerWidget(QtWidgets.QWidget): removed_instances_check.setChecked(instances_proxy.ignore_removed) instances_view = QtWidgets.QTreeView(self) + instances_view.setObjectName("PublishDetailViews") instances_view.setModel(instances_proxy) - # instances_view.setIndentation(0) + instances_view.setIndentation(0) instances_view.setHeaderHidden(True) instances_view.setEditTriggers(QtWidgets.QTreeView.NoEditTriggers) + instances_delegate = GroupItemDelegate(instances_view) + instances_view.setItemDelegate(instances_delegate) + skipped_plugins_check = QtWidgets.QCheckBox( "Hide skipped plugins", self ) skipped_plugins_check.setChecked(plugins_proxy.ignore_skipped) plugins_view = QtWidgets.QTreeView(self) + plugins_view.setObjectName("PublishDetailViews") plugins_view.setModel(plugins_proxy) - # plugins_view.setIndentation(0) + plugins_view.setIndentation(0) plugins_view.setHeaderHidden(True) plugins_view.setEditTriggers(QtWidgets.QTreeView.NoEditTriggers) + plugins_delegate = GroupItemDelegate(plugins_view) + plugins_view.setItemDelegate(plugins_delegate) + details_widget = DetailsWidget(self) layout = QtWidgets.QGridLayout(self) + # Row 1 layout.addWidget(removed_instances_check, 0, 0) - layout.addWidget(instances_view, 1, 0) layout.addWidget(skipped_plugins_check, 0, 1) + # Row 2 + layout.addWidget(instances_view, 1, 0) layout.addWidget(plugins_view, 1, 1) layout.addWidget(details_widget, 1, 2) @@ -345,6 +212,9 @@ class PublishLogViewerWidget(QtWidgets.QWidget): self._instances_model = instances_model self._instances_proxy = instances_proxy + self._instances_delegate = instances_delegate + self._plugins_delegate = plugins_delegate + self._skipped_plugins_check = skipped_plugins_check self._plugins_view = plugins_view self._plugins_model = plugins_model @@ -358,7 +228,6 @@ class PublishLogViewerWidget(QtWidgets.QWidget): self._instances_model.set_report(report_item) self._plugins_model.set_report(report_item) - self._details_widget.set_logs(report_item.logs) self._ignore_selection_changes = False diff --git a/openpype/tools/new_publisher/publish_log_viewer/window.py b/openpype/tools/new_publisher/publish_log_viewer/window.py index 517b0aadee..1bfca3b86b 100644 --- a/openpype/tools/new_publisher/publish_log_viewer/window.py +++ b/openpype/tools/new_publisher/publish_log_viewer/window.py @@ -1,27 +1,4 @@ -import os -import sys -import json -import copy -import uuid -import collections - -openpype_dir = r"C:\Users\jakub.trllo\Desktop\pype\pype3" -mongo_url = "mongodb://localhost:2707" - -os.environ["OPENPYPE_MONGO"] = mongo_url -os.environ["AVALON_MONGO"] = mongo_url -os.environ["OPENPYPE_DATABASE_NAME"] = "openpype" -os.environ["AVALON_CONFIG"] = "openpype" -os.environ["AVALON_TIMEOUT"] = "1000" -os.environ["AVALON_DB"] = "avalon" -for path in [ - openpype_dir, - r"{}\repos\avalon-core".format(openpype_dir), - r"{}\.venv\Lib\site-packages".format(openpype_dir) -]: - sys.path.append(path) - -from Qt import QtWidgets, QtCore, QtGui +from Qt import QtWidgets from openpype import style if __package__: @@ -50,22 +27,3 @@ class PublishLogViewerWindow(QtWidgets.QWidget): def set_report(self, report_data): self._main_widget.set_report(report_data) - - -def main(): - """Main function for testing purposes.""" - app = QtWidgets.QApplication([]) - window = PublishLogViewerWindow() - - log_path = os.path.join(os.path.dirname(__file__), "logs.json") - with open(log_path, "r") as file_stream: - report_data = json.load(file_stream) - - window.set_report(report_data) - - window.show() - app.exec_() - - -if __name__ == "__main__": - main() From 99a6cee26c0d98b59fc4c0242fe879353e7b7b49 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 31 Aug 2021 14:33:25 +0200 Subject: [PATCH 356/736] modified pixmap getter to be class methods --- .../publish_log_viewer/delegates.py | 53 +++++++++++-------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/openpype/tools/new_publisher/publish_log_viewer/delegates.py b/openpype/tools/new_publisher/publish_log_viewer/delegates.py index ec4555e4b3..e959c7c50a 100644 --- a/openpype/tools/new_publisher/publish_log_viewer/delegates.py +++ b/openpype/tools/new_publisher/publish_log_viewer/delegates.py @@ -31,31 +31,37 @@ class ItemDelegate(QtWidgets.QStyledItemDelegate): class GroupItemDelegate(QtWidgets.QStyledItemDelegate): """Generic delegate for instance header""" + _minus_pixmaps = {} + _plus_pixmaps = {} + _path_stroker = None + + _pix_offset_ratio = 1 / 3 + _pix_stroke_size_ratio = 1 / 7 def __init__(self, parent): super(GroupItemDelegate, self).__init__(parent) self.item_delegate = ItemDelegate(parent) - self._minus_pixmaps = {} - self._plus_pixmaps = {} - self._pix_offset_ratio = 1 / 3 - self._pix_stroke_size_ratio = 1 / 7 + @classmethod + def _get_path_stroker(cls): + if cls._path_stroker is None: + path_stroker = QtGui.QPainterPathStroker() + path_stroker.setCapStyle(QtCore.Qt.RoundCap) + path_stroker.setJoinStyle(QtCore.Qt.RoundJoin) - path_stroker = QtGui.QPainterPathStroker() - path_stroker.setCapStyle(QtCore.Qt.RoundCap) - path_stroker.setJoinStyle(QtCore.Qt.RoundJoin) + cls._path_stroker = path_stroker + return cls._path_stroker - self._path_stroker = path_stroker - - def _get_plus_pixmap(self, size): - pix = self._minus_pixmaps.get(size) + @classmethod + def _get_plus_pixmap(cls, size): + pix = cls._minus_pixmaps.get(size) if pix is not None: return pix pix = QtGui.QPixmap(size, size) pix.fill(QtCore.Qt.transparent) - offset = int(size * self._pix_offset_ratio) + offset = int(size * cls._pix_offset_ratio) pnt_1 = QtCore.QPoint(offset, int(size / 2)) pnt_2 = QtCore.QPoint(size - offset, int(size / 2)) pnt_3 = QtCore.QPoint(int(size / 2), offset) @@ -65,9 +71,10 @@ class GroupItemDelegate(QtWidgets.QStyledItemDelegate): path_2 = QtGui.QPainterPath(pnt_3) path_2.lineTo(pnt_4) - self._path_stroker.setWidth(size * self._pix_stroke_size_ratio) - stroked_path_1 = self._path_stroker.createStroke(path_1) - stroked_path_2 = self._path_stroker.createStroke(path_2) + path_stroker = cls._get_path_stroker() + path_stroker.setWidth(size * cls._pix_stroke_size_ratio) + stroked_path_1 = path_stroker.createStroke(path_1) + stroked_path_2 = path_stroker.createStroke(path_2) pix = QtGui.QPixmap(size, size) pix.fill(QtCore.Qt.transparent) @@ -80,22 +87,24 @@ class GroupItemDelegate(QtWidgets.QStyledItemDelegate): painter.drawPath(stroked_path_2) painter.end() - self._minus_pixmaps[size] = pix + cls._minus_pixmaps[size] = pix return pix - def _get_minus_pixmap(self, size): - pix = self._plus_pixmaps.get(size) + @classmethod + def _get_minus_pixmap(cls, size): + pix = cls._plus_pixmaps.get(size) if pix is not None: return pix - offset = int(size / self._pix_offset_ratio) + offset = int(size * cls._pix_offset_ratio) pnt_1 = QtCore.QPoint(offset, int(size / 2)) pnt_2 = QtCore.QPoint(size - offset, int(size / 2)) path = QtGui.QPainterPath(pnt_1) path.lineTo(pnt_2) - self._path_stroker.setWidth(size / self._pix_stroke_size_ratio) - stroked_path = self._path_stroker.createStroke(path) + path_stroker = cls._get_path_stroker() + path_stroker.setWidth(size * cls._pix_stroke_size_ratio) + stroked_path = path_stroker.createStroke(path) pix = QtGui.QPixmap(size, size) pix.fill(QtCore.Qt.transparent) @@ -107,7 +116,7 @@ class GroupItemDelegate(QtWidgets.QStyledItemDelegate): painter.drawPath(stroked_path) painter.end() - self._plus_pixmaps[size] = pix + cls._plus_pixmaps[size] = pix return pix From 1a1415ab8d581d25e2dbbc3202515a0a61cc8724 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 31 Aug 2021 14:33:50 +0200 Subject: [PATCH 357/736] change labels to empty strings when finished --- openpype/tools/new_publisher/widgets/publish_widget.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/tools/new_publisher/widgets/publish_widget.py b/openpype/tools/new_publisher/widgets/publish_widget.py index 242cbd23d1..8e85521fb1 100644 --- a/openpype/tools/new_publisher/widgets/publish_widget.py +++ b/openpype/tools/new_publisher/widgets/publish_widget.py @@ -273,6 +273,8 @@ class PublishFrame(QtWidgets.QFrame): def _set_finished(self): self.main_label.setText("Finished") + self.message_label.setText("") + self.message_label_bottom.setText("") self._set_success_property(1) def _change_bg_property(self, state=None): From ac7c7e7dd204cb56f1655243b21cf109b524bd2e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 31 Aug 2021 15:34:19 +0200 Subject: [PATCH 358/736] better item drawing --- .../publish_log_viewer/constants.py | 6 +- .../publish_log_viewer/delegates.py | 133 +++++++++++++++--- .../new_publisher/publish_log_viewer/model.py | 5 +- .../publish_log_viewer/widgets.py | 7 + 4 files changed, 129 insertions(+), 22 deletions(-) diff --git a/openpype/tools/new_publisher/publish_log_viewer/constants.py b/openpype/tools/new_publisher/publish_log_viewer/constants.py index 42a4d7be74..f1e13534d0 100644 --- a/openpype/tools/new_publisher/publish_log_viewer/constants.py +++ b/openpype/tools/new_publisher/publish_log_viewer/constants.py @@ -4,8 +4,8 @@ from Qt import QtCore ITEM_ID_ROLE = QtCore.Qt.UserRole + 1 ITEM_IS_GROUP_ROLE = QtCore.Qt.UserRole + 2 ITEM_LABEL_ROLE = QtCore.Qt.UserRole + 3 -PLUGIN_SKIPPED_ROLE = QtCore.Qt.UserRole + 4 -PLUGIN_ERRORED_ROLE = QtCore.Qt.UserRole + 5 +ITEM_ERRORED_ROLE = QtCore.Qt.UserRole + 4 +PLUGIN_SKIPPED_ROLE = QtCore.Qt.UserRole + 5 INSTANCE_REMOVED_ROLE = QtCore.Qt.UserRole + 6 @@ -13,7 +13,7 @@ __all__ = ( "ITEM_ID_ROLE", "ITEM_IS_GROUP_ROLE", "ITEM_LABEL_ROLE", + "ITEM_ERRORED_ROLE", "PLUGIN_SKIPPED_ROLE", - "PLUGIN_ERRORED_ROLE", "INSTANCE_REMOVED_ROLE" ) diff --git a/openpype/tools/new_publisher/publish_log_viewer/delegates.py b/openpype/tools/new_publisher/publish_log_viewer/delegates.py index e959c7c50a..f6077a496d 100644 --- a/openpype/tools/new_publisher/publish_log_viewer/delegates.py +++ b/openpype/tools/new_publisher/publish_log_viewer/delegates.py @@ -1,11 +1,12 @@ import platform +import collections from Qt import QtWidgets, QtCore, QtGui -from constants import ( +from .constants import ( ITEM_ID_ROLE, ITEM_IS_GROUP_ROLE, + ITEM_ERRORED_ROLE, ITEM_LABEL_ROLE, PLUGIN_SKIPPED_ROLE, - PLUGIN_ERRORED_ROLE, INSTANCE_REMOVED_ROLE ) @@ -25,22 +26,19 @@ colors = { } -class ItemDelegate(QtWidgets.QStyledItemDelegate): - pass - - class GroupItemDelegate(QtWidgets.QStyledItemDelegate): """Generic delegate for instance header""" + + _item_icons_by_name_and_size = collections.defaultdict(dict) + _minus_pixmaps = {} _plus_pixmaps = {} _path_stroker = None - _pix_offset_ratio = 1 / 3 - _pix_stroke_size_ratio = 1 / 7 - - def __init__(self, parent): - super(GroupItemDelegate, self).__init__(parent) - self.item_delegate = ItemDelegate(parent) + _item_pix_offset_ratio = 1 / 5 + _item_border_size = 1 / 7 + _group_pix_offset_ratio = 1 / 3 + _group_pix_stroke_size_ratio = 1 / 7 @classmethod def _get_path_stroker(cls): @@ -61,7 +59,7 @@ class GroupItemDelegate(QtWidgets.QStyledItemDelegate): pix = QtGui.QPixmap(size, size) pix.fill(QtCore.Qt.transparent) - offset = int(size * cls._pix_offset_ratio) + offset = int(size * cls._group_pix_offset_ratio) pnt_1 = QtCore.QPoint(offset, int(size / 2)) pnt_2 = QtCore.QPoint(size - offset, int(size / 2)) pnt_3 = QtCore.QPoint(int(size / 2), offset) @@ -72,7 +70,7 @@ class GroupItemDelegate(QtWidgets.QStyledItemDelegate): path_2.lineTo(pnt_4) path_stroker = cls._get_path_stroker() - path_stroker.setWidth(size * cls._pix_stroke_size_ratio) + path_stroker.setWidth(size * cls._group_pix_stroke_size_ratio) stroked_path_1 = path_stroker.createStroke(path_1) stroked_path_2 = path_stroker.createStroke(path_2) @@ -97,13 +95,13 @@ class GroupItemDelegate(QtWidgets.QStyledItemDelegate): if pix is not None: return pix - offset = int(size * cls._pix_offset_ratio) + offset = int(size * cls._group_pix_offset_ratio) pnt_1 = QtCore.QPoint(offset, int(size / 2)) pnt_2 = QtCore.QPoint(size - offset, int(size / 2)) path = QtGui.QPainterPath(pnt_1) path.lineTo(pnt_2) path_stroker = cls._get_path_stroker() - path_stroker.setWidth(size * cls._pix_stroke_size_ratio) + path_stroker.setWidth(size * cls._group_pix_stroke_size_ratio) stroked_path = path_stroker.createStroke(path) pix = QtGui.QPixmap(size, size) @@ -120,11 +118,112 @@ class GroupItemDelegate(QtWidgets.QStyledItemDelegate): return pix + @classmethod + def _get_icon_color(cls, name): + if name == "error": + return QtGui.QColor(colors["error"]) + return QtGui.QColor(QtCore.Qt.white) + + @classmethod + def _get_icon(cls, name, size): + icons_by_size = cls._item_icons_by_name_and_size[name] + if icons_by_size and size in icons_by_size: + return icons_by_size[size] + + pix = QtGui.QPixmap(size, size) + pix.fill(QtCore.Qt.transparent) + + painter = QtGui.QPainter(pix) + painter.setRenderHint(QtGui.QPainter.Antialiasing) + + if name == "error": + color = QtGui.QColor(colors["error"]) + painter.setPen(QtCore.Qt.NoPen) + painter.setBrush(color) + elif name == "skipped": + color = QtGui.QColor(QtCore.Qt.white) + pen = QtGui.QPen(color) + pen.setWidth(int(size * cls._item_border_size)) + painter.setPen(pen) + painter.setBrush(QtCore.Qt.transparent) + else: + color = QtGui.QColor(QtCore.Qt.white) + painter.setPen(QtCore.Qt.NoPen) + painter.setBrush(color) + + offset = int(size * cls._item_pix_offset_ratio) + painter.drawEllipse( + offset, offset, + size - (2 * offset), size - (2 * offset) + ) + painter.end() + + cls._item_icons_by_name_and_size[name][size] = pix + + return pix + def paint(self, painter, option, index): if index.data(ITEM_IS_GROUP_ROLE): self.group_item_paint(painter, option, index) else: - self.item_delegate.paint(painter, option, index) + self.item_paint(painter, option, index) + + def item_paint(self, painter, option, index): + self.initStyleOption(option, index) + + widget = option.widget + if widget: + style = widget.style() + else: + style = QtWidgets.QApplicaion.style() + + style.proxy().drawPrimitive( + style.PE_PanelItemViewItem, option, painter, widget + ) + _rect = style.proxy().subElementRect( + style.SE_ItemViewItemText, option, widget + ) + bg_rect = QtCore.QRectF(option.rect) + bg_rect.setY(_rect.y()) + bg_rect.setHeight(_rect.height()) + + expander_rect = QtCore.QRectF(bg_rect) + expander_rect.setWidth(expander_rect.height() + 5) + + label_rect = QtCore.QRectF( + expander_rect.x() + expander_rect.width(), + expander_rect.y(), + bg_rect.width() - expander_rect.width(), + expander_rect.height() + ) + + icon_size = expander_rect.height() + if index.data(ITEM_ERRORED_ROLE): + expander_icon = self._get_icon("error", icon_size) + elif index.data(PLUGIN_SKIPPED_ROLE): + expander_icon = self._get_icon("skipped", icon_size) + else: + expander_icon = self._get_icon("", icon_size) + + label = index.data(QtCore.Qt.DisplayRole) + label = option.fontMetrics.elidedText( + label, QtCore.Qt.ElideRight, label_rect.width() + ) + + painter.save() + # Draw icon + pix_point = QtCore.QPoint( + expander_rect.center().x() - int(expander_icon.width() / 2), + expander_rect.top() + ) + painter.drawPixmap(pix_point, expander_icon) + + # Draw label + painter.setFont(option.font) + painter.drawText(label_rect, QtCore.Qt.AlignVCenter, label) + + # Ok, we're done, tidy up. + painter.restore() def group_item_paint(self, painter, option, index): """Paint text diff --git a/openpype/tools/new_publisher/publish_log_viewer/model.py b/openpype/tools/new_publisher/publish_log_viewer/model.py index ccc6cb4946..3e115ff61e 100644 --- a/openpype/tools/new_publisher/publish_log_viewer/model.py +++ b/openpype/tools/new_publisher/publish_log_viewer/model.py @@ -7,8 +7,8 @@ from .constants import ( ITEM_ID_ROLE, ITEM_IS_GROUP_ROLE, ITEM_LABEL_ROLE, + ITEM_ERRORED_ROLE, PLUGIN_SKIPPED_ROLE, - PLUGIN_ERRORED_ROLE, INSTANCE_REMOVED_ROLE ) @@ -42,6 +42,7 @@ class InstancesModel(QtGui.QStandardItemModel): for instance_item in instance_items: item = QtGui.QStandardItem(instance_item.label) item.setData(instance_item.label, ITEM_LABEL_ROLE) + item.setData(instance_item.errored, ITEM_ERRORED_ROLE) item.setData(instance_item.id, ITEM_ID_ROLE) item.setData(instance_item.removed, INSTANCE_REMOVED_ROLE) item.setData(False, ITEM_IS_GROUP_ROLE) @@ -159,7 +160,7 @@ class PluginsModel(QtGui.QStandardItemModel): item.setData(plugin_item.label, ITEM_LABEL_ROLE) item.setData(plugin_item.id, ITEM_ID_ROLE) item.setData(plugin_item.skipped, PLUGIN_SKIPPED_ROLE) - item.setData(plugin_item.errored, PLUGIN_ERRORED_ROLE) + item.setData(plugin_item.errored, ITEM_ERRORED_ROLE) items.append(item) self._items_by_id[plugin_item.id] = item self._plugin_items_by_id[plugin_item.id] = item diff --git a/openpype/tools/new_publisher/publish_log_viewer/widgets.py b/openpype/tools/new_publisher/publish_log_viewer/widgets.py index d45f88dfb6..ce92d63f26 100644 --- a/openpype/tools/new_publisher/publish_log_viewer/widgets.py +++ b/openpype/tools/new_publisher/publish_log_viewer/widgets.py @@ -55,6 +55,13 @@ class InstanceItem: if instance_data_item["id"] == self._id: logs.extend(copy.deepcopy(instance_data_item["logs"])) + errored = False + for log in logs: + if log["type"] == "error": + errored = True + break + + self.errored = errored self.logs = logs @property From 7f5d623d4c163727b2a1ccfdbb31191e689bc940 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 31 Aug 2021 15:37:24 +0200 Subject: [PATCH 359/736] handle window flags --- openpype/tools/new_publisher/window.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 928724e217..c82c071a96 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -1,4 +1,4 @@ -from Qt import QtWidgets +from Qt import QtWidgets, QtCore from openpype import style @@ -13,13 +13,29 @@ from .widgets import ( ) -class PublisherWindow(QtWidgets.QWidget): +class PublisherWindow(QtWidgets.QDialog): default_width = 1000 default_height = 600 def __init__(self, parent=None): super(PublisherWindow, self).__init__(parent) + self.setWindowTitle("OpenPype publisher") + + if parent is None: + on_top_flag = QtCore.Qt.WindowStaysOnTopHint + else: + on_top_flag = QtCore.Qt.Dialog + + self.setWindowFlags( + self.windowFlags() + | QtCore.Qt.WindowTitleHint + | QtCore.Qt.WindowMaximizeButtonHint + | QtCore.Qt.WindowMinimizeButtonHint + | QtCore.Qt.WindowCloseButtonHint + | on_top_flag + ) + self._first_show = True self._refreshing_instances = False From ff3fb34cde0802d5eef07e1e9ccbc446d28e6739 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 31 Aug 2021 16:25:53 +0200 Subject: [PATCH 360/736] get info about all plugins from report --- openpype/tools/new_publisher/control.py | 41 ++++++++++++++++++++----- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index f8ef70225f..ffe14207f3 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -16,7 +16,7 @@ from openpype.pipeline.create import CreateContext from Qt import QtCore - +# Define constant for plugin orders offset PLUGIN_ORDER_OFFSET = 0.5 @@ -114,6 +114,7 @@ class PublishReport: def __init__(self, controller): self.controller = controller self._plugin_data = [] + self._stored_plugins = [] self._current_plugin_data = [] self._all_instances_by_id = {} self._current_context = None @@ -128,12 +129,18 @@ class PublishReport: for instance in context: self._all_instances_by_id[instance.id] = instance + if self._current_plugin_data: + self._current_plugin_data["passed"] = True + + self._stored_plugins.append(plugin) + self._current_plugin_data = { "name": plugin.__name__, "label": getattr(plugin, "label"), "order": plugin.order, "instances_data": [], - "skipped": False + "skipped": False, + "passed": False } self._plugin_data.append(self._current_plugin_data) @@ -151,15 +158,31 @@ class PublishReport: "logs": self._extract_log_items(result) }) - def get_report(self): + def get_report(self, publish_plugins=None): instances_details = {} for instance in self._all_instances_by_id.values(): instances_details[instance.id] = self._extract_instance_data( instance, instance in self._current_context ) + plugins_data = copy.deepcopy(self._plugin_data) + if plugins_data and not plugins_data[-1]["passed"]: + plugins_data[-1]["passed"] = True + + if publish_plugins: + for plugin in publish_plugins: + if plugin not in self._stored_plugins: + plugins_data.append({ + "name": plugin.__name__, + "label": getattr(plugin, "label"), + "order": plugin.order, + "instances_data": [], + "skipped": False, + "passed": False + }) + return { - "plugins_data": copy.deepcopy(self._plugin_data), + "plugins_data": plugins_data, "instances": instances_details, "context": self._extract_context_data(self._current_context) } @@ -193,10 +216,15 @@ class PublishReport: traceback.format_exception(*record_exc_info) ) + try: + msg = record.getMessage() + except Exception: + msg = str(record.msg) + output.append({ "instance_id": instance_id, "type": "record", - "msg": record.getMessage(), + "msg": msg, "name": record.name, "lineno": record.lineno, "levelno": record.levelno, @@ -533,7 +561,7 @@ class PublisherController: return self._publish_error def get_publish_report(self): - return self._publish_report.get_report() + return self._publish_report.get_report(self.publish_plugins) def get_validation_errors(self): return self._publish_validation_errors @@ -714,7 +742,6 @@ class PublisherController: }) def _process_and_continue(self, plugin, instance): - # TODO execute plugin result = pyblish.plugin.process( plugin, self._publish_context, instance ) From c012f410559d48e3b4c35b298d9ebd571f155a76 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 31 Aug 2021 16:35:54 +0200 Subject: [PATCH 361/736] fixed filtering of removed instances --- .../tools/new_publisher/publish_log_viewer/model.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/tools/new_publisher/publish_log_viewer/model.py b/openpype/tools/new_publisher/publish_log_viewer/model.py index 3e115ff61e..42a2ef5744 100644 --- a/openpype/tools/new_publisher/publish_log_viewer/model.py +++ b/openpype/tools/new_publisher/publish_log_viewer/model.py @@ -39,12 +39,15 @@ class InstancesModel(QtGui.QStandardItemModel): for family in all_families: items = [] instance_items = report_item.instance_items_by_family[family] + all_removed = True for instance_item in instance_items: item = QtGui.QStandardItem(instance_item.label) item.setData(instance_item.label, ITEM_LABEL_ROLE) item.setData(instance_item.errored, ITEM_ERRORED_ROLE) item.setData(instance_item.id, ITEM_ID_ROLE) item.setData(instance_item.removed, INSTANCE_REMOVED_ROLE) + if all_removed and not instance_item.removed: + all_removed = False item.setData(False, ITEM_IS_GROUP_ROLE) items.append(item) self._items_by_id[instance_item.id] = item @@ -59,6 +62,7 @@ class InstancesModel(QtGui.QStandardItemModel): family_item.setFlags(QtCore.Qt.ItemIsEnabled) family_id = uuid.uuid4() family_item.setData(family_id, ITEM_ID_ROLE) + family_item.setData(all_removed, INSTANCE_REMOVED_ROLE) family_item.setData(True, ITEM_IS_GROUP_ROLE) family_item.appendRows(items) family_items.append(family_item) @@ -86,12 +90,8 @@ class InstanceProxyModel(QtCore.QSortFilterProxyModel): self.invalidateFilter() def filterAcceptsRow(self, row, parent): - model = self.sourceModel() - source_index = model.index(row, 0, parent) - if source_index.data(ITEM_IS_GROUP_ROLE): - return model.rowCount(source_index) > 0 - - if self._ignore_removed and source_index.data(PLUGIN_SKIPPED_ROLE): + source_index = self.sourceModel().index(row, 0, parent) + if self._ignore_removed and source_index.data(INSTANCE_REMOVED_ROLE): return False return True From a5425c2f891a68608976faad548dbb5c86251810 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 31 Aug 2021 16:53:05 +0200 Subject: [PATCH 362/736] fixed parenting --- openpype/tools/new_publisher/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/app.py b/openpype/tools/new_publisher/app.py index 2176d6facb..bc1bd7cfbd 100644 --- a/openpype/tools/new_publisher/app.py +++ b/openpype/tools/new_publisher/app.py @@ -8,7 +8,7 @@ class _WindowCache: def show(parent=None): window = _WindowCache.window if window is None: - window = PublisherWindow() + window = PublisherWindow(parent) _WindowCache.window = window window.show() From 5afa2bfa814771f2af151687e7565058803690e2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 31 Aug 2021 17:24:19 +0200 Subject: [PATCH 363/736] added more colors to items --- .../publish_log_viewer/constants.py | 3 +- .../publish_log_viewer/delegates.py | 58 ++++++++++++++++--- .../new_publisher/publish_log_viewer/model.py | 2 + 3 files changed, 54 insertions(+), 9 deletions(-) diff --git a/openpype/tools/new_publisher/publish_log_viewer/constants.py b/openpype/tools/new_publisher/publish_log_viewer/constants.py index f1e13534d0..8fbb9342ca 100644 --- a/openpype/tools/new_publisher/publish_log_viewer/constants.py +++ b/openpype/tools/new_publisher/publish_log_viewer/constants.py @@ -6,7 +6,8 @@ ITEM_IS_GROUP_ROLE = QtCore.Qt.UserRole + 2 ITEM_LABEL_ROLE = QtCore.Qt.UserRole + 3 ITEM_ERRORED_ROLE = QtCore.Qt.UserRole + 4 PLUGIN_SKIPPED_ROLE = QtCore.Qt.UserRole + 5 -INSTANCE_REMOVED_ROLE = QtCore.Qt.UserRole + 6 +PLUGIN_PASSED_ROLE = QtCore.Qt.UserRole + 6 +INSTANCE_REMOVED_ROLE = QtCore.Qt.UserRole + 7 __all__ = ( diff --git a/openpype/tools/new_publisher/publish_log_viewer/delegates.py b/openpype/tools/new_publisher/publish_log_viewer/delegates.py index f6077a496d..e8ee1fd418 100644 --- a/openpype/tools/new_publisher/publish_log_viewer/delegates.py +++ b/openpype/tools/new_publisher/publish_log_viewer/delegates.py @@ -1,12 +1,11 @@ -import platform +import os import collections from Qt import QtWidgets, QtCore, QtGui from .constants import ( - ITEM_ID_ROLE, ITEM_IS_GROUP_ROLE, ITEM_ERRORED_ROLE, - ITEM_LABEL_ROLE, PLUGIN_SKIPPED_ROLE, + PLUGIN_PASSED_ROLE, INSTANCE_REMOVED_ROLE ) @@ -25,6 +24,13 @@ colors = { "group-selected-hover": QtGui.QColor("#555555") } +def get_image_path(filename): + return os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "images", + filename + ) + class GroupItemDelegate(QtWidgets.QStyledItemDelegate): """Generic delegate for instance header""" @@ -130,32 +136,64 @@ class GroupItemDelegate(QtWidgets.QStyledItemDelegate): if icons_by_size and size in icons_by_size: return icons_by_size[size] + offset = int(size * cls._item_pix_offset_ratio) + offset_size = size - (2 * offset) pix = QtGui.QPixmap(size, size) pix.fill(QtCore.Qt.transparent) painter = QtGui.QPainter(pix) painter.setRenderHint(QtGui.QPainter.Antialiasing) + draw_ellipse = True if name == "error": color = QtGui.QColor(colors["error"]) painter.setPen(QtCore.Qt.NoPen) painter.setBrush(color) + elif name == "skipped": color = QtGui.QColor(QtCore.Qt.white) pen = QtGui.QPen(color) pen.setWidth(int(size * cls._item_border_size)) painter.setPen(pen) painter.setBrush(QtCore.Qt.transparent) + + elif name == "passed": + color = QtGui.QColor(colors["ok"]) + painter.setPen(QtCore.Qt.NoPen) + painter.setBrush(color) + + elif name == "removed": + draw_ellipse = False + image_path = get_image_path("deleted_instance.png") + + source_pix = QtGui.QPixmap(image_path) + width = offset_size + height = offset_size + if source_pix.width() > source_pix.height(): + height = source_pix.width() / width * source_pix.height() + else: + width = source_pix.height() / height * source_pix.width() + + scaled_pix = source_pix.scaled( + width, + height, + QtCore.Qt.KeepAspectRatio, + QtCore.Qt.SmoothTransformation + ) + start_point = QtCore.QPointF( + offset + ((offset_size / 2) - (scaled_pix.width() / 2)), + offset + ((offset_size / 2) - (scaled_pix.height() / 2)) + ) + painter.drawPixmap(start_point, scaled_pix) + else: color = QtGui.QColor(QtCore.Qt.white) painter.setPen(QtCore.Qt.NoPen) painter.setBrush(color) - offset = int(size * cls._item_pix_offset_ratio) - painter.drawEllipse( - offset, offset, - size - (2 * offset), size - (2 * offset) - ) + if draw_ellipse: + painter.drawEllipse(offset, offset, offset_size, offset_size) + painter.end() cls._item_icons_by_name_and_size[name][size] = pix @@ -202,6 +240,10 @@ class GroupItemDelegate(QtWidgets.QStyledItemDelegate): expander_icon = self._get_icon("error", icon_size) elif index.data(PLUGIN_SKIPPED_ROLE): expander_icon = self._get_icon("skipped", icon_size) + elif index.data(PLUGIN_PASSED_ROLE): + expander_icon = self._get_icon("passed", icon_size) + elif index.data(INSTANCE_REMOVED_ROLE): + expander_icon = self._get_icon("removed", icon_size) else: expander_icon = self._get_icon("", icon_size) diff --git a/openpype/tools/new_publisher/publish_log_viewer/model.py b/openpype/tools/new_publisher/publish_log_viewer/model.py index 42a2ef5744..460d3e12d1 100644 --- a/openpype/tools/new_publisher/publish_log_viewer/model.py +++ b/openpype/tools/new_publisher/publish_log_viewer/model.py @@ -9,6 +9,7 @@ from .constants import ( ITEM_LABEL_ROLE, ITEM_ERRORED_ROLE, PLUGIN_SKIPPED_ROLE, + PLUGIN_PASSED_ROLE, INSTANCE_REMOVED_ROLE ) @@ -160,6 +161,7 @@ class PluginsModel(QtGui.QStandardItemModel): item.setData(plugin_item.label, ITEM_LABEL_ROLE) item.setData(plugin_item.id, ITEM_ID_ROLE) item.setData(plugin_item.skipped, PLUGIN_SKIPPED_ROLE) + item.setData(plugin_item.passed, PLUGIN_PASSED_ROLE) item.setData(plugin_item.errored, ITEM_ERRORED_ROLE) items.append(item) self._items_by_id[plugin_item.id] = item From cdee1c452f0a3f1519ecfd9790b2fea70bf72bd7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 31 Aug 2021 17:24:32 +0200 Subject: [PATCH 364/736] groups are expanded/collapsed on single click --- .../publish_log_viewer/widgets.py | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/publish_log_viewer/widgets.py b/openpype/tools/new_publisher/publish_log_viewer/widgets.py index ce92d63f26..cbe768845f 100644 --- a/openpype/tools/new_publisher/publish_log_viewer/widgets.py +++ b/openpype/tools/new_publisher/publish_log_viewer/widgets.py @@ -6,7 +6,8 @@ from Qt import QtWidgets, QtCore, QtGui import pyblish.api from .constants import ( - ITEM_ID_ROLE + ITEM_ID_ROLE, + ITEM_IS_GROUP_ROLE ) from .delegates import GroupItemDelegate from .model import ( @@ -25,6 +26,7 @@ class PluginItem: self.label = plugin_data["label"] self.order = plugin_data["order"] self.skipped = plugin_data["skipped"] + self.passed = plugin_data["passed"] logs = [] errored = False @@ -166,6 +168,7 @@ class PublishLogViewerWidget(QtWidgets.QWidget): instances_view.setIndentation(0) instances_view.setHeaderHidden(True) instances_view.setEditTriggers(QtWidgets.QTreeView.NoEditTriggers) + instances_view.setExpandsOnDoubleClick(False) instances_delegate = GroupItemDelegate(instances_view) instances_view.setItemDelegate(instances_delegate) @@ -181,6 +184,7 @@ class PublishLogViewerWidget(QtWidgets.QWidget): plugins_view.setIndentation(0) plugins_view.setHeaderHidden(True) plugins_view.setEditTriggers(QtWidgets.QTreeView.NoEditTriggers) + plugins_view.setExpandsOnDoubleClick(False) plugins_delegate = GroupItemDelegate(plugins_view) plugins_view.setItemDelegate(plugins_delegate) @@ -199,6 +203,8 @@ class PublishLogViewerWidget(QtWidgets.QWidget): instances_view.selectionModel().selectionChanged.connect( self._on_instance_change ) + instances_view.clicked.connect(self._on_instance_view_clicked) + plugins_view.clicked.connect(self._on_plugin_view_clicked) plugins_view.selectionModel().selectionChanged.connect( self._on_plugin_change ) @@ -227,6 +233,24 @@ class PublishLogViewerWidget(QtWidgets.QWidget): self._plugins_model = plugins_model self._plugins_proxy = plugins_proxy + def _on_instance_view_clicked(self, index): + if not index.isValid() or not index.data(ITEM_IS_GROUP_ROLE): + return + + if self._instances_view.isExpanded(index): + self._instances_view.collapse(index) + else: + self._instances_view.expand(index) + + def _on_plugin_view_clicked(self, index): + if not index.isValid() or not index.data(ITEM_IS_GROUP_ROLE): + return + + if self._plugins_view.isExpanded(index): + self._plugins_view.collapse(index) + else: + self._plugins_view.expand(index) + def set_report(self, report_data): self._ignore_selection_changes = True From c3d1edb5e2e8fc508f758da108248d5fe57caee3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 31 Aug 2021 17:58:23 +0200 Subject: [PATCH 365/736] added deleted image --- .../images/deleted_instance.png | Bin 0 -> 7384 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 openpype/tools/new_publisher/publish_log_viewer/images/deleted_instance.png diff --git a/openpype/tools/new_publisher/publish_log_viewer/images/deleted_instance.png b/openpype/tools/new_publisher/publish_log_viewer/images/deleted_instance.png new file mode 100644 index 0000000000000000000000000000000000000000..a065408f4219e4365418e3ce299e4f5b3574d7d1 GIT binary patch literal 7384 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 literal 0 HcmV?d00001 From 06e042b306c4e1b62c75fae44ac4e5222305bea6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 31 Aug 2021 17:58:39 +0200 Subject: [PATCH 366/736] set column stretch --- openpype/tools/new_publisher/publish_log_viewer/widgets.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/tools/new_publisher/publish_log_viewer/widgets.py b/openpype/tools/new_publisher/publish_log_viewer/widgets.py index cbe768845f..601c340b5f 100644 --- a/openpype/tools/new_publisher/publish_log_viewer/widgets.py +++ b/openpype/tools/new_publisher/publish_log_viewer/widgets.py @@ -200,6 +200,8 @@ class PublishLogViewerWidget(QtWidgets.QWidget): layout.addWidget(plugins_view, 1, 1) layout.addWidget(details_widget, 1, 2) + layout.setColumnStretch(2, 1) + instances_view.selectionModel().selectionChanged.connect( self._on_instance_change ) From 42568bffdbdb7b3de0f4713483a86bb698e2cf21 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 31 Aug 2021 18:03:44 +0200 Subject: [PATCH 367/736] fix state changes --- openpype/tools/new_publisher/widgets/publish_widget.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/widgets/publish_widget.py b/openpype/tools/new_publisher/widgets/publish_widget.py index 8e85521fb1..357fc56657 100644 --- a/openpype/tools/new_publisher/widgets/publish_widget.py +++ b/openpype/tools/new_publisher/widgets/publish_widget.py @@ -288,7 +288,11 @@ class PublishFrame(QtWidgets.QFrame): self.message_label.setVisible(visible) def _set_success_property(self, state=None): - self.info_frame.setProperty("state", str(state or "")) + if state is None: + state = "" + else: + state = str(state) + self.info_frame.setProperty("state", state) self.info_frame.style().polish(self.info_frame) def _on_copy_log(self): From d7a730d946f5dd7751eb575831e8871b897f7d90 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 31 Aug 2021 18:11:01 +0200 Subject: [PATCH 368/736] change progressbar color on error --- openpype/style/style.css | 4 ++++ openpype/tools/new_publisher/widgets/publish_widget.py | 8 ++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/openpype/style/style.css b/openpype/style/style.css index 4305fd68cc..88ce56f822 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -744,6 +744,10 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { border-left: 1px solid {color:border}; } +#PublishProgressBar[state="0"]::chunk { + background: {color:bg-buttons}; +} + #ArrowBtn, #ArrowBtn:disabled, #ArrowBtn:hover { background: transparent; } diff --git a/openpype/tools/new_publisher/widgets/publish_widget.py b/openpype/tools/new_publisher/widgets/publish_widget.py index 357fc56657..9c7a993b71 100644 --- a/openpype/tools/new_publisher/widgets/publish_widget.py +++ b/openpype/tools/new_publisher/widgets/publish_widget.py @@ -49,6 +49,7 @@ class PublishFrame(QtWidgets.QFrame): instance_plugin_layout.addWidget(plugin_label, 1) progress_widget = QtWidgets.QProgressBar(content_widget) + progress_widget.setObjectName("PublishProgressBar") copy_log_btn = QtWidgets.QPushButton("Copy log", content_widget) copy_log_btn.setVisible(False) @@ -292,8 +293,11 @@ class PublishFrame(QtWidgets.QFrame): state = "" else: state = str(state) - self.info_frame.setProperty("state", state) - self.info_frame.style().polish(self.info_frame) + + for widget in (self.progress_widget, self.info_frame): + if widget.property("state") != state: + widget.setProperty("state", state) + widget.style().polish(widget) def _on_copy_log(self): logs = self.controller.get_publish_report() From c35cce097176b030bfea67dace282b2ae7d3311b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 31 Aug 2021 18:11:19 +0200 Subject: [PATCH 369/736] changed details views colors --- openpype/style/style.css | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openpype/style/style.css b/openpype/style/style.css index 88ce56f822..82ae9b906c 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -748,6 +748,13 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background: {color:bg-buttons}; } +#PublishDetailViews { + background: transparent; +} +#PublishDetailViews::item { + margin: 1px 0px 1px 0px; +} + #ArrowBtn, #ArrowBtn:disabled, #ArrowBtn:hover { background: transparent; } From 4b207d74a331756a237f459eb4f5dd49b6b4bb61 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 31 Aug 2021 18:31:52 +0200 Subject: [PATCH 370/736] handle error types --- openpype/tools/new_publisher/publish_log_viewer/widgets.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/tools/new_publisher/publish_log_viewer/widgets.py b/openpype/tools/new_publisher/publish_log_viewer/widgets.py index 601c340b5f..113c5e5145 100644 --- a/openpype/tools/new_publisher/publish_log_viewer/widgets.py +++ b/openpype/tools/new_publisher/publish_log_viewer/widgets.py @@ -138,6 +138,9 @@ class DetailsWidget(QtWidgets.QWidget): if exc_info: lines.append(exc_info) + elif log["type"] == "error": + lines.append(log["traceback"]) + else: print(log["type"]) From d13c797fb8a15d5b3d20d0a1cc1d9ad4b84faf45 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 31 Aug 2021 18:34:25 +0200 Subject: [PATCH 371/736] renamed widgets and window --- .../new_publisher/publish_log_viewer/__init__.py | 15 +++++++++++---- .../new_publisher/publish_log_viewer/widgets.py | 4 ++-- .../new_publisher/publish_log_viewer/window.py | 8 ++++---- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/openpype/tools/new_publisher/publish_log_viewer/__init__.py b/openpype/tools/new_publisher/publish_log_viewer/__init__.py index eef88102cc..3cfaaa5a05 100644 --- a/openpype/tools/new_publisher/publish_log_viewer/__init__.py +++ b/openpype/tools/new_publisher/publish_log_viewer/__init__.py @@ -1,7 +1,14 @@ -from .window import ( - PublishLogViewerWindow +from .widgets import ( + PublishReportViewerWidget ) -__all__ = ( - "PublishLogViewerWindow", +from .window import ( + PublishReportViewerWindow +) + + +__all__ = ( + "PublishReportViewerWidget", + + "PublishReportViewerWindow", ) diff --git a/openpype/tools/new_publisher/publish_log_viewer/widgets.py b/openpype/tools/new_publisher/publish_log_viewer/widgets.py index 113c5e5145..c7d60ed35c 100644 --- a/openpype/tools/new_publisher/publish_log_viewer/widgets.py +++ b/openpype/tools/new_publisher/publish_log_viewer/widgets.py @@ -148,9 +148,9 @@ class DetailsWidget(QtWidgets.QWidget): self._output_widget.setPlainText(text) -class PublishLogViewerWidget(QtWidgets.QWidget): +class PublishReportViewerWidget(QtWidgets.QWidget): def __init__(self, parent=None): - super(PublishLogViewerWidget, self).__init__(parent) + super(PublishReportViewerWidget, self).__init__(parent) instances_model = InstancesModel() instances_proxy = InstanceProxyModel() diff --git a/openpype/tools/new_publisher/publish_log_viewer/window.py b/openpype/tools/new_publisher/publish_log_viewer/window.py index 1bfca3b86b..e3aac503b2 100644 --- a/openpype/tools/new_publisher/publish_log_viewer/window.py +++ b/openpype/tools/new_publisher/publish_log_viewer/window.py @@ -2,12 +2,12 @@ from Qt import QtWidgets from openpype import style if __package__: - from .widgets import PublishLogViewerWidget + from .widgets import PublishReportViewerWidget else: - from widgets import PublishLogViewerWidget + from widgets import PublishReportViewerWidget -class PublishLogViewerWindow(QtWidgets.QWidget): +class PublishReportViewerWindow(QtWidgets.QWidget): # TODO add buttons to be able load report file or paste content of report default_width = 1200 default_height = 600 @@ -15,7 +15,7 @@ class PublishLogViewerWindow(QtWidgets.QWidget): def __init__(self, parent=None): super(PublishLogViewerWindow, self).__init__(parent) - main_widget = PublishLogViewerWidget(self) + main_widget = PublishReportViewerWidget(self) layout = QtWidgets.QHBoxLayout(self) layout.addWidget(main_widget) From ed2ebb16ccb605be3582e272268641ef9f96996d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 1 Sep 2021 10:48:48 +0200 Subject: [PATCH 372/736] don't use image for deleted instances --- .../publish_log_viewer/delegates.py | 38 ++++++------------ .../images/deleted_instance.png | Bin 7384 -> 0 bytes 2 files changed, 12 insertions(+), 26 deletions(-) delete mode 100644 openpype/tools/new_publisher/publish_log_viewer/images/deleted_instance.png diff --git a/openpype/tools/new_publisher/publish_log_viewer/delegates.py b/openpype/tools/new_publisher/publish_log_viewer/delegates.py index e8ee1fd418..5a1c0d6f7e 100644 --- a/openpype/tools/new_publisher/publish_log_viewer/delegates.py +++ b/openpype/tools/new_publisher/publish_log_viewer/delegates.py @@ -24,13 +24,6 @@ colors = { "group-selected-hover": QtGui.QColor("#555555") } -def get_image_path(filename): - return os.path.join( - os.path.dirname(os.path.abspath(__file__)), - "images", - filename - ) - class GroupItemDelegate(QtWidgets.QStyledItemDelegate): """Generic delegate for instance header""" @@ -164,27 +157,20 @@ class GroupItemDelegate(QtWidgets.QStyledItemDelegate): elif name == "removed": draw_ellipse = False - image_path = get_image_path("deleted_instance.png") - source_pix = QtGui.QPixmap(image_path) - width = offset_size - height = offset_size - if source_pix.width() > source_pix.height(): - height = source_pix.width() / width * source_pix.height() - else: - width = source_pix.height() / height * source_pix.width() + offset = offset * 1.5 + p1 = QtCore.QPoint(offset, offset) + p2 = QtCore.QPoint(size - offset, size - offset) + p3 = QtCore.QPoint(offset, size - offset) + p4 = QtCore.QPoint(size - offset, offset) - scaled_pix = source_pix.scaled( - width, - height, - QtCore.Qt.KeepAspectRatio, - QtCore.Qt.SmoothTransformation - ) - start_point = QtCore.QPointF( - offset + ((offset_size / 2) - (scaled_pix.width() / 2)), - offset + ((offset_size / 2) - (scaled_pix.height() / 2)) - ) - painter.drawPixmap(start_point, scaled_pix) + pen = QtGui.QPen(QtCore.Qt.white) + pen.setWidth(offset_size / 4) + pen.setCapStyle(QtCore.Qt.RoundCap) + painter.setPen(pen) + painter.setBrush(QtCore.Qt.transparent) + painter.drawLine(p1, p2) + painter.drawLine(p3, p4) else: color = QtGui.QColor(QtCore.Qt.white) diff --git a/openpype/tools/new_publisher/publish_log_viewer/images/deleted_instance.png b/openpype/tools/new_publisher/publish_log_viewer/images/deleted_instance.png deleted file mode 100644 index a065408f4219e4365418e3ce299e4f5b3574d7d1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7384 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 From 41fcd763c62c741439f36b2c9558596fcea04445 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 1 Sep 2021 10:50:17 +0200 Subject: [PATCH 373/736] use report widget in publisher --- .../publish_log_viewer/window.py | 2 +- .../new_publisher/widgets/publish_widget.py | 28 ++++++++++++++++--- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/openpype/tools/new_publisher/publish_log_viewer/window.py b/openpype/tools/new_publisher/publish_log_viewer/window.py index e3aac503b2..7a0fef7d91 100644 --- a/openpype/tools/new_publisher/publish_log_viewer/window.py +++ b/openpype/tools/new_publisher/publish_log_viewer/window.py @@ -13,7 +13,7 @@ class PublishReportViewerWindow(QtWidgets.QWidget): default_height = 600 def __init__(self, parent=None): - super(PublishLogViewerWindow, self).__init__(parent) + super(PublishReportViewerWindow, self).__init__(parent) main_widget = PublishReportViewerWidget(self) diff --git a/openpype/tools/new_publisher/widgets/publish_widget.py b/openpype/tools/new_publisher/widgets/publish_widget.py index 9c7a993b71..914b739d62 100644 --- a/openpype/tools/new_publisher/widgets/publish_widget.py +++ b/openpype/tools/new_publisher/widgets/publish_widget.py @@ -6,6 +6,7 @@ from openpype.pipeline import KnownPublishError from .icons import get_icon from .validations_widget import ValidationsWidget +from ..publish_log_viewer import PublishReportViewerWidget class PublishFrame(QtWidgets.QFrame): @@ -93,9 +94,21 @@ class PublishFrame(QtWidgets.QFrame): content_layout.addStretch(1) content_layout.addLayout(footer_layout) - main_layout = QtWidgets.QVBoxLayout(self) - main_layout.addWidget(validation_errors_widget, 1) - main_layout.addWidget(info_frame, 0) + publish_widget = QtWidgets.QWidget(self) + publish_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + publish_layout = QtWidgets.QVBoxLayout(publish_widget) + publish_layout.addWidget(validation_errors_widget, 1) + publish_layout.addWidget(info_frame, 0) + + details_widget = PublishReportViewerWidget(self) + + main_layout = QtWidgets.QStackedLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setStackingMode(main_layout.StackOne) + main_layout.addWidget(publish_widget) + main_layout.addWidget(details_widget) + + main_layout.setCurrentWidget(publish_widget) copy_log_btn.clicked.connect(self._on_copy_log) show_details_btn.clicked.connect(self._on_show_details) @@ -117,6 +130,8 @@ class PublishFrame(QtWidgets.QFrame): self.validation_errors_widget = validation_errors_widget + self._main_layout = main_layout + self.info_frame = info_frame self.main_label = main_label self.message_label = message_label @@ -133,6 +148,8 @@ class PublishFrame(QtWidgets.QFrame): self.validate_btn = validate_btn self.publish_btn = publish_btn + self.details_widget = details_widget + def _on_publish_reset(self): self._set_success_property() self._change_bg_property() @@ -310,7 +327,10 @@ class PublishFrame(QtWidgets.QFrame): ) def _on_show_details(self): - pass + self._change_bg_property(2) + self._main_layout.setCurrentWidget(self.details_widget) + logs = self.controller.get_publish_report() + self.details_widget.set_report(logs) def _on_reset_clicked(self): self.controller.reset() From f20ad0fa7aa2071a86c38ea9dad34010d2f682c1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 1 Sep 2021 10:55:28 +0200 Subject: [PATCH 374/736] renamed publish_log_viewer to publish_report_viewer --- .../{publish_log_viewer => publish_report_viewer}/__init__.py | 0 .../{publish_log_viewer => publish_report_viewer}/constants.py | 0 .../{publish_log_viewer => publish_report_viewer}/delegates.py | 0 .../{publish_log_viewer => publish_report_viewer}/logs.json | 0 .../{publish_log_viewer => publish_report_viewer}/model.py | 0 .../{publish_log_viewer => publish_report_viewer}/widgets.py | 0 .../{publish_log_viewer => publish_report_viewer}/window.py | 0 openpype/tools/new_publisher/widgets/publish_widget.py | 2 +- 8 files changed, 1 insertion(+), 1 deletion(-) rename openpype/tools/new_publisher/{publish_log_viewer => publish_report_viewer}/__init__.py (100%) rename openpype/tools/new_publisher/{publish_log_viewer => publish_report_viewer}/constants.py (100%) rename openpype/tools/new_publisher/{publish_log_viewer => publish_report_viewer}/delegates.py (100%) rename openpype/tools/new_publisher/{publish_log_viewer => publish_report_viewer}/logs.json (100%) rename openpype/tools/new_publisher/{publish_log_viewer => publish_report_viewer}/model.py (100%) rename openpype/tools/new_publisher/{publish_log_viewer => publish_report_viewer}/widgets.py (100%) rename openpype/tools/new_publisher/{publish_log_viewer => publish_report_viewer}/window.py (100%) diff --git a/openpype/tools/new_publisher/publish_log_viewer/__init__.py b/openpype/tools/new_publisher/publish_report_viewer/__init__.py similarity index 100% rename from openpype/tools/new_publisher/publish_log_viewer/__init__.py rename to openpype/tools/new_publisher/publish_report_viewer/__init__.py diff --git a/openpype/tools/new_publisher/publish_log_viewer/constants.py b/openpype/tools/new_publisher/publish_report_viewer/constants.py similarity index 100% rename from openpype/tools/new_publisher/publish_log_viewer/constants.py rename to openpype/tools/new_publisher/publish_report_viewer/constants.py diff --git a/openpype/tools/new_publisher/publish_log_viewer/delegates.py b/openpype/tools/new_publisher/publish_report_viewer/delegates.py similarity index 100% rename from openpype/tools/new_publisher/publish_log_viewer/delegates.py rename to openpype/tools/new_publisher/publish_report_viewer/delegates.py diff --git a/openpype/tools/new_publisher/publish_log_viewer/logs.json b/openpype/tools/new_publisher/publish_report_viewer/logs.json similarity index 100% rename from openpype/tools/new_publisher/publish_log_viewer/logs.json rename to openpype/tools/new_publisher/publish_report_viewer/logs.json diff --git a/openpype/tools/new_publisher/publish_log_viewer/model.py b/openpype/tools/new_publisher/publish_report_viewer/model.py similarity index 100% rename from openpype/tools/new_publisher/publish_log_viewer/model.py rename to openpype/tools/new_publisher/publish_report_viewer/model.py diff --git a/openpype/tools/new_publisher/publish_log_viewer/widgets.py b/openpype/tools/new_publisher/publish_report_viewer/widgets.py similarity index 100% rename from openpype/tools/new_publisher/publish_log_viewer/widgets.py rename to openpype/tools/new_publisher/publish_report_viewer/widgets.py diff --git a/openpype/tools/new_publisher/publish_log_viewer/window.py b/openpype/tools/new_publisher/publish_report_viewer/window.py similarity index 100% rename from openpype/tools/new_publisher/publish_log_viewer/window.py rename to openpype/tools/new_publisher/publish_report_viewer/window.py diff --git a/openpype/tools/new_publisher/widgets/publish_widget.py b/openpype/tools/new_publisher/widgets/publish_widget.py index 914b739d62..2baeab649d 100644 --- a/openpype/tools/new_publisher/widgets/publish_widget.py +++ b/openpype/tools/new_publisher/widgets/publish_widget.py @@ -6,7 +6,7 @@ from openpype.pipeline import KnownPublishError from .icons import get_icon from .validations_widget import ValidationsWidget -from ..publish_log_viewer import PublishReportViewerWidget +from ..publish_report_viewer import PublishReportViewerWidget class PublishFrame(QtWidgets.QFrame): From 59ffc2b2b9d30591b5624118542a03fc1180cff3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 1 Sep 2021 11:14:30 +0200 Subject: [PATCH 375/736] fixed bg for report widget --- openpype/style/style.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/style/style.css b/openpype/style/style.css index 82ae9b906c..581905b347 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -685,6 +685,9 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { #PublishFrame[state="1"] { background: rgb(22, 25, 29); } +#PublishFrame[state="2"] { + background: {color:bg}; +} #PublishInfoFrame { background: {color:bg}; From b10534a018a3a9f21a4b464031313f151bb414e2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 1 Sep 2021 11:14:49 +0200 Subject: [PATCH 376/736] added close button to details widget --- .../new_publisher/widgets/publish_widget.py | 53 +++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/publish_widget.py b/openpype/tools/new_publisher/widgets/publish_widget.py index 2baeab649d..2b41aaf346 100644 --- a/openpype/tools/new_publisher/widgets/publish_widget.py +++ b/openpype/tools/new_publisher/widgets/publish_widget.py @@ -1,6 +1,6 @@ import json -from Qt import QtWidgets, QtCore +from Qt import QtWidgets, QtCore, QtGui from openpype.pipeline import KnownPublishError @@ -100,7 +100,16 @@ class PublishFrame(QtWidgets.QFrame): publish_layout.addWidget(validation_errors_widget, 1) publish_layout.addWidget(info_frame, 0) - details_widget = PublishReportViewerWidget(self) + details_widget = QtWidgets.QWidget(self) + report_view = PublishReportViewerWidget(details_widget) + close_report_btn = QtWidgets.QPushButton(details_widget) + close_report_icon = self._get_report_close_icon() + close_report_btn.setIcon(close_report_icon) + + details_layout = QtWidgets.QVBoxLayout(details_widget) + details_layout.setContentsMargins(0, 0, 0, 0) + details_layout.addWidget(report_view) + details_layout.addWidget(close_report_btn) main_layout = QtWidgets.QStackedLayout(self) main_layout.setContentsMargins(0, 0, 0, 0) @@ -118,6 +127,8 @@ class PublishFrame(QtWidgets.QFrame): validate_btn.clicked.connect(self._on_validate_clicked) publish_btn.clicked.connect(self._on_publish_clicked) + close_report_btn.clicked.connect(self._on_close_report_clicked) + controller.add_publish_reset_callback(self._on_publish_reset) controller.add_publish_started_callback(self._on_publish_start) controller.add_publish_validated_callback(self._on_publish_validated) @@ -132,6 +143,8 @@ class PublishFrame(QtWidgets.QFrame): self._main_layout = main_layout + self.publish_widget = publish_widget + self.info_frame = info_frame self.main_label = main_label self.message_label = message_label @@ -148,7 +161,34 @@ class PublishFrame(QtWidgets.QFrame): self.validate_btn = validate_btn self.publish_btn = publish_btn + self.close_report_btn = close_report_btn + self.details_widget = details_widget + self.report_view = report_view + + def _get_report_close_icon(self): + size = 100 + pix = QtGui.QPixmap(size, size) + pix.fill(QtCore.Qt.transparent) + + half_stroke_size = size / 12 + stroke_size = 2 * half_stroke_size + size_part = size / 5 + + p1 = QtCore.QPoint(half_stroke_size, size_part) + p2 = QtCore.QPoint(size / 2, size_part * 4) + p3 = QtCore.QPoint(size - half_stroke_size, size_part) + painter = QtGui.QPainter(pix) + pen = QtGui.QPen(QtCore.Qt.white) + pen.setWidth(stroke_size) + pen.setCapStyle(QtCore.Qt.RoundCap) + painter.setPen(pen) + painter.setBrush(QtCore.Qt.transparent) + painter.drawLine(p1, p2) + painter.drawLine(p2, p3) + painter.end() + + return QtGui.QIcon(pix) def _on_publish_reset(self): self._set_success_property() @@ -330,7 +370,14 @@ class PublishFrame(QtWidgets.QFrame): self._change_bg_property(2) self._main_layout.setCurrentWidget(self.details_widget) logs = self.controller.get_publish_report() - self.details_widget.set_report(logs) + self.report_view.set_report(logs) + + def _on_close_report_clicked(self): + if self.controller.get_publish_crash_error(): + self._change_bg_property() + else: + self._change_bg_property(2) + self._main_layout.setCurrentWidget(self.publish_widget) def _on_reset_clicked(self): self.controller.reset() From ec4f3fb0251c55e43e1748442b7ed23ceacaa183 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 1 Sep 2021 11:31:43 +0200 Subject: [PATCH 377/736] store crashed plugin files to report --- openpype/tools/new_publisher/control.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index ffe14207f3..a7cb23a88c 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -113,13 +113,15 @@ class AssetDocsCache: class PublishReport: def __init__(self, controller): self.controller = controller + self._publish_discover_result = None self._plugin_data = [] self._stored_plugins = [] self._current_plugin_data = [] self._all_instances_by_id = {} self._current_context = None - def reset(self, context): + def reset(self, context, publish_discover_result=None): + self._publish_discover_result = publish_discover_result self._plugin_data = [] self._current_plugin_data = {} self._all_instances_by_id = {} @@ -181,10 +183,19 @@ class PublishReport: "passed": False }) + crashed_file_paths = {} + if self._publish_discover_result is not None: + items = self._publish_discover_result.crashed_file_paths.items() + for filepath, exc_info in items: + crashed_file_paths[filepath] = "".join( + traceback.format_exception(*exc_info) + ) + return { "plugins_data": plugins_data, "instances": instances_details, - "context": self._extract_context_data(self._current_context) + "context": self._extract_context_data(self._current_context), + "crashed_file_paths": crashed_file_paths } def _extract_context_data(self, context): @@ -575,7 +586,10 @@ class PublisherController: self._main_thread_iter = self._publish_iterator() self._publish_context = pyblish.api.Context() - self._publish_report.reset(self._publish_context) + self._publish_report.reset( + self._publish_context, + self.create_context.publish_discover_result + ) self._publish_validation_errors = [] self._publish_current_plugin_validation_errors = None self._publish_error = None From f4c3075503abdbed5f8ab0818f7662b48eb81c2f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 1 Sep 2021 11:34:11 +0200 Subject: [PATCH 378/736] hound fixes --- openpype/tools/new_publisher/control.py | 2 +- .../publish_report_viewer/delegates.py | 1 - .../publish_report_viewer/logs.json | 4533 ----------------- .../publish_report_viewer/widgets.py | 4 +- 4 files changed, 2 insertions(+), 4538 deletions(-) delete mode 100644 openpype/tools/new_publisher/publish_report_viewer/logs.json diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index a7cb23a88c..0ed40f3890 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -138,7 +138,7 @@ class PublishReport: self._current_plugin_data = { "name": plugin.__name__, - "label": getattr(plugin, "label"), + "label": getattr(plugin, "label", None), "order": plugin.order, "instances_data": [], "skipped": False, diff --git a/openpype/tools/new_publisher/publish_report_viewer/delegates.py b/openpype/tools/new_publisher/publish_report_viewer/delegates.py index 5a1c0d6f7e..9d72c143ae 100644 --- a/openpype/tools/new_publisher/publish_report_viewer/delegates.py +++ b/openpype/tools/new_publisher/publish_report_viewer/delegates.py @@ -1,4 +1,3 @@ -import os import collections from Qt import QtWidgets, QtCore, QtGui from .constants import ( diff --git a/openpype/tools/new_publisher/publish_report_viewer/logs.json b/openpype/tools/new_publisher/publish_report_viewer/logs.json deleted file mode 100644 index e734e4ee7b..0000000000 --- a/openpype/tools/new_publisher/publish_report_viewer/logs.json +++ /dev/null @@ -1,4533 +0,0 @@ -{ - "instances": { - "6d2ee587-9843-47a6-a6e2-10b6d2d8e495": { - "exists": true, - "families": [ - "model", - "review" - ], - "name": "modelMain", - "family": "model", - "label": "modelMain (Alpaca_01)" - }, - "8276e1a3-418c-46fa-8a95-8a9bf5c1ef30": { - "exists": true, - "families": [ - "workfile" - ], - "name": "kuba_each_case_Alpaca_01_animation_v007", - "family": "workfile", - "label": "workfileAnimation" - }, - "b2d211bd-217a-4cfb-b989-45b3dfc4edc9": { - "exists": false, - "families": [ - "review" - ], - "name": "reviewMain", - "family": "review", - "label": "reviewMain (Alpaca_01) [991-1020]" - } - }, - "plugins_data": [ - { - "instances_data": [ - { - "id": null, - "logs": [] - } - ], - "label": "Maya Workspace", - "skipped": false, - "order": -0.5, - "name": "CollectMayaWorkspace" - }, - { - "instances_data": [ - { - "id": null, - "logs": [] - } - ], - "label": "Collect Settings", - "skipped": false, - "order": -0.491, - "name": "CollectSettings" - }, - { - "instances_data": [ - { - "id": null, - "logs": [ - { - "instance_id": null, - "name": "pyblish.CollectFileDependencies", - "threadName": "MainThread", - "msecs": 523.9999294281006, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 28, - "msg": "[]", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - } - ] - } - ], - "label": "Collect File Dependencies", - "skipped": false, - "order": -0.49, - "name": "CollectFileDependencies" - }, - { - "instances_data": [ - { - "id": null, - "logs": [ - { - "instance_id": null, - "name": "pyblish.CollectAnatomyObject", - "threadName": "MainThread", - "msecs": 585.0000381469727, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 31, - "msg": "Anatomy object collected for project \"kuba_each_case\".", - "exc_info": null, - "type": "record", - "levelname": "INFO" - } - ] - } - ], - "label": "Collect Anatomy Object", - "skipped": false, - "order": -0.4, - "name": "CollectAnatomyObject" - }, - { - "instances_data": [ - { - "id": null, - "logs": [ - { - "instance_id": null, - "name": "pyblish.CollectAvalonEntities", - "threadName": "MainThread", - "msecs": 638.0000114440918, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 33, - "msg": "Collected Project \"{u'name': u'kuba_each_case', u'parent': None, u'type': u'project', u'data': {u'code': u'kuba_each_case', u'resolutionHeight': 1080, u'library_project': False, u'clipIn': 1001, u'frameStart': 1001, u'handleStart': 10, u'entityType': u'Project', u'frameEnd': 1100, u'tools_env': [], u'pixelAspect': 1.0, u'ftrackId': u'38c5fec4-0aed-11ea-a454-3e41ec9bc0d6', u'resolutionWidth': 1920, u'fps': 25.0, u'handleEnd': 10, u'clipOut': 1100}, u'_id': ObjectId('5eb950f9e3c2c3183455f266'), u'config': {u'templates': {u'hero': {u'path': u'{@folder}/{@file}', u'folder': u'{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/hero', u'file': u'{project[code]}_{asset}_{subset}_hero<_{output}><.{frame}>.{ext}'}, u'render': {u'path': u'{@folder}/{@file}', u'folder': u'{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/{@version}', u'file': u'{project[code]}_{asset}_{subset}_{@version}<_{output}><.{@frame}>.{ext}'}, u'work': {u'path': u'{@folder}/{@file}', u'folder': u'{root[work]}/{project[name]}/{hierarchy}/{asset}/work/{task}', u'file': u'{project[code]}_{asset}_{task}_{@version}<_{comment}>.{ext}'}, u'publish': {u'path': u'{@folder}/{@file}', u'folder': u'{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/{@version}', u'thumbnail': u'{thumbnail_root}/{project[name]}/{_id}_{thumbnail_type}.{ext}', u'file': u'{project[code]}_{asset}_{subset}_{@version}<_{output}><.{@frame}><_{udim}>.{ext}'}, u'delivery': {}, u'defaults': {u'frame_padding': 4, u'version_padding': 3, u'frame': u'{frame:0>{@frame_padding}}', u'version': u'v{version:0>{@version_padding}}'}, u'others': {}}, u'tasks': {u'Previz': {u'short_name': u''}, u'Lookdev': {u'short_name': u'look'}, u'Edit': {u'short_name': u'edit'}, u'Tracking': {u'short_name': u''}, u'Layout': {u'short_name': u'lay'}, u'Art': {u'short_name': u'art'}, u'Paint': {u'short_name': u'paint'}, u'Generic': {u'short_name': u'gener'}, u'Texture': {u'short_name': u'tex'}, u'Setdress': {u'short_name': u'dress'}, u'Animation': {u'short_name': u'anim'}, u'Lighting': {u'short_name': u'lgt'}, u'Compositing': {u'short_name': u'comp'}, u'Modeling': {u'short_name': u'mdl'}, u'Rigging': {u'short_name': u'rig'}, u'FX': {u'short_name': u'fx'}}, u'imageio': {u'hiero': {u'workfile': {u'eightBitLut': u'sRGB', u'viewerLut': u'sRGB', u'workingSpace': u'linear', u'ocioconfigpath': {u'windows': [], u'darwin': [], u'linux': []}, u'ocioConfigName': u'nuke-default', u'thumbnailLut': u'sRGB', u'floatLut': u'linear', u'sixteenBitLut': u'sRGB', u'logLut': u'Cineon'}, u'regexInputs': {u'inputs': [{u'regex': u'[^-a-zA-Z0-9](plateRef).*(?=mp4)', u'colorspace': u'sRGB'}]}}, u'nuke': {u'viewer': {u'viewerProcess': u'sRGB'}, u'workfile': {u'floatLut': u'linear', u'workingSpaceLUT': u'linear', u'customOCIOConfigPath': {u'windows': [], u'darwin': [], u'linux': []}, u'int16Lut': u'sRGB', u'logLut': u'Cineon', u'int8Lut': u'sRGB', u'colorManagement': u'Nuke', u'OCIO_config': u'nuke-default', u'monitorLut': u'sRGB'}, u'nodes': {u'customNodes': [], u'requiredNodes': [{u'nukeNodeClass': u'Write', u'knobs': [{u'name': u'file_type', u'value': u'exr'}, {u'name': u'datatype', u'value': u'16 bit half'}, {u'name': u'compression', u'value': u'Zip (1 scanline)'}, {u'name': u'autocrop', u'value': u'True'}, {u'name': u'tile_color', u'value': u'0xff0000ff'}, {u'name': u'channels', u'value': u'rgb'}, {u'name': u'colorspace', u'value': u'linear'}, {u'name': u'create_directories', u'value': u'True'}], u'plugins': [u'CreateWriteRender']}, {u'nukeNodeClass': u'Write', u'knobs': [{u'name': u'file_type', u'value': u'exr'}, {u'name': u'datatype', u'value': u'16 bit half'}, {u'name': u'compression', u'value': u'Zip (1 scanline)'}, {u'name': u'autocrop', u'value': u'False'}, {u'name': u'tile_color', u'value': u'0xadab1dff'}, {u'name': u'channels', u'value': u'rgb'}, {u'name': u'colorspace', u'value': u'linear'}, {u'name': u'create_directories', u'value': u'True'}], u'plugins': [u'CreateWritePrerender']}]}, u'regexInputs': {u'inputs': [{u'regex': u'[^-a-zA-Z0-9]beauty[^-a-zA-Z0-9]', u'colorspace': u'linear'}]}}}, u'apps': [{u'name': u'blender/2-91'}, {u'name': u'hiero/11-3'}, {u'name': u'houdini/18-5'}, {u'name': u'maya/2020'}, {u'name': u'nuke/11-3'}, {u'name': u'nukestudio/11-3'}], u'roots': {u'work': {u'windows': u'C:/projects', u'darwin': u'/Volumes/path', u'linux': u'/mnt/share/projects'}}, u'schema': u'openpype:config-2.0'}, u'schema': u'openpype:project-3.0'}\"", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "pyblish.CollectAvalonEntities", - "threadName": "MainThread", - "msecs": 647.0000743865967, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 44, - "msg": "Collected Asset \"{u'name': u'Alpaca_01', u'parent': ObjectId('5eb950f9e3c2c3183455f266'), u'data': {u'visualParent': ObjectId('5dd50967e3c2c32b004b4812'), u'resolutionHeight': 1080, u'clipIn': 1001, u'frameStart': 1001, u'tasks': {u'edit': {u'type': u'Edit'}, u'modeling': {u'type': u'Modeling'}, u'animation': {u'type': u'Animation'}}, u'handleStart': 10, u'entityType': u'AssetBuild', u'frameEnd': 1010, u'parents': [u'Assets'], u'tools_env': [u'mtoa/3-1'], u'pixelAspect': 1.0, u'ftrackId': u'fc02ea60-9786-11eb-8ae9-fa8d1b9899e1', u'resolutionWidth': 1920, u'fps': 25.0, u'handleEnd': 10, u'clipOut': 1100, u'avalon_mongo_id': u'5dd50967e3c2c32b004b4817'}, u'_id': ObjectId('5dd50967e3c2c32b004b4817'), u'type': u'asset', u'schema': u'openpype:asset-3.0'}\"", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - } - ] - } - ], - "label": "Collect Avalon Entities", - "skipped": false, - "order": -0.1, - "name": "CollectAvalonEntities" - }, - { - "instances_data": [ - { - "id": null, - "logs": [ - { - "instance_id": null, - "name": "pyblish.CollectMayaScene", - "threadName": "MainThread", - "msecs": 700.9999752044678, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 53, - "msg": "Collected instance: kuba_each_case_Alpaca_01_animation_v007.ma", - "exc_info": null, - "type": "record", - "levelname": "INFO" - }, - { - "instance_id": null, - "name": "pyblish.CollectMayaScene", - "threadName": "MainThread", - "msecs": 700.9999752044678, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 54, - "msg": "Scene path: C:/projects/kuba_each_case/Assets/Alpaca_01/work/animation/kuba_each_case_Alpaca_01_animation_v007.ma", - "exc_info": null, - "type": "record", - "levelname": "INFO" - }, - { - "instance_id": null, - "name": "pyblish.CollectMayaScene", - "threadName": "MainThread", - "msecs": 701.9999027252197, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 55, - "msg": "staging Dir: C:/projects/kuba_each_case/Assets/Alpaca_01/work/animation", - "exc_info": null, - "type": "record", - "levelname": "INFO" - }, - { - "instance_id": null, - "name": "pyblish.CollectMayaScene", - "threadName": "MainThread", - "msecs": 703.0000686645508, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 56, - "msg": "subset: workfileAnimation", - "exc_info": null, - "type": "record", - "levelname": "INFO" - } - ] - } - ], - "label": "Maya Workfile", - "skipped": false, - "order": -0.01, - "name": "CollectMayaScene" - }, - { - "instances_data": [ - { - "id": null, - "logs": [ - { - "instance_id": null, - "name": "pyblish.CollectSceneVersion", - "threadName": "MainThread", - "msecs": 765.0001049041748, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 41, - "msg": "", - "exc_info": null, - "type": "record", - "levelname": "INFO" - }, - { - "instance_id": null, - "name": "pyblish.CollectSceneVersion", - "threadName": "MainThread", - "msecs": 765.0001049041748, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 42, - "msg": "Scene Version: 7", - "exc_info": null, - "type": "record", - "levelname": "INFO" - } - ] - } - ], - "label": "Collect Version", - "skipped": false, - "order": 0, - "name": "CollectSceneVersion" - }, - { - "instances_data": [ - { - "id": null, - "logs": [ - { - "instance_id": null, - "name": "pyblish.CollectWorksceneFPS", - "threadName": "MainThread", - "msecs": 832.9999446868896, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 14, - "msg": "Workscene FPS: 25.0", - "exc_info": null, - "type": "record", - "levelname": "INFO" - } - ] - } - ], - "label": "Workscene FPS", - "skipped": false, - "order": 0, - "name": "CollectWorksceneFPS" - }, - { - "instances_data": [ - { - "id": null, - "logs": [] - } - ], - "label": "Current user", - "skipped": false, - "order": 0, - "name": "CollectCurrentUser" - }, - { - "instances_data": [ - { - "id": null, - "logs": [] - } - ], - "label": "Collect Host Name", - "skipped": false, - "order": 0, - "name": "CollectHostName" - }, - { - "instances_data": [ - { - "id": null, - "logs": [ - { - "instance_id": null, - "name": "ModulesManager", - "threadName": "MainThread", - "msecs": 22.00007438659668, - "filename": "base.py", - "levelno": 10, - "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\modules\\base.py", - "lineno": 360, - "msg": "*** Pype modules initialization.", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "ModulesManager", - "threadName": "MainThread", - "msecs": 36.00001335144043, - "filename": "base.py", - "levelno": 10, - "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\modules\\base.py", - "lineno": 407, - "msg": "[X] AvalonModule", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "ModulesManager", - "threadName": "MainThread", - "msecs": 36.99994087219238, - "filename": "base.py", - "levelno": 10, - "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\modules\\base.py", - "lineno": 407, - "msg": "[ ] ClockifyModule", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "ModulesManager", - "threadName": "MainThread", - "msecs": 39.00003433227539, - "filename": "base.py", - "levelno": 10, - "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\modules\\base.py", - "lineno": 407, - "msg": "[X] DeadlineModule", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "ModulesManager", - "threadName": "MainThread", - "msecs": 39.999961853027344, - "filename": "base.py", - "levelno": 10, - "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\modules\\base.py", - "lineno": 407, - "msg": "[ ] FtrackModule", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "ModulesManager", - "threadName": "MainThread", - "msecs": 40.9998893737793, - "filename": "base.py", - "levelno": 10, - "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\modules\\base.py", - "lineno": 407, - "msg": "[X] IdleManager", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "ModulesManager", - "threadName": "MainThread", - "msecs": 42.999982833862305, - "filename": "base.py", - "levelno": 10, - "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\modules\\base.py", - "lineno": 407, - "msg": "[X] LauncherAction", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "ModulesManager", - "threadName": "MainThread", - "msecs": 43.99991035461426, - "filename": "base.py", - "levelno": 10, - "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\modules\\base.py", - "lineno": 407, - "msg": "[X] LocalSettingsAction", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "ModulesManager", - "threadName": "MainThread", - "msecs": 45.00007629394531, - "filename": "base.py", - "levelno": 10, - "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\modules\\base.py", - "lineno": 407, - "msg": "[X] LogViewModule", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "ModulesManager", - "threadName": "MainThread", - "msecs": 46.000003814697266, - "filename": "base.py", - "levelno": 10, - "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\modules\\base.py", - "lineno": 407, - "msg": "[ ] MusterModule", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "ModulesManager", - "threadName": "MainThread", - "msecs": 46.99993133544922, - "filename": "base.py", - "levelno": 10, - "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\modules\\base.py", - "lineno": 407, - "msg": "[X] ProjectManagerAction", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "ModulesManager", - "threadName": "MainThread", - "msecs": 48.00009727478027, - "filename": "base.py", - "levelno": 10, - "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\modules\\base.py", - "lineno": 407, - "msg": "[X] PythonInterpreterAction", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "ModulesManager", - "threadName": "MainThread", - "msecs": 49.00002479553223, - "filename": "base.py", - "levelno": 10, - "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\modules\\base.py", - "lineno": 407, - "msg": "[X] SettingsAction", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "ModulesManager", - "threadName": "MainThread", - "msecs": 49.99995231628418, - "filename": "base.py", - "levelno": 10, - "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\modules\\base.py", - "lineno": 407, - "msg": "[ ] SlackIntegrationModule", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "ModulesManager", - "threadName": "MainThread", - "msecs": 51.000118255615234, - "filename": "base.py", - "levelno": 10, - "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\modules\\base.py", - "lineno": 407, - "msg": "[X] StandAlonePublishAction", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "ModulesManager", - "threadName": "MainThread", - "msecs": 52.00004577636719, - "filename": "base.py", - "levelno": 10, - "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\modules\\base.py", - "lineno": 407, - "msg": "[ ] SyncServerModule", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "ModulesManager", - "threadName": "MainThread", - "msecs": 52.00004577636719, - "filename": "base.py", - "levelno": 10, - "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\modules\\base.py", - "lineno": 407, - "msg": "[ ] TimersManager", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "ModulesManager", - "threadName": "MainThread", - "msecs": 55.00006675720215, - "filename": "base.py", - "levelno": 10, - "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\modules\\base.py", - "lineno": 407, - "msg": "[X] WebServerModule", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "ModulesManager", - "threadName": "MainThread", - "msecs": 55.9999942779541, - "filename": "base.py", - "levelno": 10, - "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\modules\\base.py", - "lineno": 432, - "msg": "Has 11 enabled modules.", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - } - ] - } - ], - "label": "OpenPype Modules", - "skipped": false, - "order": 0, - "name": "CollectModules" - }, - { - "instances_data": [ - { - "id": null, - "logs": [ - { - "instance_id": null, - "name": "pyblish.CollectInstances", - "threadName": "MainThread", - "msecs": 85.00003814697266, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 65, - "msg": "Creating instance for reviewMain", - "exc_info": null, - "type": "record", - "levelname": "INFO" - }, - { - "instance_id": null, - "name": "pyblish.CollectInstances", - "threadName": "MainThread", - "msecs": 88.00005912780762, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 159, - "msg": "Found: \"reviewMain\" ", - "exc_info": null, - "type": "record", - "levelname": "INFO" - }, - { - "instance_id": null, - "name": "pyblish.CollectInstances", - "threadName": "MainThread", - "msecs": 88.00005912780762, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 161, - "msg": "DATA: {\n \"subset\": \"reviewMain\", \n \"families\": [\n \"review\"\n ], \n \"family\": \"review\", \n \"frameStart\": 1001, \n \"variant\": \"Main\", \n \"active\": true, \n \"step\": 1.0, \n \"frameStartHandle\": 991, \n \"frameEnd\": 1010, \n \"cbId\": \"5dd50967e3c2c32b004b4817:3a06e704e27a\", \n \"keepImages\": false, \n \"id\": \"pyblish.avalon.instance\", \n \"name\": \"reviewMain\", \n \"handleStart\": 10, \n \"frameEndHandle\": 1020, \n \"isolate\": false, \n \"publish\": true, \n \"label\": \"reviewMain (Alpaca_01) [991-1020]\", \n \"asset\": \"Alpaca_01\", \n \"fps\": 25.0, \n \"setMembers\": [\n \"|persp_CAM\", \n \"modelMain\"\n ], \n \"handleEnd\": 10, \n \"imagePlane\": true\n} ", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "pyblish.CollectInstances", - "threadName": "MainThread", - "msecs": 89.99991416931152, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 65, - "msg": "Creating instance for modelMain", - "exc_info": null, - "type": "record", - "levelname": "INFO" - }, - { - "instance_id": null, - "name": "pyblish.CollectInstances", - "threadName": "MainThread", - "msecs": 91.00008010864258, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 159, - "msg": "Found: \"modelMain\" ", - "exc_info": null, - "type": "record", - "levelname": "INFO" - }, - { - "instance_id": null, - "name": "pyblish.CollectInstances", - "threadName": "MainThread", - "msecs": 91.00008010864258, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 161, - "msg": "DATA: {\n \"subset\": \"modelMain\", \n \"families\": [\n \"model\"\n ], \n \"family\": \"model\", \n \"variant\": \"Main\", \n \"active\": true, \n \"attrPrefix\": \"\", \n \"cbId\": \"5dd50967e3c2c32b004b4817:bc3d17f26cde\", \n \"writeColorSets\": false, \n \"id\": \"pyblish.avalon.instance\", \n \"attr\": \"\", \n \"includeParentHierarchy\": false, \n \"name\": \"modelMain\", \n \"publish\": true, \n \"label\": \"modelMain (Alpaca_01)\", \n \"asset\": \"Alpaca_01\", \n \"setMembers\": [\n \"|pTorus_GEO\"\n ]\n} ", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - } - ] - } - ], - "label": "Collect Instances", - "skipped": false, - "order": 0, - "name": "CollectInstances" - }, - { - "instances_data": [], - "label": "Deadline Webservice from the Instance", - "skipped": true, - "order": 0, - "name": "CollectDeadlineServerFromInstance" - }, - { - "instances_data": [ - { - "id": null, - "logs": [] - } - ], - "label": "Collect DateTime data", - "skipped": false, - "order": 0, - "name": "CollectDateTimeData" - }, - { - "instances_data": [ - { - "id": null, - "logs": [] - } - ], - "label": "Collect Comment", - "skipped": false, - "order": 0, - "name": "CollectComment" - }, - { - "instances_data": [ - { - "id": null, - "logs": [] - } - ], - "label": "Maya Units", - "skipped": false, - "order": 0, - "name": "CollectMayaUnits" - }, - { - "instances_data": [ - { - "id": null, - "logs": [] - } - ], - "label": "Current working directory", - "skipped": false, - "order": 0, - "name": "CollectCurrentWorkingDirectory" - }, - { - "instances_data": [ - { - "id": null, - "logs": [ - { - "instance_id": null, - "name": "pyblish.CollectMachineName", - "threadName": "MainThread", - "msecs": 394.9999809265137, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 21, - "msg": "Machine name: 014-BAILEYS", - "exc_info": null, - "type": "record", - "levelname": "INFO" - } - ] - } - ], - "label": "Local Machine Name", - "skipped": false, - "order": 0, - "name": "CollectMachineName" - }, - { - "instances_data": [ - { - "id": null, - "logs": [] - } - ], - "label": "Current date", - "skipped": false, - "order": 0, - "name": "CollectCurrentDate" - }, - { - "instances_data": [ - { - "id": null, - "logs": [] - } - ], - "label": "Collect Current Time", - "skipped": false, - "order": 0, - "name": "CollectTime" - }, - { - "instances_data": [ - { - "id": null, - "logs": [] - } - ], - "label": "Unicode Strings", - "skipped": false, - "order": 0, - "name": "RepairUnicodeStrings" - }, - { - "instances_data": [ - { - "id": null, - "logs": [ - { - "instance_id": null, - "name": "pyblish.CollectCurrentUserPype", - "threadName": "MainThread", - "msecs": 648.0000019073486, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 17, - "msg": "Collected user \"jakub.trllo\"", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - } - ] - } - ], - "label": "Collect Pype User", - "skipped": false, - "order": 0.001, - "name": "CollectCurrentUserPype" - }, - { - "instances_data": [ - { - "id": null, - "logs": [ - { - "instance_id": null, - "name": "pyblish.CollectAnatomyContextData", - "threadName": "MainThread", - "msecs": 710.0000381469727, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 87, - "msg": "Global anatomy Data collected", - "exc_info": null, - "type": "record", - "levelname": "INFO" - }, - { - "instance_id": null, - "name": "pyblish.CollectAnatomyContextData", - "threadName": "MainThread", - "msecs": 710.0000381469727, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 88, - "msg": "{\n \"username\": \"jakub.trllo\", \n \"SS\": \"14\", \n \"hierarchy\": \"Assets\", \n \"H\": \"10\", \n \"app\": \"maya\", \n \"M\": \"23\", \n \"ht\": \"AM\", \n \"mmmm\": \"August\", \n \"S\": \"14\", \n \"dddd\": \"Wednesday\", \n \"HH\": \"10\", \n \"yyyy\": \"2021\", \n \"mmm\": \"Aug\", \n \"task\": \"animation\", \n \"d\": \"25\", \n \"mm\": \"08\", \n \"h\": \"10\", \n \"m\": \"8\", \n \"hh\": \"10\", \n \"project\": {\n \"code\": \"kuba_each_case\", \n \"name\": \"kuba_each_case\"\n }, \n \"MM\": \"23\", \n \"asset\": \"Alpaca_01\", \n \"yy\": \"21\", \n \"dd\": \"25\", \n \"ddd\": \"Wed\"\n}", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - } - ] - } - ], - "label": "Collect Anatomy Context Data", - "skipped": false, - "order": 0.002, - "name": "CollectAnatomyContextData" - }, - { - "instances_data": [], - "label": "Collect Vray Proxy", - "skipped": true, - "order": 0.01, - "name": "CollectVrayProxy" - }, - { - "instances_data": [], - "label": "Collect Vray Scene", - "skipped": true, - "order": 0.01, - "name": "CollectVrayScene" - }, - { - "instances_data": [ - { - "id": null, - "logs": [ - { - "instance_id": null, - "name": "pyblish.CollectDefaultDeadlineServer", - "threadName": "MainThread", - "msecs": 770.9999084472656, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 20, - "msg": "{u'default': u'http://127.0.0.1:8082'}", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - } - ] - } - ], - "label": "Default Deadline Webservice", - "skipped": false, - "order": 0.01, - "name": "CollectDefaultDeadlineServer" - }, - { - "instances_data": [ - { - "id": null, - "logs": [ - { - "instance_id": null, - "name": "pyblish.CollectMayaRender", - "threadName": "MainThread", - "msecs": 834.0001106262207, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 80, - "msg": "No render instance found, skipping render layer collection.", - "exc_info": null, - "type": "record", - "levelname": "INFO" - } - ] - } - ], - "label": "Collect Render Layers", - "skipped": false, - "order": 0.01, - "name": "CollectMayaRender" - }, - { - "instances_data": [], - "label": "Collect Renderable Camera(s)", - "skipped": true, - "order": 0.02, - "name": "CollectRenderableCamera" - }, - { - "instances_data": [], - "label": "Render Elements / AOVs", - "skipped": true, - "order": 0.02, - "name": "CollectRenderLayerAOVS" - }, - { - "instances_data": [], - "label": "Maya History", - "skipped": true, - "order": 0.1, - "name": "CollectMayaHistory" - }, - { - "instances_data": [], - "label": "Collect Ass", - "skipped": true, - "order": 0.2, - "name": "CollectAssData" - }, - { - "instances_data": [], - "label": "Collect Model Data", - "skipped": true, - "order": 0.2, - "name": "CollectMayaAscii" - }, - { - "instances_data": [], - "label": "Collect Rig Data", - "skipped": true, - "order": 0.2, - "name": "CollectRigData" - }, - { - "instances_data": [ - { - "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "logs": [] - } - ], - "label": "Collect Model Data", - "skipped": false, - "order": 0.2, - "name": "CollectModelData" - }, - { - "instances_data": [], - "label": "Collect Look", - "skipped": true, - "order": 0.2, - "name": "CollectLook" - }, - { - "instances_data": [], - "label": "Collect Model Data", - "skipped": true, - "order": 0.2, - "name": "CollectUnrealStaticMesh" - }, - { - "instances_data": [ - { - "id": null, - "logs": [] - } - ], - "label": "Context Label", - "skipped": false, - "order": 0.25, - "name": "CollectContextLabel" - }, - { - "instances_data": [ - { - "id": "b2d211bd-217a-4cfb-b989-45b3dfc4edc9", - "logs": [ - { - "instance_id": "b2d211bd-217a-4cfb-b989-45b3dfc4edc9", - "name": "pyblish.CollectReview", - "threadName": "MainThread", - "msecs": 22.00007438659668, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 20, - "msg": "instance: reviewMain", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "b2d211bd-217a-4cfb-b989-45b3dfc4edc9", - "name": "pyblish.CollectReview", - "threadName": "MainThread", - "msecs": 23.000001907348633, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 28, - "msg": "members: [u'|persp_CAM', u'modelMain']", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "b2d211bd-217a-4cfb-b989-45b3dfc4edc9", - "name": "pyblish.CollectReview", - "threadName": "MainThread", - "msecs": 23.000001907348633, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 33, - "msg": "camera: |persp_CAM|persp_CAMShape", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "b2d211bd-217a-4cfb-b989-45b3dfc4edc9", - "name": "pyblish.CollectReview", - "threadName": "MainThread", - "msecs": 23.999929428100586, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 41, - "msg": "subset for review: [u'modelMain']", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "b2d211bd-217a-4cfb-b989-45b3dfc4edc9", - "name": "pyblish.CollectReview", - "threadName": "MainThread", - "msecs": 23.999929428100586, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 46, - "msg": "filtering modelMain", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "b2d211bd-217a-4cfb-b989-45b3dfc4edc9", - "name": "pyblish.CollectReview", - "threadName": "MainThread", - "msecs": 25.00009536743164, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 60, - "msg": "adding review family to [u'modelMain']", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "b2d211bd-217a-4cfb-b989-45b3dfc4edc9", - "name": "pyblish.CollectReview", - "threadName": "MainThread", - "msecs": 25.00009536743164, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 74, - "msg": "data {u'subset': u'modelMain', 'families': [u'model', 'review'], 'fps': 25.0, 'family': u'model', 'frameStart': 1001, u'variant': u'Main', u'active': True, 'step': 1.0, 'review_camera': u'|persp_CAM|persp_CAMShape', 'frameEnd': 1010, u'attrPrefix': u'', u'cbId': u'5dd50967e3c2c32b004b4817:bc3d17f26cde', u'writeColorSets': False, u'id': u'pyblish.avalon.instance', u'attr': u'', u'includeParentHierarchy': False, 'name': u'modelMain', 'frameEndHandle': 1020, 'isolate': False, 'publish': True, 'label': 'modelMain (Alpaca_01)', 'handles': None, 'frameStartHandle': 991, u'asset': u'Alpaca_01', 'frameStartFtrack': 991, 'setMembers': [u'|pTorus_GEO'], 'frameEndFtrack': 1020}", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "b2d211bd-217a-4cfb-b989-45b3dfc4edc9", - "name": "pyblish.CollectReview", - "threadName": "MainThread", - "msecs": 26.999950408935547, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 77, - "msg": "isntance data {u'subset': u'reviewMain', 'families': [u'review'], 'family': u'review', u'frameStart': 1001, u'variant': u'Main', 'remove': True, u'active': True, u'step': 1.0, 'frameStartHandle': 991, u'frameEnd': 1010, u'cbId': u'5dd50967e3c2c32b004b4817:3a06e704e27a', u'keepImages': False, u'id': u'pyblish.avalon.instance', 'name': u'reviewMain', 'handleStart': 10, 'frameEndHandle': 1020, u'isolate': False, 'publish': True, 'label': 'reviewMain (Alpaca_01) [991-1020]', u'asset': u'Alpaca_01', u'fps': 25.0, 'setMembers': [u'|persp_CAM', u'modelMain'], 'handleEnd': 10, u'imagePlane': True}", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "b2d211bd-217a-4cfb-b989-45b3dfc4edc9", - "name": "pyblish.CollectReview", - "threadName": "MainThread", - "msecs": 28.0001163482666, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 46, - "msg": "filtering reviewMain", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "b2d211bd-217a-4cfb-b989-45b3dfc4edc9", - "name": "pyblish.CollectReview", - "threadName": "MainThread", - "msecs": 28.0001163482666, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 51, - "msg": "subset name does not match modelMain", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "b2d211bd-217a-4cfb-b989-45b3dfc4edc9", - "name": "pyblish.CollectReview", - "threadName": "MainThread", - "msecs": 29.000043869018555, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 46, - "msg": "filtering kuba_each_case_Alpaca_01_animation_v007", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "b2d211bd-217a-4cfb-b989-45b3dfc4edc9", - "name": "pyblish.CollectReview", - "threadName": "MainThread", - "msecs": 29.000043869018555, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 51, - "msg": "subset name does not match modelMain", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - } - ] - } - ], - "label": "Collect Review Data", - "skipped": false, - "order": 0.3, - "name": "CollectReview" - }, - { - "instances_data": [], - "label": "Collect Yeti Rig", - "skipped": true, - "order": 0.4, - "name": "CollectYetiRig" - }, - { - "instances_data": [], - "label": "Collect Animation Output Geometry", - "skipped": true, - "order": 0.4, - "name": "CollectAnimationOutputGeometry" - }, - { - "instances_data": [], - "label": "Collect Yeti Cache", - "skipped": true, - "order": 0.45, - "name": "CollectYetiCache" - }, - { - "instances_data": [ - { - "id": null, - "logs": [ - { - "instance_id": null, - "name": "pyblish.CollectAnatomyInstanceData", - "threadName": "MainThread", - "msecs": 84.0001106262207, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 42, - "msg": "Collecting anatomy data for all instances.", - "exc_info": null, - "type": "record", - "levelname": "INFO" - }, - { - "instance_id": null, - "name": "pyblish.CollectAnatomyInstanceData", - "threadName": "MainThread", - "msecs": 85.00003814697266, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 51, - "msg": "Qeurying asset documents for instances.", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "pyblish.CollectAnatomyInstanceData", - "threadName": "MainThread", - "msecs": 85.99996566772461, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 77, - "msg": "All instances already had right asset document.", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "pyblish.CollectAnatomyInstanceData", - "threadName": "MainThread", - "msecs": 85.99996566772461, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 123, - "msg": "Qeurying latest versions for instances.", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "pyblish.CollectAnatomyInstanceData", - "threadName": "MainThread", - "msecs": 98.00004959106445, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 210, - "msg": "Storing anatomy data to instance data.", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "pyblish.CollectAnatomyInstanceData", - "threadName": "MainThread", - "msecs": 98.9999771118164, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 279, - "msg": "Anatomy data for instance modelMain(modelMain (Alpaca_01)): {\n \"subset\": \"modelMain\", \n \"family\": \"model\", \n \"hierarchy\": \"Assets\", \n \"app\": \"maya\", \n \"yyyy\": \"2021\", \n \"HH\": \"10\", \n \"version\": 7, \n \"asset\": \"Alpaca_01\", \n \"fps\": 25.0, \n \"ddd\": \"Wed\", \n \"username\": \"jakub.trllo\", \n \"mm\": \"08\", \n \"H\": \"10\", \n \"dd\": \"25\", \n \"M\": \"23\", \n \"ht\": \"AM\", \n \"mmmm\": \"August\", \n \"S\": \"14\", \n \"dddd\": \"Wednesday\", \n \"mmm\": \"Aug\", \n \"task\": \"animation\", \n \"d\": \"25\", \n \"SS\": \"14\", \n \"h\": \"10\", \n \"m\": \"8\", \n \"hh\": \"10\", \n \"project\": {\n \"code\": \"kuba_each_case\", \n \"name\": \"kuba_each_case\"\n }, \n \"MM\": \"23\", \n \"yy\": \"21\"\n}", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "pyblish.CollectAnatomyInstanceData", - "threadName": "MainThread", - "msecs": 101.00007057189941, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 279, - "msg": "Anatomy data for instance reviewMain(reviewMain (Alpaca_01) [991-1020]): {\n \"subset\": \"reviewMain\", \n \"family\": \"review\", \n \"hierarchy\": \"Assets\", \n \"app\": \"maya\", \n \"yyyy\": \"2021\", \n \"HH\": \"10\", \n \"version\": 1, \n \"asset\": \"Alpaca_01\", \n \"fps\": 25.0, \n \"ddd\": \"Wed\", \n \"username\": \"jakub.trllo\", \n \"mm\": \"08\", \n \"H\": \"10\", \n \"dd\": \"25\", \n \"M\": \"23\", \n \"ht\": \"AM\", \n \"mmmm\": \"August\", \n \"S\": \"14\", \n \"dddd\": \"Wednesday\", \n \"mmm\": \"Aug\", \n \"task\": \"animation\", \n \"d\": \"25\", \n \"SS\": \"14\", \n \"h\": \"10\", \n \"m\": \"8\", \n \"hh\": \"10\", \n \"project\": {\n \"code\": \"kuba_each_case\", \n \"name\": \"kuba_each_case\"\n }, \n \"MM\": \"23\", \n \"yy\": \"21\"\n}", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "pyblish.CollectAnatomyInstanceData", - "threadName": "MainThread", - "msecs": 102.99992561340332, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 279, - "msg": "Anatomy data for instance kuba_each_case_Alpaca_01_animation_v007(workfileAnimation): {\n \"subset\": \"workfileAnimation\", \n \"family\": \"workfile\", \n \"hierarchy\": \"Assets\", \n \"app\": \"maya\", \n \"yyyy\": \"2021\", \n \"HH\": \"10\", \n \"version\": 7, \n \"asset\": \"Alpaca_01\", \n \"ddd\": \"Wed\", \n \"username\": \"jakub.trllo\", \n \"mm\": \"08\", \n \"H\": \"10\", \n \"dd\": \"25\", \n \"M\": \"23\", \n \"ht\": \"AM\", \n \"mmmm\": \"August\", \n \"S\": \"14\", \n \"dddd\": \"Wednesday\", \n \"mmm\": \"Aug\", \n \"task\": \"animation\", \n \"d\": \"25\", \n \"SS\": \"14\", \n \"h\": \"10\", \n \"m\": \"8\", \n \"hh\": \"10\", \n \"project\": {\n \"code\": \"kuba_each_case\", \n \"name\": \"kuba_each_case\"\n }, \n \"MM\": \"23\", \n \"yy\": \"21\"\n}", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "pyblish.CollectAnatomyInstanceData", - "threadName": "MainThread", - "msecs": 105.00001907348633, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 48, - "msg": "Anatomy Data collection finished.", - "exc_info": null, - "type": "record", - "levelname": "INFO" - } - ] - } - ], - "label": "Collect Anatomy Instance data", - "skipped": false, - "order": 0.49, - "name": "CollectAnatomyInstanceData" - }, - { - "instances_data": [], - "label": "Assemby", - "skipped": true, - "order": 0.49, - "name": "CollectAssembly" - }, - { - "instances_data": [ - { - "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "logs": [ - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.CollectResourcesPath", - "threadName": "MainThread", - "msecs": 164.99996185302734, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 93, - "msg": "publishDir: \"C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\"", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.CollectResourcesPath", - "threadName": "MainThread", - "msecs": 165.9998893737793, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 94, - "msg": "resourcesDir: \"C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\resources\"", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - } - ] - }, - { - "id": null, - "logs": [ - { - "instance_id": null, - "name": "pyblish.CollectResourcesPath", - "threadName": "MainThread", - "msecs": 224.99990463256836, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 93, - "msg": "publishDir: \"C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\workfile\\workfileAnimation\\v007\"", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "pyblish.CollectResourcesPath", - "threadName": "MainThread", - "msecs": 226.0000705718994, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 94, - "msg": "resourcesDir: \"C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\workfile\\workfileAnimation\\v007\\resources\"", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - } - ] - } - ], - "label": "Collect Resources Path", - "skipped": false, - "order": 0.495, - "name": "CollectResourcesPath" - }, - { - "instances_data": [ - { - "id": null, - "logs": [ - { - "instance_id": null, - "name": "pyblish.CollectRemoveMarked", - "threadName": "MainThread", - "msecs": 269.0000534057617, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 16, - "msg": "[pyblish.plugin.Instance(\"modelMain\"), pyblish.plugin.Instance(\"reviewMain\"), pyblish.plugin.Instance(\"kuba_each_case_Alpaca_01_animation_v007\")]", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - } - ] - } - ], - "label": "Remove Marked Instances", - "skipped": false, - "order": 0.499, - "name": "CollectRemoveMarked" - }, - { - "instances_data": [ - { - "id": null, - "logs": [] - } - ], - "label": "Validate File Saved", - "skipped": false, - "order": 0.9, - "name": "ValidateCurrentSaveFile" - }, - { - "instances_data": [], - "label": "Validate Assembly Namespaces", - "skipped": true, - "order": 1, - "name": "ValidateAssemblyNamespaces" - }, - { - "instances_data": [], - "label": "Validate Assembly Name", - "skipped": true, - "order": 1, - "name": "ValidateAssemblyName" - }, - { - "instances_data": [], - "label": "VRay Proxy Members", - "skipped": true, - "order": 1, - "name": "ValidateVrayProxyMembers" - }, - { - "instances_data": [ - { - "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "logs": [] - }, - { - "id": null, - "logs": [] - } - ], - "label": "Validate Version", - "skipped": false, - "order": 1, - "name": "ValidateVersion" - }, - { - "instances_data": [ - { - "id": null, - "logs": [ - { - "instance_id": null, - "name": "pyblish.ValidateEditorialAssetName", - "threadName": "MainThread", - "msecs": 517.9998874664307, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 19, - "msg": "__ asset_and_parents: {}", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "pyblish.ValidateEditorialAssetName", - "threadName": "MainThread", - "msecs": 520.9999084472656, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 26, - "msg": "__ db_assets: [{u'_id': ObjectId('5e11ec3e0002fc29f0728009'), u'data': {u'parents': []}, u'name': u'MyFolder'}, {u'_id': ObjectId('5dd50967e3c2c32b004b4811'), u'data': {u'parents': []}, u'name': u'Shots'}, {u'_id': ObjectId('5dd50967e3c2c32b004b4812'), u'data': {u'parents': []}, u'name': u'Assets'}, {u'_id': ObjectId('606d813fda091ca18ad3c1cb'), u'data': {u'parents': [u'Shots']}, u'name': u'sq01'}, {u'_id': ObjectId('60ffcef3ed7b98218feec87d'), u'data': {u'parents': [u'Shots']}, u'name': u'editorial'}, {u'_id': ObjectId('5eb40b96e3c2c3361ce67c05'), u'data': {u'parents': [u'Assets']}, u'name': u'Buddie_01'}, {u'_id': ObjectId('60ffcef3ed7b98218feec87e'), u'data': {u'parents': [u'Assets']}, u'name': u'characters'}, {u'_id': ObjectId('60ffcef3ed7b98218feec87f'), u'data': {u'parents': [u'Assets']}, u'name': u'locations'}, {u'_id': ObjectId('5dd50967e3c2c32b004b4817'), u'data': {u'parents': [u'Assets']}, u'name': u'Alpaca_01'}, {u'_id': ObjectId('60d9a8e8a5016c2a23f88534'), u'data': {u'parents': [u'Shots', u'sq01']}, u'name': u'sq01_sh0020'}, {u'_id': ObjectId('60d9a8e8a5016c2a23f88535'), u'data': {u'parents': [u'Shots', u'sq01']}, u'name': u'sq01_sh0030'}, {u'_id': ObjectId('606d813fda091ca18ad3c1cd'), u'data': {u'parents': [u'Shots', u'sq01']}, u'name': u'sq01_sh0010'}]", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "pyblish.ValidateEditorialAssetName", - "threadName": "MainThread", - "msecs": 523.0000019073486, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 33, - "msg": "__ project_entities: {'Alpaca_01': [u'Assets'],\n 'Assets': [],\n 'Buddie_01': [u'Assets'],\n 'MyFolder': [],\n 'Shots': [],\n 'characters': [u'Assets'],\n 'editorial': [u'Shots'],\n 'locations': [u'Assets'],\n 'sq01': [u'Shots'],\n 'sq01_sh0010': [u'Shots', u'sq01'],\n 'sq01_sh0020': [u'Shots', u'sq01'],\n 'sq01_sh0030': [u'Shots', u'sq01']}", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - } - ] - } - ], - "label": "Validate Editorial Asset Name", - "skipped": false, - "order": 1, - "name": "ValidateEditorialAssetName" - }, - { - "instances_data": [], - "label": "Yeti Rig Settings", - "skipped": true, - "order": 1, - "name": "ValidateYetiRigSettings" - }, - { - "instances_data": [ - { - "id": null, - "logs": [ - { - "instance_id": null, - "name": "pyblish.ValidateFFmpegInstalled", - "threadName": "MainThread", - "msecs": 579.9999237060547, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 31, - "msg": "ffmpeg path: `C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\vendor\\bin\\ffmpeg\\windows\\bin\\ffmpeg`", - "exc_info": null, - "type": "record", - "levelname": "INFO" - } - ] - } - ], - "label": "Validate ffmpeg installation", - "skipped": false, - "order": 1, - "name": "ValidateFFmpegInstalled" - }, - { - "instances_data": [], - "label": "Current Render Layer Has Renderable Camera", - "skipped": true, - "order": 1, - "name": "ValidateCurrentRenderLayerIsRenderable" - }, - { - "instances_data": [], - "label": "No V-Ray Proxies (VRayMesh)", - "skipped": true, - "order": 1, - "name": "ValidateNoVRayMesh" - }, - { - "instances_data": [], - "label": "Yeti Rig Cache State", - "skipped": true, - "order": 1, - "name": "ValidateYetiRigCacheState" - }, - { - "instances_data": [], - "label": "VRay Proxy Settings", - "skipped": true, - "order": 1, - "name": "ValidateVrayProxy" - }, - { - "instances_data": [ - { - "id": null, - "logs": [] - } - ], - "label": "Validate Containers", - "skipped": false, - "order": 1, - "name": "ValidateContainers" - }, - { - "instances_data": [], - "label": "VRay Referenced AOVs", - "skipped": true, - "order": 1, - "name": "ValidateVrayReferencedAOVs" - }, - { - "instances_data": [], - "label": "Validate Deadline Web Service", - "skipped": true, - "order": 1, - "name": "ValidateDeadlineConnection" - }, - { - "instances_data": [], - "label": "Validate Muster REST API Service", - "skipped": true, - "order": 1, - "name": "ValidateMusterConnection" - }, - { - "instances_data": [], - "label": "Instancer Content", - "skipped": true, - "order": 1, - "name": "ValidateInstancerContent" - }, - { - "instances_data": [], - "label": "Instancer Cache Frame Ranges", - "skipped": true, - "order": 1, - "name": "ValidateInstancerFrameRanges" - }, - { - "instances_data": [ - { - "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "logs": [] - } - ], - "label": "Node Ids Related (ID)", - "skipped": false, - "order": 1.05, - "name": "ValidateNodeIDsRelated" - }, - { - "instances_data": [ - { - "id": null, - "logs": [] - } - ], - "label": "Maya Workspace Set", - "skipped": false, - "order": 1.05, - "name": "ValidateSceneSetWorkspace" - }, - { - "instances_data": [ - { - "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "logs": [] - } - ], - "label": "Instance Nodes Have ID", - "skipped": false, - "order": 1.05, - "name": "ValidateNodeIDs" - }, - { - "instances_data": [], - "label": "Look members unique", - "skipped": true, - "order": 1.05, - "name": "ValidateUniqueRelationshipMembers" - }, - { - "instances_data": [ - { - "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "logs": [] - } - ], - "label": "Non Duplicate Instance Members (ID)", - "skipped": false, - "order": 1.05, - "name": "ValidateNodeIdsUnique" - }, - { - "instances_data": [ - { - "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "logs": [] - }, - { - "id": null, - "logs": [] - } - ], - "label": "Node Ids in Database", - "skipped": false, - "order": 1.05, - "name": "ValidateNodeIdsInDatabase" - }, - { - "instances_data": [], - "label": "Look Shading Engine Naming", - "skipped": true, - "order": 1.1, - "name": "ValidateShadingEngine" - }, - { - "instances_data": [], - "label": "Joints Hidden", - "skipped": true, - "order": 1.1, - "name": "ValidateRigJointsHidden" - }, - { - "instances_data": [], - "label": "Animation Content", - "skipped": true, - "order": 1.1, - "name": "ValidateAnimationContent" - }, - { - "instances_data": [], - "label": "Step size", - "skipped": true, - "order": 1.1, - "name": "ValidateStepSize" - }, - { - "instances_data": [], - "label": "Single Assembly", - "skipped": true, - "order": 1.1, - "name": "ValidateSingleAssembly" - }, - { - "instances_data": [], - "label": "Render Settings", - "skipped": true, - "order": 1.1, - "name": "ValidateRenderSettings" - }, - { - "instances_data": [], - "label": "No Default Cameras", - "skipped": true, - "order": 1.1, - "name": "ValidateNoDefaultCameras" - }, - { - "instances_data": [ - { - "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "logs": [] - }, - { - "id": null, - "logs": [] - } - ], - "label": "Instance in same Context", - "skipped": false, - "order": 1.1, - "name": "ValidateInstanceInContext" - }, - { - "instances_data": [], - "label": "VRay Distributed Rendering", - "skipped": true, - "order": 1.1, - "name": "ValidateVRayDistributedRendering" - }, - { - "instances_data": [], - "label": "No Default Cameras Renderable", - "skipped": true, - "order": 1.1, - "name": "ValidateRenderNoDefaultCameras" - }, - { - "instances_data": [], - "label": "Look Default Shader Connections", - "skipped": true, - "order": 1.1, - "name": "ValidateLookDefaultShadersConnections" - }, - { - "instances_data": [], - "label": "Yeti Render Script Callbacks", - "skipped": true, - "order": 1.1, - "name": "ValidateYetiRenderScriptCallbacks" - }, - { - "instances_data": [ - { - "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "logs": [] - } - ], - "label": "Validate Frame Range", - "skipped": false, - "order": 1.1, - "name": "ValidateFrameRange" - }, - { - "instances_data": [], - "label": "Render Passes / AOVs Are Registered", - "skipped": true, - "order": 1.1, - "name": "ValidateRenderLayerAOVs" - }, - { - "instances_data": [], - "label": "Camera Contents", - "skipped": true, - "order": 1.1, - "name": "ValidateCameraContents" - }, - { - "instances_data": [], - "label": "Render Single Camera", - "skipped": true, - "order": 1.1, - "name": "ValidateRenderSingleCamera" - }, - { - "instances_data": [ - { - "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "logs": [] - } - ], - "label": "Suffix Naming Conventions", - "skipped": false, - "order": 1.1, - "name": "ValidateTransformNamingSuffix" - }, - { - "instances_data": [ - { - "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "logs": [ - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ValidateResources", - "threadName": "MainThread", - "msecs": 13.99993896484375, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 27, - "msg": "No resources to validate..", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - } - ] - }, - { - "id": null, - "logs": [ - { - "instance_id": null, - "name": "pyblish.ValidateResources", - "threadName": "MainThread", - "msecs": 75.99997520446777, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 27, - "msg": "No resources to validate..", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - } - ] - } - ], - "label": "Resources Unique", - "skipped": false, - "order": 1.1, - "name": "ValidateResources" - }, - { - "instances_data": [], - "label": "Look Sets", - "skipped": true, - "order": 1.1, - "name": "ValidateLookSets" - }, - { - "instances_data": [], - "label": "Look Data Contents", - "skipped": true, - "order": 1.1, - "name": "ValidateLookContents" - }, - { - "instances_data": [], - "label": "Look Single Shader Per Shape", - "skipped": true, - "order": 1.1, - "name": "ValidateSingleShader" - }, - { - "instances_data": [ - { - "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "logs": [] - }, - { - "id": null, - "logs": [] - } - ], - "label": "Subset Name", - "skipped": false, - "order": 1.1, - "name": "ValidateSubsetName" - }, - { - "instances_data": [], - "label": "Skincluster Deformer Relationships", - "skipped": true, - "order": 1.1, - "name": "ValidateSkinclusterDeformerSet" - }, - { - "instances_data": [], - "label": "Images File Rule (Workspace)", - "skipped": true, - "order": 1.1, - "name": "ValidateRenderImageRule" - }, - { - "instances_data": [], - "label": "Yeti Rig Input Shapes In Instance", - "skipped": true, - "order": 1.1, - "name": "ValidateYetiRigInputShapesInInstance" - }, - { - "instances_data": [], - "label": "Animation Out Set Related Node Ids", - "skipped": true, - "order": 1.1, - "name": "ValidateOutRelatedNodeIds" - }, - { - "instances_data": [ - { - "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "logs": [] - } - ], - "label": "Model Content", - "skipped": false, - "order": 1.1, - "name": "ValidateModelContent" - }, - { - "instances_data": [], - "label": "Rig Out Set Node Ids", - "skipped": true, - "order": 1.1, - "name": "ValidateRigOutSetNodeIds" - }, - { - "instances_data": [], - "label": "VRay Translator Settings", - "skipped": true, - "order": 1.1, - "name": "ValidateVRayTranslatorEnabled" - }, - { - "instances_data": [ - { - "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "logs": [] - } - ], - "label": "No Namespaces", - "skipped": false, - "order": 1.1, - "name": "ValidateNoNamespace" - }, - { - "instances_data": [], - "label": "Unreal StaticMesh Name", - "skipped": true, - "order": 1.1, - "name": "ValidateUnrealStaticmeshName" - }, - { - "instances_data": [ - { - "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "logs": [] - } - ], - "label": "Unknown Nodes", - "skipped": false, - "order": 1.1, - "name": "ValidateNoUnknownNodes" - }, - { - "instances_data": [], - "label": "ASS has relative texture paths", - "skipped": true, - "order": 1.1, - "name": "ValidateAssRelativePaths" - }, - { - "instances_data": [], - "label": "Unreal Up-Axis check", - "skipped": true, - "order": 1.1, - "name": "ValidateUnrealUpAxis" - }, - { - "instances_data": [], - "label": "Look Id Reference Edits", - "skipped": true, - "order": 1.1, - "name": "ValidateLookIdReferenceEdits" - }, - { - "instances_data": [ - { - "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "logs": [] - }, - { - "id": null, - "logs": [] - } - ], - "label": "Instance has members", - "skipped": false, - "order": 1.1, - "name": "ValidateInstanceHasMembers" - }, - { - "instances_data": [ - { - "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "logs": [] - } - ], - "label": "No Empty/Null Transforms", - "skipped": false, - "order": 1.1, - "name": "ValidateNoNullTransforms" - }, - { - "instances_data": [], - "label": "Deformed shape ids", - "skipped": true, - "order": 1.1, - "name": "ValidateNodeIdsDeformedShape" - }, - { - "instances_data": [], - "label": "Rig Contents", - "skipped": true, - "order": 1.1, - "name": "ValidateRigContents" - }, - { - "instances_data": [], - "label": "Look No Default Shaders", - "skipped": true, - "order": 1.11, - "name": "ValidateLookNoDefaultShaders" - }, - { - "instances_data": [], - "label": "Rig Controllers (Arnold Attributes)", - "skipped": true, - "order": 1.1500000000000001, - "name": "ValidateRigControllersArnoldAttributes" - }, - { - "instances_data": [], - "label": "Rig Controllers", - "skipped": true, - "order": 1.1500000000000001, - "name": "ValidateRigControllers" - }, - { - "instances_data": [], - "label": "Rig Output Ids", - "skipped": true, - "order": 1.1500000000000001, - "name": "ValidateRigOutputIds" - }, - { - "instances_data": [ - { - "id": null, - "logs": [ - { - "instance_id": null, - "name": "pyblish.ValidateMayaUnits", - "threadName": "MainThread", - "msecs": 769.9999809265137, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 36, - "msg": "Units (linear): cm", - "exc_info": null, - "type": "record", - "levelname": "INFO" - }, - { - "instance_id": null, - "name": "pyblish.ValidateMayaUnits", - "threadName": "MainThread", - "msecs": 770.9999084472656, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 37, - "msg": "Units (angular): deg", - "exc_info": null, - "type": "record", - "levelname": "INFO" - }, - { - "instance_id": null, - "name": "pyblish.ValidateMayaUnits", - "threadName": "MainThread", - "msecs": 772.0000743865967, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 38, - "msg": "Units (time): 25.0 FPS", - "exc_info": null, - "type": "record", - "levelname": "INFO" - } - ] - } - ], - "label": "Maya Units", - "skipped": false, - "order": 1.2, - "name": "ValidateMayaUnits" - }, - { - "instances_data": [ - { - "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "logs": [] - } - ], - "label": "Mesh Shader Connections", - "skipped": false, - "order": 1.3, - "name": "ValidateMeshShaderConnections" - }, - { - "instances_data": [ - { - "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "logs": [] - } - ], - "label": "Mesh Vertices Have Edges", - "skipped": false, - "order": 1.3, - "name": "ValidateMeshVerticesHaveEdges" - }, - { - "instances_data": [ - { - "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "logs": [] - } - ], - "label": "Mesh Edge Length Non Zero", - "skipped": false, - "order": 1.3, - "name": "ValidateMeshNonZeroEdgeLength" - }, - { - "instances_data": [], - "label": "Mesh is Triangulated", - "skipped": true, - "order": 1.3, - "name": "ValidateUnrealMeshTriangulated" - }, - { - "instances_data": [ - { - "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "logs": [] - } - ], - "label": "Mesh Has UVs", - "skipped": false, - "order": 1.3, - "name": "ValidateMeshHasUVs" - }, - { - "instances_data": [ - { - "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "logs": [] - } - ], - "label": "Mesh No Negative Scale", - "skipped": false, - "order": 1.3, - "name": "ValidateMeshNoNegativeScale" - }, - { - "instances_data": [], - "label": "Assembly Model Transforms", - "skipped": true, - "order": 1.49, - "name": "ValidateAssemblyModelTransforms" - }, - { - "instances_data": [], - "label": "Extract Hierarchy To Avalon", - "skipped": true, - "order": 1.99, - "name": "ExtractHierarchyToAvalon" - }, - { - "instances_data": [], - "label": "Extract Yeti Rig", - "skipped": true, - "order": 2.0, - "name": "ExtractYetiRig" - }, - { - "instances_data": [], - "label": "VRay Proxy (.vrmesh)", - "skipped": true, - "order": 2.0, - "name": "ExtractVRayProxy" - }, - { - "instances_data": [], - "label": "Extract Animation", - "skipped": true, - "order": 2.0, - "name": "ExtractAnimation" - }, - { - "instances_data": [ - { - "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "logs": [ - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractThumbnail", - "threadName": "MainThread", - "msecs": 141.9999599456787, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 27, - "msg": "Extracting capture..", - "exc_info": null, - "type": "record", - "levelname": "INFO" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractThumbnail", - "threadName": "MainThread", - "msecs": 144.00005340576172, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 41, - "msg": "Using viewport preset: {'sound': None, 'display_options': {u'backgroundTop': [0.49019607843137253, 0.49019607843137253, 0.49019607843137253], 'displayGradient': True, u'backgroundBottom': [0.49019607843137253, 0.49019607843137253, 0.49019607843137253], u'background': [0.49019607843137253, 0.49019607843137253, 0.49019607843137253]}, 'compression': u'jpg', 'format': u'image', 'off_screen': True, 'viewport2_options': {'multiSampleEnable': True, 'textureMaxResolution': 1024, 'enableTextureMaxRes': True, 'textureMaxResMode': 1, 'multiSampleCount': 4, 'ssaoEnable': True}, 'height': 1080, 'width': 1920, 'quality': 95, 'isolate_view': True, 'viewport_options': {'hud': False, 'particleInstancers': False, 'manipulators': False, 'joints': False, 'nParticles': False, 'dynamicConstraints': False, 'rendererName': u'vp2Renderer', 'pluginShapes': False, 'deformers': False, 'motionTrails': False, 'dynamics': False, 'pivots': False, 'dimensions': False, 'cameras': False, 'ikHandles': False, 'fluids': False, 'lights': False, 'handles': False, 'controlVertices': False, 'greasePencils': False, 'locators': False, 'displayLights': u'default', 'nurbsCurves': False, 'textures': True, 'nCloths': False, 'subdivSurfaces': False, 'strokes': False, 'follicles': False, 'polymeshes': True, 'grid': False, 'imagePlane': True, 'nurbsSurfaces': False, 'shadows': True, 'clipGhosts': False, 'hairSystems': True, 'hulls': False, 'twoSidedLighting': True, 'planes': False, 'nRigids': False}}", - "exc_info": null, - "type": "record", - "levelname": "INFO" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractThumbnail", - "threadName": "MainThread", - "msecs": 150.00009536743164, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 65, - "msg": "Outputting images to c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr\\modelMain", - "exc_info": null, - "type": "record", - "levelname": "INFO" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractThumbnail", - "threadName": "MainThread", - "msecs": 519.0000534057617, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 94, - "msg": "file list modelMain.1001.jpg", - "exc_info": null, - "type": "record", - "levelname": "INFO" - } - ] - } - ], - "label": "Thumbnail", - "skipped": false, - "order": 2.0, - "name": "ExtractThumbnail" - }, - { - "instances_data": [ - { - "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "logs": [ - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractAlembic", - "threadName": "MainThread", - "msecs": 576.9999027252197, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 43, - "msg": "Extracting pointcache..", - "exc_info": null, - "type": "record", - "levelname": "INFO" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractAlembic", - "threadName": "MainThread", - "msecs": 628.9999485015869, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 92, - "msg": "Extracted modelMain to c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr", - "exc_info": null, - "type": "record", - "levelname": "INFO" - } - ] - } - ], - "label": "Extract Pointcache (Alembic)", - "skipped": false, - "order": 2.0, - "name": "ExtractAlembic" - }, - { - "instances_data": [], - "label": "Ass Standin (.ass)", - "skipped": true, - "order": 2.0, - "name": "ExtractAssStandin" - }, - { - "instances_data": [ - { - "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "logs": [ - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractPlayblast", - "threadName": "MainThread", - "msecs": 644.0000534057617, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 29, - "msg": "Extracting capture..", - "exc_info": null, - "type": "record", - "levelname": "INFO" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractPlayblast", - "threadName": "MainThread", - "msecs": 644.9999809265137, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 43, - "msg": "start: 991, end: 1020", - "exc_info": null, - "type": "record", - "levelname": "INFO" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractPlayblast", - "threadName": "MainThread", - "msecs": 644.9999809265137, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 62, - "msg": "Outputting images to c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr\\modelMain", - "exc_info": null, - "type": "record", - "levelname": "INFO" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractPlayblast", - "threadName": "MainThread", - "msecs": 657.0000648498535, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 93, - "msg": "using viewport preset: {'sound': None, 'display_options': {u'backgroundTop': [0.49019607843137253, 0.49019607843137253, 0.49019607843137253], 'displayGradient': True, u'backgroundBottom': [0.49019607843137253, 0.49019607843137253, 0.49019607843137253], u'background': [0.49019607843137253, 0.49019607843137253, 0.49019607843137253]}, 'compression': u'jpg', 'format': u'image', 'viewer': False, 'end_frame': 1020, 'start_frame': 991, 'off_screen': True, 'viewport2_options': {'multiSampleEnable': True, 'textureMaxResolution': 1024, 'enableTextureMaxRes': True, 'textureMaxResMode': 1, 'multiSampleCount': 4, 'ssaoEnable': True}, 'height': 1080, 'width': 1920, 'camera': u'|persp_CAM|persp_CAMShape', 'filename': 'c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr\\\\modelMain', 'quality': 95, 'overwrite': True, 'viewport_options': {'hud': False, 'particleInstancers': False, 'manipulators': False, 'joints': False, 'nParticles': False, 'dynamicConstraints': False, 'rendererName': u'vp2Renderer', 'pluginShapes': False, 'deformers': False, 'motionTrails': False, 'dynamics': False, 'pivots': False, 'dimensions': False, 'cameras': False, 'ikHandles': False, 'fluids': False, 'lights': False, 'handles': False, 'controlVertices': False, 'greasePencils': False, 'locators': False, 'displayLights': u'default', 'nurbsCurves': False, 'textures': True, 'nCloths': False, 'subdivSurfaces': False, 'strokes': False, 'follicles': False, 'polymeshes': True, 'grid': False, 'imagePlane': True, 'nurbsSurfaces': False, 'shadows': True, 'clipGhosts': False, 'hairSystems': True, 'hulls': False, 'twoSidedLighting': True, 'planes': False, 'nRigids': False}}", - "exc_info": null, - "type": "record", - "levelname": "INFO" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractPlayblast", - "threadName": "MainThread", - "msecs": 125.99992752075195, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 97, - "msg": "playblast path c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr\\modelMain.####.jpg", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractPlayblast", - "threadName": "MainThread", - "msecs": 128.00002098083496, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 102, - "msg": "filename c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr\\modelMain", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractPlayblast", - "threadName": "MainThread", - "msecs": 128.9999485015869, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 106, - "msg": "collection head modelMain", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractPlayblast", - "threadName": "MainThread", - "msecs": 128.9999485015869, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 111, - "msg": "we found collection of interest modelMain.%04d.jpg [991-1020]", - "exc_info": null, - "type": "record", - "levelname": "INFO" - } - ] - } - ], - "label": "Extract Playblast", - "skipped": false, - "order": 2.0, - "name": "ExtractPlayblast" - }, - { - "instances_data": [], - "label": "Extract Yeti Cache", - "skipped": true, - "order": 2.0, - "name": "ExtractYetiCache" - }, - { - "instances_data": [], - "label": "Redshift Proxy (.rs)", - "skipped": true, - "order": 2.0, - "name": "ExtractRedshiftProxy" - }, - { - "instances_data": [], - "label": "Extract RenderSetup", - "skipped": true, - "order": 2.0, - "name": "ExtractRenderSetup" - }, - { - "instances_data": [], - "label": "Camera (Alembic)", - "skipped": true, - "order": 2.0, - "name": "ExtractCameraAlembic" - }, - { - "instances_data": [], - "label": "Camera (Maya Scene)", - "skipped": true, - "order": 2.0, - "name": "ExtractCameraMayaScene" - }, - { - "instances_data": [], - "label": "Maya Scene (Raw)", - "skipped": true, - "order": 2.0, - "name": "ExtractMayaSceneRaw" - }, - { - "instances_data": [], - "label": "Extract Rig (Maya Scene)", - "skipped": true, - "order": 2.0, - "name": "ExtractRig" - }, - { - "instances_data": [], - "label": "VRay Scene (.vrscene)", - "skipped": true, - "order": 2.0, - "name": "ExtractVrayscene" - }, - { - "instances_data": [], - "label": "Extract Assembly", - "skipped": true, - "order": 2.0, - "name": "ExtractAssembly" - }, - { - "instances_data": [ - { - "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "logs": [ - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractModel", - "threadName": "MainThread", - "msecs": 282.99999237060547, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 39, - "msg": "Looking in settings for scene type ...", - "exc_info": null, - "type": "record", - "levelname": "INFO" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractModel", - "threadName": "MainThread", - "msecs": 289.0000343322754, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 45, - "msg": "Using ma as scene type", - "exc_info": null, - "type": "record", - "levelname": "INFO" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractModel", - "threadName": "MainThread", - "msecs": 289.0000343322754, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 56, - "msg": "Performing extraction ...", - "exc_info": null, - "type": "record", - "levelname": "INFO" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractModel", - "threadName": "MainThread", - "msecs": 329.9999237060547, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 102, - "msg": "Extracted instance 'modelMain' to: c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr\\modelMain.ma", - "exc_info": null, - "type": "record", - "levelname": "INFO" - } - ] - } - ], - "label": "Model (Maya Scene)", - "skipped": false, - "order": 2.0, - "name": "ExtractModel" - }, - { - "instances_data": [], - "label": "Extract Xgen ABC Cache", - "skipped": true, - "order": 2.0, - "name": "ExtractXgenCache" - }, - { - "instances_data": [], - "label": "Extract FBX", - "skipped": true, - "order": 2, - "name": "ExtractFBX" - }, - { - "instances_data": [ - { - "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "logs": [ - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractReview", - "threadName": "MainThread", - "msecs": 403.0001163482666, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 65, - "msg": "[{'files': u'modelMain.1001.jpg', 'ext': 'jpg', 'stagingDir': 'c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr', 'name': 'thumbnail', 'thumbnail': True}, {'files': 'modelMain.abc', 'ext': 'abc', 'stagingDir': 'c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr', 'name': 'abc'}, {'files': ['modelMain.0991.jpg', 'modelMain.0992.jpg', 'modelMain.0993.jpg', 'modelMain.0994.jpg', 'modelMain.0995.jpg', 'modelMain.0996.jpg', 'modelMain.0997.jpg', 'modelMain.0998.jpg', 'modelMain.0999.jpg', 'modelMain.1000.jpg', 'modelMain.1001.jpg', 'modelMain.1002.jpg', 'modelMain.1003.jpg', 'modelMain.1004.jpg', 'modelMain.1005.jpg', 'modelMain.1006.jpg', 'modelMain.1007.jpg', 'modelMain.1008.jpg', 'modelMain.1009.jpg', 'modelMain.1010.jpg', 'modelMain.1011.jpg', 'modelMain.1012.jpg', 'modelMain.1013.jpg', 'modelMain.1014.jpg', 'modelMain.1015.jpg', 'modelMain.1016.jpg', 'modelMain.1017.jpg', 'modelMain.1018.jpg', 'modelMain.1019.jpg', 'modelMain.1020.jpg'], 'ext': 'png', 'tags': ['review', 'delete'], 'name': 'png', 'fps': 25.0, 'frameStart': 991, 'camera_name': u'persp_CAM', 'preview': True, 'stagingDir': 'c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr', 'frameEnd': 1020}, {'files': 'modelMain.ma', 'ext': u'ma', 'stagingDir': 'c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr', 'name': u'ma'}]", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractReview", - "threadName": "MainThread", - "msecs": 405.99989891052246, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 96, - "msg": "Host: \"maya\"", - "exc_info": null, - "type": "record", - "levelname": "INFO" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractReview", - "threadName": "MainThread", - "msecs": 405.99989891052246, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 97, - "msg": "Task: \"animation\"", - "exc_info": null, - "type": "record", - "levelname": "INFO" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractReview", - "threadName": "MainThread", - "msecs": 407.0000648498535, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 98, - "msg": "Family: \"model\"", - "exc_info": null, - "type": "record", - "levelname": "INFO" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractReview", - "threadName": "MainThread", - "msecs": 407.0000648498535, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 110, - "msg": "Matching profile: \"{\"families\": [], \"hosts\": [], \"outputs\": {\"h264\": {\"tags\": [\"burnin\", \"ftrackreview\"], \"height\": 0, \"filter\": {\"families\": [\"render\", \"review\", \"ftrack\"]}, \"width\": 0, \"ext\": \"mp4\", \"overscan_color\": [0, 0, 0, 255], \"letter_box\": {\"line_color\": [255, 0, 0, 255], \"ratio\": 0.0, \"fill_color\": [0, 0, 0, 255], \"enabled\": false, \"state\": \"letterbox\", \"line_thickness\": 0}, \"bg_color\": [0, 0, 0, 0], \"overscan_crop\": \"\", \"ffmpeg_args\": {\"video_filters\": [], \"input\": [\"-apply_trc gamma22\"], \"audio_filters\": [], \"output\": [\"-pix_fmt yuv420p\", \"-crf 18\", \"-intra\"]}}}}\"", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractReview", - "threadName": "MainThread", - "msecs": 408.9999198913574, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 136, - "msg": "Repre: thumbnail - Didn't found \"review\" in tags. Skipping", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractReview", - "threadName": "MainThread", - "msecs": 410.0000858306885, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 136, - "msg": "Repre: abc - Didn't found \"review\" in tags. Skipping", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractReview", - "threadName": "MainThread", - "msecs": 410.0000858306885, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 192, - "msg": "New representation tags: `['review', u'burnin', u'ftrackreview']`", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractReview", - "threadName": "MainThread", - "msecs": 411.00001335144043, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 199, - "msg": "Filling gaps in sequence.", - "exc_info": null, - "type": "record", - "levelname": "INFO" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "openpype.lib.plugin_tools", - "threadName": "MainThread", - "msecs": 411.9999408721924, - "filename": "plugin_tools.py", - "levelno": 10, - "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\lib\\plugin_tools.py", - "lineno": 360, - "msg": "OIIOTool is not configured or not present at C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\vendor\\bin\\oiio\\windows\\oiiotool", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractReview", - "threadName": "MainThread", - "msecs": 413.00010681152344, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 761, - "msg": "New representation ext: `mp4`", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractReview", - "threadName": "MainThread", - "msecs": 413.00010681152344, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 817, - "msg": "Input path c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr\\modelMain.%04d.jpg", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractReview", - "threadName": "MainThread", - "msecs": 414.0000343322754, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 818, - "msg": "Output path c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr\\modelMain_h264.mp4", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractReview", - "threadName": "MainThread", - "msecs": 414.0000343322754, - "filename": "vendor_bin_utils.py", - "levelno": 20, - "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\lib\\vendor_bin_utils.py", - "lineno": 71, - "msg": "Getting information about input \"c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr\\modelMain.0991.jpg\".", - "exc_info": null, - "type": "record", - "levelname": "INFO" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractReview", - "threadName": "MainThread", - "msecs": 414.99996185302734, - "filename": "vendor_bin_utils.py", - "levelno": 10, - "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\lib\\vendor_bin_utils.py", - "lineno": 82, - "msg": "FFprobe command: \"\"C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\vendor\\bin\\ffmpeg\\windows\\bin\\ffprobe\" -v quiet -print_format json -show_format -show_streams \"c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr\\modelMain.0991.jpg\"\"", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractReview", - "threadName": "MainThread", - "msecs": 779.0000438690186, - "filename": "vendor_bin_utils.py", - "levelno": 10, - "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\lib\\vendor_bin_utils.py", - "lineno": 93, - "msg": "FFprobe stdout:\n{\r\n \"streams\": [\r\n {\r\n \"index\": 0,\r\n \"codec_name\": \"mjpeg\",\r\n \"codec_long_name\": \"Motion JPEG\",\r\n \"profile\": \"Baseline\",\r\n \"codec_type\": \"video\",\r\n \"codec_tag_string\": \"[0][0][0][0]\",\r\n \"codec_tag\": \"0x0000\",\r\n \"width\": 1920,\r\n \"height\": 1080,\r\n \"coded_width\": 1920,\r\n \"coded_height\": 1080,\r\n \"closed_captions\": 0,\r\n \"has_b_frames\": 0,\r\n \"pix_fmt\": \"yuvj420p\",\r\n \"level\": -99,\r\n \"color_range\": \"pc\",\r\n \"color_space\": \"bt470bg\",\r\n \"chroma_location\": \"center\",\r\n \"refs\": 1,\r\n \"r_frame_rate\": \"25/1\",\r\n \"avg_frame_rate\": \"25/1\",\r\n \"time_base\": \"1/25\",\r\n \"start_pts\": 0,\r\n \"start_time\": \"0.000000\",\r\n \"duration_ts\": 1,\r\n \"duration\": \"0.040000\",\r\n \"bits_per_raw_sample\": \"8\",\r\n \"disposition\": {\r\n \"default\": 0,\r\n \"dub\": 0,\r\n \"original\": 0,\r\n \"comment\": 0,\r\n \"lyrics\": 0,\r\n \"karaoke\": 0,\r\n \"forced\": 0,\r\n \"hearing_impaired\": 0,\r\n \"visual_impaired\": 0,\r\n \"clean_effects\": 0,\r\n \"attached_pic\": 0,\r\n \"timed_thumbnails\": 0\r\n }\r\n }\r\n ],\r\n \"format\": {\r\n \"filename\": \"c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr\\\\modelMain.0991.jpg\",\r\n \"nb_streams\": 1,\r\n \"nb_programs\": 0,\r\n \"format_name\": \"image2\",\r\n \"format_long_name\": \"image2 sequence\",\r\n \"start_time\": \"0.000000\",\r\n \"duration\": \"0.040000\",\r\n \"size\": \"36385\",\r\n \"bit_rate\": \"7277000\",\r\n \"probe_score\": 50\r\n }\r\n}\r\n", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractReview", - "threadName": "MainThread", - "msecs": 783.9999198913574, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 1018, - "msg": "Overscan color: `#000000`", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractReview", - "threadName": "MainThread", - "msecs": 783.9999198913574, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 1064, - "msg": "pixel_aspect: `1`", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractReview", - "threadName": "MainThread", - "msecs": 783.9999198913574, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 1065, - "msg": "input_width: `1920`", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractReview", - "threadName": "MainThread", - "msecs": 785.0000858306885, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 1066, - "msg": "input_height: `1080`", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractReview", - "threadName": "MainThread", - "msecs": 785.0000858306885, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 1075, - "msg": "Using resolution from input.", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractReview", - "threadName": "MainThread", - "msecs": 786.0000133514404, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 1099, - "msg": "Output resolution is 1920x1080", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractReview", - "threadName": "MainThread", - "msecs": 786.0000133514404, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 1111, - "msg": "Output resolution is same as input's and \"letter_box\" key is not set. Skipping reformat part.", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractReview", - "threadName": "MainThread", - "msecs": 786.9999408721924, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 220, - "msg": "Executing: \"C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\vendor\\bin\\ffmpeg\\windows\\bin\\ffmpeg\" -apply_trc gamma22 -start_number 991 -framerate 25.0 -to 1.2000000000 -i \"c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr\\modelMain.%04d.jpg\" -pix_fmt yuv420p -crf 18 -intra -y \"c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr\\modelMain_h264.mp4\"", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractReview", - "threadName": "MainThread", - "msecs": 203.00006866455078, - "filename": "execute.py", - "levelno": 30, - "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\lib\\execute.py", - "lineno": 126, - "msg": "ffmpeg version 4.4-full_build-www.gyan.dev Copyright (c) 2000-2021 the FFmpeg developers\r\n built with gcc 10.2.0 (Rev6, Built by MSYS2 project)\r\n configuration: --enable-gpl --enable-version3 --enable-static --disable-w32threads --disable-autodetect --enable-fontconfig --enable-iconv --enable-gnutls --enable-libxml2 --enable-gmp --enable-lzma --enable-libsnappy --enable-zlib --enable-librist --enable-libsrt --enable-libssh --enable-libzmq --enable-avisynth --enable-libbluray --enable-libcaca --enable-sdl2 --enable-libdav1d --enable-libzvbi --enable-librav1e --enable-libsvtav1 --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxvid --enable-libaom --enable-libopenjpeg --enable-libvpx --enable-libass --enable-frei0r --enable-libfreetype --enable-libfribidi --enable-libvidstab --enable-libvmaf --enable-libzimg --enable-amf --enable-cuda-llvm --enable-cuvid --enable-ffnvcodec --enable-nvdec --enable-nvenc --enable-d3d11va --enable-dxva2 --enable-libmfx --enable-libglslang --enable-vulkan --enable-opencl --enable-libcdio --enable-libgme --enable-libmodplug --enable-libopenmpt --enable-libopencore-amrwb --enable-libmp3lame --enable-libshine --enable-libtheora --enable-libtwolame --enable-libvo-amrwbenc --enable-libilbc --enable-libgsm --enable-libopencore-amrnb --enable-libopus --enable-libspeex --enable-libvorbis --enable-ladspa --enable-libbs2b --enable-libflite --enable-libmysofa --enable-librubberband --enable-libsoxr --enable-chromaprint\r\n libavutil 56. 70.100 / 56. 70.100\r\n libavcodec 58.134.100 / 58.134.100\r\n libavformat 58. 76.100 / 58. 76.100\r\n libavdevice 58. 13.100 / 58. 13.100\r\n libavfilter 7.110.100 / 7.110.100\r\n libswscale 5. 9.100 / 5. 9.100\r\n libswresample 3. 9.100 / 3. 9.100\r\n libpostproc 55. 9.100 / 55. 9.100\r\nInput #0, image2, from 'c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr\\modelMain.%04d.jpg':\r\n Duration: 00:00:01.20, start: 0.000000, bitrate: N/A\r\n Stream #0:0: Video: mjpeg (Baseline), yuvj420p(pc, bt470bg/unknown/unknown), 1920x1080, 25 fps, 25 tbr, 25 tbn, 25 tbc\r\nCodec AVOption apply_trc (color transfer characteristics to apply to EXR linear input) specified for input file #0 (c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr\\modelMain.%04d.jpg) has not been used for any stream. The most likely reason is either wrong type (e.g. a video option with no video streams) or that it is a private option of some decoder which was not actually used for any stream.\r\nStream mapping:\r\n Stream #0:0 -> #0:0 (mjpeg (native) -> h264 (libx264))\r\nPress [q] to stop, [?] for help\r\n[swscaler @ 00000244426a5380] deprecated pixel format used, make sure you did set range correctly\r\n[libx264 @ 000002444205e140] using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX FMA3 BMI2 AVX2\r\n[libx264 @ 000002444205e140] profile High, level 4.0, 4:2:0, 8-bit\r\n[libx264 @ 000002444205e140] 264 - core 161 r3048 b86ae3c - H.264/MPEG-4 AVC codec - Copyleft 2003-2021 - http://www.videolan.org/x264.html - options: cabac=1 ref=1 deblock=1:0:0 analyse=0x3:0x113 me=hex subme=7 psy=1 psy_rd=1.00:0.00 mixed_ref=0 me_range=16 chroma_me=1 trellis=1 8x8dct=1 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=-2 threads=24 lookahead_threads=4 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=0 weightp=0 keyint=1 keyint_min=1 scenecut=40 intra_refresh=0 rc=crf mbtree=0 crf=18.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 ip_ratio=1.40 aq=1:1.00\r\nOutput #0, mp4, to 'c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr\\modelMain_h264.mp4':\r\n Metadata:\r\n encoder : Lavf58.76.100\r\n Stream #0:0: Video: h264 (avc1 / 0x31637661), yuv420p(tv, bt470bg/unknown/unknown, progressive), 1920x1080, q=2-31, 25 fps, 12800 tbn\r\n Metadata:\r\n encoder : Lavc58.134.100 libx264\r\n Side data:\r\n cpb: bitrate max/min/avg: 0/0/0 buffer size: 0 vbv_delay: N/A\r\nframe= 1 fps=0.0 q=0.0 size= 0kB time=00:00:00.00 bitrate=N/A speed= 0x \rframe= 30 fps=0.0 q=-1.0 Lsize= 47kB time=00:00:01.16 bitrate= 332.6kbits/s speed=3.74x \r\nvideo:46kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 1.998477%\r\n[libx264 @ 000002444205e140] frame I:30 Avg QP: 1.05 size: 1556\r\n[libx264 @ 000002444205e140] mb I I16..4: 99.5% 0.2% 0.4%\r\n[libx264 @ 000002444205e140] 8x8 transform intra:0.2%\r\n[libx264 @ 000002444205e140] coded y,uvDC,uvAC intra: 0.4% 0.0% 0.0%\r\n[libx264 @ 000002444205e140] i16 v,h,dc,p: 98% 0% 1% 0%\r\n[libx264 @ 000002444205e140] i8 v,h,dc,ddl,ddr,vr,hd,vl,hu: 4% 40% 4% 10% 15% 6% 8% 6% 8%\r\n[libx264 @ 000002444205e140] i4 v,h,dc,ddl,ddr,vr,hd,vl,hu: 9% 31% 18% 5% 4% 5% 14% 5% 8%\r\n[libx264 @ 000002444205e140] i8c dc,h,v,p: 100% 0% 0% 0%\r\n[libx264 @ 000002444205e140] kb/s:311.17\r\n", - "exc_info": null, - "type": "record", - "levelname": "WARNING" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractReview", - "threadName": "MainThread", - "msecs": 217.00000762939453, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 251, - "msg": "Adding new representation: {'files': 'modelMain_h264.mp4', 'outputName': u'h264', 'resolutionHeight': 1080, 'name': u'h264', 'frameStart': 991, 'outputDef': {u'bg_color': [0, 0, 0, 0], u'tags': [u'burnin', u'ftrackreview'], u'height': 0, u'filter': {u'families': [u'render', u'review', u'ftrack']}, u'width': 0, u'ext': u'mp4', u'overscan_color': [0, 0, 0, 255], u'letter_box': {u'line_color': [255, 0, 0, 255], u'ratio': 0.0, u'fill_color': [0, 0, 0, 255], u'enabled': False, u'state': u'letterbox', u'line_thickness': 0}, 'filename_suffix': u'h264', u'overscan_crop': u'', u'ffmpeg_args': {u'video_filters': [], u'input': [u'-apply_trc gamma22'], u'output': [u'-pix_fmt yuv420p', u'-crf 18', u'-intra'], u'audio_filters': []}}, 'camera_name': u'persp_CAM', 'tags': ['review', u'burnin', u'ftrackreview'], 'stagingDir': 'c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr', 'frameEnd': 1020, 'ext': u'mp4', 'resolutionWidth': 1920, 'fps': 25.0, 'frameStartFtrack': 991, 'frameEndFtrack': 1020}", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.ExtractReview", - "threadName": "MainThread", - "msecs": 219.00010108947754, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 136, - "msg": "Repre: ma - Didn't found \"review\" in tags. Skipping", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - } - ] - } - ], - "label": "Extract Review", - "skipped": false, - "order": 2.02, - "name": "ExtractReview" - }, - { - "instances_data": [], - "label": "Review with Slate frame", - "skipped": true, - "order": 2.031, - "name": "ExtractReviewSlate" - }, - { - "instances_data": [], - "label": "Ass Proxy (Maya ASCII)", - "skipped": true, - "order": 2.2, - "name": "ExtractAssProxy" - }, - { - "instances_data": [], - "label": "Extract Look (Maya ASCII + JSON)", - "skipped": true, - "order": 2.2, - "name": "ExtractLook" - }, - { - "instances_data": [], - "label": "Save current file", - "skipped": true, - "order": 2.51, - "name": "SaveCurrentScene" - }, - { - "instances_data": [], - "label": "Integrate Resources Path", - "skipped": true, - "order": 2.95, - "name": "IntegrateResourcesPath" - }, - { - "instances_data": [ - { - "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "logs": [ - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 318.00007820129395, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 183, - "msg": "Establishing staging directory @ c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "openpype.lib.profiles_filtering", - "threadName": "MainThread", - "msecs": 319.99993324279785, - "filename": "profiles_filtering.py", - "levelno": 20, - "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\lib\\profiles_filtering.py", - "lineno": 31, - "msg": "Search for first most matching profile in match order: Host name -> Task name -> Family.", - "exc_info": null, - "type": "record", - "levelname": "INFO" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 322.00002670288086, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 199, - "msg": "Next version: v7", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 322.9999542236328, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 846, - "msg": "Source: {root[work]}/kuba_each_case/Assets/Alpaca_01/work/animation/kuba_each_case_Alpaca_01_animation_v007.ma", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 323.99988174438477, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 215, - "msg": "Creating version ...", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 332.0000171661377, - "filename": "profiles_filtering.py", - "levelno": 10, - "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\lib\\profiles_filtering.py", - "lineno": 168, - "msg": "\"families\" not found in [u'review', u'render', u'prerender']", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 332.99994468688965, - "filename": "profiles_filtering.py", - "levelno": 20, - "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\lib\\profiles_filtering.py", - "lineno": 31, - "msg": "Search for first most matching profile in match order: Host name -> Task name -> Family.", - "exc_info": null, - "type": "record", - "levelname": "INFO" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 351.99999809265137, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 506, - "msg": "__ dst: C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007.jpg", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 352.9999256134033, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 563, - "msg": "Integrating source files to destination ...", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 354.0000915527344, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 652, - "msg": "Copying file ... c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr\\modelMain.1001.jpg -> C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007.jpg.tmp", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 355.9999465942383, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 566, - "msg": "Integrated files {'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg.tmp': 36385L}", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 357.00011253356934, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 569, - "msg": "Preparing files information ...", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 358.0000400543213, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 930, - "msg": "get_resource_files_info.resources:[[u'c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr\\\\modelMain.1001.jpg', 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg']]", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 359.9998950958252, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 574, - "msg": "__ representation: {'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/v007/kuba_each_case_Alpaca_01_modelMain_v007.jpg', '_id': ObjectId('6125fdf9839e8042d68dbd30'), 'hash': 'kuba_each_case_Alpaca_01_modelMain_v007,jpg|1629879799,25|36385', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 360000)}], 'size': 36385L}], 'context': {'subset': u'modelMain', 'username': 'jakub.trllo', 'task': 'animation', 'family': u'model', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'jpg', 'version': 7, 'asset': u'Alpaca_01', 'representation': 'jpg', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': 'thumbnail', 'parent': ObjectId('6125fdf9839e8042d68dbd2d'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd2e'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 361.9999885559082, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 576, - "msg": "__ destination_list: ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg']", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 362.99991607666016, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 584, - "msg": "__ representations: [{'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/v007/kuba_each_case_Alpaca_01_modelMain_v007.jpg', '_id': ObjectId('6125fdf9839e8042d68dbd30'), 'hash': 'kuba_each_case_Alpaca_01_modelMain_v007,jpg|1629879799,25|36385', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 360000)}], 'size': 36385L}], 'context': {'subset': u'modelMain', 'username': 'jakub.trllo', 'task': 'animation', 'family': u'model', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'jpg', 'version': 7, 'asset': u'Alpaca_01', 'representation': 'jpg', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': 'thumbnail', 'parent': ObjectId('6125fdf9839e8042d68dbd2d'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd2e'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}]", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 384.0000629425049, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 506, - "msg": "__ dst: C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007.abc", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 384.99999046325684, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 563, - "msg": "Integrating source files to destination ...", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 385.9999179840088, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 652, - "msg": "Copying file ... c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr\\modelMain.abc -> C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007.abc.tmp", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 388.0000114440918, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 566, - "msg": "Integrated files {'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.abc.tmp': 25170L, 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg.tmp': 36385L}", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 388.99993896484375, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 569, - "msg": "Preparing files information ...", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 388.99993896484375, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 930, - "msg": "get_resource_files_info.resources:[['c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr\\\\modelMain.abc', 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.abc']]", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 391.00003242492676, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 574, - "msg": "__ representation: {'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/v007/kuba_each_case_Alpaca_01_modelMain_v007.abc', '_id': ObjectId('6125fdf9839e8042d68dbd33'), 'hash': 'kuba_each_case_Alpaca_01_modelMain_v007,abc|1629879798,63|25170', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 391000)}], 'size': 25170L}], 'context': {'subset': u'modelMain', 'username': 'jakub.trllo', 'task': 'animation', 'family': u'model', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'abc', 'version': 7, 'asset': u'Alpaca_01', 'representation': 'abc', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': 'abc', 'parent': ObjectId('6125fdf9839e8042d68dbd2d'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.abc', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd31'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 394.0000534057617, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 576, - "msg": "__ destination_list: ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg', 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.abc']", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 394.9999809265137, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 584, - "msg": "__ representations: [{'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/v007/kuba_each_case_Alpaca_01_modelMain_v007.jpg', '_id': ObjectId('6125fdf9839e8042d68dbd30'), 'hash': 'kuba_each_case_Alpaca_01_modelMain_v007,jpg|1629879799,25|36385', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 360000)}], 'size': 36385L}], 'context': {'subset': u'modelMain', 'username': 'jakub.trllo', 'task': 'animation', 'family': u'model', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'jpg', 'version': 7, 'asset': u'Alpaca_01', 'representation': 'jpg', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': 'thumbnail', 'parent': ObjectId('6125fdf9839e8042d68dbd2d'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd2e'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}, {'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/v007/kuba_each_case_Alpaca_01_modelMain_v007.abc', '_id': ObjectId('6125fdf9839e8042d68dbd33'), 'hash': 'kuba_each_case_Alpaca_01_modelMain_v007,abc|1629879798,63|25170', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 391000)}], 'size': 25170L}], 'context': {'subset': u'modelMain', 'username': 'jakub.trllo', 'task': 'animation', 'family': u'model', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'abc', 'version': 7, 'asset': u'Alpaca_01', 'representation': 'abc', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': 'abc', 'parent': ObjectId('6125fdf9839e8042d68dbd2d'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.abc', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd31'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}]", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 418.99991035461426, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 506, - "msg": "__ dst: C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007.ma", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 420.0000762939453, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 563, - "msg": "Integrating source files to destination ...", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 420.0000762939453, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 652, - "msg": "Copying file ... c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr\\modelMain.ma -> C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007.ma.tmp", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 423.0000972747803, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 566, - "msg": "Integrated files {'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.ma.tmp': 52747L, 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.abc.tmp': 25170L, 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg.tmp': 36385L}", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 424.0000247955322, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 569, - "msg": "Preparing files information ...", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 424.0000247955322, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 930, - "msg": "get_resource_files_info.resources:[['c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr\\\\modelMain.ma', 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.ma']]", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 427.0000457763672, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 574, - "msg": "__ representation: {'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/v007/kuba_each_case_Alpaca_01_modelMain_v007.ma', '_id': ObjectId('6125fdf9839e8042d68dbd36'), 'hash': 'kuba_each_case_Alpaca_01_modelMain_v007,ma|1629879800,33|52747', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 426000)}], 'size': 52747L}], 'context': {'subset': u'modelMain', 'username': 'jakub.trllo', 'task': 'animation', 'family': u'model', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'ma', 'version': 7, 'asset': u'Alpaca_01', 'representation': u'ma', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': u'ma', 'parent': ObjectId('6125fdf9839e8042d68dbd2d'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.ma', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd34'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 428.9999008178711, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 576, - "msg": "__ destination_list: ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg', 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.abc', 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.ma']", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 430.9999942779541, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 584, - "msg": "__ representations: [{'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/v007/kuba_each_case_Alpaca_01_modelMain_v007.jpg', '_id': ObjectId('6125fdf9839e8042d68dbd30'), 'hash': 'kuba_each_case_Alpaca_01_modelMain_v007,jpg|1629879799,25|36385', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 360000)}], 'size': 36385L}], 'context': {'subset': u'modelMain', 'username': 'jakub.trllo', 'task': 'animation', 'family': u'model', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'jpg', 'version': 7, 'asset': u'Alpaca_01', 'representation': 'jpg', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': 'thumbnail', 'parent': ObjectId('6125fdf9839e8042d68dbd2d'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd2e'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}, {'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/v007/kuba_each_case_Alpaca_01_modelMain_v007.abc', '_id': ObjectId('6125fdf9839e8042d68dbd33'), 'hash': 'kuba_each_case_Alpaca_01_modelMain_v007,abc|1629879798,63|25170', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 391000)}], 'size': 25170L}], 'context': {'subset': u'modelMain', 'username': 'jakub.trllo', 'task': 'animation', 'family': u'model', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'abc', 'version': 7, 'asset': u'Alpaca_01', 'representation': 'abc', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': 'abc', 'parent': ObjectId('6125fdf9839e8042d68dbd2d'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.abc', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd31'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}, {'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/v007/kuba_each_case_Alpaca_01_modelMain_v007.ma', '_id': ObjectId('6125fdf9839e8042d68dbd36'), 'hash': 'kuba_each_case_Alpaca_01_modelMain_v007,ma|1629879800,33|52747', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 426000)}], 'size': 52747L}], 'context': {'subset': u'modelMain', 'username': 'jakub.trllo', 'task': 'animation', 'family': u'model', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'ma', 'version': 7, 'asset': u'Alpaca_01', 'representation': u'ma', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': u'ma', 'parent': ObjectId('6125fdf9839e8042d68dbd2d'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.ma', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd34'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}]", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 456.00008964538574, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 506, - "msg": "__ dst: C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 457.0000171661377, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 563, - "msg": "Integrating source files to destination ...", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 457.99994468688965, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 652, - "msg": "Copying file ... c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr\\modelMain_h264.mp4 -> C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4.tmp", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 460.00003814697266, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 566, - "msg": "Integrated files {'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4.tmp': 48231L, 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.ma.tmp': 52747L, 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.abc.tmp': 25170L, 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg.tmp': 36385L}", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 460.9999656677246, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 569, - "msg": "Preparing files information ...", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 461.99989318847656, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 930, - "msg": "get_resource_files_info.resources:[['c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr\\\\modelMain_h264.mp4', 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4']]", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 463.99998664855957, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 574, - "msg": "__ representation: {'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/v007/kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4', '_id': ObjectId('6125fdf9839e8042d68dbd39'), 'hash': 'kuba_each_case_Alpaca_01_modelMain_v007_h264,mp4|1629879801,18|48231', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 464000)}], 'size': 48231L}], 'context': {'subset': u'modelMain', 'username': 'jakub.trllo', 'task': 'animation', 'family': u'model', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'mp4', 'version': 7, 'asset': u'Alpaca_01', 'representation': u'mp4', 'output': u'h264', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': u'h264', 'parent': ObjectId('6125fdf9839e8042d68dbd2d'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd37'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 470.99995613098145, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 576, - "msg": "__ destination_list: ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg', 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.abc', 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.ma', 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4']", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 471.9998836517334, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 584, - "msg": "__ representations: [{'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/v007/kuba_each_case_Alpaca_01_modelMain_v007.jpg', '_id': ObjectId('6125fdf9839e8042d68dbd30'), 'hash': 'kuba_each_case_Alpaca_01_modelMain_v007,jpg|1629879799,25|36385', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 360000)}], 'size': 36385L}], 'context': {'subset': u'modelMain', 'username': 'jakub.trllo', 'task': 'animation', 'family': u'model', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'jpg', 'version': 7, 'asset': u'Alpaca_01', 'representation': 'jpg', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': 'thumbnail', 'parent': ObjectId('6125fdf9839e8042d68dbd2d'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd2e'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}, {'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/v007/kuba_each_case_Alpaca_01_modelMain_v007.abc', '_id': ObjectId('6125fdf9839e8042d68dbd33'), 'hash': 'kuba_each_case_Alpaca_01_modelMain_v007,abc|1629879798,63|25170', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 391000)}], 'size': 25170L}], 'context': {'subset': u'modelMain', 'username': 'jakub.trllo', 'task': 'animation', 'family': u'model', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'abc', 'version': 7, 'asset': u'Alpaca_01', 'representation': 'abc', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': 'abc', 'parent': ObjectId('6125fdf9839e8042d68dbd2d'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.abc', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd31'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}, {'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/v007/kuba_each_case_Alpaca_01_modelMain_v007.ma', '_id': ObjectId('6125fdf9839e8042d68dbd36'), 'hash': 'kuba_each_case_Alpaca_01_modelMain_v007,ma|1629879800,33|52747', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 426000)}], 'size': 52747L}], 'context': {'subset': u'modelMain', 'username': 'jakub.trllo', 'task': 'animation', 'family': u'model', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'ma', 'version': 7, 'asset': u'Alpaca_01', 'representation': u'ma', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': u'ma', 'parent': ObjectId('6125fdf9839e8042d68dbd2d'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.ma', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd34'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}, {'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/v007/kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4', '_id': ObjectId('6125fdf9839e8042d68dbd39'), 'hash': 'kuba_each_case_Alpaca_01_modelMain_v007_h264,mp4|1629879801,18|48231', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 464000)}], 'size': 48231L}], 'context': {'subset': u'modelMain', 'username': 'jakub.trllo', 'task': 'animation', 'family': u'model', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'mp4', 'version': 7, 'asset': u'Alpaca_01', 'representation': u'mp4', 'output': u'h264', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': u'h264', 'parent': ObjectId('6125fdf9839e8042d68dbd2d'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd37'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}]", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 480.9999465942383, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 594, - "msg": "__ rep: {'files': u'modelMain.1001.jpg', 'publishedFiles': ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg'], 'name': 'thumbnail', 'published_path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg', 'stagingDir': 'c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr', 'ext': 'jpg', 'thumbnail': True}", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 483.0000400543213, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 594, - "msg": "__ rep: {'files': 'modelMain.abc', 'publishedFiles': ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.abc'], 'name': 'abc', 'published_path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.abc', 'stagingDir': 'c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr', 'ext': 'abc'}", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 483.99996757507324, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 594, - "msg": "__ rep: {'files': 'modelMain.ma', 'publishedFiles': ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.ma'], 'name': u'ma', 'published_path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.ma', 'stagingDir': 'c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr', 'ext': u'ma'}", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 484.9998950958252, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 594, - "msg": "__ rep: {'files': 'modelMain_h264.mp4', 'outputName': u'h264', 'resolutionHeight': 1080, 'name': u'h264', 'published_path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4', 'frameStart': 991, 'outputDef': {u'bg_color': [0, 0, 0, 0], u'tags': [u'burnin', u'ftrackreview'], u'height': 0, u'filter': {u'families': [u'render', u'review', u'ftrack']}, u'width': 0, u'ext': u'mp4', u'overscan_color': [0, 0, 0, 255], u'letter_box': {u'line_color': [255, 0, 0, 255], u'ratio': 0.0, u'fill_color': [0, 0, 0, 255], u'enabled': False, u'state': u'letterbox', u'line_thickness': 0}, 'filename_suffix': u'h264', u'overscan_crop': u'', u'ffmpeg_args': {u'video_filters': [], u'input': [u'-apply_trc gamma22'], u'output': [u'-pix_fmt yuv420p', u'-crf 18', u'-intra'], u'audio_filters': []}}, 'camera_name': u'persp_CAM', 'tags': ['review', u'burnin', u'ftrackreview'], 'stagingDir': 'c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr', 'frameEnd': 1020, 'ext': u'mp4', 'resolutionWidth': 1920, 'fps': 25.0, 'publishedFiles': ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4'], 'frameStartFtrack': 991, 'frameEndFtrack': 1020}", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 509.0000629425049, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 601, - "msg": "Registered 4 items", - "exc_info": null, - "type": "record", - "levelname": "INFO" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 509.99999046325684, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 124, - "msg": "Integrated Asset in to the database ...", - "exc_info": null, - "type": "record", - "levelname": "INFO" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 510.9999179840088, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 125, - "msg": "instance.data: {u'subset': u'modelMain', 'versionEntity': {u'name': 7, u'parent': ObjectId('610124ffb5db38f84962c00e'), u'type': u'version', u'_id': ObjectId('6125fdf9839e8042d68dbd2d'), u'data': {u'comment': u'', u'families': [u'model', u'model', u'review'], u'frameStart': 1001, u'author': u'jakub.trllo', u'step': 1.0, u'frameEnd': 1010, u'machine': u'014-BAILEYS', u'source': u'{root[work]}/kuba_each_case/Assets/Alpaca_01/work/animation/kuba_each_case_Alpaca_01_animation_v007.ma', u'handles': None, u'fps': 25.0, u'time': u'20210825T102314Z'}, u'schema': u'openpype:version-3.0'}, 'representations': [{'files': u'modelMain.1001.jpg', 'publishedFiles': ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg'], 'name': 'thumbnail', 'published_path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg', 'stagingDir': 'c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr', 'ext': 'jpg', 'thumbnail': True}, {'files': 'modelMain.abc', 'publishedFiles': ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.abc'], 'name': 'abc', 'published_path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.abc', 'stagingDir': 'c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr', 'ext': 'abc'}, {'files': 'modelMain.ma', 'publishedFiles': ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.ma'], 'name': u'ma', 'published_path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.ma', 'stagingDir': 'c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr', 'ext': u'ma'}, {'files': 'modelMain_h264.mp4', 'outputName': u'h264', 'resolutionHeight': 1080, 'name': u'h264', 'published_path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4', 'frameStart': 991, 'outputDef': {u'bg_color': [0, 0, 0, 0], u'tags': [u'burnin', u'ftrackreview'], u'height': 0, u'filter': {u'families': [u'render', u'review', u'ftrack']}, u'width': 0, u'ext': u'mp4', u'overscan_color': [0, 0, 0, 255], u'letter_box': {u'line_color': [255, 0, 0, 255], u'ratio': 0.0, u'fill_color': [0, 0, 0, 255], u'enabled': False, u'state': u'letterbox', u'line_thickness': 0}, 'filename_suffix': u'h264', u'overscan_crop': u'', u'ffmpeg_args': {u'video_filters': [], u'input': [u'-apply_trc gamma22'], u'output': [u'-pix_fmt yuv420p', u'-crf 18', u'-intra'], u'audio_filters': []}}, 'camera_name': u'persp_CAM', 'tags': ['review', u'burnin', u'ftrackreview'], 'stagingDir': 'c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr', 'frameEnd': 1020, 'ext': u'mp4', 'resolutionWidth': 1920, 'fps': 25.0, 'publishedFiles': ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4'], 'frameStartFtrack': 991, 'frameEndFtrack': 1020}], 'stagingDir': 'c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr', 'families': [u'model', 'review'], 'fps': 25.0, 'family': u'model', 'frameStart': 1001, 'published_representations': {ObjectId('6125fdf9839e8042d68dbd34'): {'representation': {'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/v007/kuba_each_case_Alpaca_01_modelMain_v007.ma', '_id': ObjectId('6125fdf9839e8042d68dbd36'), 'hash': 'kuba_each_case_Alpaca_01_modelMain_v007,ma|1629879800,33|52747', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 426000)}], 'size': 52747L}], 'context': {'subset': u'modelMain', 'username': 'jakub.trllo', 'task': 'animation', 'family': u'model', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'ma', 'version': 7, 'asset': u'Alpaca_01', 'representation': u'ma', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': u'ma', 'parent': ObjectId('6125fdf9839e8042d68dbd2d'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.ma', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd34'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}, 'published_files': ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.ma'], 'anatomy_data': {'subset': u'modelMain', 'family': u'model', 'hierarchy': u'Assets', 'app': 'maya', 'yyyy': '2021', 'HH': '10', 'version': 7, 'asset': u'Alpaca_01', 'fps': 25.0, 'ddd': 'Wed', 'username': 'jakub.trllo', 'H': '10', 'dd': '25', 'M': '23', 'ht': 'AM', 'mmmm': 'August', 'S': '14', 'dddd': 'Wednesday', 'mmm': 'Aug', 'task': 'animation', 'd': '25', 'mm': '08', 'h': '10', 'm': '8', 'hh': '10', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'SS': '14', 'ext': u'ma', 'yy': '21', 'representation': u'ma', 'MM': '23'}}, ObjectId('6125fdf9839e8042d68dbd37'): {'representation': {'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/v007/kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4', '_id': ObjectId('6125fdf9839e8042d68dbd39'), 'hash': 'kuba_each_case_Alpaca_01_modelMain_v007_h264,mp4|1629879801,18|48231', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 464000)}], 'size': 48231L}], 'context': {'subset': u'modelMain', 'username': 'jakub.trllo', 'task': 'animation', 'family': u'model', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'mp4', 'version': 7, 'asset': u'Alpaca_01', 'representation': u'mp4', 'output': u'h264', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': u'h264', 'parent': ObjectId('6125fdf9839e8042d68dbd2d'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd37'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}, 'published_files': ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4'], 'anatomy_data': {'subset': u'modelMain', 'resolution_width': 1920, 'family': u'model', 'hierarchy': u'Assets', 'app': 'maya', 'yyyy': '2021', 'HH': '10', 'version': 7, 'asset': u'Alpaca_01', 'fps': 25.0, 'ddd': 'Wed', 'username': 'jakub.trllo', 'H': '10', 'dd': '25', 'M': '23', 'ht': 'AM', 'mmmm': 'August', 'S': '14', 'dddd': 'Wednesday', 'representation': u'mp4', 'mmm': 'Aug', 'task': 'animation', 'd': '25', 'mm': '08', 'h': '10', 'm': '8', 'hh': '10', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'SS': '14', 'ext': u'mp4', 'resolution_height': 1080, 'yy': '21', 'output': u'h264', 'MM': '23'}}, ObjectId('6125fdf9839e8042d68dbd2e'): {'representation': {'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/v007/kuba_each_case_Alpaca_01_modelMain_v007.jpg', '_id': ObjectId('6125fdf9839e8042d68dbd30'), 'hash': 'kuba_each_case_Alpaca_01_modelMain_v007,jpg|1629879799,25|36385', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 360000)}], 'size': 36385L}], 'context': {'subset': u'modelMain', 'username': 'jakub.trllo', 'task': 'animation', 'family': u'model', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'jpg', 'version': 7, 'asset': u'Alpaca_01', 'representation': 'jpg', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': 'thumbnail', 'parent': ObjectId('6125fdf9839e8042d68dbd2d'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd2e'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}, 'published_files': ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg'], 'anatomy_data': {'subset': u'modelMain', 'family': u'model', 'hierarchy': u'Assets', 'app': 'maya', 'yyyy': '2021', 'HH': '10', 'version': 7, 'asset': u'Alpaca_01', 'fps': 25.0, 'ddd': 'Wed', 'username': 'jakub.trllo', 'H': '10', 'dd': '25', 'M': '23', 'ht': 'AM', 'mmmm': 'August', 'S': '14', 'dddd': 'Wednesday', 'mmm': 'Aug', 'task': 'animation', 'd': '25', 'mm': '08', 'h': '10', 'm': '8', 'hh': '10', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'SS': '14', 'ext': 'jpg', 'yy': '21', 'representation': 'jpg', 'MM': '23'}}, ObjectId('6125fdf9839e8042d68dbd31'): {'representation': {'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/v007/kuba_each_case_Alpaca_01_modelMain_v007.abc', '_id': ObjectId('6125fdf9839e8042d68dbd33'), 'hash': 'kuba_each_case_Alpaca_01_modelMain_v007,abc|1629879798,63|25170', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 391000)}], 'size': 25170L}], 'context': {'subset': u'modelMain', 'username': 'jakub.trllo', 'task': 'animation', 'family': u'model', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'abc', 'version': 7, 'asset': u'Alpaca_01', 'representation': 'abc', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': 'abc', 'parent': ObjectId('6125fdf9839e8042d68dbd2d'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.abc', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd31'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}, 'published_files': ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.abc'], 'anatomy_data': {'subset': u'modelMain', 'family': u'model', 'hierarchy': u'Assets', 'app': 'maya', 'yyyy': '2021', 'HH': '10', 'version': 7, 'asset': u'Alpaca_01', 'fps': 25.0, 'ddd': 'Wed', 'username': 'jakub.trllo', 'H': '10', 'dd': '25', 'M': '23', 'ht': 'AM', 'mmmm': 'August', 'S': '14', 'dddd': 'Wednesday', 'mmm': 'Aug', 'task': 'animation', 'd': '25', 'mm': '08', 'h': '10', 'm': '8', 'hh': '10', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'SS': '14', 'ext': 'abc', 'yy': '21', 'representation': 'abc', 'MM': '23'}}}, 'projectEntity': {u'name': u'kuba_each_case', u'parent': None, u'type': u'project', u'data': {u'code': u'kuba_each_case', u'resolutionHeight': 1080, u'library_project': False, u'clipIn': 1001, u'frameStart': 1001, u'handleStart': 10, u'entityType': u'Project', u'frameEnd': 1100, u'tools_env': [], u'pixelAspect': 1.0, u'ftrackId': u'38c5fec4-0aed-11ea-a454-3e41ec9bc0d6', u'resolutionWidth': 1920, u'fps': 25.0, u'handleEnd': 10, u'clipOut': 1100}, u'_id': ObjectId('5eb950f9e3c2c3183455f266'), u'config': {u'templates': {u'hero': {u'path': u'{@folder}/{@file}', u'folder': u'{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/hero', u'file': u'{project[code]}_{asset}_{subset}_hero<_{output}><.{frame}>.{ext}'}, u'render': {u'path': u'{@folder}/{@file}', u'folder': u'{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/{@version}', u'file': u'{project[code]}_{asset}_{subset}_{@version}<_{output}><.{@frame}>.{ext}'}, u'work': {u'path': u'{@folder}/{@file}', u'folder': u'{root[work]}/{project[name]}/{hierarchy}/{asset}/work/{task}', u'file': u'{project[code]}_{asset}_{task}_{@version}<_{comment}>.{ext}'}, u'publish': {u'path': u'{@folder}/{@file}', u'folder': u'{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/{@version}', u'thumbnail': u'{thumbnail_root}/{project[name]}/{_id}_{thumbnail_type}.{ext}', u'file': u'{project[code]}_{asset}_{subset}_{@version}<_{output}><.{@frame}><_{udim}>.{ext}'}, u'delivery': {}, u'defaults': {u'frame_padding': 4, u'version_padding': 3, u'frame': u'{frame:0>{@frame_padding}}', u'version': u'v{version:0>{@version_padding}}'}, u'others': {}}, u'tasks': {u'Previz': {u'short_name': u''}, u'Lookdev': {u'short_name': u'look'}, u'Edit': {u'short_name': u'edit'}, u'Tracking': {u'short_name': u''}, u'Layout': {u'short_name': u'lay'}, u'Art': {u'short_name': u'art'}, u'Paint': {u'short_name': u'paint'}, u'Generic': {u'short_name': u'gener'}, u'Texture': {u'short_name': u'tex'}, u'Setdress': {u'short_name': u'dress'}, u'Animation': {u'short_name': u'anim'}, u'Lighting': {u'short_name': u'lgt'}, u'Compositing': {u'short_name': u'comp'}, u'Modeling': {u'short_name': u'mdl'}, u'Rigging': {u'short_name': u'rig'}, u'FX': {u'short_name': u'fx'}}, u'imageio': {u'hiero': {u'workfile': {u'eightBitLut': u'sRGB', u'viewerLut': u'sRGB', u'workingSpace': u'linear', u'ocioconfigpath': {u'windows': [], u'darwin': [], u'linux': []}, u'ocioConfigName': u'nuke-default', u'thumbnailLut': u'sRGB', u'floatLut': u'linear', u'sixteenBitLut': u'sRGB', u'logLut': u'Cineon'}, u'regexInputs': {u'inputs': [{u'regex': u'[^-a-zA-Z0-9](plateRef).*(?=mp4)', u'colorspace': u'sRGB'}]}}, u'nuke': {u'viewer': {u'viewerProcess': u'sRGB'}, u'workfile': {u'floatLut': u'linear', u'workingSpaceLUT': u'linear', u'customOCIOConfigPath': {u'windows': [], u'darwin': [], u'linux': []}, u'int16Lut': u'sRGB', u'logLut': u'Cineon', u'int8Lut': u'sRGB', u'colorManagement': u'Nuke', u'OCIO_config': u'nuke-default', u'monitorLut': u'sRGB'}, u'nodes': {u'customNodes': [], u'requiredNodes': [{u'nukeNodeClass': u'Write', u'knobs': [{u'name': u'file_type', u'value': u'exr'}, {u'name': u'datatype', u'value': u'16 bit half'}, {u'name': u'compression', u'value': u'Zip (1 scanline)'}, {u'name': u'autocrop', u'value': u'True'}, {u'name': u'tile_color', u'value': u'0xff0000ff'}, {u'name': u'channels', u'value': u'rgb'}, {u'name': u'colorspace', u'value': u'linear'}, {u'name': u'create_directories', u'value': u'True'}], u'plugins': [u'CreateWriteRender']}, {u'nukeNodeClass': u'Write', u'knobs': [{u'name': u'file_type', u'value': u'exr'}, {u'name': u'datatype', u'value': u'16 bit half'}, {u'name': u'compression', u'value': u'Zip (1 scanline)'}, {u'name': u'autocrop', u'value': u'False'}, {u'name': u'tile_color', u'value': u'0xadab1dff'}, {u'name': u'channels', u'value': u'rgb'}, {u'name': u'colorspace', u'value': u'linear'}, {u'name': u'create_directories', u'value': u'True'}], u'plugins': [u'CreateWritePrerender']}]}, u'regexInputs': {u'inputs': [{u'regex': u'[^-a-zA-Z0-9]beauty[^-a-zA-Z0-9]', u'colorspace': u'linear'}]}}}, u'apps': [{u'name': u'blender/2-91'}, {u'name': u'hiero/11-3'}, {u'name': u'houdini/18-5'}, {u'name': u'maya/2020'}, {u'name': u'nuke/11-3'}, {u'name': u'nukestudio/11-3'}], u'roots': {u'work': {u'windows': u'C:/projects', u'darwin': u'/Volumes/path', u'linux': u'/mnt/share/projects'}}, u'schema': u'openpype:config-2.0'}, u'schema': u'openpype:project-3.0'}, 'version': 7, u'variant': u'Main', u'active': True, 'step': 1.0, 'review_camera': u'|persp_CAM|persp_CAMShape', 'frameEnd': 1010, u'attrPrefix': u'', u'cbId': u'5dd50967e3c2c32b004b4817:bc3d17f26cde', 'publishDir': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007', 'latestVersion': 6, u'writeColorSets': False, 'anatomyData': {'subset': u'modelMain', 'family': u'model', 'hierarchy': u'Assets', 'app': 'maya', 'yyyy': '2021', 'HH': '10', 'version': 7, 'asset': u'Alpaca_01', 'fps': 25.0, 'ddd': 'Wed', 'username': 'jakub.trllo', 'mm': '08', 'H': '10', 'dd': '25', 'M': '23', 'ht': 'AM', 'mmmm': 'August', 'S': '14', 'dddd': 'Wednesday', 'mmm': 'Aug', 'task': 'animation', 'd': '25', 'SS': '14', 'h': '10', 'm': '8', 'hh': '10', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'MM': '23', 'yy': '21'}, u'id': u'pyblish.avalon.instance', u'attr': u'', 'resourcesDir': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\resources', u'includeParentHierarchy': False, 'name': u'modelMain', 'frameEndHandle': 1020, 'isolate': False, 'assetEntity': {u'name': u'Alpaca_01', u'parent': ObjectId('5eb950f9e3c2c3183455f266'), u'data': {u'visualParent': ObjectId('5dd50967e3c2c32b004b4812'), u'resolutionHeight': 1080, u'clipIn': 1001, u'frameStart': 1001, u'tasks': {u'edit': {u'type': u'Edit'}, u'modeling': {u'type': u'Modeling'}, u'animation': {u'type': u'Animation'}}, u'handleStart': 10, u'entityType': u'AssetBuild', u'frameEnd': 1010, u'parents': [u'Assets'], u'tools_env': [u'mtoa/3-1'], u'pixelAspect': 1.0, u'ftrackId': u'fc02ea60-9786-11eb-8ae9-fa8d1b9899e1', u'resolutionWidth': 1920, u'fps': 25.0, u'handleEnd': 10, u'clipOut': 1100, u'avalon_mongo_id': u'5dd50967e3c2c32b004b4817'}, u'_id': ObjectId('5dd50967e3c2c32b004b4817'), u'type': u'asset', u'schema': u'openpype:asset-3.0'}, 'publish': True, 'label': 'modelMain (Alpaca_01)', 'handles': None, 'frameStartHandle': 991, u'asset': u'Alpaca_01', 'frameStartFtrack': 991, 'subsetEntity': {u'name': u'modelMain', u'parent': ObjectId('5dd50967e3c2c32b004b4817'), u'type': u'subset', u'_id': ObjectId('610124ffb5db38f84962c00e'), u'data': {u'families': [u'model', u'model', u'review']}, u'schema': u'openpype:subset-3.0'}, 'destination_list': ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.jpg', 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.abc', 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007.ma', 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4'], 'transfers': [['c:\\\\users\\\\jakub~1.trl\\\\appdata\\\\local\\\\temp\\\\pyblish_tmp_ajbjxr\\\\modelMain_h264.mp4', 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\model\\\\modelMain\\\\v007\\\\kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4']], 'setMembers': [u'|pTorus_GEO'], 'frameEndFtrack': 1020}", - "exc_info": null, - "type": "record", - "levelname": "INFO" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 545.0000762939453, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 1072, - "msg": "Renaming file C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4.tmp to C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 546.9999313354492, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 1072, - "msg": "Renaming file C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007.ma.tmp to C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007.ma", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 548.0000972747803, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 1072, - "msg": "Renaming file C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007.abc.tmp to C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007.abc", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 549.9999523162842, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 1072, - "msg": "Renaming file C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007.jpg.tmp to C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007.jpg", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - } - ] - }, - { - "id": null, - "logs": [ - { - "instance_id": null, - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 607.0001125335693, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 179, - "msg": "kuba_each_case_Alpaca_01_animation_v007 is missing reference to staging directory. Will try to get it from representation.", - "exc_info": null, - "type": "record", - "levelname": "INFO" - }, - { - "instance_id": null, - "name": "openpype.lib.profiles_filtering", - "threadName": "MainThread", - "msecs": 608.9999675750732, - "filename": "profiles_filtering.py", - "levelno": 20, - "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\lib\\profiles_filtering.py", - "lineno": 31, - "msg": "Search for first most matching profile in match order: Host name -> Task name -> Family.", - "exc_info": null, - "type": "record", - "levelname": "INFO" - }, - { - "instance_id": null, - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 611.0000610351562, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 199, - "msg": "Next version: v7", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 611.9999885559082, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 846, - "msg": "Source: {root[work]}/kuba_each_case/Assets/Alpaca_01/work/animation/kuba_each_case_Alpaca_01_animation_v007.ma", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 612.9999160766602, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 215, - "msg": "Creating version ...", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 619.999885559082, - "filename": "profiles_filtering.py", - "levelno": 10, - "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\lib\\profiles_filtering.py", - "lineno": 168, - "msg": "\"families\" not found in [u'review', u'render', u'prerender']", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 619.999885559082, - "filename": "profiles_filtering.py", - "levelno": 20, - "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\lib\\profiles_filtering.py", - "lineno": 31, - "msg": "Search for first most matching profile in match order: Host name -> Task name -> Family.", - "exc_info": null, - "type": "record", - "levelname": "INFO" - }, - { - "instance_id": null, - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 640.0001049041748, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 506, - "msg": "__ dst: C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\workfile\\workfileAnimation\\v007\\kuba_each_case_Alpaca_01_workfileAnimation_v007.ma", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 641.0000324249268, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 563, - "msg": "Integrating source files to destination ...", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 641.0000324249268, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 652, - "msg": "Copying file ... C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\work\\animation\\kuba_each_case_Alpaca_01_animation_v007.ma -> C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\workfile\\workfileAnimation\\v007\\kuba_each_case_Alpaca_01_workfileAnimation_v007.ma.tmp", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 644.0000534057617, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 566, - "msg": "Integrated files {'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\workfile\\\\workfileAnimation\\\\v007\\\\kuba_each_case_Alpaca_01_workfileAnimation_v007.ma.tmp': 56463L}", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 644.9999809265137, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 569, - "msg": "Preparing files information ...", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 644.9999809265137, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 930, - "msg": "get_resource_files_info.resources:[[u'C:/projects/kuba_each_case/Assets/Alpaca_01/work/animation\\\\kuba_each_case_Alpaca_01_animation_v007.ma', 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\workfile\\\\workfileAnimation\\\\v007\\\\kuba_each_case_Alpaca_01_workfileAnimation_v007.ma']]", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 647.0000743865967, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 574, - "msg": "__ representation: {'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/workfile/workfileAnimation/v007/kuba_each_case_Alpaca_01_workfileAnimation_v007.ma', '_id': ObjectId('6125fdf9839e8042d68dbd3e'), 'hash': 'kuba_each_case_Alpaca_01_workfileAnimation_v007,ma|1629816033,49|56463', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 647000)}], 'size': 56463L}], 'context': {'subset': 'workfileAnimation', 'username': 'jakub.trllo', 'task': 'animation', 'family': 'workfile', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'ma', 'version': 7, 'asset': 'Alpaca_01', 'representation': u'ma', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': u'ma', 'parent': ObjectId('6125fdf9839e8042d68dbd3b'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\workfile\\\\workfileAnimation\\\\v007\\\\kuba_each_case_Alpaca_01_workfileAnimation_v007.ma', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd3c'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 650.0000953674316, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 576, - "msg": "__ destination_list: ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\workfile\\\\workfileAnimation\\\\v007\\\\kuba_each_case_Alpaca_01_workfileAnimation_v007.ma']", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 651.0000228881836, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 584, - "msg": "__ representations: [{'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/workfile/workfileAnimation/v007/kuba_each_case_Alpaca_01_workfileAnimation_v007.ma', '_id': ObjectId('6125fdf9839e8042d68dbd3e'), 'hash': 'kuba_each_case_Alpaca_01_workfileAnimation_v007,ma|1629816033,49|56463', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 647000)}], 'size': 56463L}], 'context': {'subset': 'workfileAnimation', 'username': 'jakub.trllo', 'task': 'animation', 'family': 'workfile', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'ma', 'version': 7, 'asset': 'Alpaca_01', 'representation': u'ma', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': u'ma', 'parent': ObjectId('6125fdf9839e8042d68dbd3b'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\workfile\\\\workfileAnimation\\\\v007\\\\kuba_each_case_Alpaca_01_workfileAnimation_v007.ma', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd3c'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}]", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 654.0000438690186, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 594, - "msg": "__ rep: {'files': u'kuba_each_case_Alpaca_01_animation_v007.ma', 'publishedFiles': ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\workfile\\\\workfileAnimation\\\\v007\\\\kuba_each_case_Alpaca_01_workfileAnimation_v007.ma'], 'name': u'ma', 'published_path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\workfile\\\\workfileAnimation\\\\v007\\\\kuba_each_case_Alpaca_01_workfileAnimation_v007.ma', 'stagingDir': u'C:/projects/kuba_each_case/Assets/Alpaca_01/work/animation', 'ext': u'ma'}", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": null, - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 661.0000133514404, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 601, - "msg": "Registered 1 items", - "exc_info": null, - "type": "record", - "levelname": "INFO" - }, - { - "instance_id": null, - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 661.0000133514404, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 124, - "msg": "Integrated Asset in to the database ...", - "exc_info": null, - "type": "record", - "levelname": "INFO" - }, - { - "instance_id": null, - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 661.9999408721924, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 125, - "msg": "instance.data: {'subset': 'workfileAnimation', 'publishDir': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\workfile\\\\workfileAnimation\\\\v007', 'version': 7, 'family': 'workfile', 'frameStart': 1001, 'published_representations': {ObjectId('6125fdf9839e8042d68dbd3c'): {'representation': {'files': [{'path': '{root[work]}/kuba_each_case/Assets/Alpaca_01/publish/workfile/workfileAnimation/v007/kuba_each_case_Alpaca_01_workfileAnimation_v007.ma', '_id': ObjectId('6125fdf9839e8042d68dbd3e'), 'hash': 'kuba_each_case_Alpaca_01_workfileAnimation_v007,ma|1629816033,49|56463', 'sites': [{'name': 'studio', 'created_dt': datetime.datetime(2021, 8, 25, 10, 23, 21, 647000)}], 'size': 56463L}], 'context': {'subset': 'workfileAnimation', 'username': 'jakub.trllo', 'task': 'animation', 'family': 'workfile', 'hierarchy': u'Assets', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'ext': u'ma', 'version': 7, 'asset': 'Alpaca_01', 'representation': u'ma', 'root': {'work': u'C:/projects'}}, 'dependencies': [], 'name': u'ma', 'parent': ObjectId('6125fdf9839e8042d68dbd3b'), 'data': {'path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\workfile\\\\workfileAnimation\\\\v007\\\\kuba_each_case_Alpaca_01_workfileAnimation_v007.ma', 'template': u'{root[work]}\\\\{project[name]}\\\\{hierarchy}\\\\{asset}\\\\publish\\\\{family}\\\\{subset}\\\\v{version:0>3}\\\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}><_{udim}>.{ext}'}, '_id': ObjectId('6125fdf9839e8042d68dbd3c'), 'type': 'representation', 'schema': 'openpype:representation-2.0'}, 'published_files': ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\workfile\\\\workfileAnimation\\\\v007\\\\kuba_each_case_Alpaca_01_workfileAnimation_v007.ma'], 'anatomy_data': {'subset': 'workfileAnimation', 'family': 'workfile', 'hierarchy': u'Assets', 'app': 'maya', 'yyyy': '2021', 'HH': '10', 'version': 7, 'asset': 'Alpaca_01', 'ddd': 'Wed', 'username': 'jakub.trllo', 'H': '10', 'dd': '25', 'M': '23', 'ht': 'AM', 'mmmm': 'August', 'S': '14', 'dddd': 'Wednesday', 'mmm': 'Aug', 'task': 'animation', 'd': '25', 'SS': '14', 'h': '10', 'm': '8', 'hh': '10', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'mm': '08', 'ext': u'ma', 'yy': '21', 'representation': u'ma', 'MM': '23'}}}, 'destination_list': ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\workfile\\\\workfileAnimation\\\\v007\\\\kuba_each_case_Alpaca_01_workfileAnimation_v007.ma'], 'frameEnd': 1010, 'transfers': [[u'C:/projects/kuba_each_case/Assets/Alpaca_01/work/animation\\\\kuba_each_case_Alpaca_01_animation_v007.ma', 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\workfile\\\\workfileAnimation\\\\v007\\\\kuba_each_case_Alpaca_01_workfileAnimation_v007.ma']], 'latestVersion': 6, 'resourcesDir': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\workfile\\\\workfileAnimation\\\\v007\\\\resources', 'handleStart': 10, 'publish': True, 'label': 'workfileAnimation', 'projectEntity': {u'name': u'kuba_each_case', u'parent': None, u'type': u'project', u'data': {u'code': u'kuba_each_case', u'resolutionHeight': 1080, u'library_project': False, u'clipIn': 1001, u'frameStart': 1001, u'handleStart': 10, u'entityType': u'Project', u'frameEnd': 1100, u'tools_env': [], u'pixelAspect': 1.0, u'ftrackId': u'38c5fec4-0aed-11ea-a454-3e41ec9bc0d6', u'resolutionWidth': 1920, u'fps': 25.0, u'handleEnd': 10, u'clipOut': 1100}, u'_id': ObjectId('5eb950f9e3c2c3183455f266'), u'config': {u'templates': {u'hero': {u'path': u'{@folder}/{@file}', u'folder': u'{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/hero', u'file': u'{project[code]}_{asset}_{subset}_hero<_{output}><.{frame}>.{ext}'}, u'render': {u'path': u'{@folder}/{@file}', u'folder': u'{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/{@version}', u'file': u'{project[code]}_{asset}_{subset}_{@version}<_{output}><.{@frame}>.{ext}'}, u'work': {u'path': u'{@folder}/{@file}', u'folder': u'{root[work]}/{project[name]}/{hierarchy}/{asset}/work/{task}', u'file': u'{project[code]}_{asset}_{task}_{@version}<_{comment}>.{ext}'}, u'publish': {u'path': u'{@folder}/{@file}', u'folder': u'{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/{@version}', u'thumbnail': u'{thumbnail_root}/{project[name]}/{_id}_{thumbnail_type}.{ext}', u'file': u'{project[code]}_{asset}_{subset}_{@version}<_{output}><.{@frame}><_{udim}>.{ext}'}, u'delivery': {}, u'defaults': {u'frame_padding': 4, u'version_padding': 3, u'frame': u'{frame:0>{@frame_padding}}', u'version': u'v{version:0>{@version_padding}}'}, u'others': {}}, u'tasks': {u'Previz': {u'short_name': u''}, u'Lookdev': {u'short_name': u'look'}, u'Edit': {u'short_name': u'edit'}, u'Tracking': {u'short_name': u''}, u'Layout': {u'short_name': u'lay'}, u'Art': {u'short_name': u'art'}, u'Paint': {u'short_name': u'paint'}, u'Generic': {u'short_name': u'gener'}, u'Texture': {u'short_name': u'tex'}, u'Setdress': {u'short_name': u'dress'}, u'Animation': {u'short_name': u'anim'}, u'Lighting': {u'short_name': u'lgt'}, u'Compositing': {u'short_name': u'comp'}, u'Modeling': {u'short_name': u'mdl'}, u'Rigging': {u'short_name': u'rig'}, u'FX': {u'short_name': u'fx'}}, u'imageio': {u'hiero': {u'workfile': {u'eightBitLut': u'sRGB', u'viewerLut': u'sRGB', u'workingSpace': u'linear', u'ocioconfigpath': {u'windows': [], u'darwin': [], u'linux': []}, u'ocioConfigName': u'nuke-default', u'thumbnailLut': u'sRGB', u'floatLut': u'linear', u'sixteenBitLut': u'sRGB', u'logLut': u'Cineon'}, u'regexInputs': {u'inputs': [{u'regex': u'[^-a-zA-Z0-9](plateRef).*(?=mp4)', u'colorspace': u'sRGB'}]}}, u'nuke': {u'viewer': {u'viewerProcess': u'sRGB'}, u'workfile': {u'floatLut': u'linear', u'workingSpaceLUT': u'linear', u'customOCIOConfigPath': {u'windows': [], u'darwin': [], u'linux': []}, u'int16Lut': u'sRGB', u'logLut': u'Cineon', u'int8Lut': u'sRGB', u'colorManagement': u'Nuke', u'OCIO_config': u'nuke-default', u'monitorLut': u'sRGB'}, u'nodes': {u'customNodes': [], u'requiredNodes': [{u'nukeNodeClass': u'Write', u'knobs': [{u'name': u'file_type', u'value': u'exr'}, {u'name': u'datatype', u'value': u'16 bit half'}, {u'name': u'compression', u'value': u'Zip (1 scanline)'}, {u'name': u'autocrop', u'value': u'True'}, {u'name': u'tile_color', u'value': u'0xff0000ff'}, {u'name': u'channels', u'value': u'rgb'}, {u'name': u'colorspace', u'value': u'linear'}, {u'name': u'create_directories', u'value': u'True'}], u'plugins': [u'CreateWriteRender']}, {u'nukeNodeClass': u'Write', u'knobs': [{u'name': u'file_type', u'value': u'exr'}, {u'name': u'datatype', u'value': u'16 bit half'}, {u'name': u'compression', u'value': u'Zip (1 scanline)'}, {u'name': u'autocrop', u'value': u'False'}, {u'name': u'tile_color', u'value': u'0xadab1dff'}, {u'name': u'channels', u'value': u'rgb'}, {u'name': u'colorspace', u'value': u'linear'}, {u'name': u'create_directories', u'value': u'True'}], u'plugins': [u'CreateWritePrerender']}]}, u'regexInputs': {u'inputs': [{u'regex': u'[^-a-zA-Z0-9]beauty[^-a-zA-Z0-9]', u'colorspace': u'linear'}]}}}, u'apps': [{u'name': u'blender/2-91'}, {u'name': u'hiero/11-3'}, {u'name': u'houdini/18-5'}, {u'name': u'maya/2020'}, {u'name': u'nuke/11-3'}, {u'name': u'nukestudio/11-3'}], u'roots': {u'work': {u'windows': u'C:/projects', u'darwin': u'/Volumes/path', u'linux': u'/mnt/share/projects'}}, u'schema': u'openpype:config-2.0'}, u'schema': u'openpype:project-3.0'}, 'asset': 'Alpaca_01', 'setMembers': [u'C:/projects/kuba_each_case/Assets/Alpaca_01/work/animation/kuba_each_case_Alpaca_01_animation_v007.ma'], 'families': ['workfile'], 'handleEnd': 10, 'representations': [{'files': u'kuba_each_case_Alpaca_01_animation_v007.ma', 'publishedFiles': ['C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\workfile\\\\workfileAnimation\\\\v007\\\\kuba_each_case_Alpaca_01_workfileAnimation_v007.ma'], 'name': u'ma', 'published_path': 'C:\\\\projects\\\\kuba_each_case\\\\Assets\\\\Alpaca_01\\\\publish\\\\workfile\\\\workfileAnimation\\\\v007\\\\kuba_each_case_Alpaca_01_workfileAnimation_v007.ma', 'stagingDir': u'C:/projects/kuba_each_case/Assets/Alpaca_01/work/animation', 'ext': u'ma'}], 'anatomyData': {'subset': 'workfileAnimation', 'family': 'workfile', 'hierarchy': u'Assets', 'app': 'maya', 'yyyy': '2021', 'HH': '10', 'version': 7, 'asset': 'Alpaca_01', 'ddd': 'Wed', 'username': 'jakub.trllo', 'mm': '08', 'H': '10', 'dd': '25', 'M': '23', 'ht': 'AM', 'mmmm': 'August', 'S': '14', 'dddd': 'Wednesday', 'mmm': 'Aug', 'task': 'animation', 'd': '25', 'SS': '14', 'h': '10', 'm': '8', 'hh': '10', 'project': {'code': u'kuba_each_case', 'name': u'kuba_each_case'}, 'MM': '23', 'yy': '21'}, 'versionEntity': {u'name': 7, u'parent': ObjectId('61012500b5db38f84962c01d'), u'type': u'version', u'_id': ObjectId('6125fdf9839e8042d68dbd3b'), u'data': {u'comment': u'', u'families': [u'workfile', u'workfile'], u'frameStart': 1001, u'handleStart': 10, u'author': u'jakub.trllo', u'frameEnd': 1010, u'machine': u'014-BAILEYS', u'source': u'{root[work]}/kuba_each_case/Assets/Alpaca_01/work/animation/kuba_each_case_Alpaca_01_animation_v007.ma', u'fps': 25.0, u'time': u'20210825T102314Z', u'handleEnd': 10}, u'schema': u'openpype:version-3.0'}, 'name': u'kuba_each_case_Alpaca_01_animation_v007', 'assetEntity': {u'name': u'Alpaca_01', u'parent': ObjectId('5eb950f9e3c2c3183455f266'), u'data': {u'visualParent': ObjectId('5dd50967e3c2c32b004b4812'), u'resolutionHeight': 1080, u'clipIn': 1001, u'frameStart': 1001, u'tasks': {u'edit': {u'type': u'Edit'}, u'modeling': {u'type': u'Modeling'}, u'animation': {u'type': u'Animation'}}, u'handleStart': 10, u'entityType': u'AssetBuild', u'frameEnd': 1010, u'parents': [u'Assets'], u'tools_env': [u'mtoa/3-1'], u'pixelAspect': 1.0, u'ftrackId': u'fc02ea60-9786-11eb-8ae9-fa8d1b9899e1', u'resolutionWidth': 1920, u'fps': 25.0, u'handleEnd': 10, u'clipOut': 1100, u'avalon_mongo_id': u'5dd50967e3c2c32b004b4817'}, u'_id': ObjectId('5dd50967e3c2c32b004b4817'), u'type': u'asset', u'schema': u'openpype:asset-3.0'}, 'subsetEntity': {u'name': u'workfileAnimation', u'parent': ObjectId('5dd50967e3c2c32b004b4817'), u'type': u'subset', u'_id': ObjectId('61012500b5db38f84962c01d'), u'data': {u'families': [u'workfile', u'workfile']}, u'schema': u'openpype:subset-3.0'}}", - "exc_info": null, - "type": "record", - "levelname": "INFO" - }, - { - "instance_id": null, - "name": "pyblish.IntegrateAssetNew", - "threadName": "MainThread", - "msecs": 680.0000667572021, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 1072, - "msg": "Renaming file C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\workfile\\workfileAnimation\\v007\\kuba_each_case_Alpaca_01_workfileAnimation_v007.ma.tmp to C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\workfile\\workfileAnimation\\v007\\kuba_each_case_Alpaca_01_workfileAnimation_v007.ma", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - } - ] - } - ], - "label": "Integrate Asset New", - "skipped": false, - "order": 3, - "name": "IntegrateAssetNew" - }, - { - "instances_data": [], - "label": "Determine Subset Version", - "skipped": true, - "order": 3, - "name": "DetermineFutureVersion" - }, - { - "instances_data": [ - { - "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "logs": [ - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateThumbnails", - "threadName": "MainThread", - "msecs": 730.0000190734863, - "filename": "", - "levelno": 30, - "pathname": "", - "lineno": 29, - "msg": "AVALON_THUMBNAIL_ROOT is not set. Skipping thumbnail integration.", - "exc_info": null, - "type": "record", - "levelname": "WARNING" - } - ] - } - ], - "label": "Integrate Thumbnails", - "skipped": false, - "order": 3.01, - "name": "IntegrateThumbnails" - }, - { - "instances_data": [], - "label": "Submit to Muster", - "skipped": true, - "order": 3.1, - "name": "MayaSubmitMuster" - }, - { - "instances_data": [], - "label": "Submit to Deadline", - "skipped": true, - "order": 3.1, - "name": "MayaSubmitDeadline" - }, - { - "instances_data": [ - { - "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "logs": [ - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateHeroVersion", - "threadName": "MainThread", - "msecs": 796.9999313354492, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 43, - "msg": "--- Integration of Hero version for subset `modelMain` begins.", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateHeroVersion", - "threadName": "MainThread", - "msecs": 798.0000972747803, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 70, - "msg": "`hero` template check was successful. `{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/hero/{project[code]}_{asset}_{subset}_hero<_{output}><.{frame}>.{ext}`", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateHeroVersion", - "threadName": "MainThread", - "msecs": 816.9999122619629, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 497, - "msg": "hero publish dir: \"C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\hero\"", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateHeroVersion", - "threadName": "MainThread", - "msecs": 821.0000991821289, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 179, - "msg": "Replacing old hero version.", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateHeroVersion", - "threadName": "MainThread", - "msecs": 822.0000267028809, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 254, - "msg": "Backup folder path is \"C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\hero.BACKUP\"", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateHeroVersion", - "threadName": "MainThread", - "msecs": 914.9999618530273, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 507, - "msg": "Folder(s) created: \"C:/projects/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/hero\"", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateHeroVersion", - "threadName": "MainThread", - "msecs": 915.9998893737793, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 516, - "msg": "Copying file \"C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007.ma\" to \"C:/projects/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/hero/kuba_each_case_Alpaca_01_modelMain_hero.ma\"", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateHeroVersion", - "threadName": "MainThread", - "msecs": 917.9999828338623, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 513, - "msg": "Folder already exists: \"C:/projects/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/hero\"", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateHeroVersion", - "threadName": "MainThread", - "msecs": 917.9999828338623, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 516, - "msg": "Copying file \"C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007_h264.mp4\" to \"C:/projects/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/hero/kuba_each_case_Alpaca_01_modelMain_hero_h264.mp4\"", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateHeroVersion", - "threadName": "MainThread", - "msecs": 920.0000762939453, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 513, - "msg": "Folder already exists: \"C:/projects/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/hero\"", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateHeroVersion", - "threadName": "MainThread", - "msecs": 921.0000038146973, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 516, - "msg": "Copying file \"C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007.jpg\" to \"C:/projects/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/hero/kuba_each_case_Alpaca_01_modelMain_hero.jpg\"", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateHeroVersion", - "threadName": "MainThread", - "msecs": 921.9999313354492, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 513, - "msg": "Folder already exists: \"C:/projects/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/hero\"", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateHeroVersion", - "threadName": "MainThread", - "msecs": 923.0000972747803, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 516, - "msg": "Copying file \"C:\\projects\\kuba_each_case\\Assets\\Alpaca_01\\publish\\model\\modelMain\\v007\\kuba_each_case_Alpaca_01_modelMain_v007.abc\" to \"C:/projects/kuba_each_case/Assets/Alpaca_01/publish/model/modelMain/hero/kuba_each_case_Alpaca_01_modelMain_hero.abc\"", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - }, - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.IntegrateHeroVersion", - "threadName": "MainThread", - "msecs": 928.9999008178711, - "filename": "", - "levelno": 10, - "pathname": "", - "lineno": 457, - "msg": "--- hero version integration for subset `modelMain` seems to be successful.", - "exc_info": null, - "type": "record", - "levelname": "DEBUG" - } - ] - } - ], - "label": "Integrate Hero Version", - "skipped": false, - "order": 3.1, - "name": "IntegrateHeroVersion" - }, - { - "instances_data": [], - "label": "Submit image sequence jobs to Deadline or Muster", - "skipped": true, - "order": 3.2, - "name": "ProcessSubmittedJobOnFarm" - }, - { - "instances_data": [ - { - "id": null, - "logs": [ - { - "instance_id": null, - "name": "openpype.lib.path_tools", - "threadName": "MainThread", - "msecs": 993.0000305175781, - "filename": "path_tools.py", - "levelno": 20, - "pathname": "C:\\Users\\jakub.trllo\\Desktop\\pype\\pype3\\openpype\\lib\\path_tools.py", - "lineno": 64, - "msg": "New version _v008", - "exc_info": null, - "type": "record", - "levelname": "INFO" - } - ] - } - ], - "label": "Increment current file", - "skipped": false, - "order": 12.0, - "name": "IncrementCurrentFileDeadline" - }, - { - "instances_data": [ - { - "id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "logs": [ - { - "instance_id": "6d2ee587-9843-47a6-a6e2-10b6d2d8e495", - "name": "pyblish.CleanUp", - "threadName": "MainThread", - "msecs": 107.00011253356934, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 72, - "msg": "Removing staging directory c:\\users\\jakub~1.trl\\appdata\\local\\temp\\pyblish_tmp_ajbjxr", - "exc_info": null, - "type": "record", - "levelname": "INFO" - } - ] - }, - { - "id": null, - "logs": [ - { - "instance_id": null, - "name": "pyblish.CleanUp", - "threadName": "MainThread", - "msecs": 184.00001525878906, - "filename": "", - "levelno": 20, - "pathname": "", - "lineno": 60, - "msg": "Staging dir not set.", - "exc_info": null, - "type": "record", - "levelname": "INFO" - } - ] - } - ], - "label": "Clean Up", - "skipped": false, - "order": 13, - "name": "CleanUp" - } - ], - "context": { - "label": "Maya - kuba_each_case_Alpaca_01_animation_v007.ma" - } -} diff --git a/openpype/tools/new_publisher/publish_report_viewer/widgets.py b/openpype/tools/new_publisher/publish_report_viewer/widgets.py index c7d60ed35c..9279e77d53 100644 --- a/openpype/tools/new_publisher/publish_report_viewer/widgets.py +++ b/openpype/tools/new_publisher/publish_report_viewer/widgets.py @@ -1,9 +1,7 @@ import copy import uuid -from Qt import QtWidgets, QtCore, QtGui - -import pyblish.api +from Qt import QtWidgets, QtCore from .constants import ( ITEM_ID_ROLE, From f372dcfe4eb40baf18d9e4f54defcbdc216746e8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 1 Sep 2021 11:45:25 +0200 Subject: [PATCH 379/736] moved update and remove logic to create context --- openpype/pipeline/create/context.py | 13 +++++++++++++ openpype/tools/new_publisher/control.py | 12 +++--------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 1692c91d51..c170053de1 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -558,6 +558,19 @@ class CreateContext: self.instances = instances + def save_instance_changes(self): + update_list = [] + for instance in self.instances: + instance_changes = instance.changes() + if instance_changes: + update_list.append((instance, instance_changes)) + + if update_list: + self.host.update_instances(update_list) + + def remove_instances(self, instances): + self.host.remove_instances(instances) + def _get_publish_plugins_with_attr_for_family(self, family): if family not in self._attr_plugins_by_family: import pyblish.logic diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 0ed40f3890..189f2eb74b 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -519,23 +519,17 @@ class PublisherController: def create(self, family, subset_name, instance_data, options): # QUESTION Force to return instances or call `list_instances` on each # creation? (`list_instances` may slow down...) + # WARNING changes done before creation will be lost creator = self.creators[family] creator.create(subset_name, instance_data, options) self._reset_instances() def save_instance_changes(self): - update_list = [] - for instance in self.instances: - instance_changes = instance.changes() - if instance_changes: - update_list.append((instance, instance_changes)) - - if update_list: - self.host.update_instances(update_list) + self.create_context.save_instance_changes() def remove_instances(self, instances): - self.host.remove_instances(instances) + self.create_context.remove_instances(instances) self._reset_instances() From e86a804513811bae28cdbd8e7d71c7a6665aef0b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 1 Sep 2021 12:05:45 +0200 Subject: [PATCH 380/736] create context care about implemented methods of host --- openpype/pipeline/create/context.py | 62 +++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index c170053de1..f1ee5c968c 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -13,6 +13,19 @@ from openpype.api import ( ) +class HostMissRequiredMethod(Exception): + def __init__(self, host, missing_methods): + self.missing_methods = missing_methods + self.host = host + joined_methods = ", ".join( + ['"{}"'.format(name) for name in missing_methods] + ) + msg = "Host {} does not have implemented method/s {}".format( + str(host), joined_methods + ) + super(HostMissRequiredMethod, self).__init__(msg) + + class InstanceMember: def __init__(self, instance, name): self.instance = instance @@ -411,6 +424,12 @@ class CreatedInstance: class CreateContext: + required_methods = ( + "list_instances", + "remove_instances", + "update_instances" + ) + def __init__(self, host, dbcon=None, headless=False, reset=True): if dbcon is None: import avalon.api @@ -421,7 +440,22 @@ class CreateContext: self.dbcon = dbcon + self._log = None + self.host = host + host_is_valid = True + missing_methods = self.get_host_misssing_methods(host) + if missing_methods: + host_is_valid = False + joined_methods = ", ".join( + ['"{}"'.format(name) for name in missing_methods] + ) + self.log.warning(( + "Host miss required methods to be able use creation." + " Missing methods: {}" + ).format(joined_methods)) + + self._host_is_valid = host_is_valid self.headless = headless self.instances = [] @@ -432,11 +466,21 @@ class CreateContext: self.plugins_with_defs = [] self._attr_plugins_by_family = {} - self._log = None - if reset: self.reset() + @classmethod + def get_host_misssing_methods(cls, host): + missing = set() + for attr_name in cls.required_methods: + if not hasattr(host, attr_name): + missing.add(attr_name) + return missing + + @property + def host_is_valid(self): + return self._host_is_valid + @property def log(self): if self._log is None: @@ -498,8 +542,12 @@ class CreateContext: def reset_instances(self): # Collect instances - host_instances = self.host.list_instances() instances = [] + if not self.host_is_valid: + self.instances = instances + return + + host_instances = self.host.list_instances() task_names_by_asset_name = collections.defaultdict(set) for instance_data in host_instances: family = instance_data["family"] @@ -559,6 +607,10 @@ class CreateContext: self.instances = instances def save_instance_changes(self): + if not self.host_is_valid: + missing_methods = self.get_host_misssing_methods(self.host) + raise HostMissRequiredMethod(self.host, missing_methods) + update_list = [] for instance in self.instances: instance_changes = instance.changes() @@ -569,6 +621,10 @@ class CreateContext: self.host.update_instances(update_list) def remove_instances(self, instances): + if not self.host_is_valid: + missing_methods = self.get_host_misssing_methods(self.host) + raise HostMissRequiredMethod(self.host, missing_methods) + self.host.remove_instances(instances) def _get_publish_plugins_with_attr_for_family(self, family): From c8fd999b9105afe14899136cecb70b05a02dbab7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 1 Sep 2021 12:12:04 +0200 Subject: [PATCH 381/736] fixed host name in exception --- openpype/pipeline/create/context.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index f1ee5c968c..8ac2e0ad04 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1,3 +1,4 @@ +import os import copy import logging import collections @@ -20,8 +21,16 @@ class HostMissRequiredMethod(Exception): joined_methods = ", ".join( ['"{}"'.format(name) for name in missing_methods] ) - msg = "Host {} does not have implemented method/s {}".format( - str(host), joined_methods + dirpath = os.path.dirname( + os.path.normpath(inspect.getsourcefile(host)) + ) + dirpath_parts = dirpath.split(os.path.sep) + host_name = dirpath_parts.pop(-1) + if host_name == "api": + host_name = dirpath_parts.pop(-1) + + msg = "Host \"{}\" does not have implemented method/s {}".format( + host_name, joined_methods ) super(HostMissRequiredMethod, self).__init__(msg) From c32e6f4a99af769595b2c598d0deda8de90c9e03 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 1 Sep 2021 12:23:24 +0200 Subject: [PATCH 382/736] disable subset widget if host is not valid --- openpype/tools/new_publisher/control.py | 4 ++++ openpype/tools/new_publisher/window.py | 10 +++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 189f2eb74b..287528eda4 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -341,6 +341,10 @@ class PublisherController: def creators(self): return self.create_context.creators + @property + def host_is_valid(self): + return self.create_context.host_is_valid + @property def publish_plugins(self): return self.create_context.publish_plugins diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index c82c071a96..c1b1b6f67a 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -92,7 +92,8 @@ class PublisherWindow(QtWidgets.QDialog): subset_view_layout.addLayout(subset_view_btns_layout, 0) # Whole subset layout with attributes and details - subset_content_layout = QtWidgets.QHBoxLayout() + subset_content_widget = QtWidgets.QWidget(subset_frame) + subset_content_layout = QtWidgets.QHBoxLayout(subset_content_widget) subset_content_layout.setContentsMargins(0, 0, 0, 0) subset_content_layout.addLayout(subset_view_layout, 0) subset_content_layout.addWidget(subset_attributes_widget, 1) @@ -131,7 +132,7 @@ class PublisherWindow(QtWidgets.QDialog): marings.setTop(marings.top() * 2) marings.setBottom(marings.bottom() * 2) subset_layout.setContentsMargins(marings) - subset_layout.addLayout(subset_content_layout, 1) + subset_layout.addWidget(subset_content_widget, 1) subset_layout.addLayout(footer_layout, 0) # Create publish frame @@ -181,6 +182,7 @@ class PublisherWindow(QtWidgets.QDialog): self.content_stacked_layout = content_stacked_layout self.publish_frame = publish_frame self.subset_frame = subset_frame + self.subset_content_widget = subset_content_widget self.context_label = context_label @@ -215,7 +217,7 @@ class PublisherWindow(QtWidgets.QDialog): super(PublisherWindow, self).showEvent(event) if self._first_show: self._first_show = False - self.controller.reset() + self.reset() def reset(self): self.controller.reset() @@ -343,6 +345,8 @@ class PublisherWindow(QtWidgets.QDialog): self._set_publish_visibility(False) + self.subset_content_widget.setEnabled(self.controller.host_is_valid) + def _on_publish_start(self): self.reset_btn.setEnabled(False) self.stop_btn.setEnabled(True) From 4fb547031b9fc5f369be3792293be76bada3ab46 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 1 Sep 2021 14:42:41 +0200 Subject: [PATCH 383/736] Use openpype icon --- openpype/tools/new_publisher/window.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index c1b1b6f67a..bd0cbbb381 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -1,6 +1,9 @@ -from Qt import QtWidgets, QtCore +from Qt import QtWidgets, QtCore, QtGui -from openpype import style +from openpype import ( + resources, + style +) from .control import PublisherController from .widgets import ( @@ -22,6 +25,9 @@ class PublisherWindow(QtWidgets.QDialog): self.setWindowTitle("OpenPype publisher") + icon = QtGui.QIcon(resources.pype_icon_filepath()) + self.setWindowIcon(icon) + if parent is None: on_top_flag = QtCore.Qt.WindowStaysOnTopHint else: From 51aba446023634bcac60bff429974bd8e60d7513 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 2 Sep 2021 13:41:52 +0200 Subject: [PATCH 384/736] added instance without creator --- openpype/hosts/testhost/api/instances.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/openpype/hosts/testhost/api/instances.json b/openpype/hosts/testhost/api/instances.json index 53b5356ceb..0419bc2181 100644 --- a/openpype/hosts/testhost/api/instances.json +++ b/openpype/hosts/testhost/api/instances.json @@ -69,5 +69,22 @@ "add_ftrack_family": true } } + }, + { + "id": "pyblish.avalon.instance", + "family": "test_three", + "subset": "test_threeMain2", + "active": true, + "version": 1, + "asset": "sq01_sh0020", + "task": "Compositing", + "variant": "Main2", + "uuid": "4ccf56f6-9982-4837-967c-a49695dbe8ec", + "family_attributes": {}, + "publish_attributes": { + "CollectFtrackApi": { + "add_ftrack_family": true + } + } } ] \ No newline at end of file From 44cacca0e4c6148c85d4301d312312e8eb842eb1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 2 Sep 2021 14:48:13 +0200 Subject: [PATCH 385/736] change attribute name from `instance` to `parent` --- openpype/pipeline/create/context.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 8ac2e0ad04..dde5128f4b 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -151,13 +151,13 @@ class PublishAttributeValues(AttributeValues): super(PublishAttributeValues, self).__init__(*args, **kwargs) @property - def instance(self): - self.publish_attributes.instance + def parent(self): + self.publish_attributes.parent class PublishAttributes: - def __init__(self, instance, origin_data, attr_plugins=None): - self.instance = instance + def __init__(self, parent, origin_data, attr_plugins=None): + self.parent = parent self._origin_data = copy.deepcopy(origin_data) attr_plugins = attr_plugins or [] From 5bc93e3b5cd1fb0036250979de7ef8d84d19c534 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 2 Sep 2021 14:48:42 +0200 Subject: [PATCH 386/736] added more required functions on host side --- openpype/pipeline/create/context.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index dde5128f4b..3f21b35065 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -436,7 +436,9 @@ class CreateContext: required_methods = ( "list_instances", "remove_instances", - "update_instances" + "update_instances", + "get_context_data", + "update_context_data" ) def __init__(self, host, dbcon=None, headless=False, reset=True): From 6719a288365fc0844e7c385dc6769504fd7ad1aa Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 2 Sep 2021 14:49:10 +0200 Subject: [PATCH 387/736] collect only instance plugins for instance attributes --- openpype/pipeline/create/context.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 3f21b35065..25440adaa2 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -552,12 +552,12 @@ class CreateContext: self.creators = creators def reset_instances(self): - # Collect instances instances = [] if not self.host_is_valid: self.instances = instances return + # Collect instances host_instances = self.host.list_instances() task_names_by_asset_name = collections.defaultdict(set) for instance_data in host_instances: @@ -645,6 +645,10 @@ class CreateContext: filtered_plugins = pyblish.logic.plugins_by_families( self.plugins_with_defs, [family] ) - self._attr_plugins_by_family[family] = filtered_plugins + plugins = [] + for plugin in filtered_plugins: + if plugin.__instanceEnabled__: + plugins.append(plugin) + self._attr_plugins_by_family[family] = plugins return self._attr_plugins_by_family[family] From 448b7641e4498dfe6bddec49bee04c50a915986c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 2 Sep 2021 14:50:15 +0200 Subject: [PATCH 388/736] CreateContext has `publish_attributes` --- openpype/pipeline/create/context.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 25440adaa2..8708808c11 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -452,6 +452,7 @@ class CreateContext: self.dbcon = dbcon self._log = None + self._publish_attributes = PublishAttributes(self, {}) self.host = host host_is_valid = True @@ -480,6 +481,10 @@ class CreateContext: if reset: self.reset() + @property + def publish_attributes(self): + return self._publish_attributes + @classmethod def get_host_misssing_methods(cls, host): missing = set() From 451b43fe7c34a3174d0b5d27e94ce0066e71cb88 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 2 Sep 2021 14:50:34 +0200 Subject: [PATCH 389/736] added reset of context publish attributes --- openpype/pipeline/create/context.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 8708808c11..d3453f66ea 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -505,6 +505,7 @@ class CreateContext: def reset(self): self.reset_plugins() + self.rese_context_data() self.reset_instances() def reset_plugins(self): @@ -556,6 +557,17 @@ class CreateContext: self.creators = creators + def reset_context_data(self): + if not self.host_is_valid: + self._publish_attributes = PublishAttributes(self, {}) + return + + original_data = self.host.get_context_data() + attr_plugins = self._get_publish_plugins_with_attr_for_context() + self._publish_attributes = PublishAttributes( + self, original_data, attr_plugins + ) + def reset_instances(self): instances = [] if not self.host_is_valid: @@ -657,3 +669,10 @@ class CreateContext: self._attr_plugins_by_family[family] = plugins return self._attr_plugins_by_family[family] + + def _get_publish_plugins_with_attr_for_context(self): + plugins = [] + for plugin in self.plugins_with_defs: + if not plugin.__instanceEnabled__: + plugins.append(plugin) + return plugins From ecef4e75e0b40336b1e644b333e56f027d556946 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 2 Sep 2021 15:26:14 +0200 Subject: [PATCH 390/736] added base of new functions for context --- openpype/hosts/testhost/api/__init__.py | 4 +- openpype/hosts/testhost/api/pipeline.py | 65 ++++++++++++++++++++----- 2 files changed, 56 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/testhost/api/__init__.py b/openpype/hosts/testhost/api/__init__.py index 9febac248f..292c7fcb1e 100644 --- a/openpype/hosts/testhost/api/__init__.py +++ b/openpype/hosts/testhost/api/__init__.py @@ -8,7 +8,9 @@ from .pipeline import ( ls, list_instances, update_instances, - remove_instances + remove_instances, + get_context_data, + update_context_data ) diff --git a/openpype/hosts/testhost/api/pipeline.py b/openpype/hosts/testhost/api/pipeline.py index fb47f1aa77..4696d6e7f5 100644 --- a/openpype/hosts/testhost/api/pipeline.py +++ b/openpype/hosts/testhost/api/pipeline.py @@ -4,7 +4,31 @@ import collections class HostContext: - json_path = None + instances_json_path = None + context_json_path = None + + @classmethod + def get_current_dir_filepath(cls, filename): + return os.path.join( + os.path.dirname(os.path.abspath(__file__)), + filename + ) + + @classmethod + def get_instances_json_path(cls): + if cls.instances_json_path is None: + cls.instances_json_path = cls.get_current_dir_filepath( + "instances.json" + ) + return cls.instances_json_path + + @classmethod + def get_context_json_path(cls): + if cls.context_json_path is None: + cls.context_json_path = cls.get_current_dir_filepath( + "context.json" + ) + return cls.context_json_path @classmethod def add_instance(cls, instance): @@ -14,22 +38,13 @@ class HostContext: @classmethod def save_instances(cls, instances): - json_path = cls.get_json_path() + json_path = cls.get_instances_json_path() with open(json_path, "w") as json_stream: json.dump(instances, json_stream, indent=4) - @classmethod - def get_json_path(cls): - if cls.json_path is None: - cls.json_path = os.path.join( - os.path.dirname(os.path.abspath(__file__)), - "instances.json" - ) - return cls.json_path - @classmethod def get_instances(cls): - json_path = cls.get_json_path() + json_path = cls.get_instances_json_path() if not os.path.exists(json_path): instances = [] with open(json_path, "w") as json_stream: @@ -39,6 +54,24 @@ class HostContext: instances = json.load(json_stream) return instances + @classmethod + def get_context_data(cls): + json_path = cls.get_context_json_path() + if not os.path.exists(json_path): + data = {} + with open(json_path, "w") as json_stream: + json.dump(json_stream, data) + else: + with open(json_path, "r") as json_stream: + data = json.load(json_stream) + return data + + @classmethod + def save_context_data(cls, data): + json_path = cls.get_context_json_path() + with open(json_path, "w") as json_stream: + json.dump(json_stream, data) + def ls(): return [] @@ -83,3 +116,11 @@ def remove_instances(instances): if found_idx is not None: current_instances.pop(found_idx) HostContext.save_instances(current_instances) + + +def get_context_data(): + HostContext.get_context_data() + + +def update_context_data(data): + HostContext.save_context_data(data) From 10037b760402094a5fefabe3ed6fa36a0ce40c04 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 2 Sep 2021 15:26:56 +0200 Subject: [PATCH 391/736] keep original values of publish plugins --- openpype/pipeline/create/context.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index d3453f66ea..ac9362b316 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -166,6 +166,7 @@ class PublishAttributes: self._data = {} self._plugin_names_order = [] data = copy.deepcopy(origin_data) + added_keys = set() for plugin in attr_plugins: data = plugin.convert_attribute_values(data) attr_defs = plugin.get_attribute_defs() @@ -173,6 +174,7 @@ class PublishAttributes: continue key = plugin.__name__ + added_keys.add(key) self._plugin_names_order.append(key) value = data.get(key) or {} @@ -181,6 +183,12 @@ class PublishAttributes: self, attr_defs, value, orig_value ) + for key, value in data.items(): + if key not in added_keys: + self._data[key] = PublishAttributeValues( + self, [], value, value + ) + def __getitem__(self, key): return self._data[key] From bd048b0bc0b902342cd429a3061dcf3835c23618 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 2 Sep 2021 15:48:02 +0200 Subject: [PATCH 392/736] implemented IconBtn which helps to define height of buttons with only icon --- .../tools/new_publisher/widgets/__init__.py | 4 +++- .../new_publisher/widgets/publish_widget.py | 9 +++++---- .../tools/new_publisher/widgets/widgets.py | 20 +++++++++++++++++++ openpype/tools/new_publisher/window.py | 9 +++++---- 4 files changed, 33 insertions(+), 9 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/__init__.py b/openpype/tools/new_publisher/widgets/__init__.py index 9980606248..874bb830c7 100644 --- a/openpype/tools/new_publisher/widgets/__init__.py +++ b/openpype/tools/new_publisher/widgets/__init__.py @@ -4,7 +4,8 @@ from .icons import ( get_icon ) from .widgets import ( - SubsetAttributesWidget + SubsetAttributesWidget, + IconBtn ) from .publish_widget import ( PublishFrame @@ -25,6 +26,7 @@ __all__ = ( "get_icon", "SubsetAttributesWidget", + "IconBtn", "PublishFrame", diff --git a/openpype/tools/new_publisher/widgets/publish_widget.py b/openpype/tools/new_publisher/widgets/publish_widget.py index 2b41aaf346..3f1d4b1556 100644 --- a/openpype/tools/new_publisher/widgets/publish_widget.py +++ b/openpype/tools/new_publisher/widgets/publish_widget.py @@ -7,6 +7,7 @@ from openpype.pipeline import KnownPublishError from .icons import get_icon from .validations_widget import ValidationsWidget from ..publish_report_viewer import PublishReportViewerWidget +from .widgets import IconBtn class PublishFrame(QtWidgets.QFrame): @@ -60,16 +61,16 @@ class PublishFrame(QtWidgets.QFrame): ) show_details_btn.setVisible(False) - reset_btn = QtWidgets.QPushButton(content_widget) + reset_btn = IconBtn(content_widget) reset_btn.setIcon(get_icon("refresh")) - stop_btn = QtWidgets.QPushButton(content_widget) + stop_btn = IconBtn(content_widget) stop_btn.setIcon(get_icon("stop")) - validate_btn = QtWidgets.QPushButton(content_widget) + validate_btn = IconBtn(content_widget) validate_btn.setIcon(get_icon("validate")) - publish_btn = QtWidgets.QPushButton(content_widget) + publish_btn = IconBtn(content_widget) publish_btn.setIcon(get_icon("play")) footer_layout = QtWidgets.QHBoxLayout() diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index af689e5a67..a3bb27bbd4 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -9,6 +9,26 @@ from openpype.tools.flickcharm import FlickCharm from .icons import get_pixmap +class IconBtn(QtWidgets.QPushButton): + """PushButton with icon and size of font. + + Using font metrics height as icon size reference. + """ + def __init__(self, *args, **kwargs): + self._first_show = True + super(IconBtn, self).__init__(*args, **kwargs) + + def resizeEvent(self, event): + super(IconBtn, self).resizeEvent(event) + icon_size = self.fontMetrics().height() + self.setIconSize(QtCore.QSize(icon_size, icon_size)) + + def showEvent(self, event): + super(IconBtn, self).showEvent(event) + icon_size = self.fontMetrics().height() + self.setIconSize(QtCore.QSize(icon_size, icon_size)) + + class AssetsHierarchyModel(QtGui.QStandardItemModel): def __init__(self, controller): super(AssetsHierarchyModel, self).__init__() diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index bd0cbbb381..c3000e7b7a 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -12,6 +12,7 @@ from .widgets import ( InstanceCardView, InstanceListView, CreateDialog, + IconBtn, get_icon ) @@ -107,19 +108,19 @@ class PublisherWindow(QtWidgets.QDialog): # Footer message_input = QtWidgets.QLineEdit(subset_frame) - reset_btn = QtWidgets.QPushButton(subset_frame) + reset_btn = IconBtn(subset_frame) reset_btn.setIcon(get_icon("refresh")) reset_btn.setToolTip("Refresh publishing") - stop_btn = QtWidgets.QPushButton(subset_frame) + stop_btn = IconBtn(subset_frame) stop_btn.setIcon(get_icon("stop")) stop_btn.setToolTip("Stop/Pause publishing") - validate_btn = QtWidgets.QPushButton(subset_frame) + validate_btn = IconBtn(subset_frame) validate_btn.setIcon(get_icon("validate")) validate_btn.setToolTip("Validate") - publish_btn = QtWidgets.QPushButton(subset_frame) + publish_btn = IconBtn(subset_frame) publish_btn.setIcon(get_icon("play")) publish_btn.setToolTip("Publish") From 691e9e5b3ad276663d8b1e01dd053691a2a4d432 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 2 Sep 2021 15:48:54 +0200 Subject: [PATCH 393/736] removed init of IconBtn --- openpype/tools/new_publisher/widgets/widgets.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index a3bb27bbd4..ea4622f01c 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -14,10 +14,6 @@ class IconBtn(QtWidgets.QPushButton): Using font metrics height as icon size reference. """ - def __init__(self, *args, **kwargs): - self._first_show = True - super(IconBtn, self).__init__(*args, **kwargs) - def resizeEvent(self, event): super(IconBtn, self).resizeEvent(event) icon_size = self.fontMetrics().height() From f76ad9ac721e984261715f80d36afbbec27d499e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 2 Sep 2021 17:11:34 +0200 Subject: [PATCH 394/736] added context item to viewvs --- .../widgets/instance_views_widgets.py | 134 +++++++++++++++--- .../tools/new_publisher/widgets/widgets.py | 20 ++- openpype/tools/new_publisher/window.py | 20 ++- 3 files changed, 142 insertions(+), 32 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/instance_views_widgets.py b/openpype/tools/new_publisher/widgets/instance_views_widgets.py index 989dcf3aaf..c243840d21 100644 --- a/openpype/tools/new_publisher/widgets/instance_views_widgets.py +++ b/openpype/tools/new_publisher/widgets/instance_views_widgets.py @@ -8,6 +8,8 @@ from ..constants import ( ) from .icons import get_pixmap +CONTEXT_ID = "context" + class ContextWarningLabel(QtWidgets.QLabel): cached_images_by_size = {} @@ -149,6 +151,29 @@ class InstanceCardWidget(QtWidgets.QWidget): self._set_expanded() +class ContextCardWidget(QtWidgets.QWidget): + def __init__(self, item, parent): + super(ContextCardWidget, self).__init__(parent) + + self.item = item + + subset_name_label = QtWidgets.QLabel("Context", self) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(2, 2, 2, 2) + layout.addWidget(subset_name_label) + layout.addStretch(1) + + self.setAttribute(QtCore.Qt.WA_TranslucentBackground) + subset_name_label.setAttribute(QtCore.Qt.WA_TranslucentBackground) + + self.subset_name_label = subset_name_label + + def showEvent(self, event): + super(ContextCardWidget, self).showEvent(event) + self.item.setSizeHint(self.sizeHint()) + + class _AbstractInstanceView(QtWidgets.QWidget): selection_changed = QtCore.Signal() refreshed = False @@ -161,14 +186,14 @@ class _AbstractInstanceView(QtWidgets.QWidget): "{} Method 'refresh' is not implemented." ).format(self.__class__.__name__)) - def get_selected_instances(self): + def get_selected_items(self): raise NotImplementedError(( - "{} Method 'get_selected_instances' is not implemented." + "{} Method 'get_selected_items' is not implemented." ).format(self.__class__.__name__)) - def set_selected_instances(self, instances): + def set_selected_items(self, instances, context_selected): raise NotImplementedError(( - "{} Method 'set_selected_instances' is not implemented." + "{} Method 'set_selected_items' is not implemented." ).format(self.__class__.__name__)) @@ -195,6 +220,9 @@ class InstanceCardView(_AbstractInstanceView): self._items_by_id = {} self._widgets_by_id = {} + self._context_widget = None + self._context_item = None + self.list_widget = list_widget def refresh(self): @@ -203,6 +231,17 @@ class InstanceCardView(_AbstractInstanceView): instance_id = instance.data["uuid"] instances_by_id[instance_id] = instance + if not self._context_item: + item = QtWidgets.QListWidgetItem(self.list_widget) + item.setData(INSTANCE_ID_ROLE, CONTEXT_ID) + + widget = ContextCardWidget(item, self) + self.list_widget.addItem(item) + self.list_widget.setItemWidget(item, widget) + + self._context_item = item + self._context_widget = widget + for instance_id in tuple(self._items_by_id.keys()): if instance_id not in instances_by_id: item = self._items_by_id.pop(instance_id) @@ -251,18 +290,28 @@ class InstanceCardView(_AbstractInstanceView): def _on_selection_change(self, *_args): self.selection_changed.emit() - def get_selected_instances(self): + def get_selected_items(self): instances = [] + context_selected = False for item in self.list_widget.selectedItems(): instance_id = item.data(INSTANCE_ID_ROLE) - widget = self._widgets_by_id.get(instance_id) - if widget: - instances.append(widget.instance) - return instances + if instance_id == CONTEXT_ID: + context_selected = True + else: + widget = self._widgets_by_id.get(instance_id) + if widget: + instances.append(widget.instance) - def set_selected_instances(self, instances): + return instances, context_selected + + def set_selected_items(self, instances, context_selected): indexes = [] model = self.list_widget.model() + if context_selected and self._context_item is not None: + row = self.list_widget.row(self._context_item) + index = model.index(row, 0) + indexes.append(index) + for instance in instances: instance_id = instance.data["uuid"] item = self._items_by_id.get(instance_id) @@ -339,6 +388,24 @@ class InstanceListItemWidget(QtWidgets.QWidget): self.active_changed.emit(self.instance.data["uuid"], new_value) +class ListContextWidget(QtWidgets.QFrame): + def __init__(self, parent): + super(ListContextWidget, self).__init__(parent) + + label_widget = QtWidgets.QLabel("Context", self) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(5, 0, 2, 0) + layout.addWidget( + label_widget, 1, QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter + ) + + self.setAttribute(QtCore.Qt.WA_TranslucentBackground) + label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + + self.label_widget = label_widget + + class InstanceListGroupWidget(QtWidgets.QFrame): expand_changed = QtCore.Signal(str, bool) @@ -424,6 +491,9 @@ class InstanceListView(_AbstractInstanceView): self._group_items = {} self._group_widgets = {} self._widgets_by_id = {} + self._context_item = None + self._context_widget = None + self.instance_view = instance_view self.instance_model = instance_model self.proxy_model = proxy_model @@ -448,6 +518,28 @@ class InstanceListView(_AbstractInstanceView): families.add(family) instances_by_family[family].append(instance) + sort_at_the_end = False + root_item = self.instance_model.invisibleRootItem() + + context_item = None + if self._context_item is None: + sort_at_the_end = True + context_item = QtGui.QStandardItem() + context_item.setData(0, SORT_VALUE_ROLE) + context_item.setData(CONTEXT_ID, INSTANCE_ID_ROLE) + + root_item.appendRow(context_item) + + index = self.instance_model.index( + context_item.row(), context_item.column() + ) + proxy_index = self.proxy_model.mapFromSource(index) + widget = ListContextWidget(self.instance_view) + self.instance_view.setIndexWidget(proxy_index, widget) + + self._context_widget = widget + self._context_item = context_item + new_group_items = [] for family in families: if family in self._group_items: @@ -459,8 +551,6 @@ class InstanceListView(_AbstractInstanceView): self._group_items[family] = group_item new_group_items.append(group_item) - sort_at_the_end = False - root_item = self.instance_model.invisibleRootItem() if new_group_items: sort_at_the_end = True root_item.appendRows(new_group_items) @@ -552,24 +642,27 @@ class InstanceListView(_AbstractInstanceView): for widget in self._widgets_by_id.values(): widget.update_instance_values() - def get_selected_instances(self): + def get_selected_items(self): instances = [] instances_by_id = {} + context_selected = False for instance in self.controller.instances: instance_id = instance.data["uuid"] instances_by_id[instance_id] = instance for index in self.instance_view.selectionModel().selectedIndexes(): instance_id = index.data(INSTANCE_ID_ROLE) - if instance_id is not None: + if not context_selected and instance_id == CONTEXT_ID: + context_selected = True + + elif instance_id is not None: instance = instances_by_id.get(instance_id) if instance: instances.append(instance) - return instances + return instances, context_selected - def set_selected_instances(self, instances): - model = self.instance_view.model() + def set_selected_items(self, instances, context_selected): instance_ids_by_family = collections.defaultdict(set) for instance in instances: family = instance.data["family"] @@ -577,6 +670,11 @@ class InstanceListView(_AbstractInstanceView): instance_ids_by_family[family].add(instance_id) indexes = [] + if context_selected and self._context_item is not None: + index = self.instance_model.index(self._context_item.row(), 0) + proxy_index = self.proxy_model.mapFromSource(index) + indexes.append(proxy_index) + for family, group_item in self._group_items.items(): selected_ids = instance_ids_by_family[family] if not selected_ids: @@ -588,7 +686,7 @@ class InstanceListView(_AbstractInstanceView): proxy_group_index = self.proxy_model.mapFromSource(group_index) has_indexes = False for row in range(group_item.rowCount()): - index = model.index(row, 0, proxy_group_index) + index = self.proxy_model.index(row, 0, proxy_group_index) instance_id = index.data(INSTANCE_ID_ROLE) if instance_id in selected_ids: indexes.append(index) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index ea4622f01c..8b2ef8f472 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -625,7 +625,7 @@ class GlobalAttrsWidget(QtWidgets.QWidget): self.cancel_btn.setEnabled(enabled) self.submit_btn.setEnabled(enabled) - def set_current_instances(self, instances): + def set_current_instances(self, instances, context_selected): self.set_btns_visible(False) self._current_instances = instances @@ -688,7 +688,7 @@ class FamilyAttrsWidget(QtWidgets.QWidget): # To store content of scroll area to prevend garbage collection self._content_widget = None - def set_current_instances(self, instances): + def set_current_instances(self, instances, context_selected): prev_content_widget = self._scroll_area.widget() if prev_content_widget: self._scroll_area.takeWidget() @@ -760,7 +760,7 @@ class PublishPluginAttrsWidget(QtWidgets.QWidget): # Store content of scroll area to prevend garbage collection self._content_widget = None - def set_current_instances(self, instances): + def set_current_instances(self, instances, context_selected): prev_content_widget = self._scroll_area.widget() if prev_content_widget: self._scroll_area.takeWidget() @@ -886,10 +886,16 @@ class SubsetAttributesWidget(QtWidgets.QWidget): self.publish_attrs_widget = publish_attrs_widget self.thumbnail_widget = thumbnail_widget - def set_current_instances(self, instances): - self.global_attrs_widget.set_current_instances(instances) - self.family_attrs_widget.set_current_instances(instances) - self.publish_attrs_widget.set_current_instances(instances) + def set_current_instances(self, instances, context_selected): + self.global_attrs_widget.set_current_instances( + instances, context_selected + ) + self.family_attrs_widget.set_current_instances( + instances, context_selected + ) + self.publish_attrs_widget.set_current_instances( + instances, context_selected + ) class ThumbnailWidget(QtWidgets.QWidget): diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index c3000e7b7a..0106ba6217 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -232,9 +232,9 @@ class PublisherWindow(QtWidgets.QDialog): def set_context_label(self, label): self.context_label.setText(label) - def get_selected_instances(self): + def get_selected_items(self): view = self.subset_views_layout.currentWidget() - return view.get_selected_instances() + return view.get_selected_items() def _change_view_type(self): old_view = self.subset_views_layout.currentWidget() @@ -251,14 +251,18 @@ class PublisherWindow(QtWidgets.QDialog): new_view.refresh_active_state() if new_view is not old_view: - selected_instances = old_view.get_selected_instances() - new_view.set_selected_instances(selected_instances) + selected_instances, context_selected = ( + old_view.get_selected_items() + ) + new_view.set_selected_items( + selected_instances, context_selected + ) def _on_create_clicked(self): self.creator_window.show() def _on_delete_clicked(self): - instances = self.get_selected_instances() + instances, _ = self.get_selected_items() # Ask user if he really wants to remove instances dialog = QtWidgets.QMessageBox(self) @@ -337,12 +341,14 @@ class PublisherWindow(QtWidgets.QDialog): if self._refreshing_instances: return - instances = self.get_selected_instances() + instances, context_selected = self.get_selected_items() # Disable delete button if nothing is selected self.delete_btn.setEnabled(len(instances) >= 0) - self.subset_attributes_widget.set_current_instances(instances) + self.subset_attributes_widget.set_current_instances( + instances, context_selected + ) def _on_publish_reset(self): self.reset_btn.setEnabled(True) From 8975e14360ccf515f7fd681496b505f189c5c369 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 2 Sep 2021 17:18:30 +0200 Subject: [PATCH 395/736] moved CONTEXT_ID to constants --- openpype/tools/new_publisher/constants.py | 3 +++ .../tools/new_publisher/widgets/instance_views_widgets.py | 5 ++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/tools/new_publisher/constants.py b/openpype/tools/new_publisher/constants.py index 2b7b6eaa62..2a96693c00 100644 --- a/openpype/tools/new_publisher/constants.py +++ b/openpype/tools/new_publisher/constants.py @@ -1,11 +1,14 @@ from Qt import QtCore +CONTEXT_ID = "context" + INSTANCE_ID_ROLE = QtCore.Qt.UserRole + 1 SORT_VALUE_ROLE = QtCore.Qt.UserRole + 2 __all__ = ( + "CONTEXT_ID", "INSTANCE_ID_ROLE", "SORT_VALUE_ROLE" ) diff --git a/openpype/tools/new_publisher/widgets/instance_views_widgets.py b/openpype/tools/new_publisher/widgets/instance_views_widgets.py index c243840d21..9b4afa4245 100644 --- a/openpype/tools/new_publisher/widgets/instance_views_widgets.py +++ b/openpype/tools/new_publisher/widgets/instance_views_widgets.py @@ -4,12 +4,11 @@ from Qt import QtWidgets, QtCore, QtGui from ..constants import ( INSTANCE_ID_ROLE, - SORT_VALUE_ROLE + SORT_VALUE_ROLE, + CONTEXT_ID ) from .icons import get_pixmap -CONTEXT_ID = "context" - class ContextWarningLabel(QtWidgets.QLabel): cached_images_by_size = {} From 143853448d833ad745049a6dab1ad8372e49254a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 2 Sep 2021 17:34:13 +0200 Subject: [PATCH 396/736] publish attributes can be retrieved for context --- openpype/tools/new_publisher/control.py | 15 +++++++++++---- openpype/tools/new_publisher/widgets/widgets.py | 14 +++++--------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 287528eda4..7d0c927b5e 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -483,11 +483,18 @@ class PublisherController: item[2].append(value) return output - def get_publish_attribute_definitions(self, instances): + def get_publish_attribute_definitions(self, instances, include_context): + _tmp_items = [] + if include_context: + _tmp_items.append(self.create_context) + + for instance in instances: + _tmp_items.append(instance) + all_defs_by_plugin_name = {} all_plugin_values = {} - for instance in instances: - for plugin_name, attr_val in instance.publish_attributes.items(): + for item in _tmp_items: + for plugin_name, attr_val in item.publish_attributes.items(): attr_defs = attr_val.attr_defs if not attr_defs: continue @@ -506,7 +513,7 @@ class PublisherController: attr_values = plugin_values[attr_def.key] value = attr_val[attr_def.key] - attr_values.append((instance, value)) + attr_values.append((item, value)) output = [] for plugin in self.plugins_with_defs: diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 8b2ef8f472..e447fa954f 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -625,7 +625,7 @@ class GlobalAttrsWidget(QtWidgets.QWidget): self.cancel_btn.setEnabled(enabled) self.submit_btn.setEnabled(enabled) - def set_current_instances(self, instances, context_selected): + def set_current_instances(self, instances): self.set_btns_visible(False) self._current_instances = instances @@ -688,7 +688,7 @@ class FamilyAttrsWidget(QtWidgets.QWidget): # To store content of scroll area to prevend garbage collection self._content_widget = None - def set_current_instances(self, instances, context_selected): + def set_current_instances(self, instances): prev_content_widget = self._scroll_area.widget() if prev_content_widget: self._scroll_area.takeWidget() @@ -774,7 +774,7 @@ class PublishPluginAttrsWidget(QtWidgets.QWidget): self._attr_def_id_to_plugin_name = {} result = self.controller.get_publish_attribute_definitions( - instances + instances, context_selected ) content_widget = QtWidgets.QWidget(self._scroll_area) @@ -887,12 +887,8 @@ class SubsetAttributesWidget(QtWidgets.QWidget): self.thumbnail_widget = thumbnail_widget def set_current_instances(self, instances, context_selected): - self.global_attrs_widget.set_current_instances( - instances, context_selected - ) - self.family_attrs_widget.set_current_instances( - instances, context_selected - ) + self.global_attrs_widget.set_current_instances(instances) + self.family_attrs_widget.set_current_instances(instances) self.publish_attrs_widget.set_current_instances( instances, context_selected ) From 7a7addd35b340bdb4c60504d72416a39a59a1d90 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 2 Sep 2021 17:34:24 +0200 Subject: [PATCH 397/736] store separator widgets --- openpype/tools/new_publisher/widgets/widgets.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index e447fa954f..20dfe1ac61 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -881,11 +881,16 @@ class SubsetAttributesWidget(QtWidgets.QWidget): layout.addWidget(bottom_widget, 1) self.controller = controller + self.global_attrs_widget = global_attrs_widget + self.family_attrs_widget = family_attrs_widget self.publish_attrs_widget = publish_attrs_widget self.thumbnail_widget = thumbnail_widget + self.top_bottom = top_bottom + self.bottom_separator = bottom_separator + def set_current_instances(self, instances, context_selected): self.global_attrs_widget.set_current_instances(instances) self.family_attrs_widget.set_current_instances(instances) From c1a9a992449dbeb8032c5f6780a3427a9a9629dc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 2 Sep 2021 17:53:06 +0200 Subject: [PATCH 398/736] minor fixes of context reset --- openpype/hosts/testhost/api/pipeline.py | 2 +- openpype/pipeline/create/context.py | 2 +- openpype/tools/new_publisher/control.py | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/testhost/api/pipeline.py b/openpype/hosts/testhost/api/pipeline.py index 4696d6e7f5..816f341822 100644 --- a/openpype/hosts/testhost/api/pipeline.py +++ b/openpype/hosts/testhost/api/pipeline.py @@ -119,7 +119,7 @@ def remove_instances(instances): def get_context_data(): - HostContext.get_context_data() + return HostContext.get_context_data() def update_context_data(data): diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index ac9362b316..42066f1e56 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -513,7 +513,7 @@ class CreateContext: def reset(self): self.reset_plugins() - self.rese_context_data() + self.reset_context_data() self.reset_instances() def reset_plugins(self): diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 7d0c927b5e..3038c8a3b8 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -455,6 +455,7 @@ class PublisherController: self._resetting_instances = True + self.create_context.reset_context_data() self.create_context.reset_instances() self._resetting_instances = False From 5bee95ea3458c3b16d864e7d06d2aa0f6ab28704 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 2 Sep 2021 17:53:28 +0200 Subject: [PATCH 399/736] context collector has `get_attribute_defs` --- .../plugins/publish/collect_context.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/testhost/plugins/publish/collect_context.py b/openpype/hosts/testhost/plugins/publish/collect_context.py index 67d81073ea..418199d3b3 100644 --- a/openpype/hosts/testhost/plugins/publish/collect_context.py +++ b/openpype/hosts/testhost/plugins/publish/collect_context.py @@ -13,14 +13,14 @@ Provides: instance -> representations """ -import os -import json -import copy - import pyblish.api from avalon import io from openpype.hosts.testhost import api +from openpype.pipeline import ( + OpenPypePyblishPluginMixin, + attribute_definitions +) class CollectContextDataTestHost(pyblish.api.ContextPlugin): @@ -33,6 +33,16 @@ class CollectContextDataTestHost(pyblish.api.ContextPlugin): order = pyblish.api.CollectorOrder - 0.49 hosts = ["testhost"] + @classmethod + def get_attribute_defs(cls): + return [ + attribute_definitions.BoolDef( + "test_bool", + True, + label="Bool input" + ) + ] + def process(self, context): # get json paths from os and load them io.install() From 4c7eca92bef34f9c91f6bc7edfff462f827a47a7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 2 Sep 2021 17:53:43 +0200 Subject: [PATCH 400/736] adde dempty context.json --- openpype/hosts/testhost/api/context.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 openpype/hosts/testhost/api/context.json diff --git a/openpype/hosts/testhost/api/context.json b/openpype/hosts/testhost/api/context.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/openpype/hosts/testhost/api/context.json @@ -0,0 +1 @@ +{} From 6ed50af935fa79b6df69532e379c1b4db71031ea Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 2 Sep 2021 17:57:16 +0200 Subject: [PATCH 401/736] few hound fixes --- openpype/hosts/testhost/api/__init__.py | 12 ++++++++++++ openpype/tools/new_publisher/control.py | 6 +++++- .../new_publisher/widgets/validations_widget.py | 1 - openpype/tools/new_publisher/widgets/widgets.py | 1 - 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/testhost/api/__init__.py b/openpype/hosts/testhost/api/__init__.py index 292c7fcb1e..1a5423be61 100644 --- a/openpype/hosts/testhost/api/__init__.py +++ b/openpype/hosts/testhost/api/__init__.py @@ -27,3 +27,15 @@ def install(): pyblish.api.register_host("testhost") pyblish.api.register_plugin_path(PUBLISH_PATH) avalon.api.register_plugin_path(BaseCreator, CREATE_PATH) + + +__all__ = ( + "ls", + "list_instances", + "update_instances", + "remove_instances", + "get_context_data", + "update_context_data", + + "install" +) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 3038c8a3b8..e1578a354d 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -136,9 +136,13 @@ class PublishReport: self._stored_plugins.append(plugin) + label = None + if hasattr(plugin, "label"): + label = plugin.label + self._current_plugin_data = { "name": plugin.__name__, - "label": getattr(plugin, "label", None), + "label": label, "order": plugin.order, "instances_data": [], "skipped": False, diff --git a/openpype/tools/new_publisher/widgets/validations_widget.py b/openpype/tools/new_publisher/widgets/validations_widget.py index 9ce7ec0d4b..50bd4c9c52 100644 --- a/openpype/tools/new_publisher/widgets/validations_widget.py +++ b/openpype/tools/new_publisher/widgets/validations_widget.py @@ -293,7 +293,6 @@ class ValidationsWidget(QtWidgets.QWidget): self._previous_select = None def clear(self): - _old_title_widget = self._title_widgets self._title_widgets = {} self._error_info = {} self._previous_select = None diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 20dfe1ac61..7f33d02d74 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -1,4 +1,3 @@ -import os import copy import collections from Qt import QtWidgets, QtCore, QtGui From 89aa07509cac4376ec7e15e0a11f422410bea6f0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 2 Sep 2021 18:03:29 +0200 Subject: [PATCH 402/736] inherit from OpenPypePyblishPluginMixin --- openpype/hosts/testhost/plugins/publish/collect_context.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/testhost/plugins/publish/collect_context.py b/openpype/hosts/testhost/plugins/publish/collect_context.py index 418199d3b3..158e8404d3 100644 --- a/openpype/hosts/testhost/plugins/publish/collect_context.py +++ b/openpype/hosts/testhost/plugins/publish/collect_context.py @@ -23,7 +23,9 @@ from openpype.pipeline import ( ) -class CollectContextDataTestHost(pyblish.api.ContextPlugin): +class CollectContextDataTestHost( + pyblish.api.ContextPlugin, OpenPypePyblishPluginMixin +): """ Collecting temp json data sent from a host context and path for returning json data back to hostself. From 13302a23df933b1835cfa451ceef4ed5fd2c2365 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 2 Sep 2021 18:21:19 +0200 Subject: [PATCH 403/736] added instance plugin for testing --- .../plugins/publish/collect_context.py | 38 ++++--------- .../plugins/publish/collect_instance_1.py | 56 +++++++++++++++++++ 2 files changed, 68 insertions(+), 26 deletions(-) create mode 100644 openpype/hosts/testhost/plugins/publish/collect_instance_1.py diff --git a/openpype/hosts/testhost/plugins/publish/collect_context.py b/openpype/hosts/testhost/plugins/publish/collect_context.py index 158e8404d3..0a0e5f84d2 100644 --- a/openpype/hosts/testhost/plugins/publish/collect_context.py +++ b/openpype/hosts/testhost/plugins/publish/collect_context.py @@ -1,18 +1,3 @@ -""" -Requires: - environment -> SAPUBLISH_INPATH - environment -> SAPUBLISH_OUTPATH - -Provides: - context -> returnJsonPath (str) - context -> project - context -> asset - instance -> destination_list (list) - instance -> representations (list) - instance -> source (list) - instance -> representations -""" - import pyblish.api from avalon import io @@ -32,7 +17,7 @@ class CollectContextDataTestHost( """ label = "Collect Context - Test Host" - order = pyblish.api.CollectorOrder - 0.49 + order = pyblish.api.CollectorOrder - 0.5 hosts = ["testhost"] @classmethod @@ -59,16 +44,17 @@ class CollectContextDataTestHost( instance_families = in_data.get("families") or [] instance = context.create_instance(subset) - instance.data.update( - { - "subset": subset, - "asset": in_data["asset"], - "label": subset, - "name": subset, - "family": in_data["family"], - "families": instance_families - } - ) + instance.data.update({ + "subset": subset, + "asset": in_data["asset"], + "label": subset, + "name": subset, + "family": in_data["family"], + "families": instance_families + }) + for key, value in in_data.items(): + if key not in instance.data: + instance.data[key] = value self.log.info("collected instance: {}".format(instance.data)) self.log.info("parsing data: {}".format(in_data)) diff --git a/openpype/hosts/testhost/plugins/publish/collect_instance_1.py b/openpype/hosts/testhost/plugins/publish/collect_instance_1.py new file mode 100644 index 0000000000..eee12d02fb --- /dev/null +++ b/openpype/hosts/testhost/plugins/publish/collect_instance_1.py @@ -0,0 +1,56 @@ +import json +import pyblish.api +from avalon import io + +from openpype.hosts.testhost import api +from openpype.pipeline import ( + OpenPypePyblishPluginMixin, + attribute_definitions +) + + +class CollectInstanceOneTestHost( + pyblish.api.InstancePlugin, OpenPypePyblishPluginMixin +): + """ + Collecting temp json data sent from a host context + and path for returning json data back to hostself. + """ + + label = "Collect Instance 1 - Test Host" + order = pyblish.api.CollectorOrder - 0.3 + hosts = ["testhost"] + + @classmethod + def get_attribute_defs(cls): + return [ + attribute_definitions.NumberDef( + "version", + default=1, + minimum=1, + maximum=999, + decimals=0, + label="Version" + ) + ] + + def process(self, instance): + self._debug_log(instance) + + publish_attributes = instance.data.get("publish_attributes") + if not publish_attributes: + return + + values = publish_attributes.get(self.__class__.__name__) + if not values: + return + + instance.data["version"] = values["version"] + + def _debug_log(self, instance): + def _default_json(value): + return str(value) + + self.log.info( + json.dumps(instance.data, indent=4, default=_default_json) + ) From f3112d62c874a049380d4bb72a598d9ae1936a0e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 2 Sep 2021 18:47:19 +0200 Subject: [PATCH 404/736] fixed json dump --- openpype/hosts/testhost/api/pipeline.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/testhost/api/pipeline.py b/openpype/hosts/testhost/api/pipeline.py index 816f341822..53028e6518 100644 --- a/openpype/hosts/testhost/api/pipeline.py +++ b/openpype/hosts/testhost/api/pipeline.py @@ -60,7 +60,7 @@ class HostContext: if not os.path.exists(json_path): data = {} with open(json_path, "w") as json_stream: - json.dump(json_stream, data) + json.dump(data, json_stream) else: with open(json_path, "r") as json_stream: data = json.load(json_stream) @@ -70,7 +70,7 @@ class HostContext: def save_context_data(cls, data): json_path = cls.get_context_json_path() with open(json_path, "w") as json_stream: - json.dump(json_stream, data) + json.dump(data, json_stream, indent=4) def ls(): From 465c6d94e34842ffb3617b81cb9fc7505394bedc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 2 Sep 2021 18:47:31 +0200 Subject: [PATCH 405/736] modified what is sent to update_context_data --- openpype/hosts/testhost/api/pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/testhost/api/pipeline.py b/openpype/hosts/testhost/api/pipeline.py index 53028e6518..063b90fbcc 100644 --- a/openpype/hosts/testhost/api/pipeline.py +++ b/openpype/hosts/testhost/api/pipeline.py @@ -122,5 +122,5 @@ def get_context_data(): return HostContext.get_context_data() -def update_context_data(data): +def update_context_data(data, changes): HostContext.save_context_data(data) From 9ed8a50e3b43c2b0c0fd64bf9c41f1763d419931 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 2 Sep 2021 18:47:52 +0200 Subject: [PATCH 406/736] added saving of context to save process --- openpype/pipeline/create/context.py | 32 ++++++++++++++++++++++--- openpype/tools/new_publisher/control.py | 4 ++-- openpype/tools/new_publisher/window.py | 2 +- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 42066f1e56..e2f5f33c13 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -461,6 +461,7 @@ class CreateContext: self._log = None self._publish_attributes = PublishAttributes(self, {}) + self._original_context_data = {} self.host = host host_is_valid = True @@ -567,15 +568,31 @@ class CreateContext: def reset_context_data(self): if not self.host_is_valid: + self._original_context_data = {} self._publish_attributes = PublishAttributes(self, {}) return - original_data = self.host.get_context_data() + original_data = self.host.get_context_data() or {} + self._original_context_data = copy.deepcopy(original_data) + + publish_attributes = original_data.get("publish_attributes") or {} + attr_plugins = self._get_publish_plugins_with_attr_for_context() self._publish_attributes = PublishAttributes( - self, original_data, attr_plugins + self, publish_attributes, attr_plugins ) + def context_data_to_store(self): + return { + "publish_attributes": self._publish_attributes.data_to_store() + } + + def context_data_changes(self): + changes = { + "publish_attributes": self._publish_attributes.changes() + } + return changes + def reset_instances(self): instances = [] if not self.host_is_valid: @@ -642,11 +659,20 @@ class CreateContext: self.instances = instances - def save_instance_changes(self): + def save_changes(self): if not self.host_is_valid: missing_methods = self.get_host_misssing_methods(self.host) raise HostMissRequiredMethod(self.host, missing_methods) + self._save_context_changes() + self._save_instance_changes() + + def _save_context_changes(self): + data = self.context_data_to_store() + changes = self.context_data_changes() + self.host.update_context_data(data, changes) + + def _save_instance_changes(self): update_list = [] for instance in self.instances: instance_changes = instance.changes() diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index e1578a354d..79f967168a 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -541,8 +541,8 @@ class PublisherController: self._reset_instances() - def save_instance_changes(self): - self.create_context.save_instance_changes() + def save_changes(self): + self.create_context.save_changes() def remove_instances(self, instances): self.create_context.remove_instances(instances) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 0106ba6217..7217252910 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -291,7 +291,7 @@ class PublisherWindow(QtWidgets.QDialog): self._change_view_type() def _on_save_clicked(self): - self.controller.save_instance_changes() + self.controller.save_changes() def _set_publish_visibility(self, visible): if visible: From d187a461022efa28f76cfd0acd437cafdc61ef66 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 2 Sep 2021 18:49:04 +0200 Subject: [PATCH 407/736] save context data only if there are changes --- openpype/pipeline/create/context.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index e2f5f33c13..3fb001c2a9 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -588,9 +588,10 @@ class CreateContext: } def context_data_changes(self): - changes = { - "publish_attributes": self._publish_attributes.changes() - } + changes = {} + publish_attribute_changes = self._publish_attributes.changes() + if publish_attribute_changes: + changes["publish_attributes"] = publish_attribute_changes return changes def reset_instances(self): @@ -668,9 +669,10 @@ class CreateContext: self._save_instance_changes() def _save_context_changes(self): - data = self.context_data_to_store() changes = self.context_data_changes() - self.host.update_context_data(data, changes) + if changes: + data = self.context_data_to_store() + self.host.update_context_data(data, changes) def _save_instance_changes(self): update_list = [] From 13967eae00107c341f75112727a243db7aa336f6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 2 Sep 2021 18:51:27 +0200 Subject: [PATCH 408/736] added auto save of changes and removed save btn --- openpype/tools/new_publisher/control.py | 4 ++++ openpype/tools/new_publisher/window.py | 10 ++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 79f967168a..d8aa0da654 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -625,6 +625,10 @@ class PublisherController: """Start or continue in publishing.""" if self._publish_is_running: return + + # Make sure changes are saved + self.save_changes() + self._publish_is_running = True self._trigger_callbacks(self._publish_started_callback_refs) self._main_thread_processor.start() diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 7217252910..e5a5517c82 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -74,7 +74,6 @@ class PublisherWindow(QtWidgets.QDialog): # Buttons at the bottom of subset view create_btn = QtWidgets.QPushButton("+", subset_frame) delete_btn = QtWidgets.QPushButton("-", subset_frame) - save_btn = QtWidgets.QPushButton("Save", subset_frame) change_view_btn = QtWidgets.QPushButton("=", subset_frame) # Subset details widget @@ -88,7 +87,6 @@ class PublisherWindow(QtWidgets.QDialog): subset_view_btns_layout.setSpacing(5) subset_view_btns_layout.addWidget(create_btn) subset_view_btns_layout.addWidget(delete_btn) - subset_view_btns_layout.addWidget(save_btn) subset_view_btns_layout.addStretch(1) subset_view_btns_layout.addWidget(change_view_btn) @@ -164,7 +162,6 @@ class PublisherWindow(QtWidgets.QDialog): create_btn.clicked.connect(self._on_create_clicked) delete_btn.clicked.connect(self._on_delete_clicked) - save_btn.clicked.connect(self._on_save_clicked) change_view_btn.clicked.connect(self._on_change_view_clicked) reset_btn.clicked.connect(self._on_reset_clicked) @@ -226,6 +223,10 @@ class PublisherWindow(QtWidgets.QDialog): self._first_show = False self.reset() + def closeEvent(self, event): + self.controller.save_changes() + super(PublisherWindow, self).closeEvent(event) + def reset(self): self.controller.reset() @@ -290,9 +291,6 @@ class PublisherWindow(QtWidgets.QDialog): def _on_change_view_clicked(self): self._change_view_type() - def _on_save_clicked(self): - self.controller.save_changes() - def _set_publish_visibility(self, visible): if visible: widget = self.publish_frame From 9aa6cfd00d69b4123b601b8a7d4f62d5980b1f75 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 2 Sep 2021 19:17:06 +0200 Subject: [PATCH 409/736] fix report per instance --- openpype/tools/new_publisher/control.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index d8aa0da654..63d2cd9f87 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -157,7 +157,7 @@ class PublishReport: def add_result(self, result): instance = result["instance"] instance_id = None - if instance: + if instance is not None: instance_id = instance.id self._current_plugin_data["instances_data"].append({ "id": instance_id, From 7094866e1f463cb4f3e8630924e7387ad14b39b1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 3 Sep 2021 10:37:26 +0200 Subject: [PATCH 410/736] save changes on create/remove instance --- openpype/tools/new_publisher/control.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 63d2cd9f87..e9f6e10453 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -535,7 +535,9 @@ class PublisherController: def create(self, family, subset_name, instance_data, options): # QUESTION Force to return instances or call `list_instances` on each # creation? (`list_instances` may slow down...) - # WARNING changes done before creation will be lost + # - also save may not be required in that case + self.save_changes() + creator = self.creators[family] creator.create(subset_name, instance_data, options) @@ -545,6 +547,10 @@ class PublisherController: self.create_context.save_changes() def remove_instances(self, instances): + # QUESTION Expect that instaces are really removed? In that case save + # reset is not required and save changes too. + self.save_changes() + self.create_context.remove_instances(instances) self._reset_instances() From 7d0d87e8cbe30ac321cd647a5efc067d897acab2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 3 Sep 2021 11:02:53 +0200 Subject: [PATCH 411/736] removed auto close and use selection checkboxes --- .../new_publisher/widgets/create_dialog.py | 23 +------------------ 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/create_dialog.py b/openpype/tools/new_publisher/widgets/create_dialog.py index fe11c89100..85a4baf43d 100644 --- a/openpype/tools/new_publisher/widgets/create_dialog.py +++ b/openpype/tools/new_publisher/widgets/create_dialog.py @@ -127,19 +127,6 @@ class CreateDialog(QtWidgets.QDialog): subset_name_input = QtWidgets.QLineEdit(self) subset_name_input.setEnabled(False) - checkbox_inputs = QtWidgets.QWidget(self) - auto_close_checkbox = QtWidgets.QCheckBox( - "Auto-close", checkbox_inputs - ) - use_selection_checkbox = QtWidgets.QCheckBox( - "Use selection", checkbox_inputs - ) - - checkbox_layout = QtWidgets.QHBoxLayout(checkbox_inputs) - checkbox_layout.setContentsMargins(0, 0, 0, 0) - checkbox_layout.addWidget(auto_close_checkbox) - checkbox_layout.addWidget(use_selection_checkbox) - create_btn = QtWidgets.QPushButton("Create", self) create_btn.setEnabled(False) @@ -152,7 +139,6 @@ class CreateDialog(QtWidgets.QDialog): layout.addLayout(variant_layout, 0) layout.addWidget(QtWidgets.QLabel("Subset:", self)) layout.addWidget(subset_name_input, 0) - layout.addWidget(checkbox_inputs, 0) layout.addWidget(create_btn, 0) create_btn.clicked.connect(self._on_create) @@ -175,8 +161,6 @@ class CreateDialog(QtWidgets.QDialog): self.family_model = family_model self.family_view = family_view - self.auto_close_checkbox = auto_close_checkbox - self.use_selection_checkbox = auto_close_checkbox self.create_btn = create_btn @property @@ -408,9 +392,7 @@ class CreateDialog(QtWidgets.QDialog): variant = self.variant_input.text() asset_name = self._asset_doc["name"] task_name = self.dbcon.Session.get("AVALON_TASK") - options = { - "useSelection": self.use_selection_checkbox.isChecked() - } + options = {} # Where to define these data? # - what data show be stored? instance_data = { @@ -441,6 +423,3 @@ class CreateDialog(QtWidgets.QDialog): box.show() # Store dialog so is not garbage collected before is shown self.message_dialog = box - - if self.auto_close_checkbox.isChecked(): - self.hide() From 4f121f0645bcc6e5da25f02c43004acdd222af35 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 3 Sep 2021 11:03:19 +0200 Subject: [PATCH 412/736] it is possible to pass asset name and task name to creator dialog --- .../new_publisher/widgets/create_dialog.py | 21 ++++++++++++++----- openpype/tools/new_publisher/window.py | 2 +- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/create_dialog.py b/openpype/tools/new_publisher/widgets/create_dialog.py index 85a4baf43d..8ea70d299f 100644 --- a/openpype/tools/new_publisher/widgets/create_dialog.py +++ b/openpype/tools/new_publisher/widgets/create_dialog.py @@ -87,11 +87,22 @@ class CreateErrorMessageBox(QtWidgets.QDialog): class CreateDialog(QtWidgets.QDialog): - def __init__(self, controller, parent=None): + def __init__( + self, controller, asset_name=None, task_name=None, parent=None + ): super(CreateDialog, self).__init__(parent) self.controller = controller + if asset_name is None: + asset_name = self.dbcon.Session.get("AVALON_ASSET") + + if task_name is None: + task_name = self.dbcon.Session.get("AVALON_TASK") + + self._asset_name = asset_name + self._task_name = task_name + self._last_pos = None self._asset_doc = None self._subset_names = None @@ -189,7 +200,7 @@ class CreateDialog(QtWidgets.QDialog): self.variant_hints_btn.setEnabled(self._prereq_available) def _refresh_asset(self): - asset_name = self.dbcon.Session.get("AVALON_ASSET") + asset_name = self._asset_name # Skip if asset did not change if self._asset_doc and self._asset_doc["name"] == asset_name: @@ -298,7 +309,7 @@ class CreateDialog(QtWidgets.QDialog): return project_name = self.dbcon.Session["AVALON_PROJECT"] - task_name = self.dbcon.Session.get("AVALON_TASK") + task_name = self._task_name asset_doc = copy.deepcopy(self._asset_doc) # Calculate subset name with Creator plugin @@ -390,8 +401,8 @@ class CreateDialog(QtWidgets.QDialog): family = index.data(QtCore.Qt.DisplayRole) subset_name = self.subset_name_input.text() variant = self.variant_input.text() - asset_name = self._asset_doc["name"] - task_name = self.dbcon.Session.get("AVALON_TASK") + asset_name = self.asset_name + task_name = self._task_name options = {} # Where to define these data? # - what data show be stored? diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index e5a5517c82..750ae7c264 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -158,7 +158,7 @@ class PublisherWindow(QtWidgets.QDialog): main_layout.addWidget(line_widget, 0) main_layout.addLayout(content_stacked_layout, 1) - creator_window = CreateDialog(controller, self) + creator_window = CreateDialog(controller, parent=self) create_btn.clicked.connect(self._on_create_clicked) delete_btn.clicked.connect(self._on_delete_clicked) From 7906defafebe668e86646c387b939ac1ded4f498 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 3 Sep 2021 11:50:43 +0200 Subject: [PATCH 413/736] store actions result to report --- openpype/tools/new_publisher/control.py | 73 +++++++++++++++++++------ 1 file changed, 55 insertions(+), 18 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index e9f6e10453..e301a6e4ec 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -115,6 +115,8 @@ class PublishReport: self.controller = controller self._publish_discover_result = None self._plugin_data = [] + self._plugin_data_with_plugin = [] + self._stored_plugins = [] self._current_plugin_data = [] self._all_instances_by_id = {} @@ -123,6 +125,7 @@ class PublishReport: def reset(self, context, publish_discover_result=None): self._publish_discover_result = publish_discover_result self._plugin_data = [] + self._plugin_data_with_plugin = [] self._current_plugin_data = {} self._all_instances_by_id = {} self._current_context = context @@ -134,22 +137,41 @@ class PublishReport: if self._current_plugin_data: self._current_plugin_data["passed"] = True + self._current_plugin_data = self._add_plugin_data_item(plugin) + + def _get_plugin_data_item(self, plugin): + store_item = None + for item in self._plugin_data_with_plugin: + if item["plugin"] is plugin: + store_item = item["data"] + break + return store_item + + def _add_plugin_data_item(self, plugin): + if plugin in self._stored_plugins: + raise ValueError("Plugin is already stored") + self._stored_plugins.append(plugin) label = None if hasattr(plugin, "label"): label = plugin.label - self._current_plugin_data = { + plugin_data_item = { "name": plugin.__name__, "label": label, "order": plugin.order, "instances_data": [], + "actions_data": [], "skipped": False, "passed": False } - - self._plugin_data.append(self._current_plugin_data) + self._plugin_data_with_plugin.append({ + "plugin": plugin, + "data": plugin_data_item + }) + self._plugin_data.append(plugin_data_item) + return plugin_data_item def set_plugin_skipped(self): self._current_plugin_data["skipped"] = True @@ -161,7 +183,24 @@ class PublishReport: instance_id = instance.id self._current_plugin_data["instances_data"].append({ "id": instance_id, - "logs": self._extract_log_items(result) + "logs": self._extract_instance_log_items(result) + }) + + def add_action_result(self, action, result): + plugin = result["plugin"] + + store_item = self._get_plugin_data_item(plugin) + if store_item is None: + store_item = self._add_plugin_data_item(plugin) + + action_name = action.__name__ + action_label = action.label or action_name + log_items = self._extract_log_items(result) + store_item["actions_data"].append({ + "success": result["success"], + "name": action_name, + "label": action_label, + "logs": log_items }) def get_report(self, publish_plugins=None): @@ -178,14 +217,7 @@ class PublishReport: if publish_plugins: for plugin in publish_plugins: if plugin not in self._stored_plugins: - plugins_data.append({ - "name": plugin.__name__, - "label": getattr(plugin, "label"), - "order": plugin.order, - "instances_data": [], - "skipped": False, - "passed": False - }) + plugins_data.append(self._add_plugin_data_item(plugin)) crashed_file_paths = {} if self._publish_discover_result is not None: @@ -216,14 +248,20 @@ class PublishReport: "exists": exists } - def _extract_log_items(self, result): - output = [] - records = result.get("records") or [] + def _extract_instance_log_items(self, result): instance = result["instance"] instance_id = None if instance: instance_id = instance.id + log_items = self._extract_log_items(result) + for item in log_items: + item["instance_id"] = instance_id + return log_items + + def _extract_log_items(self, result): + output = [] + records = result.get("records") or [] for record in records: record_exc_info = record.exc_info if record_exc_info is not None: @@ -237,7 +275,6 @@ class PublishReport: msg = str(record.msg) output.append({ - "instance_id": instance_id, "type": "record", "msg": msg, "name": record.name, @@ -255,7 +292,6 @@ class PublishReport: if exception: fname, line_no, func, exc = exception.traceback output.append({ - "instance_id": instance_id, "type": "error", "msg": str(exception), "filename": str(fname), @@ -651,10 +687,11 @@ class PublisherController: self._stop_publish() def run_action(self, plugin, action): - # TODO handle result + # TODO handle result in UI result = pyblish.plugin.process( plugin, self._publish_context, None, action.id ) + self._publish_report.add_action_result(action, result) def _publish_next_process(self): # Validations of progress before using iterator From 411b62a869ba58c6d439714c0fa836f09ae31668 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 3 Sep 2021 11:56:06 +0200 Subject: [PATCH 414/736] change bg based on validation errors --- openpype/tools/new_publisher/widgets/publish_widget.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/tools/new_publisher/widgets/publish_widget.py b/openpype/tools/new_publisher/widgets/publish_widget.py index 3f1d4b1556..1d4375a57f 100644 --- a/openpype/tools/new_publisher/widgets/publish_widget.py +++ b/openpype/tools/new_publisher/widgets/publish_widget.py @@ -376,6 +376,9 @@ class PublishFrame(QtWidgets.QFrame): def _on_close_report_clicked(self): if self.controller.get_publish_crash_error(): self._change_bg_property() + + elif self.controller.get_validation_errors(): + self._change_bg_property(1) else: self._change_bg_property(2) self._main_layout.setCurrentWidget(self.publish_widget) From 2f3b7146ec3a3f8616dcd7a896cb3cb8455de4f1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 3 Sep 2021 12:21:51 +0200 Subject: [PATCH 415/736] validation error title are scrollable --- .../widgets/validations_widget.py | 53 +++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/validations_widget.py b/openpype/tools/new_publisher/widgets/validations_widget.py index 50bd4c9c52..d521d82922 100644 --- a/openpype/tools/new_publisher/widgets/validations_widget.py +++ b/openpype/tools/new_publisher/widgets/validations_widget.py @@ -239,18 +239,65 @@ class ValidateActionsWidget(QtWidgets.QFrame): self.controller.run_action(self._plugin, action) +class VerticallScrollArea(QtWidgets.QScrollArea): + def __init__(self, *args, **kwargs): + super(VerticallScrollArea, self).__init__(*args, **kwargs) + + self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) + self.setLayoutDirection(QtCore.Qt.RightToLeft) + self.setViewportMargins(0, 0, 0, 0) + + self.verticalScrollBar().installEventFilter(self) + + def setVerticalScrollBar(self, widget): + old_widget = self.verticalScrollBar() + if old_widget: + old_widget.removeEventFilter(self) + + super(VerticallScrollArea, self).setVerticalScrollBar(widget) + if widget: + widget.installEventFilter(self) + + def setWidget(self, widget): + old_widget = self.widget() + if old_widget: + old_widget.removeEventFilter(self) + + super(VerticallScrollArea, self).setWidget(widget) + if widget: + widget.installEventFilter(self) + + def eventFilter(self, obj, event): + if ( + event.type() == QtCore.QEvent.Resize + and (obj is self.widget() or obj is self.verticalScrollBar()) + ): + width = self.widget().width() + if self.verticalScrollBar().isVisible(): + width += self.verticalScrollBar().width() + self.setMinimumWidth(width) + return super(VerticallScrollArea, self).eventFilter(obj, event) + + class ValidationsWidget(QtWidgets.QWidget): def __init__(self, controller, parent): super(ValidationsWidget, self).__init__(parent) self.setAttribute(QtCore.Qt.WA_TranslucentBackground) - errors_widget = QtWidgets.QWidget(self) - errors_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + errors_scroll = VerticallScrollArea(self) + errors_scroll.setWidgetResizable(True) + errors_scroll.setAttribute(QtCore.Qt.WA_TranslucentBackground) + + errors_widget = QtWidgets.QWidget(errors_scroll) errors_widget.setFixedWidth(200) + errors_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) errors_layout = QtWidgets.QVBoxLayout(errors_widget) errors_layout.setContentsMargins(0, 0, 0, 0) + errors_scroll.setWidget(errors_widget) + error_details_widget = QtWidgets.QWidget(self) error_details_input = QtWidgets.QTextEdit(error_details_widget) error_details_input.setObjectName("InfoText") @@ -269,7 +316,7 @@ class ValidationsWidget(QtWidgets.QWidget): content_layout.setSpacing(0) content_layout.setContentsMargins(0, 0, 0, 0) - content_layout.addWidget(errors_widget, 0) + content_layout.addWidget(errors_scroll, 0) content_layout.addWidget(error_details_widget, 1) top_label = QtWidgets.QLabel("Publish validation report", self) From 294c7b05e55eb8886a5b0cc80d65765b038f56b3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 3 Sep 2021 13:58:04 +0200 Subject: [PATCH 416/736] fixed width of validation error --- .../widgets/validations_widget.py | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/validations_widget.py b/openpype/tools/new_publisher/widgets/validations_widget.py index d521d82922..a36d3d1f8f 100644 --- a/openpype/tools/new_publisher/widgets/validations_widget.py +++ b/openpype/tools/new_publisher/widgets/validations_widget.py @@ -246,10 +246,24 @@ class VerticallScrollArea(QtWidgets.QScrollArea): self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) self.setLayoutDirection(QtCore.Qt.RightToLeft) + + self.setAttribute(QtCore.Qt.WA_TranslucentBackground) + # Background of scrollbar will be transparent + scrollbar_bg = self.verticalScrollBar().parent() + if scrollbar_bg: + scrollbar_bg.setAttribute(QtCore.Qt.WA_TranslucentBackground) self.setViewportMargins(0, 0, 0, 0) self.verticalScrollBar().installEventFilter(self) + # Timer with 100ms offset after changing size + size_changed_timer = QtCore.QTimer() + size_changed_timer.setInterval(100) + size_changed_timer.setSingleShot(True) + + size_changed_timer.timeout.connect(self._on_timer_timeout) + self._size_changed_timer = size_changed_timer + def setVerticalScrollBar(self, widget): old_widget = self.verticalScrollBar() if old_widget: @@ -268,15 +282,18 @@ class VerticallScrollArea(QtWidgets.QScrollArea): if widget: widget.installEventFilter(self) + def _on_timer_timeout(self): + width = self.widget().width() + if self.verticalScrollBar().isVisible(): + width += self.verticalScrollBar().width() + self.setMinimumWidth(width) + def eventFilter(self, obj, event): if ( event.type() == QtCore.QEvent.Resize and (obj is self.widget() or obj is self.verticalScrollBar()) ): - width = self.widget().width() - if self.verticalScrollBar().isVisible(): - width += self.verticalScrollBar().width() - self.setMinimumWidth(width) + self._size_changed_timer.start() return super(VerticallScrollArea, self).eventFilter(obj, event) @@ -288,7 +305,6 @@ class ValidationsWidget(QtWidgets.QWidget): errors_scroll = VerticallScrollArea(self) errors_scroll.setWidgetResizable(True) - errors_scroll.setAttribute(QtCore.Qt.WA_TranslucentBackground) errors_widget = QtWidgets.QWidget(errors_scroll) errors_widget.setFixedWidth(200) From d69f94b8496a5988f6519acebaf477ee6e70609e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 3 Sep 2021 14:07:34 +0200 Subject: [PATCH 417/736] added get_openpype_icon_filepath and get_openpype_splash_filepath functions --- openpype/resources/__init__.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/openpype/resources/__init__.py b/openpype/resources/__init__.py index ef4ed73974..8d4f3fd1fa 100644 --- a/openpype/resources/__init__.py +++ b/openpype/resources/__init__.py @@ -30,23 +30,31 @@ def get_liberation_font_path(bold=False, italic=False): return font_path -def pype_icon_filepath(debug=None): - if debug is None: - debug = bool(os.getenv("OPENPYPE_DEV")) +def get_openpype_icon_filepath(staging=None): + if staging is None: + staging = bool(os.getenv("OPENPYPE_DEV")) - if debug: + if staging: icon_file_name = "openpype_icon_staging.png" else: icon_file_name = "openpype_icon.png" return get_resource("icons", icon_file_name) -def pype_splash_filepath(debug=None): - if debug is None: - debug = bool(os.getenv("OPENPYPE_DEV")) +def get_openpype_splash_filepath(staging=None): + if staging is None: + staging = bool(os.getenv("OPENPYPE_DEV")) - if debug: + if staging: splash_file_name = "openpype_splash_staging.png" else: splash_file_name = "openpype_splash.png" return get_resource("icons", splash_file_name) + + +def pype_icon_filepath(staging=None): + return get_openpype_icon_filepath(staging) + + +def pype_splash_filepath(staging=None): + return get_openpype_splash_filepath(staging) From 8289e93453ec426dc03b4e92b89a93f76bcd5598 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 3 Sep 2021 17:08:01 +0200 Subject: [PATCH 418/736] added icon attribute to Creator class --- openpype/pipeline/create/creator_plugins.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index d273f4cdb1..8e85ffb0ad 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -188,6 +188,9 @@ class Creator(BaseCreator): # Label shown in UI label = None + # Icon shown in UI + icon = None + # Short description of family description = None From 77e6ad09846281c8353410b8f3baf744b9cb18f8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 3 Sep 2021 17:11:42 +0200 Subject: [PATCH 419/736] added variant regex validation --- openpype/style/style.css | 3 ++ openpype/tools/new_publisher/constants.py | 9 ++++++ .../new_publisher/widgets/create_dialog.py | 29 ++++++++++++++++--- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/openpype/style/style.css b/openpype/style/style.css index 581905b347..873979afb8 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -650,6 +650,9 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { #VariantInput[state="new"], #VariantInput[state="new"]:focus, #VariantInput[state="new"]:hover { border-color: #7AAB8F; } +#VariantInput[state="invalid"], #VariantInput[state="invalid"]:focus, #VariantInput[state="invalid"]:hover { + border-color: #AA5050; +} #VariantInput[state="empty"], #VariantInput[state="empty"]:focus, #VariantInput[state="empty"]:hover { border-color: {color:bg-inputs}; diff --git a/openpype/tools/new_publisher/constants.py b/openpype/tools/new_publisher/constants.py index 2a96693c00..d281d01f4a 100644 --- a/openpype/tools/new_publisher/constants.py +++ b/openpype/tools/new_publisher/constants.py @@ -3,12 +3,21 @@ from Qt import QtCore CONTEXT_ID = "context" +SUBSET_NAME_ALLOWED_SYMBOLS = "a-zA-Z0-9_\." +VARIANT_TOOLTIP = ( + "Variant may contain alphabetical characters (a-Z)" + "\nnumerical characters (0-9) dot (\".\") or underscore (\"_\")." +) INSTANCE_ID_ROLE = QtCore.Qt.UserRole + 1 SORT_VALUE_ROLE = QtCore.Qt.UserRole + 2 __all__ = ( "CONTEXT_ID", + + "SUBSET_NAME_ALLOWED_SYMBOLS", + "VARIANT_TOOLTIP", + "INSTANCE_ID_ROLE", "SORT_VALUE_ROLE" ) diff --git a/openpype/tools/new_publisher/widgets/create_dialog.py b/openpype/tools/new_publisher/widgets/create_dialog.py index 8ea70d299f..6d199e56e8 100644 --- a/openpype/tools/new_publisher/widgets/create_dialog.py +++ b/openpype/tools/new_publisher/widgets/create_dialog.py @@ -7,6 +7,11 @@ from Qt import QtWidgets, QtCore, QtGui from openpype.pipeline.create import CreatorError +from ..constants import ( + SUBSET_NAME_ALLOWED_SYMBOLS, + VARIANT_TOOLTIP +) + SEPARATORS = ("---separator---", "---") @@ -112,12 +117,17 @@ class CreateDialog(QtWidgets.QDialog): self.message_dialog = None + name_pattern = "^[{}]*$".format(SUBSET_NAME_ALLOWED_SYMBOLS) + self._name_pattern = name_pattern + self._compiled_name_pattern = re.compile(name_pattern) + family_view = QtWidgets.QListView(self) family_model = QtGui.QStandardItemModel() family_view.setModel(family_model) variant_input = QtWidgets.QLineEdit(self) variant_input.setObjectName("VariantInput") + variant_input.setToolTip(VARIANT_TOOLTIP) variant_hints_btn = QtWidgets.QPushButton(self) variant_hints_btn.setFixedWidth(18) @@ -308,6 +318,14 @@ class CreateDialog(QtWidgets.QDialog): self.subset_name_input.setText("") return + match = self._compiled_name_pattern.match(variant_value) + valid = bool(match) + self.create_btn.setEnabled(valid) + if not valid: + self._set_variant_state_property("invalid") + self.subset_name_input.setText("< Invalid variant >") + return + project_name = self.dbcon.Session["AVALON_PROJECT"] task_name = self._task_name @@ -369,15 +387,18 @@ class CreateDialog(QtWidgets.QDialog): else: property_value = "new" - current_value = self.variant_input.property("state") - if current_value != property_value: - self.variant_input.setProperty("state", property_value) - self.variant_input.style().polish(self.variant_input) + self._set_variant_state_property(property_value) variant_is_valid = variant_value.strip() != "" if variant_is_valid != self.create_btn.isEnabled(): self.create_btn.setEnabled(variant_is_valid) + def _set_variant_state_property(self, state): + current_value = self.variant_input.property("state") + if current_value != state: + self.variant_input.setProperty("state", state) + self.variant_input.style().polish(self.variant_input) + def moveEvent(self, event): super(CreateDialog, self).moveEvent(event) self._last_pos = self.pos() From 4ae582b9ea60c9dd073f24db6104f570e3ee1a00 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 3 Sep 2021 17:12:09 +0200 Subject: [PATCH 420/736] added few docstrings --- openpype/tools/new_publisher/constants.py | 6 +++++- openpype/tools/new_publisher/widgets/create_dialog.py | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/tools/new_publisher/constants.py b/openpype/tools/new_publisher/constants.py index d281d01f4a..1e3481f594 100644 --- a/openpype/tools/new_publisher/constants.py +++ b/openpype/tools/new_publisher/constants.py @@ -1,13 +1,17 @@ from Qt import QtCore - +# ID of context item in instance view CONTEXT_ID = "context" +# Allowed symbols for subset name (and variant) +# - characters, numbers, unsercore and dash SUBSET_NAME_ALLOWED_SYMBOLS = "a-zA-Z0-9_\." VARIANT_TOOLTIP = ( "Variant may contain alphabetical characters (a-Z)" "\nnumerical characters (0-9) dot (\".\") or underscore (\"_\")." ) + +# Roles for instance views INSTANCE_ID_ROLE = QtCore.Qt.UserRole + 1 SORT_VALUE_ROLE = QtCore.Qt.UserRole + 2 diff --git a/openpype/tools/new_publisher/widgets/create_dialog.py b/openpype/tools/new_publisher/widgets/create_dialog.py index 6d199e56e8..f183454f08 100644 --- a/openpype/tools/new_publisher/widgets/create_dialog.py +++ b/openpype/tools/new_publisher/widgets/create_dialog.py @@ -422,7 +422,7 @@ class CreateDialog(QtWidgets.QDialog): family = index.data(QtCore.Qt.DisplayRole) subset_name = self.subset_name_input.text() variant = self.variant_input.text() - asset_name = self.asset_name + asset_name = self._asset_name task_name = self._task_name options = {} # Where to define these data? From a8e5528169fc5e0628f12f8b8fb215cec920409e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 3 Sep 2021 17:12:24 +0200 Subject: [PATCH 421/736] families are not editable --- openpype/tools/new_publisher/widgets/create_dialog.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/widgets/create_dialog.py b/openpype/tools/new_publisher/widgets/create_dialog.py index f183454f08..629af91ab3 100644 --- a/openpype/tools/new_publisher/widgets/create_dialog.py +++ b/openpype/tools/new_publisher/widgets/create_dialog.py @@ -251,11 +251,14 @@ class CreateDialog(QtWidgets.QDialog): # Add new families new_families = set() - for family, creator in self.controller.creators.items(): + for family in self.controller.creators.keys(): # TODO add details about creator new_families.add(family) if family not in existing_items: item = QtGui.QStandardItem(family) + item.setFlags( + QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable + ) self.family_model.appendRow(item) # Remove families that are no more available From 5edbf42ff4fc70551dff2cfa31e097dd5df867b1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 3 Sep 2021 18:04:32 +0200 Subject: [PATCH 422/736] moved more colors to data.json in style --- openpype/style/data.json | 6 +++++- openpype/style/style.css | 14 +++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/openpype/style/data.json b/openpype/style/data.json index a58829d946..08f1c3fdf6 100644 --- a/openpype/style/data.json +++ b/openpype/style/data.json @@ -47,6 +47,10 @@ "border": "#373D48", "border-hover": "hsla(220, 14%, 70%, .3)", - "border-focus": "hsl(200, 60%, 60%)" + "border-focus": "hsl(200, 60%, 60%)", + + "publish-error": "#AA5050", + "publish-success": "#458056", + "publish-warning": "#ffc671" } } diff --git a/openpype/style/style.css b/openpype/style/style.css index 873979afb8..2ca6503d29 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -648,10 +648,10 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { } #VariantInput[state="new"], #VariantInput[state="new"]:focus, #VariantInput[state="new"]:hover { - border-color: #7AAB8F; + border-color: {color:publish-success}; } #VariantInput[state="invalid"], #VariantInput[state="invalid"]:focus, #VariantInput[state="invalid"]:hover { - border-color: #AA5050; + border-color: {color:publish-error}; } #VariantInput[state="empty"], #VariantInput[state="empty"]:focus, #VariantInput[state="empty"]:hover { @@ -703,15 +703,15 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { } #PublishInfoFrame[state="0"] { - background: #AA5050; + background: {color:publish-error}; } #PublishInfoFrame[state="1"] { - background: #458056; + background: {color:publish-success}; } #PublishInfoFrame[state="2"] { - background: #ffc671; + background: {color:publish-warning}; } #PublishInfoFrame QLabel { @@ -750,6 +750,10 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { border-left: 1px solid {color:border}; } +#AssetsTreeComboBox[state="invalid"] { + border-color: {color:publish-error}; +} + #PublishProgressBar[state="0"]::chunk { background: {color:bg-buttons}; } From edbe1206c2dfebc41a1b8fd3ea856fbbfbb0bb3a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 3 Sep 2021 18:05:37 +0200 Subject: [PATCH 423/736] added validation to variant input --- .../tools/new_publisher/widgets/widgets.py | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 7f33d02d74..9690f2906a 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -1,3 +1,4 @@ +import re import copy import collections from Qt import QtWidgets, QtCore, QtGui @@ -7,6 +8,11 @@ from openpype.tools.flickcharm import FlickCharm from .icons import get_pixmap +from ..constants import ( + SUBSET_NAME_ALLOWED_SYMBOLS, + VARIANT_TOOLTIP +) + class IconBtn(QtWidgets.QPushButton): """PushButton with icon and size of font. @@ -365,6 +371,13 @@ class VariantInputWidget(QtWidgets.QLineEdit): def __init__(self, parent): super(VariantInputWidget, self).__init__(parent) + self.setObjectName("VariantInput") + self.setToolTip(VARIANT_TOOLTIP) + + name_pattern = "^[{}]*$".format(SUBSET_NAME_ALLOWED_SYMBOLS) + self._name_pattern = name_pattern + self._compiled_name_pattern = re.compile(name_pattern) + self._origin_value = [] self._current_value = [] @@ -372,18 +385,41 @@ class VariantInputWidget(QtWidgets.QLineEdit): self._has_value_changed = False self._multiselection_text = None + self._is_valid = True + self.textChanged.connect(self._on_text_change) - def set_multiselection_text(self, text): - self._multiselection_text = text + def is_valid(self): + return self._is_valid def has_value_changed(self): return self._has_value_changed + def _set_state_property(self, state): + current_value = self.property("state") + if current_value != state: + self.setProperty("state", state) + self.style().polish(self) + + def set_multiselection_text(self, text): + self._multiselection_text = text + + def _set_is_valid(self, valid): + if valid == self._is_valid: + return + self._is_valid = valid + state = "" + if not valid: + state = "invalid" + self._set_state_property(state) + def _on_text_change(self): if self._ignore_value_change: return + is_valid = bool(self._compiled_name_pattern.match(self.text())) + self._set_is_valid(is_valid) + self._current_value = [self.text()] self._has_value_changed = self._current_value != self._origin_value From 529485ab0a38dd330428a09a0f252a7d5102a652 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 3 Sep 2021 18:07:45 +0200 Subject: [PATCH 424/736] added validation of asset name --- .../tools/new_publisher/widgets/widgets.py | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 9690f2906a..1ed090e821 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -68,6 +68,9 @@ class AssetsHierarchyModel(QtGui.QStandardItemModel): self._items_by_name = items_by_name + def name_is_valid(self, item_name): + return item_name in self._items_by_name + def get_index_by_name(self, item_name): item = self._items_by_name.get(item_name) if item: @@ -212,6 +215,7 @@ class AssetsTreeComboBox(TreeComboBox): model = AssetsHierarchyModel(controller) super(AssetsTreeComboBox, self).__init__(model, parent) + self.setObjectName("AssetsTreeComboBox") self.currentIndexChanged.connect(self._on_index_change) @@ -220,6 +224,7 @@ class AssetsTreeComboBox(TreeComboBox): self._origin_value = [] self._has_value_changed = False self._model = model + self._is_valid = True self._multiselection_text = None @@ -232,12 +237,31 @@ class AssetsTreeComboBox(TreeComboBox): if self._ignore_index_change: return + self._set_is_valid(True) self._selected_items = [self.currentText()] self._has_value_changed = ( self._origin_value != self._selected_items ) self.value_changed.emit() + def _set_is_valid(self, valid): + if valid == self._is_valid: + return + self._is_valid = valid + state = "" + if not valid: + state = "invalid" + self._set_state_property(state) + + def _set_state_property(self, state): + current_value = self.property("state") + if current_value != state: + self.setProperty("state", state) + self.style().polish(self) + + def is_valid(self): + return self._is_valid + def has_value_changed(self): return self._has_value_changed @@ -253,17 +277,27 @@ class AssetsTreeComboBox(TreeComboBox): self._has_value_changed = False self._origin_value = list(asset_names) self._selected_items = list(asset_names) + is_valid = True if not asset_names: self.set_selected_item("") elif len(asset_names) == 1: - self.set_selected_item(tuple(asset_names)[0]) + asset_name = tuple(asset_names)[0] + is_valid = self._model.name_is_valid(asset_name) + self.set_selected_item(asset_name) else: + for asset_name in asset_names: + is_valid = self._model.name_is_valid(asset_name) + if not is_valid: + break + multiselection_text = self._multiselection_text if multiselection_text is None: multiselection_text = "|".join(asset_names) self.set_selected_item(multiselection_text) + self._set_is_valid(is_valid) + self._ignore_index_change = False def reset_to_origin(self): From b0b0ea5e3a8f935774ae63a425e6e8aa10568c78 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 3 Sep 2021 18:15:39 +0200 Subject: [PATCH 425/736] added validation of task input --- openpype/style/style.css | 2 +- .../tools/new_publisher/widgets/widgets.py | 39 +++++++++++++++++-- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/openpype/style/style.css b/openpype/style/style.css index 2ca6503d29..48e60f9c31 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -750,7 +750,7 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { border-left: 1px solid {color:border}; } -#AssetsTreeComboBox[state="invalid"] { +#TasksCombobox[state="invalid"], #AssetsTreeComboBox[state="invalid"] { border-color: {color:publish-error}; } diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 1ed090e821..f91f29892f 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -309,6 +309,7 @@ class TasksCombobox(QtWidgets.QComboBox): def __init__(self, controller, parent): super(TasksCombobox, self).__init__(parent) + self.setObjectName("TasksCombobox") self.setEditable(True) self.lineEdit().setReadOnly(True) @@ -328,6 +329,7 @@ class TasksCombobox(QtWidgets.QComboBox): self._has_value_changed = False self._ignore_index_change = False self._multiselection_text = None + self._is_valid = True def set_multiselection_text(self, text): self._multiselection_text = text @@ -336,6 +338,7 @@ class TasksCombobox(QtWidgets.QComboBox): if self._ignore_index_change: return + self._set_is_valid(True) self._selected_items = [self.currentText()] self._has_value_changed = ( self._origin_value != self._selected_items @@ -343,14 +346,33 @@ class TasksCombobox(QtWidgets.QComboBox): self.value_changed.emit() + def is_valid(self): + return self._is_valid + def has_value_changed(self): return self._has_value_changed + def _set_is_valid(self, valid): + if valid == self._is_valid: + return + self._is_valid = valid + state = "" + if not valid: + state = "invalid" + self._set_state_property(state) + + def _set_state_property(self, state): + current_value = self.property("state") + if current_value != state: + self.setProperty("state", state) + self.style().polish(self) + def get_selected_items(self): return list(self._selected_items) def set_asset_names(self, asset_names): self._model.set_asset_names(asset_names) + self.set_selected_items(self._origin_value) def set_selected_items(self, task_names=None): if task_names is None: @@ -363,20 +385,21 @@ class TasksCombobox(QtWidgets.QComboBox): self._selected_items = list(task_names) # Reset current index self.setCurrentIndex(-1) + is_valid = True if not task_names: self.set_selected_item("") elif len(task_names) == 1: task_name = tuple(task_names)[0] idx = self.findText(task_name) + is_valid = not idx < 0 self.set_selected_item(task_name) else: - valid_value = True for task_name in task_names: idx = self.findText(task_name) - valid_value = not idx < 0 - if not valid_value: + is_valid = not idx < 0 + if not is_valid: break multiselection_text = self._multiselection_text @@ -384,6 +407,8 @@ class TasksCombobox(QtWidgets.QComboBox): multiselection_text = "|".join(task_names) self.set_selected_item(multiselection_text) + self._set_is_valid(is_valid) + self._ignore_index_change = False self.value_changed.emit() @@ -668,12 +693,18 @@ class GlobalAttrsWidget(QtWidgets.QWidget): self.submit_btn.setEnabled(False) def _on_value_change(self): + any_invalid = ( + not self.variant_input.is_valid() + or not self.asset_value_widget.is_valid() + or not self.task_value_widget.is_valid() + ) any_changed = ( self.variant_input.has_value_changed() or self.asset_value_widget.has_value_changed() or self.task_value_widget.has_value_changed() ) - self.set_btns_visible(any_changed) + self.set_btns_visible(any_changed or any_invalid) + self.set_btns_enabled(not any_invalid) def _on_variant_change(self): self._on_value_change() From abf36845d06a664281c32151c7f9c89af07e393c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 3 Sep 2021 18:30:38 +0200 Subject: [PATCH 426/736] renamed refresh_active_state to refresh_instance_states --- .../tools/new_publisher/widgets/instance_views_widgets.py | 4 ++-- openpype/tools/new_publisher/window.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/instance_views_widgets.py b/openpype/tools/new_publisher/widgets/instance_views_widgets.py index 9b4afa4245..667ed70963 100644 --- a/openpype/tools/new_publisher/widgets/instance_views_widgets.py +++ b/openpype/tools/new_publisher/widgets/instance_views_widgets.py @@ -265,7 +265,7 @@ class InstanceCardView(_AbstractInstanceView): self._items_by_id[instance_id] = item self._widgets_by_id[instance_id] = widget - def refresh_active_state(self): + def refresh_instance_states(self): for widget in self._widgets_by_id.values(): widget.update_instance_values() @@ -637,7 +637,7 @@ class InstanceListView(_AbstractInstanceView): if sort_at_the_end: self.proxy_model.sort(0) - def refresh_active_state(self): + def refresh_instance_states(self): for widget in self._widgets_by_id.values(): widget.update_instance_values() diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 750ae7c264..d4123a632d 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -249,7 +249,7 @@ class PublisherWindow(QtWidgets.QDialog): new_view.refresh() new_view.set_refreshed(True) else: - new_view.refresh_active_state() + new_view.refresh_instance_states() if new_view is not old_view: selected_instances, context_selected = ( From 93e84fc78ffe23b5460d567a3effe787c2e0624a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 3 Sep 2021 18:31:08 +0200 Subject: [PATCH 427/736] refresh also refresh validness of instance --- openpype/tools/new_publisher/widgets/instance_views_widgets.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/tools/new_publisher/widgets/instance_views_widgets.py b/openpype/tools/new_publisher/widgets/instance_views_widgets.py index 667ed70963..005b7882c1 100644 --- a/openpype/tools/new_publisher/widgets/instance_views_widgets.py +++ b/openpype/tools/new_publisher/widgets/instance_views_widgets.py @@ -126,6 +126,7 @@ class InstanceCardWidget(QtWidgets.QWidget): def update_instance_values(self): self.set_active(self.instance.data["active"]) + self.context_warning.setVisible(not self.instance.has_valid_context) def _set_expanded(self, expanded=None): if expanded is None: From e89c0d42ad77558c71eff71ab2875313a9ad0eb1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 3 Sep 2021 18:31:55 +0200 Subject: [PATCH 428/736] change invalid state of instance on asset/task change --- openpype/tools/new_publisher/widgets/widgets.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index f91f29892f..a45dbfc979 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -667,9 +667,11 @@ class GlobalAttrsWidget(QtWidgets.QWidget): if asset_name is not None: instance.data["asset"] = asset_name + instance.set_asset_invalid(False) if task_name is not None: instance.data["task"] = task_name + instance.set_task_invalid(False) new_variant_value = instance.data.get("variant") new_asset_name = instance.data.get("asset") From def20bc03c0d809e70ec3ef5fecdf20966fc2c84 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 3 Sep 2021 18:32:21 +0200 Subject: [PATCH 429/736] instance context change triggers refresh of instance states --- openpype/tools/new_publisher/widgets/widgets.py | 10 ++++++++++ openpype/tools/new_publisher/window.py | 15 +++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index a45dbfc979..0ccf3642ef 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -572,6 +572,8 @@ class MultipleItemWidget(QtWidgets.QWidget): class GlobalAttrsWidget(QtWidgets.QWidget): + instance_context_changed = QtCore.Signal() + multiselection_text = "< Multiselection >" unknown_value = "N/A" @@ -687,6 +689,8 @@ class GlobalAttrsWidget(QtWidgets.QWidget): self.cancel_btn.setEnabled(False) self.submit_btn.setEnabled(False) + self.instance_context_changed.emit() + def _on_cancel(self): self.variant_input.reset_to_origin() self.asset_value_widget.reset_to_origin() @@ -938,6 +942,7 @@ class SubsetAttributesWidget(QtWidgets.QWidget): | attributes | attributes | BOTTOM |______________|______________| """ + instance_context_changed = QtCore.Signal() def __init__(self, controller, parent): super(SubsetAttributesWidget, self).__init__(parent) @@ -948,6 +953,7 @@ class SubsetAttributesWidget(QtWidgets.QWidget): # Global attributes global_attrs_widget = GlobalAttrsWidget(controller, top_widget) thumbnail_widget = ThumbnailWidget(top_widget) + thumbnail_widget.setVisible(False) top_layout = QtWidgets.QHBoxLayout(top_widget) top_layout.setContentsMargins(0, 0, 0, 0) @@ -982,6 +988,10 @@ class SubsetAttributesWidget(QtWidgets.QWidget): layout.addWidget(top_bottom, 0) layout.addWidget(bottom_widget, 1) + global_attrs_widget.instance_context_changed.connect( + self.instance_context_changed + ) + self.controller = controller self.global_attrs_widget = global_attrs_widget diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index d4123a632d..1caafe881f 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -175,6 +175,9 @@ class PublisherWindow(QtWidgets.QDialog): subset_view_cards.selection_changed.connect( self._on_subset_change ) + subset_attributes_widget.instance_context_changed.connect( + self._on_instance_context_change + ) controller.add_instances_refresh_callback(self._on_instances_refresh) @@ -237,6 +240,18 @@ class PublisherWindow(QtWidgets.QDialog): view = self.subset_views_layout.currentWidget() return view.get_selected_items() + def _on_instance_context_change(self): + current_idx = self.subset_views_layout.currentIndex() + for idx in range(self.subset_views_layout.count()): + if idx == current_idx: + continue + widget = self.subset_views_layout.widget(idx) + if widget.refreshed: + widget.set_refreshed(False) + + current_widget = self.subset_views_layout.widget(current_idx) + current_widget.refresh_instance_states() + def _change_view_type(self): old_view = self.subset_views_layout.currentWidget() From f8605040d6d76d2348c66019055ac82932cc0dd0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 3 Sep 2021 19:01:07 +0200 Subject: [PATCH 430/736] added active_changed signal --- openpype/tools/new_publisher/widgets/instance_views_widgets.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/tools/new_publisher/widgets/instance_views_widgets.py b/openpype/tools/new_publisher/widgets/instance_views_widgets.py index 005b7882c1..e346af3c68 100644 --- a/openpype/tools/new_publisher/widgets/instance_views_widgets.py +++ b/openpype/tools/new_publisher/widgets/instance_views_widgets.py @@ -176,6 +176,7 @@ class ContextCardWidget(QtWidgets.QWidget): class _AbstractInstanceView(QtWidgets.QWidget): selection_changed = QtCore.Signal() + active_changed = QtCore.Signal(set) refreshed = False def set_refreshed(self, refreshed): From c145c818216e7e9984315a17e1be07f9b92cade8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 3 Sep 2021 19:02:25 +0200 Subject: [PATCH 431/736] added validation of instances callbacks --- .../widgets/instance_views_widgets.py | 33 +++++++++++++++++- openpype/tools/new_publisher/window.py | 34 +++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/widgets/instance_views_widgets.py b/openpype/tools/new_publisher/widgets/instance_views_widgets.py index e346af3c68..8ad1b5277a 100644 --- a/openpype/tools/new_publisher/widgets/instance_views_widgets.py +++ b/openpype/tools/new_publisher/widgets/instance_views_widgets.py @@ -281,13 +281,19 @@ class InstanceCardView(_AbstractInstanceView): found = True if not found: - return + selected_ids = set() + selected_ids.add(changed_instance_id) + changed_ids = set() for instance_id in selected_ids: widget = self._widgets_by_id.get(instance_id) if widget: + changed_ids.add(instance_id) widget.set_active(new_value) + if changed_ids: + self.active_changed.emit(changed_ids) + def _on_selection_change(self, *_args): self.selection_changed.emit() @@ -632,6 +638,7 @@ class InstanceListView(_AbstractInstanceView): widget = InstanceListItemWidget( instance, self.instance_view ) + widget.active_changed.connect(self._on_active_changed) self.instance_view.setIndexWidget(proxy_index, widget) self._widgets_by_id[instance.data["uuid"]] = widget @@ -643,6 +650,30 @@ class InstanceListView(_AbstractInstanceView): for widget in self._widgets_by_id.values(): widget.update_instance_values() + def _on_active_changed(self, changed_instance_id, new_value): + selected_instances, _ = self.get_selected_items() + + selected_ids = set() + found = False + for instance in selected_instances: + selected_ids.add(instance.id) + if not found and instance.id == changed_instance_id: + found = True + + if not found: + selected_ids = set() + selected_ids.add(changed_instance_id) + + changed_ids = set() + for instance_id in selected_ids: + widget = self._widgets_by_id.get(instance_id) + if widget: + changed_ids.add(instance_id) + widget.set_active(new_value) + + if changed_ids: + self.active_changed.emit(changed_ids) + def get_selected_items(self): instances = [] instances_by_id = {} diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 1caafe881f..9ae673d562 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -169,12 +169,21 @@ class PublisherWindow(QtWidgets.QDialog): validate_btn.clicked.connect(self._on_validate_clicked) publish_btn.clicked.connect(self._on_publish_clicked) + # Selection changed subset_list_view.selection_changed.connect( self._on_subset_change ) subset_view_cards.selection_changed.connect( self._on_subset_change ) + # Active instances changed + subset_list_view.active_changed.connect( + self._on_active_changed + ) + subset_view_cards.active_changed.connect( + self._on_active_changed + ) + # Instance context has changed subset_attributes_widget.instance_context_changed.connect( self._on_instance_context_change ) @@ -252,6 +261,8 @@ class PublisherWindow(QtWidgets.QDialog): current_widget = self.subset_views_layout.widget(current_idx) current_widget.refresh_instance_states() + self._validate_create_instances() + def _change_view_type(self): old_view = self.subset_views_layout.currentWidget() @@ -349,6 +360,8 @@ class PublisherWindow(QtWidgets.QDialog): def _on_instances_refresh(self): self._refresh_instances() + self._validate_create_instances() + def _on_subset_change(self, *_args): # Ignore changes if in middle of refreshing if self._refreshing_instances: @@ -363,6 +376,27 @@ class PublisherWindow(QtWidgets.QDialog): instances, context_selected ) + def _on_active_changed(self, _instance_ids): + if self._refreshing_instances: + return + self._validate_create_instances() + + def _validate_create_instances(self): + if not self.controller.host_is_valid: + self.footer_widget.setEnabled(True) + return + + all_valid = None + for instance in self.controller.instances: + if not instance.data["active"]: + continue + + if not instance.has_valid_context: + all_valid = False + break + + if all_valid is None: + all_valid = True def _on_publish_reset(self): self.reset_btn.setEnabled(True) self.stop_btn.setEnabled(False) From 64dc59d030e9017bb2a5bea279e910826a9e270d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 3 Sep 2021 19:02:50 +0200 Subject: [PATCH 432/736] enable/disable footer with publish stuff in valid/invalid instances change state --- openpype/tools/new_publisher/window.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 9ae673d562..38902fef39 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -138,7 +138,7 @@ class PublisherWindow(QtWidgets.QDialog): marings.setBottom(marings.bottom() * 2) subset_layout.setContentsMargins(marings) subset_layout.addWidget(subset_content_widget, 1) - subset_layout.addLayout(footer_layout, 0) + subset_layout.addWidget(footer_widget, 0) # Create publish frame publish_frame = PublishFrame(controller, self) @@ -209,6 +209,9 @@ class PublisherWindow(QtWidgets.QDialog): self.delete_btn = delete_btn self.subset_attributes_widget = subset_attributes_widget + + self.footer_widget = footer_widget + self.message_input = message_input self.stop_btn = stop_btn @@ -397,6 +400,9 @@ class PublisherWindow(QtWidgets.QDialog): if all_valid is None: all_valid = True + + self.footer_widget.setEnabled(bool(all_valid)) + def _on_publish_reset(self): self.reset_btn.setEnabled(True) self.stop_btn.setEnabled(False) @@ -407,6 +413,8 @@ class PublisherWindow(QtWidgets.QDialog): self.subset_content_widget.setEnabled(self.controller.host_is_valid) + self.footer_widget.setEnabled(False) + def _on_publish_start(self): self.reset_btn.setEnabled(False) self.stop_btn.setEnabled(True) From 50bbe7f10279dd5cec1b501fdc3581f001be1990 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 3 Sep 2021 19:04:19 +0200 Subject: [PATCH 433/736] added creat enew instance title --- openpype/tools/new_publisher/widgets/create_dialog.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/tools/new_publisher/widgets/create_dialog.py b/openpype/tools/new_publisher/widgets/create_dialog.py index 629af91ab3..6236d1f82f 100644 --- a/openpype/tools/new_publisher/widgets/create_dialog.py +++ b/openpype/tools/new_publisher/widgets/create_dialog.py @@ -97,6 +97,8 @@ class CreateDialog(QtWidgets.QDialog): ): super(CreateDialog, self).__init__(parent) + self.setWindowTitle("Create new instance") + self.controller = controller if asset_name is None: From aa9571e99c65ce3d66e84052ce498774782e11ab Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 6 Sep 2021 12:13:40 +0200 Subject: [PATCH 434/736] added nice chekcbox widget --- openpype/widgets/nice_checkbox.py | 540 ++++++++++++++++++++++++++++++ 1 file changed, 540 insertions(+) create mode 100644 openpype/widgets/nice_checkbox.py diff --git a/openpype/widgets/nice_checkbox.py b/openpype/widgets/nice_checkbox.py new file mode 100644 index 0000000000..d273d66e9e --- /dev/null +++ b/openpype/widgets/nice_checkbox.py @@ -0,0 +1,540 @@ +from math import floor, sqrt, ceil +from Qt import QtWidgets, QtCore, QtGui + + +class NiceCheckbox(QtWidgets.QFrame): + stateChanged = QtCore.Signal(int) + clicked = QtCore.Signal() + + def __init__( + self, checked=False, draw_icons=False, parent=None, + use_checkbox_height_hint=None + ): + super(NiceCheckbox, self).__init__(parent) + self._checked = checked + if checked: + checkstate = QtCore.Qt.Checked + else: + checkstate = QtCore.Qt.Unchecked + self._checkstate = checkstate + self._is_tristate = False + + self._draw_icons = draw_icons + + self._animation_timer = QtCore.QTimer(self) + self._animation_timeout = 6 + + self._use_checkbox_height_hint = use_checkbox_height_hint + self._first_show = True + self._fixed_width_set = False + self._fixed_height_set = False + + self._current_step = None + self._steps = 21 + self._middle_step = 11 + self.set_steps(self._steps) + + self._pressed = False + self._under_mouse = False + + self.checked_bg_color = QtGui.QColor(67, 181, 129) + self.unchecked_bg_color = QtGui.QColor(230, 230, 230) + self.unchecked_bg_color = QtGui.QColor(79, 79, 79) + + self.checker_checked_color = QtGui.QColor(255, 255, 255) + self.checker_unchecked_color = self.checker_checked_color + + self.border_color = QtGui.QColor(44, 44, 44) + self.border_color_hover = QtGui.QColor(119, 131, 126) + + self.icon_scale_factor = sqrt(2) / 2 + + icon_path_stroker = QtGui.QPainterPathStroker() + icon_path_stroker.setCapStyle(QtCore.Qt.RoundCap) + icon_path_stroker.setJoinStyle(QtCore.Qt.RoundJoin) + + self.icon_path_stroker = icon_path_stroker + + self._animation_timer.timeout.connect(self._on_animation_timeout) + + self._base_size = QtCore.QSize(90, 50) + + def setTristate(self, tristate=True): + if self._is_tristate == tristate: + return + self._is_tristate = tristate + + def set_draw_icons(self, draw_icons=None): + if draw_icons is None: + draw_icons = not self._draw_icons + + if draw_icons == self._draw_icons: + return + + self._draw_icons = draw_icons + self.repaint() + + def showEvent(self, event): + super(NiceCheckbox, self).showEvent(event) + if self._first_show: + self._first_show = False + if ( + self._use_checkbox_height_hint + or ( + self._use_checkbox_height_hint is None + and not (self._fixed_width_set or self._fixed_height_set) + ) + ): + checkbox_height = self.style().pixelMetric( + QtWidgets.QStyle.PM_IndicatorHeight + ) + checkbox_height += checkbox_height % 2 + width = (2 * checkbox_height) - (checkbox_height / 5) + new_size = QtCore.QSize(width, checkbox_height) + self.setFixedSize(new_size) + + def resizeEvent(self, event): + new_size = QtCore.QSize(self._base_size) + new_size.scale(event.size(), QtCore.Qt.KeepAspectRatio) + self.resize(new_size) + + def setFixedHeight(self, *args, **kwargs): + self._fixed_height_set = True + super(NiceCheckbox, self).setFixedHeight(*args, **kwargs) + if not self._fixed_width_set: + width = ( + self.height() / self._base_size.height() + ) * self._base_size.width() + self.setFixedWidth(width) + + def setFixedWidth(self, *args, **kwargs): + self._fixed_width_set = True + super(NiceCheckbox, self).setFixedWidth(*args, **kwargs) + if not self._fixed_height_set: + width = ( + self.width() / self._base_size.width() + ) * self._base_size.height() + self.setFixedHeight(width) + + def setFixedSize(self, *args, **kwargs): + self._fixed_height_set = True + self._fixed_width_set = True + super(NiceCheckbox, self).setFixedSize(*args, **kwargs) + + def steps(self): + return self._steps + + def set_steps(self, steps): + if steps < 2: + steps = 2 + + # Make sure animation is stopped + if self._animation_timer.isActive(): + self._animation_timer.stop() + + # Set steps and set current step by current checkstate + self._steps = steps + diff = steps % 2 + self._middle_step = (int(steps - diff) / 2) + diff + if self._checkstate == QtCore.Qt.Checked: + self._current_step = self._steps + elif self._checkstate == QtCore.Qt.Unchecked: + self._current_step = 0 + else: + self._current_step = self._middle_step + + def checkState(self): + return self._checkstate + + def isChecked(self): + return self._checked + + def setCheckState(self, state): + if self._checkstate == state: + return + + self._checkstate = state + if state == QtCore.Qt.Checked: + self._checked = True + elif state == QtCore.Qt.Unchecked: + self._checked = False + + self.stateChanged.emit(self.checkState()) + + if self._animation_timer.isActive(): + self._animation_timer.stop() + + if self.isEnabled(): + # Start animation + self._animation_timer.start(self._animation_timeout) + else: + # Do not animate change if is disabled + if state == QtCore.Qt.Checked: + self._current_step = self._steps + elif state == QtCore.Qt.Unchecked: + self._current_step = 0 + else: + self._current_step = self._middle_step + self.repaint() + + def setChecked(self, checked): + if checked == self._checked: + return + + if checked: + checkstate = QtCore.Qt.Checked + else: + checkstate = QtCore.Qt.Unchecked + + self.setCheckState(checkstate) + + def nextCheckState(self): + if self._checkstate == QtCore.Qt.Unchecked: + if self._is_tristate: + return QtCore.Qt.PartiallyChecked + return QtCore.Qt.Checked + + if self._checkstate == QtCore.Qt.Checked: + return QtCore.Qt.Unchecked + + if self._checked: + return QtCore.Qt.Unchecked + return QtCore.Qt.Checked + + def sizeHint(self): + return self._base_size + + def mousePressEvent(self, event): + if event.buttons() & QtCore.Qt.LeftButton: + self._pressed = True + self.repaint() + super(NiceCheckbox, self).mousePressEvent(event) + + def mouseReleaseEvent(self, event): + if self._pressed and not event.buttons() & QtCore.Qt.LeftButton: + self._pressed = False + if self.rect().contains(event.pos()): + self.setCheckState(self.nextCheckState()) + self.clicked.emit() + super(NiceCheckbox, self).mouseReleaseEvent(event) + + def mouseMoveEvent(self, event): + if self._pressed: + under_mouse = self.rect().contains(event.pos()) + if under_mouse != self._under_mouse: + self._under_mouse = under_mouse + self.repaint() + + super(NiceCheckbox, self).mouseMoveEvent(event) + + def enterEvent(self, event): + self._under_mouse = True + if self.isEnabled(): + self.repaint() + super(NiceCheckbox, self).enterEvent(event) + + def leaveEvent(self, event): + self._under_mouse = False + if self.isEnabled(): + self.repaint() + super(NiceCheckbox, self).leaveEvent(event) + + def _on_animation_timeout(self): + if self._checkstate == QtCore.Qt.Checked: + self._current_step += 1 + if self._current_step == self._steps: + self._animation_timer.stop() + + elif self._checkstate == QtCore.Qt.Unchecked: + self._current_step -= 1 + if self._current_step == 0: + self._animation_timer.stop() + + else: + if self._current_step < self._middle_step: + self._current_step += 1 + + elif self._current_step > self._middle_step: + self._current_step -= 1 + + if self._current_step == self._middle_step: + self._animation_timer.stop() + + self.repaint() + + @staticmethod + def steped_color(color1, color2, offset_ratio): + red_dif = ( + color1.red() - color2.red() + ) + green_dif = ( + color1.green() - color2.green() + ) + blue_dif = ( + color1.blue() - color2.blue() + ) + red = int(color2.red() + ( + red_dif * offset_ratio + )) + green = int(color2.green() + ( + green_dif * offset_ratio + )) + blue = int(color2.blue() + ( + blue_dif * offset_ratio + )) + + return QtGui.QColor(red, green, blue) + + def paintEvent(self, event): + if self.width() < 1 or self.height() < 1: + return + + painter = QtGui.QPainter(self) + + painter.setRenderHint(QtGui.QPainter.Antialiasing) + frame_rect = QtCore.QRect(event.rect()) + + if self.isEnabled() and self._under_mouse: + pen_color = self.border_color_hover + else: + pen_color = self.border_color + + # Draw inner background + if self._current_step == self._steps: + bg_color = self.checked_bg_color + checker_color = self.checker_checked_color + + elif self._current_step == 0: + bg_color = self.unchecked_bg_color + checker_color = self.checker_unchecked_color + + else: + offset_ratio = self._current_step / self._steps + # Animation bg + bg_color = self.steped_color( + self.checked_bg_color, + self.unchecked_bg_color, + offset_ratio + ) + checker_color = self.steped_color( + self.checker_checked_color, + self.checker_unchecked_color, + offset_ratio + ) + + margins_ratio = 20 + size_without_margins = int( + frame_rect.height() / margins_ratio * (margins_ratio - 2) + ) + margin_size_c = ceil(frame_rect.height() - size_without_margins) / 2 + checkbox_rect = QtCore.QRect( + frame_rect.x() + margin_size_c, + frame_rect.y() + margin_size_c, + frame_rect.width() - (margin_size_c * 2), + frame_rect.height() - (margin_size_c * 2) + ) + + if checkbox_rect.width() > checkbox_rect.height(): + radius = floor(checkbox_rect.height() / 2) + else: + radius = floor(checkbox_rect.width() / 2) + + painter.setPen(QtCore.Qt.transparent) + painter.setBrush(bg_color) + painter.drawRoundedRect(checkbox_rect, radius, radius) + + # Draw checker + checker_size = size_without_margins - (margin_size_c * 2) + area_width = ( + checkbox_rect.width() + - (margin_size_c * 2) + - checker_size + ) + if self._current_step == 0: + x_offset = 0 + else: + x_offset = (area_width / self._steps) * self._current_step + + pos_x = checkbox_rect.x() + x_offset + margin_size_c + pos_y = checkbox_rect.y() + margin_size_c + + checker_rect = QtCore.QRect(pos_x, pos_y, checker_size, checker_size) + + under_mouse = self.isEnabled() and self._under_mouse + + shadow_x = checker_rect.x() + shadow_y = checker_rect.y() + margin_size_c + shadow_size = min( + frame_rect.right() - shadow_x, + frame_rect.bottom() - shadow_y, + checker_size + (2 * margin_size_c) + ) + shadow_rect = QtCore.QRect( + checker_rect.x(), + shadow_y, + shadow_size, + shadow_size + ) + + shadow_brush = QtGui.QRadialGradient( + shadow_rect.center(), + shadow_rect.height() / 2 + ) + shadow_brush.setColorAt(0.6, QtCore.Qt.black) + shadow_brush.setColorAt(1, QtCore.Qt.transparent) + + painter.setPen(QtCore.Qt.transparent) + painter.setBrush(shadow_brush) + painter.drawEllipse(shadow_rect) + + painter.setBrush(checker_color) + painter.drawEllipse(checker_rect) + + smaller_checker_rect = checker_rect.adjusted( + margin_size_c, margin_size_c, -margin_size_c, -margin_size_c + ) + gradient = QtGui.QLinearGradient( + smaller_checker_rect.bottomRight(), + smaller_checker_rect.topLeft() + ) + gradient.setColorAt(0, checker_color) + if under_mouse: + dark_value = 120 + else: + dark_value = 115 + gradient.setColorAt(1, checker_color.darker(dark_value)) + painter.setBrush(gradient) + painter.drawEllipse(smaller_checker_rect) + + if self._draw_icons: + painter.setBrush(bg_color) + icon_path = self._get_icon_path(painter, checker_rect) + painter.drawPath(icon_path) + + # Draw shadow overlay + if not self.isEnabled(): + level = 33 + alpha = 127 + painter.setPen(QtCore.Qt.transparent) + painter.setBrush(QtGui.QColor(level, level, level, alpha)) + painter.drawRoundedRect(checkbox_rect, radius, radius) + + painter.end() + + def _get_icon_path(self, painter, checker_rect): + self.icon_path_stroker.setWidth(checker_rect.height() / 5) + + if self._current_step == self._steps: + return self._get_enabled_icon_path(painter, checker_rect) + + if self._current_step == 0: + return self._get_disabled_icon_path(painter, checker_rect) + + if self._current_step == self._middle_step: + return self._get_middle_circle_path(painter, checker_rect) + + disabled_step = self._steps - self._current_step + enabled_step = self._steps - disabled_step + half_steps = self._steps + 1 - ((self._steps + 1) % 2) + if enabled_step > disabled_step: + return self._get_enabled_icon_path( + painter, checker_rect, enabled_step, half_steps + ) + else: + return self._get_disabled_icon_path( + painter, checker_rect, disabled_step, half_steps + ) + + def _get_middle_circle_path(self, painter, checker_rect): + width = self.icon_path_stroker.width() + path = QtGui.QPainterPath() + path.addEllipse(checker_rect.center(), width, width) + + return path + + def _get_enabled_icon_path( + self, painter, checker_rect, step=None, half_steps=None + ): + fifteenth = checker_rect.height() / 15 + # Left point + p1 = QtCore.QPoint( + checker_rect.x() + (5 * fifteenth), + checker_rect.y() + (9 * fifteenth) + ) + # Middle bottom point + p2 = QtCore.QPoint( + checker_rect.center().x(), + checker_rect.y() + (11 * fifteenth) + ) + # Top right point + p3 = QtCore.QPoint( + checker_rect.x() + (10 * fifteenth), + checker_rect.y() + (5 * fifteenth) + ) + if step is not None: + multiplier = (half_steps - step) + + p1c = p1 - checker_rect.center() + p2c = p2 - checker_rect.center() + p3c = p3 - checker_rect.center() + + p1o = QtCore.QPoint( + (p1c.x() / half_steps) * multiplier, + (p1c.y() / half_steps) * multiplier + ) + p2o = QtCore.QPoint( + (p2c.x() / half_steps) * multiplier, + (p2c.y() / half_steps) * multiplier + ) + p3o = QtCore.QPoint( + (p3c.x() / half_steps) * multiplier, + (p3c.y() / half_steps) * multiplier + ) + + p1 -= p1o + p2 -= p2o + p3 -= p3o + + path = QtGui.QPainterPath(p1) + path.lineTo(p2) + path.lineTo(p3) + + return self.icon_path_stroker.createStroke(path) + + def _get_disabled_icon_path( + self, painter, checker_rect, step=None, half_steps=None + ): + center_point = QtCore.QPointF( + checker_rect.width() / 2, checker_rect.height() / 2 + ) + offset = ( + (center_point + QtCore.QPointF(0, 0)) / 2 + ).x() / 4 * 5 + if step is not None: + diff = center_point.x() - offset + diff_offset = (diff / half_steps) * (half_steps - step) + offset += diff_offset + + line1_p1 = QtCore.QPointF( + checker_rect.topLeft().x() + offset, + checker_rect.topLeft().y() + offset, + ) + line1_p2 = QtCore.QPointF( + checker_rect.bottomRight().x() - offset, + checker_rect.bottomRight().y() - offset + ) + line2_p1 = QtCore.QPointF( + checker_rect.bottomLeft().x() + offset, + checker_rect.bottomLeft().y() - offset + ) + line2_p2 = QtCore.QPointF( + checker_rect.topRight().x() - offset, + checker_rect.topRight().y() + offset + ) + path = QtGui.QPainterPath() + path.moveTo(line1_p1) + path.lineTo(line1_p2) + path.moveTo(line2_p1) + path.lineTo(line2_p2) + + return self.icon_path_stroker.createStroke(path) From 5c17752f75b38822f8f94e047ca22c67604f0f21 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 6 Sep 2021 12:21:24 +0200 Subject: [PATCH 435/736] added better size hint to NiceCheckbox --- openpype/widgets/nice_checkbox.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/openpype/widgets/nice_checkbox.py b/openpype/widgets/nice_checkbox.py index d273d66e9e..720dcb41f9 100644 --- a/openpype/widgets/nice_checkbox.py +++ b/openpype/widgets/nice_checkbox.py @@ -38,7 +38,6 @@ class NiceCheckbox(QtWidgets.QFrame): self._under_mouse = False self.checked_bg_color = QtGui.QColor(67, 181, 129) - self.unchecked_bg_color = QtGui.QColor(230, 230, 230) self.unchecked_bg_color = QtGui.QColor(79, 79, 79) self.checker_checked_color = QtGui.QColor(255, 255, 255) @@ -74,6 +73,15 @@ class NiceCheckbox(QtWidgets.QFrame): self._draw_icons = draw_icons self.repaint() + def _checkbox_size_hint(self): + checkbox_height = self.style().pixelMetric( + QtWidgets.QStyle.PM_IndicatorHeight + ) + checkbox_height += checkbox_height % 2 + width = (2 * checkbox_height) - (checkbox_height / 5) + new_size = QtCore.QSize(width, checkbox_height) + return new_size + def showEvent(self, event): super(NiceCheckbox, self).showEvent(event) if self._first_show: @@ -85,12 +93,7 @@ class NiceCheckbox(QtWidgets.QFrame): and not (self._fixed_width_set or self._fixed_height_set) ) ): - checkbox_height = self.style().pixelMetric( - QtWidgets.QStyle.PM_IndicatorHeight - ) - checkbox_height += checkbox_height % 2 - width = (2 * checkbox_height) - (checkbox_height / 5) - new_size = QtCore.QSize(width, checkbox_height) + new_size = self._checkbox_size_hint() self.setFixedSize(new_size) def resizeEvent(self, event): @@ -202,7 +205,9 @@ class NiceCheckbox(QtWidgets.QFrame): return QtCore.Qt.Checked def sizeHint(self): - return self._base_size + if self._use_checkbox_height_hint is False: + return self._base_size + return self._checkbox_size_hint() def mousePressEvent(self, event): if event.buttons() & QtCore.Qt.LeftButton: From 3e8d4e46cd9f3b3d6b44c3bc4e8b802ca6d925a1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 6 Sep 2021 12:28:20 +0200 Subject: [PATCH 436/736] removed unused border colors --- openpype/widgets/nice_checkbox.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/openpype/widgets/nice_checkbox.py b/openpype/widgets/nice_checkbox.py index 720dcb41f9..228c0198f5 100644 --- a/openpype/widgets/nice_checkbox.py +++ b/openpype/widgets/nice_checkbox.py @@ -43,9 +43,6 @@ class NiceCheckbox(QtWidgets.QFrame): self.checker_checked_color = QtGui.QColor(255, 255, 255) self.checker_unchecked_color = self.checker_checked_color - self.border_color = QtGui.QColor(44, 44, 44) - self.border_color_hover = QtGui.QColor(119, 131, 126) - self.icon_scale_factor = sqrt(2) / 2 icon_path_stroker = QtGui.QPainterPathStroker() @@ -299,11 +296,6 @@ class NiceCheckbox(QtWidgets.QFrame): painter.setRenderHint(QtGui.QPainter.Antialiasing) frame_rect = QtCore.QRect(event.rect()) - if self.isEnabled() and self._under_mouse: - pen_color = self.border_color_hover - else: - pen_color = self.border_color - # Draw inner background if self._current_step == self._steps: bg_color = self.checked_bg_color From 0c77693775d76b0cbf89627719975fbfef52fead Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 6 Sep 2021 12:28:32 +0200 Subject: [PATCH 437/736] added darker gradient --- openpype/widgets/nice_checkbox.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/widgets/nice_checkbox.py b/openpype/widgets/nice_checkbox.py index 228c0198f5..d40a804714 100644 --- a/openpype/widgets/nice_checkbox.py +++ b/openpype/widgets/nice_checkbox.py @@ -396,9 +396,9 @@ class NiceCheckbox(QtWidgets.QFrame): ) gradient.setColorAt(0, checker_color) if under_mouse: - dark_value = 120 + dark_value = 155 else: - dark_value = 115 + dark_value = 130 gradient.setColorAt(1, checker_color.darker(dark_value)) painter.setBrush(gradient) painter.drawEllipse(smaller_checker_rect) From 79746d029f7ecc288788fa14dd3b1aa9d3647c94 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 6 Sep 2021 12:31:03 +0200 Subject: [PATCH 438/736] skip super call if clicked --- openpype/widgets/nice_checkbox.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/widgets/nice_checkbox.py b/openpype/widgets/nice_checkbox.py index d40a804714..09113fea73 100644 --- a/openpype/widgets/nice_checkbox.py +++ b/openpype/widgets/nice_checkbox.py @@ -218,6 +218,8 @@ class NiceCheckbox(QtWidgets.QFrame): if self.rect().contains(event.pos()): self.setCheckState(self.nextCheckState()) self.clicked.emit() + event.accept() + return super(NiceCheckbox, self).mouseReleaseEvent(event) def mouseMoveEvent(self, event): From f54f5a60bff7eacec2bcb73d578c9b429a4e61f0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 6 Sep 2021 13:33:07 +0200 Subject: [PATCH 439/736] use NiceCheckbox in instance view --- .../tools/new_publisher/widgets/instance_views_widgets.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/instance_views_widgets.py b/openpype/tools/new_publisher/widgets/instance_views_widgets.py index 8ad1b5277a..aa45f44b1e 100644 --- a/openpype/tools/new_publisher/widgets/instance_views_widgets.py +++ b/openpype/tools/new_publisher/widgets/instance_views_widgets.py @@ -2,6 +2,7 @@ import collections from Qt import QtWidgets, QtCore, QtGui +from openpype.widgets.nice_checkbox import NiceCheckbox from ..constants import ( INSTANCE_ID_ROLE, SORT_VALUE_ROLE, @@ -66,7 +67,7 @@ class InstanceCardWidget(QtWidgets.QWidget): self.item = item subset_name_label = QtWidgets.QLabel(instance.data["subset"], self) - active_checkbox = QtWidgets.QCheckBox(self) + active_checkbox = NiceCheckbox(parent=self) active_checkbox.setChecked(instance.data["active"]) context_warning = ContextWarningLabel(self) @@ -347,7 +348,7 @@ class InstanceListItemWidget(QtWidgets.QWidget): self.instance = instance subset_name_label = QtWidgets.QLabel(instance.data["subset"], self) - active_checkbox = QtWidgets.QCheckBox(self) + active_checkbox = NiceCheckbox(parent=self) active_checkbox.setChecked(instance.data["active"]) layout = QtWidgets.QHBoxLayout(self) From 5d47e600d5b93d56b1e9b5a8ea45977d7f557b48 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 6 Sep 2021 13:34:14 +0200 Subject: [PATCH 440/736] skip not active instances --- openpype/hosts/testhost/plugins/publish/collect_context.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/testhost/plugins/publish/collect_context.py b/openpype/hosts/testhost/plugins/publish/collect_context.py index 0a0e5f84d2..5595764aac 100644 --- a/openpype/hosts/testhost/plugins/publish/collect_context.py +++ b/openpype/hosts/testhost/plugins/publish/collect_context.py @@ -35,8 +35,9 @@ class CollectContextDataTestHost( io.install() for instance_data in api.list_instances(): - # create instance - self.create_instance(context, instance_data) + if instance_data.get("active", True): + # create instance + self.create_instance(context, instance_data) def create_instance(self, context, in_data): subset = in_data["subset"] From 92ed7a3013fd5711ec0b1dd31eed0d369c1a1055 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 6 Sep 2021 13:57:51 +0200 Subject: [PATCH 441/736] change layout direction --- openpype/tools/new_publisher/widgets/validations_widget.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/tools/new_publisher/widgets/validations_widget.py b/openpype/tools/new_publisher/widgets/validations_widget.py index a36d3d1f8f..4773caec01 100644 --- a/openpype/tools/new_publisher/widgets/validations_widget.py +++ b/openpype/tools/new_publisher/widgets/validations_widget.py @@ -99,6 +99,8 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget): instances_view.setModel(instances_model) instances_view.setVisible(False) + self.setLayoutDirection(QtCore.Qt.LeftToRight) + view_layout = QtWidgets.QHBoxLayout() view_layout.setContentsMargins(0, 0, 0, 0) view_layout.setSpacing(0) From 3f4e98468113e8e8f99bc9162baf236dfe53d319 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 6 Sep 2021 14:03:03 +0200 Subject: [PATCH 442/736] fixed minimum size of validation error instance list --- openpype/tools/new_publisher/widgets/validations_widget.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/validations_widget.py b/openpype/tools/new_publisher/widgets/validations_widget.py index 4773caec01..a3136e7a31 100644 --- a/openpype/tools/new_publisher/widgets/validations_widget.py +++ b/openpype/tools/new_publisher/widgets/validations_widget.py @@ -39,7 +39,7 @@ class ValidationErrorInstanceList(QtWidgets.QListView): def minimumSizeHint(self): result = super(ValidationErrorInstanceList, self).minimumSizeHint() - result.setHeight(0) + result.setHeight(self.sizeHint().height()) return result def sizeHint(self): @@ -92,7 +92,7 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget): item = QtGui.QStandardItem(label) item.setData(instance.id) items.append(item) - break + instances_model.invisibleRootItem().appendRows(items) instances_view = ValidationErrorInstanceList(self) From bbbc81c6b50387475b3741007ba21e8afdd5353d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 6 Sep 2021 14:05:49 +0200 Subject: [PATCH 443/736] added single method which handles size of icon button --- openpype/tools/new_publisher/widgets/widgets.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 0ccf3642ef..3ca6c2c15a 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -21,11 +21,13 @@ class IconBtn(QtWidgets.QPushButton): """ def resizeEvent(self, event): super(IconBtn, self).resizeEvent(event) - icon_size = self.fontMetrics().height() - self.setIconSize(QtCore.QSize(icon_size, icon_size)) + self._icon_size_change() def showEvent(self, event): super(IconBtn, self).showEvent(event) + self._icon_size_change() + + def _icon_size_change(self): icon_size = self.fontMetrics().height() self.setIconSize(QtCore.QSize(icon_size, icon_size)) From 96feb068a625a8e96c739e028f0bcb8de9bcab32 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 6 Sep 2021 18:20:42 +0200 Subject: [PATCH 444/736] use already existing methods for hiding and disabling buttons --- openpype/tools/new_publisher/widgets/widgets.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 3ca6c2c15a..76c6eb39d1 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -688,8 +688,8 @@ class GlobalAttrsWidget(QtWidgets.QWidget): ) instance.data["subset"] = new_subset_name - self.cancel_btn.setEnabled(False) - self.submit_btn.setEnabled(False) + self.set_btns_enabled(False) + self.set_btns_visible(False) self.instance_context_changed.emit() @@ -697,8 +697,7 @@ class GlobalAttrsWidget(QtWidgets.QWidget): self.variant_input.reset_to_origin() self.asset_value_widget.reset_to_origin() self.task_value_widget.reset_to_origin() - self.cancel_btn.setEnabled(False) - self.submit_btn.setEnabled(False) + self.set_btns_enabled(False) def _on_value_change(self): any_invalid = ( From e9df22b5cb928e7f7fa3740d0456d7c0d5734f1f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 6 Sep 2021 18:25:29 +0200 Subject: [PATCH 445/736] set btns methods are "private" --- openpype/tools/new_publisher/widgets/widgets.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 76c6eb39d1..9218dbcd57 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -688,8 +688,8 @@ class GlobalAttrsWidget(QtWidgets.QWidget): ) instance.data["subset"] = new_subset_name - self.set_btns_enabled(False) - self.set_btns_visible(False) + self._set_btns_enabled(False) + self._set_btns_visible(False) self.instance_context_changed.emit() @@ -697,7 +697,7 @@ class GlobalAttrsWidget(QtWidgets.QWidget): self.variant_input.reset_to_origin() self.asset_value_widget.reset_to_origin() self.task_value_widget.reset_to_origin() - self.set_btns_enabled(False) + self._set_btns_enabled(False) def _on_value_change(self): any_invalid = ( @@ -710,8 +710,8 @@ class GlobalAttrsWidget(QtWidgets.QWidget): or self.asset_value_widget.has_value_changed() or self.task_value_widget.has_value_changed() ) - self.set_btns_visible(any_changed or any_invalid) - self.set_btns_enabled(not any_invalid) + self._set_btns_visible(any_changed or any_invalid) + self._set_btns_enabled(not any_invalid) def _on_variant_change(self): self._on_value_change() @@ -724,16 +724,16 @@ class GlobalAttrsWidget(QtWidgets.QWidget): def _on_task_change(self): self._on_value_change() - def set_btns_visible(self, visible): + def _set_btns_visible(self, visible): self.cancel_btn.setVisible(visible) self.submit_btn.setVisible(visible) - def set_btns_enabled(self, enabled): + def _set_btns_enabled(self, enabled): self.cancel_btn.setEnabled(enabled) self.submit_btn.setEnabled(enabled) def set_current_instances(self, instances): - self.set_btns_visible(False) + self._set_btns_visible(False) self._current_instances = instances From 43654be4f8719d7d81a2297a040702ca58031781 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 6 Sep 2021 18:26:07 +0200 Subject: [PATCH 446/736] added instance validity of current selection on submit --- .../tools/new_publisher/widgets/widgets.py | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 9218dbcd57..a1dc97254b 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -795,6 +795,13 @@ class FamilyAttrsWidget(QtWidgets.QWidget): # To store content of scroll area to prevend garbage collection self._content_widget = None + def set_instances_valid(self, valid): + if ( + self._content_widget is not None + and self._content_widget.isEnabled() != valid + ): + self._content_widget.setEnabled(valid) + def set_current_instances(self, instances): prev_content_widget = self._scroll_area.widget() if prev_content_widget: @@ -867,6 +874,13 @@ class PublishPluginAttrsWidget(QtWidgets.QWidget): # Store content of scroll area to prevend garbage collection self._content_widget = None + def set_instances_valid(self, valid): + if ( + self._content_widget is not None + and self._content_widget.isEnabled() != valid + ): + self._content_widget.setEnabled(valid) + def set_current_instances(self, instances, context_selected): prev_content_widget = self._scroll_area.widget() if prev_content_widget: @@ -989,8 +1003,12 @@ class SubsetAttributesWidget(QtWidgets.QWidget): layout.addWidget(top_bottom, 0) layout.addWidget(bottom_widget, 1) + self._current_instances = None + self._context_selected = False + self._all_instances_valid = True + global_attrs_widget.instance_context_changed.connect( - self.instance_context_changed + self._on_instance_context_changed ) self.controller = controller @@ -1004,12 +1022,36 @@ class SubsetAttributesWidget(QtWidgets.QWidget): self.top_bottom = top_bottom self.bottom_separator = bottom_separator + def _on_instance_context_changed(self): + all_valid = True + for instance in self._current_instances: + if not instance.has_valid_context: + all_valid = False + break + self._all_instances_valid = all_valid + self.family_attrs_widget.set_instances_valid(all_valid) + self.publish_attrs_widget.set_instances_valid(all_valid) + + self.instance_context_changed.emit() + def set_current_instances(self, instances, context_selected): + all_valid = True + for instance in instances: + if not instance.has_valid_context: + all_valid = False + break + + self._current_instances = instances + self._context_selected = context_selected + self._all_instances_valid = all_valid + self.global_attrs_widget.set_current_instances(instances) self.family_attrs_widget.set_current_instances(instances) self.publish_attrs_widget.set_current_instances( instances, context_selected ) + self.family_attrs_widget.set_instances_valid(all_valid) + self.publish_attrs_widget.set_instances_valid(all_valid) class ThumbnailWidget(QtWidgets.QWidget): From 39e1fdeee2e2cc44bb54d921a06d3f6dab8c9676 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 7 Sep 2021 10:14:38 +0200 Subject: [PATCH 447/736] task model keeps information about asset/task relations --- openpype/tools/new_publisher/control.py | 17 ++++------- .../tools/new_publisher/widgets/widgets.py | 30 +++++++++++++++++-- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index e301a6e4ec..1273b12666 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -439,21 +439,16 @@ class PublisherController: output[parent_id].append(asset_doc) return output - def get_task_names_for_asset_names(self, asset_names): + def get_task_names_by_asset_names(self, asset_names): task_names_by_asset_name = ( self._asset_docs_cache.get_task_names_by_asset_name() ) - tasks = None + result = {} for asset_name in asset_names: - task_names = set(task_names_by_asset_name.get(asset_name, [])) - if tasks is None: - tasks = task_names - else: - tasks &= task_names - - if not tasks: - break - return tasks + result[asset_name] = set( + task_names_by_asset_name.get(asset_name) or [] + ) + return result def _trigger_callbacks(self, callbacks, *args, **kwargs): """Helper method to trigger callbacks stored by their rerence.""" diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index a1dc97254b..d616116e45 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -86,19 +86,45 @@ class TasksModel(QtGui.QStandardItemModel): self._controller = controller self._items_by_name = {} self._asset_names = [] + self._task_names_by_asset_name = {} def set_asset_names(self, asset_names): self._asset_names = asset_names self.reset() + @staticmethod + def get_intersection_of_tasks(task_names_by_asset_name): + tasks = None + for task_names in task_names_by_asset_name.values(): + if tasks is None: + tasks = set(task_names) + else: + tasks &= set(task_names) + + if not tasks: + break + return tasks or set() + + def is_task_name_valid(self, asset_name, task_name): + task_names = self._task_names_by_asset_name.get(asset_name) + if task_names and task_name in task_names: + return True + return False + def reset(self): if not self._asset_names: self._items_by_name = {} + self._task_names_by_asset_name = {} self.clear() return - new_task_names = self._controller.get_task_names_for_asset_names( - self._asset_names + task_names_by_asset_name = ( + self._controller.get_task_names_by_asset_names(self._asset_names) + ) + self._task_names_by_asset_name = task_names_by_asset_name + + new_task_names = self.get_intersection_of_tasks( + task_names_by_asset_name ) old_task_names = set(self._items_by_name.keys()) if new_task_names == old_task_names: From f588cfec01575dcea61737a7cf8ec698333abc06 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 7 Sep 2021 10:19:49 +0200 Subject: [PATCH 448/736] fixed how tasks are validated --- .../tools/new_publisher/widgets/widgets.py | 89 ++++++++++++++++--- 1 file changed, 76 insertions(+), 13 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index d616116e45..fca78642c3 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -353,6 +353,7 @@ class TasksCombobox(QtWidgets.QComboBox): self._delegate = delegate self._model = model self._origin_value = [] + self._origin_selection = [] self._selected_items = [] self._has_value_changed = False self._ignore_index_change = False @@ -369,7 +370,7 @@ class TasksCombobox(QtWidgets.QComboBox): self._set_is_valid(True) self._selected_items = [self.currentText()] self._has_value_changed = ( - self._origin_value != self._selected_items + self._origin_selection != self._selected_items ) self.value_changed.emit() @@ -399,17 +400,62 @@ class TasksCombobox(QtWidgets.QComboBox): return list(self._selected_items) def set_asset_names(self, asset_names): - self._model.set_asset_names(asset_names) - self.set_selected_items(self._origin_value) + self._ignore_index_change = True - def set_selected_items(self, task_names=None): - if task_names is None: - task_names = [] + self._model.set_asset_names(asset_names) + + self._ignore_index_change = False + + # It is a bug if not exactly one asset got here + if len(asset_names) != 1: + self.set_selected_item("") + self._set_is_valid(False) + return + + asset_name = tuple(asset_names)[0] + + is_valid = False + if self._selected_items: + is_valid = True + + for task_name in self._selected_items: + is_valid = self._model.is_task_name_valid(asset_name, task_name) + if not is_valid: + break + + if len(self._selected_items) == 0: + self.set_selected_item("") + + elif len(self._selected_items) == 1: + self.set_selected_item(self._selected_items[0]) + + else: + multiselection_text = self._multiselection_text + if multiselection_text is None: + multiselection_text = "|".join(self._selected_items) + self.set_selected_item(multiselection_text) + self._set_is_valid(is_valid) + + def set_selected_items(self, asset_task_combinations=None): + if asset_task_combinations is None: + asset_task_combinations = [] + + task_names = set() + task_names_by_asset_name = collections.defaultdict(set) + for asset_name, task_name in asset_task_combinations: + task_names.add(task_name) + task_names_by_asset_name[asset_name].add(task_name) + asset_names = set(task_names_by_asset_name.keys()) self._ignore_index_change = True + self._model.set_asset_names(asset_names) + self._has_value_changed = False - self._origin_value = list(task_names) + + self._origin_value = copy.deepcopy(asset_task_combinations) + + self._origin_selection = list(task_names) self._selected_items = list(task_names) # Reset current index self.setCurrentIndex(-1) @@ -421,6 +467,10 @@ class TasksCombobox(QtWidgets.QComboBox): task_name = tuple(task_names)[0] idx = self.findText(task_name) is_valid = not idx < 0 + if not is_valid and len(asset_names) > 1: + is_valid = self._validate_task_names_by_asset_names( + task_names_by_asset_name + ) self.set_selected_item(task_name) else: @@ -430,6 +480,10 @@ class TasksCombobox(QtWidgets.QComboBox): if not is_valid: break + if not is_valid and len(asset_names) > 1: + is_valid = self._validate_task_names_by_asset_names( + task_names_by_asset_name + ) multiselection_text = self._multiselection_text if multiselection_text is None: multiselection_text = "|".join(task_names) @@ -441,6 +495,13 @@ class TasksCombobox(QtWidgets.QComboBox): self.value_changed.emit() + def _validate_task_names_by_asset_names(self, task_names_by_asset_name): + for asset_name, task_names in task_names_by_asset_name.items(): + for task_name in task_names: + if not self._model.is_task_name_valid(asset_name, task_name): + return False + return True + def set_selected_item(self, item_name): idx = self.findText(item_name) if idx < 0: @@ -737,7 +798,8 @@ class GlobalAttrsWidget(QtWidgets.QWidget): or self.task_value_widget.has_value_changed() ) self._set_btns_visible(any_changed or any_invalid) - self._set_btns_enabled(not any_invalid) + self.cancel_btn.setEnabled(any_changed) + self.submit_btn.setEnabled(not any_invalid) def _on_variant_change(self): self._on_value_change() @@ -764,7 +826,6 @@ class GlobalAttrsWidget(QtWidgets.QWidget): self._current_instances = instances asset_names = set() - task_names = set() variants = set() families = set() subset_names = set() @@ -773,14 +834,17 @@ class GlobalAttrsWidget(QtWidgets.QWidget): if len(instances) == 0: editable = False + asset_task_combinations = [] for instance in instances: if instance.creator is None: editable = False variants.add(instance.data.get("variant") or self.unknown_value) families.add(instance.data.get("family") or self.unknown_value) - asset_names.add(instance.data.get("asset") or self.unknown_value) - task_names.add(instance.data.get("task") or self.unknown_value) + asset_name = instance.data.get("asset") or self.unknown_value + task_name = instance.data.get("task") or self.unknown_value + asset_names.add(asset_name) + asset_task_combinations.append((asset_name, task_name)) subset_names.add(instance.data.get("subset") or self.unknown_value) self.variant_input.set_value(variants) @@ -788,8 +852,7 @@ class GlobalAttrsWidget(QtWidgets.QWidget): # Set context of asset widget self.asset_value_widget.set_selected_items(asset_names) # Set context of task widget - self.task_value_widget.set_asset_names(asset_names) - self.task_value_widget.set_selected_items(task_names) + self.task_value_widget.set_selected_items(asset_task_combinations) self.family_value_widget.set_value(families) self.subset_value_widget.set_value(subset_names) From 7d25fa3e8bdda7f1ce6a040a77c55528a131d45f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 7 Sep 2021 10:37:16 +0200 Subject: [PATCH 449/736] fixed select index of treeview combobox --- .../tools/new_publisher/widgets/widgets.py | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index fca78642c3..4087611cee 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -197,21 +197,28 @@ class TreeComboBox(QtWidgets.QComboBox): super(TreeComboBox, self).setModel(model) def showPopup(self): - self.setRootModelIndex(QtCore.QModelIndex()) super(TreeComboBox, self).showPopup() def hidePopup(self): - # NOTE This would hide everything except parents - # self.setRootModelIndex(self._tree_view.currentIndex().parent()) - # self.setCurrentIndex(self._tree_view.currentIndex().row()) if self._skip_next_hide: self._skip_next_hide = False else: super(TreeComboBox, self).hidePopup() - def selectIndex(self, index): - self.setRootModelIndex(index.parent()) - self.setCurrentIndex(index.row()) + def select_index(self, index): + parent_indexes = [] + parent_index = index.parent() + while parent_index.isValid(): + parent_indexes.append(parent_index) + parent_index = parent_index.parent() + + for parent_index in parent_indexes: + self._tree_view.expand(parent_index) + selection_model = self._tree_view.selectionModel() + selection_model.setCurrentIndex( + index, selection_model.ClearAndSelect + ) + self.lineEdit().setText(index.data(QtCore.Qt.DisplayRole) or "") def eventFilter(self, obj, event): if ( @@ -230,7 +237,7 @@ class TreeComboBox(QtWidgets.QComboBox): self._tree_view.selectionModel().setCurrentIndex( index, QtCore.QItemSelectionModel.SelectCurrent ) - self.selectIndex(index) + self.select_index(index) else: self.lineEdit().setText(item_name) From 709c6c977363d20e1536386ae23eb175903c5955 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 7 Sep 2021 10:38:14 +0200 Subject: [PATCH 450/736] lineedit is transparent for mouse events --- openpype/tools/new_publisher/widgets/widgets.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 4087611cee..2bd7b274e7 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -191,6 +191,9 @@ class TreeComboBox(QtWidgets.QComboBox): self.setEditable(True) # Set `lineEdit` to read only self.lineEdit().setReadOnly(True) + self.lineEdit().setAttribute( + QtCore.Qt.WA_TransparentForMouseEvents, True + ) def setModel(self, model): self._model = model From 04d1af966a7f07ed073d3b6a3fce232d0cad6650 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 7 Sep 2021 10:41:06 +0200 Subject: [PATCH 451/736] removed asset input in creator dialog --- openpype/tools/new_publisher/widgets/create_dialog.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/create_dialog.py b/openpype/tools/new_publisher/widgets/create_dialog.py index 6236d1f82f..9c22ab919a 100644 --- a/openpype/tools/new_publisher/widgets/create_dialog.py +++ b/openpype/tools/new_publisher/widgets/create_dialog.py @@ -144,9 +144,6 @@ class CreateDialog(QtWidgets.QDialog): variant_layout.addWidget(variant_input, 1) variant_layout.addWidget(variant_hints_btn, 0) - asset_name_input = QtWidgets.QLineEdit(self) - asset_name_input.setEnabled(False) - subset_name_input = QtWidgets.QLineEdit(self) subset_name_input.setEnabled(False) @@ -156,8 +153,6 @@ class CreateDialog(QtWidgets.QDialog): layout = QtWidgets.QVBoxLayout(self) layout.addWidget(QtWidgets.QLabel("Family:", self)) layout.addWidget(family_view, 1) - layout.addWidget(QtWidgets.QLabel("Asset:", self)) - layout.addWidget(asset_name_input, 0) layout.addWidget(QtWidgets.QLabel("Name:", self)) layout.addLayout(variant_layout, 0) layout.addWidget(QtWidgets.QLabel("Subset:", self)) @@ -174,7 +169,6 @@ class CreateDialog(QtWidgets.QDialog): controller.add_plugins_refresh_callback(self._on_plugins_refresh) - self.asset_name_input = asset_name_input self.subset_name_input = subset_name_input self.variant_input = variant_input @@ -200,7 +194,8 @@ class CreateDialog(QtWidgets.QDialog): self._refresh_creators() if self._asset_doc is None: - self.asset_name_input.setText("< Asset is not set >") + # QUESTION how to handle invalid asset? + self.subset_name_input.setText("< Asset is not set >") self._prereq_available = False if self.family_model.rowCount() < 1: @@ -231,7 +226,6 @@ class CreateDialog(QtWidgets.QDialog): self._asset_doc = asset_doc if asset_doc: - self.asset_name_input.setText(asset_doc["name"]) subset_docs = self.dbcon.find( { "type": "subset", From c1065c57c15a989f6760ad71e903d70cd86dbb0c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 7 Sep 2021 16:55:32 +0200 Subject: [PATCH 452/736] added run publish script --- openpype/hosts/testhost/run_publish.py | 68 ++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 openpype/hosts/testhost/run_publish.py diff --git a/openpype/hosts/testhost/run_publish.py b/openpype/hosts/testhost/run_publish.py new file mode 100644 index 0000000000..f1c624678a --- /dev/null +++ b/openpype/hosts/testhost/run_publish.py @@ -0,0 +1,68 @@ +import os +import sys +openpype_dir = "" +mongo_url = "" +project_name = "" +asset_name = "" +task_name = "" +host_name = "" +ftrack_url = "" +ftrack_username = "" +ftrack_api_key = "" + +current_file = os.path.abspath(__file__) + + +def multi_dirname(path, times=1): + for _ in range(times): + path = os.path.dirname(path) + return path + + +os.environ["OPENPYPE_MONGO"] = mongo_url +os.environ["OPENPYPE_ROOT"] = multi_dirname(current_file, 4) +os.environ["AVALON_MONGO"] = mongo_url +os.environ["AVALON_PROJECT"] = project_name +os.environ["AVALON_ASSET"] = asset_name +os.environ["AVALON_TASK"] = task_name +os.environ["AVALON_APP"] = host_name +os.environ["OPENPYPE_DATABASE_NAME"] = "openpype" +os.environ["AVALON_CONFIG"] = "openpype" +os.environ["AVALON_TIMEOUT"] = "1000" +os.environ["AVALON_DB"] = "avalon" +os.environ["FTRACK_SERVER"] = ftrack_url +os.environ["FTRACK_API_USER"] = ftrack_username +os.environ["FTRACK_API_KEY"] = ftrack_api_key +for path in [ + openpype_dir, + r"{}\repos\avalon-core".format(openpype_dir), + r"{}\.venv\Lib\site-packages".format(openpype_dir) +]: + sys.path.append(path) + +from Qt import QtWidgets + +from openpype.tools.new_publisher.window import PublisherWindow + + +def main(): + """Main function for testing purposes.""" + import avalon.api + import pyblish.api + from openpype.modules import ModulesManager + from openpype.hosts.testhost import api as testhost + + manager = ModulesManager() + for plugin_path in manager.collect_plugin_paths()["publish"]: + pyblish.api.register_plugin_path(plugin_path) + + avalon.api.install(testhost) + + app = QtWidgets.QApplication([]) + window = PublisherWindow() + window.show() + app.exec_() + + +if __name__ == "__main__": + main() From 82c7115f279e5735e2a23bc685bf56b4c4ddd98b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 7 Sep 2021 16:59:54 +0200 Subject: [PATCH 453/736] added missing footer widget --- openpype/tools/new_publisher/window.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 38902fef39..ac5cd37f68 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -122,7 +122,9 @@ class PublisherWindow(QtWidgets.QDialog): publish_btn.setIcon(get_icon("play")) publish_btn.setToolTip("Publish") - footer_layout = QtWidgets.QHBoxLayout() + footer_widget = QtWidgets.QWidget(subset_frame) + footer_layout = QtWidgets.QHBoxLayout(footer_widget) + footer_layout.setContentsMargins(0, 0, 0, 0) footer_layout.addWidget(message_input, 1) footer_layout.addWidget(reset_btn, 0) footer_layout.addWidget(stop_btn, 0) From 8c8360aef8526454bc74cb107a0a5c87106183e3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 7 Sep 2021 17:43:47 +0200 Subject: [PATCH 454/736] fix task changes --- openpype/tools/new_publisher/widgets/widgets.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 2bd7b274e7..9e1aa3a7fe 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -377,8 +377,13 @@ class TasksCombobox(QtWidgets.QComboBox): if self._ignore_index_change: return + text = self.currentText() + idx = self.findText(text) + if idx < 0: + return + self._set_is_valid(True) - self._selected_items = [self.currentText()] + self._selected_items = [text] self._has_value_changed = ( self._origin_selection != self._selected_items ) @@ -514,10 +519,10 @@ class TasksCombobox(QtWidgets.QComboBox): def set_selected_item(self, item_name): idx = self.findText(item_name) + # Set current index (must be set to -1 if is invalid) + self.setCurrentIndex(idx) if idx < 0: self.lineEdit().setText(item_name) - else: - self.setCurrentIndex(idx) def reset_to_origin(self): self.set_selected_items(self._origin_value) From f7c821da2ee7442a0926e8670cab95a8248ece6a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 7 Sep 2021 17:50:13 +0200 Subject: [PATCH 455/736] added context label constant --- openpype/tools/new_publisher/constants.py | 1 + .../tools/new_publisher/widgets/instance_views_widgets.py | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/tools/new_publisher/constants.py b/openpype/tools/new_publisher/constants.py index 1e3481f594..ffdf90df9b 100644 --- a/openpype/tools/new_publisher/constants.py +++ b/openpype/tools/new_publisher/constants.py @@ -2,6 +2,7 @@ from Qt import QtCore # ID of context item in instance view CONTEXT_ID = "context" +CONTEXT_LABEL = "Publish options" # Allowed symbols for subset name (and variant) # - characters, numbers, unsercore and dash diff --git a/openpype/tools/new_publisher/widgets/instance_views_widgets.py b/openpype/tools/new_publisher/widgets/instance_views_widgets.py index aa45f44b1e..ab713bfe36 100644 --- a/openpype/tools/new_publisher/widgets/instance_views_widgets.py +++ b/openpype/tools/new_publisher/widgets/instance_views_widgets.py @@ -6,7 +6,8 @@ from openpype.widgets.nice_checkbox import NiceCheckbox from ..constants import ( INSTANCE_ID_ROLE, SORT_VALUE_ROLE, - CONTEXT_ID + CONTEXT_ID, + CONTEXT_LABEL ) from .icons import get_pixmap @@ -158,7 +159,7 @@ class ContextCardWidget(QtWidgets.QWidget): self.item = item - subset_name_label = QtWidgets.QLabel("Context", self) + subset_name_label = QtWidgets.QLabel(CONTEXT_LABEL, self) layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(2, 2, 2, 2) @@ -400,7 +401,7 @@ class ListContextWidget(QtWidgets.QFrame): def __init__(self, parent): super(ListContextWidget, self).__init__(parent) - label_widget = QtWidgets.QLabel("Context", self) + label_widget = QtWidgets.QLabel(CONTEXT_LABEL, self) layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(5, 0, 2, 0) From a42444c96f50f9ad2a9560e2922a68283d308d54 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 7 Sep 2021 18:05:32 +0200 Subject: [PATCH 456/736] update subset names on submit --- openpype/tools/new_publisher/widgets/widgets.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 9e1aa3a7fe..fd2a1d949b 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -767,6 +767,7 @@ class GlobalAttrsWidget(QtWidgets.QWidget): break project_name = self.controller.project_name + subset_names = set() for instance in self._current_instances: if variant_value is not None: instance.data["variant"] = variant_value @@ -788,8 +789,11 @@ class GlobalAttrsWidget(QtWidgets.QWidget): new_subset_name = instance.creator.get_subset_name( new_variant_value, new_task_name, asset_doc, project_name ) + subset_names.add(new_subset_name) instance.data["subset"] = new_subset_name + self.subset_value_widget.set_value(subset_names) + self._set_btns_enabled(False) self._set_btns_visible(False) @@ -1132,6 +1136,7 @@ class SubsetAttributesWidget(QtWidgets.QWidget): if not instance.has_valid_context: all_valid = False break + self._all_instances_valid = all_valid self.family_attrs_widget.set_instances_valid(all_valid) self.publish_attrs_widget.set_instances_valid(all_valid) From bc6224805f3fb994633291246a97a94a10626e0d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 8 Sep 2021 16:13:34 +0200 Subject: [PATCH 457/736] added border label widget --- .../tools/new_publisher/widgets/__init__.py | 3 + .../widgets/border_label_widget.py | 182 ++++++++++++++++++ openpype/tools/new_publisher/window.py | 13 +- 3 files changed, 195 insertions(+), 3 deletions(-) create mode 100644 openpype/tools/new_publisher/widgets/border_label_widget.py diff --git a/openpype/tools/new_publisher/widgets/__init__.py b/openpype/tools/new_publisher/widgets/__init__.py index 874bb830c7..6686905925 100644 --- a/openpype/tools/new_publisher/widgets/__init__.py +++ b/openpype/tools/new_publisher/widgets/__init__.py @@ -2,6 +2,8 @@ from .icons import ( get_icon_path, get_pixmap, get_icon +from .border_label_widget import ( + BorderedLabelWidget ) from .widgets import ( SubsetAttributesWidget, @@ -26,6 +28,7 @@ __all__ = ( "get_icon", "SubsetAttributesWidget", + "BorderedLabelWidget", "IconBtn", "PublishFrame", diff --git a/openpype/tools/new_publisher/widgets/border_label_widget.py b/openpype/tools/new_publisher/widgets/border_label_widget.py new file mode 100644 index 0000000000..dc61753346 --- /dev/null +++ b/openpype/tools/new_publisher/widgets/border_label_widget.py @@ -0,0 +1,182 @@ +from Qt import QtWidgets, QtCore, QtGui + +from openpype.style import get_colors_data + + +class _VLineWidget(QtWidgets.QWidget): + def __init__(self, color, parent): + super(_VLineWidget, self).__init__(parent) + self._color = color + + def paintEvent(self, event): + if not self.isVisible(): + return + + rect = QtCore.QRect( + 0, 0, self.width(), self.height() + ) + + painter = QtGui.QPainter(self) + painter.setRenderHints( + painter.Antialiasing + | painter.SmoothPixmapTransform + ) + painter.setPen(QtCore.Qt.NoPen) + if self._color: + painter.setBrush(self._color) + painter.drawRect(rect) + painter.end() + + +class _HLineWidget(QtWidgets.QWidget): + def __init__(self, color, parent): + super(_HLineWidget, self).__init__(parent) + self._color = color + self._radius = 0 + + def set_radius(self, radius): + self._radius = radius + + def paintEvent(self, event): + if not self.isVisible(): + return + + rect = QtCore.QRect( + 0, -self._radius, self.width(), self.height() + self._radius + ) + painter = QtGui.QPainter(self) + painter.setRenderHints( + painter.Antialiasing + | painter.SmoothPixmapTransform + ) + if self._color: + pen = QtGui.QPen(self._color) + else: + pen = painter.pen() + pen.setWidth(1) + painter.setPen(pen) + painter.setBrush(QtCore.Qt.transparent) + painter.drawRoundedRect(rect, self._radius, self._radius) + painter.end() + + +class _HCornerLineWidget(QtWidgets.QWidget): + def __init__(self, color, left_side, parent): + super(_HCornerLineWidget, self).__init__(parent) + self._left_side = left_side + self._color = color + self._radius = 0 + + def set_radius(self, radius): + self._radius = radius + + def paintEvent(self, event): + if not self.isVisible(): + return + + pos_y = self.height() / 2 + + if self._left_side: + rect = QtCore.QRect( + 0, pos_y, self.width() + self._radius, self.height() + ) + else: + rect = QtCore.QRect( + -self._radius, + pos_y, + self.width() + self._radius, + self.height() + ) + + painter = QtGui.QPainter(self) + painter.setRenderHints( + painter.Antialiasing + | painter.SmoothPixmapTransform + ) + if self._color: + pen = QtGui.QPen(self._color) + else: + pen = painter.pen() + pen.setWidth(1) + painter.setPen(pen) + painter.setBrush(QtCore.Qt.transparent) + painter.drawRoundedRect(rect, self._radius, self._radius) + painter.end() + + +class BorderedLabelWidget(QtWidgets.QFrame): + def __init__(self, label, parent): + super(BorderedLabelWidget, self).__init__(parent) + colors_data = get_colors_data() + color_value = colors_data.get("border") + try: + color = QtGui.QColor(color_value) + except Exception: + color = None + + top_left_w = _HCornerLineWidget(color, True, self) + top_right_w = _HCornerLineWidget(color, False, self) + + label_widget = QtWidgets.QLabel(label, self) + + top_layout = QtWidgets.QHBoxLayout() + top_layout.setContentsMargins(0, 0, 0, 0) + top_layout.setSpacing(5) + top_layout.addWidget(top_left_w, 1) + top_layout.addWidget(label_widget, 0) + top_layout.addWidget(top_right_w, 1) + + left_w = _VLineWidget(color, self) + right_w = _VLineWidget(color, self) + + bottom_w = _HLineWidget(color, self) + + center_layout = QtWidgets.QHBoxLayout() + center_layout.setContentsMargins(5, 5, 5, 5) + + layout = QtWidgets.QGridLayout(self) + layout.setContentsMargins(5, 5, 5, 5) + layout.setSpacing(0) + + layout.addLayout(top_layout, 0, 0, 1, 3) + + layout.addWidget(left_w, 1, 0) + layout.addLayout(center_layout, 1, 1) + layout.addWidget(right_w, 1, 2) + + layout.addWidget(bottom_w, 2, 0, 1, 3) + + layout.setColumnStretch(1, 1) + layout.setRowStretch(1, 1) + + self._top_left_w = top_left_w + self._top_right_w = top_right_w + self._left_w = left_w + self._right_w = right_w + self._bottom_w = bottom_w + self._label_widget = label_widget + self._center_layout = center_layout + + def showEvent(self, event): + super(BorderedLabelWidget, self).showEvent(event) + + height = self._label_widget.height() + radius = (height + (height % 2)) / 2 + self._left_w.setMinimumWidth(1) + self._left_w.setMaximumWidth(1) + self._right_w.setMinimumWidth(1) + self._right_w.setMaximumWidth(1) + self._bottom_w.setMinimumHeight(radius) + self._bottom_w.setMaximumHeight(radius) + self._bottom_w.set_radius(radius) + self._top_right_w.set_radius(radius) + self._top_left_w.set_radius(radius) + if self._widget: + self._widget.update() + + def set_center_widget(self, widget): + self._widget = widget + if isinstance(widget, QtWidgets.QLayout): + self._center_layout.addLayout(widget) + else: + self._center_layout.addWidget(widget) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index ac5cd37f68..44b2121c06 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -7,6 +7,7 @@ from openpype import ( from .control import PublisherController from .widgets import ( + BorderedLabelWidget, PublishFrame, SubsetAttributesWidget, InstanceCardView, @@ -64,8 +65,12 @@ class PublisherWindow(QtWidgets.QDialog): # Subset widget subset_frame = QtWidgets.QWidget(self) - subset_view_cards = InstanceCardView(controller, subset_frame) - subset_list_view = InstanceListView(controller, subset_frame) + subset_views_widget = BorderedLabelWidget( + "Subsets to publish", subset_frame + ) + + subset_view_cards = InstanceCardView(controller, subset_views_widget) + subset_list_view = InstanceListView(controller, subset_views_widget) subset_views_layout = QtWidgets.QStackedLayout() subset_views_layout.addWidget(subset_view_cards) @@ -96,11 +101,13 @@ class PublisherWindow(QtWidgets.QDialog): subset_view_layout.addLayout(subset_views_layout, 1) subset_view_layout.addLayout(subset_view_btns_layout, 0) + subset_views_widget.set_center_widget(subset_view_layout) + # Whole subset layout with attributes and details subset_content_widget = QtWidgets.QWidget(subset_frame) subset_content_layout = QtWidgets.QHBoxLayout(subset_content_widget) subset_content_layout.setContentsMargins(0, 0, 0, 0) - subset_content_layout.addLayout(subset_view_layout, 0) + subset_content_layout.addWidget(subset_views_widget, 0) subset_content_layout.addWidget(subset_attributes_widget, 1) # Footer From c301d3f70c47add84e57f49f50670ce4ba04f3b2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 8 Sep 2021 16:19:34 +0200 Subject: [PATCH 458/736] safer border label widget --- .../tools/new_publisher/widgets/border_label_widget.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openpype/tools/new_publisher/widgets/border_label_widget.py b/openpype/tools/new_publisher/widgets/border_label_widget.py index dc61753346..c4be6d1d4e 100644 --- a/openpype/tools/new_publisher/widgets/border_label_widget.py +++ b/openpype/tools/new_publisher/widgets/border_label_widget.py @@ -149,6 +149,8 @@ class BorderedLabelWidget(QtWidgets.QFrame): layout.setColumnStretch(1, 1) layout.setRowStretch(1, 1) + self._widget = None + self._top_left_w = top_left_w self._top_right_w = top_right_w self._left_w = left_w @@ -175,6 +177,12 @@ class BorderedLabelWidget(QtWidgets.QFrame): self._widget.update() def set_center_widget(self, widget): + while self._center_layout.count(): + item = self._center_layout.takeAt(0) + widget = item.widget() + if widget: + widget.deleteLater() + self._widget = widget if isinstance(widget, QtWidgets.QLayout): self._center_layout.addLayout(widget) From b21fb041cc4f59968dee44ed5669362c07740b27 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 8 Sep 2021 16:19:52 +0200 Subject: [PATCH 459/736] wrap attributes widget in border label --- openpype/tools/new_publisher/window.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 44b2121c06..aded7e3401 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -82,9 +82,13 @@ class PublisherWindow(QtWidgets.QDialog): change_view_btn = QtWidgets.QPushButton("=", subset_frame) # Subset details widget - subset_attributes_widget = SubsetAttributesWidget( - controller, subset_frame + subset_attributes_wrap = BorderedLabelWidget( + "Publish options", subset_frame ) + subset_attributes_widget = SubsetAttributesWidget( + controller, subset_attributes_wrap + ) + subset_attributes_wrap.set_center_widget(subset_attributes_widget) # Layout of buttons at the bottom of subset view subset_view_btns_layout = QtWidgets.QHBoxLayout() @@ -108,7 +112,7 @@ class PublisherWindow(QtWidgets.QDialog): subset_content_layout = QtWidgets.QHBoxLayout(subset_content_widget) subset_content_layout.setContentsMargins(0, 0, 0, 0) subset_content_layout.addWidget(subset_views_widget, 0) - subset_content_layout.addWidget(subset_attributes_widget, 1) + subset_content_layout.addWidget(subset_attributes_wrap, 1) # Footer message_input = QtWidgets.QLineEdit(subset_frame) From 23b4e3e35067916f9d3f411f88e78c361c48a6ac Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 8 Sep 2021 17:50:04 +0200 Subject: [PATCH 460/736] style have get colors data --- openpype/style/__init__.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/openpype/style/__init__.py b/openpype/style/__init__.py index 87547b1a90..bb92ada4fe 100644 --- a/openpype/style/__init__.py +++ b/openpype/style/__init__.py @@ -10,6 +10,18 @@ _FONT_IDS = None current_dir = os.path.dirname(os.path.abspath(__file__)) +def get_colors_data(): + data = _get_colors_raw_data() + return data.get("color") or {} + + +def _get_colors_raw_data(): + data_path = os.path.join(current_dir, "data.json") + with open(data_path, "r") as data_stream: + data = json.load(data_stream) + return data + + def _load_stylesheet(): from . import qrc_resources @@ -19,9 +31,7 @@ def _load_stylesheet(): with open(style_path, "r") as style_file: stylesheet = style_file.read() - data_path = os.path.join(current_dir, "data.json") - with open(data_path, "r") as data_stream: - data = json.load(data_stream) + data = _get_colors_raw_data() data_deque = collections.deque() for item in data.items(): From caec8708e9427c73732987c987a6e96df257c301 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 8 Sep 2021 17:51:15 +0200 Subject: [PATCH 461/736] each publish btn has it's class --- .../tools/new_publisher/widgets/__init__.py | 10 +- .../tools/new_publisher/widgets/widgets.py | 94 ++++++++++++++++++- openpype/tools/new_publisher/window.py | 24 ++--- 3 files changed, 110 insertions(+), 18 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/__init__.py b/openpype/tools/new_publisher/widgets/__init__.py index 6686905925..b3f74a7f19 100644 --- a/openpype/tools/new_publisher/widgets/__init__.py +++ b/openpype/tools/new_publisher/widgets/__init__.py @@ -7,7 +7,11 @@ from .border_label_widget import ( ) from .widgets import ( SubsetAttributesWidget, - IconBtn + IconBtn, + StopBtn, + ResetBtn, + ValidateBtn, + PublishBtn ) from .publish_widget import ( PublishFrame @@ -30,6 +34,10 @@ __all__ = ( "SubsetAttributesWidget", "BorderedLabelWidget", "IconBtn", + "StopBtn", + "ResetBtn", + "ValidateBtn", + "PublishBtn", "PublishFrame", diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index fd2a1d949b..d704c0e374 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -6,7 +6,10 @@ from Qt import QtWidgets, QtCore, QtGui from openpype.widgets.attribute_defs import create_widget_for_attr_def from openpype.tools.flickcharm import FlickCharm -from .icons import get_pixmap +from .icons import ( + get_pixmap, + get_icon_path +) from ..constants import ( SUBSET_NAME_ALLOWED_SYMBOLS, @@ -32,6 +35,95 @@ class IconBtn(QtWidgets.QPushButton): self.setIconSize(QtCore.QSize(icon_size, icon_size)) +class PublishIconBtn(IconBtn): + def __init__(self, pixmap_path, *args, **kwargs): + super(PublishIconBtn, self).__init__(*args, **kwargs) + + loaded_image = QtGui.QImage(pixmap_path) + + pixmap = self.paint_image_with_color(loaded_image, QtCore.Qt.white) + + self._base_image = loaded_image + self._enabled_icon = QtGui.QIcon(pixmap) + self._disabled_icon = None + + self.setIcon(self._enabled_icon) + + def get_enabled_icon(self): + return self._enabled_icon + + def get_disabled_icon(self): + if self._disabled_icon is None: + pixmap = self.paint_image_with_color( + self._base_image, QtCore.Qt.gray + ) + self._disabled_icon = QtGui.QIcon(pixmap) + return self._disabled_icon + + @staticmethod + def paint_image_with_color(image, color): + width = image.width() + height = image.height() + tenth_w = int(width / 10) + tenth_h = int(height / 10) + tenth_w -= tenth_w % 2 + tenth_h -= tenth_h % 2 + scaled_image = image.scaled( + width - (2 * tenth_w), + height - (2 * tenth_h) + ) + alpha_mask = scaled_image.createAlphaMask() + alpha_region = QtGui.QRegion(QtGui.QBitmap(alpha_mask)) + alpha_region.translate(tenth_w, tenth_h) + + pixmap = QtGui.QPixmap(width, height) + pixmap.fill(QtCore.Qt.transparent) + + painter = QtGui.QPainter(pixmap) + painter.setClipRegion(alpha_region) + painter.setPen(QtCore.Qt.NoPen) + painter.setBrush(color) + painter.drawRect(QtCore.QRect(0, 0, width, height)) + painter.end() + + return pixmap + + def setEnabled(self, enabled): + super(PublishIconBtn, self).setEnabled(enabled) + if self.isEnabled(): + self.setIcon(self.get_enabled_icon()) + else: + self.setIcon(self.get_disabled_icon()) + + +class ResetBtn(PublishIconBtn): + def __init__(self, parent=None): + icon_path = get_icon_path("refresh") + super(ResetBtn, self).__init__(icon_path, parent) + self.setToolTip("Refresh publishing") + + +class StopBtn(PublishIconBtn): + def __init__(self, parent): + icon_path = get_icon_path("stop") + super(StopBtn, self).__init__(icon_path, parent) + self.setToolTip("Stop/Pause publishing") + + +class ValidateBtn(PublishIconBtn): + def __init__(self, parent=None): + icon_path = get_icon_path("validate") + super(ValidateBtn, self).__init__(icon_path, parent) + self.setToolTip("Validate") + + +class PublishBtn(PublishIconBtn): + def __init__(self, parent=None): + icon_path = get_icon_path("play") + super(PublishBtn, self).__init__(icon_path, "Publish", parent) + self.setToolTip("Publish") + + class AssetsHierarchyModel(QtGui.QStandardItemModel): def __init__(self, controller): super(AssetsHierarchyModel, self).__init__() diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index aded7e3401..60c4c3c82f 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -14,7 +14,10 @@ from .widgets import ( InstanceListView, CreateDialog, IconBtn, - get_icon + StopBtn, + ResetBtn, + ValidateBtn, + PublishBtn, ) @@ -117,21 +120,10 @@ class PublisherWindow(QtWidgets.QDialog): # Footer message_input = QtWidgets.QLineEdit(subset_frame) - reset_btn = IconBtn(subset_frame) - reset_btn.setIcon(get_icon("refresh")) - reset_btn.setToolTip("Refresh publishing") - - stop_btn = IconBtn(subset_frame) - stop_btn.setIcon(get_icon("stop")) - stop_btn.setToolTip("Stop/Pause publishing") - - validate_btn = IconBtn(subset_frame) - validate_btn.setIcon(get_icon("validate")) - validate_btn.setToolTip("Validate") - - publish_btn = IconBtn(subset_frame) - publish_btn.setIcon(get_icon("play")) - publish_btn.setToolTip("Publish") + reset_btn = ResetBtn(subset_frame) + stop_btn = StopBtn(subset_frame) + validate_btn = ValidateBtn(subset_frame) + publish_btn = PublishBtn(subset_frame) footer_widget = QtWidgets.QWidget(subset_frame) footer_layout = QtWidgets.QHBoxLayout(footer_widget) From 5b85552e34b8d14c0a9e619ae3e8091fea1640fa Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 8 Sep 2021 17:51:27 +0200 Subject: [PATCH 462/736] change publish btns images --- .../new_publisher/widgets/images/play.png | Bin 1020 -> 4747 bytes .../new_publisher/widgets/images/refresh.png | Bin 7656 -> 7221 bytes .../new_publisher/widgets/images/stop.png | Bin 463 -> 4064 bytes .../new_publisher/widgets/images/validate.png | Bin 46158 -> 16232 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/images/play.png b/openpype/tools/new_publisher/widgets/images/play.png index 6e0fefab28e3b8a774725670d7a414bc61780490..7019bf19e9654098f39159fc1192d9c76101ef10 100644 GIT binary patch literal 4747 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 PUjTaN47G~R+J^rNu=Y9e literal 1020 zcmeAS@N?(olHy`uVBq!ia0vp^3qY8I4M=vMPuB%foCO|{#S9F5M?jcysy3fA0|WCq zPZ!6KiaBqu`{oHd3bZ|xYH2Cr+4NNFWN3r5O7^))p=Ux9Udl9Y&=R}o(Wj{{cfO?j z@1FCFZhL=(tk-1%nu-K2G_3j;aCP>@_YQ%pel5^1TUBNI%U0uALc^+6=T}v&UlqRi zeydU+o5k(PPiyRY>JBi?cTI7hR@`ZF;AU^fAGOmN&s>^xQ!ICMh<^}> zLWfyI@TZDWxzeX@o#PWT8;m!}cpPIqccO@4CcB2yw7aM64oF6ptvUr%d~;?NvqAHUi8wj?>r^8`e*JdQM_NuTJ?P27l$#vIYmfPkh=Y z@!;o)B4(Zf$xV9<*(!jfAlnYU$hc0%b?(!`6B=$So)!m^%BQ&v4w#+zG*coWI_0M< z&j$WYHB8KJ+I6HQ66#ZaGBC@u>u@v6tgV?Qxqv@)^BIO(&FQIZFZ@^P7(49uN}0*{ zYr55G9)q?mk!cMZmz*|Y?$Pv}CYcbUx#yl1RvnEzVXv zXv%wXZ7X-g`3)c;*~1{ApF2T9GiI*_3Hj-Pgm&&Y2NG)k4HByHSNc>sYuWt8@IRRAvB7PQ7XhHnTBB0Q}C1*iG-mgGH{Hh?K#8Y7XBJp zCh-EjzA?PUNcdd@wD#Ey-Eu zn8*H*Y0~Y5TKp64SOpj!d~$7KmN<|ZoZ9f@+R8VaYTM7H?rV5r*3iSOvb{0I%U~z3 z#%WfS?T)VU3OjlAvYJlj3JK~f>~xI=W{v;1SHC*@V*A2|#f<*~uUd!hmHMkEQfP3% wJ#??!U%4%hznVXYbIJSJ%!rh^fXoN??af;D9qfqaeF~EBboFyt=akR{0I7(vRR910 diff --git a/openpype/tools/new_publisher/widgets/images/refresh.png b/openpype/tools/new_publisher/widgets/images/refresh.png index fe04ab9a8d49dfa103fc2101471866b01b188854..0b7f1565a7dd28f6ffff8217a2524ade6be64bc5 100644 GIT binary patch literal 7221 zcmeI1_gfQDm&fUZo=69QfQU#JLKOr;=q*5kNYe;N7nG(HX#pZdP?~^rfh6>fASj^; zqO=4sh!mv-Dbl2K$o8rlG;F#-Sp%q&>WpItKf8{G(?qT)&YyHl5GK?Tol@(1eM z1={%C2@FBqccTgk36b->>mA^N@^_Q-yYHU2sVQ*g^*7uMp?`XMdS?Is+y7_a|6m3b zKV8^9d+k4g))qEY)HJko^bCvuCT12^Hg*mm=o}{(HxDl#zkuL*Az=})sMrPZixQV4 zFH1?w$jZqpTv1elD66QdscT%-)VijvqYKr8=^Gdt8Jn1znOj)Gt*mVjNZadnH|!l8 zot$spLfv+8b-Uy4;pye=<9pZ7|K9z8z@Xre2M%d+wcEDm2;Rb5kCSKrXs)ZEhA_Nu+(b!XR`x7~!E z-oE~UcY{Okhd+EA866v+Ad)7hrazHqKF@xcn_pP``fX`>Wp$0R{(WO}Yx~E}?%w{v z&%>kRlV898xN{iqQc>|18N+lDeEb{TOyP*7PKNmq2|Wp>uxT1eIvV7smuYsqKxq^2 z`Zg}iqV3bfhITIOXgfG5G4WPy373qd*H|K03<0Dw|CBA!*#Mw}!xfl}jQ}~rCmr^+ zNIFHOm6MM-Tf@H!8G%rMDEfan=pE%YZGBm%%&{JB%!A&DsQ#(1D15MI>9Q%LxH|cL zJ@n`5s0@=B4{1GoW4_*U!8XIpVLWhzt;*!U30D~X#dXkm77ZUDKWD8O(l@6 z*_D3Ro6~fJI7*PnG1Njd=#bL062Ip`wQ!DU#m&+&!*Z6r>vXuN@JKXyvT9Cg{k>;l zV)MjZqXdPg6+QE>I@Zv7Ed6^}CRAFl>42Qv%XAt+iBnAwflih|UJ?b#=u%pA#9&Qc z{~FXZnCN=5=SKw+`q#CGtbQdgMDScIRUJK__h*`&pf0<4({v(S@iHfY>BH8Mn$d+N zZ4wi~8P{XW=vkN2Bd9aI+;IKLL?E}{o<&rh?pLd;tL%)0Y8+5`%i5z$t77V^q^QyI zwKlE5yb%!})?)pW8{C1VU*&_M;lW28fwiOvPB7OYgHK7vQYxMeWwmi~F!q~S7|(&y zEgeYW6HFBGW(5@!jmwgS+(sY?7@b+eCyFhIVGnvxx&2pWzA!0wm1uz8&}vVzxuM}j zC>rkaO|-uzKLU&=g2($Is>h0lfA6ULum0WczoJAK>U?4V=~+-kpjM{}m& zaa)$+#kYsSw_ou7sGOFhp+D+W3*d&Y{K17moQ5E&ZURZ z^XZQv{`mW4jV!_GG)-XyhxEF)w=d#t@)RozmFR5Gpz}0qh zNL#YXzL24{vN4y?{%g{|mAKC(wc|keAmCSulVaHo2*~PG5Bc+qloui_!!!=>7>N&4 zK1EAhw!gZv5XR_siVs^!?jKakskoD#yeogy=ePNM`?&vhuB*FJ8?tg1qIYZRs^#pb z3v3Hs;e)=M&U1EVO5x*ln}Hu6893>FH05xdk^^Nyy1unAZE-15?hAIOC|+|9+{h0K z2$0+jJg?}dUFz*k+h&>6nOve*%kIS%VgE(*g8aMHPj(!4(_7NVthXn(4@PTs3oqTb zP%xP!MR!1k-4Gda{3H^~jQGz#>sAJR1I_ai`zlwM6Yn_N(gyF|+%;h;o$tx@PqkMM zbX>anJAFR++xksygMn(Jcn9%FoeJOcpZF|H)=uxJ3$+Y$gcvzO-8UkugkD_temgU; zhV6UoPdlQif%{gg)rom_T>5ju*l#L|#jGeG=KbB8#~wRuc9x7Eei9zgu04PIeghaW zd+Mw|U>37hqo$jH7#@z|S1ivSz5GeR;Y3WM)aUqyf?=oC$!fBTcHH3j&KhQMK%hsa zOK@@=bd++dCSyMk+O4;bDiZcyLvwh)MSN*pi_$Cg?lCJ6{5ejobPHeM`6i+#9VK_V zDn{Otd%h+?-aPCvR&eNXm-cMs%qGC{pN!o#op~F+B2zTmO$+= zqE27_L$172su0iCy8Zce>OrVb`9t);v3k1kLB6(+AueyUsMM z_c%8iud>_?{$W900X-M}E$O>^$EnR>s8N?()*x+`Ru>>IUyV^19RAzZ5sH|0lM12J}Uh15$w}>vWua6@3V6C zrh%`R8bQz0>)sby4sVvo((sDqev~T_8rp(=f2FE(^_Snd5)aVEwwuIlt6CcJf-%fe zWuvY__O{JT6|qNGZD8rv;LilP$h$Ghf9(PyC)#gAw5sDZ9h%yMSH6-as+- zrn4~PK0(!RHc_FOL3-h9_tCjdOpMh<&e7K}s`*_nonKv3TMqS70sg3%6{RS%S{Ref zJB4*LDUQ(UE|WODe(PFyU|}dDmK3N~dgF3)?uzaU0fTC(nQl-3mNYJaahSl8I{cvt zQk13|p2h4C(Pjz*<7?gg7FC#L<*UaU)iJ*{7;$6nvY@%#jS7p-TODykY~6)Lr-X4^ z1G?!|MK2=bqJueyP2mixlmj<10i0XYiwJ&*%DYQ+_hrMJh87&}#rpEV^9X^Mt&695 zSwN7nkjHL-`qAGwLkiqozP%{dJB8cuK@I_eBd6)6qZK#Q&gk?_1Hb$va=GOTiM zsE+inHUGmlOSVp*j{~|}pDHN7;>y+1HFrj7@uP(a46UpA?gv_`C$B16*~0S zc>ygGt%4Z95(n|C@@o1vf3q5#MJ%H?pIA}?YFg9NI@U!`=7lDsRg$oM));@8a-LJP z94?bsA`Md`+rpD3M_qx!E;nh)b9id|#bgfI8h9ezYJUqcy7z2Wxe1|kL!`ol zp|eI(9c(oKyhj89pYCSBNeF<@-HHY> z#*QKLw*WMu5m&_`7R-jEr|5XpC*kkv^lp~nn}MOf^6`Jr81quRCot5k2>%K*J%b2R zMGLE1le9tg)rcT4vti}0R1YwTPjt{NQ4-`8#&Vl6 zVN6;sgXhcjsE3FqHnEp;V`h!f`iMOc%*b!TETPlRKz~aY{VXs!o)VvlfQX1?6hKI& z?}|%q0E<~);+Ai5a+}8jmQo?4lXqn$Ou%AILz~}%S8*P!FiF2LMBKEYz8)C*FuPo; zM^vhvUM%Aige1&mJ=P2?HY>;7ha2c?i9-2}Z93zqxSgm#CMxFpnly+&YM7*~C*Hy8 zU-vQZ2Ly>P4iP8|GfJH>^N!X-1hT-4vd7IhXil&m0GLtNh)2n-uv?e7Q=$PIa=EL@ zRId;aYB9*1MQ%ldY?L_y!Y0P5I<6@{@2!r2u$*P`1H}ABK)CNk?hgro0eWhIF4~X+G22JGe7&Q&;zQ}h6mL))3>v*C`8UNzT@41jATI(zJ1bWNo-}uRo-e~M z13^Q~3d{zJfi2dgCE(Lsvz3U|?zPwE$Phi)P@!Rkkaz4vt^slaESAMx)MK!?_iq_^ znrG(z*Um`~8La^X?XHX&4=DOG@FNbqG4I^ySBpSuoR&;T^SHWO=kgneVwHx9@zp?{ z!`;;k7k;%yUaZtKkPuTvC8S70;xlpk4X%0)bFbPd5}3t(e+ZEN^(v51&x)v!%k)jC z6PMOuWHsF1!BwfFFQ$9EK?)=MK$){NsK?sb!O|ae0Gv1<6}0jVYO;?Sbg;Z39FQb2 z<}9rRC{rf6>DYZ8Da$u;xX|=(k>zot_b5dHdcvi`xCwaUrV$ekf=Y;D2%SO;3nm>pmMac+cR6Bhb{0~alpDFEzw6(Od*A`qXnEaFL4%m z0F180c8`cwnGTm(lvM*Ce^bQg(HDc2bW-BQ6iu-;YV^=fedVW!3EswsHcqLHl5odO|El%apBO2WvE3#(uFH`0C7gw>W@1huTYl zjz>J(CRP+^B4Itt%u!_rWjmORf|@jT znKsGRIzbSeaTZtKSffe+D%A22oZ)I~-91}ZWgMlk7Y$c>Iw||}ZTk`tC4|oHb+ku5 zW2cYB^<@IR6s0}KoZw>$IzqO(lDS6SUw-NgDte8vLCtT=RZAKtX&v2CFJq)5B!@^H}=g)pjX^ zZ}(QWCHEgNLrK4rdj*A8)BdA`5LzSK)u!>nrnmtC4z$=@80+;%0+2ORa|S3D-~Z?rqc^;W`}s0n#b@$vG2)!l*}*eo!5#a z@;JdnQb7;J=a>o7k=34z9xXnVC>UJfkbtUgv3uQbx{T(@Q8Imi$A{GJcQb@oWlw=2 zxQHob|P!KVNoBPlC*&e1*&9^s7Z7mFsb{0-09X z`;UQ}O0i_KSpUL8gjL=;__I58^JTLH|NMU%7BF>`cC5*^ppk7=dJS09_~MwWDat=D z2{$%y4bXZ6no_RQX3nF`d=T%S_fJrx-v_c=myXy&+lB>+o?&``kTtk|{v;{JKUfS;@9itZwNz@J^1_rtgd%GTG)9?47SwDoZ} zbvqaX?_T*=@(?cmrxc3wf9yj>Svml+?O<2KbC?#`-Fe5@mqMUzJt<8CmCd4#DE5u| zQYEw?@r!xb`8R?DHaX@|iO5RY1saZVxsG<|b9ofohV>OzLin1YXP)Xhly6Id>>k2{ z{;@oMjr)ydl!D_2-=?vwBrxk#d729IM;|IwOJ!`+#6q+%pi~PQ7C~epxPIwty1r!7 zEXrzoYD506qYa1+(lF?d^`%AC>ibf&JJ_y#Zv`gf56n$s^`nZ1LjIKwPTDDw+G&i)B5x*+DbxCJ+ zavd=1B7hEF6I>H+J&OX#tFeNMK;Lvouku*xOwX5$te#tP*PAXHlxpua1qN@MV@sqF zLFhXDpW*FU#hzXTFsScTA1@bqplXq6R#VjzyOADzo>H}Sv>0cBXGy;WLp5e(WL!nSuy5Cy~1Dj$Fu_3w0 z?6KW*u)XF&WcMmolaWAYKCpp;3`Z%TE2e&N2HJZHpw$IZ$-3?-2@3DsYR^{$bXBpT za*3$Q*gzv~0H^Ysm9JnS1z`f>FKg}iJxhzK+Zz+~sE}0_Wu3ac9dnNS?R^$dF73fl z`opH~kGtT{C4KYs7m4e?5dc-=*MZ*`p*umSP0d*`(bw?)H7et zvJ*(>KE5w#lPsJJ|12aXL0rGK7xw~cu$;awdfQ}nx0YW$s5?t69<`ET`Wje|b6)Xg zOpOTrxY*p0_&{UHd!Qv2!_p11XW;2em(k>Yp6{FI4<5F2_vo ZM3&^De%U;h1BW2|ohtAXB%`7c(h^5y^l literal 7656 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 J%H_1}{{dRE*dG7@ diff --git a/openpype/tools/new_publisher/widgets/images/stop.png b/openpype/tools/new_publisher/widgets/images/stop.png index 319e014e3166001e5c4a329c8b2433763fccbbe4..eda18d1db1ee550bb84fa7800d2c735597b5c5f8 100644 GIT binary patch literal 4064 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
f^)tEn-=sh?&BR2R;6j84wcC#$k#5bK!P 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 Q0R$bicR5h}gKyG*0jvjrKL7v# From 54da00907743a407c2c9b293fc51b04e1e0728c3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 8 Sep 2021 18:49:47 +0200 Subject: [PATCH 463/736] use new btns in publish widget --- .../new_publisher/widgets/publish_widget.py | 23 ++++++++----------- .../tools/new_publisher/widgets/widgets.py | 21 ++++++++--------- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/publish_widget.py b/openpype/tools/new_publisher/widgets/publish_widget.py index 1d4375a57f..14d50a2777 100644 --- a/openpype/tools/new_publisher/widgets/publish_widget.py +++ b/openpype/tools/new_publisher/widgets/publish_widget.py @@ -4,10 +4,14 @@ from Qt import QtWidgets, QtCore, QtGui from openpype.pipeline import KnownPublishError -from .icons import get_icon from .validations_widget import ValidationsWidget from ..publish_report_viewer import PublishReportViewerWidget -from .widgets import IconBtn +from .widgets import ( + StopBtn, + ResetBtn, + ValidateBtn, + PublishBtn +) class PublishFrame(QtWidgets.QFrame): @@ -61,17 +65,10 @@ class PublishFrame(QtWidgets.QFrame): ) show_details_btn.setVisible(False) - reset_btn = IconBtn(content_widget) - reset_btn.setIcon(get_icon("refresh")) - - stop_btn = IconBtn(content_widget) - stop_btn.setIcon(get_icon("stop")) - - validate_btn = IconBtn(content_widget) - validate_btn.setIcon(get_icon("validate")) - - publish_btn = IconBtn(content_widget) - publish_btn.setIcon(get_icon("play")) + reset_btn = ResetBtn(content_widget) + stop_btn = StopBtn(content_widget) + validate_btn = ValidateBtn(content_widget) + publish_btn = PublishBtn(content_widget) footer_layout = QtWidgets.QHBoxLayout() footer_layout.addWidget(copy_log_btn, 0) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index d704c0e374..3d8a32a5c3 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -22,17 +22,16 @@ class IconBtn(QtWidgets.QPushButton): Using font metrics height as icon size reference. """ - def resizeEvent(self, event): - super(IconBtn, self).resizeEvent(event) - self._icon_size_change() - - def showEvent(self, event): - super(IconBtn, self).showEvent(event) - self._icon_size_change() - - def _icon_size_change(self): - icon_size = self.fontMetrics().height() - self.setIconSize(QtCore.QSize(icon_size, icon_size)) + def sizeHint(self): + result = super().sizeHint() + if not self.text(): + new_height = ( + result.height() + - self.iconSize().height() + + self.fontMetrics().height() + ) + result.setHeight(new_height) + return result class PublishIconBtn(IconBtn): From 81caa48509057589e88a7f44ad68953021709760 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 8 Sep 2021 18:50:11 +0200 Subject: [PATCH 464/736] views and attributes widget are in 3:7 ratio --- openpype/tools/new_publisher/window.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 60c4c3c82f..4cf59f3fde 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -114,8 +114,8 @@ class PublisherWindow(QtWidgets.QDialog): subset_content_widget = QtWidgets.QWidget(subset_frame) subset_content_layout = QtWidgets.QHBoxLayout(subset_content_widget) subset_content_layout.setContentsMargins(0, 0, 0, 0) - subset_content_layout.addWidget(subset_views_widget, 0) - subset_content_layout.addWidget(subset_attributes_wrap, 1) + subset_content_layout.addWidget(subset_views_widget, 3) + subset_content_layout.addWidget(subset_attributes_wrap, 7) # Footer message_input = QtWidgets.QLineEdit(subset_frame) From 8f315b5b2264613fac3ed0558c8e31382e99b7d4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 8 Sep 2021 18:51:15 +0200 Subject: [PATCH 465/736] better icon image ratio --- openpype/tools/new_publisher/widgets/widgets.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 3d8a32a5c3..8ca82dbee9 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -63,17 +63,18 @@ class PublishIconBtn(IconBtn): def paint_image_with_color(image, color): width = image.width() height = image.height() - tenth_w = int(width / 10) - tenth_h = int(height / 10) - tenth_w -= tenth_w % 2 - tenth_h -= tenth_h % 2 + partition = 8 + part_w = int(width / partition) + part_h = int(height / partition) + part_w -= part_w % 2 + part_h -= part_h % 2 scaled_image = image.scaled( - width - (2 * tenth_w), - height - (2 * tenth_h) + width - (2 * part_w), + height - (2 * part_h) ) alpha_mask = scaled_image.createAlphaMask() alpha_region = QtGui.QRegion(QtGui.QBitmap(alpha_mask)) - alpha_region.translate(tenth_w, tenth_h) + alpha_region.translate(part_w, part_h) pixmap = QtGui.QPixmap(width, height) pixmap.fill(QtCore.Qt.transparent) From 1ea9ca1d5e6e981b6fed33bee050a2a3c207a593 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 9 Sep 2021 10:56:55 +0200 Subject: [PATCH 466/736] set AA_EnableHighDpiScaling --- openpype/hosts/testhost/run_publish.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/testhost/run_publish.py b/openpype/hosts/testhost/run_publish.py index f1c624678a..2c4b722376 100644 --- a/openpype/hosts/testhost/run_publish.py +++ b/openpype/hosts/testhost/run_publish.py @@ -40,7 +40,7 @@ for path in [ ]: sys.path.append(path) -from Qt import QtWidgets +from Qt import QtWidgets, QtCore from openpype.tools.new_publisher.window import PublisherWindow @@ -59,6 +59,7 @@ def main(): avalon.api.install(testhost) app = QtWidgets.QApplication([]) + app.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling) window = PublisherWindow() window.show() app.exec_() From 12c59554d0b5b3d380ab5136d0111bf6254fda86 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 9 Sep 2021 10:57:33 +0200 Subject: [PATCH 467/736] define host name --- openpype/hosts/testhost/run_publish.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/testhost/run_publish.py b/openpype/hosts/testhost/run_publish.py index 2c4b722376..21179294e1 100644 --- a/openpype/hosts/testhost/run_publish.py +++ b/openpype/hosts/testhost/run_publish.py @@ -5,11 +5,11 @@ mongo_url = "" project_name = "" asset_name = "" task_name = "" -host_name = "" ftrack_url = "" ftrack_username = "" ftrack_api_key = "" +host_name = "testhost" current_file = os.path.abspath(__file__) From e3d6a98f09f4d82ffaefb52991379c68da6fab27 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 9 Sep 2021 18:23:02 +0200 Subject: [PATCH 468/736] better handling of left and right side of line label widget --- .../widgets/border_label_widget.py | 43 +++++++++++++------ 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/border_label_widget.py b/openpype/tools/new_publisher/widgets/border_label_widget.py index c4be6d1d4e..265fa72223 100644 --- a/openpype/tools/new_publisher/widgets/border_label_widget.py +++ b/openpype/tools/new_publisher/widgets/border_label_widget.py @@ -4,27 +4,32 @@ from openpype.style import get_colors_data class _VLineWidget(QtWidgets.QWidget): - def __init__(self, color, parent): + def __init__(self, color, left, parent): super(_VLineWidget, self).__init__(parent) self._color = color + self._left = left def paintEvent(self, event): if not self.isVisible(): return - rect = QtCore.QRect( - 0, 0, self.width(), self.height() - ) - + if self._left: + pos_x = 0 + else: + pos_x = self.width() painter = QtGui.QPainter(self) painter.setRenderHints( painter.Antialiasing | painter.SmoothPixmapTransform ) - painter.setPen(QtCore.Qt.NoPen) if self._color: - painter.setBrush(self._color) - painter.drawRect(rect) + pen = QtGui.QPen(self._color) + else: + pen = painter.pen() + pen.setWidth(1) + painter.setPen(pen) + painter.setBrush(QtCore.Qt.transparent) + painter.drawLine(pos_x, 0, pos_x, self.height()) painter.end() @@ -126,8 +131,8 @@ class BorderedLabelWidget(QtWidgets.QFrame): top_layout.addWidget(label_widget, 0) top_layout.addWidget(top_right_w, 1) - left_w = _VLineWidget(color, self) - right_w = _VLineWidget(color, self) + left_w = _VLineWidget(color, True, self) + right_w = _VLineWidget(color, False, self) bottom_w = _HLineWidget(color, self) @@ -151,6 +156,8 @@ class BorderedLabelWidget(QtWidgets.QFrame): self._widget = None + self._radius = 0 + self._top_left_w = top_left_w self._top_right_w = top_right_w self._left_w = left_w @@ -159,15 +166,23 @@ class BorderedLabelWidget(QtWidgets.QFrame): self._label_widget = label_widget self._center_layout = center_layout + def set_content_margins(self, value): + self._center_layout.setContentsMargins( + value, value, value, value + ) + def showEvent(self, event): super(BorderedLabelWidget, self).showEvent(event) height = self._label_widget.height() radius = (height + (height % 2)) / 2 - self._left_w.setMinimumWidth(1) - self._left_w.setMaximumWidth(1) - self._right_w.setMinimumWidth(1) - self._right_w.setMaximumWidth(1) + self._radius = radius + + side_width = 1 + radius + self._left_w.setMinimumWidth(side_width) + self._left_w.setMaximumWidth(side_width) + self._right_w.setMinimumWidth(side_width) + self._right_w.setMaximumWidth(side_width) self._bottom_w.setMinimumHeight(radius) self._bottom_w.setMaximumHeight(radius) self._bottom_w.set_radius(radius) From c467a1664e9b3c4cb1e62ea8a2fddc854fdfc960 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 9 Sep 2021 18:23:40 +0200 Subject: [PATCH 469/736] separated card and list instance views --- .../tools/new_publisher/widgets/__init__.py | 5 +- .../widgets/card_view_widgets.py | 271 ++++++++++++++ .../widgets/instance_views_widgets.py | 332 +----------------- .../tools/new_publisher/widgets/widgets.py | 70 ++++ 4 files changed, 347 insertions(+), 331 deletions(-) create mode 100644 openpype/tools/new_publisher/widgets/card_view_widgets.py diff --git a/openpype/tools/new_publisher/widgets/__init__.py b/openpype/tools/new_publisher/widgets/__init__.py index b3f74a7f19..0ad111b76e 100644 --- a/openpype/tools/new_publisher/widgets/__init__.py +++ b/openpype/tools/new_publisher/widgets/__init__.py @@ -20,8 +20,11 @@ from .create_dialog import ( CreateDialog ) +from .card_view_widgets import ( + InstanceCardView +) + from .instance_views_widgets import ( - InstanceCardView, InstanceListView ) diff --git a/openpype/tools/new_publisher/widgets/card_view_widgets.py b/openpype/tools/new_publisher/widgets/card_view_widgets.py new file mode 100644 index 0000000000..7761c897f4 --- /dev/null +++ b/openpype/tools/new_publisher/widgets/card_view_widgets.py @@ -0,0 +1,271 @@ +from Qt import QtWidgets, QtCore + +from openpype.widgets.nice_checkbox import NiceCheckbox + +from .widgets import ( + AbstractInstanceView, + ContextWarningLabel +) +from ..constants import ( + INSTANCE_ID_ROLE, + CONTEXT_ID, + CONTEXT_LABEL +) + + +class ContextCardWidget(QtWidgets.QWidget): + def __init__(self, item, parent): + super(ContextCardWidget, self).__init__(parent) + + self.item = item + + subset_name_label = QtWidgets.QLabel(CONTEXT_LABEL, self) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(2, 2, 2, 2) + layout.addWidget(subset_name_label) + layout.addStretch(1) + + self.setAttribute(QtCore.Qt.WA_TranslucentBackground) + subset_name_label.setAttribute(QtCore.Qt.WA_TranslucentBackground) + + self.subset_name_label = subset_name_label + + def showEvent(self, event): + super(ContextCardWidget, self).showEvent(event) + self.item.setSizeHint(self.sizeHint()) + + +class InstanceCardWidget(QtWidgets.QWidget): + active_changed = QtCore.Signal(str, bool) + + def __init__(self, instance, item, parent): + super(InstanceCardWidget, self).__init__(parent) + + self.instance = instance + self.item = item + + subset_name_label = QtWidgets.QLabel(instance.data["subset"], self) + active_checkbox = NiceCheckbox(parent=self) + active_checkbox.setChecked(instance.data["active"]) + + context_warning = ContextWarningLabel(self) + if instance.has_valid_context: + context_warning.setVisible(False) + + expand_btn = QtWidgets.QToolButton(self) + expand_btn.setObjectName("ArrowBtn") + expand_btn.setArrowType(QtCore.Qt.DownArrow) + expand_btn.setMaximumWidth(14) + expand_btn.setEnabled(False) + + detail_widget = QtWidgets.QWidget(self) + detail_widget.setVisible(False) + self.detail_widget = detail_widget + + top_layout = QtWidgets.QHBoxLayout() + top_layout.addWidget(subset_name_label) + top_layout.addStretch(1) + top_layout.addWidget(context_warning) + top_layout.addWidget(active_checkbox) + top_layout.addWidget(expand_btn) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(2, 2, 2, 2) + layout.addLayout(top_layout) + layout.addWidget(detail_widget) + + self.setAttribute(QtCore.Qt.WA_TranslucentBackground) + subset_name_label.setAttribute(QtCore.Qt.WA_TranslucentBackground) + active_checkbox.setAttribute(QtCore.Qt.WA_TranslucentBackground) + expand_btn.setAttribute(QtCore.Qt.WA_TranslucentBackground) + + active_checkbox.stateChanged.connect(self._on_active_change) + expand_btn.clicked.connect(self._on_expend_clicked) + + self.subset_name_label = subset_name_label + self.context_warning = context_warning + self.active_checkbox = active_checkbox + self.expand_btn = expand_btn + + def set_active(self, new_value): + checkbox_value = self.active_checkbox.isChecked() + instance_value = self.instance.data["active"] + + # First change instance value and them change checkbox + # - prevent to trigger `active_changed` signal + if instance_value != new_value: + self.instance.data["active"] = new_value + + if checkbox_value != new_value: + self.active_checkbox.setChecked(new_value) + + def update_instance(self, instance): + self.instance = instance + self.update_instance_values() + + def update_instance_values(self): + self.set_active(self.instance.data["active"]) + self.context_warning.setVisible(not self.instance.has_valid_context) + + def _set_expanded(self, expanded=None): + if expanded is None: + expanded = not self.detail_widget.isVisible() + self.detail_widget.setVisible(expanded) + self.item.setSizeHint(self.sizeHint()) + + def showEvent(self, event): + super(InstanceCardWidget, self).showEvent(event) + self.item.setSizeHint(self.sizeHint()) + + def _on_active_change(self): + new_value = self.active_checkbox.isChecked() + old_value = self.instance.data["active"] + if new_value == old_value: + return + + self.instance.data["active"] = new_value + self.active_changed.emit(self.instance.data["uuid"], new_value) + + def _on_expend_clicked(self): + self._set_expanded() + + +class InstanceCardView(AbstractInstanceView): + def __init__(self, controller, parent): + super(InstanceCardView, self).__init__(parent) + + self.controller = controller + + list_widget = QtWidgets.QListWidget(self) + list_widget.setSelectionMode( + QtWidgets.QAbstractItemView.ExtendedSelection + ) + list_widget.setResizeMode(QtWidgets.QListView.Adjust) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(list_widget, 1) + + list_widget.selectionModel().selectionChanged.connect( + self._on_selection_change + ) + + self._items_by_id = {} + self._widgets_by_id = {} + + self._context_widget = None + self._context_item = None + + self.list_widget = list_widget + + def refresh(self): + instances_by_id = {} + for instance in self.controller.instances: + instance_id = instance.data["uuid"] + instances_by_id[instance_id] = instance + + if not self._context_item: + item = QtWidgets.QListWidgetItem(self.list_widget) + item.setData(INSTANCE_ID_ROLE, CONTEXT_ID) + + widget = ContextCardWidget(item, self) + self.list_widget.addItem(item) + self.list_widget.setItemWidget(item, widget) + + self._context_item = item + self._context_widget = widget + + for instance_id in tuple(self._items_by_id.keys()): + if instance_id not in instances_by_id: + item = self._items_by_id.pop(instance_id) + self.list_widget.removeItemWidget(item) + widget = self._widgets_by_id.pop(instance_id) + widget.deleteLater() + row = self.list_widget.row(item) + self.list_widget.takeItem(row) + + for instance_id, instance in instances_by_id.items(): + if instance_id in self._items_by_id: + widget = self._widgets_by_id[instance_id] + widget.update_instance(instance) + + else: + item = QtWidgets.QListWidgetItem(self.list_widget) + widget = InstanceCardWidget(instance, item, self) + widget.active_changed.connect(self._on_active_changed) + item.setData(INSTANCE_ID_ROLE, instance_id) + self.list_widget.addItem(item) + self.list_widget.setItemWidget(item, widget) + self._items_by_id[instance_id] = item + self._widgets_by_id[instance_id] = widget + + def refresh_instance_states(self): + for widget in self._widgets_by_id.values(): + widget.update_instance_values() + + def _on_active_changed(self, changed_instance_id, new_value): + selected_ids = set() + found = False + for item in self.list_widget.selectedItems(): + instance_id = item.data(INSTANCE_ID_ROLE) + selected_ids.add(instance_id) + if not found and instance_id == changed_instance_id: + found = True + + if not found: + selected_ids = set() + selected_ids.add(changed_instance_id) + + changed_ids = set() + for instance_id in selected_ids: + widget = self._widgets_by_id.get(instance_id) + if widget: + changed_ids.add(instance_id) + widget.set_active(new_value) + + if changed_ids: + self.active_changed.emit(changed_ids) + + def _on_selection_change(self, *_args): + self.selection_changed.emit() + + def get_selected_items(self): + instances = [] + context_selected = False + for item in self.list_widget.selectedItems(): + instance_id = item.data(INSTANCE_ID_ROLE) + if instance_id == CONTEXT_ID: + context_selected = True + else: + widget = self._widgets_by_id.get(instance_id) + if widget: + instances.append(widget.instance) + + return instances, context_selected + + def set_selected_items(self, instances, context_selected): + indexes = [] + model = self.list_widget.model() + if context_selected and self._context_item is not None: + row = self.list_widget.row(self._context_item) + index = model.index(row, 0) + indexes.append(index) + + for instance in instances: + instance_id = instance.data["uuid"] + item = self._items_by_id.get(instance_id) + if item: + row = self.list_widget.row(item) + index = model.index(row, 0) + indexes.append(index) + + selection_model = self.list_widget.selectionModel() + first_item = True + for index in indexes: + if first_item: + first_item = False + select_type = QtCore.QItemSelectionModel.ClearAndSelect + else: + select_type = QtCore.QItemSelectionModel.Select + selection_model.select(index, select_type) diff --git a/openpype/tools/new_publisher/widgets/instance_views_widgets.py b/openpype/tools/new_publisher/widgets/instance_views_widgets.py index ab713bfe36..72b0d0fc28 100644 --- a/openpype/tools/new_publisher/widgets/instance_views_widgets.py +++ b/openpype/tools/new_publisher/widgets/instance_views_widgets.py @@ -3,341 +3,13 @@ import collections from Qt import QtWidgets, QtCore, QtGui from openpype.widgets.nice_checkbox import NiceCheckbox +from .widgets import AbstractInstanceView from ..constants import ( INSTANCE_ID_ROLE, SORT_VALUE_ROLE, CONTEXT_ID, CONTEXT_LABEL ) -from .icons import get_pixmap - - -class ContextWarningLabel(QtWidgets.QLabel): - cached_images_by_size = {} - source_image = None - - def __init__(self, parent): - super(ContextWarningLabel, self).__init__(parent) - self.setToolTip( - "Contain invalid context. Please check details." - ) - - self._image = None - if self.__class__.source_image is None: - self.__class__.source_image = get_pixmap("warning") - - @classmethod - def _get_pixmap_by_size(cls, size): - image = cls.cached_images_by_size.get(size) - if image is not None: - return image - - margins = int(size / 8) - margins_double = margins * 2 - pix = QtGui.QPixmap(size, size) - pix.fill(QtCore.Qt.transparent) - - scaled_image = cls.source_image.scaled( - size - margins_double, size - margins_double, - QtCore.Qt.KeepAspectRatio, - QtCore.Qt.SmoothTransformation - ) - painter = QtGui.QPainter(pix) - painter.setRenderHints( - painter.Antialiasing | painter.SmoothPixmapTransform - ) - painter.drawPixmap(margins, margins, scaled_image) - painter.end() - - return pix - - def showEvent(self, event): - super(ContextWarningLabel, self).showEvent(event) - if self._image is None: - self._image = self._get_pixmap_by_size(self.height()) - self.setPixmap(self._image) - - -class InstanceCardWidget(QtWidgets.QWidget): - active_changed = QtCore.Signal(str, bool) - - def __init__(self, instance, item, parent): - super(InstanceCardWidget, self).__init__(parent) - - self.instance = instance - self.item = item - - subset_name_label = QtWidgets.QLabel(instance.data["subset"], self) - active_checkbox = NiceCheckbox(parent=self) - active_checkbox.setChecked(instance.data["active"]) - - context_warning = ContextWarningLabel(self) - if instance.has_valid_context: - context_warning.setVisible(False) - - expand_btn = QtWidgets.QToolButton(self) - expand_btn.setObjectName("ArrowBtn") - expand_btn.setArrowType(QtCore.Qt.DownArrow) - expand_btn.setMaximumWidth(14) - expand_btn.setEnabled(False) - - detail_widget = QtWidgets.QWidget(self) - detail_widget.setVisible(False) - self.detail_widget = detail_widget - - top_layout = QtWidgets.QHBoxLayout() - top_layout.addWidget(subset_name_label) - top_layout.addStretch(1) - top_layout.addWidget(context_warning) - top_layout.addWidget(active_checkbox) - top_layout.addWidget(expand_btn) - - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(2, 2, 2, 2) - layout.addLayout(top_layout) - layout.addWidget(detail_widget) - - self.setAttribute(QtCore.Qt.WA_TranslucentBackground) - subset_name_label.setAttribute(QtCore.Qt.WA_TranslucentBackground) - active_checkbox.setAttribute(QtCore.Qt.WA_TranslucentBackground) - expand_btn.setAttribute(QtCore.Qt.WA_TranslucentBackground) - - active_checkbox.stateChanged.connect(self._on_active_change) - expand_btn.clicked.connect(self._on_expend_clicked) - - self.subset_name_label = subset_name_label - self.context_warning = context_warning - self.active_checkbox = active_checkbox - self.expand_btn = expand_btn - - def set_active(self, new_value): - checkbox_value = self.active_checkbox.isChecked() - instance_value = self.instance.data["active"] - - # First change instance value and them change checkbox - # - prevent to trigger `active_changed` signal - if instance_value != new_value: - self.instance.data["active"] = new_value - - if checkbox_value != new_value: - self.active_checkbox.setChecked(new_value) - - def update_instance(self, instance): - self.instance = instance - self.update_instance_values() - - def update_instance_values(self): - self.set_active(self.instance.data["active"]) - self.context_warning.setVisible(not self.instance.has_valid_context) - - def _set_expanded(self, expanded=None): - if expanded is None: - expanded = not self.detail_widget.isVisible() - self.detail_widget.setVisible(expanded) - self.item.setSizeHint(self.sizeHint()) - - def showEvent(self, event): - super(InstanceCardWidget, self).showEvent(event) - self.item.setSizeHint(self.sizeHint()) - - def _on_active_change(self): - new_value = self.active_checkbox.isChecked() - old_value = self.instance.data["active"] - if new_value == old_value: - return - - self.instance.data["active"] = new_value - self.active_changed.emit(self.instance.data["uuid"], new_value) - - def _on_expend_clicked(self): - self._set_expanded() - - -class ContextCardWidget(QtWidgets.QWidget): - def __init__(self, item, parent): - super(ContextCardWidget, self).__init__(parent) - - self.item = item - - subset_name_label = QtWidgets.QLabel(CONTEXT_LABEL, self) - - layout = QtWidgets.QHBoxLayout(self) - layout.setContentsMargins(2, 2, 2, 2) - layout.addWidget(subset_name_label) - layout.addStretch(1) - - self.setAttribute(QtCore.Qt.WA_TranslucentBackground) - subset_name_label.setAttribute(QtCore.Qt.WA_TranslucentBackground) - - self.subset_name_label = subset_name_label - - def showEvent(self, event): - super(ContextCardWidget, self).showEvent(event) - self.item.setSizeHint(self.sizeHint()) - - -class _AbstractInstanceView(QtWidgets.QWidget): - selection_changed = QtCore.Signal() - active_changed = QtCore.Signal(set) - refreshed = False - - def set_refreshed(self, refreshed): - self.refreshed = refreshed - - def refresh(self): - raise NotImplementedError(( - "{} Method 'refresh' is not implemented." - ).format(self.__class__.__name__)) - - def get_selected_items(self): - raise NotImplementedError(( - "{} Method 'get_selected_items' is not implemented." - ).format(self.__class__.__name__)) - - def set_selected_items(self, instances, context_selected): - raise NotImplementedError(( - "{} Method 'set_selected_items' is not implemented." - ).format(self.__class__.__name__)) - - -class InstanceCardView(_AbstractInstanceView): - def __init__(self, controller, parent): - super(InstanceCardView, self).__init__(parent) - - self.controller = controller - - list_widget = QtWidgets.QListWidget(self) - list_widget.setSelectionMode( - QtWidgets.QAbstractItemView.ExtendedSelection - ) - list_widget.setResizeMode(QtWidgets.QListView.Adjust) - - layout = QtWidgets.QHBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.addWidget(list_widget, 1) - - list_widget.selectionModel().selectionChanged.connect( - self._on_selection_change - ) - - self._items_by_id = {} - self._widgets_by_id = {} - - self._context_widget = None - self._context_item = None - - self.list_widget = list_widget - - def refresh(self): - instances_by_id = {} - for instance in self.controller.instances: - instance_id = instance.data["uuid"] - instances_by_id[instance_id] = instance - - if not self._context_item: - item = QtWidgets.QListWidgetItem(self.list_widget) - item.setData(INSTANCE_ID_ROLE, CONTEXT_ID) - - widget = ContextCardWidget(item, self) - self.list_widget.addItem(item) - self.list_widget.setItemWidget(item, widget) - - self._context_item = item - self._context_widget = widget - - for instance_id in tuple(self._items_by_id.keys()): - if instance_id not in instances_by_id: - item = self._items_by_id.pop(instance_id) - self.list_widget.removeItemWidget(item) - widget = self._widgets_by_id.pop(instance_id) - widget.deleteLater() - row = self.list_widget.row(item) - self.list_widget.takeItem(row) - - for instance_id, instance in instances_by_id.items(): - if instance_id in self._items_by_id: - widget = self._widgets_by_id[instance_id] - widget.update_instance(instance) - - else: - item = QtWidgets.QListWidgetItem(self.list_widget) - widget = InstanceCardWidget(instance, item, self) - widget.active_changed.connect(self._on_active_changed) - item.setData(INSTANCE_ID_ROLE, instance_id) - self.list_widget.addItem(item) - self.list_widget.setItemWidget(item, widget) - self._items_by_id[instance_id] = item - self._widgets_by_id[instance_id] = widget - - def refresh_instance_states(self): - for widget in self._widgets_by_id.values(): - widget.update_instance_values() - - def _on_active_changed(self, changed_instance_id, new_value): - selected_ids = set() - found = False - for item in self.list_widget.selectedItems(): - instance_id = item.data(INSTANCE_ID_ROLE) - selected_ids.add(instance_id) - if not found and instance_id == changed_instance_id: - found = True - - if not found: - selected_ids = set() - selected_ids.add(changed_instance_id) - - changed_ids = set() - for instance_id in selected_ids: - widget = self._widgets_by_id.get(instance_id) - if widget: - changed_ids.add(instance_id) - widget.set_active(new_value) - - if changed_ids: - self.active_changed.emit(changed_ids) - - def _on_selection_change(self, *_args): - self.selection_changed.emit() - - def get_selected_items(self): - instances = [] - context_selected = False - for item in self.list_widget.selectedItems(): - instance_id = item.data(INSTANCE_ID_ROLE) - if instance_id == CONTEXT_ID: - context_selected = True - else: - widget = self._widgets_by_id.get(instance_id) - if widget: - instances.append(widget.instance) - - return instances, context_selected - - def set_selected_items(self, instances, context_selected): - indexes = [] - model = self.list_widget.model() - if context_selected and self._context_item is not None: - row = self.list_widget.row(self._context_item) - index = model.index(row, 0) - indexes.append(index) - - for instance in instances: - instance_id = instance.data["uuid"] - item = self._items_by_id.get(instance_id) - if item: - row = self.list_widget.row(item) - index = model.index(row, 0) - indexes.append(index) - - selection_model = self.list_widget.selectionModel() - first_item = True - for index in indexes: - if first_item: - first_item = False - select_type = QtCore.QItemSelectionModel.ClearAndSelect - else: - select_type = QtCore.QItemSelectionModel.Select - selection_model.select(index, select_type) class InstanceListItemWidget(QtWidgets.QWidget): @@ -462,7 +134,7 @@ class InstanceListGroupWidget(QtWidgets.QFrame): self.expand_btn.setArrowType(QtCore.Qt.RightArrow) -class InstanceListView(_AbstractInstanceView): +class InstanceListView(AbstractInstanceView): def __init__(self, controller, parent): super(InstanceListView, self).__init__(parent) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 8ca82dbee9..39c2255fba 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -124,6 +124,76 @@ class PublishBtn(PublishIconBtn): self.setToolTip("Publish") +class AbstractInstanceView(QtWidgets.QWidget): + selection_changed = QtCore.Signal() + active_changed = QtCore.Signal(set) + refreshed = False + + def set_refreshed(self, refreshed): + self.refreshed = refreshed + + def refresh(self): + raise NotImplementedError(( + "{} Method 'refresh' is not implemented." + ).format(self.__class__.__name__)) + + def get_selected_items(self): + raise NotImplementedError(( + "{} Method 'get_selected_items' is not implemented." + ).format(self.__class__.__name__)) + + def set_selected_items(self, instances, context_selected): + raise NotImplementedError(( + "{} Method 'set_selected_items' is not implemented." + ).format(self.__class__.__name__)) + + +class ContextWarningLabel(QtWidgets.QLabel): + cached_images_by_size = {} + source_image = None + + def __init__(self, parent): + super(ContextWarningLabel, self).__init__(parent) + self.setToolTip( + "Contain invalid context. Please check details." + ) + + self._image = None + if self.__class__.source_image is None: + self.__class__.source_image = get_pixmap("warning") + + @classmethod + def _get_pixmap_by_size(cls, size): + image = cls.cached_images_by_size.get(size) + if image is not None: + return image + + margins = int(size / 8) + margins_double = margins * 2 + pix = QtGui.QPixmap(size, size) + pix.fill(QtCore.Qt.transparent) + + scaled_image = cls.source_image.scaled( + size - margins_double, size - margins_double, + QtCore.Qt.KeepAspectRatio, + QtCore.Qt.SmoothTransformation + ) + painter = QtGui.QPainter(pix) + painter.setRenderHints( + painter.Antialiasing | painter.SmoothPixmapTransform + ) + painter.drawPixmap(margins, margins, scaled_image) + painter.end() + + return pix + + def showEvent(self, event): + super(ContextWarningLabel, self).showEvent(event) + if self._image is None: + self._image = self._get_pixmap_by_size(self.height()) + self.setPixmap(self._image) + + class AssetsHierarchyModel(QtGui.QStandardItemModel): def __init__(self, controller): super(AssetsHierarchyModel, self).__init__() From 9d6d131ffa1ac0372c8c4b749375f453155b9bdc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 9 Sep 2021 18:24:11 +0200 Subject: [PATCH 470/736] renamed instance_views_widget to list_view_widgets --- .../widgets/{instance_views_widgets.py => list_view_widgets.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename openpype/tools/new_publisher/widgets/{instance_views_widgets.py => list_view_widgets.py} (100%) diff --git a/openpype/tools/new_publisher/widgets/instance_views_widgets.py b/openpype/tools/new_publisher/widgets/list_view_widgets.py similarity index 100% rename from openpype/tools/new_publisher/widgets/instance_views_widgets.py rename to openpype/tools/new_publisher/widgets/list_view_widgets.py From 315eec99ba50e2d822f01422effc76a69400d9dc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 9 Sep 2021 19:28:10 +0200 Subject: [PATCH 471/736] fix import --- openpype/tools/new_publisher/widgets/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/widgets/__init__.py b/openpype/tools/new_publisher/widgets/__init__.py index 0ad111b76e..6d0c6f724c 100644 --- a/openpype/tools/new_publisher/widgets/__init__.py +++ b/openpype/tools/new_publisher/widgets/__init__.py @@ -24,7 +24,7 @@ from .card_view_widgets import ( InstanceCardView ) -from .instance_views_widgets import ( +from .list_view_widgets import ( InstanceListView ) From 8124eed5f78ed4ffb81dc2338fc8779be76e1381 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 9 Sep 2021 19:28:34 +0200 Subject: [PATCH 472/736] ClickableFrame is in widgets --- .../widgets/validations_widget.py | 25 ++----------------- .../tools/new_publisher/widgets/widgets.py | 23 +++++++++++++++++ 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/validations_widget.py b/openpype/tools/new_publisher/widgets/validations_widget.py index a3136e7a31..08b4039a81 100644 --- a/openpype/tools/new_publisher/widgets/validations_widget.py +++ b/openpype/tools/new_publisher/widgets/validations_widget.py @@ -5,28 +5,7 @@ except Exception: from Qt import QtWidgets, QtCore, QtGui - -class _ClickableFrame(QtWidgets.QFrame): - def __init__(self, parent): - super(_ClickableFrame, self).__init__(parent) - - self._mouse_pressed = False - - def _mouse_release_callback(self): - pass - - def mousePressEvent(self, event): - if event.button() == QtCore.Qt.LeftButton: - self._mouse_pressed = True - super(_ClickableFrame, self).mousePressEvent(event) - - def mouseReleaseEvent(self, event): - if self._mouse_pressed: - self._mouse_pressed = False - if self.rect().contains(event.pos()): - self._mouse_release_callback() - - super(_ClickableFrame, self).mouseReleaseEvent(event) +from .widgets import ClickableFrame class ValidationErrorInstanceList(QtWidgets.QListView): @@ -60,7 +39,7 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget): self._error_info = error_info self._selected = False - title_frame = _ClickableFrame(self) + title_frame = ClickableFrame(self) title_frame.setObjectName("ValidationErrorTitleFrame") title_frame._mouse_release_callback = self._mouse_release_callback diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 39c2255fba..d246d6d73a 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -148,6 +148,29 @@ class AbstractInstanceView(QtWidgets.QWidget): ).format(self.__class__.__name__)) +class ClickableFrame(QtWidgets.QFrame): + def __init__(self, parent): + super(ClickableFrame, self).__init__(parent) + + self._mouse_pressed = False + + def _mouse_release_callback(self): + pass + + def mousePressEvent(self, event): + if event.button() == QtCore.Qt.LeftButton: + self._mouse_pressed = True + super(ClickableFrame, self).mousePressEvent(event) + + def mouseReleaseEvent(self, event): + if self._mouse_pressed: + self._mouse_pressed = False + if self.rect().contains(event.pos()): + self._mouse_release_callback() + + super(ClickableFrame, self).mouseReleaseEvent(event) + + class ContextWarningLabel(QtWidgets.QLabel): cached_images_by_size = {} source_image = None From dc653ce73d900d4025176c2ce481183b6ba90d31 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 9 Sep 2021 19:29:47 +0200 Subject: [PATCH 473/736] initial card view modification --- openpype/style/style.css | 8 + .../widgets/card_view_widgets.py | 262 +++++++++--------- 2 files changed, 143 insertions(+), 127 deletions(-) diff --git a/openpype/style/style.css b/openpype/style/style.css index 48e60f9c31..a7a4e381e1 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -682,6 +682,14 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background: transparent; } +#CardViewWidget { + background: {color:bg-buttons}; + border-radius: 0.1em; +} +#CardViewWidget[state="selected"] { + background: {color:bg-inputs}; +} + #PublishFrame { background: rgba(0, 0, 0, 127); } diff --git a/openpype/tools/new_publisher/widgets/card_view_widgets.py b/openpype/tools/new_publisher/widgets/card_view_widgets.py index 7761c897f4..36e8891fca 100644 --- a/openpype/tools/new_publisher/widgets/card_view_widgets.py +++ b/openpype/tools/new_publisher/widgets/card_view_widgets.py @@ -4,7 +4,8 @@ from openpype.widgets.nice_checkbox import NiceCheckbox from .widgets import ( AbstractInstanceView, - ContextWarningLabel + ContextWarningLabel, + ClickableFrame ) from ..constants import ( INSTANCE_ID_ROLE, @@ -13,39 +14,88 @@ from ..constants import ( ) -class ContextCardWidget(QtWidgets.QWidget): - def __init__(self, item, parent): - super(ContextCardWidget, self).__init__(parent) +class FamilyLabel(QtWidgets.QWidget): + def __init__(self, family, parent): + super(FamilyLabel, self).__init__(parent) - self.item = item + label_widget = QtWidgets.QLabel(family, self) - subset_name_label = QtWidgets.QLabel(CONTEXT_LABEL, self) + line_widget = QtWidgets.QWidget(self) + line_widget.setObjectName("Separator") + line_widget.setMinimumHeight(1) + line_widget.setMaximumHeight(1) layout = QtWidgets.QHBoxLayout(self) - layout.setContentsMargins(2, 2, 2, 2) - layout.addWidget(subset_name_label) - layout.addStretch(1) + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(label_widget, 0) + layout.addWidget(line_widget, 1) - self.setAttribute(QtCore.Qt.WA_TranslucentBackground) - subset_name_label.setAttribute(QtCore.Qt.WA_TranslucentBackground) - - self.subset_name_label = subset_name_label - - def showEvent(self, event): - super(ContextCardWidget, self).showEvent(event) - self.item.setSizeHint(self.sizeHint()) + self._label_widget = label_widget -class InstanceCardWidget(QtWidgets.QWidget): +class CardWidget(ClickableFrame): + selected = QtCore.Signal(str) + + def __init__(self, parent): + super(CardWidget, self).__init__(parent) + self.setObjectName("CardViewWidget") + + self._selected = False + self._id = None + + def set_selected(self, selected): + if selected == self._selected: + return + self._selected = selected + state = "selected" if selected else "" + self.setProperty("state", state) + self.style().polish(self) + + def _mouse_release_callback(self): + self.selected.emit(self._id) + + +class ContextCardWidget(CardWidget): + def __init__(self, parent): + super(ContextCardWidget, self).__init__(parent) + + self._id = CONTEXT_ID + + icon_widget = QtWidgets.QLabel(self) + + label_widget = QtWidgets.QLabel(CONTEXT_LABEL, self) + + icon_layout = QtWidgets.QHBoxLayout() + icon_layout.setContentsMargins(5, 5, 5, 5) + icon_layout.addWidget(icon_widget) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 5, 10, 5) + layout.addLayout(icon_layout, 0) + layout.addWidget(label_widget, 1) + + self.icon_widget = icon_widget + self.label_widget = label_widget + + +class InstanceCardWidget(CardWidget): active_changed = QtCore.Signal(str, bool) - def __init__(self, instance, item, parent): + def __init__(self, instance, parent): super(InstanceCardWidget, self).__init__(parent) - self.instance = instance - self.item = item + self._id = instance.data["uuid"] + + self.instance = instance + + icon_widget = QtWidgets.QLabel(self) + + icon_layout = QtWidgets.QHBoxLayout() + icon_layout.setContentsMargins(5, 5, 5, 5) + icon_layout.addWidget(icon_widget) + + label_widget = QtWidgets.QLabel(instance.data["subset"], self) - subset_name_label = QtWidgets.QLabel(instance.data["subset"], self) active_checkbox = NiceCheckbox(parent=self) active_checkbox.setChecked(instance.data["active"]) @@ -54,6 +104,8 @@ class InstanceCardWidget(QtWidgets.QWidget): context_warning.setVisible(False) expand_btn = QtWidgets.QToolButton(self) + # Not yet implemented + expand_btn.setVisible(False) expand_btn.setObjectName("ArrowBtn") expand_btn.setArrowType(QtCore.Qt.DownArrow) expand_btn.setMaximumWidth(14) @@ -64,26 +116,25 @@ class InstanceCardWidget(QtWidgets.QWidget): self.detail_widget = detail_widget top_layout = QtWidgets.QHBoxLayout() - top_layout.addWidget(subset_name_label) - top_layout.addStretch(1) - top_layout.addWidget(context_warning) - top_layout.addWidget(active_checkbox) - top_layout.addWidget(expand_btn) + top_layout.addLayout(icon_layout, 0) + top_layout.addWidget(label_widget, 1) + top_layout.addWidget(context_warning, 0) + top_layout.addWidget(active_checkbox, 0) + top_layout.addWidget(expand_btn, 0) - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(2, 2, 2, 2) + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 5, 10, 5) layout.addLayout(top_layout) layout.addWidget(detail_widget) - self.setAttribute(QtCore.Qt.WA_TranslucentBackground) - subset_name_label.setAttribute(QtCore.Qt.WA_TranslucentBackground) active_checkbox.setAttribute(QtCore.Qt.WA_TranslucentBackground) expand_btn.setAttribute(QtCore.Qt.WA_TranslucentBackground) active_checkbox.stateChanged.connect(self._on_active_change) expand_btn.clicked.connect(self._on_expend_clicked) - self.subset_name_label = subset_name_label + self.icon_widget = icon_widget + self.label_widget = label_widget self.context_warning = context_warning self.active_checkbox = active_checkbox self.expand_btn = expand_btn @@ -112,11 +163,6 @@ class InstanceCardWidget(QtWidgets.QWidget): if expanded is None: expanded = not self.detail_widget.isVisible() self.detail_widget.setVisible(expanded) - self.item.setSizeHint(self.sizeHint()) - - def showEvent(self, event): - super(InstanceCardWidget, self).showEvent(event) - self.item.setSizeHint(self.sizeHint()) def _on_active_change(self): new_value = self.active_checkbox.isChecked() @@ -137,27 +183,17 @@ class InstanceCardView(AbstractInstanceView): self.controller = controller - list_widget = QtWidgets.QListWidget(self) - list_widget.setSelectionMode( - QtWidgets.QAbstractItemView.ExtendedSelection - ) - list_widget.setResizeMode(QtWidgets.QListView.Adjust) - - layout = QtWidgets.QHBoxLayout(self) + layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) - layout.addWidget(list_widget, 1) + layout.addStretch(1) - list_widget.selectionModel().selectionChanged.connect( - self._on_selection_change - ) + self._content_layout = layout - self._items_by_id = {} self._widgets_by_id = {} - + self._family_widgets_by_name = {} self._context_widget = None - self._context_item = None - self.list_widget = list_widget + self._selected_widget = None def refresh(self): instances_by_id = {} @@ -165,107 +201,79 @@ class InstanceCardView(AbstractInstanceView): instance_id = instance.data["uuid"] instances_by_id[instance_id] = instance - if not self._context_item: - item = QtWidgets.QListWidgetItem(self.list_widget) - item.setData(INSTANCE_ID_ROLE, CONTEXT_ID) - - widget = ContextCardWidget(item, self) - self.list_widget.addItem(item) - self.list_widget.setItemWidget(item, widget) - - self._context_item = item + if not self._context_widget: + widget = ContextCardWidget(self) + widget.selected.connect(self._on_widget_selection) + widget.set_selected(True) + self.selection_changed.emit() + self._content_layout.insertWidget(0, widget) self._context_widget = widget + self._selected_widget = widget - for instance_id in tuple(self._items_by_id.keys()): + for instance_id in tuple(self._widgets_by_id.keys()): if instance_id not in instances_by_id: - item = self._items_by_id.pop(instance_id) - self.list_widget.removeItemWidget(item) widget = self._widgets_by_id.pop(instance_id) + widget.setVisible(False) + self._content_widget.removeWidget(widget) widget.deleteLater() - row = self.list_widget.row(item) - self.list_widget.takeItem(row) for instance_id, instance in instances_by_id.items(): - if instance_id in self._items_by_id: + if instance_id in self._widgets_by_id: widget = self._widgets_by_id[instance_id] widget.update_instance(instance) + continue - else: - item = QtWidgets.QListWidgetItem(self.list_widget) - widget = InstanceCardWidget(instance, item, self) - widget.active_changed.connect(self._on_active_changed) - item.setData(INSTANCE_ID_ROLE, instance_id) - self.list_widget.addItem(item) - self.list_widget.setItemWidget(item, widget) - self._items_by_id[instance_id] = item - self._widgets_by_id[instance_id] = widget + family = instance.data["family"] + if family not in self._family_widgets_by_name: + widget = FamilyLabel(family, self) + self._family_widgets_by_name[family] = widget + pos = self._content_layout.count() - 1 + self._content_layout.insertWidget(pos, widget) + + widget = InstanceCardWidget(instance, self) + widget.selected.connect(self._on_widget_selection) + widget.active_changed.connect(self._on_active_changed) + self._widgets_by_id[instance_id] = widget + pos = self._content_layout.count() - 1 + self._content_layout.insertWidget(pos, widget) def refresh_instance_states(self): for widget in self._widgets_by_id.values(): widget.update_instance_values() def _on_active_changed(self, changed_instance_id, new_value): - selected_ids = set() - found = False - for item in self.list_widget.selectedItems(): - instance_id = item.data(INSTANCE_ID_ROLE) - selected_ids.add(instance_id) - if not found and instance_id == changed_instance_id: - found = True - - if not found: - selected_ids = set() - selected_ids.add(changed_instance_id) - changed_ids = set() - for instance_id in selected_ids: - widget = self._widgets_by_id.get(instance_id) - if widget: - changed_ids.add(instance_id) - widget.set_active(new_value) - if changed_ids: self.active_changed.emit(changed_ids) - def _on_selection_change(self, *_args): + def _on_widget_selection(self, widget_id): + if widget_id == CONTEXT_ID: + new_widget = self._context_widget + else: + new_widget = self._widgets_by_id.get(widget_id) + + if new_widget is self._selected_widget: + return + + if self._selected_widget is not None: + self._selected_widget.set_selected(False) + + self._selected_widget = new_widget + if new_widget is not None: + new_widget.set_selected(True) + self.selection_changed.emit() def get_selected_items(self): instances = [] context_selected = False - for item in self.list_widget.selectedItems(): - instance_id = item.data(INSTANCE_ID_ROLE) - if instance_id == CONTEXT_ID: - context_selected = True - else: - widget = self._widgets_by_id.get(instance_id) - if widget: - instances.append(widget.instance) + if self._selected_widget is self._context_widget: + context_selected = True + + elif self._selected_widget is not None: + instances.append(self._selected_widget.instance) return instances, context_selected def set_selected_items(self, instances, context_selected): - indexes = [] - model = self.list_widget.model() - if context_selected and self._context_item is not None: - row = self.list_widget.row(self._context_item) - index = model.index(row, 0) - indexes.append(index) - - for instance in instances: - instance_id = instance.data["uuid"] - item = self._items_by_id.get(instance_id) - if item: - row = self.list_widget.row(item) - index = model.index(row, 0) - indexes.append(index) - - selection_model = self.list_widget.selectionModel() - first_item = True - for index in indexes: - if first_item: - first_item = False - select_type = QtCore.QItemSelectionModel.ClearAndSelect - else: - select_type = QtCore.QItemSelectionModel.Select - selection_model.select(index, select_type) + pass From 11ef88bb38cc6b8cbf303f1ad0604a2c5c906944 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 10:34:42 +0200 Subject: [PATCH 474/736] change of view does not care about selections --- openpype/tools/new_publisher/window.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 4cf59f3fde..2361219932 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -272,8 +272,6 @@ class PublisherWindow(QtWidgets.QDialog): self._validate_create_instances() def _change_view_type(self): - old_view = self.subset_views_layout.currentWidget() - idx = self.subset_views_layout.currentIndex() new_idx = (idx + 1) % self.subset_views_layout.count() self.subset_views_layout.setCurrentIndex(new_idx) @@ -285,13 +283,7 @@ class PublisherWindow(QtWidgets.QDialog): else: new_view.refresh_instance_states() - if new_view is not old_view: - selected_instances, context_selected = ( - old_view.get_selected_items() - ) - new_view.set_selected_items( - selected_instances, context_selected - ) + self._on_subset_change() def _on_create_clicked(self): self.creator_window.show() From 7a0179d41a3c3dc3acebe13455c0e65efc869656 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 10:37:52 +0200 Subject: [PATCH 475/736] active changed signal does not require value --- openpype/tools/new_publisher/widgets/card_view_widgets.py | 6 ++---- openpype/tools/new_publisher/widgets/list_view_widgets.py | 4 ++-- openpype/tools/new_publisher/widgets/widgets.py | 2 +- openpype/tools/new_publisher/window.py | 2 +- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/card_view_widgets.py b/openpype/tools/new_publisher/widgets/card_view_widgets.py index 36e8891fca..bec28aed4a 100644 --- a/openpype/tools/new_publisher/widgets/card_view_widgets.py +++ b/openpype/tools/new_publisher/widgets/card_view_widgets.py @@ -241,10 +241,8 @@ class InstanceCardView(AbstractInstanceView): for widget in self._widgets_by_id.values(): widget.update_instance_values() - def _on_active_changed(self, changed_instance_id, new_value): - changed_ids = set() - if changed_ids: - self.active_changed.emit(changed_ids) + def _on_active_changed(self): + self.active_changed.emit() def _on_widget_selection(self, widget_id): if widget_id == CONTEXT_ID: diff --git a/openpype/tools/new_publisher/widgets/list_view_widgets.py b/openpype/tools/new_publisher/widgets/list_view_widgets.py index 72b0d0fc28..aefcc97236 100644 --- a/openpype/tools/new_publisher/widgets/list_view_widgets.py +++ b/openpype/tools/new_publisher/widgets/list_view_widgets.py @@ -66,7 +66,7 @@ class InstanceListItemWidget(QtWidgets.QWidget): return self.instance.data["active"] = new_value - self.active_changed.emit(self.instance.data["uuid"], new_value) + self.active_changed.emit(self.instance.id, new_value) class ListContextWidget(QtWidgets.QFrame): @@ -346,7 +346,7 @@ class InstanceListView(AbstractInstanceView): widget.set_active(new_value) if changed_ids: - self.active_changed.emit(changed_ids) + self.active_changed.emit() def get_selected_items(self): instances = [] diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index d246d6d73a..158e69eefa 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -126,7 +126,7 @@ class PublishBtn(PublishIconBtn): class AbstractInstanceView(QtWidgets.QWidget): selection_changed = QtCore.Signal() - active_changed = QtCore.Signal(set) + active_changed = QtCore.Signal() refreshed = False def set_refreshed(self, refreshed): diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 2361219932..faf913d704 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -376,7 +376,7 @@ class PublisherWindow(QtWidgets.QDialog): instances, context_selected ) - def _on_active_changed(self, _instance_ids): + def _on_active_changed(self): if self._refreshing_instances: return self._validate_create_instances() From e2aa7b80b0255e88145e572ce5522986e0fea26a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 10:38:22 +0200 Subject: [PATCH 476/736] better resert of families and instances --- .../widgets/card_view_widgets.py | 142 +++++++++++++----- 1 file changed, 106 insertions(+), 36 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/card_view_widgets.py b/openpype/tools/new_publisher/widgets/card_view_widgets.py index bec28aed4a..c8d831df9c 100644 --- a/openpype/tools/new_publisher/widgets/card_view_widgets.py +++ b/openpype/tools/new_publisher/widgets/card_view_widgets.py @@ -1,3 +1,5 @@ +import collections + from Qt import QtWidgets, QtCore from openpype.widgets.nice_checkbox import NiceCheckbox @@ -33,8 +35,78 @@ class FamilyLabel(QtWidgets.QWidget): self._label_widget = label_widget +class FamilyWidget(QtWidgets.QWidget): + selected = QtCore.Signal(str, str) + active_changed = QtCore.Signal() + + def __init__(self, family, parent): + super(FamilyWidget, self).__init__(parent) + + label_widget = QtWidgets.QLabel(family, self) + + line_widget = QtWidgets.QWidget(self) + line_widget.setObjectName("Separator") + line_widget.setMinimumHeight(2) + line_widget.setMaximumHeight(2) + + label_layout = QtWidgets.QHBoxLayout() + label_layout.setAlignment(QtCore.Qt.AlignVCenter) + label_layout.setSpacing(10) + label_layout.setContentsMargins(0, 0, 0, 0) + label_layout.addWidget(label_widget, 0) + label_layout.addWidget(line_widget, 1) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.addLayout(label_layout, 0) + + self._widgets_by_id = {} + + self._label_widget = label_widget + self._content_layout = layout + + def get_widget_by_instance_id(self, instance_id): + return self._widgets_by_id.get(instance_id) + + def update_instance_values(self): + for widget in self._widgets_by_id.values(): + widget.update_instance_values() + + def update_instances(self, instances): + instances_by_id = {} + instances_by_subset_name = collections.defaultdict(list) + for instance in instances: + instances_by_id[instance.id] = instance + subset_name = instance.data["subset"] + instances_by_subset_name[subset_name].append(instance) + + for instance_id in tuple(self._widgets_by_id.keys()): + if instance_id in instances_by_id: + continue + + widget = self._widgets_by_id.pop(instance_id) + widget.setVisible(False) + self._content_layout.removeWidget(widget) + widget.deleteLater() + + sorted_subset_names = list(sorted(instances_by_subset_name.keys())) + widget_idx = 1 + for subset_names in sorted_subset_names: + for instance in instances_by_subset_name[subset_names]: + if instance.id in self._widgets_by_id: + widget = self._widgets_by_id[instance.id] + widget.update_instance(instance) + else: + widget = InstanceCardWidget(instance, self) + widget.selected.connect(self.selected) + widget.active_changed.connect(self.active_changed) + self._widgets_by_id[instance.id] = widget + self._content_layout.insertWidget(widget_idx, widget) + widget_idx += 1 + + class CardWidget(ClickableFrame): - selected = QtCore.Signal(str) + selected = QtCore.Signal(str, str) def __init__(self, parent): super(CardWidget, self).__init__(parent) @@ -52,7 +124,7 @@ class CardWidget(ClickableFrame): self.style().polish(self) def _mouse_release_callback(self): - self.selected.emit(self._id) + self.selected.emit(self._id, self._family) class ContextCardWidget(CardWidget): @@ -60,6 +132,7 @@ class ContextCardWidget(CardWidget): super(ContextCardWidget, self).__init__(parent) self._id = CONTEXT_ID + self._family = "" icon_widget = QtWidgets.QLabel(self) @@ -79,12 +152,13 @@ class ContextCardWidget(CardWidget): class InstanceCardWidget(CardWidget): - active_changed = QtCore.Signal(str, bool) + active_changed = QtCore.Signal() def __init__(self, instance, parent): super(InstanceCardWidget, self).__init__(parent) - self._id = instance.data["uuid"] + self._id = instance.id + self._family = instance.data["family"] self.instance = instance @@ -171,7 +245,7 @@ class InstanceCardWidget(CardWidget): return self.instance.data["active"] = new_value - self.active_changed.emit(self.instance.data["uuid"], new_value) + self.active_changed.emit() def _on_expend_clicked(self): self._set_expanded() @@ -189,18 +263,13 @@ class InstanceCardView(AbstractInstanceView): self._content_layout = layout - self._widgets_by_id = {} + self._widgets_by_family = {} self._family_widgets_by_name = {} self._context_widget = None self._selected_widget = None def refresh(self): - instances_by_id = {} - for instance in self.controller.instances: - instance_id = instance.data["uuid"] - instances_by_id[instance_id] = instance - if not self._context_widget: widget = ContextCardWidget(self) widget.selected.connect(self._on_widget_selection) @@ -210,45 +279,46 @@ class InstanceCardView(AbstractInstanceView): self._context_widget = widget self._selected_widget = widget - for instance_id in tuple(self._widgets_by_id.keys()): - if instance_id not in instances_by_id: - widget = self._widgets_by_id.pop(instance_id) - widget.setVisible(False) - self._content_widget.removeWidget(widget) - widget.deleteLater() + instances_by_family = collections.defaultdict(list) + for instance in self.controller.instances: + family = instance.data["family"] + instances_by_family[family].append(instance) - for instance_id, instance in instances_by_id.items(): - if instance_id in self._widgets_by_id: - widget = self._widgets_by_id[instance_id] - widget.update_instance(instance) + for family in tuple(self._widgets_by_family.keys()): + if family in instances_by_family: continue - family = instance.data["family"] - if family not in self._family_widgets_by_name: - widget = FamilyLabel(family, self) - self._family_widgets_by_name[family] = widget - pos = self._content_layout.count() - 1 - self._content_layout.insertWidget(pos, widget) + widget = self._widgets_by_family.pop(family) + widget.setVisible(False) + self._content_layout.removeWidget(widget) + widget.deleteLater() - widget = InstanceCardWidget(instance, self) - widget.selected.connect(self._on_widget_selection) - widget.active_changed.connect(self._on_active_changed) - self._widgets_by_id[instance_id] = widget - pos = self._content_layout.count() - 1 - self._content_layout.insertWidget(pos, widget) + sorted_families = list(sorted(instances_by_family.keys())) + widget_idx = 1 + for family in sorted_families: + if family not in self._widgets_by_family: + family_widget = FamilyWidget(family, self) + family_widget.selected.connect(self._on_widget_selection) + self._content_layout.insertWidget(widget_idx, family_widget) + self._widgets_by_family[family] = family_widget + else: + family_widget = self._widgets_by_family[family] + widget_idx += 1 + family_widget.update_instances(instances_by_family[family]) def refresh_instance_states(self): - for widget in self._widgets_by_id.values(): + for widget in self._widgets_by_family.values(): widget.update_instance_values() def _on_active_changed(self): self.active_changed.emit() - def _on_widget_selection(self, widget_id): + def _on_widget_selection(self, widget_id, family): if widget_id == CONTEXT_ID: new_widget = self._context_widget else: - new_widget = self._widgets_by_id.get(widget_id) + family_widget = self._widgets_by_family[family] + new_widget = family_widget.get_widget_by_instance_id(widget_id) if new_widget is self._selected_widget: return From 4833ce3220e0e6dfdf58ad0baac52b8e5b932505 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 10:39:39 +0200 Subject: [PATCH 477/736] better line alignment --- openpype/tools/new_publisher/widgets/card_view_widgets.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/card_view_widgets.py b/openpype/tools/new_publisher/widgets/card_view_widgets.py index c8d831df9c..0dc491e38b 100644 --- a/openpype/tools/new_publisher/widgets/card_view_widgets.py +++ b/openpype/tools/new_publisher/widgets/card_view_widgets.py @@ -24,10 +24,12 @@ class FamilyLabel(QtWidgets.QWidget): line_widget = QtWidgets.QWidget(self) line_widget.setObjectName("Separator") - line_widget.setMinimumHeight(1) - line_widget.setMaximumHeight(1) + line_widget.setMinimumHeight(2) + line_widget.setMaximumHeight(2) layout = QtWidgets.QHBoxLayout(self) + layout.setAlignment(QtCore.Qt.AlignCenter) + layout.setSpacing(10) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(label_widget, 0) layout.addWidget(line_widget, 1) From 0f03a5b7a9ca288b98001305460bed8bbd53982d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 10:41:16 +0200 Subject: [PATCH 478/736] add spacing instead of setting it --- openpype/tools/new_publisher/window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index faf913d704..2a122bebad 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -96,8 +96,8 @@ class PublisherWindow(QtWidgets.QDialog): # Layout of buttons at the bottom of subset view subset_view_btns_layout = QtWidgets.QHBoxLayout() subset_view_btns_layout.setContentsMargins(0, 0, 0, 0) - subset_view_btns_layout.setSpacing(5) subset_view_btns_layout.addWidget(create_btn) + subset_view_btns_layout.addSpacing(5) subset_view_btns_layout.addWidget(delete_btn) subset_view_btns_layout.addStretch(1) subset_view_btns_layout.addWidget(change_view_btn) From b0a8417571c795b967e00554a92d62c031d95756 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 10:49:54 +0200 Subject: [PATCH 479/736] added helper methods to recalculate size --- openpype/widgets/nice_checkbox.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/openpype/widgets/nice_checkbox.py b/openpype/widgets/nice_checkbox.py index 09113fea73..22bf88a95e 100644 --- a/openpype/widgets/nice_checkbox.py +++ b/openpype/widgets/nice_checkbox.py @@ -93,6 +93,16 @@ class NiceCheckbox(QtWidgets.QFrame): new_size = self._checkbox_size_hint() self.setFixedSize(new_size) + def get_width_hint_by_height(self, height): + return ( + height / self._base_size.height() + ) * self._base_size.width() + + def get_height_hint_by_width(self, width): + return ( + width / self._base_size.width() + ) * self._base_size.height() + def resizeEvent(self, event): new_size = QtCore.QSize(self._base_size) new_size.scale(event.size(), QtCore.Qt.KeepAspectRatio) @@ -102,19 +112,15 @@ class NiceCheckbox(QtWidgets.QFrame): self._fixed_height_set = True super(NiceCheckbox, self).setFixedHeight(*args, **kwargs) if not self._fixed_width_set: - width = ( - self.height() / self._base_size.height() - ) * self._base_size.width() + width = self.get_width_hint_by_height(self.height()) self.setFixedWidth(width) def setFixedWidth(self, *args, **kwargs): self._fixed_width_set = True super(NiceCheckbox, self).setFixedWidth(*args, **kwargs) if not self._fixed_height_set: - width = ( - self.width() / self._base_size.width() - ) * self._base_size.height() - self.setFixedHeight(width) + height = self.get_height_hint_by_width(self.width()) + self.setFixedHeight(height) def setFixedSize(self, *args, **kwargs): self._fixed_height_set = True From a1f3a7b029ceff34b8d8e2942961185da29081ae Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 10:50:12 +0200 Subject: [PATCH 480/736] size hint use font metrics --- openpype/widgets/nice_checkbox.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/openpype/widgets/nice_checkbox.py b/openpype/widgets/nice_checkbox.py index 22bf88a95e..625e5b0113 100644 --- a/openpype/widgets/nice_checkbox.py +++ b/openpype/widgets/nice_checkbox.py @@ -92,6 +92,10 @@ class NiceCheckbox(QtWidgets.QFrame): ): new_size = self._checkbox_size_hint() self.setFixedSize(new_size) + def sizeHint(self): + height = self.fontMetrics().height() + width = self.get_width_hint_by_height(height) + return QtCore.QSize(width, height) def get_width_hint_by_height(self, height): return ( @@ -207,11 +211,6 @@ class NiceCheckbox(QtWidgets.QFrame): return QtCore.Qt.Unchecked return QtCore.Qt.Checked - def sizeHint(self): - if self._use_checkbox_height_hint is False: - return self._base_size - return self._checkbox_size_hint() - def mousePressEvent(self, event): if event.buttons() & QtCore.Qt.LeftButton: self._pressed = True From e0e579aaf0a0c7957523901b04619ed802ffafd3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 10:50:31 +0200 Subject: [PATCH 481/736] don't use checkbox size hint anymore --- openpype/widgets/nice_checkbox.py | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/openpype/widgets/nice_checkbox.py b/openpype/widgets/nice_checkbox.py index 625e5b0113..766becd310 100644 --- a/openpype/widgets/nice_checkbox.py +++ b/openpype/widgets/nice_checkbox.py @@ -6,10 +6,7 @@ class NiceCheckbox(QtWidgets.QFrame): stateChanged = QtCore.Signal(int) clicked = QtCore.Signal() - def __init__( - self, checked=False, draw_icons=False, parent=None, - use_checkbox_height_hint=None - ): + def __init__(self, checked=False, draw_icons=False, parent=None): super(NiceCheckbox, self).__init__(parent) self._checked = checked if checked: @@ -24,8 +21,6 @@ class NiceCheckbox(QtWidgets.QFrame): self._animation_timer = QtCore.QTimer(self) self._animation_timeout = 6 - self._use_checkbox_height_hint = use_checkbox_height_hint - self._first_show = True self._fixed_width_set = False self._fixed_height_set = False @@ -79,19 +74,6 @@ class NiceCheckbox(QtWidgets.QFrame): new_size = QtCore.QSize(width, checkbox_height) return new_size - def showEvent(self, event): - super(NiceCheckbox, self).showEvent(event) - if self._first_show: - self._first_show = False - if ( - self._use_checkbox_height_hint - or ( - self._use_checkbox_height_hint is None - and not (self._fixed_width_set or self._fixed_height_set) - ) - ): - new_size = self._checkbox_size_hint() - self.setFixedSize(new_size) def sizeHint(self): height = self.fontMetrics().height() width = self.get_width_hint_by_height(height) From 0734ba873a7212617c7285d54e24d47efc54585a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 10:59:30 +0200 Subject: [PATCH 482/736] nice checkbox has fixed size policy --- openpype/style/style.css | 5 +++++ openpype/widgets/nice_checkbox.py | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/openpype/style/style.css b/openpype/style/style.css index a7a4e381e1..2d1c471bb9 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -776,3 +776,8 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { #ArrowBtn, #ArrowBtn:disabled, #ArrowBtn:hover { background: transparent; } + +#NiceCheckbox { + /* Default size hint of NiceCheckbox is defined by font size. */ + font-size: 7pt; +} diff --git a/openpype/widgets/nice_checkbox.py b/openpype/widgets/nice_checkbox.py index 766becd310..22a3fb1db3 100644 --- a/openpype/widgets/nice_checkbox.py +++ b/openpype/widgets/nice_checkbox.py @@ -8,6 +8,13 @@ class NiceCheckbox(QtWidgets.QFrame): def __init__(self, checked=False, draw_icons=False, parent=None): super(NiceCheckbox, self).__init__(parent) + + self.setObjectName("NiceCheckbox") + + self.setSizePolicy( + QtWidgets.QSizePolicy.Fixed, + QtWidgets.QSizePolicy.Fixed + ) self._checked = checked if checked: checkstate = QtCore.Qt.Checked From 4718be35156c9c4fb590b9d0634774102d702854 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 11:15:59 +0200 Subject: [PATCH 483/736] enabling/disabling of footer won't disable reset button --- openpype/tools/new_publisher/window.py | 30 +++++++++++++++----------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 2a122bebad..ff682f11ab 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -125,8 +125,7 @@ class PublisherWindow(QtWidgets.QDialog): validate_btn = ValidateBtn(subset_frame) publish_btn = PublishBtn(subset_frame) - footer_widget = QtWidgets.QWidget(subset_frame) - footer_layout = QtWidgets.QHBoxLayout(footer_widget) + footer_layout = QtWidgets.QHBoxLayout() footer_layout.setContentsMargins(0, 0, 0, 0) footer_layout.addWidget(message_input, 1) footer_layout.addWidget(reset_btn, 0) @@ -143,7 +142,7 @@ class PublisherWindow(QtWidgets.QDialog): marings.setBottom(marings.bottom() * 2) subset_layout.setContentsMargins(marings) subset_layout.addWidget(subset_content_widget, 1) - subset_layout.addWidget(footer_widget, 0) + subset_layout.addLayout(footer_layout, 0) # Create publish frame publish_frame = PublishFrame(controller, self) @@ -215,8 +214,6 @@ class PublisherWindow(QtWidgets.QDialog): self.subset_attributes_widget = subset_attributes_widget - self.footer_widget = footer_widget - self.message_input = message_input self.stop_btn = stop_btn @@ -381,9 +378,21 @@ class PublisherWindow(QtWidgets.QDialog): return self._validate_create_instances() + def _set_footer_enabled(self, enabled): + self.message_input.setEnabled(enabled) + self.reset_btn.setEnabled(True) + if enabled: + self.stop_btn.setEnabled(False) + self.validate_btn.setEnabled(True) + self.publish_btn.setEnabled(True) + else: + self.stop_btn.setEnabled(enabled) + self.validate_btn.setEnabled(enabled) + self.publish_btn.setEnabled(enabled) + def _validate_create_instances(self): if not self.controller.host_is_valid: - self.footer_widget.setEnabled(True) + self._set_footer_enabled(True) return all_valid = None @@ -398,19 +407,14 @@ class PublisherWindow(QtWidgets.QDialog): if all_valid is None: all_valid = True - self.footer_widget.setEnabled(bool(all_valid)) + self._set_footer_enabled(bool(all_valid)) def _on_publish_reset(self): - self.reset_btn.setEnabled(True) - self.stop_btn.setEnabled(False) - self.validate_btn.setEnabled(True) - self.publish_btn.setEnabled(True) - self._set_publish_visibility(False) self.subset_content_widget.setEnabled(self.controller.host_is_valid) - self.footer_widget.setEnabled(False) + self._set_footer_enabled(False) def _on_publish_start(self): self.reset_btn.setEnabled(False) From d1ddcf68709550199081745c2fe9bf62d6da3c82 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 11:37:03 +0200 Subject: [PATCH 484/736] use geometry of nice checkbox instead of event rectangle --- openpype/widgets/nice_checkbox.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/openpype/widgets/nice_checkbox.py b/openpype/widgets/nice_checkbox.py index 22a3fb1db3..19d5c1b62c 100644 --- a/openpype/widgets/nice_checkbox.py +++ b/openpype/widgets/nice_checkbox.py @@ -58,9 +58,8 @@ class NiceCheckbox(QtWidgets.QFrame): self._base_size = QtCore.QSize(90, 50) def setTristate(self, tristate=True): - if self._is_tristate == tristate: - return - self._is_tristate = tristate + if self._is_tristate != tristate: + self._is_tristate = tristate def set_draw_icons(self, draw_icons=None): if draw_icons is None: @@ -96,10 +95,6 @@ class NiceCheckbox(QtWidgets.QFrame): width / self._base_size.width() ) * self._base_size.height() - def resizeEvent(self, event): - new_size = QtCore.QSize(self._base_size) - new_size.scale(event.size(), QtCore.Qt.KeepAspectRatio) - self.resize(new_size) def setFixedHeight(self, *args, **kwargs): self._fixed_height_set = True @@ -290,7 +285,7 @@ class NiceCheckbox(QtWidgets.QFrame): painter = QtGui.QPainter(self) painter.setRenderHint(QtGui.QPainter.Antialiasing) - frame_rect = QtCore.QRect(event.rect()) + frame_rect = QtCore.QRect(self.rect()) # Draw inner background if self._current_step == self._steps: From e053cdedb65323e21403bce9909034b4b5195b9e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 11:43:29 +0200 Subject: [PATCH 485/736] added instances in card view into scroll area --- .../widgets/card_view_widgets.py | 45 ++++++++++++++++--- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/card_view_widgets.py b/openpype/tools/new_publisher/widgets/card_view_widgets.py index 0dc491e38b..1e5f9fc7d5 100644 --- a/openpype/tools/new_publisher/widgets/card_view_widgets.py +++ b/openpype/tools/new_publisher/widgets/card_view_widgets.py @@ -10,7 +10,6 @@ from .widgets import ( ClickableFrame ) from ..constants import ( - INSTANCE_ID_ROLE, CONTEXT_ID, CONTEXT_LABEL ) @@ -259,11 +258,30 @@ class InstanceCardView(AbstractInstanceView): self.controller = controller + scroll_area = QtWidgets.QScrollArea(self) + scroll_area.setWidgetResizable(True) + scroll_area.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + scroll_area.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) + scrollbar_bg = scroll_area.verticalScrollBar().parent() + if scrollbar_bg: + scrollbar_bg.setAttribute(QtCore.Qt.WA_TranslucentBackground) + scroll_area.setViewportMargins(0, 0, 0, 0) + + content_widget = QtWidgets.QWidget(scroll_area) + + scroll_area.setWidget(content_widget) + + content_layout = QtWidgets.QVBoxLayout(content_widget) + content_layout.setContentsMargins(0, 0, 0, 0) + content_layout.addStretch(1) + layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) - layout.addStretch(1) + layout.addWidget(scroll_area) - self._content_layout = layout + self._scroll_area = scroll_area + self._content_layout = content_layout + self._content_widget = content_widget self._widgets_by_family = {} self._family_widgets_by_name = {} @@ -271,9 +289,26 @@ class InstanceCardView(AbstractInstanceView): self._selected_widget = None + self.setSizePolicy( + QtWidgets.QSizePolicy.Minimum, + self.sizePolicy().verticalPolicy() + ) + + def sizeHint(self): + # Calculate width hint by content widget and verticall scroll bar + scroll_bar = self._scroll_area.verticalScrollBar() + width = ( + self._content_widget.sizeHint().width() + + scroll_bar.sizeHint().width() + ) + + result = super(InstanceCardView, self).sizeHint() + result.setWidth(width) + return result + def refresh(self): if not self._context_widget: - widget = ContextCardWidget(self) + widget = ContextCardWidget(self._content_widget) widget.selected.connect(self._on_widget_selection) widget.set_selected(True) self.selection_changed.emit() @@ -299,7 +334,7 @@ class InstanceCardView(AbstractInstanceView): widget_idx = 1 for family in sorted_families: if family not in self._widgets_by_family: - family_widget = FamilyWidget(family, self) + family_widget = FamilyWidget(family, self._content_widget) family_widget.selected.connect(self._on_widget_selection) self._content_layout.insertWidget(widget_idx, family_widget) self._widgets_by_family[family] = family_widget From 24d70cae640e978ca55650c75f0cf0de0195a765 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 11:48:23 +0200 Subject: [PATCH 486/736] fixed active changed on card view --- openpype/tools/new_publisher/widgets/card_view_widgets.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/tools/new_publisher/widgets/card_view_widgets.py b/openpype/tools/new_publisher/widgets/card_view_widgets.py index 1e5f9fc7d5..302321a6c6 100644 --- a/openpype/tools/new_publisher/widgets/card_view_widgets.py +++ b/openpype/tools/new_publisher/widgets/card_view_widgets.py @@ -335,6 +335,7 @@ class InstanceCardView(AbstractInstanceView): for family in sorted_families: if family not in self._widgets_by_family: family_widget = FamilyWidget(family, self._content_widget) + family_widget.active_changed.connect(self._on_active_changed) family_widget.selected.connect(self._on_widget_selection) self._content_layout.insertWidget(widget_idx, family_widget) self._widgets_by_family[family] = family_widget From d60e8ada22e85ac07e1284b932fc214cdc1251cf Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 12:21:28 +0200 Subject: [PATCH 487/736] added new icons for instance view buttons --- openpype/style/style.css | 4 +- .../tools/new_publisher/widgets/__init__.py | 15 ++++-- .../new_publisher/widgets/images/add.png | Bin 0 -> 5823 bytes .../widgets/images/change_view.png | Bin 0 -> 8304 bytes .../new_publisher/widgets/images/delete.png | Bin 0 -> 12343 bytes .../tools/new_publisher/widgets/widgets.py | 47 ++++++++++++++---- openpype/tools/new_publisher/window.py | 12 +++-- 7 files changed, 60 insertions(+), 18 deletions(-) create mode 100644 openpype/tools/new_publisher/widgets/images/add.png create mode 100644 openpype/tools/new_publisher/widgets/images/change_view.png create mode 100644 openpype/tools/new_publisher/widgets/images/delete.png diff --git a/openpype/style/style.css b/openpype/style/style.css index 2d1c471bb9..c1f4bd437e 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -547,7 +547,9 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background: {color:bg-menu-separator}; } -#IconBtn {} +#IconButton { + padding: 4px 4px 4px 4px; +} /* Password dialog*/ #PasswordBtn { diff --git a/openpype/tools/new_publisher/widgets/__init__.py b/openpype/tools/new_publisher/widgets/__init__.py index 6d0c6f724c..55afc349ff 100644 --- a/openpype/tools/new_publisher/widgets/__init__.py +++ b/openpype/tools/new_publisher/widgets/__init__.py @@ -2,16 +2,21 @@ from .icons import ( get_icon_path, get_pixmap, get_icon +) from .border_label_widget import ( BorderedLabelWidget ) from .widgets import ( SubsetAttributesWidget, - IconBtn, + StopBtn, ResetBtn, ValidateBtn, - PublishBtn + PublishBtn, + + CreateInstanceBtn, + RemoveInstanceBtn, + ChangeViewBtn ) from .publish_widget import ( PublishFrame @@ -36,12 +41,16 @@ __all__ = ( "SubsetAttributesWidget", "BorderedLabelWidget", - "IconBtn", + "StopBtn", "ResetBtn", "ValidateBtn", "PublishBtn", + "CreateInstanceBtn", + "RemoveInstanceBtn", + "ChangeViewBtn", + "PublishFrame", "CreateDialog", diff --git a/openpype/tools/new_publisher/widgets/images/add.png b/openpype/tools/new_publisher/widgets/images/add.png new file mode 100644 index 0000000000000000000000000000000000000000..7fece2f3c675f5ecd48cca89c2a1c7b3c540e825 GIT binary patch literal 5823 zcmeHLYgAL$x?O>2K#Q@`ih>fg8=@8gv49jpd;%#iZ?(!pAs#E#26?}dP!pt-QiRx@ zmbaiq#TFtj10n=sp{4tPMx+Lc5I`fKJOU92Atand+dF!HoICFQcm6bEWMpOMn`^J{ z+be6%Z>AsdaW`6Jx(WbbMDsZGApm-Cs0R#I!jGB6Ul-s9KIQ=Jm;rnx8GQUD{B9WO z;U5FQ8@HC|wvyqY)vo38$maf`o(w>vQ3Zm@}s%&ss-BhZan_m;$ig zg?4EFvBbiOe$K5I37?HjcZYV^PV+X<@_+dHz1+)!pG_|v5Cqu*jFB z!1Uz9*Esn7ms7>zJ5#d{yrE_6F5{G6%)9ku`CuLMd)dDtfIhdMy&D%;`0Eq;uqYE9^!n6T$eQ zOcxoOS}R`^FXjX8gGT}EIFx8vzXKy#)YHE!bw%X`v`KGeok$f*W(Co=-~ii?F;R^W zx_MNuYR+(uOSYU%TVvt$E1Q!v+^rw74bXY#1g8q+&uI{RKBbLmi=9A|ZmV=1gZ|uI zYeA*^9cfUWoKFk)jUF0|@|kT%5-ip2GEIdWQNQpC;GViIocjvlpz>xiQ`>Xy;j46{ zn5JxIDVjG@-iQp*1MyYT-b|X#9W8U0R39di)yEXtrp^>lN(OY3PX(uP=qoM(O_){GojIR?2U!#k0XrQ~qj^Dsv ztdnV`;fxTJI_s`!DwK;--1D9$@E6@l8$X}MgEyoB>=;yop4`pKPF+N>Op%JjGso|I z>c|4O%7t@72-V}t1FFB)pHr!;(N8e|8?FoIO8xtTIX|%H6c#2(eS&DE7+B-PKp<3E z)Kd#y`z%eoUnJLxREkUH_#fT`m6g)oZ)t1D^`FdGHMH8GC%f64*qJRy(mg@tRcUWo zHLIog!BU;|2hjtp^W0Pe;OD_OAB@_BDEg%dh9}7CXRJ)_ig~J;)dGm z&`qq@NC4{}1!z7&CA^|Dym=duitR?k75D&gM*t&-tR6u|ctvM<^Kq!-oQz~fF$lBE z0rU$At;5Z4kAwM<6oFGfa2%u0xp+ zZ#j$ymeK&{%kp6Kq$?}iXxa}QP>4vI;cC{Afvrf@HcY+IUK4PXen1c0Oz0ZLIb60+ zO|QoTQLP9Ynp3MZ4!@#UzMY1yO>8pNFYwp49N^n<3{vL2;0Sfj*b2t_Yr_) zrEZAo&L1aH+P`qlmUBsK@Xj!$n^Ckdoo5e8<6&csNtb%{!YIM#gJ_ ztx>__=yH3neL(zP*az)ZQd=AjbXD%y)%Hi=^SHl2&&8v-RewZ)6@~z8_W)quet2}P zS_i<9%K&gM;sJOI0pKe9ul^nKcU}H-n?d}ZwJBxaR;j7wBbK5hluHo^YpK}R2*FMA zVQVL1G9%+@CI8j)V2ra;>~I$k0`Iua zHNWey)c?$r79QgSYTba?$&d4^Vwb;2J>C^H-u!gY1=~zB_fQgDw;6=iG4!trrRYgi z{J2SqB5sq^E1R)S9i6Ya0|&JHd_J-NIe(EWLc)13=0 zkJy|Gt!pz@?qf{sLsjHu_6?0|C4!GASL6&xztJ3pcJxD=Mh{S4F*1NYDTuW$)~>+O z6-=u#$BmrR=_;cRbI2oO{JuPSuYa%saA+a&`(*5;xk4MeKyXJY%YX7Sr^U(!fP#rY z|5Pz=I8liy@I3(yjly_elyK%Xfje<;E0!VC@3cAID3BMmECZ4j+RW-uf{l#k*H$XPz7B< zdY=eH+pvRC1;L=F-P|YITcLF3!-O{n${v5Z}zRtdFd(2i!cZ9rmqFllRp3WI*CA zhdoI3pO_{WngWu4_P(?#vUfjyAu64(k!Q>5P1{hmb}y=y}9se~G3ICwLv27uM~cF+Qj z+fR9+tG~$72UbHKXa;=xRt_9+>)AylXTmf3w^CHKoUTU3l1MT* zri0h^x4<2_x2hZJ#8;d<@%|`hv;CXEwqamQL@jC@#nZK`iEGRZL2g%yO_&bDasVoS zcQp|f;rVrnuGNBz{H~GVA!zOL7O*W2wydv)3VC&NC*KIj-vT;9N5zp<+BG<=>93wENX2PIFa}|`;>JQP3+F3jh`z&t59zbOsHsToJyj-C8v#91- z=t>|)%^$wXUn+U}*~MMKdWluDtDE1!x)YQxkR>!MG-3SX*S_%^0PQiGV_oJ;NQy6& ztN@MWl8{-)rIGlBL(cc`Qz)4+Q9{#MVro8R2bdZ|NMoXbJx`1$=kbF-NmE3-u_Yh7 zQ~qF|H>Hd+n|TyIrB*T^Q=*`Pjt)i5LYO$v6E^TcG#iTO7#DRIF7nw5sG7^x-i?=< z=y4xRwUj&PA|&c<{2;x(S7u3r6ExR+c~yNd5zO6ctL}Ywz*RL|Y!#^&`8Y!uzXj9q z`@WI$_$RA-GW5lhPy<(3)H`CZvncTtO?`)pe7w4@)&_ItZ#qf6ZE9x#tj4}Z(xf~B zOL6c*)5A)z_anxHH)II{+PD>CKu!DpTVTAS3h}j2pF<-M*y}BoR=pz_&tHxdoQ+E# zT;|Fgx%lqO?Ze+8-L1_r?4LRj;@P<3Sic`T5vr$Be$L!ib?RQ{WG~?MUtiV%fz|iI zW?v!|8=c=QooAxXsLN$JiKS>;Dba5z0^D7n3ba0G1KQ`pT91}r*!l82BvGj2$1VPw z=Ovj%C3kr$nZlfcA3qFRHAoAbX9)cpn_1aX*XUMy#X3-_Kh}<0+MqD>>xIf8`3$G0 z={E)5pdd_y>)*}Fx;R;>Zh2?`dg=*oe+iCHv~S*jC#slxW0QAuGH>Dj!@W4}=75Mp zM0mr?X<@p`7}z@u-H!QFssAmq{}$Q*Ig$PU=>Ej-Wo4OPUHeC`nOU6q*O&}t_Lssh zmRsB}yw5p7rM?BwO&xB$@MGk10ry4)m~bMDgHpeKNrnms;RGO~SeM*-FAAP~o1c%Q}9E8p#P2#%X}|219%*8muA z7*AX7%RR!FShA@9bY_#;tFL7m&IStp_f~xu=in8dFx3!pXtwvKuG00E@^!%Lmax{Q zjHe&;Z+iP{vnywXoiSloD}Gx%9kA>PDE@~hzz}TqP}u1@v_YE4(Y}q zw}(}-Ff;1p@Js1=oh=2r1Szvb!h#0r&VnY0<}_N@utfEe7=jME=ybm;v;`s?9*~B& zLQ)f{BFJj#sMB0!grIwEZn7t^Es^#H@?CPTXF}{tnoP=G>KxrqKk9_zZWK6H+FU9+ zLtMBB;m{`ukp?ulU{tabEF-(Mg5OxB()GY#J`BziI9BsAVX!(E218#92Td9@DSl=X zTO8WWSkcj80sMYhAgt)mksmAx{q06mk=#wSn06MrK(L@6(Jow;zv`?Rwf}3v+}wXz}mgK6|~i{5AC*Zv})8J8mQ=4H|IFr z7CJ;Z=LG%P@(Q%jk(Hgk$yb>!)0`?H;4ZWqf^oaCOywzB6P1;1m_)@I;CZZpCwMAL zk;YaiXwkN14e|+lBH5{j&GA)er$$e6b0869!0yE}+8bTUm#5!HON7kOU#;JH_qSGPHUrvWpF_6~&@cWAT^$F% literal 0 HcmV?d00001 diff --git a/openpype/tools/new_publisher/widgets/images/change_view.png b/openpype/tools/new_publisher/widgets/images/change_view.png new file mode 100644 index 0000000000000000000000000000000000000000..bda0ef1689c41a3a99f6ed172813d7065ce9be16 GIT binary patch literal 8304 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 literal 0 HcmV?d00001 diff --git a/openpype/tools/new_publisher/widgets/images/delete.png b/openpype/tools/new_publisher/widgets/images/delete.png new file mode 100644 index 0000000000000000000000000000000000000000..ab02768ba31460baedaaa4d965c6879e9781517e GIT binary patch literal 12343 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 literal 0 HcmV?d00001 diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 158e69eefa..8a7ed8332b 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -17,24 +17,30 @@ from ..constants import ( ) -class IconBtn(QtWidgets.QPushButton): +class IconButton(QtWidgets.QPushButton): """PushButton with icon and size of font. Using font metrics height as icon size reference. """ + + def __init__(self, *args, **kwargs): + super(IconButton, self).__init__(*args, **kwargs) + self.setObjectName("IconButton") + def sizeHint(self): - result = super().sizeHint() - if not self.text(): - new_height = ( - result.height() - - self.iconSize().height() - + self.fontMetrics().height() - ) - result.setHeight(new_height) + result = super(IconButton, self).sizeHint() + icon_h = self.iconSize().height() + font_height = self.fontMetrics().height() + text_set = bool(self.text()) + if not text_set and icon_h < font_height: + new_size = result.height() - icon_h + font_height + result.setHeight(new_size) + result.setWidth(new_size) + return result -class PublishIconBtn(IconBtn): +class PublishIconBtn(IconButton): def __init__(self, pixmap_path, *args, **kwargs): super(PublishIconBtn, self).__init__(*args, **kwargs) @@ -124,6 +130,27 @@ class PublishBtn(PublishIconBtn): self.setToolTip("Publish") +class CreateInstanceBtn(PublishIconBtn): + def __init__(self, parent=None): + icon_path = get_icon_path("add") + super(CreateInstanceBtn, self).__init__(icon_path, parent) + self.setToolTip("Create new instance") + + +class RemoveInstanceBtn(PublishIconBtn): + def __init__(self, parent=None): + icon_path = get_icon_path("delete") + super(RemoveInstanceBtn, self).__init__(icon_path, parent) + self.setToolTip("Remove selected instances") + + +class ChangeViewBtn(PublishIconBtn): + def __init__(self, parent=None): + icon_path = get_icon_path("change_view") + super(ChangeViewBtn, self).__init__(icon_path, parent) + self.setToolTip("Swap between views") + + class AbstractInstanceView(QtWidgets.QWidget): selection_changed = QtCore.Signal() active_changed = QtCore.Signal() diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index ff682f11ab..b1f745e63a 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -13,11 +13,15 @@ from .widgets import ( InstanceCardView, InstanceListView, CreateDialog, - IconBtn, + StopBtn, ResetBtn, ValidateBtn, PublishBtn, + + CreateInstanceBtn, + RemoveInstanceBtn, + ChangeViewBtn ) @@ -80,9 +84,9 @@ class PublisherWindow(QtWidgets.QDialog): subset_views_layout.addWidget(subset_list_view) # Buttons at the bottom of subset view - create_btn = QtWidgets.QPushButton("+", subset_frame) - delete_btn = QtWidgets.QPushButton("-", subset_frame) - change_view_btn = QtWidgets.QPushButton("=", subset_frame) + create_btn = CreateInstanceBtn(subset_frame) + delete_btn = RemoveInstanceBtn(subset_frame) + change_view_btn = ChangeViewBtn(subset_frame) # Subset details widget subset_attributes_wrap = BorderedLabelWidget( From bab047a3c5ebdf8c8b5eab0c6c88b58de5d9f7ea Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 12:21:45 +0200 Subject: [PATCH 488/736] fix delete button enabling --- openpype/tools/new_publisher/window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index b1f745e63a..a5bfbe1051 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -371,7 +371,7 @@ class PublisherWindow(QtWidgets.QDialog): instances, context_selected = self.get_selected_items() # Disable delete button if nothing is selected - self.delete_btn.setEnabled(len(instances) >= 0) + self.delete_btn.setEnabled(len(instances) > 0) self.subset_attributes_widget.set_current_instances( instances, context_selected From 2362eaad415c73be22b78635031e74c4bd309a57 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 12:22:01 +0200 Subject: [PATCH 489/736] added margin between buttons --- openpype/tools/new_publisher/window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index a5bfbe1051..592d701ecf 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -99,7 +99,7 @@ class PublisherWindow(QtWidgets.QDialog): # Layout of buttons at the bottom of subset view subset_view_btns_layout = QtWidgets.QHBoxLayout() - subset_view_btns_layout.setContentsMargins(0, 0, 0, 0) + subset_view_btns_layout.setContentsMargins(0, 5, 0, 0) subset_view_btns_layout.addWidget(create_btn) subset_view_btns_layout.addSpacing(5) subset_view_btns_layout.addWidget(delete_btn) From 1725bbc51228f675ad3710dd04ca35a1a21ccc03 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 12:22:21 +0200 Subject: [PATCH 490/736] use proper function to get icon --- openpype/tools/new_publisher/window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 592d701ecf..f7fc736154 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -34,7 +34,7 @@ class PublisherWindow(QtWidgets.QDialog): self.setWindowTitle("OpenPype publisher") - icon = QtGui.QIcon(resources.pype_icon_filepath()) + icon = QtGui.QIcon(resources.get_openpype_icon_filepath()) self.setWindowIcon(icon) if parent is None: From 17708241cc9b5a0b2d9b8bd851e0402144d0e898 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 12:56:57 +0200 Subject: [PATCH 491/736] added logo icon to header --- .../tools/new_publisher/widgets/__init__.py | 4 ++++ .../tools/new_publisher/widgets/widgets.py | 20 +++++++++++++++++++ openpype/tools/new_publisher/window.py | 8 ++++++++ 3 files changed, 32 insertions(+) diff --git a/openpype/tools/new_publisher/widgets/__init__.py b/openpype/tools/new_publisher/widgets/__init__.py index 55afc349ff..9b22a6cf25 100644 --- a/openpype/tools/new_publisher/widgets/__init__.py +++ b/openpype/tools/new_publisher/widgets/__init__.py @@ -9,6 +9,8 @@ from .border_label_widget import ( from .widgets import ( SubsetAttributesWidget, + PixmapLabel, + StopBtn, ResetBtn, ValidateBtn, @@ -42,6 +44,8 @@ __all__ = ( "SubsetAttributesWidget", "BorderedLabelWidget", + "PixmapLabel", + "StopBtn", "ResetBtn", "ValidateBtn", diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 8a7ed8332b..cb3d6efb6e 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -17,6 +17,26 @@ from ..constants import ( ) +class PixmapLabel(QtWidgets.QLabel): + """Label resizing image to height of font.""" + def __init__(self, pixmap, parent): + super(PixmapLabel, self).__init__(parent) + self._source_pixmap = pixmap + + def resizeEvent(self, event): + size = self.fontMetrics().height() + size += size % 2 + self.setPixmap( + self._source_pixmap.scaled( + size, + size, + QtCore.Qt.KeepAspectRatio, + QtCore.Qt.SmoothTransformation + ) + ) + super(PixmapLabel, self).resizeEvent(event) + + class IconButton(QtWidgets.QPushButton): """PushButton with icon and size of font. diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index f7fc736154..0576432475 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -14,6 +14,8 @@ from .widgets import ( InstanceListView, CreateDialog, + PixmapLabel, + StopBtn, ResetBtn, ValidateBtn, @@ -58,10 +60,16 @@ class PublisherWindow(QtWidgets.QDialog): # Header header_widget = QtWidgets.QWidget(self) + icon_pixmap = QtGui.QPixmap(resources.get_openpype_icon_filepath()) + icon_label = PixmapLabel(icon_pixmap, header_widget) + icon_label.setObjectName("PublishContextLabel") context_label = QtWidgets.QLabel(header_widget) context_label.setObjectName("PublishContextLabel") header_layout = QtWidgets.QHBoxLayout(header_widget) + header_layout.setContentsMargins(15, 15, 15, 15) + header_layout.setSpacing(15) + header_layout.addWidget(icon_label, 0) header_layout.addWidget(context_label, 1) line_widget = QtWidgets.QWidget(self) From aba71d5c7dbd57d4702bc54a2271a0a0e3633a7b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 12:57:27 +0200 Subject: [PATCH 492/736] renamed message_input to comment_input --- openpype/tools/new_publisher/window.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 0576432475..7870e74ad9 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -130,7 +130,7 @@ class PublisherWindow(QtWidgets.QDialog): subset_content_layout.addWidget(subset_attributes_wrap, 7) # Footer - message_input = QtWidgets.QLineEdit(subset_frame) + comment_input = QtWidgets.QLineEdit(subset_frame) reset_btn = ResetBtn(subset_frame) stop_btn = StopBtn(subset_frame) @@ -139,7 +139,7 @@ class PublisherWindow(QtWidgets.QDialog): footer_layout = QtWidgets.QHBoxLayout() footer_layout.setContentsMargins(0, 0, 0, 0) - footer_layout.addWidget(message_input, 1) + footer_layout.addWidget(comment_input, 1) footer_layout.addWidget(reset_btn, 0) footer_layout.addWidget(stop_btn, 0) footer_layout.addWidget(validate_btn, 0) @@ -226,7 +226,7 @@ class PublisherWindow(QtWidgets.QDialog): self.subset_attributes_widget = subset_attributes_widget - self.message_input = message_input + self.comment_input = comment_input self.stop_btn = stop_btn self.reset_btn = reset_btn @@ -391,7 +391,7 @@ class PublisherWindow(QtWidgets.QDialog): self._validate_create_instances() def _set_footer_enabled(self, enabled): - self.message_input.setEnabled(enabled) + self.comment_input.setEnabled(enabled) self.reset_btn.setEnabled(True) if enabled: self.stop_btn.setEnabled(False) From a250b5fbee73ed6487255d28b9db9eebf485bbb0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 14:25:33 +0200 Subject: [PATCH 493/736] added placeholder to comment input and better padding --- openpype/style/style.css | 3 +++ openpype/tools/new_publisher/window.py | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/openpype/style/style.css b/openpype/style/style.css index c1f4bd437e..c3cb918dd4 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -774,6 +774,9 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { #PublishDetailViews::item { margin: 1px 0px 1px 0px; } +#PublishCommentInput { + padding: 0.2em; +} #ArrowBtn, #ArrowBtn:disabled, #ArrowBtn:hover { background: transparent; diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 7870e74ad9..1a5f0fa826 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -131,6 +131,10 @@ class PublisherWindow(QtWidgets.QDialog): # Footer comment_input = QtWidgets.QLineEdit(subset_frame) + comment_input.setObjectName("PublishCommentInput") + comment_input.setPlaceholderText( + "Attach a comment to your publish" + ) reset_btn = ResetBtn(subset_frame) stop_btn = StopBtn(subset_frame) From 065f582579873e315811bb2f7dc0c52c07f22c31 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 14:45:05 +0200 Subject: [PATCH 494/736] made NiceChekcbox even nicer --- openpype/widgets/nice_checkbox.py | 51 +++++++++++++++++++------------ 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/openpype/widgets/nice_checkbox.py b/openpype/widgets/nice_checkbox.py index 19d5c1b62c..db9a1bf76d 100644 --- a/openpype/widgets/nice_checkbox.py +++ b/openpype/widgets/nice_checkbox.py @@ -36,6 +36,8 @@ class NiceCheckbox(QtWidgets.QFrame): self._middle_step = 11 self.set_steps(self._steps) + self._checker_margins_divider = 0 + self._pressed = False self._under_mouse = False @@ -95,7 +97,6 @@ class NiceCheckbox(QtWidgets.QFrame): width / self._base_size.width() ) * self._base_size.height() - def setFixedHeight(self, *args, **kwargs): self._fixed_height_set = True super(NiceCheckbox, self).setFixedHeight(*args, **kwargs) @@ -310,11 +311,20 @@ class NiceCheckbox(QtWidgets.QFrame): offset_ratio ) - margins_ratio = 20 - size_without_margins = int( - frame_rect.height() / margins_ratio * (margins_ratio - 2) - ) - margin_size_c = ceil(frame_rect.height() - size_without_margins) / 2 + margins_ratio = self._checker_margins_divider + if margins_ratio > 0: + size_without_margins = int( + (frame_rect.height() / margins_ratio) * (margins_ratio - 2) + ) + size_without_margins -= size_without_margins % 2 + margin_size_c = ceil( + frame_rect.height() - size_without_margins + ) / 2 + + else: + size_without_margins = frame_rect.height() + margin_size_c = 0 + checkbox_rect = QtCore.QRect( frame_rect.x() + margin_size_c, frame_rect.y() + margin_size_c, @@ -378,21 +388,22 @@ class NiceCheckbox(QtWidgets.QFrame): painter.setBrush(checker_color) painter.drawEllipse(checker_rect) - smaller_checker_rect = checker_rect.adjusted( - margin_size_c, margin_size_c, -margin_size_c, -margin_size_c - ) - gradient = QtGui.QLinearGradient( - smaller_checker_rect.bottomRight(), - smaller_checker_rect.topLeft() - ) - gradient.setColorAt(0, checker_color) if under_mouse: - dark_value = 155 - else: - dark_value = 130 - gradient.setColorAt(1, checker_color.darker(dark_value)) - painter.setBrush(gradient) - painter.drawEllipse(smaller_checker_rect) + adjust = margin_size_c + if adjust < 1 and checker_rect.height() > 4: + adjust = 1 + + smaller_checker_rect = checker_rect.adjusted( + adjust, adjust, -adjust, -adjust + ) + gradient = QtGui.QLinearGradient( + smaller_checker_rect.bottomRight(), + smaller_checker_rect.topLeft() + ) + gradient.setColorAt(0, checker_color) + gradient.setColorAt(1, checker_color.darker(155)) + painter.setBrush(gradient) + painter.drawEllipse(smaller_checker_rect) if self._draw_icons: painter.setBrush(bg_color) From 0c4f604b4cb40c335f1834ff735ffc4735f07784 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 15:19:48 +0200 Subject: [PATCH 495/736] added icons from creators next to instances --- openpype/style/style.css | 4 +- openpype/tools/new_publisher/constants.py | 2 +- openpype/tools/new_publisher/control.py | 6 ++ .../widgets/card_view_widgets.py | 48 ++++++-------- .../tools/new_publisher/widgets/widgets.py | 65 +++++++++++++++++++ 5 files changed, 94 insertions(+), 31 deletions(-) diff --git a/openpype/style/style.css b/openpype/style/style.css index c3cb918dd4..6b44f96a9c 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -777,7 +777,9 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { #PublishCommentInput { padding: 0.2em; } - +#FamilyIconLabel { + font-size: 14pt; +} #ArrowBtn, #ArrowBtn:disabled, #ArrowBtn:hover { background: transparent; } diff --git a/openpype/tools/new_publisher/constants.py b/openpype/tools/new_publisher/constants.py index ffdf90df9b..a44e425cfb 100644 --- a/openpype/tools/new_publisher/constants.py +++ b/openpype/tools/new_publisher/constants.py @@ -2,7 +2,7 @@ from Qt import QtCore # ID of context item in instance view CONTEXT_ID = "context" -CONTEXT_LABEL = "Publish options" +CONTEXT_LABEL = "Options" # Allowed symbols for subset name (and variant) # - characters, numbers, unsercore and dash diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 1273b12666..672f6fd01b 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -563,6 +563,12 @@ class PublisherController: )) return output + def get_icon_for_family(self, family): + creator = self.creators.get(family) + if creator is not None: + return creator.get_icon() + return None + def create(self, family, subset_name, instance_data, options): # QUESTION Force to return instances or call `list_instances` on each # creation? (`list_instances` may slow down...) diff --git a/openpype/tools/new_publisher/widgets/card_view_widgets.py b/openpype/tools/new_publisher/widgets/card_view_widgets.py index 302321a6c6..898d25fc92 100644 --- a/openpype/tools/new_publisher/widgets/card_view_widgets.py +++ b/openpype/tools/new_publisher/widgets/card_view_widgets.py @@ -7,7 +7,9 @@ from openpype.widgets.nice_checkbox import NiceCheckbox from .widgets import ( AbstractInstanceView, ContextWarningLabel, - ClickableFrame + ClickableFrame, + IconValuePixmapLabel, + TransparentPixmapLabel ) from ..constants import ( CONTEXT_ID, @@ -15,32 +17,11 @@ from ..constants import ( ) -class FamilyLabel(QtWidgets.QWidget): - def __init__(self, family, parent): - super(FamilyLabel, self).__init__(parent) - - label_widget = QtWidgets.QLabel(family, self) - - line_widget = QtWidgets.QWidget(self) - line_widget.setObjectName("Separator") - line_widget.setMinimumHeight(2) - line_widget.setMaximumHeight(2) - - layout = QtWidgets.QHBoxLayout(self) - layout.setAlignment(QtCore.Qt.AlignCenter) - layout.setSpacing(10) - layout.setContentsMargins(0, 0, 0, 0) - layout.addWidget(label_widget, 0) - layout.addWidget(line_widget, 1) - - self._label_widget = label_widget - - class FamilyWidget(QtWidgets.QWidget): selected = QtCore.Signal(str, str) active_changed = QtCore.Signal() - def __init__(self, family, parent): + def __init__(self, family, family_icon, parent): super(FamilyWidget, self).__init__(parent) label_widget = QtWidgets.QLabel(family, self) @@ -61,6 +42,9 @@ class FamilyWidget(QtWidgets.QWidget): layout.setContentsMargins(0, 0, 0, 0) layout.addLayout(label_layout, 0) + self._family = family + self._family_icon = family_icon + self._widgets_by_id = {} self._label_widget = label_widget @@ -98,7 +82,9 @@ class FamilyWidget(QtWidgets.QWidget): widget = self._widgets_by_id[instance.id] widget.update_instance(instance) else: - widget = InstanceCardWidget(instance, self) + widget = InstanceCardWidget( + instance, self._family_icon, self + ) widget.selected.connect(self.selected) widget.active_changed.connect(self.active_changed) self._widgets_by_id[instance.id] = widget @@ -135,7 +121,7 @@ class ContextCardWidget(CardWidget): self._id = CONTEXT_ID self._family = "" - icon_widget = QtWidgets.QLabel(self) + icon_widget = TransparentPixmapLabel(self) label_widget = QtWidgets.QLabel(CONTEXT_LABEL, self) @@ -155,18 +141,19 @@ class ContextCardWidget(CardWidget): class InstanceCardWidget(CardWidget): active_changed = QtCore.Signal() - def __init__(self, instance, parent): + def __init__(self, instance, family_icon, parent): super(InstanceCardWidget, self).__init__(parent) self._id = instance.id self._family = instance.data["family"] + self._family_icon = family_icon self.instance = instance - icon_widget = QtWidgets.QLabel(self) + icon_widget = IconValuePixmapLabel(family_icon, self) icon_layout = QtWidgets.QHBoxLayout() - icon_layout.setContentsMargins(5, 5, 5, 5) + icon_layout.setContentsMargins(10, 5, 5, 5) icon_layout.addWidget(icon_widget) label_widget = QtWidgets.QLabel(instance.data["subset"], self) @@ -334,7 +321,10 @@ class InstanceCardView(AbstractInstanceView): widget_idx = 1 for family in sorted_families: if family not in self._widgets_by_family: - family_widget = FamilyWidget(family, self._content_widget) + family_icon = self.controller.get_icon_for_family(family) + family_widget = FamilyWidget( + family, family_icon, self._content_widget + ) family_widget.active_changed.connect(self._on_active_changed) family_widget.selected.connect(self._on_widget_selection) self._content_layout.insertWidget(widget_idx, family_widget) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index cb3d6efb6e..884bd6ddcb 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -1,8 +1,11 @@ +import os import re import copy import collections from Qt import QtWidgets, QtCore, QtGui +from avalon.vendor import qtawesome + from openpype.widgets.attribute_defs import create_widget_for_attr_def from openpype.tools.flickcharm import FlickCharm @@ -37,6 +40,68 @@ class PixmapLabel(QtWidgets.QLabel): super(PixmapLabel, self).resizeEvent(event) +class TransparentPixmapLabel(QtWidgets.QLabel): + """Label resizing to width and height of font.""" + def __init__(self, *args, **kwargs): + super(TransparentPixmapLabel, self).__init__(*args, **kwargs) + + self.setObjectName("FamilyIconLabel") + + def resizeEvent(self, event): + size = self.fontMetrics().height() + size += size % 2 + pix = QtGui.QPixmap(size, size) + pix.fill(QtCore.Qt.transparent) + self.setPixmap(pix) + super(TransparentPixmapLabel, self).resizeEvent(event) + + +class IconValuePixmapLabel(PixmapLabel): + """Label resizing to width and height of font.""" + fa_prefixes = ["", "fa."] + default_size = 200 + + def __init__(self, icon_def, parent): + source_pixmap = self._parse_icon_def(icon_def) + + super(IconValuePixmapLabel, self).__init__(source_pixmap, parent) + + self.setObjectName("FamilyIconLabel") + + def _default_pixmap(self): + pix = QtGui.QPixmap(1, 1) + pix.fill(QtCore.Qt.transparent) + return pix + + def _parse_icon_def(self, icon_def): + if not icon_def: + return self._default_pixmap() + + if isinstance(icon_def, QtGui.QPixmap): + return icon_def + + if isinstance(icon_def, QtGui.QIcon): + return icon_def.pixmap(self.default_size, self.default_size) + + try: + if os.path.exists(icon_def): + return QtGui.QPixmap(icon_def) + except Exception: + # TODO logging + pass + + for prefix in self.fa_prefixes: + try: + icon_name = "{}{}".format(prefix, icon_def) + icon = qtawesome.icon(icon_name, color="white") + return icon.pixmap(self.default_size, self.default_size) + except Exception: + # TODO logging + continue + + return self._default_pixmap() + + class IconButton(QtWidgets.QPushButton): """PushButton with icon and size of font. From 85e9414cefc7ef41a329ffe3c48ff5bd273792b1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 15:20:08 +0200 Subject: [PATCH 496/736] border label widget does not have margins --- openpype/tools/new_publisher/widgets/border_label_widget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/widgets/border_label_widget.py b/openpype/tools/new_publisher/widgets/border_label_widget.py index 265fa72223..9d7a50b76e 100644 --- a/openpype/tools/new_publisher/widgets/border_label_widget.py +++ b/openpype/tools/new_publisher/widgets/border_label_widget.py @@ -140,7 +140,7 @@ class BorderedLabelWidget(QtWidgets.QFrame): center_layout.setContentsMargins(5, 5, 5, 5) layout = QtWidgets.QGridLayout(self) - layout.setContentsMargins(5, 5, 5, 5) + layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addLayout(top_layout, 0, 0, 1, 3) From 1bb95a2f7d024bf5931f65c980d04080bc1a3d0a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 15:35:04 +0200 Subject: [PATCH 497/736] make sure comment is stored to context --- openpype/tools/new_publisher/control.py | 12 ++++++++++++ openpype/tools/new_publisher/window.py | 9 +++++++++ 2 files changed, 21 insertions(+) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 672f6fd01b..e6f642dbba 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -333,6 +333,7 @@ class PublisherController: self._publish_finished = False self._publish_max_progress = 0 self._publish_progress = 0 + self._publish_comment_is_set = False # Validation order # - plugin with order same or higher than this value is extractor or @@ -621,6 +622,10 @@ class PublisherController: def publish_progress(self): return self._publish_progress + @property + def publish_comment_is_set(self): + return self._publish_comment_is_set + def get_publish_crash_error(self): return self._publish_error @@ -635,9 +640,12 @@ class PublisherController: self._publish_validated = False self._publish_up_validation = False self._publish_finished = False + self._publish_comment_is_set = False self._main_thread_processor.clear() self._main_thread_iter = self._publish_iterator() self._publish_context = pyblish.api.Context() + # Make sure "comment" is set on publish context + self._publish_context.data["comment"] = "" self._publish_report.reset( self._publish_context, @@ -652,6 +660,10 @@ class PublisherController: self._trigger_callbacks(self._publish_reset_callback_refs) + def set_comment(self, comment): + self._publish_context.data["comment"] = comment + self._publish_comment_is_set = True + def publish(self): """Run publishing.""" self._publish_up_validation = False diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 1a5f0fa826..b65a0f9094 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -343,11 +343,20 @@ class PublisherWindow(QtWidgets.QDialog): def _on_stop_clicked(self): self.controller.stop_publish() + def _set_publish_comment(self): + if self.controller.publish_comment_is_set: + return + + comment = self.comment_input.text() + self.controller.context.data["comment"] = comment + def _on_validate_clicked(self): + self._set_publish_comment() self._set_publish_visibility(True) self.controller.validate() def _on_publish_clicked(self): + self._set_publish_comment() self._set_publish_visibility(True) self.controller.publish() From b650bce21acbb039096920f20e56ef118fa303bf Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 15:50:46 +0200 Subject: [PATCH 498/736] bigger radius of card widget --- openpype/style/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/style/style.css b/openpype/style/style.css index 6b44f96a9c..fc0776c10d 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -686,7 +686,7 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { #CardViewWidget { background: {color:bg-buttons}; - border-radius: 0.1em; + border-radius: 0.2em; } #CardViewWidget[state="selected"] { background: {color:bg-inputs}; From 8672c3803f774b6201424a9b4d4b84b02c713a6a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 15:51:48 +0200 Subject: [PATCH 499/736] Context warning label replaces family icon if context is not valid --- .../widgets/card_view_widgets.py | 15 +++-- .../tools/new_publisher/widgets/widgets.py | 58 ++++--------------- 2 files changed, 22 insertions(+), 51 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/card_view_widgets.py b/openpype/tools/new_publisher/widgets/card_view_widgets.py index 898d25fc92..31dd34b243 100644 --- a/openpype/tools/new_publisher/widgets/card_view_widgets.py +++ b/openpype/tools/new_publisher/widgets/card_view_widgets.py @@ -151,20 +151,18 @@ class InstanceCardWidget(CardWidget): self.instance = instance icon_widget = IconValuePixmapLabel(family_icon, self) + context_warning = ContextWarningLabel(self) icon_layout = QtWidgets.QHBoxLayout() icon_layout.setContentsMargins(10, 5, 5, 5) icon_layout.addWidget(icon_widget) + icon_layout.addWidget(context_warning) label_widget = QtWidgets.QLabel(instance.data["subset"], self) active_checkbox = NiceCheckbox(parent=self) active_checkbox.setChecked(instance.data["active"]) - context_warning = ContextWarningLabel(self) - if instance.has_valid_context: - context_warning.setVisible(False) - expand_btn = QtWidgets.QToolButton(self) # Not yet implemented expand_btn.setVisible(False) @@ -201,6 +199,8 @@ class InstanceCardWidget(CardWidget): self.active_checkbox = active_checkbox self.expand_btn = expand_btn + self._validate_context() + def set_active(self, new_value): checkbox_value = self.active_checkbox.isChecked() instance_value = self.instance.data["active"] @@ -217,9 +217,14 @@ class InstanceCardWidget(CardWidget): self.instance = instance self.update_instance_values() + def _validate_context(self): + valid = self.instance.has_valid_context + self.icon_widget.setVisible(valid) + self.context_warning.setVisible(not valid) + def update_instance_values(self): self.set_active(self.instance.data["active"]) - self.context_warning.setVisible(not self.instance.has_valid_context) + self._validate_context() def _set_expanded(self, expanded=None): if expanded is None: diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 884bd6ddcb..1f8d80d018 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -102,6 +102,18 @@ class IconValuePixmapLabel(PixmapLabel): return self._default_pixmap() +class ContextWarningLabel(PixmapLabel): + def __init__(self, parent): + pix = get_pixmap("warning") + + super(ContextWarningLabel, self).__init__(pix, parent) + + self.setToolTip( + "Contain invalid context. Please check details." + ) + self.setObjectName("FamilyIconLabel") + + class IconButton(QtWidgets.QPushButton): """PushButton with icon and size of font. @@ -283,52 +295,6 @@ class ClickableFrame(QtWidgets.QFrame): super(ClickableFrame, self).mouseReleaseEvent(event) -class ContextWarningLabel(QtWidgets.QLabel): - cached_images_by_size = {} - source_image = None - - def __init__(self, parent): - super(ContextWarningLabel, self).__init__(parent) - self.setToolTip( - "Contain invalid context. Please check details." - ) - - self._image = None - if self.__class__.source_image is None: - self.__class__.source_image = get_pixmap("warning") - - @classmethod - def _get_pixmap_by_size(cls, size): - image = cls.cached_images_by_size.get(size) - if image is not None: - return image - - margins = int(size / 8) - margins_double = margins * 2 - pix = QtGui.QPixmap(size, size) - pix.fill(QtCore.Qt.transparent) - - scaled_image = cls.source_image.scaled( - size - margins_double, size - margins_double, - QtCore.Qt.KeepAspectRatio, - QtCore.Qt.SmoothTransformation - ) - painter = QtGui.QPainter(pix) - painter.setRenderHints( - painter.Antialiasing | painter.SmoothPixmapTransform - ) - painter.drawPixmap(margins, margins, scaled_image) - painter.end() - - return pix - - def showEvent(self, event): - super(ContextWarningLabel, self).showEvent(event) - if self._image is None: - self._image = self._get_pixmap_by_size(self.height()) - self.setPixmap(self._image) - - class AssetsHierarchyModel(QtGui.QStandardItemModel): def __init__(self, controller): super(AssetsHierarchyModel, self).__init__() From c61c3d3f42edee77a90888e756f3db183afca161 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 16:00:18 +0200 Subject: [PATCH 500/736] make variant bolded --- .../tools/new_publisher/widgets/card_view_widgets.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/widgets/card_view_widgets.py b/openpype/tools/new_publisher/widgets/card_view_widgets.py index 31dd34b243..1a7df6e76e 100644 --- a/openpype/tools/new_publisher/widgets/card_view_widgets.py +++ b/openpype/tools/new_publisher/widgets/card_view_widgets.py @@ -1,3 +1,4 @@ +import re import collections from Qt import QtWidgets, QtCore @@ -158,7 +159,15 @@ class InstanceCardWidget(CardWidget): icon_layout.addWidget(icon_widget) icon_layout.addWidget(context_warning) - label_widget = QtWidgets.QLabel(instance.data["subset"], self) + variant = instance.data["variant"] + subset_name = instance.data["subset"] + found_parts = set(re.findall(variant, subset_name, re.IGNORECASE)) + if found_parts: + for part in found_parts: + replacement = "{}".format(part) + subset_name = subset_name.replace(part, replacement) + + label_widget = QtWidgets.QLabel(subset_name, self) active_checkbox = NiceCheckbox(parent=self) active_checkbox.setChecked(instance.data["active"]) From 7a96e737b7d82c254be6396476c3868412315112 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 16:02:44 +0200 Subject: [PATCH 501/736] use NiceCheckbox in attribute def widgets --- openpype/widgets/attribute_defs/widgets.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/widgets/attribute_defs/widgets.py b/openpype/widgets/attribute_defs/widgets.py index b2781dabfe..548d9332e3 100644 --- a/openpype/widgets/attribute_defs/widgets.py +++ b/openpype/widgets/attribute_defs/widgets.py @@ -7,6 +7,7 @@ from openpype.pipeline.lib import ( EnumDef, BoolDef ) +from openpype.widgets.nice_checkbox import NiceCheckbox from Qt import QtWidgets, QtCore @@ -180,7 +181,7 @@ class TextAttrWidget(_BaseAttrDefWidget): class BoolAttrWidget(_BaseAttrDefWidget): def _ui_init(self): - input_widget = QtWidgets.QCheckBox(self) + input_widget = NiceCheckbox(parent=self) input_widget.setChecked(self.attr_def.default) input_widget.stateChanged.connect(self._on_value_change) From 51370baa090027aa4a611cec1ad0a75aef162635 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 16:05:18 +0200 Subject: [PATCH 502/736] fix subset label click --- openpype/tools/new_publisher/widgets/card_view_widgets.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/tools/new_publisher/widgets/card_view_widgets.py b/openpype/tools/new_publisher/widgets/card_view_widgets.py index 1a7df6e76e..3a1f01f438 100644 --- a/openpype/tools/new_publisher/widgets/card_view_widgets.py +++ b/openpype/tools/new_publisher/widgets/card_view_widgets.py @@ -168,6 +168,9 @@ class InstanceCardWidget(CardWidget): subset_name = subset_name.replace(part, replacement) label_widget = QtWidgets.QLabel(subset_name, self) + # HTML text will cause that label start catch mouse clicks + # - disabling with changing interaction flag + label_widget.setTextInteractionFlags(QtCore.Qt.NoTextInteraction) active_checkbox = NiceCheckbox(parent=self) active_checkbox.setChecked(instance.data["active"]) From 0ec8ab8f6ec1fdc2a903f8ce85b4d838d243f477 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 16:22:25 +0200 Subject: [PATCH 503/736] context title taken from host implementation --- openpype/hosts/testhost/api/__init__.py | 4 +++- openpype/hosts/testhost/api/pipeline.py | 31 +++++++++++++++++++++++++ openpype/tools/new_publisher/control.py | 13 +++++++++++ openpype/tools/new_publisher/window.py | 8 +++---- 4 files changed, 50 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/testhost/api/__init__.py b/openpype/hosts/testhost/api/__init__.py index 1a5423be61..7840b25892 100644 --- a/openpype/hosts/testhost/api/__init__.py +++ b/openpype/hosts/testhost/api/__init__.py @@ -10,7 +10,8 @@ from .pipeline import ( update_instances, remove_instances, get_context_data, - update_context_data + update_context_data, + get_context_title ) @@ -36,6 +37,7 @@ __all__ = ( "remove_instances", "get_context_data", "update_context_data", + "get_context_title", "install" ) diff --git a/openpype/hosts/testhost/api/pipeline.py b/openpype/hosts/testhost/api/pipeline.py index 063b90fbcc..442c6eeba0 100644 --- a/openpype/hosts/testhost/api/pipeline.py +++ b/openpype/hosts/testhost/api/pipeline.py @@ -7,6 +7,33 @@ class HostContext: instances_json_path = None context_json_path = None + @classmethod + def get_context_title(cls): + project_name = os.environ.get("AVALON_PROJECT") + if not project_name: + return "TestHost" + + asset_name = os.environ.get("AVALON_ASSET") + if not asset_name: + return project_name + + from avalon import io + + asset_doc = io.find_one( + {"type": "asset", "name": asset_name}, + {"data.parents": 1} + ) + parents = asset_doc.get("data", {}).get("parents") or [] + + hierarchy = [project_name] + hierarchy.extend(parents) + hierarchy.append("{}".format(asset_name)) + task_name = os.environ.get("AVALON_TASK") + if task_name: + hierarchy.append(task_name) + + return "/".join(hierarchy) + @classmethod def get_current_dir_filepath(cls, filename): return os.path.join( @@ -124,3 +151,7 @@ def get_context_data(): def update_context_data(data, changes): HostContext.save_context_data(data) + + +def get_context_title(): + return HostContext.get_context_title() diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index e6f642dbba..5780f086fe 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -1,3 +1,4 @@ +import os import copy import logging import traceback @@ -430,6 +431,18 @@ class PublisherController: def get_asset_docs(self): return self._asset_docs_cache.get_asset_docs() + def get_context_title(self): + context_title = None + if hasattr(self.host, "get_context_title"): + context_title = self.host.get_context_title() + + if context_title is None: + context_title = os.environ.get("AVALON_APP_NAME") + if context_title is None: + context_title = os.environ.get("AVALON_APP") + + return context_title + def get_asset_hierarchy(self): _queue = collections.deque(self.get_asset_docs()) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index b65a0f9094..e365b22362 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -245,11 +245,6 @@ class PublisherWindow(QtWidgets.QDialog): self.resize(self.default_width, self.default_height) - # DEBUGING - self.set_context_label( - "////" - ) - def showEvent(self, event): super(PublisherWindow, self).showEvent(event) if self._first_show: @@ -384,6 +379,9 @@ class PublisherWindow(QtWidgets.QDialog): self._validate_create_instances() + context_title = self.controller.get_context_title() + self.set_context_label(context_title) + def _on_subset_change(self, *_args): # Ignore changes if in middle of refreshing if self._refreshing_instances: From 714cdb35e7eb7238ad6120408a1a5eb4225072ee Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 16:41:23 +0200 Subject: [PATCH 504/736] set object names of IconValuePixmapLabel where is needed --- openpype/tools/new_publisher/widgets/card_view_widgets.py | 2 ++ openpype/tools/new_publisher/widgets/widgets.py | 4 ---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/card_view_widgets.py b/openpype/tools/new_publisher/widgets/card_view_widgets.py index 3a1f01f438..167d49707f 100644 --- a/openpype/tools/new_publisher/widgets/card_view_widgets.py +++ b/openpype/tools/new_publisher/widgets/card_view_widgets.py @@ -123,6 +123,7 @@ class ContextCardWidget(CardWidget): self._family = "" icon_widget = TransparentPixmapLabel(self) + icon_widget.setObjectName("FamilyIconLabel") label_widget = QtWidgets.QLabel(CONTEXT_LABEL, self) @@ -152,6 +153,7 @@ class InstanceCardWidget(CardWidget): self.instance = instance icon_widget = IconValuePixmapLabel(family_icon, self) + icon_widget.setObjectName("FamilyIconLabel") context_warning = ContextWarningLabel(self) icon_layout = QtWidgets.QHBoxLayout() diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 1f8d80d018..ce508ea10f 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -45,8 +45,6 @@ class TransparentPixmapLabel(QtWidgets.QLabel): def __init__(self, *args, **kwargs): super(TransparentPixmapLabel, self).__init__(*args, **kwargs) - self.setObjectName("FamilyIconLabel") - def resizeEvent(self, event): size = self.fontMetrics().height() size += size % 2 @@ -66,8 +64,6 @@ class IconValuePixmapLabel(PixmapLabel): super(IconValuePixmapLabel, self).__init__(source_pixmap, parent) - self.setObjectName("FamilyIconLabel") - def _default_pixmap(self): pix = QtGui.QPixmap(1, 1) pix.fill(QtCore.Qt.transparent) From 48b72d892f7944633bbbc6fe856c6eb54f9d2c86 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 17:03:48 +0200 Subject: [PATCH 505/736] action button has icon if action has icon --- openpype/style/style.css | 16 ++++++++++ .../widgets/validations_widget.py | 32 +++++++++++++------ 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/openpype/style/style.css b/openpype/style/style.css index fc0776c10d..52f666068b 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -737,6 +737,22 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { font-size: 13pt; } +#ValidationActionButton { + border: 1px solid transparent; + border-radius: 0.2em; + padding: 4px 6px 4px 6px; + background: {color:bg-buttons}; +} + +#ValidationActionButton:hover { + background: {color:bg-button-hover}; + color: {color:font-hover}; +} + +#ValidationActionButton:disabled { + background: {color:bg-buttons-disabled}; +} + #ValidationErrorTitleFrame { background: {color:bg-inputs}; border-left: 4px solid transparent; diff --git a/openpype/tools/new_publisher/widgets/validations_widget.py b/openpype/tools/new_publisher/widgets/validations_widget.py index 08b4039a81..a20890ecd7 100644 --- a/openpype/tools/new_publisher/widgets/validations_widget.py +++ b/openpype/tools/new_publisher/widgets/validations_widget.py @@ -5,7 +5,10 @@ except Exception: from Qt import QtWidgets, QtCore, QtGui -from .widgets import ClickableFrame +from .widgets import ( + ClickableFrame, + IconValuePixmapLabel +) class ValidationErrorInstanceList(QtWidgets.QListView): @@ -142,22 +145,33 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget): self._toggle_instance_btn.setArrowType(QtCore.Qt.RightArrow) -class ActionButton(QtWidgets.QPushButton): +class ActionButton(ClickableFrame): action_clicked = QtCore.Signal(str) def __init__(self, action, parent): super(ActionButton, self).__init__(parent) - action_label = action.label or action.__name__ - self.setText(action_label) - - # TODO handle icons - # action.icon + self.setObjectName("ValidationActionButton") self.action = action - self.clicked.connect(self._on_click) - def _on_click(self): + action_label = action.label or action.__name__ + action_icon = getattr(action, "icon", None) + label_widget = QtWidgets.QLabel(action_label, self) + if action_icon: + icon_label = IconValuePixmapLabel(action_icon, self) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(5, 0, 5, 0) + layout.addWidget(label_widget, 1) + layout.addWidget(icon_label, 0) + + self.setSizePolicy( + QtWidgets.QSizePolicy.Minimum, + self.sizePolicy().verticalPolicy() + ) + + def _mouse_release_callback(self): self.action_clicked.emit(self.action.id) From 56d931be95ee7e50881a1fcc0d4466a3fc8b4255 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 17:04:14 +0200 Subject: [PATCH 506/736] fix set comment --- openpype/tools/new_publisher/window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index e365b22362..1837463ff5 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -343,7 +343,7 @@ class PublisherWindow(QtWidgets.QDialog): return comment = self.comment_input.text() - self.controller.context.data["comment"] = comment + self.controller.set_comment(comment) def _on_validate_clicked(self): self._set_publish_comment() From a9286644387b1d34145dbcb2075699a7ac8d711d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 17:24:14 +0200 Subject: [PATCH 507/736] fixed checkbox position --- openpype/widgets/attribute_defs/widgets.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/widgets/attribute_defs/widgets.py b/openpype/widgets/attribute_defs/widgets.py index 548d9332e3..1cfed08363 100644 --- a/openpype/widgets/attribute_defs/widgets.py +++ b/openpype/widgets/attribute_defs/widgets.py @@ -189,6 +189,7 @@ class BoolAttrWidget(_BaseAttrDefWidget): self._input_widget = input_widget self.main_layout.addWidget(input_widget, 0) + self.main_layout.addStretch(1) def _on_value_change(self): new_value = self._input_widget.isChecked() From 9e34a377dbb7d52cc08efe650eab488ec370eadf Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 17:24:24 +0200 Subject: [PATCH 508/736] fixed size validation --- openpype/widgets/nice_checkbox.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/widgets/nice_checkbox.py b/openpype/widgets/nice_checkbox.py index db9a1bf76d..238d319f26 100644 --- a/openpype/widgets/nice_checkbox.py +++ b/openpype/widgets/nice_checkbox.py @@ -280,13 +280,13 @@ class NiceCheckbox(QtWidgets.QFrame): return QtGui.QColor(red, green, blue) def paintEvent(self, event): - if self.width() < 1 or self.height() < 1: + frame_rect = QtCore.QRect(self.rect()) + if frame_rect.width() < 0 or frame_rect.height() < 0: return painter = QtGui.QPainter(self) painter.setRenderHint(QtGui.QPainter.Antialiasing) - frame_rect = QtCore.QRect(self.rect()) # Draw inner background if self._current_step == self._steps: From 7746224e7f19c9ee7e925d5fcfd1bd768551f05f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 17:32:47 +0200 Subject: [PATCH 509/736] icon is not used as attribute but as method --- openpype/pipeline/create/creator_plugins.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 8e85ffb0ad..5a884abf67 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -43,6 +43,10 @@ class BaseCreator: # - default_variants may not be used if `get_default_variants` is overriden default_variants = [] + # Creator (and family) icon + # - may not be used if `get_icon` is reimplemented + icon = None + def __init__( self, create_context, system_settings, project_settings, headless=False ): @@ -74,6 +78,13 @@ class BaseCreator: """ pass + def get_icon(self): + """Icon of creator (family). + + Can return path to image file or awesome icon name. + """ + return self.icon + def get_default_variants(self): """Default variant values for UI tooltips. @@ -188,9 +199,6 @@ class Creator(BaseCreator): # Label shown in UI label = None - # Icon shown in UI - icon = None - # Short description of family description = None From d6caf3eebaffbda3521789f67ee2c370f2e208cc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 17:37:01 +0200 Subject: [PATCH 510/736] added base of creator description --- openpype/pipeline/create/creator_plugins.py | 20 +++++-- .../new_publisher/widgets/create_dialog.py | 55 +++++++++++++++++++ 2 files changed, 69 insertions(+), 6 deletions(-) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 5a884abf67..fe2a118b9e 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -200,6 +200,7 @@ class Creator(BaseCreator): label = None # Short description of family + # - may not be used if `get_description` is overriden description = None @abstractmethod @@ -218,17 +219,24 @@ class Creator(BaseCreator): # ) pass - def get_detail_description(self): - """Description of family and plugin. - - Can be detailed with html tags. + def get_description(self): + """Short description of family and plugin. Returns: - str: Detailed description of family for artist. By default returns - short description. + str: Short description of family. """ return self.description + # def get_detail_description(self): + # """Description of family and plugin. + # + # Can be detailed with html tags. + # + # Returns: + # str: Detailed description of family for artist. + # """ + # return None + class AutoCreator(BaseCreator): """Creator which is automatically triggered without user interaction. diff --git a/openpype/tools/new_publisher/widgets/create_dialog.py b/openpype/tools/new_publisher/widgets/create_dialog.py index 9c22ab919a..38b32eaba8 100644 --- a/openpype/tools/new_publisher/widgets/create_dialog.py +++ b/openpype/tools/new_publisher/widgets/create_dialog.py @@ -7,6 +7,7 @@ from Qt import QtWidgets, QtCore, QtGui from openpype.pipeline.create import CreatorError +from .widgets import IconValuePixmapLabel from ..constants import ( SUBSET_NAME_ALLOWED_SYMBOLS, VARIANT_TOOLTIP @@ -91,6 +92,52 @@ class CreateErrorMessageBox(QtWidgets.QDialog): return line +class FamilyDescriptionWidget(QtWidgets.QWidget): + def __init__(self, parent=None): + super(FamilyDescriptionWidget, self).__init__(parent=parent) + + icon_widget = IconValuePixmapLabel(None, self) + icon_widget.setObjectName("FamilyIconLabel") + + family_label = QtWidgets.QLabel("family") + family_label.setAlignment( + QtCore.Qt.AlignBottom | QtCore.Qt.AlignLeft + ) + + description_label = QtWidgets.QLabel("description") + description_label.setAlignment( + QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft + ) + + label_layout = QtWidgets.QVBoxLayout() + label_layout.setSpacing(0) + label_layout.addWidget(family_label) + label_layout.addWidget(description_label) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(icon_widget, 0) + layout.addLayout(label_layout, 1) + + self.icon_widget = icon_widget + self.family_label = family_label + self.description_label = description_label + + def set_plugin(self, plugin=None): + if not plugin: + self.icon_widget.set_icon_def(None) + self.family_label.setText("") + self.description_label.setText("") + return + + description = plugin.get_description() or "" + plugin_icon = plugin.get_icon() + + self.icon_widget.set_icon_def(plugin_icon) + self.family_label.setText(plugin.family) + self.description_label.setText(description) + + class CreateDialog(QtWidgets.QDialog): def __init__( self, controller, asset_name=None, task_name=None, parent=None @@ -123,6 +170,8 @@ class CreateDialog(QtWidgets.QDialog): self._name_pattern = name_pattern self._compiled_name_pattern = re.compile(name_pattern) + creator_description_widget = FamilyDescriptionWidget(self) + family_view = QtWidgets.QListView(self) family_model = QtGui.QStandardItemModel() family_view.setModel(family_model) @@ -151,6 +200,7 @@ class CreateDialog(QtWidgets.QDialog): create_btn.setEnabled(False) layout = QtWidgets.QVBoxLayout(self) + layout.addWidget(creator_description_widget, 0) layout.addWidget(QtWidgets.QLabel("Family:", self)) layout.addWidget(family_view, 1) layout.addWidget(QtWidgets.QLabel("Name:", self)) @@ -169,6 +219,8 @@ class CreateDialog(QtWidgets.QDialog): controller.add_plugins_refresh_callback(self._on_plugins_refresh) + self.creator_description_widget = creator_description_widget + self.subset_name_input = subset_name_input self.variant_input = variant_input @@ -282,6 +334,9 @@ class CreateDialog(QtWidgets.QDialog): family = new_index.data(QtCore.Qt.DisplayRole) creator = self.controller.creators.get(family) + + self.creator_description_widget.set_plugin(creator) + self._selected_creator = creator if not creator: return From f6d06f18f2440af8fc0e1354241b38e586ec9f8a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 18:13:53 +0200 Subject: [PATCH 511/736] reorganized creator dialog and use creator descriptions --- openpype/pipeline/create/creator_plugins.py | 22 ++++--- .../new_publisher/widgets/create_dialog.py | 58 ++++++++++++++----- .../tools/new_publisher/widgets/widgets.py | 13 ++++- 3 files changed, 69 insertions(+), 24 deletions(-) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index fe2a118b9e..c67f04c9e8 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -203,6 +203,10 @@ class Creator(BaseCreator): # - may not be used if `get_description` is overriden description = None + # Detailed description of family for artists + # - may not be used if `get_detail_description` is overriden + detailed_description = None + @abstractmethod def create(self, subset_name, instance_data, options=None): """Create new instance and store it. @@ -227,15 +231,15 @@ class Creator(BaseCreator): """ return self.description - # def get_detail_description(self): - # """Description of family and plugin. - # - # Can be detailed with html tags. - # - # Returns: - # str: Detailed description of family for artist. - # """ - # return None + def get_detail_description(self): + """Description of family and plugin. + + Can be detailed with markdown or html tags. + + Returns: + str: Detailed description of family for artist. + """ + return self.detailed_description class AutoCreator(BaseCreator): diff --git a/openpype/tools/new_publisher/widgets/create_dialog.py b/openpype/tools/new_publisher/widgets/create_dialog.py index 38b32eaba8..6a8f68e86a 100644 --- a/openpype/tools/new_publisher/widgets/create_dialog.py +++ b/openpype/tools/new_publisher/widgets/create_dialog.py @@ -3,6 +3,10 @@ import re import traceback import copy +try: + import commonmark +except Exception: + commonmark = None from Qt import QtWidgets, QtCore, QtGui from openpype.pipeline.create import CreatorError @@ -109,34 +113,55 @@ class FamilyDescriptionWidget(QtWidgets.QWidget): QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft ) + detail_description_widget = QtWidgets.QTextEdit(self) + detail_description_widget.setObjectName("InfoText") + detail_description_widget.setTextInteractionFlags( + QtCore.Qt.TextBrowserInteraction + ) + label_layout = QtWidgets.QVBoxLayout() label_layout.setSpacing(0) label_layout.addWidget(family_label) label_layout.addWidget(description_label) - layout = QtWidgets.QHBoxLayout(self) + top_layout = QtWidgets.QHBoxLayout() + top_layout.setContentsMargins(0, 0, 0, 0) + top_layout.addWidget(icon_widget, 0) + top_layout.addLayout(label_layout, 1) + + layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) - layout.addWidget(icon_widget, 0) - layout.addLayout(label_layout, 1) + layout.addLayout(top_layout, 0) + layout.addWidget(detail_description_widget, 1) self.icon_widget = icon_widget self.family_label = family_label self.description_label = description_label + self.detail_description_widget = detail_description_widget def set_plugin(self, plugin=None): if not plugin: self.icon_widget.set_icon_def(None) self.family_label.setText("") self.description_label.setText("") + self.detail_description_widget.setPlainText("") return - description = plugin.get_description() or "" plugin_icon = plugin.get_icon() + description = plugin.get_description() or "" + detailed_description = plugin.get_detail_description() or "" self.icon_widget.set_icon_def(plugin_icon) - self.family_label.setText(plugin.family) + self.family_label.setText("{}".format(plugin.family)) + self.family_label.setTextInteractionFlags(QtCore.Qt.NoTextInteraction) self.description_label.setText(description) + if commonmark: + html = commonmark.commonmark(detailed_description) + self.detail_description_widget.setHtml(html) + else: + self.detail_description_widget.setMarkdown(detailed_description) + class CreateDialog(QtWidgets.QDialog): def __init__( @@ -199,15 +224,20 @@ class CreateDialog(QtWidgets.QDialog): create_btn = QtWidgets.QPushButton("Create", self) create_btn.setEnabled(False) - layout = QtWidgets.QVBoxLayout(self) - layout.addWidget(creator_description_widget, 0) - layout.addWidget(QtWidgets.QLabel("Family:", self)) - layout.addWidget(family_view, 1) - layout.addWidget(QtWidgets.QLabel("Name:", self)) - layout.addLayout(variant_layout, 0) - layout.addWidget(QtWidgets.QLabel("Subset:", self)) - layout.addWidget(subset_name_input, 0) - layout.addWidget(create_btn, 0) + form_layout = QtWidgets.QFormLayout() + form_layout.addRow("Name:", variant_layout) + form_layout.addRow("Subset:", subset_name_input) + + left_layout = QtWidgets.QVBoxLayout() + left_layout.addWidget(QtWidgets.QLabel("Choose family:", self)) + left_layout.addWidget(family_view, 1) + left_layout.addLayout(form_layout, 0) + left_layout.addWidget(create_btn, 0) + + layout = QtWidgets.QHBoxLayout(self) + layout.addLayout(left_layout, 0) + layout.addSpacing(5) + layout.addWidget(creator_description_widget, 1) create_btn.clicked.connect(self._on_create) variant_input.returnPressed.connect(self._on_create) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index ce508ea10f..415945534f 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -26,7 +26,11 @@ class PixmapLabel(QtWidgets.QLabel): super(PixmapLabel, self).__init__(parent) self._source_pixmap = pixmap - def resizeEvent(self, event): + def set_source_pixmap(self, pixmap): + self._source_pixmap = pixmap + self._set_resized_pix() + + def _set_resized_pix(self): size = self.fontMetrics().height() size += size % 2 self.setPixmap( @@ -37,6 +41,9 @@ class PixmapLabel(QtWidgets.QLabel): QtCore.Qt.SmoothTransformation ) ) + + def resizeEvent(self, event): + self._set_resized_pix() super(PixmapLabel, self).resizeEvent(event) @@ -64,6 +71,10 @@ class IconValuePixmapLabel(PixmapLabel): super(IconValuePixmapLabel, self).__init__(source_pixmap, parent) + def set_icon_def(self, icon_def): + source_pixmap = self._parse_icon_def(icon_def) + self.set_source_pixmap(source_pixmap) + def _default_pixmap(self): pix = QtGui.QPixmap(1, 1) pix.fill(QtCore.Qt.transparent) From 65ac5bee7ec061fdcb0f285774ff0a29d9868521 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 18:15:12 +0200 Subject: [PATCH 512/736] enhanced testhost creators --- .../testhost/plugins/create/test_creator_1.py | 22 +++++++++++++ .../testhost/plugins/create/test_creator_2.py | 32 +++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/openpype/hosts/testhost/plugins/create/test_creator_1.py b/openpype/hosts/testhost/plugins/create/test_creator_1.py index 65d94d8dd7..b7cd652905 100644 --- a/openpype/hosts/testhost/plugins/create/test_creator_1.py +++ b/openpype/hosts/testhost/plugins/create/test_creator_1.py @@ -1,3 +1,4 @@ +from openpype import resources from openpype.hosts.testhost import api from openpype.pipeline import ( Creator, @@ -8,6 +9,10 @@ from openpype.pipeline import ( class TestCreatorOne(Creator): family = "test_one" + description = "Testing creator of testhost" + + def get_icon(self): + return resources.get_openpype_splash_filepath() def create(self, subset_name, data, options=None): avalon_instance = CreatedInstance(self.family, subset_name, data, self) @@ -27,3 +32,20 @@ class TestCreatorOne(Creator): lib.NumberDef("number_key", label="Number") ] return output + + def get_detail_description(self): + return """# Relictus funes est Nyseides currusque nunc oblita + +## Causa sed + +Lorem markdownum posito consumptis, *plebe Amorque*, abstitimus rogatus fictaque +gladium Circe, nos? Bos aeternum quae. Utque me, si aliquem cladis, et vestigia +arbor, sic mea ferre lacrimae agantur prospiciens hactenus. Amanti dentes pete, +vos quid laudemque rastrorumque terras in gratantibus **radix** erat cedemus? + +Pudor tu ponderibus verbaque illa; ire ergo iam Venus patris certe longae +cruentum lecta, et quaeque. Sit doce nox. Anteit ad tempora magni plenaque et +videres mersit sibique auctor in tendunt mittit cunctos ventisque gravitate +volucris quemquam Aeneaden. Pectore Mensis somnus; pectora +[ferunt](http://www.mox.org/oculosbracchia)? Fertilitatis bella dulce et suum? + """ diff --git a/openpype/hosts/testhost/plugins/create/test_creator_2.py b/openpype/hosts/testhost/plugins/create/test_creator_2.py index 2acc3273cd..81df6f2b82 100644 --- a/openpype/hosts/testhost/plugins/create/test_creator_2.py +++ b/openpype/hosts/testhost/plugins/create/test_creator_2.py @@ -8,6 +8,10 @@ from openpype.pipeline import ( class TestCreatorTwo(Creator): family = "test_two" + description = "A second testing creator" + + def get_icon(self): + return "cube" def create(self, subset_name, data, options=None): avalon_instance = CreatedInstance(self.family, subset_name, data, self) @@ -21,3 +25,31 @@ class TestCreatorTwo(Creator): lib.TextDef("text_key") ] return output + + def get_detail_description(self): + return """# Lorem ipsum, dolor sit amet. [![Awesome](https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg)](https://github.com/sindresorhus/awesome) + +> A curated list of awesome lorem ipsum generators. + +Inspired by the [awesome](https://github.com/sindresorhus/awesome) list thing. + + +## Table of Contents + +- [Legend](#legend) +- [Practical](#briefcase-practical) +- [Whimsical](#roller_coaster-whimsical) + - [Animals](#rabbit-animals) + - [Eras](#tophat-eras) + - [Famous Individuals](#sunglasses-famous-individuals) + - [Music](#microphone-music) + - [Food and Drink](#pizza-food-and-drink) + - [Geographic and Dialects](#earth_africa-geographic-and-dialects) + - [Literature](#books-literature) + - [Miscellaneous](#cyclone-miscellaneous) + - [Sports and Fitness](#bicyclist-sports-and-fitness) + - [TV and Film](#movie_camera-tv-and-film) +- [Tools, Apps, and Extensions](#wrench-tools-apps-and-extensions) +- [Contribute](#contribute) +- [TODO](#todo) +""" From e158ee7fc2df9fe44c17bf7f32dda218c229ae70 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 19:25:15 +0200 Subject: [PATCH 513/736] Asset selection is done with dialog --- openpype/style/style.css | 2 +- .../tools/new_publisher/widgets/models.py | 146 +++++++ .../tools/new_publisher/widgets/widgets.py | 390 ++++++++---------- 3 files changed, 317 insertions(+), 221 deletions(-) create mode 100644 openpype/tools/new_publisher/widgets/models.py diff --git a/openpype/style/style.css b/openpype/style/style.css index 52f666068b..9ce5db2ec5 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -776,7 +776,7 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { border-left: 1px solid {color:border}; } -#TasksCombobox[state="invalid"], #AssetsTreeComboBox[state="invalid"] { +#TasksCombobox[state="invalid"], #AssetNameInput[state="invalid"] { border-color: {color:publish-error}; } diff --git a/openpype/tools/new_publisher/widgets/models.py b/openpype/tools/new_publisher/widgets/models.py new file mode 100644 index 0000000000..a783751fb0 --- /dev/null +++ b/openpype/tools/new_publisher/widgets/models.py @@ -0,0 +1,146 @@ +import re +import collections + +from Qt import QtCore, QtGui + + +class AssetsHierarchyModel(QtGui.QStandardItemModel): + def __init__(self, controller): + super(AssetsHierarchyModel, self).__init__() + self._controller = controller + + self._items_by_name = {} + + def reset(self): + self.clear() + + self._items_by_name = {} + assets_by_parent_id = self._controller.get_asset_hierarchy() + + items_by_name = {} + _queue = collections.deque() + _queue.append((self.invisibleRootItem(), None)) + while _queue: + parent_item, parent_id = _queue.popleft() + children = assets_by_parent_id.get(parent_id) + if not children: + continue + + children_by_name = { + child["name"]: child + for child in children + } + items = [] + for name in sorted(children_by_name.keys()): + child = children_by_name[name] + item = QtGui.QStandardItem(name) + items_by_name[name] = item + items.append(item) + _queue.append((item, child["_id"])) + + parent_item.appendRows(items) + + self._items_by_name = items_by_name + + def name_is_valid(self, item_name): + return item_name in self._items_by_name + + def get_index_by_name(self, item_name): + item = self._items_by_name.get(item_name) + if item: + return item.index() + return QtCore.QModelIndex() + + +class TasksModel(QtGui.QStandardItemModel): + def __init__(self, controller): + super(TasksModel, self).__init__() + self._controller = controller + self._items_by_name = {} + self._asset_names = [] + self._task_names_by_asset_name = {} + + def set_asset_names(self, asset_names): + self._asset_names = asset_names + self.reset() + + @staticmethod + def get_intersection_of_tasks(task_names_by_asset_name): + tasks = None + for task_names in task_names_by_asset_name.values(): + if tasks is None: + tasks = set(task_names) + else: + tasks &= set(task_names) + + if not tasks: + break + return tasks or set() + + def is_task_name_valid(self, asset_name, task_name): + task_names = self._task_names_by_asset_name.get(asset_name) + if task_names and task_name in task_names: + return True + return False + + def reset(self): + if not self._asset_names: + self._items_by_name = {} + self._task_names_by_asset_name = {} + self.clear() + return + + task_names_by_asset_name = ( + self._controller.get_task_names_by_asset_names(self._asset_names) + ) + self._task_names_by_asset_name = task_names_by_asset_name + + new_task_names = self.get_intersection_of_tasks( + task_names_by_asset_name + ) + old_task_names = set(self._items_by_name.keys()) + if new_task_names == old_task_names: + return + + root_item = self.invisibleRootItem() + for task_name in old_task_names: + if task_name not in new_task_names: + item = self._items_by_name.pop(task_name) + root_item.removeRow(item.row()) + + new_items = [] + for task_name in new_task_names: + if task_name in self._items_by_name: + continue + + item = QtGui.QStandardItem(task_name) + self._items_by_name[task_name] = item + new_items.append(item) + root_item.appendRows(new_items) + + +class RecursiveSortFilterProxyModel(QtCore.QSortFilterProxyModel): + def filterAcceptsRow(self, row, parent_index): + regex = self.filterRegExp() + if not regex.isEmpty(): + model = self.sourceModel() + source_index = model.index( + row, self.filterKeyColumn(), parent_index + ) + if source_index.isValid(): + pattern = regex.pattern() + + # Check current index itself + value = model.data(source_index, self.filterRole()) + if re.search(pattern, value, re.IGNORECASE): + return True + + rows = model.rowCount(source_index) + for idx in range(rows): + if self.filterAcceptsRow(idx, source_index): + return True + return False + + return super(RecursiveSortFilterProxyModel, self).filterAcceptsRow( + row, parent_index + ) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 415945534f..769aa7adcc 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -9,6 +9,11 @@ from avalon.vendor import qtawesome from openpype.widgets.attribute_defs import create_widget_for_attr_def from openpype.tools.flickcharm import FlickCharm +from .models import ( + AssetsHierarchyModel, + TasksModel, + RecursiveSortFilterProxyModel, +) from .icons import ( get_pixmap, get_icon_path @@ -302,256 +307,202 @@ class ClickableFrame(QtWidgets.QFrame): super(ClickableFrame, self).mouseReleaseEvent(event) -class AssetsHierarchyModel(QtGui.QStandardItemModel): - def __init__(self, controller): - super(AssetsHierarchyModel, self).__init__() - self._controller = controller +class AssetsDialog(QtWidgets.QDialog): + def __init__(self, controller, parent): + super(AssetsDialog, self).__init__(parent) + self.setWindowTitle("Select asset") - self._items_by_name = {} + model = AssetsHierarchyModel(controller) + proxy_model = RecursiveSortFilterProxyModel() + proxy_model.setSourceModel(model) + proxy_model.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) - def reset(self): - self.clear() + filter_input = QtWidgets.QLineEdit() + filter_input.setPlaceholderText("Filter assets..") - self._items_by_name = {} - assets_by_parent_id = self._controller.get_asset_hierarchy() + asset_view = QtWidgets.QTreeView(self) + asset_view.setModel(proxy_model) + asset_view.setHeaderHidden(True) + asset_view.setFrameShape(QtWidgets.QFrame.NoFrame) + asset_view.setEditTriggers(QtWidgets.QTreeView.NoEditTriggers) + asset_view.setAlternatingRowColors(True) + asset_view.setSelectionBehavior(QtWidgets.QTreeView.SelectRows) + asset_view.setAllColumnsShowFocus(True) - items_by_name = {} - _queue = collections.deque() - _queue.append((self.invisibleRootItem(), None)) - while _queue: - parent_item, parent_id = _queue.popleft() - children = assets_by_parent_id.get(parent_id) - if not children: - continue + ok_btn = QtWidgets.QPushButton("OK", self) + cancel_btn = QtWidgets.QPushButton("Cancel", self) - children_by_name = { - child["name"]: child - for child in children - } - items = [] - for name in sorted(children_by_name.keys()): - child = children_by_name[name] - item = QtGui.QStandardItem(name) - items_by_name[name] = item - items.append(item) - _queue.append((item, child["_id"])) + btns_layout = QtWidgets.QHBoxLayout() + btns_layout.addStretch(1) + btns_layout.addWidget(ok_btn) + btns_layout.addWidget(cancel_btn) - parent_item.appendRows(items) + layout = QtWidgets.QVBoxLayout(self) + layout.addWidget(filter_input, 0) + layout.addWidget(asset_view, 1) + layout.addLayout(btns_layout, 0) - self._items_by_name = items_by_name + filter_input.textChanged.connect(self._on_filter_change) + ok_btn.clicked.connect(self._on_ok_clicked) + cancel_btn.clicked.connect(self._on_cancel_clicked) - def name_is_valid(self, item_name): - return item_name in self._items_by_name + self._filter_input = filter_input + self._ok_btn = ok_btn + self._cancel_btn = cancel_btn - def get_index_by_name(self, item_name): - item = self._items_by_name.get(item_name) - if item: - return item.index() - return QtCore.QModelIndex() + self._model = model + self._proxy_model = proxy_model + self._asset_view = asset_view -class TasksModel(QtGui.QStandardItemModel): - def __init__(self, controller): - super(TasksModel, self).__init__() - self._controller = controller - self._items_by_name = {} - self._asset_names = [] - self._task_names_by_asset_name = {} - - def set_asset_names(self, asset_names): - self._asset_names = asset_names - self.reset() - - @staticmethod - def get_intersection_of_tasks(task_names_by_asset_name): - tasks = None - for task_names in task_names_by_asset_name.values(): - if tasks is None: - tasks = set(task_names) - else: - tasks &= set(task_names) - - if not tasks: - break - return tasks or set() - - def is_task_name_valid(self, asset_name, task_name): - task_names = self._task_names_by_asset_name.get(asset_name) - if task_names and task_name in task_names: - return True - return False - - def reset(self): - if not self._asset_names: - self._items_by_name = {} - self._task_names_by_asset_name = {} - self.clear() - return - - task_names_by_asset_name = ( - self._controller.get_task_names_by_asset_names(self._asset_names) - ) - self._task_names_by_asset_name = task_names_by_asset_name - - new_task_names = self.get_intersection_of_tasks( - task_names_by_asset_name - ) - old_task_names = set(self._items_by_name.keys()) - if new_task_names == old_task_names: - return - - root_item = self.invisibleRootItem() - for task_name in old_task_names: - if task_name not in new_task_names: - item = self._items_by_name.pop(task_name) - root_item.removeRow(item.row()) - - new_items = [] - for task_name in new_task_names: - if task_name in self._items_by_name: - continue - - item = QtGui.QStandardItem(task_name) - self._items_by_name[task_name] = item - new_items.append(item) - root_item.appendRows(new_items) - - -class TreeComboBoxView(QtWidgets.QTreeView): - visible_rows = 12 - - def __init__(self, parent): - super(TreeComboBoxView, self).__init__(parent) - - self.setHeaderHidden(True) - self.setFrameShape(QtWidgets.QFrame.NoFrame) - self.setEditTriggers(QtWidgets.QTreeView.NoEditTriggers) - self.setAlternatingRowColors(True) - self.setSelectionBehavior(QtWidgets.QTreeView.SelectRows) - self.setWordWrap(True) - self.setAllColumnsShowFocus(True) + self._selected_asset = None + self._soft_reset_enabled = True def showEvent(self, event): - super(TreeComboBoxView, self).showEvent(event) + super(AssetsDialog, self).showEvent(event) - row_sh = self.sizeHintForRow(0) - current_height = self.height() - height = (self.visible_rows * row_sh) + (current_height % row_sh) - self.setMinimumHeight(height) + self.reset(False) + def reset(self, force=True): + if not force and not self._soft_reset_enabled: + return -class TreeComboBox(QtWidgets.QComboBox): - def __init__(self, model, parent): - super(TreeComboBox, self).__init__(parent) + if self._soft_reset_enabled: + self._soft_reset_enabled = False - tree_view = TreeComboBoxView(self) - self.setView(tree_view) + self._model.reset() - tree_view.viewport().installEventFilter(self) + def name_is_valid(self, name): + return self._model.name_is_valid(name) - self._tree_view = tree_view - self._model = None - self._skip_next_hide = False + def _on_filter_change(self, text): + self._proxy_model.setFilterFixedString(text) - if model: - self.setModel(model) + def _on_cancel_clicked(self): + self.done(0) - # Create `lineEdit` to be able set asset names that are not available - # or for multiselection. - self.setEditable(True) - # Set `lineEdit` to read only - self.lineEdit().setReadOnly(True) - self.lineEdit().setAttribute( - QtCore.Qt.WA_TransparentForMouseEvents, True - ) - - def setModel(self, model): - self._model = model - super(TreeComboBox, self).setModel(model) - - def showPopup(self): - super(TreeComboBox, self).showPopup() - - def hidePopup(self): - if self._skip_next_hide: - self._skip_next_hide = False - else: - super(TreeComboBox, self).hidePopup() - - def select_index(self, index): - parent_indexes = [] - parent_index = index.parent() - while parent_index.isValid(): - parent_indexes.append(parent_index) - parent_index = parent_index.parent() - - for parent_index in parent_indexes: - self._tree_view.expand(parent_index) - selection_model = self._tree_view.selectionModel() - selection_model.setCurrentIndex( - index, selection_model.ClearAndSelect - ) - self.lineEdit().setText(index.data(QtCore.Qt.DisplayRole) or "") - - def eventFilter(self, obj, event): - if ( - event.type() == QtCore.QEvent.MouseButtonPress - and obj is self._tree_view.viewport() - ): - index = self._tree_view.indexAt(event.pos()) - self._skip_next_hide = not ( - self._tree_view.visualRect(index).contains(event.pos()) - ) - return False - - def set_selected_item(self, item_name): - index = self._model.get_index_by_name(item_name) + def _on_ok_clicked(self): + index = self._asset_view.currentIndex() + asset_name = None if index.isValid(): - self._tree_view.selectionModel().setCurrentIndex( - index, QtCore.QItemSelectionModel.SelectCurrent - ) - self.select_index(index) + asset_name = index.data(QtCore.Qt.DisplayRole) + self._selected_asset = asset_name + self.done(1) - else: - self.lineEdit().setText(item_name) + def set_selected_assets(self, asset_names): + self.reset(False) + self._asset_view.collapseAll() + self._filter_input.setText("") + + indexes = [] + for asset_name in asset_names: + index = self._model.get_index_by_name(asset_name) + if index.isValid(): + indexes.append(index) + + if not indexes: + return + + index_deque = collections.deque() + for index in indexes: + index_deque.append(index) + + all_indexes = [] + while index_deque: + index = index_deque.popleft() + all_indexes.append(index) + + parent_index = index.parent() + if parent_index.isValid(): + index_deque.append(parent_index) + + for index in all_indexes: + proxy_index = self._proxy_model.mapFromSource(index) + self._asset_view.expand(proxy_index) + + def get_selected_asset(self): + return self._selected_asset -class AssetsTreeComboBox(TreeComboBox): +class AssetNameInput(QtWidgets.QLineEdit): + clicked = QtCore.Signal() + + def __init__(self, *args, **kwargs): + super(AssetNameInput, self).__init__(*args, **kwargs) + self.setObjectName("AssetNameInput") + self._mouse_pressed = False + + def mousePressEvent(self, event): + if event.button() == QtCore.Qt.LeftButton: + self._mouse_pressed = True + event.accept() + + def mouseMoveEvent(self, event): + event.accept() + + def mouseReleaseEvent(self, event): + if self._mouse_pressed: + self._mouse_pressed = False + if self.rect().contains(event.pos()): + self.clicked.emit() + event.accept() + + def mouseDoubleClickEvent(self, event): + event.accept() + + +class AssetsField(ClickableFrame): value_changed = QtCore.Signal() def __init__(self, controller, parent): - model = AssetsHierarchyModel(controller) + super(AssetsField, self).__init__(parent) - super(AssetsTreeComboBox, self).__init__(model, parent) - self.setObjectName("AssetsTreeComboBox") + dialog = AssetsDialog(controller, self) - self.currentIndexChanged.connect(self._on_index_change) + name_input = AssetNameInput(self) + name_input.setReadOnly(True) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(name_input, 1) + + name_input.clicked.connect(self._mouse_release_callback) + dialog.finished.connect(self._on_dialog_finish) + + self._dialog = dialog + self._name_input = name_input - self._ignore_index_change = False - self._selected_items = [] self._origin_value = [] + self._origin_selection = [] + self._selected_items = [] self._has_value_changed = False - self._model = model self._is_valid = True - self._multiselection_text = None - model.reset() - - def set_multiselection_text(self, text): - self._multiselection_text = text - - def _on_index_change(self): - if self._ignore_index_change: + def _on_dialog_finish(self, result): + if not result: return - self._set_is_valid(True) - self._selected_items = [self.currentText()] + asset_name = self._dialog.get_selected_asset() + if asset_name is None: + return + + self._selected_items = [asset_name] self._has_value_changed = ( self._origin_value != self._selected_items ) + self.set_text(asset_name) + self._set_is_valid(True) + self.value_changed.emit() + def _mouse_release_callback(self): + self._dialog.set_selected_assets(self._selected_items) + self._dialog.open() + + def set_multiselection_text(self, text): + self._multiselection_text = text + def _set_is_valid(self, valid): if valid == self._is_valid: return @@ -562,10 +513,10 @@ class AssetsTreeComboBox(TreeComboBox): self._set_state_property(state) def _set_state_property(self, state): - current_value = self.property("state") + current_value = self._name_input.property("state") if current_value != state: - self.setProperty("state", state) - self.style().polish(self) + self._name_input.setProperty("state", state) + self._name_input.style().polish(self._name_input) def is_valid(self): return self._is_valid @@ -576,38 +527,37 @@ class AssetsTreeComboBox(TreeComboBox): def get_selected_items(self): return list(self._selected_items) + def set_text(self, text): + self._name_input.setText(text) + def set_selected_items(self, asset_names=None): if asset_names is None: asset_names = [] - self._ignore_index_change = True - self._has_value_changed = False self._origin_value = list(asset_names) self._selected_items = list(asset_names) is_valid = True if not asset_names: - self.set_selected_item("") + self.set_text("") elif len(asset_names) == 1: asset_name = tuple(asset_names)[0] - is_valid = self._model.name_is_valid(asset_name) - self.set_selected_item(asset_name) + is_valid = self._dialog.name_is_valid(asset_name) + self.set_text(asset_name) else: for asset_name in asset_names: - is_valid = self._model.name_is_valid(asset_name) + is_valid = self._dialog.name_is_valid(asset_name) if not is_valid: break multiselection_text = self._multiselection_text if multiselection_text is None: multiselection_text = "|".join(asset_names) - self.set_selected_item(multiselection_text) + self.set_text(multiselection_text) self._set_is_valid(is_valid) - self._ignore_index_change = False - def reset_to_origin(self): self.set_selected_items(self._origin_value) @@ -958,7 +908,7 @@ class GlobalAttrsWidget(QtWidgets.QWidget): self._current_instances = [] variant_input = VariantInputWidget(self) - asset_value_widget = AssetsTreeComboBox(controller, self) + asset_value_widget = AssetsField(controller, self) task_value_widget = TasksCombobox(controller, self) family_value_widget = MultipleItemWidget(self) subset_value_widget = MultipleItemWidget(self) From a44ada956ee59ccc16672092f312b8424dfb60b7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Sep 2021 19:27:04 +0200 Subject: [PATCH 514/736] fix reset of asset model --- openpype/tools/new_publisher/widgets/widgets.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 769aa7adcc..acdc6e696a 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -373,6 +373,7 @@ class AssetsDialog(QtWidgets.QDialog): self._model.reset() def name_is_valid(self, name): + self.reset(False) return self._model.name_is_valid(name) def _on_filter_change(self, text): From eaba10a3369e7a21fc667a0bd181d30d0d8e0f83 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 13 Sep 2021 12:11:39 +0200 Subject: [PATCH 515/736] implemented `pop` method for `PublishAttributes` --- openpype/pipeline/create/context.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 3fb001c2a9..9966badb23 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -116,6 +116,9 @@ class AttributeValues: def pop(self, key, default=None): return self._data.pop(key, default) + def reset_values(self): + self._data = [] + @property def attr_defs(self): return self._attr_defs @@ -165,6 +168,7 @@ class PublishAttributes: self._data = {} self._plugin_names_order = [] + self._missing_plugins = [] data = copy.deepcopy(origin_data) added_keys = set() for plugin in attr_plugins: @@ -185,6 +189,7 @@ class PublishAttributes: for key, value in data.items(): if key not in added_keys: + self._missing_plugins.append(key) self._data[key] = PublishAttributeValues( self, [], value, value ) @@ -205,11 +210,20 @@ class PublishAttributes: return self._data.items() def pop(self, key, default=None): - # TODO implement if key not in self._data: return default - # if key not in self._plugin_keys: + if key in self._missing_plugins: + self._missing_plugins.remove(key) + removed_item = self._data.pop(key) + return removed_item.data_to_store() + + value_item = self._data[key] + # Prepare value to return + output = value_item.data_to_store() + # Reset values + value_item.reset_values() + return output def plugin_names_order(self): for name in self._plugin_names_order: From fc47ef8e816c08f744f0c094e76afd94f9c03df1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 13 Sep 2021 13:53:02 +0200 Subject: [PATCH 516/736] set attribute before initialization --- openpype/hosts/testhost/run_publish.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/testhost/run_publish.py b/openpype/hosts/testhost/run_publish.py index 21179294e1..1bb9c46806 100644 --- a/openpype/hosts/testhost/run_publish.py +++ b/openpype/hosts/testhost/run_publish.py @@ -58,8 +58,8 @@ def main(): avalon.api.install(testhost) + QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling) app = QtWidgets.QApplication([]) - app.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling) window = PublisherWindow() window.show() app.exec_() From 40ab7d7a5ccaf9ff8efac63778e344cbe0bf47e5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 13 Sep 2021 14:03:53 +0200 Subject: [PATCH 517/736] changed style of card widgets --- openpype/style/style.css | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/style/style.css b/openpype/style/style.css index 9ce5db2ec5..b6f39e4849 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -688,8 +688,11 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background: {color:bg-buttons}; border-radius: 0.2em; } +#CardViewWidget:hover { + background: {color:bg-button-hover}; +} #CardViewWidget[state="selected"] { - background: {color:bg-inputs}; + background: {color:bg-view-selection}; } #PublishFrame { From 8131613bfac4e0ff2e0fb6912718556d3ce536e9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 13 Sep 2021 18:01:23 +0200 Subject: [PATCH 518/736] added few docstrings --- openpype/pipeline/create/context.py | 60 +++++++++++++++++++++ openpype/pipeline/create/creator_plugins.py | 5 +- 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 9966badb23..b6a3077f8e 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -15,6 +15,7 @@ from openpype.api import ( class HostMissRequiredMethod(Exception): + """Host does not have implemented required functions for creation.""" def __init__(self, host, missing_methods): self.missing_methods = missing_methods self.host = host @@ -52,6 +53,16 @@ class InstanceMember: class AttributeValues: + """Container which keep values of Attribute definitions. + + Goal is to have one object which hold values of attribute definitions for + single instance. + + Args: + attr_defs(AbtractAttrDef): Defintions of value type and properties. + values(dict): Values after possible conversion. + origin_data(dict): Values loaded from host before conversion. + """ def __init__(self, attr_defs, values, origin_data=None): if origin_data is None: origin_data = copy.deepcopy(values) @@ -143,12 +154,26 @@ class AttributeValues: class FamilyAttributeValues(AttributeValues): + """Family specific attribute values of an instance. + + Args: + instance (CreatedInstance): Instance for which are values hold. + """ def __init__(self, instance, *args, **kwargs): self.instance = instance super(FamilyAttributeValues, self).__init__(*args, **kwargs) class PublishAttributeValues(AttributeValues): + """Publish plugin specific attribute values. + + Values are for single plugin which can be on `CreatedInstance` + or context values stored on `CreateContext`. + + Args: + publish_attributes(PublishAttributes): Wrapper for multiple publish + attributes is used as parent object. + """ def __init__(self, publish_attributes, *args, **kwargs): self.publish_attributes = publish_attributes super(PublishAttributeValues, self).__init__(*args, **kwargs) @@ -159,6 +184,17 @@ class PublishAttributeValues(AttributeValues): class PublishAttributes: + """Wrapper for publish plugin attribute definitions. + + Cares about handling attribute definitions of multiple publish plugins. + + Args: + parent(CreatedInstance, CreateContext): Parent for which will be + data stored and from which are data loaded. + origin_data(dict): Loaded data by plugin class name. + attr_plugins(list): List of publish plugins that may have defined + attribute definitions. + """ def __init__(self, parent, origin_data, attr_plugins=None): self.parent = parent self._origin_data = copy.deepcopy(origin_data) @@ -210,6 +246,15 @@ class PublishAttributes: return self._data.items() def pop(self, key, default=None): + """Remove or reset value for plugin. + + Plugin values are reset to defaults if plugin is available but + data of plugin which was not found are removed. + + Args: + key(str): Plugin name. + default: Default value if plugin was not found. + """ if key not in self._data: return default @@ -345,43 +390,58 @@ class CreatedInstance: @property def has_set_asset(self): + """Asset name is set in data.""" return "asset" in self._data @property def has_set_task(self): + """Task name is set in data.""" return "task" in self._data @property def has_valid_context(self): + """Context data are valid for publishing.""" return self.has_valid_asset and self.has_valid_task @property def has_valid_asset(self): + """Asset set in context exists in project.""" if not self.has_set_asset: return False return self._asset_is_valid @property def has_valid_task(self): + """Task set in context exists in project.""" if not self.has_set_task: return False return self._task_is_valid def set_asset_invalid(self, invalid): + # TODO replace with `set_asset_name` self._asset_is_valid = not invalid def set_task_invalid(self, invalid): + # TODO replace with `set_task_name` self._task_is_valid = not invalid @property def id(self): + """Instance identifier.""" return self._data["uuid"] @property def data(self): + """Pointer to data. + + TODO: + Define class handling which keys are change to what. + - this is dangerous as it is possible to modify any key (e.g. `uuid`) + """ return self._data def changes(self): + """Calculate and return changes.""" changes = {} new_keys = set() for key, new_value in self._data.items(): diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index c67f04c9e8..abf25956a8 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -195,7 +195,10 @@ class BaseCreator: class Creator(BaseCreator): - """""" + """Creator that has more information for artist. + + Creation requires prepared subset name and instance data. + """ # Label shown in UI label = None From d4de5d396e62608e8e8aaf77591181bcd7121853 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 13 Sep 2021 18:20:02 +0200 Subject: [PATCH 519/736] added execution of autocreators --- openpype/pipeline/create/context.py | 24 +++++++++++++++++++++++- openpype/tools/new_publisher/control.py | 1 + 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index b6a3077f8e..46a8a05ca4 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -6,7 +6,10 @@ import inspect from uuid import uuid4 from ..lib import UnknownDef -from .creator_plugins import BaseCreator +from .creator_plugins import ( + BaseCreator, + AutoCreator +) from openpype.api import ( get_system_settings, @@ -591,6 +594,8 @@ class CreateContext: self.reset_context_data() self.reset_instances() + self.execute_autocreators() + def reset_plugins(self): import avalon.api import pyblish.logic @@ -734,6 +739,23 @@ class CreateContext: self.instances = instances + def execute_autocreators(self): + for creator in self.get_autocreators(): + try: + creator.create() + except Exception: + msg = ( + "Failed to run AutoCreator with family \"{}\" ({})." + ).format(creator.family, inspect.getfile(creator.__class__)) + self.log.warning(msg, exc_info=True) + + def get_autocreators(self): + autocreators = [] + for creator in self.creators.values(): + if isinstance(creator, AutoCreator): + autocreators.append(creator) + return autocreators + def save_changes(self): if not self.host_is_valid: missing_methods = self.get_host_misssing_methods(self.host) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 5780f086fe..848d120366 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -484,6 +484,7 @@ class PublisherController: # Publish part must be resetted after plugins self._reset_publish() self._reset_instances() + self.create_context.execute_autocreators() def _reset_plugins(self): """Reset to initial state.""" From a5391f82cb996471ced4e5b10815303a5bb065e0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 13 Sep 2021 18:34:30 +0200 Subject: [PATCH 520/736] store autocreators and ui_creators on discovery --- openpype/pipeline/create/context.py | 37 +++++++++++++-------- openpype/pipeline/create/creator_plugins.py | 2 +- openpype/tools/new_publisher/control.py | 4 +++ 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 46a8a05ca4..27fdeb03f4 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -8,6 +8,7 @@ from uuid import uuid4 from ..lib import UnknownDef from .creator_plugins import ( BaseCreator, + Creator, AutoCreator ) @@ -558,7 +559,12 @@ class CreateContext: self.instances = [] + # Discovered creators self.creators = {} + # Prepare categories of creators + self.autocreators = {} + self.ui_creators = {} + self.publish_discover_result = None self.publish_plugins = [] self.plugins_with_defs = [] @@ -630,18 +636,30 @@ class CreateContext: # Discover and prepare creators creators = {} - for creator in avalon.api.discover(BaseCreator): - if inspect.isabstract(creator): + autocreators = {} + ui_creators = {} + for creator_class in avalon.api.discover(BaseCreator): + if inspect.isabstract(creator_class): self.log.info( - "Skipping abstract Creator {}".format(str(creator)) + "Skipping abstract Creator {}".format(str(creator_class)) ) continue - creators[creator.family] = creator( + + family = creator_class.family + creator = creator_class( self, system_settings, project_settings, self.headless ) + creators[family] = creator + if isinstance(creator, AutoCreator): + autocreators[family] = creator + elif isinstance(creator, Creator): + ui_creators[family] = creator + + self.autocreators = autocreators + self.ui_creators = ui_creators self.creators = creators @@ -740,22 +758,15 @@ class CreateContext: self.instances = instances def execute_autocreators(self): - for creator in self.get_autocreators(): + for family, creator in self.autocreators.items(): try: creator.create() except Exception: msg = ( "Failed to run AutoCreator with family \"{}\" ({})." - ).format(creator.family, inspect.getfile(creator.__class__)) + ).format(family, inspect.getfile(creator.__class__)) self.log.warning(msg, exc_info=True) - def get_autocreators(self): - autocreators = [] - for creator in self.creators.values(): - if isinstance(creator, AutoCreator): - autocreators.append(creator) - return autocreators - def save_changes(self): if not self.host_is_valid: missing_methods = self.get_host_misssing_methods(self.host) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index abf25956a8..0688d470bd 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -195,7 +195,7 @@ class BaseCreator: class Creator(BaseCreator): - """Creator that has more information for artist. + """Creator that has more information for artist to show in UI. Creation requires prepared subset name and instance data. """ diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 848d120366..9834ca3c94 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -383,6 +383,10 @@ class PublisherController: def creators(self): return self.create_context.creators + @property + def ui_creators(self): + return self.create_context.ui_creators + @property def host_is_valid(self): return self.create_context.host_is_valid From b667ca34d38932e6decb344e5f9092cad0708875 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 13 Sep 2021 18:52:18 +0200 Subject: [PATCH 521/736] reset instances if autocreators were executed --- openpype/pipeline/create/context.py | 5 +++++ openpype/tools/new_publisher/control.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 27fdeb03f4..771a4e8acb 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -758,15 +758,20 @@ class CreateContext: self.instances = instances def execute_autocreators(self): + any_processed = False for family, creator in self.autocreators.items(): try: creator.create() + any_processed = True except Exception: msg = ( "Failed to run AutoCreator with family \"{}\" ({})." ).format(family, inspect.getfile(creator.__class__)) self.log.warning(msg, exc_info=True) + if any_processed: + self.reset_instances() + def save_changes(self): if not self.host_is_valid: missing_methods = self.get_host_misssing_methods(self.host) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 9834ca3c94..b655a0cae9 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -488,7 +488,6 @@ class PublisherController: # Publish part must be resetted after plugins self._reset_publish() self._reset_instances() - self.create_context.execute_autocreators() def _reset_plugins(self): """Reset to initial state.""" @@ -511,6 +510,7 @@ class PublisherController: self.create_context.reset_context_data() self.create_context.reset_instances() + self.create_context.execute_autocreators() self._resetting_instances = False From d5c3deb7c72047f3f1bce201ba4830bea16c8fc5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 13 Sep 2021 18:52:40 +0200 Subject: [PATCH 522/736] fixed create dialog --- openpype/tools/new_publisher/widgets/create_dialog.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/create_dialog.py b/openpype/tools/new_publisher/widgets/create_dialog.py index 6a8f68e86a..05ca764c91 100644 --- a/openpype/tools/new_publisher/widgets/create_dialog.py +++ b/openpype/tools/new_publisher/widgets/create_dialog.py @@ -329,7 +329,7 @@ class CreateDialog(QtWidgets.QDialog): # Add new families new_families = set() - for family in self.controller.creators.keys(): + for family in self.controller.ui_creators.keys(): # TODO add details about creator new_families.add(family) if family not in existing_items: @@ -363,7 +363,7 @@ class CreateDialog(QtWidgets.QDialog): if new_index.isValid(): family = new_index.data(QtCore.Qt.DisplayRole) - creator = self.controller.creators.get(family) + creator = self.controller.ui_creators.get(family) self.creator_description_widget.set_plugin(creator) From 60167e60286b80cf72cfe8543bbf0ac921306198 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 13 Sep 2021 18:53:00 +0200 Subject: [PATCH 523/736] moved variants to Creator --- openpype/pipeline/create/creator_plugins.py | 58 ++++++++++----------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 0688d470bd..bf8eb6af89 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -39,10 +39,6 @@ class BaseCreator: # Creator is enabled (Probably does not have reason of existence?) enabled = True - # GUI Purposes - # - default_variants may not be used if `get_default_variants` is overriden - default_variants = [] - # Creator (and family) icon # - may not be used if `get_icon` is reimplemented icon = None @@ -85,31 +81,6 @@ class BaseCreator: """ return self.icon - def get_default_variants(self): - """Default variant values for UI tooltips. - - Replacement of `defatults` attribute. Using method gives ability to - have some "logic" other than attribute values. - - By default returns `default_variants` value. - - Returns: - list: Whisper variants for user input. - """ - return copy.deepcopy(self.default_variants) - - def get_default_variant(self): - """Default variant value that will be used to prefill variant input. - - This is for user input and value may not be content of result from - `get_default_variants`. - - Can return `None`. In that case first element from - `get_default_variants` should be used. - """ - - return None - def get_dynamic_data( self, variant, task_name, asset_doc, project_name, host_name ): @@ -202,6 +173,10 @@ class Creator(BaseCreator): # Label shown in UI label = None + # GUI Purposes + # - default_variants may not be used if `get_default_variants` is overriden + default_variants = [] + # Short description of family # - may not be used if `get_description` is overriden description = None @@ -244,6 +219,31 @@ class Creator(BaseCreator): """ return self.detailed_description + def get_default_variants(self): + """Default variant values for UI tooltips. + + Replacement of `defatults` attribute. Using method gives ability to + have some "logic" other than attribute values. + + By default returns `default_variants` value. + + Returns: + list: Whisper variants for user input. + """ + return copy.deepcopy(self.default_variants) + + def get_default_variant(self): + """Default variant value that will be used to prefill variant input. + + This is for user input and value may not be content of result from + `get_default_variants`. + + Can return `None`. In that case first element from + `get_default_variants` should be used. + """ + + return None + class AutoCreator(BaseCreator): """Creator which is automatically triggered without user interaction. From 87c2524397966e9f2859d7b3b4048cfd95972906 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 13 Sep 2021 18:53:13 +0200 Subject: [PATCH 524/736] added auto creator --- .../testhost/plugins/create/auto_creator.py | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 openpype/hosts/testhost/plugins/create/auto_creator.py diff --git a/openpype/hosts/testhost/plugins/create/auto_creator.py b/openpype/hosts/testhost/plugins/create/auto_creator.py new file mode 100644 index 0000000000..40cab97839 --- /dev/null +++ b/openpype/hosts/testhost/plugins/create/auto_creator.py @@ -0,0 +1,65 @@ +from openpype import resources +from openpype.hosts.testhost import api +from openpype.pipeline import ( + AutoCreator, + CreatedInstance, + lib +) +from avalon import io + + +class MyAutoCreator(AutoCreator): + family = "workfile" + + def create(self, options=None): + existing_instance = None + for instance in self.create_context.instances: + if instance.family == self.family: + existing_instance = instance + break + + variant = "Main" + project_name = io.Session["AVALON_PROJECT"] + asset_name = io.Session["AVALON_ASSET"] + task_name = io.Session["AVALON_TASK"] + host_name = io.Session["AVALON_APP"] + + if existing_instance is None: + asset_doc = io.find_one({"type": "asset", "name": asset_name}) + subset_name = self.get_subset_name( + variant, task_name, asset_doc, project_name, host_name + ) + data = { + "asset": asset_name, + "task": task_name, + "variant": variant + } + data.update(self.get_dynamic_data( + variant, task_name, asset_doc, project_name, host_name + )) + + existing_instance = CreatedInstance( + self.family, subset_name, data, self + ) + api.pipeline.HostContext.add_instance( + existing_instance.data_to_store() + ) + + elif ( + existing_instance.data["asset"] != asset_name + or existing_instance.data["task"] != task_name + ): + asset_doc = io.find_one({"type": "asset", "name": asset_name}) + subset_name = self.get_subset_name( + variant, task_name, asset_doc, project_name, host_name + ) + existing_instance.data["asset"] = asset_name + existing_instance.data["task"] = task_name + + return existing_instance + + def get_attribute_defs(self): + output = [ + lib.NumberDef("number_key", label="Number") + ] + return output From 65ee426d6eba91fa3dec5190dc5ec6e8c120bc25 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 13 Sep 2021 19:12:30 +0200 Subject: [PATCH 525/736] fixed removing in card view --- .../new_publisher/widgets/card_view_widgets.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/openpype/tools/new_publisher/widgets/card_view_widgets.py b/openpype/tools/new_publisher/widgets/card_view_widgets.py index 167d49707f..eeacb71fc0 100644 --- a/openpype/tools/new_publisher/widgets/card_view_widgets.py +++ b/openpype/tools/new_publisher/widgets/card_view_widgets.py @@ -21,6 +21,7 @@ from ..constants import ( class FamilyWidget(QtWidgets.QWidget): selected = QtCore.Signal(str, str) active_changed = QtCore.Signal() + removed = QtCore.Signal(str) def __init__(self, family, family_icon, parent): super(FamilyWidget, self).__init__(parent) @@ -72,6 +73,7 @@ class FamilyWidget(QtWidgets.QWidget): widget = self._widgets_by_id.pop(instance_id) widget.setVisible(False) + self.removed.emit(instance_id) self._content_layout.removeWidget(widget) widget.deleteLater() @@ -294,6 +296,7 @@ class InstanceCardView(AbstractInstanceView): self._context_widget = None self._selected_widget = None + self._selected_widget_id = None self.setSizePolicy( QtWidgets.QSizePolicy.Minimum, @@ -321,6 +324,7 @@ class InstanceCardView(AbstractInstanceView): self._content_layout.insertWidget(0, widget) self._context_widget = widget self._selected_widget = widget + self._selected_widget_id = CONTEXT_ID instances_by_family = collections.defaultdict(list) for instance in self.controller.instances: @@ -346,6 +350,7 @@ class InstanceCardView(AbstractInstanceView): ) family_widget.active_changed.connect(self._on_active_changed) family_widget.selected.connect(self._on_widget_selection) + family_widget.removed.connect(self._on_remove) self._content_layout.insertWidget(widget_idx, family_widget) self._widgets_by_family[family] = family_widget else: @@ -373,12 +378,21 @@ class InstanceCardView(AbstractInstanceView): if self._selected_widget is not None: self._selected_widget.set_selected(False) + self._selected_widget_id = widget_id self._selected_widget = new_widget if new_widget is not None: new_widget.set_selected(True) self.selection_changed.emit() + def _on_remove(self, widget_id): + if widget_id != self._selected_widget_id: + return + + self._selected_widget = self._context_widget + self._selected_widget_id = CONTEXT_ID + self._context_widget.set_selected(True) + def get_selected_items(self): instances = [] context_selected = False From 9c7b79c951e3147529b07b1748d61b2dcc9b88f4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 13 Sep 2021 19:21:24 +0200 Subject: [PATCH 526/736] added base of color difference in invalid items --- openpype/style/style.css | 4 ++++ .../new_publisher/widgets/list_view_widgets.py | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/openpype/style/style.css b/openpype/style/style.css index b6f39e4849..32b74d8ece 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -695,6 +695,10 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background: {color:bg-view-selection}; } +#ListViewSubsetName[state="invalid"] { + color: {color:publish-error}; +} + #PublishFrame { background: rgba(0, 0, 0, 127); } diff --git a/openpype/tools/new_publisher/widgets/list_view_widgets.py b/openpype/tools/new_publisher/widgets/list_view_widgets.py index aefcc97236..98ade8df83 100644 --- a/openpype/tools/new_publisher/widgets/list_view_widgets.py +++ b/openpype/tools/new_publisher/widgets/list_view_widgets.py @@ -21,6 +21,8 @@ class InstanceListItemWidget(QtWidgets.QWidget): self.instance = instance subset_name_label = QtWidgets.QLabel(instance.data["subset"], self) + subset_name_label.setObjectName("ListViewSubsetName") + active_checkbox = NiceCheckbox(parent=self) active_checkbox.setChecked(instance.data["active"]) @@ -40,6 +42,20 @@ class InstanceListItemWidget(QtWidgets.QWidget): self.subset_name_label = subset_name_label self.active_checkbox = active_checkbox + self._has_valid_context = None + + self._set_valid_property(instance.has_valid_context) + + def _set_valid_property(self, valid): + if self._has_valid_context == valid: + return + self._has_valid_context = valid + state = "" + if not valid: + state = "invalid" + self.subset_name_label.setProperty("state", state) + self.subset_name_label.style().polish(self.subset_name_label) + def set_active(self, new_value): checkbox_value = self.active_checkbox.isChecked() instance_value = self.instance.data["active"] @@ -58,6 +74,7 @@ class InstanceListItemWidget(QtWidgets.QWidget): def update_instance_values(self): self.set_active(self.instance.data["active"]) + self._set_valid_property(self.instance.has_valid_context) def _on_active_change(self): new_value = self.active_checkbox.isChecked() From b8de65e05a251ce9da3524c30a8db036df4ce7b9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 13 Sep 2021 19:24:52 +0200 Subject: [PATCH 527/736] expand families with instances with invalid context --- .../tools/new_publisher/widgets/list_view_widgets.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/openpype/tools/new_publisher/widgets/list_view_widgets.py b/openpype/tools/new_publisher/widgets/list_view_widgets.py index 98ade8df83..cac2244e08 100644 --- a/openpype/tools/new_publisher/widgets/list_view_widgets.py +++ b/openpype/tools/new_publisher/widgets/list_view_widgets.py @@ -273,6 +273,7 @@ class InstanceListView(AbstractInstanceView): widget = self._group_widgets.pop(family) widget.deleteLater() + expand_families = set() for family, group_item in self._group_items.items(): to_remove = set() existing_mapping = {} @@ -320,6 +321,8 @@ class InstanceListView(AbstractInstanceView): group_item.appendRows(new_items) for item, instance in new_items_with_instance: + if not instance.has_valid_context: + expand_families.add(family) item_index = self.instance_model.index( item.row(), item.column(), @@ -337,6 +340,12 @@ class InstanceListView(AbstractInstanceView): if sort_at_the_end: self.proxy_model.sort(0) + for family in expand_families: + family_item = self._group_items[family] + proxy_index = self.proxy_model.mapFromSource(family_item.index()) + + self.instance_view.expand(proxy_index) + def refresh_instance_states(self): for widget in self._widgets_by_id.values(): widget.update_instance_values() From b90526597a4caebedb8f212a6294c2fa47439fb7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 14 Sep 2021 09:18:52 +0200 Subject: [PATCH 528/736] hound fixes --- openpype/hosts/testhost/api/pipeline.py | 1 - openpype/hosts/testhost/plugins/create/auto_creator.py | 1 - openpype/hosts/testhost/plugins/publish/collect_instance_1.py | 2 -- openpype/tools/new_publisher/constants.py | 2 +- 4 files changed, 1 insertion(+), 5 deletions(-) diff --git a/openpype/hosts/testhost/api/pipeline.py b/openpype/hosts/testhost/api/pipeline.py index 442c6eeba0..a154ca18b7 100644 --- a/openpype/hosts/testhost/api/pipeline.py +++ b/openpype/hosts/testhost/api/pipeline.py @@ -1,6 +1,5 @@ import os import json -import collections class HostContext: diff --git a/openpype/hosts/testhost/plugins/create/auto_creator.py b/openpype/hosts/testhost/plugins/create/auto_creator.py index 40cab97839..c578fae74b 100644 --- a/openpype/hosts/testhost/plugins/create/auto_creator.py +++ b/openpype/hosts/testhost/plugins/create/auto_creator.py @@ -1,4 +1,3 @@ -from openpype import resources from openpype.hosts.testhost import api from openpype.pipeline import ( AutoCreator, diff --git a/openpype/hosts/testhost/plugins/publish/collect_instance_1.py b/openpype/hosts/testhost/plugins/publish/collect_instance_1.py index eee12d02fb..3c035eccb6 100644 --- a/openpype/hosts/testhost/plugins/publish/collect_instance_1.py +++ b/openpype/hosts/testhost/plugins/publish/collect_instance_1.py @@ -1,8 +1,6 @@ import json import pyblish.api -from avalon import io -from openpype.hosts.testhost import api from openpype.pipeline import ( OpenPypePyblishPluginMixin, attribute_definitions diff --git a/openpype/tools/new_publisher/constants.py b/openpype/tools/new_publisher/constants.py index a44e425cfb..4cb93313ac 100644 --- a/openpype/tools/new_publisher/constants.py +++ b/openpype/tools/new_publisher/constants.py @@ -6,7 +6,7 @@ CONTEXT_LABEL = "Options" # Allowed symbols for subset name (and variant) # - characters, numbers, unsercore and dash -SUBSET_NAME_ALLOWED_SYMBOLS = "a-zA-Z0-9_\." +SUBSET_NAME_ALLOWED_SYMBOLS = "a-zA-Z0-9_." VARIANT_TOOLTIP = ( "Variant may contain alphabetical characters (a-Z)" "\nnumerical characters (0-9) dot (\".\") or underscore (\"_\")." From f6daa24542a94b2dc1aed969e97049d438d072aa Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 14 Sep 2021 09:32:06 +0200 Subject: [PATCH 529/736] use nice checkbox in report viewer --- .../publish_report_viewer/widgets.py | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/openpype/tools/new_publisher/publish_report_viewer/widgets.py b/openpype/tools/new_publisher/publish_report_viewer/widgets.py index 9279e77d53..24f1d33d0e 100644 --- a/openpype/tools/new_publisher/publish_report_viewer/widgets.py +++ b/openpype/tools/new_publisher/publish_report_viewer/widgets.py @@ -3,6 +3,8 @@ import uuid from Qt import QtWidgets, QtCore +from openpype.widgets.nice_checkbox import NiceCheckbox + from .constants import ( ITEM_ID_ROLE, ITEM_IS_GROUP_ROLE @@ -158,10 +160,16 @@ class PublishReportViewerWidget(QtWidgets.QWidget): plugins_proxy = PluginProxyModel() plugins_proxy.setSourceModel(plugins_model) - removed_instances_check = QtWidgets.QCheckBox( + removed_instances_check = NiceCheckbox(parent=self) + removed_instances_check.setChecked(instances_proxy.ignore_removed) + removed_instances_label = QtWidgets.QLabel( "Hide removed instances", self ) - removed_instances_check.setChecked(instances_proxy.ignore_removed) + + removed_instances_layout = QtWidgets.QHBoxLayout() + removed_instances_layout.setContentsMargins(0, 0, 0, 0) + removed_instances_layout.addWidget(removed_instances_check, 0) + removed_instances_layout.addWidget(removed_instances_label, 1) instances_view = QtWidgets.QTreeView(self) instances_view.setObjectName("PublishDetailViews") @@ -174,10 +182,14 @@ class PublishReportViewerWidget(QtWidgets.QWidget): instances_delegate = GroupItemDelegate(instances_view) instances_view.setItemDelegate(instances_delegate) - skipped_plugins_check = QtWidgets.QCheckBox( - "Hide skipped plugins", self - ) + skipped_plugins_check = NiceCheckbox(parent=self) skipped_plugins_check.setChecked(plugins_proxy.ignore_skipped) + skipped_plugins_label = QtWidgets.QLabel("Hide skipped plugins", self) + + skipped_plugins_layout = QtWidgets.QHBoxLayout() + skipped_plugins_layout.setContentsMargins(0, 0, 0, 0) + skipped_plugins_layout.addWidget(skipped_plugins_check, 0) + skipped_plugins_layout.addWidget(skipped_plugins_label, 1) plugins_view = QtWidgets.QTreeView(self) plugins_view.setObjectName("PublishDetailViews") @@ -194,8 +206,8 @@ class PublishReportViewerWidget(QtWidgets.QWidget): layout = QtWidgets.QGridLayout(self) # Row 1 - layout.addWidget(removed_instances_check, 0, 0) - layout.addWidget(skipped_plugins_check, 0, 1) + layout.addLayout(removed_instances_layout, 0, 0) + layout.addLayout(skipped_plugins_layout, 0, 1) # Row 2 layout.addWidget(instances_view, 1, 0) layout.addWidget(plugins_view, 1, 1) From 82ba630bf8af9700a3497d6270a152be63099678 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 14 Sep 2021 14:13:58 +0200 Subject: [PATCH 530/736] explicit check of host attribute --- openpype/pipeline/create/context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 771a4e8acb..c506de49fb 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -324,7 +324,7 @@ class CreatedInstance: self, family, subset_name, data=None, creator=None, host=None, attr_plugins=None, new=True ): - if not host: + if host is None: import avalon.api host = avalon.api.registered_host() From 88ec21b257f9eaa38885c42085adb7e65833d923 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 14 Sep 2021 14:15:31 +0200 Subject: [PATCH 531/736] fix qimage and qbitmap conversion --- openpype/tools/new_publisher/widgets/widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index acdc6e696a..ffb9c23dc2 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -188,7 +188,7 @@ class PublishIconBtn(IconButton): height - (2 * part_h) ) alpha_mask = scaled_image.createAlphaMask() - alpha_region = QtGui.QRegion(QtGui.QBitmap(alpha_mask)) + alpha_region = QtGui.QRegion(QtGui.QBitmap.fromImage(alpha_mask)) alpha_region.translate(part_w, part_h) pixmap = QtGui.QPixmap(width, height) From da17d312ac3cfaac618dbad960fd5d1ef7a0a783 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 14 Sep 2021 14:21:40 +0200 Subject: [PATCH 532/736] prevend from saving changes if host is invalid --- openpype/tools/new_publisher/control.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index b655a0cae9..be923f1255 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -600,7 +600,8 @@ class PublisherController: self._reset_instances() def save_changes(self): - self.create_context.save_changes() + if self.create_context.host_is_valid: + self.create_context.save_changes() def remove_instances(self, instances): # QUESTION Expect that instaces are really removed? In that case save From 773b011b8aeb6b0c8fea58c71045868897f797ea Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 14 Sep 2021 14:53:50 +0200 Subject: [PATCH 533/736] fixed keeping pointer to selected widget which can cause c++ issues --- .../widgets/card_view_widgets.py | 92 +++++++++++++------ 1 file changed, 63 insertions(+), 29 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/card_view_widgets.py b/openpype/tools/new_publisher/widgets/card_view_widgets.py index eeacb71fc0..0d88a3733a 100644 --- a/openpype/tools/new_publisher/widgets/card_view_widgets.py +++ b/openpype/tools/new_publisher/widgets/card_view_widgets.py @@ -21,7 +21,7 @@ from ..constants import ( class FamilyWidget(QtWidgets.QWidget): selected = QtCore.Signal(str, str) active_changed = QtCore.Signal() - removed = QtCore.Signal(str) + removed_selected = QtCore.Signal() def __init__(self, family, family_icon, parent): super(FamilyWidget, self).__init__(parent) @@ -59,6 +59,12 @@ class FamilyWidget(QtWidgets.QWidget): for widget in self._widgets_by_id.values(): widget.update_instance_values() + def confirm_remove_instance_id(self, instance_id): + widget = self._widgets_by_id.pop(instance_id) + widget.setVisible(False) + self._content_layout.removeWidget(widget) + widget.deleteLater() + def update_instances(self, instances): instances_by_id = {} instances_by_subset_name = collections.defaultdict(list) @@ -72,8 +78,10 @@ class FamilyWidget(QtWidgets.QWidget): continue widget = self._widgets_by_id.pop(instance_id) + if widget.is_selected: + self.removed_selected.emit() + widget.setVisible(False) - self.removed.emit(instance_id) self._content_layout.removeWidget(widget) widget.deleteLater() @@ -105,6 +113,10 @@ class CardWidget(ClickableFrame): self._selected = False self._id = None + @property + def is_selected(self): + return self._selected + def set_selected(self, selected): if selected == self._selected: return @@ -292,11 +304,10 @@ class InstanceCardView(AbstractInstanceView): self._content_widget = content_widget self._widgets_by_family = {} - self._family_widgets_by_name = {} self._context_widget = None - self._selected_widget = None - self._selected_widget_id = None + self._selected_instance_family = None + self._selected_instance_id = None self.setSizePolicy( QtWidgets.QSizePolicy.Minimum, @@ -315,16 +326,33 @@ class InstanceCardView(AbstractInstanceView): result.setWidth(width) return result + def _get_selected_widget(self): + if self._selected_instance_id == CONTEXT_ID: + return self._context_widget + + family_widget = self._widgets_by_family.get( + self._selected_instance_family + ) + if family_widget is not None: + widget = family_widget.get_widget_by_instance_id( + self._selected_instance_id + ) + if widget is not None: + return widget + + return None + def refresh(self): - if not self._context_widget: + if self._context_widget is None: widget = ContextCardWidget(self._content_widget) widget.selected.connect(self._on_widget_selection) - widget.set_selected(True) + + self._context_widget = widget + self.selection_changed.emit() self._content_layout.insertWidget(0, widget) - self._context_widget = widget - self._selected_widget = widget - self._selected_widget_id = CONTEXT_ID + + self.select_item(CONTEXT_ID, None) instances_by_family = collections.defaultdict(list) for instance in self.controller.instances: @@ -335,6 +363,8 @@ class InstanceCardView(AbstractInstanceView): if family in instances_by_family: continue + if family == self._selected_instance_family: + self._on_remove_selected() widget = self._widgets_by_family.pop(family) widget.setVisible(False) self._content_layout.removeWidget(widget) @@ -350,7 +380,9 @@ class InstanceCardView(AbstractInstanceView): ) family_widget.active_changed.connect(self._on_active_changed) family_widget.selected.connect(self._on_widget_selection) - family_widget.removed.connect(self._on_remove) + family_widget.removed_selected.connect( + self._on_remove_selected + ) self._content_layout.insertWidget(widget_idx, family_widget) self._widgets_by_family[family] = family_widget else: @@ -365,42 +397,44 @@ class InstanceCardView(AbstractInstanceView): def _on_active_changed(self): self.active_changed.emit() - def _on_widget_selection(self, widget_id, family): - if widget_id == CONTEXT_ID: + def _on_widget_selection(self, instance_id, family): + self.select_item(instance_id, family) + + def select_item(self, instance_id, family): + if instance_id == CONTEXT_ID: new_widget = self._context_widget else: family_widget = self._widgets_by_family[family] - new_widget = family_widget.get_widget_by_instance_id(widget_id) + new_widget = family_widget.get_widget_by_instance_id(instance_id) - if new_widget is self._selected_widget: + selected_widget = self._get_selected_widget() + if new_widget is selected_widget: return - if self._selected_widget is not None: - self._selected_widget.set_selected(False) + if selected_widget is not None: + selected_widget.set_selected(False) - self._selected_widget_id = widget_id - self._selected_widget = new_widget + self._selected_instance_id = instance_id + self._selected_instance_family = family if new_widget is not None: new_widget.set_selected(True) self.selection_changed.emit() - def _on_remove(self, widget_id): - if widget_id != self._selected_widget_id: - return - - self._selected_widget = self._context_widget - self._selected_widget_id = CONTEXT_ID - self._context_widget.set_selected(True) + def _on_remove_selected(self): + selected_widget = self._get_selected_widget() + if selected_widget is None: + self._on_widget_selection(CONTEXT_ID, None) def get_selected_items(self): instances = [] context_selected = False - if self._selected_widget is self._context_widget: + selected_widget = self._get_selected_widget() + if selected_widget is self._context_widget: context_selected = True - elif self._selected_widget is not None: - instances.append(self._selected_widget.instance) + elif selected_widget is not None: + instances.append(selected_widget.instance) return instances, context_selected From 6cd13a67c429dfd38e68c7804a4f953e12bb0192 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 15 Sep 2021 13:13:53 +0200 Subject: [PATCH 534/736] toggling in list view with keyboard keys works as expected --- openpype/tools/new_publisher/constants.py | 4 +- .../widgets/list_view_widgets.py | 78 +++++++++++++++++-- 2 files changed, 73 insertions(+), 9 deletions(-) diff --git a/openpype/tools/new_publisher/constants.py b/openpype/tools/new_publisher/constants.py index 4cb93313ac..a4c4411534 100644 --- a/openpype/tools/new_publisher/constants.py +++ b/openpype/tools/new_publisher/constants.py @@ -15,6 +15,7 @@ VARIANT_TOOLTIP = ( # Roles for instance views INSTANCE_ID_ROLE = QtCore.Qt.UserRole + 1 SORT_VALUE_ROLE = QtCore.Qt.UserRole + 2 +IS_GROUP_ROLE = QtCore.Qt.UserRole + 3 __all__ = ( @@ -24,5 +25,6 @@ __all__ = ( "VARIANT_TOOLTIP", "INSTANCE_ID_ROLE", - "SORT_VALUE_ROLE" + "SORT_VALUE_ROLE", + "IS_GROUP_ROLE" ) diff --git a/openpype/tools/new_publisher/widgets/list_view_widgets.py b/openpype/tools/new_publisher/widgets/list_view_widgets.py index cac2244e08..5057e307e8 100644 --- a/openpype/tools/new_publisher/widgets/list_view_widgets.py +++ b/openpype/tools/new_publisher/widgets/list_view_widgets.py @@ -7,6 +7,7 @@ from .widgets import AbstractInstanceView from ..constants import ( INSTANCE_ID_ROLE, SORT_VALUE_ROLE, + IS_GROUP_ROLE, CONTEXT_ID, CONTEXT_LABEL ) @@ -59,6 +60,8 @@ class InstanceListItemWidget(QtWidgets.QWidget): def set_active(self, new_value): checkbox_value = self.active_checkbox.isChecked() instance_value = self.instance.data["active"] + if new_value is None: + new_value = not instance_value # First change instance value and them change checkbox # - prevent to trigger `active_changed` signal @@ -151,20 +154,63 @@ class InstanceListGroupWidget(QtWidgets.QFrame): self.expand_btn.setArrowType(QtCore.Qt.RightArrow) +class InstanceTreeView(QtWidgets.QTreeView): + toggle_requested = QtCore.Signal(int) + + def __init__(self, *args, **kwargs): + super(InstanceTreeView, self).__init__(*args, **kwargs) + + self.setObjectName("InstanceListView") + self.setHeaderHidden(True) + self.setIndentation(0) + self.setExpandsOnDoubleClick(False) + self.setSelectionMode( + QtWidgets.QAbstractItemView.ExtendedSelection + ) + + self.clicked.connect(self._expand_item) + + def _expand_item(self, index): + if index.data(IS_GROUP_ROLE): + if self.isExpanded(index): + self.collapse(index) + else: + self.expand(index) + + def get_selected_instance_ids(self): + instance_ids = set() + for index in self.selectionModel().selectedIndexes(): + instance_id = index.data(INSTANCE_ID_ROLE) + if instance_id is not None: + instance_ids.add(instance_id) + return instance_ids + + def event(self, event): + if not event.type() == QtCore.QEvent.KeyPress: + pass + + elif event.key() == QtCore.Qt.Key_Space: + self.toggle_requested.emit(-1) + return True + + elif event.key() == QtCore.Qt.Key_Backspace: + self.toggle_requested.emit(0) + return True + + elif event.key() == QtCore.Qt.Key_Return: + self.toggle_requested.emit(1) + return True + + return super(InstanceTreeView, self).event(event) + + class InstanceListView(AbstractInstanceView): def __init__(self, controller, parent): super(InstanceListView, self).__init__(parent) self.controller = controller - instance_view = QtWidgets.QTreeView(self) - instance_view.setObjectName("InstanceListView") - instance_view.setHeaderHidden(True) - instance_view.setIndentation(0) - instance_view.setSelectionMode( - QtWidgets.QAbstractItemView.ExtendedSelection - ) - + instance_view = InstanceTreeView(self) instance_model = QtGui.QStandardItemModel() proxy_model = QtCore.QSortFilterProxyModel() @@ -185,6 +231,7 @@ class InstanceListView(AbstractInstanceView): ) instance_view.collapsed.connect(self._on_collapse) instance_view.expanded.connect(self._on_expand) + instance_view.toggle_requested.connect(self._on_toggle_request) self._group_items = {} self._group_widgets = {} @@ -208,6 +255,20 @@ class InstanceListView(AbstractInstanceView): if group_widget: group_widget.set_expanded(False) + def _on_toggle_request(self, toggle): + selected_instance_ids = self.instance_view.get_selected_instance_ids() + if toggle == -1: + active = None + elif toggle == 1: + active = True + else: + active = False + + for instance_id in selected_instance_ids: + widget = self._widgets_by_id.get(instance_id) + if widget is not None: + widget.set_active(active) + def refresh(self): instances_by_family = collections.defaultdict(list) families = set() @@ -245,6 +306,7 @@ class InstanceListView(AbstractInstanceView): group_item = QtGui.QStandardItem() group_item.setData(family, SORT_VALUE_ROLE) + group_item.setData(True, IS_GROUP_ROLE) group_item.setFlags(QtCore.Qt.ItemIsEnabled) self._group_items[family] = group_item new_group_items.append(group_item) From 59f23d2ed802cdf73297a73df788f16903019c66 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 23 Sep 2021 17:44:58 +0200 Subject: [PATCH 535/736] added few docstrings --- openpype/tools/new_publisher/control.py | 14 ++++++++++++++ .../new_publisher/widgets/border_label_widget.py | 13 +++++++++++++ 2 files changed, 27 insertions(+) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index be923f1255..b64d0cef12 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -22,6 +22,7 @@ PLUGIN_ORDER_OFFSET = 0.5 class MainThreadItem: + """Callback with args and kwargs.""" def __init__(self, callback, *args, **kwargs): self.callback = callback self.args = args @@ -32,6 +33,12 @@ class MainThreadItem: class MainThreadProcess(QtCore.QObject): + """Qt based main thread process executor. + + Has timer which controls each 50ms if there is new item to process. + + This approach gives ability to update UI meanwhile plugin is in progress. + """ def __init__(self): super(MainThreadProcess, self).__init__() self._items_to_process = collections.deque() @@ -68,6 +75,7 @@ class MainThreadProcess(QtCore.QObject): class AssetDocsCache: + """Cache asset documents for creation part.""" projection = { "_id": True, "name": True, @@ -112,6 +120,7 @@ class AssetDocsCache: class PublishReport: + """Report for single publishing process.""" def __init__(self, controller): self.controller = controller self._publish_discover_result = None @@ -178,6 +187,7 @@ class PublishReport: self._current_plugin_data["skipped"] = True def add_result(self, result): + """Handle result of one plugin and it's instance.""" instance = result["instance"] instance_id = None if instance is not None: @@ -188,6 +198,7 @@ class PublishReport: }) def add_action_result(self, action, result): + """Add result of single action.""" plugin = result["plugin"] store_item = self._get_plugin_data_item(plugin) @@ -205,6 +216,7 @@ class PublishReport: }) def get_report(self, publish_plugins=None): + """Report data with all details of current state.""" instances_details = {} for instance in self._all_instances_by_id.values(): instances_details[instance.id] = self._extract_instance_data( @@ -334,6 +346,8 @@ class PublisherController: self._publish_finished = False self._publish_max_progress = 0 self._publish_progress = 0 + # This information is not much important for controller but for widget + # which can change (and set) the comment. self._publish_comment_is_set = False # Validation order diff --git a/openpype/tools/new_publisher/widgets/border_label_widget.py b/openpype/tools/new_publisher/widgets/border_label_widget.py index 9d7a50b76e..9afe077409 100644 --- a/openpype/tools/new_publisher/widgets/border_label_widget.py +++ b/openpype/tools/new_publisher/widgets/border_label_widget.py @@ -110,6 +110,16 @@ class _HCornerLineWidget(QtWidgets.QWidget): class BorderedLabelWidget(QtWidgets.QFrame): + """Draws borders around widget with label in the middle of top. + + +------- Label --------+ + | | + | | + | CONTENT | + | | + | | + +----------------------+ + """ def __init__(self, label, parent): super(BorderedLabelWidget, self).__init__(parent) colors_data = get_colors_data() @@ -179,6 +189,9 @@ class BorderedLabelWidget(QtWidgets.QFrame): self._radius = radius side_width = 1 + radius + # Dont't use fixed width/height as that would set also set + # the other size (When fixed width is set then is also set + # fixed height). self._left_w.setMinimumWidth(side_width) self._left_w.setMaximumWidth(side_width) self._right_w.setMinimumWidth(side_width) From 7e1d0bed222aab893a943f0d62be1b6800a9796e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 23 Sep 2021 18:03:27 +0200 Subject: [PATCH 536/736] added export option to report button --- .../new_publisher/widgets/publish_widget.py | 103 ++++++++++++++++-- 1 file changed, 94 insertions(+), 9 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/publish_widget.py b/openpype/tools/new_publisher/widgets/publish_widget.py index 14d50a2777..2ef71b57ce 100644 --- a/openpype/tools/new_publisher/widgets/publish_widget.py +++ b/openpype/tools/new_publisher/widgets/publish_widget.py @@ -1,4 +1,6 @@ +import os import json +import time from Qt import QtWidgets, QtCore, QtGui @@ -14,6 +16,55 @@ from .widgets import ( ) +class ActionsButton(QtWidgets.QToolButton): + def __init__(self, parent=None): + super(ActionsButton, self).__init__(parent) + + self.setText("< No action >") + self.setPopupMode(self.MenuButtonPopup) + menu = QtWidgets.QMenu(self) + + self.setMenu(menu) + + self._menu = menu + self._actions = [] + self._current_action = None + + self.clicked.connect(self._on_click) + + def current_action(self): + return self._current_action + + def add_action(self, action): + self._actions.append(action) + action.triggered.connect(self._on_action_trigger) + self._menu.addAction(action) + if self._current_action is None: + self._set_action(action) + + def set_action(self, action): + if action not in self._actions: + self.add_action(action) + self._set_action(action) + + def _set_action(self, action): + if action is self._current_action: + return + self._current_action = action + self.setText(action.text()) + self.setIcon(action.icon()) + + def _on_click(self): + self._current_action.trigger() + + def _on_action_trigger(self): + action = self.sender() + if action not in self._actions: + return + + self._set_action(action) + + class PublishFrame(QtWidgets.QFrame): def __init__(self, controller, parent): super(PublishFrame, self).__init__(parent) @@ -57,8 +108,17 @@ class PublishFrame(QtWidgets.QFrame): progress_widget = QtWidgets.QProgressBar(content_widget) progress_widget.setObjectName("PublishProgressBar") - copy_log_btn = QtWidgets.QPushButton("Copy log", content_widget) - copy_log_btn.setVisible(False) + report_btn = ActionsButton(content_widget) + report_btn.setVisible(False) + + export_report_action = QtWidgets.QAction( + "Export report", report_btn.menu() + ) + copy_report_action = QtWidgets.QAction( + "Copy report", report_btn.menu() + ) + report_btn.add_action(export_report_action) + report_btn.add_action(copy_report_action) show_details_btn = QtWidgets.QPushButton( "Show details", content_widget @@ -71,7 +131,7 @@ class PublishFrame(QtWidgets.QFrame): publish_btn = PublishBtn(content_widget) footer_layout = QtWidgets.QHBoxLayout() - footer_layout.addWidget(copy_log_btn, 0) + footer_layout.addWidget(report_btn, 0) footer_layout.addWidget(show_details_btn, 0) footer_layout.addWidget(message_label_bottom, 1) footer_layout.addWidget(reset_btn, 0) @@ -117,9 +177,11 @@ class PublishFrame(QtWidgets.QFrame): main_layout.setCurrentWidget(publish_widget) - copy_log_btn.clicked.connect(self._on_copy_log) show_details_btn.clicked.connect(self._on_show_details) + copy_report_action.triggered.connect(self._on_copy_report) + export_report_action.triggered.connect(self._on_export_report) + reset_btn.clicked.connect(self._on_reset_clicked) stop_btn.clicked.connect(self._on_stop_clicked) validate_btn.clicked.connect(self._on_validate_clicked) @@ -151,7 +213,7 @@ class PublishFrame(QtWidgets.QFrame): self.plugin_label = plugin_label self.progress_widget = progress_widget - self.copy_log_btn = copy_log_btn + self.report_btn = report_btn self.show_details_btn = show_details_btn self.message_label_bottom = message_label_bottom self.reset_btn = reset_btn @@ -196,7 +258,7 @@ class PublishFrame(QtWidgets.QFrame): self.main_label.setText("Hit publish (play button)! If you want") self.message_label.setText("") self.message_label_bottom.setText("") - self.copy_log_btn.setVisible(False) + self.report_btn.setVisible(False) self.show_details_btn.setVisible(False) self.reset_btn.setEnabled(True) @@ -214,7 +276,7 @@ class PublishFrame(QtWidgets.QFrame): self._change_bg_property() self._set_progress_visibility(True) self.main_label.setText("Publishing...") - self.copy_log_btn.setVisible(False) + self.report_btn.setVisible(False) self.show_details_btn.setVisible(False) self.reset_btn.setEnabled(False) @@ -256,7 +318,7 @@ class PublishFrame(QtWidgets.QFrame): def _on_publish_stop(self): self.progress_widget.setValue(self.controller.publish_progress) - self.copy_log_btn.setVisible(True) + self.report_btn.setVisible(True) self.show_details_btn.setVisible(True) self.reset_btn.setEnabled(True) @@ -354,7 +416,7 @@ class PublishFrame(QtWidgets.QFrame): widget.setProperty("state", state) widget.style().polish(widget) - def _on_copy_log(self): + def _on_copy_report(self): logs = self.controller.get_publish_report() logs_string = json.dumps(logs, indent=4) @@ -364,6 +426,29 @@ class PublishFrame(QtWidgets.QFrame): mime_data ) + def _on_export_report(self): + default_filename = "publish-report-{}".format( + time.strftime("%y%m%d-%H-%M") + ) + default_filepath = os.path.join( + os.path.expanduser("~"), + default_filename + ) + new_filepath, ext = QtWidgets.QFileDialog.getSaveFileName( + self, "Save report", default_filepath, ".json" + ) + if not ext or not new_filepath: + return + + logs = self.controller.get_publish_report() + full_path = new_filepath + ext + dir_path = os.path.dirname(full_path) + if not os.path.exists(dir_path): + os.makedirs(dir_path) + + with open(full_path, "w") as file_stream: + json.dump(logs, file_stream) + def _on_show_details(self): self._change_bg_property(2) self._main_layout.setCurrentWidget(self.details_widget) From b0e6ce3ffca65f1b104c206fe7a300f160dea8b1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 23 Sep 2021 18:03:41 +0200 Subject: [PATCH 537/736] changed style of toolbutton --- openpype/style/style.css | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/openpype/style/style.css b/openpype/style/style.css index 32b74d8ece..9b000041a0 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -91,15 +91,15 @@ QPushButton::menu-indicator { } QToolButton { - border: none; - background: transparent; + border: 1px solid transparent; + background: {color:bg-buttons}; border-radius: 0.2em; padding: 2px; } QToolButton:hover { - background: #333840; - border-color: {color:border-hover}; + background: {color:bg-button-hover}; + color: {color:font-hover}; } QToolButton:disabled { @@ -109,14 +109,15 @@ QToolButton:disabled { QToolButton[popupMode="1"], QToolButton[popupMode="MenuButtonPopup"] { /* make way for the popup button */ padding-right: 20px; - border: 1px solid {color:bg-buttons}; } QToolButton::menu-button { width: 16px; - /* Set border only of left side. */ + background: transparent; border: 1px solid transparent; - border-left: 1px solid {color:bg-buttons}; + border-left: 1px solid qlineargradient(x1:0, y1:0, x2:0, y2:1, stop: 0 transparent, stop:0.2 {color:font}, stop:0.8 {color:font}, stop: 1 transparent); + padding: 3px 0px 3px 0px; + border-radius: 0; } QToolButton::menu-arrow { From 91df823fddbb523d98bbf81d08c81c28c43e618f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 24 Sep 2021 12:47:02 +0200 Subject: [PATCH 538/736] added color parser from stylesheet values --- openpype/style/__init__.py | 24 +++ openpype/style/color_defs.py | 285 +++++++++++++++++++++++++++++++++++ 2 files changed, 309 insertions(+) create mode 100644 openpype/style/color_defs.py diff --git a/openpype/style/__init__.py b/openpype/style/__init__.py index bb92ada4fe..84cc18d7a6 100644 --- a/openpype/style/__init__.py +++ b/openpype/style/__init__.py @@ -2,6 +2,8 @@ import os import json import collections from openpype import resources +import six +from .color_defs import parse_color _STYLESHEET_CACHE = None @@ -15,6 +17,28 @@ def get_colors_data(): return data.get("color") or {} +def _convert_color_values_to_objects(value): + if isinstance(value, dict): + output = {} + for _key, _value in value.items(): + output[_key] = _convert_color_values_to_objects(_value) + return output + + if not isinstance(value, six.string_types): + raise TypeError(( + "Unexpected type in colors data '{}'. Expected 'str' or 'dict'." + ).format(str(type(value)))) + return parse_color(value) + + +def get_objected_colors(): + colors_data = get_colors_data() + output = {} + for key, value in colors_data.items(): + output[key] = _convert_color_values_to_objects(value) + return output + + def _get_colors_raw_data(): data_path = os.path.join(current_dir, "data.json") with open(data_path, "r") as data_stream: diff --git a/openpype/style/color_defs.py b/openpype/style/color_defs.py new file mode 100644 index 0000000000..4d726cc3f3 --- /dev/null +++ b/openpype/style/color_defs.py @@ -0,0 +1,285 @@ +import re + + +def parse_color(value): + modified_value = value.strip().lower() + if modified_value.startswith("hsla"): + return HSLAColor(value) + + if modified_value.startswith("hsl"): + return HSLColor(value) + + if modified_value.startswith("#"): + return HEXColor(value) + + if modified_value.startswith("rgba"): + return RGBAColor(value) + + if modified_value.startswith("rgb"): + return RGBColor(value) + return UnknownColor(value) + + +def create_qcolor(*args): + from Qt import QtGui + + return QtGui.QColor(*args) + + +def min_max_check(value, min_value, max_value): + if min_value is not None and value < min_value: + raise ValueError("Minimum expected value is '{}' got '{}'".format( + min_value, value + )) + + if max_value is not None and value > max_value: + raise ValueError("Maximum expected value is '{}' got '{}'".format( + min_value, value + )) + + +def int_validation(value, min_value=None, max_value=None): + if not isinstance(value, int): + raise TypeError(( + "Invalid type of hue expected 'int' got {}" + ).format(str(type(value)))) + + min_max_check(value, min_value, max_value) + + +def float_validation(value, min_value=None, max_value=None): + if not isinstance(value, float): + raise TypeError(( + "Invalid type of hue expected 'int' got {}" + ).format(str(type(value)))) + + min_max_check(value, min_value, max_value) + + +class UnknownColor: + def __init__(self, value): + self.value = value + + def get_qcolor(self): + return create_qcolor(self.value) + + +class HEXColor: + regex = re.compile(r"[a-fA-F0-9]{3}(?:[a-fA-F0-9]{3})?$") + + def __init__(self, color_string): + red, green, blue = self.hex_to_rgb(color_string) + + self._color_string = color_string + self._red = red + self._green = green + self._blue = blue + + @property + def red(self): + return self._red + + @property + def green(self): + return self._green + + @property + def blue(self): + return self._blue + + def to_stylesheet_str(self): + return self._color_string + + @classmethod + def hex_to_rgb(cls, value): + hex_value = value.lstrip("#") + if not cls.regex.match(hex_value): + raise ValueError("\"{}\" is not a valid HEX code.".format(value)) + + output = [] + if len(hex_value) == 3: + for char in hex_value: + output.append(int(char * 2, 16)) + else: + for idx in range(3): + start_idx = idx * 2 + output.append(int(hex_value[start_idx:start_idx + 2], 16)) + return output + + def get_qcolor(self): + return create_qcolor(self.red, self.green, self.blue) + + +class RGBColor: + def __init__(self, value): + modified_color = value.lower().strip() + content = modified_color.rstrip(")").lstrip("rgb(") + red_str, green_str, blue_str = ( + item.strip() for item in content.split(",") + ) + red = int(red_str) + green = int(green_str) + blue = int(blue_str) + + int_validation(red, 0, 255) + int_validation(green, 0, 255) + int_validation(blue, 0, 255) + + self._red = red + self._green = green + self._blue = blue + + @property + def red(self): + return self._red + + @property + def green(self): + return self._green + + @property + def blue(self): + return self._blue + + def get_qcolor(self): + return create_qcolor(self.red, self.green, self.blue) + + +class RGBAColor: + def __init__(self, value): + modified_color = value.lower().strip() + content = modified_color.rstrip(")").lstrip("rgba(") + red_str, green_str, blue_str, alpha_str = ( + item.strip() for item in content.split(",") + ) + red = int(red_str) + green = int(green_str) + blue = int(blue_str) + alpha = int(alpha_str) + + int_validation(red, 0, 255) + int_validation(green, 0, 255) + int_validation(blue, 0, 255) + int_validation(alpha, 0, 255) + + self._red = red + self._green = green + self._blue = blue + self._alpha = alpha + + @property + def red(self): + return self._red + + @property + def green(self): + return self._green + + @property + def blue(self): + return self._blue + + @property + def alpha(self): + return self._alpha + + def get_qcolor(self): + return create_qcolor(self.red, self.green, self.blue, self.alpha) + + +class HSLColor: + def __init__(self, value): + modified_color = value.lower().strip() + content = modified_color.rstrip(")").lstrip("hsl(") + hue_str, sat_str, light_str = ( + item.strip() for item in content.split(",") + ) + hue = int(hue_str) % 360 + if "%" in sat_str: + sat = float(sat_str.rstrip("%")) / 100 + else: + sat = float(sat) + + if "%" in light_str: + light = float(light_str.rstrip("%")) / 100 + else: + light = float(light_str) + + int_validation(hue, 0, 360) + float_validation(sat, 0, 1) + float_validation(light, 0, 1) + + self._hue = hue + self._saturation = sat + self._light = light + + @property + def hue(self): + return self._hue + + @property + def saturation(self): + return self._saturation + + @property + def light(self): + return self._light + + def get_qcolor(self): + color = create_qcolor() + color.setHslF(self.hue / 360, self.saturation, self.light) + return color + + +class HSLAColor: + def __init__(self, value): + modified_color = value.lower().strip() + content = modified_color.rstrip(")").lstrip("hsla(") + hue_str, sat_str, light_str, alpha_str = ( + item.strip() for item in content.split(",") + ) + hue = int(hue_str) % 360 + if "%" in sat_str: + sat = float(sat_str.rstrip("%")) / 100 + else: + sat = float(sat) + + if "%" in light_str: + light = float(light_str.rstrip("%")) / 100 + else: + light = float(light_str) + alpha = float(alpha_str) + + if isinstance(alpha, int): + alpha = float(alpha) + + int_validation(hue, 0, 360) + float_validation(sat, 0, 1) + float_validation(light, 0, 1) + float_validation(alpha, 0, 1) + + self._hue = hue + self._saturation = sat + self._light = light + self._alpha = alpha + + @property + def hue(self): + return self._hue + + @property + def saturation(self): + return self._saturation + + @property + def light(self): + return self._light + + @property + def alpha(self): + return self._alpha + + def get_qcolor(self): + color = create_qcolor() + color.setHslF(self.hue / 360, self.saturation, self.light, self.alpha) + return color From 8648aa862415c11e290d33c11702496332843df1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 24 Sep 2021 12:50:15 +0200 Subject: [PATCH 539/736] added color data for publisher --- openpype/style/data.json | 16 +++++++++++++--- openpype/style/style.css | 14 +++++++------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/openpype/style/data.json b/openpype/style/data.json index 08f1c3fdf6..a40a5ebe6c 100644 --- a/openpype/style/data.json +++ b/openpype/style/data.json @@ -49,8 +49,18 @@ "border-hover": "hsla(220, 14%, 70%, .3)", "border-focus": "hsl(200, 60%, 60%)", - "publish-error": "#AA5050", - "publish-success": "#458056", - "publish-warning": "#ffc671" + "publisher": { + "error": "#AA5050", + "success": "#458056", + "warning": "#ffc671", + "list-view-group": { + "bg": "#434a56", + "bg-hover": "hsla(220, 14%, 70%, .3)", + "bg-selected-hover": "hsla(200, 60%, 60%, .4)", + "bg-expander": "#222222", + "bg-expander-hover": "#2d6c9f", + "bg-expander-selected-hover": "#3784c5" + } + } } } diff --git a/openpype/style/style.css b/openpype/style/style.css index 9b000041a0..9ee81ae052 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -651,10 +651,10 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { } #VariantInput[state="new"], #VariantInput[state="new"]:focus, #VariantInput[state="new"]:hover { - border-color: {color:publish-success}; + border-color: {color:publisher:success}; } #VariantInput[state="invalid"], #VariantInput[state="invalid"]:focus, #VariantInput[state="invalid"]:hover { - border-color: {color:publish-error}; + border-color: {color:publisher:error}; } #VariantInput[state="empty"], #VariantInput[state="empty"]:focus, #VariantInput[state="empty"]:hover { @@ -697,7 +697,7 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { } #ListViewSubsetName[state="invalid"] { - color: {color:publish-error}; + color: {color:publisher:error}; } #PublishFrame { @@ -721,15 +721,15 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { } #PublishInfoFrame[state="0"] { - background: {color:publish-error}; + background: {color:publisher:error}; } #PublishInfoFrame[state="1"] { - background: {color:publish-success}; + background: {color:publisher:success}; } #PublishInfoFrame[state="2"] { - background: {color:publish-warning}; + background: {color:publisher:warning}; } #PublishInfoFrame QLabel { @@ -785,7 +785,7 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { } #TasksCombobox[state="invalid"], #AssetNameInput[state="invalid"] { - border-color: {color:publish-error}; + border-color: {color:publisher:error}; } #PublishProgressBar[state="0"]::chunk { From 2cf4ad1525e8b3c1d1a421ad9623eb131a9a12fa Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 24 Sep 2021 12:52:02 +0200 Subject: [PATCH 540/736] added specific delegate for group items in list view --- .../widgets/list_view_widgets.py | 303 ++++++++++++++++-- 1 file changed, 283 insertions(+), 20 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/list_view_widgets.py b/openpype/tools/new_publisher/widgets/list_view_widgets.py index 5057e307e8..3617122674 100644 --- a/openpype/tools/new_publisher/widgets/list_view_widgets.py +++ b/openpype/tools/new_publisher/widgets/list_view_widgets.py @@ -2,6 +2,7 @@ import collections from Qt import QtWidgets, QtCore, QtGui +from openpype.style import get_objected_colors from openpype.widgets.nice_checkbox import NiceCheckbox from .widgets import AbstractInstanceView from ..constants import ( @@ -13,6 +14,179 @@ from ..constants import ( ) +class ListItemDelegate(QtWidgets.QStyledItemDelegate): + """Generic delegate for instance header""" + + radius = 8.0 + + def __init__(self, parent): + super(ListItemDelegate, self).__init__(parent) + + colors_data = get_objected_colors() + group_color_info = colors_data["publisher"]["list-view-group"] + + self._group_colors = { + key: value.get_qcolor() + for key, value in group_color_info.items() + } + + def paint(self, painter, option, index): + if index.data(IS_GROUP_ROLE): + self.group_item_paint(painter, option, index) + else: + super(ListItemDelegate, self).paint(painter, option, index) + + def group_item_paint(self, painter, option, index): + body_rect = QtCore.QRectF(option.rect) + bg_rect = QtCore.QRectF( + body_rect.left(), body_rect.top() + 1, + body_rect.width() - 5, body_rect.height() - 2 + ) + + expander_rect = QtCore.QRectF(bg_rect) + expander_rect.setWidth(expander_rect.height()) + + remainder_rect = QtCore.QRectF( + expander_rect.x() + expander_rect.width(), + expander_rect.y(), + bg_rect.width() - expander_rect.width(), + expander_rect.height() + ) + + width = float(expander_rect.width()) + height = float(expander_rect.height()) + + x_pos = expander_rect.x() + y_pos = expander_rect.y() + + x_radius = min(self.radius, width / 2) + y_radius = min(self.radius, height / 2) + x_radius2 = x_radius * 2 + y_radius2 = y_radius * 2 + + expander_path = QtGui.QPainterPath() + expander_path.moveTo(x_pos, y_pos + y_radius) + expander_path.arcTo( + x_pos, y_pos, + x_radius2, y_radius2, + 180.0, -90.0 + ) + expander_path.lineTo(x_pos + width, y_pos) + expander_path.lineTo(x_pos + width, y_pos + height) + expander_path.lineTo(x_pos + x_radius, y_pos + height) + expander_path.arcTo( + x_pos, y_pos + height - y_radius2, + x_radius2, y_radius2, + 270.0, -90.0 + ) + expander_path.closeSubpath() + + width = float(remainder_rect.width()) + height = float(remainder_rect.height()) + x_pos = remainder_rect.x() + y_pos = remainder_rect.y() + + x_radius = min(self.radius, width / 2) + y_radius = min(self.radius, height / 2) + x_radius2 = x_radius * 2 + y_radius2 = y_radius * 2 + + remainder_path = QtGui.QPainterPath() + remainder_path.moveTo(x_pos + width, y_pos + height - y_radius) + remainder_path.arcTo( + x_pos + width - x_radius2, y_pos + height - y_radius2, + x_radius2, y_radius2, + 0.0, -90.0 + ) + remainder_path.lineTo(x_pos, y_pos + height) + remainder_path.lineTo(x_pos, y_pos) + remainder_path.lineTo(x_pos + width - x_radius, y_pos) + remainder_path.arcTo( + x_pos + width - x_radius2, y_pos, + x_radius2, y_radius2, + 90.0, -90.0 + ) + remainder_path.closeSubpath() + + painter.fillPath(expander_path, self._group_colors["bg-expander"]) + painter.fillPath(remainder_path, self._group_colors["bg"]) + + mouse_pos = option.widget.mapFromGlobal(QtGui.QCursor.pos()) + selected = option.state & QtWidgets.QStyle.State_Selected + hovered = option.state & QtWidgets.QStyle.State_MouseOver + + if selected and hovered: + if expander_rect.contains(mouse_pos): + painter.fillPath( + expander_path, + self._group_colors["bg-expander-selected-hover"] + ) + else: + painter.fillPath( + remainder_path, + self._group_colors["bg-selected-hover"] + ) + + elif hovered: + if expander_rect.contains(mouse_pos): + painter.fillPath( + expander_path, + self._group_colors["bg-expander-hover"] + ) + else: + painter.fillPath( + remainder_path, + self._group_colors["bg-hover"] + ) + + widget = option.widget + if widget: + style = widget.style() + else: + style = QtWidgets.QApplication.style() + font = index.data(QtCore.Qt.FontRole) + if not font: + font = option.font + + font_metrics = QtGui.QFontMetrics(font) + + text_height = expander_rect.height() + adjust_value = (expander_rect.height() - text_height) / 2 + expander_rect.adjust( + adjust_value + 1.5, adjust_value - 0.5, + -adjust_value + 1.5, -adjust_value - 0.5 + ) + + offset = (remainder_rect.height() - font_metrics.height()) / 2 + label_rect = QtCore.QRectF(remainder_rect.adjusted( + 5, offset - 1, 0, 0 + )) + + # if self.parent().isExpanded(index): + # expander_icon = icons["minus-sign"] + # else: + # expander_icon = icons["plus-sign"] + label = index.data(QtCore.Qt.DisplayRole) + + label = font_metrics.elidedText( + label, QtCore.Qt.ElideRight, label_rect.width() + ) + + # Maintain reference to state, so we can restore it once we're done + painter.save() + + # painter.setFont(fonts["awesome6"]) + # painter.setPen(QtGui.QPen(colors["idle"])) + # painter.drawText(expander_rect, QtCore.Qt.AlignCenter, expander_icon) + + # Draw label + painter.setFont(font) + painter.drawText(label_rect, label) + + # Ok, we're done, tidy up. + painter.restore() + + class InstanceListItemWidget(QtWidgets.QWidget): active_changed = QtCore.Signal(str, bool) @@ -57,6 +231,9 @@ class InstanceListItemWidget(QtWidgets.QWidget): self.subset_name_label.setProperty("state", state) self.subset_name_label.style().polish(self.subset_name_label) + def is_active(self): + return self.instance.data["active"] + def set_active(self, new_value): checkbox_value = self.active_checkbox.isChecked() instance_value = self.instance.data["active"] @@ -156,6 +333,7 @@ class InstanceListGroupWidget(QtWidgets.QFrame): class InstanceTreeView(QtWidgets.QTreeView): toggle_requested = QtCore.Signal(int) + family_toggle_requested = QtCore.Signal(str) def __init__(self, *args, **kwargs): super(InstanceTreeView, self).__init__(*args, **kwargs) @@ -167,15 +345,26 @@ class InstanceTreeView(QtWidgets.QTreeView): self.setSelectionMode( QtWidgets.QAbstractItemView.ExtendedSelection ) + self.viewport().setMouseTracking(True) + self._pressed_group_index = None + self._pressed_expander = None - self.clicked.connect(self._expand_item) + def _expand_item(self, index, expand=None): + is_expanded = self.isExpanded(index) + if expand is None: + expand = not is_expanded - def _expand_item(self, index): - if index.data(IS_GROUP_ROLE): - if self.isExpanded(index): - self.collapse(index) - else: + if expand != is_expanded: + if expand: self.expand(index) + else: + self.collapse(index) + + def _toggle_item(self, index): + if index.data(IS_GROUP_ROLE): + self.family_toggle_requested.emit( + index.data(QtCore.Qt.DisplayRole) + ) def get_selected_instance_ids(self): instance_ids = set() @@ -203,6 +392,64 @@ class InstanceTreeView(QtWidgets.QTreeView): return super(InstanceTreeView, self).event(event) + def mouseMoveEvent(self, event): + index = self.indexAt(event.pos()) + if index.data(IS_GROUP_ROLE): + self.update(index) + super(InstanceTreeView, self).mouseMoveEvent(event) + + def _mouse_press(self, event): + if event.button() != QtCore.Qt.LeftButton: + return + + pos_index = self.indexAt(event.pos()) + if not pos_index.data(IS_GROUP_ROLE): + pressed_group_index = None + pressed_expander = None + else: + height = self.indexRowSizeHint(pos_index) + pressed_group_index = pos_index + pressed_expander = event.pos().x() < height + + self._pressed_group_index = pressed_group_index + self._pressed_expander = pressed_expander + + def mousePressEvent(self, event): + if not self._mouse_press(event): + super(InstanceTreeView, self).mousePressEvent(event) + + def _mouse_release(self, event, pressed_expander, pressed_index): + if event.button() != QtCore.Qt.LeftButton: + return False + + pos_index = self.indexAt(event.pos()) + if not pos_index.data(IS_GROUP_ROLE) or pressed_index != pos_index: + return False + + if self.state() == QtWidgets.QTreeView.State.DragSelectingState: + indexes = self.selectionModel().selectedIndexes() + if len(indexes) != 1 or indexes[0] != pos_index: + return False + + height = self.indexRowSizeHint(pos_index) + if event.pos().x() < height: + if pressed_expander: + self._expand_item(pos_index) + return True + elif not pressed_expander: + self._toggle_item(pos_index) + self._expand_item(pos_index, True) + return True + + def mouseReleaseEvent(self, event): + pressed_index = self._pressed_group_index + pressed_expander = self._pressed_expander is True + self._pressed_group_index = None + self._pressed_expander = None + result = self._mouse_release(event, pressed_expander, pressed_index) + if not result: + super(InstanceTreeView, self).mouseReleaseEvent(event) + class InstanceListView(AbstractInstanceView): def __init__(self, controller, parent): @@ -211,6 +458,8 @@ class InstanceListView(AbstractInstanceView): self.controller = controller instance_view = InstanceTreeView(self) + instance_delegate = ListItemDelegate(instance_view) + instance_view.setItemDelegate(instance_delegate) instance_model = QtGui.QStandardItemModel() proxy_model = QtCore.QSortFilterProxyModel() @@ -232,6 +481,9 @@ class InstanceListView(AbstractInstanceView): instance_view.collapsed.connect(self._on_collapse) instance_view.expanded.connect(self._on_expand) instance_view.toggle_requested.connect(self._on_toggle_request) + instance_view.family_toggle_requested.connect( + self._on_family_toggle_request + ) self._group_items = {} self._group_widgets = {} @@ -269,6 +521,30 @@ class InstanceListView(AbstractInstanceView): if widget is not None: widget.set_active(active) + def _on_family_toggle_request(self, family): + family_item = self._group_items.get(family) + if not family_item: + return + + instance_ids = [] + all_active = True + for row in range(family_item.rowCount()): + item = family_item.child(row, family_item.column()) + instance_id = item.data(INSTANCE_ID_ROLE) + instance_ids.append(instance_id) + if not all_active: + continue + + widget = self._widgets_by_id.get(instance_id) + if widget is not None and not widget.is_active(): + all_active = False + + active = not all_active + for instance_id in instance_ids: + widget = self._widgets_by_id.get(instance_id) + if widget is not None: + widget.set_active(active) + def refresh(self): instances_by_family = collections.defaultdict(list) families = set() @@ -304,7 +580,7 @@ class InstanceListView(AbstractInstanceView): if family in self._group_items: continue - group_item = QtGui.QStandardItem() + group_item = QtGui.QStandardItem(family) group_item.setData(family, SORT_VALUE_ROLE) group_item.setData(True, IS_GROUP_ROLE) group_item.setFlags(QtCore.Qt.ItemIsEnabled) @@ -315,25 +591,12 @@ class InstanceListView(AbstractInstanceView): sort_at_the_end = True root_item.appendRows(new_group_items) - for group_item in new_group_items: - index = self.instance_model.index( - group_item.row(), group_item.column() - ) - proxy_index = self.proxy_model.mapFromSource(index) - family = group_item.data(SORT_VALUE_ROLE) - widget = InstanceListGroupWidget(family, self.instance_view) - widget.expand_changed.connect(self._on_group_expand_request) - self._group_widgets[family] = widget - self.instance_view.setIndexWidget(proxy_index, widget) - for family in tuple(self._group_items.keys()): if family in families: continue group_item = self._group_items.pop(family) root_item.removeRow(group_item.row()) - widget = self._group_widgets.pop(family) - widget.deleteLater() expand_families = set() for family, group_item in self._group_items.items(): From bcb0e91ecb0ee8a02d615768835bde8af88c74d8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 24 Sep 2021 13:06:59 +0200 Subject: [PATCH 541/736] simplified bg painting --- .../widgets/list_view_widgets.py | 89 ++++++------------- 1 file changed, 25 insertions(+), 64 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/list_view_widgets.py b/openpype/tools/new_publisher/widgets/list_view_widgets.py index 3617122674..d2de9d14de 100644 --- a/openpype/tools/new_publisher/widgets/list_view_widgets.py +++ b/openpype/tools/new_publisher/widgets/list_view_widgets.py @@ -17,7 +17,7 @@ from ..constants import ( class ListItemDelegate(QtWidgets.QStyledItemDelegate): """Generic delegate for instance header""" - radius = 8.0 + radius_ratio = 0.3 def __init__(self, parent): super(ListItemDelegate, self).__init__(parent) @@ -53,63 +53,14 @@ class ListItemDelegate(QtWidgets.QStyledItemDelegate): expander_rect.height() ) - width = float(expander_rect.width()) - height = float(expander_rect.height()) - - x_pos = expander_rect.x() - y_pos = expander_rect.y() - - x_radius = min(self.radius, width / 2) - y_radius = min(self.radius, height / 2) - x_radius2 = x_radius * 2 - y_radius2 = y_radius * 2 - - expander_path = QtGui.QPainterPath() - expander_path.moveTo(x_pos, y_pos + y_radius) - expander_path.arcTo( - x_pos, y_pos, - x_radius2, y_radius2, - 180.0, -90.0 + ratio = bg_rect.height() * self.radius_ratio + bg_path = QtGui.QPainterPath() + bg_path.addRoundedRect( + QtCore.QRectF(bg_rect), ratio, ratio ) - expander_path.lineTo(x_pos + width, y_pos) - expander_path.lineTo(x_pos + width, y_pos + height) - expander_path.lineTo(x_pos + x_radius, y_pos + height) - expander_path.arcTo( - x_pos, y_pos + height - y_radius2, - x_radius2, y_radius2, - 270.0, -90.0 - ) - expander_path.closeSubpath() - width = float(remainder_rect.width()) - height = float(remainder_rect.height()) - x_pos = remainder_rect.x() - y_pos = remainder_rect.y() - - x_radius = min(self.radius, width / 2) - y_radius = min(self.radius, height / 2) - x_radius2 = x_radius * 2 - y_radius2 = y_radius * 2 - - remainder_path = QtGui.QPainterPath() - remainder_path.moveTo(x_pos + width, y_pos + height - y_radius) - remainder_path.arcTo( - x_pos + width - x_radius2, y_pos + height - y_radius2, - x_radius2, y_radius2, - 0.0, -90.0 - ) - remainder_path.lineTo(x_pos, y_pos + height) - remainder_path.lineTo(x_pos, y_pos) - remainder_path.lineTo(x_pos + width - x_radius, y_pos) - remainder_path.arcTo( - x_pos + width - x_radius2, y_pos, - x_radius2, y_radius2, - 90.0, -90.0 - ) - remainder_path.closeSubpath() - - painter.fillPath(expander_path, self._group_colors["bg-expander"]) - painter.fillPath(remainder_path, self._group_colors["bg"]) + expander_colors = [self._group_colors["bg-expander"]] + remainder_colors = [self._group_colors["bg"]] mouse_pos = option.widget.mapFromGlobal(QtGui.QCursor.pos()) selected = option.state & QtWidgets.QStyle.State_Selected @@ -117,28 +68,38 @@ class ListItemDelegate(QtWidgets.QStyledItemDelegate): if selected and hovered: if expander_rect.contains(mouse_pos): - painter.fillPath( - expander_path, + expander_colors.append( self._group_colors["bg-expander-selected-hover"] ) + else: - painter.fillPath( - remainder_path, + remainder_colors.append( self._group_colors["bg-selected-hover"] ) elif hovered: if expander_rect.contains(mouse_pos): - painter.fillPath( - expander_path, + expander_colors.append( self._group_colors["bg-expander-hover"] ) + else: - painter.fillPath( - remainder_path, + remainder_colors.append( self._group_colors["bg-hover"] ) + # Draw backgrounds + painter.save() + painter.setClipRect(expander_rect) + for color in expander_colors: + painter.fillPath(bg_path, color) + + painter.setClipRect(remainder_rect) + for color in remainder_colors: + painter.fillPath(bg_path, color) + painter.restore() + + # Draw text and icon widget = option.widget if widget: style = widget.style() From b002803c2b6ee289530bf668b50e0d3eb83b561f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 24 Sep 2021 13:09:59 +0200 Subject: [PATCH 542/736] paint nicer --- openpype/tools/new_publisher/widgets/list_view_widgets.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/widgets/list_view_widgets.py b/openpype/tools/new_publisher/widgets/list_view_widgets.py index d2de9d14de..aca5a68d44 100644 --- a/openpype/tools/new_publisher/widgets/list_view_widgets.py +++ b/openpype/tools/new_publisher/widgets/list_view_widgets.py @@ -40,7 +40,7 @@ class ListItemDelegate(QtWidgets.QStyledItemDelegate): body_rect = QtCore.QRectF(option.rect) bg_rect = QtCore.QRectF( body_rect.left(), body_rect.top() + 1, - body_rect.width() - 5, body_rect.height() - 2 + body_rect.width(), body_rect.height() - 2 ) expander_rect = QtCore.QRectF(bg_rect) @@ -89,6 +89,10 @@ class ListItemDelegate(QtWidgets.QStyledItemDelegate): ) # Draw backgrounds + painter.setRenderHints( + painter.Antialiasing + | painter.SmoothPixmapTransform + ) painter.save() painter.setClipRect(expander_rect) for color in expander_colors: From 7b654ed21428a5ae9c341a11668430625c6ba110 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 24 Sep 2021 18:12:20 +0200 Subject: [PATCH 543/736] added icons for collapsed/expanded group --- openpype/tools/new_publisher/widgets/icons.py | 8 ++++++++ .../widgets/images/branch_closed.png | Bin 0 -> 1965 bytes .../widgets/images/branch_open.png | Bin 0 -> 1798 bytes .../new_publisher/widgets/list_view_widgets.py | 17 +++++++++++++++++ 4 files changed, 25 insertions(+) create mode 100644 openpype/tools/new_publisher/widgets/images/branch_closed.png create mode 100644 openpype/tools/new_publisher/widgets/images/branch_open.png diff --git a/openpype/tools/new_publisher/widgets/icons.py b/openpype/tools/new_publisher/widgets/icons.py index 7f7084bccd..c49f6f17f0 100644 --- a/openpype/tools/new_publisher/widgets/icons.py +++ b/openpype/tools/new_publisher/widgets/icons.py @@ -20,6 +20,14 @@ def get_icon_path(icon_name=None, filename=None): return None +def get_image(icon_name=None, filename=None): + path = get_icon_path(icon_name, filename) + if not path: + return None + + return QtGui.QImage(path) + + def get_pixmap(icon_name=None, filename=None): path = get_icon_path(icon_name, filename) if not path: diff --git a/openpype/tools/new_publisher/widgets/images/branch_closed.png b/openpype/tools/new_publisher/widgets/images/branch_closed.png new file mode 100644 index 0000000000000000000000000000000000000000..135cd0b29da762f1fbe4625f16e1bdb45411a0d6 GIT binary patch literal 1965 zcmah~eM}Q)7(b1QR6&DiP&Riv7K6d|+G5e3Eev}Vx>~)xc1JwD_ogT zqj3qKB$yaiK(?7>5H&_NH#0Zq=G=^Nn{$iI1x93U#>_=TGtv3A_pVT2==R6m^?85K z^LwA~)|ad)O3^LQAqbMYJ%oN28!XUfI$ff z5JwPOfg(_JHIUIN5MaZ0Y~YiV7|Jqstis|px&;Ravc+{GD6L!LrR%C`D}xo}>ud@E z2|_@oP$k5LB|@=d8ZQBVtH(HoYASNI9rL+MPzNsp)MBt0j99)7wTX>NAtG`bGehHm10fKWCER2%;bZlI!j5_qmWI;>6Njq^R5y-Oms$T) z)Kz4Hx)9`LUJCL;!l`jrB;W~#Fx^i!Q7GX_%5L|6tA|44!jNQV4NTl{5yts6a)IQH z3IO*2iI0dhaMr-=Woe01^Foxc3gCPs3zI6rYS^)5|H3NAU?$@z=-@eCEQLJ??3hVA zRuf0KLR0`$u#6lWp&C724Z;ESe3(W9RKK{0+6ZM~7Di$orSgqpkjo5PP;C#*s`80Y zq6&oOoSSE$Z@HS)DCBZDN<`kzauAa|MJrL4(~)Pg=H=FqF&5B7YhBn<8>XVKSy&{l5 zHu%f`E}#js?Wv*XgBi?cBL)arMA4@1?=xJ%`I4^`0z0_NH-e zXXm_y<#JYzZIP|3^E3 z_+FX%;q9;5AO0!HNf#{l4!nHn=N+lmfy%qnnjQx3K5WQ<Pw2c!cd8_vyl`#u(TeHaE-LntL!P_sperx?#*0&WI`{aLCAX6MZzsLi za{uyYGc%9+n2mz*w{2y*tyY3*5zD79$vX&+doXqsKWpN literal 0 HcmV?d00001 diff --git a/openpype/tools/new_publisher/widgets/images/branch_open.png b/openpype/tools/new_publisher/widgets/images/branch_open.png new file mode 100644 index 0000000000000000000000000000000000000000..1a83955306b11cadd90d4efd7aef0bfd54730877 GIT binary patch literal 1798 zcmah~eM}Q)7(Wsx!bBOMLucH1BnE@Mk227nEezW#jn*JkKH@$+dT-m4-pB5)v{WNT z#03NsOcqx`{8|EG#^5$*hB~MFFlHY*7e5w}Nz9C!X2{G2e($|279G0%(e~cw_j`W# zJiq6C-!>F2Tbz_QJrO~WBuAmG82)O{@e{_wZ)#t!8$rf(bFOl=+_{u?i#*P-VkN*s zyaaIsG3SRQ#$5$ev=VqZ!GaAOK8B$jYr)ELoTO8-0v}fxmO)8)nadrna+_EzKQGZ7 zqM-l}R0a+4enFu_7EITr;jx~G>NE&4JdAzlT=O=P)(Dm z6a%US!yt~1h=UL}sxBxa?L#974IwC;B%Y+JoaZ@o4H-k1e4;8UK2eG_@YE|}@R&qs z-?LLfjPxwB)A`?Yo`0$hMYUH$BSuVps;0*;P+T< z@R!>-9ta9_*Md>xzrczoz&R!=5n4I8>2#&ku*{UGRKfjxhNx@P0|#VMH*=`T93|# zCxK;D=KuKGen!nO!|9|8IYkxa8a))<(WMoj_h2yng1s)Fv?W{rJ!Qs3DkWmUZ;Zbj6IYA$Qde85U7^#iWPTb?- z%*2@T%{t+UBMS4PF?jUh9YVw|5x9t5LqLFcj|?xF`W-tbAV^#t2M04|APAD#5@GEH%FGQ<2~&)*{l0PN9UboKlFEG9vG}T{`ME8 z6alXiwC&wT+Hws?_uTJ2xvpVBOJqx- zf67p0%4{3*>eZ%XcKutM*qOUp^TMn>vbJ_sZ6i3ff<1qV>9%c6tLU-4+46frL)Z0$ zX`EdB=+WQjT=(uZp1T~^8UAG7g=&dEQP%fS-lVxVZX~x&mfGk1N(_8?rS-_D zZx75rn#m3F^DnKxw#MT5DsgK`@9wT!^2EPBJ`;N3;O(#5AKX;b_#bobbS=5~bNwXK zK*jL*Ef2iI4;s_A!T*Z{sjxypkQY<6PaLwnEgc4VhpoVs{$Akp@q`ma$s7AG)W^4; zvA%wGLnv(xSvISA#Fw$fO8Y_B6b+;UGC=-34Ea NIPA-8`xma-@DKJ`c?kdj literal 0 HcmV?d00001 diff --git a/openpype/tools/new_publisher/widgets/list_view_widgets.py b/openpype/tools/new_publisher/widgets/list_view_widgets.py index aca5a68d44..dcc51a4087 100644 --- a/openpype/tools/new_publisher/widgets/list_view_widgets.py +++ b/openpype/tools/new_publisher/widgets/list_view_widgets.py @@ -5,6 +5,7 @@ from Qt import QtWidgets, QtCore, QtGui from openpype.style import get_objected_colors from openpype.widgets.nice_checkbox import NiceCheckbox from .widgets import AbstractInstanceView +from .icons import get_image from ..constants import ( INSTANCE_ID_ROLE, SORT_VALUE_ROLE, @@ -18,6 +19,8 @@ class ListItemDelegate(QtWidgets.QStyledItemDelegate): """Generic delegate for instance header""" radius_ratio = 0.3 + expand_image = None + collapse_image = None def __init__(self, parent): super(ListItemDelegate, self).__init__(parent) @@ -30,6 +33,20 @@ class ListItemDelegate(QtWidgets.QStyledItemDelegate): for key, value in group_color_info.items() } + @classmethod + def get_expand_image(cls): + if cls.expand_image is None: + image = get_image("branch_open") + + cls.expand_image = image + return cls.expand_image + + @classmethod + def get_collapse_image(cls): + if cls.collapse_image is None: + cls.collapse_image = get_image("branch_closed") + return cls.collapse_image + def paint(self, painter, option, index): if index.data(IS_GROUP_ROLE): self.group_item_paint(painter, option, index) From 02e3c07595df80299e8b35b39b10b09b0a65be1e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 24 Sep 2021 18:12:35 +0200 Subject: [PATCH 544/736] modified text offset --- .../new_publisher/widgets/list_view_widgets.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/list_view_widgets.py b/openpype/tools/new_publisher/widgets/list_view_widgets.py index dcc51a4087..857605577f 100644 --- a/openpype/tools/new_publisher/widgets/list_view_widgets.py +++ b/openpype/tools/new_publisher/widgets/list_view_widgets.py @@ -132,16 +132,11 @@ class ListItemDelegate(QtWidgets.QStyledItemDelegate): font_metrics = QtGui.QFontMetrics(font) - text_height = expander_rect.height() - adjust_value = (expander_rect.height() - text_height) / 2 - expander_rect.adjust( - adjust_value + 1.5, adjust_value - 0.5, - -adjust_value + 1.5, -adjust_value - 0.5 - ) - - offset = (remainder_rect.height() - font_metrics.height()) / 2 + # Center label horizontally + diff = remainder_rect.height() - font_metrics.height() + offset = (diff + (diff % 2)) / 2 label_rect = QtCore.QRectF(remainder_rect.adjusted( - 5, offset - 1, 0, 0 + 5, offset, 0, 0 )) # if self.parent().isExpanded(index): From ac6694f7f4691a3a7d03dc2f65f42b7f795757ab Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 24 Sep 2021 18:12:54 +0200 Subject: [PATCH 545/736] draw expand/collapse icons --- .../widgets/list_view_widgets.py | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/list_view_widgets.py b/openpype/tools/new_publisher/widgets/list_view_widgets.py index 857605577f..bc9e004004 100644 --- a/openpype/tools/new_publisher/widgets/list_view_widgets.py +++ b/openpype/tools/new_publisher/widgets/list_view_widgets.py @@ -54,6 +54,7 @@ class ListItemDelegate(QtWidgets.QStyledItemDelegate): super(ListItemDelegate, self).paint(painter, option, index) def group_item_paint(self, painter, option, index): + self.initStyleOption(option, index) body_rect = QtCore.QRectF(option.rect) bg_rect = QtCore.QRectF( body_rect.left(), body_rect.top() + 1, @@ -139,10 +140,10 @@ class ListItemDelegate(QtWidgets.QStyledItemDelegate): 5, offset, 0, 0 )) - # if self.parent().isExpanded(index): - # expander_icon = icons["minus-sign"] - # else: - # expander_icon = icons["plus-sign"] + if self.parent().isExpanded(index): + expander_icon = self.get_expand_image() + else: + expander_icon = self.get_collapse_image() label = index.data(QtCore.Qt.DisplayRole) label = font_metrics.elidedText( @@ -152,9 +153,30 @@ class ListItemDelegate(QtWidgets.QStyledItemDelegate): # Maintain reference to state, so we can restore it once we're done painter.save() - # painter.setFont(fonts["awesome6"]) - # painter.setPen(QtGui.QPen(colors["idle"])) - # painter.drawText(expander_rect, QtCore.Qt.AlignCenter, expander_icon) + width = expander_rect.width() + height = expander_rect.height() + if height < width: + size = height + else: + size = width + + icon_size = int(size / 2) + offset = (size - icon_size) / 2 + + icon_width = expander_icon.width() + icon_height = expander_icon.height() + pos_x = expander_rect.x() + offset + pos_y = expander_rect.y() + offset + if icon_width < icon_height: + icon_width = (icon_size / icon_height) * icon_width + pos_x += (icon_size - icon_width) / 2 + elif icon_height < icon_width: + icon_height = (icon_size / icon_width) * icon_height + pos_y += (icon_size - icon_height) / 2 + + expander_icon_rect = QtCore.QRectF(pos_x, pos_y, icon_size, icon_size) + expander_icon_rect.moveCenter(expander_rect.center()) + painter.drawImage(expander_icon_rect, expander_icon) # Draw label painter.setFont(font) From c642c4a22ad96a79a809c2d9fabfb9fafea478ec Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 24 Sep 2021 18:13:04 +0200 Subject: [PATCH 546/736] removed unused code --- openpype/tools/new_publisher/widgets/list_view_widgets.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/list_view_widgets.py b/openpype/tools/new_publisher/widgets/list_view_widgets.py index bc9e004004..f8bb3be140 100644 --- a/openpype/tools/new_publisher/widgets/list_view_widgets.py +++ b/openpype/tools/new_publisher/widgets/list_view_widgets.py @@ -110,6 +110,7 @@ class ListItemDelegate(QtWidgets.QStyledItemDelegate): painter.setRenderHints( painter.Antialiasing | painter.SmoothPixmapTransform + | painter.TextAntialiasing ) painter.save() painter.setClipRect(expander_rect) @@ -122,11 +123,6 @@ class ListItemDelegate(QtWidgets.QStyledItemDelegate): painter.restore() # Draw text and icon - widget = option.widget - if widget: - style = widget.style() - else: - style = QtWidgets.QApplication.style() font = index.data(QtCore.Qt.FontRole) if not font: font = option.font From ec3ec0177c588340be432b1e7fc3476e408bb5ec Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 24 Sep 2021 18:23:18 +0200 Subject: [PATCH 547/736] change color of expander --- openpype/style/data.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/style/data.json b/openpype/style/data.json index a40a5ebe6c..a49180fd12 100644 --- a/openpype/style/data.json +++ b/openpype/style/data.json @@ -57,7 +57,7 @@ "bg": "#434a56", "bg-hover": "hsla(220, 14%, 70%, .3)", "bg-selected-hover": "hsla(200, 60%, 60%, .4)", - "bg-expander": "#222222", + "bg-expander": "#2C313A", "bg-expander-hover": "#2d6c9f", "bg-expander-selected-hover": "#3784c5" } From 8f49c75eebfa75eb0f19fe816aa924afb2a42179 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 24 Sep 2021 18:23:46 +0200 Subject: [PATCH 548/736] added ability to not discover publish plugins --- openpype/pipeline/create/context.py | 45 +++++++++++++++++------------ 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index c506de49fb..c84a2645be 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -527,7 +527,10 @@ class CreateContext: "update_context_data" ) - def __init__(self, host, dbcon=None, headless=False, reset=True): + def __init__( + self, host, dbcon=None, headless=False, reset=True, + discover_publish_plugins=True + ): if dbcon is None: import avalon.api @@ -571,7 +574,7 @@ class CreateContext: self._attr_plugins_by_family = {} if reset: - self.reset() + self.reset(discover_publish_plugins) @property def publish_attributes(self): @@ -595,38 +598,44 @@ class CreateContext: self._log = logging.getLogger(self.__class__.__name__) return self._log - def reset(self): - self.reset_plugins() + def reset(self, discover_publish_plugins=True): + self.reset_plugins(discover_publish_plugins) self.reset_context_data() self.reset_instances() self.execute_autocreators() - def reset_plugins(self): + def reset_plugins(self, discover_publish_plugins=True): import avalon.api import pyblish.logic from openpype.pipeline import OpenPypePyblishPluginMixin - from openpype.pipeline.publish import publish_plugins_discover + from openpype.pipeline.publish import ( + publish_plugins_discover, + DiscoverResult + ) # Reset publish plugins self._attr_plugins_by_family = {} - discover_result = publish_plugins_discover() - publish_plugins = discover_result.plugins + discover_result = DiscoverResult() + plugins_with_defs = [] + plugins_by_targets = [] + if discover_publish_plugins: + discover_result = publish_plugins_discover() + publish_plugins = discover_result.plugins + + targets = pyblish.logic.registered_targets() or ["default"] + plugins_by_targets = pyblish.logic.plugins_by_targets( + publish_plugins, targets + ) + # Collect plugins that can have attribute definitions + for plugin in publish_plugins: + if OpenPypePyblishPluginMixin in inspect.getmro(plugin): + plugins_with_defs.append(plugin) - targets = pyblish.logic.registered_targets() or ["default"] - plugins_by_targets = pyblish.logic.plugins_by_targets( - publish_plugins, targets - ) self.publish_discover_result = discover_result self.publish_plugins = plugins_by_targets - - # Collect plugins that can have attribute definitions - plugins_with_defs = [] - for plugin in publish_plugins: - if OpenPypePyblishPluginMixin in inspect.getmro(plugin): - plugins_with_defs.append(plugin) self.plugins_with_defs = plugins_with_defs # Prepare settings From ea0925e5ec9d63e6ac3ef9eb907be6193b3f8c7e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 24 Sep 2021 18:31:01 +0200 Subject: [PATCH 549/736] Autocreato should raise AutoCreationSkipped if has nothign to do --- openpype/pipeline/create/__init__.py | 4 ++++ openpype/pipeline/create/context.py | 5 +++++ openpype/pipeline/create/creator_plugins.py | 18 ++++++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/openpype/pipeline/create/__init__.py b/openpype/pipeline/create/__init__.py index 2f4e8ef5b9..cde6b1e9f8 100644 --- a/openpype/pipeline/create/__init__.py +++ b/openpype/pipeline/create/__init__.py @@ -1,5 +1,7 @@ from .creator_plugins import ( CreatorError, + AutoCreationSkipped, + BaseCreator, Creator, AutoCreator @@ -13,6 +15,8 @@ from .context import ( __all__ = ( "CreatorError", + "AutoCreationSkipped", + "BaseCreator", "Creator", "AutoCreator", diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index c84a2645be..704f6c3bf8 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -7,6 +7,7 @@ from uuid import uuid4 from ..lib import UnknownDef from .creator_plugins import ( + AutoCreationSkipped, BaseCreator, Creator, AutoCreator @@ -772,6 +773,10 @@ class CreateContext: try: creator.create() any_processed = True + + except AutoCreationSkipped as exc: + self.log.debug(str(exc)) + except Exception: msg = ( "Failed to run AutoCreator with family \"{}\" ({})." diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index bf8eb6af89..0a7aa45bf5 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -21,6 +21,22 @@ class CreatorError(Exception): super(CreatorError, self).__init__(message) +class AutoCreationSkipped(Exception): + """Should be raised when AutoCreator has nothing to do. + + When all instances that would autocreator create already exists. + + Message won't be shown in UI. + """ + + def __init__(self, message): + self._message = message + super(AutoCreationSkipped, self).__init__(message) + + def __str__(self): + return "<{}> {}".format(self.__class__.__name__, self._message) + + @six.add_metaclass(ABCMeta) class BaseCreator: """Plugin that create and modify instance data before publishing process. @@ -249,4 +265,6 @@ class AutoCreator(BaseCreator): """Creator which is automatically triggered without user interaction. Can be used e.g. for `workfile`. + + Should raise 'AutoCreationSkipped' if has nothing to do. """ From e01af64c205980d1b3baac06c1d8d498a3afb8dc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 24 Sep 2021 18:31:55 +0200 Subject: [PATCH 550/736] added few docstrings --- openpype/pipeline/create/context.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 704f6c3bf8..0a790eb22d 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -42,6 +42,11 @@ class HostMissRequiredMethod(Exception): class InstanceMember: + """Representation of instance member. + + TODO: + Implement and use! + """ def __init__(self, instance, name): self.instance = instance @@ -63,6 +68,8 @@ class AttributeValues: Goal is to have one object which hold values of attribute definitions for single instance. + Has dictionary like methods. Not all of them are allowed all the time. + Args: attr_defs(AbtractAttrDef): Defintions of value type and properties. values(dict): Values after possible conversion. @@ -137,9 +144,11 @@ class AttributeValues: @property def attr_defs(self): + """Pointer to attribute definitions.""" return self._attr_defs def data_to_store(self): + """Create new dictionary with data to store.""" output = {} for key in self._data: output[key] = self[key] @@ -147,6 +156,7 @@ class AttributeValues: @staticmethod def calculate_changes(new_data, old_data): + """Calculate changes of 2 dictionary objects.""" changes = {} for key, new_value in new_data.items(): old_value = old_data.get(key) @@ -353,6 +363,7 @@ class CreatedInstance: orig_family_attributes = data.pop("family_attributes", None) or {} orig_publish_attributes = data.pop("publish_attributes", None) or {} + # QUESTION Does it make sense to have data stored as ordered dict? self._data = collections.OrderedDict() self._data["id"] = "pyblish.avalon.instance" self._data["family"] = family @@ -520,6 +531,13 @@ class CreatedInstance: class CreateContext: + """Context of instance creation. + + Context itself also can store data related to whole creation (workfile). + - those are mainly for Context publish plugins + """ + # Methods required in host implementaion to be able create instances + # or change context data. required_methods = ( "list_instances", "remove_instances", @@ -768,6 +786,14 @@ class CreateContext: self.instances = instances def execute_autocreators(self): + """Execute discovered AutoCreator plugins. + + Reset instances if any autocreator executed properly. + + Autocreatos should raise 'AutoCreationSkipped' if has nothing to do. + - execution of autocreator requires to reset instances (can be time + time consuming) + """ any_processed = False for family, creator in self.autocreators.items(): try: From 7ab07deb9337c3fb5c8aca3189344502833ee1a2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 24 Sep 2021 18:33:20 +0200 Subject: [PATCH 551/736] simplified get image functions --- openpype/tools/new_publisher/widgets/icons.py | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/icons.py b/openpype/tools/new_publisher/widgets/icons.py index c49f6f17f0..b8c9d22af8 100644 --- a/openpype/tools/new_publisher/widgets/icons.py +++ b/openpype/tools/new_publisher/widgets/icons.py @@ -22,22 +22,20 @@ def get_icon_path(icon_name=None, filename=None): def get_image(icon_name=None, filename=None): path = get_icon_path(icon_name, filename) - if not path: - return None - - return QtGui.QImage(path) + if path: + return QtGui.QImage(path) + return None def get_pixmap(icon_name=None, filename=None): path = get_icon_path(icon_name, filename) - if not path: - return None - - return QtGui.QPixmap(path) + if path: + return QtGui.QPixmap(path) + return None def get_icon(icon_name=None, filename=None): pix = get_pixmap(icon_name, filename) - if not pix: - return None - return QtGui.QIcon(pix) + if pix: + return QtGui.QIcon(pix) + return None From 1175c0179796389e5bba73c59841af014531204e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 24 Sep 2021 18:38:16 +0200 Subject: [PATCH 552/736] removed unsused abstract methods --- .../widgets/card_view_widgets.py | 3 -- .../widgets/list_view_widgets.py | 43 ------------------- .../tools/new_publisher/widgets/widgets.py | 5 --- 3 files changed, 51 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/card_view_widgets.py b/openpype/tools/new_publisher/widgets/card_view_widgets.py index 0d88a3733a..e578e1dd65 100644 --- a/openpype/tools/new_publisher/widgets/card_view_widgets.py +++ b/openpype/tools/new_publisher/widgets/card_view_widgets.py @@ -437,6 +437,3 @@ class InstanceCardView(AbstractInstanceView): instances.append(selected_widget.instance) return instances, context_selected - - def set_selected_items(self, instances, context_selected): - pass diff --git a/openpype/tools/new_publisher/widgets/list_view_widgets.py b/openpype/tools/new_publisher/widgets/list_view_widgets.py index f8bb3be140..33d4a237e2 100644 --- a/openpype/tools/new_publisher/widgets/list_view_widgets.py +++ b/openpype/tools/new_publisher/widgets/list_view_widgets.py @@ -714,49 +714,6 @@ class InstanceListView(AbstractInstanceView): return instances, context_selected - def set_selected_items(self, instances, context_selected): - instance_ids_by_family = collections.defaultdict(set) - for instance in instances: - family = instance.data["family"] - instance_id = instance.data["uuid"] - instance_ids_by_family[family].add(instance_id) - - indexes = [] - if context_selected and self._context_item is not None: - index = self.instance_model.index(self._context_item.row(), 0) - proxy_index = self.proxy_model.mapFromSource(index) - indexes.append(proxy_index) - - for family, group_item in self._group_items.items(): - selected_ids = instance_ids_by_family[family] - if not selected_ids: - continue - - group_index = self.instance_model.index( - group_item.row(), group_item.column() - ) - proxy_group_index = self.proxy_model.mapFromSource(group_index) - has_indexes = False - for row in range(group_item.rowCount()): - index = self.proxy_model.index(row, 0, proxy_group_index) - instance_id = index.data(INSTANCE_ID_ROLE) - if instance_id in selected_ids: - indexes.append(index) - has_indexes = True - - if has_indexes: - self.instance_view.setExpanded(proxy_group_index, True) - - selection_model = self.instance_view.selectionModel() - first_item = True - for index in indexes: - if first_item: - first_item = False - select_type = QtCore.QItemSelectionModel.ClearAndSelect - else: - select_type = QtCore.QItemSelectionModel.Select - selection_model.select(index, select_type) - def _on_selection_change(self, *_args): self.selection_changed.emit() diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index ffb9c23dc2..750fab35d3 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -278,11 +278,6 @@ class AbstractInstanceView(QtWidgets.QWidget): "{} Method 'get_selected_items' is not implemented." ).format(self.__class__.__name__)) - def set_selected_items(self, instances, context_selected): - raise NotImplementedError(( - "{} Method 'set_selected_items' is not implemented." - ).format(self.__class__.__name__)) - class ClickableFrame(QtWidgets.QFrame): def __init__(self, parent): From a8204def2c36b4b55a41fc1b0a553096f8c69d79 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 24 Sep 2021 18:38:26 +0200 Subject: [PATCH 553/736] fixed import error --- openpype/pipeline/publish/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/pipeline/publish/__init__.py b/openpype/pipeline/publish/__init__.py index 7eed98e73a..ca958816fe 100644 --- a/openpype/pipeline/publish/__init__.py +++ b/openpype/pipeline/publish/__init__.py @@ -5,6 +5,7 @@ from .publish_plugins import ( ) from .lib import ( + DiscoverResult, publish_plugins_discover ) @@ -14,5 +15,6 @@ __all__ = ( "KnownPublishError", "OpenPypePyblishPluginMixin", + "DiscoverResult", "publish_plugins_discover" ) From 9b2b7e8a33101283b49d32766f55e0ed7a4efd72 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 24 Sep 2021 18:46:30 +0200 Subject: [PATCH 554/736] creator can do some stuff before instance is fully removed from context (or can remove it from context) --- openpype/pipeline/create/context.py | 24 ++++++++++++++++++++- openpype/pipeline/create/creator_plugins.py | 15 +++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 0a790eb22d..2e412dadb6 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -837,11 +837,33 @@ class CreateContext: self.host.update_instances(update_list) def remove_instances(self, instances): + """Remove instances from context. + + Args: + instances(list): Instances that should be removed + from context. + """ if not self.host_is_valid: missing_methods = self.get_host_misssing_methods(self.host) raise HostMissRequiredMethod(self.host, missing_methods) - self.host.remove_instances(instances) + instances_by_family = collections.defaultdict(list) + for instance in instances: + instances_by_family[instance.family].append(instance) + + instances_to_remove = [] + for family, family_instances in instances_by_family.items(): + creator = self.creators.get(family) + if not creator: + instances_to_remove.extend(family_instances) + continue + + for instance in family_instances: + if not creator.remove_instance(instance): + instances_to_remove.append(instances_to_remove) + + if instances_to_remove: + self.host.remove_instances(instances_to_remove) def _get_publish_plugins_with_attr_for_family(self, family): if family not in self._attr_plugins_by_family: diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 0a7aa45bf5..85f6f0cd75 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -90,6 +90,21 @@ class BaseCreator: """ pass + def remove_instance(self, instance): + """Method called on instance removement. + + Can also remove instance metadata from context but should return + 'True' if did so. + + Args: + instance(CreatedInstance): Instance object which will be removed. + + Returns: + bool: Instance was removed completely and is not required to call + host's implementation of removement. + """ + return False + def get_icon(self): """Icon of creator (family). From f25456c39360c5f929425b7666cd8944080f652d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 24 Sep 2021 18:55:02 +0200 Subject: [PATCH 555/736] replaced get_colors_data with get_objected_colors in border label widget --- .../new_publisher/widgets/border_label_widget.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/border_label_widget.py b/openpype/tools/new_publisher/widgets/border_label_widget.py index 9afe077409..d30ce67140 100644 --- a/openpype/tools/new_publisher/widgets/border_label_widget.py +++ b/openpype/tools/new_publisher/widgets/border_label_widget.py @@ -1,6 +1,6 @@ from Qt import QtWidgets, QtCore, QtGui -from openpype.style import get_colors_data +from openpype.style import get_objected_colors class _VLineWidget(QtWidgets.QWidget): @@ -122,12 +122,11 @@ class BorderedLabelWidget(QtWidgets.QFrame): """ def __init__(self, label, parent): super(BorderedLabelWidget, self).__init__(parent) - colors_data = get_colors_data() + colors_data = get_objected_colors() color_value = colors_data.get("border") - try: - color = QtGui.QColor(color_value) - except Exception: - color = None + color = None + if color_value: + color = color_value.get_qcolor() top_left_w = _HCornerLineWidget(color, True, self) top_right_w = _HCornerLineWidget(color, False, self) From a7871f54f03d1100423e1414872d7b3f613c3e98 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 24 Sep 2021 19:05:46 +0200 Subject: [PATCH 556/736] hda publishing wip --- .../houdini/plugins/create/create_hda.py | 47 ++++++++++++++++ .../hosts/houdini/plugins/load/load_hda.py | 56 +++++++++++++++++++ .../plugins/publish/collect_active_state.py | 4 +- .../plugins/publish/collect_instances.py | 6 +- .../houdini/plugins/publish/extract_hda.py | 43 ++++++++++++++ .../plugins/publish/validate_bypass.py | 2 +- openpype/plugins/publish/integrate_new.py | 3 +- 7 files changed, 157 insertions(+), 4 deletions(-) create mode 100644 openpype/hosts/houdini/plugins/create/create_hda.py create mode 100644 openpype/hosts/houdini/plugins/load/load_hda.py create mode 100644 openpype/hosts/houdini/plugins/publish/extract_hda.py diff --git a/openpype/hosts/houdini/plugins/create/create_hda.py b/openpype/hosts/houdini/plugins/create/create_hda.py new file mode 100644 index 0000000000..d58e0c5e52 --- /dev/null +++ b/openpype/hosts/houdini/plugins/create/create_hda.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +from openpype.hosts.houdini.api import plugin +from avalon.houdini import lib +import hou + + +class CreateHDA(plugin.Creator): + """Publish Houdini Digital Asset file.""" + + name = "hda" + label = "Houdini Digital Asset (Hda)" + family = "hda" + icon = "gears" + maintain_selection = False + + def __init__(self, *args, **kwargs): + super(CreateHDA, self).__init__(*args, **kwargs) + self.data.pop("active", None) + + def _process(self, instance): + + out = hou.node("/obj") + self.nodes = hou.selectedNodes() + + if (self.options or {}).get("useSelection") and self.nodes: + to_hda = self.nodes[0] + if len(self.nodes) > 1: + subnet = out.createNode( + "subnet", node_name="{}_subnet".format(self.name)) + to_hda = subnet + else: + subnet = out.createNode( + "subnet", node_name="{}_subnet".format(self.name)) + subnet.moveToGoodPosition() + to_hda = subnet + + hda_node = to_hda.createDigitalAsset( + name=self.name, + hda_file_name="$HIP/{}.hda".format(self.name) + ) + hda_node.setName(self.name) + hou.moveNodesTo(self.nodes, hda_node) + hda_node.layoutChildren() + + lib.imprint(hda_node, self.data) + + return hda_node diff --git a/openpype/hosts/houdini/plugins/load/load_hda.py b/openpype/hosts/houdini/plugins/load/load_hda.py new file mode 100644 index 0000000000..5e04d83e86 --- /dev/null +++ b/openpype/hosts/houdini/plugins/load/load_hda.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +from avalon import api + +from avalon.houdini import pipeline, lib + + +class HdaLoader(api.Loader): + """Load Houdini Digital Asset file.""" + + families = ["hda"] + label = "Load Hda" + representations = ["hda"] + order = -10 + icon = "code-fork" + color = "orange" + + def load(self, context, name=None, namespace=None, data=None): + + import os + import hou + + # Format file name, Houdini only wants forward slashes + file_path = os.path.normpath(self.fname) + file_path = file_path.replace("\\", "/") + + # Get the root node + obj = hou.node("/obj") + + # Create a unique name + counter = 1 + namespace = namespace or context["asset"]["name"] + formatted = "{}_{}".format(namespace, name) if namespace else name + node_name = "{0}_{1:03d}".format(formatted, counter) + + hou.hda.installFile(file_path) + print("installing {}".format(name)) + hda_node = obj.createNode(name, node_name) + + self[:] = [hda_node] + + return pipeline.containerise( + node_name, + namespace, + [hda_node], + context, + self.__class__.__name__, + suffix="", + ) + + def update(self, container, representation): + hda_node = container["node"] + hda_def = hda_node.type().definition() + + + def remove(self, container): + pass diff --git a/openpype/hosts/houdini/plugins/publish/collect_active_state.py b/openpype/hosts/houdini/plugins/publish/collect_active_state.py index 1193f0cd19..862d5720e1 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_active_state.py +++ b/openpype/hosts/houdini/plugins/publish/collect_active_state.py @@ -23,8 +23,10 @@ class CollectInstanceActiveState(pyblish.api.InstancePlugin): return # Check bypass state and reverse + active = True node = instance[0] - active = not node.isBypassed() + if hasattr(node, "isBypassed"): + active = not node.isBypassed() # Set instance active state instance.data.update( diff --git a/openpype/hosts/houdini/plugins/publish/collect_instances.py b/openpype/hosts/houdini/plugins/publish/collect_instances.py index 1b36526783..ac081ac297 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_instances.py +++ b/openpype/hosts/houdini/plugins/publish/collect_instances.py @@ -31,6 +31,7 @@ class CollectInstances(pyblish.api.ContextPlugin): def process(self, context): nodes = hou.node("/out").children() + nodes += hou.node("/obj").children() # Include instances in USD stage only when it exists so it # remains backwards compatible with version before houdini 18 @@ -49,9 +50,12 @@ class CollectInstances(pyblish.api.ContextPlugin): has_family = node.evalParm("family") assert has_family, "'%s' is missing 'family'" % node.name() + self.log.info("processing {}".format(node)) + data = lib.read(node) # Check bypass state and reverse - data.update({"active": not node.isBypassed()}) + if hasattr(node, "isBypassed"): + data.update({"active": not node.isBypassed()}) # temporarily translation of `active` to `publish` till issue has # been resolved, https://github.com/pyblish/pyblish-base/issues/307 diff --git a/openpype/hosts/houdini/plugins/publish/extract_hda.py b/openpype/hosts/houdini/plugins/publish/extract_hda.py new file mode 100644 index 0000000000..301dd4e297 --- /dev/null +++ b/openpype/hosts/houdini/plugins/publish/extract_hda.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +import os + +from pprint import pformat + +import pyblish.api +import openpype.api + + +class ExtractHDA(openpype.api.Extractor): + + order = pyblish.api.ExtractorOrder + label = "Extract HDA" + hosts = ["houdini"] + families = ["hda"] + + def process(self, instance): + self.log.info(pformat(instance.data)) + hda_node = instance[0] + hda_def = hda_node.type().definition() + hda_options = hda_def.options() + hda_options.setSaveInitialParmsAndContents(True) + + next_version = instance.data["anatomyData"]["version"] + self.log.info("setting version: {}".format(next_version)) + hda_def.setVersion(str(next_version)) + hda_def.setOptions(hda_options) + hda_def.save(hda_def.libraryFilePath(), hda_node, hda_options) + + if "representations" not in instance.data: + instance.data["representations"] = [] + + file = os.path.basename(hda_def.libraryFilePath()) + staging_dir = os.path.dirname(hda_def.libraryFilePath()) + self.log.info("Using HDA from {}".format(hda_def.libraryFilePath())) + + representation = { + 'name': 'hda', + 'ext': 'hda', + 'files': file, + "stagingDir": staging_dir, + } + instance.data["representations"].append(representation) diff --git a/openpype/hosts/houdini/plugins/publish/validate_bypass.py b/openpype/hosts/houdini/plugins/publish/validate_bypass.py index 79c67c3008..fc4e18f701 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_bypass.py +++ b/openpype/hosts/houdini/plugins/publish/validate_bypass.py @@ -35,5 +35,5 @@ class ValidateBypassed(pyblish.api.InstancePlugin): def get_invalid(cls, instance): rop = instance[0] - if rop.isBypassed(): + if hasattr(rop, "isBypassed") and rop.isBypassed(): return [rop] diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 3bff3ff79c..e9f9e87d52 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -98,7 +98,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "camerarig", "redshiftproxy", "effect", - "xgen" + "xgen", + "hda" ] exclude_families = ["clip"] db_representation_context_keys = [ From 2589ce46711fbf19d4c3c9a56fe8c10cad01ef01 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Mon, 27 Sep 2021 14:09:02 +0200 Subject: [PATCH 557/736] hda updating --- .../hosts/houdini/plugins/create/create_hda.py | 3 +++ openpype/hosts/houdini/plugins/load/load_hda.py | 16 +++++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_hda.py b/openpype/hosts/houdini/plugins/create/create_hda.py index d58e0c5e52..05307d4c56 100644 --- a/openpype/hosts/houdini/plugins/create/create_hda.py +++ b/openpype/hosts/houdini/plugins/create/create_hda.py @@ -41,6 +41,9 @@ class CreateHDA(plugin.Creator): hda_node.setName(self.name) hou.moveNodesTo(self.nodes, hda_node) hda_node.layoutChildren() + # delete node created by Avalon in /out + # this needs to be addressed in future Houdini workflow refactor. + hou.node("/out/{}".format(self.name)).destroy() lib.imprint(hda_node, self.data) diff --git a/openpype/hosts/houdini/plugins/load/load_hda.py b/openpype/hosts/houdini/plugins/load/load_hda.py index 5e04d83e86..f923b699d2 100644 --- a/openpype/hosts/houdini/plugins/load/load_hda.py +++ b/openpype/hosts/houdini/plugins/load/load_hda.py @@ -15,7 +15,6 @@ class HdaLoader(api.Loader): color = "orange" def load(self, context, name=None, namespace=None, data=None): - import os import hou @@ -33,7 +32,6 @@ class HdaLoader(api.Loader): node_name = "{0}_{1:03d}".format(formatted, counter) hou.hda.installFile(file_path) - print("installing {}".format(name)) hda_node = obj.createNode(name, node_name) self[:] = [hda_node] @@ -48,9 +46,17 @@ class HdaLoader(api.Loader): ) def update(self, container, representation): - hda_node = container["node"] - hda_def = hda_node.type().definition() + import hou + hda_node = container["node"] + file_path = api.get_representation_path(representation) + file_path = file_path.replace("\\", "/") + hou.hda.installFile(file_path) + defs = hda_node.type().allInstalledDefinitions() + def_paths = [d.libraryFilePath() for d in defs] + new = def_paths.index(file_path) + defs[new].setIsPreferred(True) def remove(self, container): - pass + node = container["node"] + node.destroy() From b56623ff2d45e991d2e14cc63bda4397e4821b59 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Mon, 27 Sep 2021 14:10:37 +0200 Subject: [PATCH 558/736] remove unused import --- openpype/hosts/houdini/plugins/load/load_hda.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/load/load_hda.py b/openpype/hosts/houdini/plugins/load/load_hda.py index f923b699d2..6610d5e513 100644 --- a/openpype/hosts/houdini/plugins/load/load_hda.py +++ b/openpype/hosts/houdini/plugins/load/load_hda.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from avalon import api -from avalon.houdini import pipeline, lib +from avalon.houdini import pipeline class HdaLoader(api.Loader): From 8c55039ddc1981d50b6ab676ce49a61b4a61e76d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 29 Sep 2021 18:04:44 +0200 Subject: [PATCH 559/736] commented out unfinished implementation --- openpype/pipeline/publish/publish_plugins.py | 59 ++++++++++---------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/openpype/pipeline/publish/publish_plugins.py b/openpype/pipeline/publish/publish_plugins.py index bb1a87976b..249b5180f3 100644 --- a/openpype/pipeline/publish/publish_plugins.py +++ b/openpype/pipeline/publish/publish_plugins.py @@ -24,11 +24,36 @@ class KnownPublishError(Exception): class OpenPypePyblishPluginMixin: - executable_in_thread = False - - state_message = None - state_percent = None - _state_change_callbacks = [] + # TODO + # executable_in_thread = False + # + # state_message = None + # state_percent = None + # _state_change_callbacks = [] + # + # def set_state(self, percent=None, message=None): + # """Inner callback of plugin that would help to show in UI state. + # + # Plugin have registered callbacks on state change which could trigger + # update message and percent in UI and repaint the change. + # + # This part must be optional and should not be used to display errors + # or for logging. + # + # Message should be short without details. + # + # Args: + # percent(int): Percent of processing in range <1-100>. + # message(str): Message which will be shown to user (if in UI). + # """ + # if percent is not None: + # self.state_percent = percent + # + # if message: + # self.state_message = message + # + # for callback in self._state_change_callbacks: + # callback(self) @classmethod def get_attribute_defs(cls): @@ -55,27 +80,3 @@ class OpenPypePyblishPluginMixin: plugin_values[key] ) return attribute_values - - def set_state(self, percent=None, message=None): - """Inner callback of plugin that would help to show in UI state. - - Plugin have registered callbacks on state change which could trigger - update message and percent in UI and repaint the change. - - This part must be optional and should not be used to display errors - or for logging. - - Message should be short without details. - - Args: - percent(int): Percent of processing in range <1-100>. - message(str): Message which will be shown to user (if in UI). - """ - if percent is not None: - self.state_percent = percent - - if message: - self.state_message = message - - for callback in self._state_change_callbacks: - callback(self) From f61dccc03225c33f60d0cd29f9a91106cb604a7e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 29 Sep 2021 18:05:01 +0200 Subject: [PATCH 560/736] added docstring to 'KnownPublishError' --- openpype/pipeline/publish/publish_plugins.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/pipeline/publish/publish_plugins.py b/openpype/pipeline/publish/publish_plugins.py index 249b5180f3..b60b9f43a7 100644 --- a/openpype/pipeline/publish/publish_plugins.py +++ b/openpype/pipeline/publish/publish_plugins.py @@ -20,6 +20,10 @@ class PublishValidationError(Exception): class KnownPublishError(Exception): + """Publishing crashed because of known error. + + Message will be shown in UI for artist. + """ pass From b9cee1cc1a323f62c4eb53903e9b44e86baec879 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 29 Sep 2021 18:05:13 +0200 Subject: [PATCH 561/736] initial readme for creation --- openpype/pipeline/create/readme.md | 78 ++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 openpype/pipeline/create/readme.md diff --git a/openpype/pipeline/create/readme.md b/openpype/pipeline/create/readme.md new file mode 100644 index 0000000000..5deb2a1e88 --- /dev/null +++ b/openpype/pipeline/create/readme.md @@ -0,0 +1,78 @@ +# Create +Creation is process defying what and how will be published. May work in a different way based on host implementation. + +## CreateContext +Entry point of creation. All data and metadata are stored to create context. Context hold all global data and instances. Is responsible for loading of plugins (create, publish), loading data from host, validation of host implementation and emitting changes to host implementation. + +Discovers Create plugins to be able create new instances and convert existing instances. Creators may have defined attributes that are specific for the family. Attributes definition can enhance behavior of instance during publishing. + +Publish plugins are loaded because they can also define attributes definitions. These are less family specific To be able define attributes Publish plugin must inherit from `OpenPypePyblishPluginMixin` and must override `get_attribute_defs` class method which must return list of attribute definitions. Values of publish plugin definitions are stored per plugin name under `publish_attributes`. + +Possible attribute definitions can be found in `openpype/pipeline/lib/attribute_definitions.py`. + + +## CreatedInstance +Product of creation is "instance" which holds basic data defying it. Core data are `family` and `subset`. Other data can be keys used to fill subset name or metadata modifying publishing process of the instance (more described later). +Family tells how should be instance processed and subset what name will published item have. +- There are cases when subset is not fully filled during creation and may change during publishing. That is in most of cases caused because instance is related to other instance or instance data do not represent final product. + +`CreatedInstance` is entity holding the data which are stored and used. + +```python +{ + # Immutable data after creation + ## Identifier that this data represents instance for publishing (automatically assigned) + "id": "pyblish.avalon.instance", + ## Identifier of this specific instance (automatically assigned) + "uuid": , + ## Instance family (used from Creator) + "family": , + + # Mutable data + ## Subset name based on subset name template - may change overtime (on context change) + "subset": , + ## Instance is active and will be published + "active": True, + ## Version of instance + "version": 1, + ## Creator specific attributes (defined by Creator) + "family_attributes": {...}, + ## Publish plugin specific plugins (defined by Publish plugin) + "publish_attributes": { + # Attribute values are stored by publish plugin name + # - Duplicated plugin names can cause clashes! + : {...}, + ... + }, + ## Additional data related to instance (`asset`, `task`, etc.) + ... +} +``` + +## Creator +To be able create instance there must be defined a creator. Creator represents a family and handling of it's instances. Is not responsible only about creating new instances but also about updating existing. Family is identifier of creator so there can be only one Creator with same family at a time which helps to handle changes in creation of specific family. + +Creator does not have strictly defined how is new instance created but result be approachable from host implementation and host must have ability to remove the instance metadata without the Creator. That is host specific logic and can't be handled generally. + +### AutoCreator +Auto-creators are automatically executed when CreateContext is reset. They can be used to create instances that should be always available and may not require artist's manual creation (e.g. `workfile`). Should not create duplicated instance and should raise `AutoCreationSkipped` exception when did not create any instance to speed up resetting of context. + +## Host +Host implementation should be main entrance for creators how their logic should work. In most of cases must store data somewhere ideally to workfile if host has workfile and it's possible. + +Host implementation must have available these functions to be able handle creation changes. + +### List all created instances (`list_instances`) +List of all instances for current context (from workfile). Each item is dictionary with all data that are stored. Creators must implement their creation so host will be able to find the instance with this function. + +### Remove instances (`remove_instances`) +Remove instance from context (from workfile). This must remove all metadata about instance so instance is not retrieved with `list_instances`. This is default host implementation of instance removement. Creator can do more cleanup before this function is called and can stop calling of this function completely (e.g. when Creator removed node where are also stored metadata). + +### Update instances (`update_instances`) +Instance data has changed and update of changes should be saved so next call of `list_instances` will return modified values. + +### Get global context data (`get_context_data`) +There are data that are not specific for any instance but are specific for whole context (e.g. Context plugins values). + +### Update global context data (`update_context_data`) +Update global context data. From 38099102300997b323c65a6cf179b85e7c204560 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 29 Sep 2021 18:06:35 +0200 Subject: [PATCH 562/736] added get_context_title to readme --- openpype/pipeline/create/readme.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/pipeline/create/readme.md b/openpype/pipeline/create/readme.md index 5deb2a1e88..ffaa60340c 100644 --- a/openpype/pipeline/create/readme.md +++ b/openpype/pipeline/create/readme.md @@ -76,3 +76,6 @@ There are data that are not specific for any instance but are specific for whole ### Update global context data (`update_context_data`) Update global context data. + +### Get context title (`get_context_title`) +This is optional but is recommended. String returned from this function will be shown in UI. From 1ddf67df78ce8730c587c2b0b26a2cf55fdfefa2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 30 Sep 2021 15:50:47 +0200 Subject: [PATCH 563/736] add transparent background of nice checkbox --- openpype/widgets/nice_checkbox.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/widgets/nice_checkbox.py b/openpype/widgets/nice_checkbox.py index 238d319f26..d550f361ff 100644 --- a/openpype/widgets/nice_checkbox.py +++ b/openpype/widgets/nice_checkbox.py @@ -10,7 +10,7 @@ class NiceCheckbox(QtWidgets.QFrame): super(NiceCheckbox, self).__init__(parent) self.setObjectName("NiceCheckbox") - + self.setAttribute(QtCore.Qt.WA_TranslucentBackground) self.setSizePolicy( QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed From eaafc29840150b85c30a70e12cfa0d30c34546e8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 30 Sep 2021 19:16:44 +0200 Subject: [PATCH 564/736] added back widgets for family items with checkbox at the top --- .../widgets/list_view_widgets.py | 162 +++++++++++++----- 1 file changed, 120 insertions(+), 42 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/list_view_widgets.py b/openpype/tools/new_publisher/widgets/list_view_widgets.py index 33d4a237e2..57d74960ff 100644 --- a/openpype/tools/new_publisher/widgets/list_view_widgets.py +++ b/openpype/tools/new_publisher/widgets/list_view_widgets.py @@ -281,6 +281,7 @@ class ListContextWidget(QtWidgets.QFrame): class InstanceListGroupWidget(QtWidgets.QFrame): expand_changed = QtCore.Signal(str, bool) + toggle_requested = QtCore.Signal(str, int) def __init__(self, family, parent): super(InstanceListGroupWidget, self).__init__(parent) @@ -289,28 +290,47 @@ class InstanceListGroupWidget(QtWidgets.QFrame): self.family = family self._expanded = False - subset_name_label = QtWidgets.QLabel(family, self) - expand_btn = QtWidgets.QToolButton(self) expand_btn.setObjectName("ArrowBtn") expand_btn.setArrowType(QtCore.Qt.RightArrow) expand_btn.setMaximumWidth(14) + subset_name_label = QtWidgets.QLabel(family, self) + + toggle_checkbox = NiceCheckbox(parent=self) + layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(5, 0, 2, 0) + layout.addWidget(expand_btn) layout.addWidget( subset_name_label, 1, QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter ) - layout.addWidget(expand_btn) + layout.addWidget(toggle_checkbox, 0) # self.setAttribute(QtCore.Qt.WA_TranslucentBackground) subset_name_label.setAttribute(QtCore.Qt.WA_TranslucentBackground) expand_btn.setAttribute(QtCore.Qt.WA_TranslucentBackground) expand_btn.clicked.connect(self._on_expand_clicked) + toggle_checkbox.stateChanged.connect(self._on_checkbox_change) + + self._expected_checkstate = None self.subset_name_label = subset_name_label self.expand_btn = expand_btn + self.toggle_checkbox = toggle_checkbox + + def set_checkstate(self, state): + if self.checkstate() == state: + return + + self.toggle_checkbox.setCheckState(state) + + def checkstate(self): + return self.toggle_checkbox.checkState() + + def _on_checkbox_change(self, state): + self.toggle_requested.emit(self.family, state) def _on_expand_clicked(self): self.expand_changed.emit(self.family, not self._expanded) @@ -328,7 +348,6 @@ class InstanceListGroupWidget(QtWidgets.QFrame): class InstanceTreeView(QtWidgets.QTreeView): toggle_requested = QtCore.Signal(int) - family_toggle_requested = QtCore.Signal(str) def __init__(self, *args, **kwargs): super(InstanceTreeView, self).__init__(*args, **kwargs) @@ -355,12 +374,6 @@ class InstanceTreeView(QtWidgets.QTreeView): else: self.collapse(index) - def _toggle_item(self, index): - if index.data(IS_GROUP_ROLE): - self.family_toggle_requested.emit( - index.data(QtCore.Qt.DisplayRole) - ) - def get_selected_instance_ids(self): instance_ids = set() for index in self.selectionModel().selectedIndexes(): @@ -387,12 +400,6 @@ class InstanceTreeView(QtWidgets.QTreeView): return super(InstanceTreeView, self).event(event) - def mouseMoveEvent(self, event): - index = self.indexAt(event.pos()) - if index.data(IS_GROUP_ROLE): - self.update(index) - super(InstanceTreeView, self).mouseMoveEvent(event) - def _mouse_press(self, event): if event.button() != QtCore.Qt.LeftButton: return @@ -432,7 +439,6 @@ class InstanceTreeView(QtWidgets.QTreeView): self._expand_item(pos_index) return True elif not pressed_expander: - self._toggle_item(pos_index) self._expand_item(pos_index, True) return True @@ -453,8 +459,6 @@ class InstanceListView(AbstractInstanceView): self.controller = controller instance_view = InstanceTreeView(self) - instance_delegate = ListItemDelegate(instance_view) - instance_view.setItemDelegate(instance_delegate) instance_model = QtGui.QStandardItemModel() proxy_model = QtCore.QSortFilterProxyModel() @@ -476,13 +480,11 @@ class InstanceListView(AbstractInstanceView): instance_view.collapsed.connect(self._on_collapse) instance_view.expanded.connect(self._on_expand) instance_view.toggle_requested.connect(self._on_toggle_request) - instance_view.family_toggle_requested.connect( - self._on_family_toggle_request - ) self._group_items = {} self._group_widgets = {} self._widgets_by_id = {} + self._family_by_instance_id = {} self._context_item = None self._context_widget = None @@ -516,29 +518,36 @@ class InstanceListView(AbstractInstanceView): if widget is not None: widget.set_active(active) - def _on_family_toggle_request(self, family): - family_item = self._group_items.get(family) - if not family_item: + def _update_family_checkstate(self, family): + widget = self._group_widgets.get(family) + if widget is None: return - instance_ids = [] - all_active = True - for row in range(family_item.rowCount()): - item = family_item.child(row, family_item.column()) - instance_id = item.data(INSTANCE_ID_ROLE) - instance_ids.append(instance_id) - if not all_active: + activity = None + for instance_id, _family in self._family_by_instance_id.items(): + if _family != family: continue - widget = self._widgets_by_id.get(instance_id) - if widget is not None and not widget.is_active(): - all_active = False + instance_widget = self._widgets_by_id.get(instance_id) + if not instance_widget: + continue - active = not all_active - for instance_id in instance_ids: - widget = self._widgets_by_id.get(instance_id) - if widget is not None: - widget.set_active(active) + if activity is None: + activity = int(instance_widget.is_active()) + + elif activity != instance_widget.is_active(): + activity = -1 + break + + if activity is None: + return + + state = QtCore.Qt.PartiallyChecked + if activity == 0: + state = QtCore.Qt.Unchecked + elif activity == 1: + state = QtCore.Qt.Checked + widget.set_checkstate(state) def refresh(self): instances_by_family = collections.defaultdict(list) @@ -575,7 +584,7 @@ class InstanceListView(AbstractInstanceView): if family in self._group_items: continue - group_item = QtGui.QStandardItem(family) + group_item = QtGui.QStandardItem() group_item.setData(family, SORT_VALUE_ROLE) group_item.setData(True, IS_GROUP_ROLE) group_item.setFlags(QtCore.Qt.ItemIsEnabled) @@ -586,12 +595,26 @@ class InstanceListView(AbstractInstanceView): sort_at_the_end = True root_item.appendRows(new_group_items) + for group_item in new_group_items: + index = self.instance_model.index( + group_item.row(), group_item.column() + ) + proxy_index = self.proxy_model.mapFromSource(index) + family = group_item.data(SORT_VALUE_ROLE) + widget = InstanceListGroupWidget(family, self.instance_view) + widget.expand_changed.connect(self._on_group_expand_request) + widget.toggle_requested.connect(self._on_group_toggle_request) + self._group_widgets[family] = widget + self.instance_view.setIndexWidget(proxy_index, widget) + for family in tuple(self._group_items.keys()): if family in families: continue group_item = self._group_items.pop(family) root_item.removeRow(group_item.row()) + widget = self._group_widgets.pop(family) + widget.deleteLater() expand_families = set() for family, group_item in self._group_items.items(): @@ -610,8 +633,17 @@ class InstanceListView(AbstractInstanceView): new_items = [] new_items_with_instance = [] + activity = None for instance in instances_by_family[family]: instance_id = instance.data["uuid"] + if activity is None: + activity = int(instance.data["active"]) + elif activity == -1: + pass + elif activity != instance.data["active"]: + activity = -1 + + self._family_by_instance_id[instance_id] = family if instance_id in to_remove: to_remove.remove(instance_id) widget = self._widgets_by_id[instance_id] @@ -624,6 +656,15 @@ class InstanceListView(AbstractInstanceView): new_items.append(item) new_items_with_instance.append((item, instance)) + state = QtCore.Qt.PartiallyChecked + if activity == 0: + state = QtCore.Qt.Unchecked + elif activity == 1: + state = QtCore.Qt.Checked + + widget = self._group_widgets[family] + widget.set_checkstate(state) + idx_to_remove = [] for instance_id in to_remove: idx_to_remove.append(existing_mapping[instance_id]) @@ -632,6 +673,7 @@ class InstanceListView(AbstractInstanceView): group_item.removeRows(idx, 1) for instance_id in to_remove: + self._family_by_instance_id.pop(instance_id) widget = self._widgets_by_id.pop(instance_id) widget.deleteLater() @@ -684,8 +726,22 @@ class InstanceListView(AbstractInstanceView): selected_ids = set() selected_ids.add(changed_instance_id) - changed_ids = set() + self._change_active_instances(selected_ids, new_value) + families = set() for instance_id in selected_ids: + family = self._family_by_instance_id.get(instance_id) + if family is not None: + families.add(family) + + for family in families: + self._update_family_checkstate(family) + + def _change_active_instances(self, instance_ids, new_value): + if not instance_ids: + return + + changed_ids = set() + for instance_id in instance_ids: widget = self._widgets_by_id.get(instance_id) if widget: changed_ids.add(instance_id) @@ -727,3 +783,25 @@ class InstanceListView(AbstractInstanceView): ) proxy_index = self.proxy_model.mapFromSource(group_index) self.instance_view.setExpanded(proxy_index, expanded) + + def _on_group_toggle_request(self, family, state): + if state == QtCore.Qt.PartiallyChecked: + return + + if state == QtCore.Qt.Checked: + active = True + else: + active = False + + group_item = self._group_items.get(family) + if not group_item: + return + + instance_ids = set() + for row in range(group_item.rowCount()): + item = group_item.child(row) + instance_id = item.data(INSTANCE_ID_ROLE) + if instance_id is not None: + instance_ids.add(instance_id) + + self._change_active_instances(instance_ids, active) From 68c7b06dd6dd1c9b777634179bff34c9cc4338c0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 30 Sep 2021 19:24:56 +0200 Subject: [PATCH 565/736] expand/collapse on click and of toggle change --- .../widgets/list_view_widgets.py | 42 +++++++++---------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/list_view_widgets.py b/openpype/tools/new_publisher/widgets/list_view_widgets.py index 57d74960ff..4c88a2a588 100644 --- a/openpype/tools/new_publisher/widgets/list_view_widgets.py +++ b/openpype/tools/new_publisher/widgets/list_view_widgets.py @@ -314,6 +314,8 @@ class InstanceListGroupWidget(QtWidgets.QFrame): expand_btn.clicked.connect(self._on_expand_clicked) toggle_checkbox.stateChanged.connect(self._on_checkbox_change) + self._ignore_state_change = False + self._expected_checkstate = None self.subset_name_label = subset_name_label @@ -323,14 +325,16 @@ class InstanceListGroupWidget(QtWidgets.QFrame): def set_checkstate(self, state): if self.checkstate() == state: return - + self._ignore_state_change = True self.toggle_checkbox.setCheckState(state) + self._ignore_state_change = False def checkstate(self): return self.toggle_checkbox.checkState() def _on_checkbox_change(self, state): - self.toggle_requested.emit(self.family, state) + if not self._ignore_state_change: + self.toggle_requested.emit(self.family, state) def _on_expand_clicked(self): self.expand_changed.emit(self.family, not self._expanded) @@ -361,7 +365,6 @@ class InstanceTreeView(QtWidgets.QTreeView): ) self.viewport().setMouseTracking(True) self._pressed_group_index = None - self._pressed_expander = None def _expand_item(self, index, expand=None): is_expanded = self.isExpanded(index) @@ -404,23 +407,22 @@ class InstanceTreeView(QtWidgets.QTreeView): if event.button() != QtCore.Qt.LeftButton: return + pressed_group_index = None pos_index = self.indexAt(event.pos()) - if not pos_index.data(IS_GROUP_ROLE): - pressed_group_index = None - pressed_expander = None - else: - height = self.indexRowSizeHint(pos_index) + if pos_index.data(IS_GROUP_ROLE): pressed_group_index = pos_index - pressed_expander = event.pos().x() < height self._pressed_group_index = pressed_group_index - self._pressed_expander = pressed_expander def mousePressEvent(self, event): if not self._mouse_press(event): super(InstanceTreeView, self).mousePressEvent(event) - def _mouse_release(self, event, pressed_expander, pressed_index): + def mouseDoubleClickEvent(self, event): + if not self._mouse_press(event): + super(InstanceTreeView, self).mouseDoubleClickEvent(event) + + def _mouse_release(self, event, pressed_index): if event.button() != QtCore.Qt.LeftButton: return False @@ -433,21 +435,13 @@ class InstanceTreeView(QtWidgets.QTreeView): if len(indexes) != 1 or indexes[0] != pos_index: return False - height = self.indexRowSizeHint(pos_index) - if event.pos().x() < height: - if pressed_expander: - self._expand_item(pos_index) - return True - elif not pressed_expander: - self._expand_item(pos_index, True) - return True + self._expand_item(pos_index) + return True def mouseReleaseEvent(self, event): pressed_index = self._pressed_group_index - pressed_expander = self._pressed_expander is True self._pressed_group_index = None - self._pressed_expander = None - result = self._mouse_release(event, pressed_expander, pressed_index) + result = self._mouse_release(event, pressed_index) if not result: super(InstanceTreeView, self).mouseReleaseEvent(event) @@ -805,3 +799,7 @@ class InstanceListView(AbstractInstanceView): instance_ids.add(instance_id) self._change_active_instances(instance_ids, active) + + proxy_index = self.proxy_model.mapFromSource(group_item.index()) + if not self.instance_view.isExpanded(proxy_index): + self.instance_view.expand(proxy_index) From 00b54eed0cca7f81a532d042fc9412bd4536d1a0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 1 Oct 2021 10:09:35 +0200 Subject: [PATCH 566/736] use item delegate to draw background of group item --- openpype/style/style.css | 3 +- .../widgets/list_view_widgets.py | 140 ++---------------- 2 files changed, 16 insertions(+), 127 deletions(-) diff --git a/openpype/style/style.css b/openpype/style/style.css index 9ee81ae052..d9b8ca2869 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -680,8 +680,7 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { margin: 1px; } #InstanceListGroupWidget { - border: 1px solid white; - border-radius: 0.3em; + border: none; background: transparent; } diff --git a/openpype/tools/new_publisher/widgets/list_view_widgets.py b/openpype/tools/new_publisher/widgets/list_view_widgets.py index 4c88a2a588..1553078f3a 100644 --- a/openpype/tools/new_publisher/widgets/list_view_widgets.py +++ b/openpype/tools/new_publisher/widgets/list_view_widgets.py @@ -17,10 +17,7 @@ from ..constants import ( class ListItemDelegate(QtWidgets.QStyledItemDelegate): """Generic delegate for instance header""" - radius_ratio = 0.3 - expand_image = None - collapse_image = None def __init__(self, parent): super(ListItemDelegate, self).__init__(parent) @@ -33,20 +30,6 @@ class ListItemDelegate(QtWidgets.QStyledItemDelegate): for key, value in group_color_info.items() } - @classmethod - def get_expand_image(cls): - if cls.expand_image is None: - image = get_image("branch_open") - - cls.expand_image = image - return cls.expand_image - - @classmethod - def get_collapse_image(cls): - if cls.collapse_image is None: - cls.collapse_image = get_image("branch_closed") - return cls.collapse_image - def paint(self, painter, option, index): if index.data(IS_GROUP_ROLE): self.group_item_paint(painter, option, index) @@ -55,130 +38,34 @@ class ListItemDelegate(QtWidgets.QStyledItemDelegate): def group_item_paint(self, painter, option, index): self.initStyleOption(option, index) - body_rect = QtCore.QRectF(option.rect) + bg_rect = QtCore.QRectF( - body_rect.left(), body_rect.top() + 1, - body_rect.width(), body_rect.height() - 2 + option.rect.left(), option.rect.top() + 1, + option.rect.width(), option.rect.height() - 2 ) - - expander_rect = QtCore.QRectF(bg_rect) - expander_rect.setWidth(expander_rect.height()) - - remainder_rect = QtCore.QRectF( - expander_rect.x() + expander_rect.width(), - expander_rect.y(), - bg_rect.width() - expander_rect.width(), - expander_rect.height() - ) - ratio = bg_rect.height() * self.radius_ratio bg_path = QtGui.QPainterPath() bg_path.addRoundedRect( QtCore.QRectF(bg_rect), ratio, ratio ) - expander_colors = [self._group_colors["bg-expander"]] - remainder_colors = [self._group_colors["bg"]] - - mouse_pos = option.widget.mapFromGlobal(QtGui.QCursor.pos()) - selected = option.state & QtWidgets.QStyle.State_Selected - hovered = option.state & QtWidgets.QStyle.State_MouseOver - - if selected and hovered: - if expander_rect.contains(mouse_pos): - expander_colors.append( - self._group_colors["bg-expander-selected-hover"] - ) - - else: - remainder_colors.append( - self._group_colors["bg-selected-hover"] - ) - - elif hovered: - if expander_rect.contains(mouse_pos): - expander_colors.append( - self._group_colors["bg-expander-hover"] - ) - - else: - remainder_colors.append( - self._group_colors["bg-hover"] - ) - - # Draw backgrounds + painter.save() painter.setRenderHints( painter.Antialiasing | painter.SmoothPixmapTransform | painter.TextAntialiasing ) - painter.save() - painter.setClipRect(expander_rect) - for color in expander_colors: - painter.fillPath(bg_path, color) - painter.setClipRect(remainder_rect) - for color in remainder_colors: - painter.fillPath(bg_path, color) - painter.restore() + # Draw backgrounds + painter.fillPath(bg_path, self._group_colors["bg"]) + selected = option.state & QtWidgets.QStyle.State_Selected + hovered = option.state & QtWidgets.QStyle.State_MouseOver + if selected and hovered: + painter.fillPath(bg_path, self._group_colors["bg-selected-hover"]) - # Draw text and icon - font = index.data(QtCore.Qt.FontRole) - if not font: - font = option.font + elif hovered: + painter.fillPath(bg_path, self._group_colors["bg-hover"]) - font_metrics = QtGui.QFontMetrics(font) - - # Center label horizontally - diff = remainder_rect.height() - font_metrics.height() - offset = (diff + (diff % 2)) / 2 - label_rect = QtCore.QRectF(remainder_rect.adjusted( - 5, offset, 0, 0 - )) - - if self.parent().isExpanded(index): - expander_icon = self.get_expand_image() - else: - expander_icon = self.get_collapse_image() - label = index.data(QtCore.Qt.DisplayRole) - - label = font_metrics.elidedText( - label, QtCore.Qt.ElideRight, label_rect.width() - ) - - # Maintain reference to state, so we can restore it once we're done - painter.save() - - width = expander_rect.width() - height = expander_rect.height() - if height < width: - size = height - else: - size = width - - icon_size = int(size / 2) - offset = (size - icon_size) / 2 - - icon_width = expander_icon.width() - icon_height = expander_icon.height() - pos_x = expander_rect.x() + offset - pos_y = expander_rect.y() + offset - if icon_width < icon_height: - icon_width = (icon_size / icon_height) * icon_width - pos_x += (icon_size - icon_width) / 2 - elif icon_height < icon_width: - icon_height = (icon_size / icon_width) * icon_height - pos_y += (icon_size - icon_height) / 2 - - expander_icon_rect = QtCore.QRectF(pos_x, pos_y, icon_size, icon_size) - expander_icon_rect.moveCenter(expander_rect.center()) - painter.drawImage(expander_icon_rect, expander_icon) - - # Draw label - painter.setFont(font) - painter.drawText(label_rect, label) - - # Ok, we're done, tidy up. painter.restore() @@ -453,6 +340,8 @@ class InstanceListView(AbstractInstanceView): self.controller = controller instance_view = InstanceTreeView(self) + instance_delegate = ListItemDelegate(instance_view) + instance_view.setItemDelegate(instance_delegate) instance_model = QtGui.QStandardItemModel() proxy_model = QtCore.QSortFilterProxyModel() @@ -483,6 +372,7 @@ class InstanceListView(AbstractInstanceView): self._context_widget = None self.instance_view = instance_view + self._instance_delegate = instance_delegate self.instance_model = instance_model self.proxy_model = proxy_model From 525ced9e7cb96e865cafe7e5664fc78d249f7dc4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 1 Oct 2021 10:12:44 +0200 Subject: [PATCH 567/736] instance list items are not editable --- openpype/tools/new_publisher/widgets/validations_widget.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/tools/new_publisher/widgets/validations_widget.py b/openpype/tools/new_publisher/widgets/validations_widget.py index a20890ecd7..a940a3f643 100644 --- a/openpype/tools/new_publisher/widgets/validations_widget.py +++ b/openpype/tools/new_publisher/widgets/validations_widget.py @@ -72,6 +72,9 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget): for instance in instances: label = instance.data.get("label") or instance.data.get("name") item = QtGui.QStandardItem(label) + item.setFlags( + QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable + ) item.setData(instance.id) items.append(item) From 8d03dade5004fee685897bbf423fd37abbc23f3c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 1 Oct 2021 10:13:42 +0200 Subject: [PATCH 568/736] removed unused import --- openpype/tools/new_publisher/widgets/list_view_widgets.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/tools/new_publisher/widgets/list_view_widgets.py b/openpype/tools/new_publisher/widgets/list_view_widgets.py index 1553078f3a..f1ac23018e 100644 --- a/openpype/tools/new_publisher/widgets/list_view_widgets.py +++ b/openpype/tools/new_publisher/widgets/list_view_widgets.py @@ -5,7 +5,6 @@ from Qt import QtWidgets, QtCore, QtGui from openpype.style import get_objected_colors from openpype.widgets.nice_checkbox import NiceCheckbox from .widgets import AbstractInstanceView -from .icons import get_image from ..constants import ( INSTANCE_ID_ROLE, SORT_VALUE_ROLE, From 1d0e08721f9f401cb0da3e9d4d2ff29e091904a9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 1 Oct 2021 17:24:02 +0200 Subject: [PATCH 569/736] modified update instances --- openpype/hosts/testhost/api/pipeline.py | 26 ++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/testhost/api/pipeline.py b/openpype/hosts/testhost/api/pipeline.py index a154ca18b7..49f1d3f33d 100644 --- a/openpype/hosts/testhost/api/pipeline.py +++ b/openpype/hosts/testhost/api/pipeline.py @@ -108,22 +108,22 @@ def list_instances(): def update_instances(update_list): - current_instances = HostContext.get_instances() - + updated_instances = {} for instance, _changes in update_list: - instance_id = instance.data["uuid"] + updated_instances[instance.id] = instance.data_to_store() - found_idx = None - for idx, current_instance in enumerate(current_instances): - if instance_id == current_instance["uuid"]: - found_idx = idx - break + instances = HostContext.get_instances() + for instance_data in instances: + instance_id = instance_data["uuid"] + if instance_id in updated_instances: + new_instance_data = updated_instances[instance_id] + old_keys = set(instance_data.keys()) + new_keys = set(new_instance_data.keys()) + instance_data.update(new_instance_data) + for key in (old_keys - new_keys): + instance_data.pop(key) - if found_idx is None: - return - - current_instances[found_idx] = instance.data_to_store() - HostContext.save_instances(current_instances) + HostContext.save_instances(instances) def remove_instances(instances): From 6fcb38fc7243250cc75b9a11024116bab453ec00 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 1 Oct 2021 17:24:35 +0200 Subject: [PATCH 570/736] creators have more responsibility about instances --- openpype/pipeline/create/creator_plugins.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 85f6f0cd75..6e5b4b5991 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -90,14 +90,24 @@ class BaseCreator: """ pass - def remove_instance(self, instance): + @abstractmethod + def collect_instances(self, attr_plugins=None): + pass + + @abstractmethod + def update_instances(self, update_list): + pass + + @abstractmethod + def remove_instances(self, instances): """Method called on instance removement. Can also remove instance metadata from context but should return 'True' if did so. Args: - instance(CreatedInstance): Instance object which will be removed. + instance(list): Instance objects which should be + removed. Returns: bool: Instance was removed completely and is not required to call From 7dfa424c9bb37b7a450090f1dec93d760a62ad1d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 1 Oct 2021 17:24:51 +0200 Subject: [PATCH 571/736] remove_instances is empty on autocreators --- openpype/pipeline/create/creator_plugins.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 6e5b4b5991..d7e2d0ffee 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -290,6 +290,7 @@ class AutoCreator(BaseCreator): """Creator which is automatically triggered without user interaction. Can be used e.g. for `workfile`. - - Should raise 'AutoCreationSkipped' if has nothing to do. """ + def remove_instances(self, instances): + """Skip removement.""" + pass From 521a701c2ee551e60c76726f625fe0dc078058fa Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 1 Oct 2021 17:25:08 +0200 Subject: [PATCH 572/736] creators have required attribute identifier --- openpype/pipeline/create/creator_plugins.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index d7e2d0ffee..c9df67e583 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -69,6 +69,11 @@ class BaseCreator: # - we may use UI inside processing this attribute should be checked self.headless = headless + @abstractproperty + def identifier(self): + """Family that plugin represents.""" + pass + @abstractproperty def family(self): """Family that plugin represents.""" From 256d1784c68b6bd02140f403e27f6ce9dbd46408 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 1 Oct 2021 18:54:55 +0200 Subject: [PATCH 573/736] changed creators in testhost for new structure --- .../testhost/plugins/create/auto_creator.py | 38 +++++++++++++++---- .../testhost/plugins/create/test_creator_1.py | 31 +++++++++++++-- .../testhost/plugins/create/test_creator_2.py | 31 +++++++++++++-- 3 files changed, 86 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/testhost/plugins/create/auto_creator.py b/openpype/hosts/testhost/plugins/create/auto_creator.py index c578fae74b..c3bbd5dd74 100644 --- a/openpype/hosts/testhost/plugins/create/auto_creator.py +++ b/openpype/hosts/testhost/plugins/create/auto_creator.py @@ -1,4 +1,4 @@ -from openpype.hosts.testhost import api +from openpype.hosts.testhost.api import pipeline from openpype.pipeline import ( AutoCreator, CreatedInstance, @@ -8,8 +8,36 @@ from avalon import io class MyAutoCreator(AutoCreator): + identifier = "workfile" family = "workfile" + def get_attribute_defs(self): + output = [ + lib.NumberDef("number_key", label="Number") + ] + return output + + def collect_instances(self, attr_plugins=None): + for instance_data in pipeline.list_instances(): + creator_id = instance_data.get("creator_identifier") + if creator_id is not None: + if creator_id == self.identifier: + subset_name = instance_data["subset"] + instance = CreatedInstance( + self.family, subset_name, instance_data, self + ) + self.create_context.add_instance(instance) + + elif instance_data["family"] == self.identifier: + instance_data["creator_identifier"] = self.identifier + instance = CreatedInstance.from_existing( + instance_data, self, attr_plugins + ) + self.create_context.add_instance(instance) + + def update_instances(self, update_list): + pipeline.update_instances(update_list) + def create(self, options=None): existing_instance = None for instance in self.create_context.instances: @@ -40,7 +68,7 @@ class MyAutoCreator(AutoCreator): existing_instance = CreatedInstance( self.family, subset_name, data, self ) - api.pipeline.HostContext.add_instance( + pipeline.HostContext.add_instance( existing_instance.data_to_store() ) @@ -56,9 +84,3 @@ class MyAutoCreator(AutoCreator): existing_instance.data["task"] = task_name return existing_instance - - def get_attribute_defs(self): - output = [ - lib.NumberDef("number_key", label="Number") - ] - return output diff --git a/openpype/hosts/testhost/plugins/create/test_creator_1.py b/openpype/hosts/testhost/plugins/create/test_creator_1.py index b7cd652905..1a6fc0cdf5 100644 --- a/openpype/hosts/testhost/plugins/create/test_creator_1.py +++ b/openpype/hosts/testhost/plugins/create/test_creator_1.py @@ -1,5 +1,5 @@ from openpype import resources -from openpype.hosts.testhost import api +from openpype.hosts.testhost.api import pipeline from openpype.pipeline import ( Creator, CreatedInstance, @@ -8,15 +8,40 @@ from openpype.pipeline import ( class TestCreatorOne(Creator): - family = "test_one" + identifier = "test_one" + family = "test" description = "Testing creator of testhost" def get_icon(self): return resources.get_openpype_splash_filepath() + def collect_instances(self, attr_plugins): + for instance_data in pipeline.list_instances(): + creator_id = instance_data.get("creator_identifier") + if creator_id is not None: + if creator_id == self.identifier: + subset_name = instance_data["subset"] + instance = CreatedInstance( + self.family, subset_name, instance_data, self + ) + self.create_context.add_instance(instance) + + elif instance_data["family"] == self.identifier: + instance_data["creator_identifier"] = self.identifier + instance = CreatedInstance.from_existing( + instance_data, self, attr_plugins + ) + self.create_context.add_instance(instance) + + def update_instances(self, update_list): + pipeline.update_instances(update_list) + + def remove_instances(self, instances): + pipeline.remove_instances(instances) + def create(self, subset_name, data, options=None): avalon_instance = CreatedInstance(self.family, subset_name, data, self) - api.pipeline.HostContext.add_instance(avalon_instance.data_to_store()) + pipeline.HostContext.add_instance(avalon_instance.data_to_store()) self.log.info(avalon_instance.data) return avalon_instance diff --git a/openpype/hosts/testhost/plugins/create/test_creator_2.py b/openpype/hosts/testhost/plugins/create/test_creator_2.py index 81df6f2b82..a2e4ebb13b 100644 --- a/openpype/hosts/testhost/plugins/create/test_creator_2.py +++ b/openpype/hosts/testhost/plugins/create/test_creator_2.py @@ -1,4 +1,4 @@ -from openpype.hosts.testhost import api +from openpype.hosts.testhost.api import pipeline from openpype.pipeline import ( Creator, CreatedInstance, @@ -7,7 +7,8 @@ from openpype.pipeline import ( class TestCreatorTwo(Creator): - family = "test_two" + identifier = "test_two" + family = "test" description = "A second testing creator" def get_icon(self): @@ -15,10 +16,34 @@ class TestCreatorTwo(Creator): def create(self, subset_name, data, options=None): avalon_instance = CreatedInstance(self.family, subset_name, data, self) - api.pipeline.HostContext.add_instance(avalon_instance.data_to_store()) + pipeline.HostContext.add_instance(avalon_instance.data_to_store()) self.log.info(avalon_instance.data) return avalon_instance + def collect_instances(self, attr_plugins): + for instance_data in pipeline.list_instances(): + creator_id = instance_data.get("creator_identifier") + if creator_id is not None: + if creator_id == self.identifier: + subset_name = instance_data["subset"] + instance = CreatedInstance( + self.family, subset_name, instance_data, self + ) + self.create_context.add_instance(instance) + + elif instance_data["family"] == self.identifier: + instance_data["creator_identifier"] = self.identifier + instance = CreatedInstance.from_existing( + instance_data, self, attr_plugins + ) + self.create_context.add_instance(instance) + + def update_instances(self, update_list): + pipeline.update_instances(update_list) + + def remove_instances(self, instances): + pipeline.remove_instances(instances) + def get_attribute_defs(self): output = [ lib.NumberDef("number_key"), From 69a74162e1eac17e4104ba66197bcf6895e52da6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 1 Oct 2021 18:56:00 +0200 Subject: [PATCH 574/736] instance has properties that are not modifiable --- openpype/pipeline/create/context.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 2e412dadb6..cae507ea03 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -342,10 +342,6 @@ class CreatedInstance: self.host = host self.creator = creator - # Family of instance - self.family = family - # Subset name - self.subset_name = subset_name # Instance members may have actions on them self._members = [] @@ -369,6 +365,7 @@ class CreatedInstance: self._data["family"] = family self._data["subset"] = subset_name self._data["active"] = data.get("active", True) + self._data["creator_identifier"] = creator.identifier # QUESTION handle version of instance here or in creator? if new: @@ -404,6 +401,18 @@ class CreatedInstance: self._asset_is_valid = self.has_set_asset self._task_is_valid = self.has_set_task + @property + def family(self): + return self._data["family"] + + @property + def subset_name(self): + return self._data["subset"] + + @property + def creator_identifier(self): + return self.creator.identifier + @property def has_set_asset(self): """Asset name is set in data.""" From 3aa66f97c1e633aa025ba9dcf6e5f0ebbb5e2752 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 1 Oct 2021 18:58:59 +0200 Subject: [PATCH 575/736] removed responsibility for listing, updating and removing instanctes from host --- openpype/pipeline/create/context.py | 145 +++++++++++++--------------- 1 file changed, 65 insertions(+), 80 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index cae507ea03..98ca9f606d 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -7,7 +7,6 @@ from uuid import uuid4 from ..lib import UnknownDef from .creator_plugins import ( - AutoCreationSkipped, BaseCreator, Creator, AutoCreator @@ -332,7 +331,7 @@ class CreatedInstance: already existing instance. """ def __init__( - self, family, subset_name, data=None, creator=None, host=None, + self, family, subset_name, data, creator, host=None, attr_plugins=None, new=True ): if host is None: @@ -517,7 +516,7 @@ class CreatedInstance: @classmethod def from_existing( - cls, instance_data, creator=None, host=None, attr_plugins=None + cls, instance_data, creator, attr_plugins=None, host=None ): """Convert instance data from workfile to CreatedInstance.""" instance_data = copy.deepcopy(instance_data) @@ -548,9 +547,6 @@ class CreateContext: # Methods required in host implementaion to be able create instances # or change context data. required_methods = ( - "list_instances", - "remove_instances", - "update_instances", "get_context_data", "update_context_data" ) @@ -588,6 +584,7 @@ class CreateContext: self._host_is_valid = host_is_valid self.headless = headless + # TODO convert to dictionary instance by id to validate duplicates self.instances = [] # Discovered creators @@ -601,6 +598,8 @@ class CreateContext: self.plugins_with_defs = [] self._attr_plugins_by_family = {} + self._reseting = False + if reset: self.reset(discover_publish_plugins) @@ -627,12 +626,17 @@ class CreateContext: return self._log def reset(self, discover_publish_plugins=True): + self._reseting = True + self.reset_plugins(discover_publish_plugins) self.reset_context_data() self.reset_instances() - self.execute_autocreators() + self._validate_instances_context(self.instances) + + self._reseting = False + def reset_plugins(self, discover_publish_plugins=True): import avalon.api import pyblish.logic @@ -682,18 +686,18 @@ class CreateContext: ) continue - family = creator_class.family + creator_identifier = creator_class.identifier creator = creator_class( self, system_settings, project_settings, self.headless ) - creators[family] = creator + creators[creator_identifier] = creator if isinstance(creator, AutoCreator): - autocreators[family] = creator + autocreators[creator_identifier] = creator elif isinstance(creator, Creator): - ui_creators[family] = creator + ui_creators[creator_identifier] = creator self.autocreators = autocreators self.ui_creators = ui_creators @@ -728,29 +732,43 @@ class CreateContext: changes["publish_attributes"] = publish_attribute_changes return changes + def add_instance(self, instance): + self.instances.append(instance) + if not self._reseting: + self._validate_instances_context([instance]) + def reset_instances(self): - instances = [] - if not self.host_is_valid: - self.instances = instances - return + self.instances = [] # Collect instances - host_instances = self.host.list_instances() - task_names_by_asset_name = collections.defaultdict(set) - for instance_data in host_instances: - family = instance_data["family"] - # Prepare publish plugins with attribute definitions - creator = self.creators.get(family) + for creator in self.creators.values(): + family = creator.family attr_plugins = self._get_publish_plugins_with_attr_for_family( family ) - instance = CreatedInstance.from_existing( - instance_data, creator, self.host, attr_plugins - ) - instances.append(instance) + creator.collect_instances(attr_plugins) - task_name = instance_data.get("task") - asset_name = instance_data.get("asset") + def execute_autocreators(self): + """Execute discovered AutoCreator plugins. + + Reset instances if any autocreator executed properly. + """ + for family, creator in self.autocreators.items(): + try: + creator.create() + + except Exception: + # TODO raise report exception if any crashed + msg = ( + "Failed to run AutoCreator with family \"{}\" ({})." + ).format(family, inspect.getfile(creator.__class__)) + self.log.warning(msg, exc_info=True) + + def _validate_instances_context(self, instances): + task_names_by_asset_name = collections.defaultdict(set) + for instance in instances: + task_name = instance.data.get("task") + asset_name = instance.data.get("asset") if asset_name and task_name: task_names_by_asset_name[asset_name].add(task_name) @@ -792,35 +810,6 @@ class CreateContext: if task_name not in task_names_by_asset_name[asset_name]: instance.set_task_invalid(True) - self.instances = instances - - def execute_autocreators(self): - """Execute discovered AutoCreator plugins. - - Reset instances if any autocreator executed properly. - - Autocreatos should raise 'AutoCreationSkipped' if has nothing to do. - - execution of autocreator requires to reset instances (can be time - time consuming) - """ - any_processed = False - for family, creator in self.autocreators.items(): - try: - creator.create() - any_processed = True - - except AutoCreationSkipped as exc: - self.log.debug(str(exc)) - - except Exception: - msg = ( - "Failed to run AutoCreator with family \"{}\" ({})." - ).format(family, inspect.getfile(creator.__class__)) - self.log.warning(msg, exc_info=True) - - if any_processed: - self.reset_instances() - def save_changes(self): if not self.host_is_valid: missing_methods = self.get_host_misssing_methods(self.host) @@ -836,14 +825,21 @@ class CreateContext: self.host.update_context_data(data, changes) def _save_instance_changes(self): - update_list = [] + instances_by_identifier = collections.defaultdict(list) for instance in self.instances: - instance_changes = instance.changes() - if instance_changes: - update_list.append((instance, instance_changes)) + identifier = instance.creator_identifier + instances_by_identifier[identifier].append(instance) - if update_list: - self.host.update_instances(update_list) + for identifier, cretor_instances in instances_by_identifier.items(): + update_list = [] + for instance in cretor_instances: + instance_changes = instance.changes() + if instance_changes: + update_list.append((instance, instance_changes)) + + creator = self.creators[identifier] + if update_list: + creator.update_instances(update_list) def remove_instances(self, instances): """Remove instances from context. @@ -852,27 +848,16 @@ class CreateContext: instances(list): Instances that should be removed from context. """ - if not self.host_is_valid: - missing_methods = self.get_host_misssing_methods(self.host) - raise HostMissRequiredMethod(self.host, missing_methods) - - instances_by_family = collections.defaultdict(list) + instances_by_identifier = collections.defaultdict(list) for instance in instances: - instances_by_family[instance.family].append(instance) + identifier = instance.creator_identifier + instances_by_identifier[identifier].append(instance) instances_to_remove = [] - for family, family_instances in instances_by_family.items(): - creator = self.creators.get(family) - if not creator: - instances_to_remove.extend(family_instances) - continue - - for instance in family_instances: - if not creator.remove_instance(instance): - instances_to_remove.append(instances_to_remove) - - if instances_to_remove: - self.host.remove_instances(instances_to_remove) + for identifier, creator_instances in instances_by_identifier.items(): + creator = self.creators.get(identifier) + creator.remove_instances(creator_instances) + instances_to_remove.append(instances_to_remove) def _get_publish_plugins_with_attr_for_family(self, family): if family not in self._attr_plugins_by_family: From 1457d2874e0ce518c034166319b2f940b9409452 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 1 Oct 2021 18:59:36 +0200 Subject: [PATCH 576/736] removed unused AutoCreationSkipped --- openpype/pipeline/create/__init__.py | 2 -- openpype/pipeline/create/creator_plugins.py | 16 ---------------- 2 files changed, 18 deletions(-) diff --git a/openpype/pipeline/create/__init__.py b/openpype/pipeline/create/__init__.py index cde6b1e9f8..610ef6d8e2 100644 --- a/openpype/pipeline/create/__init__.py +++ b/openpype/pipeline/create/__init__.py @@ -1,6 +1,5 @@ from .creator_plugins import ( CreatorError, - AutoCreationSkipped, BaseCreator, Creator, @@ -15,7 +14,6 @@ from .context import ( __all__ = ( "CreatorError", - "AutoCreationSkipped", "BaseCreator", "Creator", diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index c9df67e583..cd447c4f27 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -21,22 +21,6 @@ class CreatorError(Exception): super(CreatorError, self).__init__(message) -class AutoCreationSkipped(Exception): - """Should be raised when AutoCreator has nothing to do. - - When all instances that would autocreator create already exists. - - Message won't be shown in UI. - """ - - def __init__(self, message): - self._message = message - super(AutoCreationSkipped, self).__init__(message) - - def __str__(self): - return "<{}> {}".format(self.__class__.__name__, self._message) - - @six.add_metaclass(ABCMeta) class BaseCreator: """Plugin that create and modify instance data before publishing process. From 3e8c97e9d622b94922b68698e3a6705c8f33989d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 1 Oct 2021 19:13:59 +0200 Subject: [PATCH 577/736] moved python 2 compatibility to openpype.lib --- .../_python2_comp.py => lib/python_2_comp.py} | 0 openpype/tools/new_publisher/control.py | 31 +++++++++++++------ 2 files changed, 22 insertions(+), 9 deletions(-) rename openpype/{tools/new_publisher/_python2_comp.py => lib/python_2_comp.py} (100%) diff --git a/openpype/tools/new_publisher/_python2_comp.py b/openpype/lib/python_2_comp.py similarity index 100% rename from openpype/tools/new_publisher/_python2_comp.py rename to openpype/lib/python_2_comp.py diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index b64d0cef12..19cc7730ab 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -1,13 +1,15 @@ import os import copy +import inspect import logging import traceback import collections +import weakref try: from weakref import WeakMethod except Exception: - from ._python2_comp import WeakMethod + from openpype.lib.python2_comp import WeakMethod import avalon.api import pyblish.api @@ -413,37 +415,48 @@ class PublisherController: def plugins_with_defs(self): return self.create_context.plugins_with_defs + def _create_reference(self, callback): + if inspect.ismethod(callback): + ref = WeakMethod(callback) + elif callable(callback): + ref = weakref.ref(callback) + else: + raise TypeError("Expected function or method got {}".format( + str(type(callback)) + )) + return ref + def add_instances_refresh_callback(self, callback): - ref = WeakMethod(callback) + ref = self._create_reference(callback) self._instances_refresh_callback_refs.add(ref) def add_plugins_refresh_callback(self, callback): - ref = WeakMethod(callback) + ref = self._create_reference(callback) self._plugins_refresh_callback_refs.add(ref) # --- Publish specific callbacks --- def add_publish_reset_callback(self, callback): - ref = WeakMethod(callback) + ref = self._create_reference(callback) self._publish_reset_callback_refs.add(ref) def add_publish_started_callback(self, callback): - ref = WeakMethod(callback) + ref = self._create_reference(callback) self._publish_started_callback_refs.add(ref) def add_publish_validated_callback(self, callback): - ref = WeakMethod(callback) + ref = self._create_reference(callback) self._publish_validated_callback_refs.add(ref) def add_instance_change_callback(self, callback): - ref = WeakMethod(callback) + ref = self._create_reference(callback) self._publish_instance_changed_callback_refs.add(ref) def add_plugin_change_callback(self, callback): - ref = WeakMethod(callback) + ref = self._create_reference(callback) self._publish_plugin_changed_callback_refs.add(ref) def add_publish_stopped_callback(self, callback): - ref = WeakMethod(callback) + ref = self._create_reference(callback) self._publish_stopped_callback_refs.add(ref) def get_asset_docs(self): From d15240d2a3d5fce5a0d53f3c90cec3abe325732e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 1 Oct 2021 19:30:48 +0200 Subject: [PATCH 578/736] implemented helper private methods for creators --- .../hosts/testhost/plugins/create/auto_creator.py | 6 +++--- .../testhost/plugins/create/test_creator_1.py | 14 ++++++++------ .../testhost/plugins/create/test_creator_2.py | 14 ++++++++------ openpype/pipeline/create/creator_plugins.py | 12 +++++++----- 4 files changed, 26 insertions(+), 20 deletions(-) diff --git a/openpype/hosts/testhost/plugins/create/auto_creator.py b/openpype/hosts/testhost/plugins/create/auto_creator.py index c3bbd5dd74..017e391dd6 100644 --- a/openpype/hosts/testhost/plugins/create/auto_creator.py +++ b/openpype/hosts/testhost/plugins/create/auto_creator.py @@ -26,14 +26,14 @@ class MyAutoCreator(AutoCreator): instance = CreatedInstance( self.family, subset_name, instance_data, self ) - self.create_context.add_instance(instance) + self._add_instance_to_context(instance) elif instance_data["family"] == self.identifier: instance_data["creator_identifier"] = self.identifier instance = CreatedInstance.from_existing( instance_data, self, attr_plugins ) - self.create_context.add_instance(instance) + self._add_instance_to_context(instance) def update_instances(self, update_list): pipeline.update_instances(update_list) @@ -83,4 +83,4 @@ class MyAutoCreator(AutoCreator): existing_instance.data["asset"] = asset_name existing_instance.data["task"] = task_name - return existing_instance + self._add_instance_to_context(existing_instance) diff --git a/openpype/hosts/testhost/plugins/create/test_creator_1.py b/openpype/hosts/testhost/plugins/create/test_creator_1.py index 1a6fc0cdf5..1b4b944824 100644 --- a/openpype/hosts/testhost/plugins/create/test_creator_1.py +++ b/openpype/hosts/testhost/plugins/create/test_creator_1.py @@ -24,26 +24,28 @@ class TestCreatorOne(Creator): instance = CreatedInstance( self.family, subset_name, instance_data, self ) - self.create_context.add_instance(instance) + self._add_instance_to_context(instance) elif instance_data["family"] == self.identifier: instance_data["creator_identifier"] = self.identifier instance = CreatedInstance.from_existing( instance_data, self, attr_plugins ) - self.create_context.add_instance(instance) + self._add_instance_to_context(instance) def update_instances(self, update_list): pipeline.update_instances(update_list) def remove_instances(self, instances): pipeline.remove_instances(instances) + for instance in instances: + self._remove_instance_from_context(instance) def create(self, subset_name, data, options=None): - avalon_instance = CreatedInstance(self.family, subset_name, data, self) - pipeline.HostContext.add_instance(avalon_instance.data_to_store()) - self.log.info(avalon_instance.data) - return avalon_instance + new_instance = CreatedInstance(self.family, subset_name, data, self) + pipeline.HostContext.add_instance(new_instance.data_to_store()) + self.log.info(new_instance.data) + self._add_instance_to_context(new_instance) def get_default_variants(self): return [ diff --git a/openpype/hosts/testhost/plugins/create/test_creator_2.py b/openpype/hosts/testhost/plugins/create/test_creator_2.py index a2e4ebb13b..b311e12a3e 100644 --- a/openpype/hosts/testhost/plugins/create/test_creator_2.py +++ b/openpype/hosts/testhost/plugins/create/test_creator_2.py @@ -15,10 +15,10 @@ class TestCreatorTwo(Creator): return "cube" def create(self, subset_name, data, options=None): - avalon_instance = CreatedInstance(self.family, subset_name, data, self) - pipeline.HostContext.add_instance(avalon_instance.data_to_store()) - self.log.info(avalon_instance.data) - return avalon_instance + new_instance = CreatedInstance(self.family, subset_name, data, self) + pipeline.HostContext.add_instance(new_instance.data_to_store()) + self.log.info(new_instance.data) + self._add_instance_to_context(new_instance) def collect_instances(self, attr_plugins): for instance_data in pipeline.list_instances(): @@ -29,20 +29,22 @@ class TestCreatorTwo(Creator): instance = CreatedInstance( self.family, subset_name, instance_data, self ) - self.create_context.add_instance(instance) + self._add_instance_to_context(instance) elif instance_data["family"] == self.identifier: instance_data["creator_identifier"] = self.identifier instance = CreatedInstance.from_existing( instance_data, self, attr_plugins ) - self.create_context.add_instance(instance) + self._add_instance_to_context(instance) def update_instances(self, update_list): pipeline.update_instances(update_list) def remove_instances(self, instances): pipeline.remove_instances(instances) + for instance in instances: + self._remove_instance_from_context(instance) def get_attribute_defs(self): output = [ diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index cd447c4f27..5cf6a6f991 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -69,6 +69,12 @@ class BaseCreator: self._log = logging.getLogger(self.__class__.__name__) return self._log + def _add_instance_to_context(self, instance): + self.create_context.creator_adds_instance(instance) + + def _remove_instance_from_context(self, instance): + self.create_context.creator_removed_instance(instance) + @abstractmethod def create(self, options=None): """Create new instance. @@ -97,12 +103,8 @@ class BaseCreator: Args: instance(list): Instance objects which should be removed. - - Returns: - bool: Instance was removed completely and is not required to call - host's implementation of removement. """ - return False + pass def get_icon(self): """Icon of creator (family). From c65eef9c53b96513461bdc7ce16d4caca78227db Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 1 Oct 2021 19:32:01 +0200 Subject: [PATCH 579/736] added creator_identifier to instances.json --- openpype/hosts/testhost/api/instances.json | 26 ++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/testhost/api/instances.json b/openpype/hosts/testhost/api/instances.json index 0419bc2181..e7999ae523 100644 --- a/openpype/hosts/testhost/api/instances.json +++ b/openpype/hosts/testhost/api/instances.json @@ -17,7 +17,8 @@ "CollectFtrackApi": { "add_ftrack_family": false } - } + }, + "creator_identifier": "test_one" }, { "id": "pyblish.avalon.instance", @@ -34,7 +35,8 @@ "CollectFtrackApi": { "add_ftrack_family": true } - } + }, + "creator_identifier": "test_one" }, { "id": "pyblish.avalon.instance", @@ -51,7 +53,8 @@ "CollectFtrackApi": { "add_ftrack_family": true } - } + }, + "creator_identifier": "test_two" }, { "id": "pyblish.avalon.instance", @@ -68,7 +71,8 @@ "CollectFtrackApi": { "add_ftrack_family": true } - } + }, + "creator_identifier": "test_two" }, { "id": "pyblish.avalon.instance", @@ -86,5 +90,19 @@ "add_ftrack_family": true } } + }, + { + "id": "pyblish.avalon.instance", + "family": "workfile", + "subset": "workfileMain", + "active": true, + "creator_identifier": "workfile", + "version": 1, + "asset": "Alpaca_01", + "task": "modeling", + "variant": "Main", + "uuid": "7c9ddfc7-9f9c-4c1c-b233-38c966735fb6", + "family_attributes": {}, + "publish_attributes": {} } ] \ No newline at end of file From f3115b85e315209122043e85abb1c674d7a26f2e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 1 Oct 2021 19:32:32 +0200 Subject: [PATCH 580/736] validation is public method --- openpype/pipeline/create/context.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 98ca9f606d..11510ba653 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -633,7 +633,7 @@ class CreateContext: self.reset_instances() self.execute_autocreators() - self._validate_instances_context(self.instances) + self.validate_instances_context() self._reseting = False @@ -735,7 +735,7 @@ class CreateContext: def add_instance(self, instance): self.instances.append(instance) if not self._reseting: - self._validate_instances_context([instance]) + self.validate_instances_context([instance]) def reset_instances(self): self.instances = [] @@ -764,7 +764,9 @@ class CreateContext: ).format(family, inspect.getfile(creator.__class__)) self.log.warning(msg, exc_info=True) - def _validate_instances_context(self, instances): + def validate_instances_context(self, instances=None): + if instances is None: + instances = self.instances task_names_by_asset_name = collections.defaultdict(set) for instance in instances: task_name = instance.data.get("task") From a1ec6c0aab51289e099d2409736fc17db6631aee Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 1 Oct 2021 19:32:57 +0200 Subject: [PATCH 581/736] added method for removed instances and change name of `add_instance` to `creator_adds_instance` --- openpype/pipeline/create/context.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 11510ba653..24b08c103d 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -732,11 +732,14 @@ class CreateContext: changes["publish_attributes"] = publish_attribute_changes return changes - def add_instance(self, instance): + def creator_adds_instance(self, instance): self.instances.append(instance) if not self._reseting: self.validate_instances_context([instance]) + def creator_removed_instance(self, instance): + self.instances.remove(instance) + def reset_instances(self): self.instances = [] From 93593788c171201881e2ee138ec4c96989543bc9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 1 Oct 2021 19:33:10 +0200 Subject: [PATCH 582/736] removed unused variable --- openpype/pipeline/create/context.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 24b08c103d..afaaf94798 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -858,11 +858,9 @@ class CreateContext: identifier = instance.creator_identifier instances_by_identifier[identifier].append(instance) - instances_to_remove = [] for identifier, creator_instances in instances_by_identifier.items(): creator = self.creators.get(identifier) creator.remove_instances(creator_instances) - instances_to_remove.append(instances_to_remove) def _get_publish_plugins_with_attr_for_family(self, family): if family not in self._attr_plugins_by_family: From 6cf55ef876927f2501e35865e24baeb1671a84fa Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 1 Oct 2021 19:33:27 +0200 Subject: [PATCH 583/736] don't reset instances just trigger callback --- openpype/tools/new_publisher/control.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 19cc7730ab..8bd060fca7 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -624,7 +624,7 @@ class PublisherController: creator = self.creators[family] creator.create(subset_name, instance_data, options) - self._reset_instances() + self._trigger_callbacks(self._instances_refresh_callback_refs) def save_changes(self): if self.create_context.host_is_valid: @@ -637,7 +637,7 @@ class PublisherController: self.create_context.remove_instances(instances) - self._reset_instances() + self._trigger_callbacks(self._instances_refresh_callback_refs) # --- Publish specific implementations --- @property From 42055ce0bf1e78b9f3567393643ea14845b252a3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 1 Oct 2021 19:33:40 +0200 Subject: [PATCH 584/736] trigger validate_instances_context --- openpype/tools/new_publisher/control.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 8bd060fca7..170da76635 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -538,6 +538,7 @@ class PublisherController: self.create_context.reset_context_data() self.create_context.reset_instances() self.create_context.execute_autocreators() + self.create_context.validate_instances_context() self._resetting_instances = False From 3950b499b014a54845b701779f8fde90945ed59b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 1 Oct 2021 19:45:35 +0200 Subject: [PATCH 585/736] simplified creators --- openpype/hosts/testhost/plugins/create/test_creator_1.py | 9 +++++---- openpype/hosts/testhost/plugins/create/test_creator_2.py | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/testhost/plugins/create/test_creator_1.py b/openpype/hosts/testhost/plugins/create/test_creator_1.py index 1b4b944824..a7555ac89c 100644 --- a/openpype/hosts/testhost/plugins/create/test_creator_1.py +++ b/openpype/hosts/testhost/plugins/create/test_creator_1.py @@ -17,20 +17,21 @@ class TestCreatorOne(Creator): def collect_instances(self, attr_plugins): for instance_data in pipeline.list_instances(): + instance = None creator_id = instance_data.get("creator_identifier") if creator_id is not None: if creator_id == self.identifier: - subset_name = instance_data["subset"] - instance = CreatedInstance( - self.family, subset_name, instance_data, self + instance = CreatedInstance.from_existing( + instance_data, self, attr_plugins ) - self._add_instance_to_context(instance) elif instance_data["family"] == self.identifier: instance_data["creator_identifier"] = self.identifier instance = CreatedInstance.from_existing( instance_data, self, attr_plugins ) + + if instance is not None: self._add_instance_to_context(instance) def update_instances(self, update_list): diff --git a/openpype/hosts/testhost/plugins/create/test_creator_2.py b/openpype/hosts/testhost/plugins/create/test_creator_2.py index b311e12a3e..4017d74a08 100644 --- a/openpype/hosts/testhost/plugins/create/test_creator_2.py +++ b/openpype/hosts/testhost/plugins/create/test_creator_2.py @@ -22,20 +22,21 @@ class TestCreatorTwo(Creator): def collect_instances(self, attr_plugins): for instance_data in pipeline.list_instances(): + instance = None creator_id = instance_data.get("creator_identifier") if creator_id is not None: if creator_id == self.identifier: - subset_name = instance_data["subset"] - instance = CreatedInstance( - self.family, subset_name, instance_data, self + instance = CreatedInstance.from_existing( + instance_data, self, attr_plugins ) - self._add_instance_to_context(instance) elif instance_data["family"] == self.identifier: instance_data["creator_identifier"] = self.identifier instance = CreatedInstance.from_existing( instance_data, self, attr_plugins ) + + if instance is not None: self._add_instance_to_context(instance) def update_instances(self, update_list): From 56028e2dbe6d4af2248190a11d43ce38a6dc3e50 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 1 Oct 2021 19:45:54 +0200 Subject: [PATCH 586/736] added context manager ignore_added_instances --- openpype/pipeline/create/context.py | 23 +++++++++++++++-------- openpype/tools/new_publisher/control.py | 5 +++-- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index afaaf94798..36d1a24c50 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -4,6 +4,7 @@ import logging import collections import inspect from uuid import uuid4 +from contextlib import contextmanager from ..lib import UnknownDef from .creator_plugins import ( @@ -598,7 +599,7 @@ class CreateContext: self.plugins_with_defs = [] self._attr_plugins_by_family = {} - self._reseting = False + self._ignore_added_instances = False if reset: self.reset(discover_publish_plugins) @@ -626,17 +627,15 @@ class CreateContext: return self._log def reset(self, discover_publish_plugins=True): - self._reseting = True - self.reset_plugins(discover_publish_plugins) self.reset_context_data() - self.reset_instances() - self.execute_autocreators() + + with self.ignore_added_instances(): + self.reset_instances() + self.execute_autocreators() self.validate_instances_context() - self._reseting = False - def reset_plugins(self, discover_publish_plugins=True): import avalon.api import pyblish.logic @@ -734,12 +733,20 @@ class CreateContext: def creator_adds_instance(self, instance): self.instances.append(instance) - if not self._reseting: + if not self._ignore_added_instances: self.validate_instances_context([instance]) def creator_removed_instance(self, instance): self.instances.remove(instance) + @contextmanager + def ignore_added_instances(self): + self._ignore_added_instances = True + try: + yield + finally: + self._ignore_added_instances = False + def reset_instances(self): self.instances = [] diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 170da76635..c4526ddea3 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -536,8 +536,9 @@ class PublisherController: self._resetting_instances = True self.create_context.reset_context_data() - self.create_context.reset_instances() - self.create_context.execute_autocreators() + with self.create_context.ignore_added_instances(): + self.create_context.reset_instances() + self.create_context.execute_autocreators() self.create_context.validate_instances_context() self._resetting_instances = False From 9eda0ff7973f5b35c18f6594b35bf14107ffa13f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 4 Oct 2021 11:27:30 +0200 Subject: [PATCH 587/736] fix autocreator --- openpype/hosts/testhost/plugins/create/auto_creator.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/testhost/plugins/create/auto_creator.py b/openpype/hosts/testhost/plugins/create/auto_creator.py index 017e391dd6..67052562a0 100644 --- a/openpype/hosts/testhost/plugins/create/auto_creator.py +++ b/openpype/hosts/testhost/plugins/create/auto_creator.py @@ -65,12 +65,10 @@ class MyAutoCreator(AutoCreator): variant, task_name, asset_doc, project_name, host_name )) - existing_instance = CreatedInstance( + new_instance = CreatedInstance( self.family, subset_name, data, self ) - pipeline.HostContext.add_instance( - existing_instance.data_to_store() - ) + self._add_instance_to_context(new_instance) elif ( existing_instance.data["asset"] != asset_name @@ -82,5 +80,3 @@ class MyAutoCreator(AutoCreator): ) existing_instance.data["asset"] = asset_name existing_instance.data["task"] = task_name - - self._add_instance_to_context(existing_instance) From 8864e047acaebb68c3aa7bc855ff84640364c666 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 4 Oct 2021 11:58:50 +0200 Subject: [PATCH 588/736] creators have new attribute identifier replacing family as identifier which affected almost everything in current code --- openpype/hosts/testhost/api/instances.json | 12 +- openpype/pipeline/create/context.py | 56 +++++---- openpype/tools/new_publisher/constants.py | 6 +- openpype/tools/new_publisher/control.py | 17 ++- .../widgets/card_view_widgets.py | 101 +++++++++-------- .../new_publisher/widgets/create_dialog.py | 104 +++++++++-------- .../widgets/list_view_widgets.py | 106 +++++++++--------- .../tools/new_publisher/widgets/widgets.py | 24 ++-- 8 files changed, 223 insertions(+), 203 deletions(-) diff --git a/openpype/hosts/testhost/api/instances.json b/openpype/hosts/testhost/api/instances.json index e7999ae523..9588cf2df8 100644 --- a/openpype/hosts/testhost/api/instances.json +++ b/openpype/hosts/testhost/api/instances.json @@ -9,7 +9,7 @@ "task": "Compositing", "variant": "myVariant", "uuid": "a485f148-9121-46a5-8157-aa64df0fb449", - "family_attributes": { + "creator_attributes": { "number_key": 10, "ha": 10 }, @@ -30,7 +30,7 @@ "task": "Compositing", "variant": "myVariant2", "uuid": "a485f148-9121-46a5-8157-aa64df0fb444", - "family_attributes": {}, + "creator_attributes": {}, "publish_attributes": { "CollectFtrackApi": { "add_ftrack_family": true @@ -48,7 +48,7 @@ "task": "Compositing", "variant": "Main", "uuid": "3607bc95-75f6-4648-a58d-e699f413d09f", - "family_attributes": {}, + "creator_attributes": {}, "publish_attributes": { "CollectFtrackApi": { "add_ftrack_family": true @@ -66,7 +66,7 @@ "task": "Compositing", "variant": "Main2", "uuid": "4ccf56f6-9982-4837-967c-a49695dbe8eb", - "family_attributes": {}, + "creator_attributes": {}, "publish_attributes": { "CollectFtrackApi": { "add_ftrack_family": true @@ -84,7 +84,7 @@ "task": "Compositing", "variant": "Main2", "uuid": "4ccf56f6-9982-4837-967c-a49695dbe8ec", - "family_attributes": {}, + "creator_attributes": {}, "publish_attributes": { "CollectFtrackApi": { "add_ftrack_family": true @@ -102,7 +102,7 @@ "task": "modeling", "variant": "Main", "uuid": "7c9ddfc7-9f9c-4c1c-b233-38c966735fb6", - "family_attributes": {}, + "creator_attributes": {}, "publish_attributes": {} } ] \ No newline at end of file diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 36d1a24c50..880573378a 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -168,15 +168,15 @@ class AttributeValues: return self.calculate_changes(self._data, self._origin_data) -class FamilyAttributeValues(AttributeValues): - """Family specific attribute values of an instance. +class CreatorAttributeValues(AttributeValues): + """Creator specific attribute values of an instance. Args: instance (CreatedInstance): Instance for which are values hold. """ def __init__(self, instance, *args, **kwargs): self.instance = instance - super(FamilyAttributeValues, self).__init__(*args, **kwargs) + super(CreatorAttributeValues, self).__init__(*args, **kwargs) class PublishAttributeValues(AttributeValues): @@ -350,13 +350,13 @@ class CreatedInstance: # Store original value of passed data self._orig_data = copy.deepcopy(data) - # Pop family and subset to preved unexpected changes + # Pop family and subset to prevent unexpected changes data.pop("family", None) data.pop("subset", None) # Pop dictionary values that will be converted to objects to be able # catch changes - orig_family_attributes = data.pop("family_attributes", None) or {} + orig_creator_attributes = data.pop("creator_attributes", None) or {} orig_publish_attributes = data.pop("publish_attributes", None) or {} # QUESTION Does it make sense to have data stored as ordered dict? @@ -373,18 +373,13 @@ class CreatedInstance: else: self._data["version"] = data.get("version") - # Stored family specific attribute values + # Stored creator specific attribute values # {key: value} - new_family_values = copy.deepcopy(orig_family_attributes) - family_attr_defs = [] - if creator is not None: - new_family_values = creator.convert_family_attribute_values( - new_family_values - ) - family_attr_defs = creator.get_attribute_defs() + creator_values = copy.deepcopy(orig_creator_attributes) + creator_attr_defs = creator.get_attribute_defs() - self._data["family_attributes"] = FamilyAttributeValues( - self, family_attr_defs, new_family_values, orig_family_attributes + self._data["creator_attributes"] = CreatorAttributeValues( + self, creator_attr_defs, creator_values, orig_creator_attributes ) # Stored publish specific attribute values @@ -471,17 +466,17 @@ class CreatedInstance: new_keys = set() for key, new_value in self._data.items(): new_keys.add(key) - if key in ("family_attributes", "publish_attributes"): + if key in ("creator_attributes", "publish_attributes"): continue old_value = self._orig_data.get(key) if old_value != new_value: changes[key] = (old_value, new_value) - family_attributes = self.data["family_attributes"] - family_attr_changes = family_attributes.changes() - if family_attr_changes: - changes["family_attributes"] = family_attr_changes + creator_attributes = self.data["creator_attributes"] + creator_attr_changes = creator_attributes.changes() + if creator_attr_changes: + changes["creator_attributes"] = creator_attr_changes publish_attr_changes = self.publish_attributes.changes() if publish_attr_changes: @@ -493,8 +488,8 @@ class CreatedInstance: return changes @property - def family_attribute_defs(self): - return self._data["family_attributes"].attr_defs + def creator_attribute_defs(self): + return self._data["creator_attributes"].attr_defs @property def publish_attributes(self): @@ -503,12 +498,12 @@ class CreatedInstance: def data_to_store(self): output = collections.OrderedDict() for key, value in self._data.items(): - if key in ("family_attributes", "publish_attributes"): + if key in ("creator_attributes", "publish_attributes"): continue output[key] = value - family_attributes = self._data["family_attributes"] - output["family_attributes"] = family_attributes.data_to_store() + creator_attributes = self._data["creator_attributes"] + output["creator_attributes"] = creator_attributes.data_to_store() publish_attributes = self._data["publish_attributes"] output["publish_attributes"] = publish_attributes.data_to_store() @@ -523,6 +518,8 @@ class CreatedInstance: instance_data = copy.deepcopy(instance_data) family = instance_data.get("family", None) + if family is None: + family = creator.family subset_name = instance_data.get("subset", None) return cls( @@ -752,9 +749,8 @@ class CreateContext: # Collect instances for creator in self.creators.values(): - family = creator.family attr_plugins = self._get_publish_plugins_with_attr_for_family( - family + creator.family ) creator.collect_instances(attr_plugins) @@ -763,15 +759,15 @@ class CreateContext: Reset instances if any autocreator executed properly. """ - for family, creator in self.autocreators.items(): + for identifier, creator in self.autocreators.items(): try: creator.create() except Exception: # TODO raise report exception if any crashed msg = ( - "Failed to run AutoCreator with family \"{}\" ({})." - ).format(family, inspect.getfile(creator.__class__)) + "Failed to run AutoCreator with identifier \"{}\" ({})." + ).format(identifier, inspect.getfile(creator.__class__)) self.log.warning(msg, exc_info=True) def validate_instances_context(self, instances=None): diff --git a/openpype/tools/new_publisher/constants.py b/openpype/tools/new_publisher/constants.py index a4c4411534..cf0850bde8 100644 --- a/openpype/tools/new_publisher/constants.py +++ b/openpype/tools/new_publisher/constants.py @@ -16,6 +16,8 @@ VARIANT_TOOLTIP = ( INSTANCE_ID_ROLE = QtCore.Qt.UserRole + 1 SORT_VALUE_ROLE = QtCore.Qt.UserRole + 2 IS_GROUP_ROLE = QtCore.Qt.UserRole + 3 +CREATOR_IDENTIFIER_ROLE = QtCore.Qt.UserRole + 4 +FAMILY_ROLE = QtCore.Qt.UserRole + 5 __all__ = ( @@ -26,5 +28,7 @@ __all__ = ( "INSTANCE_ID_ROLE", "SORT_VALUE_ROLE", - "IS_GROUP_ROLE" + "IS_GROUP_ROLE", + "CREATOR_IDENTIFIER_ROLE", + "FAMILY_ROLE" ) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index c4526ddea3..100bf0f4a1 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -545,18 +545,18 @@ class PublisherController: self._trigger_callbacks(self._instances_refresh_callback_refs) - def get_family_attribute_definitions(self, instances): + def get_creator_attribute_definitions(self, instances): output = [] _attr_defs = {} for instance in instances: - for attr_def in instance.family_attribute_defs: + for attr_def in instance.creator_attribute_defs: found_idx = None for idx, _attr_def in _attr_defs.items(): if attr_def == _attr_def: found_idx = idx break - value = instance.data["family_attributes"][attr_def.key] + value = instance.data["creator_attributes"][attr_def.key] if found_idx is None: idx = len(output) output.append((attr_def, [instance], [value])) @@ -617,13 +617,10 @@ class PublisherController: return creator.get_icon() return None - def create(self, family, subset_name, instance_data, options): - # QUESTION Force to return instances or call `list_instances` on each - # creation? (`list_instances` may slow down...) - # - also save may not be required in that case - self.save_changes() - - creator = self.creators[family] + def create( + self, creator_identifier, subset_name, instance_data, options + ): + creator = self.creators[creator_identifier] creator.create(subset_name, instance_data, options) self._trigger_callbacks(self._instances_refresh_callback_refs) diff --git a/openpype/tools/new_publisher/widgets/card_view_widgets.py b/openpype/tools/new_publisher/widgets/card_view_widgets.py index e578e1dd65..1175b489ca 100644 --- a/openpype/tools/new_publisher/widgets/card_view_widgets.py +++ b/openpype/tools/new_publisher/widgets/card_view_widgets.py @@ -18,15 +18,15 @@ from ..constants import ( ) -class FamilyWidget(QtWidgets.QWidget): +class GroupWidget(QtWidgets.QWidget): selected = QtCore.Signal(str, str) active_changed = QtCore.Signal() removed_selected = QtCore.Signal() - def __init__(self, family, family_icon, parent): - super(FamilyWidget, self).__init__(parent) + def __init__(self, group_name, group_icon, parent): + super(GroupWidget, self).__init__(parent) - label_widget = QtWidgets.QLabel(family, self) + label_widget = QtWidgets.QLabel(group_name, self) line_widget = QtWidgets.QWidget(self) line_widget.setObjectName("Separator") @@ -44,8 +44,8 @@ class FamilyWidget(QtWidgets.QWidget): layout.setContentsMargins(0, 0, 0, 0) layout.addLayout(label_layout, 0) - self._family = family - self._family_icon = family_icon + self._group = group_name + self._group_icon = group_icon self._widgets_by_id = {} @@ -94,7 +94,7 @@ class FamilyWidget(QtWidgets.QWidget): widget.update_instance(instance) else: widget = InstanceCardWidget( - instance, self._family_icon, self + instance, self._group_icon, self ) widget.selected.connect(self.selected) widget.active_changed.connect(self.active_changed) @@ -105,6 +105,8 @@ class FamilyWidget(QtWidgets.QWidget): class CardWidget(ClickableFrame): selected = QtCore.Signal(str, str) + # This must be set + _group_identifier = None def __init__(self, parent): super(CardWidget, self).__init__(parent) @@ -126,7 +128,7 @@ class CardWidget(ClickableFrame): self.style().polish(self) def _mouse_release_callback(self): - self.selected.emit(self._id, self._family) + self.selected.emit(self._id, self._group_identifier) class ContextCardWidget(CardWidget): @@ -134,7 +136,7 @@ class ContextCardWidget(CardWidget): super(ContextCardWidget, self).__init__(parent) self._id = CONTEXT_ID - self._family = "" + self._group_identifier = "" icon_widget = TransparentPixmapLabel(self) icon_widget.setObjectName("FamilyIconLabel") @@ -157,16 +159,16 @@ class ContextCardWidget(CardWidget): class InstanceCardWidget(CardWidget): active_changed = QtCore.Signal() - def __init__(self, instance, family_icon, parent): + def __init__(self, instance, group_icon, parent): super(InstanceCardWidget, self).__init__(parent) self._id = instance.id - self._family = instance.data["family"] - self._family_icon = family_icon + self._group_identifier = instance.creator_identifier + self._group_icon = group_icon self.instance = instance - icon_widget = IconValuePixmapLabel(family_icon, self) + icon_widget = IconValuePixmapLabel(group_icon, self) icon_widget.setObjectName("FamilyIconLabel") context_warning = ContextWarningLabel(self) @@ -303,10 +305,10 @@ class InstanceCardView(AbstractInstanceView): self._content_layout = content_layout self._content_widget = content_widget - self._widgets_by_family = {} + self._widgets_by_group = {} self._context_widget = None - self._selected_instance_family = None + self._selected_group = None self._selected_instance_id = None self.setSizePolicy( @@ -330,11 +332,11 @@ class InstanceCardView(AbstractInstanceView): if self._selected_instance_id == CONTEXT_ID: return self._context_widget - family_widget = self._widgets_by_family.get( - self._selected_instance_family + group_widget = self._widgets_by_group.get( + self._selected_group ) - if family_widget is not None: - widget = family_widget.get_widget_by_instance_id( + if group_widget is not None: + widget = group_widget.get_widget_by_instance_id( self._selected_instance_id ) if widget is not None: @@ -354,58 +356,63 @@ class InstanceCardView(AbstractInstanceView): self.select_item(CONTEXT_ID, None) - instances_by_family = collections.defaultdict(list) + instances_by_creator = collections.defaultdict(list) for instance in self.controller.instances: - family = instance.data["family"] - instances_by_family[family].append(instance) + identifier = instance.creator_identifier + instances_by_creator[identifier].append(instance) - for family in tuple(self._widgets_by_family.keys()): - if family in instances_by_family: + for identifier in tuple(self._widgets_by_group.keys()): + if identifier in instances_by_creator: continue - if family == self._selected_instance_family: + if identifier == self._selected_group: self._on_remove_selected() - widget = self._widgets_by_family.pop(family) + widget = self._widgets_by_group.pop(identifier) widget.setVisible(False) self._content_layout.removeWidget(widget) widget.deleteLater() - sorted_families = list(sorted(instances_by_family.keys())) + sorted_identifiers = list(sorted(instances_by_creator.keys())) widget_idx = 1 - for family in sorted_families: - if family not in self._widgets_by_family: - family_icon = self.controller.get_icon_for_family(family) - family_widget = FamilyWidget( - family, family_icon, self._content_widget + for creator_identifier in sorted_identifiers: + if creator_identifier in self._widgets_by_group: + group_widget = self._widgets_by_group[creator_identifier] + else: + group_icon = self.controller.get_icon_for_family( + creator_identifier ) - family_widget.active_changed.connect(self._on_active_changed) - family_widget.selected.connect(self._on_widget_selection) - family_widget.removed_selected.connect( + group_widget = GroupWidget( + creator_identifier, group_icon, self._content_widget + ) + group_widget.active_changed.connect(self._on_active_changed) + group_widget.selected.connect(self._on_widget_selection) + group_widget.removed_selected.connect( self._on_remove_selected ) - self._content_layout.insertWidget(widget_idx, family_widget) - self._widgets_by_family[family] = family_widget - else: - family_widget = self._widgets_by_family[family] + self._content_layout.insertWidget(widget_idx, group_widget) + self._widgets_by_group[creator_identifier] = group_widget + widget_idx += 1 - family_widget.update_instances(instances_by_family[family]) + group_widget.update_instances( + instances_by_creator[creator_identifier] + ) def refresh_instance_states(self): - for widget in self._widgets_by_family.values(): + for widget in self._widgets_by_group.values(): widget.update_instance_values() def _on_active_changed(self): self.active_changed.emit() - def _on_widget_selection(self, instance_id, family): - self.select_item(instance_id, family) + def _on_widget_selection(self, instance_id, group_name): + self.select_item(instance_id, group_name) - def select_item(self, instance_id, family): + def select_item(self, instance_id, group_name): if instance_id == CONTEXT_ID: new_widget = self._context_widget else: - family_widget = self._widgets_by_family[family] - new_widget = family_widget.get_widget_by_instance_id(instance_id) + group_widget = self._widgets_by_group[group_name] + new_widget = group_widget.get_widget_by_instance_id(instance_id) selected_widget = self._get_selected_widget() if new_widget is selected_widget: @@ -415,7 +422,7 @@ class InstanceCardView(AbstractInstanceView): selected_widget.set_selected(False) self._selected_instance_id = instance_id - self._selected_instance_family = family + self._selected_group = group_name if new_widget is not None: new_widget.set_selected(True) diff --git a/openpype/tools/new_publisher/widgets/create_dialog.py b/openpype/tools/new_publisher/widgets/create_dialog.py index 05ca764c91..7dc61eae10 100644 --- a/openpype/tools/new_publisher/widgets/create_dialog.py +++ b/openpype/tools/new_publisher/widgets/create_dialog.py @@ -14,7 +14,9 @@ from openpype.pipeline.create import CreatorError from .widgets import IconValuePixmapLabel from ..constants import ( SUBSET_NAME_ALLOWED_SYMBOLS, - VARIANT_TOOLTIP + VARIANT_TOOLTIP, + CREATOR_IDENTIFIER_ROLE, + FAMILY_ROLE ) SEPARATORS = ("---separator---", "---") @@ -23,7 +25,7 @@ SEPARATORS = ("---separator---", "---") class CreateErrorMessageBox(QtWidgets.QDialog): def __init__( self, - family, + creator_label, subset_name, asset_name, exc_msg, @@ -47,7 +49,7 @@ class CreateErrorMessageBox(QtWidgets.QDialog): body_layout.addWidget(main_label_widget) item_name_template = ( - "Family: {}
" + "Creator: {}
" "Subset: {}
" "Asset: {}
" ) @@ -56,7 +58,9 @@ class CreateErrorMessageBox(QtWidgets.QDialog): line = self._create_line() body_layout.addWidget(line) - item_name = item_name_template.format(family, subset_name, asset_name) + item_name = item_name_template.format( + creator_label, subset_name, asset_name + ) item_name_widget = QtWidgets.QLabel( item_name.replace("\n", "
"), self ) @@ -96,9 +100,10 @@ class CreateErrorMessageBox(QtWidgets.QDialog): return line -class FamilyDescriptionWidget(QtWidgets.QWidget): +# TODO add creator identifier/label to details +class CreatorDescriptionWidget(QtWidgets.QWidget): def __init__(self, parent=None): - super(FamilyDescriptionWidget, self).__init__(parent=parent) + super(CreatorDescriptionWidget, self).__init__(parent=parent) icon_widget = IconValuePixmapLabel(None, self) icon_widget.setObjectName("FamilyIconLabel") @@ -195,11 +200,11 @@ class CreateDialog(QtWidgets.QDialog): self._name_pattern = name_pattern self._compiled_name_pattern = re.compile(name_pattern) - creator_description_widget = FamilyDescriptionWidget(self) + creator_description_widget = CreatorDescriptionWidget(self) - family_view = QtWidgets.QListView(self) - family_model = QtGui.QStandardItemModel() - family_view.setModel(family_model) + creators_view = QtWidgets.QListView(self) + creators_model = QtGui.QStandardItemModel() + creators_view.setModel(creators_model) variant_input = QtWidgets.QLineEdit(self) variant_input.setObjectName("VariantInput") @@ -230,7 +235,7 @@ class CreateDialog(QtWidgets.QDialog): left_layout = QtWidgets.QVBoxLayout() left_layout.addWidget(QtWidgets.QLabel("Choose family:", self)) - left_layout.addWidget(family_view, 1) + left_layout.addWidget(creators_view, 1) left_layout.addLayout(form_layout, 0) left_layout.addWidget(create_btn, 0) @@ -242,8 +247,8 @@ class CreateDialog(QtWidgets.QDialog): create_btn.clicked.connect(self._on_create) variant_input.returnPressed.connect(self._on_create) variant_input.textChanged.connect(self._on_variant_change) - family_view.selectionModel().currentChanged.connect( - self._on_family_change + creators_view.selectionModel().currentChanged.connect( + self._on_item_change ) variant_hints_menu.triggered.connect(self._on_variant_action) @@ -258,8 +263,8 @@ class CreateDialog(QtWidgets.QDialog): self.variant_hints_menu = variant_hints_menu self.variant_hints_group = variant_hints_group - self.family_model = family_model - self.family_view = family_view + self.creators_model = creators_model + self.creators_view = creators_view self.create_btn = create_btn @property @@ -280,11 +285,11 @@ class CreateDialog(QtWidgets.QDialog): self.subset_name_input.setText("< Asset is not set >") self._prereq_available = False - if self.family_model.rowCount() < 1: + if self.creators_model.rowCount() < 1: self._prereq_available = False self.create_btn.setEnabled(self._prereq_available) - self.family_view.setEnabled(self._prereq_available) + self.creators_view.setEnabled(self._prereq_available) self.variant_input.setEnabled(self._prereq_available) self.variant_hints_btn.setEnabled(self._prereq_available) @@ -320,50 +325,57 @@ class CreateDialog(QtWidgets.QDialog): def _refresh_creators(self): # Refresh creators and add their families to list existing_items = {} - old_families = set() - for row in range(self.family_model.rowCount()): - item = self.family_model.item(row, 0) - family = item.data(QtCore.Qt.DisplayRole) - existing_items[family] = item - old_families.add(family) + old_creators = set() + for row in range(self.creators_model.rowCount()): + item = self.creators_model.item(row, 0) + identifier = item.data(CREATOR_IDENTIFIER_ROLE) + existing_items[identifier] = item + old_creators.add(identifier) # Add new families - new_families = set() - for family in self.controller.ui_creators.keys(): + new_creators = set() + for identifier, creator in self.controller.ui_creators.items(): # TODO add details about creator - new_families.add(family) - if family not in existing_items: - item = QtGui.QStandardItem(family) + new_creators.add(identifier) + if identifier in existing_items: + item = existing_items[identifier] + else: + item = QtGui.QStandardItem() item.setFlags( QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable ) - self.family_model.appendRow(item) + self.creators_model.appendRow(item) + + label = creator.label or identifier + item.setData(label, QtCore.Qt.DisplayRole) + item.setData(identifier, CREATOR_IDENTIFIER_ROLE) + item.setData(creator.family, FAMILY_ROLE) # Remove families that are no more available - for family in (old_families - new_families): - item = existing_items[family] - self.family_model.takeRow(item.row()) + for identifier in (old_creators - new_creators): + item = existing_items[identifier] + self.creators_model.takeRow(item.row()) - if self.family_model.rowCount() < 1: + if self.creators_model.rowCount() < 1: return # Make sure there is a selection - indexes = self.family_view.selectedIndexes() + indexes = self.creators_view.selectedIndexes() if not indexes: - index = self.family_model.index(0, 0) - self.family_view.setCurrentIndex(index) + index = self.creators_model.index(0, 0) + self.creators_view.setCurrentIndex(index) def _on_plugins_refresh(self): # Trigger refresh only if is visible if self.isVisible(): self.refresh() - def _on_family_change(self, new_index, _old_index): - family = None + def _on_item_change(self, new_index, _old_index): + identifier = None if new_index.isValid(): - family = new_index.data(QtCore.Qt.DisplayRole) + identifier = new_index.data(CREATOR_IDENTIFIER_ROLE) - creator = self.controller.ui_creators.get(family) + creator = self.controller.ui_creators.get(identifier) self.creator_description_widget.set_plugin(creator) @@ -495,7 +507,7 @@ class CreateDialog(QtWidgets.QDialog): self.refresh() def _on_create(self): - indexes = self.family_view.selectedIndexes() + indexes = self.creators_view.selectedIndexes() if not indexes or len(indexes) > 1: return @@ -503,7 +515,9 @@ class CreateDialog(QtWidgets.QDialog): return index = indexes[0] - family = index.data(QtCore.Qt.DisplayRole) + creator_label = index.data(QtCore.Qt.DisplayRole) + creator_identifier = index.data(CREATOR_IDENTIFIER_ROLE) + family = index.data(FAMILY_ROLE) subset_name = self.subset_name_input.text() variant = self.variant_input.text() asset_name = self._asset_name @@ -520,7 +534,9 @@ class CreateDialog(QtWidgets.QDialog): error_info = None try: - self.controller.create(family, subset_name, instance_data, options) + self.controller.create( + creator_identifier, subset_name, instance_data, options + ) except CreatorError as exc: error_info = (str(exc), None) @@ -534,7 +550,7 @@ class CreateDialog(QtWidgets.QDialog): if error_info: box = CreateErrorMessageBox( - family, subset_name, asset_name, *error_info + creator_label, subset_name, asset_name, *error_info ) box.show() # Store dialog so is not garbage collected before is shown diff --git a/openpype/tools/new_publisher/widgets/list_view_widgets.py b/openpype/tools/new_publisher/widgets/list_view_widgets.py index f1ac23018e..062f4242e0 100644 --- a/openpype/tools/new_publisher/widgets/list_view_widgets.py +++ b/openpype/tools/new_publisher/widgets/list_view_widgets.py @@ -169,11 +169,11 @@ class InstanceListGroupWidget(QtWidgets.QFrame): expand_changed = QtCore.Signal(str, bool) toggle_requested = QtCore.Signal(str, int) - def __init__(self, family, parent): + def __init__(self, group_name, parent): super(InstanceListGroupWidget, self).__init__(parent) self.setObjectName("InstanceListGroupWidget") - self.family = family + self.group_name = group_name self._expanded = False expand_btn = QtWidgets.QToolButton(self) @@ -181,7 +181,7 @@ class InstanceListGroupWidget(QtWidgets.QFrame): expand_btn.setArrowType(QtCore.Qt.RightArrow) expand_btn.setMaximumWidth(14) - subset_name_label = QtWidgets.QLabel(family, self) + name_label = QtWidgets.QLabel(group_name, self) toggle_checkbox = NiceCheckbox(parent=self) @@ -189,12 +189,12 @@ class InstanceListGroupWidget(QtWidgets.QFrame): layout.setContentsMargins(5, 0, 2, 0) layout.addWidget(expand_btn) layout.addWidget( - subset_name_label, 1, QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter + name_label, 1, QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter ) layout.addWidget(toggle_checkbox, 0) # self.setAttribute(QtCore.Qt.WA_TranslucentBackground) - subset_name_label.setAttribute(QtCore.Qt.WA_TranslucentBackground) + name_label.setAttribute(QtCore.Qt.WA_TranslucentBackground) expand_btn.setAttribute(QtCore.Qt.WA_TranslucentBackground) expand_btn.clicked.connect(self._on_expand_clicked) @@ -204,7 +204,7 @@ class InstanceListGroupWidget(QtWidgets.QFrame): self._expected_checkstate = None - self.subset_name_label = subset_name_label + self.name_label = name_label self.expand_btn = expand_btn self.toggle_checkbox = toggle_checkbox @@ -220,10 +220,10 @@ class InstanceListGroupWidget(QtWidgets.QFrame): def _on_checkbox_change(self, state): if not self._ignore_state_change: - self.toggle_requested.emit(self.family, state) + self.toggle_requested.emit(self.group_name, state) def _on_expand_clicked(self): - self.expand_changed.emit(self.family, not self._expanded) + self.expand_changed.emit(self.group_name, not self._expanded) def set_expanded(self, expanded): if self._expanded == expanded: @@ -366,7 +366,7 @@ class InstanceListView(AbstractInstanceView): self._group_items = {} self._group_widgets = {} self._widgets_by_id = {} - self._family_by_instance_id = {} + self._group_by_instance_id = {} self._context_item = None self._context_widget = None @@ -376,14 +376,14 @@ class InstanceListView(AbstractInstanceView): self.proxy_model = proxy_model def _on_expand(self, index): - family = index.data(SORT_VALUE_ROLE) - group_widget = self._group_widgets.get(family) + group_name = index.data(SORT_VALUE_ROLE) + group_widget = self._group_widgets.get(group_name) if group_widget: group_widget.set_expanded(True) def _on_collapse(self, index): - family = index.data(SORT_VALUE_ROLE) - group_widget = self._group_widgets.get(family) + group_name = index.data(SORT_VALUE_ROLE) + group_widget = self._group_widgets.get(group_name) if group_widget: group_widget.set_expanded(False) @@ -401,14 +401,14 @@ class InstanceListView(AbstractInstanceView): if widget is not None: widget.set_active(active) - def _update_family_checkstate(self, family): - widget = self._group_widgets.get(family) + def _update_group_checkstate(self, group_name): + widget = self._group_widgets.get(group_name) if widget is None: return activity = None - for instance_id, _family in self._family_by_instance_id.items(): - if _family != family: + for instance_id, _group_name in self._group_by_instance_id.items(): + if _group_name != group_name: continue instance_widget = self._widgets_by_id.get(instance_id) @@ -433,12 +433,12 @@ class InstanceListView(AbstractInstanceView): widget.set_checkstate(state) def refresh(self): - instances_by_family = collections.defaultdict(list) - families = set() + instances_by_group_name = collections.defaultdict(list) + group_names = set() for instance in self.controller.instances: - family = instance.data["family"] - families.add(family) - instances_by_family[family].append(instance) + identifier = instance.creator_identifier + group_names.add(identifier) + instances_by_group_name[identifier].append(instance) sort_at_the_end = False root_item = self.instance_model.invisibleRootItem() @@ -463,15 +463,15 @@ class InstanceListView(AbstractInstanceView): self._context_item = context_item new_group_items = [] - for family in families: - if family in self._group_items: + for group_name in group_names: + if group_name in self._group_items: continue group_item = QtGui.QStandardItem() - group_item.setData(family, SORT_VALUE_ROLE) + group_item.setData(group_name, SORT_VALUE_ROLE) group_item.setData(True, IS_GROUP_ROLE) group_item.setFlags(QtCore.Qt.ItemIsEnabled) - self._group_items[family] = group_item + self._group_items[group_name] = group_item new_group_items.append(group_item) if new_group_items: @@ -483,24 +483,24 @@ class InstanceListView(AbstractInstanceView): group_item.row(), group_item.column() ) proxy_index = self.proxy_model.mapFromSource(index) - family = group_item.data(SORT_VALUE_ROLE) - widget = InstanceListGroupWidget(family, self.instance_view) + group_name = group_item.data(SORT_VALUE_ROLE) + widget = InstanceListGroupWidget(group_name, self.instance_view) widget.expand_changed.connect(self._on_group_expand_request) widget.toggle_requested.connect(self._on_group_toggle_request) - self._group_widgets[family] = widget + self._group_widgets[group_name] = widget self.instance_view.setIndexWidget(proxy_index, widget) - for family in tuple(self._group_items.keys()): - if family in families: + for group_name in tuple(self._group_items.keys()): + if group_name in group_names: continue - group_item = self._group_items.pop(family) + group_item = self._group_items.pop(group_name) root_item.removeRow(group_item.row()) - widget = self._group_widgets.pop(family) + widget = self._group_widgets.pop(group_name) widget.deleteLater() - expand_families = set() - for family, group_item in self._group_items.items(): + expand_groups = set() + for group_name, group_item in self._group_items.items(): to_remove = set() existing_mapping = {} @@ -517,7 +517,7 @@ class InstanceListView(AbstractInstanceView): new_items = [] new_items_with_instance = [] activity = None - for instance in instances_by_family[family]: + for instance in instances_by_group_name[group_name]: instance_id = instance.data["uuid"] if activity is None: activity = int(instance.data["active"]) @@ -526,7 +526,7 @@ class InstanceListView(AbstractInstanceView): elif activity != instance.data["active"]: activity = -1 - self._family_by_instance_id[instance_id] = family + self._group_by_instance_id[instance_id] = group_name if instance_id in to_remove: to_remove.remove(instance_id) widget = self._widgets_by_id[instance_id] @@ -545,7 +545,7 @@ class InstanceListView(AbstractInstanceView): elif activity == 1: state = QtCore.Qt.Checked - widget = self._group_widgets[family] + widget = self._group_widgets[group_name] widget.set_checkstate(state) idx_to_remove = [] @@ -556,7 +556,7 @@ class InstanceListView(AbstractInstanceView): group_item.removeRows(idx, 1) for instance_id in to_remove: - self._family_by_instance_id.pop(instance_id) + self._group_by_instance_id.pop(instance_id) widget = self._widgets_by_id.pop(instance_id) widget.deleteLater() @@ -567,7 +567,7 @@ class InstanceListView(AbstractInstanceView): for item, instance in new_items_with_instance: if not instance.has_valid_context: - expand_families.add(family) + expand_groups.add(group_name) item_index = self.instance_model.index( item.row(), item.column(), @@ -585,9 +585,9 @@ class InstanceListView(AbstractInstanceView): if sort_at_the_end: self.proxy_model.sort(0) - for family in expand_families: - family_item = self._group_items[family] - proxy_index = self.proxy_model.mapFromSource(family_item.index()) + for group_name in expand_groups: + group_item = self._group_items[group_name] + proxy_index = self.proxy_model.mapFromSource(group_item.index()) self.instance_view.expand(proxy_index) @@ -610,14 +610,14 @@ class InstanceListView(AbstractInstanceView): selected_ids.add(changed_instance_id) self._change_active_instances(selected_ids, new_value) - families = set() + group_names = set() for instance_id in selected_ids: - family = self._family_by_instance_id.get(instance_id) - if family is not None: - families.add(family) + group_name = self._group_by_instance_id.get(instance_id) + if group_name is not None: + group_names.add(group_name) - for family in families: - self._update_family_checkstate(family) + for group_name in group_names: + self._update_group_checkstate(group_name) def _change_active_instances(self, instance_ids, new_value): if not instance_ids: @@ -656,8 +656,8 @@ class InstanceListView(AbstractInstanceView): def _on_selection_change(self, *_args): self.selection_changed.emit() - def _on_group_expand_request(self, family, expanded): - group_item = self._group_items.get(family) + def _on_group_expand_request(self, group_name, expanded): + group_item = self._group_items.get(group_name) if not group_item: return @@ -667,7 +667,7 @@ class InstanceListView(AbstractInstanceView): proxy_index = self.proxy_model.mapFromSource(group_index) self.instance_view.setExpanded(proxy_index, expanded) - def _on_group_toggle_request(self, family, state): + def _on_group_toggle_request(self, group_name, state): if state == QtCore.Qt.PartiallyChecked: return @@ -676,7 +676,7 @@ class InstanceListView(AbstractInstanceView): else: active = False - group_item = self._group_items.get(family) + group_item = self._group_items.get(group_name) if not group_item: return diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 750fab35d3..daad356ecb 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -1096,9 +1096,9 @@ class GlobalAttrsWidget(QtWidgets.QWidget): self.task_value_widget.setEnabled(editable) -class FamilyAttrsWidget(QtWidgets.QWidget): +class CreatorAttrsWidget(QtWidgets.QWidget): def __init__(self, controller, parent): - super(FamilyAttrsWidget, self).__init__(parent) + super(CreatorAttrsWidget, self).__init__(parent) scroll_area = QtWidgets.QScrollArea(self) scroll_area.setWidgetResizable(True) @@ -1137,7 +1137,7 @@ class FamilyAttrsWidget(QtWidgets.QWidget): self._attr_def_id_to_instances = {} self._attr_def_id_to_attr_def = {} - result = self.controller.get_family_attribute_definitions( + result = self.controller.get_creator_attribute_definitions( instances ) @@ -1169,9 +1169,9 @@ class FamilyAttrsWidget(QtWidgets.QWidget): return for instance in instances: - family_attributes = instance.data["family_attributes"] - if attr_def.key in family_attributes: - family_attributes[attr_def.key] = value + creator_attributes = instance.data["creator_attributes"] + if attr_def.key in creator_attributes: + creator_attributes[attr_def.key] = value class PublishPluginAttrsWidget(QtWidgets.QWidget): @@ -1301,7 +1301,7 @@ class SubsetAttributesWidget(QtWidgets.QWidget): # BOTTOM PART bottom_widget = QtWidgets.QWidget(self) - family_attrs_widget = FamilyAttrsWidget( + creator_attrs_widget = CreatorAttrsWidget( controller, bottom_widget ) publish_attrs_widget = PublishPluginAttrsWidget( @@ -1314,7 +1314,7 @@ class SubsetAttributesWidget(QtWidgets.QWidget): bottom_layout = QtWidgets.QHBoxLayout(bottom_widget) bottom_layout.setContentsMargins(0, 0, 0, 0) - bottom_layout.addWidget(family_attrs_widget, 1) + bottom_layout.addWidget(creator_attrs_widget, 1) bottom_layout.addWidget(bottom_separator, 0) bottom_layout.addWidget(publish_attrs_widget, 1) @@ -1339,7 +1339,7 @@ class SubsetAttributesWidget(QtWidgets.QWidget): self.global_attrs_widget = global_attrs_widget - self.family_attrs_widget = family_attrs_widget + self.creator_attrs_widget = creator_attrs_widget self.publish_attrs_widget = publish_attrs_widget self.thumbnail_widget = thumbnail_widget @@ -1354,7 +1354,7 @@ class SubsetAttributesWidget(QtWidgets.QWidget): break self._all_instances_valid = all_valid - self.family_attrs_widget.set_instances_valid(all_valid) + self.creator_attrs_widget.set_instances_valid(all_valid) self.publish_attrs_widget.set_instances_valid(all_valid) self.instance_context_changed.emit() @@ -1371,11 +1371,11 @@ class SubsetAttributesWidget(QtWidgets.QWidget): self._all_instances_valid = all_valid self.global_attrs_widget.set_current_instances(instances) - self.family_attrs_widget.set_current_instances(instances) + self.creator_attrs_widget.set_current_instances(instances) self.publish_attrs_widget.set_current_instances( instances, context_selected ) - self.family_attrs_widget.set_instances_valid(all_valid) + self.creator_attrs_widget.set_instances_valid(all_valid) self.publish_attrs_widget.set_instances_valid(all_valid) From 6114f6aff197ccc03a695e25afe592f9bad3ba48 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 5 Oct 2021 10:50:29 +0200 Subject: [PATCH 589/736] changed families and subset names on instances --- openpype/hosts/testhost/api/instances.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/testhost/api/instances.json b/openpype/hosts/testhost/api/instances.json index 9588cf2df8..84021eff91 100644 --- a/openpype/hosts/testhost/api/instances.json +++ b/openpype/hosts/testhost/api/instances.json @@ -1,9 +1,9 @@ [ { "id": "pyblish.avalon.instance", - "family": "test_one", - "subset": "test_oneMyVariant", "active": true, + "family": "test", + "subset": "testMyVariant", "version": 1, "asset": "sq01_sh0010", "task": "Compositing", @@ -22,9 +22,9 @@ }, { "id": "pyblish.avalon.instance", - "family": "test_one", - "subset": "test_oneMyVariant2", "active": true, + "family": "test", + "subset": "testMyVariant2", "version": 1, "asset": "sq01_sh0010", "task": "Compositing", @@ -40,9 +40,9 @@ }, { "id": "pyblish.avalon.instance", - "family": "test_two", - "subset": "test_twoMain", "active": true, + "family": "test", + "subset": "testMain", "version": 1, "asset": "sq01_sh0010", "task": "Compositing", @@ -58,9 +58,9 @@ }, { "id": "pyblish.avalon.instance", - "family": "test_two", - "subset": "test_twoMain2", "active": true, + "family": "test", + "subset": "testMain2", "version": 1, "asset": "sq01_sh0020", "task": "Compositing", From ab0ea46f8930cfe791735c6f72a8d41b5cfe48a4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 5 Oct 2021 11:39:27 +0200 Subject: [PATCH 590/736] changed version handling --- openpype/pipeline/create/context.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 880573378a..35f1fc6ef0 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -368,10 +368,13 @@ class CreatedInstance: self._data["creator_identifier"] = creator.identifier # QUESTION handle version of instance here or in creator? - if new: - self._data["version"] = 1 - else: - self._data["version"] = data.get("version") + version = None + if not new: + version = data.get("version") + + if version is None: + version = 1 + self._data["version"] = version # Stored creator specific attribute values # {key: value} From 741c0d211498436030322531178d7dc8d59b126d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 5 Oct 2021 11:39:53 +0200 Subject: [PATCH 591/736] CreatedInstance is dictionary like object that validates changes of data --- openpype/pipeline/create/context.py | 57 ++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 35f1fc6ef0..ae0aba0290 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -19,6 +19,16 @@ from openpype.api import ( ) +class ImmutableKeyError(TypeError): + def __init__(self, key, msg=None): + self.immutable_key = key + if not msg: + msg = "Key \"{}\" is immutable and does not allow changes.".format( + key + ) + super(ImmutableKeyError, self).__init__(msg) + + class HostMissRequiredMethod(Exception): """Host does not have implemented required functions for creation.""" def __init__(self, host, missing_methods): @@ -330,7 +340,21 @@ class CreatedInstance: subset_name(str): Name of subset that will be created. data(dict): Data used for filling subset name or override data from already existing instance. + creator(BaseCreator): Creator responsible for instance. + host(ModuleType): Host implementation loaded with + `avalon.api.registered_host`. + attr_plugins(list): List of attribute definitions of publish plugins. + new(bool): Is instance new. """ + __immutable_keys = ( + "id", + "uuid", + "family", + "creator_identifier", + "creator_attributes", + "publish_attributes" + ) + def __init__( self, family, subset_name, data, creator, host=None, attr_plugins=None, new=True @@ -399,6 +423,37 @@ class CreatedInstance: self._asset_is_valid = self.has_set_asset self._task_is_valid = self.has_set_task + def __getitem__(self, key): + return self._data[key] + + def __setitem__(self, key, value): + # Validate immutable keys + if key not in self.__immutable_keys: + self._data[key] = value + + elif value != self._data.get(key): + # Raise exception if key is immutable and value has changed + raise ImmutableKeyError(key) + + def get(self, key, default=None): + return self._data.get(key, default) + + def pop(self, key, *args, **kwargs): + # Raise exception if is trying to pop key which is immutable + if key in self.__immutable_keys: + raise ImmutableKeyError(key) + + self._data.pop(key, *args, **kwargs) + + def keys(self): + return self._data.keys() + + def values(self): + return self._data.values() + + def items(self): + return self._data.items() + @property def family(self): return self._data["family"] @@ -461,7 +516,7 @@ class CreatedInstance: Define class handling which keys are change to what. - this is dangerous as it is possible to modify any key (e.g. `uuid`) """ - return self._data + return self def changes(self): """Calculate and return changes.""" From 03deba3ae059492dd6712b053012436f60be6c12 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 5 Oct 2021 11:43:01 +0200 Subject: [PATCH 592/736] added some docstrings --- openpype/pipeline/create/context.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index ae0aba0290..a5e4876f55 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -20,6 +20,7 @@ from openpype.api import ( class ImmutableKeyError(TypeError): + """Accessed key is immutable so does not allow changes or removements.""" def __init__(self, key, msg=None): self.immutable_key = key if not msg: @@ -346,6 +347,10 @@ class CreatedInstance: attr_plugins(list): List of attribute definitions of publish plugins. new(bool): Is instance new. """ + # Keys that can't be changed or removed from data after loading using + # creator. + # - 'creator_attributes' and 'publish_attributes' can change values of + # their individual children but not on their own __immutable_keys = ( "id", "uuid", @@ -510,11 +515,9 @@ class CreatedInstance: @property def data(self): - """Pointer to data. + """Legacy access to data. - TODO: - Define class handling which keys are change to what. - - this is dangerous as it is possible to modify any key (e.g. `uuid`) + Access to data is needed to modify values. """ return self From e32dc2d8788df8ec77b5f272fd314a9a5cc7ff49 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 5 Oct 2021 11:48:59 +0200 Subject: [PATCH 593/736] added string representation of instance --- openpype/pipeline/create/context.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index a5e4876f55..0600d0ff7f 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -428,6 +428,17 @@ class CreatedInstance: self._asset_is_valid = self.has_set_asset self._task_is_valid = self.has_set_task + def __str__(self): + return ( + "" + " {data}" + ).format( + subset=str(self._data), + creator_identifier=self.creator_identifier, + family=self.family, + data=str(self._data) + ) + def __getitem__(self, key): return self._data[key] From ed61da911b354a95a3d30068cbdafb6d431defb7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 5 Oct 2021 11:55:44 +0200 Subject: [PATCH 594/736] use direct access on instance to access data --- .../testhost/plugins/create/auto_creator.py | 8 ++--- openpype/pipeline/create/context.py | 8 ++--- openpype/tools/new_publisher/control.py | 2 +- .../widgets/card_view_widgets.py | 18 +++++----- .../widgets/list_view_widgets.py | 36 +++++++++---------- .../tools/new_publisher/widgets/widgets.py | 28 +++++++-------- openpype/tools/new_publisher/window.py | 2 +- 7 files changed, 51 insertions(+), 51 deletions(-) diff --git a/openpype/hosts/testhost/plugins/create/auto_creator.py b/openpype/hosts/testhost/plugins/create/auto_creator.py index 67052562a0..bf586f894a 100644 --- a/openpype/hosts/testhost/plugins/create/auto_creator.py +++ b/openpype/hosts/testhost/plugins/create/auto_creator.py @@ -71,12 +71,12 @@ class MyAutoCreator(AutoCreator): self._add_instance_to_context(new_instance) elif ( - existing_instance.data["asset"] != asset_name - or existing_instance.data["task"] != task_name + existing_instance["asset"] != asset_name + or existing_instance["task"] != task_name ): asset_doc = io.find_one({"type": "asset", "name": asset_name}) subset_name = self.get_subset_name( variant, task_name, asset_doc, project_name, host_name ) - existing_instance.data["asset"] = asset_name - existing_instance.data["task"] = task_name + existing_instance["asset"] = asset_name + existing_instance["task"] = task_name diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 0600d0ff7f..f870fe6a65 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -847,8 +847,8 @@ class CreateContext: instances = self.instances task_names_by_asset_name = collections.defaultdict(set) for instance in instances: - task_name = instance.data.get("task") - asset_name = instance.data.get("asset") + task_name = instance.get("task") + asset_name = instance.get("asset") if asset_name and task_name: task_names_by_asset_name[asset_name].add(task_name) @@ -878,12 +878,12 @@ class CreateContext: if not instance.has_valid_asset or not instance.has_valid_task: continue - asset_name = instance.data["asset"] + asset_name = instance["asset"] if asset_name not in task_names_by_asset_name: instance.set_asset_invalid(True) continue - task_name = instance.data["task"] + task_name = instance["task"] if not task_name: continue diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 100bf0f4a1..0d92a5f516 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -556,7 +556,7 @@ class PublisherController: found_idx = idx break - value = instance.data["creator_attributes"][attr_def.key] + value = instance["creator_attributes"][attr_def.key] if found_idx is None: idx = len(output) output.append((attr_def, [instance], [value])) diff --git a/openpype/tools/new_publisher/widgets/card_view_widgets.py b/openpype/tools/new_publisher/widgets/card_view_widgets.py index 1175b489ca..e8cfbfcaff 100644 --- a/openpype/tools/new_publisher/widgets/card_view_widgets.py +++ b/openpype/tools/new_publisher/widgets/card_view_widgets.py @@ -70,7 +70,7 @@ class GroupWidget(QtWidgets.QWidget): instances_by_subset_name = collections.defaultdict(list) for instance in instances: instances_by_id[instance.id] = instance - subset_name = instance.data["subset"] + subset_name = instance["subset"] instances_by_subset_name[subset_name].append(instance) for instance_id in tuple(self._widgets_by_id.keys()): @@ -177,8 +177,8 @@ class InstanceCardWidget(CardWidget): icon_layout.addWidget(icon_widget) icon_layout.addWidget(context_warning) - variant = instance.data["variant"] - subset_name = instance.data["subset"] + variant = instance["variant"] + subset_name = instance["subset"] found_parts = set(re.findall(variant, subset_name, re.IGNORECASE)) if found_parts: for part in found_parts: @@ -191,7 +191,7 @@ class InstanceCardWidget(CardWidget): label_widget.setTextInteractionFlags(QtCore.Qt.NoTextInteraction) active_checkbox = NiceCheckbox(parent=self) - active_checkbox.setChecked(instance.data["active"]) + active_checkbox.setChecked(instance["active"]) expand_btn = QtWidgets.QToolButton(self) # Not yet implemented @@ -233,12 +233,12 @@ class InstanceCardWidget(CardWidget): def set_active(self, new_value): checkbox_value = self.active_checkbox.isChecked() - instance_value = self.instance.data["active"] + instance_value = self.instance["active"] # First change instance value and them change checkbox # - prevent to trigger `active_changed` signal if instance_value != new_value: - self.instance.data["active"] = new_value + self.instance["active"] = new_value if checkbox_value != new_value: self.active_checkbox.setChecked(new_value) @@ -253,7 +253,7 @@ class InstanceCardWidget(CardWidget): self.context_warning.setVisible(not valid) def update_instance_values(self): - self.set_active(self.instance.data["active"]) + self.set_active(self.instance["active"]) self._validate_context() def _set_expanded(self, expanded=None): @@ -263,11 +263,11 @@ class InstanceCardWidget(CardWidget): def _on_active_change(self): new_value = self.active_checkbox.isChecked() - old_value = self.instance.data["active"] + old_value = self.instance["active"] if new_value == old_value: return - self.instance.data["active"] = new_value + self.instance["active"] = new_value self.active_changed.emit() def _on_expend_clicked(self): diff --git a/openpype/tools/new_publisher/widgets/list_view_widgets.py b/openpype/tools/new_publisher/widgets/list_view_widgets.py index 062f4242e0..1257820d1f 100644 --- a/openpype/tools/new_publisher/widgets/list_view_widgets.py +++ b/openpype/tools/new_publisher/widgets/list_view_widgets.py @@ -76,11 +76,11 @@ class InstanceListItemWidget(QtWidgets.QWidget): self.instance = instance - subset_name_label = QtWidgets.QLabel(instance.data["subset"], self) + subset_name_label = QtWidgets.QLabel(instance["subset"], self) subset_name_label.setObjectName("ListViewSubsetName") active_checkbox = NiceCheckbox(parent=self) - active_checkbox.setChecked(instance.data["active"]) + active_checkbox.setChecked(instance["active"]) layout = QtWidgets.QHBoxLayout(self) content_margins = layout.contentsMargins() @@ -113,18 +113,18 @@ class InstanceListItemWidget(QtWidgets.QWidget): self.subset_name_label.style().polish(self.subset_name_label) def is_active(self): - return self.instance.data["active"] + return self.instance["active"] def set_active(self, new_value): checkbox_value = self.active_checkbox.isChecked() - instance_value = self.instance.data["active"] + instance_value = self.instance["active"] if new_value is None: new_value = not instance_value # First change instance value and them change checkbox # - prevent to trigger `active_changed` signal if instance_value != new_value: - self.instance.data["active"] = new_value + self.instance["active"] = new_value if checkbox_value != new_value: self.active_checkbox.setChecked(new_value) @@ -134,16 +134,16 @@ class InstanceListItemWidget(QtWidgets.QWidget): self.update_instance_values() def update_instance_values(self): - self.set_active(self.instance.data["active"]) + self.set_active(self.instance["active"]) self._set_valid_property(self.instance.has_valid_context) def _on_active_change(self): new_value = self.active_checkbox.isChecked() - old_value = self.instance.data["active"] + old_value = self.instance["active"] if new_value == old_value: return - self.instance.data["active"] = new_value + self.instance["active"] = new_value self.active_changed.emit(self.instance.id, new_value) @@ -518,12 +518,12 @@ class InstanceListView(AbstractInstanceView): new_items_with_instance = [] activity = None for instance in instances_by_group_name[group_name]: - instance_id = instance.data["uuid"] + instance_id = instance.id if activity is None: - activity = int(instance.data["active"]) + activity = int(instance["active"]) elif activity == -1: pass - elif activity != instance.data["active"]: + elif activity != instance["active"]: activity = -1 self._group_by_instance_id[instance_id] = group_name @@ -534,8 +534,8 @@ class InstanceListView(AbstractInstanceView): continue item = QtGui.QStandardItem() - item.setData(instance.data["subset"], SORT_VALUE_ROLE) - item.setData(instance.data["uuid"], INSTANCE_ID_ROLE) + item.setData(instance["subset"], SORT_VALUE_ROLE) + item.setData(instance_id, INSTANCE_ID_ROLE) new_items.append(item) new_items_with_instance.append((item, instance)) @@ -579,7 +579,7 @@ class InstanceListView(AbstractInstanceView): ) widget.active_changed.connect(self._on_active_changed) self.instance_view.setIndexWidget(proxy_index, widget) - self._widgets_by_id[instance.data["uuid"]] = widget + self._widgets_by_id[instance.id] = widget # Trigger sort at the end of refresh if sort_at_the_end: @@ -635,11 +635,11 @@ class InstanceListView(AbstractInstanceView): def get_selected_items(self): instances = [] - instances_by_id = {} context_selected = False - for instance in self.controller.instances: - instance_id = instance.data["uuid"] - instances_by_id[instance_id] = instance + instances_by_id = { + instance.id: instance + for instance in self.controller.instances + } for index in self.instance_view.selectionModel().selectedIndexes(): instance_id = index.data(INSTANCE_ID_ROLE) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index daad356ecb..be97222151 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -969,7 +969,7 @@ class GlobalAttrsWidget(QtWidgets.QWidget): asset_names = set() if asset_name is None: for instance in self._current_instances: - asset_names.add(instance.data.get("asset")) + asset_names.add(instance.get("asset")) else: asset_names.add(asset_name) @@ -986,19 +986,19 @@ class GlobalAttrsWidget(QtWidgets.QWidget): subset_names = set() for instance in self._current_instances: if variant_value is not None: - instance.data["variant"] = variant_value + instance["variant"] = variant_value if asset_name is not None: - instance.data["asset"] = asset_name + instance["asset"] = asset_name instance.set_asset_invalid(False) if task_name is not None: - instance.data["task"] = task_name + instance["task"] = task_name instance.set_task_invalid(False) - new_variant_value = instance.data.get("variant") - new_asset_name = instance.data.get("asset") - new_task_name = instance.data.get("task") + new_variant_value = instance.get("variant") + new_asset_name = instance.get("asset") + new_task_name = instance.get("task") asset_doc = asset_docs_by_name[new_asset_name] @@ -1006,7 +1006,7 @@ class GlobalAttrsWidget(QtWidgets.QWidget): new_variant_value, new_task_name, asset_doc, project_name ) subset_names.add(new_subset_name) - instance.data["subset"] = new_subset_name + instance["subset"] = new_subset_name self.subset_value_widget.set_value(subset_names) @@ -1074,13 +1074,13 @@ class GlobalAttrsWidget(QtWidgets.QWidget): if instance.creator is None: editable = False - variants.add(instance.data.get("variant") or self.unknown_value) - families.add(instance.data.get("family") or self.unknown_value) - asset_name = instance.data.get("asset") or self.unknown_value - task_name = instance.data.get("task") or self.unknown_value + variants.add(instance.get("variant") or self.unknown_value) + families.add(instance.get("family") or self.unknown_value) + asset_name = instance.get("asset") or self.unknown_value + task_name = instance.get("task") or self.unknown_value asset_names.add(asset_name) asset_task_combinations.append((asset_name, task_name)) - subset_names.add(instance.data.get("subset") or self.unknown_value) + subset_names.add(instance.get("subset") or self.unknown_value) self.variant_input.set_value(variants) @@ -1169,7 +1169,7 @@ class CreatorAttrsWidget(QtWidgets.QWidget): return for instance in instances: - creator_attributes = instance.data["creator_attributes"] + creator_attributes = instance["creator_attributes"] if attr_def.key in creator_attributes: creator_attributes[attr_def.key] = value diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/new_publisher/window.py index 1837463ff5..f6f74224f1 100644 --- a/openpype/tools/new_publisher/window.py +++ b/openpype/tools/new_publisher/window.py @@ -420,7 +420,7 @@ class PublisherWindow(QtWidgets.QDialog): all_valid = None for instance in self.controller.instances: - if not instance.data["active"]: + if not instance["active"]: continue if not instance.has_valid_context: From 4ead375a6f282ff404afcf2b928268b6add1a34a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 5 Oct 2021 12:30:02 +0200 Subject: [PATCH 595/736] attr plugins are not passed as argument on instance creation --- .../testhost/plugins/create/auto_creator.py | 4 ++-- .../testhost/plugins/create/test_creator_1.py | 6 +++--- .../testhost/plugins/create/test_creator_2.py | 6 +++--- openpype/pipeline/create/context.py | 17 ++++++----------- 4 files changed, 14 insertions(+), 19 deletions(-) diff --git a/openpype/hosts/testhost/plugins/create/auto_creator.py b/openpype/hosts/testhost/plugins/create/auto_creator.py index bf586f894a..f1e9098af3 100644 --- a/openpype/hosts/testhost/plugins/create/auto_creator.py +++ b/openpype/hosts/testhost/plugins/create/auto_creator.py @@ -17,7 +17,7 @@ class MyAutoCreator(AutoCreator): ] return output - def collect_instances(self, attr_plugins=None): + def collect_instances(self): for instance_data in pipeline.list_instances(): creator_id = instance_data.get("creator_identifier") if creator_id is not None: @@ -31,7 +31,7 @@ class MyAutoCreator(AutoCreator): elif instance_data["family"] == self.identifier: instance_data["creator_identifier"] = self.identifier instance = CreatedInstance.from_existing( - instance_data, self, attr_plugins + instance_data, self ) self._add_instance_to_context(instance) diff --git a/openpype/hosts/testhost/plugins/create/test_creator_1.py b/openpype/hosts/testhost/plugins/create/test_creator_1.py index a7555ac89c..6a513d070f 100644 --- a/openpype/hosts/testhost/plugins/create/test_creator_1.py +++ b/openpype/hosts/testhost/plugins/create/test_creator_1.py @@ -15,20 +15,20 @@ class TestCreatorOne(Creator): def get_icon(self): return resources.get_openpype_splash_filepath() - def collect_instances(self, attr_plugins): + def collect_instances(self): for instance_data in pipeline.list_instances(): instance = None creator_id = instance_data.get("creator_identifier") if creator_id is not None: if creator_id == self.identifier: instance = CreatedInstance.from_existing( - instance_data, self, attr_plugins + instance_data, self ) elif instance_data["family"] == self.identifier: instance_data["creator_identifier"] = self.identifier instance = CreatedInstance.from_existing( - instance_data, self, attr_plugins + instance_data, self ) if instance is not None: diff --git a/openpype/hosts/testhost/plugins/create/test_creator_2.py b/openpype/hosts/testhost/plugins/create/test_creator_2.py index 4017d74a08..9168fea0d2 100644 --- a/openpype/hosts/testhost/plugins/create/test_creator_2.py +++ b/openpype/hosts/testhost/plugins/create/test_creator_2.py @@ -20,20 +20,20 @@ class TestCreatorTwo(Creator): self.log.info(new_instance.data) self._add_instance_to_context(new_instance) - def collect_instances(self, attr_plugins): + def collect_instances(self): for instance_data in pipeline.list_instances(): instance = None creator_id = instance_data.get("creator_identifier") if creator_id is not None: if creator_id == self.identifier: instance = CreatedInstance.from_existing( - instance_data, self, attr_plugins + instance_data, self ) elif instance_data["family"] == self.identifier: instance_data["creator_identifier"] = self.identifier instance = CreatedInstance.from_existing( - instance_data, self, attr_plugins + instance_data, self ) if instance is not None: diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index f870fe6a65..048f4c6418 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -344,7 +344,6 @@ class CreatedInstance: creator(BaseCreator): Creator responsible for instance. host(ModuleType): Host implementation loaded with `avalon.api.registered_host`. - attr_plugins(list): List of attribute definitions of publish plugins. new(bool): Is instance new. """ # Keys that can't be changed or removed from data after loading using @@ -361,8 +360,7 @@ class CreatedInstance: ) def __init__( - self, family, subset_name, data, creator, host=None, - attr_plugins=None, new=True + self, family, subset_name, data, creator, host=None, new=True ): if host is None: import avalon.api @@ -416,8 +414,9 @@ class CreatedInstance: # Stored publish specific attribute values # {: {key: value}} + # - must be set using 'set_publish_plugins' self._data["publish_attributes"] = PublishAttributes( - self, orig_publish_attributes, attr_plugins + self, orig_publish_attributes, None ) if data: self._data.update(data) @@ -584,7 +583,7 @@ class CreatedInstance: @classmethod def from_existing( - cls, instance_data, creator, attr_plugins=None, host=None + cls, instance_data, creator, host=None ): """Convert instance data from workfile to CreatedInstance.""" instance_data = copy.deepcopy(instance_data) @@ -595,8 +594,7 @@ class CreatedInstance: subset_name = instance_data.get("subset", None) return cls( - family, subset_name, instance_data, creator, host, - attr_plugins, new=False + family, subset_name, instance_data, creator, host, new=False ) def set_publish_plugins(self, attr_plugins): @@ -821,10 +819,7 @@ class CreateContext: # Collect instances for creator in self.creators.values(): - attr_plugins = self._get_publish_plugins_with_attr_for_family( - creator.family - ) - creator.collect_instances(attr_plugins) + creator.collect_instances() def execute_autocreators(self): """Execute discovered AutoCreator plugins. From 2afcf5ab3fe91b1812319ec3d4ddeee4c12876ba Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 5 Oct 2021 12:30:23 +0200 Subject: [PATCH 596/736] implemented 'set_publish_plugins' for PublishAttributes --- openpype/pipeline/create/context.py | 56 ++++++++++++++++------------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 048f4c6418..1631c962f8 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -228,33 +228,11 @@ class PublishAttributes: attr_plugins = attr_plugins or [] self.attr_plugins = attr_plugins - self._data = {} + self._data = copy.deepcopy(origin_data) self._plugin_names_order = [] self._missing_plugins = [] - data = copy.deepcopy(origin_data) - added_keys = set() - for plugin in attr_plugins: - data = plugin.convert_attribute_values(data) - attr_defs = plugin.get_attribute_defs() - if not attr_defs: - continue - key = plugin.__name__ - added_keys.add(key) - self._plugin_names_order.append(key) - - value = data.get(key) or {} - orig_value = copy.deepcopy(origin_data.get(key) or {}) - self._data[key] = PublishAttributeValues( - self, attr_defs, value, orig_value - ) - - for key, value in data.items(): - if key not in added_keys: - self._missing_plugins.append(key) - self._data[key] = PublishAttributeValues( - self, [], value, value - ) + self.set_publish_plugins(attr_plugins) def __getitem__(self, key): return self._data[key] @@ -321,13 +299,41 @@ class PublishAttributes: return changes def set_publish_plugins(self, attr_plugins): - # TODO implement + self._plugin_names_order = [] + self._missing_plugins = [] self.attr_plugins = attr_plugins or [] + if not attr_plugins: + return + + origin_data = self._origin_data + data = self._data + self._data = {} + added_keys = set() for plugin in attr_plugins: + output = plugin.convert_attribute_values(data) + if output is not None: + data = output attr_defs = plugin.get_attribute_defs() if not attr_defs: continue + key = plugin.__name__ + added_keys.add(key) + self._plugin_names_order.append(key) + + value = data.get(key) or {} + orig_value = copy.deepcopy(origin_data.get(key) or {}) + self._data[key] = PublishAttributeValues( + self, attr_defs, value, orig_value + ) + + for key, value in data.items(): + if key not in added_keys: + self._missing_plugins.append(key) + self._data[key] = PublishAttributeValues( + self, [], value, value + ) + class CreatedInstance: """Instance entity with data that will be stored to workfile. From 9e070cb3ccb301b9c45b024e2fb91613013fd7c1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 5 Oct 2021 12:30:58 +0200 Subject: [PATCH 597/736] creator_attributes and publish_attributes can be accessed with attribute --- openpype/pipeline/create/context.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 1631c962f8..f9f648f1b9 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -550,8 +550,7 @@ class CreatedInstance: if old_value != new_value: changes[key] = (old_value, new_value) - creator_attributes = self.data["creator_attributes"] - creator_attr_changes = creator_attributes.changes() + creator_attr_changes = self.creator_attributes.changes() if creator_attr_changes: changes["creator_attributes"] = creator_attr_changes @@ -566,7 +565,11 @@ class CreatedInstance: @property def creator_attribute_defs(self): - return self._data["creator_attributes"].attr_defs + return self.creator_attributes.attr_defs + + @property + def creator_attributes(self): + return self._data["creator_attributes"] @property def publish_attributes(self): @@ -579,11 +582,8 @@ class CreatedInstance: continue output[key] = value - creator_attributes = self._data["creator_attributes"] - output["creator_attributes"] = creator_attributes.data_to_store() - - publish_attributes = self._data["publish_attributes"] - output["publish_attributes"] = publish_attributes.data_to_store() + output["creator_attributes"] = self.creator_attributes.data_to_store() + output["publish_attributes"] = self.publish_attributes.data_to_store() return output @@ -604,7 +604,7 @@ class CreatedInstance: ) def set_publish_plugins(self, attr_plugins): - self._data["publish_attributes"].set_publish_plugins(attr_plugins) + self.publish_attributes.set_publish_plugins(attr_plugins) def add_members(self, members): for member in members: From cfdbb19cbdb183be98ca718648842fbbfd00b12e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 5 Oct 2021 12:31:35 +0200 Subject: [PATCH 598/736] validate_instances_context skip pricessing if passed instances are empty --- openpype/pipeline/create/context.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index f9f648f1b9..574d514251 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -846,6 +846,10 @@ class CreateContext: def validate_instances_context(self, instances=None): if instances is None: instances = self.instances + + if not instances: + return + task_names_by_asset_name = collections.defaultdict(set) for instance in instances: task_name = instance.get("task") From b7134831fb3d1a722c1b0fb58e11ebce551fb75e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 5 Oct 2021 12:32:02 +0200 Subject: [PATCH 599/736] added 'bulk_instances_collection' which triggers validation of new instances automatically --- openpype/pipeline/create/context.py | 39 +++++++++++++++++++------ openpype/tools/new_publisher/control.py | 3 +- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 574d514251..5131ccb8eb 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -672,7 +672,8 @@ class CreateContext: self.plugins_with_defs = [] self._attr_plugins_by_family = {} - self._ignore_added_instances = False + self._bulk_counter = 0 + self._bulk_instances_to_process = [] if reset: self.reset(discover_publish_plugins) @@ -703,12 +704,10 @@ class CreateContext: self.reset_plugins(discover_publish_plugins) self.reset_context_data() - with self.ignore_added_instances(): + with self.bulk_instances_collection(): self.reset_instances() self.execute_autocreators() - self.validate_instances_context() - def reset_plugins(self, discover_publish_plugins=True): import avalon.api import pyblish.logic @@ -806,19 +805,41 @@ class CreateContext: def creator_adds_instance(self, instance): self.instances.append(instance) - if not self._ignore_added_instances: - self.validate_instances_context([instance]) + attr_plugins = self._get_publish_plugins_with_attr_for_family( + instance.creator.family + ) + instance.set_publish_plugins(attr_plugins) + + with self.bulk_instances_collection(): + self._bulk_instances_to_process.append(instance) def creator_removed_instance(self, instance): self.instances.remove(instance) @contextmanager - def ignore_added_instances(self): - self._ignore_added_instances = True + def bulk_instances_collection(self): + """Validate context of instances in bulk. + + This can be used for single instance or for adding multiple instances + which is helpfull on reset. + """ + self._bulk_counter += 1 try: yield finally: - self._ignore_added_instances = False + self._bulk_counter -= 1 + + # Trigger validation if there is no more context manager for bulk + # instance validation + if self._bulk_counter == 0: + ( + self._bulk_instances_to_process, + instances_to_validate + ) = ( + [], + self._bulk_instances_to_process + ) + self.validate_instances_context(instances_to_validate) def reset_instances(self): self.instances = [] diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 0d92a5f516..508d0c01b6 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -536,10 +536,9 @@ class PublisherController: self._resetting_instances = True self.create_context.reset_context_data() - with self.create_context.ignore_added_instances(): + with self.create_context.bulk_instances_collection(): self.create_context.reset_instances() self.create_context.execute_autocreators() - self.create_context.validate_instances_context() self._resetting_instances = False From b862b2b95300b47102d1118a3b95319f608c75e7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 5 Oct 2021 12:32:25 +0200 Subject: [PATCH 600/736] adde contains function for instance class --- openpype/pipeline/create/context.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 5131ccb8eb..824849e8f2 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -447,6 +447,9 @@ class CreatedInstance: def __getitem__(self, key): return self._data[key] + def __contains__(self, key): + return key in self._data + def __setitem__(self, key, value): # Validate immutable keys if key not in self.__immutable_keys: From dcb74c333b1b8b7e2345ba3ab365a4e4d191e4c8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 5 Oct 2021 12:35:51 +0200 Subject: [PATCH 601/736] removed host from arguments of instance --- openpype/pipeline/create/context.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 824849e8f2..6d71dcfc4e 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -366,13 +366,8 @@ class CreatedInstance: ) def __init__( - self, family, subset_name, data, creator, host=None, new=True + self, family, subset_name, data, creator, new=True ): - if host is None: - import avalon.api - - host = avalon.api.registered_host() - self.host = host self.creator = creator # Instance members may have actions on them @@ -490,6 +485,14 @@ class CreatedInstance: def creator_identifier(self): return self.creator.identifier + @property + def create_context(self): + return self.creator.create_context + + @property + def host(self): + return self.create_context.host + @property def has_set_asset(self): """Asset name is set in data.""" @@ -591,9 +594,7 @@ class CreatedInstance: return output @classmethod - def from_existing( - cls, instance_data, creator, host=None - ): + def from_existing(cls, instance_data, creator): """Convert instance data from workfile to CreatedInstance.""" instance_data = copy.deepcopy(instance_data) @@ -603,7 +604,7 @@ class CreatedInstance: subset_name = instance_data.get("subset", None) return cls( - family, subset_name, instance_data, creator, host, new=False + family, subset_name, instance_data, creator, new=False ) def set_publish_plugins(self, attr_plugins): From 8eac18e4effb513df9efab9a7580068c9a8ddae8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 5 Oct 2021 15:56:55 +0200 Subject: [PATCH 602/736] removed unused method convert_family_attribute_values --- openpype/pipeline/create/creator_plugins.py | 25 --------------------- 1 file changed, 25 deletions(-) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 5cf6a6f991..8adb4326ec 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -171,31 +171,6 @@ class BaseCreator: """ return [] - def convert_family_attribute_values(self, attribute_values): - """Convert values loaded from workfile metadata. - - If passed values match current creator version just return the value - back. Update of changes in workfile must not happen in this method. - - Default implementation only convert passed values to right types. But - implementation can be changed to do more stuff (update instance - to newer version etc.). - - Args: - attribute_values(dict): Values from instance metadata. - - Returns: - dict: Converted values. - """ - attr_defs = self.get_attribute_defs() - for attr_def in attr_defs: - key = attr_def.key - if key in attribute_values: - attribute_values[key] = attr_def.convert_value( - attribute_values[key] - ) - return attribute_values - class Creator(BaseCreator): """Creator that has more information for artist to show in UI. From b964d119c4d64c70a12b0705237ea5b4b3b6a2cf Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 5 Oct 2021 17:49:27 +0200 Subject: [PATCH 603/736] added few docstrings --- openpype/pipeline/create/context.py | 99 +++++++++++++++++++-- openpype/pipeline/create/creator_plugins.py | 8 +- openpype/pipeline/create/readme.md | 39 ++++---- 3 files changed, 119 insertions(+), 27 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 6d71dcfc4e..63476cd359 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -275,16 +275,19 @@ class PublishAttributes: return output def plugin_names_order(self): + """Plugin names order by their 'order' attribute.""" for name in self._plugin_names_order: yield name def data_to_store(self): + """Convert attribute values to "data to store".""" output = {} for key, attr_value in self._data.items(): output[key] = attr_value.data_to_store() return output def changes(self): + """Return changes per each key.""" changes = {} for key, attr_val in self._data.items(): attr_changes = attr_val.changes() @@ -299,6 +302,7 @@ class PublishAttributes: return changes def set_publish_plugins(self, attr_plugins): + """Set publish plugins attribute definitions.""" self._plugin_names_order = [] self._missing_plugins = [] self.attr_plugins = attr_plugins or [] @@ -439,6 +443,7 @@ class CreatedInstance: data=str(self._data) ) + # --- Dictionary like methods --- def __getitem__(self, key): return self._data[key] @@ -472,6 +477,7 @@ class CreatedInstance: def items(self): return self._data.items() + # ------ @property def family(self): @@ -569,14 +575,14 @@ class CreatedInstance: changes[key] = (old_value, None) return changes - @property - def creator_attribute_defs(self): - return self.creator_attributes.attr_defs - @property def creator_attributes(self): return self._data["creator_attributes"] + @property + def creator_attribute_defs(self): + return self.creator_attributes.attr_defs + @property def publish_attributes(self): return self._data["publish_attributes"] @@ -611,6 +617,7 @@ class CreatedInstance: self.publish_attributes.set_publish_plugins(attr_plugins) def add_members(self, members): + """Currently unused method.""" for member in members: if member not in self._members: self._members.append(member) @@ -621,6 +628,16 @@ class CreateContext: Context itself also can store data related to whole creation (workfile). - those are mainly for Context publish plugins + + Args: + host(ModuleType): Host implementation which handles implementation and + global metadata. + dbcon(AvalonMongoDB): Connection to mongo with context (at least + project). + headless(bool): Context is created out of UI (Current not used). + reset(bool): Reset context on initialization. + discover_publish_plugins(bool): Discover publish plugins during reset + phase. """ # Methods required in host implementaion to be able create instances # or change context data. @@ -633,6 +650,7 @@ class CreateContext: self, host, dbcon=None, headless=False, reset=True, discover_publish_plugins=True ): + # Create conncetion if is not passed if dbcon is None: import avalon.api @@ -641,12 +659,17 @@ class CreateContext: dbcon.install() self.dbcon = dbcon + self.host = host + # Prepare attribute for logger (Created on demand in `log` property) self._log = None + + # Publish context plugins attributes and it's values self._publish_attributes = PublishAttributes(self, {}) self._original_context_data = {} - self.host = host + # Validate host implementation + # - defines if context is capable of handling context data host_is_valid = True missing_methods = self.get_host_misssing_methods(host) if missing_methods: @@ -660,6 +683,7 @@ class CreateContext: ).format(joined_methods)) self._host_is_valid = host_is_valid + # Currently unused variable self.headless = headless # TODO convert to dictionary instance by id to validate duplicates @@ -669,6 +693,7 @@ class CreateContext: self.creators = {} # Prepare categories of creators self.autocreators = {} + # TODO rename 'ui_creators' to something more suitable self.ui_creators = {} self.publish_discover_result = None @@ -676,18 +701,29 @@ class CreateContext: self.plugins_with_defs = [] self._attr_plugins_by_family = {} + # Helpers for validating context of collected instances + # - they can be validation for multiple instances at one time + # using context manager which will trigger validation + # after leaving of last context manager scope self._bulk_counter = 0 self._bulk_instances_to_process = [] + # Trigger reset if was enabled if reset: self.reset(discover_publish_plugins) @property def publish_attributes(self): + """Access to global publish attributes.""" return self._publish_attributes @classmethod def get_host_misssing_methods(cls, host): + """Collect missing methods from host. + + Args: + host(ModuleType): Host implementaion. + """ missing = set() for attr_name in cls.required_methods: if not hasattr(host, attr_name): @@ -696,15 +732,21 @@ class CreateContext: @property def host_is_valid(self): + """Is host valid for creation.""" return self._host_is_valid @property def log(self): + """Dynamic access to logger.""" if self._log is None: self._log = logging.getLogger(self.__class__.__name__) return self._log def reset(self, discover_publish_plugins=True): + """Reset context with all plugins and instances. + + All changes will be lost if were not saved explicitely. + """ self.reset_plugins(discover_publish_plugins) self.reset_context_data() @@ -713,6 +755,11 @@ class CreateContext: self.execute_autocreators() def reset_plugins(self, discover_publish_plugins=True): + """Reload plugins. + + Reloads creators from preregistered paths and can load publish plugins + if it's enabled on context. + """ import avalon.api import pyblish.logic @@ -780,6 +827,11 @@ class CreateContext: self.creators = creators def reset_context_data(self): + """Reload context data using host implementation. + + These data are not related to any instance but may be needed for whole + publishing. + """ if not self.host_is_valid: self._original_context_data = {} self._publish_attributes = PublishAttributes(self, {}) @@ -796,11 +848,16 @@ class CreateContext: ) def context_data_to_store(self): + """Data that should be stored by host function. + + The same data should be returned on loading. + """ return { "publish_attributes": self._publish_attributes.data_to_store() } def context_data_changes(self): + """Changes of attributes.""" changes = {} publish_attribute_changes = self._publish_attributes.changes() if publish_attribute_changes: @@ -808,12 +865,26 @@ class CreateContext: return changes def creator_adds_instance(self, instance): + """Creator adds new instance to context. + + Instances should be added only from creators. + + Args: + instance(CreatedInstance): Instance with prepared data from + creator. + + TODO: Rename method to more suit. + """ + # Add instance to instances list self.instances.append(instance) + # Prepare publish plugin attributes and set it on instance attr_plugins = self._get_publish_plugins_with_attr_for_family( instance.creator.family ) instance.set_publish_plugins(attr_plugins) + # Add instance to be validated inside 'bulk_instances_collection' + # context manager if is inside bulk with self.bulk_instances_collection(): self._bulk_instances_to_process.append(instance) @@ -826,6 +897,8 @@ class CreateContext: This can be used for single instance or for adding multiple instances which is helpfull on reset. + + Should not be executed from multiple threads. """ self._bulk_counter += 1 try: @@ -846,6 +919,7 @@ class CreateContext: self.validate_instances_context(instances_to_validate) def reset_instances(self): + """Reload instances""" self.instances = [] # Collect instances @@ -869,9 +943,12 @@ class CreateContext: self.log.warning(msg, exc_info=True) def validate_instances_context(self, instances=None): + """Validate 'asset' and 'task' instance context.""" + # Use all instances from context if 'instances' are not passed if instances is None: instances = self.instances + # Skip if instances are empty if not instances: return @@ -921,6 +998,7 @@ class CreateContext: instance.set_task_invalid(True) def save_changes(self): + """Save changes. Update all changed values.""" if not self.host_is_valid: missing_methods = self.get_host_misssing_methods(self.host) raise HostMissRequiredMethod(self.host, missing_methods) @@ -929,12 +1007,14 @@ class CreateContext: self._save_instance_changes() def _save_context_changes(self): + """Save global context values.""" changes = self.context_data_changes() if changes: data = self.context_data_to_store() self.host.update_context_data(data, changes) def _save_instance_changes(self): + """Save instance specific values.""" instances_by_identifier = collections.defaultdict(list) for instance in self.instances: identifier = instance.creator_identifier @@ -968,6 +1048,14 @@ class CreateContext: creator.remove_instances(creator_instances) def _get_publish_plugins_with_attr_for_family(self, family): + """Publish plugin attributes for passed family. + + Attribute definitions for specific family are cached. + + Args: + family(str): Instance family for which should be attribute + definitions returned. + """ if family not in self._attr_plugins_by_family: import pyblish.logic @@ -983,6 +1071,7 @@ class CreateContext: return self._attr_plugins_by_family[family] def _get_publish_plugins_with_attr_for_context(self): + """Publish plugins attributes for Context plugins.""" plugins = [] for plugin in self.plugins_with_defs: if not plugin.__instanceEnabled__: diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 8adb4326ec..f583f69605 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -55,7 +55,7 @@ class BaseCreator: @abstractproperty def identifier(self): - """Family that plugin represents.""" + """Identifier of creator (must be unique).""" pass @abstractproperty @@ -70,6 +70,7 @@ class BaseCreator: return self._log def _add_instance_to_context(self, instance): + """Helper method to ad d""" self.create_context.creator_adds_instance(instance) def _remove_instance_from_context(self, instance): @@ -116,6 +117,11 @@ class BaseCreator: def get_dynamic_data( self, variant, task_name, asset_doc, project_name, host_name ): + """Dynamic data for subset name filling. + + These may be get dynamically created based on current context of + workfile. + """ return {} def get_subset_name( diff --git a/openpype/pipeline/create/readme.md b/openpype/pipeline/create/readme.md index ffaa60340c..6e6cb27dcd 100644 --- a/openpype/pipeline/create/readme.md +++ b/openpype/pipeline/create/readme.md @@ -2,17 +2,19 @@ Creation is process defying what and how will be published. May work in a different way based on host implementation. ## CreateContext -Entry point of creation. All data and metadata are stored to create context. Context hold all global data and instances. Is responsible for loading of plugins (create, publish), loading data from host, validation of host implementation and emitting changes to host implementation. +Entry point of creation. All data and metadata are handled through create context. Context hold all global data and instances. Is responsible for loading of plugins (create, publish), triggering creator methods, validation of host implementation and emitting changes to creators and host. -Discovers Create plugins to be able create new instances and convert existing instances. Creators may have defined attributes that are specific for the family. Attributes definition can enhance behavior of instance during publishing. +Discovers Creator plugins to be able create new instances and convert existing instances. Creators may have defined attributes that are specific for their instances. Attributes definition can enhance behavior of instance during publishing. Publish plugins are loaded because they can also define attributes definitions. These are less family specific To be able define attributes Publish plugin must inherit from `OpenPypePyblishPluginMixin` and must override `get_attribute_defs` class method which must return list of attribute definitions. Values of publish plugin definitions are stored per plugin name under `publish_attributes`. Possible attribute definitions can be found in `openpype/pipeline/lib/attribute_definitions.py`. +Except creating and removing instances are all changes not automatically propagated to host context (scene/workfile/...) to propagate changes call `save_changes` which trigger update of all instances in context using Creators implementation. + ## CreatedInstance -Product of creation is "instance" which holds basic data defying it. Core data are `family` and `subset`. Other data can be keys used to fill subset name or metadata modifying publishing process of the instance (more described later). +Product of creation is "instance" which holds basic data defying it. Core data are `creator_identifier`, `family` and `subset`. Other data can be keys used to fill subset name or metadata modifying publishing process of the instance (more described later). All instances have `id` which holds constant `pyblish.avalon.instance` and `uuid` which is identifier of the instance. Family tells how should be instance processed and subset what name will published item have. - There are cases when subset is not fully filled during creation and may change during publishing. That is in most of cases caused because instance is related to other instance or instance data do not represent final product. @@ -35,8 +37,10 @@ Family tells how should be instance processed and subset what name will publishe "active": True, ## Version of instance "version": 1, + # Identifier of creator (is unique) + "creator_identifier": "", ## Creator specific attributes (defined by Creator) - "family_attributes": {...}, + "creator_attributes": {...}, ## Publish plugin specific plugins (defined by Publish plugin) "publish_attributes": { # Attribute values are stored by publish plugin name @@ -50,26 +54,19 @@ Family tells how should be instance processed and subset what name will publishe ``` ## Creator -To be able create instance there must be defined a creator. Creator represents a family and handling of it's instances. Is not responsible only about creating new instances but also about updating existing. Family is identifier of creator so there can be only one Creator with same family at a time which helps to handle changes in creation of specific family. +To be able create, update, remove or collect existing instances there must be defined a creator. Creator must have unique identifier and can represents a family. There can be multiple Creators for single family. Identifier of creator should contain family (advise). -Creator does not have strictly defined how is new instance created but result be approachable from host implementation and host must have ability to remove the instance metadata without the Creator. That is host specific logic and can't be handled generally. +Creator has abstract methods to handle instances. For new instance creation is used `create` which should create metadata in host context and add new instance object to `CreateContext`. To collect existing instances is used `collect_instances` which should find all existing instances related to creator and add them to `CreateContext`. To update data of instance is used `update_instances` which is called from `CreateContext` on `save_changes`. To remove instance use `remove_instances` which should remove metadata from host context and remove instance from `CreateContext`. + +Creator has access to `CreateContext` which created object of the creator. All new instances or removed instances must be told to context. To do so use methods `_add_instance_to_context` and `_remove_instance_from_context` where `CreatedInstance` is passed. They should be called from `create` if new instance was created and from `remove_instances` if instance was removed. + +Creators don't have strictly defined how are instances handled but it is good practice to define a way which is host specific. It is not strict because there are cases when host implementation just can't handle all requirements of all creators. ### AutoCreator -Auto-creators are automatically executed when CreateContext is reset. They can be used to create instances that should be always available and may not require artist's manual creation (e.g. `workfile`). Should not create duplicated instance and should raise `AutoCreationSkipped` exception when did not create any instance to speed up resetting of context. +Auto-creators are automatically executed when `CreateContext` is reset. They can be used to create instances that should be always available and may not require artist's manual creation (e.g. `workfile`). Should not create duplicated instance and validate existence before creates a new. Method `remove_instances` is implemented to do nothing. ## Host -Host implementation should be main entrance for creators how their logic should work. In most of cases must store data somewhere ideally to workfile if host has workfile and it's possible. - -Host implementation must have available these functions to be able handle creation changes. - -### List all created instances (`list_instances`) -List of all instances for current context (from workfile). Each item is dictionary with all data that are stored. Creators must implement their creation so host will be able to find the instance with this function. - -### Remove instances (`remove_instances`) -Remove instance from context (from workfile). This must remove all metadata about instance so instance is not retrieved with `list_instances`. This is default host implementation of instance removement. Creator can do more cleanup before this function is called and can stop calling of this function completely (e.g. when Creator removed node where are also stored metadata). - -### Update instances (`update_instances`) -Instance data has changed and update of changes should be saved so next call of `list_instances` will return modified values. +Host implementation must have available global context metadata handler functions. One to get current context data and second to update them. Currently are to context data stored only context publish plugin attribute values. ### Get global context data (`get_context_data`) There are data that are not specific for any instance but are specific for whole context (e.g. Context plugins values). @@ -77,5 +74,5 @@ There are data that are not specific for any instance but are specific for whole ### Update global context data (`update_context_data`) Update global context data. -### Get context title (`get_context_title`) -This is optional but is recommended. String returned from this function will be shown in UI. +### Optional title of context +It is recommended to implement `get_context_title` function. String returned from this function will be shown in UI as context in which artist is. From 1549cf9b8124d85f31c37ee6f75d8ed077733472 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 5 Oct 2021 17:56:30 +0200 Subject: [PATCH 604/736] store instances by their id --- openpype/pipeline/create/context.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 63476cd359..f70f7afce7 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -686,8 +686,8 @@ class CreateContext: # Currently unused variable self.headless = headless - # TODO convert to dictionary instance by id to validate duplicates - self.instances = [] + # Instances by their ID + self._instances_by_id = {} # Discovered creators self.creators = {} @@ -712,6 +712,10 @@ class CreateContext: if reset: self.reset(discover_publish_plugins) + @property + def instances(self): + return self._instances_by_id.values() + @property def publish_attributes(self): """Access to global publish attributes.""" @@ -876,7 +880,7 @@ class CreateContext: TODO: Rename method to more suit. """ # Add instance to instances list - self.instances.append(instance) + self._instances_by_id[instance.id] = instance # Prepare publish plugin attributes and set it on instance attr_plugins = self._get_publish_plugins_with_attr_for_family( instance.creator.family @@ -889,7 +893,7 @@ class CreateContext: self._bulk_instances_to_process.append(instance) def creator_removed_instance(self, instance): - self.instances.remove(instance) + self._instance.pop(instance.id, None) @contextmanager def bulk_instances_collection(self): @@ -920,7 +924,7 @@ class CreateContext: def reset_instances(self): """Reload instances""" - self.instances = [] + self._instances_by_id = {} # Collect instances for creator in self.creators.values(): @@ -946,7 +950,7 @@ class CreateContext: """Validate 'asset' and 'task' instance context.""" # Use all instances from context if 'instances' are not passed if instances is None: - instances = self.instances + instances = tuple(self._instances_by_id.values()) # Skip if instances are empty if not instances: @@ -1016,7 +1020,7 @@ class CreateContext: def _save_instance_changes(self): """Save instance specific values.""" instances_by_identifier = collections.defaultdict(list) - for instance in self.instances: + for instance in self._instances_by_id.values(): identifier = instance.creator_identifier instances_by_identifier[identifier].append(instance) From 73e07330aaa11a284b924ae1b8d93fce08a123ab Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 5 Oct 2021 18:06:33 +0200 Subject: [PATCH 605/736] changed ui_creators to manual_creators --- openpype/pipeline/create/context.py | 10 +++++----- openpype/tools/new_publisher/control.py | 5 +++-- openpype/tools/new_publisher/widgets/create_dialog.py | 4 ++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index f70f7afce7..19b596e1fc 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -693,8 +693,8 @@ class CreateContext: self.creators = {} # Prepare categories of creators self.autocreators = {} - # TODO rename 'ui_creators' to something more suitable - self.ui_creators = {} + # Manual creators + self.manual_creators = {} self.publish_discover_result = None self.publish_plugins = [] @@ -804,7 +804,7 @@ class CreateContext: # Discover and prepare creators creators = {} autocreators = {} - ui_creators = {} + manual_creators = {} for creator_class in avalon.api.discover(BaseCreator): if inspect.isabstract(creator_class): self.log.info( @@ -823,10 +823,10 @@ class CreateContext: if isinstance(creator, AutoCreator): autocreators[creator_identifier] = creator elif isinstance(creator, Creator): - ui_creators[creator_identifier] = creator + manual_creators[creator_identifier] = creator self.autocreators = autocreators - self.ui_creators = ui_creators + self.manual_creators = manual_creators self.creators = creators diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 508d0c01b6..67a55d0d88 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -400,8 +400,9 @@ class PublisherController: return self.create_context.creators @property - def ui_creators(self): - return self.create_context.ui_creators + def manual_creators(self): + """Creators""" + return self.create_context.manual_creators @property def host_is_valid(self): diff --git a/openpype/tools/new_publisher/widgets/create_dialog.py b/openpype/tools/new_publisher/widgets/create_dialog.py index 7dc61eae10..9c176592cb 100644 --- a/openpype/tools/new_publisher/widgets/create_dialog.py +++ b/openpype/tools/new_publisher/widgets/create_dialog.py @@ -334,7 +334,7 @@ class CreateDialog(QtWidgets.QDialog): # Add new families new_creators = set() - for identifier, creator in self.controller.ui_creators.items(): + for identifier, creator in self.controller.manual_creators.items(): # TODO add details about creator new_creators.add(identifier) if identifier in existing_items: @@ -375,7 +375,7 @@ class CreateDialog(QtWidgets.QDialog): if new_index.isValid(): identifier = new_index.data(CREATOR_IDENTIFIER_ROLE) - creator = self.controller.ui_creators.get(identifier) + creator = self.controller.manual_creators.get(identifier) self.creator_description_widget.set_plugin(creator) From b80f4c1590ff701966d58f12754779a316ae832b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 5 Oct 2021 18:32:38 +0200 Subject: [PATCH 606/736] few smaller changes --- openpype/tools/new_publisher/widgets/create_dialog.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/create_dialog.py b/openpype/tools/new_publisher/widgets/create_dialog.py index 9c176592cb..12205e1b12 100644 --- a/openpype/tools/new_publisher/widgets/create_dialog.py +++ b/openpype/tools/new_publisher/widgets/create_dialog.py @@ -422,7 +422,7 @@ class CreateDialog(QtWidgets.QDialog): self.subset_name_input.setText("< Invalid variant >") return - project_name = self.dbcon.Session["AVALON_PROJECT"] + project_name = self.controller.project_name task_name = self._task_name asset_doc = copy.deepcopy(self._asset_doc) @@ -541,12 +541,12 @@ class CreateDialog(QtWidgets.QDialog): except CreatorError as exc: error_info = (str(exc), None) - except Exception as exc: + except: exc_type, exc_value, exc_traceback = sys.exc_info() formatted_traceback = "".join(traceback.format_exception( exc_type, exc_value, exc_traceback )) - error_info = (str(exc), formatted_traceback) + error_info = (str(exc_value), formatted_traceback) if error_info: box = CreateErrorMessageBox( From 609f08d31728140bd9da97e4069028821d38ad8a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 5 Oct 2021 18:32:53 +0200 Subject: [PATCH 607/736] added few docstrings --- openpype/tools/new_publisher/control.py | 77 ++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 3 deletions(-) diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index 67a55d0d88..bbf67c3942 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -122,7 +122,10 @@ class AssetDocsCache: class PublishReport: - """Report for single publishing process.""" + """Report for single publishing process. + + Report keeps current state of publishing and currently processed plugin. + """ def __init__(self, controller): self.controller = controller self._publish_discover_result = None @@ -135,6 +138,7 @@ class PublishReport: self._current_context = None def reset(self, context, publish_discover_result=None): + """Reset report and clear all data.""" self._publish_discover_result = publish_discover_result self._plugin_data = [] self._plugin_data_with_plugin = [] @@ -143,6 +147,7 @@ class PublishReport: self._current_context = context def add_plugin_iter(self, plugin, context): + """Add report about single iteration of plugin.""" for instance in context: self._all_instances_by_id[instance.id] = instance @@ -186,6 +191,7 @@ class PublishReport: return plugin_data_item def set_plugin_skipped(self): + """Set that current plugin has been skipped.""" self._current_plugin_data["skipped"] = True def add_result(self, result): @@ -319,6 +325,7 @@ class PublishReport: class PublisherController: + """Middleware between UI, CreateContext and publish Context.""" def __init__(self, dbcon=None, headless=False): self.log = logging.getLogger("PublisherController") self.host = avalon.api.registered_host() @@ -385,35 +392,42 @@ class PublisherController: @property def project_name(self): + """Current project context.""" return self.dbcon.Session["AVALON_PROJECT"] @property def dbcon(self): + """Pointer to AvalonMongoDB in creator context.""" return self.create_context.dbcon @property def instances(self): + """Current instances in create context.""" return self.create_context.instances @property def creators(self): + """All creators loaded in create context.""" return self.create_context.creators @property def manual_creators(self): - """Creators""" + """Creators that can be shown in create dialog.""" return self.create_context.manual_creators @property def host_is_valid(self): + """Host is valid for creation.""" return self.create_context.host_is_valid @property def publish_plugins(self): + """Publish plugins.""" return self.create_context.publish_plugins @property def plugins_with_defs(self): + """Publish plugins with possible attribute definitions.""" return self.create_context.plugins_with_defs def _create_reference(self, callback): @@ -428,42 +442,52 @@ class PublisherController: return ref def add_instances_refresh_callback(self, callback): + """Callbacks triggered on instances refresh.""" ref = self._create_reference(callback) self._instances_refresh_callback_refs.add(ref) def add_plugins_refresh_callback(self, callback): + """Callbacks triggered on plugins refresh.""" ref = self._create_reference(callback) self._plugins_refresh_callback_refs.add(ref) # --- Publish specific callbacks --- def add_publish_reset_callback(self, callback): + """Callbacks triggered on publishing reset.""" ref = self._create_reference(callback) self._publish_reset_callback_refs.add(ref) def add_publish_started_callback(self, callback): + """Callbacks triggered on publishing start.""" ref = self._create_reference(callback) self._publish_started_callback_refs.add(ref) def add_publish_validated_callback(self, callback): + """Callbacks triggered on passing last possible validation order.""" ref = self._create_reference(callback) self._publish_validated_callback_refs.add(ref) def add_instance_change_callback(self, callback): + """Callbacks triggered before next publish instance process.""" ref = self._create_reference(callback) self._publish_instance_changed_callback_refs.add(ref) def add_plugin_change_callback(self, callback): + """Callbacks triggered before next plugin processing.""" ref = self._create_reference(callback) self._publish_plugin_changed_callback_refs.add(ref) def add_publish_stopped_callback(self, callback): + """Callbacks triggered on publishing stop (any reason).""" ref = self._create_reference(callback) self._publish_stopped_callback_refs.add(ref) def get_asset_docs(self): + """Get asset documents from cache for whole project.""" return self._asset_docs_cache.get_asset_docs() def get_context_title(self): + """Get context title for artist shown at the top of main window.""" context_title = None if hasattr(self.host, "get_context_title"): context_title = self.host.get_context_title() @@ -476,6 +500,7 @@ class PublisherController: return context_title def get_asset_hierarchy(self): + """Prepare asset documents into hierarchy.""" _queue = collections.deque(self.get_asset_docs()) output = collections.defaultdict(list) @@ -486,6 +511,7 @@ class PublisherController: return output def get_task_names_by_asset_names(self, asset_names): + """Prepare task names by asset name.""" task_names_by_asset_name = ( self._asset_docs_cache.get_task_names_by_asset_name() ) @@ -511,6 +537,7 @@ class PublisherController: callbacks.remove(ref) def reset(self): + """Reset everything related to creation and publishing.""" self.stop_publish() self._reset_plugins() # Publish part must be resetted after plugins @@ -531,6 +558,7 @@ class PublisherController: self._trigger_callbacks(self._plugins_refresh_callback_refs) def _reset_instances(self): + """Reset create instances.""" if self._resetting_instances: return @@ -546,6 +574,12 @@ class PublisherController: self._trigger_callbacks(self._instances_refresh_callback_refs) def get_creator_attribute_definitions(self, instances): + """Collect creator attribute definitions for multuple instances. + + Args: + instances(list): List of created instances for + which should be attribute definitions returned. + """ output = [] _attr_defs = {} for instance in instances: @@ -556,7 +590,7 @@ class PublisherController: found_idx = idx break - value = instance["creator_attributes"][attr_def.key] + value = instance.creator_attributes[attr_def.key] if found_idx is None: idx = len(output) output.append((attr_def, [instance], [value])) @@ -568,6 +602,13 @@ class PublisherController: return output def get_publish_attribute_definitions(self, instances, include_context): + """Collect publish attribute definitions for passed instances. + + Args: + instances(list): List of created instances for + which should be attribute definitions returned. + include_context(bool): Add context specific attribute definitions. + """ _tmp_items = [] if include_context: _tmp_items.append(self.create_context) @@ -612,6 +653,7 @@ class PublisherController: return output def get_icon_for_family(self, family): + """TODO rename to get creator icon.""" creator = self.creators.get(family) if creator is not None: return creator.get_icon() @@ -620,16 +662,19 @@ class PublisherController: def create( self, creator_identifier, subset_name, instance_data, options ): + """Trigger creation and refresh of instances in UI.""" creator = self.creators[creator_identifier] creator.create(subset_name, instance_data, options) self._trigger_callbacks(self._instances_refresh_callback_refs) def save_changes(self): + """Save changes happened during creation.""" if self.create_context.host_is_valid: self.create_context.save_changes() def remove_instances(self, instances): + """""" # QUESTION Expect that instaces are really removed? In that case save # reset is not required and save changes too. self.save_changes() @@ -691,6 +736,11 @@ class PublisherController: self._publish_context = pyblish.api.Context() # Make sure "comment" is set on publish context self._publish_context.data["comment"] = "" + # Add access to create context during publishing + # - must not be used for changing CreatedInstances during publishing! + # QUESTION + # - pop the key after first collector using it would be safest option? + self._publish_context.data["create_context"] = self.create_context self._publish_report.reset( self._publish_context, @@ -741,6 +791,7 @@ class PublisherController: self._trigger_callbacks(self._publish_stopped_callback_refs) def stop_publish(self): + """Stop publishing process (any reason).""" if self._publish_is_running: self._stop_publish() @@ -776,6 +827,18 @@ class PublisherController: self._main_thread_processor.add_item(item) def _publish_iterator(self): + """Main logic center of publishing. + + Iterator returns `MainThreadItem` objects with callbacks that should be + processed in main thread (threaded in future?). Cares about changing + states of currently processed publish plugin and instance. Also + change state of processed orders like validation order has passed etc. + + Also stops publishing if should stop on validation. + + QUESTION: + Does validate button still make sense? + """ for idx, plugin in enumerate(self.publish_plugins): self._publish_progress = idx # Add plugin to publish report @@ -851,6 +914,7 @@ class PublisherController: else: self._publish_report.set_plugin_skipped() + # Cleanup of publishing process self._publish_finished = True self._publish_progress = self._publish_max_progress yield MainThreadItem(self.stop_publish) @@ -892,6 +956,13 @@ class PublisherController: def collect_families_from_instances(instances, only_active=False): + """Collect all families for passed publish instances. + + Args: + instances(list): List of publish instances from + which are families collected. + only_active(bool): Return families only for active instances. + """ all_families = set() for instance in instances: if only_active: From 10714bb734e16c99d8bee52365e01f4d701858ae Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 5 Oct 2021 18:34:10 +0200 Subject: [PATCH 608/736] adde comment about bare except --- openpype/tools/new_publisher/widgets/create_dialog.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/tools/new_publisher/widgets/create_dialog.py b/openpype/tools/new_publisher/widgets/create_dialog.py index 12205e1b12..0206f038fb 100644 --- a/openpype/tools/new_publisher/widgets/create_dialog.py +++ b/openpype/tools/new_publisher/widgets/create_dialog.py @@ -541,6 +541,8 @@ class CreateDialog(QtWidgets.QDialog): except CreatorError as exc: error_info = (str(exc), None) + # Use bare except because some hosts raise their exceptions that + # do not inherit from python's `BaseException` except: exc_type, exc_value, exc_traceback = sys.exc_info() formatted_traceback = "".join(traceback.format_exception( From 20458ebc91c812d80d1f494117d9feaf9d5c4c34 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 5 Oct 2021 19:31:47 +0200 Subject: [PATCH 609/736] renamed AssetNameInput to ClickableLineEdit --- openpype/tools/new_publisher/widgets/widgets.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index be97222151..09025812a2 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -420,12 +420,12 @@ class AssetsDialog(QtWidgets.QDialog): return self._selected_asset -class AssetNameInput(QtWidgets.QLineEdit): +class ClickableLineEdit(QtWidgets.QLineEdit): clicked = QtCore.Signal() def __init__(self, *args, **kwargs): - super(AssetNameInput, self).__init__(*args, **kwargs) - self.setObjectName("AssetNameInput") + super(ClickableLineEdit, self).__init__(*args, **kwargs) + self.setReadOnly(True) self._mouse_pressed = False def mousePressEvent(self, event): @@ -455,8 +455,8 @@ class AssetsField(ClickableFrame): dialog = AssetsDialog(controller, self) - name_input = AssetNameInput(self) - name_input.setReadOnly(True) + name_input = ClickableLineEdit(self) + name_input.setObjectName("AssetNameInput") layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) From 1e2b3b7babba8457623d309559ef93968da6f061 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 6 Oct 2021 09:53:43 +0200 Subject: [PATCH 610/736] tasks combobox is clickable --- .../tools/new_publisher/widgets/widgets.py | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 09025812a2..03980f04cf 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -565,9 +565,6 @@ class TasksCombobox(QtWidgets.QComboBox): super(TasksCombobox, self).__init__(parent) self.setObjectName("TasksCombobox") - self.setEditable(True) - self.lineEdit().setReadOnly(True) - delegate = QtWidgets.QStyledItemDelegate() self.setItemDelegate(delegate) @@ -586,6 +583,8 @@ class TasksCombobox(QtWidgets.QComboBox): self._multiselection_text = None self._is_valid = True + self._text = None + def set_multiselection_text(self, text): self._multiselection_text = text @@ -593,6 +592,7 @@ class TasksCombobox(QtWidgets.QComboBox): if self._ignore_index_change: return + self.set_text(None) text = self.currentText() idx = self.findText(text) if idx < 0: @@ -606,6 +606,28 @@ class TasksCombobox(QtWidgets.QComboBox): self.value_changed.emit() + def set_text(self, text): + if text == self._text: + return + + self._text = text + + def paintEvent(self, event): + """Paint custom text without using QLineEdit.""" + painter = QtGui.QPainter(self) + painter.setPen(self.palette().color(QtGui.QPalette.Text)) + opt = QtWidgets.QStyleOptionComboBox() + self.initStyleOption(opt) + if self._text is not None: + opt.currentText = self._text + style = self.style() + style.drawComplexControl( + QtWidgets.QStyle.CC_ComboBox, opt, painter, self + ) + style.drawControl( + QtWidgets.QStyle.CE_ComboBoxLabel, opt, painter, self + ) + def is_valid(self): return self._is_valid @@ -738,7 +760,7 @@ class TasksCombobox(QtWidgets.QComboBox): # Set current index (must be set to -1 if is invalid) self.setCurrentIndex(idx) if idx < 0: - self.lineEdit().setText(item_name) + self.set_text(item_name) def reset_to_origin(self): self.set_selected_items(self._origin_value) From 81981fbecf8763546c0e232a640a361298027fcc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 6 Oct 2021 09:53:52 +0200 Subject: [PATCH 611/736] fix parenting --- openpype/tools/new_publisher/widgets/widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 03980f04cf..8415a7a9ca 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -312,7 +312,7 @@ class AssetsDialog(QtWidgets.QDialog): proxy_model.setSourceModel(model) proxy_model.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) - filter_input = QtWidgets.QLineEdit() + filter_input = QtWidgets.QLineEdit(self) filter_input.setPlaceholderText("Filter assets..") asset_view = QtWidgets.QTreeView(self) From 1540f880976255a88b80f6410488d1665cb2662c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 6 Oct 2021 09:56:30 +0200 Subject: [PATCH 612/736] renamed readme file to temp name --- openpype/pipeline/create/{readme.md => _readme.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename openpype/pipeline/create/{readme.md => _readme.md} (100%) diff --git a/openpype/pipeline/create/readme.md b/openpype/pipeline/create/_readme.md similarity index 100% rename from openpype/pipeline/create/readme.md rename to openpype/pipeline/create/_readme.md From 2572e5eabe37f7b91138761aee0d31fb8a4132ae Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 6 Oct 2021 09:56:52 +0200 Subject: [PATCH 613/736] renamed readme to have uppered letters --- openpype/pipeline/create/{_readme.md => README.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename openpype/pipeline/create/{_readme.md => README.md} (100%) diff --git a/openpype/pipeline/create/_readme.md b/openpype/pipeline/create/README.md similarity index 100% rename from openpype/pipeline/create/_readme.md rename to openpype/pipeline/create/README.md From e8be200f0fecbb59d02e803cc31c9acef4c05aa2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 6 Oct 2021 09:59:15 +0200 Subject: [PATCH 614/736] added information about convert_attribute_values class method on publish plugins --- openpype/pipeline/create/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/create/README.md b/openpype/pipeline/create/README.md index 6e6cb27dcd..9eef7c72a7 100644 --- a/openpype/pipeline/create/README.md +++ b/openpype/pipeline/create/README.md @@ -6,7 +6,7 @@ Entry point of creation. All data and metadata are handled through create contex Discovers Creator plugins to be able create new instances and convert existing instances. Creators may have defined attributes that are specific for their instances. Attributes definition can enhance behavior of instance during publishing. -Publish plugins are loaded because they can also define attributes definitions. These are less family specific To be able define attributes Publish plugin must inherit from `OpenPypePyblishPluginMixin` and must override `get_attribute_defs` class method which must return list of attribute definitions. Values of publish plugin definitions are stored per plugin name under `publish_attributes`. +Publish plugins are loaded because they can also define attributes definitions. These are less family specific To be able define attributes Publish plugin must inherit from `OpenPypePyblishPluginMixin` and must override `get_attribute_defs` class method which must return list of attribute definitions. Values of publish plugin definitions are stored per plugin name under `publish_attributes`. Also can override `convert_attribute_values` class method which gives ability to modify values on instance before are used in CreatedInstance. Method `convert_attribute_values` can be also used without `get_attribute_defs` to modify values when changing compatibility (remove metadata from instance because are irrelevant). Possible attribute definitions can be found in `openpype/pipeline/lib/attribute_definitions.py`. From 50749ac41cbeab97989ffba9281b1a43bface81f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 6 Oct 2021 10:12:30 +0200 Subject: [PATCH 615/736] removed backwards compatbility --- .../testhost/plugins/create/auto_creator.py | 16 ++++------------ .../testhost/plugins/create/test_creator_1.py | 12 +----------- .../testhost/plugins/create/test_creator_2.py | 12 +----------- 3 files changed, 6 insertions(+), 34 deletions(-) diff --git a/openpype/hosts/testhost/plugins/create/auto_creator.py b/openpype/hosts/testhost/plugins/create/auto_creator.py index f1e9098af3..0690164ae5 100644 --- a/openpype/hosts/testhost/plugins/create/auto_creator.py +++ b/openpype/hosts/testhost/plugins/create/auto_creator.py @@ -20,18 +20,10 @@ class MyAutoCreator(AutoCreator): def collect_instances(self): for instance_data in pipeline.list_instances(): creator_id = instance_data.get("creator_identifier") - if creator_id is not None: - if creator_id == self.identifier: - subset_name = instance_data["subset"] - instance = CreatedInstance( - self.family, subset_name, instance_data, self - ) - self._add_instance_to_context(instance) - - elif instance_data["family"] == self.identifier: - instance_data["creator_identifier"] = self.identifier - instance = CreatedInstance.from_existing( - instance_data, self + if creator_id == self.identifier: + subset_name = instance_data["subset"] + instance = CreatedInstance( + self.family, subset_name, instance_data, self ) self._add_instance_to_context(instance) diff --git a/openpype/hosts/testhost/plugins/create/test_creator_1.py b/openpype/hosts/testhost/plugins/create/test_creator_1.py index 6a513d070f..edb946424a 100644 --- a/openpype/hosts/testhost/plugins/create/test_creator_1.py +++ b/openpype/hosts/testhost/plugins/create/test_creator_1.py @@ -17,21 +17,11 @@ class TestCreatorOne(Creator): def collect_instances(self): for instance_data in pipeline.list_instances(): - instance = None creator_id = instance_data.get("creator_identifier") - if creator_id is not None: - if creator_id == self.identifier: - instance = CreatedInstance.from_existing( - instance_data, self - ) - - elif instance_data["family"] == self.identifier: - instance_data["creator_identifier"] = self.identifier + if creator_id == self.identifier: instance = CreatedInstance.from_existing( instance_data, self ) - - if instance is not None: self._add_instance_to_context(instance) def update_instances(self, update_list): diff --git a/openpype/hosts/testhost/plugins/create/test_creator_2.py b/openpype/hosts/testhost/plugins/create/test_creator_2.py index 9168fea0d2..b3f04bd511 100644 --- a/openpype/hosts/testhost/plugins/create/test_creator_2.py +++ b/openpype/hosts/testhost/plugins/create/test_creator_2.py @@ -22,21 +22,11 @@ class TestCreatorTwo(Creator): def collect_instances(self): for instance_data in pipeline.list_instances(): - instance = None creator_id = instance_data.get("creator_identifier") - if creator_id is not None: - if creator_id == self.identifier: - instance = CreatedInstance.from_existing( - instance_data, self - ) - - elif instance_data["family"] == self.identifier: - instance_data["creator_identifier"] = self.identifier + if creator_id == self.identifier: instance = CreatedInstance.from_existing( instance_data, self ) - - if instance is not None: self._add_instance_to_context(instance) def update_instances(self, update_list): From b1d29456b98b7e5ec3ae9aac797593de8f170f45 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 6 Oct 2021 10:12:46 +0200 Subject: [PATCH 616/736] added validations of duplicated instnaces and creators --- openpype/pipeline/create/context.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 19b596e1fc..16cf05d70d 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -813,6 +813,12 @@ class CreateContext: continue creator_identifier = creator_class.identifier + if creator_identifier in creators: + self.log.warning(( + "Duplicated Creator identifier. " + "Using first and skipping following" + )) + continue creator = creator_class( self, system_settings, @@ -880,6 +886,12 @@ class CreateContext: TODO: Rename method to more suit. """ # Add instance to instances list + if instance.id in self._instances_by_id: + self.log.warning(( + "Instance with id {} is already added to context." + ).format(instance.id)) + return + self._instances_by_id[instance.id] = instance # Prepare publish plugin attributes and set it on instance attr_plugins = self._get_publish_plugins_with_attr_for_family( From 4bb14b5f6b4062e4ea41f5b406d59a4a235b79af Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 6 Oct 2021 11:25:56 +0200 Subject: [PATCH 617/736] added readme to publish pipeline --- openpype/pipeline/publish/README.md | 38 +++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 openpype/pipeline/publish/README.md diff --git a/openpype/pipeline/publish/README.md b/openpype/pipeline/publish/README.md new file mode 100644 index 0000000000..870d29314d --- /dev/null +++ b/openpype/pipeline/publish/README.md @@ -0,0 +1,38 @@ +# Publish +OpenPype is using `pyblish` for publishing process which is a little bit extented and modified mainly for UI purposes. OpenPype's (new) publish UI does not allow to enable/disable instances or plugins that can be done during creation part. Also does support actions only for validators after validation exception. + +## Exceptions +OpenPype define few specific exceptions that should be used in publish plugins. + +### Validation exception +Validation plugins should raise `PublishValidationError` to show to an artist what's wrong and give him actions to fix it. The exception says that error happened in plugin can be fixed by artist himself (with or without action on plugin). Any other errors will stop publishing immediately. Exception `PublishValidationError` raised after validation order has same effect as any other exception. + +Exception `PublishValidationError` 3 arguments: +- **message** Which is not used in UI but for headless publishing. +- **title** Short description of error (2-5 words). Title is used for grouping of exceptions per plugin. +- **description** Detailed description of happened issue where markdown and html can be used. + + +### Known errors +When there is a known error that can't be fixed by user (e.g. can't connect to deadline service, etc.) `KnownPublishError` should be raise. The only difference is that it's message is shown in UI to artist otherwise a neutral message without context is shown. + +## Plugin extension +Publish plugins can be extended by additional logic when inherits from `OpenPypePyblishPluginMixin` which can be used as mixin (additional inheritance of class). + +```python +import pyblish.api +from openpype.pipeline import OpenPypePyblishPluginMixin + + +# Example context plugin +class MyExtendedPlugin( + pyblish.api.ContextPlugin, OpenPypePyblishPluginMixin +): + pass + +``` + +### Extensions +Currently only extension is ability to define attributes for instances during creation. Method `get_attribute_defs` returns attribute definitions for families defined in plugin's `families` attribute if it's instance plugin or for whole context if it's context plugin. To convert existing values (or to remove legacy values) can be implemented `convert_attribute_values`. Values of publish attributes from created instance are never removed automatically so implementing of this method is best way to remove legacy data or convert them to new data structure. + +Possible attribute definitions can be found in `openpype/pipeline/lib/attribute_definitions.py`. From 57b553a13c249bd1a246804b82b01da0824133e7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 6 Oct 2021 11:38:23 +0200 Subject: [PATCH 618/736] added reset of context --- openpype/pipeline/create/context.py | 34 +++++++++++++++++++++++++ openpype/tools/new_publisher/control.py | 5 ++++ 2 files changed, 39 insertions(+) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 16cf05d70d..943a5d49e3 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -751,6 +751,7 @@ class CreateContext: All changes will be lost if were not saved explicitely. """ + self.reset_avalon_context() self.reset_plugins(discover_publish_plugins) self.reset_context_data() @@ -758,6 +759,39 @@ class CreateContext: self.reset_instances() self.execute_autocreators() + def reset_avalon_context(self): + """Give ability to reset avalon context. + + Reset is based on optional host implementation of `get_current_context` + function or using `avalon.api.Session`. + + Some hosts have ability to change context file without using workfiles + tool but that change is not propagated to + """ + import avalon.api + + project_name = asset_name = task_name = None + if hasattr(self.host, "get_current_context"): + host_context = self.host.get_current_context() + if host_context: + project_name = host_context.get("project_name") + asset_name = host_context.get("asset_name") + task_name = host_context.get("task_name") + + if not project_name: + project_name = avalon.api.Session.get("AVALON_PROJECT") + if not asset_name: + asset_name = avalon.api.Session.get("AVALON_ASSET") + if not task_name: + task_name = avalon.api.Session.get("AVALON_TASK") + + if project_name: + self.dbcon.Session["AVALON_PROJECT"] = project_name + if asset_name: + self.dbcon.Session["AVALON_ASSET"] = asset_name + if task_name: + self.dbcon.Session["AVALON_TASK"] = task_name + def reset_plugins(self, discover_publish_plugins=True): """Reload plugins. diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/new_publisher/control.py index bbf67c3942..f361f7bb56 100644 --- a/openpype/tools/new_publisher/control.py +++ b/openpype/tools/new_publisher/control.py @@ -538,7 +538,12 @@ class PublisherController: def reset(self): """Reset everything related to creation and publishing.""" + # Stop publishing self.stop_publish() + + # Reset avalon context + self.create_context.reset_avalon_context() + self._reset_plugins() # Publish part must be resetted after plugins self._reset_publish() From b87f7ba669cf8b5f60b8aa3a861b5e095cf2a0f1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 6 Oct 2021 11:54:24 +0200 Subject: [PATCH 619/736] implemented get_subset_name_with_asset_doc which can expect asset document as argument instead of asset id --- openpype/lib/__init__.py | 2 + openpype/lib/plugin_tools.py | 92 ++++++++++++++++++++++++++++++------ 2 files changed, 79 insertions(+), 15 deletions(-) diff --git a/openpype/lib/__init__.py b/openpype/lib/__init__.py index 74004a1239..ee4821b80d 100644 --- a/openpype/lib/__init__.py +++ b/openpype/lib/__init__.py @@ -130,6 +130,7 @@ from .applications import ( from .plugin_tools import ( TaskNotSetError, get_subset_name, + get_subset_name_with_asset_doc, prepare_template_data, filter_pyblish_plugins, set_plugin_attributes_from_settings, @@ -249,6 +250,7 @@ __all__ = [ "TaskNotSetError", "get_subset_name", + "get_subset_name_with_asset_doc", "filter_pyblish_plugins", "set_plugin_attributes_from_settings", "source_hash", diff --git a/openpype/lib/plugin_tools.py b/openpype/lib/plugin_tools.py index 9dccadc44e..f6e55d58f6 100644 --- a/openpype/lib/plugin_tools.py +++ b/openpype/lib/plugin_tools.py @@ -27,16 +27,17 @@ class TaskNotSetError(KeyError): super(TaskNotSetError, self).__init__(msg) -def get_subset_name( +def _get_subset_name( family, variant, task_name, asset_id, - project_name=None, - host_name=None, - default_template=None, - dynamic_data=None, - dbcon=None + asset_doc, + project_name, + host_name, + default_template, + dynamic_data, + dbcon ): if not family: return "" @@ -62,15 +63,16 @@ def get_subset_name( dbcon.install() - asset_doc = dbcon.find_one( - { - "type": "asset", - "_id": asset_id - }, - { - "data.tasks": True - } - ) + if asset_doc is None: + asset_doc = dbcon.find_one( + { + "type": "asset", + "_id": asset_id + }, + { + "data.tasks": True + } + ) or {} asset_tasks = asset_doc.get("data", {}).get("tasks") or {} task_info = asset_tasks.get(task_name) or {} task_type = task_info.get("type") @@ -112,6 +114,66 @@ def get_subset_name( return template.format(**prepare_template_data(fill_pairs)) +def get_subset_name_with_asset_doc( + family, + variant, + task_name, + asset_doc, + project_name=None, + host_name=None, + default_template=None, + dynamic_data=None, + dbcon=None +): + """Calculate subset name using OpenPype settings. + + This variant of function expects already queried asset document. + """ + return _get_subset_name( + family, variant, + task_name, + None, + asset_doc, + project_name, + host_name, + default_template, + dynamic_data, + dbcon + ) + + +def get_subset_name( + family, + variant, + task_name, + asset_id, + project_name=None, + host_name=None, + default_template=None, + dynamic_data=None, + dbcon=None +): + """Calculate subset name using OpenPype settings. + + This variant of function expects asset id as argument. + + This is legacy function should be replaced with + `get_subset_name_with_asset_doc` where asset document is expected. + """ + return _get_subset_name( + family, + variant, + task_name, + asset_id, + None, + project_name, + host_name, + default_template, + dynamic_data, + dbcon + ) + + def prepare_template_data(fill_pairs): """ Prepares formatted data for filling template. From 8f5e3eaa3de3ce8826bb09197dd816ce2c13c757 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 6 Oct 2021 11:54:35 +0200 Subject: [PATCH 620/736] use get_subset_name_with_asset_doc in creator --- openpype/pipeline/create/creator_plugins.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index f583f69605..26804aa87f 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -8,7 +8,7 @@ from abc import ( ) import six -from openpype.lib import get_subset_name +from openpype.lib import get_subset_name_with_asset_doc class CreatorError(Exception): @@ -150,7 +150,7 @@ class BaseCreator: variant, task_name, asset_doc, project_name, host_name ) - return get_subset_name( + return get_subset_name_with_asset_doc( self.family, variant, task_name, From 49765d24a53b60c6324649f2e4b61df478991277 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 6 Oct 2021 12:07:11 +0200 Subject: [PATCH 621/736] fixed variable name --- openpype/pipeline/create/context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 943a5d49e3..0d7fca175a 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -939,7 +939,7 @@ class CreateContext: self._bulk_instances_to_process.append(instance) def creator_removed_instance(self, instance): - self._instance.pop(instance.id, None) + self._instances_by_id.pop(instance.id, None) @contextmanager def bulk_instances_collection(self): From 68b08ac0be49981bb8acb00e3e6c7bcde2a0e9c7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 6 Oct 2021 12:09:20 +0200 Subject: [PATCH 622/736] added global collector that creates instances from create context --- .../collect_instances_from_create_context.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 openpype/plugins/publish/collect_instances_from_create_context.py diff --git a/openpype/plugins/publish/collect_instances_from_create_context.py b/openpype/plugins/publish/collect_instances_from_create_context.py new file mode 100644 index 0000000000..db62ab8748 --- /dev/null +++ b/openpype/plugins/publish/collect_instances_from_create_context.py @@ -0,0 +1,46 @@ +"""Create instances based on CreateContext. + +""" +import os +import pyblish.api + +from openpype.lib import ApplicationManager + + +class CollectInstancesFromCreateContext(pyblish.api.ContextPlugin): + """Collect instances from CreateContext from new publishing.""" + + label = "Collect Instances from Create Context" + order = pyblish.api.CollectorOrder - 0.5 + + def process(self, context): + create_context = context.data.pop("create_context", None) + if not create_context: + return + + for created_instance in create_context.instances: + instance_data = created_instance.data_to_store() + if instance_data["active"]: + self.create_instance(context, instance_data) + + def create_instance(self, context, in_data): + subset = in_data["subset"] + # If instance data already contain families then use it + instance_families = in_data.get("families") or [] + + instance = context.create_instance(subset) + instance.data.update({ + "subset": subset, + "asset": in_data["asset"], + "label": subset, + "name": subset, + "family": in_data["family"], + "families": instance_families + }) + for key, value in in_data.items(): + if key not in instance.data: + instance.data[key] = value + self.log.info("collected instance: {}".format(instance.data)) + self.log.info("parsing data: {}".format(in_data)) + + instance.data["representations"] = list() From 9e69af258e7eb5cbc967d8ce165c07ddc85107f6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 6 Oct 2021 12:11:16 +0200 Subject: [PATCH 623/736] modified testhost collector --- .../plugins/publish/collect_context.py | 36 +++---------------- 1 file changed, 4 insertions(+), 32 deletions(-) diff --git a/openpype/hosts/testhost/plugins/publish/collect_context.py b/openpype/hosts/testhost/plugins/publish/collect_context.py index 5595764aac..5aff76a42d 100644 --- a/openpype/hosts/testhost/plugins/publish/collect_context.py +++ b/openpype/hosts/testhost/plugins/publish/collect_context.py @@ -1,7 +1,6 @@ import pyblish.api from avalon import io -from openpype.hosts.testhost import api from openpype.pipeline import ( OpenPypePyblishPluginMixin, attribute_definitions @@ -16,8 +15,8 @@ class CollectContextDataTestHost( and path for returning json data back to hostself. """ - label = "Collect Context - Test Host" - order = pyblish.api.CollectorOrder - 0.5 + label = "Collect Source - Test Host" + order = pyblish.api.CollectorOrder - 0.4 hosts = ["testhost"] @classmethod @@ -32,32 +31,5 @@ class CollectContextDataTestHost( def process(self, context): # get json paths from os and load them - io.install() - - for instance_data in api.list_instances(): - if instance_data.get("active", True): - # create instance - self.create_instance(context, instance_data) - - def create_instance(self, context, in_data): - subset = in_data["subset"] - # If instance data already contain families then use it - instance_families = in_data.get("families") or [] - - instance = context.create_instance(subset) - instance.data.update({ - "subset": subset, - "asset": in_data["asset"], - "label": subset, - "name": subset, - "family": in_data["family"], - "families": instance_families - }) - for key, value in in_data.items(): - if key not in instance.data: - instance.data[key] = value - self.log.info("collected instance: {}".format(instance.data)) - self.log.info("parsing data: {}".format(in_data)) - - instance.data["representations"] = list() - instance.data["source"] = "testhost" + for instance in context: + instance.data["source"] = "testhost" From 6ebe9ee253157a95f2e8b26238af01975dc62520 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 6 Oct 2021 12:15:20 +0200 Subject: [PATCH 624/736] renamed create context collector and collect also context data --- ...reate_context.py => collect_from_create_context.py} | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) rename openpype/plugins/publish/{collect_instances_from_create_context.py => collect_from_create_context.py} (80%) diff --git a/openpype/plugins/publish/collect_instances_from_create_context.py b/openpype/plugins/publish/collect_from_create_context.py similarity index 80% rename from openpype/plugins/publish/collect_instances_from_create_context.py rename to openpype/plugins/publish/collect_from_create_context.py index db62ab8748..ad788d5042 100644 --- a/openpype/plugins/publish/collect_instances_from_create_context.py +++ b/openpype/plugins/publish/collect_from_create_context.py @@ -7,10 +7,10 @@ import pyblish.api from openpype.lib import ApplicationManager -class CollectInstancesFromCreateContext(pyblish.api.ContextPlugin): - """Collect instances from CreateContext from new publishing.""" +class CollectFromCreateContext(pyblish.api.ContextPlugin): + """Collect instances and data from CreateContext from new publishing.""" - label = "Collect Instances from Create Context" + label = "Collect From Create Context" order = pyblish.api.CollectorOrder - 0.5 def process(self, context): @@ -23,6 +23,9 @@ class CollectInstancesFromCreateContext(pyblish.api.ContextPlugin): if instance_data["active"]: self.create_instance(context, instance_data) + # Update global data to context + context.data.update(create_context.context_data_to_store()) + def create_instance(self, context, in_data): subset = in_data["subset"] # If instance data already contain families then use it @@ -32,6 +35,7 @@ class CollectInstancesFromCreateContext(pyblish.api.ContextPlugin): instance.data.update({ "subset": subset, "asset": in_data["asset"], + "task": in_data["task"], "label": subset, "name": subset, "family": in_data["family"], From 418350954f5f7f124fa9b3ead6b732557c14c9ab Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 6 Oct 2021 12:19:05 +0200 Subject: [PATCH 625/736] added context update --- .../plugins/publish/collect_from_create_context.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/collect_from_create_context.py b/openpype/plugins/publish/collect_from_create_context.py index ad788d5042..16e3f669c3 100644 --- a/openpype/plugins/publish/collect_from_create_context.py +++ b/openpype/plugins/publish/collect_from_create_context.py @@ -3,8 +3,7 @@ """ import os import pyblish.api - -from openpype.lib import ApplicationManager +import avalon.api class CollectFromCreateContext(pyblish.api.ContextPlugin): @@ -15,6 +14,7 @@ class CollectFromCreateContext(pyblish.api.ContextPlugin): def process(self, context): create_context = context.data.pop("create_context", None) + # Skip if create context is not available if not create_context: return @@ -26,6 +26,13 @@ class CollectFromCreateContext(pyblish.api.ContextPlugin): # Update global data to context context.data.update(create_context.context_data_to_store()) + # Update context data + for key in ("AVALON_PROJECT", "AVALON_ASSET", "AVALON_TASK"): + value = create_context.dbcon.Session.get(key) + if value is not None: + avalon.api.Session[key] = value + os.environ[key] = value + def create_instance(self, context, in_data): subset = in_data["subset"] # If instance data already contain families then use it From 4446c535448b4d01a87dcb1cfaddd1dd36004720 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 6 Oct 2021 16:26:21 +0200 Subject: [PATCH 626/736] add parent to qt windows --- .../hosts/houdini/startup/MainMenuCommon.xml | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/houdini/startup/MainMenuCommon.xml b/openpype/hosts/houdini/startup/MainMenuCommon.xml index 76585085e2..cb73d2643f 100644 --- a/openpype/hosts/houdini/startup/MainMenuCommon.xml +++ b/openpype/hosts/houdini/startup/MainMenuCommon.xml @@ -7,24 +7,30 @@ @@ -45,7 +51,8 @@ publish.show(parent) From 6a8c23f46e99ddf9dd9f6f21754f07f016cc0e1d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 7 Oct 2021 10:25:20 +0200 Subject: [PATCH 627/736] label is available for all creators --- openpype/pipeline/create/context.py | 4 ++++ openpype/pipeline/create/creator_plugins.py | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 0d7fca175a..752854574a 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -491,6 +491,10 @@ class CreatedInstance: def creator_identifier(self): return self.creator.identifier + @property + def creator_label(self): + return self.creator.label or self.creator_identifier + @property def create_context(self): return self.creator.create_context diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 26804aa87f..aa2e3333ce 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -33,6 +33,9 @@ class BaseCreator: to `self` if it's not Plugin specific. """ + # Label shown in UI + label = None + # Variable to store logger _log = None @@ -183,8 +186,6 @@ class Creator(BaseCreator): Creation requires prepared subset name and instance data. """ - # Label shown in UI - label = None # GUI Purposes # - default_variants may not be used if `get_default_variants` is overriden From 3ee8b487da81329cc0a82f2fcb53ec0995c5c2f3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 7 Oct 2021 10:27:02 +0200 Subject: [PATCH 628/736] use creator label for grouping --- .../widgets/card_view_widgets.py | 49 +++++++++++-------- .../widgets/list_view_widgets.py | 6 +-- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/openpype/tools/new_publisher/widgets/card_view_widgets.py b/openpype/tools/new_publisher/widgets/card_view_widgets.py index e8cfbfcaff..a4062fb5e3 100644 --- a/openpype/tools/new_publisher/widgets/card_view_widgets.py +++ b/openpype/tools/new_publisher/widgets/card_view_widgets.py @@ -23,7 +23,7 @@ class GroupWidget(QtWidgets.QWidget): active_changed = QtCore.Signal() removed_selected = QtCore.Signal() - def __init__(self, group_name, group_icon, parent): + def __init__(self, group_name, group_icons, parent): super(GroupWidget, self).__init__(parent) label_widget = QtWidgets.QLabel(group_name, self) @@ -45,7 +45,7 @@ class GroupWidget(QtWidgets.QWidget): layout.addLayout(label_layout, 0) self._group = group_name - self._group_icon = group_icon + self._group_icons = group_icons self._widgets_by_id = {} @@ -93,8 +93,9 @@ class GroupWidget(QtWidgets.QWidget): widget = self._widgets_by_id[instance.id] widget.update_instance(instance) else: + group_icon = self._group_icons[instance.creator_identifier] widget = InstanceCardWidget( - instance, self._group_icon, self + instance, group_icon, self ) widget.selected.connect(self.selected) widget.active_changed.connect(self.active_changed) @@ -163,7 +164,7 @@ class InstanceCardWidget(CardWidget): super(InstanceCardWidget, self).__init__(parent) self._id = instance.id - self._group_identifier = instance.creator_identifier + self._group_identifier = instance.creator_label self._group_icon = group_icon self.instance = instance @@ -356,33 +357,39 @@ class InstanceCardView(AbstractInstanceView): self.select_item(CONTEXT_ID, None) - instances_by_creator = collections.defaultdict(list) + instances_by_group = collections.defaultdict(list) + identifiers_by_group = collections.defaultdict(set) for instance in self.controller.instances: - identifier = instance.creator_identifier - instances_by_creator[identifier].append(instance) + group_name = instance.creator_label + instances_by_group[group_name].append(instance) + identifiers_by_group[group_name].add( + instance.creator_identifier + ) - for identifier in tuple(self._widgets_by_group.keys()): - if identifier in instances_by_creator: + for group_name in tuple(self._widgets_by_group.keys()): + if group_name in instances_by_group: continue - if identifier == self._selected_group: + if group_name == self._selected_group: self._on_remove_selected() - widget = self._widgets_by_group.pop(identifier) + widget = self._widgets_by_group.pop(group_name) widget.setVisible(False) self._content_layout.removeWidget(widget) widget.deleteLater() - sorted_identifiers = list(sorted(instances_by_creator.keys())) + sorted_group_names = list(sorted(instances_by_group.keys())) widget_idx = 1 - for creator_identifier in sorted_identifiers: - if creator_identifier in self._widgets_by_group: - group_widget = self._widgets_by_group[creator_identifier] + for group_name in sorted_group_names: + if group_name in self._widgets_by_group: + group_widget = self._widgets_by_group[group_name] else: - group_icon = self.controller.get_icon_for_family( - creator_identifier - ) + group_icons = { + idenfier: self.controller.get_icon_for_family(idenfier) + for idenfier in identifiers_by_group[group_name] + } + group_widget = GroupWidget( - creator_identifier, group_icon, self._content_widget + group_name, group_icons, self._content_widget ) group_widget.active_changed.connect(self._on_active_changed) group_widget.selected.connect(self._on_widget_selection) @@ -390,11 +397,11 @@ class InstanceCardView(AbstractInstanceView): self._on_remove_selected ) self._content_layout.insertWidget(widget_idx, group_widget) - self._widgets_by_group[creator_identifier] = group_widget + self._widgets_by_group[group_name] = group_widget widget_idx += 1 group_widget.update_instances( - instances_by_creator[creator_identifier] + instances_by_group[group_name] ) def refresh_instance_states(self): diff --git a/openpype/tools/new_publisher/widgets/list_view_widgets.py b/openpype/tools/new_publisher/widgets/list_view_widgets.py index 1257820d1f..d6a10e735f 100644 --- a/openpype/tools/new_publisher/widgets/list_view_widgets.py +++ b/openpype/tools/new_publisher/widgets/list_view_widgets.py @@ -436,9 +436,9 @@ class InstanceListView(AbstractInstanceView): instances_by_group_name = collections.defaultdict(list) group_names = set() for instance in self.controller.instances: - identifier = instance.creator_identifier - group_names.add(identifier) - instances_by_group_name[identifier].append(instance) + group_label = instance.creator_label + group_names.add(group_label) + instances_by_group_name[group_label].append(instance) sort_at_the_end = False root_item = self.instance_model.invisibleRootItem() From 30e431d85e83c8924f8a373c814d20b9ecc42144 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 8 Oct 2021 16:08:15 +0200 Subject: [PATCH 629/736] check existing subset wip --- .../houdini/plugins/create/create_hda.py | 58 ++++++++++++++++--- 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_hda.py b/openpype/hosts/houdini/plugins/create/create_hda.py index 05307d4c56..775c51166a 100644 --- a/openpype/hosts/houdini/plugins/create/create_hda.py +++ b/openpype/hosts/houdini/plugins/create/create_hda.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from openpype.hosts.houdini.api import plugin from avalon.houdini import lib +from avalon import io import hou @@ -17,34 +18,75 @@ class CreateHDA(plugin.Creator): super(CreateHDA, self).__init__(*args, **kwargs) self.data.pop("active", None) + def _check_existing(self, subset_name): + # type: (str) -> bool + """Check if existing subset name versions already exists.""" + # Get all subsets of the current asset + subset_docs = io.find( + { + "type": "subset", + "parent": self.data["asset"] + }, + {"name": 1} + ) + existing_subset_names = set(subset_docs.distinct("name")) + existing_subset_names_low = { + _name.lower() for _name in existing_subset_names + } + return subset_name.lower() in existing_subset_names_low + def _process(self, instance): + # get selected nodes out = hou.node("/obj") self.nodes = hou.selectedNodes() if (self.options or {}).get("useSelection") and self.nodes: + # if we have `use selection` enabled and we have some + # selected nodes ... to_hda = self.nodes[0] if len(self.nodes) > 1: + # if there is more then one node, create subnet first subnet = out.createNode( "subnet", node_name="{}_subnet".format(self.name)) to_hda = subnet else: + # in case of no selection, just create subnet node subnet = out.createNode( "subnet", node_name="{}_subnet".format(self.name)) subnet.moveToGoodPosition() to_hda = subnet - hda_node = to_hda.createDigitalAsset( - name=self.name, - hda_file_name="$HIP/{}.hda".format(self.name) - ) + if not to_hda.type().definition(): + # if node type has not its definition, it is not user + # created hda. We test if hda can be created from the node. + + if not to_hda.canCreateDigitalAsset(): + raise Exception( + "cannot create hda from node {}".format(to_hda)) + + hda_node = to_hda.createDigitalAsset( + name=self.name, + hda_file_name="$HIP/{}.hda".format(self.name) + ) + hou.moveNodesTo(self.nodes, hda_node) + hda_node.layoutChildren() + else: + hda_node = to_hda + hda_node.setName(self.name) - hou.moveNodesTo(self.nodes, hda_node) - hda_node.layoutChildren() + # delete node created by Avalon in /out # this needs to be addressed in future Houdini workflow refactor. + hou.node("/out/{}".format(self.name)).destroy() - lib.imprint(hda_node, self.data) + try: + lib.imprint(hda_node, self.data) + except hou.OperationFailed as e: + raise plugin.OpenPypeCreatorError( + ("Cannot set metadata on asset. Might be that it already is " + "OpenPype asset.") + ) - return hda_node + return hda_node \ No newline at end of file From cff256b914e50b9c43730aadc18f743df73afa6c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 14 Oct 2021 15:51:49 +0200 Subject: [PATCH 630/736] added same label for both creators --- openpype/hosts/testhost/plugins/create/test_creator_1.py | 1 + openpype/hosts/testhost/plugins/create/test_creator_2.py | 1 + 2 files changed, 2 insertions(+) diff --git a/openpype/hosts/testhost/plugins/create/test_creator_1.py b/openpype/hosts/testhost/plugins/create/test_creator_1.py index edb946424a..6ec4d16467 100644 --- a/openpype/hosts/testhost/plugins/create/test_creator_1.py +++ b/openpype/hosts/testhost/plugins/create/test_creator_1.py @@ -9,6 +9,7 @@ from openpype.pipeline import ( class TestCreatorOne(Creator): identifier = "test_one" + label = "test" family = "test" description = "Testing creator of testhost" diff --git a/openpype/hosts/testhost/plugins/create/test_creator_2.py b/openpype/hosts/testhost/plugins/create/test_creator_2.py index b3f04bd511..4b1430a6a2 100644 --- a/openpype/hosts/testhost/plugins/create/test_creator_2.py +++ b/openpype/hosts/testhost/plugins/create/test_creator_2.py @@ -8,6 +8,7 @@ from openpype.pipeline import ( class TestCreatorTwo(Creator): identifier = "test_two" + label = "test" family = "test" description = "A second testing creator" From 903bbf03ee627e857054d825819589aa18785493 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 14 Oct 2021 17:11:26 +0200 Subject: [PATCH 631/736] added docstring to `_get_subset_name` --- openpype/lib/plugin_tools.py | 49 ++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/openpype/lib/plugin_tools.py b/openpype/lib/plugin_tools.py index f6e55d58f6..27031cd1ff 100644 --- a/openpype/lib/plugin_tools.py +++ b/openpype/lib/plugin_tools.py @@ -39,6 +39,36 @@ def _get_subset_name( dynamic_data, dbcon ): + """Calculate subset name based on passed context and OpenPype settings. + + Subst name templates are defined in `project_settings/global/tools/creator + /subset_name_profiles` where are profiles with host name, family, task name + and task type filters. If context does not match any profile then + `DEFAULT_SUBSET_TEMPLATE` is used as default template. + + That's main reason why so many arguments are required to calculate subset + name. + + Args: + family (str): Instance family. + variant (str): In most of cases it is user input during creation. + task_name (str): Task name on which context is instance created. + asset_id (ObjectId): Id of object. Is optional if `asset_doc` is + passed. + asset_doc (dict): Queried asset document with it's tasks in data. + Used to get task type. + project_name (str): Name of project on which is instance created. + Important for project settings that are loaded. + host_name (str): One of filtering criteria for template profile + filters. + default_template (str): Default template if any profile does not match + passed context. Constant 'DEFAULT_SUBSET_TEMPLATE' is used if + is not passed. + dynamic_data (dict): Dynamic data specific for a creator which creates + instance. + dbcon (AvalonMongoDB): Mongo connection to be able query asset document + if 'asset_doc' is not passed. + """ if not family: return "" @@ -53,17 +83,16 @@ def _get_subset_name( project_name = avalon.api.Session["AVALON_PROJECT"] - # Function should expect asset document instead of asset id - # - that way `dbcon` is not needed - if dbcon is None: - from avalon.api import AvalonMongoDB - - dbcon = AvalonMongoDB() - dbcon.Session["AVALON_PROJECT"] = project_name - - dbcon.install() - + # Query asset document if was not passed if asset_doc is None: + if dbcon is None: + from avalon.api import AvalonMongoDB + + dbcon = AvalonMongoDB() + dbcon.Session["AVALON_PROJECT"] = project_name + + dbcon.install() + asset_doc = dbcon.find_one( { "type": "asset", From 9065204e95b964e76b4ea1beebca55933eab7549 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 15 Oct 2021 01:32:20 +0200 Subject: [PATCH 632/736] subset check and documentation --- openpype/hosts/houdini/api/plugin.py | 3 ++- .../houdini/plugins/create/create_hda.py | 24 ++++++++++-------- website/docs/artist_hosts_houdini.md | 25 +++++++++++++++++++ 3 files changed, 41 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/houdini/api/plugin.py b/openpype/hosts/houdini/api/plugin.py index efdaa60084..63d9bba470 100644 --- a/openpype/hosts/houdini/api/plugin.py +++ b/openpype/hosts/houdini/api/plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- """Houdini specific Avalon/Pyblish plugin definitions.""" import sys +from avalon.api import CreatorError from avalon import houdini import six @@ -8,7 +9,7 @@ import hou from openpype.api import PypeCreatorMixin -class OpenPypeCreatorError(Exception): +class OpenPypeCreatorError(CreatorError): pass diff --git a/openpype/hosts/houdini/plugins/create/create_hda.py b/openpype/hosts/houdini/plugins/create/create_hda.py index 775c51166a..63d235d235 100644 --- a/openpype/hosts/houdini/plugins/create/create_hda.py +++ b/openpype/hosts/houdini/plugins/create/create_hda.py @@ -22,12 +22,13 @@ class CreateHDA(plugin.Creator): # type: (str) -> bool """Check if existing subset name versions already exists.""" # Get all subsets of the current asset + asset_id = io.find_one({"name": self.data["asset"], "type": "asset"}, + projection={"_id": True})['_id'] subset_docs = io.find( { "type": "subset", - "parent": self.data["asset"] - }, - {"name": 1} + "parent": asset_id + }, {"name": 1} ) existing_subset_names = set(subset_docs.distinct("name")) existing_subset_names_low = { @@ -36,7 +37,7 @@ class CreateHDA(plugin.Creator): return subset_name.lower() in existing_subset_names_low def _process(self, instance): - + subset_name = self.data["subset"] # get selected nodes out = hou.node("/obj") self.nodes = hou.selectedNodes() @@ -60,26 +61,29 @@ class CreateHDA(plugin.Creator): if not to_hda.type().definition(): # if node type has not its definition, it is not user # created hda. We test if hda can be created from the node. - if not to_hda.canCreateDigitalAsset(): raise Exception( "cannot create hda from node {}".format(to_hda)) hda_node = to_hda.createDigitalAsset( - name=self.name, - hda_file_name="$HIP/{}.hda".format(self.name) + name=subset_name, + hda_file_name="$HIP/{}.hda".format(subset_name) ) hou.moveNodesTo(self.nodes, hda_node) hda_node.layoutChildren() else: + if self._check_existing(subset_name): + raise plugin.OpenPypeCreatorError( + ("subset {} is already published with different HDA" + "definition.").format(subset_name)) hda_node = to_hda - hda_node.setName(self.name) + hda_node.setName(subset_name) # delete node created by Avalon in /out # this needs to be addressed in future Houdini workflow refactor. - hou.node("/out/{}".format(self.name)).destroy() + hou.node("/out/{}".format(subset_name)).destroy() try: lib.imprint(hda_node, self.data) @@ -89,4 +93,4 @@ class CreateHDA(plugin.Creator): "OpenPype asset.") ) - return hda_node \ No newline at end of file + return hda_node diff --git a/website/docs/artist_hosts_houdini.md b/website/docs/artist_hosts_houdini.md index d2aadf05cb..bd422b046e 100644 --- a/website/docs/artist_hosts_houdini.md +++ b/website/docs/artist_hosts_houdini.md @@ -76,3 +76,28 @@ I've selected `vdb1` and went **OpenPype -> Create** and selected **VDB Cache**. geometry ROP in `/out` and sets its paths to output vdb files. During the publishing process whole dops are cooked. +## Publishing Houdini Digital Assets (HDA) + +You can publish most of the nodes in Houdini as hda for easy interchange of data between Houdini instances or even +other DCCs with Houdini Engine. + +## Creating HDA + +Simply select nodes you want to include in hda and go **OpenPype -> Create** and select **Houdini digital asset (hda)**. +You can even use already existing hda as a selected node, and it will be published (see below for limitation). + +:::caution HDA Workflow limitations +As long as the hda is of same type - it is created from different nodes but using the same (subset) name, everything +is ok. But once you've published version of hda subset, you cannot change its type. For example, you create hda **Foo** +from *Cube* and *Sphere* - it will create hda subset named `hdaFoo` with the same type. You publish it as version 1. +Then you create version 2 with added *Torus*. Then you create version 3 from the scratch from completely different nodes, +but still using resulting subset name `hdaFoo`. Everything still works as expected. But then you use already +existing hda as a base, for example from different artist. Its type cannot be changed from what it was and so even if +it is named `hdaFoo` it has different type. It could be published, but you would never load it and retain ability to +switch versions between different hda types. +::: + +## Loading HDA + +When you load hda, it will install its type in your hip file and add published version as its definition file. When +you switch version via Scene Manager, it will add its definition and set it as preferred. \ No newline at end of file From ecd485309e6e08cf4c090b46e4378464c5e9ce6c Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 15 Oct 2021 14:31:04 +0200 Subject: [PATCH 633/736] fix hound --- openpype/hosts/houdini/plugins/create/create_hda.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_hda.py b/openpype/hosts/houdini/plugins/create/create_hda.py index 63d235d235..2af1e4a257 100644 --- a/openpype/hosts/houdini/plugins/create/create_hda.py +++ b/openpype/hosts/houdini/plugins/create/create_hda.py @@ -74,8 +74,8 @@ class CreateHDA(plugin.Creator): else: if self._check_existing(subset_name): raise plugin.OpenPypeCreatorError( - ("subset {} is already published with different HDA" - "definition.").format(subset_name)) + ("subset {} is already published with different HDA" + "definition.").format(subset_name)) hda_node = to_hda hda_node.setName(subset_name) @@ -87,7 +87,7 @@ class CreateHDA(plugin.Creator): try: lib.imprint(hda_node, self.data) - except hou.OperationFailed as e: + except hou.OperationFailed: raise plugin.OpenPypeCreatorError( ("Cannot set metadata on asset. Might be that it already is " "OpenPype asset.") From 60eb375b67cb56b6737c43a2971f6f00c1a41bda Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 19 Oct 2021 10:05:58 +0200 Subject: [PATCH 634/736] renamed 'new_publisher' to 'publisher' --- openpype/hosts/testhost/run_publish.py | 2 +- .../tools/{new_publisher => publisher}/__init__.py | 0 openpype/tools/{new_publisher => publisher}/app.py | 0 .../tools/{new_publisher => publisher}/constants.py | 0 .../tools/{new_publisher => publisher}/control.py | 0 .../publish_report_viewer/__init__.py | 0 .../publish_report_viewer/constants.py | 0 .../publish_report_viewer/delegates.py | 0 .../publish_report_viewer/model.py | 0 .../publish_report_viewer/widgets.py | 0 .../publish_report_viewer/window.py | 0 .../widgets/__init__.py | 0 .../widgets/border_label_widget.py | 0 .../widgets/card_view_widgets.py | 0 .../widgets/create_dialog.py | 0 .../{new_publisher => publisher}/widgets/icons.py | 0 .../widgets/images/add.png | Bin .../widgets/images/branch_closed.png | Bin .../widgets/images/branch_open.png | Bin .../widgets/images/change_view.png | Bin .../widgets/images/delete.png | Bin .../widgets/images/play.png | Bin .../widgets/images/refresh.png | Bin .../widgets/images/stop.png | Bin .../widgets/images/thumbnail.png | Bin .../widgets/images/validate.png | Bin .../widgets/images/warning.png | Bin .../widgets/list_view_widgets.py | 0 .../{new_publisher => publisher}/widgets/models.py | 0 .../widgets/publish_widget.py | 0 .../widgets/validations_widget.py | 0 .../{new_publisher => publisher}/widgets/widgets.py | 0 .../tools/{new_publisher => publisher}/window.py | 0 33 files changed, 1 insertion(+), 1 deletion(-) rename openpype/tools/{new_publisher => publisher}/__init__.py (100%) rename openpype/tools/{new_publisher => publisher}/app.py (100%) rename openpype/tools/{new_publisher => publisher}/constants.py (100%) rename openpype/tools/{new_publisher => publisher}/control.py (100%) rename openpype/tools/{new_publisher => publisher}/publish_report_viewer/__init__.py (100%) rename openpype/tools/{new_publisher => publisher}/publish_report_viewer/constants.py (100%) rename openpype/tools/{new_publisher => publisher}/publish_report_viewer/delegates.py (100%) rename openpype/tools/{new_publisher => publisher}/publish_report_viewer/model.py (100%) rename openpype/tools/{new_publisher => publisher}/publish_report_viewer/widgets.py (100%) rename openpype/tools/{new_publisher => publisher}/publish_report_viewer/window.py (100%) rename openpype/tools/{new_publisher => publisher}/widgets/__init__.py (100%) rename openpype/tools/{new_publisher => publisher}/widgets/border_label_widget.py (100%) rename openpype/tools/{new_publisher => publisher}/widgets/card_view_widgets.py (100%) rename openpype/tools/{new_publisher => publisher}/widgets/create_dialog.py (100%) rename openpype/tools/{new_publisher => publisher}/widgets/icons.py (100%) rename openpype/tools/{new_publisher => publisher}/widgets/images/add.png (100%) rename openpype/tools/{new_publisher => publisher}/widgets/images/branch_closed.png (100%) rename openpype/tools/{new_publisher => publisher}/widgets/images/branch_open.png (100%) rename openpype/tools/{new_publisher => publisher}/widgets/images/change_view.png (100%) rename openpype/tools/{new_publisher => publisher}/widgets/images/delete.png (100%) rename openpype/tools/{new_publisher => publisher}/widgets/images/play.png (100%) rename openpype/tools/{new_publisher => publisher}/widgets/images/refresh.png (100%) rename openpype/tools/{new_publisher => publisher}/widgets/images/stop.png (100%) rename openpype/tools/{new_publisher => publisher}/widgets/images/thumbnail.png (100%) rename openpype/tools/{new_publisher => publisher}/widgets/images/validate.png (100%) rename openpype/tools/{new_publisher => publisher}/widgets/images/warning.png (100%) rename openpype/tools/{new_publisher => publisher}/widgets/list_view_widgets.py (100%) rename openpype/tools/{new_publisher => publisher}/widgets/models.py (100%) rename openpype/tools/{new_publisher => publisher}/widgets/publish_widget.py (100%) rename openpype/tools/{new_publisher => publisher}/widgets/validations_widget.py (100%) rename openpype/tools/{new_publisher => publisher}/widgets/widgets.py (100%) rename openpype/tools/{new_publisher => publisher}/window.py (100%) diff --git a/openpype/hosts/testhost/run_publish.py b/openpype/hosts/testhost/run_publish.py index 1bb9c46806..5aa2df75fa 100644 --- a/openpype/hosts/testhost/run_publish.py +++ b/openpype/hosts/testhost/run_publish.py @@ -42,7 +42,7 @@ for path in [ from Qt import QtWidgets, QtCore -from openpype.tools.new_publisher.window import PublisherWindow +from openpype.tools.publisher.window import PublisherWindow def main(): diff --git a/openpype/tools/new_publisher/__init__.py b/openpype/tools/publisher/__init__.py similarity index 100% rename from openpype/tools/new_publisher/__init__.py rename to openpype/tools/publisher/__init__.py diff --git a/openpype/tools/new_publisher/app.py b/openpype/tools/publisher/app.py similarity index 100% rename from openpype/tools/new_publisher/app.py rename to openpype/tools/publisher/app.py diff --git a/openpype/tools/new_publisher/constants.py b/openpype/tools/publisher/constants.py similarity index 100% rename from openpype/tools/new_publisher/constants.py rename to openpype/tools/publisher/constants.py diff --git a/openpype/tools/new_publisher/control.py b/openpype/tools/publisher/control.py similarity index 100% rename from openpype/tools/new_publisher/control.py rename to openpype/tools/publisher/control.py diff --git a/openpype/tools/new_publisher/publish_report_viewer/__init__.py b/openpype/tools/publisher/publish_report_viewer/__init__.py similarity index 100% rename from openpype/tools/new_publisher/publish_report_viewer/__init__.py rename to openpype/tools/publisher/publish_report_viewer/__init__.py diff --git a/openpype/tools/new_publisher/publish_report_viewer/constants.py b/openpype/tools/publisher/publish_report_viewer/constants.py similarity index 100% rename from openpype/tools/new_publisher/publish_report_viewer/constants.py rename to openpype/tools/publisher/publish_report_viewer/constants.py diff --git a/openpype/tools/new_publisher/publish_report_viewer/delegates.py b/openpype/tools/publisher/publish_report_viewer/delegates.py similarity index 100% rename from openpype/tools/new_publisher/publish_report_viewer/delegates.py rename to openpype/tools/publisher/publish_report_viewer/delegates.py diff --git a/openpype/tools/new_publisher/publish_report_viewer/model.py b/openpype/tools/publisher/publish_report_viewer/model.py similarity index 100% rename from openpype/tools/new_publisher/publish_report_viewer/model.py rename to openpype/tools/publisher/publish_report_viewer/model.py diff --git a/openpype/tools/new_publisher/publish_report_viewer/widgets.py b/openpype/tools/publisher/publish_report_viewer/widgets.py similarity index 100% rename from openpype/tools/new_publisher/publish_report_viewer/widgets.py rename to openpype/tools/publisher/publish_report_viewer/widgets.py diff --git a/openpype/tools/new_publisher/publish_report_viewer/window.py b/openpype/tools/publisher/publish_report_viewer/window.py similarity index 100% rename from openpype/tools/new_publisher/publish_report_viewer/window.py rename to openpype/tools/publisher/publish_report_viewer/window.py diff --git a/openpype/tools/new_publisher/widgets/__init__.py b/openpype/tools/publisher/widgets/__init__.py similarity index 100% rename from openpype/tools/new_publisher/widgets/__init__.py rename to openpype/tools/publisher/widgets/__init__.py diff --git a/openpype/tools/new_publisher/widgets/border_label_widget.py b/openpype/tools/publisher/widgets/border_label_widget.py similarity index 100% rename from openpype/tools/new_publisher/widgets/border_label_widget.py rename to openpype/tools/publisher/widgets/border_label_widget.py diff --git a/openpype/tools/new_publisher/widgets/card_view_widgets.py b/openpype/tools/publisher/widgets/card_view_widgets.py similarity index 100% rename from openpype/tools/new_publisher/widgets/card_view_widgets.py rename to openpype/tools/publisher/widgets/card_view_widgets.py diff --git a/openpype/tools/new_publisher/widgets/create_dialog.py b/openpype/tools/publisher/widgets/create_dialog.py similarity index 100% rename from openpype/tools/new_publisher/widgets/create_dialog.py rename to openpype/tools/publisher/widgets/create_dialog.py diff --git a/openpype/tools/new_publisher/widgets/icons.py b/openpype/tools/publisher/widgets/icons.py similarity index 100% rename from openpype/tools/new_publisher/widgets/icons.py rename to openpype/tools/publisher/widgets/icons.py diff --git a/openpype/tools/new_publisher/widgets/images/add.png b/openpype/tools/publisher/widgets/images/add.png similarity index 100% rename from openpype/tools/new_publisher/widgets/images/add.png rename to openpype/tools/publisher/widgets/images/add.png diff --git a/openpype/tools/new_publisher/widgets/images/branch_closed.png b/openpype/tools/publisher/widgets/images/branch_closed.png similarity index 100% rename from openpype/tools/new_publisher/widgets/images/branch_closed.png rename to openpype/tools/publisher/widgets/images/branch_closed.png diff --git a/openpype/tools/new_publisher/widgets/images/branch_open.png b/openpype/tools/publisher/widgets/images/branch_open.png similarity index 100% rename from openpype/tools/new_publisher/widgets/images/branch_open.png rename to openpype/tools/publisher/widgets/images/branch_open.png diff --git a/openpype/tools/new_publisher/widgets/images/change_view.png b/openpype/tools/publisher/widgets/images/change_view.png similarity index 100% rename from openpype/tools/new_publisher/widgets/images/change_view.png rename to openpype/tools/publisher/widgets/images/change_view.png diff --git a/openpype/tools/new_publisher/widgets/images/delete.png b/openpype/tools/publisher/widgets/images/delete.png similarity index 100% rename from openpype/tools/new_publisher/widgets/images/delete.png rename to openpype/tools/publisher/widgets/images/delete.png diff --git a/openpype/tools/new_publisher/widgets/images/play.png b/openpype/tools/publisher/widgets/images/play.png similarity index 100% rename from openpype/tools/new_publisher/widgets/images/play.png rename to openpype/tools/publisher/widgets/images/play.png diff --git a/openpype/tools/new_publisher/widgets/images/refresh.png b/openpype/tools/publisher/widgets/images/refresh.png similarity index 100% rename from openpype/tools/new_publisher/widgets/images/refresh.png rename to openpype/tools/publisher/widgets/images/refresh.png diff --git a/openpype/tools/new_publisher/widgets/images/stop.png b/openpype/tools/publisher/widgets/images/stop.png similarity index 100% rename from openpype/tools/new_publisher/widgets/images/stop.png rename to openpype/tools/publisher/widgets/images/stop.png diff --git a/openpype/tools/new_publisher/widgets/images/thumbnail.png b/openpype/tools/publisher/widgets/images/thumbnail.png similarity index 100% rename from openpype/tools/new_publisher/widgets/images/thumbnail.png rename to openpype/tools/publisher/widgets/images/thumbnail.png diff --git a/openpype/tools/new_publisher/widgets/images/validate.png b/openpype/tools/publisher/widgets/images/validate.png similarity index 100% rename from openpype/tools/new_publisher/widgets/images/validate.png rename to openpype/tools/publisher/widgets/images/validate.png diff --git a/openpype/tools/new_publisher/widgets/images/warning.png b/openpype/tools/publisher/widgets/images/warning.png similarity index 100% rename from openpype/tools/new_publisher/widgets/images/warning.png rename to openpype/tools/publisher/widgets/images/warning.png diff --git a/openpype/tools/new_publisher/widgets/list_view_widgets.py b/openpype/tools/publisher/widgets/list_view_widgets.py similarity index 100% rename from openpype/tools/new_publisher/widgets/list_view_widgets.py rename to openpype/tools/publisher/widgets/list_view_widgets.py diff --git a/openpype/tools/new_publisher/widgets/models.py b/openpype/tools/publisher/widgets/models.py similarity index 100% rename from openpype/tools/new_publisher/widgets/models.py rename to openpype/tools/publisher/widgets/models.py diff --git a/openpype/tools/new_publisher/widgets/publish_widget.py b/openpype/tools/publisher/widgets/publish_widget.py similarity index 100% rename from openpype/tools/new_publisher/widgets/publish_widget.py rename to openpype/tools/publisher/widgets/publish_widget.py diff --git a/openpype/tools/new_publisher/widgets/validations_widget.py b/openpype/tools/publisher/widgets/validations_widget.py similarity index 100% rename from openpype/tools/new_publisher/widgets/validations_widget.py rename to openpype/tools/publisher/widgets/validations_widget.py diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py similarity index 100% rename from openpype/tools/new_publisher/widgets/widgets.py rename to openpype/tools/publisher/widgets/widgets.py diff --git a/openpype/tools/new_publisher/window.py b/openpype/tools/publisher/window.py similarity index 100% rename from openpype/tools/new_publisher/window.py rename to openpype/tools/publisher/window.py From 169b3d578febd412868f1c01f9c8ba08dbca8fcf Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 19 Oct 2021 10:12:27 +0200 Subject: [PATCH 635/736] reorganized variables --- openpype/hosts/testhost/run_publish.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/testhost/run_publish.py b/openpype/hosts/testhost/run_publish.py index 5aa2df75fa..44860a30e4 100644 --- a/openpype/hosts/testhost/run_publish.py +++ b/openpype/hosts/testhost/run_publish.py @@ -1,6 +1,6 @@ import os import sys -openpype_dir = "" + mongo_url = "" project_name = "" asset_name = "" @@ -9,9 +9,6 @@ ftrack_url = "" ftrack_username = "" ftrack_api_key = "" -host_name = "testhost" -current_file = os.path.abspath(__file__) - def multi_dirname(path, times=1): for _ in range(times): @@ -19,8 +16,12 @@ def multi_dirname(path, times=1): return path +host_name = "testhost" +current_file = os.path.abspath(__file__) +openpype_dir = multi_dirname(current_file, 4) + os.environ["OPENPYPE_MONGO"] = mongo_url -os.environ["OPENPYPE_ROOT"] = multi_dirname(current_file, 4) +os.environ["OPENPYPE_ROOT"] = openpype_dir os.environ["AVALON_MONGO"] = mongo_url os.environ["AVALON_PROJECT"] = project_name os.environ["AVALON_ASSET"] = asset_name From 0150397174cfe65dfcaefe826834af19e0235908 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 19 Oct 2021 10:13:23 +0200 Subject: [PATCH 636/736] removed unused import --- openpype/hosts/testhost/plugins/publish/collect_context.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/testhost/plugins/publish/collect_context.py b/openpype/hosts/testhost/plugins/publish/collect_context.py index 5aff76a42d..0ab98fb84b 100644 --- a/openpype/hosts/testhost/plugins/publish/collect_context.py +++ b/openpype/hosts/testhost/plugins/publish/collect_context.py @@ -1,5 +1,4 @@ import pyblish.api -from avalon import io from openpype.pipeline import ( OpenPypePyblishPluginMixin, From 715f6f42d668e552e9c0debcd13be74b50704960 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 19 Oct 2021 11:29:25 +0200 Subject: [PATCH 637/736] added import of window to init --- openpype/tools/publisher/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/tools/publisher/__init__.py b/openpype/tools/publisher/__init__.py index 2d81de8fee..a7b597eece 100644 --- a/openpype/tools/publisher/__init__.py +++ b/openpype/tools/publisher/__init__.py @@ -1,5 +1,7 @@ from .app import show +from .window import PublisherWindow __all__ = ( "show", + "PublisherWindow" ) From 0188febe4f17066249d34f18c020deafd869f64a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 19 Oct 2021 13:01:28 +0200 Subject: [PATCH 638/736] PYPE-1901 - added check that no other batch is currently processed PS cannot be run twice on a host machine --- openpype/lib/remote_publish.py | 49 +++++++++++++++++++++++++++++++ openpype/pype_commands.py | 53 ++++++++++++++++++++-------------- 2 files changed, 81 insertions(+), 21 deletions(-) diff --git a/openpype/lib/remote_publish.py b/openpype/lib/remote_publish.py index 4946e1bd53..51007cfad2 100644 --- a/openpype/lib/remote_publish.py +++ b/openpype/lib/remote_publish.py @@ -100,6 +100,55 @@ def publish_and_log(dbcon, _id, log, close_plugin_name=None): ) +def fail_batch(_id, batches_in_progress, dbcon): + """Set current batch as failed as there are some stuck batches.""" + running_batches = [str(batch["_id"]) + for batch in batches_in_progress + if batch["_id"] != _id] + msg = "There are still running batches {}\n". \ + format("\n".join(running_batches)) + msg += "Ask admin to check them and reprocess current batch" + dbcon.update_one( + {"_id": _id}, + {"$set": + { + "finish_date": datetime.now(), + "status": "error", + "log": msg + + }} + ) + raise ValueError(msg) + + +def find_variant_key(application_manager, host): + """Searches for latest installed variant for 'host' + + Args: + application_manager (ApplicationManager) + host (str) + Returns + (string) (optional) + Raises: + (ValueError) if no variant found + """ + app_group = application_manager.app_groups.get(host) + if not app_group or not app_group.enabled: + raise ValueError("No application {} configured".format(host)) + + found_variant_key = None + # finds most up-to-date variant if any installed + for variant_key, variant in app_group.variants.items(): + for executable in variant.executables: + if executable.exists(): + found_variant_key = variant_key + + if not found_variant_key: + raise ValueError("No executable for {} found".format(host)) + + return found_variant_key + + def _get_close_plugin(close_plugin_name, log): if close_plugin_name: plugins = pyblish.api.discover() diff --git a/openpype/pype_commands.py b/openpype/pype_commands.py index e2869a956d..3071629ee5 100644 --- a/openpype/pype_commands.py +++ b/openpype/pype_commands.py @@ -3,7 +3,6 @@ import os import sys import json -from datetime import datetime import time from openpype.lib import PypeLogger @@ -12,7 +11,9 @@ from openpype.lib.plugin_tools import parse_json, get_batch_asset_task_info from openpype.lib.remote_publish import ( get_webpublish_conn, start_webpublish_log, - publish_and_log + publish_and_log, + fail_batch, + find_variant_key ) @@ -125,10 +126,17 @@ class PypeCommands: wants to process uploaded .psd file and publish collected layers from there. + Checks if no other batches are running (status =='in_progress). If + so, it sleeps for SLEEP (this is separate process), + waits for WAIT_FOR seconds altogether. + Requires installed host application on the machine. Runs publish process as user would, in automatic fashion. """ + SLEEP = 5 # seconds for another loop check for concurrently runs + WAIT_FOR = 300 # seconds to wait for conc. runs + from openpype import install, uninstall from openpype.api import Logger @@ -141,25 +149,12 @@ class PypeCommands: from openpype.lib import ApplicationManager application_manager = ApplicationManager() - app_group = application_manager.app_groups.get(host) - if not app_group or not app_group.enabled: - raise ValueError("No application {} configured".format(host)) - - found_variant_key = None - # finds most up-to-date variant if any installed - for variant_key, variant in app_group.variants.items(): - for executable in variant.executables: - if executable.exists(): - found_variant_key = variant_key - - if not found_variant_key: - raise ValueError("No executable for {} found".format(host)) + found_variant_key = find_variant_key(application_manager, host) app_name = "{}/{}".format(host, found_variant_key) batch_data = None if batch_dir and os.path.exists(batch_dir): - # TODO check if batch manifest is same as tasks manifests batch_data = parse_json(os.path.join(batch_dir, "manifest.json")) if not batch_data: @@ -174,6 +169,27 @@ class PypeCommands: batch_data["files"][0]) print("workfile_path {}".format(workfile_path)) + _, batch_id = os.path.split(batch_dir) + dbcon = get_webpublish_conn() + # safer to start logging here, launch might be broken altogether + _id = start_webpublish_log(dbcon, batch_id, user) + + in_progress = True + slept_times = 0 + while in_progress: + batches_in_progress = list(dbcon.find({ + "status": "in_progress" + })) + if len(batches_in_progress) > 1: + if slept_times * SLEEP >= WAIT_FOR: + fail_batch(_id, batches_in_progress, dbcon) + + print("Another batch running, sleeping for a bit") + time.sleep(SLEEP) + slept_times += 1 + else: + in_progress = False + # must have for proper launch of app env = get_app_environments_for_context( project, @@ -183,11 +199,6 @@ class PypeCommands: ) os.environ.update(env) - _, batch_id = os.path.split(batch_dir) - dbcon = get_webpublish_conn() - # safer to start logging here, launch might be broken altogether - _id = start_webpublish_log(dbcon, batch_id, user) - os.environ["OPENPYPE_PUBLISH_DATA"] = batch_dir os.environ["IS_HEADLESS"] = "true" # must pass identifier to update log lines for a batch From 4e000a6cb425a56abd14a9d406f13099e8da3898 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 19 Oct 2021 13:22:58 +0200 Subject: [PATCH 639/736] PYPE-1901 - added proper selection of host based on studio_processing --- .../webserver_service/webpublish_routes.py | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py index 0014d1b344..3f67038fbf 100644 --- a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py +++ b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py @@ -175,6 +175,9 @@ class TaskNode(Node): class WebpublisherBatchPublishEndpoint(_RestApiEndpoint): """Triggers headless publishing of batch.""" async def post(self, request) -> Response: + # for postprocessing in host, currently only PS + host_map = {"photoshop": [".psd", ".psb"]} + output = {} log.info("WebpublisherBatchPublishEndpoint called") content = await request.json() @@ -182,10 +185,28 @@ class WebpublisherBatchPublishEndpoint(_RestApiEndpoint): batch_path = os.path.join(self.resource.upload_dir, content["batch"]) + add_args = { + "host": "webpublisher", + "project": content["project_name"], + "user": content["user"] + } + + command = "remotepublish" + + if content.get("studio_processing"): + log.info("Post processing called") + command = "remotepublishfromapp" + for host, extensions in host_map.items(): + for ext in extensions: + for file_name in content.get("files", []): + if ext in file_name: + add_args["host"] = host + break + openpype_app = self.resource.executable args = [ openpype_app, - 'remotepublish', + command, batch_path ] @@ -193,12 +214,6 @@ class WebpublisherBatchPublishEndpoint(_RestApiEndpoint): msg = "Non existent OpenPype executable {}".format(openpype_app) raise RuntimeError(msg) - add_args = { - "host": "webpublisher", - "project": content["project_name"], - "user": content["user"] - } - for key, value in add_args.items(): args.append("--{}".format(key)) args.append(value) From 50aaaec4daafa266fb4517a2420d2daa4a7640c4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 20 Oct 2021 10:55:06 +0200 Subject: [PATCH 640/736] added publisher to experimental tools --- .../tools/experimental_tools/tools_def.py | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/openpype/tools/experimental_tools/tools_def.py b/openpype/tools/experimental_tools/tools_def.py index 353ec8e1d5..db1d8e87d7 100644 --- a/openpype/tools/experimental_tools/tools_def.py +++ b/openpype/tools/experimental_tools/tools_def.py @@ -50,7 +50,14 @@ class ExperimentalTools: """ def __init__(self, parent=None, host_name=None, filter_hosts=None): - experimental_tools = [] + experimental_tools = [ + ExperimentalTool( + "publisher", + "New publisher", + self._show_publisher, + "Combined creation and publishing into one tool." + ) + ] if filter_hosts is None: filter_hosts = host_name is not None @@ -71,6 +78,8 @@ class ExperimentalTools: self.tools = experimental_tools self._parent_widget = parent + self._publisher_tool = None + def refresh_availability(self): local_settings = get_local_settings() experimental_settings = ( @@ -80,3 +89,13 @@ class ExperimentalTools: for identifier, eperimental_tool in self.tools_by_identifier.items(): enabled = experimental_settings.get(identifier, False) eperimental_tool.set_enabled(enabled) + + def _show_publisher(self): + if self._publisher_tool is None: + from openpype.tools import publisher + + self._publisher_tool = publisher.PublisherWindow( + parent=self._parent_widget + ) + + self._publisher_tool.show() From 34b74ee7f9dec4cd34c07246828dcb750eceffdc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 20 Oct 2021 11:05:36 +0200 Subject: [PATCH 641/736] validate tool identifier keys --- openpype/tools/experimental_tools/tools_def.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/openpype/tools/experimental_tools/tools_def.py b/openpype/tools/experimental_tools/tools_def.py index db1d8e87d7..f3f510e5a3 100644 --- a/openpype/tools/experimental_tools/tools_def.py +++ b/openpype/tools/experimental_tools/tools_def.py @@ -71,10 +71,16 @@ class ExperimentalTools: if tool.is_available_for_host(host_name) ] - self.tools_by_identifier = { - tool.identifier: tool - for tool in experimental_tools - } + # Store tools by identifier + tools_by_identifier = {} + for tool in experimental_tools: + if tool.identifier in tools_by_identifier: + raise KeyError(( + "Duplicated experimental tool identifier \"{}\"" + ).format(tool.identifier)) + tools_by_identifier[tool.identifier] = tool + + self.tools_by_identifier = tools_by_identifier self.tools = experimental_tools self._parent_widget = parent From d3862772ced3ab7e23a0a34e0a53d88b614ff269 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 20 Oct 2021 11:05:46 +0200 Subject: [PATCH 642/736] fix 80char line --- openpype/tools/experimental_tools/tools_def.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/tools/experimental_tools/tools_def.py b/openpype/tools/experimental_tools/tools_def.py index f3f510e5a3..91a9bebb8e 100644 --- a/openpype/tools/experimental_tools/tools_def.py +++ b/openpype/tools/experimental_tools/tools_def.py @@ -15,7 +15,9 @@ class ExperimentalTool: hosts_filter (list): List of host names for which is tool available. Some tools may not be available in all hosts. """ - def __init__(self, identifier, label, callback, tooltip, hosts_filter=None): + def __init__( + self, identifier, label, callback, tooltip, hosts_filter=None + ): self.identifier = identifier self.label = label self.callback = callback From baab0d73aed32e4f963651488dea594842ebeb62 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 20 Oct 2021 11:06:21 +0200 Subject: [PATCH 643/736] get host name from environment if not passed --- openpype/tools/experimental_tools/tools_def.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/tools/experimental_tools/tools_def.py b/openpype/tools/experimental_tools/tools_def.py index 91a9bebb8e..e2f066687f 100644 --- a/openpype/tools/experimental_tools/tools_def.py +++ b/openpype/tools/experimental_tools/tools_def.py @@ -1,3 +1,4 @@ +import os from openpype.settings import get_local_settings # Constant key under which local settings are stored @@ -60,6 +61,9 @@ class ExperimentalTools: "Combined creation and publishing into one tool." ) ] + if not host_name: + host_name = os.environ.get("AVALON_APP") + if filter_hosts is None: filter_hosts = host_name is not None From e57629dbbcabd504738cf0d50b3df53c97db6e58 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 20 Oct 2021 11:06:29 +0200 Subject: [PATCH 644/736] added few docstrings --- openpype/tools/experimental_tools/tools_def.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/openpype/tools/experimental_tools/tools_def.py b/openpype/tools/experimental_tools/tools_def.py index e2f066687f..ef6b004b50 100644 --- a/openpype/tools/experimental_tools/tools_def.py +++ b/openpype/tools/experimental_tools/tools_def.py @@ -51,8 +51,16 @@ class ExperimentalTools: To add/remove experimental tool just add/remove tool to `experimental_tools` variable in __init__ function. + Args: + parent (QtWidgets.QWidget): Parent widget for tools. + host_name (str): Name of host in which context we're now. Environment + value 'AVALON_APP' is used when not passed. + filter_hosts (bool): Should filter tools. By default is set to 'True' + when 'host_name' is passed. Is always set to 'False' if 'host_name' + is not defined. """ def __init__(self, parent=None, host_name=None, filter_hosts=None): + # Definition of experimental tools experimental_tools = [ ExperimentalTool( "publisher", @@ -61,15 +69,18 @@ class ExperimentalTools: "Combined creation and publishing into one tool." ) ] + # Try to get host name from env variable `AVALON_APP` if not host_name: host_name = os.environ.get("AVALON_APP") + # Decide if filtering by host name should happen if filter_hosts is None: filter_hosts = host_name is not None if filter_hosts and not host_name: filter_hosts = False + # Filter tools by host name if filter_hosts: experimental_tools = [ tool From fe2c583fc343c4ae2767e663a55f8fb84339d701 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 20 Oct 2021 11:18:13 +0200 Subject: [PATCH 645/736] use OpenPype stylesheet in experimental dialog --- openpype/tools/experimental_tools/dialog.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/tools/experimental_tools/dialog.py b/openpype/tools/experimental_tools/dialog.py index 237052c055..a611416efc 100644 --- a/openpype/tools/experimental_tools/dialog.py +++ b/openpype/tools/experimental_tools/dialog.py @@ -29,6 +29,7 @@ class ExperimentalDialog(QtWidgets.QDialog): self.setWindowTitle("OpenPype Experimental tools") icon = QtGui.QIcon(app_icon_path()) self.setWindowIcon(icon) + self.setStyleSheet(load_stylesheet()) empty_widget = QtWidgets.QWidget(self) From e5e502502d35c6f692224bbb32564362d383634e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 20 Oct 2021 13:28:12 +0200 Subject: [PATCH 646/736] PYPE-1901 - updated parsing of host Payload doesn't contain all necessary data, manifests must be parsed --- .../webserver_service/webpublish_routes.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py index 3f67038fbf..920ed042dc 100644 --- a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py +++ b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py @@ -11,6 +11,7 @@ from avalon.api import AvalonMongoDB from openpype.lib import OpenPypeMongoConnection from openpype_modules.avalon_apps.rest_api import _RestApiEndpoint +from openpype.lib.plugin_tools import parse_json from openpype.lib import PypeLogger @@ -195,14 +196,30 @@ class WebpublisherBatchPublishEndpoint(_RestApiEndpoint): if content.get("studio_processing"): log.info("Post processing called") + + batch_data = parse_json(os.path.join(batch_path, "manifest.json")) + if not batch_data: + raise ValueError( + "Cannot parse batch meta in {} folder".format(batch_path)) + task_dir_name = batch_data["tasks"][0] + task_data = parse_json(os.path.join(batch_path, task_dir_name, + "manifest.json")) + if not task_data: + raise ValueError( + "Cannot parse batch meta in {} folder".format(task_data)) + command = "remotepublishfromapp" for host, extensions in host_map.items(): for ext in extensions: - for file_name in content.get("files", []): + for file_name in task_data["files"]: if ext in file_name: add_args["host"] = host break + if not add_args.get("host"): + raise ValueError( + "Couldn't discern host from {}".format(task_data["files"])) + openpype_app = self.resource.executable args = [ openpype_app, From 524844f98b78abf3d78cfc65526c8d124c1e3f6a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 20 Oct 2021 13:28:43 +0200 Subject: [PATCH 647/736] PYPE-1901 - updated parsing of workfile Payload doesn't contain all necessary data, manifests must be parsed --- openpype/pype_commands.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/openpype/pype_commands.py b/openpype/pype_commands.py index 3071629ee5..4bef3b7a15 100644 --- a/openpype/pype_commands.py +++ b/openpype/pype_commands.py @@ -164,9 +164,15 @@ class PypeCommands: asset, task_name, _task_type = get_batch_asset_task_info( batch_data["context"]) + # processing from app expects JUST ONE task in batch and 1 workfile + task_dir_name = batch_data["tasks"][0] + task_data = parse_json(os.path.join(batch_dir, task_dir_name, + "manifest.json")) + workfile_path = os.path.join(batch_dir, - batch_data["task"], - batch_data["files"][0]) + task_dir_name, + task_data["files"][0]) + print("workfile_path {}".format(workfile_path)) _, batch_id = os.path.split(batch_dir) From 706fd31e7abc84073f60bfeb47222ab1d5fa8ea7 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 21 Oct 2021 14:08:30 +0200 Subject: [PATCH 648/736] PYPE-1905 - renamed and fixed script for creating dummy records for performance testing --- ...go_performance.py => mongo_performance.py} | 45 ++++++++----------- 1 file changed, 18 insertions(+), 27 deletions(-) rename openpype/tests/{test_mongo_performance.py => mongo_performance.py} (82%) diff --git a/openpype/tests/test_mongo_performance.py b/openpype/tests/mongo_performance.py similarity index 82% rename from openpype/tests/test_mongo_performance.py rename to openpype/tests/mongo_performance.py index cd606d6483..1090cc5e82 100644 --- a/openpype/tests/test_mongo_performance.py +++ b/openpype/tests/mongo_performance.py @@ -80,7 +80,7 @@ class TestPerformance(): file_id3 = bson.objectid.ObjectId() self.inserted_ids.extend([file_id, file_id2, file_id3]) - version_str = "v{0:03}".format(i + 1) + version_str = "v{:03d}".format(i + 1) file_name = "test_Cylinder_workfileLookdev_{}.mb".\ format(version_str) @@ -95,7 +95,7 @@ class TestPerformance(): "family": "workfile", "hierarchy": "Assets", "project": {"code": "test", "name": "Test"}, - "version": 1, + "version": i + 1, "asset": "Cylinder", "representation": "mb", "root": self.ROOT_DIR @@ -104,8 +104,8 @@ class TestPerformance(): "name": "mb", "parent": {"oid": '{}'.format(id)}, "data": { - "path": "C:\\projects\\Test\\Assets\\Cylinder\\publish\\workfile\\workfileLookdev\\{}\\{}".format(version_str, file_name), - "template": "{root}\\{project[name]}\\{hierarchy}\\{asset}\\publish\\{family}\\{subset}\\v{version:0>3}\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}>.{representation}" + "path": "C:\\projects\\test_performance\\Assets\\Cylinder\\publish\\workfile\\workfileLookdev\\{}\\{}".format(version_str, file_name), # noqa + "template": "{root[work]}\\{project[name]}\\{hierarchy}\\{asset}\\publish\\{family}\\{subset}\\v{version:0>3}\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}>.{representation}" # noqa }, "type": "representation", "schema": "openpype:representation-2.0" @@ -188,30 +188,21 @@ class TestPerformance(): create_files=False): ret = [ { - "path": "{root}" + "/Test/Assets/Cylinder/publish/workfile/" + - "workfileLookdev/v{0:03}/" + - "test_Cylinder_A_workfileLookdev_v{0:03}.dat" - .format(i, i), + "path": "{root[work]}" + "/test_performance/Assets/Cylinder/publish/workfile/workfileLookdev/v{:03d}/test_Cylinder_A_workfileLookdev_v{:03d}.dat".format(i, i), #noqa "_id": '{}'.format(file_id), "hash": "temphash", "sites": self.get_sites(self.MAX_NUMBER_OF_SITES), "size": random.randint(0, self.MAX_FILE_SIZE_B) }, { - "path": "{root}" + "/Test/Assets/Cylinder/publish/workfile/" + - "workfileLookdev/v{0:03}/" + - "test_Cylinder_B_workfileLookdev_v{0:03}.dat" - .format(i, i), + "path": "{root[work]}" + "/test_performance/Assets/Cylinder/publish/workfile/workfileLookdev/v{:03d}/test_Cylinder_B_workfileLookdev_v{:03d}.dat".format(i, i), #noqa "_id": '{}'.format(file_id2), "hash": "temphash", "sites": self.get_sites(self.MAX_NUMBER_OF_SITES), "size": random.randint(0, self.MAX_FILE_SIZE_B) }, { - "path": "{root}" + "/Test/Assets/Cylinder/publish/workfile/" + - "workfileLookdev/v{0:03}/" + - "test_Cylinder_C_workfileLookdev_v{0:03}.dat" - .format(i, i), + "path": "{root[work]}" + "/test_performance/Assets/Cylinder/publish/workfile/workfileLookdev/v{:03d}/test_Cylinder_C_workfileLookdev_v{:03d}.dat".format(i, i), #noqa "_id": '{}'.format(file_id3), "hash": "temphash", "sites": self.get_sites(self.MAX_NUMBER_OF_SITES), @@ -221,7 +212,7 @@ class TestPerformance(): ] if create_files: for f in ret: - path = f.get("path").replace("{root}", self.ROOT_DIR) + path = f.get("path").replace("{root[work]}", self.ROOT_DIR) os.makedirs(os.path.dirname(path), exist_ok=True) with open(path, 'wb') as fp: fp.write(os.urandom(f.get("size"))) @@ -231,26 +222,26 @@ class TestPerformance(): def get_files_doc(self, i, file_id, file_id2, file_id3): ret = {} ret['{}'.format(file_id)] = { - "path": "{root}" + - "/Test/Assets/Cylinder/publish/workfile/workfileLookdev/" - "v001/test_CylinderA_workfileLookdev_v{0:03}.mb".format(i), + "path": "{root[work]}" + + "/test_performance/Assets/Cylinder/publish/workfile/workfileLookdev/" + "v{:03d}/test_CylinderA_workfileLookdev_v{:03d}.mb".format(i, i), # noqa "hash": "temphash", "sites": ["studio"], "size": 87236 } ret['{}'.format(file_id2)] = { - "path": "{root}" + - "/Test/Assets/Cylinder/publish/workfile/workfileLookdev/" - "v001/test_CylinderB_workfileLookdev_v{0:03}.mb".format(i), + "path": "{root[work]}" + + "/test_performance/Assets/Cylinder/publish/workfile/workfileLookdev/" + "v{:03d}/test_CylinderB_workfileLookdev_v{:03d}.mb".format(i, i), # noqa "hash": "temphash", "sites": ["studio"], "size": 87236 } ret['{}'.format(file_id3)] = { - "path": "{root}" + - "/Test/Assets/Cylinder/publish/workfile/workfileLookdev/" - "v001/test_CylinderC_workfileLookdev_v{0:03}.mb".format(i), + "path": "{root[work]}" + + "/test_performance/Assets/Cylinder/publish/workfile/workfileLookdev/" + "v{:03d}/test_CylinderC_workfileLookdev_v{:03d}.mb".format(i, i), # noqa "hash": "temphash", "sites": ["studio"], "size": 87236 @@ -287,7 +278,7 @@ class TestPerformance(): if __name__ == '__main__': tp = TestPerformance('array') - tp.prepare(no_of_records=10, create_files=True) # enable to prepare data + tp.prepare(no_of_records=10000, create_files=True) # enable to prepare data # tp.run(10, 3) # print('-'*50) From a4f710b221f7065835246c04a4594e5dacddf825 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 21 Oct 2021 14:09:07 +0200 Subject: [PATCH 649/736] PYPE-1905 - added better logging for broken path values --- .../sync_server/providers/abstract_provider.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/modules/default_modules/sync_server/providers/abstract_provider.py b/openpype/modules/default_modules/sync_server/providers/abstract_provider.py index 8ae0ceed79..688a17f14f 100644 --- a/openpype/modules/default_modules/sync_server/providers/abstract_provider.py +++ b/openpype/modules/default_modules/sync_server/providers/abstract_provider.py @@ -201,5 +201,9 @@ class AbstractProvider: msg = "Error in resolving local root from anatomy" log.error(msg) raise ValueError(msg) + except IndexError: + msg = "Path {} contains unfillable placeholder" + log.error(msg) + raise ValueError(msg) return path From 22e8886fd1f0db96e06feaaafd8280e7e930bfb0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 22 Oct 2021 15:25:13 +0200 Subject: [PATCH 650/736] added few docstrings --- .../publisher/widgets/border_label_widget.py | 64 +++++++++++++++---- openpype/tools/publisher/widgets/icons.py | 4 ++ openpype/tools/publisher/widgets/models.py | 55 ++++++++++++++++ openpype/tools/publisher/widgets/widgets.py | 57 ++++++++++++++++- 4 files changed, 164 insertions(+), 16 deletions(-) diff --git a/openpype/tools/publisher/widgets/border_label_widget.py b/openpype/tools/publisher/widgets/border_label_widget.py index d30ce67140..6f476fabee 100644 --- a/openpype/tools/publisher/widgets/border_label_widget.py +++ b/openpype/tools/publisher/widgets/border_label_widget.py @@ -4,6 +4,14 @@ from openpype.style import get_objected_colors class _VLineWidget(QtWidgets.QWidget): + """Widget drawing 1px wide vertical line. + + ``` β”‚ ``` + + Line is drawn in the middle of widget. + + It is expected that parent widget will set width. + """ def __init__(self, color, left, parent): super(_VLineWidget, self).__init__(parent) self._color = color @@ -33,9 +41,21 @@ class _VLineWidget(QtWidgets.QWidget): painter.end() -class _HLineWidget(QtWidgets.QWidget): +class _HBottomLineWidget(QtWidgets.QWidget): + """Widget drawing 1px wide vertical line with side lines going upwards. + + ```β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜``` + + Corners may have curve set by radius (`set_radius`). Radius should expect + height of widget. + + Bottom line is drawed at the bottom of widget. If radius is 0 then height + of widget should be 1px. + + It is expected that parent widget will set height and radius. + """ def __init__(self, color, parent): - super(_HLineWidget, self).__init__(parent) + super(_HBottomLineWidget, self).__init__(parent) self._color = color self._radius = 0 @@ -65,9 +85,23 @@ class _HLineWidget(QtWidgets.QWidget): painter.end() -class _HCornerLineWidget(QtWidgets.QWidget): +class _HTopCornerLineWidget(QtWidgets.QWidget): + """Widget drawing 1px wide horizontal line with side line going downwards. + + ```β”Œβ”€β”€β”€β”€β”€β”€β”€``` + or + ```────────┐``` + + Horizontal line is drawed in the middle of widget. + + Widget represents left or right corner. Corner may have curve set by + radius (`set_radius`). Radius should expect height of widget (maximum half + height of widget). + + It is expected that parent widget will set height and radius. + """ def __init__(self, color, left_side, parent): - super(_HCornerLineWidget, self).__init__(parent) + super(_HTopCornerLineWidget, self).__init__(parent) self._left_side = left_side self._color = color self._radius = 0 @@ -112,13 +146,13 @@ class _HCornerLineWidget(QtWidgets.QWidget): class BorderedLabelWidget(QtWidgets.QFrame): """Draws borders around widget with label in the middle of top. - +------- Label --------+ - | | - | | - | CONTENT | - | | - | | - +----------------------+ + β”Œβ”€β”€β”€β”€β”€β”€β”€ Label ────────┐ + β”‚ β”‚ + β”‚ β”‚ + β”‚ CONTENT β”‚ + β”‚ β”‚ + β”‚ β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ """ def __init__(self, label, parent): super(BorderedLabelWidget, self).__init__(parent) @@ -128,8 +162,8 @@ class BorderedLabelWidget(QtWidgets.QFrame): if color_value: color = color_value.get_qcolor() - top_left_w = _HCornerLineWidget(color, True, self) - top_right_w = _HCornerLineWidget(color, False, self) + top_left_w = _HTopCornerLineWidget(color, True, self) + top_right_w = _HTopCornerLineWidget(color, False, self) label_widget = QtWidgets.QLabel(label, self) @@ -143,7 +177,7 @@ class BorderedLabelWidget(QtWidgets.QFrame): left_w = _VLineWidget(color, True, self) right_w = _VLineWidget(color, False, self) - bottom_w = _HLineWidget(color, self) + bottom_w = _HBottomLineWidget(color, self) center_layout = QtWidgets.QHBoxLayout() center_layout.setContentsMargins(5, 5, 5, 5) @@ -176,6 +210,7 @@ class BorderedLabelWidget(QtWidgets.QFrame): self._center_layout = center_layout def set_content_margins(self, value): + """Set margins around content.""" self._center_layout.setContentsMargins( value, value, value, value ) @@ -204,6 +239,7 @@ class BorderedLabelWidget(QtWidgets.QFrame): self._widget.update() def set_center_widget(self, widget): + """Set content widget and add it to center.""" while self._center_layout.count(): item = self._center_layout.takeAt(0) widget = item.widget() diff --git a/openpype/tools/publisher/widgets/icons.py b/openpype/tools/publisher/widgets/icons.py index b8c9d22af8..fd5c45f901 100644 --- a/openpype/tools/publisher/widgets/icons.py +++ b/openpype/tools/publisher/widgets/icons.py @@ -4,6 +4,7 @@ from Qt import QtGui def get_icon_path(icon_name=None, filename=None): + """Path to image in './images' folder.""" if icon_name is None and filename is None: return None @@ -21,6 +22,7 @@ def get_icon_path(icon_name=None, filename=None): def get_image(icon_name=None, filename=None): + """Load image from './images' as QImage.""" path = get_icon_path(icon_name, filename) if path: return QtGui.QImage(path) @@ -28,6 +30,7 @@ def get_image(icon_name=None, filename=None): def get_pixmap(icon_name=None, filename=None): + """Load image from './images' as QPixmap.""" path = get_icon_path(icon_name, filename) if path: return QtGui.QPixmap(path) @@ -35,6 +38,7 @@ def get_pixmap(icon_name=None, filename=None): def get_icon(icon_name=None, filename=None): + """Load image from './images' as QICon.""" pix = get_pixmap(icon_name, filename) if pix: return QtGui.QIcon(pix) diff --git a/openpype/tools/publisher/widgets/models.py b/openpype/tools/publisher/widgets/models.py index a783751fb0..0cfd771ef1 100644 --- a/openpype/tools/publisher/widgets/models.py +++ b/openpype/tools/publisher/widgets/models.py @@ -5,6 +5,13 @@ from Qt import QtCore, QtGui class AssetsHierarchyModel(QtGui.QStandardItemModel): + """Assets hiearrchy model. + + For selecting asset for which should beinstance created. + + Uses controller to load asset hierarchy. All asset documents are stored by + their parents. + """ def __init__(self, controller): super(AssetsHierarchyModel, self).__init__() self._controller = controller @@ -53,6 +60,19 @@ class AssetsHierarchyModel(QtGui.QStandardItemModel): class TasksModel(QtGui.QStandardItemModel): + """Tasks model. + + Task model must have set context of asset documents. + + Items in model are based on 0-infinite asset documents. Always contain + an interserction of context asset tasks. When no assets are in context + them model is empty if 2 or more are in context assets that don't have + tasks with same names then model is empty too. + + Args: + controller (PublisherController): Controller which handles creation and + publishing. + """ def __init__(self, controller): super(TasksModel, self).__init__() self._controller = controller @@ -61,11 +81,31 @@ class TasksModel(QtGui.QStandardItemModel): self._task_names_by_asset_name = {} def set_asset_names(self, asset_names): + """Set assets context.""" self._asset_names = asset_names self.reset() @staticmethod def get_intersection_of_tasks(task_names_by_asset_name): + """Calculate intersection of task names from passed data. + + Example: + ``` + # Passed `task_names_by_asset_name` + { + "asset_1": ["compositing", "animation"], + "asset_2": ["compositing", "editorial"] + } + ``` + Result: + ``` + # Set + {"compositing"} + ``` + + Args: + task_names_by_asset_name (dict): Task names in iterable by parent. + """ tasks = None for task_names in task_names_by_asset_name.values(): if tasks is None: @@ -78,12 +118,20 @@ class TasksModel(QtGui.QStandardItemModel): return tasks or set() def is_task_name_valid(self, asset_name, task_name): + """Is task name available for asset. + + Args: + asset_name (str): Name of asset where should look for task. + task_name (str): Name of task which should be available in asset's + tasks. + """ task_names = self._task_names_by_asset_name.get(asset_name) if task_names and task_name in task_names: return True return False def reset(self): + """Update model by current context.""" if not self._asset_names: self._items_by_name = {} self._task_names_by_asset_name = {} @@ -120,6 +168,13 @@ class TasksModel(QtGui.QStandardItemModel): class RecursiveSortFilterProxyModel(QtCore.QSortFilterProxyModel): + """Recursive proxy model. + + Item is not filtered if any children match the filter. + + Use case: Filtering by string - parent won't be filtered if does not match + the filter string but first checks if any children does. + """ def filterAcceptsRow(self, row, parent_index): regex = self.filterRegExp() if not regex.isEmpty(): diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index 8415a7a9ca..0940dcda79 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -32,6 +32,7 @@ class PixmapLabel(QtWidgets.QLabel): self._source_pixmap = pixmap def set_source_pixmap(self, pixmap): + """Change source image.""" self._source_pixmap = pixmap self._set_resized_pix() @@ -53,7 +54,7 @@ class PixmapLabel(QtWidgets.QLabel): class TransparentPixmapLabel(QtWidgets.QLabel): - """Label resizing to width and height of font.""" + """Transparent label resizing to width and height of font.""" def __init__(self, *args, **kwargs): super(TransparentPixmapLabel, self).__init__(*args, **kwargs) @@ -67,7 +68,11 @@ class TransparentPixmapLabel(QtWidgets.QLabel): class IconValuePixmapLabel(PixmapLabel): - """Label resizing to width and height of font.""" + """Label resizing to width and height of font. + + Handle icon parsing from creators/instances. Using of QAwesome module + of path to images. + """ fa_prefixes = ["", "fa."] default_size = 200 @@ -77,6 +82,11 @@ class IconValuePixmapLabel(PixmapLabel): super(IconValuePixmapLabel, self).__init__(source_pixmap, parent) def set_icon_def(self, icon_def): + """Set icon by it's definition name. + + Args: + icon_def (str): Name of FontAwesome icon or path to image. + """ source_pixmap = self._parse_icon_def(icon_def) self.set_source_pixmap(source_pixmap) @@ -115,6 +125,7 @@ class IconValuePixmapLabel(PixmapLabel): class ContextWarningLabel(PixmapLabel): + """Pixmap label with warning icon.""" def __init__(self, parent): pix = get_pixmap("warning") @@ -150,6 +161,18 @@ class IconButton(QtWidgets.QPushButton): class PublishIconBtn(IconButton): + """Button using alpha of source image to redraw with different color. + + Main class for buttons showed in publisher. + + TODO: + Add different states: + - normal : before publishing + - publishing : publishing is running + - validation error : validation error happened + - error : other error happened + - success : publishing finished + """ def __init__(self, pixmap_path, *args, **kwargs): super(PublishIconBtn, self).__init__(*args, **kwargs) @@ -164,9 +187,11 @@ class PublishIconBtn(IconButton): self.setIcon(self._enabled_icon) def get_enabled_icon(self): + """Enabled icon.""" return self._enabled_icon def get_disabled_icon(self): + """Disabled icon.""" if self._disabled_icon is None: pixmap = self.paint_image_with_color( self._base_image, QtCore.Qt.gray @@ -176,6 +201,14 @@ class PublishIconBtn(IconButton): @staticmethod def paint_image_with_color(image, color): + """Redraw image with single color using it's alpha. + + It is expected that input image is singlecolor image with alpha. + + Args: + image (QImage): Loaded image with alpha. + color (QColor): Color that will be used to paint image. + """ width = image.width() height = image.height() partition = 8 @@ -212,6 +245,7 @@ class PublishIconBtn(IconButton): class ResetBtn(PublishIconBtn): + """Publish reset button.""" def __init__(self, parent=None): icon_path = get_icon_path("refresh") super(ResetBtn, self).__init__(icon_path, parent) @@ -219,6 +253,7 @@ class ResetBtn(PublishIconBtn): class StopBtn(PublishIconBtn): + """Publish stop button.""" def __init__(self, parent): icon_path = get_icon_path("stop") super(StopBtn, self).__init__(icon_path, parent) @@ -226,6 +261,7 @@ class StopBtn(PublishIconBtn): class ValidateBtn(PublishIconBtn): + """Publish validate button.""" def __init__(self, parent=None): icon_path = get_icon_path("validate") super(ValidateBtn, self).__init__(icon_path, parent) @@ -233,6 +269,7 @@ class ValidateBtn(PublishIconBtn): class PublishBtn(PublishIconBtn): + """Publish start publish button.""" def __init__(self, parent=None): icon_path = get_icon_path("play") super(PublishBtn, self).__init__(icon_path, "Publish", parent) @@ -240,6 +277,7 @@ class PublishBtn(PublishIconBtn): class CreateInstanceBtn(PublishIconBtn): + """Create add button.""" def __init__(self, parent=None): icon_path = get_icon_path("add") super(CreateInstanceBtn, self).__init__(icon_path, parent) @@ -247,6 +285,7 @@ class CreateInstanceBtn(PublishIconBtn): class RemoveInstanceBtn(PublishIconBtn): + """Create remove button.""" def __init__(self, parent=None): icon_path = get_icon_path("delete") super(RemoveInstanceBtn, self).__init__(icon_path, parent) @@ -254,6 +293,7 @@ class RemoveInstanceBtn(PublishIconBtn): class ChangeViewBtn(PublishIconBtn): + """Create toggle view button.""" def __init__(self, parent=None): icon_path = get_icon_path("change_view") super(ChangeViewBtn, self).__init__(icon_path, parent) @@ -261,19 +301,32 @@ class ChangeViewBtn(PublishIconBtn): class AbstractInstanceView(QtWidgets.QWidget): + """Abstract class for instance view in creation part.""" selection_changed = QtCore.Signal() active_changed = QtCore.Signal() + # Refreshed attribute is not changed by view itself + # - widget which triggers `refresh` is changing the state + # TODO store that information in widget which cares about refreshing refreshed = False def set_refreshed(self, refreshed): + """View is refreshed with last instances. + + Views are not updated all the time. Only if are visible. + """ self.refreshed = refreshed def refresh(self): + """Refresh instances in the view from current `CreatedContext`.""" raise NotImplementedError(( "{} Method 'refresh' is not implemented." ).format(self.__class__.__name__)) def get_selected_items(self): + """Selected instances required for callbacks. + + Example: When delete button is clicked to know what should be deleted. + """ raise NotImplementedError(( "{} Method 'get_selected_items' is not implemented." ).format(self.__class__.__name__)) From bee176ecb25efdf3172be2049633e64a8a4d2c5a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 22 Oct 2021 15:25:24 +0200 Subject: [PATCH 651/736] resize and set stylesheet after show --- openpype/tools/publisher/window.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index f6f74224f1..8440a727ca 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -28,6 +28,7 @@ from .widgets import ( class PublisherWindow(QtWidgets.QDialog): + """Main window of publisher.""" default_width = 1000 default_height = 600 @@ -241,14 +242,13 @@ class PublisherWindow(QtWidgets.QDialog): self.creator_window = creator_window - self.setStyleSheet(style.load_stylesheet()) - - self.resize(self.default_width, self.default_height) def showEvent(self, event): super(PublisherWindow, self).showEvent(event) if self._first_show: self._first_show = False + self.resize(self.default_width, self.default_height) + self.setStyleSheet(style.load_stylesheet()) self.reset() def closeEvent(self, event): From dc206f0ce671bbec73f0a8c657391d92b2911eb8 Mon Sep 17 00:00:00 2001 From: karimmozlia Date: Fri, 22 Oct 2021 16:11:40 +0200 Subject: [PATCH 652/736] add menu item --- openpype/hosts/maya/api/lib.py | 46 +++++++++++++++++++++++++++++++++ openpype/hosts/maya/api/menu.py | 27 +++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index b24235447f..ce93b5d701 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -22,6 +22,7 @@ import avalon.maya.lib import avalon.maya.interactive from openpype import lib +from openpype.api import get_anatomy_settings log = logging.getLogger(__name__) @@ -2740,3 +2741,48 @@ def iter_shader_edits(relationships, shader_nodes, nodes_by_id, label=None): "uuid": data["uuid"], "nodes": nodes, "attributes": attr_value} + + +def set_colorspace(): + """Set Colorspace from project configuration + + """ + imageio = get_anatomy_settings(os.getenv("AVALON_PROJECT"))["imageio"]["maya"] + root_dict = imageio["colorManagmentPreference"] + + if not isinstance(root_dict, dict): + msg = "set_colorspace(): argument should be dictionary" + log.error(msg) + + log.debug(">> root_dict: {}".format(root_dict)) + + # first enable color management + cmds.colorManagementPrefs(e=True, cmEnabled=True) + cmds.colorManagementPrefs(e=True, ocioRulesEnabled=True) + + # second set config path + if root_dict.get("configFilePath"): + unresolved_path = root_dict["configFilePath"] + ocio_paths = unresolved_path[platform.system().lower()] + + resolved_path = None + for ocio_p in ocio_paths: + resolved_path = str(ocio_p).format(**os.environ) + if not os.path.exists(resolved_path): + continue + + if resolved_path: + cmds.colorManagementPrefs(e=True, configFilePath= str(resolved_path).replace("\\", "/") ) + cmds.colorManagementPrefs(e=True, cmConfigFileEnabled=True) + + log.debug("maya '{}' changed to: {}".format( + "configFilePath", resolved_path)) + root_dict.pop("configFilePath") + + + # third define rendering space and view transform + renderSpace = root_dict["renderSpace"] + cmds.colorManagementPrefs(e=True, renderingSpaceName=renderSpace) + viewTransform = root_dict["viewTransform"] + cmds.colorManagementPrefs(e=True, viewTransformName=viewTransform) + diff --git a/openpype/hosts/maya/api/menu.py b/openpype/hosts/maya/api/menu.py index d43cdaa3f1..6aebc7e075 100644 --- a/openpype/hosts/maya/api/menu.py +++ b/openpype/hosts/maya/api/menu.py @@ -11,6 +11,7 @@ from avalon.maya import pipeline from openpype.api import BuildWorkfile from openpype.settings import get_project_settings from openpype.tools.utils import host_tools +from openpype.hosts.maya.api import lib log = logging.getLogger(__name__) @@ -125,6 +126,31 @@ def deferred(): if project_manager_action is not None: system_menu.menu().removeAction(project_manager_action) + def add_colorspace(): + # Find the pipeline menu + top_menu = _get_menu() + + # Try to find workfile tool action in the menu + workfile_action = None + for action in top_menu.actions(): + if action.text() == "Reset Resolution": + workfile_action = action + break + + # Add at the top of menu if "Work Files" action was not found + after_action = "" + if workfile_action: + # Use action's object name for `insertAfter` argument + after_action = workfile_action.objectName() + + # Insert action to menu + cmds.menuItem( + "Set Colorspace", + parent=pipeline._menu, + command=lambda *args: lib.set_colorspace(), + insertAfter=after_action + ) + log.info("Attempting to install scripts menu ...") # add_scripts_menu() @@ -132,6 +158,7 @@ def deferred(): add_look_assigner_item() modify_workfiles() remove_project_manager() + add_colorspace() add_scripts_menu() From b8964a0fe147ad465f0a4d10f142a353c443fa09 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 22 Oct 2021 17:36:55 +0200 Subject: [PATCH 653/736] added few docstrings --- .../publisher/widgets/validations_widget.py | 59 ++++++++++++++ openpype/tools/publisher/widgets/widgets.py | 79 ++++++++++++++++++- 2 files changed, 136 insertions(+), 2 deletions(-) diff --git a/openpype/tools/publisher/widgets/validations_widget.py b/openpype/tools/publisher/widgets/validations_widget.py index a940a3f643..0480719d78 100644 --- a/openpype/tools/publisher/widgets/validations_widget.py +++ b/openpype/tools/publisher/widgets/validations_widget.py @@ -12,6 +12,10 @@ from .widgets import ( class ValidationErrorInstanceList(QtWidgets.QListView): + """List of publish instances that caused a validation error. + + Instances are collected per plugin's validation error title. + """ def __init__(self, *args, **kwargs): super(ValidationErrorInstanceList, self).__init__(*args, **kwargs) @@ -33,6 +37,14 @@ class ValidationErrorInstanceList(QtWidgets.QListView): class ValidationErrorTitleWidget(QtWidgets.QWidget): + """Title of validation error. + + Widget is used as radio button so requires clickable functionality and + changing style on selection/deselection. + + Has toggle button to show/hide instances on which validation error happened + if there is a list (Valdation error may happen on context). + """ selected = QtCore.Signal(int) def __init__(self, index, error_info, parent): @@ -109,25 +121,31 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget): self._instances_view = instances_view def _mouse_release_callback(self): + """Mark this widget as selected on click.""" self.set_selected(True) @property def is_selected(self): + """Is widget marked a selected""" return self._selected @property def index(self): + """Widget's index set by parent.""" return self._index def set_index(self, index): + """Set index of widget (called by parent).""" self._index = index def _change_style_property(self, selected): + """Change style of widget based on selection.""" value = "1" if selected else "" self._title_frame.setProperty("selected", value) self._title_frame.style().polish(self._title_frame) def set_selected(self, selected=None): + """Change selected state of widget.""" if selected is None: selected = not self._selected @@ -140,6 +158,7 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget): self.selected.emit(self._index) def _on_toggle_btn_click(self): + """Show/hide instances list.""" new_visible = not self._instances_view.isVisible() self._instances_view.setVisible(new_visible) if new_visible: @@ -149,6 +168,10 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget): class ActionButton(ClickableFrame): + """Plugin's action callback button. + + Action may have label or icon or both. + """ action_clicked = QtCore.Signal(str) def __init__(self, action, parent): @@ -179,6 +202,10 @@ class ActionButton(ClickableFrame): class ValidateActionsWidget(QtWidgets.QFrame): + """Wrapper widget for plugin actions. + + Change actions based on selected validation error. + """ def __init__(self, controller, parent): super(ValidateActionsWidget, self).__init__(parent) @@ -198,6 +225,7 @@ class ValidateActionsWidget(QtWidgets.QFrame): self._actions_mapping = {} def clear(self): + """Remove actions from widget.""" while self._content_layout.count(): item = self._content_layout.takeAt(0) widget = item.widget() @@ -206,6 +234,10 @@ class ValidateActionsWidget(QtWidgets.QFrame): self._actions_mapping = {} def set_plugin(self, plugin): + """Set selected plugin and show it's actions. + + Clears current actions from widget and recreate them from the plugin. + """ self.clear() self._plugin = plugin if not plugin: @@ -238,6 +270,14 @@ class ValidateActionsWidget(QtWidgets.QFrame): class VerticallScrollArea(QtWidgets.QScrollArea): + """Scroll area for validation error titles. + + The biggest difference is that the scroll area has scroll bar on left side + and resize of content will also resize scrollarea itself. + + Resize if deffered by 100ms because at the moment of resize are not yet + propagated sizes and visibility of scroll bars. + """ def __init__(self, *args, **kwargs): super(VerticallScrollArea, self).__init__(*args, **kwargs) @@ -296,6 +336,23 @@ class VerticallScrollArea(QtWidgets.QScrollArea): class ValidationsWidget(QtWidgets.QWidget): + """Widgets showing validation error. + + This widget is shown if validation error/s happened during validation part. + + Shows validation error titles with instances on which happened and + validation error detail with possible actions (repair). + + β”Œβ”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β” + β”‚titlesβ”‚ β”‚actionsβ”‚ + β”‚ β”‚ β”‚ β”‚ + β”‚ β”‚ Error detail β”‚ β”‚ + β”‚ β”‚ β”‚ β”‚ + β”‚ β”‚ β”‚ β”‚ + β”œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€ + β”‚ Publish buttons β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + """ def __init__(self, controller, parent): super(ValidationsWidget, self).__init__(parent) @@ -354,6 +411,7 @@ class ValidationsWidget(QtWidgets.QWidget): self._previous_select = None def clear(self): + """Delete all dynamic widgets and hide all wrappers.""" self._title_widgets = {} self._error_info = {} self._previous_select = None @@ -369,6 +427,7 @@ class ValidationsWidget(QtWidgets.QWidget): self._actions_widget.setVisible(False) def set_errors(self, errors): + """Set errors into context and created titles.""" self.clear() if not errors: return diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index 0940dcda79..1b12fd16ed 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -333,6 +333,10 @@ class AbstractInstanceView(QtWidgets.QWidget): class ClickableFrame(QtWidgets.QFrame): + """Widget that catch left mouse click and can trigger a callback. + + Callback is defined by overriding `_mouse_release_callback`. + """ def __init__(self, parent): super(ClickableFrame, self).__init__(parent) @@ -356,6 +360,7 @@ class ClickableFrame(QtWidgets.QFrame): class AssetsDialog(QtWidgets.QDialog): + """Dialog to select asset for a context of instance.""" def __init__(self, controller, parent): super(AssetsDialog, self).__init__(parent) self.setWindowTitle("Select asset") @@ -407,8 +412,9 @@ class AssetsDialog(QtWidgets.QDialog): self._soft_reset_enabled = True def showEvent(self, event): + """Refresh asset model on show.""" super(AssetsDialog, self).showEvent(event) - + # Refresh on show self.reset(False) def reset(self, force=True): @@ -421,10 +427,13 @@ class AssetsDialog(QtWidgets.QDialog): self._model.reset() def name_is_valid(self, name): + # Make sure we're reset self.reset(False) + # Valid the name by model return self._model.name_is_valid(name) def _on_filter_change(self, text): + """Trigger change of filter of assets.""" self._proxy_model.setFilterFixedString(text) def _on_cancel_clicked(self): @@ -439,6 +448,10 @@ class AssetsDialog(QtWidgets.QDialog): self.done(1) def set_selected_assets(self, asset_names): + """Change preselected asset before showing the dialog. + + This also resets model and clean filter. + """ self.reset(False) self._asset_view.collapseAll() self._filter_input.setText("") @@ -470,10 +483,15 @@ class AssetsDialog(QtWidgets.QDialog): self._asset_view.expand(proxy_index) def get_selected_asset(self): + """Get selected asset name.""" return self._selected_asset class ClickableLineEdit(QtWidgets.QLineEdit): + """QLineEdit capturing left mouse click. + + Triggers `clicked` signal on mouse click. + """ clicked = QtCore.Signal() def __init__(self, *args, **kwargs): @@ -501,6 +519,10 @@ class ClickableLineEdit(QtWidgets.QLineEdit): class AssetsField(ClickableFrame): + """Field where asset name of selected instance/s is showed. + + Click on the field will trigger `AssetsDialog`. + """ value_changed = QtCore.Signal() def __init__(self, controller, parent): @@ -550,6 +572,11 @@ class AssetsField(ClickableFrame): self._dialog.open() def set_multiselection_text(self, text): + """Change text for multiselection of different assets. + + When there are selected multiple instances at once and they don't have + same asset in context. + """ self._multiselection_text = text def _set_is_valid(self, valid): @@ -568,18 +595,33 @@ class AssetsField(ClickableFrame): self._name_input.style().polish(self._name_input) def is_valid(self): + """Is asset valid.""" return self._is_valid def has_value_changed(self): + """Value of asset has changed.""" return self._has_value_changed def get_selected_items(self): + """Selected asset names.""" return list(self._selected_items) def set_text(self, text): + """Set text in text field. + + Does not change selected items (assets). + """ self._name_input.setText(text) def set_selected_items(self, asset_names=None): + """Set asset names for selection of instances. + + Passed asset names are validated and if there are 2 or more different + asset names then multiselection text is shown. + + Args: + asset_names (list, tuple, set, NoneType): List of asset names. + """ if asset_names is None: asset_names = [] @@ -608,16 +650,26 @@ class AssetsField(ClickableFrame): self._set_is_valid(is_valid) def reset_to_origin(self): + """Change to asset names set with last `set_selected_items` call.""" self.set_selected_items(self._origin_value) class TasksCombobox(QtWidgets.QComboBox): + """Combobox to show tasks for selected instances. + + Combobox gives ability to select only from intersection of task names for + asset names in selected instances. + + If asset names in selected instances does not have same tasks then combobox + will be empty. + """ value_changed = QtCore.Signal() def __init__(self, controller, parent): super(TasksCombobox, self).__init__(parent) self.setObjectName("TasksCombobox") + # Set empty delegate to propagate stylesheet to a combobox delegate = QtWidgets.QStyledItemDelegate() self.setItemDelegate(delegate) @@ -639,6 +691,7 @@ class TasksCombobox(QtWidgets.QComboBox): self._text = None def set_multiselection_text(self, text): + """Change text shown when multiple different tasks are in context.""" self._multiselection_text = text def _on_index_change(self): @@ -660,13 +713,18 @@ class TasksCombobox(QtWidgets.QComboBox): self.value_changed.emit() def set_text(self, text): + """Set context shown in combobox without chaning selected items.""" if text == self._text: return self._text = text def paintEvent(self, event): - """Paint custom text without using QLineEdit.""" + """Paint custom text without using QLineEdit. + + The easiest way how to draw custom text in combobox and keep combobox + properties and event handling. + """ painter = QtGui.QPainter(self) painter.setPen(self.palette().color(QtGui.QPalette.Text)) opt = QtWidgets.QStyleOptionComboBox() @@ -682,9 +740,11 @@ class TasksCombobox(QtWidgets.QComboBox): ) def is_valid(self): + """Are all selected items valid.""" return self._is_valid def has_value_changed(self): + """Did selection of task changed.""" return self._has_value_changed def _set_is_valid(self, valid): @@ -703,9 +763,17 @@ class TasksCombobox(QtWidgets.QComboBox): self.style().polish(self) def get_selected_items(self): + """Get selected tasks. + + If value has changed then will return list with single item. + + Returns: + list: Selected tasks. + """ return list(self._selected_items) def set_asset_names(self, asset_names): + """Set asset names for which should show tasks.""" self._ignore_index_change = True self._model.set_asset_names(asset_names) @@ -743,6 +811,12 @@ class TasksCombobox(QtWidgets.QComboBox): self._set_is_valid(is_valid) def set_selected_items(self, asset_task_combinations=None): + """Set items for selected instances. + + Args: + asset_task_combinations (list): List of tuples. Each item in + the list contain asset name and task name. + """ if asset_task_combinations is None: asset_task_combinations = [] @@ -816,6 +890,7 @@ class TasksCombobox(QtWidgets.QComboBox): self.set_text(item_name) def reset_to_origin(self): + """Change to task names set with last `set_selected_items` call.""" self.set_selected_items(self._origin_value) From 64df91537ad1aac6732a84767e0faac908c0f9a5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 22 Oct 2021 17:37:19 +0200 Subject: [PATCH 654/736] added docstrings to controller --- openpype/tools/publisher/control.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index f361f7bb56..0929157288 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -325,14 +325,21 @@ class PublishReport: class PublisherController: - """Middleware between UI, CreateContext and publish Context.""" + """Middleware between UI, CreateContext and publish Context. + + Handle both creation and publishing parts. + + Args: + dbcon (AvalonMongoDB): Connection to mongo with context. + headless (bool): Headless publishing. ATM not implemented or used. + """ def __init__(self, dbcon=None, headless=False): self.log = logging.getLogger("PublisherController") self.host = avalon.api.registered_host() self.headless = headless self.create_context = CreateContext( - self.host, dbcon, headless=False, reset=False + self.host, dbcon, headless=headless, reset=False ) # pyblish.api.Context @@ -371,7 +378,7 @@ class PublisherController: # Plugin iterator self._main_thread_iter = None - # Varianbles where callbacks are stored + # Variables where callbacks are stored self._instances_refresh_callback_refs = set() self._plugins_refresh_callback_refs = set() From 076d090de4485c3a784e1f7c79536c4cd1b1e0d7 Mon Sep 17 00:00:00 2001 From: karimmozlia Date: Fri, 22 Oct 2021 17:45:43 +0200 Subject: [PATCH 655/736] add to imageio schema --- openpype/hosts/maya/api/lib.py | 3 ++ .../defaults/project_anatomy/imageio.json | 11 +++++++ .../schemas/schema_anatomy_imageio.json | 32 +++++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index ce93b5d701..140ebc7f26 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2,6 +2,7 @@ import re import os +import platform import uuid import math @@ -1885,6 +1886,8 @@ def set_context_settings(): # Set frame range. avalon.maya.interactive.reset_frame_range() + # Set colorspace + set_colorspace() # Valid FPS diff --git a/openpype/settings/defaults/project_anatomy/imageio.json b/openpype/settings/defaults/project_anatomy/imageio.json index 38313a3d84..2069777079 100644 --- a/openpype/settings/defaults/project_anatomy/imageio.json +++ b/openpype/settings/defaults/project_anatomy/imageio.json @@ -174,5 +174,16 @@ } ] } + }, + "maya": { + "colorManagmentPreference": { + "configFilePath": { + "windows": [], + "darwin": [], + "linux": [] + }, + "renderSpace": "scene-linear Rec 709/sRGB", + "viewTransform": "sRGB gamma" + } } } \ No newline at end of file diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_imageio.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_imageio.json index 3c589f9492..2bcdab8bc9 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_imageio.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_imageio.json @@ -358,6 +358,38 @@ ] } ] + }, + { + "key": "maya", + "type": "dict", + "label": "Maya", + "children": [ + { + "key": "colorManagmentPreference", + "type": "dict", + "label": "Color Managment Preference", + "collapsible": false, + "children": [ + { + "type": "path", + "key": "configFilePath", + "label": "OCIO Config File Path", + "multiplatform": true, + "multipath": true + }, + { + "type": "text", + "key": "renderSpace", + "label": "Rendering Space" + }, + { + "type": "text", + "key": "viewTransform", + "label": "Viewer Transform" + } + ] + } + ] } ] } From ccc1ff3099171a877091e0cad95444c89fe193bc Mon Sep 17 00:00:00 2001 From: karimmozlia Date: Fri, 22 Oct 2021 18:01:55 +0200 Subject: [PATCH 656/736] un check color managment when no path --- openpype/hosts/maya/api/lib.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 140ebc7f26..fbc2fee75c 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -1886,6 +1886,7 @@ def set_context_settings(): # Set frame range. avalon.maya.interactive.reset_frame_range() + # Set colorspace set_colorspace() @@ -2781,6 +2782,10 @@ def set_colorspace(): log.debug("maya '{}' changed to: {}".format( "configFilePath", resolved_path)) root_dict.pop("configFilePath") + else : + cmds.colorManagementPrefs(e=True, cmConfigFileEnabled=False) + cmds.colorManagementPrefs(e=True, configFilePath= "" ) + # third define rendering space and view transform From 7cc48b50261a3e842e819c0ab73b7894b1e5de21 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 22 Oct 2021 18:02:51 +0200 Subject: [PATCH 657/736] fix publisher tool def --- openpype/tools/experimental_tools/tools_def.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/tools/experimental_tools/tools_def.py b/openpype/tools/experimental_tools/tools_def.py index 63116c72c4..991eb5e4a3 100644 --- a/openpype/tools/experimental_tools/tools_def.py +++ b/openpype/tools/experimental_tools/tools_def.py @@ -117,6 +117,8 @@ class ExperimentalTools: self._tools = experimental_tools self._parent_widget = parent + self._publisher_tool = None + @property def tools(self): """Tools in list. From cbdd4c2f77b49b53f67c3d1c3fa0949fe56aeeeb Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 22 Oct 2021 18:03:58 +0200 Subject: [PATCH 658/736] fix import of python 2 comp function --- openpype/tools/publisher/control.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 0929157288..24ec9dcb0e 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -9,7 +9,7 @@ import weakref try: from weakref import WeakMethod except Exception: - from openpype.lib.python2_comp import WeakMethod + from openpype.lib.python_2_comp import WeakMethod import avalon.api import pyblish.api From 3a594358c0218adc502bd0ab271a3e8599c1af90 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 22 Oct 2021 18:06:51 +0200 Subject: [PATCH 659/736] replaced hsla with rgba color definitions --- openpype/style/data.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/style/data.json b/openpype/style/data.json index 78cc3d3e47..26f6743d72 100644 --- a/openpype/style/data.json +++ b/openpype/style/data.json @@ -65,8 +65,8 @@ "warning": "#ffc671", "list-view-group": { "bg": "#434a56", - "bg-hover": "hsla(220, 14%, 70%, .3)", - "bg-selected-hover": "hsla(200, 60%, 60%, .4)", + "bg-hover": "rgba(168, 175, 189, 0.3)", + "bg-selected-hover": "rgba(92, 173, 214, 0.4)", "bg-expander": "#2C313A", "bg-expander-hover": "#2d6c9f", "bg-expander-selected-hover": "#3784c5" From 5e7468ff4f42a0c34759fcb71c28b0cc2f439065 Mon Sep 17 00:00:00 2001 From: karimmozlia Date: Fri, 22 Oct 2021 18:08:06 +0200 Subject: [PATCH 660/736] fix typo colorManagementPreference --- openpype/hosts/maya/api/lib.py | 2 +- openpype/settings/defaults/project_anatomy/imageio.json | 2 +- .../schemas/projects_schema/schemas/schema_anatomy_imageio.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index fbc2fee75c..6dabd4812a 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2752,7 +2752,7 @@ def set_colorspace(): """ imageio = get_anatomy_settings(os.getenv("AVALON_PROJECT"))["imageio"]["maya"] - root_dict = imageio["colorManagmentPreference"] + root_dict = imageio["colorManagementPreference"] if not isinstance(root_dict, dict): msg = "set_colorspace(): argument should be dictionary" diff --git a/openpype/settings/defaults/project_anatomy/imageio.json b/openpype/settings/defaults/project_anatomy/imageio.json index 2069777079..9524ffcd3b 100644 --- a/openpype/settings/defaults/project_anatomy/imageio.json +++ b/openpype/settings/defaults/project_anatomy/imageio.json @@ -176,7 +176,7 @@ } }, "maya": { - "colorManagmentPreference": { + "colorManagementPreference": { "configFilePath": { "windows": [], "darwin": [], diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_imageio.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_imageio.json index 2bcdab8bc9..7423d6fd3e 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_imageio.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_imageio.json @@ -365,7 +365,7 @@ "label": "Maya", "children": [ { - "key": "colorManagmentPreference", + "key": "colorManagementPreference", "type": "dict", "label": "Color Managment Preference", "collapsible": false, From 8903e9d909b86086167243ab0f5ce6d49d84befb Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 22 Oct 2021 18:13:07 +0200 Subject: [PATCH 661/736] use valid ascii characters in docstrings --- .../publisher/widgets/border_label_widget.py | 28 +++++++++++-------- .../publisher/widgets/validations_widget.py | 18 ++++++------ 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/openpype/tools/publisher/widgets/border_label_widget.py b/openpype/tools/publisher/widgets/border_label_widget.py index 6f476fabee..157d36ffc0 100644 --- a/openpype/tools/publisher/widgets/border_label_widget.py +++ b/openpype/tools/publisher/widgets/border_label_widget.py @@ -6,7 +6,7 @@ from openpype.style import get_objected_colors class _VLineWidget(QtWidgets.QWidget): """Widget drawing 1px wide vertical line. - ``` β”‚ ``` + ``` | ``` Line is drawn in the middle of widget. @@ -44,7 +44,7 @@ class _VLineWidget(QtWidgets.QWidget): class _HBottomLineWidget(QtWidgets.QWidget): """Widget drawing 1px wide vertical line with side lines going upwards. - ```β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜``` + ```|_____________|``` Corners may have curve set by radius (`set_radius`). Radius should expect height of widget. @@ -88,9 +88,15 @@ class _HBottomLineWidget(QtWidgets.QWidget): class _HTopCornerLineWidget(QtWidgets.QWidget): """Widget drawing 1px wide horizontal line with side line going downwards. - ```β”Œβ”€β”€β”€β”€β”€β”€β”€``` + ``` + _______ + | + ``` or - ```────────┐``` + ``` + _______ + | + ``` Horizontal line is drawed in the middle of widget. @@ -146,13 +152,13 @@ class _HTopCornerLineWidget(QtWidgets.QWidget): class BorderedLabelWidget(QtWidgets.QFrame): """Draws borders around widget with label in the middle of top. - β”Œβ”€β”€β”€β”€β”€β”€β”€ Label ────────┐ - β”‚ β”‚ - β”‚ β”‚ - β”‚ CONTENT β”‚ - β”‚ β”‚ - β”‚ β”‚ - β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + +------- Label --------+ + | | + | | + | CONTENT | + | | + | | + +----------------------+ """ def __init__(self, label, parent): super(BorderedLabelWidget, self).__init__(parent) diff --git a/openpype/tools/publisher/widgets/validations_widget.py b/openpype/tools/publisher/widgets/validations_widget.py index 0480719d78..f6044db5c5 100644 --- a/openpype/tools/publisher/widgets/validations_widget.py +++ b/openpype/tools/publisher/widgets/validations_widget.py @@ -343,15 +343,15 @@ class ValidationsWidget(QtWidgets.QWidget): Shows validation error titles with instances on which happened and validation error detail with possible actions (repair). - β”Œβ”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β” - β”‚titlesβ”‚ β”‚actionsβ”‚ - β”‚ β”‚ β”‚ β”‚ - β”‚ β”‚ Error detail β”‚ β”‚ - β”‚ β”‚ β”‚ β”‚ - β”‚ β”‚ β”‚ β”‚ - β”œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€ - β”‚ Publish buttons β”‚ - β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + +------+----------------+-------+ + |titles| |actions| + | | | | + | | Error detail | | + | | | | + | | | | + +------+----------------+-------+ + | Publish buttons | + +-------------------------------+ """ def __init__(self, controller, parent): super(ValidationsWidget, self).__init__(parent) From bb97734cf67b2ad4a8d70a7752bdc8532d88e575 Mon Sep 17 00:00:00 2001 From: karimmozlia Date: Fri, 22 Oct 2021 18:25:02 +0200 Subject: [PATCH 662/736] hound --- openpype/hosts/maya/api/lib.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 6dabd4812a..403372a0f3 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -1886,7 +1886,7 @@ def set_context_settings(): # Set frame range. avalon.maya.interactive.reset_frame_range() - + # Set colorspace set_colorspace() @@ -2749,11 +2749,11 @@ def iter_shader_edits(relationships, shader_nodes, nodes_by_id, label=None): def set_colorspace(): """Set Colorspace from project configuration - """ - imageio = get_anatomy_settings(os.getenv("AVALON_PROJECT"))["imageio"]["maya"] + project_name = os.getenv("AVALON_PROJECT") + imageio = get_anatomy_settings(project_name)["imageio"]["maya"] root_dict = imageio["colorManagementPreference"] - + if not isinstance(root_dict, dict): msg = "set_colorspace(): argument should be dictionary" log.error(msg) @@ -2776,21 +2776,18 @@ def set_colorspace(): continue if resolved_path: - cmds.colorManagementPrefs(e=True, configFilePath= str(resolved_path).replace("\\", "/") ) + filepath = str(resolved_path).replace("\\", "/") + cmds.colorManagementPrefs(e=True, configFilePath=filepath ) cmds.colorManagementPrefs(e=True, cmConfigFileEnabled=True) - log.debug("maya '{}' changed to: {}".format( "configFilePath", resolved_path)) root_dict.pop("configFilePath") - else : + else: cmds.colorManagementPrefs(e=True, cmConfigFileEnabled=False) - cmds.colorManagementPrefs(e=True, configFilePath= "" ) + cmds.colorManagementPrefs(e=True, configFilePath="" ) - - - # third define rendering space and view transform + # third set rendering space and view transform renderSpace = root_dict["renderSpace"] cmds.colorManagementPrefs(e=True, renderingSpaceName=renderSpace) viewTransform = root_dict["viewTransform"] - cmds.colorManagementPrefs(e=True, viewTransformName=viewTransform) - + cmds.colorManagementPrefs(e=True, viewTransformName=viewTransform) \ No newline at end of file From a09689431c0b0eaa60bf03edc4ca5c7f5c4721fd Mon Sep 17 00:00:00 2001 From: karimmozlia Date: Fri, 22 Oct 2021 18:26:23 +0200 Subject: [PATCH 663/736] hound --- openpype/hosts/maya/api/lib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 403372a0f3..5d8e8c077d 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2777,7 +2777,7 @@ def set_colorspace(): if resolved_path: filepath = str(resolved_path).replace("\\", "/") - cmds.colorManagementPrefs(e=True, configFilePath=filepath ) + cmds.colorManagementPrefs(e=True, configFilePath=filepath) cmds.colorManagementPrefs(e=True, cmConfigFileEnabled=True) log.debug("maya '{}' changed to: {}".format( "configFilePath", resolved_path)) @@ -2790,4 +2790,4 @@ def set_colorspace(): renderSpace = root_dict["renderSpace"] cmds.colorManagementPrefs(e=True, renderingSpaceName=renderSpace) viewTransform = root_dict["viewTransform"] - cmds.colorManagementPrefs(e=True, viewTransformName=viewTransform) \ No newline at end of file + cmds.colorManagementPrefs(e=True, viewTransformName=viewTransform) From 82388e801801ad571afe77372487bb9f532cd425 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 22 Oct 2021 18:28:28 +0200 Subject: [PATCH 664/736] ratios are float numbers --- .../tools/publisher/publish_report_viewer/delegates.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/tools/publisher/publish_report_viewer/delegates.py b/openpype/tools/publisher/publish_report_viewer/delegates.py index 9d72c143ae..5fb6c0f9cd 100644 --- a/openpype/tools/publisher/publish_report_viewer/delegates.py +++ b/openpype/tools/publisher/publish_report_viewer/delegates.py @@ -33,10 +33,10 @@ class GroupItemDelegate(QtWidgets.QStyledItemDelegate): _plus_pixmaps = {} _path_stroker = None - _item_pix_offset_ratio = 1 / 5 - _item_border_size = 1 / 7 - _group_pix_offset_ratio = 1 / 3 - _group_pix_stroke_size_ratio = 1 / 7 + _item_pix_offset_ratio = 1.0 / 5.0 + _item_border_size = 1.0 / 7.0 + _group_pix_offset_ratio = 1.0 / 3.0 + _group_pix_stroke_size_ratio = 1.0 / 7.0 @classmethod def _get_path_stroker(cls): From 5cdadc5b7c94090e3f797880e14d88a2fa06aaae Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 22 Oct 2021 18:32:08 +0200 Subject: [PATCH 665/736] fix height of icons to have odd numbers --- .../publish_report_viewer/delegates.py | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/openpype/tools/publisher/publish_report_viewer/delegates.py b/openpype/tools/publisher/publish_report_viewer/delegates.py index 5fb6c0f9cd..9cd4f52174 100644 --- a/openpype/tools/publisher/publish_report_viewer/delegates.py +++ b/openpype/tools/publisher/publish_report_viewer/delegates.py @@ -272,14 +272,22 @@ class GroupItemDelegate(QtWidgets.QStyledItemDelegate): bg_rect.setY(_rect.y()) bg_rect.setHeight(_rect.height()) - expander_rect = QtCore.QRectF(bg_rect) - expander_rect.setWidth(expander_rect.height() + 5) + expander_height = bg_rect.height() + expander_width = expander_height + 5 + expander_y_offset = expander_height % 2 + expander_height -= expander_y_offset + expander_rect = QtCore.QRectF( + bg_rect.x(), + bg_rect.y() + expander_y_offset, + expander_width, + expander_height + ) label_rect = QtCore.QRectF( - expander_rect.x() + expander_rect.width(), - expander_rect.y(), - bg_rect.width() - expander_rect.width(), - expander_rect.height() + bg_rect.x() + expander_width, + bg_rect.y(), + bg_rect.width() - expander_width, + bg_rect.height() ) bg_path = QtGui.QPainterPath() @@ -298,9 +306,9 @@ class GroupItemDelegate(QtWidgets.QStyledItemDelegate): expanded = self.parent().isExpanded(index) if expanded: - expander_icon = self._get_minus_pixmap(expander_rect.height()) + expander_icon = self._get_minus_pixmap(expander_height) else: - expander_icon = self._get_plus_pixmap(expander_rect.height()) + expander_icon = self._get_plus_pixmap(expander_height) label = index.data(QtCore.Qt.DisplayRole) label = option.fontMetrics.elidedText( From 91ee0777a340491dc99a1221fd973f2c1da899c9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 22 Oct 2021 18:35:14 +0200 Subject: [PATCH 666/736] fix parenting of experimental tools --- openpype/tools/experimental_tools/dialog.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/tools/experimental_tools/dialog.py b/openpype/tools/experimental_tools/dialog.py index 72503a1aff..ad65caa8e3 100644 --- a/openpype/tools/experimental_tools/dialog.py +++ b/openpype/tools/experimental_tools/dialog.py @@ -81,7 +81,9 @@ class ExperimentalToolsDialog(QtWidgets.QDialog): tool_btns_layout.addWidget(separator_widget, 0) tool_btns_layout.addWidget(tool_btns_label, 0) - experimental_tools = ExperimentalTools() + experimental_tools = ExperimentalTools( + parent=parent, filter_hosts=True + ) # Main layout layout = QtWidgets.QVBoxLayout(self) From 2e98795ad0999cc930762cf3aa7e6db769824809 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 22 Oct 2021 18:51:36 +0200 Subject: [PATCH 667/736] pop keys that are already set --- openpype/pipeline/create/context.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 752854574a..8f0aad896b 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -408,6 +408,14 @@ class CreatedInstance: version = 1 self._data["version"] = version + # Pop from source data all keys that are defined in `_data` before + # this moment and through their values away + # - they should be the same and if are not then should not change + # already set values + for key in self._data.keys(): + if key in data: + data.pop(key) + # Stored creator specific attribute values # {key: value} creator_values = copy.deepcopy(orig_creator_attributes) From c135fe3bdee342f045fcaff470aadbc045493e5d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 22 Oct 2021 18:51:52 +0200 Subject: [PATCH 668/736] added question to "id" key --- openpype/pipeline/create/context.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 8f0aad896b..7b0f50b1dc 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -393,6 +393,7 @@ class CreatedInstance: # QUESTION Does it make sense to have data stored as ordered dict? self._data = collections.OrderedDict() + # QUESTION Do we need this "id" information on instance? self._data["id"] = "pyblish.avalon.instance" self._data["family"] = family self._data["subset"] = subset_name From 0bae203487294fff678a0ce43ae4fac4c8d873bb Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 25 Oct 2021 10:40:36 +0200 Subject: [PATCH 669/736] thumbnails widget is vibile but has empty pixmap --- openpype/tools/publisher/widgets/widgets.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index 1b12fd16ed..f0d21b7f80 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -1442,7 +1442,6 @@ class SubsetAttributesWidget(QtWidgets.QWidget): # Global attributes global_attrs_widget = GlobalAttrsWidget(controller, top_widget) thumbnail_widget = ThumbnailWidget(top_widget) - thumbnail_widget.setVisible(False) top_layout = QtWidgets.QHBoxLayout(top_widget) top_layout.setContentsMargins(0, 0, 0, 0) @@ -1530,10 +1529,19 @@ class SubsetAttributesWidget(QtWidgets.QWidget): class ThumbnailWidget(QtWidgets.QWidget): + """Instance thumbnail widget. + + Logic implementation of this widget is missing but widget is used + to offset `GlobalAttrsWidget` inputs visually. + """ def __init__(self, parent): super(ThumbnailWidget, self).__init__(parent) - default_pix = get_pixmap("thumbnail") + # Missing implementation for thumbnail + # - widget kept to make a visial offset of global attr widget offset + # default_pix = get_pixmap("thumbnail") + default_pix = QtGui.QPixmap(10, 10) + default_pix.fill(QtCore.Qt.transparent) thumbnail_label = QtWidgets.QLabel(self) thumbnail_label.setPixmap( From 44d33953b64f11371cb5dad9f7bbd23631397bcc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 25 Oct 2021 10:41:47 +0200 Subject: [PATCH 670/736] added docstrings to widgets.py --- openpype/tools/publisher/widgets/widgets.py | 100 +++++++++++++++++++- 1 file changed, 99 insertions(+), 1 deletion(-) diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index f0d21b7f80..ba7f1d5215 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -409,6 +409,9 @@ class AssetsDialog(QtWidgets.QDialog): self._asset_view = asset_view self._selected_asset = None + # Soft refresh is enabled + # - reset will happen at all cost if soft reset is enabled + # - adds ability to call reset on multiple places without repeating self._soft_reset_enabled = True def showEvent(self, event): @@ -418,6 +421,7 @@ class AssetsDialog(QtWidgets.QDialog): self.reset(False) def reset(self, force=True): + """Reset asset model.""" if not force and not self._soft_reset_enabled: return @@ -427,6 +431,11 @@ class AssetsDialog(QtWidgets.QDialog): self._model.reset() def name_is_valid(self, name): + """Is asset name valid. + + Args: + name(str): Asset name that should be checked. + """ # Make sure we're reset self.reset(False) # Valid the name by model @@ -883,6 +892,11 @@ class TasksCombobox(QtWidgets.QComboBox): return True def set_selected_item(self, item_name): + """Set task which is set on selected instance. + + Args: + item_name(str): Task name which should be selected. + """ idx = self.findText(item_name) # Set current index (must be set to -1 if is invalid) self.setCurrentIndex(idx) @@ -895,6 +909,7 @@ class TasksCombobox(QtWidgets.QComboBox): class VariantInputWidget(QtWidgets.QLineEdit): + """Input widget for variant.""" value_changed = QtCore.Signal() def __init__(self, parent): @@ -919,9 +934,11 @@ class VariantInputWidget(QtWidgets.QLineEdit): self.textChanged.connect(self._on_text_change) def is_valid(self): + """Is variant text valid.""" return self._is_valid def has_value_changed(self): + """Value of variant has changed.""" return self._has_value_changed def _set_state_property(self, state): @@ -931,6 +948,7 @@ class VariantInputWidget(QtWidgets.QLineEdit): self.style().polish(self) def set_multiselection_text(self, text): + """Change text of multiselection.""" self._multiselection_text = text def _set_is_valid(self, valid): @@ -955,12 +973,18 @@ class VariantInputWidget(QtWidgets.QLineEdit): self.value_changed.emit() def reset_to_origin(self): + """Set origin value of selected instnaces.""" self.set_value(self._origin_value) def get_value(self): + """Get current value. + + Origin value returned if didn't change. + """ return copy.deepcopy(self._current_value) def set_value(self, variants=None): + """Set value of currently selected instances.""" if variants is None: variants = [] @@ -987,6 +1011,12 @@ class VariantInputWidget(QtWidgets.QLineEdit): class MultipleItemWidget(QtWidgets.QWidget): + """Widget for immutable text which can have more than one value. + + Content may be bigger than widget's size and does not have scroll but has + flick widget on top (is possible to move around with clicked mouse). + """ + def __init__(self, parent): super(MultipleItemWidget, self).__init__(parent) @@ -1019,6 +1049,7 @@ class MultipleItemWidget(QtWidgets.QWidget): super(MultipleItemWidget, self).showEvent(event) tmp_item = None if not self._value: + # Add temp item to be able calculate maximum height of widget tmp_item = QtGui.QStandardItem("tmp") self._model.appendRow(tmp_item) @@ -1029,6 +1060,7 @@ class MultipleItemWidget(QtWidgets.QWidget): self._model.clear() def set_value(self, value=None): + """Set value/s of currently selected instance.""" if value is None: value = [] self._value = value @@ -1042,6 +1074,23 @@ class MultipleItemWidget(QtWidgets.QWidget): class GlobalAttrsWidget(QtWidgets.QWidget): + """Global attributes mainly to define context and subset name of instances. + + Subset name is or may be affected on context. Gives abiity to modify + context and subset name of instance. This change is not autopromoted but + must be submited. + + Warning: Until artist hit `Submit` changes must not be propagated to + instance data. + + Global attributes contain these widgets: + Variant: [ text input ] + Asset: [ asset dialog ] + Task: [ combobox ] + Family: [ immutable ] + Subset name: [ immutable ] + [Submit] [Cancel] + """ instance_context_changed = QtCore.Signal() multiselection_text = "< Multiselection >" @@ -1103,6 +1152,7 @@ class GlobalAttrsWidget(QtWidgets.QWidget): self.cancel_btn = cancel_btn def _on_submit(self): + """Commit changes for selected instnaces.""" variant_value = None asset_name = None task_name = None @@ -1166,6 +1216,7 @@ class GlobalAttrsWidget(QtWidgets.QWidget): self.instance_context_changed.emit() def _on_cancel(self): + """Cancel changes and set back to their irigin value.""" self.variant_input.reset_to_origin() self.asset_value_widget.reset_to_origin() self.task_value_widget.reset_to_origin() @@ -1206,6 +1257,12 @@ class GlobalAttrsWidget(QtWidgets.QWidget): self.submit_btn.setEnabled(enabled) def set_current_instances(self, instances): + """Set currently selected instances. + + Args: + instances(list): List of selected instances. + Empty instances tells that nothing or context is selected. + """ self._set_btns_visible(False) self._current_instances = instances @@ -1247,6 +1304,19 @@ class GlobalAttrsWidget(QtWidgets.QWidget): class CreatorAttrsWidget(QtWidgets.QWidget): + """Widget showing creator specific attributes for selected instances. + + Attributes are defined on creator so are dynamic. Their look and type is + based on attribute definitions that are defined in + `~/openpype/pipeline/lib/attribute_definitions.py` and their widget + representation in `~/openpype/widgets/attribute_defs/*`. + + Widgets are disabled if context of instance is not valid. + + Definitions are shown for all instance no matter if they are created with + different creators. If creator have same (similar) definitions their + widgets are merged into one (different label does not count). + """ def __init__(self, controller, parent): super(CreatorAttrsWidget, self).__init__(parent) @@ -1270,6 +1340,7 @@ class CreatorAttrsWidget(QtWidgets.QWidget): self._content_widget = None def set_instances_valid(self, valid): + """Change valid state of current instances.""" if ( self._content_widget is not None and self._content_widget.isEnabled() != valid @@ -1277,6 +1348,7 @@ class CreatorAttrsWidget(QtWidgets.QWidget): self._content_widget.setEnabled(valid) def set_current_instances(self, instances): + """Set current instances for which are attribute definitons shown.""" prev_content_widget = self._scroll_area.widget() if prev_content_widget: self._scroll_area.takeWidget() @@ -1325,6 +1397,23 @@ class CreatorAttrsWidget(QtWidgets.QWidget): class PublishPluginAttrsWidget(QtWidgets.QWidget): + """Widget showing publsish plugin attributes for selected instances. + + Attributes are defined on publish plugins. Publihs plugin may define + attribute definitions but must inherit `OpenPypePyblishPluginMixin` + (~/openpype/pipeline/publish). At the moment requires to implement + `get_attribute_defs` and `convert_attribute_values` class methods. + + Look and type of attributes is based on attribute definitions that are + defined in `~/openpype/pipeline/lib/attribute_definitions.py` and their + widget representation in `~/openpype/widgets/attribute_defs/*`. + + Widgets are disabled if context of instance is not valid. + + Definitions are shown for all instance no matter if they have different + families. Similar definitions are merged into one (different label + does not count). + """ def __init__(self, controller, parent): super(PublishPluginAttrsWidget, self).__init__(parent) @@ -1349,6 +1438,7 @@ class PublishPluginAttrsWidget(QtWidgets.QWidget): self._content_widget = None def set_instances_valid(self, valid): + """Change valid state of current instances.""" if ( self._content_widget is not None and self._content_widget.isEnabled() != valid @@ -1356,6 +1446,7 @@ class PublishPluginAttrsWidget(QtWidgets.QWidget): self._content_widget.setEnabled(valid) def set_current_instances(self, instances, context_selected): + """Set current instances for which are attribute definitons shown.""" prev_content_widget = self._scroll_area.widget() if prev_content_widget: self._scroll_area.takeWidget() @@ -1419,7 +1510,7 @@ class PublishPluginAttrsWidget(QtWidgets.QWidget): class SubsetAttributesWidget(QtWidgets.QWidget): - """Widget where attributes of instance/s are modified. + """Wrapper widget where attributes of instance/s are modified. _____________________________ | | | | Global | Thumbnail | @@ -1509,6 +1600,13 @@ class SubsetAttributesWidget(QtWidgets.QWidget): self.instance_context_changed.emit() def set_current_instances(self, instances, context_selected): + """Change currently selected items. + + Args: + instances(list): List of currently selected + instances. + context_selected(bool): Is context selected. + """ all_valid = True for instance in instances: if not instance.has_valid_context: From 9257023f18c7465e0235e0b184cdaf327de7071b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 25 Oct 2021 12:08:09 +0200 Subject: [PATCH 671/736] icons are scaled smoothly --- openpype/tools/publisher/widgets/widgets.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index ba7f1d5215..c7bd43fb9a 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -218,7 +218,9 @@ class PublishIconBtn(IconButton): part_h -= part_h % 2 scaled_image = image.scaled( width - (2 * part_w), - height - (2 * part_h) + height - (2 * part_h), + QtCore.Qt.IgnoreAspectRatio, + QtCore.Qt.SmoothTransformation ) alpha_mask = scaled_image.createAlphaMask() alpha_region = QtGui.QRegion(QtGui.QBitmap.fromImage(alpha_mask)) From 26d4ea9fc38573ccb8bbc2e495186f87daf998d0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 25 Oct 2021 12:08:18 +0200 Subject: [PATCH 672/736] added new buttons for report --- openpype/tools/publisher/widgets/widgets.py | 24 +++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index c7bd43fb9a..31282472be 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -286,6 +286,30 @@ class CreateInstanceBtn(PublishIconBtn): self.setToolTip("Create new instance") +class CopyPublishReportBtn(PublishIconBtn): + """Copy report button.""" + def __init__(self, parent=None): + icon_path = get_icon_path("copy") + super(CopyPublishReportBtn, self).__init__(icon_path, parent) + self.setToolTip("Copy report") + + +class SavePublishReportBtn(PublishIconBtn): + """Save report button.""" + def __init__(self, parent=None): + icon_path = get_icon_path("download_arrow") + super(SavePublishReportBtn, self).__init__(icon_path, parent) + self.setToolTip("Export and save report") + + +class ShowPublishReportBtn(PublishIconBtn): + """Show report button.""" + def __init__(self, parent=None): + icon_path = get_icon_path("view_report") + super(ShowPublishReportBtn, self).__init__(icon_path, parent) + self.setToolTip("Show details") + + class RemoveInstanceBtn(PublishIconBtn): """Create remove button.""" def __init__(self, parent=None): From 77afc0e4695cd29f05d7d0f1c5f01f96157569cb Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 25 Oct 2021 12:08:36 +0200 Subject: [PATCH 673/736] modified publish widget and added report buttons --- .../tools/publisher/widgets/publish_widget.py | 243 +++++++++++------- 1 file changed, 143 insertions(+), 100 deletions(-) diff --git a/openpype/tools/publisher/widgets/publish_widget.py b/openpype/tools/publisher/widgets/publish_widget.py index 2ef71b57ce..e4f3579978 100644 --- a/openpype/tools/publisher/widgets/publish_widget.py +++ b/openpype/tools/publisher/widgets/publish_widget.py @@ -12,7 +12,10 @@ from .widgets import ( StopBtn, ResetBtn, ValidateBtn, - PublishBtn + PublishBtn, + CopyPublishReportBtn, + SavePublishReportBtn, + ShowPublishReportBtn ) @@ -66,16 +69,49 @@ class ActionsButton(QtWidgets.QToolButton): class PublishFrame(QtWidgets.QFrame): + """Frame showed during publishing. + + Shows all information related to publishing. Contains validation error + widget which is showed if only validation error happens during validation. + + Processing layer is default layer. Validation error layer is shown if only + validation exception is raised during publishing. Report layer is available + only when publishing process is stopped and must be manually triggered to + change into that layer. + + +------------------------------------------------------------------------+ + | | + | | + | | + | < Validation error widget > | + | | + | | + | | + | | + +------------------------------------------------------------------------+ + | < Main label > | + | < Label top > | + | (#### 10% ) | + | | + | Report: