diff --git a/igniter/bootstrap_repos.py b/igniter/bootstrap_repos.py index f7f35824c8..151597e505 100644 --- a/igniter/bootstrap_repos.py +++ b/igniter/bootstrap_repos.py @@ -10,6 +10,7 @@ import tempfile from pathlib import Path from typing import Union, Callable, List, Tuple import hashlib +import platform from zipfile import ZipFile, BadZipFile @@ -196,21 +197,23 @@ class OpenPypeVersion(semver.VersionInfo): return str(self.finalize_version()) @staticmethod - def version_in_str(string: str) -> Tuple: + def version_in_str(string: str) -> Union[None, OpenPypeVersion]: """Find OpenPype version in given string. Args: string (str): string to search. Returns: - tuple: True/False and OpenPypeVersion if found. + OpenPypeVersion: of detected or None. """ m = re.search(OpenPypeVersion._VERSION_REGEX, string) if not m: - return False, None + return None version = OpenPypeVersion.parse(string[m.start():m.end()]) - return True, version + if "staging" in string[m.start():m.end()]: + version.staging = True + return version @classmethod def parse(cls, version): @@ -531,6 +534,7 @@ class BootstrapRepos: processed_path = file self._print(f"- processing {processed_path}") + checksums.append( ( sha256sum(file.as_posix()), @@ -542,7 +546,10 @@ class BootstrapRepos: checksums_str = "" for c in checksums: - checksums_str += "{}:{}\n".format(c[0], c[1]) + file_str = c[1] + if platform.system().lower() == "windows": + file_str = c[1].as_posix().replace("\\", "/") + checksums_str += "{}:{}\n".format(c[0], file_str) zip_file.writestr("checksums", checksums_str) # test if zip is ok zip_file.testzip() @@ -563,6 +570,8 @@ class BootstrapRepos: and string with reason as second. """ + if os.getenv("OPENPYPE_DONT_VALIDATE_VERSION"): + return True, "Disabled validation" if not path.exists(): return False, "Path doesn't exist" @@ -589,13 +598,16 @@ class BootstrapRepos: # calculate and compare checksums in the zip file for file in checksums: + file_name = file[1] + if platform.system().lower() == "windows": + file_name = file_name.replace("/", "\\") h = hashlib.sha256() try: - h.update(zip_file.read(file[1])) + h.update(zip_file.read(file_name)) except FileNotFoundError: - return False, f"Missing file [ {file[1]} ]" + return False, f"Missing file [ {file_name} ]" if h.hexdigest() != file[0]: - return False, f"Invalid checksum on {file[1]}" + return False, f"Invalid checksum on {file_name}" # get list of files in zip minus `checksums` file itself # and turn in to set to compare against list of files @@ -604,7 +616,7 @@ class BootstrapRepos: files_in_zip = zip_file.namelist() files_in_zip.remove("checksums") files_in_zip = set(files_in_zip) - files_in_checksum = set([file[1] for file in checksums]) + files_in_checksum = {file[1] for file in checksums} diff = files_in_zip.difference(files_in_checksum) if diff: return False, f"Missing files {diff}" @@ -628,16 +640,19 @@ class BootstrapRepos: ] files_in_dir.remove("checksums") files_in_dir = set(files_in_dir) - files_in_checksum = set([file[1] for file in checksums]) + files_in_checksum = {file[1] for file in checksums} for file in checksums: + file_name = file[1] + if platform.system().lower() == "windows": + file_name = file_name.replace("/", "\\") try: - current = sha256sum((path / file[1]).as_posix()) + current = sha256sum((path / file_name).as_posix()) except FileNotFoundError: - return False, f"Missing file [ {file[1]} ]" + return False, f"Missing file [ {file_name} ]" if file[0] != current: - return False, f"Invalid checksum on {file[1]}" + return False, f"Invalid checksum on {file_name}" diff = files_in_dir.difference(files_in_checksum) if diff: return False, f"Missing files {diff}" @@ -1161,9 +1176,9 @@ class BootstrapRepos: name = item.name if item.is_dir() else item.stem result = OpenPypeVersion.version_in_str(name) - if result[0]: + if result: detected_version: OpenPypeVersion - detected_version = result[1] + detected_version = result if item.is_dir() and not self._is_openpype_in_dir( item, detected_version diff --git a/openpype/api.py b/openpype/api.py index e4bbb104a3..a6529202ff 100644 --- a/openpype/api.py +++ b/openpype/api.py @@ -17,6 +17,7 @@ from .lib import ( version_up, get_asset, get_hierarchy, + get_workdir_data, get_version_from_path, get_last_version_from_path, get_app_environments_for_context, diff --git a/openpype/cli.py b/openpype/cli.py index 3194723d4c..4c4dc1a3c6 100644 --- a/openpype/cli.py +++ b/openpype/cli.py @@ -384,3 +384,15 @@ def syncserver(debug, active_site): if debug: os.environ['OPENPYPE_DEBUG'] = '3' PypeCommands().syncserver(active_site) + + +@main.command() +@click.argument("directory") +def repack_version(directory): + """Repack OpenPype version from directory. + + This command will re-create zip file from specified directory, + recalculating file checksums. It will try to use version detected in + directory name. + """ + PypeCommands().repack_version(directory) diff --git a/openpype/hosts/harmony/plugins/publish/collect_farm_render.py b/openpype/hosts/harmony/plugins/publish/collect_farm_render.py index fc80e7c029..31a249591e 100644 --- a/openpype/hosts/harmony/plugins/publish/collect_farm_render.py +++ b/openpype/hosts/harmony/plugins/publish/collect_farm_render.py @@ -126,7 +126,8 @@ class CollectFarmRender(openpype.lib.abstract_collect_render. # because of using 'renderFarm' as a family, replace 'Farm' with # capitalized task name - issue of avalon-core Creator app subset_name = node.split("/")[1] - task_name = context.data["anatomyData"]["task"].capitalize() + task_name = context.data["anatomyData"]["task"][ + "name"].capitalize() replace_str = "" if task_name.lower() not in subset_name.lower(): replace_str = task_name diff --git a/openpype/hosts/harmony/plugins/publish/collect_palettes.py b/openpype/hosts/harmony/plugins/publish/collect_palettes.py index b8671badb3..e47cbaf17e 100644 --- a/openpype/hosts/harmony/plugins/publish/collect_palettes.py +++ b/openpype/hosts/harmony/plugins/publish/collect_palettes.py @@ -28,7 +28,7 @@ class CollectPalettes(pyblish.api.ContextPlugin): # skip collecting if not in allowed task if self.allowed_tasks: - task_name = context.data["anatomyData"]["task"].lower() + task_name = context.data["anatomyData"]["task"]["name"].lower() if (not any([re.search(pattern, task_name) for pattern in self.allowed_tasks])): return diff --git a/openpype/hosts/houdini/startup/MainMenuCommon.xml b/openpype/hosts/houdini/startup/MainMenuCommon.xml index 2b556a2e75..c34310cf72 100644 --- a/openpype/hosts/houdini/startup/MainMenuCommon.xml +++ b/openpype/hosts/houdini/startup/MainMenuCommon.xml @@ -67,6 +67,16 @@ from avalon.houdini import pipeline pipeline.reload_pipeline()]]> + + + + + + diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index a321e576ee..f4c3a55c2b 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -18,7 +18,7 @@ from openpype.api import ( BuildWorkfile, get_version_from_path, get_anatomy_settings, - get_hierarchy, + get_workdir_data, get_asset, get_current_project_settings, ApplicationManager @@ -268,15 +268,21 @@ def format_anatomy(data): if not version: file = script_name() data["version"] = get_version_from_path(file) - project_document = io.find_one({"type": "project"}) + + project_doc = io.find_one({"type": "project"}) + asset_doc = io.find_one({ + "type": "asset", + "name": data["avalon"]["asset"] + }) + task_name = os.environ["AVALON_TASK"] + host_name = os.environ["AVALON_APP"] + context_data = get_workdir_data( + project_doc, asset_doc, task_name, host_name + ) + data.update(context_data) data.update({ "subset": data["avalon"]["subset"], - "asset": data["avalon"]["asset"], - "task": os.environ["AVALON_TASK"], "family": data["avalon"]["family"], - "project": {"name": project_document["name"], - "code": project_document["data"].get("code", '')}, - "hierarchy": get_hierarchy(), "frame": "#" * padding, }) return anatomy.format(data) diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_harmony_scenes.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_harmony_scenes.py index a4fed3bc3f..48c36aa067 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/collect_harmony_scenes.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_harmony_scenes.py @@ -49,10 +49,22 @@ class CollectHarmonyScenes(pyblish.api.InstancePlugin): # fix anatomy data anatomy_data_new = copy.deepcopy(anatomy_data) + + project_entity = context.data["projectEntity"] + asset_entity = context.data["assetEntity"] + + task_type = asset_entity["data"]["tasks"].get(task, {}).get("type") + project_task_types = project_entity["config"]["tasks"] + task_code = project_task_types.get(task_type, {}).get("short_name") + # updating hierarchy data anatomy_data_new.update({ "asset": asset_data["name"], - "task": task, + "task": { + "name": task, + "type": task_type, + "short": task_code, + }, "subset": subset_name }) diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_harmony_zips.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_harmony_zips.py index 93eff85486..40a969f8df 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/collect_harmony_zips.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_harmony_zips.py @@ -27,6 +27,7 @@ class CollectHarmonyZips(pyblish.api.InstancePlugin): anatomy_data = instance.context.data["anatomyData"] repres = instance.data["representations"] files = repres[0]["files"] + project_entity = context.data["projectEntity"] if files.endswith(".zip"): # A zip file was dropped @@ -45,14 +46,24 @@ class CollectHarmonyZips(pyblish.api.InstancePlugin): self.log.info("Copied data: {}".format(new_instance.data)) + task_type = asset_data["data"]["tasks"].get(task, {}).get("type") + project_task_types = project_entity["config"]["tasks"] + task_code = project_task_types.get(task_type, {}).get("short_name") + # 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 - }) + anatomy_data_new.update( + { + "asset": asset_data["name"], + "task": { + "name": task, + "type": task_type, + "short": task_code, + }, + "subset": subset_name + } + ) new_instance.data["label"] = f"{instance_name}" new_instance.data["subset"] = subset_name diff --git a/openpype/hosts/standalonepublisher/plugins/publish/extract_harmony_zip.py b/openpype/hosts/standalonepublisher/plugins/publish/extract_harmony_zip.py index adbac6ef09..ceac710bb5 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/extract_harmony_zip.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/extract_harmony_zip.py @@ -11,7 +11,7 @@ import zipfile import pyblish.api from avalon import api, io import openpype.api -from openpype.lib import get_workfile_template_key_from_context +from openpype.lib import get_workfile_template_key class ExtractHarmonyZip(openpype.api.Extractor): @@ -31,8 +31,10 @@ class ExtractHarmonyZip(openpype.api.Extractor): # Presets create_workfile = True - default_task = "harmonyIngest" - default_task_type = "Ingest" + default_task = { + "name": "harmonyIngest", + "type": "Ingest", + } default_task_status = "Ingested" assetversion_status = "Ingested" @@ -219,6 +221,19 @@ class ExtractHarmonyZip(openpype.api.Extractor): # Setup the data needed to form a valid work path filename anatomy = openpype.api.Anatomy() project_entity = instance.context.data["projectEntity"] + asset_entity = io.find_one({ + "type": "asset", + "name": instance.data["asset"] + }) + + task_name = instance.data.get("task") + task_type = asset_entity["data"]["tasks"][task_name].get("type") + + if task_type: + task_short = project_entity["config"]["tasks"].get( + task_type, {}).get("short_name") + else: + task_short = None data = { "root": api.registered_root(), @@ -229,18 +244,20 @@ class ExtractHarmonyZip(openpype.api.Extractor): "asset": instance.data["asset"], "hierarchy": openpype.api.get_hierarchy(instance.data["asset"]), "family": instance.data["family"], - "task": instance.data.get("task"), + "task": { + "name": task_name, + "type": task_type, + "short": task_short, + }, "subset": instance.data["subset"], "version": 1, "ext": "zip", } host_name = "harmony" - template_name = get_workfile_template_key_from_context( - instance.data["asset"], - instance.data.get("task"), + template_name = get_workfile_template_key( + instance.data.get("task").get("type"), host_name, project_name=project_entity["name"], - dbcon=io ) # Get a valid work filename first with version 1 diff --git a/openpype/lib/anatomy.py b/openpype/lib/anatomy.py index 7a4a55363c..aaf10479fd 100644 --- a/openpype/lib/anatomy.py +++ b/openpype/lib/anatomy.py @@ -989,6 +989,14 @@ class Templates: invalid_required = [] missing_required = [] replace_keys = [] + + task_data = data.get("task") + if ( + isinstance(task_data, StringType) + and "{task[name]}" in orig_template + ): + data["task"] = {"name": task_data} + for group in self.key_pattern.findall(template): orig_key = group[1:-1] key = str(orig_key) @@ -1074,6 +1082,10 @@ class Templates: output = collections.defaultdict(dict) for key, orig_value in templates.items(): if isinstance(orig_value, StringType): + # Replace {task} by '{task[name]}' for backward compatibility + if '{task}' in orig_value: + orig_value = orig_value.replace('{task}', '{task[name]}') + output[key] = self._format(orig_value, data) continue diff --git a/openpype/lib/applications.py b/openpype/lib/applications.py index b9bcecd3a0..30be92e886 100644 --- a/openpype/lib/applications.py +++ b/openpype/lib/applications.py @@ -1280,23 +1280,12 @@ def prepare_context_environments(data): anatomy = data["anatomy"] - asset_tasks = asset_doc.get("data", {}).get("tasks") or {} - task_info = asset_tasks.get(task_name) or {} - task_type = task_info.get("type") + task_type = workdir_data["task"]["type"] # Temp solution how to pass task type to `_prepare_last_workfile` data["task_type"] = task_type - workfile_template_key = get_workfile_template_key( - task_type, - app.host_name, - project_name=project_name, - project_settings=project_settings - ) - try: - workdir = get_workdir_with_workdir_data( - workdir_data, anatomy, template_key=workfile_template_key - ) + workdir = get_workdir_with_workdir_data(workdir_data, anatomy) except Exception as exc: raise ApplicationLaunchFailed( @@ -1329,10 +1318,10 @@ def prepare_context_environments(data): ) data["env"].update(context_env) - _prepare_last_workfile(data, workdir, workfile_template_key) + _prepare_last_workfile(data, workdir) -def _prepare_last_workfile(data, workdir, workfile_template_key): +def _prepare_last_workfile(data, workdir): """last workfile workflow preparation. Function check if should care about last workfile workflow and tries @@ -1395,6 +1384,10 @@ def _prepare_last_workfile(data, workdir, workfile_template_key): anatomy = data["anatomy"] # Find last workfile file_template = anatomy.templates["work"]["file"] + # Replace {task} by '{task[name]}' for backward compatibility + if '{task}' in file_template: + file_template = file_template.replace('{task}', '{task[name]}') + workdir_data.update({ "version": 1, "user": get_openpype_username(), diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index b043cbfdb4..372e116f43 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -7,6 +7,7 @@ import platform import logging import collections import functools +import getpass from openpype.settings import get_project_settings from .anatomy import Anatomy @@ -464,6 +465,7 @@ def get_workfile_template_key( return default +# TODO rename function as is not just "work" specific def get_workdir_data(project_doc, asset_doc, task_name, host_name): """Prepare data for workdir template filling from entered information. @@ -479,22 +481,31 @@ def get_workdir_data(project_doc, asset_doc, task_name, host_name): """ hierarchy = "/".join(asset_doc["data"]["parents"]) + task_type = asset_doc['data']['tasks'].get(task_name, {}).get('type') + + project_task_types = project_doc["config"]["tasks"] + task_code = project_task_types.get(task_type, {}).get("short_name") + data = { "project": { "name": project_doc["name"], "code": project_doc["data"].get("code") }, - "task": task_name, + "task": { + "name": task_name, + "type": task_type, + "short": task_code, + }, "asset": asset_doc["name"], "app": host_name, - "hierarchy": hierarchy + "user": getpass.getuser(), + "hierarchy": hierarchy, } return data def get_workdir_with_workdir_data( - workdir_data, anatomy=None, project_name=None, - template_key=None, dbcon=None + workdir_data, anatomy=None, project_name=None, template_key=None ): """Fill workdir path from entered data and project's anatomy. @@ -529,12 +540,10 @@ def get_workdir_with_workdir_data( anatomy = Anatomy(project_name) if not template_key: - template_key = get_workfile_template_key_from_context( - workdir_data["asset"], - workdir_data["task"], + template_key = get_workfile_template_key( + workdir_data["task"]["type"], workdir_data["app"], - project_name=workdir_data["project"]["name"], - dbcon=dbcon + project_name=workdir_data["project"]["name"] ) anatomy_filled = anatomy.format(workdir_data) @@ -648,7 +657,7 @@ def create_workfile_doc(asset_doc, task_name, filename, workdir, dbcon=None): anatomy = Anatomy(project_doc["name"]) # Get workdir path (result is anatomy.TemplateResult) template_workdir = get_workdir_with_workdir_data( - workdir_data, anatomy, dbcon=dbcon + workdir_data, anatomy ) template_workdir_path = str(template_workdir).replace("\\", "/") diff --git a/openpype/modules/default_modules/ftrack/plugins/publish/integrate_remove_components.py b/openpype/modules/default_modules/ftrack/plugins/publish/integrate_remove_components.py deleted file mode 100644 index 26cac0f1ae..0000000000 --- a/openpype/modules/default_modules/ftrack/plugins/publish/integrate_remove_components.py +++ /dev/null @@ -1,30 +0,0 @@ -import pyblish.api -import os - - -class IntegrateCleanComponentData(pyblish.api.InstancePlugin): - """ - Cleaning up thumbnail an mov files after they have been integrated - """ - - order = pyblish.api.IntegratorOrder + 0.5 - label = 'Clean component data' - families = ["ftrack"] - optional = True - active = False - - def process(self, instance): - - for comp in instance.data['representations']: - self.log.debug('component {}'.format(comp)) - - if "%" in comp['published_path'] or "#" in comp['published_path']: - continue - - if comp.get('thumbnail') or ("thumbnail" in comp.get('tags', [])): - os.remove(comp['published_path']) - self.log.info('Thumbnail image was erased') - - elif comp.get('preview') or ("preview" in comp.get('tags', [])): - os.remove(comp['published_path']) - self.log.info('Preview mov file was erased') diff --git a/openpype/modules/default_modules/sync_server/providers/sftp.py b/openpype/modules/default_modules/sync_server/providers/sftp.py index 4f505ae016..3390cd5d3d 100644 --- a/openpype/modules/default_modules/sync_server/providers/sftp.py +++ b/openpype/modules/default_modules/sync_server/providers/sftp.py @@ -192,7 +192,7 @@ class SFTPHandler(AbstractProvider): Format is importing for usage of python's format ** approach """ # roots cannot be locally overridden - return self.presets['roots'] + return self.presets['root'] def get_tree(self): """ diff --git a/openpype/plugins/publish/collect_anatomy_context_data.py b/openpype/plugins/publish/collect_anatomy_context_data.py index ec88d5669d..6b95979b76 100644 --- a/openpype/plugins/publish/collect_anatomy_context_data.py +++ b/openpype/plugins/publish/collect_anatomy_context_data.py @@ -54,6 +54,12 @@ class CollectAnatomyContextData(pyblish.api.ContextPlugin): if hierarchy_items: hierarchy = os.path.join(*hierarchy_items) + asset_tasks = asset_entity["data"]["tasks"] + task_type = asset_tasks.get(task_name, {}).get("type") + + project_task_types = project_entity["config"]["tasks"] + task_code = project_task_types.get(task_type, {}).get("short_name") + context_data = { "project": { "name": project_entity["name"], @@ -61,7 +67,11 @@ class CollectAnatomyContextData(pyblish.api.ContextPlugin): }, "asset": asset_entity["name"], "hierarchy": hierarchy.replace("\\", "/"), - "task": task_name, + "task": { + "name": task_name, + "type": task_type, + "short": task_code, + }, "username": context.data["user"], "app": context.data["hostName"] } diff --git a/openpype/plugins/publish/collect_anatomy_instance_data.py b/openpype/plugins/publish/collect_anatomy_instance_data.py index e0eb1618b5..da6a2195ee 100644 --- a/openpype/plugins/publish/collect_anatomy_instance_data.py +++ b/openpype/plugins/publish/collect_anatomy_instance_data.py @@ -214,6 +214,8 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin): project_doc = context.data["projectEntity"] context_asset_doc = context.data["assetEntity"] + project_task_types = project_doc["config"]["tasks"] + for instance in context: if self.follow_workfile_version: version_number = context.data('version') @@ -245,7 +247,18 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin): # Task task_name = instance.data.get("task") if task_name: - anatomy_updates["task"] = task_name + asset_tasks = asset_doc["data"]["tasks"] + task_type = asset_tasks.get(task_name, {}).get("type") + task_code = ( + project_task_types + .get(task_type, {}) + .get("short_name") + ) + anatomy_updates["task"] = { + "name": task_name, + "type": task_type, + "short": task_code + } # Additional data resolution_width = instance.data.get("resolutionWidth") diff --git a/openpype/plugins/publish/extract_burnin.py b/openpype/plugins/publish/extract_burnin.py index 06eb85c593..cbebed927a 100644 --- a/openpype/plugins/publish/extract_burnin.py +++ b/openpype/plugins/publish/extract_burnin.py @@ -184,7 +184,9 @@ class ExtractBurnin(openpype.api.Extractor): for key in self.positions: value = burnin_def.get(key) if value: - burnin_values[key] = value + burnin_values[key] = value.replace( + "{task}", "{task[name]}" + ) # Remove "delete" tag from new representation if "delete" in new_repre["tags"]: diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 7ff7466a2a..1611bd4afd 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -172,21 +172,26 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): anatomy_data["hierarchy"] = hierarchy # Make sure task name in anatomy data is same as on instance.data - task_name = instance.data.get("task") - if task_name: - anatomy_data["task"] = task_name - else: - # Just set 'task_name' variable to context task - task_name = anatomy_data["task"] - - # Find task type for current task name - # - this should be already prepared on instance asset_tasks = ( asset_entity.get("data", {}).get("tasks") ) or {} - task_info = asset_tasks.get(task_name) or {} - task_type = task_info.get("type") - instance.data["task_type"] = task_type + task_name = instance.data.get("task") + if task_name: + task_info = asset_tasks.get(task_name) or {} + task_type = task_info.get("type") + + project_task_types = project_entity["config"]["tasks"] + task_code = project_task_types.get(task_type, {}).get("short_name") + anatomy_data["task"] = { + "name": task_name, + "type": task_type, + "short": task_code + } + + else: + # Just set 'task_name' variable to context task + task_name = anatomy_data["task"]["name"] + task_type = anatomy_data["task"]["type"] # Fill family in anatomy data anatomy_data["family"] = instance.data.get("family") @@ -804,11 +809,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): # - is there a chance that task name is not filled in anatomy # data? # - should we use context task in that case? - task_name = ( - instance.data["anatomyData"]["task"] - or io.Session["AVALON_TASK"] - ) - task_type = instance.data["task_type"] + task_name = instance.data["anatomyData"]["task"]["name"] + task_type = instance.data["anatomyData"]["task"]["type"] filtering_criteria = { "families": instance.data["family"], "hosts": instance.context.data["hostName"], diff --git a/openpype/pype_commands.py b/openpype/pype_commands.py index f0ba9a997e..519e7c285b 100644 --- a/openpype/pype_commands.py +++ b/openpype/pype_commands.py @@ -392,3 +392,10 @@ class PypeCommands: import time while True: time.sleep(1.0) + + def repack_version(self, directory): + """Repacking OpenPype version.""" + from openpype.tools.repack_version import VersionRepacker + + version_packer = VersionRepacker(directory) + version_packer.process() diff --git a/openpype/settings/defaults/project_anatomy/templates.json b/openpype/settings/defaults/project_anatomy/templates.json index 53abd35ed5..9a03b893bf 100644 --- a/openpype/settings/defaults/project_anatomy/templates.json +++ b/openpype/settings/defaults/project_anatomy/templates.json @@ -6,8 +6,8 @@ "frame": "{frame:0>{@frame_padding}}" }, "work": { - "folder": "{root[work]}/{project[name]}/{hierarchy}/{asset}/work/{task}", - "file": "{project[code]}_{asset}_{task}_{@version}<_{comment}>.{ext}", + "folder": "{root[work]}/{project[name]}/{hierarchy}/{asset}/work/{task[name]}", + "file": "{project[code]}_{asset}_{task[name]}_{@version}<_{comment}>.{ext}", "path": "{@folder}/{@file}" }, "render": { diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_templates.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_templates.json index a8534e7e29..e208069e6f 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_templates.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_templates.json @@ -11,6 +11,10 @@ "type": "dict", "key": "defaults", "children": [ + { + "type": "label", + "label": "The list of existing placeholders is available here:
https://openpype.io/docs/admin_settings_project_anatomy/#available-template-keys " + }, { "type": "number", "key": "version_padding", diff --git a/openpype/settings/lib.py b/openpype/settings/lib.py index 60ed54bd4a..ff75562413 100644 --- a/openpype/settings/lib.py +++ b/openpype/settings/lib.py @@ -856,6 +856,7 @@ def get_anatomy_settings( apply_local_settings_on_anatomy_settings( result, local_settings, project_name, site_name ) + return result diff --git a/openpype/tools/repack_version.py b/openpype/tools/repack_version.py new file mode 100644 index 0000000000..0172264c79 --- /dev/null +++ b/openpype/tools/repack_version.py @@ -0,0 +1,164 @@ +# -*- coding: utf-8 -*- +"""Script to rehash and repack current version.""" + +import enlighten +import blessed +from pathlib import Path +import platform +from zipfile import ZipFile +from typing import List +import hashlib +import sys +from igniter.bootstrap_repos import OpenPypeVersion + + +class VersionRepacker: + + def __init__(self, directory: str): + self._term = blessed.Terminal() + self._manager = enlighten.get_manager() + self._last_increment = 0 + self.version_path = Path(directory) + self.zip_path = self.version_path.parent + _version = {} + with open(self.version_path / "openpype" / "version.py") as fp: + exec(fp.read(), _version) + self._version_py = _version["__version__"] + del _version + + def _print(self, msg: str, message_type: int = 0) -> None: + """Print message to console. + + Args: + msg (str): message to print + message_type (int): type of message (0 info, 1 error, 2 note) + + """ + if message_type == 0: + header = self._term.aquamarine3(">>> ") + elif message_type == 1: + header = self._term.orangered2("!!! ") + elif message_type == 2: + header = self._term.tan1("... ") + else: + header = self._term.darkolivegreen3("--- ") + + print("{}{}".format(header, msg)) + + @staticmethod + def sha256sum(filename): + """Calculate sha256 for content of the file. + + Args: + filename (str): Path to file. + + Returns: + str: hex encoded sha256 + + """ + h = hashlib.sha256() + b = bytearray(128 * 1024) + mv = memoryview(b) + with open(filename, 'rb', buffering=0) as f: + for n in iter(lambda: f.readinto(mv), 0): + h.update(mv[:n]) + return h.hexdigest() + + @staticmethod + def _filter_dir(path: Path, path_filter: List) -> List[Path]: + """Recursively crawl over path and filter.""" + result = [] + for item in path.iterdir(): + if item.name in path_filter: + continue + if item.name.startswith('.'): + continue + if item.is_dir(): + result.extend(VersionRepacker._filter_dir(item, path_filter)) + else: + result.append(item) + return result + + def process(self): + if (self.version_path / "pyproject.toml").exists(): + self._print( + ("This cannot run on OpenPype sources. " + "Please run it on extracted version."), 1) + return + self._print(f"Rehashing and zipping {self.version_path}") + version = OpenPypeVersion.version_in_str(self.version_path.name) + if not version: + self._print("Cannot get version from directory", 1) + return + + self._print(f"Detected version is {version}") + # replace version in version.py + self._replace_version(version, self.version_path) + self._print("Recalculating checksums ...", 2) + + checksums = [] + + file_list = VersionRepacker._filter_dir(self.version_path, []) + progress_bar = enlighten.Counter( + total=len(file_list), desc="Calculating checksums", + nits="%", color="green") + for file in file_list: + checksums.append(( + VersionRepacker.sha256sum(file.as_posix()), + file.resolve().relative_to(self.version_path), + file + )) + progress_bar.update() + progress_bar.close() + + progress_bar = enlighten.Counter( + total=len(checksums), desc="Zipping directory", + nits="%", color=(56, 211, 159)) + + zip_filename = self.zip_path / f"openpype-v{version}.zip" + with ZipFile(zip_filename, "w") as zip_file: + + for item in checksums: + if item[1].as_posix() == "checksums": + progress_bar.update() + continue + zip_file.write(item[2], item[1]) + progress_bar.update() + + checksums_str = "" + for c in checksums: + file_str = c[1] + if platform.system().lower() == "windows": + file_str = c[1].as_posix().replace("\\", "/") + checksums_str += "{}:{}\n".format(c[0], file_str) + zip_file.writestr("checksums", checksums_str) + # test if zip is ok + zip_file.testzip() + self._print(f"All done, you can find new zip here: {zip_filename}") + + @staticmethod + def _replace_version(version: OpenPypeVersion, path: Path): + """Replace version in version.py. + + Args: + version (OpenPypeVersion): OpenPype version to set + path (Path): Path to unzipped version. + + """ + with open(path / "openpype" / "version.py", "r") as op_version_file: + replacement = "" + + for line in op_version_file: + stripped_line = line.strip() + if stripped_line.strip().startswith("__version__ ="): + line = f'__version__ = "{version}"\n' + replacement += line + + with open(path / "openpype" / "version.py", "w") as op_version_file: + op_version_file.write(replacement) + + +if __name__ == '__main__': + print(sys.argv[1]) + version_packer = VersionRepacker(sys.argv[1]) + version_packer.process() diff --git a/openpype/tools/workfiles/app.py b/openpype/tools/workfiles/app.py index c056a2f22f..a4b1717a1c 100644 --- a/openpype/tools/workfiles/app.py +++ b/openpype/tools/workfiles/app.py @@ -59,20 +59,39 @@ class NameWindow(QtWidgets.QDialog): # Set work file data for template formatting asset_name = session["AVALON_ASSET"] + task_name = session["AVALON_TASK"] project_doc = io.find_one( {"type": "project"}, { "name": True, - "data.code": True + "data.code": True, + "config.tasks": True, } ) + asset_doc = io.find_one( + { + "type": "asset", + "name": asset_name + }, + {"data.tasks": True} + ) + + task_type = asset_doc["data"]["tasks"].get(task_name, {}).get("type") + + project_task_types = project_doc["config"]["tasks"] + task_short = project_task_types.get(task_type, {}).get("short_name") + self.data = { "project": { "name": project_doc["name"], "code": project_doc["data"].get("code") }, "asset": asset_name, - "task": session["AVALON_TASK"], + "task": { + "name": task_name, + "type": task_type, + "short": task_short, + }, "version": 1, "user": getpass.getuser(), "comment": "", @@ -640,7 +659,7 @@ class FilesWidget(QtWidgets.QWidget): self.host.save_file(file_path) self.set_asset_task( - self._asse_id, self._task_name, self._task_type + self._asset_id, self._task_name, self._task_type ) pipeline.emit("after.workfile.save", [file_path]) diff --git a/tests/unit/igniter/test_bootstrap_repos.py b/tests/unit/igniter/test_bootstrap_repos.py index 740a71a5ce..d6e861c262 100644 --- a/tests/unit/igniter/test_bootstrap_repos.py +++ b/tests/unit/igniter/test_bootstrap_repos.py @@ -140,7 +140,7 @@ def test_search_string_for_openpype_version(printer): ] for ver_string in strings: printer(f"testing {ver_string[0]} should be {ver_string[1]}") - assert OpenPypeVersion.version_in_str(ver_string[0])[0] == \ + assert OpenPypeVersion.version_in_str(ver_string[0]) == \ ver_string[1] diff --git a/website/docs/admin_openpype_commands.md b/website/docs/admin_openpype_commands.md index 7a46ee7906..0831cf4f5a 100644 --- a/website/docs/admin_openpype_commands.md +++ b/website/docs/admin_openpype_commands.md @@ -32,7 +32,10 @@ For more information [see here](admin_use#run-openpype). | Command | Description | Arguments | | --- | --- |: --- :| -| tray | Launch OpenPype Tray. | [📑](#tray-arguments) +| contextselection | Open Context selection dialog. | | +| module | Run command line arguments for modules. | | +| repack-version | Tool to re-create version zip. | [📑](#repack-version-arguments) | +| tray | Launch OpenPype Tray. | [📑](#tray-arguments) | eventserver | This should be ideally used by system service (such as systemd or upstart on linux and window service). | [📑](#eventserver-arguments) | | launch | Launch application in Pype environment. | [📑](#launch-arguments) | | publish | Pype takes JSON from provided path and use it to publish data in it. | [📑](#publish-arguments) | @@ -156,4 +159,10 @@ openpypeconsole settings `standalonepublisher` has no command-line arguments. ```shell openpype_console standalonepublisher -``` \ No newline at end of file +``` + +### `repack-version` arguments {#repack-version-arguments} +Takes path to unzipped and possibly modified OpenPype version. Files will be +zipped, checksums recalculated and version will be determined by folder name +(and written to `version.py`). + diff --git a/website/docs/admin_settings_project_anatomy.md b/website/docs/admin_settings_project_anatomy.md index 54023d468f..30784686e2 100644 --- a/website/docs/admin_settings_project_anatomy.md +++ b/website/docs/admin_settings_project_anatomy.md @@ -57,7 +57,9 @@ We have a few required anatomy templates for OpenPype to work properly, however | `project[code]` | Project's code | | `hierarchy` | All hierarchical parents as subfolders | | `asset` | Name of asset or shot | -| `task` | Name of task | +| `task[name]` | Name of task | +| `task[type]` | Type of task | +| `task[short]` | Shortname of task | | `version` | Version number | | `subset` | Subset name | | `family` | Main family name |