From 507adfa9368f24d9eaff47e5d386cad08671ab9e Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Mon, 5 Jun 2023 18:39:32 +0200 Subject: [PATCH 001/107] Refactor: Blender new publisher --- openpype/hosts/blender/api/ops.py | 6 ++++- openpype/hosts/blender/api/plugin.py | 25 ++++++++++++++++--- .../blender/plugins/create/create_action.py | 2 +- .../plugins/create/create_animation.py | 2 +- .../blender/plugins/create/create_camera.py | 2 +- .../blender/plugins/create/create_layout.py | 2 +- .../blender/plugins/create/create_model.py | 2 +- .../plugins/create/create_pointcache.py | 2 +- .../blender/plugins/create/create_review.py | 2 +- .../blender/plugins/create/create_rig.py | 2 +- .../blender/plugins/publish/extract_blend.py | 5 +++- 11 files changed, 39 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/blender/api/ops.py b/openpype/hosts/blender/api/ops.py index 208c11cfe8..22c590d4bd 100644 --- a/openpype/hosts/blender/api/ops.py +++ b/openpype/hosts/blender/api/ops.py @@ -275,6 +275,10 @@ class LaunchCreator(LaunchQtApp): def before_window_show(self): self._window.refresh() + def execute(self, context): + host_tools.show_publisher(tab="create") + return {"FINISHED"} + class LaunchLoader(LaunchQtApp): """Launch Avalon Loader.""" @@ -299,7 +303,7 @@ class LaunchPublisher(LaunchQtApp): bl_label = "Publish..." def execute(self, context): - host_tools.show_publish() + host_tools.show_publisher(tab="publish") return {"FINISHED"} diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index fb87d08cce..221c8d8936 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -6,7 +6,8 @@ from typing import Dict, List, Optional import bpy from openpype.pipeline import ( - LegacyCreator, + Creator, + CreatedInstance, LoaderPlugin, ) from .pipeline import AVALON_CONTAINERS @@ -134,10 +135,11 @@ def deselect_all(): bpy.context.view_layer.objects.active = active -class Creator(LegacyCreator): - """Base class for Creator plug-ins.""" +class BlenderCreator(Creator): + """Base class for Blender Creator plug-ins.""" defaults = ['Main'] + # Deprecated? def process(self): collection = bpy.data.collections.new(name=self.data["subset"]) bpy.context.scene.collection.children.link(collection) @@ -150,6 +152,23 @@ class Creator(LegacyCreator): return collection + def create( + self, subset_name: str, instance_data: dict, pre_create_data: dict + ): + """Override abstract method from Creator. + Create new instance and store it. + + Args: + subset_name(str): Subset name of created instance. + instance_data(dict): Base data for instance. + pre_create_data(dict): Data based on pre creation attributes. + Those may affect how creator works. + """ + instance = CreatedInstance( + self.family, subset_name, instance_data + ) + + class Loader(LoaderPlugin): """Base class for Loader plug-ins.""" diff --git a/openpype/hosts/blender/plugins/create/create_action.py b/openpype/hosts/blender/plugins/create/create_action.py index 0203ba74c0..effbccd430 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -7,7 +7,7 @@ import openpype.hosts.blender.api.plugin from openpype.hosts.blender.api import lib -class CreateAction(openpype.hosts.blender.api.plugin.Creator): +class CreateAction(openpype.hosts.blender.api.plugin.BlenderCreator): """Action output for character rigs""" name = "actionMain" diff --git a/openpype/hosts/blender/plugins/create/create_animation.py b/openpype/hosts/blender/plugins/create/create_animation.py index bc2840952b..1b9bbcacd9 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -7,7 +7,7 @@ from openpype.hosts.blender.api import plugin, lib, ops from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES -class CreateAnimation(plugin.Creator): +class CreateAnimation(plugin.BlenderCreator): """Animation output for character rigs""" name = "animationMain" diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index 7a770a3e77..c72f2b92ff 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -7,7 +7,7 @@ from openpype.hosts.blender.api import plugin, lib, ops from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES -class CreateCamera(plugin.Creator): +class CreateCamera(plugin.BlenderCreator): """Polygonal static geometry""" name = "cameraMain" diff --git a/openpype/hosts/blender/plugins/create/create_layout.py b/openpype/hosts/blender/plugins/create/create_layout.py index 73ed683256..ba75df6735 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -7,7 +7,7 @@ from openpype.hosts.blender.api import plugin, lib, ops from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES -class CreateLayout(plugin.Creator): +class CreateLayout(plugin.BlenderCreator): """Layout output for character rigs""" name = "layoutMain" diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index 51fc6683f6..a7e71622ea 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -7,7 +7,7 @@ from openpype.hosts.blender.api import plugin, lib, ops from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES -class CreateModel(plugin.Creator): +class CreateModel(plugin.BlenderCreator): """Polygonal static geometry""" name = "modelMain" diff --git a/openpype/hosts/blender/plugins/create/create_pointcache.py b/openpype/hosts/blender/plugins/create/create_pointcache.py index 65cf18472d..0555d956de 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -7,7 +7,7 @@ from openpype.hosts.blender.api import plugin, lib, ops from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES -class CreatePointcache(plugin.Creator): +class CreatePointcache(plugin.BlenderCreator): """Polygonal static geometry""" name = "pointcacheMain" diff --git a/openpype/hosts/blender/plugins/create/create_review.py b/openpype/hosts/blender/plugins/create/create_review.py index 914f249891..58c26e0324 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -7,7 +7,7 @@ from openpype.hosts.blender.api import plugin, lib, ops from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES -class CreateReview(plugin.Creator): +class CreateReview(plugin.BlenderCreator): """Single baked camera""" name = "reviewDefault" diff --git a/openpype/hosts/blender/plugins/create/create_rig.py b/openpype/hosts/blender/plugins/create/create_rig.py index 08cc46ee3e..7a0393f0ba 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -7,7 +7,7 @@ from openpype.hosts.blender.api import plugin, lib, ops from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES -class CreateRig(plugin.Creator): +class CreateRig(plugin.BlenderCreator): """Artist-friendly rig with controls to direct motion""" name = "rigMain" diff --git a/openpype/hosts/blender/plugins/publish/extract_blend.py b/openpype/hosts/blender/plugins/publish/extract_blend.py index d4f26b4f3c..fba9a861a0 100644 --- a/openpype/hosts/blender/plugins/publish/extract_blend.py +++ b/openpype/hosts/blender/plugins/publish/extract_blend.py @@ -5,7 +5,7 @@ import bpy from openpype.pipeline import publish -class ExtractBlend(publish.Extractor): +class ExtractBlend(publish.Extractor, publish.OptionalPyblishPluginMixin): """Extract a blend file.""" label = "Extract Blend" @@ -16,6 +16,9 @@ class ExtractBlend(publish.Extractor): def process(self, instance): # Define extract output file path + if not self.is_active(instance.data): + return + stagingdir = self.staging_dir(instance) filename = f"{instance.name}.blend" filepath = os.path.join(stagingdir, filename) From e06dfbb8e8dbf1a92f2164a0b929968384d1aaff Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Tue, 6 Jun 2023 16:09:09 +0200 Subject: [PATCH 002/107] draft implementation for blender creator refactor --- openpype/hosts/blender/api/plugin.py | 75 ++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 221c8d8936..39d0f5e662 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -152,6 +152,29 @@ class BlenderCreator(Creator): return collection + @staticmethod + def cache_subsets(shared_data): + """Cache instances for Creators shared data. + + Create `blender_cached_subsets` key when needed in shared data and + fill it with all collected instances from the scene under its + respective creator identifiers. + + If legacy instances are detected in the scene, create + `blender_cached_legacy_subsets` key and fill it with + all legacy subsets from this family as a value. # key or value? + + Args: + shared_data(Dict[str, Any]): Shared data. + + Return: + Dict[str, Any]: Shared data with cached subsets. + """ + if not shared_data.get('blender_cached_subsets'): + cache = {} + cache_legacy = {} + + def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): @@ -168,6 +191,58 @@ class BlenderCreator(Creator): self.family, subset_name, instance_data ) + collection = bpy.data.collections.new(name=self.data['subset']) + bpy.context.scene.collection.children.link(collection) + + if (self.options or {}).get("useSelection"): + for obj in get_selection(): + collection.objects.link(obj) + + + def collect_instances(self): + """Override abstract method from BaseCreator. + Collect existing instances related to this creator plugin.""" + for ( + instance_data in self.cache_subsets( + self.collection_shared_data + ).get('blender_cached_subsets') + ): + # Process only instances that were created by this creator + creator_id = instance_data.get('creator_identifier') + + if creator_id == self.identifier: + # Create instance object from existing data + instance = CreatedInstance.from_existing( + instance_data, self + ) + + # Add instance to create context + self.add_instance_to_context(instance) + + + def update_instances(self, update_list): + """Override abstract method from BaseCreator. + Store changes of existing instances so they can be recollected. + + Args: + update_list(List[UpdateData]): Changed instances + and their changes, as a list of tuples.""" + for created_instance, _changes in update_list: + data = created_instance.data_to_store() + + # TODO + + + def remove_instances(self, instances: List[CreatedInstance]): + """Override abstract method from BaseCreator. + Method called when instances are removed. + + Args: + instance(List[CreatedInstance]): Instance objects to remove. + """ + for instance in instances: + self._remove_instance_from_context(instance) + class Loader(LoaderPlugin): """Base class for Loader plug-ins.""" From 3c198694a9be7dace95e5e027b604f23e3f2bdfe Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Tue, 6 Jun 2023 18:03:02 +0200 Subject: [PATCH 003/107] blender creator cache subsets --- openpype/hosts/blender/api/plugin.py | 59 +++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 39d0f5e662..9a982c45e7 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -10,7 +10,11 @@ from openpype.pipeline import ( CreatedInstance, LoaderPlugin, ) -from .pipeline import AVALON_CONTAINERS +from .pipeline import ( + AVALON_CONTAINERS, + AVALON_INSTANCES, + AVALON_PROPERTY, +) from .ops import ( MainThreadItem, execute_in_main_thread @@ -174,6 +178,44 @@ class BlenderCreator(Creator): cache = {} cache_legacy = {} + avalon_instances = bpy.data.collections.get(AVALON_INSTANCES) + if avalon_instances: + for obj in bpy.data.collections.get(AVALON_INSTANCES).objects: + avalon_prop = obj.get(AVALON_PROPERTY, {}) + if avalon_prop.get('id') == 'pyblish.avalon.instance': + creator_id = avalon_prop.get('creator_identifier') + + if creator_id: + # Creator instance + cache.setdefault(creator_id, []).append( + avalon_prop + ) + else: + family = avalon_prop.get('family') + if family: + # Legacy creator instance + cache_legacy.setdefault(family, []).append( + avalon_prop + ) + + for col in bpy.data.collections: + avalon_prop = col.get(AVALON_PROPERTY, {}) + if avalon_prop.get('id') == 'pyblish.avalon.instance': + creaor_id = avalon_prop.get('creator_identifier') + + if creator_id: + # Creator instance + cache.setdefault(creator_id, []).append(avalon_prop) + else: + family = avalon_prop.get('family') + if family: + cache_legacy.setdefault(family, []) + if family: + # Legacy creator instance + cache_legacy.setdefault(family, []).append( + avalon_prop + ) + def create( self, subset_name: str, instance_data: dict, pre_create_data: dict @@ -202,11 +244,16 @@ class BlenderCreator(Creator): def collect_instances(self): """Override abstract method from BaseCreator. Collect existing instances related to this creator plugin.""" - for ( - instance_data in self.cache_subsets( - self.collection_shared_data - ).get('blender_cached_subsets') - ): + + # Cache subsets in shared data + self.cache_subsets(self.collection_shared_data) + + # Get cached subsets + cached_subsets = self.collection_shared_data.get('blender_cached_subsets') + if not cached_subsets: + return + + for instance_data in cached_subsets: # Process only instances that were created by this creator creator_id = instance_data.get('creator_identifier') From d4c030e77d38b7db3fc50c98d0d43ca6d6beae8e Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Fri, 16 Jun 2023 11:16:42 +0200 Subject: [PATCH 004/107] Add identifiers to blender creators. Fix wrong method name --- openpype/hosts/blender/api/plugin.py | 2 +- openpype/hosts/blender/plugins/create/create_action.py | 1 + openpype/hosts/blender/plugins/create/create_animation.py | 1 + openpype/hosts/blender/plugins/create/create_camera.py | 1 + openpype/hosts/blender/plugins/create/create_layout.py | 1 + openpype/hosts/blender/plugins/create/create_model.py | 1 + openpype/hosts/blender/plugins/create/create_pointcache.py | 1 + openpype/hosts/blender/plugins/create/create_review.py | 1 + openpype/hosts/blender/plugins/create/create_rig.py | 1 + 9 files changed, 9 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 9a982c45e7..c13af363c5 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -264,7 +264,7 @@ class BlenderCreator(Creator): ) # Add instance to create context - self.add_instance_to_context(instance) + self._add_instance_to_context(instance) def update_instances(self, update_list): diff --git a/openpype/hosts/blender/plugins/create/create_action.py b/openpype/hosts/blender/plugins/create/create_action.py index effbccd430..5f4ded3688 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -10,6 +10,7 @@ from openpype.hosts.blender.api import lib class CreateAction(openpype.hosts.blender.api.plugin.BlenderCreator): """Action output for character rigs""" + identifier = "io.openpype.creators.blender.action" name = "actionMain" label = "Action" family = "action" diff --git a/openpype/hosts/blender/plugins/create/create_animation.py b/openpype/hosts/blender/plugins/create/create_animation.py index 1b9bbcacd9..277c588610 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -10,6 +10,7 @@ from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES class CreateAnimation(plugin.BlenderCreator): """Animation output for character rigs""" + identifier = "io.openpype.creators.blender.animation" name = "animationMain" label = "Animation" family = "animation" diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index c72f2b92ff..9086c44c5f 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -10,6 +10,7 @@ from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES class CreateCamera(plugin.BlenderCreator): """Polygonal static geometry""" + identifier = "io.openpype.creators.blender.camera" name = "cameraMain" label = "Camera" family = "camera" diff --git a/openpype/hosts/blender/plugins/create/create_layout.py b/openpype/hosts/blender/plugins/create/create_layout.py index ba75df6735..ae567e6495 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -10,6 +10,7 @@ from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES class CreateLayout(plugin.BlenderCreator): """Layout output for character rigs""" + identifier = "io.openpype.creators.blender.layout" name = "layoutMain" label = "Layout" family = "layout" diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index a7e71622ea..46196ab383 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -10,6 +10,7 @@ from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES class CreateModel(plugin.BlenderCreator): """Polygonal static geometry""" + identifier = "io.openpype.creators.blender.model" name = "modelMain" label = "Model" family = "model" diff --git a/openpype/hosts/blender/plugins/create/create_pointcache.py b/openpype/hosts/blender/plugins/create/create_pointcache.py index 0555d956de..4c434202c7 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -10,6 +10,7 @@ from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES class CreatePointcache(plugin.BlenderCreator): """Polygonal static geometry""" + identifier = "io.openpype.creators.blender.pointcache" name = "pointcacheMain" label = "Point Cache" family = "pointcache" diff --git a/openpype/hosts/blender/plugins/create/create_review.py b/openpype/hosts/blender/plugins/create/create_review.py index 58c26e0324..87774aed7a 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -10,6 +10,7 @@ from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES class CreateReview(plugin.BlenderCreator): """Single baked camera""" + identifier = "io.openpype.creators.blender.review" name = "reviewDefault" label = "Review" family = "review" diff --git a/openpype/hosts/blender/plugins/create/create_rig.py b/openpype/hosts/blender/plugins/create/create_rig.py index 7a0393f0ba..84924a659b 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -10,6 +10,7 @@ from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES class CreateRig(plugin.BlenderCreator): """Artist-friendly rig with controls to direct motion""" + identifier = "io.openpype.creators.blender.rig" name = "rigMain" label = "Rig" family = "rig" From efa294defcc625acdd36e7948bde3f75b15beb88 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Fri, 16 Jun 2023 17:54:28 +0200 Subject: [PATCH 005/107] fixed wrong variable name --- openpype/hosts/blender/api/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index c13af363c5..02436e3583 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -201,7 +201,7 @@ class BlenderCreator(Creator): for col in bpy.data.collections: avalon_prop = col.get(AVALON_PROPERTY, {}) if avalon_prop.get('id') == 'pyblish.avalon.instance': - creaor_id = avalon_prop.get('creator_identifier') + creator_id = avalon_prop.get('creator_identifier') if creator_id: # Creator instance From b681173dc5b2ee34862ffa41dbc3fc452c682a56 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Mon, 3 Jul 2023 17:44:36 +0200 Subject: [PATCH 006/107] draft blenderhost class implementation --- openpype/hosts/blender/api/__init__.py | 2 + openpype/hosts/blender/api/pipeline.py | 41 ++++++++++++++++++- .../blender/blender_addon/startup/init.py | 4 +- 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/blender/api/__init__.py b/openpype/hosts/blender/api/__init__.py index e15f1193a5..ce2b444997 100644 --- a/openpype/hosts/blender/api/__init__.py +++ b/openpype/hosts/blender/api/__init__.py @@ -10,6 +10,7 @@ from .pipeline import ( ls, publish, containerise, + BlenderHost, ) from .plugin import ( @@ -47,6 +48,7 @@ __all__ = [ "ls", "publish", "containerise", + "BlenderHost", "Creator", "Loader", diff --git a/openpype/hosts/blender/api/pipeline.py b/openpype/hosts/blender/api/pipeline.py index 84af0904f0..935981da86 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -6,10 +6,14 @@ from typing import Callable, Dict, Iterator, List, Optional import bpy from . import lib -from . import ops +from . import ops, properties import pyblish.api +from openpype.host import( + HostBase, + IPublishHost, +) from openpype.client import get_asset_by_name from openpype.pipeline import ( schema, @@ -47,6 +51,39 @@ IS_HEADLESS = bpy.app.background log = Logger.get_logger(__name__) +class BlenderHost(HostBase, IPublishHost): + name = "blender" + + def install(self): + """Override install method from HostBase. + Install Blender host functionality.""" + install() + + def ls(self) -> Iterator: + """List containers from active Blender scene.""" + return ls() + + def get_context_data(self): + """Override abstract method from IPublishHost. + Get global data related to creation-publishing from workfile. + + Returns: + dict: Context data stored using 'update_context_data'. + """ + return bpy.context.scene.openpype_context + + def update_context_data(self, data, changes): + """Override abstract method from IPublishHost. + Store global context data to workfile. + + Args: + data (dict): New data as are. + changes (dict): Only data that has been changed. Each value has + tuple with '(, )' value. + """ + bpy.context.scene.openpype_context.update(data) + + def pype_excepthook_handler(*args): traceback.print_exception(*args) @@ -72,6 +109,7 @@ def install(): if not IS_HEADLESS: ops.register() + properties.register() def uninstall(): @@ -86,6 +124,7 @@ def uninstall(): if not IS_HEADLESS: ops.unregister() + properties.unregister() def show_message(title, message): diff --git a/openpype/hosts/blender/blender_addon/startup/init.py b/openpype/hosts/blender/blender_addon/startup/init.py index 8dbff8a91d..603691675d 100644 --- a/openpype/hosts/blender/blender_addon/startup/init.py +++ b/openpype/hosts/blender/blender_addon/startup/init.py @@ -1,9 +1,9 @@ from openpype.pipeline import install_host -from openpype.hosts.blender import api +from openpype.hosts.blender.api import BlenderHost def register(): - install_host(api) + install_host(BlenderHost()) def unregister(): From 3b7ed1d71f86f6bd04029cc6ec0e369aff94c365 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Tue, 4 Jul 2023 10:55:04 +0200 Subject: [PATCH 007/107] Fix update_container_data error --- openpype/hosts/blender/api/pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/blender/api/pipeline.py b/openpype/hosts/blender/api/pipeline.py index 935981da86..968c70089b 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -81,7 +81,7 @@ class BlenderHost(HostBase, IPublishHost): changes (dict): Only data that has been changed. Each value has tuple with '(, )' value. """ - bpy.context.scene.openpype_context.update(data) + bpy.context.scene.openpype_context |= data def pype_excepthook_handler(*args): From 7bdbf0252211167c065aa8f3e622d48e725c0bf8 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Tue, 4 Jul 2023 16:07:44 +0200 Subject: [PATCH 008/107] Added properties to blender host --- openpype/hosts/blender/api/properties.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 openpype/hosts/blender/api/properties.py diff --git a/openpype/hosts/blender/api/properties.py b/openpype/hosts/blender/api/properties.py new file mode 100644 index 0000000000..ffc1dea733 --- /dev/null +++ b/openpype/hosts/blender/api/properties.py @@ -0,0 +1,23 @@ +import bpy +from bpy.utils import register_classes_factory + +class OpenpypeContext(bpy.types.PropertyGroup): + pass + +classes = [OpenpypeContext] + +factory_register, factory_unregister = register_classes_factory(classes) + +def register(): + """Register the properties.""" + factory_register() + + bpy.types.Scene.openpype_context = bpy.props.CollectionProperty( + name="OpenPype Context", type=OpenpypeContext, options={"HIDDEN"} + ) + +def unregister(): + """Unregister the properties.""" + factory_unregister() + + del bpy.types.Scene.openpype_context From d9960e84d44dab94ac5fec6bf45315ce79ab5d37 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Tue, 4 Jul 2023 17:54:40 +0200 Subject: [PATCH 009/107] Fixed errors during create --- openpype/hosts/blender/api/pipeline.py | 2 +- openpype/hosts/blender/api/plugin.py | 6 +++--- openpype/hosts/blender/api/properties.py | 9 +++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/blender/api/pipeline.py b/openpype/hosts/blender/api/pipeline.py index 968c70089b..935981da86 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -81,7 +81,7 @@ class BlenderHost(HostBase, IPublishHost): changes (dict): Only data that has been changed. Each value has tuple with '(, )' value. """ - bpy.context.scene.openpype_context |= data + bpy.context.scene.openpype_context.update(data) def pype_excepthook_handler(*args): diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 02436e3583..91068244c5 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -230,13 +230,13 @@ class BlenderCreator(Creator): Those may affect how creator works. """ instance = CreatedInstance( - self.family, subset_name, instance_data + self.family, subset_name, instance_data, self ) - collection = bpy.data.collections.new(name=self.data['subset']) + collection = bpy.data.collections.new(name=subset_name) bpy.context.scene.collection.children.link(collection) - if (self.options or {}).get("useSelection"): + if pre_create_data.get("useSelection"): for obj in get_selection(): collection.objects.link(obj) diff --git a/openpype/hosts/blender/api/properties.py b/openpype/hosts/blender/api/properties.py index ffc1dea733..c6b5ffe011 100644 --- a/openpype/hosts/blender/api/properties.py +++ b/openpype/hosts/blender/api/properties.py @@ -4,7 +4,7 @@ from bpy.utils import register_classes_factory class OpenpypeContext(bpy.types.PropertyGroup): pass -classes = [OpenpypeContext] +classes = [] # [OpenpypeContext] factory_register, factory_unregister = register_classes_factory(classes) @@ -12,9 +12,10 @@ def register(): """Register the properties.""" factory_register() - bpy.types.Scene.openpype_context = bpy.props.CollectionProperty( - name="OpenPype Context", type=OpenpypeContext, options={"HIDDEN"} - ) + bpy.types.Scene.openpype_context = {} + # bpy.types.Scene.openpype_context = bpy.props.CollectionProperty( + # name="OpenPype Context", type=OpenpypeContext, options={"HIDDEN"} + # ) def unregister(): """Unregister the properties.""" From cf66d12ef20feb2e1dcf326d4888434a3c506bdb Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Wed, 5 Jul 2023 17:36:42 +0200 Subject: [PATCH 010/107] Fixes to base creator, creators compatibility --- openpype/hosts/blender/api/plugin.py | 3 + .../blender/plugins/create/create_action.py | 27 +++++++++ .../plugins/create/create_animation.py | 47 +++++++++++++++- .../blender/plugins/create/create_camera.py | 56 ++++++++++++++++++- .../blender/plugins/create/create_layout.py | 45 ++++++++++++++- .../blender/plugins/create/create_model.py | 45 ++++++++++++++- .../plugins/create/create_pointcache.py | 22 ++++++++ .../blender/plugins/create/create_review.py | 43 +++++++++++++- .../blender/plugins/create/create_rig.py | 45 ++++++++++++++- 9 files changed, 315 insertions(+), 18 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 91068244c5..153897cb9a 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -216,6 +216,8 @@ class BlenderCreator(Creator): avalon_prop ) + return shared_data + def create( self, subset_name: str, instance_data: dict, pre_create_data: dict @@ -232,6 +234,7 @@ class BlenderCreator(Creator): instance = CreatedInstance( self.family, subset_name, instance_data, self ) + self._add_instance_to_context(instance) collection = bpy.data.collections.new(name=subset_name) bpy.context.scene.collection.children.link(collection) diff --git a/openpype/hosts/blender/plugins/create/create_action.py b/openpype/hosts/blender/plugins/create/create_action.py index 5f4ded3688..32e924d758 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -16,6 +16,33 @@ class CreateAction(openpype.hosts.blender.api.plugin.BlenderCreator): family = "action" icon = "male" + def create( + self, subset_name: str, instance_data: dict, pre_create_data: dict + ): + + name = openpype.hosts.blender.api.plugin.asset_name( + instance_data["asset"], subset_name + ) + collection = bpy.data.collections.new(name=name) + bpy.context.scene.collection.children.link(collection) + instance_data['task'] = get_current_task_name() + lib.imprint(collection, instance_data) + + if pre_create_data.get("useSelection"): + for obj in lib.get_selection(): + if (obj.animation_data is not None + and obj.animation_data.action is not None): + + empty_obj = bpy.data.objects.new(name=name, + object_data=None) + empty_obj.animation_data_create() + empty_obj.animation_data.action = obj.animation_data.action + empty_obj.animation_data.action.name = name + collection.objects.link(empty_obj) + + return collection + + # Deprecated def process(self): asset = self.data["asset"] diff --git a/openpype/hosts/blender/plugins/create/create_animation.py b/openpype/hosts/blender/plugins/create/create_animation.py index 277c588610..12478fd7e5 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -16,12 +16,53 @@ class CreateAnimation(plugin.BlenderCreator): family = "animation" icon = "male" - def process(self): + def create( + self, subset_name: str, instance_data: dict, pre_create_data: dict + ): """ Run the creator on Blender main thread""" - mti = ops.MainThreadItem(self._process) + mti = ops.MainThreadItem( + self._process, subset_name, instance_data, pre_create_data + ) ops.execute_in_main_thread(mti) - def _process(self): + def _process( + self, subset_name: str, instance_data: dict, pre_create_data: dict + ): + # Get Instance Container or create it if it does not exist + instances = bpy.data.collections.get(AVALON_INSTANCES) + if not instances: + instances = bpy.data.collections.new(name=AVALON_INSTANCES) + bpy.context.scene.collection.children.link(instances) + + # Create instance object + # name = self.name + # if not name: + name = plugin.asset_name(instance_data["asset"], subset_name) + # asset_group = bpy.data.objects.new(name=name, object_data=None) + # asset_group.empty_display_type = 'SINGLE_ARROW' + asset_group = bpy.data.collections.new(name=name) + instances.children.link(asset_group) + instance_data['task'] = get_current_task_name() + lib.imprint(asset_group, instance_data) + + if pre_create_data.get("useSelection"): + selected = lib.get_selection() + for obj in selected: + asset_group.objects.link(obj) + elif pre_create_data.get("asset_group"): + obj = (self.options or {}).get("asset_group") + asset_group.objects.link(obj) + + return asset_group + + # Deprecated + def process(self): + """ Run the creator on Blender main thread""" + mti = ops.MainThreadItem(self._process_legacy) + ops.execute_in_main_thread(mti) + + # Deprecated + def _process_legacy(self): # Get Instance Container or create it if it does not exist instances = bpy.data.collections.get(AVALON_INSTANCES) if not instances: diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index 9086c44c5f..6b9d1b7f73 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -16,12 +16,62 @@ class CreateCamera(plugin.BlenderCreator): family = "camera" icon = "video-camera" - def process(self): + def create( + self, subset_name: str, instance_data: dict, pre_create_data: dict + ): """ Run the creator on Blender main thread""" - mti = ops.MainThreadItem(self._process) + mti = ops.MainThreadItem( + self._process, subset_name, instance_data, pre_create_data + ) ops.execute_in_main_thread(mti) - def _process(self): + def _process( + self, subset_name: str, instance_data: dict, pre_create_data: dict + ): + # Get Instance Container or create it if it does not exist + instances = bpy.data.collections.get(AVALON_INSTANCES) + if not instances: + instances = bpy.data.collections.new(name=AVALON_INSTANCES) + bpy.context.scene.collection.children.link(instances) + + # Create instance object + name = plugin.asset_name(instance_data["asset"], subset_name) + + asset_group = bpy.data.objects.new(name=name, object_data=None) + asset_group.empty_display_type = 'SINGLE_ARROW' + instances.objects.link(asset_group) + instance_data['task'] = get_current_task_name() + lib.imprint(asset_group, instance_data) + + if pre_create_data.get("useSelection"): + bpy.context.view_layer.objects.active = asset_group + selected = lib.get_selection() + for obj in selected: + obj.select_set(True) + selected.append(asset_group) + bpy.ops.object.parent_set(keep_transform=True) + else: + plugin.deselect_all() + camera = bpy.data.cameras.new(subset_name) + camera_obj = bpy.data.objects.new(subset_name, camera) + + instances.objects.link(camera_obj) + + camera_obj.select_set(True) + asset_group.select_set(True) + bpy.context.view_layer.objects.active = asset_group + bpy.ops.object.parent_set(keep_transform=True) + + return asset_group + + # Deprecated + def process(self): + """ Run the creator on Blender main thread""" + mti = ops.MainThreadItem(self._process_legacy) + ops.execute_in_main_thread(mti) + + # Deprecated + def _process_legacy(self): # Get Instance Container or create it if it does not exist instances = bpy.data.collections.get(AVALON_INSTANCES) if not instances: diff --git a/openpype/hosts/blender/plugins/create/create_layout.py b/openpype/hosts/blender/plugins/create/create_layout.py index ae567e6495..24aa277349 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -16,12 +16,51 @@ class CreateLayout(plugin.BlenderCreator): family = "layout" icon = "cubes" - def process(self): + def create( + self, subset_name: str, instance_data: dict, pre_create_data: dict + ): """ Run the creator on Blender main thread""" - mti = ops.MainThreadItem(self._process) + mti = ops.MainThreadItem( + self._process, subset_name, instance_data, pre_create_data + ) ops.execute_in_main_thread(mti) - def _process(self): + def _process( + self, subset_name: str, instance_data: dict, pre_create_data: dict + ): + # Get Instance Container or create it if it does not exist + instances = bpy.data.collections.get(AVALON_INSTANCES) + if not instances: + instances = bpy.data.collections.new(name=AVALON_INSTANCES) + bpy.context.scene.collection.children.link(instances) + + # Create instance object + name = plugin.asset_name(instance_data["asset"], subset_name) + asset_group = bpy.data.objects.new(name=name, object_data=None) + asset_group.empty_display_type = 'SINGLE_ARROW' + instances.objects.link(asset_group) + instance_data['task'] = get_current_task_name() + lib.imprint(asset_group, instance_data) + + # Add selected objects to instance + if pre_create_data.get("useSelection"): + bpy.context.view_layer.objects.active = asset_group + selected = lib.get_selection() + for obj in selected: + obj.select_set(True) + selected.append(asset_group) + bpy.ops.object.parent_set(keep_transform=True) + + return asset_group + + # Deprecated + def process(self): + """ Run the creator on Blender main thread""" + mti = ops.MainThreadItem(self._process_legacy) + ops.execute_in_main_thread(mti) + + # Deprecated + def _process_legacy(self): # Get Instance Container or create it if it does not exist instances = bpy.data.collections.get(AVALON_INSTANCES) if not instances: diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index 46196ab383..2e713dd661 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -16,12 +16,51 @@ class CreateModel(plugin.BlenderCreator): family = "model" icon = "cube" - def process(self): + def create( + self, subset_name: str, instance_data: dict, pre_create_data: dict + ): """ Run the creator on Blender main thread""" - mti = ops.MainThreadItem(self._process) + mti = ops.MainThreadItem( + self._process, subset_name, instance_data, pre_create_data + ) ops.execute_in_main_thread(mti) - def _process(self): + def _process( + self, subset_name: str, instance_data: dict, pre_create_data: dict + ): + # Get Instance Container or create it if it does not exist + instances = bpy.data.collections.get(AVALON_INSTANCES) + if not instances: + instances = bpy.data.collections.new(name=AVALON_INSTANCES) + bpy.context.scene.collection.children.link(instances) + + # Create instance object + name = plugin.asset_name(instance_data["asset"], subset_name) + asset_group = bpy.data.objects.new(name=name, object_data=None) + asset_group.empty_display_type = 'SINGLE_ARROW' + instances.objects.link(asset_group) + instance_data['task'] = get_current_task_name() + lib.imprint(asset_group, instance_data) + + # Add selected objects to instance + if pre_create_data.get("useSelection"): + bpy.context.view_layer.objects.active = asset_group + selected = lib.get_selection() + for obj in selected: + obj.select_set(True) + selected.append(asset_group) + bpy.ops.object.parent_set(keep_transform=True) + + return asset_group + + # Deprecated + def process(self): + """ Run the creator on Blender main thread""" + mti = ops.MainThreadItem(self._process_legacy) + ops.execute_in_main_thread(mti) + + # Deprecated + def _process_legacy(self): # Get Instance Container or create it if it does not exist instances = bpy.data.collections.get(AVALON_INSTANCES) if not instances: diff --git a/openpype/hosts/blender/plugins/create/create_pointcache.py b/openpype/hosts/blender/plugins/create/create_pointcache.py index 4c434202c7..5932315bc8 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -16,6 +16,28 @@ class CreatePointcache(plugin.BlenderCreator): family = "pointcache" icon = "gears" + def create( + self, subset_name: str, instance_data: dict, pre_create_data: dict + ): + + name = openpype.hosts.blender.api.plugin.asset_name( + instance_data["asset"], subset_name + ) + collection = bpy.data.collections.new(name=name) + bpy.context.scene.collection.children.link(collection) + instance_data['task'] = get_current_task_name() + lib.imprint(collection, instance_data) + + if pre_create_data.get("useSelection"): + objects = lib.get_selection() + for obj in objects: + collection.objects.link(obj) + if obj.type == 'EMPTY': + objects.extend(obj.children) + + return collection + + # Deprecated def process(self): """ Run the creator on Blender main thread""" mti = ops.MainThreadItem(self._process) diff --git a/openpype/hosts/blender/plugins/create/create_review.py b/openpype/hosts/blender/plugins/create/create_review.py index 87774aed7a..4b5cfb4c35 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -16,12 +16,49 @@ class CreateReview(plugin.BlenderCreator): family = "review" icon = "video-camera" - def process(self): + def create( + self, subset_name: str, instance_data: dict, pre_create_data: dict + ): """ Run the creator on Blender main thread""" - mti = ops.MainThreadItem(self._process) + mti = ops.MainThreadItem( + self._process, subset_name, instance_data, pre_create_data + ) ops.execute_in_main_thread(mti) - def _process(self): + def _process( + self, subset_name: str, instance_data: dict, pre_create_data: dict + ): + # Get Instance Container or create it if it does not exist + instances = bpy.data.collections.get(AVALON_INSTANCES) + if not instances: + instances = bpy.data.collections.new(name=AVALON_INSTANCES) + bpy.context.scene.collection.children.link(instances) + + # Create instance object + name = plugin.asset_name(instance_data["asset"], subset_name) + asset_group = bpy.data.collections.new(name=name) + instances.children.link(asset_group) + instance_data['task'] = get_current_task_name() + lib.imprint(asset_group, instance_data) + + if pre_create_data.get("useSelection"): + selected = lib.get_selection() + for obj in selected: + asset_group.objects.link(obj) + elif pre_create_data.get("asset_group"): + obj = (self.options or {}).get("asset_group") + asset_group.objects.link(obj) + + return asset_group + + # Deprecated + def process(self): + """ Run the creator on Blender main thread""" + mti = ops.MainThreadItem(self._process_legacy) + ops.execute_in_main_thread(mti) + + # Deprecated + def _process_legacy(self): # Get Instance Container or create it if it does not exist instances = bpy.data.collections.get(AVALON_INSTANCES) if not instances: diff --git a/openpype/hosts/blender/plugins/create/create_rig.py b/openpype/hosts/blender/plugins/create/create_rig.py index 84924a659b..5ebf952d4a 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -16,12 +16,51 @@ class CreateRig(plugin.BlenderCreator): family = "rig" icon = "wheelchair" - def process(self): + def create( + self, subset_name: str, instance_data: dict, pre_create_data: dict + ): """ Run the creator on Blender main thread""" - mti = ops.MainThreadItem(self._process) + mti = ops.MainThreadItem( + self._process, subset_name, instance_data, pre_create_data + ) ops.execute_in_main_thread(mti) - def _process(self): + def _process( + self, subset_name: str, instance_data: dict, pre_create_data: dict + ): + # Get Instance Container or create it if it does not exist + instances = bpy.data.collections.get(AVALON_INSTANCES) + if not instances: + instances = bpy.data.collections.new(name=AVALON_INSTANCES) + bpy.context.scene.collection.children.link(instances) + + # Create instance object + name = plugin.asset_name(instance_data["asset"], subset_name) + asset_group = bpy.data.objects.new(name=name, object_data=None) + asset_group.empty_display_type = 'SINGLE_ARROW' + instances.objects.link(asset_group) + instance_data['task'] = get_current_task_name() + lib.imprint(asset_group, instance_data) + + # Add selected objects to instance + if pre_create_data.get("useSelection"): + bpy.context.view_layer.objects.active = asset_group + selected = lib.get_selection() + for obj in selected: + obj.select_set(True) + selected.append(asset_group) + bpy.ops.object.parent_set(keep_transform=True) + + return asset_group + + # Deprecated + def process(self): + """ Run the creator on Blender main thread""" + mti = ops.MainThreadItem(self._process_legacy) + ops.execute_in_main_thread(mti) + + # Deprecated + def _process_legacy(self): # Get Instance Container or create it if it does not exist instances = bpy.data.collections.get(AVALON_INSTANCES) if not instances: From e0e7c965856795776e86cfab83a582b2fd4c134d Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Mon, 10 Jul 2023 14:59:31 +0200 Subject: [PATCH 011/107] blender creator instances added to context --- openpype/hosts/blender/plugins/create/create_action.py | 5 ++++- openpype/hosts/blender/plugins/create/create_animation.py | 6 +++++- openpype/hosts/blender/plugins/create/create_camera.py | 6 +++++- openpype/hosts/blender/plugins/create/create_layout.py | 6 +++++- openpype/hosts/blender/plugins/create/create_model.py | 6 +++++- openpype/hosts/blender/plugins/create/create_pointcache.py | 7 +++++-- openpype/hosts/blender/plugins/create/create_review.py | 6 +++++- openpype/hosts/blender/plugins/create/create_rig.py | 6 +++++- 8 files changed, 39 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/blender/plugins/create/create_action.py b/openpype/hosts/blender/plugins/create/create_action.py index 32e924d758..6c2c6d98ce 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -2,7 +2,7 @@ import bpy -from openpype.pipeline import get_current_task_name +from openpype.pipeline import get_current_task_name, CreatedInstance import openpype.hosts.blender.api.plugin from openpype.hosts.blender.api import lib @@ -19,6 +19,9 @@ class CreateAction(openpype.hosts.blender.api.plugin.BlenderCreator): def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): + self._add_instance_to_context( + CreatedInstance(self.family, subset_name, instance_data, self) + ) name = openpype.hosts.blender.api.plugin.asset_name( instance_data["asset"], subset_name diff --git a/openpype/hosts/blender/plugins/create/create_animation.py b/openpype/hosts/blender/plugins/create/create_animation.py index 12478fd7e5..ec77569889 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -2,7 +2,7 @@ import bpy -from openpype.pipeline import get_current_task_name +from openpype.pipeline import get_current_task_name, CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES @@ -20,6 +20,10 @@ class CreateAnimation(plugin.BlenderCreator): self, subset_name: str, instance_data: dict, pre_create_data: dict ): """ Run the creator on Blender main thread""" + self._add_instance_to_context( + CreatedInstance(self.family, subset_name, instance_data, self) + ) + mti = ops.MainThreadItem( self._process, subset_name, instance_data, pre_create_data ) diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index 6b9d1b7f73..55ea07d90f 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -2,7 +2,7 @@ import bpy -from openpype.pipeline import get_current_task_name +from openpype.pipeline import get_current_task_name, CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES @@ -20,6 +20,10 @@ class CreateCamera(plugin.BlenderCreator): self, subset_name: str, instance_data: dict, pre_create_data: dict ): """ Run the creator on Blender main thread""" + self._add_instance_to_context( + CreatedInstance(self.family, subset_name, instance_data, self) + ) + mti = ops.MainThreadItem( self._process, subset_name, instance_data, pre_create_data ) diff --git a/openpype/hosts/blender/plugins/create/create_layout.py b/openpype/hosts/blender/plugins/create/create_layout.py index 24aa277349..60812d7fc9 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -2,7 +2,7 @@ import bpy -from openpype.pipeline import get_current_task_name +from openpype.pipeline import get_current_task_name, CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES @@ -20,6 +20,10 @@ class CreateLayout(plugin.BlenderCreator): self, subset_name: str, instance_data: dict, pre_create_data: dict ): """ Run the creator on Blender main thread""" + self._add_instance_to_context( + CreatedInstance(self.family, subset_name, instance_data, self) + ) + mti = ops.MainThreadItem( self._process, subset_name, instance_data, pre_create_data ) diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index 2e713dd661..59bbae5bc4 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -2,7 +2,7 @@ import bpy -from openpype.pipeline import get_current_task_name +from openpype.pipeline import get_current_task_name, CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES @@ -20,6 +20,10 @@ class CreateModel(plugin.BlenderCreator): self, subset_name: str, instance_data: dict, pre_create_data: dict ): """ Run the creator on Blender main thread""" + self._add_instance_to_context( + CreatedInstance(self.family, subset_name, instance_data, self) + ) + mti = ops.MainThreadItem( self._process, subset_name, instance_data, pre_create_data ) diff --git a/openpype/hosts/blender/plugins/create/create_pointcache.py b/openpype/hosts/blender/plugins/create/create_pointcache.py index 5932315bc8..bb843c5a22 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -2,7 +2,7 @@ import bpy -from openpype.pipeline import get_current_task_name +from openpype.pipeline import get_current_task_name, CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES @@ -19,8 +19,11 @@ class CreatePointcache(plugin.BlenderCreator): def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): + self._add_instance_to_context( + CreatedInstance(self.family, subset_name, instance_data, self) + ) - name = openpype.hosts.blender.api.plugin.asset_name( + name = plugin.asset_name( instance_data["asset"], subset_name ) collection = bpy.data.collections.new(name=name) diff --git a/openpype/hosts/blender/plugins/create/create_review.py b/openpype/hosts/blender/plugins/create/create_review.py index 4b5cfb4c35..1191fb95f1 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -2,7 +2,7 @@ import bpy -from openpype.pipeline import get_current_task_name +from openpype.pipeline import get_current_task_name, CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES @@ -20,6 +20,10 @@ class CreateReview(plugin.BlenderCreator): self, subset_name: str, instance_data: dict, pre_create_data: dict ): """ Run the creator on Blender main thread""" + self._add_instance_to_context( + CreatedInstance(self.family, subset_name, instance_data, self) + ) + mti = ops.MainThreadItem( self._process, subset_name, instance_data, pre_create_data ) diff --git a/openpype/hosts/blender/plugins/create/create_rig.py b/openpype/hosts/blender/plugins/create/create_rig.py index 5ebf952d4a..752f3f7b18 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -2,7 +2,7 @@ import bpy -from openpype.pipeline import get_current_task_name +from openpype.pipeline import get_current_task_name, CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES @@ -20,6 +20,10 @@ class CreateRig(plugin.BlenderCreator): self, subset_name: str, instance_data: dict, pre_create_data: dict ): """ Run the creator on Blender main thread""" + self._add_instance_to_context( + CreatedInstance(self.family, subset_name, instance_data, self) + ) + mti = ops.MainThreadItem( self._process, subset_name, instance_data, pre_create_data ) From 6eae2c264d57b4ed5395de03950e09bc54ae7acb Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Wed, 12 Jul 2023 15:54:41 +0200 Subject: [PATCH 012/107] Add dict to imprint, type hints in blenderhost, imprint in create --- openpype/hosts/blender/api/lib.py | 2 +- openpype/hosts/blender/api/pipeline.py | 4 ++-- openpype/hosts/blender/api/plugin.py | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/blender/api/lib.py b/openpype/hosts/blender/api/lib.py index 9bb560c364..76bef09ceb 100644 --- a/openpype/hosts/blender/api/lib.py +++ b/openpype/hosts/blender/api/lib.py @@ -188,7 +188,7 @@ def imprint(node: bpy.types.bpy_struct_meta_idprop, data: Dict): # Support values evaluated at imprint value = value() - if not isinstance(value, (int, float, bool, str, list)): + if not isinstance(value, (int, float, bool, str, list, dict)): raise TypeError(f"Unsupported type: {type(value)}") imprint_data[key] = value diff --git a/openpype/hosts/blender/api/pipeline.py b/openpype/hosts/blender/api/pipeline.py index 935981da86..4c06b1959e 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -63,7 +63,7 @@ class BlenderHost(HostBase, IPublishHost): """List containers from active Blender scene.""" return ls() - def get_context_data(self): + def get_context_data(self) -> dict: """Override abstract method from IPublishHost. Get global data related to creation-publishing from workfile. @@ -72,7 +72,7 @@ class BlenderHost(HostBase, IPublishHost): """ return bpy.context.scene.openpype_context - def update_context_data(self, data, changes): + def update_context_data(self, data: dict, changes: dict): """Override abstract method from IPublishHost. Store global context data to workfile. diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 153897cb9a..fe1e4ce81c 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -238,6 +238,7 @@ class BlenderCreator(Creator): collection = bpy.data.collections.new(name=subset_name) bpy.context.scene.collection.children.link(collection) + imprint(collection, instance_data) if pre_create_data.get("useSelection"): for obj in get_selection(): From 7617d1e0ad63042d39a3c626f6e614100d2bd879 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Wed, 19 Jul 2023 20:28:45 +0200 Subject: [PATCH 013/107] Set shared data, edit loop in cached_subsets --- openpype/hosts/blender/api/plugin.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index fe1e4ce81c..97a3dfa6a6 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -216,6 +216,9 @@ class BlenderCreator(Creator): avalon_prop ) + shared_data["blender_cached_subsets"] = cache + shared_data["blender_cached_legacy_subsets"] = cache_legacy + return shared_data @@ -257,14 +260,17 @@ class BlenderCreator(Creator): if not cached_subsets: return - for instance_data in cached_subsets: + for instance_data in cached_subsets.get(self.identifier, []): # Process only instances that were created by this creator + data = dict() + for key, value in instance_data.items(): + data[key] = value creator_id = instance_data.get('creator_identifier') if creator_id == self.identifier: # Create instance object from existing data instance = CreatedInstance.from_existing( - instance_data, self + data, self ) # Add instance to create context From aeebd78cc71fb3edbe51157c60da849f62e727f3 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Thu, 20 Jul 2023 15:31:23 +0200 Subject: [PATCH 014/107] Collect instances functional --- openpype/hosts/blender/api/plugin.py | 4 ++-- openpype/hosts/blender/plugins/create/create_action.py | 3 +++ openpype/hosts/blender/plugins/create/create_animation.py | 3 +++ openpype/hosts/blender/plugins/create/create_camera.py | 3 +++ openpype/hosts/blender/plugins/create/create_layout.py | 3 +++ openpype/hosts/blender/plugins/create/create_model.py | 3 +++ openpype/hosts/blender/plugins/create/create_pointcache.py | 3 +++ openpype/hosts/blender/plugins/create/create_review.py | 3 +++ openpype/hosts/blender/plugins/create/create_rig.py | 3 +++ 9 files changed, 26 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 97a3dfa6a6..5dd352ff6c 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -262,10 +262,10 @@ class BlenderCreator(Creator): for instance_data in cached_subsets.get(self.identifier, []): # Process only instances that were created by this creator - data = dict() + data = {} for key, value in instance_data.items(): data[key] = value - creator_id = instance_data.get('creator_identifier') + creator_id = data.get('creator_identifier') if creator_id == self.identifier: # Create instance object from existing data diff --git a/openpype/hosts/blender/plugins/create/create_action.py b/openpype/hosts/blender/plugins/create/create_action.py index 6c2c6d98ce..2c0d248994 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -29,6 +29,9 @@ class CreateAction(openpype.hosts.blender.api.plugin.BlenderCreator): collection = bpy.data.collections.new(name=name) bpy.context.scene.collection.children.link(collection) instance_data['task'] = get_current_task_name() + instance_data['id'] = 'pyblish.avalon.instance' + instance_data['creator_identifier'] = self.identifier + instance_data['label'] = self.label lib.imprint(collection, instance_data) if pre_create_data.get("useSelection"): diff --git a/openpype/hosts/blender/plugins/create/create_animation.py b/openpype/hosts/blender/plugins/create/create_animation.py index ec77569889..538c0455ac 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -47,6 +47,9 @@ class CreateAnimation(plugin.BlenderCreator): asset_group = bpy.data.collections.new(name=name) instances.children.link(asset_group) instance_data['task'] = get_current_task_name() + instance_data['id'] = 'pyblish.avalon.instance' + instance_data['creator_identifier'] = self.identifier + instance_data['label'] = self.label lib.imprint(asset_group, instance_data) if pre_create_data.get("useSelection"): diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index 55ea07d90f..ae85273353 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -45,6 +45,9 @@ class CreateCamera(plugin.BlenderCreator): asset_group.empty_display_type = 'SINGLE_ARROW' instances.objects.link(asset_group) instance_data['task'] = get_current_task_name() + instance_data['id'] = 'pyblish.avalon.instance' + instance_data['creator_identifier'] = self.identifier + instance_data['label'] = self.label lib.imprint(asset_group, instance_data) if pre_create_data.get("useSelection"): diff --git a/openpype/hosts/blender/plugins/create/create_layout.py b/openpype/hosts/blender/plugins/create/create_layout.py index 60812d7fc9..cce422b229 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -44,6 +44,9 @@ class CreateLayout(plugin.BlenderCreator): asset_group.empty_display_type = 'SINGLE_ARROW' instances.objects.link(asset_group) instance_data['task'] = get_current_task_name() + instance_data['id'] = 'pyblish.avalon.instance' + instance_data['creator_identifier'] = self.identifier + instance_data['label'] = self.label lib.imprint(asset_group, instance_data) # Add selected objects to instance diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index 59bbae5bc4..57f9e79aa1 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -44,6 +44,9 @@ class CreateModel(plugin.BlenderCreator): asset_group.empty_display_type = 'SINGLE_ARROW' instances.objects.link(asset_group) instance_data['task'] = get_current_task_name() + instance_data['id'] = 'pyblish.avalon.instance' + instance_data['creator_identifier'] = self.identifier + instance_data['label'] = self.label lib.imprint(asset_group, instance_data) # Add selected objects to instance diff --git a/openpype/hosts/blender/plugins/create/create_pointcache.py b/openpype/hosts/blender/plugins/create/create_pointcache.py index bb843c5a22..f95f79ae78 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -29,6 +29,9 @@ class CreatePointcache(plugin.BlenderCreator): collection = bpy.data.collections.new(name=name) bpy.context.scene.collection.children.link(collection) instance_data['task'] = get_current_task_name() + instance_data['id'] = 'pyblish.avalon.instance' + instance_data['creator_identifier'] = self.identifier + instance_data['label'] = self.label lib.imprint(collection, instance_data) if pre_create_data.get("useSelection"): diff --git a/openpype/hosts/blender/plugins/create/create_review.py b/openpype/hosts/blender/plugins/create/create_review.py index 1191fb95f1..8472600c2f 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -43,6 +43,9 @@ class CreateReview(plugin.BlenderCreator): asset_group = bpy.data.collections.new(name=name) instances.children.link(asset_group) instance_data['task'] = get_current_task_name() + instance_data['id'] = 'pyblish.avalon.instance' + instance_data['creator_identifier'] = self.identifier + instance_data['label'] = self.label lib.imprint(asset_group, instance_data) if pre_create_data.get("useSelection"): diff --git a/openpype/hosts/blender/plugins/create/create_rig.py b/openpype/hosts/blender/plugins/create/create_rig.py index 752f3f7b18..a60f2a72ee 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -44,6 +44,9 @@ class CreateRig(plugin.BlenderCreator): asset_group.empty_display_type = 'SINGLE_ARROW' instances.objects.link(asset_group) instance_data['task'] = get_current_task_name() + instance_data['id'] = 'pyblish.avalon.instance' + instance_data['creator_identifier'] = self.identifier + instance_data['label'] = self.label lib.imprint(asset_group, instance_data) # Add selected objects to instance From 4d03c1c0e60b8e32248a4bb96a326c7c3a3762fb Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Thu, 20 Jul 2023 18:26:59 +0200 Subject: [PATCH 015/107] Set subset in inst data, use update to set it --- .../hosts/blender/plugins/create/create_action.py | 13 +++++++++---- .../blender/plugins/create/create_animation.py | 13 +++++++++---- .../hosts/blender/plugins/create/create_camera.py | 13 +++++++++---- .../hosts/blender/plugins/create/create_layout.py | 13 +++++++++---- .../hosts/blender/plugins/create/create_model.py | 13 +++++++++---- .../blender/plugins/create/create_pointcache.py | 13 +++++++++---- .../hosts/blender/plugins/create/create_review.py | 13 +++++++++---- openpype/hosts/blender/plugins/create/create_rig.py | 13 +++++++++---- 8 files changed, 72 insertions(+), 32 deletions(-) diff --git a/openpype/hosts/blender/plugins/create/create_action.py b/openpype/hosts/blender/plugins/create/create_action.py index 2c0d248994..6951e86c46 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -28,10 +28,15 @@ class CreateAction(openpype.hosts.blender.api.plugin.BlenderCreator): ) collection = bpy.data.collections.new(name=name) bpy.context.scene.collection.children.link(collection) - instance_data['task'] = get_current_task_name() - instance_data['id'] = 'pyblish.avalon.instance' - instance_data['creator_identifier'] = self.identifier - instance_data['label'] = self.label + instance_data.update( + { + "id": "pyblish.avalon.instance", + "creator_identifier": self.identifier, + "label": self.label, + "task": get_current_task_name(), + "subset": subset_name, + } + ) lib.imprint(collection, instance_data) if pre_create_data.get("useSelection"): diff --git a/openpype/hosts/blender/plugins/create/create_animation.py b/openpype/hosts/blender/plugins/create/create_animation.py index 538c0455ac..9e7dfbaf84 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -46,10 +46,15 @@ class CreateAnimation(plugin.BlenderCreator): # asset_group.empty_display_type = 'SINGLE_ARROW' asset_group = bpy.data.collections.new(name=name) instances.children.link(asset_group) - instance_data['task'] = get_current_task_name() - instance_data['id'] = 'pyblish.avalon.instance' - instance_data['creator_identifier'] = self.identifier - instance_data['label'] = self.label + instance_data.update( + { + "id": "pyblish.avalon.instance", + "creator_identifier": self.identifier, + "label": self.label, + "task": get_current_task_name(), + "subset": subset_name, + } + ) lib.imprint(asset_group, instance_data) if pre_create_data.get("useSelection"): diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index ae85273353..c5987779c0 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -44,10 +44,15 @@ class CreateCamera(plugin.BlenderCreator): asset_group = bpy.data.objects.new(name=name, object_data=None) asset_group.empty_display_type = 'SINGLE_ARROW' instances.objects.link(asset_group) - instance_data['task'] = get_current_task_name() - instance_data['id'] = 'pyblish.avalon.instance' - instance_data['creator_identifier'] = self.identifier - instance_data['label'] = self.label + instance_data.update( + { + "id": "pyblish.avalon.instance", + "creator_identifier": self.identifier, + "label": self.label, + "task": get_current_task_name(), + "subset": subset_name, + } + ) lib.imprint(asset_group, instance_data) if pre_create_data.get("useSelection"): diff --git a/openpype/hosts/blender/plugins/create/create_layout.py b/openpype/hosts/blender/plugins/create/create_layout.py index cce422b229..cb61799f4a 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -43,10 +43,15 @@ class CreateLayout(plugin.BlenderCreator): asset_group = bpy.data.objects.new(name=name, object_data=None) asset_group.empty_display_type = 'SINGLE_ARROW' instances.objects.link(asset_group) - instance_data['task'] = get_current_task_name() - instance_data['id'] = 'pyblish.avalon.instance' - instance_data['creator_identifier'] = self.identifier - instance_data['label'] = self.label + instance_data.update( + { + "id": "pyblish.avalon.instance", + "creator_identifier": self.identifier, + "label": self.label, + "task": get_current_task_name(), + "subset": subset_name, + } + ) lib.imprint(asset_group, instance_data) # Add selected objects to instance diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index 57f9e79aa1..ccf3668d98 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -43,10 +43,15 @@ class CreateModel(plugin.BlenderCreator): asset_group = bpy.data.objects.new(name=name, object_data=None) asset_group.empty_display_type = 'SINGLE_ARROW' instances.objects.link(asset_group) - instance_data['task'] = get_current_task_name() - instance_data['id'] = 'pyblish.avalon.instance' - instance_data['creator_identifier'] = self.identifier - instance_data['label'] = self.label + instance_data.update( + { + "id": "pyblish.avalon.instance", + "creator_identifier": self.identifier, + "label": self.label, + "task": get_current_task_name(), + "subset": subset_name, + } + ) lib.imprint(asset_group, instance_data) # Add selected objects to instance diff --git a/openpype/hosts/blender/plugins/create/create_pointcache.py b/openpype/hosts/blender/plugins/create/create_pointcache.py index f95f79ae78..e27cb22389 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -28,10 +28,15 @@ class CreatePointcache(plugin.BlenderCreator): ) collection = bpy.data.collections.new(name=name) bpy.context.scene.collection.children.link(collection) - instance_data['task'] = get_current_task_name() - instance_data['id'] = 'pyblish.avalon.instance' - instance_data['creator_identifier'] = self.identifier - instance_data['label'] = self.label + instance_data.update( + { + "id": "pyblish.avalon.instance", + "creator_identifier": self.identifier, + "label": self.label, + "task": get_current_task_name(), + "subset": subset_name, + } + ) lib.imprint(collection, instance_data) if pre_create_data.get("useSelection"): diff --git a/openpype/hosts/blender/plugins/create/create_review.py b/openpype/hosts/blender/plugins/create/create_review.py index 8472600c2f..afeaea951b 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -42,10 +42,15 @@ class CreateReview(plugin.BlenderCreator): name = plugin.asset_name(instance_data["asset"], subset_name) asset_group = bpy.data.collections.new(name=name) instances.children.link(asset_group) - instance_data['task'] = get_current_task_name() - instance_data['id'] = 'pyblish.avalon.instance' - instance_data['creator_identifier'] = self.identifier - instance_data['label'] = self.label + instance_data.update( + { + "id": "pyblish.avalon.instance", + "creator_identifier": self.identifier, + "label": self.label, + "task": get_current_task_name(), + "subset": subset_name, + } + ) lib.imprint(asset_group, instance_data) if pre_create_data.get("useSelection"): diff --git a/openpype/hosts/blender/plugins/create/create_rig.py b/openpype/hosts/blender/plugins/create/create_rig.py index a60f2a72ee..2db766f7ed 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -43,10 +43,15 @@ class CreateRig(plugin.BlenderCreator): asset_group = bpy.data.objects.new(name=name, object_data=None) asset_group.empty_display_type = 'SINGLE_ARROW' instances.objects.link(asset_group) - instance_data['task'] = get_current_task_name() - instance_data['id'] = 'pyblish.avalon.instance' - instance_data['creator_identifier'] = self.identifier - instance_data['label'] = self.label + instance_data.update( + { + "id": "pyblish.avalon.instance", + "creator_identifier": self.identifier, + "label": self.label, + "task": get_current_task_name(), + "subset": subset_name, + } + ) lib.imprint(asset_group, instance_data) # Add selected objects to instance From 8236e46ca93b562feb66df51a81ec74c6785586f Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Mon, 31 Jul 2023 17:16:40 +0200 Subject: [PATCH 016/107] implement IWorkfileHost for Blender host --- openpype/hosts/blender/api/pipeline.py | 70 +++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/blender/api/pipeline.py b/openpype/hosts/blender/api/pipeline.py index 4c06b1959e..c3f8f06694 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -12,6 +12,7 @@ import pyblish.api from openpype.host import( HostBase, + IWorkfileHost, IPublishHost, ) from openpype.client import get_asset_by_name @@ -33,6 +34,14 @@ from openpype.lib import ( ) import openpype.hosts.blender from openpype.settings import get_project_settings +from .workio import ( + open_file, + save_file, + current_file, + has_unsaved_changes, + file_extensions, + work_root, +) HOST_DIR = os.path.dirname(os.path.abspath(openpype.hosts.blender.__file__)) @@ -51,7 +60,7 @@ IS_HEADLESS = bpy.app.background log = Logger.get_logger(__name__) -class BlenderHost(HostBase, IPublishHost): +class BlenderHost(HostBase, IWorkfileHost, IPublishHost): name = "blender" def install(self): @@ -63,6 +72,65 @@ class BlenderHost(HostBase, IPublishHost): """List containers from active Blender scene.""" return ls() + def get_workfile_extensions(self) -> List[str]: + """Override get_workfile_extensions method from IWorkfileHost. + Get workfile possible extensions. + + Returns: + List[str]: Workfile extensions. + """ + return file_extensions() + + def save_workfile(self, dst_path: str = None): + """Override save_workfile method from IWorkfileHost. + Save currently opened workfile. + + Args: + dst_path (str): Where the current scene should be saved. Or use + current path if `None` is passed. + """ + save_file(dst_path if dst_path else bpy.data.filepath) + + def open_workfile(self, filepath: str): + """Override open_workfile method from IWorkfileHost. + Open workfile at specified filepath in the host. + + Args: + filepath (str): Path to workfile. + """ + open_file(filepath) + + def get_current_workfile(self) -> str: + """Override get_current_workfile method from IWorkfileHost. + Retrieve currently opened workfile path. + + Returns: + str: Path to currently opened workfile. + """ + return current_file() + + def workfile_has_unsaved_changes(self) -> bool: + """Override wokfile_has_unsaved_changes method from IWorkfileHost. + Returns True if opened workfile has no unsaved changes. + + Returns: + bool: True if scene is saved and False if it has unsaved + modifications. + """ + return has_unsaved_changes() + + def work_root(self, session) -> str: + """Override work_root method from IWorkfileHost. + Modify workdir per host. + + Args: + session (dict): Session context data. + + Returns: + str: Path to new workdir. + """ + return work_root(session) + def get_context_data(self) -> dict: """Override abstract method from IPublishHost. Get global data related to creation-publishing from workfile. From e9d022bab37f67391267788c3de940c1db0f955d Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Wed, 2 Aug 2023 16:13:34 +0200 Subject: [PATCH 017/107] Handling optional plugins --- openpype/hosts/blender/plugins/publish/extract_abc.py | 2 +- .../blender/plugins/publish/extract_abc_animation.py | 5 ++++- .../blender/plugins/publish/extract_blend_animation.py | 5 ++++- .../blender/plugins/publish/extract_camera_abc.py | 2 +- .../blender/plugins/publish/extract_camera_fbx.py | 2 +- openpype/hosts/blender/plugins/publish/extract_fbx.py | 2 +- .../blender/plugins/publish/extract_fbx_animation.py | 5 ++++- .../hosts/blender/plugins/publish/extract_layout.py | 2 +- .../hosts/blender/plugins/publish/extract_playblast.py | 2 +- .../plugins/publish/increment_workfile_version.py | 6 +++++- .../blender/plugins/publish/integrate_animation.py | 6 +++++- .../blender/plugins/publish/validate_mesh_has_uv.py | 10 ++++++++-- .../blender/plugins/publish/validate_object_mode.py | 6 +++++- 13 files changed, 41 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/extract_abc.py b/openpype/hosts/blender/plugins/publish/extract_abc.py index 7b6c4d7ae7..5af8104344 100644 --- a/openpype/hosts/blender/plugins/publish/extract_abc.py +++ b/openpype/hosts/blender/plugins/publish/extract_abc.py @@ -7,7 +7,7 @@ from openpype.hosts.blender.api import plugin from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY -class ExtractABC(publish.Extractor): +class ExtractABC(publish.Extractor, publish.OptionalPyblishPluginMixin): """Extract as ABC.""" label = "Extract ABC" diff --git a/openpype/hosts/blender/plugins/publish/extract_abc_animation.py b/openpype/hosts/blender/plugins/publish/extract_abc_animation.py index 44b2ba3761..0b6b93b7a5 100644 --- a/openpype/hosts/blender/plugins/publish/extract_abc_animation.py +++ b/openpype/hosts/blender/plugins/publish/extract_abc_animation.py @@ -6,7 +6,10 @@ from openpype.pipeline import publish from openpype.hosts.blender.api import plugin -class ExtractAnimationABC(publish.Extractor): +class ExtractAnimationABC( + publish.Extractor, + publish.OptionalPyblishPluginMixin, +): """Extract as ABC.""" label = "Extract Animation ABC" diff --git a/openpype/hosts/blender/plugins/publish/extract_blend_animation.py b/openpype/hosts/blender/plugins/publish/extract_blend_animation.py index 477411b73d..3a5b788c9e 100644 --- a/openpype/hosts/blender/plugins/publish/extract_blend_animation.py +++ b/openpype/hosts/blender/plugins/publish/extract_blend_animation.py @@ -5,7 +5,10 @@ import bpy from openpype.pipeline import publish -class ExtractBlendAnimation(publish.Extractor): +class ExtractBlendAnimation( + publish.Extractor, + publish.OptionalPyblishPluginMixin, +): """Extract a blend file.""" label = "Extract Blend" diff --git a/openpype/hosts/blender/plugins/publish/extract_camera_abc.py b/openpype/hosts/blender/plugins/publish/extract_camera_abc.py index 036be7bf3c..2a327f4d65 100644 --- a/openpype/hosts/blender/plugins/publish/extract_camera_abc.py +++ b/openpype/hosts/blender/plugins/publish/extract_camera_abc.py @@ -7,7 +7,7 @@ from openpype.hosts.blender.api import plugin from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY -class ExtractCameraABC(publish.Extractor): +class ExtractCameraABC(publish.Extractor, publish.OptionalPyblishPluginMixin): """Extract camera as ABC.""" label = "Extract Camera (ABC)" diff --git a/openpype/hosts/blender/plugins/publish/extract_camera_fbx.py b/openpype/hosts/blender/plugins/publish/extract_camera_fbx.py index 315994140e..8e5e4d37d4 100644 --- a/openpype/hosts/blender/plugins/publish/extract_camera_fbx.py +++ b/openpype/hosts/blender/plugins/publish/extract_camera_fbx.py @@ -6,7 +6,7 @@ from openpype.pipeline import publish from openpype.hosts.blender.api import plugin -class ExtractCamera(publish.Extractor): +class ExtractCamera(publish.Extractor, publish.OptionalPyblishPluginMixin): """Extract as the camera as FBX.""" label = "Extract Camera (FBX)" diff --git a/openpype/hosts/blender/plugins/publish/extract_fbx.py b/openpype/hosts/blender/plugins/publish/extract_fbx.py index 0ad797c226..8ace6a43a7 100644 --- a/openpype/hosts/blender/plugins/publish/extract_fbx.py +++ b/openpype/hosts/blender/plugins/publish/extract_fbx.py @@ -7,7 +7,7 @@ from openpype.hosts.blender.api import plugin from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY -class ExtractFBX(publish.Extractor): +class ExtractFBX(publish.Extractor, publish.OptionalPyblishPluginMixin): """Extract as FBX.""" label = "Extract FBX" diff --git a/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py b/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py index 062b42e99d..04f50f8207 100644 --- a/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py @@ -10,7 +10,10 @@ from openpype.hosts.blender.api import plugin from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY -class ExtractAnimationFBX(publish.Extractor): +class ExtractAnimationFBX( + publish.Extractor, + publish.OptionalPyblishPluginMixin, +): """Extract as animation.""" label = "Extract FBX" diff --git a/openpype/hosts/blender/plugins/publish/extract_layout.py b/openpype/hosts/blender/plugins/publish/extract_layout.py index f2d04f1178..8445560bba 100644 --- a/openpype/hosts/blender/plugins/publish/extract_layout.py +++ b/openpype/hosts/blender/plugins/publish/extract_layout.py @@ -11,7 +11,7 @@ from openpype.hosts.blender.api import plugin from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY -class ExtractLayout(publish.Extractor): +class ExtractLayout(publish.Extractor, publish.OptionalPyblishPluginMixin): """Extract a layout.""" label = "Extract Layout" diff --git a/openpype/hosts/blender/plugins/publish/extract_playblast.py b/openpype/hosts/blender/plugins/publish/extract_playblast.py index 196e75b8cc..82ddbc1fc2 100644 --- a/openpype/hosts/blender/plugins/publish/extract_playblast.py +++ b/openpype/hosts/blender/plugins/publish/extract_playblast.py @@ -9,7 +9,7 @@ from openpype.hosts.blender.api import capture from openpype.hosts.blender.api.lib import maintained_time -class ExtractPlayblast(publish.Extractor): +class ExtractPlayblast(publish.Extractor, publish.OptionalPyblishPluginMixin): """ Extract viewport playblast. diff --git a/openpype/hosts/blender/plugins/publish/increment_workfile_version.py b/openpype/hosts/blender/plugins/publish/increment_workfile_version.py index 6ace14d77c..176668f366 100644 --- a/openpype/hosts/blender/plugins/publish/increment_workfile_version.py +++ b/openpype/hosts/blender/plugins/publish/increment_workfile_version.py @@ -1,8 +1,12 @@ import pyblish.api +from openpype.pipeline.publish import OptionalPyblishPluginMixin from openpype.hosts.blender.api.workio import save_file -class IncrementWorkfileVersion(pyblish.api.ContextPlugin): +class IncrementWorkfileVersion( + pyblish.api.ContextPlugin, + OptionalPyblishPluginMixin +): """Increment current workfile version.""" order = pyblish.api.IntegratorOrder + 0.9 diff --git a/openpype/hosts/blender/plugins/publish/integrate_animation.py b/openpype/hosts/blender/plugins/publish/integrate_animation.py index d9a85bc79b..b7e5423fa8 100644 --- a/openpype/hosts/blender/plugins/publish/integrate_animation.py +++ b/openpype/hosts/blender/plugins/publish/integrate_animation.py @@ -1,9 +1,13 @@ import json import pyblish.api +from openpype.pipeline.publish import OptionalPyblishPluginMixin -class IntegrateAnimation(pyblish.api.InstancePlugin): +class IntegrateAnimation( + pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin, +): """Generate a JSON file for animation.""" label = "Integrate Animation" diff --git a/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py b/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py index edf47193be..687371b362 100644 --- a/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py +++ b/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py @@ -4,11 +4,17 @@ import bpy import pyblish.api -from openpype.pipeline.publish import ValidateContentsOrder +from openpype.pipeline.publish import ( + ValidateContentsOrder, + OptionalPyblishPluginMixin, +) import openpype.hosts.blender.api.action -class ValidateMeshHasUvs(pyblish.api.InstancePlugin): +class ValidateMeshHasUvs( + pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin, +): """Validate that the current mesh has UV's.""" order = ValidateContentsOrder diff --git a/openpype/hosts/blender/plugins/publish/validate_object_mode.py b/openpype/hosts/blender/plugins/publish/validate_object_mode.py index ac60e00f89..d8d2e3c8bf 100644 --- a/openpype/hosts/blender/plugins/publish/validate_object_mode.py +++ b/openpype/hosts/blender/plugins/publish/validate_object_mode.py @@ -3,10 +3,14 @@ from typing import List import bpy import pyblish.api +from openpype.pipeline.publish import OptionalPyblishPluginMixin import openpype.hosts.blender.api.action -class ValidateObjectIsInObjectMode(pyblish.api.InstancePlugin): +class ValidateObjectIsInObjectMode( + pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin, +): """Validate that the objects in the instance are in Object Mode.""" order = pyblish.api.ValidatorOrder - 0.01 From 656a42dd525317f83b22a232f8dc68756d2c389a Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Wed, 9 Aug 2023 11:45:58 +0200 Subject: [PATCH 018/107] Handling instance noce --- openpype/hosts/blender/api/plugin.py | 13 ++++++++++--- .../hosts/blender/plugins/create/create_action.py | 8 ++++++++ .../blender/plugins/create/create_animation.py | 6 ++++++ .../hosts/blender/plugins/create/create_camera.py | 6 ++++++ .../hosts/blender/plugins/create/create_layout.py | 6 ++++++ .../hosts/blender/plugins/create/create_model.py | 6 ++++++ .../blender/plugins/create/create_pointcache.py | 6 ++++++ .../hosts/blender/plugins/create/create_review.py | 6 ++++++ openpype/hosts/blender/plugins/create/create_rig.py | 6 ++++++ 9 files changed, 60 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 5dd352ff6c..fe0d53f84e 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -234,13 +234,20 @@ class BlenderCreator(Creator): pre_create_data(dict): Data based on pre creation attributes. Those may affect how creator works. """ + collection = bpy.data.collections.new(name=subset_name) + bpy.context.scene.collection.children.link(collection) + + instance_node = {} + for key, value in collection.items(): + instance_node[key] = value + + instance_data["instance_node"] = instance_node + instance = CreatedInstance( self.family, subset_name, instance_data, self ) self._add_instance_to_context(instance) - collection = bpy.data.collections.new(name=subset_name) - bpy.context.scene.collection.children.link(collection) imprint(collection, instance_data) if pre_create_data.get("useSelection"): @@ -287,7 +294,7 @@ class BlenderCreator(Creator): for created_instance, _changes in update_list: data = created_instance.data_to_store() - # TODO + imprint(data.get("instance_node", {}), data) def remove_instances(self, instances: List[CreatedInstance]): diff --git a/openpype/hosts/blender/plugins/create/create_action.py b/openpype/hosts/blender/plugins/create/create_action.py index 6951e86c46..a43258082c 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -28,6 +28,11 @@ class CreateAction(openpype.hosts.blender.api.plugin.BlenderCreator): ) collection = bpy.data.collections.new(name=name) bpy.context.scene.collection.children.link(collection) + + instance_node = {} + for key, value in collection.items(): + instance_node[key] = value + instance_data.update( { "id": "pyblish.avalon.instance", @@ -35,8 +40,11 @@ class CreateAction(openpype.hosts.blender.api.plugin.BlenderCreator): "label": self.label, "task": get_current_task_name(), "subset": subset_name, + "instance_node": instance_node, } ) + from pprint import pprint + pprint(instance_data) lib.imprint(collection, instance_data) if pre_create_data.get("useSelection"): diff --git a/openpype/hosts/blender/plugins/create/create_animation.py b/openpype/hosts/blender/plugins/create/create_animation.py index 9e7dfbaf84..842292f0f9 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -46,6 +46,11 @@ class CreateAnimation(plugin.BlenderCreator): # asset_group.empty_display_type = 'SINGLE_ARROW' asset_group = bpy.data.collections.new(name=name) instances.children.link(asset_group) + + instance_node = {} + for key, value in asset_group.items(): + instance_node[key] = value + instance_data.update( { "id": "pyblish.avalon.instance", @@ -53,6 +58,7 @@ class CreateAnimation(plugin.BlenderCreator): "label": self.label, "task": get_current_task_name(), "subset": subset_name, + "instance_node": instance_node, } ) lib.imprint(asset_group, instance_data) diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index c5987779c0..8360abbc7d 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -44,6 +44,11 @@ class CreateCamera(plugin.BlenderCreator): asset_group = bpy.data.objects.new(name=name, object_data=None) asset_group.empty_display_type = 'SINGLE_ARROW' instances.objects.link(asset_group) + + instance_node = {} + for key, value in asset_group.items(): + instance_node[key] = value + instance_data.update( { "id": "pyblish.avalon.instance", @@ -51,6 +56,7 @@ class CreateCamera(plugin.BlenderCreator): "label": self.label, "task": get_current_task_name(), "subset": subset_name, + "instance_node": instance_node, } ) lib.imprint(asset_group, instance_data) diff --git a/openpype/hosts/blender/plugins/create/create_layout.py b/openpype/hosts/blender/plugins/create/create_layout.py index cb61799f4a..b4b127f32c 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -43,6 +43,11 @@ class CreateLayout(plugin.BlenderCreator): asset_group = bpy.data.objects.new(name=name, object_data=None) asset_group.empty_display_type = 'SINGLE_ARROW' instances.objects.link(asset_group) + + instance_node = {} + for key, value in asset_group.items(): + instance_node[key] = value + instance_data.update( { "id": "pyblish.avalon.instance", @@ -50,6 +55,7 @@ class CreateLayout(plugin.BlenderCreator): "label": self.label, "task": get_current_task_name(), "subset": subset_name, + "instance_node": instance_node, } ) lib.imprint(asset_group, instance_data) diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index ccf3668d98..5cb2de0fae 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -43,6 +43,11 @@ class CreateModel(plugin.BlenderCreator): asset_group = bpy.data.objects.new(name=name, object_data=None) asset_group.empty_display_type = 'SINGLE_ARROW' instances.objects.link(asset_group) + + instance_node = {} + for key, value in asset_group.items(): + instance_node[key] = value + instance_data.update( { "id": "pyblish.avalon.instance", @@ -50,6 +55,7 @@ class CreateModel(plugin.BlenderCreator): "label": self.label, "task": get_current_task_name(), "subset": subset_name, + "instance_node": instance_node, } ) lib.imprint(asset_group, instance_data) diff --git a/openpype/hosts/blender/plugins/create/create_pointcache.py b/openpype/hosts/blender/plugins/create/create_pointcache.py index e27cb22389..a95ae547c8 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -28,6 +28,11 @@ class CreatePointcache(plugin.BlenderCreator): ) collection = bpy.data.collections.new(name=name) bpy.context.scene.collection.children.link(collection) + + instance_node = {} + for key, value in collection.items(): + instance_node[key] = value + instance_data.update( { "id": "pyblish.avalon.instance", @@ -35,6 +40,7 @@ class CreatePointcache(plugin.BlenderCreator): "label": self.label, "task": get_current_task_name(), "subset": subset_name, + "instance_node": instance_node, } ) lib.imprint(collection, instance_data) diff --git a/openpype/hosts/blender/plugins/create/create_review.py b/openpype/hosts/blender/plugins/create/create_review.py index afeaea951b..1bcafbc265 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -42,6 +42,11 @@ class CreateReview(plugin.BlenderCreator): name = plugin.asset_name(instance_data["asset"], subset_name) asset_group = bpy.data.collections.new(name=name) instances.children.link(asset_group) + + instance_node = {} + for key, value in asset_group.items(): + instance_node[key] = value + instance_data.update( { "id": "pyblish.avalon.instance", @@ -49,6 +54,7 @@ class CreateReview(plugin.BlenderCreator): "label": self.label, "task": get_current_task_name(), "subset": subset_name, + "instance_node": instance_node, } ) lib.imprint(asset_group, instance_data) diff --git a/openpype/hosts/blender/plugins/create/create_rig.py b/openpype/hosts/blender/plugins/create/create_rig.py index 2db766f7ed..e93f4a171f 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -43,6 +43,11 @@ class CreateRig(plugin.BlenderCreator): asset_group = bpy.data.objects.new(name=name, object_data=None) asset_group.empty_display_type = 'SINGLE_ARROW' instances.objects.link(asset_group) + + instance_node = {} + for key, value in asset_group.items(): + instance_node[key] = value + instance_data.update( { "id": "pyblish.avalon.instance", @@ -50,6 +55,7 @@ class CreateRig(plugin.BlenderCreator): "label": self.label, "task": get_current_task_name(), "subset": subset_name, + "instance_node": instance_node, } ) lib.imprint(asset_group, instance_data) From 5f7847c6973d378779e3b27a0ef25ed3fa5a9e1c Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Wed, 9 Aug 2023 12:04:06 +0200 Subject: [PATCH 019/107] Removed useless pprint --- openpype/hosts/blender/plugins/create/create_action.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/blender/plugins/create/create_action.py b/openpype/hosts/blender/plugins/create/create_action.py index a43258082c..ee7ca092b5 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -43,8 +43,6 @@ class CreateAction(openpype.hosts.blender.api.plugin.BlenderCreator): "instance_node": instance_node, } ) - from pprint import pprint - pprint(instance_data) lib.imprint(collection, instance_data) if pre_create_data.get("useSelection"): From f52c9f0a38de8c9d3f50d6a1a721f20afdfa1b5f Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Thu, 10 Aug 2023 10:37:46 +0200 Subject: [PATCH 020/107] Enhance instance_node, wip remove_inst, rm extra empty lines --- openpype/hosts/blender/api/plugin.py | 22 ++++++++++++++----- .../blender/plugins/create/create_action.py | 8 ++++--- .../plugins/create/create_animation.py | 12 ++++++---- .../blender/plugins/create/create_camera.py | 12 ++++++---- .../blender/plugins/create/create_layout.py | 12 ++++++---- .../blender/plugins/create/create_model.py | 12 ++++++---- .../plugins/create/create_pointcache.py | 8 ++++--- .../blender/plugins/create/create_review.py | 12 ++++++---- .../blender/plugins/create/create_rig.py | 12 ++++++---- 9 files changed, 75 insertions(+), 35 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index fe0d53f84e..9967f9479c 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -155,7 +155,6 @@ class BlenderCreator(Creator): return collection - @staticmethod def cache_subsets(shared_data): """Cache instances for Creators shared data. @@ -221,7 +220,6 @@ class BlenderCreator(Creator): return shared_data - def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): @@ -254,7 +252,6 @@ class BlenderCreator(Creator): for obj in get_selection(): collection.objects.link(obj) - def collect_instances(self): """Override abstract method from BaseCreator. Collect existing instances related to this creator plugin.""" @@ -283,7 +280,6 @@ class BlenderCreator(Creator): # Add instance to create context self._add_instance_to_context(instance) - def update_instances(self, update_list): """Override abstract method from BaseCreator. Store changes of existing instances so they can be recollected. @@ -296,7 +292,6 @@ class BlenderCreator(Creator): imprint(data.get("instance_node", {}), data) - def remove_instances(self, instances: List[CreatedInstance]): """Override abstract method from BaseCreator. Method called when instances are removed. @@ -305,6 +300,23 @@ class BlenderCreator(Creator): instance(List[CreatedInstance]): Instance objects to remove. """ for instance in instances: + outliner_entity = instance.data.get("instance_node", {}).get( + "datablock" + ) + if not outliner_entity: + continue + + if isinstance(outliner_entity, bpy.types.Collection): + for children in outliner_entity.children_recursive: + if isinstance(children, bpy.types.Collection): + bpy.data.collections.remove(children) + else: + bpy.data.objects.remove(children) + + bpy.data.collections.remove(outliner_entity) + elif isinstance(outliner_entity, bpy.types.Object): + bpy.data.objects.remove(outliner_entity) + self._remove_instance_from_context(instance) diff --git a/openpype/hosts/blender/plugins/create/create_action.py b/openpype/hosts/blender/plugins/create/create_action.py index ee7ca092b5..7404e7e037 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -5,6 +5,7 @@ import bpy from openpype.pipeline import get_current_task_name, CreatedInstance import openpype.hosts.blender.api.plugin from openpype.hosts.blender.api import lib +from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY class CreateAction(openpype.hosts.blender.api.plugin.BlenderCreator): @@ -29,9 +30,10 @@ class CreateAction(openpype.hosts.blender.api.plugin.BlenderCreator): collection = bpy.data.collections.new(name=name) bpy.context.scene.collection.children.link(collection) - instance_node = {} - for key, value in collection.items(): - instance_node[key] = value + collection[AVALON_PROPERTY] = instance_node = { + "name": collection.name, + "datablock": collection, + } instance_data.update( { diff --git a/openpype/hosts/blender/plugins/create/create_animation.py b/openpype/hosts/blender/plugins/create/create_animation.py index 842292f0f9..c2f24250e1 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -4,7 +4,10 @@ import bpy from openpype.pipeline import get_current_task_name, CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops -from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES +from openpype.hosts.blender.api.pipeline import ( + AVALON_INSTANCES, + AVALON_PROPERTY, +) class CreateAnimation(plugin.BlenderCreator): @@ -47,9 +50,10 @@ class CreateAnimation(plugin.BlenderCreator): asset_group = bpy.data.collections.new(name=name) instances.children.link(asset_group) - instance_node = {} - for key, value in asset_group.items(): - instance_node[key] = value + asset_group[AVALON_PROPERTY] = instance_node = { + "name": asset_group.name, + "datablock": asset_group, + } instance_data.update( { diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index 8360abbc7d..a83124cbe7 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -4,7 +4,10 @@ import bpy from openpype.pipeline import get_current_task_name, CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops -from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES +from openpype.hosts.blender.api.pipeline import ( + AVALON_INSTANCES, + AVALON_PROPERTY, +) class CreateCamera(plugin.BlenderCreator): @@ -45,9 +48,10 @@ class CreateCamera(plugin.BlenderCreator): asset_group.empty_display_type = 'SINGLE_ARROW' instances.objects.link(asset_group) - instance_node = {} - for key, value in asset_group.items(): - instance_node[key] = value + asset_group[AVALON_PROPERTY] = instance_node = { + "name": asset_group.name, + "datablock": asset_group, + } instance_data.update( { diff --git a/openpype/hosts/blender/plugins/create/create_layout.py b/openpype/hosts/blender/plugins/create/create_layout.py index b4b127f32c..4fb76ef41e 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -4,7 +4,10 @@ import bpy from openpype.pipeline import get_current_task_name, CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops -from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES +from openpype.hosts.blender.api.pipeline import ( + AVALON_INSTANCES, + AVALON_PROPERTY, +) class CreateLayout(plugin.BlenderCreator): @@ -44,9 +47,10 @@ class CreateLayout(plugin.BlenderCreator): asset_group.empty_display_type = 'SINGLE_ARROW' instances.objects.link(asset_group) - instance_node = {} - for key, value in asset_group.items(): - instance_node[key] = value + asset_group[AVALON_PROPERTY] = instance_node = { + "name": asset_group.name, + "datablock": asset_group, + } instance_data.update( { diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index 5cb2de0fae..45f1d66ad9 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -4,7 +4,10 @@ import bpy from openpype.pipeline import get_current_task_name, CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops -from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES +from openpype.hosts.blender.api.pipeline import ( + AVALON_INSTANCES, + AVALON_PROPERTY, +) class CreateModel(plugin.BlenderCreator): @@ -44,9 +47,10 @@ class CreateModel(plugin.BlenderCreator): asset_group.empty_display_type = 'SINGLE_ARROW' instances.objects.link(asset_group) - instance_node = {} - for key, value in asset_group.items(): - instance_node[key] = value + asset_group[AVALON_PROPERTY] = instance_node = { + "name": asset_group.name, + "datablock": asset_group, + } instance_data.update( { diff --git a/openpype/hosts/blender/plugins/create/create_pointcache.py b/openpype/hosts/blender/plugins/create/create_pointcache.py index a95ae547c8..7aa3b22466 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -5,6 +5,7 @@ import bpy from openpype.pipeline import get_current_task_name, CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES +from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY class CreatePointcache(plugin.BlenderCreator): @@ -29,9 +30,10 @@ class CreatePointcache(plugin.BlenderCreator): collection = bpy.data.collections.new(name=name) bpy.context.scene.collection.children.link(collection) - instance_node = {} - for key, value in collection.items(): - instance_node[key] = value + collection[AVALON_PROPERTY] = instance_node = { + "name": collection.name, + "datablock": collection, + } instance_data.update( { diff --git a/openpype/hosts/blender/plugins/create/create_review.py b/openpype/hosts/blender/plugins/create/create_review.py index 1bcafbc265..a6ca5b1b92 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -4,7 +4,10 @@ import bpy from openpype.pipeline import get_current_task_name, CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops -from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES +from openpype.hosts.blender.api.pipeline import ( + AVALON_INSTANCES, + AVALON_PROPERTY, +) class CreateReview(plugin.BlenderCreator): @@ -43,9 +46,10 @@ class CreateReview(plugin.BlenderCreator): asset_group = bpy.data.collections.new(name=name) instances.children.link(asset_group) - instance_node = {} - for key, value in asset_group.items(): - instance_node[key] = value + asset_group[AVALON_PROPERTY] = instance_node = { + "name": asset_group.name, + "datablock": asset_group, + } instance_data.update( { diff --git a/openpype/hosts/blender/plugins/create/create_rig.py b/openpype/hosts/blender/plugins/create/create_rig.py index e93f4a171f..f7c99b0b03 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -4,7 +4,10 @@ import bpy from openpype.pipeline import get_current_task_name, CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops -from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES +from openpype.hosts.blender.api.pipeline import ( + AVALON_INSTANCES, + AVALON_PROPERTY, +) class CreateRig(plugin.BlenderCreator): @@ -44,9 +47,10 @@ class CreateRig(plugin.BlenderCreator): asset_group.empty_display_type = 'SINGLE_ARROW' instances.objects.link(asset_group) - instance_node = {} - for key, value in asset_group.items(): - instance_node[key] = value + asset_group[AVALON_PROPERTY] = instance_node = { + "name": asset_group.name, + "datablock": asset_group, + } instance_data.update( { From 371d7405ec10257fe52c1259c383604862b080f7 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Thu, 10 Aug 2023 11:19:05 +0200 Subject: [PATCH 021/107] Apply creator change to bl base creator --- openpype/hosts/blender/api/plugin.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 9967f9479c..060d229eb5 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -235,9 +235,10 @@ class BlenderCreator(Creator): collection = bpy.data.collections.new(name=subset_name) bpy.context.scene.collection.children.link(collection) - instance_node = {} - for key, value in collection.items(): - instance_node[key] = value + collection["instance_node"] = instance_node = { + "name": collection.name, + "datablock": collection, + } instance_data["instance_node"] = instance_node From e832cf59ccb2bbf6a436184f1143ebf53231c5df Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Tue, 12 Sep 2023 15:49:33 +0200 Subject: [PATCH 022/107] Fix Cannot pickle object error --- openpype/hosts/blender/api/plugin.py | 5 +---- openpype/hosts/blender/plugins/create/create_action.py | 1 - openpype/hosts/blender/plugins/create/create_animation.py | 1 - openpype/hosts/blender/plugins/create/create_camera.py | 1 - openpype/hosts/blender/plugins/create/create_layout.py | 1 - openpype/hosts/blender/plugins/create/create_model.py | 1 - openpype/hosts/blender/plugins/create/create_pointcache.py | 1 - openpype/hosts/blender/plugins/create/create_review.py | 1 - openpype/hosts/blender/plugins/create/create_rig.py | 1 - 9 files changed, 1 insertion(+), 12 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 060d229eb5..73d8fc0ed5 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -237,7 +237,6 @@ class BlenderCreator(Creator): collection["instance_node"] = instance_node = { "name": collection.name, - "datablock": collection, } instance_data["instance_node"] = instance_node @@ -267,9 +266,7 @@ class BlenderCreator(Creator): for instance_data in cached_subsets.get(self.identifier, []): # Process only instances that were created by this creator - data = {} - for key, value in instance_data.items(): - data[key] = value + data = instance_data.to_dict() creator_id = data.get('creator_identifier') if creator_id == self.identifier: diff --git a/openpype/hosts/blender/plugins/create/create_action.py b/openpype/hosts/blender/plugins/create/create_action.py index 7404e7e037..d766fce038 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -32,7 +32,6 @@ class CreateAction(openpype.hosts.blender.api.plugin.BlenderCreator): collection[AVALON_PROPERTY] = instance_node = { "name": collection.name, - "datablock": collection, } instance_data.update( diff --git a/openpype/hosts/blender/plugins/create/create_animation.py b/openpype/hosts/blender/plugins/create/create_animation.py index c2f24250e1..88ae9e5996 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -52,7 +52,6 @@ class CreateAnimation(plugin.BlenderCreator): asset_group[AVALON_PROPERTY] = instance_node = { "name": asset_group.name, - "datablock": asset_group, } instance_data.update( diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index a83124cbe7..026b5739d6 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -50,7 +50,6 @@ class CreateCamera(plugin.BlenderCreator): asset_group[AVALON_PROPERTY] = instance_node = { "name": asset_group.name, - "datablock": asset_group, } instance_data.update( diff --git a/openpype/hosts/blender/plugins/create/create_layout.py b/openpype/hosts/blender/plugins/create/create_layout.py index 4fb76ef41e..f46ae58a43 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -49,7 +49,6 @@ class CreateLayout(plugin.BlenderCreator): asset_group[AVALON_PROPERTY] = instance_node = { "name": asset_group.name, - "datablock": asset_group, } instance_data.update( diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index 45f1d66ad9..069b78626b 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -49,7 +49,6 @@ class CreateModel(plugin.BlenderCreator): asset_group[AVALON_PROPERTY] = instance_node = { "name": asset_group.name, - "datablock": asset_group, } instance_data.update( diff --git a/openpype/hosts/blender/plugins/create/create_pointcache.py b/openpype/hosts/blender/plugins/create/create_pointcache.py index 7aa3b22466..3054b81ef5 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -32,7 +32,6 @@ class CreatePointcache(plugin.BlenderCreator): collection[AVALON_PROPERTY] = instance_node = { "name": collection.name, - "datablock": collection, } instance_data.update( diff --git a/openpype/hosts/blender/plugins/create/create_review.py b/openpype/hosts/blender/plugins/create/create_review.py index a6ca5b1b92..10a96c94fd 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -48,7 +48,6 @@ class CreateReview(plugin.BlenderCreator): asset_group[AVALON_PROPERTY] = instance_node = { "name": asset_group.name, - "datablock": asset_group, } instance_data.update( diff --git a/openpype/hosts/blender/plugins/create/create_rig.py b/openpype/hosts/blender/plugins/create/create_rig.py index f7c99b0b03..8daffe638d 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -49,7 +49,6 @@ class CreateRig(plugin.BlenderCreator): asset_group[AVALON_PROPERTY] = instance_node = { "name": asset_group.name, - "datablock": asset_group, } instance_data.update( From 78b33370dc3418083d323d465332ff28aa32fbeb Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Mon, 18 Sep 2023 17:59:33 +0200 Subject: [PATCH 023/107] Fix collectreview --- openpype/hosts/blender/plugins/publish/collect_review.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/blender/plugins/publish/collect_review.py b/openpype/hosts/blender/plugins/publish/collect_review.py index 3bf2e39e24..66a3d7b5e8 100644 --- a/openpype/hosts/blender/plugins/publish/collect_review.py +++ b/openpype/hosts/blender/plugins/publish/collect_review.py @@ -16,10 +16,14 @@ class CollectReview(pyblish.api.InstancePlugin): self.log.debug(f"instance: {instance}") + datablock = bpy.data.collections.get( + instance.data.get("instance_node", {}).get("name", "") + ) + # get cameras cameras = [ obj - for obj in instance + for obj in datablock.all_objects if isinstance(obj, bpy.types.Object) and obj.type == "CAMERA" ] From a12d599b442540a46b88212fcdc873ad1c85db46 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Wed, 25 Oct 2023 14:04:00 +0200 Subject: [PATCH 024/107] change creator labels --- openpype/hosts/blender/plugins/create/create_action.py | 2 +- openpype/hosts/blender/plugins/create/create_animation.py | 2 +- openpype/hosts/blender/plugins/create/create_camera.py | 2 +- openpype/hosts/blender/plugins/create/create_layout.py | 2 +- openpype/hosts/blender/plugins/create/create_model.py | 2 +- openpype/hosts/blender/plugins/create/create_pointcache.py | 2 +- openpype/hosts/blender/plugins/create/create_review.py | 2 +- openpype/hosts/blender/plugins/create/create_rig.py | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/blender/plugins/create/create_action.py b/openpype/hosts/blender/plugins/create/create_action.py index d766fce038..e7b689c54e 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -38,7 +38,7 @@ class CreateAction(openpype.hosts.blender.api.plugin.BlenderCreator): { "id": "pyblish.avalon.instance", "creator_identifier": self.identifier, - "label": self.label, + "label": subset_name, "task": get_current_task_name(), "subset": subset_name, "instance_node": instance_node, diff --git a/openpype/hosts/blender/plugins/create/create_animation.py b/openpype/hosts/blender/plugins/create/create_animation.py index 88ae9e5996..8b4214ceda 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -58,7 +58,7 @@ class CreateAnimation(plugin.BlenderCreator): { "id": "pyblish.avalon.instance", "creator_identifier": self.identifier, - "label": self.label, + "label": subset_name, "task": get_current_task_name(), "subset": subset_name, "instance_node": instance_node, diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index 026b5739d6..4747e50b2e 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -56,7 +56,7 @@ class CreateCamera(plugin.BlenderCreator): { "id": "pyblish.avalon.instance", "creator_identifier": self.identifier, - "label": self.label, + "label": subset_name, "task": get_current_task_name(), "subset": subset_name, "instance_node": instance_node, diff --git a/openpype/hosts/blender/plugins/create/create_layout.py b/openpype/hosts/blender/plugins/create/create_layout.py index f46ae58a43..0c97d57af3 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -55,7 +55,7 @@ class CreateLayout(plugin.BlenderCreator): { "id": "pyblish.avalon.instance", "creator_identifier": self.identifier, - "label": self.label, + "label": subset_name, "task": get_current_task_name(), "subset": subset_name, "instance_node": instance_node, diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index 069b78626b..3c8e9c4900 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -55,7 +55,7 @@ class CreateModel(plugin.BlenderCreator): { "id": "pyblish.avalon.instance", "creator_identifier": self.identifier, - "label": self.label, + "label": subset_name, "task": get_current_task_name(), "subset": subset_name, "instance_node": instance_node, diff --git a/openpype/hosts/blender/plugins/create/create_pointcache.py b/openpype/hosts/blender/plugins/create/create_pointcache.py index 3054b81ef5..a40bd5af61 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -38,7 +38,7 @@ class CreatePointcache(plugin.BlenderCreator): { "id": "pyblish.avalon.instance", "creator_identifier": self.identifier, - "label": self.label, + "label": subset_name, "task": get_current_task_name(), "subset": subset_name, "instance_node": instance_node, diff --git a/openpype/hosts/blender/plugins/create/create_review.py b/openpype/hosts/blender/plugins/create/create_review.py index 10a96c94fd..8c9a8d5927 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -54,7 +54,7 @@ class CreateReview(plugin.BlenderCreator): { "id": "pyblish.avalon.instance", "creator_identifier": self.identifier, - "label": self.label, + "label": subset_name, "task": get_current_task_name(), "subset": subset_name, "instance_node": instance_node, diff --git a/openpype/hosts/blender/plugins/create/create_rig.py b/openpype/hosts/blender/plugins/create/create_rig.py index 8daffe638d..110a9f5c8e 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -55,7 +55,7 @@ class CreateRig(plugin.BlenderCreator): { "id": "pyblish.avalon.instance", "creator_identifier": self.identifier, - "label": self.label, + "label": subset_name, "task": get_current_task_name(), "subset": subset_name, "instance_node": instance_node, From cb4dd2559b9d51469870a3962cf69435f7d290b4 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Thu, 26 Oct 2023 10:44:34 +0200 Subject: [PATCH 025/107] wip create blend scene create render --- .../plugins/create/create_blendScene.py | 41 +++++++++++++++---- .../blender/plugins/create/create_render.py | 35 ++++++++++++---- 2 files changed, 59 insertions(+), 17 deletions(-) diff --git a/openpype/hosts/blender/plugins/create/create_blendScene.py b/openpype/hosts/blender/plugins/create/create_blendScene.py index 63bcf212ff..ee8e52d3c5 100644 --- a/openpype/hosts/blender/plugins/create/create_blendScene.py +++ b/openpype/hosts/blender/plugins/create/create_blendScene.py @@ -4,7 +4,10 @@ import bpy from openpype.pipeline import get_current_task_name from openpype.hosts.blender.api import plugin, lib, ops -from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES +from openpype.hosts.blender.api.pipeline import ( + AVALON_INSTANCES, + AVALON_PROPERTY, +) class CreateBlendScene(plugin.Creator): @@ -15,12 +18,18 @@ class CreateBlendScene(plugin.Creator): family = "blendScene" icon = "cubes" - def process(self): + def create( + self, subset_name: str, instance_data: dict, pre_create_data: dict + ): """ Run the creator on Blender main thread""" - mti = ops.MainThreadItem(self._process) + mti = ops.MainThreadItem( + self._process, subset_name, instance_data, pre_create_data + ) ops.execute_in_main_thread(mti) - def _process(self): + def _process( + self, subset_name: str, instance_data: dict, pre_create_data: dict + ): # Get Instance Container or create it if it does not exist instances = bpy.data.collections.get(AVALON_INSTANCES) if not instances: @@ -28,14 +37,28 @@ class CreateBlendScene(plugin.Creator): bpy.context.scene.collection.children.link(instances) # Create instance object - asset = self.data["asset"] - subset = self.data["subset"] - name = plugin.asset_name(asset, subset) + asset = instance_data.get("asset") + name = plugin.asset_name(asset, subset_name) asset_group = bpy.data.objects.new(name=name, object_data=None) asset_group.empty_display_type = 'SINGLE_ARROW' instances.objects.link(asset_group) - self.data['task'] = get_current_task_name() - lib.imprint(asset_group, self.data) + + asset_group[AVALON_PROPERTY] = instance_node = { + "name": asset_group.name + } + + instance_data.update( + { + "id": "publish.avalon.instance", + "creator_identifier": self.identifier, + "label": subset_name, + "task": get_current_task_name(), + "subset": subset_name, + "instance_node": instance_node, + } + ) + + lib.imprint(asset_group, instance_data) # Add selected objects to instance if (self.options or {}).get("useSelection"): diff --git a/openpype/hosts/blender/plugins/create/create_render.py b/openpype/hosts/blender/plugins/create/create_render.py index f938a21808..ab3119b32e 100644 --- a/openpype/hosts/blender/plugins/create/create_render.py +++ b/openpype/hosts/blender/plugins/create/create_render.py @@ -4,10 +4,13 @@ import bpy from openpype.pipeline import get_current_task_name from openpype.hosts.blender.api import plugin, lib from openpype.hosts.blender.api.render_lib import prepare_rendering -from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES +from openpype.hosts.blender.api.pipeline import ( + AVALON_INSTANCES, + AVALON_PROPERTY, +) -class CreateRenderlayer(plugin.Creator): +class CreateRenderlayer(plugin.BlenderCreator): """Single baked camera""" name = "renderingMain" @@ -15,7 +18,9 @@ class CreateRenderlayer(plugin.Creator): family = "render" icon = "eye" - def process(self): + def create( + self, subset_name: str, instance_data: dict, pre_create_data: dict + ): # Get Instance Container or create it if it does not exist instances = bpy.data.collections.get(AVALON_INSTANCES) if not instances: @@ -23,15 +28,29 @@ class CreateRenderlayer(plugin.Creator): bpy.context.scene.collection.children.link(instances) # Create instance object - asset = self.data["asset"] - subset = self.data["subset"] - name = plugin.asset_name(asset, subset) + asset = instance_data.get("asset") + name = plugin.asset_name(asset, subset_name) asset_group = bpy.data.collections.new(name=name) try: instances.children.link(asset_group) - self.data['task'] = get_current_task_name() - lib.imprint(asset_group, self.data) + + asset_group[AVALON_PROPERTY] = instance_node = { + "name": asset_group.name + } + + instance_data.update( + { + "id": "pyblish.avalon.instance", + "creator_identifier": self.identifier, + "label": subset_name, + "task": get_current_task_name(), + "subset": subset_name, + "instance_node": instance_node, + } + ) + + lib.imprint(asset_group, instance_data) prepare_rendering(asset_group) except Exception: From ca2ff805910510a6ecf75e0ae233b8b818665924 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 26 Oct 2023 12:43:13 +0200 Subject: [PATCH 026/107] nuke: updating colorspace defaults --- .../defaults/project_settings/nuke.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index 1cadedd797..20df0ad5c2 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -19,16 +19,16 @@ "rules": {} }, "viewer": { - "viewerProcess": "sRGB" + "viewerProcess": "sRGB (default)" }, "baking": { - "viewerProcess": "rec709" + "viewerProcess": "rec709 (default)" }, "workfile": { - "colorManagement": "Nuke", + "colorManagement": "OCIO", "OCIO_config": "nuke-default", - "workingSpaceLUT": "linear", - "monitorLut": "sRGB" + "workingSpaceLUT": "scene_linear", + "monitorLut": "sRGB (default)" }, "nodes": { "requiredNodes": [ @@ -76,7 +76,7 @@ { "type": "text", "name": "colorspace", - "value": "linear" + "value": "scene_linear" }, { "type": "bool", @@ -129,7 +129,7 @@ { "type": "text", "name": "colorspace", - "value": "linear" + "value": "scene_linear" }, { "type": "bool", @@ -177,7 +177,7 @@ { "type": "text", "name": "colorspace", - "value": "sRGB" + "value": "texture_paint" }, { "type": "bool", @@ -193,7 +193,7 @@ "inputs": [ { "regex": "(beauty).*(?=.exr)", - "colorspace": "linear" + "colorspace": "scene_linear" } ] } From ae2c4bd5548c6ba81e7937320b0b91554ea614cd Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 26 Oct 2023 17:09:34 +0200 Subject: [PATCH 027/107] nuke: aligning server addon settings with openpype --- server_addon/nuke/server/settings/imageio.py | 16 ++++++++-------- server_addon/nuke/server/version.py | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/server_addon/nuke/server/settings/imageio.py b/server_addon/nuke/server/settings/imageio.py index 15ccd4e89a..19ad5ff24a 100644 --- a/server_addon/nuke/server/settings/imageio.py +++ b/server_addon/nuke/server/settings/imageio.py @@ -213,16 +213,16 @@ class ImageIOSettings(BaseSettingsModel): DEFAULT_IMAGEIO_SETTINGS = { "viewer": { - "viewerProcess": "sRGB" + "viewerProcess": "sRGB (default)" }, "baking": { - "viewerProcess": "rec709" + "viewerProcess": "rec709 (default)" }, "workfile": { - "color_management": "Nuke", + "color_management": "OCIO", "native_ocio_config": "nuke-default", - "working_space": "linear", - "thumbnail_space": "sRGB", + "working_space": "scene_linear", + "thumbnail_space": "sRGB (default)", }, "nodes": { "required_nodes": [ @@ -269,7 +269,7 @@ DEFAULT_IMAGEIO_SETTINGS = { { "type": "text", "name": "colorspace", - "text": "linear" + "text": "scene_linear" }, { "type": "boolean", @@ -321,7 +321,7 @@ DEFAULT_IMAGEIO_SETTINGS = { { "type": "text", "name": "colorspace", - "text": "linear" + "text": "scene_linear" }, { "type": "boolean", @@ -368,7 +368,7 @@ DEFAULT_IMAGEIO_SETTINGS = { { "type": "text", "name": "colorspace", - "text": "sRGB" + "text": "texture_paint" }, { "type": "boolean", diff --git a/server_addon/nuke/server/version.py b/server_addon/nuke/server/version.py index bbab0242f6..1276d0254f 100644 --- a/server_addon/nuke/server/version.py +++ b/server_addon/nuke/server/version.py @@ -1 +1 @@ -__version__ = "0.1.4" +__version__ = "0.1.5" From 4f658d2f51cd3357a3d31ba7d607354debabaa19 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Fri, 27 Oct 2023 16:05:01 +0200 Subject: [PATCH 028/107] update blender creator --- openpype/hosts/blender/api/plugin.py | 14 ++++++++++++-- .../blender/plugins/create/create_blendScene.py | 2 +- .../hosts/blender/plugins/create/create_model.py | 2 +- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 73d8fc0ed5..dbd9f25d68 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -9,6 +9,7 @@ from openpype.pipeline import ( Creator, CreatedInstance, LoaderPlugin, + get_current_task_name, ) from .pipeline import ( AVALON_CONTAINERS, @@ -235,11 +236,20 @@ class BlenderCreator(Creator): collection = bpy.data.collections.new(name=subset_name) bpy.context.scene.collection.children.link(collection) - collection["instance_node"] = instance_node = { + collection[AVALON_PROPERTY] = instance_node = { "name": collection.name, } - instance_data["instance_node"] = instance_node + instance_data.update( + { + "id": "pyblish.avalon.instance", + "creator_identifier": self.identifier, + "label": subset_name, + "task": get_current_task_name(), + "subset": subset_name, + "instance_node": instance_node, + } + ) instance = CreatedInstance( self.family, subset_name, instance_data, self diff --git a/openpype/hosts/blender/plugins/create/create_blendScene.py b/openpype/hosts/blender/plugins/create/create_blendScene.py index ee8e52d3c5..23ff991654 100644 --- a/openpype/hosts/blender/plugins/create/create_blendScene.py +++ b/openpype/hosts/blender/plugins/create/create_blendScene.py @@ -21,7 +21,7 @@ class CreateBlendScene(plugin.Creator): def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): - """ Run the creator on Blender main thread""" + """Run the creator on Blender main thread.""" mti = ops.MainThreadItem( self._process, subset_name, instance_data, pre_create_data ) diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index 3c8e9c4900..761d9fca9f 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -22,7 +22,7 @@ class CreateModel(plugin.BlenderCreator): def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): - """ Run the creator on Blender main thread""" + """Run the creator on Blender main thread.""" self._add_instance_to_context( CreatedInstance(self.family, subset_name, instance_data, self) ) From 59bb86dc3337cb340d3ed9bf1c842167f98fa6b0 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Thu, 2 Nov 2023 11:42:39 +0100 Subject: [PATCH 029/107] Rename BlenderCreator into BaseCreator --- openpype/hosts/blender/api/plugin.py | 2 +- openpype/hosts/blender/plugins/create/create_action.py | 2 +- openpype/hosts/blender/plugins/create/create_animation.py | 2 +- openpype/hosts/blender/plugins/create/create_camera.py | 2 +- openpype/hosts/blender/plugins/create/create_layout.py | 2 +- openpype/hosts/blender/plugins/create/create_model.py | 2 +- openpype/hosts/blender/plugins/create/create_pointcache.py | 2 +- openpype/hosts/blender/plugins/create/create_render.py | 2 +- openpype/hosts/blender/plugins/create/create_review.py | 2 +- openpype/hosts/blender/plugins/create/create_rig.py | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index dbd9f25d68..3ddc375670 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -140,7 +140,7 @@ def deselect_all(): bpy.context.view_layer.objects.active = active -class BlenderCreator(Creator): +class BaseCreator(Creator): """Base class for Blender Creator plug-ins.""" defaults = ['Main'] diff --git a/openpype/hosts/blender/plugins/create/create_action.py b/openpype/hosts/blender/plugins/create/create_action.py index e7b689c54e..7d00aa1dcb 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -8,7 +8,7 @@ from openpype.hosts.blender.api import lib from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY -class CreateAction(openpype.hosts.blender.api.plugin.BlenderCreator): +class CreateAction(openpype.hosts.blender.api.plugin.BaseCreator): """Action output for character rigs""" identifier = "io.openpype.creators.blender.action" diff --git a/openpype/hosts/blender/plugins/create/create_animation.py b/openpype/hosts/blender/plugins/create/create_animation.py index 8b4214ceda..6cfd054e74 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -10,7 +10,7 @@ from openpype.hosts.blender.api.pipeline import ( ) -class CreateAnimation(plugin.BlenderCreator): +class CreateAnimation(plugin.BaseCreator): """Animation output for character rigs""" identifier = "io.openpype.creators.blender.animation" diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index 4747e50b2e..5d9682e575 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -10,7 +10,7 @@ from openpype.hosts.blender.api.pipeline import ( ) -class CreateCamera(plugin.BlenderCreator): +class CreateCamera(plugin.BaseCreator): """Polygonal static geometry""" identifier = "io.openpype.creators.blender.camera" diff --git a/openpype/hosts/blender/plugins/create/create_layout.py b/openpype/hosts/blender/plugins/create/create_layout.py index 0c97d57af3..ed47b0632f 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -10,7 +10,7 @@ from openpype.hosts.blender.api.pipeline import ( ) -class CreateLayout(plugin.BlenderCreator): +class CreateLayout(plugin.BaseCreator): """Layout output for character rigs""" identifier = "io.openpype.creators.blender.layout" diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index 761d9fca9f..949fae0f76 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -10,7 +10,7 @@ from openpype.hosts.blender.api.pipeline import ( ) -class CreateModel(plugin.BlenderCreator): +class CreateModel(plugin.BaseCreator): """Polygonal static geometry""" identifier = "io.openpype.creators.blender.model" diff --git a/openpype/hosts/blender/plugins/create/create_pointcache.py b/openpype/hosts/blender/plugins/create/create_pointcache.py index a40bd5af61..2ad12caa9c 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -8,7 +8,7 @@ from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY -class CreatePointcache(plugin.BlenderCreator): +class CreatePointcache(plugin.BaseCreator): """Polygonal static geometry""" identifier = "io.openpype.creators.blender.pointcache" diff --git a/openpype/hosts/blender/plugins/create/create_render.py b/openpype/hosts/blender/plugins/create/create_render.py index ab3119b32e..45570f3491 100644 --- a/openpype/hosts/blender/plugins/create/create_render.py +++ b/openpype/hosts/blender/plugins/create/create_render.py @@ -10,7 +10,7 @@ from openpype.hosts.blender.api.pipeline import ( ) -class CreateRenderlayer(plugin.BlenderCreator): +class CreateRenderlayer(plugin.BaseCreator): """Single baked camera""" name = "renderingMain" diff --git a/openpype/hosts/blender/plugins/create/create_review.py b/openpype/hosts/blender/plugins/create/create_review.py index 8c9a8d5927..e8b893b4c0 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -10,7 +10,7 @@ from openpype.hosts.blender.api.pipeline import ( ) -class CreateReview(plugin.BlenderCreator): +class CreateReview(plugin.BaseCreator): """Single baked camera""" identifier = "io.openpype.creators.blender.review" diff --git a/openpype/hosts/blender/plugins/create/create_rig.py b/openpype/hosts/blender/plugins/create/create_rig.py index 110a9f5c8e..6223e64174 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -10,7 +10,7 @@ from openpype.hosts.blender.api.pipeline import ( ) -class CreateRig(plugin.BlenderCreator): +class CreateRig(plugin.BaseCreator): """Artist-friendly rig with controls to direct motion""" identifier = "io.openpype.creators.blender.rig" From 976ff308e97abed1efd6b87f09c477ad16c8f329 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Thu, 2 Nov 2023 16:18:51 +0100 Subject: [PATCH 030/107] Add Create workfile plugin --- .../blender/plugins/create/create_workfile.py | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 openpype/hosts/blender/plugins/create/create_workfile.py diff --git a/openpype/hosts/blender/plugins/create/create_workfile.py b/openpype/hosts/blender/plugins/create/create_workfile.py new file mode 100644 index 0000000000..d1529f75f6 --- /dev/null +++ b/openpype/hosts/blender/plugins/create/create_workfile.py @@ -0,0 +1,100 @@ +import bpy + +from openpype.pipeline import CreatedInstance, AutoCreator +from openpype.client import get_asset_by_name +from openpype.hosts.blender.api.plugin import BaseCreator +from openpype.hosts.blender.api.lib import imprint +from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY + + +class CreateWorkfile(BaseCreator, AutoCreator): + """Workfile auto-creator.""" + identifier = "io.openpype.creators.blender.workfile" + label = "Workfile" + family = "workfile" + icon = "fa5.file" + + def create(self): + """Create workfile instances.""" + current_instance = next( + ( + instance for instance in self.create_context.instances + if instance.creator_identifier == self.identifier + ), + None, + ) + + project_name = self.project_name + asset_name = self.create_context.get_current_asset_name() + task_name = self.create_context.get_current_task_name() + host_name = self.create_context.host_name + + if not current_instance: + asset_doc = get_asset_by_name(project_name, asset_name) + subset_name = self.get_subset_name( + task_name, task_name, asset_doc, project_name, host_name + ) + data = { + "asset": asset_name, + "task": task_name, + "variant": task_name, + } + data.update( + self.get_dynamic_data( + task_name, + task_name, + asset_doc, + project_name, + host_name, + current_instance, + ) + ) + self.log.info("Auto-creating workfile instance...") + current_instance = CreatedInstance( + self.family, subset_name, data, self + ) + self._add_instance_to_context(current_instance) + elif ( + current_instance["asset"] != asset_name + or current_instance["task"] != task_name + ): + # Update instance context if it's different + asset_doc = get_asset_by_name(project_name, asset_name) + subset_name = self.get_subset_name( + task_name, task_name, asset_doc, project_name, host_name + ) + + current_instance.update( + { + "asset": asset_name, + "task": task_name, + "subset": subset_name, + } + ) + + def collect_instances(self): + """Collect workfile instances.""" + self.cache_subsets(self.collection_shared_data) + cached_subsets = self.collection_shared_data["blender_cached_subsets"] + for node in cached_subsets.get(self.identifier, []): + created_instance = CreatedInstance.from_existing( + self.read_instance_node(node), self + ) + self._add_instance_to_context(created_instance) + + def update_instances(self, update_list): + """Update workfile instances.""" + for created_inst, _changes in update_list: + data = created_inst.data_to_store() + node = data.get("instance_node") + if not node: + task_name = self.create_context.get_current_task_name() + + bpy.context.scene[AVALON_PROPERTY] = node = { + "name": f"workfile{task_name}" + } + + created_inst["instance_node"] = node + data = created_inst.data_to_store() + + imprint(node, data) From 10aea1088a68f49218255e4988febe4bc1f0f462 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Fri, 3 Nov 2023 17:06:44 +0100 Subject: [PATCH 031/107] Add collect workfile to blender host --- .../plugins/publish/collect_workfile.py | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 openpype/hosts/blender/plugins/publish/collect_workfile.py diff --git a/openpype/hosts/blender/plugins/publish/collect_workfile.py b/openpype/hosts/blender/plugins/publish/collect_workfile.py new file mode 100644 index 0000000000..e431405e80 --- /dev/null +++ b/openpype/hosts/blender/plugins/publish/collect_workfile.py @@ -0,0 +1,38 @@ +from pathlib import Path + +import bpy +from pyblish.api import InstancePlugin, CollectorOrder + + +class CollectWorkfile(InstancePlugin): + """Inject workfile data into its instance.""" + + order = CollectorOrder + label = "Collect Workfile" + hosts = ["blender"] + families = ["workfile"] + + def process(self, instance): + """Process collector.""" + + context = instance.context + filepath = Path(context.data["currentFile"]) + ext = filepath.suffix + + instance.data.update( + { + "setMembers": [filepath.as_posix()], + "frameStart": context.data.get("frameStart", 1), + "frameEnd": context.data.get("frameEnd", 1), + "handleStart": context.data.get("handleStart", 1), + "handledEnd": context.data.get("handleEnd", 1), + "representations": [ + { + "name": ext.lstrip("."), + "ext": ext, + "files": filepath.name, # TODO resources + "stagingDir": filepath.parent, + } + ], + } + ) From 93101debd87d1d72cbeec69f2131f093f06c27a7 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Tue, 7 Nov 2023 10:23:55 +0100 Subject: [PATCH 032/107] collect workfile remove unused import and comment --- openpype/hosts/blender/plugins/publish/collect_workfile.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/collect_workfile.py b/openpype/hosts/blender/plugins/publish/collect_workfile.py index e431405e80..01c033084b 100644 --- a/openpype/hosts/blender/plugins/publish/collect_workfile.py +++ b/openpype/hosts/blender/plugins/publish/collect_workfile.py @@ -1,6 +1,5 @@ from pathlib import Path -import bpy from pyblish.api import InstancePlugin, CollectorOrder @@ -30,7 +29,7 @@ class CollectWorkfile(InstancePlugin): { "name": ext.lstrip("."), "ext": ext, - "files": filepath.name, # TODO resources + "files": filepath.name, "stagingDir": filepath.parent, } ], From cf356e7ecd66521f91996024bac4e911a4ce09ac Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Tue, 7 Nov 2023 16:00:50 +0100 Subject: [PATCH 033/107] add create render identifier --- openpype/hosts/blender/plugins/create/create_render.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/blender/plugins/create/create_render.py b/openpype/hosts/blender/plugins/create/create_render.py index 45570f3491..e036ae7df3 100644 --- a/openpype/hosts/blender/plugins/create/create_render.py +++ b/openpype/hosts/blender/plugins/create/create_render.py @@ -13,6 +13,7 @@ from openpype.hosts.blender.api.pipeline import ( class CreateRenderlayer(plugin.BaseCreator): """Single baked camera""" + identifier = "io.openpype.creators.blender.render" name = "renderingMain" label = "Render" family = "render" @@ -28,8 +29,7 @@ class CreateRenderlayer(plugin.BaseCreator): bpy.context.scene.collection.children.link(instances) # Create instance object - asset = instance_data.get("asset") - name = plugin.asset_name(asset, subset_name) + name = plugin.asset_name(instance_data.get("asset"), subset_name) asset_group = bpy.data.collections.new(name=name) try: From 2f23b83481de1d7cad5a5139ff0955091e980da0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 8 Nov 2023 13:38:45 +0100 Subject: [PATCH 034/107] traypublisher: failed validator in editorial not necessary --- .../traypublisher/plugins/publish/validate_frame_ranges.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py b/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py index 09de2d8db2..7a5a3c7fc1 100644 --- a/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py +++ b/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py @@ -30,12 +30,17 @@ class ValidateFrameRange(OptionalPyblishPluginMixin, if not self.is_active(instance.data): return + # Skip the instance if does not have asset entity in database + asset_doc = instance.data.get("assetEntity") + if not asset_doc: + self.log.warning("No asset data found, skipping.") + return + if (self.skip_timelines_check and any(re.search(pattern, instance.data["task"]) for pattern in self.skip_timelines_check)): self.log.info("Skipping for {} task".format(instance.data["task"])) - asset_doc = instance.data["assetEntity"] asset_data = asset_doc["data"] frame_start = asset_data["frameStart"] frame_end = asset_data["frameEnd"] From eda0afc26052f1ac1d5a6088aed71db94e8c3010 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 8 Nov 2023 14:27:47 +0100 Subject: [PATCH 035/107] traypublisher: adding exceptions for editorial instances --- .../traypublisher/plugins/create/create_editorial.py | 2 ++ .../plugins/publish/collect_sequence_frame_data.py | 6 ++++++ .../plugins/publish/validate_frame_ranges.py | 8 ++++---- openpype/plugins/publish/collect_resources_path.py | 6 ++++++ 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index 8640500b18..a2746f115f 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -701,6 +701,8 @@ or updating already created. Publishing will create OTIO file. # parent time properties "trackStartFrame": track_start_frame, "timelineOffset": timeline_offset, + "isEditorial": True, + # creator_attributes "creator_attributes": creator_attributes } diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_sequence_frame_data.py b/openpype/hosts/traypublisher/plugins/publish/collect_sequence_frame_data.py index db70d4fe0a..92cedf6b5b 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_sequence_frame_data.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_sequence_frame_data.py @@ -27,6 +27,12 @@ class CollectSequenceFrameData( if not self.is_active(instance.data): return + # editorial would fail since they might not be in database yet + is_editorial = instance.data.get("isEditorial") + if is_editorial: + self.log.debug("Instance is Editorial. Skipping.") + return + frame_data = self.get_frame_data_from_repre_sequence(instance) if not frame_data: diff --git a/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py b/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py index 7a5a3c7fc1..4977a13374 100644 --- a/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py +++ b/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py @@ -30,10 +30,10 @@ class ValidateFrameRange(OptionalPyblishPluginMixin, if not self.is_active(instance.data): return - # Skip the instance if does not have asset entity in database - asset_doc = instance.data.get("assetEntity") - if not asset_doc: - self.log.warning("No asset data found, skipping.") + # editorial would fail since they might not be in database yet + is_editorial = instance.data.get("isEditorial") + if is_editorial: + self.log.debug("Instance is Editorial. Skipping.") return if (self.skip_timelines_check and diff --git a/openpype/plugins/publish/collect_resources_path.py b/openpype/plugins/publish/collect_resources_path.py index cfb4d63c1b..14c13310df 100644 --- a/openpype/plugins/publish/collect_resources_path.py +++ b/openpype/plugins/publish/collect_resources_path.py @@ -68,6 +68,12 @@ class CollectResourcesPath(pyblish.api.InstancePlugin): ] def process(self, instance): + # editorial would fail since they might not be in database yet + is_editorial = instance.data.get("isEditorial") + if is_editorial: + self.log.debug("Instance is Editorial. Skipping.") + return + anatomy = instance.context.data["anatomy"] template_data = copy.deepcopy(instance.data["anatomyData"]) From 7cd98fe9033c17b4897208ab7c54bda5e7e53c45 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Thu, 9 Nov 2023 15:52:36 +0100 Subject: [PATCH 036/107] Remove deprecated code, use avalon instances in basecreator --- openpype/hosts/blender/api/plugin.py | 25 +++++----- .../blender/plugins/create/create_action.py | 25 ---------- .../plugins/create/create_animation.py | 37 -------------- .../blender/plugins/create/create_camera.py | 49 ------------------- .../blender/plugins/create/create_layout.py | 37 -------------- .../blender/plugins/create/create_model.py | 37 -------------- .../plugins/create/create_pointcache.py | 36 -------------- .../blender/plugins/create/create_review.py | 33 ------------- .../blender/plugins/create/create_rig.py | 37 -------------- 9 files changed, 11 insertions(+), 305 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 3ddc375670..629cb4dac9 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -144,18 +144,6 @@ class BaseCreator(Creator): """Base class for Blender Creator plug-ins.""" defaults = ['Main'] - # Deprecated? - def process(self): - collection = bpy.data.collections.new(name=self.data["subset"]) - bpy.context.scene.collection.children.link(collection) - imprint(collection, self.data) - - if (self.options or {}).get("useSelection"): - for obj in get_selection(): - collection.objects.link(obj) - - return collection - @staticmethod def cache_subsets(shared_data): """Cache instances for Creators shared data. @@ -233,8 +221,17 @@ class BaseCreator(Creator): pre_create_data(dict): Data based on pre creation attributes. Those may affect how creator works. """ - collection = bpy.data.collections.new(name=subset_name) - bpy.context.scene.collection.children.link(collection) + # Get Instance Container or create it if it does not exist + instances = bpy.data.collections.get(AVALON_INSTANCES) + if not instances: + instances = bpy.data.collections.new(name=AVALON_INSTANCES) + bpy.context.scene.collection.children.link(instances) + + # Create instance collection + collection = bpy.data.collections.new( + name=asset_name(instance_data["asset"], subset_name) + ) + instances.children.link(collection) collection[AVALON_PROPERTY] = instance_node = { "name": collection.name, diff --git a/openpype/hosts/blender/plugins/create/create_action.py b/openpype/hosts/blender/plugins/create/create_action.py index 7d00aa1dcb..9267fc0765 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -59,28 +59,3 @@ class CreateAction(openpype.hosts.blender.api.plugin.BaseCreator): collection.objects.link(empty_obj) return collection - - # Deprecated - def process(self): - - asset = self.data["asset"] - subset = self.data["subset"] - name = openpype.hosts.blender.api.plugin.asset_name(asset, subset) - collection = bpy.data.collections.new(name=name) - bpy.context.scene.collection.children.link(collection) - self.data['task'] = get_current_task_name() - lib.imprint(collection, self.data) - - if (self.options or {}).get("useSelection"): - for obj in lib.get_selection(): - if (obj.animation_data is not None - and obj.animation_data.action is not None): - - empty_obj = bpy.data.objects.new(name=name, - object_data=None) - empty_obj.animation_data_create() - empty_obj.animation_data.action = obj.animation_data.action - empty_obj.animation_data.action.name = name - collection.objects.link(empty_obj) - - return collection diff --git a/openpype/hosts/blender/plugins/create/create_animation.py b/openpype/hosts/blender/plugins/create/create_animation.py index 6cfd054e74..89567061b6 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -75,40 +75,3 @@ class CreateAnimation(plugin.BaseCreator): asset_group.objects.link(obj) return asset_group - - # Deprecated - def process(self): - """ Run the creator on Blender main thread""" - mti = ops.MainThreadItem(self._process_legacy) - ops.execute_in_main_thread(mti) - - # Deprecated - def _process_legacy(self): - # Get Instance Container or create it if it does not exist - instances = bpy.data.collections.get(AVALON_INSTANCES) - if not instances: - instances = bpy.data.collections.new(name=AVALON_INSTANCES) - bpy.context.scene.collection.children.link(instances) - - # Create instance object - # name = self.name - # if not name: - asset = self.data["asset"] - subset = self.data["subset"] - name = plugin.asset_name(asset, subset) - # asset_group = bpy.data.objects.new(name=name, object_data=None) - # asset_group.empty_display_type = 'SINGLE_ARROW' - asset_group = bpy.data.collections.new(name=name) - instances.children.link(asset_group) - self.data['task'] = get_current_task_name() - lib.imprint(asset_group, self.data) - - if (self.options or {}).get("useSelection"): - selected = lib.get_selection() - for obj in selected: - asset_group.objects.link(obj) - elif (self.options or {}).get("asset_group"): - obj = (self.options or {}).get("asset_group") - asset_group.objects.link(obj) - - return asset_group diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index 5d9682e575..125514ae01 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -84,52 +84,3 @@ class CreateCamera(plugin.BaseCreator): bpy.ops.object.parent_set(keep_transform=True) return asset_group - - # Deprecated - def process(self): - """ Run the creator on Blender main thread""" - mti = ops.MainThreadItem(self._process_legacy) - ops.execute_in_main_thread(mti) - - # Deprecated - def _process_legacy(self): - # Get Instance Container or create it if it does not exist - instances = bpy.data.collections.get(AVALON_INSTANCES) - if not instances: - instances = bpy.data.collections.new(name=AVALON_INSTANCES) - bpy.context.scene.collection.children.link(instances) - - # Create instance object - asset = self.data["asset"] - subset = self.data["subset"] - name = plugin.asset_name(asset, subset) - - asset_group = bpy.data.objects.new(name=name, object_data=None) - asset_group.empty_display_type = 'SINGLE_ARROW' - instances.objects.link(asset_group) - self.data['task'] = get_current_task_name() - print(f"self.data: {self.data}") - lib.imprint(asset_group, self.data) - - if (self.options or {}).get("useSelection"): - bpy.context.view_layer.objects.active = asset_group - selected = lib.get_selection() - for obj in selected: - if obj.parent in selected: - obj.select_set(False) - continue - selected.append(asset_group) - bpy.ops.object.parent_set(keep_transform=True) - else: - plugin.deselect_all() - camera = bpy.data.cameras.new(subset) - camera_obj = bpy.data.objects.new(subset, camera) - - instances.objects.link(camera_obj) - - camera_obj.select_set(True) - asset_group.select_set(True) - bpy.context.view_layer.objects.active = asset_group - bpy.ops.object.parent_set(keep_transform=True) - - return asset_group diff --git a/openpype/hosts/blender/plugins/create/create_layout.py b/openpype/hosts/blender/plugins/create/create_layout.py index ed47b0632f..a6c7053cb2 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -73,40 +73,3 @@ class CreateLayout(plugin.BaseCreator): bpy.ops.object.parent_set(keep_transform=True) return asset_group - - # Deprecated - def process(self): - """ Run the creator on Blender main thread""" - mti = ops.MainThreadItem(self._process_legacy) - ops.execute_in_main_thread(mti) - - # Deprecated - def _process_legacy(self): - # Get Instance Container or create it if it does not exist - instances = bpy.data.collections.get(AVALON_INSTANCES) - if not instances: - instances = bpy.data.collections.new(name=AVALON_INSTANCES) - bpy.context.scene.collection.children.link(instances) - - # Create instance object - asset = self.data["asset"] - subset = self.data["subset"] - name = plugin.asset_name(asset, subset) - asset_group = bpy.data.objects.new(name=name, object_data=None) - asset_group.empty_display_type = 'SINGLE_ARROW' - instances.objects.link(asset_group) - self.data['task'] = get_current_task_name() - lib.imprint(asset_group, self.data) - - # Add selected objects to instance - if (self.options or {}).get("useSelection"): - bpy.context.view_layer.objects.active = asset_group - selected = lib.get_selection() - for obj in selected: - if obj.parent in selected: - obj.select_set(False) - continue - selected.append(asset_group) - bpy.ops.object.parent_set(keep_transform=True) - - return asset_group diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index 949fae0f76..8beb8025c4 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -73,40 +73,3 @@ class CreateModel(plugin.BaseCreator): bpy.ops.object.parent_set(keep_transform=True) return asset_group - - # Deprecated - def process(self): - """ Run the creator on Blender main thread""" - mti = ops.MainThreadItem(self._process_legacy) - ops.execute_in_main_thread(mti) - - # Deprecated - def _process_legacy(self): - # Get Instance Container or create it if it does not exist - instances = bpy.data.collections.get(AVALON_INSTANCES) - if not instances: - instances = bpy.data.collections.new(name=AVALON_INSTANCES) - bpy.context.scene.collection.children.link(instances) - - # Create instance object - asset = self.data["asset"] - subset = self.data["subset"] - name = plugin.asset_name(asset, subset) - asset_group = bpy.data.objects.new(name=name, object_data=None) - asset_group.empty_display_type = 'SINGLE_ARROW' - instances.objects.link(asset_group) - self.data['task'] = get_current_task_name() - lib.imprint(asset_group, self.data) - - # Add selected objects to instance - if (self.options or {}).get("useSelection"): - bpy.context.view_layer.objects.active = asset_group - selected = lib.get_selection() - for obj in selected: - if obj.parent in selected: - obj.select_set(False) - continue - selected.append(asset_group) - bpy.ops.object.parent_set(keep_transform=True) - - return asset_group diff --git a/openpype/hosts/blender/plugins/create/create_pointcache.py b/openpype/hosts/blender/plugins/create/create_pointcache.py index 2ad12caa9c..aa8b297d16 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -54,39 +54,3 @@ class CreatePointcache(plugin.BaseCreator): objects.extend(obj.children) return collection - - # Deprecated - def process(self): - """ Run the creator on Blender main thread""" - mti = ops.MainThreadItem(self._process) - ops.execute_in_main_thread(mti) - - def _process(self): - # Get Instance Container or create it if it does not exist - instances = bpy.data.collections.get(AVALON_INSTANCES) - if not instances: - instances = bpy.data.collections.new(name=AVALON_INSTANCES) - bpy.context.scene.collection.children.link(instances) - - # Create instance object - asset = self.data["asset"] - subset = self.data["subset"] - name = plugin.asset_name(asset, subset) - asset_group = bpy.data.objects.new(name=name, object_data=None) - asset_group.empty_display_type = 'SINGLE_ARROW' - instances.objects.link(asset_group) - self.data['task'] = get_current_task_name() - lib.imprint(asset_group, self.data) - - # Add selected objects to instance - if (self.options or {}).get("useSelection"): - bpy.context.view_layer.objects.active = asset_group - selected = lib.get_selection() - for obj in selected: - if obj.parent in selected: - obj.select_set(False) - continue - selected.append(asset_group) - bpy.ops.object.parent_set(keep_transform=True) - - return asset_group diff --git a/openpype/hosts/blender/plugins/create/create_review.py b/openpype/hosts/blender/plugins/create/create_review.py index e8b893b4c0..13fa3b621f 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -71,36 +71,3 @@ class CreateReview(plugin.BaseCreator): asset_group.objects.link(obj) return asset_group - - # Deprecated - def process(self): - """ Run the creator on Blender main thread""" - mti = ops.MainThreadItem(self._process_legacy) - ops.execute_in_main_thread(mti) - - # Deprecated - def _process_legacy(self): - # Get Instance Container or create it if it does not exist - instances = bpy.data.collections.get(AVALON_INSTANCES) - if not instances: - instances = bpy.data.collections.new(name=AVALON_INSTANCES) - bpy.context.scene.collection.children.link(instances) - - # Create instance object - asset = self.data["asset"] - subset = self.data["subset"] - name = plugin.asset_name(asset, subset) - asset_group = bpy.data.collections.new(name=name) - instances.children.link(asset_group) - self.data['task'] = get_current_task_name() - lib.imprint(asset_group, self.data) - - if (self.options or {}).get("useSelection"): - selected = lib.get_selection() - for obj in selected: - asset_group.objects.link(obj) - elif (self.options or {}).get("asset_group"): - obj = (self.options or {}).get("asset_group") - asset_group.objects.link(obj) - - return asset_group diff --git a/openpype/hosts/blender/plugins/create/create_rig.py b/openpype/hosts/blender/plugins/create/create_rig.py index 6223e64174..6682162f4b 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -73,40 +73,3 @@ class CreateRig(plugin.BaseCreator): bpy.ops.object.parent_set(keep_transform=True) return asset_group - - # Deprecated - def process(self): - """ Run the creator on Blender main thread""" - mti = ops.MainThreadItem(self._process_legacy) - ops.execute_in_main_thread(mti) - - # Deprecated - def _process_legacy(self): - # Get Instance Container or create it if it does not exist - instances = bpy.data.collections.get(AVALON_INSTANCES) - if not instances: - instances = bpy.data.collections.new(name=AVALON_INSTANCES) - bpy.context.scene.collection.children.link(instances) - - # Create instance object - asset = self.data["asset"] - subset = self.data["subset"] - name = plugin.asset_name(asset, subset) - asset_group = bpy.data.objects.new(name=name, object_data=None) - asset_group.empty_display_type = 'SINGLE_ARROW' - instances.objects.link(asset_group) - self.data['task'] = get_current_task_name() - lib.imprint(asset_group, self.data) - - # Add selected objects to instance - if (self.options or {}).get("useSelection"): - bpy.context.view_layer.objects.active = asset_group - selected = lib.get_selection() - for obj in selected: - if obj.parent in selected: - obj.select_set(False) - continue - selected.append(asset_group) - bpy.ops.object.parent_set(keep_transform=True) - - return asset_group From 01be65d283a531ad8eae43beca9d3637bf7221f2 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Fri, 10 Nov 2023 16:18:28 +0100 Subject: [PATCH 037/107] Reduce redundancy in create action, animation, pointcache, render and review --- openpype/hosts/blender/api/plugin.py | 16 ++++--- .../blender/plugins/create/create_action.py | 34 +++----------- .../plugins/create/create_animation.py | 45 +++---------------- .../plugins/create/create_pointcache.py | 34 ++------------ .../blender/plugins/create/create_render.py | 43 +++--------------- .../blender/plugins/create/create_review.py | 42 +++-------------- 6 files changed, 41 insertions(+), 173 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 629cb4dac9..aef891bd83 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -237,6 +237,7 @@ class BaseCreator(Creator): "name": collection.name, } + # Set instance data instance_data.update( { "id": "pyblish.avalon.instance", @@ -248,16 +249,15 @@ class BaseCreator(Creator): } ) - instance = CreatedInstance( - self.family, subset_name, instance_data, self + self._add_instance_to_context( + CreatedInstance( + self.family, subset_name, instance_data, self + ) ) - self._add_instance_to_context(instance) imprint(collection, instance_data) - if pre_create_data.get("useSelection"): - for obj in get_selection(): - collection.objects.link(obj) + return collection def collect_instances(self): """Override abstract method from BaseCreator. @@ -267,7 +267,9 @@ class BaseCreator(Creator): self.cache_subsets(self.collection_shared_data) # Get cached subsets - cached_subsets = self.collection_shared_data.get('blender_cached_subsets') + cached_subsets = self.collection_shared_data.get( + "blender_cached_subsets" + ) if not cached_subsets: return diff --git a/openpype/hosts/blender/plugins/create/create_action.py b/openpype/hosts/blender/plugins/create/create_action.py index 9267fc0765..95bd42682c 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -2,13 +2,11 @@ import bpy -from openpype.pipeline import get_current_task_name, CreatedInstance -import openpype.hosts.blender.api.plugin +from openpype.hosts.blender.api.plugin import BaseCreator, asset_name from openpype.hosts.blender.api import lib -from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY -class CreateAction(openpype.hosts.blender.api.plugin.BaseCreator): +class CreateAction(BaseCreator): """Action output for character rigs""" identifier = "io.openpype.creators.blender.action" @@ -20,31 +18,13 @@ class CreateAction(openpype.hosts.blender.api.plugin.BaseCreator): def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): - self._add_instance_to_context( - CreatedInstance(self.family, subset_name, instance_data, self) + # Run parent create method + collection = super().create( + subset_name, instance_data, pre_create_data ) - name = openpype.hosts.blender.api.plugin.asset_name( - instance_data["asset"], subset_name - ) - collection = bpy.data.collections.new(name=name) - bpy.context.scene.collection.children.link(collection) - - collection[AVALON_PROPERTY] = instance_node = { - "name": collection.name, - } - - instance_data.update( - { - "id": "pyblish.avalon.instance", - "creator_identifier": self.identifier, - "label": subset_name, - "task": get_current_task_name(), - "subset": subset_name, - "instance_node": instance_node, - } - ) - lib.imprint(collection, instance_data) + # Get instance name + name = asset_name(instance_data["asset"], subset_name) if pre_create_data.get("useSelection"): for obj in lib.get_selection(): diff --git a/openpype/hosts/blender/plugins/create/create_animation.py b/openpype/hosts/blender/plugins/create/create_animation.py index 89567061b6..3c70ae1bd0 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -1,13 +1,8 @@ """Create an animation asset.""" -import bpy -from openpype.pipeline import get_current_task_name, CreatedInstance +from openpype.pipeline import CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops -from openpype.hosts.blender.api.pipeline import ( - AVALON_INSTANCES, - AVALON_PROPERTY, -) class CreateAnimation(plugin.BaseCreator): @@ -35,43 +30,17 @@ class CreateAnimation(plugin.BaseCreator): def _process( self, subset_name: str, instance_data: dict, pre_create_data: dict ): - # Get Instance Container or create it if it does not exist - instances = bpy.data.collections.get(AVALON_INSTANCES) - if not instances: - instances = bpy.data.collections.new(name=AVALON_INSTANCES) - bpy.context.scene.collection.children.link(instances) - - # Create instance object - # name = self.name - # if not name: - name = plugin.asset_name(instance_data["asset"], subset_name) - # asset_group = bpy.data.objects.new(name=name, object_data=None) - # asset_group.empty_display_type = 'SINGLE_ARROW' - asset_group = bpy.data.collections.new(name=name) - instances.children.link(asset_group) - - asset_group[AVALON_PROPERTY] = instance_node = { - "name": asset_group.name, - } - - instance_data.update( - { - "id": "pyblish.avalon.instance", - "creator_identifier": self.identifier, - "label": subset_name, - "task": get_current_task_name(), - "subset": subset_name, - "instance_node": instance_node, - } + # Run parent create method + collection = super().create( + subset_name, instance_data, pre_create_data ) - lib.imprint(asset_group, instance_data) if pre_create_data.get("useSelection"): selected = lib.get_selection() for obj in selected: - asset_group.objects.link(obj) + collection.objects.link(obj) elif pre_create_data.get("asset_group"): obj = (self.options or {}).get("asset_group") - asset_group.objects.link(obj) + collection.objects.link(obj) - return asset_group + return collection diff --git a/openpype/hosts/blender/plugins/create/create_pointcache.py b/openpype/hosts/blender/plugins/create/create_pointcache.py index aa8b297d16..d823193249 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -1,11 +1,6 @@ """Create a pointcache asset.""" -import bpy - -from openpype.pipeline import get_current_task_name, CreatedInstance -from openpype.hosts.blender.api import plugin, lib, ops -from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES -from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY +from openpype.hosts.blender.api import plugin, lib class CreatePointcache(plugin.BaseCreator): @@ -20,32 +15,11 @@ class CreatePointcache(plugin.BaseCreator): def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): - self._add_instance_to_context( - CreatedInstance(self.family, subset_name, instance_data, self) + # Run parent create method + collection = super().create( + subset_name, instance_data, pre_create_data ) - name = plugin.asset_name( - instance_data["asset"], subset_name - ) - collection = bpy.data.collections.new(name=name) - bpy.context.scene.collection.children.link(collection) - - collection[AVALON_PROPERTY] = instance_node = { - "name": collection.name, - } - - instance_data.update( - { - "id": "pyblish.avalon.instance", - "creator_identifier": self.identifier, - "label": subset_name, - "task": get_current_task_name(), - "subset": subset_name, - "instance_node": instance_node, - } - ) - lib.imprint(collection, instance_data) - if pre_create_data.get("useSelection"): objects = lib.get_selection() for obj in objects: diff --git a/openpype/hosts/blender/plugins/create/create_render.py b/openpype/hosts/blender/plugins/create/create_render.py index e036ae7df3..0e5a284caf 100644 --- a/openpype/hosts/blender/plugins/create/create_render.py +++ b/openpype/hosts/blender/plugins/create/create_render.py @@ -1,13 +1,8 @@ """Create render.""" import bpy -from openpype.pipeline import get_current_task_name -from openpype.hosts.blender.api import plugin, lib +from openpype.hosts.blender.api import plugin from openpype.hosts.blender.api.render_lib import prepare_rendering -from openpype.hosts.blender.api.pipeline import ( - AVALON_INSTANCES, - AVALON_PROPERTY, -) class CreateRenderlayer(plugin.BaseCreator): @@ -22,40 +17,16 @@ class CreateRenderlayer(plugin.BaseCreator): def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): - # Get Instance Container or create it if it does not exist - instances = bpy.data.collections.get(AVALON_INSTANCES) - if not instances: - instances = bpy.data.collections.new(name=AVALON_INSTANCES) - bpy.context.scene.collection.children.link(instances) - - # Create instance object - name = plugin.asset_name(instance_data.get("asset"), subset_name) - asset_group = bpy.data.collections.new(name=name) - try: - instances.children.link(asset_group) - - asset_group[AVALON_PROPERTY] = instance_node = { - "name": asset_group.name - } - - instance_data.update( - { - "id": "pyblish.avalon.instance", - "creator_identifier": self.identifier, - "label": subset_name, - "task": get_current_task_name(), - "subset": subset_name, - "instance_node": instance_node, - } + # Run parent create method + collection = super().create( + subset_name, instance_data, pre_create_data ) - lib.imprint(asset_group, instance_data) - - prepare_rendering(asset_group) + prepare_rendering(collection) except Exception: # Remove the instance if there was an error - bpy.data.collections.remove(asset_group) + bpy.data.collections.remove(collection) raise # TODO: this is undesiderable, but it's the only way to be sure that @@ -69,4 +40,4 @@ class CreateRenderlayer(plugin.BaseCreator): # now it is to force the file to be saved. bpy.ops.wm.save_as_mainfile(filepath=bpy.data.filepath) - return asset_group + return collection diff --git a/openpype/hosts/blender/plugins/create/create_review.py b/openpype/hosts/blender/plugins/create/create_review.py index 13fa3b621f..e35e405ee1 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -1,13 +1,7 @@ """Create review.""" -import bpy - -from openpype.pipeline import get_current_task_name, CreatedInstance +from openpype.pipeline import CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops -from openpype.hosts.blender.api.pipeline import ( - AVALON_INSTANCES, - AVALON_PROPERTY, -) class CreateReview(plugin.BaseCreator): @@ -35,39 +29,17 @@ class CreateReview(plugin.BaseCreator): def _process( self, subset_name: str, instance_data: dict, pre_create_data: dict ): - # Get Instance Container or create it if it does not exist - instances = bpy.data.collections.get(AVALON_INSTANCES) - if not instances: - instances = bpy.data.collections.new(name=AVALON_INSTANCES) - bpy.context.scene.collection.children.link(instances) - - # Create instance object - name = plugin.asset_name(instance_data["asset"], subset_name) - asset_group = bpy.data.collections.new(name=name) - instances.children.link(asset_group) - - asset_group[AVALON_PROPERTY] = instance_node = { - "name": asset_group.name, - } - - instance_data.update( - { - "id": "pyblish.avalon.instance", - "creator_identifier": self.identifier, - "label": subset_name, - "task": get_current_task_name(), - "subset": subset_name, - "instance_node": instance_node, - } + # Run parent create method + collection = super().create( + subset_name, instance_data, pre_create_data ) - lib.imprint(asset_group, instance_data) if pre_create_data.get("useSelection"): selected = lib.get_selection() for obj in selected: - asset_group.objects.link(obj) + collection.objects.link(obj) elif pre_create_data.get("asset_group"): obj = (self.options or {}).get("asset_group") - asset_group.objects.link(obj) + collection.objects.link(obj) - return asset_group + return collection From c614522b4c3df1f80b1151e7e7a59dee4108c05f Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Fri, 10 Nov 2023 17:16:55 +0100 Subject: [PATCH 038/107] Fix animation and review duplication in publisher UI --- .../blender/plugins/create/create_action.py | 2 +- .../plugins/create/create_animation.py | 20 +++---------------- .../plugins/create/create_blendScene.py | 2 +- .../blender/plugins/create/create_camera.py | 4 ++-- .../blender/plugins/create/create_layout.py | 4 ++-- .../blender/plugins/create/create_model.py | 2 +- .../plugins/create/create_pointcache.py | 2 +- .../blender/plugins/create/create_render.py | 2 +- .../blender/plugins/create/create_review.py | 19 +++--------------- .../blender/plugins/create/create_rig.py | 4 ++-- 10 files changed, 17 insertions(+), 44 deletions(-) diff --git a/openpype/hosts/blender/plugins/create/create_action.py b/openpype/hosts/blender/plugins/create/create_action.py index 95bd42682c..ac425dff74 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -7,7 +7,7 @@ from openpype.hosts.blender.api import lib class CreateAction(BaseCreator): - """Action output for character rigs""" + """Action output for character rigs.""" identifier = "io.openpype.creators.blender.action" name = "actionMain" diff --git a/openpype/hosts/blender/plugins/create/create_animation.py b/openpype/hosts/blender/plugins/create/create_animation.py index 3c70ae1bd0..f7cb9f88aa 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -1,12 +1,10 @@ """Create an animation asset.""" - -from openpype.pipeline import CreatedInstance -from openpype.hosts.blender.api import plugin, lib, ops +from openpype.hosts.blender.api import plugin, lib class CreateAnimation(plugin.BaseCreator): - """Animation output for character rigs""" + """Animation output for character rigs.""" identifier = "io.openpype.creators.blender.animation" name = "animationMain" @@ -17,19 +15,7 @@ class CreateAnimation(plugin.BaseCreator): def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): - """ Run the creator on Blender main thread""" - self._add_instance_to_context( - CreatedInstance(self.family, subset_name, instance_data, self) - ) - - mti = ops.MainThreadItem( - self._process, subset_name, instance_data, pre_create_data - ) - ops.execute_in_main_thread(mti) - - def _process( - self, subset_name: str, instance_data: dict, pre_create_data: dict - ): + """Run the creator on Blender main thread.""" # Run parent create method collection = super().create( subset_name, instance_data, pre_create_data diff --git a/openpype/hosts/blender/plugins/create/create_blendScene.py b/openpype/hosts/blender/plugins/create/create_blendScene.py index 23ff991654..df3a70f199 100644 --- a/openpype/hosts/blender/plugins/create/create_blendScene.py +++ b/openpype/hosts/blender/plugins/create/create_blendScene.py @@ -11,7 +11,7 @@ from openpype.hosts.blender.api.pipeline import ( class CreateBlendScene(plugin.Creator): - """Generic group of assets""" + """Generic group of assets.""" name = "blendScene" label = "Blender Scene" diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index 125514ae01..a42a9d6ef0 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -11,7 +11,7 @@ from openpype.hosts.blender.api.pipeline import ( class CreateCamera(plugin.BaseCreator): - """Polygonal static geometry""" + """Polygonal static geometry.""" identifier = "io.openpype.creators.blender.camera" name = "cameraMain" @@ -22,7 +22,7 @@ class CreateCamera(plugin.BaseCreator): def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): - """ Run the creator on Blender main thread""" + """Run the creator on Blender main thread.""" self._add_instance_to_context( CreatedInstance(self.family, subset_name, instance_data, self) ) diff --git a/openpype/hosts/blender/plugins/create/create_layout.py b/openpype/hosts/blender/plugins/create/create_layout.py index a6c7053cb2..ef1822dedf 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -11,7 +11,7 @@ from openpype.hosts.blender.api.pipeline import ( class CreateLayout(plugin.BaseCreator): - """Layout output for character rigs""" + """Layout output for character rigs.""" identifier = "io.openpype.creators.blender.layout" name = "layoutMain" @@ -22,7 +22,7 @@ class CreateLayout(plugin.BaseCreator): def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): - """ Run the creator on Blender main thread""" + """Run the creator on Blender main thread.""" self._add_instance_to_context( CreatedInstance(self.family, subset_name, instance_data, self) ) diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index 8beb8025c4..e4937f989e 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -11,7 +11,7 @@ from openpype.hosts.blender.api.pipeline import ( class CreateModel(plugin.BaseCreator): - """Polygonal static geometry""" + """Polygonal static geometry.""" identifier = "io.openpype.creators.blender.model" name = "modelMain" diff --git a/openpype/hosts/blender/plugins/create/create_pointcache.py b/openpype/hosts/blender/plugins/create/create_pointcache.py index d823193249..d05b3adb04 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -4,7 +4,7 @@ from openpype.hosts.blender.api import plugin, lib class CreatePointcache(plugin.BaseCreator): - """Polygonal static geometry""" + """Polygonal static geometry.""" identifier = "io.openpype.creators.blender.pointcache" name = "pointcacheMain" diff --git a/openpype/hosts/blender/plugins/create/create_render.py b/openpype/hosts/blender/plugins/create/create_render.py index 0e5a284caf..bffa5696df 100644 --- a/openpype/hosts/blender/plugins/create/create_render.py +++ b/openpype/hosts/blender/plugins/create/create_render.py @@ -6,7 +6,7 @@ from openpype.hosts.blender.api.render_lib import prepare_rendering class CreateRenderlayer(plugin.BaseCreator): - """Single baked camera""" + """Single baked camera.""" identifier = "io.openpype.creators.blender.render" name = "renderingMain" diff --git a/openpype/hosts/blender/plugins/create/create_review.py b/openpype/hosts/blender/plugins/create/create_review.py index e35e405ee1..91333b7741 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -1,11 +1,10 @@ """Create review.""" -from openpype.pipeline import CreatedInstance -from openpype.hosts.blender.api import plugin, lib, ops +from openpype.hosts.blender.api import plugin, lib class CreateReview(plugin.BaseCreator): - """Single baked camera""" + """Single baked camera.""" identifier = "io.openpype.creators.blender.review" name = "reviewDefault" @@ -16,19 +15,7 @@ class CreateReview(plugin.BaseCreator): def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): - """ Run the creator on Blender main thread""" - self._add_instance_to_context( - CreatedInstance(self.family, subset_name, instance_data, self) - ) - - mti = ops.MainThreadItem( - self._process, subset_name, instance_data, pre_create_data - ) - ops.execute_in_main_thread(mti) - - def _process( - self, subset_name: str, instance_data: dict, pre_create_data: dict - ): + """Run the creator on Blender main thread.""" # Run parent create method collection = super().create( subset_name, instance_data, pre_create_data diff --git a/openpype/hosts/blender/plugins/create/create_rig.py b/openpype/hosts/blender/plugins/create/create_rig.py index 6682162f4b..b54cc73d4a 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -11,7 +11,7 @@ from openpype.hosts.blender.api.pipeline import ( class CreateRig(plugin.BaseCreator): - """Artist-friendly rig with controls to direct motion""" + """Artist-friendly rig with controls to direct motion.""" identifier = "io.openpype.creators.blender.rig" name = "rigMain" @@ -22,7 +22,7 @@ class CreateRig(plugin.BaseCreator): def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): - """ Run the creator on Blender main thread""" + """Run the creator on Blender main thread.""" self._add_instance_to_context( CreatedInstance(self.family, subset_name, instance_data, self) ) From 62fa3304178fc0d9d14bf4f7b5a58e2dee1c5680 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Mon, 13 Nov 2023 11:58:47 +0100 Subject: [PATCH 039/107] Add set_instance_data method to reduce redundancy --- openpype/hosts/blender/api/plugin.py | 41 +++++++++++++------ .../plugins/create/create_blendScene.py | 12 +----- .../blender/plugins/create/create_camera.py | 11 +---- .../blender/plugins/create/create_layout.py | 11 +---- .../blender/plugins/create/create_model.py | 11 +---- .../blender/plugins/create/create_rig.py | 11 +---- 6 files changed, 34 insertions(+), 63 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index aef891bd83..e9683bee3a 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -217,7 +217,7 @@ class BaseCreator(Creator): Args: subset_name(str): Subset name of created instance. - instance_data(dict): Base data for instance. + instance_data(dict): Instance base data. pre_create_data(dict): Data based on pre creation attributes. Those may affect how creator works. """ @@ -237,17 +237,7 @@ class BaseCreator(Creator): "name": collection.name, } - # Set instance data - instance_data.update( - { - "id": "pyblish.avalon.instance", - "creator_identifier": self.identifier, - "label": subset_name, - "task": get_current_task_name(), - "subset": subset_name, - "instance_node": instance_node, - } - ) + self.set_instance_data(subset_name, instance_data, instance_node) self._add_instance_to_context( CreatedInstance( @@ -326,6 +316,33 @@ class BaseCreator(Creator): self._remove_instance_from_context(instance) + def set_instance_data( + self, + subset_name: str, + instance_data: dict, + instance_node: bpy.types.ID, + ): + """Fill instance data with required items. + + Args: + subset_name(str): Subset name of created instance. + instance_data(dict): Instance base data. + instance_node(bpy.types.ID): Instance node in blender scene. + """ + if not instance_data: + instance_data = {} + + instance_data.update( + { + "id": "pyblish.avalon.instance", + "creator_identifier": self.identifier, + "label": subset_name, + "task": get_current_task_name(), + "subset": subset_name, + "instance_node": instance_node, + } + ) + class Loader(LoaderPlugin): """Base class for Loader plug-ins.""" diff --git a/openpype/hosts/blender/plugins/create/create_blendScene.py b/openpype/hosts/blender/plugins/create/create_blendScene.py index df3a70f199..0773c4dae3 100644 --- a/openpype/hosts/blender/plugins/create/create_blendScene.py +++ b/openpype/hosts/blender/plugins/create/create_blendScene.py @@ -47,17 +47,7 @@ class CreateBlendScene(plugin.Creator): "name": asset_group.name } - instance_data.update( - { - "id": "publish.avalon.instance", - "creator_identifier": self.identifier, - "label": subset_name, - "task": get_current_task_name(), - "subset": subset_name, - "instance_node": instance_node, - } - ) - + self.set_instance_data(subset_name, instance_data, instance_node) lib.imprint(asset_group, instance_data) # Add selected objects to instance diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index a42a9d6ef0..8dbba229a9 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -52,16 +52,7 @@ class CreateCamera(plugin.BaseCreator): "name": asset_group.name, } - instance_data.update( - { - "id": "pyblish.avalon.instance", - "creator_identifier": self.identifier, - "label": subset_name, - "task": get_current_task_name(), - "subset": subset_name, - "instance_node": instance_node, - } - ) + self.set_instance_data(subset_name, instance_data, instance_node) lib.imprint(asset_group, instance_data) if pre_create_data.get("useSelection"): diff --git a/openpype/hosts/blender/plugins/create/create_layout.py b/openpype/hosts/blender/plugins/create/create_layout.py index ef1822dedf..a9bf115ea9 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -51,16 +51,7 @@ class CreateLayout(plugin.BaseCreator): "name": asset_group.name, } - instance_data.update( - { - "id": "pyblish.avalon.instance", - "creator_identifier": self.identifier, - "label": subset_name, - "task": get_current_task_name(), - "subset": subset_name, - "instance_node": instance_node, - } - ) + self.set_instance_data(subset_name, instance_data, instance_node) lib.imprint(asset_group, instance_data) # Add selected objects to instance diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index e4937f989e..3501071e8f 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -51,16 +51,7 @@ class CreateModel(plugin.BaseCreator): "name": asset_group.name, } - instance_data.update( - { - "id": "pyblish.avalon.instance", - "creator_identifier": self.identifier, - "label": subset_name, - "task": get_current_task_name(), - "subset": subset_name, - "instance_node": instance_node, - } - ) + self.set_instance_data(subset_name, instance_data, instance_node) lib.imprint(asset_group, instance_data) # Add selected objects to instance diff --git a/openpype/hosts/blender/plugins/create/create_rig.py b/openpype/hosts/blender/plugins/create/create_rig.py index b54cc73d4a..b5b61b3971 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -51,16 +51,7 @@ class CreateRig(plugin.BaseCreator): "name": asset_group.name, } - instance_data.update( - { - "id": "pyblish.avalon.instance", - "creator_identifier": self.identifier, - "label": subset_name, - "task": get_current_task_name(), - "subset": subset_name, - "instance_node": instance_node, - } - ) + self.set_instance_data(subset_name, instance_data, instance_node) lib.imprint(asset_group, instance_data) # Add selected objects to instance From f906d05c7305f123d8e037bd02e3d5742103883f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 13 Nov 2023 16:56:47 +0100 Subject: [PATCH 040/107] fusion: removing hardcoded template name for saver --- openpype/hosts/fusion/plugins/create/create_saver.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/fusion/plugins/create/create_saver.py b/openpype/hosts/fusion/plugins/create/create_saver.py index 2dc48f4b60..e8ba2880a4 100644 --- a/openpype/hosts/fusion/plugins/create/create_saver.py +++ b/openpype/hosts/fusion/plugins/create/create_saver.py @@ -149,9 +149,13 @@ class CreateSaver(NewCreator): # get frame padding from anatomy templates anatomy = Anatomy() - frame_padding = int( - anatomy.templates["render"].get("frame_padding", 4) - ) + render_anatomy_template = anatomy.templates.get("render") + if render_anatomy_template: + frame_padding = int( + render_anatomy_template.get("frame_padding", 4) + ) + else: + frame_padding = int(anatomy.templates.get("frame_padding", 4)) # Subset change detected workdir = os.path.normpath(legacy_io.Session["AVALON_WORKDIR"]) From 8d58b284fc5a6fac5aeb535c3adcfabde4f7ff54 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Mon, 13 Nov 2023 17:21:28 +0100 Subject: [PATCH 041/107] Remove unused dependency, fix collect_render --- openpype/hosts/blender/api/plugin.py | 5 +---- openpype/hosts/blender/plugins/publish/collect_render.py | 4 +++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index e9683bee3a..4bb489dca2 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -20,10 +20,7 @@ from .ops import ( MainThreadItem, execute_in_main_thread ) -from .lib import ( - imprint, - get_selection -) +from .lib import imprint VALID_EXTENSIONS = [".blend", ".json", ".abc", ".fbx"] diff --git a/openpype/hosts/blender/plugins/publish/collect_render.py b/openpype/hosts/blender/plugins/publish/collect_render.py index 92e2473a95..578ac36ed3 100644 --- a/openpype/hosts/blender/plugins/publish/collect_render.py +++ b/openpype/hosts/blender/plugins/publish/collect_render.py @@ -73,7 +73,9 @@ class CollectBlenderRender(pyblish.api.InstancePlugin): def process(self, instance): context = instance.context - render_data = bpy.data.collections[str(instance)].get("render_data") + render_data = bpy.data.collections[ + instance.data["instance_node"]["name"] + ].get("render_data") assert render_data, "No render data found." From 51f1b14fd679f7df16f9ec8c21dad24725f10e43 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 13 Nov 2023 23:20:09 +0100 Subject: [PATCH 042/107] Fix optional states --- .../blender/plugins/publish/extract_abc.py | 3 ++ .../plugins/publish/extract_abc_animation.py | 3 ++ .../blender/plugins/publish/extract_blend.py | 3 ++ .../publish/extract_blend_animation.py | 3 ++ .../plugins/publish/extract_camera_abc.py | 3 ++ .../plugins/publish/extract_camera_fbx.py | 3 ++ .../blender/plugins/publish/extract_fbx.py | 3 ++ .../plugins/publish/extract_fbx_animation.py | 3 ++ .../blender/plugins/publish/extract_layout.py | 3 ++ .../plugins/publish/extract_playblast.py | 3 +- .../publish/increment_workfile_version.py | 2 ++ .../publish/validate_camera_zero_keyframe.py | 3 ++ .../plugins/publish/validate_file_saved.py | 30 +++++++++++++++++-- .../plugins/publish/validate_mesh_has_uv.py | 3 ++ .../validate_mesh_no_negative_scale.py | 11 +++++-- .../publish/validate_no_colons_in_name.py | 11 +++++-- .../plugins/publish/validate_object_mode.py | 3 ++ .../publish/validate_render_camera_is_set.py | 8 ++++- .../publish/validate_transform_zero.py | 11 +++++-- 19 files changed, 102 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/extract_abc.py b/openpype/hosts/blender/plugins/publish/extract_abc.py index 5af8104344..61ff17e441 100644 --- a/openpype/hosts/blender/plugins/publish/extract_abc.py +++ b/openpype/hosts/blender/plugins/publish/extract_abc.py @@ -15,6 +15,9 @@ class ExtractABC(publish.Extractor, publish.OptionalPyblishPluginMixin): families = ["pointcache"] def process(self, instance): + if not self.is_active(instance.data): + return + # Define extract output file path stagingdir = self.staging_dir(instance) filename = f"{instance.name}.abc" diff --git a/openpype/hosts/blender/plugins/publish/extract_abc_animation.py b/openpype/hosts/blender/plugins/publish/extract_abc_animation.py index 0b6b93b7a5..7ec10ed6c4 100644 --- a/openpype/hosts/blender/plugins/publish/extract_abc_animation.py +++ b/openpype/hosts/blender/plugins/publish/extract_abc_animation.py @@ -18,6 +18,9 @@ class ExtractAnimationABC( optional = True def process(self, instance): + if not self.is_active(instance.data): + return + # Define extract output file path stagingdir = self.staging_dir(instance) filename = f"{instance.name}.abc" diff --git a/openpype/hosts/blender/plugins/publish/extract_blend.py b/openpype/hosts/blender/plugins/publish/extract_blend.py index fba9a861a0..37842ed90f 100644 --- a/openpype/hosts/blender/plugins/publish/extract_blend.py +++ b/openpype/hosts/blender/plugins/publish/extract_blend.py @@ -14,6 +14,9 @@ class ExtractBlend(publish.Extractor, publish.OptionalPyblishPluginMixin): optional = True def process(self, instance): + if not self.is_active(instance.data): + return + # Define extract output file path if not self.is_active(instance.data): diff --git a/openpype/hosts/blender/plugins/publish/extract_blend_animation.py b/openpype/hosts/blender/plugins/publish/extract_blend_animation.py index 3a5b788c9e..84e1e7d602 100644 --- a/openpype/hosts/blender/plugins/publish/extract_blend_animation.py +++ b/openpype/hosts/blender/plugins/publish/extract_blend_animation.py @@ -17,6 +17,9 @@ class ExtractBlendAnimation( optional = True def process(self, instance): + if not self.is_active(instance.data): + return + # Define extract output file path stagingdir = self.staging_dir(instance) diff --git a/openpype/hosts/blender/plugins/publish/extract_camera_abc.py b/openpype/hosts/blender/plugins/publish/extract_camera_abc.py index 2a327f4d65..1c0c033364 100644 --- a/openpype/hosts/blender/plugins/publish/extract_camera_abc.py +++ b/openpype/hosts/blender/plugins/publish/extract_camera_abc.py @@ -16,6 +16,9 @@ class ExtractCameraABC(publish.Extractor, publish.OptionalPyblishPluginMixin): optional = True def process(self, instance): + if not self.is_active(instance.data): + return + # Define extract output file path stagingdir = self.staging_dir(instance) filename = f"{instance.name}.abc" diff --git a/openpype/hosts/blender/plugins/publish/extract_camera_fbx.py b/openpype/hosts/blender/plugins/publish/extract_camera_fbx.py index 8e5e4d37d4..8e5cc6f651 100644 --- a/openpype/hosts/blender/plugins/publish/extract_camera_fbx.py +++ b/openpype/hosts/blender/plugins/publish/extract_camera_fbx.py @@ -15,6 +15,9 @@ class ExtractCamera(publish.Extractor, publish.OptionalPyblishPluginMixin): optional = True def process(self, instance): + if not self.is_active(instance.data): + return + # Define extract output file path stagingdir = self.staging_dir(instance) filename = f"{instance.name}.fbx" diff --git a/openpype/hosts/blender/plugins/publish/extract_fbx.py b/openpype/hosts/blender/plugins/publish/extract_fbx.py index 8ace6a43a7..07c35207d8 100644 --- a/openpype/hosts/blender/plugins/publish/extract_fbx.py +++ b/openpype/hosts/blender/plugins/publish/extract_fbx.py @@ -16,6 +16,9 @@ class ExtractFBX(publish.Extractor, publish.OptionalPyblishPluginMixin): optional = True def process(self, instance): + if not self.is_active(instance.data): + return + # Define extract output file path stagingdir = self.staging_dir(instance) filename = f"{instance.name}.fbx" diff --git a/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py b/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py index 04f50f8207..5a8381dbc3 100644 --- a/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py @@ -22,6 +22,9 @@ class ExtractAnimationFBX( optional = True def process(self, instance): + if not self.is_active(instance.data): + return + # Define extract output file path stagingdir = self.staging_dir(instance) diff --git a/openpype/hosts/blender/plugins/publish/extract_layout.py b/openpype/hosts/blender/plugins/publish/extract_layout.py index 8445560bba..576c2c33fc 100644 --- a/openpype/hosts/blender/plugins/publish/extract_layout.py +++ b/openpype/hosts/blender/plugins/publish/extract_layout.py @@ -113,6 +113,9 @@ class ExtractLayout(publish.Extractor, publish.OptionalPyblishPluginMixin): return None, n def process(self, instance): + if not self.is_active(instance.data): + return + # Define extract output file path stagingdir = self.staging_dir(instance) diff --git a/openpype/hosts/blender/plugins/publish/extract_playblast.py b/openpype/hosts/blender/plugins/publish/extract_playblast.py index 82ddbc1fc2..07b51becce 100644 --- a/openpype/hosts/blender/plugins/publish/extract_playblast.py +++ b/openpype/hosts/blender/plugins/publish/extract_playblast.py @@ -24,7 +24,8 @@ class ExtractPlayblast(publish.Extractor, publish.OptionalPyblishPluginMixin): order = pyblish.api.ExtractorOrder + 0.01 def process(self, instance): - self.log.info("Extracting capture..") + if not self.is_active(instance.data): + return self.log.info(instance.data) diff --git a/openpype/hosts/blender/plugins/publish/increment_workfile_version.py b/openpype/hosts/blender/plugins/publish/increment_workfile_version.py index 176668f366..8e522977e4 100644 --- a/openpype/hosts/blender/plugins/publish/increment_workfile_version.py +++ b/openpype/hosts/blender/plugins/publish/increment_workfile_version.py @@ -17,6 +17,8 @@ class IncrementWorkfileVersion( "pointcache", "render"] def process(self, context): + if not self.is_active(context.data): + return assert all(result["success"] for result in context.data["results"]), ( "Publishing not successful so version is not increased.") diff --git a/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py b/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py index 48c267fd18..f109d9c3ce 100644 --- a/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py +++ b/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py @@ -40,6 +40,9 @@ class ValidateCameraZeroKeyframe(pyblish.api.InstancePlugin): return invalid def process(self, instance): + if not self.is_active(instance.data): + return + invalid = self.get_invalid(instance) if invalid: raise RuntimeError( diff --git a/openpype/hosts/blender/plugins/publish/validate_file_saved.py b/openpype/hosts/blender/plugins/publish/validate_file_saved.py index e191585c55..44b33a573a 100644 --- a/openpype/hosts/blender/plugins/publish/validate_file_saved.py +++ b/openpype/hosts/blender/plugins/publish/validate_file_saved.py @@ -2,8 +2,24 @@ import bpy import pyblish.api +from openpype.pipeline.publish import ( + OptionalPyblishPluginMixin, + PublishValidationError +) -class ValidateFileSaved(pyblish.api.InstancePlugin): + +class SaveWorkfileAction(pyblish.api.Action): + """Save Workfile.""" + label = "Save Workfile" + on = "failed" + icon = "save" + + def process(self, context, plugin): + bpy.ops.wm.avalon_workfiles() + + +class ValidateFileSaved(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Validate that the workfile has been saved.""" order = pyblish.api.ValidatorOrder - 0.01 @@ -11,10 +27,20 @@ class ValidateFileSaved(pyblish.api.InstancePlugin): label = "Validate File Saved" optional = False exclude_families = [] + actions = [SaveWorkfileAction] def process(self, instance): + if not self.is_active(instance.data): + return + + if not instance.context.data["currentFile"]: + # File has not been saved at all and has no filename + raise PublishValidationError( + "Current file is empty. Save the file before continuing." + ) + if [ef for ef in self.exclude_families if instance.data["family"] in ef]: return if bpy.data.is_dirty: - raise RuntimeError("Workfile is not saved.") + raise PublishValidationError("Workfile has unsaved changes.") diff --git a/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py b/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py index 687371b362..c2d63685e2 100644 --- a/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py +++ b/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py @@ -55,6 +55,9 @@ class ValidateMeshHasUvs( return invalid def process(self, instance): + if not self.is_active(instance.data): + return + invalid = self.get_invalid(instance) if invalid: raise RuntimeError( diff --git a/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py b/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py index 618feb95c1..737fd80798 100644 --- a/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py +++ b/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py @@ -4,11 +4,15 @@ import bpy import pyblish.api -from openpype.pipeline.publish import ValidateContentsOrder +from openpype.pipeline.publish import ( + ValidateContentsOrder, + OptionalPyblishPluginMixin, +) import openpype.hosts.blender.api.action -class ValidateMeshNoNegativeScale(pyblish.api.Validator): +class ValidateMeshNoNegativeScale(pyblish.api.Validator, + OptionalPyblishPluginMixin): """Ensure that meshes don't have a negative scale.""" order = ValidateContentsOrder @@ -27,6 +31,9 @@ class ValidateMeshNoNegativeScale(pyblish.api.Validator): return invalid def process(self, instance): + if not self.is_active(instance.data): + return + invalid = self.get_invalid(instance) if invalid: raise RuntimeError( diff --git a/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py b/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py index 1a98ec4c1d..fbaf40e91f 100644 --- a/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py +++ b/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py @@ -5,10 +5,14 @@ import bpy import pyblish.api import openpype.hosts.blender.api.action -from openpype.pipeline.publish import ValidateContentsOrder +from openpype.pipeline.publish import ( + ValidateContentsOrder, + OptionalPyblishPluginMixin, +) -class ValidateNoColonsInName(pyblish.api.InstancePlugin): +class ValidateNoColonsInName(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """There cannot be colons in names Object or bone names cannot include colons. Other software do not @@ -36,6 +40,9 @@ class ValidateNoColonsInName(pyblish.api.InstancePlugin): return invalid def process(self, instance): + if not self.is_active(instance.data): + return + invalid = self.get_invalid(instance) if invalid: raise RuntimeError( diff --git a/openpype/hosts/blender/plugins/publish/validate_object_mode.py b/openpype/hosts/blender/plugins/publish/validate_object_mode.py index d8d2e3c8bf..19e8d1bc8d 100644 --- a/openpype/hosts/blender/plugins/publish/validate_object_mode.py +++ b/openpype/hosts/blender/plugins/publish/validate_object_mode.py @@ -29,6 +29,9 @@ class ValidateObjectIsInObjectMode( return invalid def process(self, instance): + if not self.is_active(instance.data): + return + invalid = self.get_invalid(instance) if invalid: raise RuntimeError( diff --git a/openpype/hosts/blender/plugins/publish/validate_render_camera_is_set.py b/openpype/hosts/blender/plugins/publish/validate_render_camera_is_set.py index ba3a796f35..0207b0fd09 100644 --- a/openpype/hosts/blender/plugins/publish/validate_render_camera_is_set.py +++ b/openpype/hosts/blender/plugins/publish/validate_render_camera_is_set.py @@ -2,8 +2,11 @@ import bpy import pyblish.api +from openpype.pipeline.publish import OptionalPyblishPluginMixin -class ValidateRenderCameraIsSet(pyblish.api.InstancePlugin): + +class ValidateRenderCameraIsSet(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Validate that there is a camera set as active for rendering.""" order = pyblish.api.ValidatorOrder @@ -13,5 +16,8 @@ class ValidateRenderCameraIsSet(pyblish.api.InstancePlugin): optional = False def process(self, instance): + if not self.is_active(instance.data): + return + if not bpy.context.scene.camera: raise RuntimeError("No camera is active for rendering.") diff --git a/openpype/hosts/blender/plugins/publish/validate_transform_zero.py b/openpype/hosts/blender/plugins/publish/validate_transform_zero.py index 66ef731e6e..3c68e61277 100644 --- a/openpype/hosts/blender/plugins/publish/validate_transform_zero.py +++ b/openpype/hosts/blender/plugins/publish/validate_transform_zero.py @@ -6,10 +6,14 @@ import bpy import pyblish.api import openpype.hosts.blender.api.action -from openpype.pipeline.publish import ValidateContentsOrder +from openpype.pipeline.publish import ( + ValidateContentsOrder, + OptionalPyblishPluginMixin, +) -class ValidateTransformZero(pyblish.api.InstancePlugin): +class ValidateTransformZero(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Transforms can't have any values To solve this issue, try freezing the transforms. So long @@ -38,6 +42,9 @@ class ValidateTransformZero(pyblish.api.InstancePlugin): return invalid def process(self, instance): + if not self.is_active(instance.data): + return + invalid = self.get_invalid(instance) if invalid: raise RuntimeError( From 7a0fd92481dcbb36e75bb564a1d0f951804f34c0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 13 Nov 2023 23:23:08 +0100 Subject: [PATCH 043/107] Remove legacy instance collecting (since it resulted in duplicate instances) + fix workfile publishing --- .../plugins/publish/collect_current_file.py | 57 --------------- .../plugins/publish/collect_instances.py | 70 ------------------- .../plugins/publish/collect_workfile.py | 2 +- 3 files changed, 1 insertion(+), 128 deletions(-) delete mode 100644 openpype/hosts/blender/plugins/publish/collect_instances.py diff --git a/openpype/hosts/blender/plugins/publish/collect_current_file.py b/openpype/hosts/blender/plugins/publish/collect_current_file.py index c2d8a96a18..91c88f2e28 100644 --- a/openpype/hosts/blender/plugins/publish/collect_current_file.py +++ b/openpype/hosts/blender/plugins/publish/collect_current_file.py @@ -1,72 +1,15 @@ -import os -import bpy - import pyblish.api -from openpype.pipeline import get_current_task_name, get_current_asset_name from openpype.hosts.blender.api import workio -class SaveWorkfiledAction(pyblish.api.Action): - """Save Workfile.""" - label = "Save Workfile" - on = "failed" - icon = "save" - - def process(self, context, plugin): - bpy.ops.wm.avalon_workfiles() - - class CollectBlenderCurrentFile(pyblish.api.ContextPlugin): """Inject the current working file into context""" order = pyblish.api.CollectorOrder - 0.5 label = "Blender Current File" hosts = ["blender"] - actions = [SaveWorkfiledAction] def process(self, context): """Inject the current working file""" current_file = workio.current_file() - context.data["currentFile"] = current_file - - assert current_file, ( - "Current file is empty. Save the file before continuing." - ) - - folder, file = os.path.split(current_file) - filename, ext = os.path.splitext(file) - - task = get_current_task_name() - - data = {} - - # create instance - instance = context.create_instance(name=filename) - subset = "workfile" + task.capitalize() - - data.update({ - "subset": subset, - "asset": get_current_asset_name(), - "label": subset, - "publish": True, - "family": "workfile", - "families": ["workfile"], - "setMembers": [current_file], - "frameStart": bpy.context.scene.frame_start, - "frameEnd": bpy.context.scene.frame_end, - }) - - data["representations"] = [{ - "name": ext.lstrip("."), - "ext": ext.lstrip("."), - "files": file, - "stagingDir": folder, - }] - - instance.data.update(data) - - self.log.info("Collected instance: {}".format(file)) - self.log.info("Scene path: {}".format(current_file)) - self.log.info("staging Dir: {}".format(folder)) - self.log.info("subset: {}".format(subset)) diff --git a/openpype/hosts/blender/plugins/publish/collect_instances.py b/openpype/hosts/blender/plugins/publish/collect_instances.py deleted file mode 100644 index ad2ce54147..0000000000 --- a/openpype/hosts/blender/plugins/publish/collect_instances.py +++ /dev/null @@ -1,70 +0,0 @@ -import json -from typing import Generator - -import bpy - -import pyblish.api -from openpype.hosts.blender.api.pipeline import ( - AVALON_INSTANCES, - AVALON_PROPERTY, -) - - -class CollectInstances(pyblish.api.ContextPlugin): - """Collect the data of a model.""" - - hosts = ["blender"] - label = "Collect Instances" - order = pyblish.api.CollectorOrder - - @staticmethod - def get_asset_groups() -> Generator: - """Return all instances that are empty objects asset groups. - """ - instances = bpy.data.collections.get(AVALON_INSTANCES) - for obj in list(instances.objects) + list(instances.children): - avalon_prop = obj.get(AVALON_PROPERTY) or {} - if avalon_prop.get('id') == 'pyblish.avalon.instance': - yield obj - - @staticmethod - def create_instance(context, group): - avalon_prop = group[AVALON_PROPERTY] - asset = avalon_prop['asset'] - family = avalon_prop['family'] - subset = avalon_prop['subset'] - task = avalon_prop['task'] - name = f"{asset}_{subset}" - return context.create_instance( - name=name, - family=family, - families=[family], - subset=subset, - asset=asset, - task=task, - ) - - def process(self, context): - """Collect the models from the current Blender scene.""" - asset_groups = self.get_asset_groups() - - for group in asset_groups: - instance = self.create_instance(context, group) - members = [] - if isinstance(group, bpy.types.Collection): - members = list(group.objects) - family = instance.data["family"] - if family == "animation": - for obj in group.objects: - if obj.type == 'EMPTY' and obj.get(AVALON_PROPERTY): - members.extend( - child for child in obj.children - if child.type == 'ARMATURE') - else: - members = group.children_recursive - - members.append(group) - instance[:] = members - self.log.debug(json.dumps(instance.data, indent=4)) - for obj in instance: - self.log.debug(obj) diff --git a/openpype/hosts/blender/plugins/publish/collect_workfile.py b/openpype/hosts/blender/plugins/publish/collect_workfile.py index 01c033084b..6561c89605 100644 --- a/openpype/hosts/blender/plugins/publish/collect_workfile.py +++ b/openpype/hosts/blender/plugins/publish/collect_workfile.py @@ -28,7 +28,7 @@ class CollectWorkfile(InstancePlugin): "representations": [ { "name": ext.lstrip("."), - "ext": ext, + "ext": ext.lstrip("."), "files": filepath.name, "stagingDir": filepath.parent, } From 60486c2d9f07775d6dcd41f34fa7434d7120d84a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 13 Nov 2023 23:36:26 +0100 Subject: [PATCH 044/107] Tweak logging levels for artist-facing reports + some log message cosmetics --- openpype/hosts/blender/plugins/publish/collect_render.py | 4 ++-- openpype/hosts/blender/plugins/publish/extract_abc.py | 6 +++--- .../blender/plugins/publish/extract_abc_animation.py | 6 +++--- openpype/hosts/blender/plugins/publish/extract_blend.py | 6 +++--- .../blender/plugins/publish/extract_blend_animation.py | 6 +++--- .../hosts/blender/plugins/publish/extract_camera_abc.py | 6 +++--- .../hosts/blender/plugins/publish/extract_camera_fbx.py | 6 +++--- openpype/hosts/blender/plugins/publish/extract_fbx.py | 6 +++--- .../blender/plugins/publish/extract_fbx_animation.py | 8 ++++---- openpype/hosts/blender/plugins/publish/extract_layout.py | 8 ++++---- .../hosts/blender/plugins/publish/extract_playblast.py | 6 ++---- .../hosts/blender/plugins/publish/extract_thumbnail.py | 6 +++--- .../blender/plugins/publish/increment_workfile_version.py | 2 +- .../hosts/blender/plugins/publish/integrate_animation.py | 2 +- 14 files changed, 38 insertions(+), 40 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/collect_render.py b/openpype/hosts/blender/plugins/publish/collect_render.py index 578ac36ed3..48b03256ae 100644 --- a/openpype/hosts/blender/plugins/publish/collect_render.py +++ b/openpype/hosts/blender/plugins/publish/collect_render.py @@ -79,7 +79,7 @@ class CollectBlenderRender(pyblish.api.InstancePlugin): assert render_data, "No render data found." - self.log.info(f"render_data: {dict(render_data)}") + self.log.debug(f"render_data: {dict(render_data)}") render_product = render_data.get("render_product") aov_file_product = render_data.get("aov_file_product") @@ -122,4 +122,4 @@ class CollectBlenderRender(pyblish.api.InstancePlugin): "renderProducts": colorspace.ARenderProduct(), }) - self.log.info(f"data: {instance.data}") + self.log.debug(f"data: {instance.data}") diff --git a/openpype/hosts/blender/plugins/publish/extract_abc.py b/openpype/hosts/blender/plugins/publish/extract_abc.py index 61ff17e441..1602c4d266 100644 --- a/openpype/hosts/blender/plugins/publish/extract_abc.py +++ b/openpype/hosts/blender/plugins/publish/extract_abc.py @@ -24,7 +24,7 @@ class ExtractABC(publish.Extractor, publish.OptionalPyblishPluginMixin): filepath = os.path.join(stagingdir, filename) # Perform extraction - self.log.info("Performing extraction..") + self.log.debug("Performing extraction..") plugin.deselect_all() @@ -62,8 +62,8 @@ class ExtractABC(publish.Extractor, publish.OptionalPyblishPluginMixin): } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s", - instance.name, representation) + self.log.debug("Extracted instance '%s' to: %s", + instance.name, representation) class ExtractModelABC(ExtractABC): diff --git a/openpype/hosts/blender/plugins/publish/extract_abc_animation.py b/openpype/hosts/blender/plugins/publish/extract_abc_animation.py index 7ec10ed6c4..0ed2819407 100644 --- a/openpype/hosts/blender/plugins/publish/extract_abc_animation.py +++ b/openpype/hosts/blender/plugins/publish/extract_abc_animation.py @@ -27,7 +27,7 @@ class ExtractAnimationABC( filepath = os.path.join(stagingdir, filename) # Perform extraction - self.log.info("Performing extraction..") + self.log.debug("Performing extraction..") plugin.deselect_all() @@ -72,5 +72,5 @@ class ExtractAnimationABC( } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s", - instance.name, representation) + self.log.debug("Extracted instance '%s' to: %s", + instance.name, representation) diff --git a/openpype/hosts/blender/plugins/publish/extract_blend.py b/openpype/hosts/blender/plugins/publish/extract_blend.py index 37842ed90f..884169f7f1 100644 --- a/openpype/hosts/blender/plugins/publish/extract_blend.py +++ b/openpype/hosts/blender/plugins/publish/extract_blend.py @@ -27,7 +27,7 @@ class ExtractBlend(publish.Extractor, publish.OptionalPyblishPluginMixin): filepath = os.path.join(stagingdir, filename) # Perform extraction - self.log.info("Performing extraction..") + self.log.debug("Performing extraction..") data_blocks = set() @@ -58,5 +58,5 @@ class ExtractBlend(publish.Extractor, publish.OptionalPyblishPluginMixin): } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s", - instance.name, representation) + self.log.debug("Extracted instance '%s' to: %s", + instance.name, representation) diff --git a/openpype/hosts/blender/plugins/publish/extract_blend_animation.py b/openpype/hosts/blender/plugins/publish/extract_blend_animation.py index 84e1e7d602..2e0de9317e 100644 --- a/openpype/hosts/blender/plugins/publish/extract_blend_animation.py +++ b/openpype/hosts/blender/plugins/publish/extract_blend_animation.py @@ -27,7 +27,7 @@ class ExtractBlendAnimation( filepath = os.path.join(stagingdir, filename) # Perform extraction - self.log.info("Performing extraction..") + self.log.debug("Performing extraction..") data_blocks = set() @@ -56,5 +56,5 @@ class ExtractBlendAnimation( } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s", - instance.name, representation) + self.log.debug("Extracted instance '%s' to: %s", + instance.name, representation) diff --git a/openpype/hosts/blender/plugins/publish/extract_camera_abc.py b/openpype/hosts/blender/plugins/publish/extract_camera_abc.py index 1c0c033364..9db1505d37 100644 --- a/openpype/hosts/blender/plugins/publish/extract_camera_abc.py +++ b/openpype/hosts/blender/plugins/publish/extract_camera_abc.py @@ -25,7 +25,7 @@ class ExtractCameraABC(publish.Extractor, publish.OptionalPyblishPluginMixin): filepath = os.path.join(stagingdir, filename) # Perform extraction - self.log.info("Performing extraction..") + self.log.debug("Performing extraction..") plugin.deselect_all() @@ -67,5 +67,5 @@ class ExtractCameraABC(publish.Extractor, publish.OptionalPyblishPluginMixin): } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s", - instance.name, representation) + self.log.debug("Extracted instance '%s' to: %s", + instance.name, representation) diff --git a/openpype/hosts/blender/plugins/publish/extract_camera_fbx.py b/openpype/hosts/blender/plugins/publish/extract_camera_fbx.py index 8e5cc6f651..9bbcf047cc 100644 --- a/openpype/hosts/blender/plugins/publish/extract_camera_fbx.py +++ b/openpype/hosts/blender/plugins/publish/extract_camera_fbx.py @@ -24,7 +24,7 @@ class ExtractCamera(publish.Extractor, publish.OptionalPyblishPluginMixin): filepath = os.path.join(stagingdir, filename) # Perform extraction - self.log.info("Performing extraction..") + self.log.debug("Performing extraction..") plugin.deselect_all() @@ -76,5 +76,5 @@ class ExtractCamera(publish.Extractor, publish.OptionalPyblishPluginMixin): } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s", - instance.name, representation) + self.log.debug("Extracted instance '%s' to: %s", + instance.name, representation) diff --git a/openpype/hosts/blender/plugins/publish/extract_fbx.py b/openpype/hosts/blender/plugins/publish/extract_fbx.py index 07c35207d8..fae438158a 100644 --- a/openpype/hosts/blender/plugins/publish/extract_fbx.py +++ b/openpype/hosts/blender/plugins/publish/extract_fbx.py @@ -25,7 +25,7 @@ class ExtractFBX(publish.Extractor, publish.OptionalPyblishPluginMixin): filepath = os.path.join(stagingdir, filename) # Perform extraction - self.log.info("Performing extraction..") + self.log.debug("Performing extraction..") plugin.deselect_all() @@ -87,5 +87,5 @@ class ExtractFBX(publish.Extractor, publish.OptionalPyblishPluginMixin): } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s", - instance.name, representation) + self.log.debug("Extracted instance '%s' to: %s", + instance.name, representation) diff --git a/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py b/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py index 5a8381dbc3..ba584ac99a 100644 --- a/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py @@ -29,7 +29,7 @@ class ExtractAnimationFBX( stagingdir = self.staging_dir(instance) # Perform extraction - self.log.info("Performing extraction..") + self.log.debug("Performing extraction..") # The first collection object in the instance is taken, as there # should be only one that contains the asset group. @@ -62,7 +62,7 @@ class ExtractAnimationFBX( starting_frames.append(curr_frame_range[0]) ending_frames.append(curr_frame_range[1]) else: - self.log.info("Object have no animation.") + self.log.info("Object has no animation.") return asset_group_name = asset_group.name @@ -164,5 +164,5 @@ class ExtractAnimationFBX( instance.data["representations"].append(fbx_representation) instance.data["representations"].append(json_representation) - self.log.info("Extracted instance '{}' to: {}".format( - instance.name, fbx_representation)) + self.log.debug("Extracted instance '{}' to: {}".format( + instance.name, fbx_representation)) diff --git a/openpype/hosts/blender/plugins/publish/extract_layout.py b/openpype/hosts/blender/plugins/publish/extract_layout.py index 576c2c33fc..d0adac6edb 100644 --- a/openpype/hosts/blender/plugins/publish/extract_layout.py +++ b/openpype/hosts/blender/plugins/publish/extract_layout.py @@ -45,7 +45,7 @@ class ExtractLayout(publish.Extractor, publish.OptionalPyblishPluginMixin): starting_frames.append(curr_frame_range[0]) ending_frames.append(curr_frame_range[1]) else: - self.log.info("Object have no animation.") + self.log.info("Object has no animation.") continue asset_group_name = asset.name @@ -120,7 +120,7 @@ class ExtractLayout(publish.Extractor, publish.OptionalPyblishPluginMixin): stagingdir = self.staging_dir(instance) # Perform extraction - self.log.info("Performing extraction..") + self.log.debug("Performing extraction..") if "representations" not in instance.data: instance.data["representations"] = [] @@ -248,5 +248,5 @@ class ExtractLayout(publish.Extractor, publish.OptionalPyblishPluginMixin): } instance.data["representations"].append(fbx_representation) - self.log.info("Extracted instance '%s' to: %s", - instance.name, json_representation) + self.log.debug("Extracted instance '%s' to: %s", + instance.name, json_representation) diff --git a/openpype/hosts/blender/plugins/publish/extract_playblast.py b/openpype/hosts/blender/plugins/publish/extract_playblast.py index 07b51becce..696cf85089 100644 --- a/openpype/hosts/blender/plugins/publish/extract_playblast.py +++ b/openpype/hosts/blender/plugins/publish/extract_playblast.py @@ -27,8 +27,6 @@ class ExtractPlayblast(publish.Extractor, publish.OptionalPyblishPluginMixin): if not self.is_active(instance.data): return - self.log.info(instance.data) - # get scene fps fps = instance.data.get("fps") if fps is None: @@ -56,7 +54,7 @@ class ExtractPlayblast(publish.Extractor, publish.OptionalPyblishPluginMixin): filename = instance.name path = os.path.join(stagingdir, filename) - self.log.info(f"Outputting images to {path}") + self.log.debug(f"Outputting images to {path}") project_settings = instance.context.data["project_settings"]["blender"] presets = project_settings["publish"]["ExtractPlayblast"]["presets"] @@ -101,7 +99,7 @@ class ExtractPlayblast(publish.Extractor, publish.OptionalPyblishPluginMixin): frame_collection = collections[0] - self.log.info(f"We found collection of interest {frame_collection}") + self.log.debug(f"We found collection of interest {frame_collection}") instance.data.setdefault("representations", []) diff --git a/openpype/hosts/blender/plugins/publish/extract_thumbnail.py b/openpype/hosts/blender/plugins/publish/extract_thumbnail.py index 65c3627375..52e5d98fc4 100644 --- a/openpype/hosts/blender/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/blender/plugins/publish/extract_thumbnail.py @@ -24,13 +24,13 @@ class ExtractThumbnail(publish.Extractor): presets = {} def process(self, instance): - self.log.info("Extracting capture..") + self.log.debug("Extracting capture..") stagingdir = self.staging_dir(instance) filename = instance.name path = os.path.join(stagingdir, filename) - self.log.info(f"Outputting images to {path}") + self.log.debug(f"Outputting images to {path}") camera = instance.data.get("review_camera", "AUTO") start = instance.data.get("frameStart", bpy.context.scene.frame_start) @@ -61,7 +61,7 @@ class ExtractThumbnail(publish.Extractor): thumbnail = os.path.basename(self._fix_output_path(path)) - self.log.info(f"thumbnail: {thumbnail}") + self.log.debug(f"thumbnail: {thumbnail}") instance.data.setdefault("representations", []) diff --git a/openpype/hosts/blender/plugins/publish/increment_workfile_version.py b/openpype/hosts/blender/plugins/publish/increment_workfile_version.py index 8e522977e4..7e33fd53fa 100644 --- a/openpype/hosts/blender/plugins/publish/increment_workfile_version.py +++ b/openpype/hosts/blender/plugins/publish/increment_workfile_version.py @@ -29,4 +29,4 @@ class IncrementWorkfileVersion( save_file(filepath, copy=False) - self.log.info('Incrementing script version') + self.log.debug('Incrementing blender workfile version') diff --git a/openpype/hosts/blender/plugins/publish/integrate_animation.py b/openpype/hosts/blender/plugins/publish/integrate_animation.py index b7e5423fa8..623da9c585 100644 --- a/openpype/hosts/blender/plugins/publish/integrate_animation.py +++ b/openpype/hosts/blender/plugins/publish/integrate_animation.py @@ -17,7 +17,7 @@ class IntegrateAnimation( families = ["setdress"] def process(self, instance): - self.log.info("Integrate Animation") + self.log.debug("Integrate Animation") representation = instance.data.get('representations')[0] json_path = representation.get('publishedFiles')[0] From ae6c810ba54091d5157dd6d656589377da7f2446 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 13 Nov 2023 23:38:21 +0100 Subject: [PATCH 045/107] Raise PublishValidationError from validators --- .../plugins/publish/validate_camera_zero_keyframe.py | 7 +++++-- .../blender/plugins/publish/validate_deadline_publish.py | 2 +- .../hosts/blender/plugins/publish/validate_mesh_has_uv.py | 3 ++- .../plugins/publish/validate_mesh_no_negative_scale.py | 3 ++- .../blender/plugins/publish/validate_no_colons_in_name.py | 3 ++- .../hosts/blender/plugins/publish/validate_object_mode.py | 7 +++++-- .../plugins/publish/validate_render_camera_is_set.py | 7 +++++-- .../blender/plugins/publish/validate_transform_zero.py | 3 ++- 8 files changed, 24 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py b/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py index f109d9c3ce..65697cb86d 100644 --- a/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py +++ b/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py @@ -5,7 +5,10 @@ import bpy import pyblish.api import openpype.hosts.blender.api.action -from openpype.pipeline.publish import ValidateContentsOrder +from openpype.pipeline.publish import ( + ValidateContentsOrder, + PublishValidationError +) class ValidateCameraZeroKeyframe(pyblish.api.InstancePlugin): @@ -45,6 +48,6 @@ class ValidateCameraZeroKeyframe(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: - raise RuntimeError( + raise PublishValidationError( f"Camera must have a keyframe at frame 0: {invalid}" ) diff --git a/openpype/hosts/blender/plugins/publish/validate_deadline_publish.py b/openpype/hosts/blender/plugins/publish/validate_deadline_publish.py index 14220b5c9c..58047d7e23 100644 --- a/openpype/hosts/blender/plugins/publish/validate_deadline_publish.py +++ b/openpype/hosts/blender/plugins/publish/validate_deadline_publish.py @@ -36,7 +36,7 @@ class ValidateDeadlinePublish(pyblish.api.InstancePlugin, "Render output folder " "doesn't match the blender scene name! " "Use Repair action to " - "fix the folder file path.." + "fix the folder file path." ) @classmethod diff --git a/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py b/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py index c2d63685e2..dd955dc5da 100644 --- a/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py +++ b/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py @@ -7,6 +7,7 @@ import pyblish.api from openpype.pipeline.publish import ( ValidateContentsOrder, OptionalPyblishPluginMixin, + PublishValidationError ) import openpype.hosts.blender.api.action @@ -60,6 +61,6 @@ class ValidateMeshHasUvs( invalid = self.get_invalid(instance) if invalid: - raise RuntimeError( + raise PublishValidationError( f"Meshes found in instance without valid UV's: {invalid}" ) diff --git a/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py b/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py index 737fd80798..a498a3b4cb 100644 --- a/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py +++ b/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py @@ -7,6 +7,7 @@ import pyblish.api from openpype.pipeline.publish import ( ValidateContentsOrder, OptionalPyblishPluginMixin, + PublishValidationError ) import openpype.hosts.blender.api.action @@ -36,6 +37,6 @@ class ValidateMeshNoNegativeScale(pyblish.api.Validator, invalid = self.get_invalid(instance) if invalid: - raise RuntimeError( + raise PublishValidationError( f"Meshes found in instance with negative scale: {invalid}" ) diff --git a/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py b/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py index fbaf40e91f..17119f8d88 100644 --- a/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py +++ b/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py @@ -8,6 +8,7 @@ import openpype.hosts.blender.api.action from openpype.pipeline.publish import ( ValidateContentsOrder, OptionalPyblishPluginMixin, + PublishValidationError ) @@ -45,6 +46,6 @@ class ValidateNoColonsInName(pyblish.api.InstancePlugin, invalid = self.get_invalid(instance) if invalid: - raise RuntimeError( + raise PublishValidationError( f"Objects found with colon in name: {invalid}" ) diff --git a/openpype/hosts/blender/plugins/publish/validate_object_mode.py b/openpype/hosts/blender/plugins/publish/validate_object_mode.py index 19e8d1bc8d..3b6f29a79e 100644 --- a/openpype/hosts/blender/plugins/publish/validate_object_mode.py +++ b/openpype/hosts/blender/plugins/publish/validate_object_mode.py @@ -3,7 +3,10 @@ from typing import List import bpy import pyblish.api -from openpype.pipeline.publish import OptionalPyblishPluginMixin +from openpype.pipeline.publish import ( + OptionalPyblishPluginMixin, + PublishValidationError +) import openpype.hosts.blender.api.action @@ -34,6 +37,6 @@ class ValidateObjectIsInObjectMode( invalid = self.get_invalid(instance) if invalid: - raise RuntimeError( + raise PublishValidationError( f"Object found in instance is not in Object Mode: {invalid}" ) diff --git a/openpype/hosts/blender/plugins/publish/validate_render_camera_is_set.py b/openpype/hosts/blender/plugins/publish/validate_render_camera_is_set.py index 0207b0fd09..86d1fcc681 100644 --- a/openpype/hosts/blender/plugins/publish/validate_render_camera_is_set.py +++ b/openpype/hosts/blender/plugins/publish/validate_render_camera_is_set.py @@ -2,7 +2,10 @@ import bpy import pyblish.api -from openpype.pipeline.publish import OptionalPyblishPluginMixin +from openpype.pipeline.publish import ( + OptionalPyblishPluginMixin, + PublishValidationError +) class ValidateRenderCameraIsSet(pyblish.api.InstancePlugin, @@ -20,4 +23,4 @@ class ValidateRenderCameraIsSet(pyblish.api.InstancePlugin, return if not bpy.context.scene.camera: - raise RuntimeError("No camera is active for rendering.") + raise PublishValidationError("No camera is active for rendering.") diff --git a/openpype/hosts/blender/plugins/publish/validate_transform_zero.py b/openpype/hosts/blender/plugins/publish/validate_transform_zero.py index 3c68e61277..5270a50888 100644 --- a/openpype/hosts/blender/plugins/publish/validate_transform_zero.py +++ b/openpype/hosts/blender/plugins/publish/validate_transform_zero.py @@ -9,6 +9,7 @@ import openpype.hosts.blender.api.action from openpype.pipeline.publish import ( ValidateContentsOrder, OptionalPyblishPluginMixin, + PublishValidationError ) @@ -47,7 +48,7 @@ class ValidateTransformZero(pyblish.api.InstancePlugin, invalid = self.get_invalid(instance) if invalid: - raise RuntimeError( + raise PublishValidationError( "Object found in instance has not" f" transform to zero: {invalid}" ) From 1e7ef83b188b9c415cb403e702f93c380d0afdce Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 13 Nov 2023 23:53:17 +0100 Subject: [PATCH 046/107] Validate workfile saved once; not per instance - refactored to ContextPlugin --- .../plugins/publish/validate_file_saved.py | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/validate_file_saved.py b/openpype/hosts/blender/plugins/publish/validate_file_saved.py index 44b33a573a..442f856e05 100644 --- a/openpype/hosts/blender/plugins/publish/validate_file_saved.py +++ b/openpype/hosts/blender/plugins/publish/validate_file_saved.py @@ -18,7 +18,7 @@ class SaveWorkfileAction(pyblish.api.Action): bpy.ops.wm.avalon_workfiles() -class ValidateFileSaved(pyblish.api.InstancePlugin, +class ValidateFileSaved(pyblish.api.ContextPlugin, OptionalPyblishPluginMixin): """Validate that the workfile has been saved.""" @@ -29,18 +29,33 @@ class ValidateFileSaved(pyblish.api.InstancePlugin, exclude_families = [] actions = [SaveWorkfileAction] - def process(self, instance): - if not self.is_active(instance.data): + def process(self, context): + if not self.is_active(context.data): return - if not instance.context.data["currentFile"]: + if not context.data["currentFile"]: # File has not been saved at all and has no filename raise PublishValidationError( "Current file is empty. Save the file before continuing." ) - if [ef for ef in self.exclude_families - if instance.data["family"] in ef]: + # Do not validate workfile has unsaved changes if only instances + # present of families that should be excluded + families = { + instance.data["family"] for instance in context + # Consider only enabled instances + if instance.data.get("publish", True) + and instance.data.get("active", True) + } + + def is_excluded(family): + return any(family in exclude_family + for exclude_family in self.exclude_families) + + if all(is_excluded(family) for family in families): + self.log.debug("Only excluded families found, skipping workfile " + "unsaved changes validation..") return + if bpy.data.is_dirty: raise PublishValidationError("Workfile has unsaved changes.") From b8255c6ee3e79d5a9d4466437ed6ec5628edf382 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 13 Nov 2023 23:53:44 +0100 Subject: [PATCH 047/107] Simplify collecting instances (re-use logic) --- openpype/hosts/blender/api/plugin.py | 55 +++++++++++----------------- 1 file changed, 22 insertions(+), 33 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 4bb489dca2..1114d136ce 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -1,5 +1,6 @@ """Shared functionality for pipeline plugins for Blender.""" +import itertools from pathlib import Path from typing import Dict, List, Optional @@ -164,42 +165,30 @@ class BaseCreator(Creator): cache_legacy = {} avalon_instances = bpy.data.collections.get(AVALON_INSTANCES) - if avalon_instances: - for obj in bpy.data.collections.get(AVALON_INSTANCES).objects: - avalon_prop = obj.get(AVALON_PROPERTY, {}) - if avalon_prop.get('id') == 'pyblish.avalon.instance': - creator_id = avalon_prop.get('creator_identifier') + avalon_instance_objs = ( + avalon_instances.objects if avalon_instances else [] + ) - if creator_id: - # Creator instance - cache.setdefault(creator_id, []).append( - avalon_prop - ) - else: - family = avalon_prop.get('family') - if family: - # Legacy creator instance - cache_legacy.setdefault(family, []).append( - avalon_prop - ) + for obj_or_col in itertools.chain( + avalon_instance_objs, + bpy.data.collections + ): + avalon_prop = obj_or_col.get(AVALON_PROPERTY, {}) + if not avalon_prop: + continue - for col in bpy.data.collections: - avalon_prop = col.get(AVALON_PROPERTY, {}) - if avalon_prop.get('id') == 'pyblish.avalon.instance': - creator_id = avalon_prop.get('creator_identifier') + if avalon_prop.get('id') != 'pyblish.avalon.instance': + continue - if creator_id: - # Creator instance - cache.setdefault(creator_id, []).append(avalon_prop) - else: - family = avalon_prop.get('family') - if family: - cache_legacy.setdefault(family, []) - if family: - # Legacy creator instance - cache_legacy.setdefault(family, []).append( - avalon_prop - ) + creator_id = avalon_prop.get('creator_identifier') + if creator_id: + # Creator instance + cache.setdefault(creator_id, []).append(avalon_prop) + else: + family = avalon_prop.get('family') + if family: + # Legacy creator instance + cache_legacy.setdefault(family, []).append(avalon_prop) shared_data["blender_cached_subsets"] = cache shared_data["blender_cached_legacy_subsets"] = cache_legacy From 66981669545046e14551250b14a195132a231887 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 00:24:57 +0100 Subject: [PATCH 048/107] Refactor to correct name --- .../hosts/blender/plugins/publish/validate_instance_empty.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/blender/plugins/publish/validate_instance_empty.py b/openpype/hosts/blender/plugins/publish/validate_instance_empty.py index 3ebc6515d3..7669d76943 100644 --- a/openpype/hosts/blender/plugins/publish/validate_instance_empty.py +++ b/openpype/hosts/blender/plugins/publish/validate_instance_empty.py @@ -13,7 +13,7 @@ class ValidateInstanceEmpty(pyblish.api.InstancePlugin): optional = False def process(self, instance): - asset_group = instance.data["instance_group"] + asset_group = instance.data["instance_node"] if isinstance(asset_group, bpy.types.Collection): if not (asset_group.objects or asset_group.children): From 871bf194da2e7743363697032a984c41a2e54fc4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 00:36:33 +0100 Subject: [PATCH 049/107] Avoid double space in log message --- openpype/plugins/publish/collect_comment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/collect_comment.py b/openpype/plugins/publish/collect_comment.py index 9f41e37f22..38d61a7071 100644 --- a/openpype/plugins/publish/collect_comment.py +++ b/openpype/plugins/publish/collect_comment.py @@ -103,10 +103,10 @@ class CollectComment( instance.data["comment"] = instance_comment if instance_comment: - msg_end = " has comment set to: \"{}\"".format( + msg_end = "has comment set to: \"{}\"".format( instance_comment) else: - msg_end = " does not have set comment" + msg_end = "does not have set comment" self.log.debug("Instance {} {}".format(instance_label, msg_end)) def cleanup_comment(self, comment): From 3d10bfdf0916d9dd74db3ad5e1edf655fdbd3478 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 00:37:13 +0100 Subject: [PATCH 050/107] Cosmetics --- openpype/hosts/blender/api/plugin.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index e126511fd6..0399bf2e50 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -256,19 +256,16 @@ class BaseCreator(Creator): if not cached_subsets: return + # Process only instances that were created by this creator for instance_data in cached_subsets.get(self.identifier, []): - # Process only instances that were created by this creator - data = instance_data.to_dict() - creator_id = data.get('creator_identifier') + # Create instance object from existing data + instance = CreatedInstance.from_existing( + instance_data=instance_data.to_dict(), + creator=self + ) - if creator_id == self.identifier: - # Create instance object from existing data - instance = CreatedInstance.from_existing( - data, self - ) - - # Add instance to create context - self._add_instance_to_context(instance) + # Add instance to create context + self._add_instance_to_context(instance) def update_instances(self, update_list): """Override abstract method from BaseCreator. From 8575c4843caf7074dbdc5376076d4ee805e2fb7b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 02:25:42 +0100 Subject: [PATCH 051/107] Fix storing instance and context data correctly, fix removing instances + refactor instance nodes --- openpype/hosts/blender/api/ops.py | 8 ++ openpype/hosts/blender/api/pipeline.py | 7 +- openpype/hosts/blender/api/plugin.py | 89 ++++++++++--------- .../blender/plugins/create/create_action.py | 9 +- .../plugins/create/create_animation.py | 2 +- .../plugins/create/create_blendScene.py | 39 ++------ .../blender/plugins/create/create_camera.py | 41 ++------- .../blender/plugins/create/create_layout.py | 37 ++------ .../blender/plugins/create/create_model.py | 40 ++------- .../plugins/create/create_pointcache.py | 2 +- .../blender/plugins/create/create_review.py | 3 +- .../blender/plugins/create/create_rig.py | 39 ++------ .../blender/plugins/create/create_workfile.py | 52 ++++++----- .../hosts/blender/plugins/load/load_blend.py | 1 + .../blender/plugins/load/load_layout_json.py | 1 + .../blender/plugins/publish/collect_render.py | 5 +- .../blender/plugins/publish/collect_review.py | 4 +- .../publish/validate_instance_empty.py | 2 +- 18 files changed, 140 insertions(+), 241 deletions(-) diff --git a/openpype/hosts/blender/api/ops.py b/openpype/hosts/blender/api/ops.py index 22c590d4bd..c617144b6f 100644 --- a/openpype/hosts/blender/api/ops.py +++ b/openpype/hosts/blender/api/ops.py @@ -31,6 +31,14 @@ PREVIEW_COLLECTIONS: Dict = dict() TIMER_INTERVAL: float = 0.01 if platform.system() == "Windows" else 0.1 +def execute_function_in_main_thread(f): + """Decorator to move a function call into main thread items""" + def wrapper(*args, **kwargs): + mti = MainThreadItem(f, *args, **kwargs) + execute_in_main_thread(mti) + return wrapper + + class BlenderApplication(QtWidgets.QApplication): _instance = None blender_windows = {} diff --git a/openpype/hosts/blender/api/pipeline.py b/openpype/hosts/blender/api/pipeline.py index c3f8f06694..9ac59f5620 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -138,7 +138,10 @@ class BlenderHost(HostBase, IWorkfileHost, IPublishHost): Returns: dict: Context data stored using 'update_context_data'. """ - return bpy.context.scene.openpype_context + property = bpy.context.scene.get(AVALON_PROPERTY) + if property: + return property.to_dict() + return {} def update_context_data(self, data: dict, changes: dict): """Override abstract method from IPublishHost. @@ -149,7 +152,7 @@ class BlenderHost(HostBase, IWorkfileHost, IPublishHost): changes (dict): Only data that has been changed. Each value has tuple with '(, )' value. """ - bpy.context.scene.openpype_context.update(data) + bpy.context.scene[AVALON_PROPERTY] = data def pype_excepthook_handler(*args): diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 0399bf2e50..d1cefe0a62 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -12,6 +12,8 @@ from openpype.pipeline import ( LoaderPlugin, get_current_task_name, ) +from openpype.lib import BoolDef + from .pipeline import ( AVALON_CONTAINERS, AVALON_INSTANCES, @@ -149,6 +151,8 @@ class BaseCreator(Creator): """Base class for Blender Creator plug-ins.""" defaults = ['Main'] + create_as_asset_group = False + @staticmethod def cache_subsets(shared_data): """Cache instances for Creators shared data. @@ -190,12 +194,12 @@ class BaseCreator(Creator): creator_id = avalon_prop.get('creator_identifier') if creator_id: # Creator instance - cache.setdefault(creator_id, []).append(avalon_prop) + cache.setdefault(creator_id, []).append(obj_or_col) else: family = avalon_prop.get('family') if family: # Legacy creator instance - cache_legacy.setdefault(family, []).append(avalon_prop) + cache_legacy.setdefault(family, []).append(obj_or_col) shared_data["blender_cached_subsets"] = cache shared_data["blender_cached_legacy_subsets"] = cache_legacy @@ -220,27 +224,29 @@ class BaseCreator(Creator): instances = bpy.data.collections.new(name=AVALON_INSTANCES) bpy.context.scene.collection.children.link(instances) - # Create instance collection - collection = bpy.data.collections.new( - name=asset_name(instance_data["asset"], subset_name) + # Create asset group + name = asset_name(instance_data["asset"], subset_name) + if self.create_as_asset_group: + # Create instance as empty + instance_node = bpy.data.objects.new(name=name, object_data=None) + instance_node.empty_display_type = 'SINGLE_ARROW' + instances.objects.link(instance_node) + else: + # Create instance collection + instance_node = bpy.data.collections.new(name=name) + instances.children.link(instance_node) + + self.set_instance_data(subset_name, instance_data) + + instance = CreatedInstance( + self.family, subset_name, instance_data, self ) - instances.children.link(collection) + instance.transient_data["instance_node"] = instance_node + self._add_instance_to_context(instance) - collection[AVALON_PROPERTY] = instance_node = { - "name": collection.name, - } + imprint(instance_node, instance_data) - self.set_instance_data(subset_name, instance_data, instance_node) - - self._add_instance_to_context( - CreatedInstance( - self.family, subset_name, instance_data, self - ) - ) - - imprint(collection, instance_data) - - return collection + return instance_node def collect_instances(self): """Override abstract method from BaseCreator. @@ -257,12 +263,14 @@ class BaseCreator(Creator): return # Process only instances that were created by this creator - for instance_data in cached_subsets.get(self.identifier, []): + for instance_node in cached_subsets.get(self.identifier, []): + property = instance_node.get(AVALON_PROPERTY) # Create instance object from existing data instance = CreatedInstance.from_existing( - instance_data=instance_data.to_dict(), + instance_data=property.to_dict(), creator=self ) + instance.transient_data["instance_node"] = instance_node # Add instance to create context self._add_instance_to_context(instance) @@ -276,41 +284,32 @@ class BaseCreator(Creator): and their changes, as a list of tuples.""" for created_instance, _changes in update_list: data = created_instance.data_to_store() - - imprint(data.get("instance_node", {}), data) + node = created_instance.transient_data["instance_node"] + if node: + imprint(node, data) def remove_instances(self, instances: List[CreatedInstance]): - """Override abstract method from BaseCreator. - Method called when instances are removed. - Args: - instance(List[CreatedInstance]): Instance objects to remove. - """ for instance in instances: - outliner_entity = instance.data.get("instance_node", {}).get( - "datablock" - ) - if not outliner_entity: - continue + node = instance.transient_data["instance_node"] - if isinstance(outliner_entity, bpy.types.Collection): - for children in outliner_entity.children_recursive: + if isinstance(node, bpy.types.Collection): + for children in node.children_recursive: if isinstance(children, bpy.types.Collection): bpy.data.collections.remove(children) else: bpy.data.objects.remove(children) - bpy.data.collections.remove(outliner_entity) - elif isinstance(outliner_entity, bpy.types.Object): - bpy.data.objects.remove(outliner_entity) + bpy.data.collections.remove(node) + elif isinstance(node, bpy.types.Object): + bpy.data.objects.remove(node) self._remove_instance_from_context(instance) def set_instance_data( self, subset_name: str, - instance_data: dict, - instance_node: bpy.types.ID, + instance_data: dict ): """Fill instance data with required items. @@ -329,10 +328,16 @@ class BaseCreator(Creator): "label": subset_name, "task": get_current_task_name(), "subset": subset_name, - "instance_node": instance_node, } ) + def get_pre_create_attr_defs(self): + return [ + BoolDef("use_selection", + label="Use selection", + default=True) + ] + class Loader(LoaderPlugin): """Base class for Loader plug-ins.""" diff --git a/openpype/hosts/blender/plugins/create/create_action.py b/openpype/hosts/blender/plugins/create/create_action.py index ac425dff74..f14023639f 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -2,11 +2,10 @@ import bpy -from openpype.hosts.blender.api.plugin import BaseCreator, asset_name -from openpype.hosts.blender.api import lib +from openpype.hosts.blender.api import lib, plugin -class CreateAction(BaseCreator): +class CreateAction(plugin.BaseCreator): """Action output for character rigs.""" identifier = "io.openpype.creators.blender.action" @@ -24,9 +23,9 @@ class CreateAction(BaseCreator): ) # Get instance name - name = asset_name(instance_data["asset"], subset_name) + name = plugin.asset_name(instance_data["asset"], subset_name) - if pre_create_data.get("useSelection"): + if pre_create_data.get("use_selection"): for obj in lib.get_selection(): if (obj.animation_data is not None and obj.animation_data.action is not None): diff --git a/openpype/hosts/blender/plugins/create/create_animation.py b/openpype/hosts/blender/plugins/create/create_animation.py index f7cb9f88aa..6f0cabb259 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -21,7 +21,7 @@ class CreateAnimation(plugin.BaseCreator): subset_name, instance_data, pre_create_data ) - if pre_create_data.get("useSelection"): + if pre_create_data.get("use_selection"): selected = lib.get_selection() for obj in selected: collection.objects.link(obj) diff --git a/openpype/hosts/blender/plugins/create/create_blendScene.py b/openpype/hosts/blender/plugins/create/create_blendScene.py index 1dc8f44a63..f1090ae397 100644 --- a/openpype/hosts/blender/plugins/create/create_blendScene.py +++ b/openpype/hosts/blender/plugins/create/create_blendScene.py @@ -23,42 +23,17 @@ class CreateBlendScene(plugin.Creator): def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): - """Run the creator on Blender main thread.""" - mti = ops.MainThreadItem( - self._process, subset_name, instance_data, pre_create_data - ) - ops.execute_in_main_thread(mti) - def _process( - self, subset_name: str, instance_data: dict, pre_create_data: dict - ): - # Get Instance Container or create it if it does not exist - instances = bpy.data.collections.get(AVALON_INSTANCES) - if not instances: - instances = bpy.data.collections.new(name=AVALON_INSTANCES) - bpy.context.scene.collection.children.link(instances) + instance_node = super().create(subset_name, + instance_data, + pre_create_data) - # Create instance object - asset = instance_data.get("asset") - name = plugin.asset_name(asset, subset_name) - - # Create the new asset group as collection - asset_group = bpy.data.collections.new(name=name) - instances.children.link(asset_group) - asset_group[AVALON_PROPERTY] = instance_node = { - "name": asset_group.name - } - - self.set_instance_data(subset_name, instance_data, instance_node) - lib.imprint(asset_group, instance_data) - - if (self.options or {}).get("useSelection"): + if pre_create_data.get("use_selection"): selection = lib.get_selection(include_collections=True) - for data in selection: if isinstance(data, bpy.types.Collection): - asset_group.children.link(data) + instance_node.children.link(data) elif isinstance(data, bpy.types.Object): - asset_group.objects.link(data) + instance_node.objects.link(data) - return asset_group + return instance_node diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index 8dbba229a9..d30c60ba21 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -2,7 +2,7 @@ import bpy -from openpype.pipeline import get_current_task_name, CreatedInstance +from openpype.pipeline import CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops from openpype.hosts.blender.api.pipeline import ( AVALON_INSTANCES, @@ -19,43 +19,19 @@ class CreateCamera(plugin.BaseCreator): family = "camera" icon = "video-camera" + create_as_asset_group = True + + @ops.execute_function_in_main_thread def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): """Run the creator on Blender main thread.""" - self._add_instance_to_context( - CreatedInstance(self.family, subset_name, instance_data, self) - ) - mti = ops.MainThreadItem( - self._process, subset_name, instance_data, pre_create_data - ) - ops.execute_in_main_thread(mti) + asset_group = super().create(subset_name, + instance_data, + pre_create_data) - def _process( - self, subset_name: str, instance_data: dict, pre_create_data: dict - ): - # Get Instance Container or create it if it does not exist - instances = bpy.data.collections.get(AVALON_INSTANCES) - if not instances: - instances = bpy.data.collections.new(name=AVALON_INSTANCES) - bpy.context.scene.collection.children.link(instances) - - # Create instance object - name = plugin.asset_name(instance_data["asset"], subset_name) - - asset_group = bpy.data.objects.new(name=name, object_data=None) - asset_group.empty_display_type = 'SINGLE_ARROW' - instances.objects.link(asset_group) - - asset_group[AVALON_PROPERTY] = instance_node = { - "name": asset_group.name, - } - - self.set_instance_data(subset_name, instance_data, instance_node) - lib.imprint(asset_group, instance_data) - - if pre_create_data.get("useSelection"): + if pre_create_data.get("use_selection"): bpy.context.view_layer.objects.active = asset_group selected = lib.get_selection() for obj in selected: @@ -67,6 +43,7 @@ class CreateCamera(plugin.BaseCreator): camera = bpy.data.cameras.new(subset_name) camera_obj = bpy.data.objects.new(subset_name, camera) + instances = bpy.data.collections.get(AVALON_INSTANCES) instances.objects.link(camera_obj) camera_obj.select_set(True) diff --git a/openpype/hosts/blender/plugins/create/create_layout.py b/openpype/hosts/blender/plugins/create/create_layout.py index a9bf115ea9..f569a81c29 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -19,43 +19,18 @@ class CreateLayout(plugin.BaseCreator): family = "layout" icon = "cubes" + create_as_asset_group = True + def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): - """Run the creator on Blender main thread.""" - self._add_instance_to_context( - CreatedInstance(self.family, subset_name, instance_data, self) - ) - mti = ops.MainThreadItem( - self._process, subset_name, instance_data, pre_create_data - ) - ops.execute_in_main_thread(mti) - - def _process( - self, subset_name: str, instance_data: dict, pre_create_data: dict - ): - # Get Instance Container or create it if it does not exist - instances = bpy.data.collections.get(AVALON_INSTANCES) - if not instances: - instances = bpy.data.collections.new(name=AVALON_INSTANCES) - bpy.context.scene.collection.children.link(instances) - - # Create instance object - name = plugin.asset_name(instance_data["asset"], subset_name) - asset_group = bpy.data.objects.new(name=name, object_data=None) - asset_group.empty_display_type = 'SINGLE_ARROW' - instances.objects.link(asset_group) - - asset_group[AVALON_PROPERTY] = instance_node = { - "name": asset_group.name, - } - - self.set_instance_data(subset_name, instance_data, instance_node) - lib.imprint(asset_group, instance_data) + asset_group = super().create(subset_name, + instance_data, + pre_create_data) # Add selected objects to instance - if pre_create_data.get("useSelection"): + if pre_create_data.get("use_selection"): bpy.context.view_layer.objects.active = asset_group selected = lib.get_selection() for obj in selected: diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index 3501071e8f..9774d4bec3 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -19,48 +19,24 @@ class CreateModel(plugin.BaseCreator): family = "model" icon = "cube" + create_as_asset_group = True + + @ops.execute_function_in_main_thread def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): - """Run the creator on Blender main thread.""" - self._add_instance_to_context( - CreatedInstance(self.family, subset_name, instance_data, self) - ) - - mti = ops.MainThreadItem( - self._process, subset_name, instance_data, pre_create_data - ) - ops.execute_in_main_thread(mti) - - def _process( - self, subset_name: str, instance_data: dict, pre_create_data: dict - ): - # Get Instance Container or create it if it does not exist - instances = bpy.data.collections.get(AVALON_INSTANCES) - if not instances: - instances = bpy.data.collections.new(name=AVALON_INSTANCES) - bpy.context.scene.collection.children.link(instances) - - # Create instance object - name = plugin.asset_name(instance_data["asset"], subset_name) - asset_group = bpy.data.objects.new(name=name, object_data=None) - asset_group.empty_display_type = 'SINGLE_ARROW' - instances.objects.link(asset_group) - - asset_group[AVALON_PROPERTY] = instance_node = { - "name": asset_group.name, - } - - self.set_instance_data(subset_name, instance_data, instance_node) - lib.imprint(asset_group, instance_data) + asset_group = super().create(subset_name, + instance_data, + pre_create_data) # Add selected objects to instance - if pre_create_data.get("useSelection"): + if pre_create_data.get("use_selection"): bpy.context.view_layer.objects.active = asset_group selected = lib.get_selection() for obj in selected: obj.select_set(True) selected.append(asset_group) + bpy.ops.object.parent_set(keep_transform=True) return asset_group diff --git a/openpype/hosts/blender/plugins/create/create_pointcache.py b/openpype/hosts/blender/plugins/create/create_pointcache.py index d05b3adb04..d80a1b07f0 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -20,7 +20,7 @@ class CreatePointcache(plugin.BaseCreator): subset_name, instance_data, pre_create_data ) - if pre_create_data.get("useSelection"): + if pre_create_data.get("use_selection"): objects = lib.get_selection() for obj in objects: collection.objects.link(obj) diff --git a/openpype/hosts/blender/plugins/create/create_review.py b/openpype/hosts/blender/plugins/create/create_review.py index 91333b7741..2d521c4255 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -21,11 +21,12 @@ class CreateReview(plugin.BaseCreator): subset_name, instance_data, pre_create_data ) - if pre_create_data.get("useSelection"): + if pre_create_data.get("use_selection"): selected = lib.get_selection() for obj in selected: collection.objects.link(obj) elif pre_create_data.get("asset_group"): + # TODO: What is the intended behavior for this? obj = (self.options or {}).get("asset_group") collection.objects.link(obj) diff --git a/openpype/hosts/blender/plugins/create/create_rig.py b/openpype/hosts/blender/plugins/create/create_rig.py index b5b61b3971..87f0124164 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -19,43 +19,18 @@ class CreateRig(plugin.BaseCreator): family = "rig" icon = "wheelchair" + create_as_asset_group = True + + @ops.execute_function_in_main_thread def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): - """Run the creator on Blender main thread.""" - self._add_instance_to_context( - CreatedInstance(self.family, subset_name, instance_data, self) - ) - - mti = ops.MainThreadItem( - self._process, subset_name, instance_data, pre_create_data - ) - ops.execute_in_main_thread(mti) - - def _process( - self, subset_name: str, instance_data: dict, pre_create_data: dict - ): - # Get Instance Container or create it if it does not exist - instances = bpy.data.collections.get(AVALON_INSTANCES) - if not instances: - instances = bpy.data.collections.new(name=AVALON_INSTANCES) - bpy.context.scene.collection.children.link(instances) - - # Create instance object - name = plugin.asset_name(instance_data["asset"], subset_name) - asset_group = bpy.data.objects.new(name=name, object_data=None) - asset_group.empty_display_type = 'SINGLE_ARROW' - instances.objects.link(asset_group) - - asset_group[AVALON_PROPERTY] = instance_node = { - "name": asset_group.name, - } - - self.set_instance_data(subset_name, instance_data, instance_node) - lib.imprint(asset_group, instance_data) + asset_group = super().create(subset_name, + instance_data, + pre_create_data) # Add selected objects to instance - if pre_create_data.get("useSelection"): + if pre_create_data.get("use_selection"): bpy.context.view_layer.objects.active = asset_group selected = lib.get_selection() for obj in selected: diff --git a/openpype/hosts/blender/plugins/create/create_workfile.py b/openpype/hosts/blender/plugins/create/create_workfile.py index d1529f75f6..8f5adb5583 100644 --- a/openpype/hosts/blender/plugins/create/create_workfile.py +++ b/openpype/hosts/blender/plugins/create/create_workfile.py @@ -3,8 +3,10 @@ import bpy from openpype.pipeline import CreatedInstance, AutoCreator from openpype.client import get_asset_by_name from openpype.hosts.blender.api.plugin import BaseCreator -from openpype.hosts.blender.api.lib import imprint -from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY +from openpype.hosts.blender.api.pipeline import ( + AVALON_PROPERTY, + AVALON_CONTAINERS +) class CreateWorkfile(BaseCreator, AutoCreator): @@ -53,6 +55,8 @@ class CreateWorkfile(BaseCreator, AutoCreator): current_instance = CreatedInstance( self.family, subset_name, data, self ) + instance_node = bpy.data.collections.get(AVALON_CONTAINERS, {}) + current_instance.transient_data["instance_node"] = instance_node self._add_instance_to_context(current_instance) elif ( current_instance["asset"] != asset_name @@ -73,28 +77,30 @@ class CreateWorkfile(BaseCreator, AutoCreator): ) def collect_instances(self): - """Collect workfile instances.""" - self.cache_subsets(self.collection_shared_data) - cached_subsets = self.collection_shared_data["blender_cached_subsets"] - for node in cached_subsets.get(self.identifier, []): - created_instance = CreatedInstance.from_existing( - self.read_instance_node(node), self - ) - self._add_instance_to_context(created_instance) - def update_instances(self, update_list): - """Update workfile instances.""" - for created_inst, _changes in update_list: - data = created_inst.data_to_store() - node = data.get("instance_node") - if not node: - task_name = self.create_context.get_current_task_name() + print("Collecting!") + instance_node = bpy.data.collections.get(AVALON_CONTAINERS) + if not instance_node: + return + print(instance_node) + property = instance_node.get(AVALON_PROPERTY) + if not property: + return + print(property) - bpy.context.scene[AVALON_PROPERTY] = node = { - "name": f"workfile{task_name}" - } + # Create instance object from existing data + instance = CreatedInstance.from_existing( + instance_data=property.to_dict(), + creator=self + ) + instance.transient_data["instance_node"] = instance_node - created_inst["instance_node"] = node - data = created_inst.data_to_store() + # Add instance to create context + self._add_instance_to_context(instance) - imprint(node, data) + def remove_instances(self, instances): + for instance in instances: + node = instance.transient_data["instance_node"] + del node[AVALON_PROPERTY] + + self._remove_instance_from_context(instance) diff --git a/openpype/hosts/blender/plugins/load/load_blend.py b/openpype/hosts/blender/plugins/load/load_blend.py index f7bbc630de..1804de29b6 100644 --- a/openpype/hosts/blender/plugins/load/load_blend.py +++ b/openpype/hosts/blender/plugins/load/load_blend.py @@ -60,6 +60,7 @@ class BlendLoader(plugin.AssetLoader): for rig in rigs: creator_plugin = get_legacy_creator_by_name("CreateAnimation") + # TODO: Refactor legacy create usage to new style creators legacy_create( creator_plugin, name=rig.name.split(':')[-1] + "_animation", diff --git a/openpype/hosts/blender/plugins/load/load_layout_json.py b/openpype/hosts/blender/plugins/load/load_layout_json.py index 81683b8de8..a941c77a8e 100644 --- a/openpype/hosts/blender/plugins/load/load_layout_json.py +++ b/openpype/hosts/blender/plugins/load/load_layout_json.py @@ -123,6 +123,7 @@ class JsonLayoutLoader(plugin.AssetLoader): # raise ValueError("Creator plugin \"CreateCamera\" was " # "not found.") + # TODO: Refactor legacy create usage to new style creators # legacy_create( # creator_plugin, # name="camera", diff --git a/openpype/hosts/blender/plugins/publish/collect_render.py b/openpype/hosts/blender/plugins/publish/collect_render.py index 48b03256ae..00faf85aed 100644 --- a/openpype/hosts/blender/plugins/publish/collect_render.py +++ b/openpype/hosts/blender/plugins/publish/collect_render.py @@ -73,9 +73,8 @@ class CollectBlenderRender(pyblish.api.InstancePlugin): def process(self, instance): context = instance.context - render_data = bpy.data.collections[ - instance.data["instance_node"]["name"] - ].get("render_data") + instance_node = instance.data["transientData"]["instance_node"] + render_data = instance_node.get("render_data") assert render_data, "No render data found." diff --git a/openpype/hosts/blender/plugins/publish/collect_review.py b/openpype/hosts/blender/plugins/publish/collect_review.py index 4889c66be3..2c077398da 100644 --- a/openpype/hosts/blender/plugins/publish/collect_review.py +++ b/openpype/hosts/blender/plugins/publish/collect_review.py @@ -16,9 +16,7 @@ class CollectReview(pyblish.api.InstancePlugin): self.log.debug(f"instance: {instance}") - datablock = bpy.data.collections.get( - instance.data.get("instance_node", {}).get("name", "") - ) + datablock = instance.data["transientData"]["instance_node"] # get cameras cameras = [ diff --git a/openpype/hosts/blender/plugins/publish/validate_instance_empty.py b/openpype/hosts/blender/plugins/publish/validate_instance_empty.py index 7669d76943..6845c29b37 100644 --- a/openpype/hosts/blender/plugins/publish/validate_instance_empty.py +++ b/openpype/hosts/blender/plugins/publish/validate_instance_empty.py @@ -13,7 +13,7 @@ class ValidateInstanceEmpty(pyblish.api.InstancePlugin): optional = False def process(self, instance): - asset_group = instance.data["instance_node"] + asset_group = instance.data["transientData"]["instance_node"] if isinstance(asset_group, bpy.types.Collection): if not (asset_group.objects or asset_group.children): From bf49ffea16a6c83c0a7ba099143b3f35ca068d85 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 02:33:26 +0100 Subject: [PATCH 052/107] Hound --- openpype/hosts/blender/api/properties.py | 5 +++++ openpype/hosts/blender/plugins/create/create_blendScene.py | 7 +------ openpype/hosts/blender/plugins/create/create_camera.py | 6 +----- openpype/hosts/blender/plugins/create/create_layout.py | 7 +------ openpype/hosts/blender/plugins/create/create_model.py | 6 ------ openpype/hosts/blender/plugins/create/create_rig.py | 5 ----- 6 files changed, 8 insertions(+), 28 deletions(-) diff --git a/openpype/hosts/blender/api/properties.py b/openpype/hosts/blender/api/properties.py index c6b5ffe011..44ca7582b9 100644 --- a/openpype/hosts/blender/api/properties.py +++ b/openpype/hosts/blender/api/properties.py @@ -1,13 +1,17 @@ import bpy from bpy.utils import register_classes_factory + class OpenpypeContext(bpy.types.PropertyGroup): pass + classes = [] # [OpenpypeContext] + factory_register, factory_unregister = register_classes_factory(classes) + def register(): """Register the properties.""" factory_register() @@ -17,6 +21,7 @@ def register(): # name="OpenPype Context", type=OpenpypeContext, options={"HIDDEN"} # ) + def unregister(): """Unregister the properties.""" factory_unregister() diff --git a/openpype/hosts/blender/plugins/create/create_blendScene.py b/openpype/hosts/blender/plugins/create/create_blendScene.py index f1090ae397..c580cdfe46 100644 --- a/openpype/hosts/blender/plugins/create/create_blendScene.py +++ b/openpype/hosts/blender/plugins/create/create_blendScene.py @@ -2,12 +2,7 @@ import bpy -from openpype.pipeline import get_current_task_name -from openpype.hosts.blender.api import plugin, lib, ops -from openpype.hosts.blender.api.pipeline import ( - AVALON_INSTANCES, - AVALON_PROPERTY, -) +from openpype.hosts.blender.api import plugin, lib class CreateBlendScene(plugin.Creator): diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index d30c60ba21..13075f718e 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -2,12 +2,8 @@ import bpy -from openpype.pipeline import CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops -from openpype.hosts.blender.api.pipeline import ( - AVALON_INSTANCES, - AVALON_PROPERTY, -) +from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES class CreateCamera(plugin.BaseCreator): diff --git a/openpype/hosts/blender/plugins/create/create_layout.py b/openpype/hosts/blender/plugins/create/create_layout.py index f569a81c29..05d11bf315 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -2,12 +2,7 @@ import bpy -from openpype.pipeline import get_current_task_name, CreatedInstance -from openpype.hosts.blender.api import plugin, lib, ops -from openpype.hosts.blender.api.pipeline import ( - AVALON_INSTANCES, - AVALON_PROPERTY, -) +from openpype.hosts.blender.api import plugin, lib class CreateLayout(plugin.BaseCreator): diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index 9774d4bec3..9f1367ebb2 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -2,13 +2,7 @@ import bpy -from openpype.pipeline import get_current_task_name, CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops -from openpype.hosts.blender.api.pipeline import ( - AVALON_INSTANCES, - AVALON_PROPERTY, -) - class CreateModel(plugin.BaseCreator): """Polygonal static geometry.""" diff --git a/openpype/hosts/blender/plugins/create/create_rig.py b/openpype/hosts/blender/plugins/create/create_rig.py index 87f0124164..c7ab9b81e1 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -2,12 +2,7 @@ import bpy -from openpype.pipeline import get_current_task_name, CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops -from openpype.hosts.blender.api.pipeline import ( - AVALON_INSTANCES, - AVALON_PROPERTY, -) class CreateRig(plugin.BaseCreator): From e67ecd4065ffa40e4b563762a969d4650300e694 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 02:34:44 +0100 Subject: [PATCH 053/107] Hound --- openpype/hosts/blender/api/pipeline.py | 2 +- openpype/hosts/blender/plugins/create/create_model.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/blender/api/pipeline.py b/openpype/hosts/blender/api/pipeline.py index 9ac59f5620..b9f802d221 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -10,7 +10,7 @@ from . import ops, properties import pyblish.api -from openpype.host import( +from openpype.host import ( HostBase, IWorkfileHost, IPublishHost, diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index 9f1367ebb2..7047304014 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -4,6 +4,7 @@ import bpy from openpype.hosts.blender.api import plugin, lib, ops + class CreateModel(plugin.BaseCreator): """Polygonal static geometry.""" From b16bc2a80e85282855460c93906783ae46a96577 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 02:43:21 +0100 Subject: [PATCH 054/107] Remove debugging prints --- openpype/hosts/blender/plugins/create/create_workfile.py | 4 +--- openpype/hosts/blender/plugins/load/load_blend.py | 3 --- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/openpype/hosts/blender/plugins/create/create_workfile.py b/openpype/hosts/blender/plugins/create/create_workfile.py index 8f5adb5583..4dda1a31cc 100644 --- a/openpype/hosts/blender/plugins/create/create_workfile.py +++ b/openpype/hosts/blender/plugins/create/create_workfile.py @@ -78,15 +78,13 @@ class CreateWorkfile(BaseCreator, AutoCreator): def collect_instances(self): - print("Collecting!") instance_node = bpy.data.collections.get(AVALON_CONTAINERS) if not instance_node: return - print(instance_node) + property = instance_node.get(AVALON_PROPERTY) if not property: return - print(property) # Create instance object from existing data instance = CreatedInstance.from_existing( diff --git a/openpype/hosts/blender/plugins/load/load_blend.py b/openpype/hosts/blender/plugins/load/load_blend.py index 1804de29b6..84e181cd6c 100644 --- a/openpype/hosts/blender/plugins/load/load_blend.py +++ b/openpype/hosts/blender/plugins/load/load_blend.py @@ -91,7 +91,6 @@ class BlendLoader(plugin.AssetLoader): members.append(data) container = self._get_asset_container(data_to.objects) - print(container) assert container, "No asset group found" container.name = group_name @@ -105,8 +104,6 @@ class BlendLoader(plugin.AssetLoader): print(obj) bpy.context.scene.collection.objects.link(obj) - print("") - # Remove the library from the blend file library = bpy.data.libraries.get(bpy.path.basename(libpath)) bpy.data.libraries.remove(library) From 6e89f3301470029ae996b02cb784b66ccfcd6986 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 08:50:03 +0100 Subject: [PATCH 055/107] Clarify Workfile instance data persistence --- openpype/hosts/blender/plugins/create/create_workfile.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/blender/plugins/create/create_workfile.py b/openpype/hosts/blender/plugins/create/create_workfile.py index 4dda1a31cc..ffd423ad74 100644 --- a/openpype/hosts/blender/plugins/create/create_workfile.py +++ b/openpype/hosts/blender/plugins/create/create_workfile.py @@ -10,7 +10,13 @@ from openpype.hosts.blender.api.pipeline import ( class CreateWorkfile(BaseCreator, AutoCreator): - """Workfile auto-creator.""" + """Workfile auto-creator. + + The workfile instance stores its data on the `AVALON_CONTAINERS` collection + as custom attributes, because unlike other instances it doesn't have an + instance node of its own. + + """ identifier = "io.openpype.creators.blender.workfile" label = "Workfile" family = "workfile" From a2a47787db707215d3597e53cf4dc33857a96219 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 09:12:09 +0100 Subject: [PATCH 056/107] Avoid need for executing in main thread by just not using `bpy.ops` functions. I also skipped the "keep_transform" part of the functionality since it's redundant because the created asset group is always at origin by default, and thus needs no matrix inverse computations. --- .../blender/plugins/create/create_camera.py | 16 +++++----------- .../blender/plugins/create/create_layout.py | 7 ++----- .../hosts/blender/plugins/create/create_model.py | 11 +++-------- .../hosts/blender/plugins/create/create_rig.py | 10 +++------- 4 files changed, 13 insertions(+), 31 deletions(-) diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index 13075f718e..b0c0fc28db 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -2,7 +2,7 @@ import bpy -from openpype.hosts.blender.api import plugin, lib, ops +from openpype.hosts.blender.api import plugin, lib from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES @@ -17,7 +17,6 @@ class CreateCamera(plugin.BaseCreator): create_as_asset_group = True - @ops.execute_function_in_main_thread def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): @@ -27,13 +26,10 @@ class CreateCamera(plugin.BaseCreator): instance_data, pre_create_data) + bpy.context.view_layer.objects.active = asset_group if pre_create_data.get("use_selection"): - bpy.context.view_layer.objects.active = asset_group - selected = lib.get_selection() - for obj in selected: - obj.select_set(True) - selected.append(asset_group) - bpy.ops.object.parent_set(keep_transform=True) + for obj in lib.get_selection(): + obj.parent = asset_group else: plugin.deselect_all() camera = bpy.data.cameras.new(subset_name) @@ -42,9 +38,7 @@ class CreateCamera(plugin.BaseCreator): instances = bpy.data.collections.get(AVALON_INSTANCES) instances.objects.link(camera_obj) - camera_obj.select_set(True) - asset_group.select_set(True) bpy.context.view_layer.objects.active = asset_group - bpy.ops.object.parent_set(keep_transform=True) + camera_obj.parent = asset_group return asset_group diff --git a/openpype/hosts/blender/plugins/create/create_layout.py b/openpype/hosts/blender/plugins/create/create_layout.py index 05d11bf315..93fb91a324 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -27,10 +27,7 @@ class CreateLayout(plugin.BaseCreator): # Add selected objects to instance if pre_create_data.get("use_selection"): bpy.context.view_layer.objects.active = asset_group - selected = lib.get_selection() - for obj in selected: - obj.select_set(True) - selected.append(asset_group) - bpy.ops.object.parent_set(keep_transform=True) + for obj in lib.get_selection(): + obj.parent = asset_group return asset_group diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index 7047304014..8b5eaeb999 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -2,7 +2,7 @@ import bpy -from openpype.hosts.blender.api import plugin, lib, ops +from openpype.hosts.blender.api import plugin, lib class CreateModel(plugin.BaseCreator): @@ -16,7 +16,6 @@ class CreateModel(plugin.BaseCreator): create_as_asset_group = True - @ops.execute_function_in_main_thread def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): @@ -27,11 +26,7 @@ class CreateModel(plugin.BaseCreator): # Add selected objects to instance if pre_create_data.get("use_selection"): bpy.context.view_layer.objects.active = asset_group - selected = lib.get_selection() - for obj in selected: - obj.select_set(True) - selected.append(asset_group) - - bpy.ops.object.parent_set(keep_transform=True) + for obj in lib.get_selection(): + obj.parent = asset_group return asset_group diff --git a/openpype/hosts/blender/plugins/create/create_rig.py b/openpype/hosts/blender/plugins/create/create_rig.py index c7ab9b81e1..8228442249 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -2,7 +2,7 @@ import bpy -from openpype.hosts.blender.api import plugin, lib, ops +from openpype.hosts.blender.api import plugin, lib class CreateRig(plugin.BaseCreator): @@ -16,7 +16,6 @@ class CreateRig(plugin.BaseCreator): create_as_asset_group = True - @ops.execute_function_in_main_thread def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): @@ -27,10 +26,7 @@ class CreateRig(plugin.BaseCreator): # Add selected objects to instance if pre_create_data.get("use_selection"): bpy.context.view_layer.objects.active = asset_group - selected = lib.get_selection() - for obj in selected: - obj.select_set(True) - selected.append(asset_group) - bpy.ops.object.parent_set(keep_transform=True) + for obj in lib.get_selection(): + obj.parent = asset_group return asset_group From dcfdb25217bf3c72bcad16500582c4a984ad6892 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 10:52:54 +0100 Subject: [PATCH 057/107] Implement `ILoadHost` + refactor `host.ls()` to `host.get_containers()` --- openpype/hosts/blender/api/pipeline.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/blender/api/pipeline.py b/openpype/hosts/blender/api/pipeline.py index b9f802d221..ee43108594 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -14,6 +14,7 @@ from openpype.host import ( HostBase, IWorkfileHost, IPublishHost, + ILoadHost ) from openpype.client import get_asset_by_name from openpype.pipeline import ( @@ -60,7 +61,7 @@ IS_HEADLESS = bpy.app.background log = Logger.get_logger(__name__) -class BlenderHost(HostBase, IWorkfileHost, IPublishHost): +class BlenderHost(HostBase, IWorkfileHost, IPublishHost, ILoadHost): name = "blender" def install(self): @@ -68,7 +69,7 @@ class BlenderHost(HostBase, IWorkfileHost, IPublishHost): Install Blender host functionality.""" install() - def ls(self) -> Iterator: + def get_containers(self) -> Iterator: """List containers from active Blender scene.""" return ls() From 28c83c3bb63085b87d861d23644bea2871e7b73b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 11:09:34 +0100 Subject: [PATCH 058/107] Remove duplicated code Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/blender/plugins/publish/extract_blend.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/extract_blend.py b/openpype/hosts/blender/plugins/publish/extract_blend.py index 991cd040de..a1b49dcd8f 100644 --- a/openpype/hosts/blender/plugins/publish/extract_blend.py +++ b/openpype/hosts/blender/plugins/publish/extract_blend.py @@ -19,9 +19,6 @@ class ExtractBlend(publish.Extractor, publish.OptionalPyblishPluginMixin): # Define extract output file path - if not self.is_active(instance.data): - return - stagingdir = self.staging_dir(instance) filename = f"{instance.name}.blend" filepath = os.path.join(stagingdir, filename) From 6b59b718238f51ec1aef7be40b804b842adf6062 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 11:42:12 +0100 Subject: [PATCH 059/107] Re-add collector to collect instance members --- .../plugins/publish/collect_instance.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 openpype/hosts/blender/plugins/publish/collect_instance.py diff --git a/openpype/hosts/blender/plugins/publish/collect_instance.py b/openpype/hosts/blender/plugins/publish/collect_instance.py new file mode 100644 index 0000000000..f8fc038347 --- /dev/null +++ b/openpype/hosts/blender/plugins/publish/collect_instance.py @@ -0,0 +1,43 @@ +import bpy + +import pyblish.api + +from openpype.pipeline.publish import KnownPublishError +from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY + + +class CollectBlenderInstanceData(pyblish.api.InstancePlugin): + """Validator to verify that the instance is not empty""" + + order = pyblish.api.CollectorOrder + hosts = ["blender"] + families = ["model", "pointcache", "rig", "camera" "layout", "blendScene"] + label = "Collect Instance" + + def process(self, instance): + instance_node = instance.data["transientData"]["instance_node"] + + # Collect members of the instance + members = [instance_node] + if isinstance(instance_node, bpy.types.Collection): + members.extend(instance_node.objects) + members.extend(instance_node.children) + + # Special case for animation instances, include armatures + # TODO: Does this still work as intended? + if instance.data["family"] == "animation": + for obj in instance_node.objects: + if obj.type == 'EMPTY' and obj.get(AVALON_PROPERTY): + members.extend( + child for child in obj.children + if child.type == 'ARMATURE' + ) + elif isinstance(instance_node, bpy.types.Object): + members.extend(instance_node.children_recursive) + else: + raise KnownPublishError( + f"Unsupported instance node type '{type(instance_node)}' " + f"for instance '{instance}'" + ) + + instance[:] = members From 19b7abb8ff3def802d3cf9074cd77a20c77b12cb Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 11:47:20 +0100 Subject: [PATCH 060/107] Label cosmetics --- openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py b/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py index dd955dc5da..060bccbd04 100644 --- a/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py +++ b/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py @@ -21,7 +21,7 @@ class ValidateMeshHasUvs( order = ValidateContentsOrder hosts = ["blender"] families = ["model"] - label = "Mesh Has UV's" + label = "Mesh Has UVs" actions = [openpype.hosts.blender.api.action.SelectInvalidAction] optional = True From b8f361f75d2e7f5d4c3097ba1bd67c095b37f3b2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 11:55:14 +0100 Subject: [PATCH 061/107] Refactor Validate Instance Empty + raise PublishValidationError --- .../plugins/publish/validate_instance_empty.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/validate_instance_empty.py b/openpype/hosts/blender/plugins/publish/validate_instance_empty.py index 6845c29b37..51a1dcf6ca 100644 --- a/openpype/hosts/blender/plugins/publish/validate_instance_empty.py +++ b/openpype/hosts/blender/plugins/publish/validate_instance_empty.py @@ -1,6 +1,5 @@ -import bpy - import pyblish.api +from openpype.pipeline.publish import PublishValidationError class ValidateInstanceEmpty(pyblish.api.InstancePlugin): @@ -13,11 +12,8 @@ class ValidateInstanceEmpty(pyblish.api.InstancePlugin): optional = False def process(self, instance): - asset_group = instance.data["transientData"]["instance_node"] - - if isinstance(asset_group, bpy.types.Collection): - if not (asset_group.objects or asset_group.children): - raise RuntimeError(f"Instance {instance.name} is empty.") - elif isinstance(asset_group, bpy.types.Object): - if not asset_group.children: - raise RuntimeError(f"Instance {instance.name} is empty.") + # Members are collected by `collect_instance` so we only need to check + # whether any member is included. The instance node will be included + # as a member as well, hence we will check for at least 2 members + if len(instance) < 2: + raise PublishValidationError(f"Instance {instance.name} is empty.") From 3f2bd4c001a8a35dc5f5f510074b170ceda2fbc7 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 11:56:08 +0100 Subject: [PATCH 062/107] Improve object name readability in reports --- .../blender/plugins/publish/validate_camera_zero_keyframe.py | 3 ++- .../plugins/publish/validate_mesh_no_negative_scale.py | 3 ++- .../blender/plugins/publish/validate_no_colons_in_name.py | 3 ++- .../hosts/blender/plugins/publish/validate_object_mode.py | 3 ++- .../hosts/blender/plugins/publish/validate_transform_zero.py | 5 +++-- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py b/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py index 65697cb86d..ee0a0e4dc9 100644 --- a/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py +++ b/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py @@ -48,6 +48,7 @@ class ValidateCameraZeroKeyframe(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: + names = ", ".join(obj.name for obj in invalid) raise PublishValidationError( - f"Camera must have a keyframe at frame 0: {invalid}" + f"Camera must have a keyframe at frame 0: {names}" ) diff --git a/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py b/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py index a498a3b4cb..7f77bbe38c 100644 --- a/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py +++ b/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py @@ -37,6 +37,7 @@ class ValidateMeshNoNegativeScale(pyblish.api.Validator, invalid = self.get_invalid(instance) if invalid: + names = ", ".join(obj.name for obj in invalid) raise PublishValidationError( - f"Meshes found in instance with negative scale: {invalid}" + f"Meshes found in instance with negative scale: {names}" ) diff --git a/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py b/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py index 17119f8d88..caf555b535 100644 --- a/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py +++ b/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py @@ -46,6 +46,7 @@ class ValidateNoColonsInName(pyblish.api.InstancePlugin, invalid = self.get_invalid(instance) if invalid: + names = ", ".join(obj.name for obj in invalid) raise PublishValidationError( - f"Objects found with colon in name: {invalid}" + f"Objects found with colon in name: {names}" ) diff --git a/openpype/hosts/blender/plugins/publish/validate_object_mode.py b/openpype/hosts/blender/plugins/publish/validate_object_mode.py index 3b6f29a79e..ab5f4bb467 100644 --- a/openpype/hosts/blender/plugins/publish/validate_object_mode.py +++ b/openpype/hosts/blender/plugins/publish/validate_object_mode.py @@ -37,6 +37,7 @@ class ValidateObjectIsInObjectMode( invalid = self.get_invalid(instance) if invalid: + names = ", ".join(obj.name for obj in invalid) raise PublishValidationError( - f"Object found in instance is not in Object Mode: {invalid}" + f"Object found in instance is not in Object Mode: {names}" ) diff --git a/openpype/hosts/blender/plugins/publish/validate_transform_zero.py b/openpype/hosts/blender/plugins/publish/validate_transform_zero.py index 5270a50888..1fb9535ee4 100644 --- a/openpype/hosts/blender/plugins/publish/validate_transform_zero.py +++ b/openpype/hosts/blender/plugins/publish/validate_transform_zero.py @@ -48,7 +48,8 @@ class ValidateTransformZero(pyblish.api.InstancePlugin, invalid = self.get_invalid(instance) if invalid: + names = ", ".join(obj.name for obj in invalid) raise PublishValidationError( - "Object found in instance has not" - f" transform to zero: {invalid}" + "Objects found in instance which do not" + f" have transform set to zero: {names}" ) From f12b4106717be573f6149f52b2419cbcaaaf274b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 12:01:41 +0100 Subject: [PATCH 063/107] Clean up some todos --- openpype/hosts/blender/api/ops.py | 1 - openpype/hosts/blender/api/plugin.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/blender/api/ops.py b/openpype/hosts/blender/api/ops.py index c617144b6f..408e38e6cd 100644 --- a/openpype/hosts/blender/api/ops.py +++ b/openpype/hosts/blender/api/ops.py @@ -428,7 +428,6 @@ class TOPBAR_MT_avalon(bpy.types.Menu): layout.operator(SetResolution.bl_idname, text="Set Resolution") layout.separator() layout.operator(LaunchWorkFiles.bl_idname, text="Work Files...") - # TODO (jasper): maybe add 'Reload Pipeline' def draw_avalon_menu(self, context): diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index d1cefe0a62..57fccf9299 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -430,7 +430,7 @@ class AssetLoader(LoaderPlugin): namespace: Use pre-defined namespace options: Additional settings dictionary """ - # TODO (jasper): make it possible to add the asset several times by + # TODO: make it possible to add the asset several times by # just re-using the collection filepath = self.filepath_from_context(context) assert Path(filepath).exists(), f"{filepath} doesn't exist." From 9419fe5d51a23b9378394638eb91b9212a4dc61d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 12:02:20 +0100 Subject: [PATCH 064/107] Clarify what `asset_group` pre_create_data intends to do, because it is not an Attribute Definition on the Creator --- openpype/hosts/blender/plugins/create/create_animation.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/blender/plugins/create/create_animation.py b/openpype/hosts/blender/plugins/create/create_animation.py index 6f0cabb259..b3d10090e3 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -15,7 +15,6 @@ class CreateAnimation(plugin.BaseCreator): def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): - """Run the creator on Blender main thread.""" # Run parent create method collection = super().create( subset_name, instance_data, pre_create_data @@ -26,7 +25,9 @@ class CreateAnimation(plugin.BaseCreator): for obj in selected: collection.objects.link(obj) elif pre_create_data.get("asset_group"): - obj = (self.options or {}).get("asset_group") + # Use for Load Blend automated creation of animation instances + # upon loading rig files + obj = pre_create_data.get("asset_group") collection.objects.link(obj) return collection From a450eab0ce67b7c2bce903b2008776f7863b401b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 12:02:46 +0100 Subject: [PATCH 065/107] Remove "asset_group" functionality in CreateReview, since it's unused there --- openpype/hosts/blender/plugins/create/create_review.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/openpype/hosts/blender/plugins/create/create_review.py b/openpype/hosts/blender/plugins/create/create_review.py index 2d521c4255..3dd52395df 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -15,7 +15,6 @@ class CreateReview(plugin.BaseCreator): def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): - """Run the creator on Blender main thread.""" # Run parent create method collection = super().create( subset_name, instance_data, pre_create_data @@ -25,9 +24,5 @@ class CreateReview(plugin.BaseCreator): selected = lib.get_selection() for obj in selected: collection.objects.link(obj) - elif pre_create_data.get("asset_group"): - # TODO: What is the intended behavior for this? - obj = (self.options or {}).get("asset_group") - collection.objects.link(obj) return collection From 8e6c533ee6dbabd99b322336196e976806a676c0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 12:03:16 +0100 Subject: [PATCH 066/107] Remove redundant docstring - since `create` now isn't off-loaded to 'main thread' anymore --- openpype/hosts/blender/plugins/create/create_camera.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index b0c0fc28db..d624a7f05a 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -20,7 +20,6 @@ class CreateCamera(plugin.BaseCreator): def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): - """Run the creator on Blender main thread.""" asset_group = super().create(subset_name, instance_data, From 4d1c887ece74c6dc638bf9df250d8e39cd2e2ee9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 12:07:08 +0100 Subject: [PATCH 067/107] Fix missing class inheritance --- .../plugins/publish/validate_camera_zero_keyframe.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py b/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py index ee0a0e4dc9..9b6e513897 100644 --- a/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py +++ b/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py @@ -7,11 +7,13 @@ import pyblish.api import openpype.hosts.blender.api.action from openpype.pipeline.publish import ( ValidateContentsOrder, - PublishValidationError + PublishValidationError, + OptionalPyblishPluginMixin ) -class ValidateCameraZeroKeyframe(pyblish.api.InstancePlugin): +class ValidateCameraZeroKeyframe(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Camera must have a keyframe at frame 0. Unreal shifts the first keyframe to frame 0. Forcing the camera to have From 7acc5f9dd61a73841363a9a1000052ddc6f88e7f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 12:09:24 +0100 Subject: [PATCH 068/107] Collect camera instance members --- openpype/hosts/blender/plugins/publish/collect_instance.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/blender/plugins/publish/collect_instance.py b/openpype/hosts/blender/plugins/publish/collect_instance.py index f8fc038347..b170e3d06f 100644 --- a/openpype/hosts/blender/plugins/publish/collect_instance.py +++ b/openpype/hosts/blender/plugins/publish/collect_instance.py @@ -11,7 +11,8 @@ class CollectBlenderInstanceData(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder hosts = ["blender"] - families = ["model", "pointcache", "rig", "camera" "layout", "blendScene"] + families = ["model", "pointcache", "rig", "camera" "layout", "blendScene", + "camera"] label = "Collect Instance" def process(self, instance): From 53bcf0e17cc841c901a887e8c09619aacab2d4d4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 12:17:06 +0100 Subject: [PATCH 069/107] Use instance node explicitly as the 'asset group' --- openpype/hosts/blender/plugins/publish/extract_abc.py | 9 +++------ .../blender/plugins/publish/extract_abc_animation.py | 2 +- .../hosts/blender/plugins/publish/extract_camera_abc.py | 7 +------ openpype/hosts/blender/plugins/publish/extract_fbx.py | 6 ++---- .../blender/plugins/publish/extract_fbx_animation.py | 9 +-------- 5 files changed, 8 insertions(+), 25 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/extract_abc.py b/openpype/hosts/blender/plugins/publish/extract_abc.py index 1602c4d266..b0cd6d0d32 100644 --- a/openpype/hosts/blender/plugins/publish/extract_abc.py +++ b/openpype/hosts/blender/plugins/publish/extract_abc.py @@ -28,18 +28,15 @@ class ExtractABC(publish.Extractor, publish.OptionalPyblishPluginMixin): plugin.deselect_all() - selected = [] - active = None + asset_group = instance.data["transientData"]["instance_node"] + selected = [] for obj in instance: obj.select_set(True) selected.append(obj) - # Set as active the asset group - if obj.get(AVALON_PROPERTY): - active = obj context = plugin.create_blender_context( - active=active, selected=selected) + active=asset_group, selected=selected) with bpy.context.temp_override(**context): # We export the abc diff --git a/openpype/hosts/blender/plugins/publish/extract_abc_animation.py b/openpype/hosts/blender/plugins/publish/extract_abc_animation.py index 0ed2819407..1c23fc8acb 100644 --- a/openpype/hosts/blender/plugins/publish/extract_abc_animation.py +++ b/openpype/hosts/blender/plugins/publish/extract_abc_animation.py @@ -32,7 +32,7 @@ class ExtractAnimationABC( plugin.deselect_all() selected = [] - asset_group = None + asset_group = instance.data["transientData"]["instance_node"] objects = [] for obj in instance: diff --git a/openpype/hosts/blender/plugins/publish/extract_camera_abc.py b/openpype/hosts/blender/plugins/publish/extract_camera_abc.py index 9db1505d37..9d0b7f132b 100644 --- a/openpype/hosts/blender/plugins/publish/extract_camera_abc.py +++ b/openpype/hosts/blender/plugins/publish/extract_camera_abc.py @@ -29,12 +29,7 @@ class ExtractCameraABC(publish.Extractor, publish.OptionalPyblishPluginMixin): plugin.deselect_all() - asset_group = None - for obj in instance: - if obj.get(AVALON_PROPERTY): - asset_group = obj - break - assert asset_group, "No asset group found" + asset_group = instance.data["transientData"]["instance_node"] # Need to cast to list because children is a tuple selected = list(asset_group.children) diff --git a/openpype/hosts/blender/plugins/publish/extract_fbx.py b/openpype/hosts/blender/plugins/publish/extract_fbx.py index fae438158a..0ba82eca4e 100644 --- a/openpype/hosts/blender/plugins/publish/extract_fbx.py +++ b/openpype/hosts/blender/plugins/publish/extract_fbx.py @@ -29,14 +29,12 @@ class ExtractFBX(publish.Extractor, publish.OptionalPyblishPluginMixin): plugin.deselect_all() - selected = [] - asset_group = None + asset_group = instance.data["transientData"]["instance_node"] + selected = [] for obj in instance: obj.select_set(True) selected.append(obj) - if obj.get(AVALON_PROPERTY): - asset_group = obj context = plugin.create_blender_context( active=asset_group, selected=selected) diff --git a/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py b/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py index ba584ac99a..6fbcdc63c3 100644 --- a/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py @@ -31,14 +31,7 @@ class ExtractAnimationFBX( # Perform extraction self.log.debug("Performing extraction..") - # The first collection object in the instance is taken, as there - # should be only one that contains the asset group. - collection = [ - obj for obj in instance if type(obj) is bpy.types.Collection][0] - - # Again, the first object in the collection is taken , as there - # should be only the asset group in the collection. - asset_group = collection.objects[0] + asset_group = instance.data["transientData"]["instance_node"] armature = [ obj for obj in asset_group.children if obj.type == 'ARMATURE'][0] From 2b5c20e6e0beaf56767a000c09d09c8860d45d08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Tue, 14 Nov 2023 12:49:46 +0100 Subject: [PATCH 070/107] Update openpype/hosts/fusion/plugins/create/create_saver.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/fusion/plugins/create/create_saver.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/openpype/hosts/fusion/plugins/create/create_saver.py b/openpype/hosts/fusion/plugins/create/create_saver.py index e8ba2880a4..ecf36abdd2 100644 --- a/openpype/hosts/fusion/plugins/create/create_saver.py +++ b/openpype/hosts/fusion/plugins/create/create_saver.py @@ -149,13 +149,7 @@ class CreateSaver(NewCreator): # get frame padding from anatomy templates anatomy = Anatomy() - render_anatomy_template = anatomy.templates.get("render") - if render_anatomy_template: - frame_padding = int( - render_anatomy_template.get("frame_padding", 4) - ) - else: - frame_padding = int(anatomy.templates.get("frame_padding", 4)) + frame_padding = anatomy.templates["frame_padding"] # Subset change detected workdir = os.path.normpath(legacy_io.Session["AVALON_WORKDIR"]) From 162394a56cfd3f62756603dd821596c478191747 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 15:17:55 +0100 Subject: [PATCH 071/107] Fix typo, missing comment *facepalm* --- openpype/hosts/blender/plugins/publish/collect_instance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/blender/plugins/publish/collect_instance.py b/openpype/hosts/blender/plugins/publish/collect_instance.py index b170e3d06f..72c8f13362 100644 --- a/openpype/hosts/blender/plugins/publish/collect_instance.py +++ b/openpype/hosts/blender/plugins/publish/collect_instance.py @@ -11,7 +11,7 @@ class CollectBlenderInstanceData(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder hosts = ["blender"] - families = ["model", "pointcache", "rig", "camera" "layout", "blendScene", + families = ["model", "pointcache", "rig", "camera", "layout", "blendScene", "camera"] label = "Collect Instance" From 8d57f5f3ef7224d4bae5920ff1e82bde93340f2b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 15:23:45 +0100 Subject: [PATCH 072/107] Fix extract layout --- .../hosts/blender/plugins/publish/extract_layout.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/blender/plugins/publish/extract_layout.py b/openpype/hosts/blender/plugins/publish/extract_layout.py index d0adac6edb..7e8ca7a250 100644 --- a/openpype/hosts/blender/plugins/publish/extract_layout.py +++ b/openpype/hosts/blender/plugins/publish/extract_layout.py @@ -128,13 +128,22 @@ class ExtractLayout(publish.Extractor, publish.OptionalPyblishPluginMixin): json_data = [] fbx_files = [] - asset_group = bpy.data.objects[str(instance)] + asset_group = instance.data["transientData"]["instance_node"] fbx_count = 0 project_name = instance.context.data["projectEntity"]["name"] for asset in asset_group.children: metadata = asset.get(AVALON_PROPERTY) + if not metadata: + # Avoid erroring directly if there's just invalid data + # inside the instance + # TODO: This should actually be validated in a validator + self.log.warning( + f"Found content in layout that is not a loaded " + f"asset, skipping: {asset.name_full}" + ) + continue version_id = metadata["parent"] family = metadata["family"] From d1aaefab9c916da3e2ad94a77792489c1300f60b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 15:24:20 +0100 Subject: [PATCH 073/107] Improve comment grammar --- openpype/hosts/blender/plugins/publish/extract_layout.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/extract_layout.py b/openpype/hosts/blender/plugins/publish/extract_layout.py index 7e8ca7a250..73d92961bc 100644 --- a/openpype/hosts/blender/plugins/publish/extract_layout.py +++ b/openpype/hosts/blender/plugins/publish/extract_layout.py @@ -136,8 +136,8 @@ class ExtractLayout(publish.Extractor, publish.OptionalPyblishPluginMixin): for asset in asset_group.children: metadata = asset.get(AVALON_PROPERTY) if not metadata: - # Avoid erroring directly if there's just invalid data - # inside the instance + # Avoid raising error directly if there's just invalid data + # inside the instance; better to log it to the artist # TODO: This should actually be validated in a validator self.log.warning( f"Found content in layout that is not a loaded " From fa5638d06bb73d4f327d3de120718b72d70c6b9f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 15:33:59 +0100 Subject: [PATCH 074/107] Fix parent class --- openpype/hosts/blender/plugins/create/create_blendScene.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/blender/plugins/create/create_blendScene.py b/openpype/hosts/blender/plugins/create/create_blendScene.py index c580cdfe46..bda026286d 100644 --- a/openpype/hosts/blender/plugins/create/create_blendScene.py +++ b/openpype/hosts/blender/plugins/create/create_blendScene.py @@ -5,7 +5,7 @@ import bpy from openpype.hosts.blender.api import plugin, lib -class CreateBlendScene(plugin.Creator): +class CreateBlendScene(plugin.BaseCreator): """Generic group of assets.""" name = "blendScene" From 0a714778736118d78c6f5c7a3660c7828de26d26 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 15:42:29 +0100 Subject: [PATCH 075/107] Allow to fall back to a window from the window manager --- openpype/hosts/blender/api/lib.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/blender/api/lib.py b/openpype/hosts/blender/api/lib.py index 5d8c93dc49..e80ed61bc8 100644 --- a/openpype/hosts/blender/api/lib.py +++ b/openpype/hosts/blender/api/lib.py @@ -278,9 +278,11 @@ def get_selected_collections(): list: A list of `bpy.types.Collection` objects that are currently selected in the outliner. """ + window = bpy.context.window or bpy.context.window_manager.windows[0] + try: area = next( - area for area in bpy.context.window.screen.areas + area for area in window.screen.areas if area.type == 'OUTLINER') region = next( region for region in area.regions @@ -290,10 +292,10 @@ def get_selected_collections(): "must be in the main Blender window.") from e with bpy.context.temp_override( - window=bpy.context.window, + window=window, area=area, region=region, - screen=bpy.context.window.screen + screen=window.screen ): ids = bpy.context.selected_ids From 63ff830d12cdfaf8ed4cee4e681f5c5e650cc76a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 15:50:07 +0100 Subject: [PATCH 076/107] Ignore collections in instance, only select objects --- openpype/hosts/blender/plugins/publish/extract_abc.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/extract_abc.py b/openpype/hosts/blender/plugins/publish/extract_abc.py index b0cd6d0d32..415cbb186c 100644 --- a/openpype/hosts/blender/plugins/publish/extract_abc.py +++ b/openpype/hosts/blender/plugins/publish/extract_abc.py @@ -32,8 +32,9 @@ class ExtractABC(publish.Extractor, publish.OptionalPyblishPluginMixin): selected = [] for obj in instance: - obj.select_set(True) - selected.append(obj) + if isinstance(obj, bpy.types.Object): + obj.select_set(True) + selected.append(obj) context = plugin.create_blender_context( active=asset_group, selected=selected) From 00d5b90568e6a35df49abe3bc8129a4cd6ab7a4a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 15:50:23 +0100 Subject: [PATCH 077/107] Remove unused import --- openpype/hosts/blender/plugins/publish/extract_abc.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/blender/plugins/publish/extract_abc.py b/openpype/hosts/blender/plugins/publish/extract_abc.py index 415cbb186c..12d062d925 100644 --- a/openpype/hosts/blender/plugins/publish/extract_abc.py +++ b/openpype/hosts/blender/plugins/publish/extract_abc.py @@ -4,7 +4,6 @@ import bpy from openpype.pipeline import publish from openpype.hosts.blender.api import plugin -from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY class ExtractABC(publish.Extractor, publish.OptionalPyblishPluginMixin): From f281da5784f79cacda9730c5b211a77e48bfeb2b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 16:01:30 +0100 Subject: [PATCH 078/107] Fix missing identifier --- openpype/hosts/blender/plugins/create/create_blendScene.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/blender/plugins/create/create_blendScene.py b/openpype/hosts/blender/plugins/create/create_blendScene.py index bda026286d..c09261705f 100644 --- a/openpype/hosts/blender/plugins/create/create_blendScene.py +++ b/openpype/hosts/blender/plugins/create/create_blendScene.py @@ -8,6 +8,7 @@ from openpype.hosts.blender.api import plugin, lib class CreateBlendScene(plugin.BaseCreator): """Generic group of assets.""" + identifier = "io.openpype.creators.blender.blendscene" name = "blendScene" label = "Blender Scene" family = "blendScene" From 0556986a3e39c45be8c923f18a87981c17163c3a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 16:40:29 +0100 Subject: [PATCH 079/107] Remove redundant data --- openpype/hosts/blender/api/plugin.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 57fccf9299..5a0026d862 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -325,8 +325,6 @@ class BaseCreator(Creator): { "id": "pyblish.avalon.instance", "creator_identifier": self.identifier, - "label": subset_name, - "task": get_current_task_name(), "subset": subset_name, } ) From 9d6352c68dc69383534ab4a0757751a7829d7a01 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 16:43:04 +0100 Subject: [PATCH 080/107] Remove `name` attributes on Creator that are irrelevant to new-style creators --- openpype/hosts/blender/plugins/create/create_action.py | 1 - openpype/hosts/blender/plugins/create/create_animation.py | 1 - openpype/hosts/blender/plugins/create/create_blendScene.py | 1 - openpype/hosts/blender/plugins/create/create_camera.py | 1 - openpype/hosts/blender/plugins/create/create_layout.py | 1 - openpype/hosts/blender/plugins/create/create_model.py | 1 - openpype/hosts/blender/plugins/create/create_pointcache.py | 1 - openpype/hosts/blender/plugins/create/create_render.py | 1 - openpype/hosts/blender/plugins/create/create_review.py | 1 - openpype/hosts/blender/plugins/create/create_rig.py | 1 - 10 files changed, 10 deletions(-) diff --git a/openpype/hosts/blender/plugins/create/create_action.py b/openpype/hosts/blender/plugins/create/create_action.py index f14023639f..0929778d78 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -9,7 +9,6 @@ class CreateAction(plugin.BaseCreator): """Action output for character rigs.""" identifier = "io.openpype.creators.blender.action" - name = "actionMain" label = "Action" family = "action" icon = "male" diff --git a/openpype/hosts/blender/plugins/create/create_animation.py b/openpype/hosts/blender/plugins/create/create_animation.py index b3d10090e3..3a91b2d5ff 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -7,7 +7,6 @@ class CreateAnimation(plugin.BaseCreator): """Animation output for character rigs.""" identifier = "io.openpype.creators.blender.animation" - name = "animationMain" label = "Animation" family = "animation" icon = "male" diff --git a/openpype/hosts/blender/plugins/create/create_blendScene.py b/openpype/hosts/blender/plugins/create/create_blendScene.py index c09261705f..e1026282c0 100644 --- a/openpype/hosts/blender/plugins/create/create_blendScene.py +++ b/openpype/hosts/blender/plugins/create/create_blendScene.py @@ -9,7 +9,6 @@ class CreateBlendScene(plugin.BaseCreator): """Generic group of assets.""" identifier = "io.openpype.creators.blender.blendscene" - name = "blendScene" label = "Blender Scene" family = "blendScene" icon = "cubes" diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index d624a7f05a..2e2e6cec22 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -10,7 +10,6 @@ class CreateCamera(plugin.BaseCreator): """Polygonal static geometry.""" identifier = "io.openpype.creators.blender.camera" - name = "cameraMain" label = "Camera" family = "camera" icon = "video-camera" diff --git a/openpype/hosts/blender/plugins/create/create_layout.py b/openpype/hosts/blender/plugins/create/create_layout.py index 93fb91a324..16d227e50e 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -9,7 +9,6 @@ class CreateLayout(plugin.BaseCreator): """Layout output for character rigs.""" identifier = "io.openpype.creators.blender.layout" - name = "layoutMain" label = "Layout" family = "layout" icon = "cubes" diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index 8b5eaeb999..2f3f61728b 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -9,7 +9,6 @@ class CreateModel(plugin.BaseCreator): """Polygonal static geometry.""" identifier = "io.openpype.creators.blender.model" - name = "modelMain" label = "Model" family = "model" icon = "cube" diff --git a/openpype/hosts/blender/plugins/create/create_pointcache.py b/openpype/hosts/blender/plugins/create/create_pointcache.py index d80a1b07f0..b3329bcb3b 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -7,7 +7,6 @@ class CreatePointcache(plugin.BaseCreator): """Polygonal static geometry.""" identifier = "io.openpype.creators.blender.pointcache" - name = "pointcacheMain" label = "Point Cache" family = "pointcache" icon = "gears" diff --git a/openpype/hosts/blender/plugins/create/create_render.py b/openpype/hosts/blender/plugins/create/create_render.py index bffa5696df..7fb3e5eb00 100644 --- a/openpype/hosts/blender/plugins/create/create_render.py +++ b/openpype/hosts/blender/plugins/create/create_render.py @@ -9,7 +9,6 @@ class CreateRenderlayer(plugin.BaseCreator): """Single baked camera.""" identifier = "io.openpype.creators.blender.render" - name = "renderingMain" label = "Render" family = "render" icon = "eye" diff --git a/openpype/hosts/blender/plugins/create/create_review.py b/openpype/hosts/blender/plugins/create/create_review.py index 3dd52395df..940bcbea22 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -7,7 +7,6 @@ class CreateReview(plugin.BaseCreator): """Single baked camera.""" identifier = "io.openpype.creators.blender.review" - name = "reviewDefault" label = "Review" family = "review" icon = "video-camera" diff --git a/openpype/hosts/blender/plugins/create/create_rig.py b/openpype/hosts/blender/plugins/create/create_rig.py index 8228442249..d63b8d56ff 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -9,7 +9,6 @@ class CreateRig(plugin.BaseCreator): """Artist-friendly rig with controls to direct motion.""" identifier = "io.openpype.creators.blender.rig" - name = "rigMain" label = "Rig" family = "rig" icon = "wheelchair" From d077b7526f289a992bb89f53e589f8909950e556 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 16:57:25 +0100 Subject: [PATCH 081/107] Add legacy instance conversion --- .../blender/plugins/create/convert_legacy.py | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 openpype/hosts/blender/plugins/create/convert_legacy.py diff --git a/openpype/hosts/blender/plugins/create/convert_legacy.py b/openpype/hosts/blender/plugins/create/convert_legacy.py new file mode 100644 index 0000000000..f05a6b1f5a --- /dev/null +++ b/openpype/hosts/blender/plugins/create/convert_legacy.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +"""Converter for legacy Houdini subsets.""" +from openpype.pipeline.create.creator_plugins import SubsetConvertorPlugin +from openpype.hosts.blender.api.lib import imprint + + +class BlenderLegacyConvertor(SubsetConvertorPlugin): + """Find and convert any legacy subsets in the scene. + + This Converter will find all legacy subsets in the scene and will + transform them to the current system. Since the old subsets doesn't + retain any information about their original creators, the only mapping + we can do is based on their families. + + Its limitation is that you can have multiple creators creating subset + of the same family and there is no way to handle it. This code should + nevertheless cover all creators that came with OpenPype. + + """ + identifier = "io.openpype.creators.blender.legacy" + family_to_id = { + "action": "io.openpype.creators.blender.action", + "camera": "io.openpype.creators.blender.camera", + "animation": "io.openpype.creators.blender.animation", + "blendScene": "io.openpype.creators.blender.blendscene", + "layout": "io.openpype.creators.blender.layout", + "model": "io.openpype.creators.blender.model", + "pointcache": "io.openpype.creators.blender.pointcache", + "render": "io.openpype.creators.blender.render", + "review": "io.openpype.creators.blender.review", + "rig": "io.openpype.creators.blender.rig", + } + + def __init__(self, *args, **kwargs): + super(BlenderLegacyConvertor, self).__init__(*args, **kwargs) + self.legacy_subsets = {} + + def find_instances(self): + """Find legacy subsets in the scene. + + Legacy subsets are the ones that doesn't have `creator_identifier` + parameter on them. + + This is using cached entries done in + :py:meth:`~BaseCreator.cache_subsets()` + + """ + self.legacy_subsets = self.collection_shared_data.get( + "blender_cached_legacy_subsets") + if not self.legacy_subsets: + return + self.add_convertor_item( + "Found {} incompatible subset{}".format( + len(self.legacy_subsets), + "s" if len(self.legacy_subsets) > 1 else "" + ) + ) + + def convert(self): + """Convert all legacy subsets to current. + + It is enough to add `creator_identifier` and `instance_node`. + + """ + if not self.legacy_subsets: + return + + for family, instance_nodes in self.legacy_subsets.items(): + if family in self.family_to_id: + for instance_node in instance_nodes: + creator_identifier = self.family_to_id[family] + self.log.info( + "Converting {} to {}".format(instance_node.name, + creator_identifier) + ) + imprint(instance_node, data={ + "creator_identifier": creator_identifier + }) From 1fbe1b3163aa296bb9a007a3c95e61b24b55306d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 17:11:46 +0100 Subject: [PATCH 082/107] Refactor load layout to create new-style creator animation instances --- .../hosts/blender/plugins/load/load_blend.py | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/blender/plugins/load/load_blend.py b/openpype/hosts/blender/plugins/load/load_blend.py index 84e181cd6c..8b1af5a0da 100644 --- a/openpype/hosts/blender/plugins/load/load_blend.py +++ b/openpype/hosts/blender/plugins/load/load_blend.py @@ -4,11 +4,11 @@ from pathlib import Path import bpy from openpype.pipeline import ( - legacy_create, get_representation_path, AVALON_CONTAINER_ID, + registered_host ) -from openpype.pipeline.create import get_legacy_creator_by_name +from openpype.pipeline.create import CreateContext from openpype.hosts.blender.api import plugin from openpype.hosts.blender.api.lib import imprint from openpype.hosts.blender.api.pipeline import ( @@ -57,20 +57,21 @@ class BlendLoader(plugin.AssetLoader): obj.get(AVALON_PROPERTY).get('family') == 'rig' ) ] + if not rigs: + return + + # Create animation instances for each rig + creator_identifier = "io.openpype.creators.blender.animation" + host = registered_host() + create_context = CreateContext(host) for rig in rigs: - creator_plugin = get_legacy_creator_by_name("CreateAnimation") - # TODO: Refactor legacy create usage to new style creators - legacy_create( - creator_plugin, - name=rig.name.split(':')[-1] + "_animation", - asset=asset, - options={ - "useSelection": False, + create_context.create( + creator_identifier=creator_identifier, + variant=rig.name.split(':')[-1], + pre_create_data={ + "use_selection": False, "asset_group": rig - }, - data={ - "dependencies": representation } ) From b04ae30b821c8a26d68eedf62864100e2fd04989 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 17:13:46 +0100 Subject: [PATCH 083/107] Collect animation members --- openpype/hosts/blender/plugins/publish/collect_instance.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/collect_instance.py b/openpype/hosts/blender/plugins/publish/collect_instance.py index 72c8f13362..3d4e634e9e 100644 --- a/openpype/hosts/blender/plugins/publish/collect_instance.py +++ b/openpype/hosts/blender/plugins/publish/collect_instance.py @@ -11,8 +11,8 @@ class CollectBlenderInstanceData(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder hosts = ["blender"] - families = ["model", "pointcache", "rig", "camera", "layout", "blendScene", - "camera"] + families = ["model", "pointcache", "animation", "rig", "camera", "layout", + "blendScene"] label = "Collect Instance" def process(self, instance): From d1c3eb83cfe1d7bb485d88914b42cdc0640aba68 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 17:17:35 +0100 Subject: [PATCH 084/107] Remove todo --- openpype/hosts/blender/plugins/publish/collect_instance.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/blender/plugins/publish/collect_instance.py b/openpype/hosts/blender/plugins/publish/collect_instance.py index 3d4e634e9e..4685472213 100644 --- a/openpype/hosts/blender/plugins/publish/collect_instance.py +++ b/openpype/hosts/blender/plugins/publish/collect_instance.py @@ -25,7 +25,6 @@ class CollectBlenderInstanceData(pyblish.api.InstancePlugin): members.extend(instance_node.children) # Special case for animation instances, include armatures - # TODO: Does this still work as intended? if instance.data["family"] == "animation": for obj in instance_node.objects: if obj.type == 'EMPTY' and obj.get(AVALON_PROPERTY): From 78bcd7ce74a67299c3a389209210f200a22e3654 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 17:37:53 +0100 Subject: [PATCH 085/107] Pull window to front when menu entries are clicked; no more searching for minimized windows --- openpype/hosts/blender/api/ops.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/openpype/hosts/blender/api/ops.py b/openpype/hosts/blender/api/ops.py index 408e38e6cd..c1403d44b6 100644 --- a/openpype/hosts/blender/api/ops.py +++ b/openpype/hosts/blender/api/ops.py @@ -246,8 +246,24 @@ class LaunchQtApp(bpy.types.Operator): self.before_window_show() + def pull_to_front(window): + """Pull window forward to screen. + + If Window is minimized this will un-minimize, then it can be raised + and activated to the front. + """ + window.setWindowState( + (window.windowState() & ~QtCore.Qt.WindowMinimized) | + QtCore.Qt.WindowActive + ) + window.raise_() + window.activateWindow() + if isinstance(self._window, ModuleType): self._window.show() + pull_to_front(self.window) + + # Pull window to the front window = None if hasattr(self._window, "window"): window = self._window.window @@ -262,6 +278,7 @@ class LaunchQtApp(bpy.types.Operator): on_top_flags = origin_flags | QtCore.Qt.WindowStaysOnTopHint self._window.setWindowFlags(on_top_flags) self._window.show() + pull_to_front(self.window) # if on_top_flags != origin_flags: # self._window.setWindowFlags(origin_flags) From d42d57133b4981b6c9780ee0e2167daf3be42308 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 17:39:22 +0100 Subject: [PATCH 086/107] Fix typo --- openpype/hosts/blender/api/ops.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/blender/api/ops.py b/openpype/hosts/blender/api/ops.py index c1403d44b6..f4d96e563a 100644 --- a/openpype/hosts/blender/api/ops.py +++ b/openpype/hosts/blender/api/ops.py @@ -261,7 +261,7 @@ class LaunchQtApp(bpy.types.Operator): if isinstance(self._window, ModuleType): self._window.show() - pull_to_front(self.window) + pull_to_front(self._window) # Pull window to the front window = None @@ -278,7 +278,7 @@ class LaunchQtApp(bpy.types.Operator): on_top_flags = origin_flags | QtCore.Qt.WindowStaysOnTopHint self._window.setWindowFlags(on_top_flags) self._window.show() - pull_to_front(self.window) + pull_to_front(self._window) # if on_top_flags != origin_flags: # self._window.setWindowFlags(origin_flags) From 6398dbf1c46dd3dd921995886703a64146a0cad3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 17:49:13 +0100 Subject: [PATCH 087/107] Remove unused properties --- openpype/hosts/blender/api/pipeline.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openpype/hosts/blender/api/pipeline.py b/openpype/hosts/blender/api/pipeline.py index ee43108594..b386dd49d3 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -6,7 +6,7 @@ from typing import Callable, Dict, Iterator, List, Optional import bpy from . import lib -from . import ops, properties +from . import ops import pyblish.api @@ -181,7 +181,6 @@ def install(): if not IS_HEADLESS: ops.register() - properties.register() def uninstall(): @@ -196,7 +195,6 @@ def uninstall(): if not IS_HEADLESS: ops.unregister() - properties.unregister() def show_message(title, message): From 31f24f8cc2436eda1930c6affcd92d376d8b13cf Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 17:50:03 +0100 Subject: [PATCH 088/107] Remove unused properties lib --- openpype/hosts/blender/api/properties.py | 29 ------------------------ 1 file changed, 29 deletions(-) delete mode 100644 openpype/hosts/blender/api/properties.py diff --git a/openpype/hosts/blender/api/properties.py b/openpype/hosts/blender/api/properties.py deleted file mode 100644 index 44ca7582b9..0000000000 --- a/openpype/hosts/blender/api/properties.py +++ /dev/null @@ -1,29 +0,0 @@ -import bpy -from bpy.utils import register_classes_factory - - -class OpenpypeContext(bpy.types.PropertyGroup): - pass - - -classes = [] # [OpenpypeContext] - - -factory_register, factory_unregister = register_classes_factory(classes) - - -def register(): - """Register the properties.""" - factory_register() - - bpy.types.Scene.openpype_context = {} - # bpy.types.Scene.openpype_context = bpy.props.CollectionProperty( - # name="OpenPype Context", type=OpenpypeContext, options={"HIDDEN"} - # ) - - -def unregister(): - """Unregister the properties.""" - factory_unregister() - - del bpy.types.Scene.openpype_context From 45699444ef8d4e466d3fed4652d26b4d1c55033a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 18:03:08 +0100 Subject: [PATCH 089/107] Fix setting data - `current_instance` is not a `dict` --- .../hosts/blender/plugins/create/create_workfile.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/blender/plugins/create/create_workfile.py b/openpype/hosts/blender/plugins/create/create_workfile.py index ffd423ad74..28c21c5811 100644 --- a/openpype/hosts/blender/plugins/create/create_workfile.py +++ b/openpype/hosts/blender/plugins/create/create_workfile.py @@ -74,13 +74,9 @@ class CreateWorkfile(BaseCreator, AutoCreator): task_name, task_name, asset_doc, project_name, host_name ) - current_instance.update( - { - "asset": asset_name, - "task": task_name, - "subset": subset_name, - } - ) + current_instance["asset"] = asset_name + current_instance["task"] = task_name + current_instance["subset"] = subset_name def collect_instances(self): From 478afb7f4899189dab0e3a99b0c83dd8e01f7d67 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 18:04:57 +0100 Subject: [PATCH 090/107] Cosmetics; match code of other hosts more --- openpype/hosts/blender/plugins/create/create_workfile.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/blender/plugins/create/create_workfile.py b/openpype/hosts/blender/plugins/create/create_workfile.py index 28c21c5811..9245434766 100644 --- a/openpype/hosts/blender/plugins/create/create_workfile.py +++ b/openpype/hosts/blender/plugins/create/create_workfile.py @@ -73,7 +73,6 @@ class CreateWorkfile(BaseCreator, AutoCreator): subset_name = self.get_subset_name( task_name, task_name, asset_doc, project_name, host_name ) - current_instance["asset"] = asset_name current_instance["task"] = task_name current_instance["subset"] = subset_name From d148671d02563ce11413e7cb4fd010f9fb3e41f1 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 20:56:00 +0100 Subject: [PATCH 091/107] Fix finding ARMATURE objects in asset group Collection --- .../plugins/publish/extract_fbx_animation.py | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py b/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py index 6fbcdc63c3..712fbb2d14 100644 --- a/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py @@ -33,8 +33,27 @@ class ExtractAnimationFBX( asset_group = instance.data["transientData"]["instance_node"] - armature = [ - obj for obj in asset_group.children if obj.type == 'ARMATURE'][0] + # Get objects in this collection (but not in children collections) + # and for those objects include the children hierarchy + # TODO: Would it make more sense for the Collect Instance collector + # to also always retrieve all the children? + objects = set(asset_group.objects) + for obj in list(objects): + objects.update(obj.children_recursive) + + # Find all armatures among the objects, assume to find only one + armatures = [obj for obj in objects if obj.type == "ARMATURE"] + if not armatures: + raise RuntimeError( + f"Unable to find ARMATURE in collection: " + f"{asset_group.name}" + ) + elif len(armatures) > 1: + self.log.warning( + "Found more than one ARMATURE, using " + f"only first of: {armatures}" + ) + armature = armatures[0] object_action_pairs = [] original_actions = [] @@ -43,9 +62,6 @@ class ExtractAnimationFBX( ending_frames = [] # For each armature, we make a copy of the current action - curr_action = None - copy_action = None - if armature.animation_data and armature.animation_data.action: curr_action = armature.animation_data.action copy_action = curr_action.copy() @@ -55,7 +71,10 @@ class ExtractAnimationFBX( starting_frames.append(curr_frame_range[0]) ending_frames.append(curr_frame_range[1]) else: - self.log.info("Object has no animation.") + self.log.info( + f"Armature '{armature.name}' has no animation, " + f"skipping FBX animation extraction for {instance}." + ) return asset_group_name = asset_group.name From 91e230a321d6946b612fb9476f8dbe56e179c14b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 15 Nov 2023 10:09:37 +0100 Subject: [PATCH 092/107] Enhancement: Some publish logs cosmetics (#5917) * Write logs in sorted order for better logs * Typo in log message * More descriptive log * Skip logging profile since `filter_profiles` already logs "Profile selected: {profile}" * Fix grammar * Fix grammar * Improve logged information --- .../plugins/load/load_reference_image.py | 2 +- .../action_tranfer_hierarchical_values.py | 2 +- .../event_next_task_update.py | 2 +- .../event_sync_to_avalon.py | 2 +- .../action_component_open.py | 2 +- .../action_delete_asset.py | 2 +- .../event_handlers_user/action_delivery.py | 2 +- .../event_handlers_user/action_job_killer.py | 2 +- openpype/plugins/publish/cleanup_explicit.py | 26 +++++++++++-------- .../plugins/publish/collect_rendered_files.py | 5 ++-- openpype/plugins/publish/extract_burnin.py | 4 +-- .../publish/extract_color_transcode.py | 1 - openpype/plugins/publish/extract_review.py | 2 +- .../plugins/publish/integrate_thumbnail.py | 2 +- .../publish/integrate_thumbnail_ayon.py | 2 +- .../settings/entities/dict_conditional.py | 2 +- .../entities/dict_immutable_keys_entity.py | 2 +- .../entities/dict_mutable_keys_entity.py | 2 +- openpype/settings/entities/item_entities.py | 2 +- openpype/settings/entities/list_entity.py | 2 +- openpype/settings/entities/root_entities.py | 2 +- 21 files changed, 36 insertions(+), 34 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/load/load_reference_image.py b/openpype/hosts/tvpaint/plugins/load/load_reference_image.py index 3707ef97aa..53061c6885 100644 --- a/openpype/hosts/tvpaint/plugins/load/load_reference_image.py +++ b/openpype/hosts/tvpaint/plugins/load/load_reference_image.py @@ -190,7 +190,7 @@ class LoadImage(plugin.Loader): if pop_idx is None: self.log.warning( - "Didn't found container in workfile containers. {}".format( + "Didn't find container in workfile containers. {}".format( container ) ) diff --git a/openpype/modules/ftrack/event_handlers_server/action_tranfer_hierarchical_values.py b/openpype/modules/ftrack/event_handlers_server/action_tranfer_hierarchical_values.py index f6899843a3..1d73318f6e 100644 --- a/openpype/modules/ftrack/event_handlers_server/action_tranfer_hierarchical_values.py +++ b/openpype/modules/ftrack/event_handlers_server/action_tranfer_hierarchical_values.py @@ -66,7 +66,7 @@ class TransferHierarchicalValues(ServerAction): "items": [{ "type": "label", "value": ( - "Didn't found custom attributes" + "Didn't find custom attributes" " that can be transferred." ) }] diff --git a/openpype/modules/ftrack/event_handlers_server/event_next_task_update.py b/openpype/modules/ftrack/event_handlers_server/event_next_task_update.py index 07a8ff433e..8632f038b8 100644 --- a/openpype/modules/ftrack/event_handlers_server/event_next_task_update.py +++ b/openpype/modules/ftrack/event_handlers_server/event_next_task_update.py @@ -257,7 +257,7 @@ class NextTaskUpdate(BaseEvent): new_task_name = mapping.get(old_status_name) if not new_task_name: self.log.debug( - "Didn't found mapping for status \"{}\".".format( + "Didn't find mapping for status \"{}\".".format( task_status["name"] ) ) diff --git a/openpype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py b/openpype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py index 0aa0b9f9f5..d4dc53b655 100644 --- a/openpype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py +++ b/openpype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py @@ -387,7 +387,7 @@ class SyncToAvalonEvent(BaseEvent): if not data: # TODO logging self.log.warning( - "Didn't found entity by key/value \"{}\" / \"{}\"".format( + "Didn't find entity by key/value \"{}\" / \"{}\"".format( key, value ) ) diff --git a/openpype/modules/ftrack/event_handlers_user/action_component_open.py b/openpype/modules/ftrack/event_handlers_user/action_component_open.py index c731713c10..0efade9d8f 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_component_open.py +++ b/openpype/modules/ftrack/event_handlers_user/action_component_open.py @@ -51,7 +51,7 @@ class ComponentOpen(BaseAction): else: return { 'success': False, - 'message': "Didn't found file: " + fpath + 'message': "Didn't find file: " + fpath } return { diff --git a/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py b/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py index 72a5efbcfe..e1df8e1537 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py +++ b/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py @@ -169,7 +169,7 @@ class DeleteAssetSubset(BaseAction): return { "success": True, "message": ( - "Didn't found entities in avalon." + "Didn't find entities in avalon." " You can use Ftrack's Delete button for the selection." ) } diff --git a/openpype/modules/ftrack/event_handlers_user/action_delivery.py b/openpype/modules/ftrack/event_handlers_user/action_delivery.py index 559de3a24d..c198389b98 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_delivery.py +++ b/openpype/modules/ftrack/event_handlers_user/action_delivery.py @@ -61,7 +61,7 @@ class Delivery(BaseAction): return { "success": False, "message": ( - "Didn't found project \"{}\" in avalon." + "Didn't find project \"{}\" in avalon." ).format(project_name) } diff --git a/openpype/modules/ftrack/event_handlers_user/action_job_killer.py b/openpype/modules/ftrack/event_handlers_user/action_job_killer.py index dd68c75f84..250670f016 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_job_killer.py +++ b/openpype/modules/ftrack/event_handlers_user/action_job_killer.py @@ -29,7 +29,7 @@ class JobKiller(BaseAction): if not jobs: return { "success": True, - "message": "Didn't found any running jobs" + "message": "Didn't find any running jobs" } # Collect user ids from jobs diff --git a/openpype/plugins/publish/cleanup_explicit.py b/openpype/plugins/publish/cleanup_explicit.py index 983c9223c6..cc6b99e8f8 100644 --- a/openpype/plugins/publish/cleanup_explicit.py +++ b/openpype/plugins/publish/cleanup_explicit.py @@ -58,21 +58,21 @@ class ExplicitCleanUp(pyblish.api.ContextPlugin): # Store failed paths with exception failed = [] # Store removed filepaths for logging - succeded_files = set() + succeeded_files = set() # Remove file by file for filepath in filepaths: try: os.remove(filepath) - succeded_files.add(filepath) + succeeded_files.add(filepath) except Exception as exc: failed.append((filepath, exc)) - if succeded_files: + if succeeded_files: self.log.info( - "Removed files:\n{}".format("\n".join(succeded_files)) + "Removed files:\n{}".format("\n".join(sorted(succeeded_files))) ) - # Delete folders with it's content + # Delete folders with its content succeeded = set() for dirpath in dirpaths: # Check if directory still exists @@ -87,17 +87,21 @@ class ExplicitCleanUp(pyblish.api.ContextPlugin): if succeeded: self.log.info( - "Removed directories:\n{}".format("\n".join(succeeded)) + "Removed directories:\n{}".format( + "\n".join(sorted(succeeded)) + ) ) - # Prepare lines for report of failed removements + # Prepare lines for report of failed removals lines = [] for filepath, exc in failed: lines.append("{}: {}".format(filepath, str(exc))) if lines: self.log.warning( - "Failed to remove filepaths:\n{}".format("\n".join(lines)) + "Failed to remove filepaths:\n{}".format( + "\n".join(sorted(lines)) + ) ) def _remove_empty_dirs(self, empty_dirpaths): @@ -134,8 +138,8 @@ class ExplicitCleanUp(pyblish.api.ContextPlugin): if to_skip_dirpaths: self.log.debug( - "Skipped directories because contain files:\n{}".format( - "\n".join(to_skip_dirpaths) + "Skipped directories because they contain files:\n{}".format( + "\n".join(sorted(to_skip_dirpaths)) ) ) @@ -147,6 +151,6 @@ class ExplicitCleanUp(pyblish.api.ContextPlugin): if to_delete_dirpaths: self.log.debug( "Deleted empty directories:\n{}".format( - "\n".join(to_delete_dirpaths) + "\n".join(sorted(to_delete_dirpaths)) ) ) diff --git a/openpype/plugins/publish/collect_rendered_files.py b/openpype/plugins/publish/collect_rendered_files.py index a249b3acda..6160b4f5c8 100644 --- a/openpype/plugins/publish/collect_rendered_files.py +++ b/openpype/plugins/publish/collect_rendered_files.py @@ -54,6 +54,8 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): staging_dir = data_object.get("stagingDir") if staging_dir: data_object["stagingDir"] = anatomy.fill_root(staging_dir) + self.log.debug("Filling stagingDir with root to: %s", + data_object["stagingDir"]) def _process_path(self, data, anatomy): """Process data of a single JSON publish metadata file. @@ -108,7 +110,6 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): instance = self._context.create_instance( instance_data.get("subset") ) - self.log.debug("Filling stagingDir...") self._fill_staging_dir(instance_data, anatomy) instance.data.update(instance_data) @@ -161,7 +162,7 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): anatomy.project_name )) - self.log.debug("anatomy: {}".format(anatomy.roots)) + self.log.debug("Anatomy roots: {}".format(anatomy.roots)) try: session_is_set = False for path in paths: diff --git a/openpype/plugins/publish/extract_burnin.py b/openpype/plugins/publish/extract_burnin.py index dc8aab6ce4..9a978ed286 100644 --- a/openpype/plugins/publish/extract_burnin.py +++ b/openpype/plugins/publish/extract_burnin.py @@ -171,8 +171,6 @@ class ExtractBurnin(publish.Extractor): ).format(host_name, family, task_name, task_type, subset)) return - self.log.debug("profile: {}".format(profile)) - # Pre-filter burnin definitions by instance families burnin_defs = self.filter_burnins_defs(profile, instance) if not burnin_defs: @@ -450,7 +448,7 @@ class ExtractBurnin(publish.Extractor): filling burnin strings. `temp_data` are for repre pre-process preparation. """ - self.log.debug("Prepring basic data for burnins") + self.log.debug("Preparing basic data for burnins") context = instance.context version = instance.data.get("version") diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index dbf1b6c8a6..faacb7af2e 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -326,7 +326,6 @@ class ExtractOIIOTranscode(publish.Extractor): " | Task type \"{}\" | Subset \"{}\" " ).format(host_name, family, task_name, task_type, subset)) - self.log.debug("profile: {}".format(profile)) return profile def _repre_is_valid(self, repre): diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index db8a030dfa..cd0f78530a 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -143,7 +143,7 @@ class ExtractReview(pyblish.api.InstancePlugin): custom_tags = repre.get("custom_tags") if "review" not in tags: self.log.debug(( - "Repre: {} - Didn't found \"review\" in tags. Skipping" + "Repre: {} - Didn't find \"review\" in tags. Skipping" ).format(repre_name)) continue diff --git a/openpype/plugins/publish/integrate_thumbnail.py b/openpype/plugins/publish/integrate_thumbnail.py index 0c12255d38..b154940469 100644 --- a/openpype/plugins/publish/integrate_thumbnail.py +++ b/openpype/plugins/publish/integrate_thumbnail.py @@ -200,7 +200,7 @@ class IntegrateThumbnails(pyblish.api.ContextPlugin): if thumb_repre_doc is None: self.log.debug( - "There is not representation with name \"thumbnail\"" + "There is no representation with name \"thumbnail\"" ) return None diff --git a/openpype/plugins/publish/integrate_thumbnail_ayon.py b/openpype/plugins/publish/integrate_thumbnail_ayon.py index cf05327ce8..f9b48eebec 100644 --- a/openpype/plugins/publish/integrate_thumbnail_ayon.py +++ b/openpype/plugins/publish/integrate_thumbnail_ayon.py @@ -137,7 +137,7 @@ class IntegrateThumbnailsAYON(pyblish.api.ContextPlugin): if thumb_repre_doc is None: self.log.debug( - "There is not representation with name \"thumbnail\"" + "There is no representation with name \"thumbnail\"" ) return None diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 88d2dc8296..f26d86e6df 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -352,7 +352,7 @@ class DictConditionalEntity(ItemEntity): break if result_key is None: - raise ValueError("Didn't found child {}".format(child_obj)) + raise ValueError("Didn't find child {}".format(child_obj)) return "/".join([self.path, result_key]) diff --git a/openpype/settings/entities/dict_immutable_keys_entity.py b/openpype/settings/entities/dict_immutable_keys_entity.py index 0209681e95..a25c22aa19 100644 --- a/openpype/settings/entities/dict_immutable_keys_entity.py +++ b/openpype/settings/entities/dict_immutable_keys_entity.py @@ -232,7 +232,7 @@ class DictImmutableKeysEntity(ItemEntity): break if result_key is None: - raise ValueError("Didn't found child {}".format(child_obj)) + raise ValueError("Didn't find child {}".format(child_obj)) return "/".join([self.path, result_key]) diff --git a/openpype/settings/entities/dict_mutable_keys_entity.py b/openpype/settings/entities/dict_mutable_keys_entity.py index e6d332b9ad..c11a7cf059 100644 --- a/openpype/settings/entities/dict_mutable_keys_entity.py +++ b/openpype/settings/entities/dict_mutable_keys_entity.py @@ -284,7 +284,7 @@ class DictMutableKeysEntity(EndpointEntity): break if result_key is None: - raise ValueError("Didn't found child {}".format(child_obj)) + raise ValueError("Didn't find child {}".format(child_obj)) return "/".join([self.path, result_key]) diff --git a/openpype/settings/entities/item_entities.py b/openpype/settings/entities/item_entities.py index 3b756e4ede..c888cf3b78 100644 --- a/openpype/settings/entities/item_entities.py +++ b/openpype/settings/entities/item_entities.py @@ -295,7 +295,7 @@ class ListStrictEntity(ItemEntity): break if result_idx is None: - raise ValueError("Didn't found child {}".format(child_obj)) + raise ValueError("Didn't find child {}".format(child_obj)) return "/".join([self.path, str(result_idx)]) diff --git a/openpype/settings/entities/list_entity.py b/openpype/settings/entities/list_entity.py index 5d6a64b3ea..d9a18e0177 100644 --- a/openpype/settings/entities/list_entity.py +++ b/openpype/settings/entities/list_entity.py @@ -258,7 +258,7 @@ class ListEntity(EndpointEntity): break if result_idx is None: - raise ValueError("Didn't found child {}".format(child_obj)) + raise ValueError("Didn't find child {}".format(child_obj)) return "/".join([self.path, str(result_idx)]) diff --git a/openpype/settings/entities/root_entities.py b/openpype/settings/entities/root_entities.py index f2e24fb522..bd617c6b7c 100644 --- a/openpype/settings/entities/root_entities.py +++ b/openpype/settings/entities/root_entities.py @@ -270,7 +270,7 @@ class RootEntity(BaseItemEntity): for key, _child_entity in self.non_gui_children.items(): if _child_entity is child_entity: return key - raise ValueError("Didn't found child {}".format(child_entity)) + raise ValueError("Didn't find child {}".format(child_entity)) @property def value(self): From 4d48a6981bb77c93c7f99e386bb6a82ab4603239 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 15 Nov 2023 11:25:47 +0100 Subject: [PATCH 093/107] Bugfix: Ayon Deadline env vars + error message on no executable found (#5815) * Fix ingesting env vars correctly for `DeadlinePlugin.RunProcess` * Fix error message reporting `;` between each character of the string * Cosmetics * Use `SetEnvironmentVariable` instead of `SetProcessEnvironmentVariable` because it's a Simple Plugin See: https://docs.thinkboxsoftware.com/products/deadline/10.1/1_User%20Manual/manual/environment.html#job-rendering --- .../deadline/repository/custom/plugins/Ayon/Ayon.py | 12 ++++++------ .../repository/custom/plugins/GlobalJobPreLoad.py | 3 +++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/openpype/modules/deadline/repository/custom/plugins/Ayon/Ayon.py b/openpype/modules/deadline/repository/custom/plugins/Ayon/Ayon.py index 2c55e7c951..a1f752605d 100644 --- a/openpype/modules/deadline/repository/custom/plugins/Ayon/Ayon.py +++ b/openpype/modules/deadline/repository/custom/plugins/Ayon/Ayon.py @@ -85,7 +85,7 @@ class AyonDeadlinePlugin(DeadlinePlugin): } for env, val in environment.items(): - self.SetProcessEnvironmentVariable(env, val) + self.SetEnvironmentVariable(env, val) exe_list = self.GetConfigEntry("AyonExecutable") # clean '\ ' for MacOS pasting @@ -101,11 +101,11 @@ class AyonDeadlinePlugin(DeadlinePlugin): if exe == "": self.FailRender( - "Ayon executable was not found " + - "in the semicolon separated list " + - "\"" + ";".join(exe_list) + "\". " + - "The path to the render executable can be configured " + - "from the Plugin Configuration in the Deadline Monitor.") + "Ayon executable was not found in the semicolon separated " + "list: \"{}\". The path to the render executable can be " + "configured from the Plugin Configuration in the Deadline " + "Monitor.".format(exe_list) + ) return exe def RenderArgument(self): diff --git a/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py b/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py index e9b81369ca..642608f991 100644 --- a/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py +++ b/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py @@ -495,7 +495,10 @@ def inject_ayon_environment(deadlinePlugin): "AYON_BUNDLE_NAME": ayon_bundle_name, } for env, val in environment.items(): + # Add the env var for the Render Plugin that is about to render deadlinePlugin.SetEnvironmentVariable(env, val) + # Add the env var for current calls to `DeadlinePlugin.RunProcess` + deadlinePlugin.SetProcessEnvironmentVariable(env, val) args_str = subprocess.list2cmdline(args) print(">>> Executing: {} {}".format(exe, args_str)) From 480c37533b8ce0e542e7b052ae28ff4d7a775acb Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 15 Nov 2023 12:43:19 +0100 Subject: [PATCH 094/107] Fix extract animation - `asset_group` is a Collection and can't be selected, and thus not exported from so we now take the 'root' node in that collection --- .../plugins/publish/extract_fbx_animation.py | 49 +++++++++++++++++-- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py b/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py index 712fbb2d14..a705345edb 100644 --- a/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py @@ -10,6 +10,37 @@ from openpype.hosts.blender.api import plugin from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY +def get_all_parents(obj): + """Get all recursive parents of object""" + result = [] + while True: + obj = obj.parent + if not obj: + break + result.append(obj) + return result + + +def get_highest_root(objects): + # Get the highest object that is also in the collection + included_objects = {obj.name_full for obj in objects} + num_parents_to_obj = {} + for obj in objects: + if isinstance(obj, bpy.types.Object): + parents = get_all_parents(obj) + # included parents + parents = [parent for parent in parents if + parent.name_full in included_objects] + if not parents: + # A node without parents must be a highest root + return obj + + num_parents_to_obj.setdefault(len(parents), obj) + + minimum_parent = min(num_parents_to_obj) + return num_parents_to_obj[minimum_parent] + + class ExtractAnimationFBX( publish.Extractor, publish.OptionalPyblishPluginMixin, @@ -38,6 +69,11 @@ class ExtractAnimationFBX( # TODO: Would it make more sense for the Collect Instance collector # to also always retrieve all the children? objects = set(asset_group.objects) + + # From the direct children of the collection find the 'root' node + # that we want to export - it is the 'highest' node in a hierarchy + root = get_highest_root(objects) + for obj in list(objects): objects.update(obj.children_recursive) @@ -78,8 +114,13 @@ class ExtractAnimationFBX( return asset_group_name = asset_group.name - asset_group.name = asset_group.get(AVALON_PROPERTY).get("asset_name") + asset_name = asset_group.get(AVALON_PROPERTY).get("asset_name") + if asset_name: + # Rename for the export; this data is only present when loaded + # from a JSON Layout (layout family) + asset_group.name = asset_name + # Remove : from the armature name for the export armature_name = armature.name original_name = armature_name.split(':')[1] armature.name = original_name @@ -102,13 +143,13 @@ class ExtractAnimationFBX( for obj in bpy.data.objects: obj.select_set(False) - asset_group.select_set(True) + root.select_set(True) armature.select_set(True) fbx_filename = f"{instance.name}_{armature.name}.fbx" filepath = os.path.join(stagingdir, fbx_filename) override = plugin.create_blender_context( - active=asset_group, selected=[asset_group, armature]) + active=root, selected=[root, armature]) bpy.ops.export_scene.fbx( override, filepath=filepath, @@ -122,7 +163,7 @@ class ExtractAnimationFBX( ) armature.name = armature_name asset_group.name = asset_group_name - asset_group.select_set(False) + root.select_set(True) armature.select_set(False) # We delete the baked action and set the original one back From dd54866fe0925d606cb10be0b70d42e50cae2ad7 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 15 Nov 2023 13:08:59 +0100 Subject: [PATCH 095/107] Rename instance node if asset or subset changed to match with new name --- openpype/hosts/blender/api/plugin.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 5a0026d862..7ac12b5549 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -282,11 +282,26 @@ class BaseCreator(Creator): Args: update_list(List[UpdateData]): Changed instances and their changes, as a list of tuples.""" - for created_instance, _changes in update_list: + for created_instance, changes in update_list: data = created_instance.data_to_store() node = created_instance.transient_data["instance_node"] - if node: - imprint(node, data) + if not node: + # We can't update if we don't know the node + self.log.error( + f"Unable to update instance {created_instance} " + f"without instance node." + ) + return + + # Rename the instance node in the scene if subset or asset changed + if ( + "subset" in changes.changed_keys + or "asset" in changes.changed_keys + ): + name = asset_name(asset=data["asset"], subset=data["subset"]) + node.name = name + + imprint(node, data) def remove_instances(self, instances: List[CreatedInstance]): From 5d10f7f2dd0e7668a14ff7630ddf97b85d42e0cb Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 15 Nov 2023 13:15:50 +0100 Subject: [PATCH 096/107] Remove global `RENDER_ATTRS`, refactor to a `RenderSettings.get_padding_attr` method (#5801) - Partial cleanup extracted from #3880 --- openpype/hosts/maya/api/lib.py | 13 ------------- openpype/hosts/maya/api/lib_rendersettings.py | 8 ++++++++ .../plugins/publish/validate_rendersettings.py | 17 +++++++---------- .../plugins/publish/submit_maya_muster.py | 5 ++--- 4 files changed, 17 insertions(+), 26 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 7c49c837e9..2ecaf87fce 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -62,19 +62,6 @@ SHAPE_ATTRS = {"castsShadows", "doubleSided", "opposite"} -RENDER_ATTRS = {"vray": { - "node": "vraySettings", - "prefix": "fileNamePrefix", - "padding": "fileNamePadding", - "ext": "imageFormatStr" -}, - "default": { - "node": "defaultRenderGlobals", - "prefix": "imageFilePrefix", - "padding": "extensionPadding" -} -} - DEFAULT_MATRIX = [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, diff --git a/openpype/hosts/maya/api/lib_rendersettings.py b/openpype/hosts/maya/api/lib_rendersettings.py index 20264c2cdf..8b57c2e481 100644 --- a/openpype/hosts/maya/api/lib_rendersettings.py +++ b/openpype/hosts/maya/api/lib_rendersettings.py @@ -33,6 +33,14 @@ class RenderSettings(object): def get_image_prefix_attr(cls, renderer): return cls._image_prefix_nodes[renderer] + @staticmethod + def get_padding_attr(renderer): + """Return attribute for renderer that defines frame padding amount""" + if renderer == "vray": + return "vraySettings.fileNamePadding" + else: + return "defaultRenderGlobals.extensionPadding" + def __init__(self, project_settings=None): if not project_settings: project_settings = get_project_settings( diff --git a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py index dccb4ade78..3409b4ec91 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py @@ -12,6 +12,7 @@ from openpype.pipeline.publish import ( PublishValidationError, ) from openpype.hosts.maya.api import lib +from openpype.hosts.maya.api.lib_rendersettings import RenderSettings def convert_to_int_or_float(string_value): @@ -129,13 +130,13 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): layer = instance.data['renderlayer'] cameras = instance.data.get("cameras", []) - # Get the node attributes for current renderer - attrs = lib.RENDER_ATTRS.get(renderer, lib.RENDER_ATTRS['default']) # Prefix attribute can return None when a value was never set prefix = lib.get_attr_in_layer(cls.ImagePrefixes[renderer], layer=layer) or "" - padding = lib.get_attr_in_layer("{node}.{padding}".format(**attrs), - layer=layer) + padding = lib.get_attr_in_layer( + attr=RenderSettings.get_padding_attr(renderer), + layer=layer + ) anim_override = lib.get_attr_in_layer("defaultRenderGlobals.animation", layer=layer) @@ -372,8 +373,6 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): lib.set_attribute(data["attribute"], data["values"][0], node) with lib.renderlayer(layer_node): - default = lib.RENDER_ATTRS['default'] - render_attrs = lib.RENDER_ATTRS.get(renderer, default) # Repair animation must be enabled cmds.setAttr("defaultRenderGlobals.animation", True) @@ -391,15 +390,13 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): default_prefix = default_prefix.replace(variant, "") if renderer != "renderman": - node = render_attrs["node"] - prefix_attr = render_attrs["prefix"] - + prefix_attr = RenderSettings.get_image_prefix_attr(renderer) fname_prefix = default_prefix cmds.setAttr("{}.{}".format(node, prefix_attr), fname_prefix, type="string") # Repair padding - padding_attr = render_attrs["padding"] + padding_attr = RenderSettings.get_padding_attr(renderer) cmds.setAttr("{}.{}".format(node, padding_attr), cls.DEFAULT_PADDING) else: diff --git a/openpype/modules/muster/plugins/publish/submit_maya_muster.py b/openpype/modules/muster/plugins/publish/submit_maya_muster.py index 5c95744876..f6b3bfbbfd 100644 --- a/openpype/modules/muster/plugins/publish/submit_maya_muster.py +++ b/openpype/modules/muster/plugins/publish/submit_maya_muster.py @@ -10,6 +10,7 @@ from maya import cmds import pyblish.api from openpype.lib import requests_post from openpype.hosts.maya.api import lib +from openpype.hosts.maya.api.lib_rendersettings import RenderSettings from openpype.pipeline import legacy_io from openpype.settings import get_system_settings @@ -68,10 +69,8 @@ def get_renderer_variables(renderlayer=None): """ renderer = lib.get_renderer(renderlayer or lib.get_current_renderlayer()) - render_attrs = lib.RENDER_ATTRS.get(renderer, lib.RENDER_ATTRS["default"]) - padding = cmds.getAttr("{}.{}".format(render_attrs["node"], - render_attrs["padding"])) + padding = cmds.getAttr(RenderSettings.get_padding_attr(renderer)) filename_0 = cmds.renderSettings(fullPath=True, firstImageName=True)[0] From 025c114de854688bbf99e60a3d8fe3843c05025c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 15 Nov 2023 16:40:46 +0100 Subject: [PATCH 097/107] Fix repair --- .../hosts/blender/plugins/publish/validate_deadline_publish.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/blender/plugins/publish/validate_deadline_publish.py b/openpype/hosts/blender/plugins/publish/validate_deadline_publish.py index 58047d7e23..d8826adc9c 100644 --- a/openpype/hosts/blender/plugins/publish/validate_deadline_publish.py +++ b/openpype/hosts/blender/plugins/publish/validate_deadline_publish.py @@ -41,7 +41,7 @@ class ValidateDeadlinePublish(pyblish.api.InstancePlugin, @classmethod def repair(cls, instance): - container = bpy.data.collections[str(instance)] + container = instance.data["transientData"]["instance_node"] prepare_rendering(container) bpy.ops.wm.save_as_mainfile(filepath=bpy.data.filepath) cls.log.debug("Reset the render output folder...") From 23291ac53c1fcc00ed603ecdd3d23897b1c844e0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 15 Nov 2023 18:35:54 +0100 Subject: [PATCH 098/107] AYON workfiles tools: Revisit workfiles tool (#5897) * implemented base hierarchy expected selection model * use existing models and widgets in ayon workfiles tool * move private method under public method * added more methods to cache * reset models during controller reset * create workfile info all the time --- openpype/tools/ayon_utils/models/__init__.py | 3 + openpype/tools/ayon_utils/models/cache.py | 49 +- openpype/tools/ayon_utils/models/selection.py | 179 ++++++++ .../ayon_utils/widgets/projects_widget.py | 22 +- openpype/tools/ayon_workfiles/abstract.py | 48 +- openpype/tools/ayon_workfiles/control.py | 224 ++++++---- .../tools/ayon_workfiles/models/__init__.py | 2 - .../tools/ayon_workfiles/models/hierarchy.py | 236 ---------- .../tools/ayon_workfiles/models/selection.py | 23 +- .../tools/ayon_workfiles/models/workfiles.py | 24 +- .../ayon_workfiles/widgets/files_widget.py | 2 +- .../widgets/files_widget_published.py | 34 +- .../widgets/files_widget_workarea.py | 22 +- .../ayon_workfiles/widgets/folders_widget.py | 324 -------------- .../ayon_workfiles/widgets/side_panel.py | 2 +- .../ayon_workfiles/widgets/tasks_widget.py | 420 ------------------ .../tools/ayon_workfiles/widgets/window.py | 57 +-- 17 files changed, 501 insertions(+), 1170 deletions(-) create mode 100644 openpype/tools/ayon_utils/models/selection.py delete mode 100644 openpype/tools/ayon_workfiles/models/hierarchy.py delete mode 100644 openpype/tools/ayon_workfiles/widgets/folders_widget.py delete mode 100644 openpype/tools/ayon_workfiles/widgets/tasks_widget.py diff --git a/openpype/tools/ayon_utils/models/__init__.py b/openpype/tools/ayon_utils/models/__init__.py index 69722b5e21..8895515b1a 100644 --- a/openpype/tools/ayon_utils/models/__init__.py +++ b/openpype/tools/ayon_utils/models/__init__.py @@ -13,6 +13,7 @@ from .hierarchy import ( HIERARCHY_MODEL_SENDER, ) from .thumbnails import ThumbnailsModel +from .selection import HierarchyExpectedSelection __all__ = ( @@ -29,4 +30,6 @@ __all__ = ( "HIERARCHY_MODEL_SENDER", "ThumbnailsModel", + + "HierarchyExpectedSelection", ) diff --git a/openpype/tools/ayon_utils/models/cache.py b/openpype/tools/ayon_utils/models/cache.py index 44b97e930d..221a14160c 100644 --- a/openpype/tools/ayon_utils/models/cache.py +++ b/openpype/tools/ayon_utils/models/cache.py @@ -81,11 +81,11 @@ class NestedCacheItem: """Helper for cached items stored in nested structure. Example: - >>> cache = NestedCacheItem(levels=2) + >>> cache = NestedCacheItem(levels=2, default_factory=lambda: 0) >>> cache["a"]["b"].is_valid False >>> cache["a"]["b"].get_data() - None + 0 >>> cache["a"]["b"] = 1 >>> cache["a"]["b"].is_valid True @@ -167,8 +167,51 @@ class NestedCacheItem: return self[key] + def cached_count(self): + """Amount of cached items. + + Returns: + int: Amount of cached items. + """ + + return len(self._data_by_key) + + def clear_key(self, key): + """Clear cached item by key. + + Args: + key (str): Key of the cache item. + """ + + self._data_by_key.pop(key, None) + + def clear_invalid(self): + """Clear all invalid cache items. + + Note: + To clear all cache items use 'reset'. + """ + + changed = {} + children_are_nested = self._levels > 1 + for key, cache in tuple(self._data_by_key.items()): + if children_are_nested: + output = cache.clear_invalid() + if output: + changed[key] = output + if not cache.cached_count(): + self._data_by_key.pop(key) + elif not cache.is_valid: + changed[key] = cache.get_data() + self._data_by_key.pop(key) + return changed + def reset(self): - """Reset cache.""" + """Reset cache. + + Note: + To clear only invalid cache items use 'clear_invalid'. + """ self._data_by_key = {} diff --git a/openpype/tools/ayon_utils/models/selection.py b/openpype/tools/ayon_utils/models/selection.py new file mode 100644 index 0000000000..0ff239882b --- /dev/null +++ b/openpype/tools/ayon_utils/models/selection.py @@ -0,0 +1,179 @@ +class _ExampleController: + def emit_event(self, topic, data, **kwargs): + pass + + +class HierarchyExpectedSelection: + """Base skeleton of expected selection model. + + Expected selection model holds information about which entities should be + selected. The order of selection is very important as change of project + will affect what folders are available in folders UI and so on. Because + of that should expected selection model know what is current entity + to select. + + If any of 'handle_project', 'handle_folder' or 'handle_task' is set to + 'False' expected selection data won't contain information about the + entity type at all. Also if project is not handled then it is not + necessary to call 'expected_project_selected'. Same goes for folder and + task. + + Model is triggering event with 'expected_selection_changed' topic and + data > data structure is matching 'get_expected_selection_data' method. + + Questions: + Require '_ExampleController' as abstraction? + + Args: + controller (Any): Controller object. ('_ExampleController') + handle_project (bool): Project can be considered as can have expected + selection. + handle_folder (bool): Folder can be considered as can have expected + selection. + handle_task (bool): Task can be considered as can have expected + selection. + """ + + def __init__( + self, + controller, + handle_project=True, + handle_folder=True, + handle_task=True + ): + self._project_name = None + self._folder_id = None + self._task_name = None + + self._project_selected = True + self._folder_selected = True + self._task_selected = True + + self._controller = controller + + self._handle_project = handle_project + self._handle_folder = handle_folder + self._handle_task = handle_task + + def set_expected_selection( + self, + project_name=None, + folder_id=None, + task_name=None + ): + """Sets expected selection. + + Args: + project_name (Optional[str]): Project name. + folder_id (Optional[str]): Folder id. + task_name (Optional[str]): Task name. + """ + + self._project_name = project_name + self._folder_id = folder_id + self._task_name = task_name + + self._project_selected = not self._handle_project + self._folder_selected = not self._handle_folder + self._task_selected = not self._handle_task + self._emit_change() + + def get_expected_selection_data(self): + project_current = False + folder_current = False + task_current = False + if not self._project_selected: + project_current = True + elif not self._folder_selected: + folder_current = True + elif not self._task_selected: + task_current = True + data = {} + if self._handle_project: + data["project"] = { + "name": self._project_name, + "current": project_current, + "selected": self._project_selected, + } + if self._handle_folder: + data["folder"] = { + "id": self._folder_id, + "current": folder_current, + "selected": self._folder_selected, + } + if self._handle_task: + data["task"] = { + "name": self._task_name, + "current": task_current, + "selected": self._task_selected, + } + + return data + + def is_expected_project_selected(self, project_name): + if not self._handle_project: + return True + return project_name == self._project_name and self._project_selected + + def is_expected_folder_selected(self, folder_id): + if not self._handle_folder: + return True + return folder_id == self._folder_id and self._folder_selected + + def expected_project_selected(self, project_name): + """UI selected requested project. + + Other entity types can be requested for selection. + + Args: + project_name (str): Name of project. + """ + + if project_name != self._project_name: + return False + self._project_selected = True + self._emit_change() + return True + + def expected_folder_selected(self, folder_id): + """UI selected requested folder. + + Other entity types can be requested for selection. + + Args: + folder_id (str): Folder id. + """ + + if folder_id != self._folder_id: + return False + self._folder_selected = True + self._emit_change() + return True + + def expected_task_selected(self, folder_id, task_name): + """UI selected requested task. + + Other entity types can be requested for selection. + + Because task name is not unique across project a folder id is also + required to confirm the right task has been selected. + + Args: + folder_id (str): Folder id. + task_name (str): Task name. + """ + + if self._folder_id != folder_id: + return False + + if task_name != self._task_name: + return False + self._task_selected = True + self._emit_change() + return True + + def _emit_change(self): + self._controller.emit_event( + "expected_selection_changed", + self.get_expected_selection_data(), + ) diff --git a/openpype/tools/ayon_utils/widgets/projects_widget.py b/openpype/tools/ayon_utils/widgets/projects_widget.py index f98bfcdf8a..728433f929 100644 --- a/openpype/tools/ayon_utils/widgets/projects_widget.py +++ b/openpype/tools/ayon_utils/widgets/projects_widget.py @@ -503,17 +503,6 @@ class ProjectsCombobox(QtWidgets.QWidget): self._projects_model.set_current_context_project(project_name) self._projects_proxy_model.invalidateFilter() - def _update_select_item_visiblity(self, **kwargs): - if not self._select_item_visible: - return - if "project_name" not in kwargs: - project_name = self.get_selected_project_name() - else: - project_name = kwargs.get("project_name") - - # Hide the item if a project is selected - self._projects_model.set_selected_project(project_name) - def set_select_item_visible(self, visible): self._select_item_visible = visible self._projects_model.set_select_item_visible(visible) @@ -534,6 +523,17 @@ class ProjectsCombobox(QtWidgets.QWidget): def set_library_filter_enabled(self, enabled): return self._projects_proxy_model.set_library_filter_enabled(enabled) + def _update_select_item_visiblity(self, **kwargs): + if not self._select_item_visible: + return + if "project_name" not in kwargs: + project_name = self.get_selected_project_name() + else: + project_name = kwargs.get("project_name") + + # Hide the item if a project is selected + self._projects_model.set_selected_project(project_name) + def _on_current_index_changed(self, idx): if not self._listen_selection_change: return diff --git a/openpype/tools/ayon_workfiles/abstract.py b/openpype/tools/ayon_workfiles/abstract.py index ce399fd4c6..260f701d4b 100644 --- a/openpype/tools/ayon_workfiles/abstract.py +++ b/openpype/tools/ayon_workfiles/abstract.py @@ -443,8 +443,11 @@ class AbstractWorkfilesBackend(AbstractWorkfilesCommon): pass @abstractmethod - def get_project_entity(self): - """Get current project entity. + def get_project_entity(self, project_name): + """Get project entity by name. + + Args: + project_name (str): Project name. Returns: dict[str, Any]: Project entity data. @@ -453,10 +456,11 @@ class AbstractWorkfilesBackend(AbstractWorkfilesCommon): pass @abstractmethod - def get_folder_entity(self, folder_id): + def get_folder_entity(self, project_name, folder_id): """Get folder entity by id. Args: + project_name (str): Project name. folder_id (str): Folder id. Returns: @@ -466,10 +470,11 @@ class AbstractWorkfilesBackend(AbstractWorkfilesCommon): pass @abstractmethod - def get_task_entity(self, task_id): + def get_task_entity(self, project_name, task_id): """Get task entity by id. Args: + project_name (str): Project name. task_id (str): Task id. Returns: @@ -574,12 +579,10 @@ class AbstractWorkfilesFrontend(AbstractWorkfilesCommon): pass @abstractmethod - def set_selected_task(self, folder_id, task_id, task_name): + def set_selected_task(self, task_id, task_name): """Change selected task. Args: - folder_id (Union[str, None]): Folder id or None if no folder - is selected. task_id (Union[str, None]): Task id or None if no task is selected. task_name (Union[str, None]): Task name or None if no task @@ -711,21 +714,27 @@ class AbstractWorkfilesFrontend(AbstractWorkfilesCommon): pass @abstractmethod - def expected_representation_selected(self, representation_id): + def expected_representation_selected( + self, folder_id, task_name, representation_id + ): """Expected representation was selected in UI. Args: + folder_id (str): Folder id under which representation is. + task_name (str): Task name under which representation is. representation_id (str): Representation id which was selected. """ pass @abstractmethod - def expected_workfile_selected(self, workfile_path): + def expected_workfile_selected(self, folder_id, task_name, workfile_name): """Expected workfile was selected in UI. Args: - workfile_path (str): Workfile path which was selected. + folder_id (str): Folder id under which workfile is. + task_name (str): Task name under which workfile is. + workfile_name (str): Workfile filename which was selected. """ pass @@ -738,7 +747,7 @@ class AbstractWorkfilesFrontend(AbstractWorkfilesCommon): # Model functions @abstractmethod - def get_folder_items(self, sender): + def get_folder_items(self, project_name, sender): """Folder items to visualize project hierarchy. This function may trigger events 'folders.refresh.started' and @@ -746,6 +755,7 @@ class AbstractWorkfilesFrontend(AbstractWorkfilesCommon): That may help to avoid re-refresh of folder items in UI elements. Args: + project_name (str): Project name for which are folders requested. sender (str): Who requested folder items. Returns: @@ -756,7 +766,7 @@ class AbstractWorkfilesFrontend(AbstractWorkfilesCommon): pass @abstractmethod - def get_task_items(self, folder_id, sender): + def get_task_items(self, project_name, folder_id, sender): """Task items. This function may trigger events 'tasks.refresh.started' and @@ -764,6 +774,7 @@ class AbstractWorkfilesFrontend(AbstractWorkfilesCommon): That may help to avoid re-refresh of task items in UI elements. Args: + project_name (str): Project name for which are tasks requested. folder_id (str): Folder ID for which are tasks requested. sender (str): Who requested folder items. @@ -892,22 +903,25 @@ class AbstractWorkfilesFrontend(AbstractWorkfilesCommon): At this moment the only information which can be saved about workfile is 'note'. + When 'note' is 'None' it is only validated if workfile info exists, + and if not then creates one with empty note. + Args: folder_id (str): Folder id. task_id (str): Task id. filepath (str): Workfile path. - note (str): Note. + note (Union[str, None]): Note. """ pass # General commands @abstractmethod - def refresh(self): - """Refresh everything, models, ui etc. + def reset(self): + """Reset everything, models, ui etc. - Triggers 'controller.refresh.started' event at the beginning and - 'controller.refresh.finished' at the end. + Triggers 'controller.reset.started' event at the beginning and + 'controller.reset.finished' at the end. """ pass diff --git a/openpype/tools/ayon_workfiles/control.py b/openpype/tools/ayon_workfiles/control.py index 3784959caf..fbe6df3155 100644 --- a/openpype/tools/ayon_workfiles/control.py +++ b/openpype/tools/ayon_workfiles/control.py @@ -16,93 +16,120 @@ from openpype.pipeline.context_tools import ( ) from openpype.pipeline.workfile import create_workdir_extra_folders +from openpype.tools.ayon_utils.models import ( + HierarchyModel, + HierarchyExpectedSelection, + ProjectsModel, +) + from .abstract import ( AbstractWorkfilesFrontend, AbstractWorkfilesBackend, ) -from .models import SelectionModel, EntitiesModel, WorkfilesModel +from .models import SelectionModel, WorkfilesModel -class ExpectedSelection: - def __init__(self): - self._folder_id = None - self._task_name = None +class WorkfilesToolExpectedSelection(HierarchyExpectedSelection): + def __init__(self, controller): + super(WorkfilesToolExpectedSelection, self).__init__( + controller, + handle_project=False, + handle_folder=True, + handle_task=True, + ) + self._workfile_name = None self._representation_id = None - self._folder_selected = True - self._task_selected = True - self._workfile_name_selected = True - self._representation_id_selected = True + + self._workfile_selected = True + self._representation_selected = True def set_expected_selection( self, - folder_id, - task_name, + project_name=None, + folder_id=None, + task_name=None, workfile_name=None, - representation_id=None + representation_id=None, ): - self._folder_id = folder_id - self._task_name = task_name self._workfile_name = workfile_name self._representation_id = representation_id - self._folder_selected = False - self._task_selected = False - self._workfile_name_selected = workfile_name is None - self._representation_id_selected = representation_id is None + + self._workfile_selected = False + self._representation_selected = False + + super(WorkfilesToolExpectedSelection, self).set_expected_selection( + project_name, + folder_id, + task_name, + ) def get_expected_selection_data(self): - return { - "folder_id": self._folder_id, - "task_name": self._task_name, - "workfile_name": self._workfile_name, - "representation_id": self._representation_id, - "folder_selected": self._folder_selected, - "task_selected": self._task_selected, - "workfile_name_selected": self._workfile_name_selected, - "representation_id_selected": self._representation_id_selected, + data = super( + WorkfilesToolExpectedSelection, self + ).get_expected_selection_data() + + _is_current = ( + self._project_selected + and self._folder_selected + and self._task_selected + ) + workfile_is_current = False + repre_is_current = False + if _is_current: + workfile_is_current = not self._workfile_selected + repre_is_current = not self._representation_selected + + data["workfile"] = { + "name": self._workfile_name, + "current": workfile_is_current, + "selected": self._workfile_selected, } + data["representation"] = { + "id": self._representation_id, + "current": repre_is_current, + "selected": self._workfile_selected, + } + return data - def is_expected_folder_selected(self, folder_id): - return folder_id == self._folder_id and self._folder_selected + def is_expected_workfile_selected(self, workfile_name): + return ( + workfile_name == self._workfile_name + and self._workfile_selected + ) - def is_expected_task_selected(self, folder_id, task_name): - if not self.is_expected_folder_selected(folder_id): - return False - return task_name == self._task_name and self._task_selected + def is_expected_representation_selected(self, representation_id): + return ( + representation_id == self._representation_id + and self._representation_selected + ) - def expected_folder_selected(self, folder_id): + def expected_workfile_selected(self, folder_id, task_name, workfile_name): if folder_id != self._folder_id: return False - self._folder_selected = True - return True - - def expected_task_selected(self, folder_id, task_name): - if not self.is_expected_folder_selected(folder_id): - return False if task_name != self._task_name: return False - self._task_selected = True - return True - - def expected_workfile_selected(self, folder_id, task_name, workfile_name): - if not self.is_expected_task_selected(folder_id, task_name): - return False - if workfile_name != self._workfile_name: return False - self._workfile_name_selected = True + self._workfile_selected = True + self._emit_change() return True def expected_representation_selected( self, folder_id, task_name, representation_id ): - if not self.is_expected_task_selected(folder_id, task_name): + if folder_id != self._folder_id: return False + + if task_name != self._task_name: + return False + if representation_id != self._representation_id: return False - self._representation_id_selected = True + self._representation_selected = True + self._emit_change() return True @@ -136,9 +163,9 @@ class BaseWorkfileController( # Expected selected folder and task self._expected_selection = self._create_expected_selection_obj() - self._selection_model = self._create_selection_model() - self._entities_model = self._create_entities_model() + self._projects_model = self._create_projects_model() + self._hierarchy_model = self._create_hierarchy_model() self._workfiles_model = self._create_workfiles_model() @property @@ -151,13 +178,16 @@ class BaseWorkfileController( return self._host_is_valid def _create_expected_selection_obj(self): - return ExpectedSelection() + return WorkfilesToolExpectedSelection(self) + + def _create_projects_model(self): + return ProjectsModel(self) def _create_selection_model(self): return SelectionModel(self) - def _create_entities_model(self): - return EntitiesModel(self) + def _create_hierarchy_model(self): + return HierarchyModel(self) def _create_workfiles_model(self): return WorkfilesModel(self) @@ -193,14 +223,17 @@ class BaseWorkfileController( self._project_anatomy = Anatomy(self.get_current_project_name()) return self._project_anatomy - def get_project_entity(self): - return self._entities_model.get_project_entity() + def get_project_entity(self, project_name): + return self._projects_model.get_project_entity( + project_name) - def get_folder_entity(self, folder_id): - return self._entities_model.get_folder_entity(folder_id) + def get_folder_entity(self, project_name, folder_id): + return self._hierarchy_model.get_folder_entity( + project_name, folder_id) - def get_task_entity(self, task_id): - return self._entities_model.get_task_entity(task_id) + def get_task_entity(self, project_name, task_id): + return self._hierarchy_model.get_task_entity( + project_name, task_id) # --------------------------------- # Implementation of abstract methods @@ -293,9 +326,8 @@ class BaseWorkfileController( def get_selected_task_name(self): return self._selection_model.get_selected_task_name() - def set_selected_task(self, folder_id, task_id, task_name): - return self._selection_model.set_selected_task( - folder_id, task_id, task_name) + def set_selected_task(self, task_id, task_name): + return self._selection_model.set_selected_task(task_id, task_name) def get_selected_workfile_path(self): return self._selection_model.get_selected_workfile_path() @@ -318,7 +350,11 @@ class BaseWorkfileController( representation_id=None ): self._expected_selection.set_expected_selection( - folder_id, task_name, workfile_name, representation_id + self.get_current_project_name(), + folder_id, + task_name, + workfile_name, + representation_id ) self._trigger_expected_selection_changed() @@ -355,11 +391,13 @@ class BaseWorkfileController( ) # Model functions - def get_folder_items(self, sender): - return self._entities_model.get_folder_items(sender) + def get_folder_items(self, project_name, sender=None): + return self._hierarchy_model.get_folder_items(project_name, sender) - def get_task_items(self, folder_id, sender): - return self._entities_model.get_tasks_items(folder_id, sender) + def get_task_items(self, project_name, folder_id, sender=None): + return self._hierarchy_model.get_task_items( + project_name, folder_id, sender + ) def get_workarea_dir_by_context(self, folder_id, task_id): return self._workfiles_model.get_workarea_dir_by_context( @@ -394,7 +432,9 @@ class BaseWorkfileController( def get_published_file_items(self, folder_id, task_id): task_name = None if task_id: - task = self.get_task_entity(task_id) + task = self.get_task_entity( + self.get_current_project_name(), task_id + ) task_name = task.get("name") return self._workfiles_model.get_published_file_items( @@ -410,21 +450,27 @@ class BaseWorkfileController( folder_id, task_id, filepath, note ) - def refresh(self): + def reset(self): if not self._host_is_valid: - self._emit_event("controller.refresh.started") - self._emit_event("controller.refresh.finished") + self._emit_event("controller.reset.started") + self._emit_event("controller.reset.finished") return expected_folder_id = self.get_selected_folder_id() expected_task_name = self.get_selected_task_name() + expected_work_path = self.get_selected_workfile_path() + expected_repre_id = self.get_selected_representation_id() + expected_work_name = None + if expected_work_path: + expected_work_name = os.path.basename(expected_work_path) - self._emit_event("controller.refresh.started") + self._emit_event("controller.reset.started") context = self._get_host_current_context() project_name = context["project_name"] folder_name = context["asset_name"] task_name = context["task_name"] + current_file = self.get_current_workfile() folder_id = None if folder_name: folder = ayon_api.get_folder_by_name(project_name, folder_name) @@ -439,18 +485,25 @@ class BaseWorkfileController( self._current_folder_id = folder_id self._current_task_name = task_name + self._projects_model.reset() + self._hierarchy_model.reset() + if not expected_folder_id: expected_folder_id = folder_id expected_task_name = task_name + if current_file: + expected_work_name = os.path.basename(current_file) + + self._emit_event("controller.reset.finished") self._expected_selection.set_expected_selection( - expected_folder_id, expected_task_name + project_name, + expected_folder_id, + expected_task_name, + expected_work_name, + expected_repre_id, ) - self._entities_model.refresh() - - self._emit_event("controller.refresh.finished") - # Controller actions def open_workfile(self, folder_id, task_id, filepath): self._emit_event("open_workfile.started") @@ -579,9 +632,9 @@ class BaseWorkfileController( self, project_name, folder_id, task_id, folder=None, task=None ): if folder is None: - folder = self.get_folder_entity(folder_id) + folder = self.get_folder_entity(project_name, folder_id) if task is None: - task = self.get_task_entity(task_id) + task = self.get_task_entity(project_name, task_id) # NOTE keys should be OpenPype compatible return { "project_name": project_name, @@ -633,8 +686,8 @@ class BaseWorkfileController( ): # Trigger before save event project_name = self.get_current_project_name() - folder = self.get_folder_entity(folder_id) - task = self.get_task_entity(task_id) + folder = self.get_folder_entity(project_name, folder_id) + task = self.get_task_entity(project_name, task_id) task_name = task["name"] # QUESTION should the data be different for 'before' and 'after'? @@ -674,6 +727,9 @@ class BaseWorkfileController( else: self._host_save_workfile(dst_filepath) + # Make sure workfile info exists + self.save_workfile_info(folder_id, task_id, dst_filepath, None) + # Create extra folders create_workdir_extra_folders( workdir, @@ -685,4 +741,4 @@ class BaseWorkfileController( # Trigger after save events emit_event("workfile.save.after", event_data, source="workfiles.tool") - self.refresh() + self.reset() diff --git a/openpype/tools/ayon_workfiles/models/__init__.py b/openpype/tools/ayon_workfiles/models/__init__.py index d906b9e7bd..734cb08cb6 100644 --- a/openpype/tools/ayon_workfiles/models/__init__.py +++ b/openpype/tools/ayon_workfiles/models/__init__.py @@ -1,10 +1,8 @@ -from .hierarchy import EntitiesModel from .selection import SelectionModel from .workfiles import WorkfilesModel __all__ = ( "SelectionModel", - "EntitiesModel", "WorkfilesModel", ) diff --git a/openpype/tools/ayon_workfiles/models/hierarchy.py b/openpype/tools/ayon_workfiles/models/hierarchy.py deleted file mode 100644 index a1d51525da..0000000000 --- a/openpype/tools/ayon_workfiles/models/hierarchy.py +++ /dev/null @@ -1,236 +0,0 @@ -"""Hierarchy model that handles folders and tasks. - -The model can be extracted for common usage. In that case it will be required -to add more handling of project name changes. -""" - -import time -import collections -import contextlib - -import ayon_api - -from openpype.tools.ayon_workfiles.abstract import ( - FolderItem, - TaskItem, -) - - -def _get_task_items_from_tasks(tasks): - """ - - Returns: - TaskItem: Task item. - """ - - output = [] - for task in tasks: - folder_id = task["folderId"] - output.append(TaskItem( - task["id"], - task["name"], - task["type"], - folder_id, - None, - None - )) - return output - - -def _get_folder_item_from_hierarchy_item(item): - return FolderItem( - item["id"], - item["parentId"], - item["name"], - item["label"], - None, - None, - ) - - -class CacheItem: - def __init__(self, lifetime=120): - self._lifetime = lifetime - self._last_update = None - self._data = None - - @property - def is_valid(self): - if self._last_update is None: - return False - - return (time.time() - self._last_update) < self._lifetime - - def set_invalid(self, data=None): - self._last_update = None - self._data = data - - def get_data(self): - return self._data - - def update_data(self, data): - self._data = data - self._last_update = time.time() - - -class EntitiesModel(object): - event_source = "entities.model" - - def __init__(self, controller): - project_cache = CacheItem() - project_cache.set_invalid({}) - folders_cache = CacheItem() - folders_cache.set_invalid({}) - self._project_cache = project_cache - self._folders_cache = folders_cache - self._tasks_cache = {} - - self._folders_by_id = {} - self._tasks_by_id = {} - - self._folders_refreshing = False - self._tasks_refreshing = set() - self._controller = controller - - def reset(self): - self._project_cache.set_invalid({}) - self._folders_cache.set_invalid({}) - self._tasks_cache = {} - - self._folders_by_id = {} - self._tasks_by_id = {} - - def refresh(self): - self._refresh_folders_cache() - - def get_project_entity(self): - if not self._project_cache.is_valid: - project_name = self._controller.get_current_project_name() - project_entity = ayon_api.get_project(project_name) - self._project_cache.update_data(project_entity) - return self._project_cache.get_data() - - def get_folder_items(self, sender): - if not self._folders_cache.is_valid: - self._refresh_folders_cache(sender) - return self._folders_cache.get_data() - - def get_tasks_items(self, folder_id, sender): - if not folder_id: - return [] - - task_cache = self._tasks_cache.get(folder_id) - if task_cache is None or not task_cache.is_valid: - self._refresh_tasks_cache(folder_id, sender) - task_cache = self._tasks_cache.get(folder_id) - return task_cache.get_data() - - def get_folder_entity(self, folder_id): - if folder_id not in self._folders_by_id: - entity = None - if folder_id: - project_name = self._controller.get_current_project_name() - entity = ayon_api.get_folder_by_id(project_name, folder_id) - self._folders_by_id[folder_id] = entity - return self._folders_by_id[folder_id] - - def get_task_entity(self, task_id): - if task_id not in self._tasks_by_id: - entity = None - if task_id: - project_name = self._controller.get_current_project_name() - entity = ayon_api.get_task_by_id(project_name, task_id) - self._tasks_by_id[task_id] = entity - return self._tasks_by_id[task_id] - - @contextlib.contextmanager - def _folder_refresh_event_manager(self, project_name, sender): - self._folders_refreshing = True - self._controller.emit_event( - "folders.refresh.started", - {"project_name": project_name, "sender": sender}, - self.event_source - ) - try: - yield - - finally: - self._controller.emit_event( - "folders.refresh.finished", - {"project_name": project_name, "sender": sender}, - self.event_source - ) - self._folders_refreshing = False - - @contextlib.contextmanager - def _task_refresh_event_manager( - self, project_name, folder_id, sender - ): - self._tasks_refreshing.add(folder_id) - self._controller.emit_event( - "tasks.refresh.started", - { - "project_name": project_name, - "folder_id": folder_id, - "sender": sender, - }, - self.event_source - ) - try: - yield - - finally: - self._controller.emit_event( - "tasks.refresh.finished", - { - "project_name": project_name, - "folder_id": folder_id, - "sender": sender, - }, - self.event_source - ) - self._tasks_refreshing.discard(folder_id) - - def _refresh_folders_cache(self, sender=None): - if self._folders_refreshing: - return - project_name = self._controller.get_current_project_name() - with self._folder_refresh_event_manager(project_name, sender): - folder_items = self._query_folders(project_name) - self._folders_cache.update_data(folder_items) - - def _query_folders(self, project_name): - hierarchy = ayon_api.get_folders_hierarchy(project_name) - - folder_items = {} - hierachy_queue = collections.deque(hierarchy["hierarchy"]) - while hierachy_queue: - item = hierachy_queue.popleft() - folder_item = _get_folder_item_from_hierarchy_item(item) - folder_items[folder_item.entity_id] = folder_item - hierachy_queue.extend(item["children"] or []) - return folder_items - - def _refresh_tasks_cache(self, folder_id, sender=None): - if folder_id in self._tasks_refreshing: - return - - project_name = self._controller.get_current_project_name() - with self._task_refresh_event_manager( - project_name, folder_id, sender - ): - cache_item = self._tasks_cache.get(folder_id) - if cache_item is None: - cache_item = CacheItem() - self._tasks_cache[folder_id] = cache_item - - task_items = self._query_tasks(project_name, folder_id) - cache_item.update_data(task_items) - - def _query_tasks(self, project_name, folder_id): - tasks = list(ayon_api.get_tasks( - project_name, - folder_ids=[folder_id], - fields={"id", "name", "label", "folderId", "type"} - )) - return _get_task_items_from_tasks(tasks) diff --git a/openpype/tools/ayon_workfiles/models/selection.py b/openpype/tools/ayon_workfiles/models/selection.py index ad034794d8..2f0896842d 100644 --- a/openpype/tools/ayon_workfiles/models/selection.py +++ b/openpype/tools/ayon_workfiles/models/selection.py @@ -4,7 +4,7 @@ class SelectionModel(object): Triggering events: - "selection.folder.changed" - "selection.task.changed" - - "workarea.selection.changed" + - "selection.workarea.changed" - "selection.representation.changed" """ @@ -29,7 +29,10 @@ class SelectionModel(object): self._folder_id = folder_id self._controller.emit_event( "selection.folder.changed", - {"folder_id": folder_id}, + { + "project_name": self._controller.get_current_project_name(), + "folder_id": folder_id + }, self.event_source ) @@ -39,10 +42,7 @@ class SelectionModel(object): def get_selected_task_id(self): return self._task_id - def set_selected_task(self, folder_id, task_id, task_name): - if folder_id != self._folder_id: - self.set_selected_folder(folder_id) - + def set_selected_task(self, task_id, task_name): if task_id == self._task_id: return @@ -51,7 +51,8 @@ class SelectionModel(object): self._controller.emit_event( "selection.task.changed", { - "folder_id": folder_id, + "project_name": self._controller.get_current_project_name(), + "folder_id": self._folder_id, "task_name": task_name, "task_id": task_id }, @@ -67,8 +68,9 @@ class SelectionModel(object): self._workfile_path = path self._controller.emit_event( - "workarea.selection.changed", + "selection.workarea.changed", { + "project_name": self._controller.get_current_project_name(), "path": path, "folder_id": self._folder_id, "task_name": self._task_name, @@ -86,6 +88,9 @@ class SelectionModel(object): self._representation_id = representation_id self._controller.emit_event( "selection.representation.changed", - {"representation_id": representation_id}, + { + "project_name": self._controller.get_current_project_name(), + "representation_id": representation_id, + }, self.event_source ) diff --git a/openpype/tools/ayon_workfiles/models/workfiles.py b/openpype/tools/ayon_workfiles/models/workfiles.py index 4d989ed22c..907b9b5383 100644 --- a/openpype/tools/ayon_workfiles/models/workfiles.py +++ b/openpype/tools/ayon_workfiles/models/workfiles.py @@ -148,7 +148,9 @@ class WorkareaModel: def _get_folder_data(self, folder_id): fill_data = self._fill_data_by_folder_id.get(folder_id) if fill_data is None: - folder = self._controller.get_folder_entity(folder_id) + folder = self._controller.get_folder_entity( + self.project_name, folder_id + ) fill_data = get_folder_template_data(folder) self._fill_data_by_folder_id[folder_id] = fill_data return copy.deepcopy(fill_data) @@ -156,7 +158,9 @@ class WorkareaModel: def _get_task_data(self, project_entity, folder_id, task_id): task_data = self._task_data_by_folder_id.setdefault(folder_id, {}) if task_id not in task_data: - task = self._controller.get_task_entity(task_id) + task = self._controller.get_task_entity( + self.project_name, task_id + ) if task: task_data[task_id] = get_task_template_data( project_entity, task) @@ -167,8 +171,9 @@ class WorkareaModel: return {} base_data = self._get_base_data() + project_name = base_data["project"]["name"] folder_data = self._get_folder_data(folder_id) - project_entity = self._controller.get_project_entity() + project_entity = self._controller.get_project_entity(project_name) task_data = self._get_task_data(project_entity, folder_id, task_id) base_data.update(folder_data) @@ -292,9 +297,13 @@ class WorkareaModel: folder = None task = None if folder_id: - folder = self._controller.get_folder_entity(folder_id) + folder = self._controller.get_folder_entity( + self.project_name, folder_id + ) if task_id: - task = self._controller.get_task_entity(task_id) + task = self._controller.get_task_entity( + self.project_name, task_id + ) if not folder or not task: return { @@ -491,10 +500,13 @@ class WorkfileEntitiesModel: ) if not workfile_info: self._cache[identifier] = self._create_workfile_info_entity( - task_id, rootless_path, note) + task_id, rootless_path, note or "") self._items.pop(identifier, None) return + if note is None: + return + new_workfile_info = copy.deepcopy(workfile_info) attrib = new_workfile_info.setdefault("attrib", {}) attrib["description"] = note diff --git a/openpype/tools/ayon_workfiles/widgets/files_widget.py b/openpype/tools/ayon_workfiles/widgets/files_widget.py index 656ddf1dd8..16f0b6fce3 100644 --- a/openpype/tools/ayon_workfiles/widgets/files_widget.py +++ b/openpype/tools/ayon_workfiles/widgets/files_widget.py @@ -69,7 +69,7 @@ class FilesWidget(QtWidgets.QWidget): main_layout.addWidget(btns_widget, 0) controller.register_event_callback( - "workarea.selection.changed", + "selection.workarea.changed", self._on_workarea_path_changed ) controller.register_event_callback( diff --git a/openpype/tools/ayon_workfiles/widgets/files_widget_published.py b/openpype/tools/ayon_workfiles/widgets/files_widget_published.py index 576cf18d73..704f7b2f39 100644 --- a/openpype/tools/ayon_workfiles/widgets/files_widget_published.py +++ b/openpype/tools/ayon_workfiles/widgets/files_widget_published.py @@ -59,14 +59,6 @@ class PublishedFilesModel(QtGui.QStandardItemModel): self._add_empty_item() - def _clear_items(self): - self._remove_missing_context_item() - self._remove_empty_item() - if self._items_by_id: - root = self.invisibleRootItem() - root.removeRows(0, root.rowCount()) - self._items_by_id = {} - def set_published_mode(self, published_mode): if self._published_mode == published_mode: return @@ -89,6 +81,18 @@ class PublishedFilesModel(QtGui.QStandardItemModel): return QtCore.QModelIndex() return self.indexFromItem(item) + def refresh(self): + if self._published_mode: + self._fill_items() + + def _clear_items(self): + self._remove_missing_context_item() + self._remove_empty_item() + if self._items_by_id: + root = self.invisibleRootItem() + root.removeRows(0, root.rowCount()) + self._items_by_id = {} + def _get_missing_context_item(self): if self._missing_context_item is None: message = "Select folder" @@ -149,7 +153,6 @@ class PublishedFilesModel(QtGui.QStandardItemModel): def _on_folder_changed(self, event): self._last_folder_id = event["folder_id"] - self._last_task_id = None if self._context_select_mode: return @@ -356,14 +359,13 @@ class PublishedFilesWidget(QtWidgets.QWidget): self.save_as_requested.emit() def _on_expected_selection_change(self, event): - if ( - event["representation_id_selected"] - or not event["folder_selected"] - or (event["task_name"] and not event["task_selected"]) - ): + repre_info = event["representation"] + if not repre_info["current"]: return - representation_id = event["representation_id"] + self._model.refresh() + + representation_id = repre_info["id"] selected_repre_id = self.get_selected_repre_id() if ( representation_id is not None @@ -376,5 +378,5 @@ class PublishedFilesWidget(QtWidgets.QWidget): self._view.setCurrentIndex(proxy_index) self._controller.expected_representation_selected( - event["folder_id"], event["task_name"], representation_id + event["folder"]["id"], event["task"]["name"], representation_id ) diff --git a/openpype/tools/ayon_workfiles/widgets/files_widget_workarea.py b/openpype/tools/ayon_workfiles/widgets/files_widget_workarea.py index 3a8e90f933..8eefd3cf81 100644 --- a/openpype/tools/ayon_workfiles/widgets/files_widget_workarea.py +++ b/openpype/tools/ayon_workfiles/widgets/files_widget_workarea.py @@ -28,6 +28,10 @@ class WorkAreaFilesModel(QtGui.QStandardItemModel): self.setHeaderData(0, QtCore.Qt.Horizontal, "Name") self.setHeaderData(1, QtCore.Qt.Horizontal, "Date Modified") + controller.register_event_callback( + "selection.folder.changed", + self._on_folder_changed + ) controller.register_event_callback( "selection.task.changed", self._on_task_changed @@ -63,6 +67,10 @@ class WorkAreaFilesModel(QtGui.QStandardItemModel): return QtCore.QModelIndex() return self.indexFromItem(item) + def refresh(self): + if not self._published_mode: + self._fill_items() + def _get_missing_context_item(self): if self._missing_context_item is None: message = "Select folder and task" @@ -129,6 +137,11 @@ class WorkAreaFilesModel(QtGui.QStandardItemModel): root_item.takeRow(self._empty_root_item.row()) self._empty_item_used = False + def _on_folder_changed(self, event): + self._selected_folder_id = event["folder_id"] + if not self._published_mode: + self._fill_items() + def _on_task_changed(self, event): self._selected_folder_id = event["folder_id"] self._selected_task_id = event["task_id"] @@ -362,10 +375,13 @@ class WorkAreaFilesWidget(QtWidgets.QWidget): self.duplicate_requested.emit() def _on_expected_selection_change(self, event): - if event["workfile_name_selected"]: + workfile_info = event["workfile"] + if not workfile_info["current"]: return - workfile_name = event["workfile_name"] + self._model.refresh() + + workfile_name = workfile_info["name"] if ( workfile_name is not None and workfile_name != self._get_selected_info()["filename"] @@ -376,5 +392,5 @@ class WorkAreaFilesWidget(QtWidgets.QWidget): self._view.setCurrentIndex(proxy_index) self._controller.expected_workfile_selected( - event["folder_id"], event["task_name"], workfile_name + event["folder"]["id"], event["task"]["name"], workfile_name ) diff --git a/openpype/tools/ayon_workfiles/widgets/folders_widget.py b/openpype/tools/ayon_workfiles/widgets/folders_widget.py deleted file mode 100644 index b04f8e4098..0000000000 --- a/openpype/tools/ayon_workfiles/widgets/folders_widget.py +++ /dev/null @@ -1,324 +0,0 @@ -import uuid -import collections - -import qtawesome -from qtpy import QtWidgets, QtGui, QtCore - -from openpype.tools.utils import ( - RecursiveSortFilterProxyModel, - DeselectableTreeView, -) - -from .constants import ITEM_ID_ROLE, ITEM_NAME_ROLE - -SENDER_NAME = "qt_folders_model" - - -class FoldersRefreshThread(QtCore.QThread): - """Thread for refreshing folders. - - Call controller to get folders and emit signal when finished. - - Args: - controller (AbstractWorkfilesFrontend): The control object. - """ - - refresh_finished = QtCore.Signal(str) - - def __init__(self, controller): - super(FoldersRefreshThread, self).__init__() - self._id = uuid.uuid4().hex - self._controller = controller - self._result = None - - @property - def id(self): - """Thread id. - - Returns: - str: Unique id of the thread. - """ - - return self._id - - def run(self): - self._result = self._controller.get_folder_items(SENDER_NAME) - self.refresh_finished.emit(self.id) - - def get_result(self): - return self._result - - -class FoldersModel(QtGui.QStandardItemModel): - """Folders model which cares about refresh of folders. - - Args: - controller (AbstractWorkfilesFrontend): The control object. - """ - - refreshed = QtCore.Signal() - - def __init__(self, controller): - super(FoldersModel, self).__init__() - - self._controller = controller - self._items_by_id = {} - self._parent_id_by_id = {} - - self._refresh_threads = {} - self._current_refresh_thread = None - - self._has_content = False - self._is_refreshing = False - - @property - def is_refreshing(self): - """Model is refreshing. - - Returns: - bool: True if model is refreshing. - """ - return self._is_refreshing - - @property - def has_content(self): - """Has at least one folder. - - Returns: - bool: True if model has at least one folder. - """ - - return self._has_content - - def clear(self): - self._items_by_id = {} - self._parent_id_by_id = {} - self._has_content = False - super(FoldersModel, self).clear() - - def get_index_by_id(self, item_id): - """Get index by folder id. - - Returns: - QtCore.QModelIndex: Index of the folder. Can be invalid if folder - is not available. - """ - item = self._items_by_id.get(item_id) - if item is None: - return QtCore.QModelIndex() - return self.indexFromItem(item) - - def refresh(self): - """Refresh folders items. - - Refresh start thread because it can cause that controller can - start query from database if folders are not cached. - """ - - self._is_refreshing = True - - thread = FoldersRefreshThread(self._controller) - self._current_refresh_thread = thread.id - self._refresh_threads[thread.id] = thread - thread.refresh_finished.connect(self._on_refresh_thread) - thread.start() - - def _on_refresh_thread(self, thread_id): - """Callback when refresh thread is finished. - - Technically can be running multiple refresh threads at the same time, - to avoid using values from wrong thread, we check if thread id is - current refresh thread id. - - Folders are stored by id. - - Args: - thread_id (str): Thread id. - """ - - thread = self._refresh_threads.pop(thread_id) - if thread_id != self._current_refresh_thread: - return - - folder_items_by_id = thread.get_result() - if not folder_items_by_id: - if folder_items_by_id is not None: - self.clear() - self._is_refreshing = False - return - - self._has_content = True - - folder_ids = set(folder_items_by_id) - ids_to_remove = set(self._items_by_id) - folder_ids - - folder_items_by_parent = collections.defaultdict(list) - for folder_item in folder_items_by_id.values(): - folder_items_by_parent[folder_item.parent_id].append(folder_item) - - hierarchy_queue = collections.deque() - hierarchy_queue.append(None) - - while hierarchy_queue: - parent_id = hierarchy_queue.popleft() - folder_items = folder_items_by_parent[parent_id] - if parent_id is None: - parent_item = self.invisibleRootItem() - else: - parent_item = self._items_by_id[parent_id] - - new_items = [] - for folder_item in folder_items: - item_id = folder_item.entity_id - item = self._items_by_id.get(item_id) - if item is None: - is_new = True - item = QtGui.QStandardItem() - item.setEditable(False) - else: - is_new = self._parent_id_by_id[item_id] != parent_id - - icon = qtawesome.icon( - folder_item.icon_name, - color=folder_item.icon_color, - ) - item.setData(item_id, ITEM_ID_ROLE) - item.setData(folder_item.name, ITEM_NAME_ROLE) - item.setData(folder_item.label, QtCore.Qt.DisplayRole) - item.setData(icon, QtCore.Qt.DecorationRole) - if is_new: - new_items.append(item) - self._items_by_id[item_id] = item - self._parent_id_by_id[item_id] = parent_id - - hierarchy_queue.append(item_id) - - if new_items: - parent_item.appendRows(new_items) - - for item_id in ids_to_remove: - item = self._items_by_id[item_id] - parent_id = self._parent_id_by_id[item_id] - if parent_id is None: - parent_item = self.invisibleRootItem() - else: - parent_item = self._items_by_id[parent_id] - parent_item.takeChild(item.row()) - - for item_id in ids_to_remove: - self._items_by_id.pop(item_id) - self._parent_id_by_id.pop(item_id) - - self._is_refreshing = False - self.refreshed.emit() - - -class FoldersWidget(QtWidgets.QWidget): - """Folders widget. - - Widget that handles folders view, model and selection. - - Args: - controller (AbstractWorkfilesFrontend): The control object. - parent (QtWidgets.QWidget): The parent widget. - """ - - def __init__(self, controller, parent): - super(FoldersWidget, self).__init__(parent) - - folders_view = DeselectableTreeView(self) - folders_view.setHeaderHidden(True) - - folders_model = FoldersModel(controller) - folders_proxy_model = RecursiveSortFilterProxyModel() - folders_proxy_model.setSourceModel(folders_model) - - folders_view.setModel(folders_proxy_model) - - main_layout = QtWidgets.QHBoxLayout(self) - main_layout.setContentsMargins(0, 0, 0, 0) - main_layout.addWidget(folders_view, 1) - - controller.register_event_callback( - "folders.refresh.finished", - self._on_folders_refresh_finished - ) - controller.register_event_callback( - "controller.refresh.finished", - self._on_controller_refresh - ) - controller.register_event_callback( - "expected_selection_changed", - self._on_expected_selection_change - ) - - selection_model = folders_view.selectionModel() - selection_model.selectionChanged.connect(self._on_selection_change) - - folders_model.refreshed.connect(self._on_model_refresh) - - self._controller = controller - self._folders_view = folders_view - self._folders_model = folders_model - self._folders_proxy_model = folders_proxy_model - - self._expected_selection = None - - def set_name_filter(self, name): - self._folders_proxy_model.setFilterFixedString(name) - - def _clear(self): - self._folders_model.clear() - - def _on_folders_refresh_finished(self, event): - if event["sender"] != SENDER_NAME: - self._folders_model.refresh() - - def _on_controller_refresh(self): - self._update_expected_selection() - - def _update_expected_selection(self, expected_data=None): - if expected_data is None: - expected_data = self._controller.get_expected_selection_data() - - # We're done - if expected_data["folder_selected"]: - return - - folder_id = expected_data["folder_id"] - self._expected_selection = folder_id - if not self._folders_model.is_refreshing: - self._set_expected_selection() - - def _set_expected_selection(self): - folder_id = self._expected_selection - self._expected_selection = None - if ( - folder_id is not None - and folder_id != self._get_selected_item_id() - ): - index = self._folders_model.get_index_by_id(folder_id) - if index.isValid(): - proxy_index = self._folders_proxy_model.mapFromSource(index) - self._folders_view.setCurrentIndex(proxy_index) - self._controller.expected_folder_selected(folder_id) - - def _on_model_refresh(self): - if self._expected_selection: - self._set_expected_selection() - self._folders_proxy_model.sort(0) - - def _on_expected_selection_change(self, event): - self._update_expected_selection(event.data) - - def _get_selected_item_id(self): - selection_model = self._folders_view.selectionModel() - for index in selection_model.selectedIndexes(): - item_id = index.data(ITEM_ID_ROLE) - if item_id is not None: - return item_id - return None - - def _on_selection_change(self): - item_id = self._get_selected_item_id() - self._controller.set_selected_folder(item_id) diff --git a/openpype/tools/ayon_workfiles/widgets/side_panel.py b/openpype/tools/ayon_workfiles/widgets/side_panel.py index 7f06576a00..5085f4701e 100644 --- a/openpype/tools/ayon_workfiles/widgets/side_panel.py +++ b/openpype/tools/ayon_workfiles/widgets/side_panel.py @@ -66,7 +66,7 @@ class SidePanelWidget(QtWidgets.QWidget): btn_note_save.clicked.connect(self._on_save_click) controller.register_event_callback( - "workarea.selection.changed", self._on_selection_change + "selection.workarea.changed", self._on_selection_change ) self._details_input = details_input diff --git a/openpype/tools/ayon_workfiles/widgets/tasks_widget.py b/openpype/tools/ayon_workfiles/widgets/tasks_widget.py deleted file mode 100644 index 04f5b286b1..0000000000 --- a/openpype/tools/ayon_workfiles/widgets/tasks_widget.py +++ /dev/null @@ -1,420 +0,0 @@ -import uuid -import qtawesome -from qtpy import QtWidgets, QtGui, QtCore - -from openpype.style import get_disabled_entity_icon_color -from openpype.tools.utils import DeselectableTreeView - -from .constants import ( - ITEM_NAME_ROLE, - ITEM_ID_ROLE, - PARENT_ID_ROLE, -) - -SENDER_NAME = "qt_tasks_model" - - -class RefreshThread(QtCore.QThread): - """Thread for refreshing tasks. - - Call controller to get tasks and emit signal when finished. - - Args: - controller (AbstractWorkfilesFrontend): The control object. - folder_id (str): Folder id. - """ - - refresh_finished = QtCore.Signal(str) - - def __init__(self, controller, folder_id): - super(RefreshThread, self).__init__() - self._id = uuid.uuid4().hex - self._controller = controller - self._folder_id = folder_id - self._result = None - - @property - def id(self): - return self._id - - def run(self): - self._result = self._controller.get_task_items( - self._folder_id, SENDER_NAME) - self.refresh_finished.emit(self.id) - - def get_result(self): - return self._result - - -class TasksModel(QtGui.QStandardItemModel): - """Tasks model which cares about refresh of tasks by folder id. - - Args: - controller (AbstractWorkfilesFrontend): The control object. - """ - - refreshed = QtCore.Signal() - - def __init__(self, controller): - super(TasksModel, self).__init__() - - self._controller = controller - - self._items_by_name = {} - self._has_content = False - self._is_refreshing = False - - self._invalid_selection_item_used = False - self._invalid_selection_item = None - self._empty_tasks_item_used = False - self._empty_tasks_item = None - - self._last_folder_id = None - - self._refresh_threads = {} - self._current_refresh_thread = None - - # Initial state - self._add_invalid_selection_item() - - def clear(self): - self._items_by_name = {} - self._has_content = False - self._remove_invalid_items() - super(TasksModel, self).clear() - - def refresh(self, folder_id): - """Refresh tasks for folder. - - Args: - folder_id (Union[str, None]): Folder id. - """ - - self._refresh(folder_id) - - def get_index_by_name(self, task_name): - """Find item by name and return its index. - - Returns: - QtCore.QModelIndex: Index of item. Is invalid if task is not - found by name. - """ - - item = self._items_by_name.get(task_name) - if item is None: - return QtCore.QModelIndex() - return self.indexFromItem(item) - - def get_last_folder_id(self): - """Get last refreshed folder id. - - Returns: - Union[str, None]: Folder id. - """ - - return self._last_folder_id - - def _get_invalid_selection_item(self): - if self._invalid_selection_item is None: - item = QtGui.QStandardItem("Select a folder") - item.setFlags(QtCore.Qt.NoItemFlags) - icon = qtawesome.icon( - "fa.times", - color=get_disabled_entity_icon_color() - ) - item.setData(icon, QtCore.Qt.DecorationRole) - self._invalid_selection_item = item - return self._invalid_selection_item - - def _get_empty_task_item(self): - if self._empty_tasks_item is None: - item = QtGui.QStandardItem("No task") - icon = qtawesome.icon( - "fa.exclamation-circle", - color=get_disabled_entity_icon_color() - ) - item.setData(icon, QtCore.Qt.DecorationRole) - item.setFlags(QtCore.Qt.NoItemFlags) - self._empty_tasks_item = item - return self._empty_tasks_item - - def _add_invalid_item(self, item): - self.clear() - root_item = self.invisibleRootItem() - root_item.appendRow(item) - - def _remove_invalid_item(self, item): - root_item = self.invisibleRootItem() - root_item.takeRow(item.row()) - - def _remove_invalid_items(self): - self._remove_invalid_selection_item() - self._remove_empty_task_item() - - def _add_invalid_selection_item(self): - if not self._invalid_selection_item_used: - self._add_invalid_item(self._get_invalid_selection_item()) - self._invalid_selection_item_used = True - - def _remove_invalid_selection_item(self): - if self._invalid_selection_item: - self._remove_invalid_item(self._get_invalid_selection_item()) - self._invalid_selection_item_used = False - - def _add_empty_task_item(self): - if not self._empty_tasks_item_used: - self._add_invalid_item(self._get_empty_task_item()) - self._empty_tasks_item_used = True - - def _remove_empty_task_item(self): - if self._empty_tasks_item_used: - self._remove_invalid_item(self._get_empty_task_item()) - self._empty_tasks_item_used = False - - def _refresh(self, folder_id): - self._is_refreshing = True - self._last_folder_id = folder_id - if not folder_id: - self._add_invalid_selection_item() - self._current_refresh_thread = None - self._is_refreshing = False - self.refreshed.emit() - return - - thread = RefreshThread(self._controller, folder_id) - self._current_refresh_thread = thread.id - self._refresh_threads[thread.id] = thread - thread.refresh_finished.connect(self._on_refresh_thread) - thread.start() - - def _on_refresh_thread(self, thread_id): - """Callback when refresh thread is finished. - - Technically can be running multiple refresh threads at the same time, - to avoid using values from wrong thread, we check if thread id is - current refresh thread id. - - Tasks are stored by name, so if a folder has same task name as - previously selected folder it keeps the selection. - - Args: - thread_id (str): Thread id. - """ - - thread = self._refresh_threads.pop(thread_id) - if thread_id != self._current_refresh_thread: - return - - task_items = thread.get_result() - # Task items are refreshed - if task_items is None: - return - - # No tasks are available on folder - if not task_items: - self._add_empty_task_item() - return - self._remove_invalid_items() - - new_items = [] - new_names = set() - for task_item in task_items: - name = task_item.name - new_names.add(name) - item = self._items_by_name.get(name) - if item is None: - item = QtGui.QStandardItem() - item.setEditable(False) - new_items.append(item) - self._items_by_name[name] = item - - # TODO cache locally - icon = qtawesome.icon( - task_item.icon_name, - color=task_item.icon_color, - ) - item.setData(task_item.label, QtCore.Qt.DisplayRole) - item.setData(name, ITEM_NAME_ROLE) - item.setData(task_item.id, ITEM_ID_ROLE) - item.setData(task_item.parent_id, PARENT_ID_ROLE) - item.setData(icon, QtCore.Qt.DecorationRole) - - root_item = self.invisibleRootItem() - - for name in set(self._items_by_name) - new_names: - item = self._items_by_name.pop(name) - root_item.removeRow(item.row()) - - if new_items: - root_item.appendRows(new_items) - - self._has_content = root_item.rowCount() > 0 - self._is_refreshing = False - self.refreshed.emit() - - @property - def is_refreshing(self): - """Model is refreshing. - - Returns: - bool: Model is refreshing - """ - - return self._is_refreshing - - @property - def has_content(self): - """Model has content. - - Returns: - bools: Have at least one task. - """ - - return self._has_content - - def headerData(self, section, orientation, role): - # Show nice labels in the header - if ( - role == QtCore.Qt.DisplayRole - and orientation == QtCore.Qt.Horizontal - ): - if section == 0: - return "Tasks" - - return super(TasksModel, self).headerData( - section, orientation, role - ) - - -class TasksWidget(QtWidgets.QWidget): - """Tasks widget. - - Widget that handles tasks view, model and selection. - - Args: - controller (AbstractWorkfilesFrontend): Workfiles controller. - """ - - def __init__(self, controller, parent): - super(TasksWidget, self).__init__(parent) - - tasks_view = DeselectableTreeView(self) - tasks_view.setIndentation(0) - - tasks_model = TasksModel(controller) - tasks_proxy_model = QtCore.QSortFilterProxyModel() - tasks_proxy_model.setSourceModel(tasks_model) - - tasks_view.setModel(tasks_proxy_model) - - main_layout = QtWidgets.QHBoxLayout(self) - main_layout.setContentsMargins(0, 0, 0, 0) - main_layout.addWidget(tasks_view, 1) - - controller.register_event_callback( - "tasks.refresh.finished", - self._on_tasks_refresh_finished - ) - controller.register_event_callback( - "selection.folder.changed", - self._folder_selection_changed - ) - controller.register_event_callback( - "expected_selection_changed", - self._on_expected_selection_change - ) - - selection_model = tasks_view.selectionModel() - selection_model.selectionChanged.connect(self._on_selection_change) - - tasks_model.refreshed.connect(self._on_tasks_model_refresh) - - self._controller = controller - self._tasks_view = tasks_view - self._tasks_model = tasks_model - self._tasks_proxy_model = tasks_proxy_model - - self._selected_folder_id = None - - self._expected_selection_data = None - - def _clear(self): - self._tasks_model.clear() - - def _on_tasks_refresh_finished(self, event): - """Tasks were refreshed in controller. - - Ignore if refresh was triggered by tasks model, or refreshed folder is - not the same as currently selected folder. - - Args: - event (Event): Event object. - """ - - # Refresh only if current folder id is the same - if ( - event["sender"] == SENDER_NAME - or event["folder_id"] != self._selected_folder_id - ): - return - self._tasks_model.refresh(self._selected_folder_id) - - def _folder_selection_changed(self, event): - self._selected_folder_id = event["folder_id"] - self._tasks_model.refresh(self._selected_folder_id) - - def _on_tasks_model_refresh(self): - if not self._set_expected_selection(): - self._on_selection_change() - self._tasks_proxy_model.sort(0) - - def _set_expected_selection(self): - if self._expected_selection_data is None: - return False - folder_id = self._expected_selection_data["folder_id"] - task_name = self._expected_selection_data["task_name"] - self._expected_selection_data = None - model_folder_id = self._tasks_model.get_last_folder_id() - if folder_id != model_folder_id: - return False - if task_name is not None: - index = self._tasks_model.get_index_by_name(task_name) - if index.isValid(): - proxy_index = self._tasks_proxy_model.mapFromSource(index) - self._tasks_view.setCurrentIndex(proxy_index) - self._controller.expected_task_selected(folder_id, task_name) - return True - - def _on_expected_selection_change(self, event): - if event["task_selected"] or not event["folder_selected"]: - return - - model_folder_id = self._tasks_model.get_last_folder_id() - folder_id = event["folder_id"] - self._expected_selection_data = { - "task_name": event["task_name"], - "folder_id": folder_id, - } - - if folder_id != model_folder_id or self._tasks_model.is_refreshing: - return - self._set_expected_selection() - - def _get_selected_item_ids(self): - selection_model = self._tasks_view.selectionModel() - for index in selection_model.selectedIndexes(): - task_id = index.data(ITEM_ID_ROLE) - task_name = index.data(ITEM_NAME_ROLE) - parent_id = index.data(PARENT_ID_ROLE) - if task_name is not None: - return parent_id, task_id, task_name - return self._selected_folder_id, None, None - - def _on_selection_change(self): - # Don't trigger task change during refresh - # - a task was deselected if that happens - # - can cause crash triggered during tasks refreshing - if self._tasks_model.is_refreshing: - return - parent_id, task_id, task_name = self._get_selected_item_ids() - self._controller.set_selected_task(parent_id, task_id, task_name) diff --git a/openpype/tools/ayon_workfiles/widgets/window.py b/openpype/tools/ayon_workfiles/widgets/window.py index 6218d2dd06..eb2f2bc1c7 100644 --- a/openpype/tools/ayon_workfiles/widgets/window.py +++ b/openpype/tools/ayon_workfiles/widgets/window.py @@ -5,32 +5,16 @@ from openpype.tools.utils import ( PlaceholderLineEdit, MessageOverlayObject, ) -from openpype.tools.utils.lib import get_qta_icon_by_name_and_color +from openpype.tools.ayon_utils.widgets import FoldersWidget, TasksWidget from openpype.tools.ayon_workfiles.control import BaseWorkfileController +from openpype.tools.utils import GoToCurrentButton, RefreshButton from .side_panel import SidePanelWidget -from .folders_widget import FoldersWidget -from .tasks_widget import TasksWidget from .files_widget import FilesWidget from .utils import BaseOverlayFrame -# TODO move to utils -# from openpype.tools.utils.lib import ( -# get_refresh_icon, get_go_to_current_icon) -def get_refresh_icon(): - return get_qta_icon_by_name_and_color( - "fa.refresh", style.get_default_tools_icon_color() - ) - - -def get_go_to_current_icon(): - return get_qta_icon_by_name_and_color( - "fa.arrow-down", style.get_default_tools_icon_color() - ) - - class InvalidHostOverlay(BaseOverlayFrame): def __init__(self, parent): super(InvalidHostOverlay, self).__init__(parent) @@ -80,7 +64,7 @@ class WorkfilesToolWindow(QtWidgets.QWidget): self._default_window_flags = flags - self._folder_widget = None + self._folders_widget = None self._folder_filter_input = None self._files_widget = None @@ -100,7 +84,9 @@ class WorkfilesToolWindow(QtWidgets.QWidget): home_body_widget = QtWidgets.QWidget(home_page_widget) col_1_widget = self._create_col_1_widget(controller, parent) - tasks_widget = TasksWidget(controller, home_body_widget) + tasks_widget = TasksWidget( + controller, home_body_widget, handle_expected_selection=True + ) col_3_widget = self._create_col_3_widget(controller, home_body_widget) side_panel = SidePanelWidget(controller, home_body_widget) @@ -151,11 +137,11 @@ class WorkfilesToolWindow(QtWidgets.QWidget): self._on_open_finished ) controller.register_event_callback( - "controller.refresh.started", + "controller.reset.started", self._on_controller_refresh_started, ) controller.register_event_callback( - "controller.refresh.finished", + "controller.reset.finished", self._on_controller_refresh_finished, ) @@ -188,19 +174,12 @@ class WorkfilesToolWindow(QtWidgets.QWidget): folder_filter_input = PlaceholderLineEdit(header_widget) folder_filter_input.setPlaceholderText("Filter folders..") - go_to_current_btn = QtWidgets.QPushButton(header_widget) - go_to_current_btn.setIcon(get_go_to_current_icon()) - go_to_current_btn_sp = go_to_current_btn.sizePolicy() - go_to_current_btn_sp.setVerticalPolicy(QtWidgets.QSizePolicy.Minimum) - go_to_current_btn.setSizePolicy(go_to_current_btn_sp) + go_to_current_btn = GoToCurrentButton(header_widget) + refresh_btn = RefreshButton(header_widget) - refresh_btn = QtWidgets.QPushButton(header_widget) - refresh_btn.setIcon(get_refresh_icon()) - refresh_btn_sp = refresh_btn.sizePolicy() - refresh_btn_sp.setVerticalPolicy(QtWidgets.QSizePolicy.Minimum) - refresh_btn.setSizePolicy(refresh_btn_sp) - - folder_widget = FoldersWidget(controller, col_widget) + folder_widget = FoldersWidget( + controller, col_widget, handle_expected_selection=True + ) header_layout = QtWidgets.QHBoxLayout(header_widget) header_layout.setContentsMargins(0, 0, 0, 0) @@ -218,7 +197,7 @@ class WorkfilesToolWindow(QtWidgets.QWidget): refresh_btn.clicked.connect(self._on_refresh_clicked) self._folder_filter_input = folder_filter_input - self._folder_widget = folder_widget + self._folders_widget = folder_widget return col_widget @@ -300,7 +279,7 @@ class WorkfilesToolWindow(QtWidgets.QWidget): def refresh(self): """Trigger refresh of workfiles tool controller.""" - self._controller.refresh() + self._controller.reset() def showEvent(self, event): super(WorkfilesToolWindow, self).showEvent(event) @@ -338,7 +317,7 @@ class WorkfilesToolWindow(QtWidgets.QWidget): self._side_panel.set_published_mode(published_mode) def _on_folder_filter_change(self, text): - self._folder_widget.set_name_filter(text) + self._folders_widget.set_name_filter(text) def _on_go_to_current_clicked(self): self._controller.go_to_current_context() @@ -357,6 +336,10 @@ class WorkfilesToolWindow(QtWidgets.QWidget): if not self._host_is_valid: return + self._folders_widget.set_project_name( + self._controller.get_current_project_name() + ) + def _on_save_as_finished(self, event): if event["failed"]: self._overlay_messages_widget.add_message( From e340b9c7bc772d41c272bdbb4dfd42321c03df5d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Nov 2023 10:57:09 +0100 Subject: [PATCH 099/107] rename function 'asset_name' to 'prepare_scene_name' --- openpype/hosts/blender/api/plugin.py | 10 +++++----- openpype/hosts/blender/plugins/create/create_action.py | 2 +- openpype/hosts/blender/plugins/load/import_workfile.py | 2 +- openpype/hosts/blender/plugins/load/load_abc.py | 4 ++-- openpype/hosts/blender/plugins/load/load_action.py | 10 +++++----- openpype/hosts/blender/plugins/load/load_audio.py | 4 ++-- openpype/hosts/blender/plugins/load/load_blend.py | 4 ++-- openpype/hosts/blender/plugins/load/load_blendscene.py | 4 ++-- openpype/hosts/blender/plugins/load/load_camera_abc.py | 4 ++-- openpype/hosts/blender/plugins/load/load_camera_fbx.py | 4 ++-- openpype/hosts/blender/plugins/load/load_fbx.py | 4 ++-- .../hosts/blender/plugins/load/load_layout_json.py | 4 ++-- openpype/hosts/blender/plugins/load/load_look.py | 4 ++-- 13 files changed, 30 insertions(+), 30 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 7ac12b5549..d7155a1b53 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -28,7 +28,7 @@ from .lib import imprint VALID_EXTENSIONS = [".blend", ".json", ".abc", ".fbx"] -def asset_name( +def prepare_scene_name( asset: str, subset: str, namespace: Optional[str] = None ) -> str: """Return a consistent name for an asset.""" @@ -225,7 +225,7 @@ class BaseCreator(Creator): bpy.context.scene.collection.children.link(instances) # Create asset group - name = asset_name(instance_data["asset"], subset_name) + name = prepare_scene_name(instance_data["asset"], subset_name) if self.create_as_asset_group: # Create instance as empty instance_node = bpy.data.objects.new(name=name, object_data=None) @@ -298,7 +298,7 @@ class BaseCreator(Creator): "subset" in changes.changed_keys or "asset" in changes.changed_keys ): - name = asset_name(asset=data["asset"], subset=data["subset"]) + name = prepare_scene_name(asset=data["asset"], subset=data["subset"]) node.name = name imprint(node, data) @@ -454,7 +454,7 @@ class AssetLoader(LoaderPlugin): asset, subset ) namespace = namespace or f"{asset}_{unique_number}" - name = name or asset_name( + name = name or prepare_scene_name( asset, subset, unique_number ) @@ -483,7 +483,7 @@ class AssetLoader(LoaderPlugin): # asset = context["asset"]["name"] # subset = context["subset"]["name"] - # instance_name = asset_name(asset, subset, unique_number) + '_CON' + # instance_name = prepare_scene_name(asset, subset, unique_number) + '_CON' # return self._get_instance_collection(instance_name, nodes) diff --git a/openpype/hosts/blender/plugins/create/create_action.py b/openpype/hosts/blender/plugins/create/create_action.py index 0929778d78..caaa72fe8d 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -22,7 +22,7 @@ class CreateAction(plugin.BaseCreator): ) # Get instance name - name = plugin.asset_name(instance_data["asset"], subset_name) + name = plugin.prepare_scene_name(instance_data["asset"], subset_name) if pre_create_data.get("use_selection"): for obj in lib.get_selection(): diff --git a/openpype/hosts/blender/plugins/load/import_workfile.py b/openpype/hosts/blender/plugins/load/import_workfile.py index 4f5016d422..331f6a8bdb 100644 --- a/openpype/hosts/blender/plugins/load/import_workfile.py +++ b/openpype/hosts/blender/plugins/load/import_workfile.py @@ -7,7 +7,7 @@ def append_workfile(context, fname, do_import): asset = context['asset']['name'] subset = context['subset']['name'] - group_name = plugin.asset_name(asset, subset) + group_name = plugin.prepare_scene_name(asset, subset) # We need to preserve the original names of the scenes, otherwise, # if there are duplicate names in the current workfile, the imported diff --git a/openpype/hosts/blender/plugins/load/load_abc.py b/openpype/hosts/blender/plugins/load/load_abc.py index 8d1863d4d5..d7e82d1900 100644 --- a/openpype/hosts/blender/plugins/load/load_abc.py +++ b/openpype/hosts/blender/plugins/load/load_abc.py @@ -137,9 +137,9 @@ class CacheModelLoader(plugin.AssetLoader): asset = context["asset"]["name"] subset = context["subset"]["name"] - asset_name = plugin.asset_name(asset, subset) + asset_name = plugin.prepare_scene_name(asset, subset) unique_number = plugin.get_unique_number(asset, subset) - group_name = plugin.asset_name(asset, subset, unique_number) + group_name = plugin.prepare_scene_name(asset, subset, unique_number) namespace = namespace or f"{asset}_{unique_number}" containers = bpy.data.collections.get(AVALON_CONTAINERS) diff --git a/openpype/hosts/blender/plugins/load/load_action.py b/openpype/hosts/blender/plugins/load/load_action.py index 3447e67ebf..f7d32f92a5 100644 --- a/openpype/hosts/blender/plugins/load/load_action.py +++ b/openpype/hosts/blender/plugins/load/load_action.py @@ -7,7 +7,7 @@ from typing import Dict, List, Optional import bpy from openpype.pipeline import get_representation_path -import openpype.hosts.blender.api.plugin +from openpype.hosts.blender.api import plugin from openpype.hosts.blender.api.pipeline import ( containerise_existing, AVALON_PROPERTY, @@ -16,7 +16,7 @@ from openpype.hosts.blender.api.pipeline import ( logger = logging.getLogger("openpype").getChild("blender").getChild("load_action") -class BlendActionLoader(openpype.hosts.blender.api.plugin.AssetLoader): +class BlendActionLoader(plugin.AssetLoader): """Load action from a .blend file. Warning: @@ -46,8 +46,8 @@ class BlendActionLoader(openpype.hosts.blender.api.plugin.AssetLoader): libpath = self.filepath_from_context(context) asset = context["asset"]["name"] subset = context["subset"]["name"] - lib_container = openpype.hosts.blender.api.plugin.asset_name(asset, subset) - container_name = openpype.hosts.blender.api.plugin.asset_name( + lib_container = plugin.prepare_scene_name(asset, subset) + container_name = plugin.prepare_scene_name( asset, subset, namespace ) @@ -152,7 +152,7 @@ class BlendActionLoader(openpype.hosts.blender.api.plugin.AssetLoader): assert libpath.is_file(), ( f"The file doesn't exist: {libpath}" ) - assert extension in openpype.hosts.blender.api.plugin.VALID_EXTENSIONS, ( + assert extension in plugin.VALID_EXTENSIONS, ( f"Unsupported file: {libpath}" ) diff --git a/openpype/hosts/blender/plugins/load/load_audio.py b/openpype/hosts/blender/plugins/load/load_audio.py index ac8f363316..1e5bd39a32 100644 --- a/openpype/hosts/blender/plugins/load/load_audio.py +++ b/openpype/hosts/blender/plugins/load/load_audio.py @@ -42,9 +42,9 @@ class AudioLoader(plugin.AssetLoader): asset = context["asset"]["name"] subset = context["subset"]["name"] - asset_name = plugin.asset_name(asset, subset) + asset_name = plugin.prepare_scene_name(asset, subset) unique_number = plugin.get_unique_number(asset, subset) - group_name = plugin.asset_name(asset, subset, unique_number) + group_name = plugin.prepare_scene_name(asset, subset, unique_number) namespace = namespace or f"{asset}_{unique_number}" avalon_container = bpy.data.collections.get(AVALON_CONTAINERS) diff --git a/openpype/hosts/blender/plugins/load/load_blend.py b/openpype/hosts/blender/plugins/load/load_blend.py index 8b1af5a0da..f437e66795 100644 --- a/openpype/hosts/blender/plugins/load/load_blend.py +++ b/openpype/hosts/blender/plugins/load/load_blend.py @@ -133,9 +133,9 @@ class BlendLoader(plugin.AssetLoader): representation = str(context["representation"]["_id"]) - asset_name = plugin.asset_name(asset, subset) + asset_name = plugin.prepare_scene_name(asset, subset) unique_number = plugin.get_unique_number(asset, subset) - group_name = plugin.asset_name(asset, subset, unique_number) + group_name = plugin.prepare_scene_name(asset, subset, unique_number) namespace = namespace or f"{asset}_{unique_number}" avalon_container = bpy.data.collections.get(AVALON_CONTAINERS) diff --git a/openpype/hosts/blender/plugins/load/load_blendscene.py b/openpype/hosts/blender/plugins/load/load_blendscene.py index 2c955af9e8..6cc7f39d03 100644 --- a/openpype/hosts/blender/plugins/load/load_blendscene.py +++ b/openpype/hosts/blender/plugins/load/load_blendscene.py @@ -85,9 +85,9 @@ class BlendSceneLoader(plugin.AssetLoader): except ValueError: family = "model" - asset_name = plugin.asset_name(asset, subset) + asset_name = plugin.prepare_scene_name(asset, subset) unique_number = plugin.get_unique_number(asset, subset) - group_name = plugin.asset_name(asset, subset, unique_number) + group_name = plugin.prepare_scene_name(asset, subset, unique_number) namespace = namespace or f"{asset}_{unique_number}" avalon_container = bpy.data.collections.get(AVALON_CONTAINERS) diff --git a/openpype/hosts/blender/plugins/load/load_camera_abc.py b/openpype/hosts/blender/plugins/load/load_camera_abc.py index 05d3fb764d..ecd6bb98f1 100644 --- a/openpype/hosts/blender/plugins/load/load_camera_abc.py +++ b/openpype/hosts/blender/plugins/load/load_camera_abc.py @@ -87,9 +87,9 @@ class AbcCameraLoader(plugin.AssetLoader): asset = context["asset"]["name"] subset = context["subset"]["name"] - asset_name = plugin.asset_name(asset, subset) + asset_name = plugin.prepare_scene_name(asset, subset) unique_number = plugin.get_unique_number(asset, subset) - group_name = plugin.asset_name(asset, subset, unique_number) + group_name = plugin.prepare_scene_name(asset, subset, unique_number) namespace = namespace or f"{asset}_{unique_number}" avalon_container = bpy.data.collections.get(AVALON_CONTAINERS) diff --git a/openpype/hosts/blender/plugins/load/load_camera_fbx.py b/openpype/hosts/blender/plugins/load/load_camera_fbx.py index 3cca6e7fd3..2d53d3e573 100644 --- a/openpype/hosts/blender/plugins/load/load_camera_fbx.py +++ b/openpype/hosts/blender/plugins/load/load_camera_fbx.py @@ -90,9 +90,9 @@ class FbxCameraLoader(plugin.AssetLoader): asset = context["asset"]["name"] subset = context["subset"]["name"] - asset_name = plugin.asset_name(asset, subset) + asset_name = plugin.prepare_scene_name(asset, subset) unique_number = plugin.get_unique_number(asset, subset) - group_name = plugin.asset_name(asset, subset, unique_number) + group_name = plugin.prepare_scene_name(asset, subset, unique_number) namespace = namespace or f"{asset}_{unique_number}" avalon_container = bpy.data.collections.get(AVALON_CONTAINERS) diff --git a/openpype/hosts/blender/plugins/load/load_fbx.py b/openpype/hosts/blender/plugins/load/load_fbx.py index e129ea6754..8fce53a5d5 100644 --- a/openpype/hosts/blender/plugins/load/load_fbx.py +++ b/openpype/hosts/blender/plugins/load/load_fbx.py @@ -134,9 +134,9 @@ class FbxModelLoader(plugin.AssetLoader): asset = context["asset"]["name"] subset = context["subset"]["name"] - asset_name = plugin.asset_name(asset, subset) + asset_name = plugin.prepare_scene_name(asset, subset) unique_number = plugin.get_unique_number(asset, subset) - group_name = plugin.asset_name(asset, subset, unique_number) + group_name = plugin.prepare_scene_name(asset, subset, unique_number) namespace = namespace or f"{asset}_{unique_number}" avalon_container = bpy.data.collections.get(AVALON_CONTAINERS) diff --git a/openpype/hosts/blender/plugins/load/load_layout_json.py b/openpype/hosts/blender/plugins/load/load_layout_json.py index a941c77a8e..748ac619b6 100644 --- a/openpype/hosts/blender/plugins/load/load_layout_json.py +++ b/openpype/hosts/blender/plugins/load/load_layout_json.py @@ -149,9 +149,9 @@ class JsonLayoutLoader(plugin.AssetLoader): asset = context["asset"]["name"] subset = context["subset"]["name"] - asset_name = plugin.asset_name(asset, subset) + asset_name = plugin.prepare_scene_name(asset, subset) unique_number = plugin.get_unique_number(asset, subset) - group_name = plugin.asset_name(asset, subset, unique_number) + group_name = plugin.prepare_scene_name(asset, subset, unique_number) namespace = namespace or f"{asset}_{unique_number}" avalon_container = bpy.data.collections.get(AVALON_CONTAINERS) diff --git a/openpype/hosts/blender/plugins/load/load_look.py b/openpype/hosts/blender/plugins/load/load_look.py index c121f55633..8d3118d83b 100644 --- a/openpype/hosts/blender/plugins/load/load_look.py +++ b/openpype/hosts/blender/plugins/load/load_look.py @@ -96,14 +96,14 @@ class BlendLookLoader(plugin.AssetLoader): asset = context["asset"]["name"] subset = context["subset"]["name"] - lib_container = plugin.asset_name( + lib_container = plugin.prepare_scene_name( asset, subset ) unique_number = plugin.get_unique_number( asset, subset ) namespace = namespace or f"{asset}_{unique_number}" - container_name = plugin.asset_name( + container_name = plugin.prepare_scene_name( asset, subset, unique_number ) From e8f7f146ab670e5c21a8374143456858df80260b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Nov 2023 11:02:29 +0100 Subject: [PATCH 100/107] formatting fix --- openpype/hosts/blender/api/plugin.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index d7155a1b53..8d33187da3 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -298,7 +298,9 @@ class BaseCreator(Creator): "subset" in changes.changed_keys or "asset" in changes.changed_keys ): - name = prepare_scene_name(asset=data["asset"], subset=data["subset"]) + name = prepare_scene_name( + asset=data["asset"], subset=data["subset"] + ) node.name = name imprint(node, data) @@ -483,7 +485,9 @@ class AssetLoader(LoaderPlugin): # asset = context["asset"]["name"] # subset = context["subset"]["name"] - # instance_name = prepare_scene_name(asset, subset, unique_number) + '_CON' + # instance_name = prepare_scene_name( + # asset, subset, unique_number + # ) + '_CON' # return self._get_instance_collection(instance_name, nodes) From 781a1047de260a8afa570b2e67c957fb5622fac4 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Thu, 16 Nov 2023 12:45:04 +0000 Subject: [PATCH 101/107] [Automated] Release --- CHANGELOG.md | 380 ++++++++++++++++++++++++++++++++++++++++++++ openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 382 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3daf581ac..5909c26f7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,386 @@ # Changelog +## [3.17.6](https://github.com/ynput/OpenPype/tree/3.17.6) + + +[Full Changelog](https://github.com/ynput/OpenPype/compare/3.17.5...3.17.6) + +### **🚀 Enhancements** + + +
+Testing: Validate Maya Logs #5775 + +This PR adds testing of the logs within Maya such as Python and Pyblish errors.The reason why we need to touch so many files outside of Maya is because of the pyblish errors below; +``` +pyblish (ERROR) (line: 1371) pyblish.plugin: +Skipped: "collect_otio_frame_ranges" (No module named 'opentimelineio') +# Error: pyblish.plugin : Skipped: "collect_otio_frame_ranges" (No module named 'opentimelineio') # +pyblish (ERROR) (line: 1371) pyblish.plugin: +Skipped: "collect_otio_review" (No module named 'opentimelineio') +# Error: pyblish.plugin : Skipped: "collect_otio_review" (No module named 'opentimelineio') # +pyblish (ERROR) (line: 1371) pyblish.plugin: +Skipped: "collect_otio_subset_resources" (No module named 'opentimelineio') +# Error: pyblish.plugin : Skipped: "collect_otio_subset_resources" (No module named 'opentimelineio') # +pyblish (ERROR) (line: 1371) pyblish.plugin: +Skipped: "extract_otio_audio_tracks" (No module named 'opentimelineio') +# Error: pyblish.plugin : Skipped: "extract_otio_audio_tracks" (No module named 'opentimelineio') # +pyblish (ERROR) (line: 1371) pyblish.plugin: +Skipped: "extract_otio_file" (No module named 'opentimelineio') +# Error: pyblish.plugin : Skipped: "extract_otio_file" (No module named 'opentimelineio') # +pyblish (ERROR) (line: 1371) pyblish.plugin: +Skipped: "extract_otio_review" (No module named 'opentimelineio') +# Error: pyblish.plugin : Skipped: "extract_otio_review" (No module named 'opentimelineio') # +pyblish (ERROR) (line: 1371) pyblish.plugin: +Skipped: "extract_otio_trimming_video" (No module named 'opentimelineio') +# Error: pyblish.plugin : Skipped: "extract_otio_trimming_video" (No module named 'opentimelineio') # +pyblish (ERROR) (line: 1371) pyblish.plugin: +Skipped: "submit_blender_deadline" (No module named 'bpy') +# Error: pyblish.plugin : Skipped: "submit_blender_deadline" (No module named 'bpy') # +pyblish (ERROR) (line: 1371) pyblish.plugin: +Skipped: "submit_houdini_remote_publish" (No module named 'hou') +# Error: pyblish.plugin : Skipped: "submit_houdini_remote_publish" (No module named 'hou') # +pyblish (ERROR) (line: 1371) pyblish.plugin: +Skipped: "submit_houdini_render_deadline" (No module named 'hou') +# Error: pyblish.plugin : Skipped: "submit_houdini_render_deadline" (No module named 'hou') # +pyblish (ERROR) (line: 1371) pyblish.plugin: +Skipped: "submit_max_deadline" (No module named 'pymxs') +# Error: pyblish.plugin : Skipped: "submit_max_deadline" (No module named 'pymxs') # +pyblish (ERROR) (line: 1371) pyblish.plugin: +Skipped: "submit_nuke_deadline" (No module named 'nuke') +# Error: pyblish.plugin : Skipped: "submit_nuke_deadline" (No module named 'nuke') # +``` +We also needed to `stdout` and `stderr` from the launched application to capture the output.Split from #5644.Dependent on #5734 + + +___ + +
+ + +
+Maya: Render Settings cleanup remove global `RENDER_ATTRS` #5801 + +Remove global `lib.RENDER_ATTRS` and implement a `RenderSettings.get_padding_attr(renderer)` method instead. + + +___ + +
+ + +
+Testing: Ingest expected files and input workfile #5840 + +This ingests the Maya workfile from the Drive storage. Have changed the format to MayaAscii so its easier to see what changes are happening in a PR. This meant changing the expected files and database entries as well. + + +___ + +
+ + +
+Chore: Create plugin auto-apply settings #5908 + +Create plugins can auto-apply settings. + + +___ + +
+ + +
+Resolve: Add save current file button + "Save" shortcut when menu is active #5691 + +Adds a "Save current file" to the OpenPype menu.Also adds a "Save" shortcut key sequence (CTRL+S on Windows) to the button, so that clicking CTRL+S when the menu is active will save the current workfile. However this of course does not work if the menu does not receive the key press event (e.g. when Resolve UI is active instead)Resolves #5684 + + +___ + +
+ + +
+Reference USD file as maya native geometry #5781 + +Add MayaUsdReferenceLoader to reference USD as Maya native geometry using `mayaUSDImport` file translator. + + +___ + +
+ + +
+Max: Bug fix on wrong aspect ratio and viewport not being maximized during context in review family #5839 + +This PR will fix the bug on wrong aspect ratio and viewport not being maximized when creating preview animationBesides, the support of tga image format and the options for AA quality are implemented in this PR + + +___ + +
+ + +
+Blender: Incorporate blender "Collections" into Publish/Load #5841 + +Allow `blendScene` family to include collections. + + +___ + +
+ + +
+Max: Allows user preset the setting of preview animation in OP/AYON Setting #5859 + +Allows user preset the setting of preview animation in OP/AYON Setting for review family. +- [x] Openpype +- [x] AYON + + +___ + +
+ + +
+Publisher: Center publisher window on first show #5877 + +Move publisher window to center of a screen on first show. + + +___ + +
+ + +
+Publisher: Instance context changes confirm works #5881 + +Confirmation of context changes in publisher on existing instances does not cause glitches. + + +___ + +
+ + +
+AYON workfiles tools: Revisit workfiles tool #5897 + +Revisited workfiles tool for AYON mode to reuse common models and widgets. + + +___ + +
+ + +
+Nuke: updated colorspace settings #5906 + +Updating nuke colorspace settings into more convenient way with usage of ocio config roles rather then particular colorspace names. This way we should not have troubles to switch between linear Rec709 or ACES configs without any additional settings changes. + + +___ + +
+ + +
+Blender: Refactor to new publisher #5910 + +Refactor Blender integration to use the new publisher + + +___ + +
+ + +
+Enhancement: Some publish logs cosmetics #5917 + +General logging message tweaks: +- Sort some lists of folder/filenames so they appear sorted in the logs +- Fix some grammar / typos +- In some cases provide slightly more information in a log + + +___ + +
+ + +
+Blender: Better name of 'asset_name' function #5927 + +Renamed function `asset_name` to `prepare_scene_name`. + + +___ + +
+ +### **🐛 Bug fixes** + + +
+Maya: Bug fix the fbx animation export errored out when the skeletonAnim set is empty #5875 + +Resolve this bug discordIf the skeletonAnim SET is empty and fbx animation collect, the fbx animation extractor would skip the fbx extraction + + +___ + +
+ + +
+Bugfix: fix few typos in houdini's and Maya's Ayon settings #5882 + +Fixing few typos +- [x] Maya unreal static mesh +- [x] Houdini static mesh +- [x] Houdini collect asset handles + + +___ + +
+ + +
+Bugfix: Ayon Deadline env vars + error message on no executable found #5815 + +Fix some Ayon x Deadline issues as came up in this topic: +- missing Environment Variables issue explained here for `deadlinePlugin.RunProcess` for the AYON _extract environments_ call. +- wrong error formatting described here with a `;` between each character like this: `Ayon executable was not found in the semicolon separated list "C;:;/;P;r;o;g;r;a;m; ;F;i;l;e;s;/;Y;n;p;u;t;/;A;Y;O;N; ;1;.;0;.;0;-;b;e;t;a;.;5;/;a;y;o;n;_;c;o;n;s;o;l;e;.;e;x;e". The path to the render executable can be configured from the Plugin Configuration in the Deadline Monitor.` + + +___ + +
+ + +
+AYON: Fix bundles access in settings #5856 + +Fixed access to bundles data in settings to define correct develop variant. + + +___ + +
+ + +
+AYON 3dsMax settings: 'ValidateAttributes' settings converte only if available #5878 + +Convert `ValidateAttributes` settings only if are available in AYON settings. + + +___ + +
+ + +
+AYON: Fix TrayPublisher editorial settings #5880 + +Fixing Traypublisher settings for adding task in simple editorial. + + +___ + +
+ + +
+TrayPublisher: editorial frame range check not needed #5884 + +Validator for frame ranges is not needed during editorial publishing since entity data are not yet in database. + + +___ + +
+ + +
+Update houdini license validator #5886 + +As reported in this community commentHoudini USD publishing is only restricted in Houdini apprentice. + + +___ + +
+ + +
+Blender: Fix blend extraction and packed images #5888 + +Fixed a with blend extractor and packed images. + + +___ + +
+ + +
+AYON: Initialize connection with all information #5890 + +Create global AYON api connection with all informations all the time. + + +___ + +
+ + +
+AYON: Scene inventory tool without site sync #5896 + +Skip 'get_site_icons' if site sync addon is disabled. + + +___ + +
+ + +
+Publish report tool: Fix PySide6 #5898 + +Use constants from classes instead of objects. + + +___ + +
+ + +
+fusion: removing hardcoded template name for saver #5907 + +Fusion is not hardcoded for `render` anatomy template only anymore. This was blocking AYON deployment. + + +___ + +
+ + + + ## [3.17.5](https://github.com/ynput/OpenPype/tree/3.17.5) diff --git a/openpype/version.py b/openpype/version.py index b7394c203d..adb62abd9d 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.17.6-nightly.3" +__version__ = "3.17.6" diff --git a/pyproject.toml b/pyproject.toml index c6f4880cdd..21ba7d1199 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.17.5" # OpenPype +version = "3.17.6" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 95209df395b995782cc5bba872ca34d70bec21a7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 16 Nov 2023 12:46:03 +0000 Subject: [PATCH 102/107] 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 86e3638ffe..6f1b01bd2f 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.17.6 - 3.17.6-nightly.3 - 3.17.6-nightly.2 - 3.17.6-nightly.1 @@ -134,7 +135,6 @@ body: - 3.15.2-nightly.3 - 3.15.2-nightly.2 - 3.15.2-nightly.1 - - 3.15.1 validations: required: true - type: dropdown From 05673e934d6c872c953243d059d468a3bc9f1d77 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 16 Nov 2023 15:26:52 +0100 Subject: [PATCH 103/107] AYON: Loader tool bugs hunt (#5915) * handle cases when server is missing product type in project database * ignore representations which do not match uuid * add comments --- openpype/tools/ayon_loader/control.py | 18 ++++++++++++++++-- openpype/tools/ayon_loader/models/products.py | 19 ++++++++++++++++++- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/openpype/tools/ayon_loader/control.py b/openpype/tools/ayon_loader/control.py index 2b779f5c2e..c38973d0b3 100644 --- a/openpype/tools/ayon_loader/control.py +++ b/openpype/tools/ayon_loader/control.py @@ -1,4 +1,5 @@ import logging +import uuid import ayon_api @@ -314,8 +315,21 @@ class LoaderController(BackendLoaderController, FrontendLoaderController): containers = self._host.get_containers() else: containers = self._host.ls() - repre_ids = {c.get("representation") for c in containers} - repre_ids.discard(None) + repre_ids = set() + for container in containers: + repre_id = container.get("representation") + # Ignore invalid representation ids. + # - invalid representation ids may be available if e.g. is + # opened scene from OpenPype whe 'ObjectId' was used instead + # of 'uuid'. + # NOTE: Server call would crash if there is any invalid id. + # That would cause crash we won't get any information. + try: + uuid.UUID(repre_id) + repre_ids.add(repre_id) + except ValueError: + pass + product_ids = self._products_model.get_product_ids_by_repre_ids( project_name, repre_ids ) diff --git a/openpype/tools/ayon_loader/models/products.py b/openpype/tools/ayon_loader/models/products.py index 33023cc164..816dabaf90 100644 --- a/openpype/tools/ayon_loader/models/products.py +++ b/openpype/tools/ayon_loader/models/products.py @@ -77,7 +77,15 @@ def product_item_from_entity( product_attribs = product_entity["attrib"] group = product_attribs.get("productGroup") product_type = product_entity["productType"] - product_type_item = product_type_items_by_name[product_type] + product_type_item = product_type_items_by_name.get(product_type) + # NOTE This is needed for cases when products were not created on server + # using api functions. In that case product type item may not be + # available and we need to create a default. + if product_type_item is None: + product_type_item = create_default_product_type_item(product_type) + # Cache the item for future use + product_type_items_by_name[product_type] = product_type_item + product_type_icon = product_type_item.icon product_icon = { @@ -117,6 +125,15 @@ def product_type_item_from_data(product_type_data): return ProductTypeItem(product_type_data["name"], icon, True) +def create_default_product_type_item(product_type): + icon = { + "type": "awesome-font", + "name": "fa.folder", + "color": "#0091B2", + } + return ProductTypeItem(product_type, icon, True) + + class ProductsModel: """Model for products, version and representation. From a3fc30b408161b6d6aa116142661acffbf9659ee Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 16 Nov 2023 15:27:33 +0100 Subject: [PATCH 104/107] use project, task and host name from context data (#5918) --- .../hosts/photoshop/plugins/publish/collect_auto_image.py | 4 ++-- .../hosts/photoshop/plugins/publish/collect_auto_review.py | 4 ++-- .../hosts/photoshop/plugins/publish/collect_auto_workfile.py | 4 ++-- .../modules/deadline/plugins/publish/submit_publish_job.py | 3 ++- .../modules/slack/plugins/publish/collect_slack_family.py | 2 +- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/photoshop/plugins/publish/collect_auto_image.py b/openpype/hosts/photoshop/plugins/publish/collect_auto_image.py index 77f1a3e91f..d4b5f480b1 100644 --- a/openpype/hosts/photoshop/plugins/publish/collect_auto_image.py +++ b/openpype/hosts/photoshop/plugins/publish/collect_auto_image.py @@ -22,9 +22,9 @@ class CollectAutoImage(pyblish.api.ContextPlugin): self.log.debug("Auto image instance found, won't create new") return - project_name = context.data["anatomyData"]["project"]["name"] + project_name = context.data["projectName"] proj_settings = context.data["project_settings"] - task_name = context.data["anatomyData"]["task"]["name"] + task_name = context.data["task"] host_name = context.data["hostName"] asset_doc = context.data["assetEntity"] asset_name = asset_doc["name"] diff --git a/openpype/hosts/photoshop/plugins/publish/collect_auto_review.py b/openpype/hosts/photoshop/plugins/publish/collect_auto_review.py index 82ba0ac09c..8964582a45 100644 --- a/openpype/hosts/photoshop/plugins/publish/collect_auto_review.py +++ b/openpype/hosts/photoshop/plugins/publish/collect_auto_review.py @@ -60,9 +60,9 @@ class CollectAutoReview(pyblish.api.ContextPlugin): variant = (context.data.get("variant") or auto_creator["default_variant"]) - project_name = context.data["anatomyData"]["project"]["name"] + project_name = context.data["projectName"] proj_settings = context.data["project_settings"] - task_name = context.data["anatomyData"]["task"]["name"] + task_name = context.data["task"] host_name = context.data["hostName"] asset_doc = context.data["assetEntity"] asset_name = asset_doc["name"] diff --git a/openpype/hosts/photoshop/plugins/publish/collect_auto_workfile.py b/openpype/hosts/photoshop/plugins/publish/collect_auto_workfile.py index 01dc50af40..d3cc8d44d0 100644 --- a/openpype/hosts/photoshop/plugins/publish/collect_auto_workfile.py +++ b/openpype/hosts/photoshop/plugins/publish/collect_auto_workfile.py @@ -51,7 +51,7 @@ class CollectAutoWorkfile(pyblish.api.ContextPlugin): self.log.debug("Workfile instance disabled") return - project_name = context.data["anatomyData"]["project"]["name"] + project_name = context.data["projectName"] proj_settings = context.data["project_settings"] auto_creator = proj_settings.get( "photoshop", {}).get( @@ -66,7 +66,7 @@ class CollectAutoWorkfile(pyblish.api.ContextPlugin): variant = (context.data.get("variant") or auto_creator["default_variant"]) - task_name = context.data["anatomyData"]["task"]["name"] + task_name = context.data["task"] host_name = context.data["hostName"] asset_doc = context.data["assetEntity"] asset_name = asset_doc["name"] diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 6ed5819f2b..c9019b496b 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -708,6 +708,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, """ project_name = context.data["projectName"] + host_name = context.data["hostName"] if not version: version = get_last_version_by_subset_name( project_name, @@ -719,7 +720,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, else: version = get_versioning_start( project_name, - template_data["app"], + host_name, task_name=template_data["task"]["name"], task_type=template_data["task"]["type"], family="render", diff --git a/openpype/modules/slack/plugins/publish/collect_slack_family.py b/openpype/modules/slack/plugins/publish/collect_slack_family.py index b3e7bbdcec..cbed2d1012 100644 --- a/openpype/modules/slack/plugins/publish/collect_slack_family.py +++ b/openpype/modules/slack/plugins/publish/collect_slack_family.py @@ -38,7 +38,7 @@ class CollectSlackFamilies(pyblish.api.InstancePlugin, "families": family, "tasks": task_data.get("name"), "task_types": task_data.get("type"), - "hosts": instance.data["anatomyData"]["app"], + "hosts": instance.context.data["hostName"], "subsets": instance.data["subset"] } profile = filter_profiles(self.profiles, key_values, From 7c86115b7e40625e670e0fa69ca5e28929b3ee57 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 16 Nov 2023 15:41:41 +0100 Subject: [PATCH 105/107] AYON: Handle staging templates category (#5905) * do template replacements on all templates * handle and convert staging templates * use key 'staging' instead of 'staging_dir' --- openpype/client/server/conversion_utils.py | 45 ++++++++++++++++------ 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/openpype/client/server/conversion_utils.py b/openpype/client/server/conversion_utils.py index 8c18cb1c13..51af99e722 100644 --- a/openpype/client/server/conversion_utils.py +++ b/openpype/client/server/conversion_utils.py @@ -138,16 +138,22 @@ def _template_replacements_to_v3(template): ) -def _convert_template_item(template): - # Others won't have 'directory' - if "directory" not in template: - return - folder = _template_replacements_to_v3(template.pop("directory")) - template["folder"] = folder - template["file"] = _template_replacements_to_v3(template["file"]) - template["path"] = "/".join( - (folder, template["file"]) - ) +def _convert_template_item(template_item): + for key, value in tuple(template_item.items()): + template_item[key] = _template_replacements_to_v3(value) + + # Change 'directory' to 'folder' + if "directory" in template_item: + template_item["folder"] = template_item.pop("directory") + + if ( + "path" not in template_item + and "file" in template_item + and "folder" in template_item + ): + template_item["path"] = "/".join( + (template_item["folder"], template_item["file"]) + ) def _fill_template_category(templates, cat_templates, cat_key): @@ -212,10 +218,27 @@ def convert_v4_project_to_v3(project): _convert_template_item(template) new_others_templates[name] = template + staging_templates = templates.pop("staging", None) + # Key 'staging_directories' is legacy key that changed + # to 'staging_dir' + _legacy_staging_templates = templates.pop("staging_directories", None) + if staging_templates is None: + staging_templates = _legacy_staging_templates + + if staging_templates is None: + staging_templates = {} + + # Prefix all staging template names with 'staging_' prefix + # and add them to 'others' + for name, template in staging_templates.items(): + _convert_template_item(template) + new_name = "staging_{}".format(name) + new_others_templates[new_name] = template + for key in ( "work", "publish", - "hero" + "hero", ): cat_templates = templates.pop(key) _fill_template_category(templates, cat_templates, key) From fe8711cf7804336593de7d0ac1ff94ec6e1b3b13 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 16 Nov 2023 15:42:44 +0100 Subject: [PATCH 106/107] Publisher: Bugfixes and enhancements (#5924) * fix logger getter * catch crashes of create plugin initializations * use 'product' instead of 'subset' in AYON mode * fix import --- openpype/tools/publisher/control.py | 19 ++++++++++++++----- .../publisher/widgets/overview_widget.py | 7 ++++++- openpype/tools/publisher/widgets/widgets.py | 10 ++++++++-- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index a6264303d5..3192fe949f 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -1453,7 +1453,7 @@ class BasePublisherController(AbstractPublisherController): """ if self._log is None: - self._log = logging.getLogget(self.__class__.__name__) + self._log = logging.getLogger(self.__class__.__name__) return self._log @property @@ -1881,10 +1881,19 @@ class PublisherController(BasePublisherController): self._emit_event("plugins.refresh.finished") def _collect_creator_items(self): - return { - identifier: CreatorItem.from_creator(creator) - for identifier, creator in self._create_context.creators.items() - } + # TODO add crashed initialization of create plugins to report + output = {} + for identifier, creator in self._create_context.creators.items(): + try: + output[identifier] = CreatorItem.from_creator(creator) + except Exception: + self.log.error( + "Failed to create creator item for '%s'", + identifier, + exc_info=True + ) + + return output def _reset_instances(self): """Reset create instances.""" diff --git a/openpype/tools/publisher/widgets/overview_widget.py b/openpype/tools/publisher/widgets/overview_widget.py index 778aa1139f..10151250f6 100644 --- a/openpype/tools/publisher/widgets/overview_widget.py +++ b/openpype/tools/publisher/widgets/overview_widget.py @@ -1,5 +1,7 @@ from qtpy import QtWidgets, QtCore +from openpype import AYON_SERVER_ENABLED + from .border_label_widget import BorderedLabelWidget from .card_view_widgets import InstanceCardView @@ -35,7 +37,10 @@ class OverviewWidget(QtWidgets.QFrame): # --- Created Subsets/Instances --- # Common widget for creation and overview subset_views_widget = BorderedLabelWidget( - "Subsets to publish", subset_content_widget + "{} to publish".format( + "Products" if AYON_SERVER_ENABLED else "Subsets" + ), + subset_content_widget ) subset_view_cards = InstanceCardView(controller, subset_views_widget) diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index 6dbeaad821..1860287fcf 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -210,7 +210,9 @@ class CreateBtn(PublishIconBtn): def __init__(self, parent=None): icon_path = get_icon_path("create") super(CreateBtn, self).__init__(icon_path, "Create", parent) - self.setToolTip("Create new subset/s") + self.setToolTip("Create new {}/s".format( + "product" if AYON_SERVER_ENABLED else "subset" + )) self.setLayoutDirection(QtCore.Qt.RightToLeft) @@ -655,7 +657,11 @@ class TasksCombobox(QtWidgets.QComboBox): self._proxy_model.set_filter_empty(invalid) if invalid: self._set_is_valid(False) - self.set_text("< One or more subsets require Task selected >") + self.set_text( + "< One or more {} require Task selected >".format( + "products" if AYON_SERVER_ENABLED else "subsets" + ) + ) else: self.set_text(None) From 19f0a77966dbc84898e48425b06015416c48eeef Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 16 Nov 2023 17:22:04 +0100 Subject: [PATCH 107/107] Nuke: Change context label enhancement (#5887) * use QAction to change label of context action * do not handle context label change in nuke assist --- openpype/hosts/nuke/api/lib.py | 2 +- openpype/hosts/nuke/api/pipeline.py | 35 ++++++++++++++--------------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 8b1ba0ab0d..de5d13d347 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -120,7 +120,7 @@ def deprecated(new_destination): class Context: main_window = None - context_label = None + context_action_item = None project_name = os.getenv("AVALON_PROJECT") # Workfile related code workfiles_launched = False diff --git a/openpype/hosts/nuke/api/pipeline.py b/openpype/hosts/nuke/api/pipeline.py index ba4d66ab63..7bc17ff504 100644 --- a/openpype/hosts/nuke/api/pipeline.py +++ b/openpype/hosts/nuke/api/pipeline.py @@ -236,9 +236,13 @@ def _install_menu(): if not ASSIST: label = get_context_label() - Context.context_label = label - context_action = menu.addCommand(label) - context_action.setEnabled(False) + context_action_item = menu.addCommand("Context") + context_action_item.setEnabled(False) + + Context.context_action_item = context_action_item + + context_action = context_action_item.action() + context_action.setText(label) # add separator after context label menu.addSeparator() @@ -348,26 +352,21 @@ def _install_menu(): def change_context_label(): - menubar = nuke.menu("Nuke") - menu = menubar.findItem(MENU_LABEL) + if ASSIST: + return - label = get_context_label() + context_action_item = Context.context_action_item + if context_action_item is None: + return + context_action = context_action_item.action() - rm_item = [ - (i, item) for i, item in enumerate(menu.items()) - if Context.context_label in item.name() - ][0] + old_label = context_action.text() + new_label = get_context_label() - menu.removeItem(rm_item[1].name()) - - context_action = menu.addCommand( - label, - index=(rm_item[0]) - ) - context_action.setEnabled(False) + context_action.setText(new_label) log.info("Task label changed from `{}` to `{}`".format( - Context.context_label, label)) + old_label, new_label)) def add_shortcuts_from_presets():