From 10c66f496e911b105c7ce34818d10bd471059a14 Mon Sep 17 00:00:00 2001 From: Philippe Leprince Date: Fri, 9 May 2025 14:39:26 +0200 Subject: [PATCH 01/39] first version Signed-off-by: Philippe Leprince --- .github/workflows/deploy_mkdocs.yml | 78 +++++++++++++++++++++++++++++ docs/mkdocs_requirements.txt | 9 ++++ 2 files changed, 87 insertions(+) create mode 100644 .github/workflows/deploy_mkdocs.yml create mode 100644 docs/mkdocs_requirements.txt diff --git a/.github/workflows/deploy_mkdocs.yml b/.github/workflows/deploy_mkdocs.yml new file mode 100644 index 0000000000..9fd97c1e82 --- /dev/null +++ b/.github/workflows/deploy_mkdocs.yml @@ -0,0 +1,78 @@ +name: Deploy MkDocs + +on: + workflow_dispatch: + workflow_call: + inputs: + repo: + type: string + required: true + branch_name: + type: string + required: true + default: "main" + tag: + type: string + required: true + secrets: + token: + required: true + user: + required: true + email: + required: true + +env: + GH_TOKEN: ${{ secrets.token || secrets.YNPUT_BOT_TOKEN }} + GH_USER: ${{ secrets.user || secrets.CI_USER }} + GH_EMAIL: ${{ secrets.email || secrets.CI_EMAIL }} + +jobs: + + verify-latest-release: + uses: ynput/ops-repo-automation/.github/workflows/verify_latest_release.yml@main + with: + repo: ${{ github.repository }} + expect_release: true + secrets: + gh_token: ${{ secrets.token }} + + verify-repo-secrets: + uses: ynput/ops-repo-automation/.github/workflows/verify_secrets.yml@main + with: + repo: ${{ github.repository }} + secrets: + gh_token: ${{ secrets.token }} + gh_user: ${{ secrets.user }} + gh_email: ${{ secrets.email }} + + verify-repo-vars: + uses: ynput/ops-repo-automation/.github/workflows/verify_variables.yml@main + with: + variables: "MAIN_BRANCH,MINOR_BUMP_LABEL,PATCH_BUMP_LABEL,PROJECT_NAME" + repo: ${{ github.repository }} + secrets: + gh_token: ${{ secrets.token }} + + deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout ${{ inputs.branch_name }} + uses: actions/checkout@v4 + with: + ref: ${{ inputs.branch_name }} + fetch-depth: 0 + submodules: true + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.x" + + - name: Install dependencies + run: | + python3 -m pip install -r ./docs/mkdocs_requirements.txt + + - name: Mike deploy + run: mike deploy --update-aliases ${{ inputs.tag }} latest diff --git a/docs/mkdocs_requirements.txt b/docs/mkdocs_requirements.txt new file mode 100644 index 0000000000..829d02951a --- /dev/null +++ b/docs/mkdocs_requirements.txt @@ -0,0 +1,9 @@ +mkdocs-material >= 9.6.7 +mkdocs-autoapi >= 0.4.0 +mkdocstrings-python >= 1.16.2 +mkdocs-minify-plugin >= 0.8.0 +markdown-checklist >= 0.4.4 +mdx-gh-links >= 0.4 +pymdown-extensions >= 10.14.3 +mike >= 2.1.3 +mkdocstrings-shell >= 1.0.2 From bd1dd87d242ee3d637133bea0e84ee64c2b76172 Mon Sep 17 00:00:00 2001 From: Philippe Leprince Date: Fri, 9 May 2025 16:41:16 +0200 Subject: [PATCH 02/39] can I test this ? Signed-off-by: Philippe Leprince --- .github/workflows/deploy_mkdocs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deploy_mkdocs.yml b/.github/workflows/deploy_mkdocs.yml index 9fd97c1e82..fcee1dd3c2 100644 --- a/.github/workflows/deploy_mkdocs.yml +++ b/.github/workflows/deploy_mkdocs.yml @@ -1,6 +1,7 @@ name: Deploy MkDocs on: + pull_request: workflow_dispatch: workflow_call: inputs: From 4977e771224ed202d76d11c6bfd6187d37812240 Mon Sep 17 00:00:00 2001 From: Philippe Leprince Date: Fri, 9 May 2025 16:44:05 +0200 Subject: [PATCH 03/39] remove some tests Signed-off-by: Philippe Leprince --- .github/workflows/deploy_mkdocs.yml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/deploy_mkdocs.yml b/.github/workflows/deploy_mkdocs.yml index fcee1dd3c2..80e69b8fa1 100644 --- a/.github/workflows/deploy_mkdocs.yml +++ b/.github/workflows/deploy_mkdocs.yml @@ -30,13 +30,13 @@ env: jobs: - verify-latest-release: - uses: ynput/ops-repo-automation/.github/workflows/verify_latest_release.yml@main - with: - repo: ${{ github.repository }} - expect_release: true - secrets: - gh_token: ${{ secrets.token }} +# verify-latest-release: +# uses: ynput/ops-repo-automation/.github/workflows/verify_latest_release.yml@main +# with: +# repo: ${{ github.repository }} +# expect_release: true +# secrets: +# gh_token: ${{ secrets.token }} verify-repo-secrets: uses: ynput/ops-repo-automation/.github/workflows/verify_secrets.yml@main @@ -47,13 +47,13 @@ jobs: gh_user: ${{ secrets.user }} gh_email: ${{ secrets.email }} - verify-repo-vars: - uses: ynput/ops-repo-automation/.github/workflows/verify_variables.yml@main - with: - variables: "MAIN_BRANCH,MINOR_BUMP_LABEL,PATCH_BUMP_LABEL,PROJECT_NAME" - repo: ${{ github.repository }} - secrets: - gh_token: ${{ secrets.token }} +# verify-repo-vars: +# uses: ynput/ops-repo-automation/.github/workflows/verify_variables.yml@main +# with: +# variables: "MAIN_BRANCH,MINOR_BUMP_LABEL,PATCH_BUMP_LABEL,PROJECT_NAME" +# repo: ${{ github.repository }} +# secrets: +# gh_token: ${{ secrets.token }} deploy: runs-on: ubuntu-latest From dc9d65d9b85d6a4afe5eda2e3638facf2d7d2c6a Mon Sep 17 00:00:00 2001 From: Philippe Leprince Date: Fri, 9 May 2025 17:01:09 +0200 Subject: [PATCH 04/39] fix authentification error Signed-off-by: Philippe Leprince --- .github/workflows/deploy_mkdocs.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/deploy_mkdocs.yml b/.github/workflows/deploy_mkdocs.yml index 80e69b8fa1..98866eb43e 100644 --- a/.github/workflows/deploy_mkdocs.yml +++ b/.github/workflows/deploy_mkdocs.yml @@ -66,6 +66,11 @@ jobs: fetch-depth: 0 submodules: true + - name: πŸ”‘ Set Authentication + run: | + git config --global user.name "${{ secrets.user }}" + git config --global user.email "${{ secrets.email }}" + - name: Set up Python uses: actions/setup-python@v4 with: From 5899f484a5d242904b5ee21808b2c53249deadc7 Mon Sep 17 00:00:00 2001 From: Philippe Leprince Date: Fri, 9 May 2025 17:10:26 +0200 Subject: [PATCH 05/39] better authentification fix Signed-off-by: Philippe Leprince --- .github/workflows/deploy_mkdocs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy_mkdocs.yml b/.github/workflows/deploy_mkdocs.yml index 98866eb43e..8c05985613 100644 --- a/.github/workflows/deploy_mkdocs.yml +++ b/.github/workflows/deploy_mkdocs.yml @@ -68,8 +68,8 @@ jobs: - name: πŸ”‘ Set Authentication run: | - git config --global user.name "${{ secrets.user }}" - git config --global user.email "${{ secrets.email }}" + git config --global user.name "${{ secrets.user || secrets.CI_USER }}" + git config --global user.email "${{ secrets.email || secrets.CI_EMAIL }}" - name: Set up Python uses: actions/setup-python@v4 From b5006abc8b51bf18f7ba4fa083925df364877692 Mon Sep 17 00:00:00 2001 From: Philippe Leprince Date: Fri, 9 May 2025 17:15:09 +0200 Subject: [PATCH 06/39] display tag Signed-off-by: Philippe Leprince --- .github/workflows/deploy_mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy_mkdocs.yml b/.github/workflows/deploy_mkdocs.yml index 8c05985613..49f6cfafba 100644 --- a/.github/workflows/deploy_mkdocs.yml +++ b/.github/workflows/deploy_mkdocs.yml @@ -80,5 +80,5 @@ jobs: run: | python3 -m pip install -r ./docs/mkdocs_requirements.txt - - name: Mike deploy + - name: Mike deploy ${{ inputs.tag }} run: mike deploy --update-aliases ${{ inputs.tag }} latest From 71a5a4603020905dbd2a33751b4f24030bbabadb Mon Sep 17 00:00:00 2001 From: Philippe Leprince Date: Fri, 9 May 2025 17:38:41 +0200 Subject: [PATCH 07/39] Get the current tag with fallback to 1.0.0 Signed-off-by: Philippe Leprince --- .github/workflows/deploy_mkdocs.yml | 30 ++++++++--------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/.github/workflows/deploy_mkdocs.yml b/.github/workflows/deploy_mkdocs.yml index 49f6cfafba..2e6329f924 100644 --- a/.github/workflows/deploy_mkdocs.yml +++ b/.github/workflows/deploy_mkdocs.yml @@ -12,9 +12,6 @@ on: type: string required: true default: "main" - tag: - type: string - required: true secrets: token: required: true @@ -29,15 +26,6 @@ env: GH_EMAIL: ${{ secrets.email || secrets.CI_EMAIL }} jobs: - -# verify-latest-release: -# uses: ynput/ops-repo-automation/.github/workflows/verify_latest_release.yml@main -# with: -# repo: ${{ github.repository }} -# expect_release: true -# secrets: -# gh_token: ${{ secrets.token }} - verify-repo-secrets: uses: ynput/ops-repo-automation/.github/workflows/verify_secrets.yml@main with: @@ -47,14 +35,6 @@ jobs: gh_user: ${{ secrets.user }} gh_email: ${{ secrets.email }} -# verify-repo-vars: -# uses: ynput/ops-repo-automation/.github/workflows/verify_variables.yml@main -# with: -# variables: "MAIN_BRANCH,MINOR_BUMP_LABEL,PATCH_BUMP_LABEL,PROJECT_NAME" -# repo: ${{ github.repository }} -# secrets: -# gh_token: ${{ secrets.token }} - deploy: runs-on: ubuntu-latest @@ -71,6 +51,12 @@ jobs: git config --global user.name "${{ secrets.user || secrets.CI_USER }}" git config --global user.email "${{ secrets.email || secrets.CI_EMAIL }}" + - name: Get current tag + id: git_tag + uses: "devops-actions/action-get-tag@v1.0.3" + with: + default: 1.0.0 + - name: Set up Python uses: actions/setup-python@v4 with: @@ -80,5 +66,5 @@ jobs: run: | python3 -m pip install -r ./docs/mkdocs_requirements.txt - - name: Mike deploy ${{ inputs.tag }} - run: mike deploy --update-aliases ${{ inputs.tag }} latest + - name: Mike deploy ${{ steps.git_tag.outputs.tag }} + run: mike deploy --update-aliases ${{ steps.git_tag.outputs.tag }} latest From 17fbd5228cad7a867137751f2a0ceb125f10f0a0 Mon Sep 17 00:00:00 2001 From: Philippe Leprince Date: Fri, 9 May 2025 17:43:42 +0200 Subject: [PATCH 08/39] remove quotes Signed-off-by: Philippe Leprince --- .github/workflows/deploy_mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy_mkdocs.yml b/.github/workflows/deploy_mkdocs.yml index 2e6329f924..a8281a1de0 100644 --- a/.github/workflows/deploy_mkdocs.yml +++ b/.github/workflows/deploy_mkdocs.yml @@ -53,7 +53,7 @@ jobs: - name: Get current tag id: git_tag - uses: "devops-actions/action-get-tag@v1.0.3" + uses: devops-actions/action-get-tag@v1.0.3 with: default: 1.0.0 From 18c0299bfb9a1812a485d8c454f68f5d165f5271 Mon Sep 17 00:00:00 2001 From: Philippe Leprince Date: Fri, 9 May 2025 17:47:57 +0200 Subject: [PATCH 09/39] fix missing branch name Signed-off-by: Philippe Leprince --- .github/workflows/deploy_mkdocs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy_mkdocs.yml b/.github/workflows/deploy_mkdocs.yml index a8281a1de0..4d6fd9104e 100644 --- a/.github/workflows/deploy_mkdocs.yml +++ b/.github/workflows/deploy_mkdocs.yml @@ -39,10 +39,10 @@ jobs: runs-on: ubuntu-latest steps: - - name: Checkout ${{ inputs.branch_name }} + - name: Checkout ${{ inputs.branch_name || 'main' }} uses: actions/checkout@v4 with: - ref: ${{ inputs.branch_name }} + ref: ${{ inputs.branch_name || 'main' }} fetch-depth: 0 submodules: true From af9fd17ee8108f4a13b9777fa9daf68e3a09fa74 Mon Sep 17 00:00:00 2001 From: Philippe Leprince Date: Fri, 9 May 2025 17:55:49 +0200 Subject: [PATCH 10/39] format Signed-off-by: Philippe Leprince --- .github/workflows/deploy_mkdocs.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/deploy_mkdocs.yml b/.github/workflows/deploy_mkdocs.yml index 4d6fd9104e..2f46bdc6ad 100644 --- a/.github/workflows/deploy_mkdocs.yml +++ b/.github/workflows/deploy_mkdocs.yml @@ -21,28 +21,28 @@ on: required: true env: - GH_TOKEN: ${{ secrets.token || secrets.YNPUT_BOT_TOKEN }} - GH_USER: ${{ secrets.user || secrets.CI_USER }} - GH_EMAIL: ${{ secrets.email || secrets.CI_EMAIL }} + GH_TOKEN: ${{ secrets.token || secrets.YNPUT_BOT_TOKEN }} + GH_USER: ${{ secrets.user || secrets.CI_USER }} + GH_EMAIL: ${{ secrets.email || secrets.CI_EMAIL }} jobs: verify-repo-secrets: - uses: ynput/ops-repo-automation/.github/workflows/verify_secrets.yml@main - with: - repo: ${{ github.repository }} - secrets: - gh_token: ${{ secrets.token }} - gh_user: ${{ secrets.user }} - gh_email: ${{ secrets.email }} + uses: ynput/ops-repo-automation/.github/workflows/verify_secrets.yml@main + with: + repo: ${{ github.repository }} + secrets: + gh_token: ${{ secrets.token }} + gh_user: ${{ secrets.user }} + gh_email: ${{ secrets.email }} deploy: runs-on: ubuntu-latest steps: - - name: Checkout ${{ inputs.branch_name || 'main' }} + - name: Checkout ${{ inputs.branch_name}} uses: actions/checkout@v4 with: - ref: ${{ inputs.branch_name || 'main' }} + ref: ${{ inputs.branch_name}} fetch-depth: 0 submodules: true From dba8d78a2abc79583d64016c46b6aa5df13916bb Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 20 May 2025 10:53:37 +0200 Subject: [PATCH 11/39] Refactors color space conversion with oiiotool Consolidates color space conversion logic into a dedicated `oiiotool_transcode` function for better flexibility and clarity. This change introduces support for display/view transformations, enhancing the tool's ability to handle complex color management workflows. It also fixes issues with conflicting color space parameters and improves handling of source and target display/view configurations. --- client/ayon_core/lib/transcoding.py | 170 ++++++++++++++++-- .../publish/extract_color_transcode.py | 42 +++-- .../plugins/publish/extract_thumbnail.py | 10 +- 3 files changed, 184 insertions(+), 38 deletions(-) diff --git a/client/ayon_core/lib/transcoding.py b/client/ayon_core/lib/transcoding.py index 8c84e1c4dc..6648f5219e 100644 --- a/client/ayon_core/lib/transcoding.py +++ b/client/ayon_core/lib/transcoding.py @@ -977,7 +977,60 @@ def convert_colorspace( additional_command_args=None, logger=None, ): - """Convert source file from one color space to another. + """Backward compatibility function for convert_colorspace. + + Args: + input_path (str): Path to input file that should be converted. + output_path (str): Path to output file where result will be stored. + config_path (str): Path to OCIO config file. + source_colorspace (str): OCIO valid color space of source files. + target_colorspace (str, optional): OCIO valid target color space. + If filled, 'view' and 'display' must be empty. + view (str, optional): Name for target viewer space (OCIO valid). + Both 'view' and 'display' must be filled (if not 'target_colorspace'). + display (str, optional): Name for target display-referred reference space. + Both 'view' and 'display' must be filled (if not 'target_colorspace'). + additional_command_args (list, optional): Additional arguments for oiiotool + (like binary depth for .dpx). + logger (logging.Logger, optional): Logger used for logging. + + Returns: + None: Function returns None. + + Raises: + ValueError: If parameters are misconfigured. + """ + return oiiotool_transcode( + input_path, + output_path, + config_path, + source_colorspace, + target_colorspace=target_colorspace, + target_display=display, + target_view=view, + additional_command_args=additional_command_args, + logger=logger, + ) + + +def oiiotool_transcode( + input_path, + output_path, + config_path, + source_colorspace, + source_display=None, + source_view=None, + target_colorspace=None, + target_display=None, + target_view=None, + additional_command_args=None, + logger=None, +): + """Transcode source file to other with colormanagement. + + Oiiotool also support additional arguments for transcoding. + For more information, see the official documentation: + https://openimageio.readthedocs.io/en/latest/oiiotool.html Args: input_path (str): Path that should be converted. It is expected that @@ -989,17 +1042,26 @@ def convert_colorspace( sequence in 'file.FRAMESTART-FRAMEEND#.ext', `output.1-3#.tif`) config_path (str): path to OCIO config file source_colorspace (str): ocio valid color space of source files + source_display (str, optional): name for source display-referred + reference space (ocio valid). If provided, source_view must also be + provided, and source_colorspace will be ignored + source_view (str, optional): name for source viewer space (ocio valid) + If provided, source_display must also be provided, and + source_colorspace will be ignored target_colorspace (str): ocio valid target color space if filled, 'view' and 'display' must be empty - view (str): name for viewer space (ocio valid) - both 'view' and 'display' must be filled (if 'target_colorspace') - display (str): name for display-referred reference space (ocio valid) + target_display (str): name for target display-referred reference space + (ocio valid) both 'view' and 'display' must be filled (if + 'target_colorspace') + target_view (str): name for target viewer space (ocio valid) both 'view' and 'display' must be filled (if 'target_colorspace') additional_command_args (list): arguments for oiiotool (like binary depth for .dpx) logger (logging.Logger): Logger used for logging. + Raises: ValueError: if misconfigured + """ if logger is None: logger = logging.getLogger(__name__) @@ -1024,23 +1086,99 @@ def convert_colorspace( "--ch", channels_arg ]) - if all([target_colorspace, view, display]): - raise ValueError("Colorspace and both screen and display" - " cannot be set together." - "Choose colorspace or screen and display") - if not target_colorspace and not all([view, display]): - raise ValueError("Both screen and display must be set.") + # Validate input parameters + if all([target_colorspace, target_view, target_display]): + raise ValueError( + "Colorspace and both screen and display cannot be set together." + "Choose colorspace or screen and display" + ) + + if all([source_view, source_display]) and source_colorspace: + logger.warning( + "Both source display/view and source_colorspace provided. " + "Using source display/view pair and ignoring source_colorspace." + ) + + if not target_colorspace and not all([target_view, target_display]): + raise ValueError( + "Both screen and display must be set if target_colorspace is not " + "provided." + ) + + if ((source_view and not source_display) or + (source_display and not source_view)): + raise ValueError( + "Both source_view and source_display must be provided if using " + "display/view inputs." + ) if additional_command_args: oiio_cmd.extend(additional_command_args) + # Handle the different conversion cases if target_colorspace: - oiio_cmd.extend(["--colorconvert:subimages=0", - source_colorspace, - target_colorspace]) - if view and display: - oiio_cmd.extend(["--iscolorspace", source_colorspace]) - oiio_cmd.extend(["--ociodisplay:subimages=0", display, view]) + # Case 1: Converting to a named colorspace + if all([source_view, source_display]): + # First convert from source display/view to a role/reference space + # that can be used with colorconvert + # For example, converting to "scene_linear" or an appropriate + # intermediate space + # This is a two-step conversion process since there's no direct + # display/view to colorspace command + # This could be a config parameter or determined from OCIO config + tmp_role_space = "scene_linear" + oiio_cmd.extend([ + "--ociodisplay:inverse=1:subimages=0", source_display, + source_view, "--colorconvert:subimages=0", tmp_role_space, + target_colorspace, + ]) + else: + # Standard color space to color space conversion + oiio_cmd.extend([ + "--colorconvert:subimages=0", source_colorspace, + target_colorspace, + ]) + else: # Using display/view target + if all([source_view, source_display]): + if source_display == target_display and source_view == target_view: + # No conversion needed if source and target display/view are + # the same + logger.debug( + "Source and target display/view pairs are identical. " + "No color conversion needed." + ) + elif source_display == target_display: + # When only the view changes but display stays the same + # First convert from source view to a reference space, then to + # target view + # This could be configured + tmp_role_space = "scene_linear" + oiio_cmd.extend([ + "--ociodisplay:inverse=1:subimages=0", + source_display, + source_view, + "--ociodisplay:subimages=0", + target_display, + target_view, + ]) + else: + # Complete display/view pair conversion + # Similar approach: go through a reference space + # This could be configured + tmp_role_space = "scene_linear" + oiio_cmd.extend([ + "--ociodisplay:inverse=1:subimages=0", + source_display, + source_view, "--ociodisplay:subimages=0", + target_display, + target_view, + ]) + else: + # Standard conversion from colorspace to display/view + oiio_cmd.extend([ + "--iscolorspace", source_colorspace, + "--ociodisplay:subimages=0", target_display, target_view, + ]) oiio_cmd.extend(["-o", output_path]) diff --git a/client/ayon_core/plugins/publish/extract_color_transcode.py b/client/ayon_core/plugins/publish/extract_color_transcode.py index 6cf30857a4..f61379189c 100644 --- a/client/ayon_core/plugins/publish/extract_color_transcode.py +++ b/client/ayon_core/plugins/publish/extract_color_transcode.py @@ -11,7 +11,7 @@ from ayon_core.lib import ( is_oiio_supported, ) from ayon_core.lib.transcoding import ( - convert_colorspace, + oiiotool_transcode, ) from ayon_core.lib.profiles_filtering import filter_profiles @@ -94,6 +94,8 @@ class ExtractOIIOTranscode(publish.Extractor): colorspace_data = repre["colorspaceData"] source_colorspace = colorspace_data["colorspace"] + source_display = colorspace_data.get("display") + source_view = colorspace_data.get("view") config_path = colorspace_data.get("config", {}).get("path") if not config_path or not os.path.exists(config_path): self.log.warning("Config file doesn't exist, skipping") @@ -124,7 +126,7 @@ class ExtractOIIOTranscode(publish.Extractor): transcoding_type = output_def["transcoding_type"] - target_colorspace = view = display = None + target_colorspace = target_view = target_display = None # NOTE: we use colorspace_data as the fallback values for # the target colorspace. if transcoding_type == "colorspace": @@ -136,18 +138,20 @@ class ExtractOIIOTranscode(publish.Extractor): colorspace_data.get("colorspace")) elif transcoding_type == "display_view": display_view = output_def["display_view"] - view = display_view["view"] or colorspace_data.get("view") - display = ( + target_view = ( + display_view["view"] + or colorspace_data.get("view")) + target_display = ( display_view["display"] or colorspace_data.get("display") ) # both could be already collected by DCC, # but could be overwritten when transcoding - if view: - new_repre["colorspaceData"]["view"] = view - if display: - new_repre["colorspaceData"]["display"] = display + if target_view: + new_repre["colorspaceData"]["view"] = target_view + if target_display: + new_repre["colorspaceData"]["display"] = target_display if target_colorspace: new_repre["colorspaceData"]["colorspace"] = \ target_colorspace @@ -166,16 +170,18 @@ class ExtractOIIOTranscode(publish.Extractor): new_staging_dir, output_extension) - convert_colorspace( - input_path, - output_path, - config_path, - source_colorspace, - target_colorspace, - view, - display, - additional_command_args, - self.log + oiiotool_transcode( + input_path=input_path, + output_path=output_path, + config_path=config_path, + source_colorspace=source_colorspace, + target_colorspace=target_colorspace, + target_display=target_display, + target_view=target_view, + source_display=source_display, + source_view=source_view, + additional_command_args=additional_command_args, + logger=self.log ) # cleanup temporary transcoded files diff --git a/client/ayon_core/plugins/publish/extract_thumbnail.py b/client/ayon_core/plugins/publish/extract_thumbnail.py index 3a428c46a7..f65308121b 100644 --- a/client/ayon_core/plugins/publish/extract_thumbnail.py +++ b/client/ayon_core/plugins/publish/extract_thumbnail.py @@ -15,7 +15,7 @@ from ayon_core.lib import ( path_to_subprocess_arg, run_subprocess, ) -from ayon_core.lib.transcoding import convert_colorspace +from ayon_core.lib.transcoding import oiiotool_transcode from ayon_core.lib.transcoding import VIDEO_EXTENSIONS, IMAGE_EXTENSIONS @@ -431,13 +431,15 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): oiio_default_view = display_and_view["view"] try: - convert_colorspace( + oiiotool_transcode( src_path, dst_path, colorspace_data["config"]["path"], colorspace_data["colorspace"], - display=repre_display or oiio_default_display, - view=repre_view or oiio_default_view, + source_display=colorspace_data.get("display"), + source_view=colorspace_data.get("view"), + target_display=repre_display or oiio_default_display, + target_view=repre_view or oiio_default_view, target_colorspace=oiio_default_colorspace, additional_command_args=resolution_arg, logger=self.log, From 08f6b61a3e5f4d91d8a4d7247024a14fe9067d27 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 20 May 2025 11:19:14 +0200 Subject: [PATCH 12/39] Adds deprecation decorator and marks function Introduces a `deprecated` decorator to mark functions as deprecated, issuing a warning when they are called. The `convert_colorspace` function is marked as deprecated, advising users to switch to `oiiotool_transcode`. --- client/ayon_core/lib/transcoding.py | 90 +++++++++++++++++++++-------- 1 file changed, 66 insertions(+), 24 deletions(-) diff --git a/client/ayon_core/lib/transcoding.py b/client/ayon_core/lib/transcoding.py index 6648f5219e..e62873184d 100644 --- a/client/ayon_core/lib/transcoding.py +++ b/client/ayon_core/lib/transcoding.py @@ -6,6 +6,8 @@ import collections import tempfile import subprocess import platform +import warnings +import functools from typing import Optional import xml.etree.ElementTree @@ -67,6 +69,48 @@ VIDEO_EXTENSIONS = { } + +def deprecated(new_destination): + """Mark functions as deprecated. + + It will result in a warning being emitted when the function is used. + """ + + func = None + if callable(new_destination): + func = new_destination + new_destination = None + + def _decorator(decorated_func): + if new_destination is None: + warning_message = ( + " Please check content of deprecated function to figure out" + " possible replacement." + ) + else: + warning_message = " Please replace your usage with '{}'.".format( + new_destination + ) + + @functools.wraps(decorated_func) + def wrapper(*args, **kwargs): + warnings.simplefilter("always", DeprecationWarning) + warnings.warn( + ( + "Call to deprecated function '{}'" + "\nFunction was moved or removed.{}" + ).format(decorated_func.__name__, warning_message), + category=DeprecationWarning, + stacklevel=4 + ) + return decorated_func(*args, **kwargs) + return wrapper + + if func is None: + return _decorator + return _decorator(func) + + def get_transcode_temp_directory(): """Creates temporary folder for transcoding. @@ -966,6 +1010,8 @@ def convert_ffprobe_fps_to_float(value): return dividend / divisor +# --- Deprecated functions --- +@deprecated("oiiotool_transcode") def convert_colorspace( input_path, output_path, @@ -977,7 +1023,7 @@ def convert_colorspace( additional_command_args=None, logger=None, ): - """Backward compatibility function for convert_colorspace. + """DEPRECATED function use `oiiotool_transcode` instead Args: input_path (str): Path to input file that should be converted. @@ -1105,8 +1151,10 @@ def oiiotool_transcode( "provided." ) - if ((source_view and not source_display) or - (source_display and not source_view)): + if ( + (source_view and not source_display) + or (source_display and not source_view) + ): raise ValueError( "Both source_view and source_display must be provided if using " "display/view inputs." @@ -1128,14 +1176,18 @@ def oiiotool_transcode( # This could be a config parameter or determined from OCIO config tmp_role_space = "scene_linear" oiio_cmd.extend([ - "--ociodisplay:inverse=1:subimages=0", source_display, - source_view, "--colorconvert:subimages=0", tmp_role_space, + "--ociodisplay:inverse=1:subimages=0", + source_display, + source_view, + "--colorconvert:subimages=0", + tmp_role_space, target_colorspace, ]) else: # Standard color space to color space conversion oiio_cmd.extend([ - "--colorconvert:subimages=0", source_colorspace, + "--colorconvert:subimages=0", + source_colorspace, target_colorspace, ]) else: # Using display/view target @@ -1147,10 +1199,9 @@ def oiiotool_transcode( "Source and target display/view pairs are identical. " "No color conversion needed." ) - elif source_display == target_display: - # When only the view changes but display stays the same - # First convert from source view to a reference space, then to - # target view + else: + # Complete display/view pair conversion + # Similar approach: go through a reference space # This could be configured tmp_role_space = "scene_linear" oiio_cmd.extend([ @@ -1161,23 +1212,14 @@ def oiiotool_transcode( target_display, target_view, ]) - else: - # Complete display/view pair conversion - # Similar approach: go through a reference space - # This could be configured - tmp_role_space = "scene_linear" - oiio_cmd.extend([ - "--ociodisplay:inverse=1:subimages=0", - source_display, - source_view, "--ociodisplay:subimages=0", - target_display, - target_view, - ]) else: # Standard conversion from colorspace to display/view oiio_cmd.extend([ - "--iscolorspace", source_colorspace, - "--ociodisplay:subimages=0", target_display, target_view, + "--iscolorspace", + source_colorspace, + "--ociodisplay:subimages=0", + target_display, + target_view, ]) oiio_cmd.extend(["-o", output_path]) From 4ebf35dd8b68ef10a66551ef47276516a291e360 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 20 May 2025 11:26:28 +0200 Subject: [PATCH 13/39] Refactors docstring formatting in transcoding Improves readability by adjusting docstring formatting in the `convert_colorspace` function. This change ensures consistent documentation style and enhances clarity. --- client/ayon_core/lib/transcoding.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/lib/transcoding.py b/client/ayon_core/lib/transcoding.py index e62873184d..4f985d5128 100644 --- a/client/ayon_core/lib/transcoding.py +++ b/client/ayon_core/lib/transcoding.py @@ -69,7 +69,6 @@ VIDEO_EXTENSIONS = { } - def deprecated(new_destination): """Mark functions as deprecated. @@ -1033,11 +1032,13 @@ def convert_colorspace( target_colorspace (str, optional): OCIO valid target color space. If filled, 'view' and 'display' must be empty. view (str, optional): Name for target viewer space (OCIO valid). - Both 'view' and 'display' must be filled (if not 'target_colorspace'). - display (str, optional): Name for target display-referred reference space. - Both 'view' and 'display' must be filled (if not 'target_colorspace'). - additional_command_args (list, optional): Additional arguments for oiiotool - (like binary depth for .dpx). + Both 'view' and 'display' must be filled + (if not 'target_colorspace'). + display (str, optional): Name for target display-referred + reference space. Both 'view' and 'display' must be filled + (if not 'target_colorspace'). + additional_command_args (list, optional): Additional arguments + for oiiotool (like binary depth for .dpx). logger (logging.Logger, optional): Logger used for logging. Returns: From 0b20e49eda6eb85b3b3c32039ee24a3880fc12e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Fri, 6 Jun 2025 16:07:06 +0200 Subject: [PATCH 14/39] Update client/ayon_core/pipeline/colorspace.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/pipeline/colorspace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index a7d1d80b0a..41241e17ca 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -1404,7 +1404,7 @@ def _get_display_view_colorspace_name(config_path, display, view): """ config = _get_ocio_config(config_path) colorspace = config.getDisplayViewColorSpaceName(display, view) - # Special token. See https://opencolorio.readthedocs.io/en/latest/guides/authoring/authoring.html#shared-views # noqa + # Special token. See https://opencolorio.readthedocs.io/en/latest/guides/authoring/authoring.html#shared-views # noqa if colorspace == "": colorspace = display From 21737339d37432cc62f83d88839711f6dc5e32fd Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 12 Jun 2025 15:46:23 +0200 Subject: [PATCH 15/39] rename 'oiiotool_transcode' to 'oiio_color_convert' --- client/ayon_core/lib/transcoding.py | 8 ++++---- .../ayon_core/plugins/publish/extract_color_transcode.py | 4 ++-- client/ayon_core/plugins/publish/extract_thumbnail.py | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/client/ayon_core/lib/transcoding.py b/client/ayon_core/lib/transcoding.py index 4f985d5128..97e9f417f0 100644 --- a/client/ayon_core/lib/transcoding.py +++ b/client/ayon_core/lib/transcoding.py @@ -1010,7 +1010,7 @@ def convert_ffprobe_fps_to_float(value): # --- Deprecated functions --- -@deprecated("oiiotool_transcode") +@deprecated("oiio_color_convert") def convert_colorspace( input_path, output_path, @@ -1022,7 +1022,7 @@ def convert_colorspace( additional_command_args=None, logger=None, ): - """DEPRECATED function use `oiiotool_transcode` instead + """DEPRECATED function use `oiio_color_convert` instead Args: input_path (str): Path to input file that should be converted. @@ -1047,7 +1047,7 @@ def convert_colorspace( Raises: ValueError: If parameters are misconfigured. """ - return oiiotool_transcode( + return oiio_color_convert( input_path, output_path, config_path, @@ -1060,7 +1060,7 @@ def convert_colorspace( ) -def oiiotool_transcode( +def oiio_color_convert( input_path, output_path, config_path, diff --git a/client/ayon_core/plugins/publish/extract_color_transcode.py b/client/ayon_core/plugins/publish/extract_color_transcode.py index 9759b340c7..152e0a27ea 100644 --- a/client/ayon_core/plugins/publish/extract_color_transcode.py +++ b/client/ayon_core/plugins/publish/extract_color_transcode.py @@ -11,7 +11,7 @@ from ayon_core.lib import ( is_oiio_supported, ) from ayon_core.lib.transcoding import ( - oiiotool_transcode, + oiio_color_convert, ) from ayon_core.lib.profiles_filtering import filter_profiles @@ -170,7 +170,7 @@ class ExtractOIIOTranscode(publish.Extractor): new_staging_dir, output_extension) - oiiotool_transcode( + oiio_color_convert( input_path=input_path, output_path=output_path, config_path=config_path, diff --git a/client/ayon_core/plugins/publish/extract_thumbnail.py b/client/ayon_core/plugins/publish/extract_thumbnail.py index 4d51405f01..541082352b 100644 --- a/client/ayon_core/plugins/publish/extract_thumbnail.py +++ b/client/ayon_core/plugins/publish/extract_thumbnail.py @@ -15,7 +15,7 @@ from ayon_core.lib import ( path_to_subprocess_arg, run_subprocess, ) -from ayon_core.lib.transcoding import oiiotool_transcode +from ayon_core.lib.transcoding import oiio_color_convert from ayon_core.lib.transcoding import VIDEO_EXTENSIONS, IMAGE_EXTENSIONS @@ -431,7 +431,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): oiio_default_view = display_and_view["view"] try: - oiiotool_transcode( + oiio_color_convert( src_path, dst_path, colorspace_data["config"]["path"], From 28978f07c654446c3af3b64e54cc93bff879bd56 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 12 Jun 2025 15:50:21 +0200 Subject: [PATCH 16/39] use standard conditions --- client/ayon_core/lib/transcoding.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/client/ayon_core/lib/transcoding.py b/client/ayon_core/lib/transcoding.py index 97e9f417f0..01a21615b5 100644 --- a/client/ayon_core/lib/transcoding.py +++ b/client/ayon_core/lib/transcoding.py @@ -1134,19 +1134,13 @@ def oiio_color_convert( ]) # Validate input parameters - if all([target_colorspace, target_view, target_display]): + if target_colorspace and target_view and target_display: raise ValueError( "Colorspace and both screen and display cannot be set together." "Choose colorspace or screen and display" ) - if all([source_view, source_display]) and source_colorspace: - logger.warning( - "Both source display/view and source_colorspace provided. " - "Using source display/view pair and ignoring source_colorspace." - ) - - if not target_colorspace and not all([target_view, target_display]): + if not target_colorspace and not target_view and not target_display: raise ValueError( "Both screen and display must be set if target_colorspace is not " "provided." @@ -1161,6 +1155,12 @@ def oiio_color_convert( "display/view inputs." ) + if source_view and source_display and source_colorspace: + logger.warning( + "Both source display/view and source_colorspace provided. " + "Using source display/view pair and ignoring source_colorspace." + ) + if additional_command_args: oiio_cmd.extend(additional_command_args) From 3eb88ae8506eac6dbbd8fd8f94abf88703c7bc72 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 12 Jun 2025 15:50:34 +0200 Subject: [PATCH 17/39] use 'view' instead of 'screen' --- client/ayon_core/lib/transcoding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/lib/transcoding.py b/client/ayon_core/lib/transcoding.py index 01a21615b5..7a4fd65e7c 100644 --- a/client/ayon_core/lib/transcoding.py +++ b/client/ayon_core/lib/transcoding.py @@ -1136,7 +1136,7 @@ def oiio_color_convert( # Validate input parameters if target_colorspace and target_view and target_display: raise ValueError( - "Colorspace and both screen and display cannot be set together." + "Colorspace and both view and display cannot be set together." "Choose colorspace or screen and display" ) From 2b209044ed4861668b908ee08f2115805eb0fa77 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 12 Jun 2025 15:50:44 +0200 Subject: [PATCH 18/39] better conditions order --- client/ayon_core/lib/transcoding.py | 83 ++++++++++++----------------- 1 file changed, 35 insertions(+), 48 deletions(-) diff --git a/client/ayon_core/lib/transcoding.py b/client/ayon_core/lib/transcoding.py index 7a4fd65e7c..bf88e187ae 100644 --- a/client/ayon_core/lib/transcoding.py +++ b/client/ayon_core/lib/transcoding.py @@ -1165,64 +1165,51 @@ def oiio_color_convert( oiio_cmd.extend(additional_command_args) # Handle the different conversion cases - if target_colorspace: - # Case 1: Converting to a named colorspace - if all([source_view, source_display]): - # First convert from source display/view to a role/reference space - # that can be used with colorconvert - # For example, converting to "scene_linear" or an appropriate - # intermediate space + # Source view and display are known + if source_view and source_display: + if target_colorspace: # This is a two-step conversion process since there's no direct # display/view to colorspace command # This could be a config parameter or determined from OCIO config - tmp_role_space = "scene_linear" + # Use temporarty role space 'scene_linear' + color_convert_args = ("scene_linear", target_colorspace) + elif source_display != target_display or source_view != target_view: + # Complete display/view pair conversion + # - go through a reference space + color_convert_args = (target_display, target_view) + else: + color_convert_args = None + logger.debug( + "Source and target display/view pairs are identical." + " No color conversion needed." + ) + + if color_convert_args: oiio_cmd.extend([ "--ociodisplay:inverse=1:subimages=0", source_display, source_view, "--colorconvert:subimages=0", - tmp_role_space, - target_colorspace, - ]) - else: - # Standard color space to color space conversion - oiio_cmd.extend([ - "--colorconvert:subimages=0", - source_colorspace, - target_colorspace, - ]) - else: # Using display/view target - if all([source_view, source_display]): - if source_display == target_display and source_view == target_view: - # No conversion needed if source and target display/view are - # the same - logger.debug( - "Source and target display/view pairs are identical. " - "No color conversion needed." - ) - else: - # Complete display/view pair conversion - # Similar approach: go through a reference space - # This could be configured - tmp_role_space = "scene_linear" - oiio_cmd.extend([ - "--ociodisplay:inverse=1:subimages=0", - source_display, - source_view, - "--ociodisplay:subimages=0", - target_display, - target_view, - ]) - else: - # Standard conversion from colorspace to display/view - oiio_cmd.extend([ - "--iscolorspace", - source_colorspace, - "--ociodisplay:subimages=0", - target_display, - target_view, + *color_convert_args ]) + elif target_colorspace: + # Standard color space to color space conversion + oiio_cmd.extend([ + "--colorconvert:subimages=0", + source_colorspace, + target_colorspace, + ]) + else: + # Standard conversion from colorspace to display/view + oiio_cmd.extend([ + "--iscolorspace", + source_colorspace, + "--ociodisplay:subimages=0", + target_display, + target_view, + ]) + oiio_cmd.extend(["-o", output_path]) logger.debug("Conversion command: {}".format(" ".join(oiio_cmd))) From f673abebc8393c9fdce5faf18291d2015fa0b039 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 19 Jun 2025 10:29:45 +0200 Subject: [PATCH 19/39] use 'view' instead of 'screen' Co-authored-by: Roy Nieterau --- client/ayon_core/lib/transcoding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/lib/transcoding.py b/client/ayon_core/lib/transcoding.py index bf88e187ae..a4edf0aa1c 100644 --- a/client/ayon_core/lib/transcoding.py +++ b/client/ayon_core/lib/transcoding.py @@ -1142,7 +1142,7 @@ def oiio_color_convert( if not target_colorspace and not target_view and not target_display: raise ValueError( - "Both screen and display must be set if target_colorspace is not " + "Both view and display must be set if target_colorspace is not " "provided." ) From 63c81970316e66153cb2e2d05f1899c62fac89fd Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 2 Jul 2025 14:47:05 +0200 Subject: [PATCH 20/39] try to fix flickering issue in publisher --- client/ayon_core/tools/utils/widgets.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/tools/utils/widgets.py b/client/ayon_core/tools/utils/widgets.py index de2c42c91f..941aa692f9 100644 --- a/client/ayon_core/tools/utils/widgets.py +++ b/client/ayon_core/tools/utils/widgets.py @@ -441,14 +441,18 @@ class ExpandingTextEdit(QtWidgets.QTextEdit): margins = self.contentsMargins() document_width = 0 - if width >= margins.left() + margins.right(): - document_width = width - margins.left() - margins.right() + margins_size = margins.left() + margins.right() + if width >= margins_size: + document_width = width - margins_size document = self.document().clone() document.setTextWidth(document_width) return math.ceil( - margins.top() + document.size().height() + margins.bottom() + margins.top() + + document.size().height() + + margins.bottom() + + 2 ) def sizeHint(self): From 2228037656b8eeaa400fcaab1329c6416dbc050f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 3 Jul 2025 09:57:20 +0200 Subject: [PATCH 21/39] Adds target color space data extraction to OIIO transcode processor. - Introduces extraction of `targetOCIOColorspace`, `targetOCIODisplay`, and `targetOCIOView` from instance data. - Removes redundant assignment of `target_colorspace`, `target_display`, and `target_view`. --- client/ayon_core/plugins/publish/extract_color_transcode.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/extract_color_transcode.py b/client/ayon_core/plugins/publish/extract_color_transcode.py index ef718b8d02..7083d5094c 100644 --- a/client/ayon_core/plugins/publish/extract_color_transcode.py +++ b/client/ayon_core/plugins/publish/extract_color_transcode.py @@ -85,6 +85,11 @@ class ExtractOIIOTranscode(publish.Extractor): new_representations = [] repres = instance.data["representations"] for idx, repre in enumerate(list(repres)): + # target space, display and view might be defined upstream + target_colorspace = instance.data.get("targetOCIOColorspace") + target_display = instance.data.get("targetOCIODisplay") + target_view = instance.data.get("targetOCIOView") + self.log.debug("repre ({}): `{}`".format(idx + 1, repre["name"])) if not self._repre_is_valid(repre): continue @@ -126,7 +131,6 @@ class ExtractOIIOTranscode(publish.Extractor): transcoding_type = output_def["transcoding_type"] - target_colorspace = target_view = target_display = None # NOTE: we use colorspace_data as the fallback values for # the target colorspace. if transcoding_type == "colorspace": From 00cfb962e4f748aaec7e68b25d6d9c89833ee39f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Mon, 14 Jul 2025 14:38:41 +0200 Subject: [PATCH 22/39] Update client/ayon_core/lib/transcoding.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: OndΕ™ej Samohel <33513211+antirotor@users.noreply.github.com> --- client/ayon_core/lib/transcoding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/lib/transcoding.py b/client/ayon_core/lib/transcoding.py index a4edf0aa1c..b3958863fe 100644 --- a/client/ayon_core/lib/transcoding.py +++ b/client/ayon_core/lib/transcoding.py @@ -1136,7 +1136,7 @@ def oiio_color_convert( # Validate input parameters if target_colorspace and target_view and target_display: raise ValueError( - "Colorspace and both view and display cannot be set together." + "Colorspace and both view and display cannot be set together." "Choose colorspace or screen and display" ) From 8ac8352dcd14c7d720c8439961e014cff34fc884 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 8 Sep 2025 14:47:00 +0200 Subject: [PATCH 23/39] added burnins adapter --- client/pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/client/pyproject.toml b/client/pyproject.toml index bccc0b9872..5acfdf439d 100644 --- a/client/pyproject.toml +++ b/client/pyproject.toml @@ -16,6 +16,7 @@ qtawesome = "0.7.3" aiohttp-middlewares = "^2.0.0" Click = "^8" OpenTimelineIO = "0.17.0" +otio-burnins-adapter = "1.0.0" opencolorio = "^2.3.2,<2.4.0" Pillow = "9.5.0" websocket-client = ">=0.40.0,<2" From db2c1858d5a0014d27a16ecf138075f437b096a6 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 8 Sep 2025 16:10:49 +0200 Subject: [PATCH 24/39] Updates color space handling in OIIO transcode. Temporarily disables upstream OCIO color space logic. Uses 'colorspaceDisplay' and 'colorspaceView' instead. This is a temporary workaround. --- .../ayon_core/plugins/publish/extract_color_transcode.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_color_transcode.py b/client/ayon_core/plugins/publish/extract_color_transcode.py index 24820f7022..8b351c7f31 100644 --- a/client/ayon_core/plugins/publish/extract_color_transcode.py +++ b/client/ayon_core/plugins/publish/extract_color_transcode.py @@ -88,9 +88,12 @@ class ExtractOIIOTranscode(publish.Extractor): repres = instance.data["representations"] for idx, repre in enumerate(list(repres)): # target space, display and view might be defined upstream - target_colorspace = instance.data.get("targetOCIOColorspace") - target_display = instance.data.get("targetOCIODisplay") - target_view = instance.data.get("targetOCIOView") + # TODO: address https://github.com/ynput/ayon-core/pull/1268#discussion_r2156555474 + # Implement upstream logic to handle target_colorspace, + # target_display, target_view in other DCCs + target_colorspace = False + target_display = instance.data.get("colorspaceDisplay") + target_view = instance.data.get("colorspaceView") self.log.debug("repre ({}): `{}`".format(idx + 1, repre["name"])) if not self._repre_is_valid(repre): From 6419e3ed749fe33c80100a5fe5716166d4f404e2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 11 Sep 2025 13:35:19 +0200 Subject: [PATCH 25/39] use python 3 super --- client/ayon_core/tools/utils/widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/tools/utils/widgets.py b/client/ayon_core/tools/utils/widgets.py index 941aa692f9..4b787ff830 100644 --- a/client/ayon_core/tools/utils/widgets.py +++ b/client/ayon_core/tools/utils/widgets.py @@ -418,7 +418,7 @@ class ExpandingTextEdit(QtWidgets.QTextEdit): """QTextEdit which does not have sroll area but expands height.""" def __init__(self, parent=None): - super(ExpandingTextEdit, self).__init__(parent) + super().__init__(parent) size_policy = self.sizePolicy() size_policy.setHeightForWidth(True) From 6b058fccf092717111444b89e168e4f678d9ca51 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 11 Sep 2025 13:35:50 +0200 Subject: [PATCH 26/39] disable scroll bards on message input --- client/ayon_core/tools/publisher/widgets/report_page.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/ayon_core/tools/publisher/widgets/report_page.py b/client/ayon_core/tools/publisher/widgets/report_page.py index 1e46e7e52c..033ddab0ef 100644 --- a/client/ayon_core/tools/publisher/widgets/report_page.py +++ b/client/ayon_core/tools/publisher/widgets/report_page.py @@ -1147,6 +1147,8 @@ class LogItemMessage(QtWidgets.QTextEdit): QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum ) + self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) document = self.document() document.documentLayout().documentSizeChanged.connect( self._adjust_minimum_size From 8f2f10bce1efca04f6f0a22f663d98a99cce95c6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 12 Sep 2025 18:20:43 +0200 Subject: [PATCH 27/39] added 'get_project_entity' to controller --- client/ayon_core/tools/loader/abstract.py | 6 ++++++ client/ayon_core/tools/loader/control.py | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/client/ayon_core/tools/loader/abstract.py b/client/ayon_core/tools/loader/abstract.py index 5ab7e78212..42e88c15d7 100644 --- a/client/ayon_core/tools/loader/abstract.py +++ b/client/ayon_core/tools/loader/abstract.py @@ -513,6 +513,12 @@ class BackendLoaderController(_BaseLoaderController): pass + @abstractmethod + def get_project_entity( + self, project_name: Optional[str] + ) -> Optional[dict[str, Any]]: + pass + class FrontendLoaderController(_BaseLoaderController): @abstractmethod diff --git a/client/ayon_core/tools/loader/control.py b/client/ayon_core/tools/loader/control.py index 7ba42a0981..492a05e82f 100644 --- a/client/ayon_core/tools/loader/control.py +++ b/client/ayon_core/tools/loader/control.py @@ -2,6 +2,7 @@ from __future__ import annotations import logging import uuid +from typing import Optional, Any import ayon_api @@ -188,6 +189,11 @@ class LoaderController(BackendLoaderController, FrontendLoaderController): def get_project_items(self, sender=None): return self._projects_model.get_project_items(sender) + def get_project_entity( + self, project_name: Optional[str] + ) -> Optional[dict[str, Any]]: + return self._projects_model.get_project_entity(project_name) + def get_folder_type_items(self, project_name, sender=None): return self._projects_model.get_folder_type_items( project_name, sender From 7f8e62e8efc2efa016fe66cabc305d1a215fd5bf Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 12 Sep 2025 18:29:55 +0200 Subject: [PATCH 28/39] add icons mapping to products --- client/ayon_core/tools/loader/abstract.py | 7 - .../ayon_core/tools/loader/models/products.py | 205 +++++++++--------- .../tools/loader/ui/products_model.py | 8 +- 3 files changed, 103 insertions(+), 117 deletions(-) diff --git a/client/ayon_core/tools/loader/abstract.py b/client/ayon_core/tools/loader/abstract.py index 42e88c15d7..87cfb1f3f2 100644 --- a/client/ayon_core/tools/loader/abstract.py +++ b/client/ayon_core/tools/loader/abstract.py @@ -78,7 +78,6 @@ class ProductItem: product_type (str): Product type. product_name (str): Product name. product_icon (dict[str, Any]): Product icon definition. - product_type_icon (dict[str, Any]): Product type icon definition. product_in_scene (bool): Is product in scene (only when used in DCC). group_name (str): Group name. folder_id (str): Folder id. @@ -93,8 +92,6 @@ class ProductItem: product_base_type: str, product_name: str, product_icon: dict[str, Any], - product_type_icon: dict[str, Any], - product_base_type_icon: dict[str, Any], group_name: str, folder_id: str, folder_label: str, @@ -106,8 +103,6 @@ class ProductItem: self.product_base_type = product_base_type self.product_name = product_name self.product_icon = product_icon - self.product_type_icon = product_type_icon - self.product_base_type_icon = product_base_type_icon self.product_in_scene = product_in_scene self.group_name = group_name self.folder_id = folder_id @@ -121,8 +116,6 @@ class ProductItem: "product_base_type": self.product_base_type, "product_name": self.product_name, "product_icon": self.product_icon, - "product_type_icon": self.product_type_icon, - "product_base_type_icon": self.product_base_type_icon, "product_in_scene": self.product_in_scene, "group_name": self.group_name, "folder_id": self.folder_id, diff --git a/client/ayon_core/tools/loader/models/products.py b/client/ayon_core/tools/loader/models/products.py index 87e2406c81..f5e0b2fa80 100644 --- a/client/ayon_core/tools/loader/models/products.py +++ b/client/ayon_core/tools/loader/models/products.py @@ -9,7 +9,6 @@ import arrow import ayon_api from ayon_api.operations import OperationsSession - from ayon_core.lib import NestedCacheItem from ayon_core.style import get_default_entity_icon_color from ayon_core.tools.loader.abstract import ( @@ -21,12 +20,63 @@ from ayon_core.tools.loader.abstract import ( ) if TYPE_CHECKING: - from ayon_api.typing import ProductBaseTypeDict, ProductDict, VersionDict - + from ayon_api.typing import ( + ProductBaseTypeDict, + ProductDict, + VersionDict, + ) PRODUCTS_MODEL_SENDER = "products.model" +class ProductBaseTypeIconMapping: + def __init__( + self, + default: Optional[dict[str, str]] = None, + definitions: Optional[list[dict[str, str]]] = None, + ): + self._default = default or {} + self._definitions = definitions or [] + + self._default_def = None + self._definitions_by_name = None + + def get_icon( + self, + product_base_type: Optional[str] = None, + product_type: Optional[str] = None, + ) -> dict[str, str]: + defs = self._get_defs_by_name() + icon = defs.get(product_type) + if icon is None: + icon = defs.get(product_base_type) + if icon is None: + icon = self._get_default_def() + return icon.copy() + + def _get_default_def(self) -> dict[str, str]: + if self._default_def is None: + self._default_def = { + "type": "material-symbols", + "name": self._default.get("icon", "inventory_2"), + "color": self._default.get("color", "#cccccc"), + } + + return self._default_def + + def _get_defs_by_name(self) -> dict[str, dict[str, str]]: + if self._definitions_by_name is None: + self._definitions_by_name = { + product_base_type_def["name"]: { + "type": "material-symbols", + "name": product_base_type_def.get("icon", "inventory_2"), + "color": product_base_type_def.get("color", "#cccccc"), + } + for product_base_type_def in self._definitions + } + return self._definitions_by_name + + def version_item_from_entity(version): version_attribs = version["attrib"] tags = version["tags"] @@ -84,42 +134,18 @@ def version_item_from_entity(version): def product_item_from_entity( product_entity: ProductDict, version_entities, - product_type_items_by_name: dict[str, ProductTypeItem], - product_base_type_items_by_name: dict[str, ProductBaseTypeItem], folder_label, + icons_mapping, product_in_scene, ): product_attribs = product_entity["attrib"] group = product_attribs.get("productGroup") product_type = product_entity["productType"] - product_type_item = product_type_items_by_name.get(product_type) - # NOTE This is needed for cases when products were not created on server - # using api functions. In that case product type item may not be - # available and we need to create a default. - if product_type_item is None: - product_type_item = create_default_product_type_item(product_type) - # Cache the item for future use - product_type_items_by_name[product_type] = product_type_item - product_base_type = product_entity.get("productBaseType") - product_base_type_item = product_base_type_items_by_name.get( - product_base_type) - # Same as for product type item above. Not sure if this is still needed - # though. - if product_base_type_item is None: - product_base_type_item = create_default_product_base_type_item( - product_base_type) - # Cache the item for future use - product_base_type_items_by_name[product_base_type] = ( - product_base_type_item) - product_type_icon = product_type_item.icon - product_base_type_icon = product_base_type_item.icon - product_icon = { - "type": "awesome-font", - "name": "fa.file-o", - "color": get_default_entity_icon_color(), - } + product_icon = icons_mapping.get_icon( + product_base_type, product_type + ) version_items = { version_entity["id"]: version_item_from_entity(version_entity) for version_entity in version_entities @@ -131,8 +157,6 @@ def product_item_from_entity( product_base_type=product_base_type, product_name=product_entity["name"], product_icon=product_icon, - product_type_icon=product_type_icon, - product_base_type_icon=product_base_type_icon, product_in_scene=product_in_scene, group_name=group, folder_id=product_entity["folderId"], @@ -141,22 +165,8 @@ def product_item_from_entity( ) -def product_type_item_from_data( - product_type_data: ProductDict) -> ProductTypeItem: - # TODO implement icon implementation - # icon = product_type_data["icon"] - # color = product_type_data["color"] - icon = { - "type": "awesome-font", - "name": "fa.folder", - "color": "#0091B2", - } - # TODO implement checked logic - return ProductTypeItem(product_type_data["name"], icon) - - def product_base_type_item_from_data( - product_base_type_data: ProductBaseTypeDict + product_base_type_data: ProductBaseTypeDict ) -> ProductBaseTypeItem: """Create product base type item from data. @@ -174,34 +184,8 @@ def product_base_type_item_from_data( } return ProductBaseTypeItem( name=product_base_type_data["name"], - icon=icon) - - -def create_default_product_type_item(product_type: str) -> ProductTypeItem: - icon = { - "type": "awesome-font", - "name": "fa.folder", - "color": "#0091B2", - } - return ProductTypeItem(product_type, icon) - - -def create_default_product_base_type_item( - product_base_type: str) -> ProductBaseTypeItem: - """Create default product base type item. - - Args: - product_base_type (str): Product base type name. - - Returns: - ProductBaseTypeItem: Default product base type item. - """ - icon = { - "type": "awesome-font", - "name": "fa.folder", - "color": "#0091B2", - } - return ProductBaseTypeItem(product_base_type, icon) + icon=icon + ) class ProductsModel: @@ -227,6 +211,8 @@ class ProductsModel: self._product_folder_ids_mapping = collections.defaultdict(dict) # Cache helpers + self._product_type_icons_mapping = NestedCacheItem( + levels=1, default_factory=list, lifetime=self.lifetime) self._product_type_items_cache = NestedCacheItem( levels=1, default_factory=list, lifetime=self.lifetime) self._product_base_type_items_cache = NestedCacheItem( @@ -243,11 +229,14 @@ class ProductsModel: self._version_item_by_id.clear() self._product_folder_ids_mapping.clear() + self._product_type_icons_mapping.reset() self._product_type_items_cache.reset() self._product_items_cache.reset() self._repre_items_cache.reset() - def get_product_type_items(self, project_name): + def get_product_type_items( + self, project_name: Optional[str] + ) -> list[ProductTypeItem]: """Product type items for project. Args: @@ -255,23 +244,27 @@ class ProductsModel: Returns: list[ProductTypeItem]: Product type items. - """ + """ if not project_name: return [] cache = self._product_type_items_cache[project_name] if not cache.is_valid: + icons_mapping = self._get_product_type_icons(project_name) product_types = ayon_api.get_project_product_types(project_name) cache.update_data([ - product_type_item_from_data(product_type) + ProductTypeItem( + product_type["name"], + icons_mapping.get_icon(product_type=product_type["name"]), + ) for product_type in product_types ]) return cache.get_data() def get_product_base_type_items( - self, - project_name: Optional[str]) -> list[ProductBaseTypeItem]: + self, project_name: Optional[str] + ) -> list[ProductBaseTypeItem]: """Product base type items for the project. Args: @@ -286,6 +279,7 @@ class ProductsModel: cache = self._product_base_type_items_cache[project_name] if not cache.is_valid: + icons_mapping = self._get_product_type_icons(project_name) product_base_types = [] # TODO add temp implementation here when it is actually # implemented and available on server. @@ -294,7 +288,10 @@ class ProductsModel: project_name ) cache.update_data([ - product_base_type_item_from_data(product_base_type) + ProductBaseTypeItem( + product_base_type["name"], + icons_mapping.get_icon(product_base_type["name"]), + ) for product_base_type in product_base_types ]) return cache.get_data() @@ -511,6 +508,27 @@ class ProductsModel: PRODUCTS_MODEL_SENDER ) + def _get_product_type_icons( + self, project_name: Optional[str] + ) -> ProductBaseTypeIconMapping: + cache = self._product_type_icons_mapping[project_name] + if cache.is_valid: + return cache.get_data() + + project_entity = self._controller.get_project_entity(project_name) + icons_mapping = ProductBaseTypeIconMapping() + if project_entity: + product_base_types = ( + project_entity["config"].get("productBaseTypes", {}) + ) + icons_mapping = ProductBaseTypeIconMapping( + product_base_types.get("default"), + product_base_types.get("definitions") + ) + + cache.update_data(icons_mapping) + return icons_mapping + def _get_product_items_by_id(self, project_name, product_ids): product_item_by_id = self._product_item_by_id[project_name] missing_product_ids = set() @@ -553,36 +571,18 @@ class ProductsModel: products: Iterable[ProductDict], versions: Iterable[VersionDict], folder_items=None, - product_type_items=None, - product_base_type_items: Optional[Iterable[ProductBaseTypeItem]] = None ): if folder_items is None: folder_items = self._controller.get_folder_items(project_name) - if product_type_items is None: - product_type_items = self.get_product_type_items(project_name) - - if product_base_type_items is None: - product_base_type_items = self.get_product_base_type_items( - project_name - ) - loaded_product_ids = self._controller.get_loaded_product_ids() versions_by_product_id = collections.defaultdict(list) for version in versions: versions_by_product_id[version["productId"]].append(version) - product_type_items_by_name = { - product_type_item.name: product_type_item - for product_type_item in product_type_items - } - - product_base_type_items_by_name: dict[str, ProductBaseTypeItem] = { - product_base_type_item.name: product_base_type_item - for product_base_type_item in product_base_type_items - } output: dict[str, ProductItem] = {} + icons_mapping = self._get_product_type_icons(project_name) for product in products: product_id = product["id"] folder_id = product["folderId"] @@ -595,9 +595,8 @@ class ProductsModel: product_item = product_item_from_entity( product, versions, - product_type_items_by_name, - product_base_type_items_by_name, folder_item.label, + icons_mapping, product_id in loaded_product_ids, ) output[product_id] = product_item diff --git a/client/ayon_core/tools/loader/ui/products_model.py b/client/ayon_core/tools/loader/ui/products_model.py index f3e5271f51..79ed197d83 100644 --- a/client/ayon_core/tools/loader/ui/products_model.py +++ b/client/ayon_core/tools/loader/ui/products_model.py @@ -17,7 +17,6 @@ PRODUCT_ID_ROLE = QtCore.Qt.UserRole + 6 PRODUCT_NAME_ROLE = QtCore.Qt.UserRole + 7 PRODUCT_TYPE_ROLE = QtCore.Qt.UserRole + 8 PRODUCT_BASE_TYPE_ROLE = QtCore.Qt.UserRole + 9 -PRODUCT_TYPE_ICON_ROLE = QtCore.Qt.UserRole + 10 PRODUCT_IN_SCENE_ROLE = QtCore.Qt.UserRole + 11 VERSION_ID_ROLE = QtCore.Qt.UserRole + 12 VERSION_HERO_ROLE = QtCore.Qt.UserRole + 13 @@ -228,10 +227,7 @@ class ProductsModel(QtGui.QStandardItemModel): return super().data(index, role) if role == QtCore.Qt.DecorationRole: - if col == 1: - role = PRODUCT_TYPE_ICON_ROLE - else: - return None + return None if ( role == VERSION_NAME_EDIT_ROLE @@ -455,7 +451,6 @@ class ProductsModel(QtGui.QStandardItemModel): model_item = QtGui.QStandardItem(product_item.product_name) model_item.setEditable(False) icon = get_qt_icon(product_item.product_icon) - product_type_icon = get_qt_icon(product_item.product_type_icon) model_item.setColumnCount(self.columnCount()) model_item.setData(icon, QtCore.Qt.DecorationRole) model_item.setData(product_id, PRODUCT_ID_ROLE) @@ -464,7 +459,6 @@ class ProductsModel(QtGui.QStandardItemModel): product_item.product_base_type, PRODUCT_BASE_TYPE_ROLE ) model_item.setData(product_item.product_type, PRODUCT_TYPE_ROLE) - model_item.setData(product_type_icon, PRODUCT_TYPE_ICON_ROLE) model_item.setData(product_item.folder_id, FOLDER_ID_ROLE) self._product_items_by_id[product_id] = product_item From d8045df9a7f6d82941f280d4ae9783b58557a7f7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 12 Sep 2025 18:33:03 +0200 Subject: [PATCH 29/39] use same deafult as backend has --- client/ayon_core/tools/loader/models/products.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/tools/loader/models/products.py b/client/ayon_core/tools/loader/models/products.py index f5e0b2fa80..11468e4db0 100644 --- a/client/ayon_core/tools/loader/models/products.py +++ b/client/ayon_core/tools/loader/models/products.py @@ -58,7 +58,7 @@ class ProductBaseTypeIconMapping: if self._default_def is None: self._default_def = { "type": "material-symbols", - "name": self._default.get("icon", "inventory_2"), + "name": self._default.get("icon", "deployed_code"), "color": self._default.get("color", "#cccccc"), } @@ -69,7 +69,7 @@ class ProductBaseTypeIconMapping: self._definitions_by_name = { product_base_type_def["name"]: { "type": "material-symbols", - "name": product_base_type_def.get("icon", "inventory_2"), + "name": product_base_type_def.get("icon", "deployed_code"), "color": product_base_type_def.get("color", "#cccccc"), } for product_base_type_def in self._definitions From 7b92047a6bea0e10e3e3e8cb34d97129a75d72c7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 15 Sep 2025 11:38:58 +0200 Subject: [PATCH 30/39] move 'ProductTypeIconMapping' to common project model --- .../ayon_core/tools/common_models/__init__.py | 2 + .../ayon_core/tools/common_models/projects.py | 77 +++++++++++++++++- client/ayon_core/tools/loader/abstract.py | 20 +++-- client/ayon_core/tools/loader/control.py | 13 ++-- .../ayon_core/tools/loader/models/products.py | 78 ++----------------- 5 files changed, 108 insertions(+), 82 deletions(-) diff --git a/client/ayon_core/tools/common_models/__init__.py b/client/ayon_core/tools/common_models/__init__.py index ec69e20b64..77cc2dfb0f 100644 --- a/client/ayon_core/tools/common_models/__init__.py +++ b/client/ayon_core/tools/common_models/__init__.py @@ -10,6 +10,7 @@ from .projects import ( PROJECTS_MODEL_SENDER, FolderTypeItem, TaskTypeItem, + ProductTypeIconMapping, ) from .hierarchy import ( FolderItem, @@ -34,6 +35,7 @@ __all__ = ( "PROJECTS_MODEL_SENDER", "FolderTypeItem", "TaskTypeItem", + "ProductTypeIconMapping", "FolderItem", "TaskItem", diff --git a/client/ayon_core/tools/common_models/projects.py b/client/ayon_core/tools/common_models/projects.py index 034947de3a..710482a871 100644 --- a/client/ayon_core/tools/common_models/projects.py +++ b/client/ayon_core/tools/common_models/projects.py @@ -2,7 +2,7 @@ from __future__ import annotations import contextlib from abc import ABC, abstractmethod -from typing import Dict, Any +from typing import Any, Optional from dataclasses import dataclass import ayon_api @@ -51,7 +51,7 @@ class StatusItem: self.icon: str = icon self.state: str = state - def to_data(self) -> Dict[str, Any]: + def to_data(self) -> dict[str, Any]: return { "name": self.name, "color": self.color, @@ -218,6 +218,54 @@ class ProjectItem: return cls(**data) +class ProductTypeIconMapping: + def __init__( + self, + default: Optional[dict[str, str]] = None, + definitions: Optional[list[dict[str, str]]] = None, + ): + self._default = default or {} + self._definitions = definitions or [] + + self._default_def = None + self._definitions_by_name = None + + def get_icon( + self, + product_base_type: Optional[str] = None, + product_type: Optional[str] = None, + ) -> dict[str, str]: + defs = self._get_defs_by_name() + icon = defs.get(product_type) + if icon is None: + icon = defs.get(product_base_type) + if icon is None: + icon = self._get_default_def() + return icon.copy() + + def _get_default_def(self) -> dict[str, str]: + if self._default_def is None: + self._default_def = { + "type": "material-symbols", + "name": self._default.get("icon", "deployed_code"), + "color": self._default.get("color", "#cccccc"), + } + + return self._default_def + + def _get_defs_by_name(self) -> dict[str, dict[str, str]]: + if self._definitions_by_name is None: + self._definitions_by_name = { + product_base_type_def["name"]: { + "type": "material-symbols", + "name": product_base_type_def.get("icon", "deployed_code"), + "color": product_base_type_def.get("color", "#cccccc"), + } + for product_base_type_def in self._definitions + } + return self._definitions_by_name + + def _get_project_items_from_entitiy( projects: list[dict[str, Any]] ) -> list[ProjectItem]: @@ -242,6 +290,9 @@ class ProjectsModel(object): self._projects_by_name = NestedCacheItem( levels=1, default_factory=list ) + self._product_type_icons_mapping = NestedCacheItem( + levels=1, default_factory=ProductTypeIconMapping + ) self._project_statuses_cache = {} self._folder_types_cache = {} self._task_types_cache = {} @@ -255,6 +306,7 @@ class ProjectsModel(object): self._task_types_cache = {} self._projects_cache.reset() self._projects_by_name.reset() + self._product_type_icons_mapping.reset() def refresh(self): """Refresh project items. @@ -390,6 +442,27 @@ class ProjectsModel(object): self._task_type_items_getter, ) + def get_product_type_icons_mapping( + self, project_name: Optional[str] + ) -> ProductTypeIconMapping: + cache = self._product_type_icons_mapping[project_name] + if cache.is_valid: + return cache.get_data() + + project_entity = self.get_project_entity(project_name) + icons_mapping = ProductTypeIconMapping() + if project_entity: + product_base_types = ( + project_entity["config"].get("productBaseTypes", {}) + ) + icons_mapping = ProductTypeIconMapping( + product_base_types.get("default"), + product_base_types.get("definitions") + ) + + cache.update_data(icons_mapping) + return icons_mapping + def _get_project_items( self, project_name, sender, item_type, cache_obj, getter ): diff --git a/client/ayon_core/tools/loader/abstract.py b/client/ayon_core/tools/loader/abstract.py index 87cfb1f3f2..9c7934d2db 100644 --- a/client/ayon_core/tools/loader/abstract.py +++ b/client/ayon_core/tools/loader/abstract.py @@ -9,7 +9,11 @@ from ayon_core.lib.attribute_definitions import ( deserialize_attr_defs, serialize_attr_defs, ) -from ayon_core.tools.common_models import TaskItem, TagItem +from ayon_core.tools.common_models import ( + TaskItem, + TagItem, + ProductTypeIconMapping, +) class ProductTypeItem: @@ -492,8 +496,8 @@ class BackendLoaderController(_BaseLoaderController): topic (str): Event topic name. data (Optional[dict[str, Any]]): Event data. source (Optional[str]): Event source. - """ + """ pass @abstractmethod @@ -502,14 +506,20 @@ class BackendLoaderController(_BaseLoaderController): Returns: set[str]: Set of loaded product ids. - """ + """ pass @abstractmethod - def get_project_entity( + def get_product_type_icons_mapping( self, project_name: Optional[str] - ) -> Optional[dict[str, Any]]: + ) -> ProductTypeIconMapping: + """Product type icons mapping. + + Returns: + ProductTypeIconMapping: Product type icons mapping. + + """ pass diff --git a/client/ayon_core/tools/loader/control.py b/client/ayon_core/tools/loader/control.py index 492a05e82f..c7c182b192 100644 --- a/client/ayon_core/tools/loader/control.py +++ b/client/ayon_core/tools/loader/control.py @@ -17,6 +17,7 @@ from ayon_core.tools.common_models import ( HierarchyModel, ThumbnailsModel, TagItem, + ProductTypeIconMapping, ) from .abstract import ( @@ -189,11 +190,6 @@ class LoaderController(BackendLoaderController, FrontendLoaderController): def get_project_items(self, sender=None): return self._projects_model.get_project_items(sender) - def get_project_entity( - self, project_name: Optional[str] - ) -> Optional[dict[str, Any]]: - return self._projects_model.get_project_entity(project_name) - def get_folder_type_items(self, project_name, sender=None): return self._projects_model.get_folder_type_items( project_name, sender @@ -204,6 +200,13 @@ class LoaderController(BackendLoaderController, FrontendLoaderController): project_name, sender ) + def get_product_type_icons_mapping( + self, project_name: Optional[str] + ) -> ProductTypeIconMapping: + return self._projects_model.get_product_type_icons_mapping( + project_name + ) + def get_folder_items(self, project_name, sender=None): return self._hierarchy_model.get_folder_items(project_name, sender) diff --git a/client/ayon_core/tools/loader/models/products.py b/client/ayon_core/tools/loader/models/products.py index 11468e4db0..7915a75bcf 100644 --- a/client/ayon_core/tools/loader/models/products.py +++ b/client/ayon_core/tools/loader/models/products.py @@ -11,6 +11,7 @@ from ayon_api.operations import OperationsSession from ayon_core.lib import NestedCacheItem from ayon_core.style import get_default_entity_icon_color +from ayon_core.tools.common_models import ProductTypeIconMapping from ayon_core.tools.loader.abstract import ( ProductTypeItem, ProductBaseTypeItem, @@ -29,54 +30,6 @@ if TYPE_CHECKING: PRODUCTS_MODEL_SENDER = "products.model" -class ProductBaseTypeIconMapping: - def __init__( - self, - default: Optional[dict[str, str]] = None, - definitions: Optional[list[dict[str, str]]] = None, - ): - self._default = default or {} - self._definitions = definitions or [] - - self._default_def = None - self._definitions_by_name = None - - def get_icon( - self, - product_base_type: Optional[str] = None, - product_type: Optional[str] = None, - ) -> dict[str, str]: - defs = self._get_defs_by_name() - icon = defs.get(product_type) - if icon is None: - icon = defs.get(product_base_type) - if icon is None: - icon = self._get_default_def() - return icon.copy() - - def _get_default_def(self) -> dict[str, str]: - if self._default_def is None: - self._default_def = { - "type": "material-symbols", - "name": self._default.get("icon", "deployed_code"), - "color": self._default.get("color", "#cccccc"), - } - - return self._default_def - - def _get_defs_by_name(self) -> dict[str, dict[str, str]]: - if self._definitions_by_name is None: - self._definitions_by_name = { - product_base_type_def["name"]: { - "type": "material-symbols", - "name": product_base_type_def.get("icon", "deployed_code"), - "color": product_base_type_def.get("color", "#cccccc"), - } - for product_base_type_def in self._definitions - } - return self._definitions_by_name - - def version_item_from_entity(version): version_attribs = version["attrib"] tags = version["tags"] @@ -211,8 +164,6 @@ class ProductsModel: self._product_folder_ids_mapping = collections.defaultdict(dict) # Cache helpers - self._product_type_icons_mapping = NestedCacheItem( - levels=1, default_factory=list, lifetime=self.lifetime) self._product_type_items_cache = NestedCacheItem( levels=1, default_factory=list, lifetime=self.lifetime) self._product_base_type_items_cache = NestedCacheItem( @@ -229,7 +180,6 @@ class ProductsModel: self._version_item_by_id.clear() self._product_folder_ids_mapping.clear() - self._product_type_icons_mapping.reset() self._product_type_items_cache.reset() self._product_items_cache.reset() self._repre_items_cache.reset() @@ -267,6 +217,10 @@ class ProductsModel: ) -> list[ProductBaseTypeItem]: """Product base type items for the project. + Notes: + This will be used for filtering product types in UI when + product base types are fully implemented. + Args: project_name (optional, str): Project name. @@ -510,24 +464,8 @@ class ProductsModel: def _get_product_type_icons( self, project_name: Optional[str] - ) -> ProductBaseTypeIconMapping: - cache = self._product_type_icons_mapping[project_name] - if cache.is_valid: - return cache.get_data() - - project_entity = self._controller.get_project_entity(project_name) - icons_mapping = ProductBaseTypeIconMapping() - if project_entity: - product_base_types = ( - project_entity["config"].get("productBaseTypes", {}) - ) - icons_mapping = ProductBaseTypeIconMapping( - product_base_types.get("default"), - product_base_types.get("definitions") - ) - - cache.update_data(icons_mapping) - return icons_mapping + ) -> ProductTypeIconMapping: + return self._controller.get_product_type_icons_mapping(project_name) def _get_product_items_by_id(self, project_name, product_ids): product_item_by_id = self._product_item_by_id[project_name] @@ -542,7 +480,7 @@ class ProductsModel: output.update( self._query_product_items_by_ids( - project_name, missing_product_ids + project_name, product_ids=missing_product_ids ) ) return output From cf874e8f0a274b8a38ff8221d7f3d1a567095286 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 15 Sep 2025 11:46:28 +0200 Subject: [PATCH 31/39] use product type icon in scene inventory --- .../ayon_core/tools/sceneinventory/control.py | 15 ++++++++++- .../ayon_core/tools/sceneinventory/model.py | 5 +--- .../tools/sceneinventory/models/containers.py | 25 ++++++++++++++++++- 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/tools/sceneinventory/control.py b/client/ayon_core/tools/sceneinventory/control.py index 45f76a54ac..606c9e7298 100644 --- a/client/ayon_core/tools/sceneinventory/control.py +++ b/client/ayon_core/tools/sceneinventory/control.py @@ -1,3 +1,5 @@ +from typing import Optional + import ayon_api from ayon_core.lib.events import QueuedEventSystem @@ -6,7 +8,11 @@ from ayon_core.pipeline import ( registered_host, get_current_context, ) -from ayon_core.tools.common_models import HierarchyModel, ProjectsModel +from ayon_core.tools.common_models import ( + HierarchyModel, + ProjectsModel, + ProductTypeIconMapping, +) from .models import SiteSyncModel, ContainersModel @@ -93,6 +99,13 @@ class SceneInventoryController: project_name, None ) + def get_product_type_icons_mapping( + self, project_name: Optional[str] + ) -> ProductTypeIconMapping: + return self._projects_model.get_product_type_icons_mapping( + project_name + ) + # Containers methods def get_containers(self): return self._containers_model.get_containers() diff --git a/client/ayon_core/tools/sceneinventory/model.py b/client/ayon_core/tools/sceneinventory/model.py index 885553acaf..9977acea21 100644 --- a/client/ayon_core/tools/sceneinventory/model.py +++ b/client/ayon_core/tools/sceneinventory/model.py @@ -214,9 +214,6 @@ class InventoryModel(QtGui.QStandardItemModel): group_icon = qtawesome.icon( "fa.object-group", color=self._default_icon_color ) - product_type_icon = qtawesome.icon( - "fa.folder", color="#0091B2" - ) group_item_font = QtGui.QFont() group_item_font.setBold(True) @@ -303,7 +300,7 @@ class InventoryModel(QtGui.QStandardItemModel): remote_site_progress = "{}%".format( max(progress["remote_site"], 0) * 100 ) - + product_type_icon = get_qt_icon(repre_info.product_type_icon) group_item = QtGui.QStandardItem() group_item.setColumnCount(root_item.columnCount()) group_item.setData(group_name, QtCore.Qt.DisplayRole) diff --git a/client/ayon_core/tools/sceneinventory/models/containers.py b/client/ayon_core/tools/sceneinventory/models/containers.py index f841f87c8e..47f74476de 100644 --- a/client/ayon_core/tools/sceneinventory/models/containers.py +++ b/client/ayon_core/tools/sceneinventory/models/containers.py @@ -126,6 +126,7 @@ class RepresentationInfo: product_id, product_name, product_type, + product_type_icon, product_group, version_id, representation_name, @@ -135,6 +136,7 @@ class RepresentationInfo: self.product_id = product_id self.product_name = product_name self.product_type = product_type + self.product_type_icon = product_type_icon self.product_group = product_group self.version_id = version_id self.representation_name = representation_name @@ -153,7 +155,17 @@ class RepresentationInfo: @classmethod def new_invalid(cls): - return cls(None, None, None, None, None, None, None, None) + return cls( + None, + None, + None, + None, + None, + None, + None, + None, + None, + ) class VersionItem: @@ -229,6 +241,9 @@ class ContainersModel: def get_representation_info_items(self, project_name, representation_ids): output = {} missing_repre_ids = set() + icons_mapping = self._controller.get_product_type_icons_mapping( + project_name + ) for repre_id in representation_ids: try: uuid.UUID(repre_id) @@ -253,6 +268,7 @@ class ContainersModel: "product_id": None, "product_name": None, "product_type": None, + "product_type_icon": None, "product_group": None, "version_id": None, "representation_name": None, @@ -265,10 +281,17 @@ class ContainersModel: kwargs["folder_id"] = folder["id"] kwargs["folder_path"] = folder["path"] if product: + product_type = product["productType"] + product_base_type = product.get("productBaseType") + icon = icons_mapping.get_icon( + product_base_type=product_base_type, + product_type=product_type, + ) group = product["attrib"]["productGroup"] kwargs["product_id"] = product["id"] kwargs["product_name"] = product["name"] kwargs["product_type"] = product["productType"] + kwargs["product_type_icon"] = icon kwargs["product_group"] = group if version: kwargs["version_id"] = version["id"] From 31469bfd9a0b7d29b88ce9162cfb297fc1c4aef5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 15 Sep 2025 11:52:37 +0200 Subject: [PATCH 32/39] remove unused import --- client/ayon_core/tools/loader/control.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/tools/loader/control.py b/client/ayon_core/tools/loader/control.py index c7c182b192..9f159bfb21 100644 --- a/client/ayon_core/tools/loader/control.py +++ b/client/ayon_core/tools/loader/control.py @@ -2,7 +2,7 @@ from __future__ import annotations import logging import uuid -from typing import Optional, Any +from typing import Optional import ayon_api From 7082905e5e25524c16d0bd5a7376eb010d025975 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 15 Sep 2025 12:04:45 +0200 Subject: [PATCH 33/39] use icon color from project anatomy for tasks --- client/ayon_core/tools/common_models/projects.py | 15 ++++++++++++--- client/ayon_core/tools/utils/tasks_widget.py | 3 ++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/tools/common_models/projects.py b/client/ayon_core/tools/common_models/projects.py index 034947de3a..7a5620b033 100644 --- a/client/ayon_core/tools/common_models/projects.py +++ b/client/ayon_core/tools/common_models/projects.py @@ -2,7 +2,7 @@ from __future__ import annotations import contextlib from abc import ABC, abstractmethod -from typing import Dict, Any +from typing import Any, Optional from dataclasses import dataclass import ayon_api @@ -51,7 +51,7 @@ class StatusItem: self.icon: str = icon self.state: str = state - def to_data(self) -> Dict[str, Any]: + def to_data(self) -> dict[str, Any]: return { "name": self.name, "color": self.color, @@ -125,16 +125,24 @@ class TaskTypeItem: icon (str): Icon name in MaterialIcons ("fiber_new"). """ - def __init__(self, name, short, icon): + def __init__( + self, + name: str, + short: str, + icon: str, + color: Optional[str], + ): self.name = name self.short = short self.icon = icon + self.color = color def to_data(self): return { "name": self.name, "short": self.short, "icon": self.icon, + "color": self.color, } @classmethod @@ -147,6 +155,7 @@ class TaskTypeItem: name=task_type_data["name"], short=task_type_data["shortName"], icon=task_type_data["icon"], + color=task_type_data.get("color"), ) diff --git a/client/ayon_core/tools/utils/tasks_widget.py b/client/ayon_core/tools/utils/tasks_widget.py index 744eb6060a..d77ce1e1f4 100644 --- a/client/ayon_core/tools/utils/tasks_widget.py +++ b/client/ayon_core/tools/utils/tasks_widget.py @@ -234,10 +234,11 @@ class TasksQtModel(QtGui.QStandardItemModel): ) icon = None if task_type_item is not None: + color = task_type_item.color or get_default_entity_icon_color() icon = get_qt_icon({ "type": "material-symbols", "name": task_type_item.icon, - "color": get_default_entity_icon_color() + "color": color, }) if icon is None: From 8979fc7608b81db11740738c17c6f581ae1dca1e Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 15 Sep 2025 13:54:22 +0200 Subject: [PATCH 34/39] elite status name --- client/ayon_core/tools/utils/delegates.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/tools/utils/delegates.py b/client/ayon_core/tools/utils/delegates.py index 1cc18b5722..059fc1da0e 100644 --- a/client/ayon_core/tools/utils/delegates.py +++ b/client/ayon_core/tools/utils/delegates.py @@ -186,8 +186,15 @@ class StatusDelegate(QtWidgets.QStyledItemDelegate): ) fm = QtGui.QFontMetrics(option.font) if text_rect.width() < fm.width(text): - text = self._get_status_short_name(index) - if text_rect.width() < fm.width(text): + short_text = self._get_status_short_name(index) + if short_text: + text = short_text + + text = fm.elidedText( + text, QtCore.Qt.ElideRight, text_rect.width() + ) + # Allow at least one character + if len(text) < 2: text = "" fg_color = self._get_status_color(index) From 00603d05a4014c1871e742d8dee5c12dae1f7166 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 15 Sep 2025 15:17:27 +0200 Subject: [PATCH 35/39] fill task type color in publisher's tasks enum --- .../tools/publisher/widgets/tasks_model.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/tools/publisher/widgets/tasks_model.py b/client/ayon_core/tools/publisher/widgets/tasks_model.py index 8bfa81116a..1803e46c5f 100644 --- a/client/ayon_core/tools/publisher/widgets/tasks_model.py +++ b/client/ayon_core/tools/publisher/widgets/tasks_model.py @@ -146,19 +146,19 @@ class TasksModel(QtGui.QStandardItemModel): self._controller.get_current_project_name() ) } - icon_name_by_task_name = {} + type_item_by_task_name = {} for task_items in task_items_by_folder_path.values(): for task_item in task_items: task_name = task_item.name if ( task_name not in new_task_names - or task_name in icon_name_by_task_name + or task_name in type_item_by_task_name ): continue task_type_name = task_item.task_type task_type_item = task_type_items.get(task_type_name) if task_type_item: - icon_name_by_task_name[task_name] = task_type_item.icon + type_item_by_task_name[task_name] = task_type_item for task_name in new_task_names: item = self._items_by_name.get(task_name) @@ -171,13 +171,18 @@ class TasksModel(QtGui.QStandardItemModel): if not task_name: continue - icon_name = icon_name_by_task_name.get(task_name) - icon = None + icon = icon_name = icon_color = None + task_type_item = type_item_by_task_name.get(task_name) + if task_type_item is not None: + icon_name = task_type_item.icon + icon_color = task_type_item.color if icon_name: + if not icon_color: + icon_color = get_default_entity_icon_color() icon = get_qt_icon({ "type": "material-symbols", "name": icon_name, - "color": get_default_entity_icon_color(), + "color": icon_color, }) if icon is None: icon = default_icon From 216c06437fe2ea50d58a56f5d917f6d962818f28 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 15 Sep 2025 19:20:10 +0300 Subject: [PATCH 36/39] MK Docs: Update light/dark modes icons --- mkdocs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index 8e4c2663bc..a3b89b5455 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -11,12 +11,12 @@ theme: - media: "(prefers-color-scheme: dark)" scheme: slate toggle: - icon: material/toggle-switch-off-outline + icon: material/weather-sunny name: Switch to light mode - media: "(prefers-color-scheme: light)" scheme: default toggle: - icon: material/toggle-switch + icon: material/weather-night name: Switch to dark mode logo: img/ay-symbol-blackw-full.png favicon: img/favicon.ico From 99c50c3a14a96a5b227a1f27c17191ce8ade0d73 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 16 Sep 2025 11:54:01 +0300 Subject: [PATCH 37/39] move mkdocs_requirements to root location. --- docs/mkdocs_requirements.txt => mkdocs_requirements.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/mkdocs_requirements.txt => mkdocs_requirements.txt (100%) diff --git a/docs/mkdocs_requirements.txt b/mkdocs_requirements.txt similarity index 100% rename from docs/mkdocs_requirements.txt rename to mkdocs_requirements.txt From eaa6727a9d0ae5289961b5b46d9fdf9245e6c93a Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 16 Sep 2025 11:56:00 +0300 Subject: [PATCH 38/39] CI: Re-use `deploy_mkdocs` from ynput/ops-repo-automation --- .github/workflows/deploy_mkdocs.yml | 69 ++++------------------------- 1 file changed, 8 insertions(+), 61 deletions(-) diff --git a/.github/workflows/deploy_mkdocs.yml b/.github/workflows/deploy_mkdocs.yml index 2f46bdc6ad..1e02643a3d 100644 --- a/.github/workflows/deploy_mkdocs.yml +++ b/.github/workflows/deploy_mkdocs.yml @@ -1,70 +1,17 @@ name: Deploy MkDocs on: - pull_request: + push: + tags: + - "*" workflow_dispatch: - workflow_call: - inputs: - repo: - type: string - required: true - branch_name: - type: string - required: true - default: "main" - secrets: - token: - required: true - user: - required: true - email: - required: true - -env: - GH_TOKEN: ${{ secrets.token || secrets.YNPUT_BOT_TOKEN }} - GH_USER: ${{ secrets.user || secrets.CI_USER }} - GH_EMAIL: ${{ secrets.email || secrets.CI_EMAIL }} jobs: - verify-repo-secrets: - uses: ynput/ops-repo-automation/.github/workflows/verify_secrets.yml@main + build-mk-docs: + uses: ynput/ops-repo-automation/.github/workflows/deploy_mkdocs.yml@feature/34-add-mk-docs-reusable-workflow with: repo: ${{ github.repository }} secrets: - gh_token: ${{ secrets.token }} - gh_user: ${{ secrets.user }} - gh_email: ${{ secrets.email }} - - deploy: - runs-on: ubuntu-latest - - steps: - - name: Checkout ${{ inputs.branch_name}} - uses: actions/checkout@v4 - with: - ref: ${{ inputs.branch_name}} - fetch-depth: 0 - submodules: true - - - name: πŸ”‘ Set Authentication - run: | - git config --global user.name "${{ secrets.user || secrets.CI_USER }}" - git config --global user.email "${{ secrets.email || secrets.CI_EMAIL }}" - - - name: Get current tag - id: git_tag - uses: devops-actions/action-get-tag@v1.0.3 - with: - default: 1.0.0 - - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: "3.x" - - - name: Install dependencies - run: | - python3 -m pip install -r ./docs/mkdocs_requirements.txt - - - name: Mike deploy ${{ steps.git_tag.outputs.tag }} - run: mike deploy --update-aliases ${{ steps.git_tag.outputs.tag }} latest + YNPUT_BOT_TOKEN: ${{ secrets.YNPUT_BOT_TOKEN }} + CI_USER: ${{ secrets.CI_USER }} + CI_EMAIL: ${{ secrets.CI_EMAIL }} From a46a9e3143998c93d04824d3aa795e8f53b580d8 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 16 Sep 2025 13:04:54 +0300 Subject: [PATCH 39/39] CI: Update Deploy MkDocs to use action from develop branch from ynput/ops-repo-automation --- .github/workflows/deploy_mkdocs.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy_mkdocs.yml b/.github/workflows/deploy_mkdocs.yml index 1e02643a3d..deafc7b850 100644 --- a/.github/workflows/deploy_mkdocs.yml +++ b/.github/workflows/deploy_mkdocs.yml @@ -8,7 +8,8 @@ on: jobs: build-mk-docs: - uses: ynput/ops-repo-automation/.github/workflows/deploy_mkdocs.yml@feature/34-add-mk-docs-reusable-workflow + # FIXME: Update @develop to @main after `ops-repo-automation` release. + uses: ynput/ops-repo-automation/.github/workflows/deploy_mkdocs.yml@develop with: repo: ${{ github.repository }} secrets: