From 75cb30fe1da52f124ab25ed084ea1e63fab1a677 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 8 Jan 2020 17:11:27 +0100 Subject: [PATCH 1/7] inital version of delivery action in ftrack --- pype/ftrack/actions/action_delivery.py | 421 +++++++++++++++++++++++++ 1 file changed, 421 insertions(+) create mode 100644 pype/ftrack/actions/action_delivery.py diff --git a/pype/ftrack/actions/action_delivery.py b/pype/ftrack/actions/action_delivery.py new file mode 100644 index 0000000000..e23e35f91c --- /dev/null +++ b/pype/ftrack/actions/action_delivery.py @@ -0,0 +1,421 @@ +import os +import copy +import shutil + +import clique +from bson.objectid import ObjectId +from avalon import pipeline +from avalon.vendor import filelink +from avalon.tools.libraryloader.io_nonsingleton import DbConnector + +from pypeapp import Anatomy +from pype.ftrack import BaseAction +from pype.ftrack.lib.avalon_sync import CustAttrIdKey + + +class Delivery(BaseAction): + '''Edit meta data action.''' + + #: Action identifier. + identifier = "delivery.action" + #: Action label. + label = "Delivery" + #: Action description. + description = "Deliver data to client" + #: roles that are allowed to register this action + role_list = ["Pypeclub", "Administrator", "Project manager"] + # icon = '{}/ftrack/action_icons/TestAction.svg'.format( + # os.environ.get('PYPE_STATICS_SERVER', '') + # ) + + db_con = DbConnector() + + def discover(self, session, entities, event): + ''' Validation ''' + for entity in entities: + if entity.entity_type.lower() == "assetversion": + return True + + return False + + def interface(self, session, entities, event): + if event["data"].get("values", {}): + return + + title = "Delivery data to Client" + + items = [] + item_splitter = {"type": "label", "value": "---"} + + # Prepare component names for processing + components = None + project = None + for entity in entities: + if project is None: + project_id = None + for ent_info in entity["link"]: + if ent_info["type"].lower() == "project": + project_id = ent_info["id"] + break + + if project_id is None: + project = entity["asset"]["parent"]["project"] + else: + project = session.query(( + "select id, full_name from Project where id is \"{}\"" + ).format(project_id)).one() + + _components = set( + [component["name"] for component in entity["components"]] + ) + if components is None: + components = _components + continue + + components = components.intersection(_components) + if not components: + break + + project_name = project["full_name"] + items.append({ + "type": "hidden", + "name": "__project_name__", + "value": project_name + }) + + # Prpeare anatomy data + anatomy = Anatomy(project_name) + new_anatomies = [] + first = None + for key in (anatomy.templates.get("delivery") or {}): + new_anatomies.append({ + "label": key, + "value": key + }) + if first is None: + first = key + + skipped = False + # Add message if there are any common components + if not components or not new_anatomies: + skipped = True + items.append({ + "type": "label", + "value": "

Something went wrong:

" + }) + + items.append({ + "type": "hidden", + "name": "__skipped__", + "value": skipped + }) + + if not components: + if len(entities) == 1: + items.append({ + "type": "label", + "value": ( + "- Selected entity doesn't have components to deliver." + ) + }) + else: + items.append({ + "type": "label", + "value": ( + "- Selected entities don't have common components." + ) + }) + + # Add message if delivery anatomies are not set + if not new_anatomies: + items.append({ + "type": "label", + "value": ( + "- `\"delivery\"` anatomy key is not set in config." + ) + }) + + # Skip if there are any data shortcomings + if skipped: + return { + "items": items, + "title": title + } + + items.append({ + "value": "

Choose Components to deliver

", + "type": "label" + }) + + for component in components: + items.append({ + "type": "boolean", + "value": False, + "label": component, + "name": component + }) + + items.append(item_splitter) + + items.append({ + "value": "

Location for delivery

", + "type": "label" + }) + + items.append({ + "type": "text", + "name": "__location_path__", + "empty_text": "Type location path here..." + }) + + items.append(item_splitter) + + items.append({ + "value": "

Anatomy of delivery files

", + "type": "label" + }) + + items.append({ + "type": "label", + "value": ( + "

NOTE: These can be set in Anatomy.yaml" + " within `delivery` key.

