diff --git a/.github/workflows/release_trigger.yml b/.github/workflows/release_trigger.yml new file mode 100644 index 0000000..01a3b3a --- /dev/null +++ b/.github/workflows/release_trigger.yml @@ -0,0 +1,12 @@ +name: 🚀 Release Trigger + +on: + workflow_dispatch: + +jobs: + call-release-trigger: + uses: ynput/ops-repo-automation/.github/workflows/release_trigger.yml@main + secrets: + token: ${{ secrets.YNPUT_BOT_TOKEN }} + email: ${{ secrets.CI_EMAIL }} + user: ${{ secrets.CI_USER }} diff --git a/.github/workflows/upload_to_ynput_cloud.yml b/.github/workflows/upload_to_ynput_cloud.yml new file mode 100644 index 0000000..7745a8e --- /dev/null +++ b/.github/workflows/upload_to_ynput_cloud.yml @@ -0,0 +1,16 @@ +name: 📤 Upload to Ynput Cloud + +on: + workflow_dispatch: + release: + types: [published] + +jobs: + call-upload-to-ynput-cloud: + uses: ynput/ops-repo-automation/.github/workflows/upload_to_ynput_cloud.yml@main + secrets: + CI_EMAIL: ${{ secrets.CI_EMAIL }} + CI_USER: ${{ secrets.CI_USER }} + YNPUT_BOT_TOKEN: ${{ secrets.YNPUT_BOT_TOKEN }} + YNPUT_CLOUD_URL: ${{ secrets.YNPUT_CLOUD_URL }} + YNPUT_CLOUD_TOKEN: ${{ secrets.YNPUT_CLOUD_TOKEN }} diff --git a/client/ayon_traypublisher/addon.py b/client/ayon_traypublisher/addon.py index dd78a70..27db382 100644 --- a/client/ayon_traypublisher/addon.py +++ b/client/ayon_traypublisher/addon.py @@ -1,6 +1,9 @@ import os from pathlib import Path + +import ayon_api + from ayon_core.lib import get_ayon_launcher_args from ayon_core.lib.execute import run_detached_process from ayon_core.addon import ( @@ -109,6 +112,14 @@ def ingestcsv( """ from .csv_publish import csvpublish + # Allow user override through AYON_USERNAME when + # current connection is made through a service user. + username = os.environ.get("AYON_USERNAME") + if username: + con = ayon_api.get_server_api_connection() + if con.is_service_user(): + con.set_default_service_username(username) + # use Path to check if csv_filepath exists if not Path(filepath).exists(): raise FileNotFoundError(f"File {filepath} does not exist.") diff --git a/client/ayon_traypublisher/plugins/create/create_csv_ingest.py b/client/ayon_traypublisher/plugins/create/create_csv_ingest.py index e055444..58529d8 100644 --- a/client/ayon_traypublisher/plugins/create/create_csv_ingest.py +++ b/client/ayon_traypublisher/plugins/create/create_csv_ingest.py @@ -55,7 +55,7 @@ def _get_row_value_with_validation( # get column default value column_default = column_data["default"] - if column_type in ["number", "decimal"] and column_default == 0: + if column_type in ["number", "decimal"] and column_default in (0, '0'): column_default = None # check if column value is not empty string @@ -654,10 +654,25 @@ def _add_representation( # convert ### string in file name to %03d # this is for correct frame range validation # example: file.###.exr -> file.%03d.exr + file_head = basename.split(".")[0] if "#" in basename: padding = len(basename.split("#")) - 1 - basename = basename.replace("#" * padding, f"%0{padding}d") + seq_padding = f"%0{padding}d" + basename = basename.replace("#" * padding, seq_padding) + file_head = basename.split(seq_padding)[0] is_sequence = True + elif "%" in basename: + pattern = re.compile(r"%\d+d|%d") + padding = pattern.findall(basename) + if not padding: + raise CreatorError( + f"File sequence padding not found in '{basename}'." + ) + file_head = basename.split("%")[0] + is_sequence = True + else: + # in case it is still image + is_sequence = False # make absolute path to file dirname: str = os.path.dirname(repre_item.filepath) @@ -672,8 +687,14 @@ def _add_representation( frame_end: Union[int, None] = None files: Union[str, List[str]] = basename if is_sequence: + # get only filtered files form dirname + files_from_dir = [ + filename + for filename in os.listdir(dirname) + if filename.startswith(file_head) + ] # collect all data from dirname - cols, _ = clique.assemble(list(os.listdir(dirname))) + cols, _ = clique.assemble(files_from_dir) if not cols: raise CreatorError( f"No collections found in directory '{dirname}'." @@ -768,7 +789,11 @@ def _create_instances_from_csv_data(self, csv_dir: str, filename: str): product_item.product_type, product_item.variant ) - label: str = f"{folder_path}_{product_name}_v{version:>03}" + + if version is not None: + label: str = f"{folder_path}_{product_name}_v{version:>03}" + else: + label: str = f"{folder_path}_{product_name}_v[next]" repre_items: List[RepreItem] = product_item.repre_items first_repre_item: RepreItem = repre_items[0] diff --git a/client/ayon_traypublisher/plugins/publish/collect_frame_data_from_folder_entity.py b/client/ayon_traypublisher/plugins/publish/collect_frame_data_from_folder_entity.py index 2e564a2..5c9f910 100644 --- a/client/ayon_traypublisher/plugins/publish/collect_frame_data_from_folder_entity.py +++ b/client/ayon_traypublisher/plugins/publish/collect_frame_data_from_folder_entity.py @@ -2,14 +2,14 @@ class CollectFrameDataFromAssetEntity(pyblish.api.InstancePlugin): - """Collect Frame Data From 'folderEntity' found in context. + """Collect Frame Data From `taskEntity` or `folderEntity` of instance. - Frame range data will only be collected if the keys - are not yet collected for the instance. + Frame range data will only be collected if the keys are not yet + collected for the instance. """ order = pyblish.api.CollectorOrder + 0.491 - label = "Collect Missing Frame Data From Folder" + label = "Collect Missing Frame Data From Folder/Task" families = [ "plate", "pointcache", @@ -38,14 +38,20 @@ def process(self, instance): return keys_set = [] - folder_attributes = instance.data["folderEntity"]["attrib"] + + folder_entity = instance.data["folderEntity"] + task_entity = instance.data.get("taskEntity") + context_attributes = ( + task_entity["attrib"] if task_entity else folder_entity["attrib"] + ) + for key in missing_keys: - if key in folder_attributes: - instance.data[key] = folder_attributes[key] + if key in context_attributes: + instance.data[key] = context_attributes[key] keys_set.append(key) if keys_set: self.log.debug( f"Frame range data {keys_set} " - "has been collected from folder entity." + "has been collected from folder (or task) entity." ) diff --git a/client/ayon_traypublisher/plugins/publish/collect_review_frames.py b/client/ayon_traypublisher/plugins/publish/collect_review_frames.py index 7eceda9..5755b7a 100644 --- a/client/ayon_traypublisher/plugins/publish/collect_review_frames.py +++ b/client/ayon_traypublisher/plugins/publish/collect_review_frames.py @@ -20,12 +20,16 @@ class CollectReviewInfo(pyblish.api.InstancePlugin): hosts = ["traypublisher"] def process(self, instance): - folder_entity = instance.data.get("folderEntity") - if instance.data.get("frameStart") is not None or not folder_entity: + + entity = ( + instance.data.get("taskEntity") + or instance.data.get("folderEntity") + ) + if instance.data.get("frameStart") is not None or not entity: self.log.debug("Missing required data on instance") return - folder_attributes = folder_entity["attrib"] + context_attributes = entity["attrib"] # Store collected data for logging collected_data = {} for key in ( @@ -35,9 +39,9 @@ def process(self, instance): "handleStart", "handleEnd", ): - if key in instance.data or key not in folder_attributes: + if key in instance.data or key not in context_attributes: continue - value = folder_attributes[key] + value = context_attributes[key] collected_data[key] = value instance.data[key] = value self.log.debug("Collected data: {}".format(str(collected_data))) diff --git a/client/ayon_traypublisher/plugins/publish/collect_sequence_frame_data.py b/client/ayon_traypublisher/plugins/publish/collect_sequence_frame_data.py index c2894e1..39a05b9 100644 --- a/client/ayon_traypublisher/plugins/publish/collect_sequence_frame_data.py +++ b/client/ayon_traypublisher/plugins/publish/collect_sequence_frame_data.py @@ -49,7 +49,11 @@ def process(self, instance): def get_frame_data_from_repre_sequence(self, instance): repres = instance.data.get("representations") - folder_attributes = instance.data["folderEntity"]["attrib"] + + entity: dict = ( + instance.data.get("taskEntity") or instance.data["folderEntity"] + ) + entity_attributes: dict = entity["attrib"] if repres: first_repre = repres[0] @@ -78,5 +82,5 @@ def get_frame_data_from_repre_sequence(self, instance): "frameEnd": repres_frames[-1], "handleStart": 0, "handleEnd": 0, - "fps": folder_attributes["fps"] + "fps": entity_attributes["fps"] } diff --git a/client/ayon_traypublisher/plugins/publish/validate_frame_ranges.py b/client/ayon_traypublisher/plugins/publish/validate_frame_ranges.py index 42127f4..a6e0c78 100644 --- a/client/ayon_traypublisher/plugins/publish/validate_frame_ranges.py +++ b/client/ayon_traypublisher/plugins/publish/validate_frame_ranges.py @@ -47,11 +47,15 @@ def process(self, instance): for pattern in self.skip_timelines_check)): self.log.info("Skipping for {} task".format(instance.data["task"])) - folder_attributes = instance.data["folderEntity"]["attrib"] - frame_start = folder_attributes["frameStart"] - frame_end = folder_attributes["frameEnd"] - handle_start = folder_attributes["handleStart"] - handle_end = folder_attributes["handleEnd"] + # Use attributes from task entity if set, otherwise from folder entity + entity = ( + instance.data.get("taskEntity") or instance.data["folderEntity"] + ) + attributes = entity["attrib"] + frame_start = attributes["frameStart"] + frame_end = attributes["frameEnd"] + handle_start = attributes["handleStart"] + handle_end = attributes["handleEnd"] duration = (frame_end - frame_start + 1) + handle_start + handle_end repres = instance.data.get("representations") @@ -73,7 +77,7 @@ def process(self, instance): msg = ( "Frame duration from DB:'{}' doesn't match number of files:'{}'" - " Please change frame range for Folder or limit no. of files" + " Please change frame range for folder/task or limit no. of files" ). format(int(duration), frames) formatting_data = {"duration": duration, diff --git a/client/ayon_traypublisher/version.py b/client/ayon_traypublisher/version.py index 405e47d..7ffcf53 100644 --- a/client/ayon_traypublisher/version.py +++ b/client/ayon_traypublisher/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring AYON addon 'traypublisher' version.""" -__version__ = "0.2.6-dev.1" +__version__ = "0.2.8+dev" diff --git a/package.py b/package.py index 4c64b7c..8fed3a3 100644 --- a/package.py +++ b/package.py @@ -1,9 +1,10 @@ name = "traypublisher" title = "TrayPublisher" -version = "0.2.6-dev.1" - +version = "0.2.8+dev" +app_host_name = "traypublisher" client_dir = "ayon_traypublisher" +ayon_server_version = ">=1.1.2" ayon_required_addons = { "core": ">0.3.2", } diff --git a/server/settings/creator_plugins.py b/server/settings/creator_plugins.py index 0e7963a..9fca980 100644 --- a/server/settings/creator_plugins.py +++ b/server/settings/creator_plugins.py @@ -182,7 +182,7 @@ class TrayPublisherCreatePluginsModel(BaseSettingsModel): "type": "text", "default": "", "required_column": True, - "validation_pattern": "^([a-zA-Z\\:\\ 0-9#._\\\\/]*)$" + "validation_pattern": "^([a-zA-Z\\:\\ 0-9#\\-\\._\\\\/]*)$" }, { "name": "Folder Path", @@ -215,8 +215,8 @@ class TrayPublisherCreatePluginsModel(BaseSettingsModel): { "name": "Version", "type": "number", - "default": "1", - "required_column": True, + "default": "0", + "required_column": False, "validation_pattern": "^(\\d{1,3})$" }, { @@ -231,7 +231,7 @@ class TrayPublisherCreatePluginsModel(BaseSettingsModel): "type": "text", "default": "", "required_column": False, - "validation_pattern": "^([a-zA-Z\\:\\ 0-9#._\\\\/]*)$" + "validation_pattern": "^([a-zA-Z\\:\\ 0-9#\\-\\._\\\\/]*)$" }, { "name": "Frame Start",