From 6a6ce4d5c5976038bf4f296183603883d38d9f92 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 3 Mar 2022 14:52:24 +0100 Subject: [PATCH 1/5] added funciton to convert string fpx into float --- openpype/modules/ftrack/lib/avalon_sync.py | 106 +++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/openpype/modules/ftrack/lib/avalon_sync.py b/openpype/modules/ftrack/lib/avalon_sync.py index db7c592c9b..11478925d6 100644 --- a/openpype/modules/ftrack/lib/avalon_sync.py +++ b/openpype/modules/ftrack/lib/avalon_sync.py @@ -2,6 +2,9 @@ import re import json import collections import copy +import numbers + +import six from avalon.api import AvalonMongoDB @@ -32,6 +35,109 @@ CURRENT_DOC_SCHEMAS = { "config": "openpype:config-2.0" } +FPS_KEYS = { + "fps", + # For development purposes + "fps_string" +} + + +class InvalidFpsValue(Exception): + pass + + +def is_string_number(value): + """Can string value be converted to number (float).""" + if not isinstance(value, six.string_types): + raise TypeError("Expected {} got {}".format( + ", ".join(str(t) for t in six.string_types), str(type(value)) + )) + if value == ".": + return False + + if value.startswith("."): + value = "0" + value + elif value.endswith("."): + value = value + "0" + + if re.match(r"^\d+(\.\d+)?$", value) is None: + return False + return True + + +def convert_to_fps(source_value): + """Convert value into fps value. + + Non string values are kept untouched. String is tried to convert. + Valid values: + "1000" + "1000.05" + "1000,05" + ",05" + ".05" + "1000," + "1000." + "1000/1000" + "1000.05/1000" + "1000/1000.05" + "1000.05/1000.05" + "1000,05/1000" + "1000/1000,05" + "1000,05/1000,05" + + Invalid values: + "/" + "/1000" + "1000/" + "," + "." + ...any other string + + Returns: + float: Converted value. + + Raises: + InvalidFpsValue: When value can't be converted to float. + """ + if not isinstance(source_value, six.string_types): + if isinstance(source_value, numbers.Number): + return float(source_value) + return source_value + + value = source_value.strip().replace(",", ".") + if not value: + raise InvalidFpsValue("Got empty value") + + subs = value.split("/") + if len(subs) == 1: + str_value = subs[0] + if not is_string_number(str_value): + raise InvalidFpsValue( + "Value \"{}\" can't be converted to number.".format(value) + ) + return float(str_value) + + elif len(subs) == 2: + divident, divisor = subs + if not divident or not is_string_number(divident): + raise InvalidFpsValue( + "Divident value \"{}\" can't be converted to number".format( + divident + ) + ) + + if not divisor or not is_string_number(divisor): + raise InvalidFpsValue( + "Divisor value \"{}\" can't be converted to number".format( + divident + ) + ) + return float(divident) / float(divisor) + + raise InvalidFpsValue( + "Value can't be converted to number \"{}\"".format(source_value) + ) + def create_chunks(iterable, chunk_size=None): """Separate iterable into multiple chunks by size. From f88bf7b5be19280ba9ea2088a46ffe579644d564 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 3 Mar 2022 14:52:41 +0100 Subject: [PATCH 2/5] use fps conversion function during synchronization --- .../event_sync_to_avalon.py | 49 +++++++++++++++++++ openpype/modules/ftrack/lib/avalon_sync.py | 44 ++++++++++++++++- 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/openpype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py b/openpype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py index 9f85000dbb..76f4be1419 100644 --- a/openpype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py +++ b/openpype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py @@ -25,6 +25,11 @@ from openpype_modules.ftrack.lib import ( BaseEvent ) +from openpype_modules.ftrack.lib.avalon_sync import ( + convert_to_fps, + InvalidFpsValue, + FPS_KEYS +) from openpype.lib import CURRENT_DOC_SCHEMAS @@ -1149,12 +1154,31 @@ class SyncToAvalonEvent(BaseEvent): "description": ftrack_ent["description"] } } + invalid_fps_items = [] cust_attrs = self.get_cust_attr_values(ftrack_ent) for key, val in cust_attrs.items(): if key.startswith("avalon_"): continue + + if key in FPS_KEYS: + try: + val = convert_to_fps(val) + except InvalidFpsValue: + invalid_fps_items.append((ftrack_ent["id"], val)) + continue + final_entity["data"][key] = val + if invalid_fps_items: + fps_msg = ( + "These entities have invalid fps value in custom attributes" + ) + items = [] + for entity_id, value in invalid_fps_items: + ent_path = self.get_ent_path(entity_id) + items.append("{} - \"{}\"".format(ent_path, value)) + self.report_items["error"][fps_msg] = items + _mongo_id_str = cust_attrs.get(CUST_ATTR_ID_KEY) if _mongo_id_str: try: @@ -2155,11 +2179,19 @@ class SyncToAvalonEvent(BaseEvent): ) convert_types_by_id[attr_id] = convert_type + default_value = attr["default"] + if key in FPS_KEYS: + try: + default_value = convert_to_fps(default_value) + except InvalidFpsValue: + pass + entities_dict[ftrack_project_id]["hier_attrs"][key] = ( attr["default"] ) # PREPARE DATA BEFORE THIS + invalid_fps_items = [] avalon_hier = [] for item in values: value = item["value"] @@ -2173,8 +2205,25 @@ class SyncToAvalonEvent(BaseEvent): if convert_type: value = convert_type(value) + + if key in FPS_KEYS: + try: + value = convert_to_fps(value) + except InvalidFpsValue: + invalid_fps_items.append((entity_id, value)) + continue entities_dict[entity_id]["hier_attrs"][key] = value + if invalid_fps_items: + fps_msg = ( + "These entities have invalid fps value in custom attributes" + ) + items = [] + for entity_id, value in invalid_fps_items: + ent_path = self.get_ent_path(entity_id) + items.append("{} - \"{}\"".format(ent_path, value)) + self.report_items["error"][fps_msg] = items + # Get dictionary with not None hierarchical values to pull to childs project_values = {} for key, value in ( diff --git a/openpype/modules/ftrack/lib/avalon_sync.py b/openpype/modules/ftrack/lib/avalon_sync.py index 11478925d6..07b974d84f 100644 --- a/openpype/modules/ftrack/lib/avalon_sync.py +++ b/openpype/modules/ftrack/lib/avalon_sync.py @@ -1086,6 +1086,7 @@ class SyncEntitiesFactory: sync_ids ) + invalid_fps_items = [] for item in items: entity_id = item["entity_id"] attr_id = item["configuration_id"] @@ -1098,8 +1099,24 @@ class SyncEntitiesFactory: value = item["value"] if convert_type: value = convert_type(value) + + if key in FPS_KEYS: + try: + value = convert_to_fps(value) + except InvalidFpsValue: + invalid_fps_items.append((entity_id, value)) self.entities_dict[entity_id][store_key][key] = value + if invalid_fps_items: + fps_msg = ( + "These entities have invalid fps value in custom attributes" + ) + items = [] + for entity_id, value in invalid_fps_items: + ent_path = self.get_ent_path(entity_id) + items.append("{} - \"{}\"".format(ent_path, value)) + self.report_items["error"][fps_msg] = items + # process hierarchical attributes self.set_hierarchical_attribute( hier_attrs, sync_ids, cust_attr_type_name_by_id @@ -1132,8 +1149,15 @@ class SyncEntitiesFactory: if key.startswith("avalon_"): store_key = "avalon_attrs" + default_value = attr["default"] + if key in FPS_KEYS: + try: + default_value = convert_to_fps(default_value) + except InvalidFpsValue: + pass + self.entities_dict[self.ft_project_id][store_key][key] = ( - attr["default"] + default_value ) # Add attribute ids to entities dictionary @@ -1175,6 +1199,7 @@ class SyncEntitiesFactory: True ) + invalid_fps_items = [] avalon_hier = [] for item in items: value = item["value"] @@ -1194,6 +1219,13 @@ class SyncEntitiesFactory: entity_id = item["entity_id"] key = attribute_key_by_id[attr_id] + if key in FPS_KEYS: + try: + value = convert_to_fps(value) + except InvalidFpsValue: + invalid_fps_items.append((entity_id, value)) + continue + if key.startswith("avalon_"): store_key = "avalon_attrs" avalon_hier.append(key) @@ -1201,6 +1233,16 @@ class SyncEntitiesFactory: store_key = "hier_attrs" self.entities_dict[entity_id][store_key][key] = value + if invalid_fps_items: + fps_msg = ( + "These entities have invalid fps value in custom attributes" + ) + items = [] + for entity_id, value in invalid_fps_items: + ent_path = self.get_ent_path(entity_id) + items.append("{} - \"{}\"".format(ent_path, value)) + self.report_items["error"][fps_msg] = items + # Get dictionary with not None hierarchical values to pull to childs top_id = self.ft_project_id project_values = {} From 630c8193366edaceacaadd59bb47c5f0fe47ee70 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 3 Mar 2022 15:29:30 +0100 Subject: [PATCH 3/5] moved FPS_KEYS to constants --- .../ftrack/event_handlers_server/event_sync_to_avalon.py | 4 ++-- openpype/modules/ftrack/lib/__init__.py | 5 ++++- openpype/modules/ftrack/lib/avalon_sync.py | 8 +------- openpype/modules/ftrack/lib/constants.py | 6 ++++++ 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py b/openpype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py index 76f4be1419..eea6436b53 100644 --- a/openpype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py +++ b/openpype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py @@ -20,6 +20,7 @@ from openpype_modules.ftrack.lib import ( query_custom_attributes, CUST_ATTR_ID_KEY, CUST_ATTR_AUTO_SYNC, + FPS_KEYS, avalon_sync, @@ -27,8 +28,7 @@ from openpype_modules.ftrack.lib import ( ) from openpype_modules.ftrack.lib.avalon_sync import ( convert_to_fps, - InvalidFpsValue, - FPS_KEYS + InvalidFpsValue ) from openpype.lib import CURRENT_DOC_SCHEMAS diff --git a/openpype/modules/ftrack/lib/__init__.py b/openpype/modules/ftrack/lib/__init__.py index 80b4db9dd6..7fc2bc99eb 100644 --- a/openpype/modules/ftrack/lib/__init__.py +++ b/openpype/modules/ftrack/lib/__init__.py @@ -4,7 +4,8 @@ from .constants import ( CUST_ATTR_GROUP, CUST_ATTR_TOOLS, CUST_ATTR_APPLICATIONS, - CUST_ATTR_INTENT + CUST_ATTR_INTENT, + FPS_KEYS ) from .settings import ( get_ftrack_event_mongo_info @@ -30,6 +31,8 @@ __all__ = ( "CUST_ATTR_GROUP", "CUST_ATTR_TOOLS", "CUST_ATTR_APPLICATIONS", + "CUST_ATTR_INTENT", + "FPS_KEYS", "get_ftrack_event_mongo_info", diff --git a/openpype/modules/ftrack/lib/avalon_sync.py b/openpype/modules/ftrack/lib/avalon_sync.py index 07b974d84f..5a0c3c1574 100644 --- a/openpype/modules/ftrack/lib/avalon_sync.py +++ b/openpype/modules/ftrack/lib/avalon_sync.py @@ -17,7 +17,7 @@ from openpype.api import ( ) from openpype.lib import ApplicationManager -from .constants import CUST_ATTR_ID_KEY +from .constants import CUST_ATTR_ID_KEY, FPS_KEYS from .custom_attributes import get_openpype_attr, query_custom_attributes from bson.objectid import ObjectId @@ -35,12 +35,6 @@ CURRENT_DOC_SCHEMAS = { "config": "openpype:config-2.0" } -FPS_KEYS = { - "fps", - # For development purposes - "fps_string" -} - class InvalidFpsValue(Exception): pass diff --git a/openpype/modules/ftrack/lib/constants.py b/openpype/modules/ftrack/lib/constants.py index e6e2013d2b..636dcfbc3d 100644 --- a/openpype/modules/ftrack/lib/constants.py +++ b/openpype/modules/ftrack/lib/constants.py @@ -12,3 +12,9 @@ CUST_ATTR_APPLICATIONS = "applications" CUST_ATTR_TOOLS = "tools_env" # Intent custom attribute name CUST_ATTR_INTENT = "intent" + +FPS_KEYS = { + "fps", + # For development purposes + "fps_string" +} From c237434ad682f4477791df3751c05835b9a99551 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 3 Mar 2022 15:30:15 +0100 Subject: [PATCH 4/5] create custom attributes action does not replace text fps custom attribute --- .../action_create_cust_attrs.py | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_user/action_create_cust_attrs.py b/openpype/modules/ftrack/event_handlers_user/action_create_cust_attrs.py index cb5b88ad50..88dc8213bd 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_create_cust_attrs.py +++ b/openpype/modules/ftrack/event_handlers_user/action_create_cust_attrs.py @@ -11,6 +11,7 @@ from openpype_modules.ftrack.lib import ( CUST_ATTR_TOOLS, CUST_ATTR_APPLICATIONS, CUST_ATTR_INTENT, + FPS_KEYS, default_custom_attributes_definition, app_definitions_from_app_manager, @@ -519,20 +520,28 @@ class CustomAttributes(BaseAction): self.show_message(event, msg) def process_attribute(self, data): - existing_attrs = self.session.query( - "CustomAttributeConfiguration" - ).all() + existing_attrs = self.session.query(( + "select is_hierarchical, key, type, entity_type, object_type_id" + " from CustomAttributeConfiguration" + )).all() matching = [] + is_hierarchical = data.get("is_hierarchical", False) for attr in existing_attrs: if ( - attr["key"] != data["key"] or - attr["type"]["name"] != data["type"]["name"] + is_hierarchical != attr["is_hierarchical"] + or attr["key"] != data["key"] ): continue - if data.get("is_hierarchical") is True: - if attr["is_hierarchical"] is True: - matching.append(attr) + if attr["type"]["name"] != data["type"]["name"]: + if data["key"] in FPS_KEYS and attr["type"]["name"] == "text": + self.log.info("Kept 'fps' as text custom attribute.") + return + continue + + if is_hierarchical: + matching.append(attr) + elif "object_type_id" in data: if ( attr["entity_type"] == data["entity_type"] and From cec7adab1c1163f1ae15fbd4deeb57ea4b4a2924 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 3 Mar 2022 17:53:14 +0100 Subject: [PATCH 5/5] fix zero division error --- openpype/modules/ftrack/lib/avalon_sync.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/modules/ftrack/lib/avalon_sync.py b/openpype/modules/ftrack/lib/avalon_sync.py index 5a0c3c1574..5301ec568e 100644 --- a/openpype/modules/ftrack/lib/avalon_sync.py +++ b/openpype/modules/ftrack/lib/avalon_sync.py @@ -126,7 +126,10 @@ def convert_to_fps(source_value): divident ) ) - return float(divident) / float(divisor) + divisor_float = float(divisor) + if divisor_float == 0.0: + raise InvalidFpsValue("Can't divide by zero") + return float(divident) / divisor_float raise InvalidFpsValue( "Value can't be converted to number \"{}\"".format(source_value)