From 5c06f10bab03bf35584411261b5d9aacbc429d43 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 12 Jan 2021 13:51:29 +0100 Subject: [PATCH 01/24] feat(nuke): encoding env var in filepath knob --- setup/nuke/nuke_path/init.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/setup/nuke/nuke_path/init.py b/setup/nuke/nuke_path/init.py index 0ea5d1ad7dd..091ad63bafd 100644 --- a/setup/nuke/nuke_path/init.py +++ b/setup/nuke/nuke_path/init.py @@ -1,2 +1,16 @@ +import os +import nuke + # default write mov nuke.knobDefault('Write.mov.colorspace', 'sRGB') + + +def filter_envvars_in_filepath(filename): + """Expand variables in path such as ``$PROJECT_ROOT``. + """ + expanded_path = os.path.expandvars(filename) + return expanded_path + + +# register callback +nuke.addFilenameFilter(filter_envvars_in_filepath) From dcb0a56e59693c6956d432bec96961cad0e6561e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 13 Jan 2021 17:38:49 +0100 Subject: [PATCH 02/24] feat(global, ftrack): event for packaging jobs for artists --- pype/lib/__init__.py | 6 +- pype/lib/packaging.py | 231 ++++++++++++++++++ .../ftrack/events/event_pack_workfiles.py | 156 ++++++++++++ 3 files changed, 392 insertions(+), 1 deletion(-) create mode 100644 pype/lib/packaging.py create mode 100644 pype/modules/ftrack/events/event_pack_workfiles.py diff --git a/pype/lib/__init__.py b/pype/lib/__init__.py index 03cab2aad2c..c90787dfc45 100644 --- a/pype/lib/__init__.py +++ b/pype/lib/__init__.py @@ -46,6 +46,8 @@ from .ffmpeg_utils import ffprobe_streams +from .packaging import make_workload_package + __all__ = [ "get_avalon_database", "set_io_database", @@ -82,5 +84,7 @@ "ffprobe_streams", "source_hash", - "_subprocess" + "_subprocess", + + "make_workload_package" ] diff --git a/pype/lib/packaging.py b/pype/lib/packaging.py new file mode 100644 index 00000000000..bdbc20fdd46 --- /dev/null +++ b/pype/lib/packaging.py @@ -0,0 +1,231 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import os +import sys +import re +import clique +import shutil +from pypeapp import Anatomy +import zipfile +import logging +log = logging.getLogger(__name__) + +_self = sys.modules[__name__] +_self.project_name = None +_self.anatomy = None + +extension = ".nk" +path_nk = r"C:/projects/jtest03dev/shots/sq01/mainsq01sh010/work/compositing/jt3d_mainsq01sh010_compositing_v001.nk" + + +def _zipdir(path, ziph): + # ziph is zipfile handle + for root, dirs, files in os.walk(path): + for file in files: + ziph.write(os.path.join(root, file), os.path.relpath( + os.path.join(root, file), os.path.join(path, '..'))) + + +def _sync_files_to_package(sync_paths): + for paths in sync_paths: + for file in paths["files"]: + os.makedirs(paths["to_dir"], mode=0o777, exist_ok=True) + src_path = os.path.normpath(os.path.join(paths["from_dir"], file)) + dst_path = os.path.normpath(os.path.join(paths["to_dir"], file)) + shutil.copy(src_path, dst_path, follow_symlinks=True) + log.debug( + "File `{}` coppied to `{}`...".format(src_path, dst_path)) + + +def _save_text_lines_to_file(filepath, lines): + """ + Droping lines into filepath as file. + + Args: + filepath (str): desired path to file + lines (list): list with lines of string with `\n` at the end + + """ + if os.path.exists(filepath): + os.remove(filepath) + with open(filepath, 'a') as filepath_file: + filepath_file.writelines(lines) + filepath_file.close() + + +def _get_packaging_path(anatomy, anatomy_data): + anatomy_filled = anatomy.format_all(anatomy_data) + + delivery = anatomy_filled.get("delivery") + assert delivery, KeyError( + "`{}` project override is not having available key " + "`delivery` template".format( + _self.project_name + ) + ) + packaging_template = delivery.get("packaging") + assert packaging_template, KeyError( + "`{}` project's override `delivery` is missing " + "`packaging` key template".format( + _self.project_name + ) + ) + return anatomy_filled["delivery"]["packaging"] + + +def _swap_root_to_package(path, destination_root): + success, rootless_path = _self.anatomy.find_root_template_from_path( + path) + + assert success, ValueError( + "{}: Project's roots were not found in path: {}".format( + _self.project_name, path + ) + ) + return rootless_path.format(root=destination_root) + + +def _collect_files(filepath): + files = list() + dirpath = os.path.dirname(filepath) + basename = os.path.basename(filepath) + # if hashes then get collection of files + if "#" in filepath or "%" in filepath: + head = basename.split("%") + if len(head) == 1: + head = basename.split("#") + + collections, reminders = clique.assemble( + [f for f in os.listdir(dirpath) + if head[0] in f]) + collection = collections.pop() + + # add collection to files for coppying + files = list(collection) + else: + files.append(basename) + return files + + +def make_workload_package(avalon_session): + _self.project_name = avalon_session["AVALON_PROJECT"] + _self.anatomy = Anatomy(_self.project_name) + log.warning(_self.anatomy.root_environments()) + anatomy_data = { + "project": {"name": _self.project_name}, + "task": avalon_session["AVALON_TASK"], + "asset": avalon_session["AVALON_ASSET"], + "root": _self.anatomy.roots, + "ext": "zip" + } + log.warning(anatomy_data) + # get packaging zip path + zip_package_path = _get_packaging_path(_self.anatomy, anatomy_data) + dir_package_path = os.path.splitext(zip_package_path)[0] + os.makedirs(dir_package_path, mode=0o777, exist_ok=True) + log.debug(dir_package_path) + + # potentially used alternative file if any change had been made + new_workfile_suffix = "OutsideResourcesIncuded" + nk_file_altered = ''.join([ + path_nk.replace(extension, ''), + "_", new_workfile_suffix, extension]) + save_altered_nkfile = False + + resources_path = os.path.join( + os.path.dirname(path_nk), "resources" + ) + + # nuke path for zip package + package_path_nk = _swap_root_to_package(path_nk, dir_package_path) + log.debug(package_path_nk) + + pattern = re.compile("^(?:\\s+file\\s)(?P.*)", re.VERBOSE) + sync_paths = list() + done_paths = list() + with open(path_nk, "r") as nk_file: + nk_file_lines = list(nk_file.readlines()) + nk_file_lines_new = nk_file_lines.copy() + for line_index, line_string in enumerate(nk_file_lines): + for result in pattern.finditer(line_string): + if "path" in result.groupdict().keys(): + from_path = result.groupdict()["path"] + # try env replace + try: + env_path = _self.anatomy.replace_root_with_env_key( + from_path, "\\[getenv {}]") + except ValueError: + # excepth test if path exists | except script fail + dirpath = os.path.dirname(from_path) + basename = os.path.basename(from_path) + + if os.path.exists(dirpath): + move_files = _collect_files(from_path) + + # if resources folder not existing > create one + os.makedirs( + resources_path, mode=0o777, exist_ok=True) + + # copy all appropriate data to `workfile/resources` + for file in move_files: + copy_from = os.path.join(dirpath, file) + copy_to = os.path.join(resources_path, file) + shutil.copy(copy_from, copy_to) + + # create new path to resources as new original + new_from_path = os.path.join( + resources_path, basename).replace("\\", "/") + + # change path in original `nk_file_lines` + nk_file_lines[line_index] = nk_file_lines[ + line_index].replace(from_path, new_from_path) + + # trigger after saving as new subversion + save_altered_nkfile = True + + env_path = _self.anatomy.replace_root_with_env_key( + new_from_path, "\\[getenv {}]") + from_path = new_from_path + else: + IOError( + "Used path in script does not " + "exist: {}".format( + from_path)) + + to_path = _swap_root_to_package( + from_path, dir_package_path) + + # replace path in .nk file wich will be delivered + # with package + nk_file_lines_new[line_index] = nk_file_lines[ + line_index].replace( + from_path, "\"{}\"".format(env_path)) + + # skip path if it had been already used + if from_path in done_paths: + continue + + # save paths for later processing + sync_paths.append({ + "from_dir": os.path.dirname(from_path), + "to_dir": os.path.dirname(to_path), + "files": _collect_files(from_path) + }) + done_paths.append(from_path) + + # copy all files from sync_paths + _sync_files_to_package(sync_paths) + + # save nk file for alteret original + if save_altered_nkfile: + _save_text_lines_to_file(nk_file_altered, nk_file_lines) + + # save nk file to package + _save_text_lines_to_file(package_path_nk, nk_file_lines_new) + + zipf = zipfile.ZipFile(zip_package_path, 'w', zipfile.ZIP_DEFLATED) + _zipdir(dir_package_path, zipf) + zipf.close() + + log.info(f"Zip file was collected to: `{zip_package_path}`") diff --git a/pype/modules/ftrack/events/event_pack_workfiles.py b/pype/modules/ftrack/events/event_pack_workfiles.py new file mode 100644 index 00000000000..556c1c6458c --- /dev/null +++ b/pype/modules/ftrack/events/event_pack_workfiles.py @@ -0,0 +1,156 @@ +import os +import sys +import json +import tempfile +import datetime +import traceback +from pype.modules.ftrack import ServerAction +from avalon.api import AvalonMongoDB +import pype + + +class PackWorkfilesAction(ServerAction): + ignore_me = False + identifier = "pack.workfiles.server" + label = "Pype Admin" + variant = "- Pack workfiles" + db_con = AvalonMongoDB() + + def discover(self, session, entities, event): + """Defines if action will be discovered for a selection.""" + allowed = ["task"] + if entities[0].entity_type.lower() not in allowed: + return False + return True + + def launch(self, session, entities, event): + """Workfile pack action trigger callback.""" + # Get user for job entity + user_id = event["source"]["user"]["id"] + user = session.query("User where id is \"{}\"".format(user_id)).one() + + # Create job + job_data = { + "description": "Preparing status changes report." + } + job = session.create("Job", { + "user": user, + "status": "running", + "data": json.dumps(job_data) + }) + session.commit() + + # Run action logic and handle errors + try: + self.pack_workfiles(session, entities, event) + + except Exception: + self.handle_exception(job, session, sys.exc_info(), event) + return + + job["status"] = "done" + session.commit() + + def pack_workfiles(self, session, entities, event): + project_entity = self.get_project_from_entity(entities[0]) + project_name = project_entity["full_name"] + self.db_con.install() + self.db_con.Session["AVALON_PROJECT"] = project_name + project_doc = self.db_con.find_one({"type": "project"}) + + if not project_doc: + Exception(( + "Didn't found project \"{}\" in avalon." + ).format(project_name)) + + allowed_task_names = ["compositing"] + for entity in entities: + if entity['name'] not in allowed_task_names: + self.log.warning(f"Not allowed task name: `{entity['name']}`!") + continue + self.db_con.Session["AVALON_ASSET"] = entity["parent"]["name"] + self.db_con.Session["AVALON_TASK"] = entity['name'] + pype.lib.make_workload_package(self.db_con.Session) + self.db_con.uninstall() + + def add_component_to_job(self, job, session, filepath, basename=None): + """Add filepath as downloadable component to job. + + Args: + job (JobEntity): Entity of job where file should be able to + download. + session (Session): Ftrack session which was used to query/create + entered job. + filepath (str): Path to file which should be added to job. + basename (str): Defines name of file which will be downloaded on + user's side. Must be without extension otherwise extension will + be duplicated in downloaded name. Basename from entered path + used when not entered. + """ + # Make sure session's locations are configured + session._configure_locations() + # Query `ftrack.server` location where component will be stored + location = session.query( + "Location where name is \"ftrack.server\"" + ).one() + + # Use filename as basename if not entered (must be without extension) + if basename is None: + basename = os.path.splitext( + os.path.basename(filepath) + )[0] + + component = session.create_component( + filepath, + data={"name": basename}, + location=location + ) + session.create( + "JobComponent", + { + "component_id": component["id"], + "job_id": job["id"] + } + ) + session.commit() + + def handle_exception(self, job, session, exc_info, event): + """Handle unexpected crash of action processing.""" + job_data = { + "description": ( + "Workfiles preparation crashed! (Click to download traceback)" + ) + } + job["data"] = json.dumps(job_data) + job["status"] = "failed" + + # Create temp file where traceback will be stored + temp_obj = tempfile.NamedTemporaryFile( + mode="w", prefix="pype_ftrack_", suffix=".txt", delete=False + ) + temp_obj.close() + temp_filepath = temp_obj.name + + # Store traceback to file + result = traceback.format_exception(*exc_info) + with open(temp_filepath, "w") as temp_file: + temp_file.write("".join(result)) + + # Upload file with traceback to ftrack server and add it to job + component_basename = "{}_{}".format( + self.__class__.__name__, + datetime.datetime.now().strftime("%y-%m-%d-%H%M") + ) + self.add_component_to_job( + job, session, temp_filepath, component_basename + ) + # Delete temp file + os.remove(temp_filepath) + + msg = "Failed to prepare notes." + self.log.warning(msg, exc_info=True) + self.show_message(event, msg, False) + + +def register(session, plugins_presets={}): + PackWorkfilesAction(session, plugins_presets).register() From 7bca7fbb2bf614ee53869b77edac742afd663235 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 13 Jan 2021 17:42:26 +0100 Subject: [PATCH 03/24] feat(nuke): clean obsolete code --- pype/hosts/nuke/__init__.py | 1 - pype/hosts/nuke/lib.py | 2 +- setup/nuke/nuke_path/init.py | 13 +------------ setup/nuke/nuke_path/menu.py | 10 ++-------- 4 files changed, 4 insertions(+), 22 deletions(-) diff --git a/pype/hosts/nuke/__init__.py b/pype/hosts/nuke/__init__.py index 787f69f6351..bc17b1e2b61 100644 --- a/pype/hosts/nuke/__init__.py +++ b/pype/hosts/nuke/__init__.py @@ -1,7 +1,6 @@ import os import sys import logging - import nuke from avalon import api as avalon diff --git a/pype/hosts/nuke/lib.py b/pype/hosts/nuke/lib.py index 50b9697d8e3..3f911f0dde8 100644 --- a/pype/hosts/nuke/lib.py +++ b/pype/hosts/nuke/lib.py @@ -840,7 +840,7 @@ def reset_frame_range_handles(self): handle_start = data["handleStart"] handle_end = data["handleEnd"] - fps = data["fps"] + fps = float(data["fps"]) frame_start = int(data["frameStart"]) - handle_start frame_end = int(data["frameEnd"]) + handle_end diff --git a/setup/nuke/nuke_path/init.py b/setup/nuke/nuke_path/init.py index 091ad63bafd..b864715dc68 100644 --- a/setup/nuke/nuke_path/init.py +++ b/setup/nuke/nuke_path/init.py @@ -1,16 +1,5 @@ -import os import nuke -# default write mov -nuke.knobDefault('Write.mov.colorspace', 'sRGB') - - -def filter_envvars_in_filepath(filename): - """Expand variables in path such as ``$PROJECT_ROOT``. - """ - expanded_path = os.path.expandvars(filename) - return expanded_path - # register callback -nuke.addFilenameFilter(filter_envvars_in_filepath) +nuke.knobDefault('Write.mov.colorspace', 'sRGB') diff --git a/setup/nuke/nuke_path/menu.py b/setup/nuke/nuke_path/menu.py index d9341045a93..d12c95a9135 100644 --- a/setup/nuke/nuke_path/menu.py +++ b/setup/nuke/nuke_path/menu.py @@ -1,23 +1,17 @@ -import os -import sys +import nuke +from pype.api import Logger import KnobScripter from pype.hosts.nuke.lib import ( - writes_version_sync, on_script_load, check_inventory_versions ) -import nuke -from pype.api import Logger - log = Logger().get_logger(__name__, "nuke") -# nuke.addOnScriptSave(writes_version_sync) nuke.addOnScriptSave(on_script_load) nuke.addOnScriptLoad(check_inventory_versions) nuke.addOnScriptSave(check_inventory_versions) -# nuke.addOnScriptSave(writes_version_sync) log.info('Automatic syncing of write file knob to script version') From 1a391c3523da1265f6eba2dff67874d54c4bc146 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 13 Jan 2021 17:53:41 +0100 Subject: [PATCH 04/24] don't use `lib.packaging` as singleton --- pype/lib/packaging.py | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/pype/lib/packaging.py b/pype/lib/packaging.py index bdbc20fdd46..8b859916940 100644 --- a/pype/lib/packaging.py +++ b/pype/lib/packaging.py @@ -11,10 +11,6 @@ import logging log = logging.getLogger(__name__) -_self = sys.modules[__name__] -_self.project_name = None -_self.anatomy = None - extension = ".nk" path_nk = r"C:/projects/jtest03dev/shots/sq01/mainsq01sh010/work/compositing/jt3d_mainsq01sh010_compositing_v001.nk" @@ -61,26 +57,26 @@ def _get_packaging_path(anatomy, anatomy_data): assert delivery, KeyError( "`{}` project override is not having available key " "`delivery` template".format( - _self.project_name + anatomy.project_name ) ) packaging_template = delivery.get("packaging") assert packaging_template, KeyError( "`{}` project's override `delivery` is missing " "`packaging` key template".format( - _self.project_name + anatomy.project_name ) ) return anatomy_filled["delivery"]["packaging"] -def _swap_root_to_package(path, destination_root): - success, rootless_path = _self.anatomy.find_root_template_from_path( +def _swap_root_to_package(anatomy, path, destination_root): + success, rootless_path = anatomy.find_root_template_from_path( path) assert success, ValueError( "{}: Project's roots were not found in path: {}".format( - _self.project_name, path + anatomy.project_name, path ) ) return rootless_path.format(root=destination_root) @@ -108,20 +104,18 @@ def _collect_files(filepath): return files -def make_workload_package(avalon_session): - _self.project_name = avalon_session["AVALON_PROJECT"] - _self.anatomy = Anatomy(_self.project_name) - log.warning(_self.anatomy.root_environments()) +def make_workload_package(avalon_session, anatomy, project_name): + log.warning(anatomy.root_environments()) anatomy_data = { - "project": {"name": _self.project_name}, + "project": {"name": project_name}, "task": avalon_session["AVALON_TASK"], "asset": avalon_session["AVALON_ASSET"], - "root": _self.anatomy.roots, + "root": anatomy.roots, "ext": "zip" } log.warning(anatomy_data) # get packaging zip path - zip_package_path = _get_packaging_path(_self.anatomy, anatomy_data) + zip_package_path = _get_packaging_path(anatomy, anatomy_data) dir_package_path = os.path.splitext(zip_package_path)[0] os.makedirs(dir_package_path, mode=0o777, exist_ok=True) log.debug(dir_package_path) @@ -138,7 +132,7 @@ def make_workload_package(avalon_session): ) # nuke path for zip package - package_path_nk = _swap_root_to_package(path_nk, dir_package_path) + package_path_nk = _swap_root_to_package(anatomy, path_nk, dir_package_path) log.debug(package_path_nk) pattern = re.compile("^(?:\\s+file\\s)(?P.*)", re.VERBOSE) @@ -153,7 +147,7 @@ def make_workload_package(avalon_session): from_path = result.groupdict()["path"] # try env replace try: - env_path = _self.anatomy.replace_root_with_env_key( + env_path = anatomy.replace_root_with_env_key( from_path, "\\[getenv {}]") except ValueError: # excepth test if path exists | except script fail @@ -184,7 +178,7 @@ def make_workload_package(avalon_session): # trigger after saving as new subversion save_altered_nkfile = True - env_path = _self.anatomy.replace_root_with_env_key( + env_path = anatomy.replace_root_with_env_key( new_from_path, "\\[getenv {}]") from_path = new_from_path else: @@ -194,7 +188,7 @@ def make_workload_package(avalon_session): from_path)) to_path = _swap_root_to_package( - from_path, dir_package_path) + anatomy, from_path, dir_package_path) # replace path in .nk file wich will be delivered # with package From 27f38d07ee69609a58bc4f22f18b32801930b93d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 13 Jan 2021 17:57:06 +0100 Subject: [PATCH 05/24] save some indentation --- pype/lib/packaging.py | 137 +++++++++++++++++++++--------------------- 1 file changed, 70 insertions(+), 67 deletions(-) diff --git a/pype/lib/packaging.py b/pype/lib/packaging.py index 8b859916940..4e7ba6e00a6 100644 --- a/pype/lib/packaging.py +++ b/pype/lib/packaging.py @@ -140,73 +140,76 @@ def make_workload_package(avalon_session, anatomy, project_name): done_paths = list() with open(path_nk, "r") as nk_file: nk_file_lines = list(nk_file.readlines()) - nk_file_lines_new = nk_file_lines.copy() - for line_index, line_string in enumerate(nk_file_lines): - for result in pattern.finditer(line_string): - if "path" in result.groupdict().keys(): - from_path = result.groupdict()["path"] - # try env replace - try: - env_path = anatomy.replace_root_with_env_key( - from_path, "\\[getenv {}]") - except ValueError: - # excepth test if path exists | except script fail - dirpath = os.path.dirname(from_path) - basename = os.path.basename(from_path) - - if os.path.exists(dirpath): - move_files = _collect_files(from_path) - - # if resources folder not existing > create one - os.makedirs( - resources_path, mode=0o777, exist_ok=True) - - # copy all appropriate data to `workfile/resources` - for file in move_files: - copy_from = os.path.join(dirpath, file) - copy_to = os.path.join(resources_path, file) - shutil.copy(copy_from, copy_to) - - # create new path to resources as new original - new_from_path = os.path.join( - resources_path, basename).replace("\\", "/") - - # change path in original `nk_file_lines` - nk_file_lines[line_index] = nk_file_lines[ - line_index].replace(from_path, new_from_path) - - # trigger after saving as new subversion - save_altered_nkfile = True - - env_path = anatomy.replace_root_with_env_key( - new_from_path, "\\[getenv {}]") - from_path = new_from_path - else: - IOError( - "Used path in script does not " - "exist: {}".format( - from_path)) - - to_path = _swap_root_to_package( - anatomy, from_path, dir_package_path) - - # replace path in .nk file wich will be delivered - # with package - nk_file_lines_new[line_index] = nk_file_lines[ - line_index].replace( - from_path, "\"{}\"".format(env_path)) - - # skip path if it had been already used - if from_path in done_paths: - continue - - # save paths for later processing - sync_paths.append({ - "from_dir": os.path.dirname(from_path), - "to_dir": os.path.dirname(to_path), - "files": _collect_files(from_path) - }) - done_paths.append(from_path) + + nk_file_lines_new = nk_file_lines.copy() + for line_index, line_string in enumerate(nk_file_lines): + for result in pattern.finditer(line_string): + if "path" not in result.groupdict().keys(): + continue + + from_path = result.groupdict()["path"] + # try env replace + try: + env_path = anatomy.replace_root_with_env_key( + from_path, "\\[getenv {}]") + except ValueError: + # excepth test if path exists | except script fail + dirpath = os.path.dirname(from_path) + basename = os.path.basename(from_path) + + if not os.path.exists(dirpath): + IOError( + "Used path in script does not exist: {}".format( + from_path + ) + ) + move_files = _collect_files(from_path) + + # if resources folder not existing > create one + os.makedirs(resources_path, mode=0o777, exist_ok=True) + + # copy all appropriate data to `workfile/resources` + for file in move_files: + copy_from = os.path.join(dirpath, file) + copy_to = os.path.join(resources_path, file) + shutil.copy(copy_from, copy_to) + + # create new path to resources as new original + new_from_path = os.path.join( + resources_path, basename + ).replace("\\", "/") + + # change path in original `nk_file_lines` + nk_file_lines[line_index] = nk_file_lines[ + line_index].replace(from_path, new_from_path) + + # trigger after saving as new subversion + save_altered_nkfile = True + + env_path = anatomy.replace_root_with_env_key( + new_from_path, "\\[getenv {}]") + from_path = new_from_path + + to_path = _swap_root_to_package( + anatomy, from_path, dir_package_path) + + # replace path in .nk file wich will be delivered + # with package + nk_file_lines_new[line_index] = nk_file_lines[ + line_index].replace( + from_path, "\"{}\"".format(env_path)) + + # skip path if it had been already used + if from_path in done_paths: + continue + + # save paths for later processing + sync_paths.append({ + "from_dir": os.path.dirname(from_path), + "to_dir": os.path.dirname(to_path), + "files": _collect_files(from_path) + }) + done_paths.append(from_path) # copy all files from sync_paths _sync_files_to_package(sync_paths) From 2bc1e5edc3c596e90865d9e797953475c3ece481 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 13 Jan 2021 17:58:52 +0100 Subject: [PATCH 06/24] renamed event to action --- .../events/{event_pack_workfiles.py => action_pack_workfiles.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pype/modules/ftrack/events/{event_pack_workfiles.py => action_pack_workfiles.py} (100%) diff --git a/pype/modules/ftrack/events/event_pack_workfiles.py b/pype/modules/ftrack/events/action_pack_workfiles.py similarity index 100% rename from pype/modules/ftrack/events/event_pack_workfiles.py rename to pype/modules/ftrack/events/action_pack_workfiles.py From d0da4887c6bebe34c1e4ad30e24311630e13c524 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 13 Jan 2021 19:40:34 +0100 Subject: [PATCH 07/24] `make_workload_package` has different attributes and added `make_workload_package_for_tasks` function --- pype/lib/packaging.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/pype/lib/packaging.py b/pype/lib/packaging.py index 4e7ba6e00a6..e0f1a9758b3 100644 --- a/pype/lib/packaging.py +++ b/pype/lib/packaging.py @@ -11,8 +11,6 @@ import logging log = logging.getLogger(__name__) -extension = ".nk" -path_nk = r"C:/projects/jtest03dev/shots/sq01/mainsq01sh010/work/compositing/jt3d_mainsq01sh010_compositing_v001.nk" def _zipdir(path, ziph): @@ -71,8 +69,7 @@ def _get_packaging_path(anatomy, anatomy_data): def _swap_root_to_package(anatomy, path, destination_root): - success, rootless_path = anatomy.find_root_template_from_path( - path) + success, rootless_path = anatomy.find_root_template_from_path(path) assert success, ValueError( "{}: Project's roots were not found in path: {}".format( @@ -104,7 +101,13 @@ def _collect_files(filepath): return files -def make_workload_package(avalon_session, anatomy, project_name): + + +def make_workload_package_for_tasks( + project_doc, asset_docs_by_id, task_names_by_asset_id +): + # Create anatomy object + anatomy = Anatomy(project_doc["name"]) log.warning(anatomy.root_environments()) anatomy_data = { "project": {"name": project_name}, @@ -114,14 +117,23 @@ def make_workload_package(avalon_session, anatomy, project_name): "ext": "zip" } log.warning(anatomy_data) + + +def make_workload_package(anatomy, fill_data, path_nk): + packaging_data = copy.deepcopy(fill_data) + # Set extension to zip + packaging_data["ext"] = "zip" + log.warning(packaging_data) + # get packaging zip path - zip_package_path = _get_packaging_path(anatomy, anatomy_data) + zip_package_path = _get_packaging_path(anatomy, packaging_data) dir_package_path = os.path.splitext(zip_package_path)[0] os.makedirs(dir_package_path, mode=0o777, exist_ok=True) log.debug(dir_package_path) # potentially used alternative file if any change had been made new_workfile_suffix = "OutsideResourcesIncuded" + extension = os.path.splitext(path_nk)[1] nk_file_altered = ''.join([ path_nk.replace(extension, ''), "_", new_workfile_suffix, extension]) From da6fff610a1260f658670bf5ccf15ffce932c4ff Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 13 Jan 2021 19:41:08 +0100 Subject: [PATCH 08/24] added `prepare_data` function that collects all required data for packaging --- pype/lib/packaging.py | 47 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/pype/lib/packaging.py b/pype/lib/packaging.py index e0f1a9758b3..f8313523389 100644 --- a/pype/lib/packaging.py +++ b/pype/lib/packaging.py @@ -101,6 +101,53 @@ def _collect_files(filepath): return files +def prepare_data( + anatomy, + project_doc, + asset_docs_by_id, + task_names_by_asset_id, + host_name +): + from avalon.api import ( + HOST_WORKFILE_EXTENSIONS, + last_workfile_with_version + ) + # Extensions + host_exts = HOST_WORKFILE_EXTENSIONS[host_name] + + output = [] + for asset_id, task_names in task_names_by_asset_id.items(): + asset_doc = asset_docs_by_id[asset_id] + hierarchy = "/".join(asset_doc["data"]["parents"]) + for task_name in task_names: + fill_data = { + "project": { + "name": project_doc["name"], + "code": project_doc["data"].get("code") + }, + "asset": asset_doc["name"], + "hierarchy": hierarchy, + "task": task_name, + "user": getpass.getuser(), + "app": host_name + } + + anatomy_filled = anatomy.format(fill_data) + workdir = anatomy_filled["work"]["folder"] + + file_template = anatomy.templates["work"]["file"] + last_workfile_path, _version = last_workfile_with_version( + workdir, file_template, fill_data, host_exts + ) + # Skip tasks without last workfile + if not last_workfile_path: + continue + + output.append( + (fill_data, last_workfile_path) + ) + + return output def make_workload_package_for_tasks( From 2034d0e17aedf99dc9181f3c0071906720e3a21c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 13 Jan 2021 19:41:48 +0100 Subject: [PATCH 09/24] implemented `make_workfload_package_for_tasks` --- pype/lib/packaging.py | 44 +++++++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/pype/lib/packaging.py b/pype/lib/packaging.py index f8313523389..bd0943adc78 100644 --- a/pype/lib/packaging.py +++ b/pype/lib/packaging.py @@ -4,11 +4,15 @@ import os import sys import re -import clique +import copy +import getpass import shutil -from pypeapp import Anatomy import zipfile import logging +import clique + +from ..api import Anatomy + log = logging.getLogger(__name__) @@ -156,14 +160,34 @@ def make_workload_package_for_tasks( # Create anatomy object anatomy = Anatomy(project_doc["name"]) log.warning(anatomy.root_environments()) - anatomy_data = { - "project": {"name": project_name}, - "task": avalon_session["AVALON_TASK"], - "asset": avalon_session["AVALON_ASSET"], - "root": anatomy.roots, - "ext": "zip" - } - log.warning(anatomy_data) + + # Do some template validations + delivery = anatomy.templates.get("delivery") + assert delivery, KeyError( + "`{}` project override is not having available key " + "`delivery` template".format( + anatomy.project_name + ) + ) + packaging_template = delivery.get("packaging") + assert packaging_template, KeyError( + "`{}` project's override `delivery` is missing " + "`packaging` key template".format( + anatomy.project_name + ) + ) + + # Prepare data needed for processing + host_name = "nuke" + prepared_data = prepare_data( + anatomy, + project_doc, + asset_docs_by_id, + task_names_by_asset_id, + host_name + ) + for fill_data, last_workfile in prepared_data: + make_workload_package(anatomy, fill_data, last_workfile) def make_workload_package(anatomy, fill_data, path_nk): From 82c8aa247b563ce3de0a94cb8f07677e52b4cc5b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 13 Jan 2021 19:42:16 +0100 Subject: [PATCH 10/24] moved AvalonMongoDB objec creation to `__init__` --- pype/modules/ftrack/events/action_pack_workfiles.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pype/modules/ftrack/events/action_pack_workfiles.py b/pype/modules/ftrack/events/action_pack_workfiles.py index 556c1c6458c..7e6ff7b0501 100644 --- a/pype/modules/ftrack/events/action_pack_workfiles.py +++ b/pype/modules/ftrack/events/action_pack_workfiles.py @@ -14,7 +14,10 @@ class PackWorkfilesAction(ServerAction): identifier = "pack.workfiles.server" label = "Pype Admin" variant = "- Pack workfiles" - db_con = AvalonMongoDB() + + def __init__(self, *args, **kwargs): + super(PackWorkfilesAction, self).__init__(*args, **kwargs) + self.db_con = AvalonMongoDB() def discover(self, session, entities, event): """Defines if action will be discovered for a selection.""" From bba19715b0c81f238350f3513906fd53ec3bf28b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 13 Jan 2021 19:43:14 +0100 Subject: [PATCH 11/24] allowed_task_names is class attribute --- pype/modules/ftrack/events/action_pack_workfiles.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pype/modules/ftrack/events/action_pack_workfiles.py b/pype/modules/ftrack/events/action_pack_workfiles.py index 7e6ff7b0501..f08d531a925 100644 --- a/pype/modules/ftrack/events/action_pack_workfiles.py +++ b/pype/modules/ftrack/events/action_pack_workfiles.py @@ -15,6 +15,8 @@ class PackWorkfilesAction(ServerAction): label = "Pype Admin" variant = "- Pack workfiles" + allowed_task_names = ["compositing"] + def __init__(self, *args, **kwargs): super(PackWorkfilesAction, self).__init__(*args, **kwargs) self.db_con = AvalonMongoDB() From 715608169619ea6bc1ad50e5a7f08e989ce2c4fa Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 13 Jan 2021 19:45:58 +0100 Subject: [PATCH 12/24] added proper discover filtering --- pype/modules/ftrack/events/action_pack_workfiles.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pype/modules/ftrack/events/action_pack_workfiles.py b/pype/modules/ftrack/events/action_pack_workfiles.py index f08d531a925..5f17e7ab217 100644 --- a/pype/modules/ftrack/events/action_pack_workfiles.py +++ b/pype/modules/ftrack/events/action_pack_workfiles.py @@ -23,10 +23,10 @@ def __init__(self, *args, **kwargs): def discover(self, session, entities, event): """Defines if action will be discovered for a selection.""" - allowed = ["task"] - if entities[0].entity_type.lower() not in allowed: - return False - return True + for entity in entities: + if entity.entity_type.lower() == "task": + return True + return False def launch(self, session, entities, event): """Workfile pack action trigger callback.""" From 814a140a70e84131f660dfb45cd3f8737887b1fa Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 13 Jan 2021 19:47:43 +0100 Subject: [PATCH 13/24] don't raise an exception but return message with information --- .../ftrack/events/action_pack_workfiles.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/pype/modules/ftrack/events/action_pack_workfiles.py b/pype/modules/ftrack/events/action_pack_workfiles.py index 5f17e7ab217..2173fb318dc 100644 --- a/pype/modules/ftrack/events/action_pack_workfiles.py +++ b/pype/modules/ftrack/events/action_pack_workfiles.py @@ -47,7 +47,7 @@ def launch(self, session, entities, event): # Run action logic and handle errors try: - self.pack_workfiles(session, entities, event) + result = self.prepare_and_pack_workfiles(session, entities) except Exception: self.handle_exception(job, session, sys.exc_info(), event) @@ -56,17 +56,23 @@ def launch(self, session, entities, event): job["status"] = "done" session.commit() - def pack_workfiles(self, session, entities, event): + if result is not None: + return result + return True + + def prepare_and_pack_workfiles(self, session, entities): project_entity = self.get_project_from_entity(entities[0]) project_name = project_entity["full_name"] self.db_con.install() self.db_con.Session["AVALON_PROJECT"] = project_name project_doc = self.db_con.find_one({"type": "project"}) - if not project_doc: - Exception(( - "Didn't found project \"{}\" in avalon." - ).format(project_name)) + return { + "success": False, + "message": "Project \"{}\" was not found in avalon.".format( + project_name + ) + } allowed_task_names = ["compositing"] for entity in entities: From 4f2f91a9eb24b4fb3628e7e5c82167987a68a0ce Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 13 Jan 2021 19:48:12 +0100 Subject: [PATCH 14/24] implemented `find_asset_docs_by_name` fallback --- .../ftrack/events/action_pack_workfiles.py | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/pype/modules/ftrack/events/action_pack_workfiles.py b/pype/modules/ftrack/events/action_pack_workfiles.py index 2173fb318dc..398441b96f6 100644 --- a/pype/modules/ftrack/events/action_pack_workfiles.py +++ b/pype/modules/ftrack/events/action_pack_workfiles.py @@ -84,6 +84,56 @@ def prepare_and_pack_workfiles(self, session, entities): pype.lib.make_workload_package(self.db_con.Session) self.db_con.uninstall() + def find_asset_docs_by_name(self, session, not_found_parent_ids): + """Try to find missing asset documents by their name. + + That may happend when `data.ftrackId` is not filled due to bad + synchronization. This is fallback. Best case scenario is to not get + here. + + Args: + session (ftrack_api.Session): Ftrack session to be able query. + not_found_parent_ids (list): List of ftrack ids that didn't match + any `data.ftrackId` in asset docs. + + Returns: + tuple: Output contain 2 items. (1)Asset documents by ftrack id and + (2) list of ftrack ids which didn't match any name. + """ + parent_entities = session.query( + "select id, name from TypedContext where id in ({})".format( + self.join_query_keys(not_found_parent_ids) + ) + ).all() + parent_ids_by_name = { + parent_entity["name"]: parent_entity["id"] + for parent_entity in parent_entities + } + parent_names = set(parent_ids_by_name.keys()) + asset_docs = self.dbcon.find({ + "type": "asset", + "name": {"$in": list(parent_names)} + }) + + asset_docs_by_ftrack_id = {} + found_asset_names = set() + for asset_doc in asset_docs: + asset_name = asset_doc["name"] + # Store found name + found_asset_names.add(asset_name) + # Store document by ftrack id to be able pair selected tasks + ftrack_id = parent_ids_by_name[asset_name] + asset_docs_by_ftrack_id[ftrack_id] = asset_doc + + # Get not found asset documents + missing_names = parent_names - found_asset_names + # Get ftrack ids of not found assets + missing_parent_ids = [ + parent_ids_by_name[missing_name] + for missing_name in missing_names + ] + return asset_docs_by_ftrack_id, missing_parent_ids + def add_component_to_job(self, job, session, filepath, basename=None): """Add filepath as downloadable component to job. From 2aa3aba2ada4f1f1941d0ca8e1b2bfa6451ace22 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 13 Jan 2021 19:48:55 +0100 Subject: [PATCH 15/24] implemented collecting of entities info for packaging and executing package --- .../ftrack/events/action_pack_workfiles.py | 86 +++++++++++++++++-- 1 file changed, 78 insertions(+), 8 deletions(-) diff --git a/pype/modules/ftrack/events/action_pack_workfiles.py b/pype/modules/ftrack/events/action_pack_workfiles.py index 398441b96f6..020814595c5 100644 --- a/pype/modules/ftrack/events/action_pack_workfiles.py +++ b/pype/modules/ftrack/events/action_pack_workfiles.py @@ -3,10 +3,11 @@ import json import tempfile import datetime +import collections import traceback from pype.modules.ftrack import ServerAction from avalon.api import AvalonMongoDB -import pype +from pype.lib.packaging import make_workload_package_for_tasks class PackWorkfilesAction(ServerAction): @@ -63,7 +64,7 @@ def launch(self, session, entities, event): def prepare_and_pack_workfiles(self, session, entities): project_entity = self.get_project_from_entity(entities[0]) project_name = project_entity["full_name"] - self.db_con.install() + self.db_con.Session["AVALON_PROJECT"] = project_name project_doc = self.db_con.find_one({"type": "project"}) if not project_doc: @@ -74,15 +75,84 @@ def prepare_and_pack_workfiles(self, session, entities): ) } - allowed_task_names = ["compositing"] + # Collect task entities and theird parent ids + task_entities_by_parent_id = collections.defaultdict(list) for entity in entities: - if entity['name'] not in allowed_task_names: + if entity.entity_type.lower() != "task": + continue + + if entity["name"] not in self.allowed_task_names: self.log.warning(f"Not allowed task name: `{entity['name']}`!") continue - self.db_con.Session["AVALON_ASSET"] = entity["parent"]["name"] - self.db_con.Session["AVALON_TASK"] = entity['name'] - pype.lib.make_workload_package(self.db_con.Session) - self.db_con.uninstall() + + parent_id = entity["parent_id"] + task_entities_by_parent_id[parent_id].append(entity) + + parent_ftrack_ids = set(task_entities_by_parent_id.keys()) + + # Query asset documents by collected parent ids + # NOTE variable `asset_docs` can be used only once + asset_docs = self.dbcon.find({ + "type": "asset", + "data.ftrackId": {"$in": list(parent_ftrack_ids)} + }) + + # This variable should be used in future lines + asset_docs_by_id = {} + selected_task_names_by_asset_id = {} + found_parent_ids = set() + for asset_doc in asset_docs: + # Store asset by it's mongo id + asset_id = asset_doc["_id"] + asset_docs_by_id[asset_id] = asset_doc + # Store found ftrack ids + ftrack_id = asset_doc["data"]["ftrackId"] + found_parent_ids.add(ftrack_id) + + # Store task names related to the parent + selected_task_names_by_asset_id[asset_id] = [] + for task_entity in task_entities_by_parent_id[ftrack_id]: + selected_task_names_by_asset_id[asset_id].append( + task_entity["name"] + ) + + # Handle not found entities + not_found_parent_ids = parent_ftrack_ids - found_parent_ids + if not_found_parent_ids: + self.log.warning(( + "There are few asset documents that were" + " not found by Ftrack id. {}".format(not_found_parent_ids) + )) + missing_docs_by_ftrack_id, missing_ftrack_ids = ( + self.find_asset_docs_by_name(session, not_found_parent_ids) + ) + for ftrack_id, asset_doc in missing_docs_by_ftrack_id.items(): + asset_id = asset_doc["_id"] + asset_docs_by_id[asset_id] = asset_doc + for task_entity in task_entities_by_parent_id[ftrack_id]: + selected_task_names_by_asset_id[asset_id].append( + task_entity["name"] + ) + + # Should we say to user that he need to synchronize? + # - or tell him which tasks were not prepared? + self.log.warning(( + "There are still some parents without asset document." + " Ftrack ids: {}" + ).format(self.join_query_keys(missing_ftrack_ids))) + + if not asset_docs_by_id: + return { + "success": False, + "message": ( + "Didn't found documents in pipeline database. Try to sync" + " the project first." + ) + } + + make_workload_package_for_tasks( + project_doc, asset_docs_by_id, selected_task_names_by_asset_id + ) def find_asset_docs_by_name(self, session, not_found_parent_ids): """Try to find missing asset documents by their name. From 76fda6ba1f2ba55d5b219e73e38e9a7fb861fe3f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 13 Jan 2021 19:56:07 +0100 Subject: [PATCH 16/24] added some docstring to action --- .../ftrack/events/action_pack_workfiles.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/pype/modules/ftrack/events/action_pack_workfiles.py b/pype/modules/ftrack/events/action_pack_workfiles.py index 020814595c5..2698a8e93a3 100644 --- a/pype/modules/ftrack/events/action_pack_workfiles.py +++ b/pype/modules/ftrack/events/action_pack_workfiles.py @@ -62,6 +62,35 @@ def launch(self, session, entities, event): return True def prepare_and_pack_workfiles(self, session, entities): + """Prepares data for packaging of last workfiles. + + Filter selected entities and keep only `Task` entity types with names + specified in `allowed_task_names`. + + Collect parent ftrack ids of filtered tasks which are used to query + asset documents. + + If asset documents are not found by `data.ftrackId` then fallback to + find documents by names is executed. Entities that are not found even + with fallback are skipped. + + Then selection of task entities is pointed to asset document and lib + function `make_workload_package_for_tasks` is executed. + + Args: + session (ftrack_api.Session): Ftrack session to be able query + entities. + entities (list): List of selected entities on which action was + triggered. + + Returns: + None: If everything if Ok. + dict: Result of action with message. + + Raises: + Exception: Method may raise any exception mainly due to + `make_workload_package_for_tasks` function from lib. + """ project_entity = self.get_project_from_entity(entities[0]) project_name = project_entity["full_name"] From c74c785fe186e75ad8d4fd39951cb84853dc84a4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 14 Jan 2021 10:18:17 +0100 Subject: [PATCH 17/24] small tweaks --- pype/lib/packaging.py | 6 +++--- pype/modules/ftrack/events/action_pack_workfiles.py | 7 +++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/pype/lib/packaging.py b/pype/lib/packaging.py index bd0943adc78..e9cdba9190e 100644 --- a/pype/lib/packaging.py +++ b/pype/lib/packaging.py @@ -105,7 +105,7 @@ def _collect_files(filepath): return files -def prepare_data( +def prepare_workfile_data( anatomy, project_doc, asset_docs_by_id, @@ -179,7 +179,7 @@ def make_workload_package_for_tasks( # Prepare data needed for processing host_name = "nuke" - prepared_data = prepare_data( + prepared_data = prepare_workfile_data( anatomy, project_doc, asset_docs_by_id, @@ -308,4 +308,4 @@ def make_workload_package(anatomy, fill_data, path_nk): _zipdir(dir_package_path, zipf) zipf.close() - log.info(f"Zip file was collected to: `{zip_package_path}`") + log.info("Zip file was collected to: `{}`".format(zip_package_path)) diff --git a/pype/modules/ftrack/events/action_pack_workfiles.py b/pype/modules/ftrack/events/action_pack_workfiles.py index 2698a8e93a3..d89b5e06008 100644 --- a/pype/modules/ftrack/events/action_pack_workfiles.py +++ b/pype/modules/ftrack/events/action_pack_workfiles.py @@ -50,9 +50,12 @@ def launch(self, session, entities, event): try: result = self.prepare_and_pack_workfiles(session, entities) - except Exception: + except Exception as exc: self.handle_exception(job, session, sys.exc_info(), event) - return + return { + "success": False, + "message": "Error: {}".format(str(exc)) + } job["status"] = "done" session.commit() From d706f1c4213d0da43e9a10cf1c2058313e64e1e1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 14 Jan 2021 10:33:20 +0100 Subject: [PATCH 18/24] do not double close file stream --- pype/lib/packaging.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pype/lib/packaging.py b/pype/lib/packaging.py index e9cdba9190e..60ba103d5ee 100644 --- a/pype/lib/packaging.py +++ b/pype/lib/packaging.py @@ -47,9 +47,9 @@ def _save_text_lines_to_file(filepath, lines): """ if os.path.exists(filepath): os.remove(filepath) - with open(filepath, 'a') as filepath_file: - filepath_file.writelines(lines) - filepath_file.close() + + with open(filepath, "w") as file_stream: + file_stream.writelines(lines) def _get_packaging_path(anatomy, anatomy_data): From a5cb2ba4c1d690f66890b7d43adc7f43d3570465 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 14 Jan 2021 10:33:32 +0100 Subject: [PATCH 19/24] make sure package directory exists --- pype/lib/packaging.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pype/lib/packaging.py b/pype/lib/packaging.py index 60ba103d5ee..d4a72736c12 100644 --- a/pype/lib/packaging.py +++ b/pype/lib/packaging.py @@ -48,6 +48,11 @@ def _save_text_lines_to_file(filepath, lines): if os.path.exists(filepath): os.remove(filepath) + # Make sure directory exists + file_dir_path = os.path.dirname(filepath) + if not os.path.exists(file_dir_path): + os.makedirs(file_dir_path) + with open(filepath, "w") as file_stream: file_stream.writelines(lines) From 37865fc67e2bcd781f46e2d9ab39771eb8544481 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 14 Jan 2021 10:33:43 +0100 Subject: [PATCH 20/24] fix last workfile path --- pype/lib/packaging.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pype/lib/packaging.py b/pype/lib/packaging.py index d4a72736c12..d5413525034 100644 --- a/pype/lib/packaging.py +++ b/pype/lib/packaging.py @@ -145,13 +145,14 @@ def prepare_workfile_data( workdir = anatomy_filled["work"]["folder"] file_template = anatomy.templates["work"]["file"] - last_workfile_path, _version = last_workfile_with_version( + last_workfile_name, _version = last_workfile_with_version( workdir, file_template, fill_data, host_exts ) # Skip tasks without last workfile - if not last_workfile_path: + if not last_workfile_name: continue + last_workfile_path = os.path.join(workdir, last_workfile_name) output.append( (fill_data, last_workfile_path) ) From 068eb00b7ca32e67ebd74106afd8a8215bef0042 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 14 Jan 2021 10:34:06 +0100 Subject: [PATCH 21/24] fix dbcon in action --- pype/modules/ftrack/events/action_pack_workfiles.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pype/modules/ftrack/events/action_pack_workfiles.py b/pype/modules/ftrack/events/action_pack_workfiles.py index d89b5e06008..8b1991f74d5 100644 --- a/pype/modules/ftrack/events/action_pack_workfiles.py +++ b/pype/modules/ftrack/events/action_pack_workfiles.py @@ -20,7 +20,7 @@ class PackWorkfilesAction(ServerAction): def __init__(self, *args, **kwargs): super(PackWorkfilesAction, self).__init__(*args, **kwargs) - self.db_con = AvalonMongoDB() + self.dbcon = AvalonMongoDB() def discover(self, session, entities, event): """Defines if action will be discovered for a selection.""" @@ -47,6 +47,7 @@ def launch(self, session, entities, event): session.commit() # Run action logic and handle errors + self.dbcon.install() try: result = self.prepare_and_pack_workfiles(session, entities) @@ -57,6 +58,9 @@ def launch(self, session, entities, event): "message": "Error: {}".format(str(exc)) } + finally: + self.dbcon.uninstall() + job["status"] = "done" session.commit() @@ -97,8 +101,8 @@ def prepare_and_pack_workfiles(self, session, entities): project_entity = self.get_project_from_entity(entities[0]) project_name = project_entity["full_name"] - self.db_con.Session["AVALON_PROJECT"] = project_name - project_doc = self.db_con.find_one({"type": "project"}) + self.dbcon.Session["AVALON_PROJECT"] = project_name + project_doc = self.dbcon.find_one({"type": "project"}) if not project_doc: return { "success": False, From a967768a43e4b6af7a8eb03a86f98868be548b79 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 14 Jan 2021 11:06:12 +0100 Subject: [PATCH 22/24] allow multiroot setup --- pype/lib/packaging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/lib/packaging.py b/pype/lib/packaging.py index d5413525034..dc201abae4c 100644 --- a/pype/lib/packaging.py +++ b/pype/lib/packaging.py @@ -85,7 +85,7 @@ def _swap_root_to_package(anatomy, path, destination_root): anatomy.project_name, path ) ) - return rootless_path.format(root=destination_root) + return anatomy.fill_root_with_path(rootless_path, destination_root) def _collect_files(filepath): From 9b8804045635968b89c6d2ed96b693016b1802ed Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 14 Jan 2021 16:05:10 +0100 Subject: [PATCH 23/24] hound(global): improvements --- pype/lib/packaging.py | 6 ++---- pype/modules/ftrack/events/action_pack_workfiles.py | 3 ++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/pype/lib/packaging.py b/pype/lib/packaging.py index dc201abae4c..73fe615b661 100644 --- a/pype/lib/packaging.py +++ b/pype/lib/packaging.py @@ -2,7 +2,6 @@ # -*- coding: utf-8 -*- import os -import sys import re import copy import getpass @@ -16,10 +15,9 @@ log = logging.getLogger(__name__) - def _zipdir(path, ziph): # ziph is zipfile handle - for root, dirs, files in os.walk(path): + for root, _dirs, files in os.walk(path): for file in files: ziph.write(os.path.join(root, file), os.path.relpath( os.path.join(root, file), os.path.join(path, '..'))) @@ -200,7 +198,7 @@ def make_workload_package(anatomy, fill_data, path_nk): packaging_data = copy.deepcopy(fill_data) # Set extension to zip packaging_data["ext"] = "zip" - log.warning(packaging_data) + log.debug(packaging_data) # get packaging zip path zip_package_path = _get_packaging_path(anatomy, packaging_data) diff --git a/pype/modules/ftrack/events/action_pack_workfiles.py b/pype/modules/ftrack/events/action_pack_workfiles.py index 8b1991f74d5..0bf99ea6983 100644 --- a/pype/modules/ftrack/events/action_pack_workfiles.py +++ b/pype/modules/ftrack/events/action_pack_workfiles.py @@ -319,5 +319,6 @@ def handle_exception(self, job, session, exc_info, event): self.show_message(event, msg, False) -def register(session, plugins_presets={}): +def register(session, plugins_presets=None): + plugins_presets = plugins_presets or {} PackWorkfilesAction(session, plugins_presets).register() From e49f3763ccea5dd3be0771e33787dbdd7f801125 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 14 Jan 2021 17:32:18 +0100 Subject: [PATCH 24/24] feat(global): removing temp packaging dir after zip is created --- pype/lib/packaging.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pype/lib/packaging.py b/pype/lib/packaging.py index 73fe615b661..ee17755413d 100644 --- a/pype/lib/packaging.py +++ b/pype/lib/packaging.py @@ -312,4 +312,7 @@ def make_workload_package(anatomy, fill_data, path_nk): _zipdir(dir_package_path, zipf) zipf.close() + # remove the packaged dir + shutil.rmtree(dir_package_path, ignore_errors=True) + log.info("Zip file was collected to: `{}`".format(zip_package_path))