diff --git a/client/ayon_core/pipeline/create/structures.py b/client/ayon_core/pipeline/create/structures.py index 311d382ac9..9019b05b21 100644 --- a/client/ayon_core/pipeline/create/structures.py +++ b/client/ayon_core/pipeline/create/structures.py @@ -724,7 +724,7 @@ class CreatedInstance: value when set to True. """ - return self._data.get("has_promised_context", False) + return self._transient_data.get("has_promised_context", False) def data_to_store(self): """Collect data that contain json parsable types. diff --git a/client/ayon_core/plugins/actions/show_in_ayon.py b/client/ayon_core/plugins/actions/show_in_ayon.py new file mode 100644 index 0000000000..e30eaa2bc9 --- /dev/null +++ b/client/ayon_core/plugins/actions/show_in_ayon.py @@ -0,0 +1,87 @@ +import os +import urllib.parse +import webbrowser + +from ayon_core.pipeline import LauncherAction +from ayon_core.resources import get_ayon_icon_filepath +import ayon_api + + +def get_ayon_entity_uri( + project_name, + entity_id, + entity_type, +) -> str: + """Resolve AYON Entity URI from representation context. + + Note: + The representation context is the `get_representation_context` dict + containing the `project`, `folder, `representation` and so forth. + It is not the representation entity `context` key. + + Arguments: + project_name (str): The project name. + entity_id (str): The entity UUID. + entity_type (str): The entity type, like "folder" or"task". + + Raises: + RuntimeError: Unable to resolve to a single valid URI. + + Returns: + str: The AYON entity URI. + + """ + response = ayon_api.post( + f"projects/{project_name}/uris", + entityType=entity_type, + ids=[entity_id]) + if response.status_code != 200: + raise RuntimeError( + f"Unable to resolve AYON entity URI for '{project_name}' " + f"{entity_type} id '{entity_id}': {response.text}" + ) + uris = response.data["uris"] + if len(uris) != 1: + raise RuntimeError( + f"Unable to resolve AYON entity URI for '{project_name}' " + f"{entity_type} id '{entity_id}' to single URI. " + f"Received data: {response.data}" + ) + return uris[0]["uri"] + + +class ShowInAYON(LauncherAction): + """Open AYON browser page to the current context.""" + name = "showinayon" + label = "Show in AYON" + icon = get_ayon_icon_filepath() + order = 999 + + def process(self, selection, **kwargs): + url = os.environ["AYON_SERVER_URL"] + if selection.is_project_selected: + project_name = selection.project_name + url += f"/projects/{project_name}/browser" + + # Specify entity URI if task or folder is select + entity = None + entity_type = None + if selection.is_task_selected: + entity = selection.get_task_entity() + entity_type = "task" + elif selection.is_folder_selected: + entity = selection.get_folder_entity() + entity_type = "folder" + + if entity and entity_type: + uri = get_ayon_entity_uri( + project_name, + entity_id=entity["id"], + entity_type=entity_type + ) + uri_encoded = urllib.parse.quote_plus(uri) + url += f"?uri={uri_encoded}" + + # Open URL in webbrowser + self.log.info(f"Opening URL: {url}") + webbrowser.open_new_tab(url) diff --git a/client/ayon_core/plugins/publish/collect_context_entities.py b/client/ayon_core/plugins/publish/collect_context_entities.py index c8d25bc3e6..4de83f0d53 100644 --- a/client/ayon_core/plugins/publish/collect_context_entities.py +++ b/client/ayon_core/plugins/publish/collect_context_entities.py @@ -53,8 +53,9 @@ class CollectContextEntities(pyblish.api.ContextPlugin): context.data["folderEntity"] = folder_entity context.data["taskEntity"] = task_entity - - folder_attributes = folder_entity["attrib"] + context_attributes = ( + task_entity["attrib"] if task_entity else folder_entity["attrib"] + ) # Task type task_type = None @@ -63,12 +64,12 @@ class CollectContextEntities(pyblish.api.ContextPlugin): context.data["taskType"] = task_type - frame_start = folder_attributes.get("frameStart") + frame_start = context_attributes.get("frameStart") if frame_start is None: frame_start = 1 self.log.warning("Missing frame start. Defaulting to 1.") - frame_end = folder_attributes.get("frameEnd") + frame_end = context_attributes.get("frameEnd") if frame_end is None: frame_end = 2 self.log.warning("Missing frame end. Defaulting to 2.") @@ -76,8 +77,8 @@ class CollectContextEntities(pyblish.api.ContextPlugin): context.data["frameStart"] = frame_start context.data["frameEnd"] = frame_end - handle_start = folder_attributes.get("handleStart") or 0 - handle_end = folder_attributes.get("handleEnd") or 0 + handle_start = context_attributes.get("handleStart") or 0 + handle_end = context_attributes.get("handleEnd") or 0 context.data["handleStart"] = int(handle_start) context.data["handleEnd"] = int(handle_end) @@ -87,7 +88,7 @@ class CollectContextEntities(pyblish.api.ContextPlugin): context.data["frameStartHandle"] = frame_start_h context.data["frameEndHandle"] = frame_end_h - context.data["fps"] = folder_attributes["fps"] + context.data["fps"] = context_attributes["fps"] def _get_folder_entity(self, project_name, folder_path): if not folder_path: diff --git a/client/ayon_core/plugins/publish/extract_burnin.py b/client/ayon_core/plugins/publish/extract_burnin.py index 58a032a030..2007240d3d 100644 --- a/client/ayon_core/plugins/publish/extract_burnin.py +++ b/client/ayon_core/plugins/publish/extract_burnin.py @@ -199,7 +199,7 @@ class ExtractBurnin(publish.Extractor): if not burnins_per_repres: self.log.debug( "Skipped instance. No representations found matching a burnin" - "definition in: %s", burnin_defs + " definition in: %s", burnin_defs ) return @@ -399,7 +399,7 @@ class ExtractBurnin(publish.Extractor): add_repre_files_for_cleanup(instance, new_repre) - # Cleanup temp staging dir after procesisng of output definitions + # Cleanup temp staging dir after processing of output definitions if do_convert: temp_dir = repre["stagingDir"] shutil.rmtree(temp_dir) @@ -420,6 +420,12 @@ class ExtractBurnin(publish.Extractor): self.log.debug("Removed: \"{}\"".format(filepath)) def _get_burnin_options(self): + """Get the burnin options from `ExtractBurnin` settings. + + Returns: + dict[str, Any]: Burnin options. + + """ # Prepare burnin options burnin_options = copy.deepcopy(self.default_options) if self.options: @@ -696,7 +702,7 @@ class ExtractBurnin(publish.Extractor): """Prepare data for representation. Args: - instance (Instance): Currently processed Instance. + instance (pyblish.api.Instance): Currently processed Instance. repre (dict): Currently processed representation. burnin_data (dict): Copy of basic burnin data based on instance data. @@ -752,9 +758,11 @@ class ExtractBurnin(publish.Extractor): Args: profile (dict): Profile from presets matching current context. + instance (pyblish.api.Instance): Publish instance. Returns: - list: Contain all valid output definitions. + list[dict[str, Any]]: Contain all valid output definitions. + """ filtered_burnin_defs = [] @@ -773,12 +781,11 @@ class ExtractBurnin(publish.Extractor): if not self.families_filter_validation( families, families_filters ): - self.log.debug(( - "Skipped burnin definition \"{}\". Family" - " filters ({}) does not match current instance families: {}" - ).format( - filename_suffix, str(families_filters), str(families) - )) + self.log.debug( + f"Skipped burnin definition \"{filename_suffix}\"." + f" Family filters ({families_filters}) does not match" + f" current instance families: {families}" + ) continue # Burnin values