diff --git a/pype/hooks/aftereffects/pre_launch_args.py b/pype/hooks/aftereffects/pre_launch_args.py deleted file mode 100644 index 00b3557429..0000000000 --- a/pype/hooks/aftereffects/pre_launch_args.py +++ /dev/null @@ -1,52 +0,0 @@ -import os - -from pype.lib import PreLaunchHook - - -class AfterEffectsPrelaunchHook(PreLaunchHook): - """Launch arguments preparation. - - Hook add python executable and execute python script of AfterEffects - implementation before AfterEffects executable. - """ - app_groups = ["aftereffects"] - - def execute(self): - # Pop tvpaint executable - aftereffects_executable = self.launch_context.launch_args.pop(0) - - # Pop rest of launch arguments - There should not be other arguments! - remainders = [] - while self.launch_context.launch_args: - remainders.append(self.launch_context.launch_args.pop(0)) - - workfile_path = self.data["last_workfile_path"] - if not os.path.exists(workfile_path): - workfile_path = "" - - new_launch_args = [ - self.python_executable(), - "-c", - ( - "import avalon.aftereffects;" - "avalon.aftereffects.launch(\"{}\", \"{}\")" - ).format( - aftereffects_executable.replace("\\", "\\\\"), - workfile_path.replace("\\", "\\\\") - ) - ] - - # Append as whole list as these arguments should not be separated - self.launch_context.launch_args.append(new_launch_args) - - if remainders: - self.log.warning(( - "There are unexpected launch arguments " - "in AfterEffects launch. {}" - ).format(str(remainders))) - self.launch_context.launch_args.extend(remainders) - - def python_executable(self): - """Should lead to python executable.""" - # TODO change in Pype 3 - return os.environ["PYPE_PYTHON_EXE"] diff --git a/pype/hooks/global/pre_non_python_host_launch.py b/pype/hooks/global/pre_non_python_host_launch.py new file mode 100644 index 0000000000..87b6cbad01 --- /dev/null +++ b/pype/hooks/global/pre_non_python_host_launch.py @@ -0,0 +1,46 @@ +import os + +from pype.lib import ( + PreLaunchHook, + get_pype_execute_args +) +import pype.PACKAGE_DIR + + +class NonPythonHostHook(PreLaunchHook): + """Launch arguments preparation. + + Non python host implementation do not launch host directly but use + python script which launch the host. For these cases it is necessary to + prepend python (or pype) executable and script path before application's. + """ + app_groups = ["harmony", "photoshop", "aftereffects"] + + def execute(self): + # Pop executable + executable_path = self.launch_context.launch_args.pop(0) + + # Pop rest of launch arguments - There should not be other arguments! + remainders = [] + while self.launch_context.launch_args: + remainders.append(self.launch_context.launch_args.pop(0)) + + script_path = os.path.join( + pype.PACKAGE_DIR, + "scripts", + "non_python_host_launch.py" + ) + + new_launch_args = get_pype_execute_args( + "run", script_path, executable_path + ) + # Add workfile path if exists + workfile_path = self.data["last_workfile_path"] + if os.path.exists(workfile_path): + new_launch_args.append(workfile_path) + + # Append as whole list as these areguments should not be separated + self.launch_context.launch_args.append(new_launch_args) + + if remainders: + self.launch_context.launch_args.extend(remainders) diff --git a/pype/hooks/harmony/pre_launch_args.py b/pype/hooks/harmony/pre_launch_args.py deleted file mode 100644 index a61977f4e7..0000000000 --- a/pype/hooks/harmony/pre_launch_args.py +++ /dev/null @@ -1,44 +0,0 @@ -import os - -from pype.lib import PreLaunchHook - - -class HarmonyPrelaunchHook(PreLaunchHook): - """Launch arguments preparation. - - Hook add python executable and execute python script of harmony - implementation before harmony executable. - """ - app_groups = ["harmony"] - - def execute(self): - # Pop tvpaint executable - harmony_executable = self.launch_context.launch_args.pop(0) - - # Pop rest of launch arguments - There should not be other arguments! - remainders = [] - while self.launch_context.launch_args: - remainders.append(self.launch_context.launch_args.pop(0)) - - new_launch_args = [ - self.python_executable(), - "-c", - ( - "import avalon.harmony;" - "avalon.harmony.launch(\"{}\")" - ).format(harmony_executable.replace("\\", "\\\\")) - ] - - # Append as whole list as these areguments should not be separated - self.launch_context.launch_args.append(new_launch_args) - - if remainders: - self.log.warning(( - "There are unexpected launch arguments in Harmony launch. {}" - ).format(str(remainders))) - self.launch_context.launch_args.extend(remainders) - - def python_executable(self): - """Should lead to python executable.""" - # TODO change in Pype 3 - return os.environ["PYPE_PYTHON_EXE"] diff --git a/pype/hosts/celaction/hooks/pre_celaction_registers.py b/pype/hosts/celaction/hooks/pre_celaction_registers.py index 8f6b1b72bb..40e8eaa6ff 100644 --- a/pype/hosts/celaction/hooks/pre_celaction_registers.py +++ b/pype/hosts/celaction/hooks/pre_celaction_registers.py @@ -34,11 +34,10 @@ class CelactionPrelaunchHook(PreLaunchHook): "Software\\CelAction\\CelAction2D\\User Settings", 0, winreg.KEY_ALL_ACCESS) - # TODO: change to pype executable - pype_root_path = os.getenv("PYPE_SETUP_PATH") - path = os.path.join(pype_root_path, "pype.bat") + # TODO: this will need to be checked more thoroughly + pype_exe = os.getenv("PYPE_EXECUTABLE") - winreg.SetValueEx(hKey, "SubmitAppTitle", 0, winreg.REG_SZ, path) + winreg.SetValueEx(hKey, "SubmitAppTitle", 0, winreg.REG_SZ, pype_exe) parameters = [ "launch", diff --git a/pype/hosts/photoshop/hooks/pre_launch_args.py b/pype/hosts/photoshop/hooks/pre_launch_args.py deleted file mode 100644 index 6bd40eb2e1..0000000000 --- a/pype/hosts/photoshop/hooks/pre_launch_args.py +++ /dev/null @@ -1,51 +0,0 @@ -import os - -from pype.lib import PreLaunchHook - - -class PhotoshopPrelaunchHook(PreLaunchHook): - """Launch arguments preparation. - - Hook add python executable and execute python script of photoshop - implementation before photoshop executable. - """ - app_groups = ["photoshop"] - - def execute(self): - # Pop tvpaint executable - photoshop_executable = self.launch_context.launch_args.pop(0) - - # Pop rest of launch arguments - There should not be other arguments! - remainders = [] - while self.launch_context.launch_args: - remainders.append(self.launch_context.launch_args.pop(0)) - - workfile_path = self.data["last_workfile_path"] - if not os.path.exists(workfile_path): - workfile_path = "" - - new_launch_args = [ - self.python_executable(), - "-c", - ( - "import avalon.photoshop;" - "avalon.photoshop.launch(\"{}\", \"{}\")" - ).format( - photoshop_executable.replace("\\", "\\\\"), - workfile_path.replace("\\", "\\\\") - ) - ] - - # Append as whole list as these areguments should not be separated - self.launch_context.launch_args.append(new_launch_args) - - if remainders: - self.log.warning(( - "There are unexpected launch arguments in Photoshop launch. {}" - ).format(str(remainders))) - self.launch_context.launch_args.extend(remainders) - - def python_executable(self): - """Should lead to python executable.""" - # TODO change in Pype 3 - return os.environ["PYPE_PYTHON_EXE"] diff --git a/pype/plugins/standalonepublisher/publish/collect_context.py b/pype/plugins/standalonepublisher/publish/collect_context.py index cd7246c0a2..b40c081fcc 100644 --- a/pype/plugins/standalonepublisher/publish/collect_context.py +++ b/pype/plugins/standalonepublisher/publish/collect_context.py @@ -89,15 +89,15 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): files = [files] self.log.debug(f"_ files: {files}") - for index, f in enumerate(files): + for index, _file in enumerate(files): index += 1 # copy dictionaries in_data_copy = copy.deepcopy(in_data_copy) - repr_new = copy.deepcopy(repr) + new_repre = copy.deepcopy(repre) - repr_new["files"] = f - repr_new["name"] = ext - in_data_copy["representations"] = [repr_new] + new_repre["files"] = _file + new_repre["name"] = ext + in_data_copy["representations"] = [new_repre] # create subset Name new_subset = f"{ext}{index}{subset}" diff --git a/pype/plugins/standalonepublisher/publish/extract_bg_for_compositing.py b/pype/plugins/standalonepublisher/publish/extract_bg_for_compositing.py index 064c226ff7..db64a408e9 100644 --- a/pype/plugins/standalonepublisher/publish/extract_bg_for_compositing.py +++ b/pype/plugins/standalonepublisher/publish/extract_bg_for_compositing.py @@ -52,7 +52,11 @@ class ExtractBGForComp(pype.api.Extractor): for repre in tuple(repres): # Skip all files without .psd extension - if repre["ext"] != ".psd": + repre_ext = repre["ext"].lower() + if repre_ext.startswith("."): + repre_ext = repre_ext[1:] + + if repre_ext != "psd": continue # Prepare publish dir for transfers diff --git a/pype/plugins/standalonepublisher/publish/extract_bg_main_groups.py b/pype/plugins/standalonepublisher/publish/extract_bg_main_groups.py index 42530aeb14..e18baf378e 100644 --- a/pype/plugins/standalonepublisher/publish/extract_bg_main_groups.py +++ b/pype/plugins/standalonepublisher/publish/extract_bg_main_groups.py @@ -61,7 +61,11 @@ class ExtractBGMainGroups(pype.api.Extractor): for repre in tuple(repres): # Skip all files without .psd extension - if repre["ext"] != ".psd": + repre_ext = repre["ext"].lower() + if repre_ext.startswith("."): + repre_ext = repre_ext[1:] + + if repre_ext != "psd": continue # Prepare json filepath where extracted metadata are stored @@ -101,7 +105,9 @@ class ExtractBGMainGroups(pype.api.Extractor): "__schema_version__": 1, "children": [] } - transfers = [] + output_ext = ".png" + + to_export = [] for layer_idx, layer in enumerate(psd_object): layer_name = layer.name.replace(" ", "_") if ( @@ -117,13 +123,42 @@ class ExtractBGMainGroups(pype.api.Extractor): ).format(layer.name)) continue - filename = "{:0>2}_{}.png".format(layer_idx, layer_name) - layer_data = { + filebase = "{:0>2}_{}".format(layer_idx, layer_name) + if layer_name.lower() == "anim": + if not layer.is_group: + self.log.warning("ANIM layer is not a group layer.") + continue + + children = [] + for anim_idx, anim_layer in enumerate(layer): + anim_layer_name = anim_layer.name.replace(" ", "_") + filename = "{}_{:0>2}_{}{}".format( + filebase, anim_idx, anim_layer_name, output_ext + ) + children.append({ + "index": anim_idx, + "name": anim_layer.name, + "filename": filename + }) + to_export.append((anim_layer, filename)) + + json_data["children"].append({ + "index": layer_idx, + "name": layer.name, + "children": children + }) + continue + + filename = filebase + output_ext + json_data["children"].append({ "index": layer_idx, "name": layer.name, "filename": filename - } + }) + to_export.append((layer, filename)) + transfers = [] + for layer, filename in to_export: output_filepath = os.path.join(output_dir, filename) dst_filepath = os.path.join(publish_dir, filename) transfers.append((output_filepath, dst_filepath)) @@ -131,8 +166,6 @@ class ExtractBGMainGroups(pype.api.Extractor): pil_object = layer.composite(viewport=psd_object.viewbox) pil_object.save(output_filepath, "PNG") - json_data["children"].append(layer_data) - return json_data, transfers def redo_global_plugins(self, instance): diff --git a/pype/plugins/standalonepublisher/publish/extract_images_from_psd.py b/pype/plugins/standalonepublisher/publish/extract_images_from_psd.py index 5a2109478c..3cffaf17d9 100644 --- a/pype/plugins/standalonepublisher/publish/extract_images_from_psd.py +++ b/pype/plugins/standalonepublisher/publish/extract_images_from_psd.py @@ -46,7 +46,11 @@ class ExtractImagesFromPSD(pype.api.Extractor): for repre in tuple(repres): # Skip all files without .psd extension - if repre["ext"] != ".psd": + repre_ext = repre["ext"].lower() + if repre_ext.startswith("."): + repre_ext = repre_ext[1:] + + if repre_ext != "psd": continue # TODO add check of list of "files" value diff --git a/pype/scripts/non_python_host_launch.py b/pype/scripts/non_python_host_launch.py new file mode 100644 index 0000000000..1637eec7ed --- /dev/null +++ b/pype/scripts/non_python_host_launch.py @@ -0,0 +1,107 @@ +"""Script wraps launch mechanism of non python host implementations. + +Arguments passed to the script are passed to launch function in host +implementation. In all cases requires host app executable and may contain +workfile or others. +""" + +import os +import sys + +# Get current file to locate start point of sys.argv +CURRENT_FILE = os.path.abspath(__file__) + + +def show_error_messagebox(title, message, detail_message=None): + """Function will show message and process ends after closing it.""" + from Qt import QtWidgets, QtCore + from avalon import style + + app = QtWidgets.QApplication([]) + app.setStyleSheet(style.load_stylesheet()) + + msgbox = QtWidgets.QMessageBox() + msgbox.setWindowTitle(title) + msgbox.setText(message) + + if detail_message: + msgbox.setDetailedText(detail_message) + + msgbox.setWindowModality(QtCore.Qt.ApplicationModal) + msgbox.show() + + sys.exit(app.exec_()) + + +def on_invalid_args(script_not_found): + """Show to user message box saying that something went wrong. + + Tell user that arguments to launch implementation are invalid with + arguments details. + + Args: + script_not_found (bool): Use different message based on this value. + """ + + title = "Invalid arguments" + joined_args = ", ".join("\"{}\"".format(arg) for arg in sys.argv) + if script_not_found: + submsg = "Where couldn't find script path:\n\"{}\"" + else: + submsg = "Expected Host executable after script path:\n\"{}\"" + + message = "BUG: Got invalid arguments so can't launch Host application." + detail_message = "Process was launched with arguments:\n{}\n\n{}".format( + joined_args, + submsg.format(CURRENT_FILE) + ) + + show_error_messagebox(title, message, detail_message) + + +def main(argv): + # Modify current file path to find match in sys.argv which may be different + # on windows (different letter cases and slashes). + modified_current_file = CURRENT_FILE.replace("\\", "/").lower() + + # Create a copy of sys argv + sys_args = list(argv) + after_script_idx = None + # Find script path in sys.argv to know index of argv where host + # executable should be. + for idx, item in enumerate(sys_args): + if item.replace("\\", "/").lower() == modified_current_file: + after_script_idx = idx + 1 + break + + # Validate that there is at least one argument after script path + launch_args = None + if after_script_idx is not None: + launch_args = sys_args[after_script_idx:] + + host_name = os.environ["AVALON_APP"].lower() + if host_name == "photoshop": + from avalon.photoshop.lib import launch + elif host_name == "aftereffects": + from avalon.aftereffects.lib import launch + elif host_name == "harmony": + from avalon.photoshop.lib import launch + else: + title = "Unknown host name" + message = ( + "BUG: Environment variable AVALON_APP contains unknown" + " host name \"{}\"" + ).format(host_name) + show_error_messagebox(title, message) + return + + if launch_args: + # Launch host implementation + launch(*launch_args) + else: + # Show message box + on_invalid_args(after_script_idx is None) + + +if __name__ == "__main__": + main(sys.argv) diff --git a/pype/settings/entities/exceptions.py b/pype/settings/entities/exceptions.py index 92e6b13ba7..1d5624c967 100644 --- a/pype/settings/entities/exceptions.py +++ b/pype/settings/entities/exceptions.py @@ -71,7 +71,9 @@ class SchemaTemplateMissingKeys(Exception): self.missing_keys = missing_keys self.required_keys = required_keys if template_name: - msg = f"Schema template \"{template_name}\" require more keys.\n" + msg = "Schema template \"{}\" require more keys.\n".format( + template_name + ) else: msg = "" msg += "Required keys: {}\nMissing keys: {}".format( @@ -82,5 +84,5 @@ class SchemaTemplateMissingKeys(Exception): def join_keys(self, keys): return ", ".join([ - f"\"{key}\"" for key in keys + "\"{}\"".format(key) for key in keys ]) diff --git a/pype/settings/entities/lib.py b/pype/settings/entities/lib.py index 3d827c4e73..1ca3f9efe0 100644 --- a/pype/settings/entities/lib.py +++ b/pype/settings/entities/lib.py @@ -252,9 +252,9 @@ def gui_schema(subfolder, main_schema_name): try: schema_data = json.load(json_stream) except Exception as exc: - raise Exception(( - f"Unable to parse JSON file {filepath}\n{exc}" - )) from exc + raise ValueError(( + "Unable to parse JSON file {}\n{}" + ).format(filepath, str(exc))) if isinstance(schema_data, list): loaded_schema_templates[basename] = schema_data else: