From 122a4dc9db074f7bd14421e1d8b9244a05318da7 Mon Sep 17 00:00:00 2001 From: Michael reda Date: Wed, 5 Apr 2023 12:14:06 +0200 Subject: [PATCH 01/50] add sync to specific projects or listen only --- openpype/modules/kitsu/kitsu_module.py | 16 ++++++++++-- .../modules/kitsu/utils/update_op_with_zou.py | 25 +++++++++++++++++-- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/openpype/modules/kitsu/kitsu_module.py b/openpype/modules/kitsu/kitsu_module.py index b91373af20..f4e3dd5691 100644 --- a/openpype/modules/kitsu/kitsu_module.py +++ b/openpype/modules/kitsu/kitsu_module.py @@ -124,19 +124,31 @@ def push_to_zou(login, password): @cli_main.command() +@click.option("-prjs", "--projects", envvar="SYNC_PROJECTS", help="Sync specific kitsu projects") @click.option("-l", "--login", envvar="KITSU_LOGIN", help="Kitsu login") @click.option( "-p", "--password", envvar="KITSU_PWD", help="Password for kitsu username" ) -def sync_service(login, password): +def sync_service(login, password, projects="^"): """Synchronize openpype database from Zou sever database. Args: login (str): Kitsu user login password (str): Kitsu user password + projects (str): specific kitsu projects + + SYNC_PROJECTS: + *: all projects + ^: dont sync any project just listen + "project01 project02 ...": to choose custom projects + + """ from .utils.update_op_with_zou import sync_all_projects from .utils.sync_service import start_listeners - sync_all_projects(login, password) + projects = projects.strip() + projects = projects.split(' ') + + sync_all_projects(login, password, specific_projects=projects) start_listeners(login, password) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 4f4f0810bc..a397198a13 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -359,7 +359,7 @@ def write_project_to_op(project: dict, dbcon: AvalonMongoDB) -> UpdateOne: def sync_all_projects( - login: str, password: str, ignore_projects: list = None + login: str, password: str, ignore_projects: list = None, specific_projects: list = None ): """Update all OP projects in DB with Zou data. @@ -367,6 +367,7 @@ def sync_all_projects( login (str): Kitsu user login password (str): Kitsu user password ignore_projects (list): List of unsynced project names + specific_projects (list): List of synced project names Raises: gazu.exception.AuthFailedException: Wrong user login and/or password """ @@ -381,7 +382,27 @@ def sync_all_projects( dbcon = AvalonMongoDB() dbcon.install() all_projects = gazu.project.all_projects() - for project in all_projects: + + + project_to_sync = [] + if specific_projects == ['*']: + project_to_sync = all_projects + + elif specific_projects == ['^']: + return + + elif isinstance(specific_projects, list): + all_kitsu_projects = {p['name']: p for p in all_projects} + for proj_name in specific_projects: + if proj_name in all_kitsu_projects: + project_to_sync.append(all_kitsu_projects[proj_name]) + else: + log.info(f'`{proj_name}` project does not exists in kitsu.' + f' Please make sure you write the project correctly.') + else: + return + + for project in project_to_sync: if ignore_projects and project["name"] in ignore_projects: continue sync_project_from_kitsu(dbcon, project) From 3c801ca1187ac3d1cf1d0da50cf27eceaae5fa30 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 18 Apr 2023 22:39:07 +0200 Subject: [PATCH 02/50] Fix imports --- openpype/hosts/maya/api/setdress.py | 8 +++----- openpype/hosts/maya/plugins/load/load_assembly.py | 15 ++++++--------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/maya/api/setdress.py b/openpype/hosts/maya/api/setdress.py index 159bfe9eb3..0bb1f186eb 100644 --- a/openpype/hosts/maya/api/setdress.py +++ b/openpype/hosts/maya/api/setdress.py @@ -28,7 +28,9 @@ from openpype.pipeline import ( ) from openpype.hosts.maya.api.lib import ( matrix_equals, - unique_namespace + unique_namespace, + get_container_transforms, + DEFAULT_MATRIX ) log = logging.getLogger("PackageLoader") @@ -183,8 +185,6 @@ def _add(instance, representation_id, loaders, namespace, root="|"): """ - from openpype.hosts.maya.lib import get_container_transforms - # Process within the namespace with namespaced(namespace, new=False) as namespace: @@ -379,8 +379,6 @@ def update_scene(set_container, containers, current_data, new_data, new_file): """ - from openpype.hosts.maya.lib import DEFAULT_MATRIX, get_container_transforms - set_namespace = set_container['namespace'] project_name = legacy_io.active_project() diff --git a/openpype/hosts/maya/plugins/load/load_assembly.py b/openpype/hosts/maya/plugins/load/load_assembly.py index 902f38695c..275f21be5d 100644 --- a/openpype/hosts/maya/plugins/load/load_assembly.py +++ b/openpype/hosts/maya/plugins/load/load_assembly.py @@ -1,8 +1,14 @@ +import maya.cmds as cmds + from openpype.pipeline import ( load, remove_container ) +from openpype.hosts.maya.api.pipeline import containerise +from openpype.hosts.maya.api.lib import unique_namespace +from openpype.hosts.maya.api import setdress + class AssemblyLoader(load.LoaderPlugin): @@ -16,9 +22,6 @@ class AssemblyLoader(load.LoaderPlugin): def load(self, context, name, namespace, data): - from openpype.hosts.maya.api.pipeline import containerise - from openpype.hosts.maya.api.lib import unique_namespace - asset = context['asset']['name'] namespace = namespace or unique_namespace( asset + "_", @@ -26,8 +29,6 @@ class AssemblyLoader(load.LoaderPlugin): suffix="_", ) - from openpype.hosts.maya.api import setdress - containers = setdress.load_package( filepath=self.fname, name=name, @@ -50,15 +51,11 @@ class AssemblyLoader(load.LoaderPlugin): def update(self, container, representation): - from openpype import setdress return setdress.update_package(container, representation) def remove(self, container): """Remove all sub containers""" - from openpype import setdress - import maya.cmds as cmds - # Remove all members member_containers = setdress.get_contained_containers(container) for member_container in member_containers: From 557cbb72cefa955f75fa6c3f1df7fe38665e1c99 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 27 Apr 2023 11:26:57 +0200 Subject: [PATCH 03/50] :recycle: escape rootless path --- openpype/modules/deadline/plugins/publish/submit_publish_job.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index f80bd40133..6ee100ddb4 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -275,7 +275,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): args = [ "--headless", 'publish', - rootless_metadata_path, + '"{}"'.format(rootless_metadata_path), "--targets", "deadline", "--targets", "farm" ] From 84cef9d3cf6135fd26b490f57e9bc68d9da36ace Mon Sep 17 00:00:00 2001 From: Michael reda Date: Tue, 2 May 2023 11:12:18 +0200 Subject: [PATCH 04/50] handel long lines --- openpype/modules/kitsu/kitsu_module.py | 19 ++++++-- .../modules/kitsu/utils/update_op_with_zou.py | 48 +++++++++---------- 2 files changed, 40 insertions(+), 27 deletions(-) diff --git a/openpype/modules/kitsu/kitsu_module.py b/openpype/modules/kitsu/kitsu_module.py index f4e3dd5691..7c9d888aa7 100644 --- a/openpype/modules/kitsu/kitsu_module.py +++ b/openpype/modules/kitsu/kitsu_module.py @@ -124,10 +124,23 @@ def push_to_zou(login, password): @cli_main.command() -@click.option("-prjs", "--projects", envvar="SYNC_PROJECTS", help="Sync specific kitsu projects") -@click.option("-l", "--login", envvar="KITSU_LOGIN", help="Kitsu login") @click.option( - "-p", "--password", envvar="KITSU_PWD", help="Password for kitsu username" + "-prjs", + "--projects", + envvar="SYNC_PROJECTS", + help="Sync specific kitsu projects" +) +@click.option( + "-l", + "--login", + envvar="KITSU_LOGIN", + help="Kitsu login" +) +@click.option( + "-p", + "--password", + envvar="KITSU_PWD", + help="Password for kitsu username" ) def sync_service(login, password, projects="^"): """Synchronize openpype database from Zou sever database. diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index a397198a13..ad8ccd9f3f 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -63,11 +63,11 @@ def set_op_project(dbcon: AvalonMongoDB, project_id: str): def update_op_assets( - dbcon: AvalonMongoDB, - gazu_project: dict, - project_doc: dict, - entities_list: List[dict], - asset_doc_ids: Dict[str, dict], + dbcon: AvalonMongoDB, + gazu_project: dict, + project_doc: dict, + entities_list: List[dict], + asset_doc_ids: Dict[str, dict], ) -> List[Dict[str, dict]]: """Update OpenPype assets. Set 'data' and 'parent' fields. @@ -210,10 +210,10 @@ def update_op_assets( item.get("entity_type_id") if item_type == "Asset" else None - # Else, fallback on usual hierarchy - or item.get("parent_id") - or item.get("episode_id") - or item.get("source_id") + # Else, fallback on usual hierarchy + or item.get("parent_id") + or item.get("episode_id") + or item.get("source_id") ) # Substitute item type for general classification (assets or shots) @@ -350,7 +350,7 @@ def write_project_to_op(project: dict, dbcon: AvalonMongoDB) -> UpdateOne: "config.tasks": { t["name"]: {"short_name": t.get("short_name", t["name"])} for t in gazu.task.all_task_types_for_project(project) - or gazu.task.all_task_types() + or gazu.task.all_task_types() }, "data": project_data, } @@ -359,7 +359,8 @@ def write_project_to_op(project: dict, dbcon: AvalonMongoDB) -> UpdateOne: def sync_all_projects( - login: str, password: str, ignore_projects: list = None, specific_projects: list = None + login: str, password: str, ignore_projects: list = None, + specific_projects: list = None ): """Update all OP projects in DB with Zou data. @@ -383,7 +384,6 @@ def sync_all_projects( dbcon.install() all_projects = gazu.project.all_projects() - project_to_sync = [] if specific_projects == ['*']: project_to_sync = all_projects @@ -435,8 +435,8 @@ def sync_project_from_kitsu(dbcon: AvalonMongoDB, project: dict): # Do not sync closed kitsu project that is not found in openpype if ( - project['project_status_name'] == "Closed" - and not get_project(project['name']) + project['project_status_name'] == "Closed" + and not get_project(project['name']) ): return @@ -451,10 +451,10 @@ def sync_project_from_kitsu(dbcon: AvalonMongoDB, project: dict): all_entities = [ item for item in all_assets - + all_asset_types - + all_episodes - + all_seqs - + all_shots + + all_asset_types + + all_episodes + + all_seqs + + all_shots if naming_pattern.match(item["name"]) ] @@ -526,12 +526,12 @@ def sync_project_from_kitsu(dbcon: AvalonMongoDB, project: dict): [ UpdateOne({"_id": id}, update) for id, update in update_op_assets( - dbcon, - project, - project_dict, - all_entities, - zou_ids_and_asset_docs, - ) + dbcon, + project, + project_dict, + all_entities, + zou_ids_and_asset_docs, + ) ] ) From 2a9a7d6fd4f0a96dc70b1f61525d5b8f1ac05eb5 Mon Sep 17 00:00:00 2001 From: Michael <71185790+Michaelredaa@users.noreply.github.com> Date: Wed, 3 May 2023 12:18:43 +0300 Subject: [PATCH 05/50] handle reviews --- openpype/modules/kitsu/kitsu_module.py | 19 ++---- .../modules/kitsu/utils/update_op_with_zou.py | 62 +++++++++---------- 2 files changed, 36 insertions(+), 45 deletions(-) diff --git a/openpype/modules/kitsu/kitsu_module.py b/openpype/modules/kitsu/kitsu_module.py index 7c9d888aa7..2a3a0f3bff 100644 --- a/openpype/modules/kitsu/kitsu_module.py +++ b/openpype/modules/kitsu/kitsu_module.py @@ -125,9 +125,10 @@ def push_to_zou(login, password): @cli_main.command() @click.option( - "-prjs", - "--projects", - envvar="SYNC_PROJECTS", + "-prj", + "--project", + multiple=True, + default=[""] help="Sync specific kitsu projects" ) @click.option( @@ -142,26 +143,18 @@ def push_to_zou(login, password): envvar="KITSU_PWD", help="Password for kitsu username" ) -def sync_service(login, password, projects="^"): +def sync_service(login, password, project): """Synchronize openpype database from Zou sever database. Args: login (str): Kitsu user login password (str): Kitsu user password projects (str): specific kitsu projects - - SYNC_PROJECTS: - *: all projects - ^: dont sync any project just listen - "project01 project02 ...": to choose custom projects - - """ from .utils.update_op_with_zou import sync_all_projects from .utils.sync_service import start_listeners - projects = projects.strip() - projects = projects.split(' ') + projects = ' '.join(project) sync_all_projects(login, password, specific_projects=projects) start_listeners(login, password) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index ad8ccd9f3f..7983765e83 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -63,11 +63,11 @@ def set_op_project(dbcon: AvalonMongoDB, project_id: str): def update_op_assets( - dbcon: AvalonMongoDB, - gazu_project: dict, - project_doc: dict, - entities_list: List[dict], - asset_doc_ids: Dict[str, dict], + dbcon: AvalonMongoDB, + gazu_project: dict, + project_doc: dict, + entities_list: List[dict], + asset_doc_ids: Dict[str, dict], ) -> List[Dict[str, dict]]: """Update OpenPype assets. Set 'data' and 'parent' fields. @@ -210,10 +210,10 @@ def update_op_assets( item.get("entity_type_id") if item_type == "Asset" else None - # Else, fallback on usual hierarchy - or item.get("parent_id") - or item.get("episode_id") - or item.get("source_id") + # Else, fallback on usual hierarchy + or item.get("parent_id") + or item.get("episode_id") + or item.get("source_id") ) # Substitute item type for general classification (assets or shots) @@ -350,7 +350,7 @@ def write_project_to_op(project: dict, dbcon: AvalonMongoDB) -> UpdateOne: "config.tasks": { t["name"]: {"short_name": t.get("short_name", t["name"])} for t in gazu.task.all_task_types_for_project(project) - or gazu.task.all_task_types() + or gazu.task.all_task_types() }, "data": project_data, } @@ -358,9 +358,8 @@ def write_project_to_op(project: dict, dbcon: AvalonMongoDB) -> UpdateOne: ) -def sync_all_projects( - login: str, password: str, ignore_projects: list = None, - specific_projects: list = None +def sync_all_projects(login: str, password: str, ignore_projects: list = None, + filter_projects: list = None ): """Update all OP projects in DB with Zou data. @@ -368,7 +367,7 @@ def sync_all_projects( login (str): Kitsu user login password (str): Kitsu user password ignore_projects (list): List of unsynced project names - specific_projects (list): List of synced project names + filter_projects (list): List of filter project names to sync with Raises: gazu.exception.AuthFailedException: Wrong user login and/or password """ @@ -385,22 +384,21 @@ def sync_all_projects( all_projects = gazu.project.all_projects() project_to_sync = [] - if specific_projects == ['*']: + + if not filter_projects: + # listen only + return + + if filter_projects == ['*']: project_to_sync = all_projects - elif specific_projects == ['^']: - return - - elif isinstance(specific_projects, list): - all_kitsu_projects = {p['name']: p for p in all_projects} - for proj_name in specific_projects: - if proj_name in all_kitsu_projects: - project_to_sync.append(all_kitsu_projects[proj_name]) - else: - log.info(f'`{proj_name}` project does not exists in kitsu.' - f' Please make sure you write the project correctly.') - else: - return + all_kitsu_projects = {p['name']: p for p in all_projects} + for proj_name in filter_projects: + if proj_name in all_kitsu_projects: + project_to_sync.append(all_kitsu_projects[proj_name]) + else: + log.info(f'`{proj_name}` project does not exist in Kitsu.' + f' Please make sure the project is spelled correctly.') for project in project_to_sync: if ignore_projects and project["name"] in ignore_projects: @@ -451,10 +449,10 @@ def sync_project_from_kitsu(dbcon: AvalonMongoDB, project: dict): all_entities = [ item for item in all_assets - + all_asset_types - + all_episodes - + all_seqs - + all_shots + + all_asset_types + + all_episodes + + all_seqs + + all_shots if naming_pattern.match(item["name"]) ] From 0a3d206680ed7b21cfb30f1800ae1625e284793d Mon Sep 17 00:00:00 2001 From: Michael <71185790+Michaelredaa@users.noreply.github.com> Date: Wed, 3 May 2023 12:23:17 +0300 Subject: [PATCH 06/50] solve syntex error --- openpype/modules/kitsu/kitsu_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/kitsu_module.py b/openpype/modules/kitsu/kitsu_module.py index 2a3a0f3bff..59ad2efd29 100644 --- a/openpype/modules/kitsu/kitsu_module.py +++ b/openpype/modules/kitsu/kitsu_module.py @@ -128,7 +128,7 @@ def push_to_zou(login, password): "-prj", "--project", multiple=True, - default=[""] + default=[""], help="Sync specific kitsu projects" ) @click.option( From 6e9b6f6bef0c290dd46b7b3669fad2d3b338c0e8 Mon Sep 17 00:00:00 2001 From: Michael <71185790+Michaelredaa@users.noreply.github.com> Date: Wed, 3 May 2023 12:38:42 +0300 Subject: [PATCH 07/50] correct arg name in sync_all_projects --- openpype/modules/kitsu/kitsu_module.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/openpype/modules/kitsu/kitsu_module.py b/openpype/modules/kitsu/kitsu_module.py index 59ad2efd29..bd8ade62d8 100644 --- a/openpype/modules/kitsu/kitsu_module.py +++ b/openpype/modules/kitsu/kitsu_module.py @@ -149,12 +149,10 @@ def sync_service(login, password, project): Args: login (str): Kitsu user login password (str): Kitsu user password - projects (str): specific kitsu projects + project (str): specific kitsu projects """ from .utils.update_op_with_zou import sync_all_projects from .utils.sync_service import start_listeners - projects = ' '.join(project) - - sync_all_projects(login, password, specific_projects=projects) + sync_all_projects(login, password, filter_projects=project) start_listeners(login, password) From ad4f5876ce18239b244d59332ed40b5469cc0433 Mon Sep 17 00:00:00 2001 From: Michael <71185790+Michaelredaa@users.noreply.github.com> Date: Wed, 3 May 2023 12:40:32 +0300 Subject: [PATCH 08/50] update condition to sync specific projects --- .../modules/kitsu/utils/update_op_with_zou.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 7983765e83..40e4191508 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -389,16 +389,18 @@ def sync_all_projects(login: str, password: str, ignore_projects: list = None, # listen only return - if filter_projects == ['*']: + if '*' in filter_projects: + # all projects project_to_sync = all_projects - all_kitsu_projects = {p['name']: p for p in all_projects} - for proj_name in filter_projects: - if proj_name in all_kitsu_projects: - project_to_sync.append(all_kitsu_projects[proj_name]) - else: - log.info(f'`{proj_name}` project does not exist in Kitsu.' - f' Please make sure the project is spelled correctly.') + else: + all_kitsu_projects = {p['name']: p for p in all_projects} + for proj_name in filter_projects: + if proj_name in all_kitsu_projects: + project_to_sync.append(all_kitsu_projects[proj_name]) + else: + log.info(f'`{proj_name}` project does not exist in Kitsu.' + f' Please make sure the project is spelled correctly.') for project in project_to_sync: if ignore_projects and project["name"] in ignore_projects: From d6e7cd638d403f0d3f8ad53e7fef444122b1dc39 Mon Sep 17 00:00:00 2001 From: Michael <71185790+Michaelredaa@users.noreply.github.com> Date: Wed, 3 May 2023 13:02:08 +0300 Subject: [PATCH 09/50] update kitsu docs --- website/docs/module_kitsu.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/website/docs/module_kitsu.md b/website/docs/module_kitsu.md index d79c78fecf..4b827b3802 100644 --- a/website/docs/module_kitsu.md +++ b/website/docs/module_kitsu.md @@ -16,10 +16,18 @@ If you want to connect Kitsu to OpenPype you have to set the `Server` url in Kit This setting is available for all the users of the OpenPype instance. ## Synchronize -Updating OP with Kitsu data is executed running the `sync-service`, which requires to provide your Kitsu credentials with `-l, --login` and `-p, --password` or by setting the environment variables `KITSU_LOGIN` and `KITSU_PWD`. This process will request data from Kitsu and create/delete/update OP assets. +Updating OP with Kitsu data is executed running the `sync-service`, which requires to provide your Kitsu credentials with `-l, --login` and `-p, --password` or by setting the environment variables `KITSU_LOGIN` and `KITSU_PWD`. This process will request data from Kitsu projects with `-proj, --project` and create/delete/update OP assets. Once this sync is done, the thread will automatically start a loop to listen to Kitsu events. +The args for `-proj, --project` accept multiple project name, `-proj *` to sync all active projects, and the default value to start a loop to listen to Kitsu events only without any sync. ```bash +// sync specific projects then run listen +openpype_console module kitsu sync-service -l me@domain.ext -p my_password -proj project_name01 -proj project_name02 + +// sync all projects then run listen +openpype_console module kitsu sync-service -l me@domain.ext -p my_password -proj * + +// start listen only openpype_console module kitsu sync-service -l me@domain.ext -p my_password ``` From 925ea3fe9312a3e78df1604e370a019cb2adbe5c Mon Sep 17 00:00:00 2001 From: Michael <71185790+Michaelredaa@users.noreply.github.com> Date: Wed, 3 May 2023 20:14:33 +0300 Subject: [PATCH 10/50] Update module_kitsu docs --- website/docs/module_kitsu.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/website/docs/module_kitsu.md b/website/docs/module_kitsu.md index 4b827b3802..970bfb275e 100644 --- a/website/docs/module_kitsu.md +++ b/website/docs/module_kitsu.md @@ -16,19 +16,22 @@ If you want to connect Kitsu to OpenPype you have to set the `Server` url in Kit This setting is available for all the users of the OpenPype instance. ## Synchronize -Updating OP with Kitsu data is executed running the `sync-service`, which requires to provide your Kitsu credentials with `-l, --login` and `-p, --password` or by setting the environment variables `KITSU_LOGIN` and `KITSU_PWD`. This process will request data from Kitsu projects with `-proj, --project` and create/delete/update OP assets. +Updating OP with Kitsu data is executed running the `sync-service`, which requires to provide your Kitsu credentials with `-l, --login` and `-p, --password` or by setting the environment variables `KITSU_LOGIN` and `KITSU_PWD`. This process will request data from Kitsu and create/delete/update OP assets. Once this sync is done, the thread will automatically start a loop to listen to Kitsu events. -The args for `-proj, --project` accept multiple project name, `-proj *` to sync all active projects, and the default value to start a loop to listen to Kitsu events only without any sync. +- `-prj, --project` This flag accepts multiple project name to sync specific projects, and the default to sync all projects. +- `-lo, --listen-only` This flag to run listen to Kitsu events only without any sync. + +Note: You must use one argument of `-pro` or `-lo`, because the listen only flag override syncing flag. ```bash -// sync specific projects then run listen -openpype_console module kitsu sync-service -l me@domain.ext -p my_password -proj project_name01 -proj project_name02 - // sync all projects then run listen -openpype_console module kitsu sync-service -l me@domain.ext -p my_password -proj * +openpype_console module kitsu sync-service -l me@domain.ext -p my_password + +// sync specific projects then run listen +openpype_console module kitsu sync-service -l me@domain.ext -p my_password -prj project_name01 -prj project_name02 // start listen only -openpype_console module kitsu sync-service -l me@domain.ext -p my_password +openpype_console module kitsu sync-service -l me@domain.ext -p my_password -lo ``` ### Events listening From c5dd4d21eed2b7d9b67ecdb980a291742c050e7c Mon Sep 17 00:00:00 2001 From: Michael <71185790+Michaelredaa@users.noreply.github.com> Date: Wed, 3 May 2023 20:16:23 +0300 Subject: [PATCH 11/50] Add listen-only flag to sync --- openpype/modules/kitsu/kitsu_module.py | 27 +++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/openpype/modules/kitsu/kitsu_module.py b/openpype/modules/kitsu/kitsu_module.py index bd8ade62d8..319b5de16b 100644 --- a/openpype/modules/kitsu/kitsu_module.py +++ b/openpype/modules/kitsu/kitsu_module.py @@ -124,13 +124,6 @@ def push_to_zou(login, password): @cli_main.command() -@click.option( - "-prj", - "--project", - multiple=True, - default=[""], - help="Sync specific kitsu projects" -) @click.option( "-l", "--login", @@ -143,16 +136,32 @@ def push_to_zou(login, password): envvar="KITSU_PWD", help="Password for kitsu username" ) -def sync_service(login, password, project): +@click.option( + "-prj", + "--project", + multiple=True, + default=[], + help="Sync specific kitsu projects" +) +@click.option( + "-lo", + "--listen_only/--listen-only", + default=False, + help="Listen to events only without any syncing" +) +def sync_service(login, password, project, listen_only): """Synchronize openpype database from Zou sever database. Args: login (str): Kitsu user login password (str): Kitsu user password project (str): specific kitsu projects + listen_only (bool): run listen only without any syncing """ from .utils.update_op_with_zou import sync_all_projects from .utils.sync_service import start_listeners - sync_all_projects(login, password, filter_projects=project) + if not listen_only: + sync_all_projects(login, password, filter_projects=project) + start_listeners(login, password) From 44e533ff3306aa218a25960a1a41062e3fa82b5a Mon Sep 17 00:00:00 2001 From: Michael <71185790+Michaelredaa@users.noreply.github.com> Date: Wed, 3 May 2023 20:20:43 +0300 Subject: [PATCH 12/50] Update sync_all_projects function with filtered projects --- .../modules/kitsu/utils/update_op_with_zou.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 40e4191508..bfb4bd58fa 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -367,7 +367,7 @@ def sync_all_projects(login: str, password: str, ignore_projects: list = None, login (str): Kitsu user login password (str): Kitsu user password ignore_projects (list): List of unsynced project names - filter_projects (list): List of filter project names to sync with + filter_projects (tuple): Tuple of filter project names to sync with Raises: gazu.exception.AuthFailedException: Wrong user login and/or password """ @@ -385,15 +385,7 @@ def sync_all_projects(login: str, password: str, ignore_projects: list = None, project_to_sync = [] - if not filter_projects: - # listen only - return - - if '*' in filter_projects: - # all projects - project_to_sync = all_projects - - else: + if filter_projects: all_kitsu_projects = {p['name']: p for p in all_projects} for proj_name in filter_projects: if proj_name in all_kitsu_projects: @@ -401,6 +393,9 @@ def sync_all_projects(login: str, password: str, ignore_projects: list = None, else: log.info(f'`{proj_name}` project does not exist in Kitsu.' f' Please make sure the project is spelled correctly.') + else: + # all project + project_to_sync = all_projects for project in project_to_sync: if ignore_projects and project["name"] in ignore_projects: From 8242e61ad893843f4539e6a1517e50aa4e785de2 Mon Sep 17 00:00:00 2001 From: Michael reda Date: Fri, 5 May 2023 13:54:24 +0200 Subject: [PATCH 13/50] update linting --- openpype/modules/kitsu/kitsu_module.py | 18 ++----- .../modules/kitsu/utils/update_op_with_zou.py | 54 ++++++++++--------- 2 files changed, 33 insertions(+), 39 deletions(-) diff --git a/openpype/modules/kitsu/kitsu_module.py b/openpype/modules/kitsu/kitsu_module.py index 319b5de16b..dec19989ea 100644 --- a/openpype/modules/kitsu/kitsu_module.py +++ b/openpype/modules/kitsu/kitsu_module.py @@ -94,7 +94,7 @@ class KitsuModule(OpenPypeModule, IPluginPaths, ITrayAction): return { "publish": [os.path.join(current_dir, "plugins", "publish")], - "actions": [os.path.join(current_dir, "actions")] + "actions": [os.path.join(current_dir, "actions")], } def cli(self, click_group): @@ -124,30 +124,22 @@ def push_to_zou(login, password): @cli_main.command() +@click.option("-l", "--login", envvar="KITSU_LOGIN", help="Kitsu login") @click.option( - "-l", - "--login", - envvar="KITSU_LOGIN", - help="Kitsu login" -) -@click.option( - "-p", - "--password", - envvar="KITSU_PWD", - help="Password for kitsu username" + "-p", "--password", envvar="KITSU_PWD", help="Password for kitsu username" ) @click.option( "-prj", "--project", multiple=True, default=[], - help="Sync specific kitsu projects" + help="Sync specific kitsu projects", ) @click.option( "-lo", "--listen_only/--listen-only", default=False, - help="Listen to events only without any syncing" + help="Listen to events only without any syncing", ) def sync_service(login, password, project, listen_only): """Synchronize openpype database from Zou sever database. diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index bfb4bd58fa..4ad08bb739 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -94,9 +94,7 @@ def update_op_assets( if not item_doc: # Create asset op_asset = create_op_asset(item) insert_result = dbcon.insert_one(op_asset) - item_doc = get_asset_by_id( - project_name, insert_result.inserted_id - ) + item_doc = get_asset_by_id(project_name, insert_result.inserted_id) # Update asset item_data = deepcopy(item_doc["data"]) @@ -210,10 +208,10 @@ def update_op_assets( item.get("entity_type_id") if item_type == "Asset" else None - # Else, fallback on usual hierarchy - or item.get("parent_id") - or item.get("episode_id") - or item.get("source_id") + # Else, fallback on usual hierarchy + or item.get("parent_id") + or item.get("episode_id") + or item.get("source_id") ) # Substitute item type for general classification (assets or shots) @@ -329,7 +327,7 @@ def write_project_to_op(project: dict, dbcon: AvalonMongoDB) -> UpdateOne: "code": project_code, "fps": float(project["fps"]), "zou_id": project["id"], - "active": project['project_status_name'] != "Closed", + "active": project["project_status_name"] != "Closed", } ) @@ -350,7 +348,7 @@ def write_project_to_op(project: dict, dbcon: AvalonMongoDB) -> UpdateOne: "config.tasks": { t["name"]: {"short_name": t.get("short_name", t["name"])} for t in gazu.task.all_task_types_for_project(project) - or gazu.task.all_task_types() + or gazu.task.all_task_types() }, "data": project_data, } @@ -358,8 +356,11 @@ def write_project_to_op(project: dict, dbcon: AvalonMongoDB) -> UpdateOne: ) -def sync_all_projects(login: str, password: str, ignore_projects: list = None, - filter_projects: list = None +def sync_all_projects( + login: str, + password: str, + ignore_projects: list = None, + filter_projects: list = None, ): """Update all OP projects in DB with Zou data. @@ -386,13 +387,15 @@ def sync_all_projects(login: str, password: str, ignore_projects: list = None, project_to_sync = [] if filter_projects: - all_kitsu_projects = {p['name']: p for p in all_projects} + all_kitsu_projects = {p["name"]: p for p in all_projects} for proj_name in filter_projects: if proj_name in all_kitsu_projects: project_to_sync.append(all_kitsu_projects[proj_name]) else: - log.info(f'`{proj_name}` project does not exist in Kitsu.' - f' Please make sure the project is spelled correctly.') + log.info( + f"`{proj_name}` project does not exist in Kitsu." + f" Please make sure the project is spelled correctly." + ) else: # all project project_to_sync = all_projects @@ -424,14 +427,13 @@ def sync_project_from_kitsu(dbcon: AvalonMongoDB, project: dict): # Get all statuses for projects from Kitsu all_status = gazu.project.all_project_status() for status in all_status: - if project['project_status_id'] == status['id']: - project['project_status_name'] = status['name'] + if project["project_status_id"] == status["id"]: + project["project_status_name"] = status["name"] break # Do not sync closed kitsu project that is not found in openpype - if ( - project['project_status_name'] == "Closed" - and not get_project(project['name']) + if project["project_status_name"] == "Closed" and not get_project( + project["name"] ): return @@ -460,7 +462,7 @@ def sync_project_from_kitsu(dbcon: AvalonMongoDB, project: dict): log.info("Project created: {}".format(project_name)) bulk_writes.append(write_project_to_op(project, dbcon)) - if project['project_status_name'] == "Closed": + if project["project_status_name"] == "Closed": return # Try to find project document @@ -521,12 +523,12 @@ def sync_project_from_kitsu(dbcon: AvalonMongoDB, project: dict): [ UpdateOne({"_id": id}, update) for id, update in update_op_assets( - dbcon, - project, - project_dict, - all_entities, - zou_ids_and_asset_docs, - ) + dbcon, + project, + project_dict, + all_entities, + zou_ids_and_asset_docs, + ) ] ) From 07cf84e6481d013e79493e3d87c0189ab9ecf2cd Mon Sep 17 00:00:00 2001 From: Michael reda Date: Fri, 5 May 2023 14:14:12 +0200 Subject: [PATCH 14/50] add variable name to `@click.option` --- openpype/modules/kitsu/kitsu_module.py | 11 +++++++---- openpype/modules/kitsu/utils/update_op_with_zou.py | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/openpype/modules/kitsu/kitsu_module.py b/openpype/modules/kitsu/kitsu_module.py index dec19989ea..8d2d5ccd60 100644 --- a/openpype/modules/kitsu/kitsu_module.py +++ b/openpype/modules/kitsu/kitsu_module.py @@ -131,29 +131,32 @@ def push_to_zou(login, password): @click.option( "-prj", "--project", + "projects", multiple=True, default=[], help="Sync specific kitsu projects", ) @click.option( "-lo", - "--listen_only/--listen-only", + "--listen-only", + "listen_only", + is_flag=True, default=False, help="Listen to events only without any syncing", ) -def sync_service(login, password, project, listen_only): +def sync_service(login, password, projects, listen_only): """Synchronize openpype database from Zou sever database. Args: login (str): Kitsu user login password (str): Kitsu user password - project (str): specific kitsu projects + projects (tuple): specific kitsu projects listen_only (bool): run listen only without any syncing """ from .utils.update_op_with_zou import sync_all_projects from .utils.sync_service import start_listeners if not listen_only: - sync_all_projects(login, password, filter_projects=project) + sync_all_projects(login, password, filter_projects=projects) start_listeners(login, password) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 4ad08bb739..b495cd1bea 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -360,7 +360,7 @@ def sync_all_projects( login: str, password: str, ignore_projects: list = None, - filter_projects: list = None, + filter_projects: tuple = None, ): """Update all OP projects in DB with Zou data. From a5efddf96926cd2a3137dc9a5ef6e7fd272d3326 Mon Sep 17 00:00:00 2001 From: Michael <71185790+Michaelredaa@users.noreply.github.com> Date: Fri, 5 May 2023 15:47:39 +0300 Subject: [PATCH 15/50] Update docs to be more specific MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: FΓ©lix David --- website/docs/module_kitsu.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/module_kitsu.md b/website/docs/module_kitsu.md index 970bfb275e..9695542723 100644 --- a/website/docs/module_kitsu.md +++ b/website/docs/module_kitsu.md @@ -30,7 +30,7 @@ openpype_console module kitsu sync-service -l me@domain.ext -p my_password // sync specific projects then run listen openpype_console module kitsu sync-service -l me@domain.ext -p my_password -prj project_name01 -prj project_name02 -// start listen only +// start listen only for all projects openpype_console module kitsu sync-service -l me@domain.ext -p my_password -lo ``` From 2369d50224cd4768c182cee7004df4da7d381f27 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 15 May 2023 11:25:56 +0100 Subject: [PATCH 16/50] Skipping rendersetup for members. --- .../maya/plugins/publish/validate_instance_has_members.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/hosts/maya/plugins/publish/validate_instance_has_members.py b/openpype/hosts/maya/plugins/publish/validate_instance_has_members.py index 4870f27bff..fcafc2be79 100644 --- a/openpype/hosts/maya/plugins/publish/validate_instance_has_members.py +++ b/openpype/hosts/maya/plugins/publish/validate_instance_has_members.py @@ -14,6 +14,11 @@ class ValidateInstanceHasMembers(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance): + # Allow renderlayer and workfile to be empty + skip_families = ["workfile", "renderlayer", "rendersetup"] + if instance.data.get("family") in skip_families: + return + invalid = list() if not instance.data["setMembers"]: objectset_name = instance.data['name'] From b47d172944aa1e0039b3fb554093dbc7508b3033 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 15 May 2023 12:30:32 +0100 Subject: [PATCH 17/50] Move family check to process --- .../plugins/publish/validate_instance_has_members.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_instance_has_members.py b/openpype/hosts/maya/plugins/publish/validate_instance_has_members.py index fcafc2be79..63849cfd12 100644 --- a/openpype/hosts/maya/plugins/publish/validate_instance_has_members.py +++ b/openpype/hosts/maya/plugins/publish/validate_instance_has_members.py @@ -13,12 +13,6 @@ class ValidateInstanceHasMembers(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance): - - # Allow renderlayer and workfile to be empty - skip_families = ["workfile", "renderlayer", "rendersetup"] - if instance.data.get("family") in skip_families: - return - invalid = list() if not instance.data["setMembers"]: objectset_name = instance.data['name'] @@ -27,6 +21,10 @@ class ValidateInstanceHasMembers(pyblish.api.InstancePlugin): return invalid def process(self, instance): + # Allow renderlayer and workfile to be empty + skip_families = ["workfile", "renderlayer", "rendersetup"] + if instance.data.get("family") in skip_families: + return invalid = self.get_invalid(instance) if invalid: From 11d47eb9407be381a6a10731b683886f9f26f0da Mon Sep 17 00:00:00 2001 From: Petr Dvorak Date: Tue, 16 May 2023 10:28:24 +0200 Subject: [PATCH 18/50] Company name and URL changed --- inno_setup.iss | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/inno_setup.iss b/inno_setup.iss index 3adde52a8b..418bedbd4d 100644 --- a/inno_setup.iss +++ b/inno_setup.iss @@ -14,10 +14,10 @@ AppId={{B9E9DF6A-5BDA-42DD-9F35-C09D564C4D93} AppName={#MyAppName} AppVersion={#AppVer} AppVerName={#MyAppName} version {#AppVer} -AppPublisher=Orbi Tools s.r.o -AppPublisherURL=http://pype.club -AppSupportURL=http://pype.club -AppUpdatesURL=http://pype.club +AppPublisher=Ynput s.r.o +AppPublisherURL=https://ynput.io +AppSupportURL=https://ynput.io +AppUpdatesURL=https://ynput.io DefaultDirName={autopf}\{#MyAppName}\{#AppVer} UsePreviousAppDir=no DisableProgramGroupPage=yes From 6d9c0e30802db386476f5541732526abfa77265e Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 17 May 2023 15:46:34 +0100 Subject: [PATCH 19/50] Added setting for base file unit scale --- openpype/settings/defaults/project_settings/blender.json | 1 + .../schemas/projects_schema/schema_project_blender.json | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/openpype/settings/defaults/project_settings/blender.json b/openpype/settings/defaults/project_settings/blender.json index 20eec0c09d..0b3f38a40f 100644 --- a/openpype/settings/defaults/project_settings/blender.json +++ b/openpype/settings/defaults/project_settings/blender.json @@ -1,4 +1,5 @@ { + "base_file_unit_scale": 0.01, "imageio": { "ocio_config": { "enabled": false, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json index 725d9bfb08..00414b3210 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json @@ -5,6 +5,12 @@ "label": "Blender", "is_file": true, "children": [ + { + "key": "base_file_unit_scale", + "type": "number", + "label": "Base File Unit Scale", + "decimal": 2 + }, { "key": "imageio", "type": "dict", From 3350e0995f43094a93a2e2a54abba7b52a7916fe Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 17 May 2023 15:47:38 +0100 Subject: [PATCH 20/50] Set base unit scale when opening a file or creating a new one --- openpype/hosts/blender/api/pipeline.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/openpype/hosts/blender/api/pipeline.py b/openpype/hosts/blender/api/pipeline.py index c2aee1e653..02b1560e56 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -26,6 +26,8 @@ from openpype.lib import ( emit_event ) import openpype.hosts.blender +from openpype.settings import get_project_settings + HOST_DIR = os.path.dirname(os.path.abspath(openpype.hosts.blender.__file__)) PLUGINS_DIR = os.path.join(HOST_DIR, "plugins") @@ -122,12 +124,23 @@ def set_start_end_frames(): scene.render.resolution_y = resolution_y +def set_base_file_unit_scale(): + project = os.environ.get("AVALON_PROJECT") + settings = get_project_settings(project) + + unit_scale = settings.get("blender").get("base_file_unit_scale") + + bpy.context.scene.unit_settings.scale_length = unit_scale + + def on_new(): set_start_end_frames() + set_base_file_unit_scale() def on_open(): set_start_end_frames() + set_base_file_unit_scale() @bpy.app.handlers.persistent From d95299a31b1d8aae602ac06993f57ce14dd397ee Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 18 May 2023 10:37:38 +0100 Subject: [PATCH 21/50] Added settings to make optional the setting the unit scale --- openpype/hosts/blender/api/pipeline.py | 27 ++++++++++++++---- .../defaults/project_settings/blender.json | 6 +++- .../schema_project_blender.json | 28 ++++++++++++++++--- 3 files changed, 50 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/blender/api/pipeline.py b/openpype/hosts/blender/api/pipeline.py index 02b1560e56..3618c1f4c8 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -124,19 +124,34 @@ def set_start_end_frames(): scene.render.resolution_y = resolution_y -def set_base_file_unit_scale(): +def on_new(): + set_start_end_frames() + project = os.environ.get("AVALON_PROJECT") settings = get_project_settings(project) - unit_scale = settings.get("blender").get("base_file_unit_scale") - - bpy.context.scene.unit_settings.scale_length = unit_scale + unit_scale_settings = settings.get("blender").get("unit_scale_settings") + unit_scale_enabled = unit_scale_settings.get("enabled") + if unit_scale_enabled: + unit_scale = unit_scale_settings.get("base_file_unit_scale") + bpy.context.scene.unit_settings.scale_length = unit_scale -def on_new(): +def on_open(): set_start_end_frames() - set_base_file_unit_scale() + project = os.environ.get("AVALON_PROJECT") + settings = get_project_settings(project) + + unit_scale_settings = settings.get("blender").get("unit_scale_settings") + unit_scale_enabled = unit_scale_settings.get("enabled") + apply_on_opening = unit_scale_settings.get("apply_on_opening") + if unit_scale_enabled and apply_on_opening: + unit_scale = unit_scale_settings.get("base_file_unit_scale") + prev_unit_scale = bpy.context.scene.unit_settings.scale_length + + if unit_scale != prev_unit_scale: + bpy.context.scene.unit_settings.scale_length = unit_scale def on_open(): set_start_end_frames() diff --git a/openpype/settings/defaults/project_settings/blender.json b/openpype/settings/defaults/project_settings/blender.json index 0b3f38a40f..41aebfa537 100644 --- a/openpype/settings/defaults/project_settings/blender.json +++ b/openpype/settings/defaults/project_settings/blender.json @@ -1,5 +1,9 @@ { - "base_file_unit_scale": 0.01, + "unit_scale_settings": { + "enabled": true, + "apply_on_opening": false, + "base_file_unit_scale": 0.01 + }, "imageio": { "ocio_config": { "enabled": false, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json index 00414b3210..0d0952a70a 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json @@ -6,10 +6,30 @@ "is_file": true, "children": [ { - "key": "base_file_unit_scale", - "type": "number", - "label": "Base File Unit Scale", - "decimal": 2 + "key": "unit_scale_settings", + "type": "dict", + "label": "Set Unit Scale", + "collapsible": true, + "is_group": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "key": "apply_on_opening", + "type": "boolean", + "label": "Apply on Opening Existing Files" + }, + { + "key": "base_file_unit_scale", + "type": "number", + "label": "Base File Unit Scale", + "decimal": 2 + } + ] }, { "key": "imageio", From 0d4ae4efa7668ad5b5dc2a5a991b3e5cfcd92bf3 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 18 May 2023 10:38:22 +0100 Subject: [PATCH 22/50] Added message if base scale has been changed when opening a file --- openpype/hosts/blender/api/pipeline.py | 31 +++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/blender/api/pipeline.py b/openpype/hosts/blender/api/pipeline.py index 3618c1f4c8..9cc557c01a 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -85,6 +85,31 @@ def uninstall(): ops.unregister() +def show_message(title, message): + from openpype.widgets.message_window import Window + from .ops import BlenderApplication + + BlenderApplication.get_app() + + Window( + parent=None, + title=title, + message=message, + level="warning") + + +def message_window(title, message): + from .ops import ( + MainThreadItem, + execute_in_main_thread, + _process_app_events + ) + + mti = MainThreadItem(show_message, title, message) + execute_in_main_thread(mti) + _process_app_events() + + def set_start_end_frames(): project_name = legacy_io.active_project() asset_name = legacy_io.Session["AVALON_ASSET"] @@ -153,9 +178,9 @@ def on_open(): if unit_scale != prev_unit_scale: bpy.context.scene.unit_settings.scale_length = unit_scale -def on_open(): - set_start_end_frames() - set_base_file_unit_scale() + message_window( + "Base file unit scale changed", + "Base file unit scale changed to match the project settings.") @bpy.app.handlers.persistent From 8b68371e0c399b6e8b80f5300a77223bfefc7007 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 18 May 2023 14:33:26 +0100 Subject: [PATCH 23/50] Increased the number of decimals for the unit scale --- .../schemas/projects_schema/schema_project_blender.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json index 0d0952a70a..5b40169872 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json @@ -27,7 +27,7 @@ "key": "base_file_unit_scale", "type": "number", "label": "Base File Unit Scale", - "decimal": 2 + "decimal": 10 } ] }, From ec7c172fb2ec82d43ae014381829275581cf0ed2 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 18 May 2023 15:26:12 +0100 Subject: [PATCH 24/50] Implement loading of abc camera --- .../blender/plugins/load/load_camera_abc.py | 209 ++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 openpype/hosts/blender/plugins/load/load_camera_abc.py diff --git a/openpype/hosts/blender/plugins/load/load_camera_abc.py b/openpype/hosts/blender/plugins/load/load_camera_abc.py new file mode 100644 index 0000000000..21b48f409f --- /dev/null +++ b/openpype/hosts/blender/plugins/load/load_camera_abc.py @@ -0,0 +1,209 @@ +"""Load an asset in Blender from an Alembic file.""" + +from pathlib import Path +from pprint import pformat +from typing import Dict, List, Optional + +import bpy + +from openpype.pipeline import ( + get_representation_path, + AVALON_CONTAINER_ID, +) +from openpype.hosts.blender.api import plugin, lib +from openpype.hosts.blender.api.pipeline import ( + AVALON_CONTAINERS, + AVALON_PROPERTY, +) + + +class AbcCameraLoader(plugin.AssetLoader): + """Load a camera from Alembic file. + + Stores the imported asset in an empty named after the asset. + """ + + families = ["camera"] + representations = ["abc"] + + label = "Load Camera (ABC)" + icon = "code-fork" + color = "orange" + + def _remove(self, asset_group): + objects = list(asset_group.children) + + for obj in objects: + if obj.type == "CAMERA": + bpy.data.cameras.remove(obj.data) + elif obj.type == "EMPTY": + objects.extend(obj.children) + bpy.data.objects.remove(obj) + + def _process(self, libpath, asset_group, group_name): + plugin.deselect_all() + + bpy.ops.wm.alembic_import(filepath=libpath) + + objects = lib.get_selection() + + for obj in objects: + obj.parent = asset_group + + for obj in objects: + name = obj.name + obj.name = f"{group_name}:{name}" + if obj.type != "EMPTY": + name_data = obj.data.name + obj.data.name = f"{group_name}:{name_data}" + + if not obj.get(AVALON_PROPERTY): + obj[AVALON_PROPERTY] = dict() + + avalon_info = obj[AVALON_PROPERTY] + avalon_info.update({"container_name": group_name}) + + plugin.deselect_all() + + return objects + + def process_asset( + self, + context: dict, + name: str, + namespace: Optional[str] = None, + options: Optional[Dict] = None, + ) -> Optional[List]: + """ + Arguments: + name: Use pre-defined name + namespace: Use pre-defined namespace + context: Full parenthood of representation to load + options: Additional settings dictionary + """ + libpath = self.fname + asset = context["asset"]["name"] + subset = context["subset"]["name"] + + asset_name = plugin.asset_name(asset, subset) + unique_number = plugin.get_unique_number(asset, subset) + group_name = plugin.asset_name(asset, subset, unique_number) + namespace = namespace or f"{asset}_{unique_number}" + + avalon_container = bpy.data.collections.get(AVALON_CONTAINERS) + if not avalon_container: + avalon_container = bpy.data.collections.new(name=AVALON_CONTAINERS) + bpy.context.scene.collection.children.link(avalon_container) + + asset_group = bpy.data.objects.new(group_name, object_data=None) + avalon_container.objects.link(asset_group) + + objects = self._process(libpath, asset_group, group_name) + + objects = [] + nodes = list(asset_group.children) + + for obj in nodes: + objects.append(obj) + nodes.extend(list(obj.children)) + + bpy.context.scene.collection.objects.link(asset_group) + + asset_group[AVALON_PROPERTY] = { + "schema": "openpype:container-2.0", + "id": AVALON_CONTAINER_ID, + "name": name, + "namespace": namespace or "", + "loader": str(self.__class__.__name__), + "representation": str(context["representation"]["_id"]), + "libpath": libpath, + "asset_name": asset_name, + "parent": str(context["representation"]["parent"]), + "family": context["representation"]["context"]["family"], + "objectName": group_name, + } + + self[:] = objects + return objects + + def exec_update(self, container: Dict, representation: Dict): + """Update the loaded asset. + + This will remove all objects of the current collection, load the new + ones and add them to the collection. + If the objects of the collection are used in another collection they + will not be removed, only unlinked. Normally this should not be the + case though. + + Warning: + No nested collections are supported at the moment! + """ + object_name = container["objectName"] + asset_group = bpy.data.objects.get(object_name) + libpath = Path(get_representation_path(representation)) + extension = libpath.suffix.lower() + + self.log.info( + "Container: %s\nRepresentation: %s", + pformat(container, indent=2), + pformat(representation, indent=2), + ) + + assert asset_group, ( + f"The asset is not loaded: {container['objectName']}") + assert libpath, ( + f"No existing library file found for {container['objectName']}") + assert libpath.is_file(), f"The file doesn't exist: {libpath}" + assert extension in plugin.VALID_EXTENSIONS, ( + f"Unsupported file: {libpath}") + + metadata = asset_group.get(AVALON_PROPERTY) + group_libpath = metadata["libpath"] + + normalized_group_libpath = str( + Path(bpy.path.abspath(group_libpath)).resolve()) + normalized_libpath = str( + Path(bpy.path.abspath(str(libpath))).resolve()) + self.log.debug( + "normalized_group_libpath:\n %s\nnormalized_libpath:\n %s", + normalized_group_libpath, + normalized_libpath, + ) + if normalized_group_libpath == normalized_libpath: + self.log.info("Library already loaded, not updating...") + return + + mat = asset_group.matrix_basis.copy() + + self._remove(asset_group) + self._process(str(libpath), asset_group, object_name) + + asset_group.matrix_basis = mat + + metadata["libpath"] = str(libpath) + metadata["representation"] = str(representation["_id"]) + + def exec_remove(self, container: Dict) -> bool: + """Remove an existing container from a Blender scene. + + Arguments: + container (openpype:container-1.0): Container to remove, + from `host.ls()`. + + Returns: + bool: Whether the container was deleted. + + Warning: + No nested collections are supported at the moment! + """ + object_name = container["objectName"] + asset_group = bpy.data.objects.get(object_name) + + if not asset_group: + return False + + self._remove(asset_group) + + bpy.data.objects.remove(asset_group) + + return True From 14360f02176b3eb5fbb5a1bc9ba7ab54e33e6bd0 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Mon, 22 May 2023 18:11:13 +0100 Subject: [PATCH 25/50] Changed name and path of the camera levels to fix problem with sequencer --- openpype/hosts/unreal/api/__init__.py | 4 + openpype/hosts/unreal/api/pipeline.py | 130 ++++++++++++ .../hosts/unreal/plugins/load/load_camera.py | 192 +++++++----------- .../hosts/unreal/plugins/load/load_layout.py | 152 ++------------ 4 files changed, 228 insertions(+), 250 deletions(-) diff --git a/openpype/hosts/unreal/api/__init__.py b/openpype/hosts/unreal/api/__init__.py index de0fce13d5..ac6a91eae9 100644 --- a/openpype/hosts/unreal/api/__init__.py +++ b/openpype/hosts/unreal/api/__init__.py @@ -22,6 +22,8 @@ from .pipeline import ( show_tools_popup, instantiate, UnrealHost, + set_sequence_hierarchy, + generate_sequence, maintained_selection ) @@ -41,5 +43,7 @@ __all__ = [ "show_tools_popup", "instantiate", "UnrealHost", + "set_sequence_hierarchy", + "generate_sequence", "maintained_selection" ] diff --git a/openpype/hosts/unreal/api/pipeline.py b/openpype/hosts/unreal/api/pipeline.py index bb45fa8c01..5030e8ee86 100644 --- a/openpype/hosts/unreal/api/pipeline.py +++ b/openpype/hosts/unreal/api/pipeline.py @@ -9,12 +9,14 @@ import time import pyblish.api +from openpype.client import get_asset_by_name, get_assets from openpype.pipeline import ( register_loader_plugin_path, register_creator_plugin_path, deregister_loader_plugin_path, deregister_creator_plugin_path, AYON_CONTAINER_ID, + legacy_io, ) from openpype.tools.utils import host_tools import openpype.hosts.unreal @@ -512,6 +514,134 @@ def get_subsequences(sequence: unreal.LevelSequence): return [] +def set_sequence_hierarchy( + seq_i, seq_j, max_frame_i, min_frame_j, max_frame_j, map_paths +): + # Get existing sequencer tracks or create them if they don't exist + tracks = seq_i.get_master_tracks() + subscene_track = None + visibility_track = None + for t in tracks: + if t.get_class() == unreal.MovieSceneSubTrack.static_class(): + subscene_track = t + if (t.get_class() == + unreal.MovieSceneLevelVisibilityTrack.static_class()): + visibility_track = t + if not subscene_track: + subscene_track = seq_i.add_master_track(unreal.MovieSceneSubTrack) + if not visibility_track: + visibility_track = seq_i.add_master_track( + unreal.MovieSceneLevelVisibilityTrack) + + # Create the sub-scene section + subscenes = subscene_track.get_sections() + subscene = None + for s in subscenes: + if s.get_editor_property('sub_sequence') == seq_j: + subscene = s + break + if not subscene: + subscene = subscene_track.add_section() + subscene.set_row_index(len(subscene_track.get_sections())) + subscene.set_editor_property('sub_sequence', seq_j) + subscene.set_range( + min_frame_j, + max_frame_j + 1) + + # Create the visibility section + ar = unreal.AssetRegistryHelpers.get_asset_registry() + maps = [] + for m in map_paths: + # Unreal requires to load the level to get the map name + unreal.EditorLevelLibrary.save_all_dirty_levels() + unreal.EditorLevelLibrary.load_level(m) + maps.append(str(ar.get_asset_by_object_path(m).asset_name)) + + vis_section = visibility_track.add_section() + index = len(visibility_track.get_sections()) + + vis_section.set_range( + min_frame_j, + max_frame_j + 1) + vis_section.set_visibility(unreal.LevelVisibility.VISIBLE) + vis_section.set_row_index(index) + vis_section.set_level_names(maps) + + if min_frame_j > 1: + hid_section = visibility_track.add_section() + hid_section.set_range( + 1, + min_frame_j) + hid_section.set_visibility(unreal.LevelVisibility.HIDDEN) + hid_section.set_row_index(index) + hid_section.set_level_names(maps) + if max_frame_j < max_frame_i: + hid_section = visibility_track.add_section() + hid_section.set_range( + max_frame_j + 1, + max_frame_i + 1) + hid_section.set_visibility(unreal.LevelVisibility.HIDDEN) + hid_section.set_row_index(index) + hid_section.set_level_names(maps) + + +def generate_sequence(h, h_dir): + tools = unreal.AssetToolsHelpers().get_asset_tools() + + sequence = tools.create_asset( + asset_name=h, + package_path=h_dir, + asset_class=unreal.LevelSequence, + factory=unreal.LevelSequenceFactoryNew() + ) + + project_name = legacy_io.active_project() + asset_data = get_asset_by_name( + project_name, + h_dir.split('/')[-1], + fields=["_id", "data.fps"] + ) + + start_frames = [] + end_frames = [] + + elements = list(get_assets( + project_name, + parent_ids=[asset_data["_id"]], + fields=["_id", "data.clipIn", "data.clipOut"] + )) + for e in elements: + start_frames.append(e.get('data').get('clipIn')) + end_frames.append(e.get('data').get('clipOut')) + + elements.extend(get_assets( + project_name, + parent_ids=[e["_id"]], + fields=["_id", "data.clipIn", "data.clipOut"] + )) + + min_frame = min(start_frames) + max_frame = max(end_frames) + + sequence.set_display_rate( + unreal.FrameRate(asset_data.get('data').get("fps"), 1.0)) + sequence.set_playback_start(min_frame) + sequence.set_playback_end(max_frame) + + tracks = sequence.get_master_tracks() + track = None + for t in tracks: + if (t.get_class() == + unreal.MovieSceneCameraCutTrack.static_class()): + track = t + break + if not track: + track = sequence.add_master_track( + unreal.MovieSceneCameraCutTrack) + + return sequence, (min_frame, max_frame) + + @contextmanager def maintained_selection(): """Stub to be either implemented or replaced. diff --git a/openpype/hosts/unreal/plugins/load/load_camera.py b/openpype/hosts/unreal/plugins/load/load_camera.py index 1bd398349f..02634104e7 100644 --- a/openpype/hosts/unreal/plugins/load/load_camera.py +++ b/openpype/hosts/unreal/plugins/load/load_camera.py @@ -6,13 +6,18 @@ import unreal from unreal import EditorAssetLibrary from unreal import EditorLevelLibrary from unreal import EditorLevelUtils -from openpype.client import get_assets, get_asset_by_name +from openpype.client import get_asset_by_name from openpype.pipeline import ( AYON_CONTAINER_ID, legacy_io, ) from openpype.hosts.unreal.api import plugin -from openpype.hosts.unreal.api import pipeline as unreal_pipeline +from openpype.hosts.unreal.api.pipeline import ( + generate_sequence, + set_sequence_hierarchy, + create_container, + imprint, +) class CameraLoader(plugin.Loader): @@ -24,32 +29,6 @@ class CameraLoader(plugin.Loader): icon = "cube" color = "orange" - def _set_sequence_hierarchy( - self, seq_i, seq_j, min_frame_j, max_frame_j - ): - tracks = seq_i.get_master_tracks() - track = None - for t in tracks: - if t.get_class() == unreal.MovieSceneSubTrack.static_class(): - track = t - break - if not track: - track = seq_i.add_master_track(unreal.MovieSceneSubTrack) - - subscenes = track.get_sections() - subscene = None - for s in subscenes: - if s.get_editor_property('sub_sequence') == seq_j: - subscene = s - break - if not subscene: - subscene = track.add_section() - subscene.set_row_index(len(track.get_sections())) - subscene.set_editor_property('sub_sequence', seq_j) - subscene.set_range( - min_frame_j, - max_frame_j + 1) - def _import_camera( self, world, sequence, bindings, import_fbx_settings, import_filename ): @@ -156,9 +135,9 @@ class CameraLoader(plugin.Loader): if not EditorAssetLibrary.does_asset_exist(master_level): EditorLevelLibrary.new_level(f"{h_dir}/{h_asset}_map") - level = f"{asset_path_parent}/{asset}_map.{asset}_map" + level = f"{asset_dir}/{asset}_map_camera.{asset}_map_camera" if not EditorAssetLibrary.does_asset_exist(level): - EditorLevelLibrary.new_level(f"{asset_path_parent}/{asset}_map") + EditorLevelLibrary.new_level(f"{asset_dir}/{asset}_map_camera") EditorLevelLibrary.load_level(master_level) EditorLevelUtils.add_level_to_world( @@ -169,27 +148,13 @@ class CameraLoader(plugin.Loader): EditorLevelLibrary.save_all_dirty_levels() EditorLevelLibrary.load_level(level) - project_name = legacy_io.active_project() - # TODO refactor - # - Creating of hierarchy should be a function in unreal integration - # - it's used in multiple loaders but must not be loader's logic - # - hard to say what is purpose of the loop - # - variables does not match their meaning - # - why scene is stored to sequences? - # - asset documents vs. elements - # - cleanup variable names in whole function - # - e.g. 'asset', 'asset_name', 'asset_data', 'asset_doc' - # - really inefficient queries of asset documents - # - existing asset in scene is considered as "with correct values" - # - variable 'elements' is modified during it's loop # Get all the sequences in the hierarchy. It will create them, if # they don't exist. - sequences = [] frame_ranges = [] - i = 0 - for h in hierarchy_dir_list: + sequences = [] + for (h_dir, h) in zip(hierarchy_dir_list, hierarchy): root_content = EditorAssetLibrary.list_assets( - h, recursive=False, include_folder=False) + h_dir, recursive=False, include_folder=False) existing_sequences = [ EditorAssetLibrary.find_asset_data(asset) @@ -199,48 +164,10 @@ class CameraLoader(plugin.Loader): ] if not existing_sequences: - scene = tools.create_asset( - asset_name=hierarchy[i], - package_path=h, - asset_class=unreal.LevelSequence, - factory=unreal.LevelSequenceFactoryNew() - ) + sequence, frame_range = generate_sequence(h, h_dir) - asset_data = get_asset_by_name( - project_name, - h.split('/')[-1], - fields=["_id", "data.fps"] - ) - - start_frames = [] - end_frames = [] - - elements = list(get_assets( - project_name, - parent_ids=[asset_data["_id"]], - fields=["_id", "data.clipIn", "data.clipOut"] - )) - - for e in elements: - start_frames.append(e.get('data').get('clipIn')) - end_frames.append(e.get('data').get('clipOut')) - - elements.extend(get_assets( - project_name, - parent_ids=[e["_id"]], - fields=["_id", "data.clipIn", "data.clipOut"] - )) - - min_frame = min(start_frames) - max_frame = max(end_frames) - - scene.set_display_rate( - unreal.FrameRate(asset_data.get('data').get("fps"), 1.0)) - scene.set_playback_start(min_frame) - scene.set_playback_end(max_frame) - - sequences.append(scene) - frame_ranges.append((min_frame, max_frame)) + sequences.append(sequence) + frame_ranges.append(frame_range) else: for e in existing_sequences: sequences.append(e.get_asset()) @@ -248,8 +175,6 @@ class CameraLoader(plugin.Loader): e.get_asset().get_playback_start(), e.get_asset().get_playback_end())) - i += 1 - EditorAssetLibrary.make_directory(asset_dir) cam_seq = tools.create_asset( @@ -260,19 +185,24 @@ class CameraLoader(plugin.Loader): ) # Add sequences data to hierarchy - for i in range(0, len(sequences) - 1): - self._set_sequence_hierarchy( + for i in range(len(sequences) - 1): + set_sequence_hierarchy( sequences[i], sequences[i + 1], - frame_ranges[i + 1][0], frame_ranges[i + 1][1]) + frame_ranges[i][1], + frame_ranges[i + 1][0], frame_ranges[i + 1][1], + [level]) + project_name = legacy_io.active_project() data = get_asset_by_name(project_name, asset)["data"] cam_seq.set_display_rate( unreal.FrameRate(data.get("fps"), 1.0)) cam_seq.set_playback_start(data.get('clipIn')) cam_seq.set_playback_end(data.get('clipOut') + 1) - self._set_sequence_hierarchy( + set_sequence_hierarchy( sequences[-1], cam_seq, - data.get('clipIn'), data.get('clipOut')) + frame_ranges[-1][1], + data.get('clipIn'), data.get('clipOut'), + [level]) settings = unreal.MovieSceneUserImportFBXSettings() settings.set_editor_property('reduce_keys', False) @@ -307,7 +237,7 @@ class CameraLoader(plugin.Loader): key.set_time(unreal.FrameNumber(value=new_time)) # Create Asset Container - unreal_pipeline.create_container( + create_container( container=container_name, path=asset_dir) data = { @@ -322,7 +252,7 @@ class CameraLoader(plugin.Loader): "parent": context["representation"]["parent"], "family": context["representation"]["context"]["family"] } - unreal_pipeline.imprint( + imprint( "{}/{}".format(asset_dir, container_name), data) EditorLevelLibrary.save_all_dirty_levels() @@ -360,7 +290,7 @@ class CameraLoader(plugin.Loader): sequences = ar.get_assets(filter) filter = unreal.ARFilter( class_names=["World"], - package_paths=[str(Path(asset_dir).parent.as_posix())], + package_paths=[asset_dir], recursive_paths=True) maps = ar.get_assets(filter) @@ -470,7 +400,7 @@ class CameraLoader(plugin.Loader): "representation": str(representation["_id"]), "parent": str(representation["parent"]) } - unreal_pipeline.imprint( + imprint( "{}/{}".format(asset_dir, container.get('container_name')), data) EditorLevelLibrary.save_current_level() @@ -484,15 +414,15 @@ class CameraLoader(plugin.Loader): EditorLevelLibrary.load_level(master_level) def remove(self, container): - path = Path(container.get("namespace")) - parent_path = str(path.parent.as_posix()) + asset_dir = container.get('namespace') + path = Path(asset_dir) ar = unreal.AssetRegistryHelpers.get_asset_registry() - filter = unreal.ARFilter( + _filter = unreal.ARFilter( class_names=["LevelSequence"], - package_paths=[f"{str(path.as_posix())}"], + package_paths=[asset_dir], recursive_paths=False) - sequences = ar.get_assets(filter) + sequences = ar.get_assets(_filter) if not sequences: raise Exception("Could not find sequence.") @@ -500,11 +430,11 @@ class CameraLoader(plugin.Loader): world = ar.get_asset_by_object_path( EditorLevelLibrary.get_editor_world().get_path_name()) - filter = unreal.ARFilter( + _filter = unreal.ARFilter( class_names=["World"], - package_paths=[f"{parent_path}"], + package_paths=[asset_dir], recursive_paths=True) - maps = ar.get_assets(filter) + maps = ar.get_assets(_filter) # There should be only one map in the list if not maps: @@ -534,12 +464,18 @@ class CameraLoader(plugin.Loader): root = "/Game/Ayon" namespace = container.get('namespace').replace(f"{root}/", "") ms_asset = namespace.split('/')[0] - filter = unreal.ARFilter( + _filter = unreal.ARFilter( class_names=["LevelSequence"], package_paths=[f"{root}/{ms_asset}"], recursive_paths=False) - sequences = ar.get_assets(filter) + sequences = ar.get_assets(_filter) master_sequence = sequences[0].get_asset() + _filter = unreal.ARFilter( + class_names=["World"], + package_paths=[f"{root}/{ms_asset}"], + recursive_paths=False) + levels = ar.get_assets(_filter) + master_level = levels[0].get_full_name() sequences = [master_sequence] @@ -547,10 +483,13 @@ class CameraLoader(plugin.Loader): for s in sequences: tracks = s.get_master_tracks() subscene_track = None + visibility_track = None for t in tracks: if t.get_class() == unreal.MovieSceneSubTrack.static_class(): subscene_track = t - break + if (t.get_class() == + unreal.MovieSceneLevelVisibilityTrack.static_class()): + visibility_track = t if subscene_track: sections = subscene_track.get_sections() for ss in sections: @@ -565,18 +504,45 @@ class CameraLoader(plugin.Loader): ss.set_row_index(i) i += 1 + if visibility_track: + sections = visibility_track.get_sections() + for ss in sections: + if (unreal.Name(f"{container.get('asset')}_map_camera") + in ss.get_level_names()): + visibility_track.remove_section(ss) + # Update visibility sections indexes. + i = -1 + prev_name = [] + for ss in sections: + if prev_name != ss.get_level_names(): + i += 1 + ss.set_row_index(i) + prev_name = ss.get_level_names() if parent: break assert parent, "Could not find the parent sequence" - EditorAssetLibrary.delete_directory(str(path.as_posix())) + # Create a temporary level to delete the layout level. + EditorLevelLibrary.save_all_dirty_levels() + EditorAssetLibrary.make_directory(f"{root}/tmp") + tmp_level = f"{root}/tmp/temp_map" + if not EditorAssetLibrary.does_asset_exist(f"{tmp_level}.temp_map"): + EditorLevelLibrary.new_level(tmp_level) + else: + EditorLevelLibrary.load_level(tmp_level) + + # Delete the layout directory. + EditorAssetLibrary.delete_directory(asset_dir) + + EditorLevelLibrary.load_level(master_level) + EditorAssetLibrary.delete_directory(f"{root}/tmp") # Check if there isn't any more assets in the parent folder, and # delete it if not. asset_content = EditorAssetLibrary.list_assets( - parent_path, recursive=False, include_folder=True + path.parent.as_posix(), recursive=False, include_folder=True ) if len(asset_content) == 0: - EditorAssetLibrary.delete_directory(parent_path) + EditorAssetLibrary.delete_directory(path.parent.as_posix()) diff --git a/openpype/hosts/unreal/plugins/load/load_layout.py b/openpype/hosts/unreal/plugins/load/load_layout.py index e5f32c3412..1dfe6f1f5c 100644 --- a/openpype/hosts/unreal/plugins/load/load_layout.py +++ b/openpype/hosts/unreal/plugins/load/load_layout.py @@ -13,7 +13,7 @@ from unreal import FBXImportType from unreal import MovieSceneLevelVisibilityTrack from unreal import MovieSceneSubTrack -from openpype.client import get_asset_by_name, get_assets, get_representations +from openpype.client import get_asset_by_name, get_representations from openpype.pipeline import ( discover_loader_plugins, loaders_from_representation, @@ -25,7 +25,13 @@ from openpype.pipeline import ( from openpype.pipeline.context_tools import get_current_project_asset from openpype.settings import get_current_project_settings from openpype.hosts.unreal.api import plugin -from openpype.hosts.unreal.api import pipeline as unreal_pipeline +from openpype.hosts.unreal.api.pipeline import ( + generate_sequence, + set_sequence_hierarchy, + create_container, + imprint, + ls, +) class LayoutLoader(plugin.Loader): @@ -91,77 +97,6 @@ class LayoutLoader(plugin.Loader): return None - @staticmethod - def _set_sequence_hierarchy( - seq_i, seq_j, max_frame_i, min_frame_j, max_frame_j, map_paths - ): - # Get existing sequencer tracks or create them if they don't exist - tracks = seq_i.get_master_tracks() - subscene_track = None - visibility_track = None - for t in tracks: - if t.get_class() == unreal.MovieSceneSubTrack.static_class(): - subscene_track = t - if (t.get_class() == - unreal.MovieSceneLevelVisibilityTrack.static_class()): - visibility_track = t - if not subscene_track: - subscene_track = seq_i.add_master_track(unreal.MovieSceneSubTrack) - if not visibility_track: - visibility_track = seq_i.add_master_track( - unreal.MovieSceneLevelVisibilityTrack) - - # Create the sub-scene section - subscenes = subscene_track.get_sections() - subscene = None - for s in subscenes: - if s.get_editor_property('sub_sequence') == seq_j: - subscene = s - break - if not subscene: - subscene = subscene_track.add_section() - subscene.set_row_index(len(subscene_track.get_sections())) - subscene.set_editor_property('sub_sequence', seq_j) - subscene.set_range( - min_frame_j, - max_frame_j + 1) - - # Create the visibility section - ar = unreal.AssetRegistryHelpers.get_asset_registry() - maps = [] - for m in map_paths: - # Unreal requires to load the level to get the map name - EditorLevelLibrary.save_all_dirty_levels() - EditorLevelLibrary.load_level(m) - maps.append(str(ar.get_asset_by_object_path(m).asset_name)) - - vis_section = visibility_track.add_section() - index = len(visibility_track.get_sections()) - - vis_section.set_range( - min_frame_j, - max_frame_j + 1) - vis_section.set_visibility(unreal.LevelVisibility.VISIBLE) - vis_section.set_row_index(index) - vis_section.set_level_names(maps) - - if min_frame_j > 1: - hid_section = visibility_track.add_section() - hid_section.set_range( - 1, - min_frame_j) - hid_section.set_visibility(unreal.LevelVisibility.HIDDEN) - hid_section.set_row_index(index) - hid_section.set_level_names(maps) - if max_frame_j < max_frame_i: - hid_section = visibility_track.add_section() - hid_section.set_range( - max_frame_j + 1, - max_frame_i + 1) - hid_section.set_visibility(unreal.LevelVisibility.HIDDEN) - hid_section.set_row_index(index) - hid_section.set_level_names(maps) - def _transform_from_basis(self, transform, basis): """Transform a transform from a basis to a new basis.""" # Get the basis matrix @@ -352,63 +287,6 @@ class LayoutLoader(plugin.Loader): sec_params = section.get_editor_property('params') sec_params.set_editor_property('animation', animation) - @staticmethod - def _generate_sequence(h, h_dir): - tools = unreal.AssetToolsHelpers().get_asset_tools() - - sequence = tools.create_asset( - asset_name=h, - package_path=h_dir, - asset_class=unreal.LevelSequence, - factory=unreal.LevelSequenceFactoryNew() - ) - - project_name = legacy_io.active_project() - asset_data = get_asset_by_name( - project_name, - h_dir.split('/')[-1], - fields=["_id", "data.fps"] - ) - - start_frames = [] - end_frames = [] - - elements = list(get_assets( - project_name, - parent_ids=[asset_data["_id"]], - fields=["_id", "data.clipIn", "data.clipOut"] - )) - for e in elements: - start_frames.append(e.get('data').get('clipIn')) - end_frames.append(e.get('data').get('clipOut')) - - elements.extend(get_assets( - project_name, - parent_ids=[e["_id"]], - fields=["_id", "data.clipIn", "data.clipOut"] - )) - - min_frame = min(start_frames) - max_frame = max(end_frames) - - sequence.set_display_rate( - unreal.FrameRate(asset_data.get('data').get("fps"), 1.0)) - sequence.set_playback_start(min_frame) - sequence.set_playback_end(max_frame) - - tracks = sequence.get_master_tracks() - track = None - for t in tracks: - if (t.get_class() == - unreal.MovieSceneCameraCutTrack.static_class()): - track = t - break - if not track: - track = sequence.add_master_track( - unreal.MovieSceneCameraCutTrack) - - return sequence, (min_frame, max_frame) - def _get_repre_docs_by_version_id(self, data): version_ids = { element.get("version") @@ -696,7 +574,7 @@ class LayoutLoader(plugin.Loader): ] if not existing_sequences: - sequence, frame_range = self._generate_sequence(h, h_dir) + sequence, frame_range = generate_sequence(h, h_dir) sequences.append(sequence) frame_ranges.append(frame_range) @@ -716,7 +594,7 @@ class LayoutLoader(plugin.Loader): # sequences and frame_ranges have the same length for i in range(0, len(sequences) - 1): - self._set_sequence_hierarchy( + set_sequence_hierarchy( sequences[i], sequences[i + 1], frame_ranges[i][1], frame_ranges[i + 1][0], frame_ranges[i + 1][1], @@ -729,7 +607,7 @@ class LayoutLoader(plugin.Loader): shot.set_playback_start(0) shot.set_playback_end(data.get('clipOut') - data.get('clipIn') + 1) if sequences: - self._set_sequence_hierarchy( + set_sequence_hierarchy( sequences[-1], shot, frame_ranges[-1][1], data.get('clipIn'), data.get('clipOut'), @@ -745,7 +623,7 @@ class LayoutLoader(plugin.Loader): EditorLevelLibrary.save_current_level() # Create Asset Container - unreal_pipeline.create_container( + create_container( container=container_name, path=asset_dir) data = { @@ -761,7 +639,7 @@ class LayoutLoader(plugin.Loader): "family": context["representation"]["context"]["family"], "loaded_assets": loaded_assets } - unreal_pipeline.imprint( + imprint( "{}/{}".format(asset_dir, container_name), data) asset_content = EditorAssetLibrary.list_assets( @@ -843,7 +721,7 @@ class LayoutLoader(plugin.Loader): "parent": str(representation["parent"]), "loaded_assets": loaded_assets } - unreal_pipeline.imprint( + imprint( "{}/{}".format(asset_dir, container.get('container_name')), data) EditorLevelLibrary.save_current_level() @@ -870,7 +748,7 @@ class LayoutLoader(plugin.Loader): root = "/Game/Ayon" path = Path(container.get("namespace")) - containers = unreal_pipeline.ls() + containers = ls() layout_containers = [ c for c in containers if (c.get('asset_name') != container.get('asset_name') and From 4adf8388b4c08738c99b3f4fd8dfb778fb841dee Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Tue, 23 May 2023 09:17:54 +0100 Subject: [PATCH 26/50] Fix problem with updating camera --- .../hosts/unreal/plugins/load/load_camera.py | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/unreal/plugins/load/load_camera.py b/openpype/hosts/unreal/plugins/load/load_camera.py index 02634104e7..9361ed2ac5 100644 --- a/openpype/hosts/unreal/plugins/load/load_camera.py +++ b/openpype/hosts/unreal/plugins/load/load_camera.py @@ -345,17 +345,21 @@ class CameraLoader(plugin.Loader): for s in sequences: tracks = s.get_master_tracks() subscene_track = None + visibility_track = None for t in tracks: if t.get_class() == unreal.MovieSceneSubTrack.static_class(): subscene_track = t - break + if (t.get_class() == + unreal.MovieSceneLevelVisibilityTrack.static_class()): + visibility_track = t if subscene_track: sections = subscene_track.get_sections() for ss in sections: - if ss.get_sequence().get_name() == sequence_name: + if (ss.get_sequence().get_name() == + container.get('asset')): parent = s sub_scene = ss - # subscene_track.remove_section(ss) + subscene_track.remove_section(ss) break sequences.append(ss.get_sequence()) # Update subscenes indexes. @@ -364,10 +368,24 @@ class CameraLoader(plugin.Loader): ss.set_row_index(i) i += 1 + if visibility_track: + sections = visibility_track.get_sections() + for ss in sections: + if (unreal.Name(f"{container.get('asset')}_map") + in ss.get_level_names()): + visibility_track.remove_section(ss) + # Update visibility sections indexes. + i = -1 + prev_name = [] + for ss in sections: + if prev_name != ss.get_level_names(): + i += 1 + ss.set_row_index(i) + prev_name = ss.get_level_names() if parent: break - assert parent, "Could not find the parent sequence" + assert parent, "Could not find the parent sequence" EditorAssetLibrary.delete_asset(level_sequence.get_path_name()) From b7556f76d5cbc61841ec4593c99fa20dd42d7023 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Tue, 23 May 2023 10:23:14 +0100 Subject: [PATCH 27/50] Fix sequence when updating camera... again --- .../hosts/unreal/plugins/load/load_camera.py | 43 ++++--------------- 1 file changed, 9 insertions(+), 34 deletions(-) diff --git a/openpype/hosts/unreal/plugins/load/load_camera.py b/openpype/hosts/unreal/plugins/load/load_camera.py index 9361ed2ac5..6f20a466a9 100644 --- a/openpype/hosts/unreal/plugins/load/load_camera.py +++ b/openpype/hosts/unreal/plugins/load/load_camera.py @@ -270,7 +270,7 @@ class CameraLoader(plugin.Loader): def update(self, container, representation): ar = unreal.AssetRegistryHelpers.get_asset_registry() - root = "/Game/ayon" + root = "/Game/Ayon" asset_dir = container.get('namespace') @@ -283,16 +283,16 @@ class CameraLoader(plugin.Loader): EditorLevelLibrary.save_current_level() - filter = unreal.ARFilter( + _filter = unreal.ARFilter( class_names=["LevelSequence"], package_paths=[asset_dir], recursive_paths=False) - sequences = ar.get_assets(filter) - filter = unreal.ARFilter( + sequences = ar.get_assets(_filter) + _filter = unreal.ARFilter( class_names=["World"], package_paths=[asset_dir], recursive_paths=True) - maps = ar.get_assets(filter) + maps = ar.get_assets(_filter) # There should be only one map in the list EditorLevelLibrary.load_level(maps[0].get_full_name()) @@ -328,14 +328,13 @@ class CameraLoader(plugin.Loader): # Remove the Level Sequence from the parent. # We need to traverse the hierarchy from the master sequence to find # the level sequence. - root = "/Game/Ayon" namespace = container.get('namespace').replace(f"{root}/", "") ms_asset = namespace.split('/')[0] - filter = unreal.ARFilter( + _filter = unreal.ARFilter( class_names=["LevelSequence"], package_paths=[f"{root}/{ms_asset}"], recursive_paths=False) - sequences = ar.get_assets(filter) + sequences = ar.get_assets(_filter) master_sequence = sequences[0].get_asset() sequences = [master_sequence] @@ -345,43 +344,19 @@ class CameraLoader(plugin.Loader): for s in sequences: tracks = s.get_master_tracks() subscene_track = None - visibility_track = None for t in tracks: if t.get_class() == unreal.MovieSceneSubTrack.static_class(): subscene_track = t - if (t.get_class() == - unreal.MovieSceneLevelVisibilityTrack.static_class()): - visibility_track = t if subscene_track: sections = subscene_track.get_sections() for ss in sections: - if (ss.get_sequence().get_name() == - container.get('asset')): + if ss.get_sequence().get_name() == sequence_name: parent = s sub_scene = ss - subscene_track.remove_section(ss) break sequences.append(ss.get_sequence()) - # Update subscenes indexes. - i = 0 - for ss in sections: + for i, ss in enumerate(sections): ss.set_row_index(i) - i += 1 - - if visibility_track: - sections = visibility_track.get_sections() - for ss in sections: - if (unreal.Name(f"{container.get('asset')}_map") - in ss.get_level_names()): - visibility_track.remove_section(ss) - # Update visibility sections indexes. - i = -1 - prev_name = [] - for ss in sections: - if prev_name != ss.get_level_names(): - i += 1 - ss.set_row_index(i) - prev_name = ss.get_level_names() if parent: break From bf816562f09dc53510a8834e8e3c5ae9ba8b6597 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Tue, 23 May 2023 10:48:17 +0100 Subject: [PATCH 28/50] Fix old camera not being deleted on update --- .../hosts/unreal/plugins/load/load_camera.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/unreal/plugins/load/load_camera.py b/openpype/hosts/unreal/plugins/load/load_camera.py index 6f20a466a9..8fd6cc3e80 100644 --- a/openpype/hosts/unreal/plugins/load/load_camera.py +++ b/openpype/hosts/unreal/plugins/load/load_camera.py @@ -270,17 +270,8 @@ class CameraLoader(plugin.Loader): def update(self, container, representation): ar = unreal.AssetRegistryHelpers.get_asset_registry() - root = "/Game/Ayon" - asset_dir = container.get('namespace') - context = representation.get("context") - - hierarchy = context.get('hierarchy').split("/") - h_dir = f"{root}/{hierarchy[0]}" - h_asset = hierarchy[0] - master_level = f"{h_dir}/{h_asset}_map.{h_asset}_map" - EditorLevelLibrary.save_current_level() _filter = unreal.ARFilter( @@ -295,7 +286,7 @@ class CameraLoader(plugin.Loader): maps = ar.get_assets(_filter) # There should be only one map in the list - EditorLevelLibrary.load_level(maps[0].get_full_name()) + EditorLevelLibrary.load_level(maps[0].get_asset().get_path_name()) level_sequence = sequences[0].get_asset() @@ -328,6 +319,7 @@ class CameraLoader(plugin.Loader): # Remove the Level Sequence from the parent. # We need to traverse the hierarchy from the master sequence to find # the level sequence. + root = "/Game/Ayon" namespace = container.get('namespace').replace(f"{root}/", "") ms_asset = namespace.split('/')[0] _filter = unreal.ARFilter( @@ -336,6 +328,12 @@ class CameraLoader(plugin.Loader): recursive_paths=False) sequences = ar.get_assets(_filter) master_sequence = sequences[0].get_asset() + _filter = unreal.ARFilter( + class_names=["World"], + package_paths=[f"{root}/{ms_asset}"], + recursive_paths=False) + levels = ar.get_assets(_filter) + master_level = levels[0].get_asset().get_path_name() sequences = [master_sequence] From 902e6a9b61a7290e910c30009beb23dad9337c58 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 23 May 2023 15:54:11 +0100 Subject: [PATCH 29/50] Long names fixes. --- .../hosts/maya/plugins/publish/validate_rig_output_ids.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py b/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py index 499bfd4e37..cba70a21b7 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py @@ -55,7 +55,8 @@ class ValidateRigOutputIds(pyblish.api.InstancePlugin): if shapes: instance_nodes.extend(shapes) - scene_nodes = cmds.ls(type="transform") + cmds.ls(type="mesh") + scene_nodes = cmds.ls(type="transform", long=True) + scene_nodes += cmds.ls(type="mesh", long=True) scene_nodes = set(scene_nodes) - set(instance_nodes) scene_nodes_by_basename = defaultdict(list) @@ -76,7 +77,7 @@ class ValidateRigOutputIds(pyblish.api.InstancePlugin): if len(ids) > 1: cls.log.error( "\"{}\" id mismatch to: {}".format( - instance_node.longName(), matches + instance_node, matches ) ) invalid[instance_node] = matches From b5827e8cdfcde48a7a9eff6aa29a21f48541ab66 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 24 May 2023 12:18:05 +0100 Subject: [PATCH 30/50] Fix section range on update camera --- .../hosts/unreal/plugins/load/load_camera.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/openpype/hosts/unreal/plugins/load/load_camera.py b/openpype/hosts/unreal/plugins/load/load_camera.py index d198be29f4..c4fe9df70b 100644 --- a/openpype/hosts/unreal/plugins/load/load_camera.py +++ b/openpype/hosts/unreal/plugins/load/load_camera.py @@ -387,6 +387,30 @@ class CameraLoader(plugin.Loader): str(representation["data"]["path"]) ) + # Set range of all sections + # Changing the range of the section is not enough. We need to change + # the frame of all the keys in the section. + project_name = legacy_io.active_project() + asset = container.get('asset') + data = get_asset_by_name(project_name, asset)["data"] + + for possessable in new_sequence.get_possessables(): + for tracks in possessable.get_tracks(): + for section in tracks.get_sections(): + section.set_range( + data.get('clipIn'), + data.get('clipOut') + 1) + for channel in section.get_all_channels(): + for key in channel.get_keys(): + old_time = key.get_time().get_editor_property( + 'frame_number') + old_time_value = old_time.get_editor_property( + 'value') + new_time = old_time_value + ( + data.get('clipIn') - data.get('frameStart') + ) + key.set_time(unreal.FrameNumber(value=new_time)) + data = { "representation": str(representation["_id"]), "parent": str(representation["parent"]) From b47143b472eec6bb35ced0bbbb1d2b9a77c9acd4 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 25 May 2023 17:41:36 +0200 Subject: [PATCH 31/50] collect frames to fix settings --- .../plugins/publish/collect_frames_fix.py | 21 ++++++++++++++----- .../defaults/project_settings/global.json | 8 ++++++- .../schemas/schema_global_publish.json | 20 ++++++++++++++++++ 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/openpype/plugins/publish/collect_frames_fix.py b/openpype/plugins/publish/collect_frames_fix.py index bdd49585a5..837738eb06 100644 --- a/openpype/plugins/publish/collect_frames_fix.py +++ b/openpype/plugins/publish/collect_frames_fix.py @@ -26,11 +26,13 @@ class CollectFramesFixDef( targets = ["local"] hosts = ["nuke"] families = ["render", "prerender"] - enabled = True + + rewrite_version_enable = False def process(self, instance): attribute_values = self.get_attr_values_from_data(instance.data) frames_to_fix = attribute_values.get("frames_to_fix") + rewrite_version = attribute_values.get("rewrite_version") if frames_to_fix: @@ -71,10 +73,19 @@ class CollectFramesFixDef( @classmethod def get_attribute_defs(cls): - return [ + attributes = [ TextDef("frames_to_fix", label="Frames to fix", placeholder="5,10-15", - regex="[0-9,-]+"), - BoolDef("rewrite_version", label="Rewrite latest version", - default=False), + regex="[0-9,-]+") ] + + if cls.rewrite_version_enable: + attributes.append( + BoolDef( + "rewrite_version", + label="Rewrite latest version", + default=False + ) + ) + + return attributes diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 75f335f1de..002e547feb 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -46,6 +46,10 @@ "enabled": false, "families": [] }, + "CollectFramesFixDef": { + "enabled": true, + "rewrite_version_enable": true + }, "ValidateEditorialAssetName": { "enabled": true, "optional": false @@ -252,7 +256,9 @@ } }, { - "families": ["review"], + "families": [ + "review" + ], "hosts": [ "maya", "houdini" diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index a7617918a3..8000e6156b 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -81,6 +81,26 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "checkbox_key": "enabled", + "key": "CollectFramesFixDef", + "label": "Collect Frames to Fix", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "rewrite_version_enable", + "label": "Rewrite latest version" + } + ] + }, { "type": "dict", "collapsible": true, From 31da5582fca78f9f84977700c69523e077df6e19 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 25 May 2023 21:39:07 +0200 Subject: [PATCH 32/50] make understandable label in settings --- .../schemas/projects_schema/schemas/schema_global_publish.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index 8000e6156b..3164cfb62d 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -97,7 +97,7 @@ { "type": "boolean", "key": "rewrite_version_enable", - "label": "Rewrite latest version" + "label": "Show 'Rewrite latest version' toggle" } ] }, From a4fd3c7c6dae31509fc37621878aa4600933f2ae Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Mon, 29 May 2023 11:50:56 +0100 Subject: [PATCH 33/50] Minor refactor --- .../hosts/unreal/plugins/load/load_camera.py | 45 +++++++------------ 1 file changed, 15 insertions(+), 30 deletions(-) diff --git a/openpype/hosts/unreal/plugins/load/load_camera.py b/openpype/hosts/unreal/plugins/load/load_camera.py index c4fe9df70b..af7a594b41 100644 --- a/openpype/hosts/unreal/plugins/load/load_camera.py +++ b/openpype/hosts/unreal/plugins/load/load_camera.py @@ -89,10 +89,7 @@ class CameraLoader(plugin.Loader): hierarchy_dir_list.append(hierarchy_dir) asset = context.get('asset').get('name') suffix = "_CON" - if asset: - asset_name = "{}_{}".format(asset, name) - else: - asset_name = "{}".format(name) + asset_name = f"{asset}_{name}" if asset else f"{name}" tools = unreal.AssetToolsHelpers().get_asset_tools() @@ -106,23 +103,15 @@ class CameraLoader(plugin.Loader): # Get highest number to make a unique name folders = [a for a in asset_content if a[-1] == "/" and f"{name}_" in a] - f_numbers = [] - for f in folders: - # Get number from folder name. Splits the string by "_" and - # removes the last element (which is a "/"). - f_numbers.append(int(f.split("_")[-1][:-1])) + # Get number from folder name. Splits the string by "_" and + # removes the last element (which is a "/"). + f_numbers = [int(f.split("_")[-1][:-1]) for f in folders] f_numbers.sort() - if not f_numbers: - unique_number = 1 - else: - unique_number = f_numbers[-1] + 1 + unique_number = f_numbers[-1] + 1 if f_numbers else 1 asset_dir, container_name = tools.create_unique_asset_name( f"{hierarchy_dir}/{asset}/{name}_{unique_number:02d}", suffix="") - asset_path = Path(asset_dir) - asset_path_parent = str(asset_path.parent.as_posix()) - container_name += suffix EditorAssetLibrary.make_directory(asset_dir) @@ -163,17 +152,17 @@ class CameraLoader(plugin.Loader): asset).get_class().get_name() == 'LevelSequence' ] - if not existing_sequences: + if existing_sequences: + for seq in existing_sequences: + sequences.append(seq.get_asset()) + frame_ranges.append(( + seq.get_asset().get_playback_start(), + seq.get_asset().get_playback_end())) + else: sequence, frame_range = generate_sequence(h, h_dir) sequences.append(sequence) frame_ranges.append(frame_range) - else: - for e in existing_sequences: - sequences.append(e.get_asset()) - frame_ranges.append(( - e.get_asset().get_playback_start(), - e.get_asset().get_playback_end())) EditorAssetLibrary.make_directory(asset_dir) @@ -252,8 +241,7 @@ class CameraLoader(plugin.Loader): "parent": context["representation"]["parent"], "family": context["representation"]["context"]["family"] } - imprint( - "{}/{}".format(asset_dir, container_name), data) + imprint(f"{asset_dir}/{container_name}", data) EditorLevelLibrary.save_all_dirty_levels() EditorLevelLibrary.load_level(master_level) @@ -415,8 +403,7 @@ class CameraLoader(plugin.Loader): "representation": str(representation["_id"]), "parent": str(representation["parent"]) } - imprint( - "{}/{}".format(asset_dir, container.get('container_name')), data) + imprint(f"{asset_dir}/{container.get('container_name')}", data) EditorLevelLibrary.save_current_level() @@ -514,10 +501,8 @@ class CameraLoader(plugin.Loader): break sequences.append(ss.get_sequence()) # Update subscenes indexes. - i = 0 - for ss in sections: + for i, ss in enumerate(sections): ss.set_row_index(i) - i += 1 if visibility_track: sections = visibility_track.get_sections() From 79a0210c35eda89c355afd0497d2ae14fa9bcb45 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Mon, 29 May 2023 11:56:03 +0100 Subject: [PATCH 34/50] Save whole hierarchy when loading camera or layout --- openpype/hosts/unreal/plugins/load/load_camera.py | 5 +++-- openpype/hosts/unreal/plugins/load/load_layout.py | 11 ++++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/unreal/plugins/load/load_camera.py b/openpype/hosts/unreal/plugins/load/load_camera.py index af7a594b41..3ed7b055e3 100644 --- a/openpype/hosts/unreal/plugins/load/load_camera.py +++ b/openpype/hosts/unreal/plugins/load/load_camera.py @@ -246,8 +246,9 @@ class CameraLoader(plugin.Loader): EditorLevelLibrary.save_all_dirty_levels() EditorLevelLibrary.load_level(master_level) + # Save all assets in the hierarchy asset_content = EditorAssetLibrary.list_assets( - asset_dir, recursive=True, include_folder=True + hierarchy_dir_list[0], recursive=True, include_folder=False ) for a in asset_content: @@ -408,7 +409,7 @@ class CameraLoader(plugin.Loader): EditorLevelLibrary.save_current_level() asset_content = EditorAssetLibrary.list_assets( - asset_dir, recursive=True, include_folder=False) + f"{root}/{ms_asset}", recursive=True, include_folder=False) for a in asset_content: EditorAssetLibrary.save_asset(a) diff --git a/openpype/hosts/unreal/plugins/load/load_layout.py b/openpype/hosts/unreal/plugins/load/load_layout.py index 5a3953f82e..51ca0383e0 100644 --- a/openpype/hosts/unreal/plugins/load/load_layout.py +++ b/openpype/hosts/unreal/plugins/load/load_layout.py @@ -642,8 +642,10 @@ class LayoutLoader(plugin.Loader): imprint( "{}/{}".format(asset_dir, container_name), data) + save_dir = hierarchy_dir_list[0] if create_sequences else asset_dir + asset_content = EditorAssetLibrary.list_assets( - asset_dir, recursive=True, include_folder=False) + save_dir, recursive=True, include_folder=False) for a in asset_content: EditorAssetLibrary.save_asset(a) @@ -664,11 +666,12 @@ class LayoutLoader(plugin.Loader): asset_dir = container.get('namespace') context = representation.get("context") + hierarchy = context.get('hierarchy').split("/") + sequence = None master_level = None if create_sequences: - hierarchy = context.get('hierarchy').split("/") h_dir = f"{root}/{hierarchy[0]}" h_asset = hierarchy[0] master_level = f"{h_dir}/{h_asset}_map.{h_asset}_map" @@ -726,8 +729,10 @@ class LayoutLoader(plugin.Loader): EditorLevelLibrary.save_current_level() + save_dir = f"{root}/{hierarchy[0]}" if create_sequences else asset_dir + asset_content = EditorAssetLibrary.list_assets( - asset_dir, recursive=True, include_folder=False) + save_dir, recursive=True, include_folder=False) for a in asset_content: EditorAssetLibrary.save_asset(a) From ff2c494705f79d21b75dcfc77fb2c1a49b23bae8 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Mon, 29 May 2023 12:10:23 +0100 Subject: [PATCH 35/50] Set view range in sequencer when creating sequences --- openpype/hosts/unreal/api/pipeline.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/unreal/api/pipeline.py b/openpype/hosts/unreal/api/pipeline.py index 5030e8ee86..72816c9b81 100644 --- a/openpype/hosts/unreal/api/pipeline.py +++ b/openpype/hosts/unreal/api/pipeline.py @@ -623,11 +623,18 @@ def generate_sequence(h, h_dir): min_frame = min(start_frames) max_frame = max(end_frames) + fps = asset_data.get('data').get("fps") + sequence.set_display_rate( - unreal.FrameRate(asset_data.get('data').get("fps"), 1.0)) + unreal.FrameRate(fps, 1.0)) sequence.set_playback_start(min_frame) sequence.set_playback_end(max_frame) + sequence.set_work_range_start(min_frame / fps) + sequence.set_work_range_end(max_frame / fps) + sequence.set_view_range_start(min_frame / fps) + sequence.set_view_range_end(max_frame / fps) + tracks = sequence.get_master_tracks() track = None for t in tracks: From eb5b5bf492c69d3369d944ff019f36a6e306b3bd Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Mon, 29 May 2023 12:59:54 +0100 Subject: [PATCH 36/50] Set back sequencer and viewport when updating layout or camera --- .../hosts/unreal/plugins/load/load_camera.py | 23 ++++++++++++-- .../hosts/unreal/plugins/load/load_layout.py | 31 ++++++++++++++----- 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/unreal/plugins/load/load_camera.py b/openpype/hosts/unreal/plugins/load/load_camera.py index 3ed7b055e3..59ea14697d 100644 --- a/openpype/hosts/unreal/plugins/load/load_camera.py +++ b/openpype/hosts/unreal/plugins/load/load_camera.py @@ -3,9 +3,12 @@ from pathlib import Path import unreal -from unreal import EditorAssetLibrary -from unreal import EditorLevelLibrary -from unreal import EditorLevelUtils +from unreal import ( + EditorAssetLibrary, + EditorLevelLibrary, + EditorLevelUtils, + LevelSequenceEditorBlueprintLibrary as LevelSequenceLib, +) from openpype.client import get_asset_by_name from openpype.pipeline import ( AYON_CONTAINER_ID, @@ -259,6 +262,13 @@ class CameraLoader(plugin.Loader): def update(self, container, representation): ar = unreal.AssetRegistryHelpers.get_asset_registry() + curr_level_sequence = LevelSequenceLib.get_current_level_sequence() + curr_time = LevelSequenceLib.get_current_time() + is_cam_lock = LevelSequenceLib.is_camera_cut_locked_to_viewport() + + editor_subsystem = unreal.UnrealEditorSubsystem() + vp_loc, vp_rot = editor_subsystem.get_level_viewport_camera_info() + asset_dir = container.get('namespace') EditorLevelLibrary.save_current_level() @@ -416,6 +426,13 @@ class CameraLoader(plugin.Loader): EditorLevelLibrary.load_level(master_level) + if curr_level_sequence: + LevelSequenceLib.open_level_sequence(curr_level_sequence) + LevelSequenceLib.set_current_time(curr_time) + LevelSequenceLib.set_lock_camera_cut_to_viewport(is_cam_lock) + + editor_subsystem.set_level_viewport_camera_info(vp_loc, vp_rot) + def remove(self, container): asset_dir = container.get('namespace') path = Path(asset_dir) diff --git a/openpype/hosts/unreal/plugins/load/load_layout.py b/openpype/hosts/unreal/plugins/load/load_layout.py index 51ca0383e0..86b2e1456c 100644 --- a/openpype/hosts/unreal/plugins/load/load_layout.py +++ b/openpype/hosts/unreal/plugins/load/load_layout.py @@ -5,13 +5,16 @@ import collections from pathlib import Path import unreal -from unreal import EditorAssetLibrary -from unreal import EditorLevelLibrary -from unreal import EditorLevelUtils -from unreal import AssetToolsHelpers -from unreal import FBXImportType -from unreal import MovieSceneLevelVisibilityTrack -from unreal import MovieSceneSubTrack +from unreal import ( + EditorAssetLibrary, + EditorLevelLibrary, + EditorLevelUtils, + AssetToolsHelpers, + FBXImportType, + MovieSceneLevelVisibilityTrack, + MovieSceneSubTrack, + LevelSequenceEditorBlueprintLibrary as LevelSequenceLib, +) from openpype.client import get_asset_by_name, get_representations from openpype.pipeline import ( @@ -661,6 +664,13 @@ class LayoutLoader(plugin.Loader): ar = unreal.AssetRegistryHelpers.get_asset_registry() + curr_level_sequence = LevelSequenceLib.get_current_level_sequence() + curr_time = LevelSequenceLib.get_current_time() + is_cam_lock = LevelSequenceLib.is_camera_cut_locked_to_viewport() + + editor_subsystem = unreal.UnrealEditorSubsystem() + vp_loc, vp_rot = editor_subsystem.get_level_viewport_camera_info() + root = "/Game/Ayon" asset_dir = container.get('namespace') @@ -742,6 +752,13 @@ class LayoutLoader(plugin.Loader): elif prev_level: EditorLevelLibrary.load_level(prev_level) + if curr_level_sequence: + LevelSequenceLib.open_level_sequence(curr_level_sequence) + LevelSequenceLib.set_current_time(curr_time) + LevelSequenceLib.set_lock_camera_cut_to_viewport(is_cam_lock) + + editor_subsystem.set_level_viewport_camera_info(vp_loc, vp_rot) + def remove(self, container): """ Delete the layout. First, check if the assets loaded with the layout From 5fbae39a745b0016b2ada9d054b696aef2007fee Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 30 May 2023 13:32:27 +0200 Subject: [PATCH 37/50] Ftrack: Role names are not case sensitive in ftrack event server status action (#5058) * statuser is not case sensitive about role names * safer role check --- .../ftrack/lib/ftrack_action_handler.py | 23 +++++++++++++++---- .../ftrack/scripts/sub_event_status.py | 4 ++-- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/openpype/modules/ftrack/lib/ftrack_action_handler.py b/openpype/modules/ftrack/lib/ftrack_action_handler.py index 07b3a780a2..1be4353b26 100644 --- a/openpype/modules/ftrack/lib/ftrack_action_handler.py +++ b/openpype/modules/ftrack/lib/ftrack_action_handler.py @@ -234,6 +234,10 @@ class BaseAction(BaseHandler): if not settings_roles: return default + user_roles = { + role_name.lower() + for role_name in user_roles + } for role_name in settings_roles: if role_name.lower() in user_roles: return True @@ -264,8 +268,15 @@ class BaseAction(BaseHandler): return user_entity @classmethod - def get_user_roles_from_event(cls, session, event): - """Query user entity from event.""" + def get_user_roles_from_event(cls, session, event, lower=True): + """Get user roles based on data in event. + + Args: + session (ftrack_api.Session): Prepared ftrack session. + event (ftrack_api.event.Event): Event which is processed. + lower (Optional[bool]): Lower the role names. Default 'True'. + """ + not_set = object() user_roles = event["data"].get("user_roles", not_set) @@ -273,7 +284,10 @@ class BaseAction(BaseHandler): user_roles = [] user_entity = cls.get_user_entity_from_event(session, event) for role in user_entity["user_security_roles"]: - user_roles.append(role["security_role"]["name"].lower()) + role_name = role["security_role"]["name"] + if lower: + role_name = role_name.lower() + user_roles.append(role_name) event["data"]["user_roles"] = user_roles return user_roles @@ -322,7 +336,8 @@ class BaseAction(BaseHandler): if not settings.get(self.settings_enabled_key, True): return False - user_role_list = self.get_user_roles_from_event(session, event) + user_role_list = self.get_user_roles_from_event( + session, event, lower=False) if not self.roles_check(settings.get("role_list"), user_role_list): return False return True diff --git a/openpype/modules/ftrack/scripts/sub_event_status.py b/openpype/modules/ftrack/scripts/sub_event_status.py index dc5836e7f2..c6c2e9e1f6 100644 --- a/openpype/modules/ftrack/scripts/sub_event_status.py +++ b/openpype/modules/ftrack/scripts/sub_event_status.py @@ -296,9 +296,9 @@ def server_activity_validate_user(event): if not user_ent: return False - role_list = ["Pypeclub", "Administrator"] + role_list = {"pypeclub", "administrator"} for role in user_ent["user_security_roles"]: - if role["security_role"]["name"] in role_list: + if role["security_role"]["name"].lower() in role_list: return True return False From 4bc61d1c89f1c3adfbbc7ba80fa2fff5f156eec0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 30 May 2023 13:33:58 +0200 Subject: [PATCH 38/50] Fix border widget --- .../publisher/widgets/border_label_widget.py | 125 +++++++++++++----- 1 file changed, 94 insertions(+), 31 deletions(-) diff --git a/openpype/tools/publisher/widgets/border_label_widget.py b/openpype/tools/publisher/widgets/border_label_widget.py index 5617e159cd..1381d74eb3 100644 --- a/openpype/tools/publisher/widgets/border_label_widget.py +++ b/openpype/tools/publisher/widgets/border_label_widget.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- +from math import ceil from qtpy import QtWidgets, QtCore, QtGui from openpype.style import get_objected_colors @@ -14,32 +15,44 @@ class _VLineWidget(QtWidgets.QWidget): It is expected that parent widget will set width. """ - def __init__(self, color, left, parent): + def __init__(self, color, line_size, left, parent): super(_VLineWidget, self).__init__(parent) self._color = color self._left = left + self._line_size = line_size + + def set_line_size(self, line_size): + self._line_size = line_size def paintEvent(self, event): if not self.isVisible(): return - if self._left: - pos_x = 0 - else: - pos_x = self.width() + pos_x = self._line_size * 0.5 + if not self._left: + pos_x = self.width() - pos_x + painter = QtGui.QPainter(self) painter.setRenderHints( QtGui.QPainter.Antialiasing | QtGui.QPainter.SmoothPixmapTransform ) + if self._color: pen = QtGui.QPen(self._color) else: pen = painter.pen() - pen.setWidth(1) + pen.setWidth(self._line_size) painter.setPen(pen) painter.setBrush(QtCore.Qt.transparent) - painter.drawLine(pos_x, 0, pos_x, self.height()) + painter.drawRect( + QtCore.QRectF( + pos_x, + -self._line_size, + pos_x + (self.width() * 2), + self.height() + (self._line_size * 2) + ) + ) painter.end() @@ -56,34 +69,46 @@ class _HBottomLineWidget(QtWidgets.QWidget): It is expected that parent widget will set height and radius. """ - def __init__(self, color, parent): + def __init__(self, color, line_size, parent): super(_HBottomLineWidget, self).__init__(parent) self._color = color self._radius = 0 + self._line_size = line_size def set_radius(self, radius): self._radius = radius + def set_line_size(self, line_size): + self._line_size = line_size + def paintEvent(self, event): if not self.isVisible(): return - rect = QtCore.QRect( - 0, -self._radius, self.width(), self.height() + self._radius + x_offset = self._line_size * 0.5 + rect = QtCore.QRectF( + x_offset, + -self._radius, + self.width() - (2 * x_offset), + (self.height() + self._radius) - x_offset ) painter = QtGui.QPainter(self) painter.setRenderHints( QtGui.QPainter.Antialiasing | QtGui.QPainter.SmoothPixmapTransform ) + if self._color: pen = QtGui.QPen(self._color) else: pen = painter.pen() - pen.setWidth(1) + pen.setWidth(self._line_size) painter.setPen(pen) painter.setBrush(QtCore.Qt.transparent) - painter.drawRoundedRect(rect, self._radius, self._radius) + if self._radius: + painter.drawRoundedRect(rect, self._radius, self._radius) + else: + painter.drawRect(rect) painter.end() @@ -102,30 +127,38 @@ class _HTopCornerLineWidget(QtWidgets.QWidget): It is expected that parent widget will set height and radius. """ - def __init__(self, color, left_side, parent): + + def __init__(self, color, line_size, left_side, parent): super(_HTopCornerLineWidget, self).__init__(parent) self._left_side = left_side + self._line_size = line_size self._color = color self._radius = 0 def set_radius(self, radius): self._radius = radius + def set_line_size(self, line_size): + self._line_size = line_size + def paintEvent(self, event): if not self.isVisible(): return - pos_y = self.height() / 2 - + pos_y = self.height() * 0.5 + x_offset = self._line_size * 0.5 if self._left_side: - rect = QtCore.QRect( - 0, pos_y, self.width() + self._radius, self.height() + rect = QtCore.QRectF( + x_offset, + pos_y, + self.width() + self._radius + x_offset, + self.height() ) else: - rect = QtCore.QRect( - -self._radius, + rect = QtCore.QRectF( + (-self._radius), pos_y, - self.width() + self._radius, + (self.width() + self._radius) - x_offset, self.height() ) @@ -138,10 +171,13 @@ class _HTopCornerLineWidget(QtWidgets.QWidget): pen = QtGui.QPen(self._color) else: pen = painter.pen() - pen.setWidth(1) + pen.setWidth(self._line_size) painter.setPen(pen) painter.setBrush(QtCore.Qt.transparent) - painter.drawRoundedRect(rect, self._radius, self._radius) + if self._radius: + painter.drawRoundedRect(rect, self._radius, self._radius) + else: + painter.drawRect(rect) painter.end() @@ -163,8 +199,10 @@ class BorderedLabelWidget(QtWidgets.QFrame): if color_value: color = color_value.get_qcolor() - top_left_w = _HTopCornerLineWidget(color, True, self) - top_right_w = _HTopCornerLineWidget(color, False, self) + line_size = 1 + + top_left_w = _HTopCornerLineWidget(color, line_size, True, self) + top_right_w = _HTopCornerLineWidget(color, line_size, False, self) label_widget = QtWidgets.QLabel(label, self) @@ -175,10 +213,10 @@ class BorderedLabelWidget(QtWidgets.QFrame): top_layout.addWidget(label_widget, 0) top_layout.addWidget(top_right_w, 1) - left_w = _VLineWidget(color, True, self) - right_w = _VLineWidget(color, False, self) + left_w = _VLineWidget(color, line_size, True, self) + right_w = _VLineWidget(color, line_size, False, self) - bottom_w = _HBottomLineWidget(color, self) + bottom_w = _HBottomLineWidget(color, line_size, self) center_layout = QtWidgets.QHBoxLayout() center_layout.setContentsMargins(5, 5, 5, 5) @@ -201,6 +239,7 @@ class BorderedLabelWidget(QtWidgets.QFrame): self._widget = None self._radius = 0 + self._line_size = line_size self._top_left_w = top_left_w self._top_right_w = top_right_w @@ -216,14 +255,38 @@ class BorderedLabelWidget(QtWidgets.QFrame): value, value, value, value ) + def set_line_size(self, line_size): + if self._line_size == line_size: + return + self._line_size = line_size + for widget in ( + self._top_left_w, + self._top_right_w, + self._left_w, + self._right_w, + self._bottom_w + ): + widget.set_line_size(line_size) + self._recalculate_sizes() + def showEvent(self, event): super(BorderedLabelWidget, self).showEvent(event) + self._recalculate_sizes() + def _recalculate_sizes(self): height = self._label_widget.height() - radius = (height + (height % 2)) / 2 + radius = int((height + (height % 2)) / 2) self._radius = radius - side_width = 1 + radius + radius_size = self._line_size + 1 + if radius_size < radius: + radius_size = radius + + if radius: + side_width = self._line_size + radius + else: + side_width = self._line_size + 1 + # Don't use fixed width/height as that would set also set # the other size (When fixed width is set then is also set # fixed height). @@ -231,8 +294,8 @@ class BorderedLabelWidget(QtWidgets.QFrame): self._left_w.setMaximumWidth(side_width) self._right_w.setMinimumWidth(side_width) self._right_w.setMaximumWidth(side_width) - self._bottom_w.setMinimumHeight(radius) - self._bottom_w.setMaximumHeight(radius) + self._bottom_w.setMinimumHeight(radius_size) + self._bottom_w.setMaximumHeight(radius_size) self._bottom_w.set_radius(radius) self._top_right_w.set_radius(radius) self._top_left_w.set_radius(radius) From 26b99db61e4ab17033bfaa5b0f9099a3b6561275 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 30 May 2023 15:18:53 +0200 Subject: [PATCH 39/50] removed unused import --- openpype/tools/publisher/widgets/border_label_widget.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/tools/publisher/widgets/border_label_widget.py b/openpype/tools/publisher/widgets/border_label_widget.py index 1381d74eb3..e5693368b1 100644 --- a/openpype/tools/publisher/widgets/border_label_widget.py +++ b/openpype/tools/publisher/widgets/border_label_widget.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- -from math import ceil from qtpy import QtWidgets, QtCore, QtGui from openpype.style import get_objected_colors From be523bc5ec967efce3181668a932eb644a5e8f59 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Tue, 30 May 2023 16:49:49 +0100 Subject: [PATCH 40/50] Use temp folder to copy commandlet project --- openpype/hosts/unreal/ue_workers.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/unreal/ue_workers.py b/openpype/hosts/unreal/ue_workers.py index e7a690ac9c..2b7e1375e6 100644 --- a/openpype/hosts/unreal/ue_workers.py +++ b/openpype/hosts/unreal/ue_workers.py @@ -6,6 +6,8 @@ import subprocess from distutils import dir_util from pathlib import Path from typing import List, Union +import tempfile +from distutils.dir_util import copy_tree import openpype.hosts.unreal.lib as ue_lib @@ -90,9 +92,20 @@ class UEProjectGenerationWorker(QtCore.QObject): ("Generating a new UE project ... 1 out of " f"{stage_count}")) + # Need to copy the commandlet project to a temporary folder where + # users don't need admin rights to write to. + cmdlet_tmp = tempfile.TemporaryDirectory() + cmdlet_filename = cmdlet_project.name + cmdlet_dir = cmdlet_project.parent.as_posix() + cmdlet_tmp_name = Path(cmdlet_tmp.name) + cmdlet_tmp_file = cmdlet_tmp_name.joinpath(cmdlet_filename) + copy_tree( + cmdlet_dir, + cmdlet_tmp_name.as_posix()) + commandlet_cmd = [ f"{ue_editor_exe.as_posix()}", - f"{cmdlet_project.as_posix()}", + f"{cmdlet_tmp_file.as_posix()}", "-run=AyonGenerateProject", f"{project_file.resolve().as_posix()}", ] @@ -111,6 +124,8 @@ class UEProjectGenerationWorker(QtCore.QObject): gen_process.stdout.close() return_code = gen_process.wait() + cmdlet_tmp.cleanup() + if return_code and return_code != 0: msg = ( f"Failed to generate {self.project_name} " From a215e4f00665f779f1056f5a128b3e2674e3c68f Mon Sep 17 00:00:00 2001 From: Ynbot Date: Wed, 31 May 2023 03:26:47 +0000 Subject: [PATCH 41/50] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index c24388b2ff..5c7105e7e0 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.15.9-nightly.1" +__version__ = "3.15.9-nightly.2" From e8b47be8d562e8bb1edd77dd4aebf6e75f6fc720 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 31 May 2023 03:27:31 +0000 Subject: [PATCH 42/50] chore(): update bug report / version --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 54a4ee6ac0..0036e121b7 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,7 @@ body: label: Version description: What version are you running? Look to OpenPype Tray options: + - 3.15.9-nightly.2 - 3.15.9-nightly.1 - 3.15.8 - 3.15.8-nightly.3 @@ -134,7 +135,6 @@ body: - 3.14.2 - 3.14.2-nightly.5 - 3.14.2-nightly.4 - - 3.14.2-nightly.3 validations: required: true - type: dropdown From e6d10fa3358b61eae7c9461da2119af72adf9be6 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Wed, 31 May 2023 10:37:21 +0100 Subject: [PATCH 43/50] Update settings_project_global.md (#5045) --- website/docs/project_settings/settings_project_global.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index c17f707830..7bd24a5773 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -63,7 +63,7 @@ Example here describes use case for creation of new color coded review of png im ![global_oiio_transcode](assets/global_oiio_transcode.png) Another use case is to transcode in Maya only `beauty` render layers and use collected `Display` and `View` colorspaces from DCC. -![global_oiio_transcode_in_Maya](assets/global_oiio_transcode.png)n +![global_oiio_transcode_in_Maya](assets/global_oiio_transcode2.png) ## Profile filters From e22d1bf78b4e9d4aeeb7c0295c8bb92b1c5595d9 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 31 May 2023 10:45:47 +0100 Subject: [PATCH 44/50] Set dev mode off by default --- openpype/settings/defaults/project_settings/unreal.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/unreal.json b/openpype/settings/defaults/project_settings/unreal.json index 737a17d289..92bdb468ba 100644 --- a/openpype/settings/defaults/project_settings/unreal.json +++ b/openpype/settings/defaults/project_settings/unreal.json @@ -15,6 +15,6 @@ "preroll_frames": 0, "render_format": "png", "project_setup": { - "dev_mode": true + "dev_mode": false } } From c2b753326fb6cb6460a43e401fb5731cdee1fabb Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 31 May 2023 11:59:21 +0100 Subject: [PATCH 45/50] Check if the Unreal app name follows the right format --- openpype/hosts/unreal/addon.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/openpype/hosts/unreal/addon.py b/openpype/hosts/unreal/addon.py index 1119b5c16c..fddceb00a8 100644 --- a/openpype/hosts/unreal/addon.py +++ b/openpype/hosts/unreal/addon.py @@ -1,5 +1,7 @@ import os +import re from openpype.modules import IHostAddon, OpenPypeModule +from openpype.widgets.message_window import Window UNREAL_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -19,6 +21,18 @@ class UnrealAddon(OpenPypeModule, IHostAddon): from .lib import get_compatible_integration + pattern = re.compile(r'^\d+-\d+$') + + if not pattern.match(app.name): + Window( + parent=None, + title="Unreal application name format", + message="Unreal application name must be in format '5-0' or '5-1'", + level="critical") + raise ValueError( + "Unreal application name must be in format '5-0' or '5-1'" + ) + ue_version = app.name.replace("-", ".") unreal_plugin_path = os.path.join( UNREAL_ROOT_DIR, "integration", "UE_{}".format(ue_version), "Ayon" From 142ae35d9b81b5325c462b67cc870c959a801524 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 31 May 2023 12:13:00 +0100 Subject: [PATCH 46/50] Hound fixes --- openpype/hosts/unreal/addon.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/unreal/addon.py b/openpype/hosts/unreal/addon.py index fddceb00a8..16f6fcf27c 100644 --- a/openpype/hosts/unreal/addon.py +++ b/openpype/hosts/unreal/addon.py @@ -24,14 +24,13 @@ class UnrealAddon(OpenPypeModule, IHostAddon): pattern = re.compile(r'^\d+-\d+$') if not pattern.match(app.name): + msg = "Unreal application name must be in format '5-0' or '5-1'" Window( parent=None, title="Unreal application name format", - message="Unreal application name must be in format '5-0' or '5-1'", + message=msg, level="critical") - raise ValueError( - "Unreal application name must be in format '5-0' or '5-1'" - ) + raise ValueError(msg) ue_version = app.name.replace("-", ".") unreal_plugin_path = os.path.join( From 0635c39a37094bf4f78898a9a179fbf657baf021 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 31 May 2023 14:32:24 +0100 Subject: [PATCH 47/50] Improved error message --- openpype/hosts/unreal/addon.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/unreal/addon.py b/openpype/hosts/unreal/addon.py index 16f6fcf27c..ed23950b35 100644 --- a/openpype/hosts/unreal/addon.py +++ b/openpype/hosts/unreal/addon.py @@ -24,7 +24,10 @@ class UnrealAddon(OpenPypeModule, IHostAddon): pattern = re.compile(r'^\d+-\d+$') if not pattern.match(app.name): - msg = "Unreal application name must be in format '5-0' or '5-1'" + msg = ( + "Unreal application key in the settings must be in format" + "'5-0' or '5-1'" + ) Window( parent=None, title="Unreal application name format", From 396486067bd4c33f0ac6ef75f0676e05b9b6c985 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Wed, 31 May 2023 13:43:50 +0000 Subject: [PATCH 48/50] [Automated] Release --- CHANGELOG.md | 335 ++++++++++++++++++++++++++++++++++++++++++++ openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 337 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a33904735b..ec6544e659 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,341 @@ # Changelog +## [3.15.9](https://github.com/ynput/OpenPype/tree/3.15.9) + + +[Full Changelog](https://github.com/ynput/OpenPype/compare/3.15.8...3.15.9) + +### **πŸ†• New features** + + +
+Blender: Implemented Loading of Alembic Camera #4990 + +Implemented loading of Alembic cameras in Blender. + + +___ + +
+ + +
+Unreal: Implemented Creator, Loader and Extractor for Levels #5008 + +Creator, Loader and Extractor for Unreal Levels have been implemented. + + +___ + +
+ +### **πŸš€ Enhancements** + + +
+Blender: Added setting for base unit scale #4987 + +A setting for the base unit scale has been added for Blender.The unit scale is automatically applied when opening a file or creating a new one. + + +___ + +
+ + +
+Unreal: Changed naming and path of Camera Levels #5010 + +The levels created for the camera in Unreal now include `_camera` in the name, to be better identifiable, and are placed in the camera folder. + + +___ + +
+ + +
+Settings: Added option to nest settings templates #5022 + +It is possible to nest settings templates in another templates. + + +___ + +
+ + +
+Enhancement/publisher: Remove "hit play to continue" label on continue #5029 + +Remove "hit play to continue" message on continue so that it doesn't show anymore when play was clicked. + + +___ + +
+ + +
+Ftrack: Limit number of ftrack events to query at once #5033 + +Limit the amount of ftrack events received from mongo at once to 100. + + +___ + +
+ + +
+General: Small code cleanups #5034 + +Small code cleanup and updates. + + +___ + +
+ + +
+Global: collect frames to fix with settings #5036 + +Settings for `Collect Frames to Fix` will allow disable per project the plugin. Also `Rewriting latest version` attribute is hiddable from settings. + + +___ + +
+ + +
+General: Publish plugin apply settings can expect only project settings #5037 + +Only project settings are passed to optional `apply_settings` method, if the method expects only one argument. + + +___ + +
+ +### **πŸ› Bug fixes** + + +
+Maya: Load Assembly fix invalid imports #4859 + +Refactors imports so they are now correct. + + +___ + +
+ + +
+Maya: Skipping rendersetup for members. #4973 + +When publishing a `rendersetup`, the objectset is and should be empty. + + +___ + +
+ + +
+Maya: Validate Rig Output IDs #5016 + +Absolute names of node were not used, so plugin did not fetch the nodes properly.Also missed pymel command. + + +___ + +
+ + +
+Deadline: escape rootless path in publish job #4910 + +If the publish path on Deadline job contains spaces or other characters, command was failing because the path wasn't properly escaped. This is fixing it. + + +___ + +
+ + +
+General: Company name and URL changed #4974 + +The current records were obsolete in inno_setup, changed to the up-to-date. +___ + +
+ + +
+Unreal: Fix usage of 'get_full_path' function #5014 + +This PR changes all the occurrences of `get_full_path` functions to alternatives to get the path of the objects. + + +___ + +
+ + +
+Unreal: Fix sequence frames validator to use correct data #5021 + +Fix sequence frames validator to use clipIn and clipOut data instead of frameStart and frameEnd. + + +___ + +
+ + +
+Unreal: Fix render instances collection to use correct data #5023 + +Fix render instances collection to use `frameStart` and `frameEnd` from the Project Manager, instead of the sequence's ones. + + +___ + +
+ + +
+Resolve: loader is opening even if no timeline in project #5025 + +Loader is opening now even no timeline is available in a project. + + +___ + +
+ + +
+nuke: callback for dirmapping is on demand #5030 + +Nuke was slowed down on processing due this callback. Since it is disabled by default it made sense to add it only on demand. + + +___ + +
+ + +
+Publisher: UI works with instances without label #5032 + +Publisher UI does not crash if instance don't have filled 'label' key in instance data. + + +___ + +
+ + +
+Publisher: Call explicitly prepared tab methods #5044 + +It is not possible to go to Create tab during publishing from OpenPype menu. + + +___ + +
+ + +
+Ftrack: Role names are not case sensitive in ftrack event server status action #5058 + +Event server status action is not case sensitive for role names of user. + + +___ + +
+ + +
+Publisher: Fix border widget #5063 + +Fixed border lines in Publisher UI to be painted correctly with correct indentation and size. + + +___ + +
+ + +
+Unreal: Fix Commandlet Project and Permissions #5066 + +Fix problem when creating an Unreal Project when Commandlet Project is in a protected location. + + +___ + +
+ + +
+Unreal: Added verification for Unreal app name format #5070 + +The Unreal app name is used to determine the Unreal version folder, so it is necessary that if follows the format `x-x`, where `x` is any integer. This PR adds a verification that the app name follows that format. + + +___ + +
+ +### **πŸ“ƒ Documentation** + + +
+Docs: Display wrong image in ExtractOIIOTranscode #5045 + +Wrong image display in `https://openpype.io/docs/project_settings/settings_project_global#extract-oiio-transcode`. + + +___ + +
+ +### **Merged pull requests** + + +
+Drop-down menu to list all families in create placeholder #4928 + +Currently in the create placeholder window, we need to write the family manually. This replace the text field by an enum field with all families for the current software. + + +___ + +
+ + +
+add sync to specific projects or listen only #4919 + +Extend kitsu sync service with additional arguments to sync specific projects. + + +___ + +
+ + + + ## [3.15.8](https://github.com/ynput/OpenPype/tree/3.15.8) diff --git a/openpype/version.py b/openpype/version.py index 5c7105e7e0..dd23138dee 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.15.9-nightly.2" +__version__ = "3.15.9" diff --git a/pyproject.toml b/pyproject.toml index a72a3d66d7..633899d3a0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.15.8" # OpenPype +version = "3.15.9" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 776dcb5a6fcf65eec08f95655c870d7e43ec4ce9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 31 May 2023 13:44:50 +0000 Subject: [PATCH 49/50] chore(): update bug report / version --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 0036e121b7..aa5b8decdc 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,7 @@ body: label: Version description: What version are you running? Look to OpenPype Tray options: + - 3.15.9 - 3.15.9-nightly.2 - 3.15.9-nightly.1 - 3.15.8 @@ -134,7 +135,6 @@ body: - 3.14.3-nightly.1 - 3.14.2 - 3.14.2-nightly.5 - - 3.14.2-nightly.4 validations: required: true - type: dropdown From 3955c466c3a0f01e20bf95bfa594b561884517e5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 31 May 2023 17:17:35 +0200 Subject: [PATCH 50/50] fix apply settings on hiero loader (#5073) --- openpype/hosts/hiero/plugins/load/load_clip.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/hiero/plugins/load/load_clip.py b/openpype/hosts/hiero/plugins/load/load_clip.py index 77844d2448..c9bebfa8b2 100644 --- a/openpype/hosts/hiero/plugins/load/load_clip.py +++ b/openpype/hosts/hiero/plugins/load/load_clip.py @@ -41,8 +41,8 @@ class LoadClip(phiero.SequenceLoader): clip_name_template = "{asset}_{subset}_{representation}" + @classmethod def apply_settings(cls, project_settings, system_settings): - plugin_type_settings = ( project_settings .get("hiero", {})