" + ) + }) + + items.append({ + "type": "enumerator", + "name": "__new_anatomies__", + "data": new_anatomies, + "value": first + }) + + return { + "items": items, + "title": title + } + + def launch(self, session, entities, event): + if "values" not in event["data"]: + return + + values = event["data"]["values"] + skipped = values.pop("__skipped__") + if skipped: + return None + + component_names = [] + location_path = values.pop("__location_path__") + anatomy_name = values.pop("__new_anatomies__") + project_name = values.pop("__project_name__") + + for key, value in values.items(): + if value is True: + component_names.append(key) + + if not component_names: + return None + + location_path = os.path.normpath(location_path.strip()) + if location_path and not os.path.exists(location_path): + return { + "success": False, + "message": ( + "Entered location path does not exists. \"{}\"" + ).format(location_path) + } + + self.db_con.install() + self.db_con.Session["AVALON_PROJECT"] = project_name + + components = [] + repres_to_deliver = [] + for entity in entities: + asset = entity["asset"] + subset_name = asset["name"] + version = entity["version"] + + parent = asset["parent"] + parent_mongo_id = parent["custom_attributes"].get(CustAttrIdKey) + if not parent_mongo_id: + # TODO log error (much better) + self.log.warning(( + "Seems like entity <{}> is not synchronized to avalon" + ).format(parent["name"])) + continue + + parent_mongo_id = ObjectId(parent_mongo_id) + subset_ent = self.db_con.find_one({ + "type": "subset", + "parent": parent_mongo_id, + "name": subset_name + }) + + version_ent = self.db_con.find_one({ + "type": "version", + "name": version, + "parent": subset_ent["_id"] + }) + + repre_ents = self.db_con.find({ + "type": "representation", + "parent": version_ent["_id"] + }) + + repres_by_name = {} + for repre in repre_ents: + repre_name = repre["name"] + repres_by_name[repre_name] = repre + + for component in entity["components"]: + comp_name = component["name"] + if comp_name not in component_names: + continue + + repre = repres_by_name.get(comp_name) + repres_to_deliver.append(repre) + + src_dst_files = {} + anatomy = Anatomy(project_name) + for repre in repres_to_deliver: + # Get destination repre path + anatomy_data = copy.deepcopy(repre["context"]) + if location_path: + anatomy_data["root"] = location_path + else: + anatomy_data["root"] = pipeline.registered_root() + + # Get source repre path + repre_path = self.path_from_represenation(repre) + # TODO add backup solution where root of path from component + # is repalced with AVALON_PROJECTS root + + if repre_path and os.path.exists(repre_path): + self.process_single_file( + repre_path, anatomy, anatomy_name, anatomy_data + ) + + else: + self.process_sequence( + repre_path, anatomy, anatomy_name, anatomy_data + ) + + self.db_con.uninstall() + + def process_single_file( + self, repre_path, anatomy, anatomy_name, anatomy_data + ): + anatomy_filled = anatomy.format(anatomy_data) + delivery_path = anatomy_filled.get("delivery", {}).get(anatomy_name) + if not delivery_path: + # TODO log error! - missing keys in anatomy + return + + delivery_folder = os.path.dirname(delivery_path) + if not os.path.exists(delivery_folder): + os.makedirs(delivery_folder) + + self.copy_file(repre_path, delivery_path) + + def process_sequence( + self, repre_path, anatomy, anatomy_name, anatomy_data + ): + dir_path, file_name = os.path.split(repre_path) + if not os.path.exists(dir_path): + # TODO log if folder don't exist + return + + base_name, ext = os.path.splitext(file_name) + file_name_items = None + if "#" in base_name: + file_name_items = [part for part in base_name.split("#") if part] + + elif "%" in base_name: + file_name_items = base_name.split("%") + + if not file_name_items: + # TODO log if file does not exists + return + + src_collections, remainder = clique.assemble(os.listdir(dir_path)) + src_collection = None + for col in src_collections: + if col.tail != ext: + continue + + # skip if collection don't have same basename + if not col.head.startswith(file_name_items[0]): + continue + + src_collection = col + break + + if src_collection is None: + # TODO log error! + return + + anatomy_data["frame"] = "{frame}" + anatomy_filled = anatomy.format(anatomy_data) + delivery_path = anatomy_filled.get("delivery", {}).get(anatomy_name) + if not delivery_path: + # TODO log error! - missing keys in anatomy + return + + delivery_folder = os.path.dirname(delivery_path) + dst_head, dst_tail = delivery_path.split("{frame}") + dst_padding = src_collection.padding + dst_collection = clique.Collection( + head=dst_head, + tail=dst_tail, + padding=dst_padding + ) + + if not os.path.exists(delivery_folder): + os.makedirs(delivery_folder) + + src_head = src_collection.head + src_tail = src_collection.tail + for index in src_collection.indexes: + src_padding = src_collection.format("{padding}") % index + src_file_name = "{}{}{}".format(src_head, src_padding, src_tail) + + dst_padding = dst_collection.format("{padding}") % index + dst_file_name = "{}{}{}".format(dst_head, dst_padding, dst_tail) + + self.copy_file(src, dst) + + def path_from_represenation(self, representation): + try: + template = representation["data"]["template"] + + except KeyError: + return None + + try: + context = representation["context"] + context["root"] = os.environ.get("AVALON_PROJECTS") or "" + path = pipeline.format_template_with_optional_keys( + context, template + ) + + except KeyError: + # Template references unavailable data + return None + + if os.path.exists(path): + return os.path.normpath(path) + + def copy_file(self, src_path, dst_path): + try: + filelink.create( + src_path, + dst_path, + filelink.HARDLINK + ) + except OSError: + shutil.copyfile(src_path, dst_path) + +def register(session, plugins_presets={}): + '''Register plugin. Called when used as an plugin.''' + + Delivery(session, plugins_presets).register() From 830373f3d5c35c298285236a3a36b9eed0aaf5c4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 8 Jan 2020 17:19:35 +0100 Subject: [PATCH 2/7] added delivery icon --- pype/ftrack/actions/action_delivery.py | 6 ++--- res/ftrack/action_icons/Delivery.svg | 34 ++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 res/ftrack/action_icons/Delivery.svg diff --git a/pype/ftrack/actions/action_delivery.py b/pype/ftrack/actions/action_delivery.py index e23e35f91c..572a9bc8e0 100644 --- a/pype/ftrack/actions/action_delivery.py +++ b/pype/ftrack/actions/action_delivery.py @@ -24,9 +24,9 @@ class Delivery(BaseAction): description = "Deliver data to client" #: roles that are allowed to register this action role_list = ["Pypeclub", "Administrator", "Project manager"] - # icon = '{}/ftrack/action_icons/TestAction.svg'.format( - # os.environ.get('PYPE_STATICS_SERVER', '') - # ) + icon = '{}/ftrack/action_icons/Delivery.svg'.format( + os.environ.get('PYPE_STATICS_SERVER', '') + ) db_con = DbConnector() diff --git a/res/ftrack/action_icons/Delivery.svg b/res/ftrack/action_icons/Delivery.svg new file mode 100644 index 0000000000..3380487c31 --- /dev/null +++ b/res/ftrack/action_icons/Delivery.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + From cbbb074a25c929582a26807691bf00a27c7325a4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 8 Jan 2020 17:24:35 +0100 Subject: [PATCH 3/7] fix source filepath --- pype/ftrack/actions/action_delivery.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pype/ftrack/actions/action_delivery.py b/pype/ftrack/actions/action_delivery.py index 572a9bc8e0..ad3d6ef6cc 100644 --- a/pype/ftrack/actions/action_delivery.py +++ b/pype/ftrack/actions/action_delivery.py @@ -228,7 +228,6 @@ class Delivery(BaseAction): self.db_con.install() self.db_con.Session["AVALON_PROJECT"] = project_name - components = [] repres_to_deliver = [] for entity in entities: asset = entity["asset"] @@ -275,7 +274,6 @@ class Delivery(BaseAction): repre = repres_by_name.get(comp_name) repres_to_deliver.append(repre) - src_dst_files = {} anatomy = Anatomy(project_name) for repre in repres_to_deliver: # Get destination repre path @@ -302,6 +300,8 @@ class Delivery(BaseAction): self.db_con.uninstall() + return True + def process_single_file( self, repre_path, anatomy, anatomy_name, anatomy_data ): @@ -378,9 +378,12 @@ class Delivery(BaseAction): for index in src_collection.indexes: src_padding = src_collection.format("{padding}") % index src_file_name = "{}{}{}".format(src_head, src_padding, src_tail) + src = os.path.normpath( + os.path.join(dir_path, src_file_name) + ) dst_padding = dst_collection.format("{padding}") % index - dst_file_name = "{}{}{}".format(dst_head, dst_padding, dst_tail) + dst = "{}{}{}".format(dst_head, dst_padding, dst_tail) self.copy_file(src, dst) From cfd9823abc0c8109f4c5e18e2a6f1a55e2977047 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 8 Jan 2020 17:41:35 +0100 Subject: [PATCH 4/7] replaced {frame} with <> --- pype/ftrack/actions/action_delivery.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/ftrack/actions/action_delivery.py b/pype/ftrack/actions/action_delivery.py index ad3d6ef6cc..22fb15198b 100644 --- a/pype/ftrack/actions/action_delivery.py +++ b/pype/ftrack/actions/action_delivery.py @@ -354,7 +354,7 @@ class Delivery(BaseAction): # TODO log error! return - anatomy_data["frame"] = "{frame}" + anatomy_data["frame"] = "<>" anatomy_filled = anatomy.format(anatomy_data) delivery_path = anatomy_filled.get("delivery", {}).get(anatomy_name) if not delivery_path: @@ -362,7 +362,7 @@ class Delivery(BaseAction): return delivery_folder = os.path.dirname(delivery_path) - dst_head, dst_tail = delivery_path.split("{frame}") + dst_head, dst_tail = delivery_path.split("<>") dst_padding = src_collection.padding dst_collection = clique.Collection( head=dst_head, From 3cf559afba5058eae3e96cbb1d873e1b7403affe Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 8 Jan 2020 19:21:15 +0100 Subject: [PATCH 5/7] better reporting and logging --- pype/ftrack/actions/action_delivery.py | 144 +++++++++++++++++++++---- 1 file changed, 121 insertions(+), 23 deletions(-) diff --git a/pype/ftrack/actions/action_delivery.py b/pype/ftrack/actions/action_delivery.py index 22fb15198b..e698c371e1 100644 --- a/pype/ftrack/actions/action_delivery.py +++ b/pype/ftrack/actions/action_delivery.py @@ -1,9 +1,12 @@ import os import copy import shutil +import collections +import string import clique from bson.objectid import ObjectId + from avalon import pipeline from avalon.vendor import filelink from avalon.tools.libraryloader.io_nonsingleton import DbConnector @@ -162,10 +165,17 @@ class Delivery(BaseAction): "type": "label" }) + items.append({ + "type": "label", + "value": ( + "NOTE: It is possible to replace `root` key in anatomy." + ) + }) + items.append({ "type": "text", "name": "__location_path__", - "empty_text": "Type location path here..." + "empty_text": "Type location path here...(Optional)" }) items.append(item_splitter) @@ -199,6 +209,8 @@ class Delivery(BaseAction): if "values" not in event["data"]: return + self.report_items = collections.defaultdict(list) + values = event["data"]["values"] skipped = values.pop("__skipped__") if skipped: @@ -214,7 +226,10 @@ class Delivery(BaseAction): component_names.append(key) if not component_names: - return None + return { + "success": True, + "message": "Not selected components to deliver." + } location_path = os.path.normpath(location_path.strip()) if location_path and not os.path.exists(location_path): @@ -236,14 +251,24 @@ class Delivery(BaseAction): parent = asset["parent"] parent_mongo_id = parent["custom_attributes"].get(CustAttrIdKey) - if not parent_mongo_id: - # TODO log error (much better) - self.log.warning(( - "Seems like entity <{}> is not synchronized to avalon" - ).format(parent["name"])) - continue + if parent_mongo_id: + parent_mongo_id = ObjectId(parent_mongo_id) + else: + asset_ent = self.db_con.find_one({ + "type": "asset", + "data.ftrackId": parent["id"] + }) + if not asset_ent: + ent_path = "/".join( + [ent["name"] for ent in parent["link"]] + ) + msg = "Not synchronized entities to avalon" + self.report_items[msg].append(ent_path) + self.log.warning("{} <{}>".format(msg, ent_path)) + continue + + parent_mongo_id = asset_ent["_id"] - parent_mongo_id = ObjectId(parent_mongo_id) subset_ent = self.db_con.find_one({ "type": "subset", "parent": parent_mongo_id, @@ -283,6 +308,50 @@ class Delivery(BaseAction): else: anatomy_data["root"] = pipeline.registered_root() + anatomy_filled = anatomy.format(anatomy_data) + test_path = ( + anatomy_filled + .get("delivery", {}) + .get(anatomy_name) + ) + + if not test_path: + msg = ( + "Missing keys in Representation's context" + " for anatomy template \"{}\"." + ).format(anatomy_name) + + all_anatomies = anatomy.format_all(anatomy_data) + result = None + for anatomies in all_anatomies.values(): + for key, temp in anatomies.get("delivery", {}).items(): + if key != anatomy_name: + continue + + result = temp + break + + # TODO log error! - missing keys in anatomy + if result: + missing_keys = [ + key[1] for key in string.Formatter().parse(result) + if key[1] is not None + ] + else: + missing_keys = ["unknown"] + + keys = ", ".join(missing_keys) + sub_msg = ( + "Representation: {}
- Missing keys: \"{}\"
" + ).format(str(repre["_id"]), keys) + self.report_items[msg].append(sub_msg) + self.log.warning( + "{} Representation: \"{}\" Filled: <{}>".format( + msg, str(repre["_id"]), str(result) + ) + ) + continue + # Get source repre path repre_path = self.path_from_represenation(repre) # TODO add backup solution where root of path from component @@ -300,17 +369,13 @@ class Delivery(BaseAction): self.db_con.uninstall() - return True + return self.report() def process_single_file( self, repre_path, anatomy, anatomy_name, anatomy_data ): anatomy_filled = anatomy.format(anatomy_data) - delivery_path = anatomy_filled.get("delivery", {}).get(anatomy_name) - if not delivery_path: - # TODO log error! - missing keys in anatomy - return - + delivery_path = anatomy_filled["delivery"][anatomy_name] delivery_folder = os.path.dirname(delivery_path) if not os.path.exists(delivery_folder): os.makedirs(delivery_folder) @@ -321,9 +386,6 @@ class Delivery(BaseAction): self, repre_path, anatomy, anatomy_name, anatomy_data ): dir_path, file_name = os.path.split(repre_path) - if not os.path.exists(dir_path): - # TODO log if folder don't exist - return base_name, ext = os.path.splitext(file_name) file_name_items = None @@ -334,7 +396,9 @@ class Delivery(BaseAction): file_name_items = base_name.split("%") if not file_name_items: - # TODO log if file does not exists + msg = "Source file was not found" + self.report_items[msg].append(repre_path) + self.log.warning("{} <{}>".format(msg, repre_path)) return src_collections, remainder = clique.assemble(os.listdir(dir_path)) @@ -352,15 +416,15 @@ class Delivery(BaseAction): if src_collection is None: # TODO log error! + msg = "Source collection of files was not found" + self.report_items[msg].append(repre_path) + self.log.warning("{} <{}>".format(msg, repre_path)) return anatomy_data["frame"] = "<>" anatomy_filled = anatomy.format(anatomy_data) - delivery_path = anatomy_filled.get("delivery", {}).get(anatomy_name) - if not delivery_path: - # TODO log error! - missing keys in anatomy - return + delivery_path = anatomy_filled["delivery"][anatomy_name] delivery_folder = os.path.dirname(delivery_path) dst_head, dst_tail = delivery_path.split("<>") dst_padding = src_collection.padding @@ -418,6 +482,40 @@ class Delivery(BaseAction): except OSError: shutil.copyfile(src_path, dst_path) + def report(self): + items = [] + title = "Delivery report" + for msg, _items in self.report_items.items(): + if not _items: + continue + + if items: + items.append({"type": "label", "value": "---"}) + + items.append({ + "type": "label", + "value": "# {}".format(msg) + }) + if isinstance(_items, str): + _items = [_items] + items.append({ + "type": "label", + "value": '

{}

'.format("
".join(_items)) + }) + + if not items: + return { + "success": True, + "message": "Delivery Finished" + } + + return { + "items": items, + "title": title, + "success": False, + "message": "Delivery Finished" + } + def register(session, plugins_presets={}): '''Register plugin. Called when used as an plugin.''' From bf24580b6f87ded4672661fb055a85ba92fd8b78 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 8 Jan 2020 19:31:58 +0100 Subject: [PATCH 6/7] fix root path --- pype/ftrack/actions/action_delivery.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/ftrack/actions/action_delivery.py b/pype/ftrack/actions/action_delivery.py index e698c371e1..9edb7a5964 100644 --- a/pype/ftrack/actions/action_delivery.py +++ b/pype/ftrack/actions/action_delivery.py @@ -171,7 +171,7 @@ class Delivery(BaseAction): "NOTE: It is possible to replace `root` key in anatomy." ) }) - + items.append({ "type": "text", "name": "__location_path__", @@ -306,7 +306,7 @@ class Delivery(BaseAction): if location_path: anatomy_data["root"] = location_path else: - anatomy_data["root"] = pipeline.registered_root() + anatomy_data["root"] = os.environ.get("AVALON_PROJECTS") or "" anatomy_filled = anatomy.format(anatomy_data) test_path = ( From 271a935ee754672d1b34592e86db7ca3b0f24360 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Sat, 11 Jan 2020 14:11:04 +0100 Subject: [PATCH 7/7] fixes to getting the path --- pype/ftrack/actions/action_delivery.py | 58 ++++++++++++++++---------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/pype/ftrack/actions/action_delivery.py b/pype/ftrack/actions/action_delivery.py index 9edb7a5964..afd20d12d1 100644 --- a/pype/ftrack/actions/action_delivery.py +++ b/pype/ftrack/actions/action_delivery.py @@ -231,14 +231,16 @@ class Delivery(BaseAction): "message": "Not selected components to deliver." } - location_path = os.path.normpath(location_path.strip()) - if location_path and not os.path.exists(location_path): - return { - "success": False, - "message": ( - "Entered location path does not exists. \"{}\"" - ).format(location_path) - } + location_path = location_path.strip() + if location_path: + location_path = os.path.normpath(location_path) + if not os.path.exists(location_path): + return { + "success": False, + "message": ( + "Entered location path does not exists. \"{}\"" + ).format(location_path) + } self.db_con.install() self.db_con.Session["AVALON_PROJECT"] = project_name @@ -299,14 +301,16 @@ class Delivery(BaseAction): repre = repres_by_name.get(comp_name) repres_to_deliver.append(repre) + if not location_path: + location_path = os.environ.get("AVALON_PROJECTS") or "" + + print(location_path) + anatomy = Anatomy(project_name) for repre in repres_to_deliver: # Get destination repre path anatomy_data = copy.deepcopy(repre["context"]) - if location_path: - anatomy_data["root"] = location_path - else: - anatomy_data["root"] = os.environ.get("AVALON_PROJECTS") or "" + anatomy_data["root"] = location_path anatomy_filled = anatomy.format(anatomy_data) test_path = ( @@ -353,11 +357,15 @@ class Delivery(BaseAction): continue # Get source repre path + frame = repre['context'].get('frame') + + if frame: + repre["context"]["frame"] = len(str(frame)) * "#" + repre_path = self.path_from_represenation(repre) # TODO add backup solution where root of path from component # is repalced with AVALON_PROJECTS root - - if repre_path and os.path.exists(repre_path): + if not frame: self.process_single_file( repre_path, anatomy, anatomy_name, anatomy_data ) @@ -385,7 +393,7 @@ class Delivery(BaseAction): def process_sequence( self, repre_path, anatomy, anatomy_name, anatomy_data ): - dir_path, file_name = os.path.split(repre_path) + dir_path, file_name = os.path.split(str(repre_path)) base_name, ext = os.path.splitext(file_name) file_name_items = None @@ -421,12 +429,15 @@ class Delivery(BaseAction): self.log.warning("{} <{}>".format(msg, repre_path)) return - anatomy_data["frame"] = "<>" + frame_indicator = "@####@" + + anatomy_data["frame"] = frame_indicator anatomy_filled = anatomy.format(anatomy_data) delivery_path = anatomy_filled["delivery"][anatomy_name] + print(delivery_path) delivery_folder = os.path.dirname(delivery_path) - dst_head, dst_tail = delivery_path.split("<>") + dst_head, dst_tail = delivery_path.split(frame_indicator) dst_padding = src_collection.padding dst_collection = clique.Collection( head=dst_head, @@ -469,10 +480,11 @@ class Delivery(BaseAction): # Template references unavailable data return None - if os.path.exists(path): - return os.path.normpath(path) + return os.path.normpath(path) def copy_file(self, src_path, dst_path): + if os.path.exists(dst_path): + return try: filelink.create( src_path, @@ -496,11 +508,15 @@ class Delivery(BaseAction): "type": "label", "value": "# {}".format(msg) }) - if isinstance(_items, str): + if not isinstance(_items, (list, tuple)): _items = [_items] + __items = [] + for item in _items: + __items.append(str(item)) + items.append({ "type": "label", - "value": '

{}

'.format("
".join(_items)) + "value": '

{}

'.format("
".join(__items)) }) if not items: