From 3d54b9f7542c8d76087cab37cc17f02c3007f114 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 20 Oct 2020 16:28:17 +0200 Subject: [PATCH] extract js code from plugins --- pype/hosts/harmony/__init__.py | 16 +- pype/hosts/harmony/js/PypeHarmony.js | 43 +- .../hosts/harmony/js/creators/CreateRender.js | 33 ++ .../harmony/js/loaders/TemplateLoader.js | 3 +- .../harmony/js/publish/ExtractPalette.js | 38 ++ .../harmony/js/publish/ExtractTemplate.js | 54 +++ pype/plugins/harmony/create/create_render.py | 20 +- .../harmony/publish/collect_palettes.py | 2 +- pype/plugins/harmony/publish/collect_scene.py | 23 +- .../harmony/publish/collect_workfile.py | 3 + .../harmony/publish/extract_palette.py | 169 +++++++- .../harmony/publish/extract_template.py | 86 ++-- .../publish/collect_harmony_scenes.py | 87 ++++ .../publish/collect_harmony_zips.py | 68 +++ .../publish/collect_remove_marked.py | 21 + .../publish/extract_harmony_zip.py | 404 ++++++++++++++++++ 16 files changed, 975 insertions(+), 95 deletions(-) create mode 100644 pype/hosts/harmony/js/creators/CreateRender.js create mode 100644 pype/hosts/harmony/js/publish/ExtractPalette.js create mode 100644 pype/hosts/harmony/js/publish/ExtractTemplate.js create mode 100644 pype/plugins/standalonepublisher/publish/collect_harmony_scenes.py create mode 100644 pype/plugins/standalonepublisher/publish/collect_harmony_zips.py create mode 100644 pype/plugins/standalonepublisher/publish/collect_remove_marked.py create mode 100644 pype/plugins/standalonepublisher/publish/extract_harmony_zip.py diff --git a/pype/hosts/harmony/__init__.py b/pype/hosts/harmony/__init__.py index a5820a352f..7ea261292e 100644 --- a/pype/hosts/harmony/__init__.py +++ b/pype/hosts/harmony/__init__.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- """Pype Harmony Host implementation.""" import os +from pathlib import Path from avalon import api, io, harmony import avalon.tools.sceneinventory @@ -130,11 +131,24 @@ def check_inventory(): def application_launch(): + """Event that is executed after Harmony is launched.""" # FIXME: This is breaking server <-> client communication. # It is now moved so it it manually called. # ensure_scene_settings() # check_inventory() - pass + pype_harmony_path = Path(__file__).parent / "js" / "PypeHarmony.js" + pype_harmony_js = pype_harmony_path.read_text() + + # go through js/creators, loaders and publish folders and load all scripts + script = "" + for item in ["creators", "loaders", "publish"]: + dir_to_scan = Path(__file__).parent / "js" / item + for child in dir_to_scan.iterdir(): + script += child.read_text() + + # send scripts to Harmony + harmony.send({"script": pype_harmony_js}) + harmony.send({"script": script}) def export_template(backdrops, nodes, filepath): diff --git a/pype/hosts/harmony/js/PypeHarmony.js b/pype/hosts/harmony/js/PypeHarmony.js index 20dc1cc95e..61d09f649a 100644 --- a/pype/hosts/harmony/js/PypeHarmony.js +++ b/pype/hosts/harmony/js/PypeHarmony.js @@ -59,11 +59,31 @@ PypeHarmony.setSceneSettings = function(settings) { }; +/** + * Get scene settings. + * @function + * @return {array} Scene settings. + */ +PypeHarmony.getSceneSettings = function() { + return [ + about.getApplicationPath(), + scene.currentProjectPath(), + scene.currentScene(), + scene.getFrameRate(), + scene.getStartFrame(), + scene.getStopFrame(), + sound.getSoundtrackAll().path(), + scene.defaultResolutionX(), + scene.defaultResolutionY() + ]; +}; + + /** * Set color of nodes. * @function * @param {array} nodes List of nodes. - * @param {array} rgba array of RGBA components of color. + * @param {array} rgba array of RGBA components of color. */ PypeHarmony.setColor = function(nodes, rgba) { for (var i =0; i <= nodes.length - 1; ++i) { @@ -152,9 +172,26 @@ PypeHarmony.copyFile = function(src, dst) { /** * create RGBA color from array. * @function - * @param {array} rgba array of rgba values. - * @return {ColorRGBA} ColorRGBA Harmony class. + * @param {array} rgba array of rgba values. + * @return {ColorRGBA} ColorRGBA Harmony class. */ PypeHarmony.color = function(rgba) { return new ColorRGBA(rgba[0], rgba[1], rgba[2], rgba[3]); }; + + +/** + * get all dependencies for given node. + * @function + * @param {string} node node path. + * @return {array} List of dependent nodes. + */ +PypeHarmony.getDependencies = function(node) { + var target_node = node; + var numInput = node.numberOfInputPorts(target_node); + var dependencies = []; + for (var i = 0 ; i < numInput; i++) { + dependencies.push(node.srcNode(target_node, i)); + } + return dependencies; +}; diff --git a/pype/hosts/harmony/js/creators/CreateRender.js b/pype/hosts/harmony/js/creators/CreateRender.js new file mode 100644 index 0000000000..d8283ea30b --- /dev/null +++ b/pype/hosts/harmony/js/creators/CreateRender.js @@ -0,0 +1,33 @@ +/* global PypeHarmony:writable, include */ +// *************************************************************************** +// * CreateRender * +// *************************************************************************** + + +// check if PypeHarmony is defined and if not, load it. +if (typeof PypeHarmony !== 'undefined') { + var PYPE_HARMONY_JS = System.getenv('PYPE_HARMONY_JS'); + include(PYPE_HARMONY_JS + '/pype_harmony.js'); +} + + +/** + * @namespace + * @classdesc Code creating render containers in Harmony. + */ +var CreateRender = function() {}; + + +/** + * Create render instance. + * @function + * @param {array} args Arguments for instance. + */ +CreateRender.prototype.create = function(args) { + node.setTextAttr(args[0], 'DRAWING_TYPE', 1, 'PNG4'); + node.setTextAttr(args[0], 'DRAWING_NAME', 1, args[1]); + node.setTextAttr(args[0], 'MOVIE_PATH', 1, args[1]); +}; + +// add self to Pype Loaders +PypeHarmony.Creators.CreateRender = new CreateRender(); diff --git a/pype/hosts/harmony/js/loaders/TemplateLoader.js b/pype/hosts/harmony/js/loaders/TemplateLoader.js index 05498cd0ea..bf7eda62e9 100644 --- a/pype/hosts/harmony/js/loaders/TemplateLoader.js +++ b/pype/hosts/harmony/js/loaders/TemplateLoader.js @@ -85,12 +85,11 @@ TemplateLoader.prototype.loadContainer = function(args) { * @param {string} srcNodePath Harmony path to source Node. * @param {string} renameSrc ... * @param {boolean} cloneSrc ... - * @param {array} linkColumns ... * @return {boolean} Success * @todo This is work in progress. */ TemplateLoader.prototype.replaceNode = function( - dstNodePath, srcNodePath, renameSrc, cloneSrc, linkColumns) { + dstNodePath, srcNodePath, renameSrc, cloneSrc) { var doc = $.scn; var srcNode = doc.$node(srcNodePath); var dstNode = doc.$node(dstNodePath); diff --git a/pype/hosts/harmony/js/publish/ExtractPalette.js b/pype/hosts/harmony/js/publish/ExtractPalette.js new file mode 100644 index 0000000000..bc63dcdc7a --- /dev/null +++ b/pype/hosts/harmony/js/publish/ExtractPalette.js @@ -0,0 +1,38 @@ +/* global PypeHarmony:writable, include */ +// *************************************************************************** +// * ExtractPalette * +// *************************************************************************** + + +// check if PypeHarmony is defined and if not, load it. +if (typeof PypeHarmony !== 'undefined') { + var PYPE_HARMONY_JS = System.getenv('PYPE_HARMONY_JS'); + include(PYPE_HARMONY_JS + '/pype_harmony.js'); +} + + +/** + * @namespace + * @classdesc Code for extracting palettes. + */ +var ExtractPalette = function() {}; + + +/** + * Get palette from Harmony. + * @function + * @param {string} paletteId ID of palette to get. + * @return {array} [paletteName, palettePath] + */ +ExtractPalette.prototype.getPalette = function(paletteId) { + var palette_list = PaletteObjectManager.getScenePaletteList(); + var palette = palette_list.getPaletteById(paletteId); + var palette_name = palette.getName(); + return [ + palette_name, + (palette.getPath() + '/' + palette.getName() + '.plt') + ]; +}; + +// add self to Pype Loaders +PypeHarmony.Publish.ExtractPalette = new ExtractPalette(); diff --git a/pype/hosts/harmony/js/publish/ExtractTemplate.js b/pype/hosts/harmony/js/publish/ExtractTemplate.js new file mode 100644 index 0000000000..eb3668f833 --- /dev/null +++ b/pype/hosts/harmony/js/publish/ExtractTemplate.js @@ -0,0 +1,54 @@ +/* global PypeHarmony:writable, include */ +// *************************************************************************** +// * ExtractTemplate * +// *************************************************************************** + + +// check if PypeHarmony is defined and if not, load it. +if (typeof PypeHarmony !== 'undefined') { + var PYPE_HARMONY_JS = System.getenv('PYPE_HARMONY_JS'); + include(PYPE_HARMONY_JS + '/pype_harmony.js'); +} + + +/** + * @namespace + * @classdesc Code for extracting palettes. + */ +var ExtractTemplate = function() {}; + + +/** + * Get backdrops for given node. + * @function + * @param {string} probeNode Node path to probe for backdrops. + * @return {array} list of backdrops. + */ +ExtractTemplate.prototype.getBackdropsByNode = function(probeNode) { + var backdrops = Backdrop.backdrops('Top'); + var valid_backdrops = []; + for(var i=0; i list: + """Get backdrops for the node. - if (x_valid && y_valid){ - valid_backdrops.push(backdrops[i]) - }; - } - return valid_backdrops; - } - %s - """ % (sig, sig) - return harmony.send( - {"function": func, "args": [node]} - )["result"] + Args: + node (str): Node path. - def get_dependencies(self, node, dependencies): - sig = harmony.signature() - func = """function %s(args) - { - var target_node = args[0]; - var numInput = node.numberOfInputPorts(target_node); - var dependencies = []; - for (var i = 0 ; i < numInput; i++) - { - dependencies.push(node.srcNode(target_node, i)); - } - return dependencies; - } - %s - """ % (sig, sig) + Returns: + list: list of Backdrops. + """ + self_name = self.__class__.__name__ + return harmony.send({ + "function": f"PypeHarmony.Publish.{self_name}.getBackdropsByNode", + "args": node})["result"] + + def get_dependencies( + self, node: str, dependencies: list = None) -> list: + """Get node dependencies. + + This will return recursive dependency list of given node. + + Args: + node (str): Path to the node. + dependencies (list, optional): existing dependency list. + + Returns: + list: List of dependent nodes. + + """ current_dependencies = harmony.send( - {"function": func, "args": [node]} + { + "function": "PypeHarmony.getDependencies", + "args": node} )["result"] for dependency in current_dependencies: diff --git a/pype/plugins/standalonepublisher/publish/collect_harmony_scenes.py b/pype/plugins/standalonepublisher/publish/collect_harmony_scenes.py new file mode 100644 index 0000000000..a4fed3bc3f --- /dev/null +++ b/pype/plugins/standalonepublisher/publish/collect_harmony_scenes.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- +"""Collect Harmony scenes in Standalone Publisher.""" +import copy +import glob +import os +from pprint import pformat + +import pyblish.api + + +class CollectHarmonyScenes(pyblish.api.InstancePlugin): + """Collect Harmony xstage files.""" + + order = pyblish.api.CollectorOrder + 0.498 + label = "Collect Harmony Scene" + hosts = ["standalonepublisher"] + families = ["harmony.scene"] + + # presets + ignored_instance_data_keys = ("name", "label", "stagingDir", "version") + + def process(self, instance): + """Plugin entry point.""" + context = instance.context + asset_data = instance.context.data["assetEntity"] + asset_name = instance.data["asset"] + subset_name = instance.data.get("subset", "sceneMain") + anatomy_data = instance.context.data["anatomyData"] + repres = instance.data["representations"] + staging_dir = repres[0]["stagingDir"] + files = repres[0]["files"] + + if not files.endswith(".zip"): + # A harmony project folder / .xstage was dropped + instance_name = f"{asset_name}_{subset_name}" + task = instance.data.get("task", "harmonyIngest") + + # create new instance + new_instance = context.create_instance(instance_name) + + # add original instance data except name key + for key, value in instance.data.items(): + # Make sure value is copy since value may be object which + # can be shared across all new created objects + if key not in self.ignored_instance_data_keys: + new_instance.data[key] = copy.deepcopy(value) + + self.log.info("Copied data: {}".format(new_instance.data)) + + # fix anatomy data + anatomy_data_new = copy.deepcopy(anatomy_data) + # updating hierarchy data + anatomy_data_new.update({ + "asset": asset_data["name"], + "task": task, + "subset": subset_name + }) + + new_instance.data["label"] = f"{instance_name}" + new_instance.data["subset"] = subset_name + new_instance.data["extension"] = ".zip" + new_instance.data["anatomyData"] = anatomy_data_new + new_instance.data["publish"] = True + + # When a project folder was dropped vs. just an xstage file, find + # the latest file xstage version and update the instance + if not files.endswith(".xstage"): + + source_dir = os.path.join( + staging_dir, files + ).replace("\\", "/") + + latest_file = max(glob.iglob(source_dir + "/*.xstage"), + key=os.path.getctime).replace("\\", "/") + + new_instance.data["representations"][0]["stagingDir"] = ( + source_dir + ) + new_instance.data["representations"][0]["files"] = ( + os.path.basename(latest_file) + ) + self.log.info(f"Created new instance: {instance_name}") + self.log.debug(f"_ inst_data: {pformat(new_instance.data)}") + + # set original instance for removal + self.log.info("Context data: {}".format(context.data)) + instance.data["remove"] = True diff --git a/pype/plugins/standalonepublisher/publish/collect_harmony_zips.py b/pype/plugins/standalonepublisher/publish/collect_harmony_zips.py new file mode 100644 index 0000000000..93eff85486 --- /dev/null +++ b/pype/plugins/standalonepublisher/publish/collect_harmony_zips.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +"""Collect zips as Harmony scene files.""" +import copy +from pprint import pformat + +import pyblish.api + + +class CollectHarmonyZips(pyblish.api.InstancePlugin): + """Collect Harmony zipped projects.""" + + order = pyblish.api.CollectorOrder + 0.497 + label = "Collect Harmony Zipped Projects" + hosts = ["standalonepublisher"] + families = ["harmony.scene"] + extensions = ["zip"] + + # presets + ignored_instance_data_keys = ("name", "label", "stagingDir", "version") + + def process(self, instance): + """Plugin entry point.""" + context = instance.context + asset_data = instance.context.data["assetEntity"] + asset_name = instance.data["asset"] + subset_name = instance.data.get("subset", "sceneMain") + anatomy_data = instance.context.data["anatomyData"] + repres = instance.data["representations"] + files = repres[0]["files"] + + if files.endswith(".zip"): + # A zip file was dropped + instance_name = f"{asset_name}_{subset_name}" + task = instance.data.get("task", "harmonyIngest") + + # create new instance + new_instance = context.create_instance(instance_name) + + # add original instance data except name key + for key, value in instance.data.items(): + # Make sure value is copy since value may be object which + # can be shared across all new created objects + if key not in self.ignored_instance_data_keys: + new_instance.data[key] = copy.deepcopy(value) + + self.log.info("Copied data: {}".format(new_instance.data)) + + # fix anatomy data + anatomy_data_new = copy.deepcopy(anatomy_data) + # updating hierarchy data + anatomy_data_new.update({ + "asset": asset_data["name"], + "task": task, + "subset": subset_name + }) + + new_instance.data["label"] = f"{instance_name}" + new_instance.data["subset"] = subset_name + new_instance.data["extension"] = ".zip" + new_instance.data["anatomyData"] = anatomy_data_new + new_instance.data["publish"] = True + + self.log.info(f"Created new instance: {instance_name}") + self.log.debug(f"_ inst_data: {pformat(new_instance.data)}") + + # set original instance for removal + self.log.info("Context data: {}".format(context.data)) + instance.data["remove"] = True diff --git a/pype/plugins/standalonepublisher/publish/collect_remove_marked.py b/pype/plugins/standalonepublisher/publish/collect_remove_marked.py new file mode 100644 index 0000000000..4279d67655 --- /dev/null +++ b/pype/plugins/standalonepublisher/publish/collect_remove_marked.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +"""Collect instances that are marked for removal and remove them.""" +import pyblish.api + + +class CollectRemoveMarked(pyblish.api.ContextPlugin): + """Clean up instances marked for removal. + + Note: + This is a workaround for race conditions and removing of instances + used to generate other instances. + """ + + order = pyblish.api.CollectorOrder + 0.499 + label = 'Remove Marked Instances' + + def process(self, context): + """Plugin entry point.""" + for instance in context: + if instance.data.get('remove'): + context.remove(instance) diff --git a/pype/plugins/standalonepublisher/publish/extract_harmony_zip.py b/pype/plugins/standalonepublisher/publish/extract_harmony_zip.py new file mode 100644 index 0000000000..16811e5707 --- /dev/null +++ b/pype/plugins/standalonepublisher/publish/extract_harmony_zip.py @@ -0,0 +1,404 @@ +# -*- coding: utf-8 -*- +"""Extract Harmony scene from zip file.""" +import glob +import os +import shutil +import six +import sys +import tempfile +import zipfile + +import pyblish.api +from avalon import api, io +import pype.api + + +class ExtractHarmonyZip(pype.api.Extractor): + """Extract Harmony zip.""" + + # Pyblish settings + label = "Extract Harmony zip" + order = pyblish.api.ExtractorOrder + 0.02 + hosts = ["standalonepublisher"] + families = ["scene"] + + # Properties + session = None + task_types = None + task_statuses = None + assetversion_statuses = None + + # Presets + create_workfile = True + default_task = "harmonyIngest" + default_task_type = "Ingest" + default_task_status = "Ingested" + assetversion_status = "Ingested" + + def process(self, instance): + """Plugin entry point.""" + context = instance.context + self.session = context.data["ftrackSession"] + asset_doc = context.data["assetEntity"] + # asset_name = instance.data["asset"] + subset_name = instance.data["subset"] + instance_name = instance.data["name"] + family = instance.data["family"] + task = context.data["anatomyData"]["task"] or self.default_task + project_entity = instance.context.data["projectEntity"] + ftrack_id = asset_doc["data"]["ftrackId"] + repres = instance.data["representations"] + submitted_staging_dir = repres[0]["stagingDir"] + submitted_files = repres[0]["files"] + + # Get all the ftrack entities needed + + # Asset Entity + query = 'AssetBuild where id is "{}"'.format(ftrack_id) + asset_entity = self.session.query(query).first() + + # Project Entity + query = 'Project where full_name is "{}"'.format( + project_entity["name"] + ) + project_entity = self.session.query(query).one() + + # Get Task types and Statuses for creation if needed + self.task_types = self._get_all_task_types(project_entity) + self.task_statuses = self.get_all_task_statuses(project_entity) + + # Get Statuses of AssetVersions + self.assetversion_statuses = self.get_all_assetversion_statuses( + project_entity + ) + + # Setup the status that we want for the AssetVersion + if self.assetversion_status: + instance.data["assetversion_status"] = self.assetversion_status + + # Create the default_task if it does not exist + if task == self.default_task: + existing_tasks = [] + entity_children = asset_entity.get('children', []) + for child in entity_children: + if child.entity_type.lower() == 'task': + existing_tasks.append(child['name'].lower()) + + if task.lower() in existing_tasks: + print("Task {} already exists".format(task)) + + else: + self.create_task( + name=task, + task_type=self.default_task_type, + task_status=self.default_task_status, + parent=asset_entity, + ) + + # Find latest version + latest_version = self._find_last_version(subset_name, asset_doc) + version_number = 1 + if latest_version is not None: + version_number += latest_version + + self.log.info( + "Next version of instance \"{}\" will be {}".format( + instance_name, version_number + ) + ) + + # update instance info + instance.data["task"] = task + instance.data["version_name"] = "{}_{}".format(subset_name, task) + instance.data["family"] = family + instance.data["subset"] = subset_name + instance.data["version"] = version_number + instance.data["latestVersion"] = latest_version + instance.data["anatomyData"].update({ + "subset": subset_name, + "family": family, + "version": version_number + }) + + # Copy `families` and check if `family` is not in current families + families = instance.data.get("families") or list() + if families: + families = list(set(families)) + + instance.data["families"] = families + + # Prepare staging dir for new instance and zip + sanitize scene name + staging_dir = tempfile.mkdtemp(prefix="pyblish_tmp_") + + # Handle if the representation is a .zip and not an .xstage + pre_staged = False + if submitted_files.endswith(".zip"): + submitted_zip_file = os.path.join(submitted_staging_dir, + submitted_files + ).replace("\\", "/") + + pre_staged = self.sanitize_prezipped_project(instance, + submitted_zip_file, + staging_dir) + + # Get the file to work with + source_dir = str(repres[0]["stagingDir"]) + source_file = str(repres[0]["files"]) + + staging_scene_dir = os.path.join(staging_dir, "scene") + staging_scene = os.path.join(staging_scene_dir, source_file) + + # If the file is an .xstage / directory, we must stage it + if not pre_staged: + shutil.copytree(source_dir, staging_scene_dir) + + # Rename this latest file as 'scene.xstage' + # This is is determined in the collector from the latest scene in a + # submitted directory / directory the submitted .xstage is in. + # In the case of a zip file being submitted, this is determined within + # the self.sanitize_project() method in this extractor. + os.rename(staging_scene, + os.path.join(staging_scene_dir, "scene.xstage") + ) + + # Required to set the current directory where the zip will end up + os.chdir(staging_dir) + + # Create the zip file + zip_filepath = shutil.make_archive(os.path.basename(source_dir), + "zip", + staging_scene_dir + ) + + zip_filename = os.path.basename(zip_filepath) + + self.log.info("Zip file: {}".format(zip_filepath)) + + # Setup representation + new_repre = { + "name": "zip", + "ext": "zip", + "files": zip_filename, + "stagingDir": staging_dir + } + + self.log.debug( + "Creating new representation: {}".format(new_repre) + ) + instance.data["representations"] = [new_repre] + + self.log.debug("Completed prep of zipped Harmony scene: {}" + .format(zip_filepath) + ) + + # If this extractor is setup to also extract a workfile... + if self.create_workfile: + workfile_path = self.extract_workfile(instance, + staging_scene + ) + + self.log.debug("Extracted Workfile to: {}".format(workfile_path)) + + def extract_workfile(self, instance, staging_scene): + """Extract a valid workfile for this corresponding publish. + + Args: + instance (:class:`pyblish.api.Instance`): Instance data. + staging_scene (str): path of staging scene. + + Returns: + str: Path to workdir. + + """ + # Since the staging scene was renamed to "scene.xstage" for publish + # rename the staging scene in the temp stagingdir + staging_scene = os.path.join(os.path.dirname(staging_scene), + "scene.xstage") + + # Setup the data needed to form a valid work path filename + anatomy = pype.api.Anatomy() + project_entity = instance.context.data["projectEntity"] + + data = { + "root": api.registered_root(), + "project": { + "name": project_entity["name"], + "code": project_entity["data"].get("code", '') + }, + "asset": instance.data["asset"], + "hierarchy": pype.api.get_hierarchy(instance.data["asset"]), + "family": instance.data["family"], + "task": instance.data.get("task"), + "subset": instance.data["subset"], + "version": 1, + "ext": "zip", + } + + # Get a valid work filename first with version 1 + file_template = anatomy.templates["work"]["file"] + anatomy_filled = anatomy.format(data) + work_path = anatomy_filled["work"]["path"] + + # Get the final work filename with the proper version + data["version"] = api.last_workfile_with_version( + os.path.dirname(work_path), file_template, data, [".zip"] + )[1] + + work_path = anatomy_filled["work"]["path"] + base_name = os.path.splitext(os.path.basename(work_path))[0] + + staging_work_path = os.path.join(os.path.dirname(staging_scene), + base_name + ".xstage" + ) + + # Rename this latest file after the workfile path filename + os.rename(staging_scene, staging_work_path) + + # Required to set the current directory where the zip will end up + os.chdir(os.path.dirname(os.path.dirname(staging_scene))) + + # Create the zip file + zip_filepath = shutil.make_archive(base_name, + "zip", + os.path.dirname(staging_scene) + ) + self.log.info(staging_scene) + self.log.info(work_path) + self.log.info(staging_work_path) + self.log.info(os.path.dirname(os.path.dirname(staging_scene))) + self.log.info(base_name) + self.log.info(zip_filepath) + + # Create the work path on disk if it does not exist + os.makedirs(os.path.dirname(work_path), exist_ok=True) + shutil.copy(zip_filepath, work_path) + + return work_path + + def sanitize_prezipped_project( + self, instance, zip_filepath, staging_dir): + """Fix when a zip contains a folder. + + Handle zip file root contains folder instead of the project. + + Args: + instance (:class:`pyblish.api.Instance`): Instance data. + zip_filepath (str): Path to zip. + staging_dir (str): Path to staging directory. + + """ + zip = zipfile.ZipFile(zip_filepath) + zip_contents = zipfile.ZipFile.namelist(zip) + + # Determine if any xstage file is in root of zip + project_in_root = [pth for pth in zip_contents + if "/" not in pth and pth.endswith(".xstage")] + + staging_scene_dir = os.path.join(staging_dir, "scene") + + # The project is nested, so we must extract and move it + if not project_in_root: + + staging_tmp_dir = os.path.join(staging_dir, "tmp") + + with zipfile.ZipFile(zip_filepath, "r") as zip_ref: + zip_ref.extractall(staging_tmp_dir) + + nested_project_folder = os.path.join(staging_tmp_dir, + zip_contents[0] + ) + + shutil.copytree(nested_project_folder, staging_scene_dir) + + else: + # The project is not nested, so we just extract to scene folder + with zipfile.ZipFile(zip_filepath, "r") as zip_ref: + zip_ref.extractall(staging_scene_dir) + + latest_file = max(glob.iglob(staging_scene_dir + "/*.xstage"), + key=os.path.getctime).replace("\\", "/") + + instance.data["representations"][0]["stagingDir"] = staging_scene_dir + instance.data["representations"][0]["files"] = os.path.basename( + latest_file) + + # We have staged the scene already so return True + return True + + def _find_last_version(self, subset_name, asset_doc): + """Find last version of subset.""" + subset_doc = io.find_one({ + "type": "subset", + "name": subset_name, + "parent": asset_doc["_id"] + }) + + if subset_doc is None: + self.log.debug("Subset entity does not exist yet.") + else: + version_doc = io.find_one( + { + "type": "version", + "parent": subset_doc["_id"] + }, + sort=[("name", -1)] + ) + if version_doc: + return int(version_doc["name"]) + return None + + def _get_all_task_types(self, project): + """Get all task types.""" + tasks = {} + proj_template = project['project_schema'] + temp_task_types = proj_template['_task_type_schema']['types'] + + for type in temp_task_types: + if type['name'] not in tasks: + tasks[type['name']] = type + + return tasks + + def _get_all_task_statuses(self, project): + """Get all statuses of tasks.""" + statuses = {} + proj_template = project['project_schema'] + temp_task_statuses = proj_template.get_statuses("Task") + + for status in temp_task_statuses: + if status['name'] not in statuses: + statuses[status['name']] = status + + return statuses + + def _get_all_assetversion_statuses(self, project): + """Get statuses of all asset versions.""" + statuses = {} + proj_template = project['project_schema'] + temp_task_statuses = proj_template.get_statuses("AssetVersion") + + for status in temp_task_statuses: + if status['name'] not in statuses: + statuses[status['name']] = status + + return statuses + + def _create_task(self, name, task_type, parent, task_status): + """Create task.""" + task_data = { + 'name': name, + 'parent': parent, + } + self.log.info(task_type) + task_data['type'] = self.task_types[task_type] + task_data['status'] = self.task_statuses[task_status] + self.log.info(task_data) + task = self.session.create('Task', task_data) + try: + self.session.commit() + except Exception: + tp, value, tb = sys.exc_info() + self.session.rollback() + six.reraise(tp, value, tb) + + return task