From 507adfa9368f24d9eaff47e5d386cad08671ab9e Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Mon, 5 Jun 2023 18:39:32 +0200 Subject: [PATCH 001/121] 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 208c11cfe8f..22c590d4bd6 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 fb87d08cce3..221c8d89364 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -6,7 +6,8 @@ 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 @@ def process(self): 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 0203ba74c08..effbccd4304 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(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 bc2840952b6..1b9bbcacd9c 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.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 7a770a3e77e..c72f2b92ff3 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.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 73ed6832560..ba75df6735d 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.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 51fc6683f6e..a7e71622ea9 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.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 65cf18472d4..0555d956de0 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.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 914f249891e..58c26e03244 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.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 08cc46ee3e3..7a0393f0ba5 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.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 d4f26b4f3cf..fba9a861a00 100644 --- a/openpype/hosts/blender/plugins/publish/extract_blend.py +++ b/openpype/hosts/blender/plugins/publish/extract_blend.py @@ -5,7 +5,7 @@ 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/121] 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 221c8d89364..39d0f5e6629 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -152,6 +152,29 @@ def process(self): 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 @@ def create( 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/121] 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 39d0f5e6629..9a982c45e77 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -10,7 +10,11 @@ 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 @@ def cache_subsets(shared_data): 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 @@ def create( 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/121] 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 9a982c45e77..c13af363c5f 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -264,7 +264,7 @@ def collect_instances(self): ) # 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 effbccd4304..5f4ded36882 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -10,6 +10,7 @@ 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 1b9bbcacd9c..277c588610a 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -10,6 +10,7 @@ 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 c72f2b92ff3..9086c44c5ff 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -10,6 +10,7 @@ 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 ba75df6735d..ae567e6495d 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -10,6 +10,7 @@ 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 a7e71622ea9..46196ab3839 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -10,6 +10,7 @@ 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 0555d956de0..4c434202c7f 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -10,6 +10,7 @@ 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 58c26e03244..87774aed7aa 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -10,6 +10,7 @@ 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 7a0393f0ba5..84924a659b0 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -10,6 +10,7 @@ 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/121] 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 c13af363c5f..02436e3583b 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -201,7 +201,7 @@ def cache_subsets(shared_data): 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/121] 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 e15f1193a53..ce2b444997b 100644 --- a/openpype/hosts/blender/api/__init__.py +++ b/openpype/hosts/blender/api/__init__.py @@ -10,6 +10,7 @@ ls, publish, containerise, + BlenderHost, ) from .plugin import ( @@ -47,6 +48,7 @@ "ls", "publish", "containerise", + "BlenderHost", "Creator", "Loader", diff --git a/openpype/hosts/blender/api/pipeline.py b/openpype/hosts/blender/api/pipeline.py index 84af0904f0d..935981da863 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -6,10 +6,14 @@ 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 @@ 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 8dbff8a91de..603691675df 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/121] 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 935981da863..968c70089bf 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -81,7 +81,7 @@ def update_context_data(self, data, changes): 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/121] 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 00000000000..ffc1dea7338 --- /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/121] 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 968c70089bf..935981da863 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -81,7 +81,7 @@ def update_context_data(self, data, changes): 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 02436e3583b..91068244c56 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -230,13 +230,13 @@ def create( 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 ffc1dea7338..c6b5ffe0111 100644 --- a/openpype/hosts/blender/api/properties.py +++ b/openpype/hosts/blender/api/properties.py @@ -4,7 +4,7 @@ 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/121] 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 | 45 +++++++++++++++- .../blender/plugins/create/create_camera.py | 54 ++++++++++++++++++- .../blender/plugins/create/create_layout.py | 43 ++++++++++++++- .../blender/plugins/create/create_model.py | 43 ++++++++++++++- .../plugins/create/create_pointcache.py | 22 ++++++++ .../blender/plugins/create/create_review.py | 41 +++++++++++++- .../blender/plugins/create/create_rig.py | 43 ++++++++++++++- 9 files changed, 309 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 91068244c56..153897cb9a6 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -216,6 +216,8 @@ def cache_subsets(shared_data): avalon_prop ) + return shared_data + def create( self, subset_name: str, instance_data: dict, pre_create_data: dict @@ -232,6 +234,7 @@ def create( 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 5f4ded36882..32e924d7588 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 277c588610a..12478fd7e56 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 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) + + # 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) + mti = ops.MainThreadItem(self._process_legacy) ops.execute_in_main_thread(mti) - def _process(self): + # 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 9086c44c5ff..6b9d1b7f73f 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 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) + + # 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) + mti = ops.MainThreadItem(self._process_legacy) ops.execute_in_main_thread(mti) - def _process(self): + # 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 ae567e6495d..24aa2773496 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 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) + + # 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) + mti = ops.MainThreadItem(self._process_legacy) ops.execute_in_main_thread(mti) - def _process(self): + # 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 46196ab3839..2e713dd661c 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 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) + + # 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) + mti = ops.MainThreadItem(self._process_legacy) ops.execute_in_main_thread(mti) - def _process(self): + # 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 4c434202c7f..5932315bc80 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 87774aed7aa..4b5cfb4c352 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 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) + + # 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) + mti = ops.MainThreadItem(self._process_legacy) ops.execute_in_main_thread(mti) - def _process(self): + # 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 84924a659b0..5ebf952d4af 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 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) + + # 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) + mti = ops.MainThreadItem(self._process_legacy) ops.execute_in_main_thread(mti) - def _process(self): + # 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/121] 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 32e924d7588..6c2c6d98ce2 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 12478fd7e56..ec775698891 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 @@ 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 ) diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index 6b9d1b7f73f..55ea07d90f0 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 @@ 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 ) diff --git a/openpype/hosts/blender/plugins/create/create_layout.py b/openpype/hosts/blender/plugins/create/create_layout.py index 24aa2773496..60812d7fc9e 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 @@ 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 ) diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index 2e713dd661c..59bbae5bc47 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 @@ 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 ) diff --git a/openpype/hosts/blender/plugins/create/create_pointcache.py b/openpype/hosts/blender/plugins/create/create_pointcache.py index 5932315bc80..bb843c5a223 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 4b5cfb4c352..1191fb95f11 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 @@ 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 ) diff --git a/openpype/hosts/blender/plugins/create/create_rig.py b/openpype/hosts/blender/plugins/create/create_rig.py index 5ebf952d4af..752f3f7b183 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 @@ 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 ) From 6eae2c264d57b4ed5395de03950e09bc54ae7acb Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Wed, 12 Jul 2023 15:54:41 +0200 Subject: [PATCH 012/121] 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 9bb560c3643..76bef09ceb7 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 935981da863..4c06b1959e2 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -63,7 +63,7 @@ def ls(self) -> Iterator: """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 @@ def get_context_data(self): """ 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 153897cb9a6..fe1e4ce81c4 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -238,6 +238,7 @@ def create( 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/121] 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 fe1e4ce81c4..97a3dfa6a69 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -216,6 +216,9 @@ def cache_subsets(shared_data): avalon_prop ) + shared_data["blender_cached_subsets"] = cache + shared_data["blender_cached_legacy_subsets"] = cache_legacy + return shared_data @@ -257,14 +260,17 @@ def collect_instances(self): 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/121] 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 97a3dfa6a69..5dd352ff6ca 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -262,10 +262,10 @@ def collect_instances(self): 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 6c2c6d98ce2..2c0d2489944 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -29,6 +29,9 @@ def create( 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 ec775698891..538c0455acc 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -47,6 +47,9 @@ def _process( 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 55ea07d90f0..ae852733537 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -45,6 +45,9 @@ def _process( 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 60812d7fc9e..cce422b2299 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -44,6 +44,9 @@ def _process( 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 59bbae5bc47..57f9e79aa10 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -44,6 +44,9 @@ def _process( 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 bb843c5a223..f95f79ae78b 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -29,6 +29,9 @@ def create( 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 1191fb95f11..8472600c2fe 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -43,6 +43,9 @@ def _process( 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 752f3f7b183..a60f2a72eee 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -44,6 +44,9 @@ def _process( 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/121] 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 2c0d2489944..6951e86c465 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -28,10 +28,15 @@ def create( ) 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 538c0455acc..9e7dfbaf84f 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -46,10 +46,15 @@ def _process( # 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 ae852733537..c5987779c00 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -44,10 +44,15 @@ def _process( 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 cce422b2299..cb61799f4aa 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -43,10 +43,15 @@ def _process( 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 57f9e79aa10..ccf3668d98c 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -43,10 +43,15 @@ def _process( 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 f95f79ae78b..e27cb223897 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -28,10 +28,15 @@ def create( ) 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 8472600c2fe..afeaea951b9 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -42,10 +42,15 @@ def _process( 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 a60f2a72eee..2db766f7ed4 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -43,10 +43,15 @@ def _process( 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/121] 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 4c06b1959e2..c3f8f066943 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -12,6 +12,7 @@ from openpype.host import( HostBase, + IWorkfileHost, IPublishHost, ) from openpype.client import get_asset_by_name @@ -33,6 +34,14 @@ ) 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 @@ log = Logger.get_logger(__name__) -class BlenderHost(HostBase, IPublishHost): +class BlenderHost(HostBase, IWorkfileHost, IPublishHost): name = "blender" def install(self): @@ -63,6 +72,65 @@ def ls(self) -> Iterator: """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/121] 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 7b6c4d7ae7c..5af8104344b 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.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 44b2ba37612..0b6b93b7a58 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.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 477411b73d6..3a5b788c9e5 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 @@ 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 036be7bf3c4..2a327f4d659 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.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 315994140e9..8e5e4d37d4a 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.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 0ad797c2267..8ace6a43a7e 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.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 062b42e99d3..04f50f8207d 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.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 f2d04f11783..8445560bba0 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.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 196e75b8ccb..82ddbc1fc25 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.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 6ace14d77cf..176668f3661 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 d9a85bc79b8..b7e5423fa89 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 edf47193bee..687371b362a 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 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 ac60e00f897..d8d2e3c8bf0 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 @@ 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/121] 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 5dd352ff6ca..fe0d53f84e9 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -234,13 +234,20 @@ def create( 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 @@ def update_instances(self, update_list): 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 6951e86c465..a43258082c5 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -28,6 +28,11 @@ def create( ) 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 @@ def create( "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 9e7dfbaf84f..842292f0f94 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -46,6 +46,11 @@ def _process( # 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 @@ def _process( "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 c5987779c00..8360abbc7d0 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -44,6 +44,11 @@ def _process( 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 @@ def _process( "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 cb61799f4aa..b4b127f32cd 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -43,6 +43,11 @@ def _process( 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 @@ def _process( "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 ccf3668d98c..5cb2de0fae1 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -43,6 +43,11 @@ def _process( 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 @@ def _process( "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 e27cb223897..a95ae547c85 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -28,6 +28,11 @@ def create( ) 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 @@ def create( "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 afeaea951b9..1bcafbc265f 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -42,6 +42,11 @@ def _process( 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 @@ def _process( "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 2db766f7ed4..e93f4a171fb 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -43,6 +43,11 @@ def _process( 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 @@ def _process( "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/121] 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 a43258082c5..ee7ca092b57 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -43,8 +43,6 @@ def create( "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/121] 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 fe0d53f84e9..9967f9479cd 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -155,7 +155,6 @@ def process(self): return collection - @staticmethod def cache_subsets(shared_data): """Cache instances for Creators shared data. @@ -221,7 +220,6 @@ def cache_subsets(shared_data): return shared_data - def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): @@ -254,7 +252,6 @@ def create( 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 @@ def collect_instances(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. @@ -296,7 +292,6 @@ def update_instances(self, update_list): 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 @@ def remove_instances(self, instances: List[CreatedInstance]): 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 ee7ca092b57..7404e7e0373 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -5,6 +5,7 @@ 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 @@ def create( 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 842292f0f94..c2f24250e19 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -4,7 +4,10 @@ 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 @@ def _process( 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 8360abbc7d0..a83124cbe7f 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -4,7 +4,10 @@ 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 @@ def _process( 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 b4b127f32cd..4fb76ef41e8 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -4,7 +4,10 @@ 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 @@ def _process( 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 5cb2de0fae1..45f1d66ad95 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -4,7 +4,10 @@ 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 @@ def _process( 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 a95ae547c85..7aa3b224664 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -5,6 +5,7 @@ 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 @@ def create( 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 1bcafbc265f..a6ca5b1b92c 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -4,7 +4,10 @@ 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 @@ def _process( 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 e93f4a171fb..f7c99b0b039 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -4,7 +4,10 @@ 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 @@ def _process( 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/121] 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 9967f9479cd..060d229eb50 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -235,9 +235,10 @@ def create( 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/121] 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 060d229eb50..73d8fc0ed5f 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -237,7 +237,6 @@ def create( collection["instance_node"] = instance_node = { "name": collection.name, - "datablock": collection, } instance_data["instance_node"] = instance_node @@ -267,9 +266,7 @@ def collect_instances(self): 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 7404e7e0373..d766fce0388 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -32,7 +32,6 @@ def create( 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 c2f24250e19..88ae9e59966 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -52,7 +52,6 @@ def _process( 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 a83124cbe7f..026b5739d6c 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -50,7 +50,6 @@ def _process( 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 4fb76ef41e8..f46ae58a433 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -49,7 +49,6 @@ def _process( 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 45f1d66ad95..069b78626ba 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -49,7 +49,6 @@ def _process( 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 7aa3b224664..3054b81ef5c 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -32,7 +32,6 @@ def create( 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 a6ca5b1b92c..10a96c94fdf 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -48,7 +48,6 @@ def _process( 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 f7c99b0b039..8daffe638d7 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -49,7 +49,6 @@ def _process( 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/121] 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 3bf2e39e241..66a3d7b5e83 100644 --- a/openpype/hosts/blender/plugins/publish/collect_review.py +++ b/openpype/hosts/blender/plugins/publish/collect_review.py @@ -16,10 +16,14 @@ def process(self, instance): 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/121] 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 d766fce0388..e7b689c54e4 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -38,7 +38,7 @@ def create( { "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 88ae9e59966..8b4214ceda1 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -58,7 +58,7 @@ def _process( { "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 026b5739d6c..4747e50b2e3 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -56,7 +56,7 @@ def _process( { "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 f46ae58a433..0c97d57af39 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -55,7 +55,7 @@ def _process( { "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 069b78626ba..3c8e9c49000 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -55,7 +55,7 @@ def _process( { "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 3054b81ef5c..a40bd5af612 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -38,7 +38,7 @@ def create( { "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 10a96c94fdf..8c9a8d59277 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -54,7 +54,7 @@ def _process( { "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 8daffe638d7..110a9f5c8e1 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -55,7 +55,7 @@ def _process( { "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/121] 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 63bcf212ff8..ee8e52d3c58 100644 --- a/openpype/hosts/blender/plugins/create/create_blendScene.py +++ b/openpype/hosts/blender/plugins/create/create_blendScene.py @@ -4,7 +4,10 @@ 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 @@ def _process(self): 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 f938a218082..ab3119b32ed 100644 --- a/openpype/hosts/blender/plugins/create/create_render.py +++ b/openpype/hosts/blender/plugins/create/create_render.py @@ -4,10 +4,13 @@ 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 @@ def process(self): 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/121] 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 1cadedd797a..20df0ad5c2b 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/121] 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 15ccd4e89ae..19ad5ff24aa 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 @@ class ImageIOSettings(BaseSettingsModel): { "type": "text", "name": "colorspace", - "text": "linear" + "text": "scene_linear" }, { "type": "boolean", @@ -321,7 +321,7 @@ class ImageIOSettings(BaseSettingsModel): { "type": "text", "name": "colorspace", - "text": "linear" + "text": "scene_linear" }, { "type": "boolean", @@ -368,7 +368,7 @@ class ImageIOSettings(BaseSettingsModel): { "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 bbab0242f6a..1276d0254ff 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/121] 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 73d8fc0ed5f..dbd9f25d68a 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -9,6 +9,7 @@ Creator, CreatedInstance, LoaderPlugin, + get_current_task_name, ) from .pipeline import ( AVALON_CONTAINERS, @@ -235,11 +236,20 @@ def create( 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 ee8e52d3c58..23ff9916540 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 3c8e9c49000..761d9fca9f0 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/121] 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 dbd9f25d68a..3ddc3756705 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 e7b689c54e4..7d00aa1dcb8 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.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 8b4214ceda1..6cfd054e748 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -10,7 +10,7 @@ ) -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 4747e50b2e3..5d9682e5751 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -10,7 +10,7 @@ ) -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 0c97d57af39..ed47b0632f8 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -10,7 +10,7 @@ ) -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 761d9fca9f0..949fae0f769 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -10,7 +10,7 @@ ) -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 a40bd5af612..2ad12caa9c9 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_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 ab3119b32ed..45570f3491a 100644 --- a/openpype/hosts/blender/plugins/create/create_render.py +++ b/openpype/hosts/blender/plugins/create/create_render.py @@ -10,7 +10,7 @@ ) -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 8c9a8d59277..e8b893b4c05 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -10,7 +10,7 @@ ) -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 110a9f5c8e1..6223e641744 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -10,7 +10,7 @@ ) -class CreateRig(plugin.BlenderCreator): +class CreateRig(plugin.BaseCreator): """Artist-friendly rig with controls to direct motion""" identifier = "io.openpype.creators.blender.rig" From 88116be4c63079598ec8a38b45ad3cc329155a87 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 2 Nov 2023 20:44:21 +0800 Subject: [PATCH 030/121] allows users to preset the settings before the creator setting --- .../hosts/max/plugins/create/create_review.py | 58 ++++++++--- openpype/plugins/publish/extract_review.py | 2 +- .../defaults/project_settings/max.json | 10 ++ .../projects_schema/schema_project_max.json | 98 +++++++++++++++++++ 4 files changed, 154 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/max/plugins/create/create_review.py b/openpype/hosts/max/plugins/create/create_review.py index 8052b74f069..67dc1580012 100644 --- a/openpype/hosts/max/plugins/create/create_review.py +++ b/openpype/hosts/max/plugins/create/create_review.py @@ -12,6 +12,32 @@ class CreateReview(plugin.MaxCreator): family = "review" icon = "video-camera" + review_width = 1920 + review_height = 1080 + percentSize = 100 + keep_images = False + image_format = "png" + visual_style = "Realistic" + viewport_preset = "Quality" + vp_texture = True + anti_aliasing = None + + + @classmethod + def apply_settings(cls, project_settings): + settings = project_settings["max"]["PreviewAnimation"] # noqa + + # Take some defaults from settings + cls.review_width = settings.get("review_width", cls.review_width) + cls.review_height = settings.get("review_height", cls.review_height) + cls.percentSize = settings.get("percentSize", cls.percentSize) + cls.keep_images = settings.get("keep_images", cls.keep_images) + cls.image_format = settings.get("image_format", cls.image_format) + cls.visual_style = settings.get("visual_style", cls.visual_style) + cls.viewport_preset = settings.get("viewport_preset", cls.viewport_preset) + cls.vp_texture = settings.get("vp_texture", cls.vp_texture) + cls.anti_aliasing = settings.get("anti_aliasing", cls.anti_aliasing) + def create(self, subset_name, instance_data, pre_create_data): # Transfer settings from pre create to instance creator_attributes = instance_data.setdefault( @@ -23,6 +49,7 @@ def create(self, subset_name, instance_data, pre_create_data): "percentSize", "visualStyleMode", "viewportPreset", + "anti_aliasing", "vpTexture"]: if key in pre_create_data: creator_attributes[key] = pre_create_data[key] @@ -33,7 +60,7 @@ def create(self, subset_name, instance_data, pre_create_data): pre_create_data) def get_instance_attr_defs(self): - image_format_enum = ["exr", "jpg", "png"] + image_format_enum = ["exr", "jpg", "png", "tga"] visual_style_preset_enum = [ "Realistic", "Shaded", "Facets", @@ -45,41 +72,46 @@ def get_instance_attr_defs(self): preview_preset_enum = [ "Quality", "Standard", "Performance", "DXMode", "Customize"] + anti_aliasing_enum = ["None", "2X", "4X", "8X"] return [ NumberDef("review_width", label="Review width", decimals=0, minimum=0, - default=1920), + default=self.review_width), NumberDef("review_height", label="Review height", decimals=0, minimum=0, - default=1080), - BoolDef("keepImages", - label="Keep Image Sequences", - default=False), - EnumDef("imageFormat", - image_format_enum, - default="png", - label="Image Format Options"), + default=self.review_height), NumberDef("percentSize", label="Percent of Output", default=100, minimum=1, decimals=0), + BoolDef("keepImages", + label="Keep Image Sequences", + default=self.keep_images), + EnumDef("imageFormat", + image_format_enum, + default=self.image_format, + label="Image Format Options"), EnumDef("visualStyleMode", visual_style_preset_enum, - default="Realistic", + default=self.visual_style, label="Preference"), EnumDef("viewportPreset", preview_preset_enum, - default="Quality", + default=self.viewport_preset, label="Pre-View Preset"), + EnumDef("anti_aliasing", + anti_aliasing_enum, + default=self.anti_aliasing, + label="Anti-aliasing Quality"), BoolDef("vpTexture", label="Viewport Texture", - default=False) + default=self.vp_texture) ] def get_pre_create_attr_defs(self): diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index 0ae941511c8..db8a030dfa3 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -68,7 +68,7 @@ class ExtractReview(pyblish.api.InstancePlugin): ] # Supported extensions - image_exts = ["exr", "jpg", "jpeg", "png", "dpx"] + image_exts = ["exr", "jpg", "jpeg", "png", "dpx", "tga"] video_exts = ["mov", "mp4"] supported_exts = image_exts + video_exts diff --git a/openpype/settings/defaults/project_settings/max.json b/openpype/settings/defaults/project_settings/max.json index bfb1aa4aeb0..c610a963d4b 100644 --- a/openpype/settings/defaults/project_settings/max.json +++ b/openpype/settings/defaults/project_settings/max.json @@ -16,6 +16,16 @@ "image_format": "exr", "multipass": true }, + "PreviewAnimation": { + "review_width": 1920, + "review_height": 1080, + "percentSize": 100, + "keep_images": false, + "image_format": "png", + "visual_style": "Realistic", + "viewport_preset": "Quality", + "vp_texture": true + }, "PointCloud": { "attribute": { "Age": "age", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_max.json b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json index e314174dff5..b012e73fc42 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_max.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json @@ -65,6 +65,104 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "key": "PreviewAnimation", + "label": "Preview Animation", + "children": [ + { + "type": "number", + "key": "review_width", + "label": "Review Width" + }, + { + "type": "number", + "key": "review_height", + "label": "Review Height" + }, + { + "type": "number", + "key": "percentSize", + "label": "Percent of Output" + }, + { + "type": "boolean", + "key": "keep_images", + "label": "Keep Image Sequences" + }, + { + "key": "image_format", + "label": "Image Format Options", + "type": "enum", + "multiselection": false, + "defaults": "exr", + "enum_items": [ + {"exr": "exr"}, + {"jpg": "jpg"}, + {"png": "png"}, + {"tga": "tga"} + ] + }, + { + "key": "visual_style", + "label": "Preference", + "type": "enum", + "multiselection": false, + "defaults": "Realistic", + "enum_items": [ + {"Realistic": "Realistic"}, + {"Shaded": "Shaded"}, + {"Facets": "Facets"}, + {"ConsistentColors": "ConsistentColors"}, + {"HiddenLine": "HiddenLine"}, + {"Wireframe": "Wireframe"}, + {"BoundingBox": "BoundingBox"}, + {"Ink": "Ink"}, + {"ColorInk": "ColorInk"}, + {"Acrylic": "Acrylic"}, + {"Tech": "Tech"}, + {"Graphite": "Graphite"}, + {"ColorPencil": "ColorPencil"}, + {"Pastel": "Pastel"}, + {"Clay": "Clay"}, + {"ModelAssist": "ModelAssist"} + ] + }, + { + "key": "viewport_preset", + "label": "Pre-View Preset", + "type": "enum", + "multiselection": false, + "defaults": "Quality", + "enum_items": [ + {"Quality": "Quality"}, + {"Standard": "Standard"}, + {"Performance": "Performance"}, + {"DXMode": "DXMode"}, + {"Customize": "Customize"} + ] + }, + { + "key": "anti_aliasing", + "label": "Anti-aliasing Quality", + "type": "enum", + "multiselection": false, + "defaults": "None", + "enum_items": [ + {"None": "None"}, + {"2X": "2X"}, + {"4X": "4X"}, + {"8X": "8X"} + ] + }, + { + "type": "boolean", + "key": "vp_texture", + "label": "Viewport Texture" + } + ] + }, { "type": "dict", "collapsible": true, From 597260ad520393f2942b5685a817210d18544f45 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 2 Nov 2023 20:49:40 +0800 Subject: [PATCH 031/121] cosmetic fix --- openpype/settings/defaults/project_settings/max.json | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/settings/defaults/project_settings/max.json b/openpype/settings/defaults/project_settings/max.json index c610a963d4b..ac04c60b54b 100644 --- a/openpype/settings/defaults/project_settings/max.json +++ b/openpype/settings/defaults/project_settings/max.json @@ -24,6 +24,7 @@ "image_format": "png", "visual_style": "Realistic", "viewport_preset": "Quality", + "anti_aliasing": "None", "vp_texture": true }, "PointCloud": { From db26cdd6e35a08d0025dcf8a54024b17af484099 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 2 Nov 2023 21:55:22 +0800 Subject: [PATCH 032/121] hound --- openpype/hosts/max/plugins/create/create_review.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/max/plugins/create/create_review.py b/openpype/hosts/max/plugins/create/create_review.py index 67dc1580012..ed0359ebd79 100644 --- a/openpype/hosts/max/plugins/create/create_review.py +++ b/openpype/hosts/max/plugins/create/create_review.py @@ -22,7 +22,6 @@ class CreateReview(plugin.MaxCreator): vp_texture = True anti_aliasing = None - @classmethod def apply_settings(cls, project_settings): settings = project_settings["max"]["PreviewAnimation"] # noqa @@ -34,9 +33,11 @@ def apply_settings(cls, project_settings): cls.keep_images = settings.get("keep_images", cls.keep_images) cls.image_format = settings.get("image_format", cls.image_format) cls.visual_style = settings.get("visual_style", cls.visual_style) - cls.viewport_preset = settings.get("viewport_preset", cls.viewport_preset) + cls.viewport_preset = settings.get( + "viewport_preset", cls.viewport_preset) cls.vp_texture = settings.get("vp_texture", cls.vp_texture) - cls.anti_aliasing = settings.get("anti_aliasing", cls.anti_aliasing) + cls.anti_aliasing = settings.get( + "anti_aliasing", cls.anti_aliasing) def create(self, subset_name, instance_data, pre_create_data): # Transfer settings from pre create to instance @@ -49,7 +50,7 @@ def create(self, subset_name, instance_data, pre_create_data): "percentSize", "visualStyleMode", "viewportPreset", - "anti_aliasing", + "antialiasingQuality", "vpTexture"]: if key in pre_create_data: creator_attributes[key] = pre_create_data[key] @@ -105,7 +106,7 @@ def get_instance_attr_defs(self): preview_preset_enum, default=self.viewport_preset, label="Pre-View Preset"), - EnumDef("anti_aliasing", + EnumDef("antialiasingQuality", anti_aliasing_enum, default=self.anti_aliasing, label="Anti-aliasing Quality"), From 976ff308e97abed1efd6b87f09c477ad16c8f329 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Thu, 2 Nov 2023 16:18:51 +0100 Subject: [PATCH 033/121] 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 00000000000..d1529f75f62 --- /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 1e4005f4454efa4ea78f3bb14c0f96de400b8734 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 3 Nov 2023 16:01:44 +0800 Subject: [PATCH 034/121] add AYON settings support and finalize the settings --- .../hosts/max/plugins/create/create_review.py | 2 +- server_addon/max/server/settings/main.py | 8 ++ .../max/server/settings/preview_animation.py | 92 +++++++++++++++++++ server_addon/max/server/version.py | 2 +- 4 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 server_addon/max/server/settings/preview_animation.py diff --git a/openpype/hosts/max/plugins/create/create_review.py b/openpype/hosts/max/plugins/create/create_review.py index ed0359ebd79..7aeea39b649 100644 --- a/openpype/hosts/max/plugins/create/create_review.py +++ b/openpype/hosts/max/plugins/create/create_review.py @@ -35,9 +35,9 @@ def apply_settings(cls, project_settings): cls.visual_style = settings.get("visual_style", cls.visual_style) cls.viewport_preset = settings.get( "viewport_preset", cls.viewport_preset) - cls.vp_texture = settings.get("vp_texture", cls.vp_texture) cls.anti_aliasing = settings.get( "anti_aliasing", cls.anti_aliasing) + cls.vp_texture = settings.get("vp_texture", cls.vp_texture) def create(self, subset_name, instance_data, pre_create_data): # Transfer settings from pre create to instance diff --git a/server_addon/max/server/settings/main.py b/server_addon/max/server/settings/main.py index 7f4561cbb1f..0280fcebb94 100644 --- a/server_addon/max/server/settings/main.py +++ b/server_addon/max/server/settings/main.py @@ -4,6 +4,9 @@ from .render_settings import ( RenderSettingsModel, DEFAULT_RENDER_SETTINGS ) +from .preview_animation import ( + PreviewAnimationModel, DEFAULT_PREVIEW_ANIMATION_SETTINGS +) from .publishers import ( PublishersModel, DEFAULT_PUBLISH_SETTINGS ) @@ -29,6 +32,10 @@ class MaxSettings(BaseSettingsModel): default_factory=RenderSettingsModel, title="Render Settings" ) + PreviewAnimation: PreviewAnimationModel = Field( + default_factory=PreviewAnimationModel, + title="Preview Animation" + ) PointCloud: PointCloudSettings = Field( default_factory=PointCloudSettings, title="Point Cloud" @@ -40,6 +47,7 @@ class MaxSettings(BaseSettingsModel): DEFAULT_VALUES = { "RenderSettings": DEFAULT_RENDER_SETTINGS, + "PreviewAnimation": DEFAULT_PREVIEW_ANIMATION_SETTINGS, "PointCloud": { "attribute": [ {"name": "Age", "value": "age"}, diff --git a/server_addon/max/server/settings/preview_animation.py b/server_addon/max/server/settings/preview_animation.py new file mode 100644 index 00000000000..2496e8e5486 --- /dev/null +++ b/server_addon/max/server/settings/preview_animation.py @@ -0,0 +1,92 @@ +from pydantic import Field + +from ayon_server.settings import BaseSettingsModel + + +def image_format_enum(): + """Return enumerator for image output formats.""" + return [ + {"label": "exr", "value": "exr"}, + {"label": "jpg", "value": "jpg"}, + {"label": "png", "value": "png"}, + {"label": "tga", "value": "tga"} + ] + + +def visual_style_enum(): + """Return enumerator for viewport visual style.""" + return [ + {"label": "Realistic", "value": "Realistic"}, + {"label": "Shaded", "value": "Shaded"}, + {"label": "Facets", "value": "Facets"}, + {"label": "ConsistentColors", + "value": "ConsistentColors"}, + {"label": "Wireframe", "value": "Wireframe"}, + {"label": "BoundingBox", "value": "BoundingBox"}, + {"label": "Ink", "value": "Ink"}, + {"label": "ColorInk", "value": "ColorInk"}, + {"label": "Acrylic", "value": "Acrylic"}, + {"label": "Tech", "value": "Tech"}, + {"label": "Graphite", "value": "Graphite"}, + {"label": "ColorPencil", "value": "ColorPencil"}, + {"label": "Pastel", "value": "Pastel"}, + {"label": "Clay", "value": "Clay"}, + {"label": "ModelAssist", "value": "ModelAssist"} + ] + +def visual_preset_enum(): + """Return enumerator for viewport visual preset.""" + return [ + {"label": "Quality", "value": "Quality"}, + {"label": "Standard", "value": "Standard"}, + {"label": "Performance", "value": "Performance"}, + {"label": "DXMode", "value": "DXMode"}, + {"label": "Customize", "value": "Customize"}, + ] + + +def anti_aliasing_enum(): + """Return enumerator for viewport anti-aliasing.""" + return [ + {"label": "None", "value": "None"}, + {"label": "2X", "value": "2X"}, + {"label": "4X", "value": "4X"}, + {"label": "8X", "value": "8X"} + ] + + +class PreviewAnimationModel(BaseSettingsModel): + review_width: int = Field(1920, title="Review Width") + review_height: int = Field(1080, title="Review Height") + percentSize: float = Field(100.0, title="Percent of Output") + keep_images: bool = Field(False, title="Keep Image Sequences") + image_format: str = Field( + enum_resolver=image_format_enum, + title="Image Format Options" + ) + visual_style: str = Field( + enum_resolver=visual_style_enum, + title="Preference" + ) + viewport_preset: str = Field( + enum_resolver=visual_preset_enum, + title="Pre-View Preset" + ) + anti_aliasing: str = Field( + enum_resolver=anti_aliasing_enum, + title="Anti-aliasing Quality" + ) + vp_texture: bool = Field(True, title="Viewport Texture") + + +DEFAULT_PREVIEW_ANIMATION_SETTINGS = { + "review_width": 1920, + "review_height": 1080, + "percentSize": 100.0, + "keep_images": False, + "image_format": "png", + "visual_style": "Realistic", + "viewport_preset": "Quality", + "anti_aliasing": "None", + "vp_texture": True +} diff --git a/server_addon/max/server/version.py b/server_addon/max/server/version.py index 3dc1f76bc69..485f44ac21b 100644 --- a/server_addon/max/server/version.py +++ b/server_addon/max/server/version.py @@ -1 +1 @@ -__version__ = "0.1.0" +__version__ = "0.1.1" From 2728379bbc0810155447cae35c01c65341ec12bd Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 3 Nov 2023 16:04:39 +0800 Subject: [PATCH 035/121] hound --- server_addon/max/server/settings/preview_animation.py | 1 + 1 file changed, 1 insertion(+) diff --git a/server_addon/max/server/settings/preview_animation.py b/server_addon/max/server/settings/preview_animation.py index 2496e8e5486..759ce782914 100644 --- a/server_addon/max/server/settings/preview_animation.py +++ b/server_addon/max/server/settings/preview_animation.py @@ -34,6 +34,7 @@ def visual_style_enum(): {"label": "ModelAssist", "value": "ModelAssist"} ] + def visual_preset_enum(): """Return enumerator for viewport visual preset.""" return [ From 10aea1088a68f49218255e4988febe4bc1f0f462 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Fri, 3 Nov 2023 17:06:44 +0100 Subject: [PATCH 036/121] 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 00000000000..e431405e80d --- /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 037/121] 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 e431405e80d..01c033084b8 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 @@ def process(self, instance): { "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 038/121] 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 45570f3491a..e036ae7df34 100644 --- a/openpype/hosts/blender/plugins/create/create_render.py +++ b/openpype/hosts/blender/plugins/create/create_render.py @@ -13,6 +13,7 @@ class CreateRenderlayer(plugin.BaseCreator): """Single baked camera""" + identifier = "io.openpype.creators.blender.render" name = "renderingMain" label = "Render" family = "render" @@ -28,8 +29,7 @@ def create( 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 039/121] 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 09de2d8db25..7a5a3c7fc18 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 @@ def process(self, instance): 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 040/121] 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 8640500b189..a2746f115fe 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -701,6 +701,8 @@ def _get_base_instance_data( # 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 db70d4fe0a9..92cedf6b5b3 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 @@ def process(self, instance): 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 7a5a3c7fc18..4977a133748 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 @@ def process(self, instance): 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 cfb4d63c1b3..14c13310dfe 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 ea69e9943ec21cbcb0c5094ed6f063dd393cccb8 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Wed, 8 Nov 2023 15:56:37 +0200 Subject: [PATCH 041/121] update houdini license validator --- .../validate_houdini_license_category.py | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/validate_houdini_license_category.py b/openpype/hosts/houdini/plugins/publish/validate_houdini_license_category.py index f1c52f22c19..e0e06e37c8f 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_houdini_license_category.py +++ b/openpype/hosts/houdini/plugins/publish/validate_houdini_license_category.py @@ -3,30 +3,29 @@ from openpype.pipeline import PublishValidationError -class ValidateHoudiniCommercialLicense(pyblish.api.InstancePlugin): - """Validate the Houdini instance runs a Commercial license. +class ValidateHoudiniNotApprenticeLicense(pyblish.api.InstancePlugin): + """Validate the Houdini instance runs a non Apprentice license. - When extracting USD files from a non-commercial Houdini license, even with - Houdini Indie license, the resulting files will get "scrambled" with - a license protection and get a special .usdnc or .usdlc suffix. + When extracting USD files from an apprentice Houdini license, + the resulting files will get "scrambled" with a license protection + and get a special .usdnc or .usdlc suffix. This currently breaks the Subset/representation pipeline so we disallow - any publish with those licenses. Only the commercial license is valid. + any publish with apprentice license. """ order = pyblish.api.ValidatorOrder families = ["usd"] hosts = ["houdini"] - label = "Houdini Commercial License" + label = "Houdini Apprentice License" def process(self, instance): import hou - license = hou.licenseCategory() - if license != hou.licenseCategoryType.Commercial: + if hou.isApprentice(): raise PublishValidationError( - ("USD Publishing requires a full Commercial " - "license. You are on: {}").format(license), + ("USD Publishing requires a non apprentice " + "license."), title=self.label) From af9718f753b836542aa55a837123caa655beb369 Mon Sep 17 00:00:00 2001 From: Mustafa Taher Date: Wed, 8 Nov 2023 16:25:17 +0200 Subject: [PATCH 042/121] BigRoy's comment - update doc string Co-authored-by: Roy Nieterau --- .../plugins/publish/validate_houdini_license_category.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/publish/validate_houdini_license_category.py b/openpype/hosts/houdini/plugins/publish/validate_houdini_license_category.py index e0e06e37c8f..fd6ad9e3be3 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_houdini_license_category.py +++ b/openpype/hosts/houdini/plugins/publish/validate_houdini_license_category.py @@ -8,7 +8,7 @@ class ValidateHoudiniNotApprenticeLicense(pyblish.api.InstancePlugin): When extracting USD files from an apprentice Houdini license, the resulting files will get "scrambled" with a license protection - and get a special .usdnc or .usdlc suffix. + and get a special .usdnc suffix. This currently breaks the Subset/representation pipeline so we disallow any publish with apprentice license. From 7cd98fe9033c17b4897208ab7c54bda5e7e53c45 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Thu, 9 Nov 2023 15:52:36 +0100 Subject: [PATCH 043/121] 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 3ddc3756705..629cb4dac93 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 @@ def create( 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 7d00aa1dcb8..9267fc07659 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -59,28 +59,3 @@ def create( 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 6cfd054e748..89567061b6d 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -75,40 +75,3 @@ def _process( 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 5d9682e5751..125514ae018 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -84,52 +84,3 @@ def _process( 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 ed47b0632f8..a6c7053cb24 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -73,40 +73,3 @@ def _process( 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 949fae0f769..8beb8025c4e 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -73,40 +73,3 @@ def _process( 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 2ad12caa9c9..aa8b297d160 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -54,39 +54,3 @@ def create( 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 e8b893b4c05..13fa3b621fc 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -71,36 +71,3 @@ def _process( 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 6223e641744..6682162f4b7 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -73,40 +73,3 @@ def _process( 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 044/121] 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 629cb4dac93..aef891bd831 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -237,6 +237,7 @@ def create( "name": collection.name, } + # Set instance data instance_data.update( { "id": "pyblish.avalon.instance", @@ -248,16 +249,15 @@ def create( } ) - 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 @@ def collect_instances(self): 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 9267fc07659..95bd42682ca 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 89567061b6d..3c70ae1bd03 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 @@ def create( 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 aa8b297d160..d8231932492 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,31 +15,10 @@ 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) - ) - - 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, - } + # Run parent create method + collection = super().create( + subset_name, instance_data, pre_create_data ) - lib.imprint(collection, instance_data) if pre_create_data.get("useSelection"): objects = lib.get_selection() diff --git a/openpype/hosts/blender/plugins/create/create_render.py b/openpype/hosts/blender/plugins/create/create_render.py index e036ae7df34..0e5a284caf2 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 @@ def create( # 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 13fa3b621fc..e35e405ee11 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 @@ def create( 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 045/121] 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 95bd42682ca..ac425dff74f 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -7,7 +7,7 @@ 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 3c70ae1bd03..f7cb9f88aa7 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 23ff9916540..df3a70f1993 100644 --- a/openpype/hosts/blender/plugins/create/create_blendScene.py +++ b/openpype/hosts/blender/plugins/create/create_blendScene.py @@ -11,7 +11,7 @@ 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 125514ae018..a42a9d6ef02 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -11,7 +11,7 @@ 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 a6c7053cb24..ef1822dedfb 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -11,7 +11,7 @@ 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 8beb8025c4e..e4937f989e7 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -11,7 +11,7 @@ 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 d8231932492..d05b3adb042 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -4,7 +4,7 @@ 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 0e5a284caf2..bffa5696df2 100644 --- a/openpype/hosts/blender/plugins/create/create_render.py +++ b/openpype/hosts/blender/plugins/create/create_render.py @@ -6,7 +6,7 @@ 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 e35e405ee11..91333b77418 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 6682162f4b7..b54cc73d4a7 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -11,7 +11,7 @@ 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 d3f4a397f4fa897ae66e44f04ddbd8fcdbaf22f5 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 13 Nov 2023 09:47:52 +0200 Subject: [PATCH 046/121] add pointcache --- .../publish/validate_houdini_license_category.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/validate_houdini_license_category.py b/openpype/hosts/houdini/plugins/publish/validate_houdini_license_category.py index e0e06e37c8f..1a21cd4746b 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_houdini_license_category.py +++ b/openpype/hosts/houdini/plugins/publish/validate_houdini_license_category.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import pyblish.api from openpype.pipeline import PublishValidationError +import hou class ValidateHoudiniNotApprenticeLicense(pyblish.api.InstancePlugin): @@ -16,16 +17,18 @@ class ValidateHoudiniNotApprenticeLicense(pyblish.api.InstancePlugin): """ order = pyblish.api.ValidatorOrder - families = ["usd"] + families = ["usd", "abc"] hosts = ["houdini"] label = "Houdini Apprentice License" def process(self, instance): - import hou + if hou.isApprentice() or 1: + families = [instance.data["family"]] + families += instance.data.get("families", []) + families = " ".join(families).title() - if hou.isApprentice(): raise PublishValidationError( - ("USD Publishing requires a non apprentice " - "license."), + "{} Publishing requires a non apprentice license." + .format(families), title=self.label) From 43a2955f865db051832363b5421ea624c2a1a607 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 13 Nov 2023 09:50:47 +0200 Subject: [PATCH 047/121] remove debugging code --- .../plugins/publish/validate_houdini_license_category.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/publish/validate_houdini_license_category.py b/openpype/hosts/houdini/plugins/publish/validate_houdini_license_category.py index 3894a8d57be..feb28aeaa69 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_houdini_license_category.py +++ b/openpype/hosts/houdini/plugins/publish/validate_houdini_license_category.py @@ -23,7 +23,7 @@ class ValidateHoudiniNotApprenticeLicense(pyblish.api.InstancePlugin): def process(self, instance): - if hou.isApprentice() or 1: + if hou.isApprentice(): families = [instance.data["family"]] families += instance.data.get("families", []) families = " ".join(families).title() From f84cf14316c36bb37d2f499a5d3f63866ece0010 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 13 Nov 2023 09:59:10 +0200 Subject: [PATCH 048/121] update doc string --- .../publish/validate_houdini_license_category.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/validate_houdini_license_category.py b/openpype/hosts/houdini/plugins/publish/validate_houdini_license_category.py index feb28aeaa69..3d9e854dcd5 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_houdini_license_category.py +++ b/openpype/hosts/houdini/plugins/publish/validate_houdini_license_category.py @@ -7,13 +7,16 @@ class ValidateHoudiniNotApprenticeLicense(pyblish.api.InstancePlugin): """Validate the Houdini instance runs a non Apprentice license. - When extracting USD files from an apprentice Houdini license, - the resulting files will get "scrambled" with a license protection - and get a special .usdnc suffix. + USD ROPs: + When extracting USD files from an apprentice Houdini license, + the resulting files will get "scrambled" with a license protection + and get a special .usdnc suffix. - This currently breaks the Subset/representation pipeline so we disallow - any publish with apprentice license. + This currently breaks the Subset/representation pipeline so we disallow + any publish with apprentice license. + Alembic ROPs: + Houdini Apprentice does not export Alembic. """ order = pyblish.api.ValidatorOrder From 73a570d0f45342d2a1a09226de1f12d01db62024 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 13 Nov 2023 12:40:15 +0200 Subject: [PATCH 049/121] better error reporting --- .../plugins/publish/validate_houdini_license_category.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/validate_houdini_license_category.py b/openpype/hosts/houdini/plugins/publish/validate_houdini_license_category.py index 3d9e854dcd5..4124d0c489a 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_houdini_license_category.py +++ b/openpype/hosts/houdini/plugins/publish/validate_houdini_license_category.py @@ -27,11 +27,12 @@ class ValidateHoudiniNotApprenticeLicense(pyblish.api.InstancePlugin): def process(self, instance): if hou.isApprentice(): - families = [instance.data["family"]] - families += instance.data.get("families", []) + # Find which family was matched with the plug-in + families = {instance.data["family"]} + families.update(instance.data.get("families", [])) families = " ".join(families).title() raise PublishValidationError( - "{} Publishing requires a non apprentice license." + "{} publishing requires a non apprentice license." .format(families), title=self.label) From c89ccfc3eaead3db4daddce53e19c042f728b7ce Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 13 Nov 2023 18:42:20 +0800 Subject: [PATCH 050/121] adding default value back to Anti-aliasing Quality and fix tab spaces issues in OP settings --- .../hosts/max/plugins/create/create_review.py | 2 +- .../projects_schema/schema_project_max.json | 28 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/openpype/hosts/max/plugins/create/create_review.py b/openpype/hosts/max/plugins/create/create_review.py index 6651326a052..7aeea39b649 100644 --- a/openpype/hosts/max/plugins/create/create_review.py +++ b/openpype/hosts/max/plugins/create/create_review.py @@ -108,7 +108,7 @@ def get_instance_attr_defs(self): label="Pre-View Preset"), EnumDef("antialiasingQuality", anti_aliasing_enum, - default="None", + default=self.anti_aliasing, label="Anti-aliasing Quality"), BoolDef("vpTexture", label="Viewport Texture", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_max.json b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json index b012e73fc42..82905480faa 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_max.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json @@ -98,10 +98,10 @@ "multiselection": false, "defaults": "exr", "enum_items": [ - {"exr": "exr"}, + {"exr": "exr"}, {"jpg": "jpg"}, - {"png": "png"}, - {"tga": "tga"} + {"png": "png"}, + {"tga": "tga"} ] }, { @@ -111,11 +111,11 @@ "multiselection": false, "defaults": "Realistic", "enum_items": [ - {"Realistic": "Realistic"}, + {"Realistic": "Realistic"}, {"Shaded": "Shaded"}, - {"Facets": "Facets"}, - {"ConsistentColors": "ConsistentColors"}, - {"HiddenLine": "HiddenLine"}, + {"Facets": "Facets"}, + {"ConsistentColors": "ConsistentColors"}, + {"HiddenLine": "HiddenLine"}, {"Wireframe": "Wireframe"}, {"BoundingBox": "BoundingBox"}, {"Ink": "Ink"}, @@ -136,11 +136,11 @@ "multiselection": false, "defaults": "Quality", "enum_items": [ - {"Quality": "Quality"}, + {"Quality": "Quality"}, {"Standard": "Standard"}, - {"Performance": "Performance"}, - {"DXMode": "DXMode"}, - {"Customize": "Customize"} + {"Performance": "Performance"}, + {"DXMode": "DXMode"}, + {"Customize": "Customize"} ] }, { @@ -150,10 +150,10 @@ "multiselection": false, "defaults": "None", "enum_items": [ - {"None": "None"}, + {"None": "None"}, {"2X": "2X"}, - {"4X": "4X"}, - {"8X": "8X"} + {"4X": "4X"}, + {"8X": "8X"} ] }, { From e209308ad71b49b7992aed1377d9c6719c848128 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 13 Nov 2023 12:54:06 +0200 Subject: [PATCH 051/121] BigRoy's comment - only report the conflicting families --- .../plugins/publish/validate_houdini_license_category.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/publish/validate_houdini_license_category.py b/openpype/hosts/houdini/plugins/publish/validate_houdini_license_category.py index 4124d0c489a..5076acda601 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_houdini_license_category.py +++ b/openpype/hosts/houdini/plugins/publish/validate_houdini_license_category.py @@ -30,7 +30,8 @@ def process(self, instance): # Find which family was matched with the plug-in families = {instance.data["family"]} families.update(instance.data.get("families", [])) - families = " ".join(families).title() + disallowed_families = families.intersection(self.families) + families = " ".join(sorted(disallowed_families)).title() raise PublishValidationError( "{} publishing requires a non apprentice license." From 62fa3304178fc0d9d14bf4f7b5a58e2dee1c5680 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Mon, 13 Nov 2023 11:58:47 +0100 Subject: [PATCH 052/121] 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 aef891bd831..e9683bee3af 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -217,7 +217,7 @@ def create( 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 @@ def create( "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 @@ def remove_instances(self, instances: List[CreatedInstance]): 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 df3a70f1993..0773c4dae3c 100644 --- a/openpype/hosts/blender/plugins/create/create_blendScene.py +++ b/openpype/hosts/blender/plugins/create/create_blendScene.py @@ -47,17 +47,7 @@ def _process( "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 a42a9d6ef02..8dbba229a9a 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -52,16 +52,7 @@ def _process( "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 ef1822dedfb..a9bf115ea9b 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -51,16 +51,7 @@ def _process( "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 e4937f989e7..3501071e8f1 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -51,16 +51,7 @@ def _process( "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 b54cc73d4a7..b5b61b3971d 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -51,16 +51,7 @@ def _process( "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 573d103889db8810c6780fb0ee0113ac6bf735df Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 13 Nov 2023 19:10:34 +0800 Subject: [PATCH 053/121] use self.percentSize --- openpype/hosts/max/plugins/create/create_review.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/create/create_review.py b/openpype/hosts/max/plugins/create/create_review.py index 7aeea39b649..0284831533f 100644 --- a/openpype/hosts/max/plugins/create/create_review.py +++ b/openpype/hosts/max/plugins/create/create_review.py @@ -88,7 +88,7 @@ def get_instance_attr_defs(self): default=self.review_height), NumberDef("percentSize", label="Percent of Output", - default=100, + default=self.percentSize, minimum=1, decimals=0), BoolDef("keepImages", From 7ac1d541321dd769c94598fc5c3f7c2c3a200051 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 13 Nov 2023 19:48:42 +0800 Subject: [PATCH 054/121] Renamed preview animation to create review in OP/AYON settings --- openpype/hosts/max/plugins/create/create_review.py | 2 +- openpype/settings/defaults/project_settings/max.json | 2 +- .../entities/schemas/projects_schema/schema_project_max.json | 4 ++-- server_addon/max/server/settings/main.py | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/max/plugins/create/create_review.py b/openpype/hosts/max/plugins/create/create_review.py index 0284831533f..538b445e66c 100644 --- a/openpype/hosts/max/plugins/create/create_review.py +++ b/openpype/hosts/max/plugins/create/create_review.py @@ -24,7 +24,7 @@ class CreateReview(plugin.MaxCreator): @classmethod def apply_settings(cls, project_settings): - settings = project_settings["max"]["PreviewAnimation"] # noqa + settings = project_settings["max"]["CreateReview"] # noqa # Take some defaults from settings cls.review_width = settings.get("review_width", cls.review_width) diff --git a/openpype/settings/defaults/project_settings/max.json b/openpype/settings/defaults/project_settings/max.json index 0a1209668d1..150cc9e6848 100644 --- a/openpype/settings/defaults/project_settings/max.json +++ b/openpype/settings/defaults/project_settings/max.json @@ -16,7 +16,7 @@ "image_format": "exr", "multipass": true }, - "PreviewAnimation": { + "CreateReview": { "review_width": 1920, "review_height": 1080, "percentSize": 100, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_max.json b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json index 82905480faa..78cca357a39 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_max.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json @@ -68,8 +68,8 @@ { "type": "dict", "collapsible": true, - "key": "PreviewAnimation", - "label": "Preview Animation", + "key": "CreateReview", + "label": "Create Review", "children": [ { "type": "number", diff --git a/server_addon/max/server/settings/main.py b/server_addon/max/server/settings/main.py index 0280fcebb94..ef02c6221ee 100644 --- a/server_addon/max/server/settings/main.py +++ b/server_addon/max/server/settings/main.py @@ -32,7 +32,7 @@ class MaxSettings(BaseSettingsModel): default_factory=RenderSettingsModel, title="Render Settings" ) - PreviewAnimation: PreviewAnimationModel = Field( + CreateReview: PreviewAnimationModel = Field( default_factory=PreviewAnimationModel, title="Preview Animation" ) @@ -47,7 +47,7 @@ class MaxSettings(BaseSettingsModel): DEFAULT_VALUES = { "RenderSettings": DEFAULT_RENDER_SETTINGS, - "PreviewAnimation": DEFAULT_PREVIEW_ANIMATION_SETTINGS, + "CreateReview": DEFAULT_PREVIEW_ANIMATION_SETTINGS, "PointCloud": { "attribute": [ {"name": "Age", "value": "age"}, From cf21c8e56f3bfba2f2c509aeb0b6686db0205876 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 13 Nov 2023 20:47:14 +0800 Subject: [PATCH 055/121] reaame preview animation to create review --- ...review_animation.py => create_review_settings.py} | 4 ++-- server_addon/max/server/settings/main.py | 12 ++++++------ server_addon/max/server/version.py | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) rename server_addon/max/server/settings/{preview_animation.py => create_review_settings.py} (97%) diff --git a/server_addon/max/server/settings/preview_animation.py b/server_addon/max/server/settings/create_review_settings.py similarity index 97% rename from server_addon/max/server/settings/preview_animation.py rename to server_addon/max/server/settings/create_review_settings.py index 759ce782914..cc0f35ecb84 100644 --- a/server_addon/max/server/settings/preview_animation.py +++ b/server_addon/max/server/settings/create_review_settings.py @@ -56,7 +56,7 @@ def anti_aliasing_enum(): ] -class PreviewAnimationModel(BaseSettingsModel): +class CreateReviewModel(BaseSettingsModel): review_width: int = Field(1920, title="Review Width") review_height: int = Field(1080, title="Review Height") percentSize: float = Field(100.0, title="Percent of Output") @@ -80,7 +80,7 @@ class PreviewAnimationModel(BaseSettingsModel): vp_texture: bool = Field(True, title="Viewport Texture") -DEFAULT_PREVIEW_ANIMATION_SETTINGS = { +DEFAULT_CREATE_REVIEW_SETTINGS = { "review_width": 1920, "review_height": 1080, "percentSize": 100.0, diff --git a/server_addon/max/server/settings/main.py b/server_addon/max/server/settings/main.py index ef02c6221ee..ea6a11915af 100644 --- a/server_addon/max/server/settings/main.py +++ b/server_addon/max/server/settings/main.py @@ -4,8 +4,8 @@ from .render_settings import ( RenderSettingsModel, DEFAULT_RENDER_SETTINGS ) -from .preview_animation import ( - PreviewAnimationModel, DEFAULT_PREVIEW_ANIMATION_SETTINGS +from .create_review_settings import ( + CreateReviewModel, DEFAULT_CREATE_REVIEW_SETTINGS ) from .publishers import ( PublishersModel, DEFAULT_PUBLISH_SETTINGS @@ -32,9 +32,9 @@ class MaxSettings(BaseSettingsModel): default_factory=RenderSettingsModel, title="Render Settings" ) - CreateReview: PreviewAnimationModel = Field( - default_factory=PreviewAnimationModel, - title="Preview Animation" + CreateReview: CreateReviewModel = Field( + default_factory=CreateReviewModel, + title="Create Review" ) PointCloud: PointCloudSettings = Field( default_factory=PointCloudSettings, @@ -47,7 +47,7 @@ class MaxSettings(BaseSettingsModel): DEFAULT_VALUES = { "RenderSettings": DEFAULT_RENDER_SETTINGS, - "CreateReview": DEFAULT_PREVIEW_ANIMATION_SETTINGS, + "CreateReview": DEFAULT_CREATE_REVIEW_SETTINGS, "PointCloud": { "attribute": [ {"name": "Age", "value": "age"}, diff --git a/server_addon/max/server/version.py b/server_addon/max/server/version.py index 485f44ac21b..b3f4756216d 100644 --- a/server_addon/max/server/version.py +++ b/server_addon/max/server/version.py @@ -1 +1 @@ -__version__ = "0.1.1" +__version__ = "0.1.2" From a4cbd80fa23e4398cd0dbb430c6c060c672b04f9 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 13 Nov 2023 20:56:41 +0800 Subject: [PATCH 056/121] variable renaming & not using classmethod for apply_settings --- .../hosts/max/plugins/create/create_review.py | 25 +++++++++---------- .../server/settings/create_review_settings.py | 6 ++--- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/openpype/hosts/max/plugins/create/create_review.py b/openpype/hosts/max/plugins/create/create_review.py index 538b445e66c..40358aefbf8 100644 --- a/openpype/hosts/max/plugins/create/create_review.py +++ b/openpype/hosts/max/plugins/create/create_review.py @@ -22,22 +22,21 @@ class CreateReview(plugin.MaxCreator): vp_texture = True anti_aliasing = None - @classmethod - def apply_settings(cls, project_settings): + def apply_settings(self, project_settings): settings = project_settings["max"]["CreateReview"] # noqa # Take some defaults from settings - cls.review_width = settings.get("review_width", cls.review_width) - cls.review_height = settings.get("review_height", cls.review_height) - cls.percentSize = settings.get("percentSize", cls.percentSize) - cls.keep_images = settings.get("keep_images", cls.keep_images) - cls.image_format = settings.get("image_format", cls.image_format) - cls.visual_style = settings.get("visual_style", cls.visual_style) - cls.viewport_preset = settings.get( - "viewport_preset", cls.viewport_preset) - cls.anti_aliasing = settings.get( - "anti_aliasing", cls.anti_aliasing) - cls.vp_texture = settings.get("vp_texture", cls.vp_texture) + self.review_width = settings.get("review_width", self.review_width) + self.review_height = settings.get("review_height", self.review_height) + self.percentSize = settings.get("percentSize", self.percentSize) + self.keep_images = settings.get("keep_images", self.keep_images) + self.image_format = settings.get("image_format", self.image_format) + self.visual_style = settings.get("visual_style", self.visual_style) + self.viewport_preset = settings.get( + "viewport_preset", self.viewport_preset) + self.anti_aliasing = settings.get( + "anti_aliasing", self.anti_aliasing) + self.vp_texture = settings.get("vp_texture", self.vp_texture) def create(self, subset_name, instance_data, pre_create_data): # Transfer settings from pre create to instance diff --git a/server_addon/max/server/settings/create_review_settings.py b/server_addon/max/server/settings/create_review_settings.py index cc0f35ecb84..205ebbd09fd 100644 --- a/server_addon/max/server/settings/create_review_settings.py +++ b/server_addon/max/server/settings/create_review_settings.py @@ -35,7 +35,7 @@ def visual_style_enum(): ] -def visual_preset_enum(): +def preview_preset_enum(): """Return enumerator for viewport visual preset.""" return [ {"label": "Quality", "value": "Quality"}, @@ -70,8 +70,8 @@ class CreateReviewModel(BaseSettingsModel): title="Preference" ) viewport_preset: str = Field( - enum_resolver=visual_preset_enum, - title="Pre-View Preset" + enum_resolver=preview_preset_enum , + title="Preview Preset" ) anti_aliasing: str = Field( enum_resolver=anti_aliasing_enum, From 7a778b3a83e74be670da7bd502137aefb386e3e1 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 13 Nov 2023 20:57:20 +0800 Subject: [PATCH 057/121] hound --- server_addon/max/server/settings/create_review_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/max/server/settings/create_review_settings.py b/server_addon/max/server/settings/create_review_settings.py index 205ebbd09fd..43dac0730a1 100644 --- a/server_addon/max/server/settings/create_review_settings.py +++ b/server_addon/max/server/settings/create_review_settings.py @@ -70,7 +70,7 @@ class CreateReviewModel(BaseSettingsModel): title="Preference" ) viewport_preset: str = Field( - enum_resolver=preview_preset_enum , + enum_resolver=preview_preset_enum, title="Preview Preset" ) anti_aliasing: str = Field( From aac075f93b2fa653ccd5dd4f1ca18339a78e42a9 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 13 Nov 2023 20:58:16 +0800 Subject: [PATCH 058/121] renamed the label for preview preset --- openpype/hosts/max/plugins/create/create_review.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/create/create_review.py b/openpype/hosts/max/plugins/create/create_review.py index 40358aefbf8..e8c92fce79e 100644 --- a/openpype/hosts/max/plugins/create/create_review.py +++ b/openpype/hosts/max/plugins/create/create_review.py @@ -104,7 +104,7 @@ def get_instance_attr_defs(self): EnumDef("viewportPreset", preview_preset_enum, default=self.viewport_preset, - label="Pre-View Preset"), + label="Preview Preset"), EnumDef("antialiasingQuality", anti_aliasing_enum, default=self.anti_aliasing, From cc77114db9e5e2ce81bdabdabf07d9ca4d86ef31 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 13 Nov 2023 21:36:22 +0800 Subject: [PATCH 059/121] tweaks on the settings for variable --- openpype/hosts/max/plugins/create/create_review.py | 2 +- openpype/settings/defaults/project_settings/max.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/max/plugins/create/create_review.py b/openpype/hosts/max/plugins/create/create_review.py index e8c92fce79e..78d27a722b0 100644 --- a/openpype/hosts/max/plugins/create/create_review.py +++ b/openpype/hosts/max/plugins/create/create_review.py @@ -20,7 +20,7 @@ class CreateReview(plugin.MaxCreator): visual_style = "Realistic" viewport_preset = "Quality" vp_texture = True - anti_aliasing = None + anti_aliasing = "None" def apply_settings(self, project_settings): settings = project_settings["max"]["CreateReview"] # noqa diff --git a/openpype/settings/defaults/project_settings/max.json b/openpype/settings/defaults/project_settings/max.json index 150cc9e6848..fdaa8d2b910 100644 --- a/openpype/settings/defaults/project_settings/max.json +++ b/openpype/settings/defaults/project_settings/max.json @@ -19,7 +19,7 @@ "CreateReview": { "review_width": 1920, "review_height": 1080, - "percentSize": 100, + "percentSize": 100.0, "keep_images": false, "image_format": "png", "visual_style": "Realistic", From 8d58b284fc5a6fac5aeb535c3adcfabde4f7ff54 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Mon, 13 Nov 2023 17:21:28 +0100 Subject: [PATCH 060/121] 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 e9683bee3af..4bb489dca24 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -20,10 +20,7 @@ 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 92e2473a95e..578ac36ed3a 100644 --- a/openpype/hosts/blender/plugins/publish/collect_render.py +++ b/openpype/hosts/blender/plugins/publish/collect_render.py @@ -73,7 +73,9 @@ def generate_expected_aovs( 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 061/121] 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 5af8104344b..61ff17e4415 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 0b6b93b7a58..7ec10ed6c43 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 fba9a861a00..37842ed90f1 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 3a5b788c9e5..84e1e7d602f 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 2a327f4d659..1c0c033364c 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 8e5e4d37d4a..8e5cc6f6513 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 8ace6a43a7e..07c35207d87 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 04f50f8207d..5a8381dbc34 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 8445560bba0..576c2c33fc6 100644 --- a/openpype/hosts/blender/plugins/publish/extract_layout.py +++ b/openpype/hosts/blender/plugins/publish/extract_layout.py @@ -113,6 +113,9 @@ def _export_animation(self, asset, instance, stagingdir, fbx_count): 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 82ddbc1fc25..07b51becce8 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 176668f3661..8e522977e44 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 48c267fd18c..f109d9c3ce7 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 @@ def get_invalid(instance) -> List: 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 e191585c555..44b33a573a3 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 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 687371b362a..c2d63685e2b 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 @@ def get_invalid(cls, instance) -> List: 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 618feb95c15..737fd80798e 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 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 @@ def get_invalid(instance) -> List: 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 1a98ec4c1db..fbaf40e91f8 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 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 @@ def get_invalid(instance) -> List: 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 d8d2e3c8bf0..19e8d1bc8d1 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 @@ def get_invalid(instance) -> List: 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 ba3a796f35c..0207b0fd092 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 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 66ef731e6e2..3c68e612776 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 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 @@ def get_invalid(cls, instance) -> List: 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 062/121] 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 c2d8a96a18a..91c88f2e282 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 ad2ce54147d..00000000000 --- 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 01c033084b8..6561c896052 100644 --- a/openpype/hosts/blender/plugins/publish/collect_workfile.py +++ b/openpype/hosts/blender/plugins/publish/collect_workfile.py @@ -28,7 +28,7 @@ def process(self, instance): "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 063/121] 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 578ac36ed3a..48b03256ae4 100644 --- a/openpype/hosts/blender/plugins/publish/collect_render.py +++ b/openpype/hosts/blender/plugins/publish/collect_render.py @@ -79,7 +79,7 @@ def process(self, instance): 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 @@ def process(self, instance): "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 61ff17e4415..1602c4d266b 100644 --- a/openpype/hosts/blender/plugins/publish/extract_abc.py +++ b/openpype/hosts/blender/plugins/publish/extract_abc.py @@ -24,7 +24,7 @@ def process(self, instance): 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 @@ def process(self, instance): } 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 7ec10ed6c43..0ed28194079 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 @@ def process(self, instance): 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 @@ def process(self, instance): } 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 37842ed90f1..884169f7f1c 100644 --- a/openpype/hosts/blender/plugins/publish/extract_blend.py +++ b/openpype/hosts/blender/plugins/publish/extract_blend.py @@ -27,7 +27,7 @@ def process(self, instance): 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 @@ def process(self, instance): } 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 84e1e7d602f..2e0de9317e9 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 @@ def process(self, instance): 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 @@ def process(self, instance): } 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 1c0c033364c..9db1505d37d 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 @@ def process(self, instance): 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 @@ def process(self, instance): } 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 8e5cc6f6513..9bbcf047cc3 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 @@ def process(self, instance): 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 @@ def process(self, instance): } 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 07c35207d87..fae438158a6 100644 --- a/openpype/hosts/blender/plugins/publish/extract_fbx.py +++ b/openpype/hosts/blender/plugins/publish/extract_fbx.py @@ -25,7 +25,7 @@ def process(self, instance): 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 @@ def process(self, instance): } 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 5a8381dbc34..ba584ac99a0 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 @@ def process(self, instance): 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 @@ def process(self, instance): 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 @@ def process(self, instance): 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 576c2c33fc6..d0adac6edb2 100644 --- a/openpype/hosts/blender/plugins/publish/extract_layout.py +++ b/openpype/hosts/blender/plugins/publish/extract_layout.py @@ -45,7 +45,7 @@ def _export_animation(self, asset, instance, stagingdir, fbx_count): 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 @@ def process(self, instance): 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 @@ def process(self, instance): } 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 07b51becce8..696cf850895 100644 --- a/openpype/hosts/blender/plugins/publish/extract_playblast.py +++ b/openpype/hosts/blender/plugins/publish/extract_playblast.py @@ -27,8 +27,6 @@ def process(self, instance): 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 @@ def process(self, 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}") project_settings = instance.context.data["project_settings"]["blender"] presets = project_settings["publish"]["ExtractPlayblast"]["presets"] @@ -101,7 +99,7 @@ def process(self, instance): 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 65c3627375e..52e5d98fc4f 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 @@ def process(self, instance): 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 8e522977e44..7e33fd53fa3 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 @@ def process(self, context): 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 b7e5423fa89..623da9c5854 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 064/121] 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 f109d9c3ce7..65697cb86df 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 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 @@ def process(self, instance): 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 14220b5c9cd..58047d7e23a 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 @@ def process(self, instance): "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 c2d63685e2b..dd955dc5da0 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 @@ from openpype.pipeline.publish import ( ValidateContentsOrder, OptionalPyblishPluginMixin, + PublishValidationError ) import openpype.hosts.blender.api.action @@ -60,6 +61,6 @@ def process(self, instance): 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 737fd80798e..a498a3b4cb7 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 @@ from openpype.pipeline.publish import ( ValidateContentsOrder, OptionalPyblishPluginMixin, + PublishValidationError ) import openpype.hosts.blender.api.action @@ -36,6 +37,6 @@ def process(self, instance): 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 fbaf40e91f8..17119f8d88b 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 @@ from openpype.pipeline.publish import ( ValidateContentsOrder, OptionalPyblishPluginMixin, + PublishValidationError ) @@ -45,6 +46,6 @@ def process(self, instance): 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 19e8d1bc8d1..3b6f29a79ed 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 @@ 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 @@ def process(self, instance): 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 0207b0fd092..86d1fcc6815 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 pyblish.api -from openpype.pipeline.publish import OptionalPyblishPluginMixin +from openpype.pipeline.publish import ( + OptionalPyblishPluginMixin, + PublishValidationError +) class ValidateRenderCameraIsSet(pyblish.api.InstancePlugin, @@ -20,4 +23,4 @@ def process(self, instance): 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 3c68e612776..5270a508883 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 @@ from openpype.pipeline.publish import ( ValidateContentsOrder, OptionalPyblishPluginMixin, + PublishValidationError ) @@ -47,7 +48,7 @@ def process(self, instance): 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 065/121] 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 44b33a573a3..442f856e058 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 @@ def process(self, context, plugin): 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 066/121] Simplify collecting instances (re-use logic) --- openpype/hosts/blender/api/plugin.py | 61 ++++++++++++---------------- 1 file changed, 25 insertions(+), 36 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 4bb489dca24..1114d136cee 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 @@ def cache_subsets(shared_data): 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': - 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: - cache_legacy.setdefault(family, []) - if family: - # Legacy creator instance - cache_legacy.setdefault(family, []).append( - avalon_prop - ) + avalon_instance_objs = ( + avalon_instances.objects if avalon_instances else [] + ) + + 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 + + if avalon_prop.get('id') != 'pyblish.avalon.instance': + continue + + 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 067/121] 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 3ebc6515d37..7669d769438 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 068/121] 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 9f41e37f222..38d61a70710 100644 --- a/openpype/plugins/publish/collect_comment.py +++ b/openpype/plugins/publish/collect_comment.py @@ -103,10 +103,10 @@ def process(self, context): 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 069/121] Cosmetics --- openpype/hosts/blender/api/plugin.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index e126511fd69..0399bf2e50e 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -256,19 +256,16 @@ def collect_instances(self): 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') - - 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) + # Create instance object from existing data + instance = CreatedInstance.from_existing( + instance_data=instance_data.to_dict(), + creator=self + ) + + # 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 070/121] 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 22c590d4bd6..c617144b6fa 100644 --- a/openpype/hosts/blender/api/ops.py +++ b/openpype/hosts/blender/api/ops.py @@ -31,6 +31,14 @@ 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 c3f8f066943..9ac59f5620c 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -138,7 +138,10 @@ def get_context_data(self) -> dict: 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 @@ def update_context_data(self, data: dict, changes: dict): 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 0399bf2e50e..d1cefe0a62d 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -12,6 +12,8 @@ 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 @@ def cache_subsets(shared_data): 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 @@ def create( 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, - } - - self.set_instance_data(subset_name, instance_data, instance_node) - - self._add_instance_to_context( - CreatedInstance( - self.family, subset_name, instance_data, self - ) + # 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 ) + instance.transient_data["instance_node"] = instance_node + self._add_instance_to_context(instance) - imprint(collection, instance_data) + imprint(instance_node, instance_data) - return collection + return instance_node def collect_instances(self): """Override abstract method from BaseCreator. @@ -257,12 +263,14 @@ def collect_instances(self): 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 @@ def update_instances(self, update_list): 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 @@ def set_instance_data( "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 ac425dff74f..f14023639f2 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 @@ def create( ) # 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 f7cb9f88aa7..6f0cabb2598 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -21,7 +21,7 @@ def create( 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 1dc8f44a63a..f1090ae397a 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) - - # Create instance object - asset = instance_data.get("asset") - name = plugin.asset_name(asset, subset_name) + instance_node = super().create(subset_name, + instance_data, + pre_create_data) - # 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 8dbba229a9a..d30c60ba210 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) - - 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) - 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 @@ def _process( 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 a9bf115ea9b..f569a81c29c 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" - 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) + create_as_asset_group = True - def _process( + 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["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 3501071e8f1..9774d4bec3e 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" - 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) - ) + create_as_asset_group = True - mti = ops.MainThreadItem( - self._process, subset_name, instance_data, pre_create_data - ) - ops.execute_in_main_thread(mti) - - def _process( + @ops.execute_function_in_main_thread + 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["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 d05b3adb042..d80a1b07f03 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -20,7 +20,7 @@ def create( 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 91333b77418..2d521c42558 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -21,11 +21,12 @@ def create( 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 b5b61b3971d..87f01241648 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" - 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) + create_as_asset_group = True - def _process( + @ops.execute_function_in_main_thread + 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["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 d1529f75f62..8f5adb55836 100644 --- a/openpype/hosts/blender/plugins/create/create_workfile.py +++ b/openpype/hosts/blender/plugins/create/create_workfile.py @@ -3,8 +3,10 @@ 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 @@ def create(self): 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 @@ def create(self): ) 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 + + # Add instance to create context + self._add_instance_to_context(instance) - created_inst["instance_node"] = node - data = created_inst.data_to_store() + def remove_instances(self, instances): + for instance in instances: + node = instance.transient_data["instance_node"] + del node[AVALON_PROPERTY] - imprint(node, data) + 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 f7bbc630de5..1804de29b61 100644 --- a/openpype/hosts/blender/plugins/load/load_blend.py +++ b/openpype/hosts/blender/plugins/load/load_blend.py @@ -60,6 +60,7 @@ def _post_process_layout(self, container, asset, representation): 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 81683b8de81..a941c77a8ee 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 @@ def _process(self, libpath, asset, asset_group, actions): # 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 48b03256ae4..00faf85aed1 100644 --- a/openpype/hosts/blender/plugins/publish/collect_render.py +++ b/openpype/hosts/blender/plugins/publish/collect_render.py @@ -73,9 +73,8 @@ def generate_expected_aovs( 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 4889c66be3f..2c077398dad 100644 --- a/openpype/hosts/blender/plugins/publish/collect_review.py +++ b/openpype/hosts/blender/plugins/publish/collect_review.py @@ -16,9 +16,7 @@ def process(self, instance): 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 7669d769438..6845c29b379 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 071/121] 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 c6b5ffe0111..44ca7582b98 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 f1090ae397a..c580cdfe46a 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 d30c60ba210..13075f718e0 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 f569a81c29c..05d11bf3150 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 9774d4bec3e..9f1367ebb2f 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 87f01241648..c7ab9b81e17 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 072/121] 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 9ac59f5620c..b9f802d2212 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -10,7 +10,7 @@ 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 9f1367ebb2f..70473040143 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -4,6 +4,7 @@ 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 073/121] 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 8f5adb55836..4dda1a31cc5 100644 --- a/openpype/hosts/blender/plugins/create/create_workfile.py +++ b/openpype/hosts/blender/plugins/create/create_workfile.py @@ -78,15 +78,13 @@ def create(self): 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 1804de29b61..84e181cd6c0 100644 --- a/openpype/hosts/blender/plugins/load/load_blend.py +++ b/openpype/hosts/blender/plugins/load/load_blend.py @@ -91,7 +91,6 @@ def _process_data(self, libpath, group_name): 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 @@ def _process_data(self, libpath, group_name): 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 074/121] 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 4dda1a31cc5..ffd423ad747 100644 --- a/openpype/hosts/blender/plugins/create/create_workfile.py +++ b/openpype/hosts/blender/plugins/create/create_workfile.py @@ -10,7 +10,13 @@ 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 075/121] 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 13075f718e0..b0c0fc28db2 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 @@ def create( 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 @@ def create( 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 05d11bf3150..93fb91a3242 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -27,10 +27,7 @@ def create( # 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 70473040143..8b5eaeb999f 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 @@ def create( # 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 c7ab9b81e17..82284422499 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 @@ def create( # 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 076/121] 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 b9f802d2212..ee43108594a 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -14,6 +14,7 @@ HostBase, IWorkfileHost, IPublishHost, + ILoadHost ) from openpype.client import get_asset_by_name from openpype.pipeline import ( @@ -60,7 +61,7 @@ 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 @@ def install(self): 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 077/121] 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 991cd040deb..a1b49dcd8f9 100644 --- a/openpype/hosts/blender/plugins/publish/extract_blend.py +++ b/openpype/hosts/blender/plugins/publish/extract_blend.py @@ -19,9 +19,6 @@ 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 6b59b718238f51ec1aef7be40b804b842adf6062 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 11:42:12 +0100 Subject: [PATCH 078/121] 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 00000000000..f8fc0383472 --- /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 079/121] 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 dd955dc5da0..060bccbd04f 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 080/121] 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 6845c29b379..51a1dcf6ca6 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 081/121] 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 65697cb86df..ee0a0e4dc9f 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 @@ def process(self, instance): 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 a498a3b4cb7..7f77bbe38ce 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 @@ def process(self, instance): 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 17119f8d88b..caf555b5352 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 @@ def process(self, instance): 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 3b6f29a79ed..ab5f4bb467c 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 @@ def process(self, instance): 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 5270a508883..1fb9535ee41 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 @@ def process(self, instance): 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 082/121] 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 c617144b6fa..408e38e6cde 100644 --- a/openpype/hosts/blender/api/ops.py +++ b/openpype/hosts/blender/api/ops.py @@ -428,7 +428,6 @@ def draw(self, context): 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 d1cefe0a62d..57fccf92990 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -430,7 +430,7 @@ def _load(self, 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 083/121] 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 6f0cabb2598..b3d10090e3f 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 @@ def create( 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 084/121] 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 2d521c42558..3dd52395df5 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 @@ def create( 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 085/121] 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 b0c0fc28db2..d624a7f05a0 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 086/121] 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 ee0a0e4dc9f..9b6e5138978 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 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 087/121] 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 f8fc0383472..b170e3d06fd 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 088/121] 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 1602c4d266b..b0cd6d0d324 100644 --- a/openpype/hosts/blender/plugins/publish/extract_abc.py +++ b/openpype/hosts/blender/plugins/publish/extract_abc.py @@ -28,18 +28,15 @@ def process(self, instance): 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 0ed28194079..1c23fc8acb6 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 @@ def process(self, instance): 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 9db1505d37d..9d0b7f132b1 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 @@ def process(self, instance): 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 fae438158a6..0ba82eca4e5 100644 --- a/openpype/hosts/blender/plugins/publish/extract_fbx.py +++ b/openpype/hosts/blender/plugins/publish/extract_fbx.py @@ -29,14 +29,12 @@ def process(self, instance): 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 ba584ac99a0..6fbcdc63c39 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 @@ def process(self, instance): # 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 b382b7c77609be4b78fbd00e0758a1e3218224b8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 14 Nov 2023 14:57:53 +0100 Subject: [PATCH 089/121] Chore: Create plugin auto-apply settings (#5908) * implemented default logic 'apply_settings' to auto-apply values * added docstring to 'apply_settings' * replace auto apply settings logic in maya * add missing quote Co-authored-by: Roy Nieterau --------- Co-authored-by: Roy Nieterau --- openpype/hosts/maya/api/plugin.py | 24 ++---- openpype/pipeline/create/creator_plugins.py | 85 ++++++++++++++++++++- 2 files changed, 89 insertions(+), 20 deletions(-) diff --git a/openpype/hosts/maya/api/plugin.py b/openpype/hosts/maya/api/plugin.py index 07167a9a32a..212c4df4927 100644 --- a/openpype/hosts/maya/api/plugin.py +++ b/openpype/hosts/maya/api/plugin.py @@ -271,7 +271,7 @@ def _default_remove_instances(self, instances): @six.add_metaclass(ABCMeta) class MayaCreator(NewCreator, MayaCreatorBase): - settings_name = None + settings_category = "maya" def create(self, subset_name, instance_data, pre_create_data): @@ -317,24 +317,6 @@ def get_pre_create_attr_defs(self): default=True) ] - def apply_settings(self, project_settings): - """Method called on initialization of plugin to apply settings.""" - - settings_name = self.settings_name - if settings_name is None: - settings_name = self.__class__.__name__ - - settings = project_settings["maya"]["create"] - settings = settings.get(settings_name) - if settings is None: - self.log.debug( - "No settings found for {}".format(self.__class__.__name__) - ) - return - - for key, value in settings.items(): - setattr(self, key, value) - class MayaAutoCreator(AutoCreator, MayaCreatorBase): """Automatically triggered creator for Maya. @@ -343,6 +325,8 @@ class MayaAutoCreator(AutoCreator, MayaCreatorBase): any arguments. """ + settings_category = "maya" + def collect_instances(self): return self._default_collect_instances() @@ -360,6 +344,8 @@ class MayaHiddenCreator(HiddenCreator, MayaCreatorBase): arguments for 'create' method. """ + settings_category = "maya" + def create(self, *args, **kwargs): return MayaCreator.create(self, *args, **kwargs) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 6aa08cae705..b51f69379ca 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import copy import collections @@ -193,6 +194,12 @@ class BaseCreator: # QUESTION make this required? host_name = None + # Settings auto-apply helpers + # Root key in project settings (mandatory for auto-apply to work) + settings_category = None + # Name of plugin in create settings > class name is used if not set + settings_name = None + def __init__( self, project_settings, system_settings, create_context, headless=False ): @@ -233,14 +240,90 @@ def __init__( " need to keep system settings." ).format(self.__class__.__name__)) + @staticmethod + def _get_settings_values(project_settings, category_name, plugin_name): + """Helper method to get settings values. + + Args: + project_settings (dict[str, Any]): Project settings. + category_name (str): Category of settings. + plugin_name (str): Name of settings. + + Returns: + Union[dict[str, Any], None]: Settings values or None. + """ + + settings = project_settings.get(category_name) + if not settings: + return None + + create_settings = settings.get("create") + if not create_settings: + return None + + return create_settings.get(plugin_name) + def apply_settings(self, project_settings): """Method called on initialization of plugin to apply settings. + Default implementation tries to auto-apply settings values if are + in expected hierarchy. + + Data hierarchy to auto-apply settings: + ├─ {self.settings_category} - Root key in settings + │ └─ "create" - Hardcoded key + │ └─ {self.settings_name} | {class name} - Name of plugin + │ ├─ ... attribute values... - Attribute/value pair + + It is mandatory to define 'settings_category' attribute. Attribute + 'settings_name' is optional and class name is used if is not defined. + + Example data: + ProjectSettings { + "maya": { # self.settings_category + "create": { # Hardcoded key + "CreateAnimation": { # self.settings_name / class name + "enabled": True, # --- Attributes to set --- + "optional": True,# + "active": True, # + "fps": 25, # ------------------------- + }, + ... + }, + ... + }, + ... + } + Args: project_settings (dict[str, Any]): Project settings. """ - pass + settings_category = self.settings_category + if not settings_category: + return + + cls_name = self.__class__.__name__ + settings_name = self.settings_name or cls_name + + settings = self._get_settings_values( + project_settings, settings_category, settings_name + ) + if settings is None: + self.log.debug("No settings found for {}".format(cls_name)) + return + + for key, value in settings.items(): + # Log out attributes that are not defined on plugin object + # - those may be potential dangerous typos in settings + if not hasattr(self, key): + self.log.debug(( + "Applying settings to unknown attribute '{}' on '{}'." + ).format( + key, cls_name + )) + setattr(self, key, value) + @property def identifier(self): From 162394a56cfd3f62756603dd821596c478191747 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 15:17:55 +0100 Subject: [PATCH 090/121] 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 b170e3d06fd..72c8f133621 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 091/121] 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 d0adac6edb2..7e8ca7a250b 100644 --- a/openpype/hosts/blender/plugins/publish/extract_layout.py +++ b/openpype/hosts/blender/plugins/publish/extract_layout.py @@ -128,13 +128,22 @@ def process(self, instance): 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 092/121] 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 7e8ca7a250b..73d92961bc3 100644 --- a/openpype/hosts/blender/plugins/publish/extract_layout.py +++ b/openpype/hosts/blender/plugins/publish/extract_layout.py @@ -136,8 +136,8 @@ def process(self, instance): 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 093/121] 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 c580cdfe46a..bda026286d8 100644 --- a/openpype/hosts/blender/plugins/create/create_blendScene.py +++ b/openpype/hosts/blender/plugins/create/create_blendScene.py @@ -5,7 +5,7 @@ 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 094/121] 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 5d8c93dc49b..e80ed61bc81 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 095/121] 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 b0cd6d0d324..415cbb186c5 100644 --- a/openpype/hosts/blender/plugins/publish/extract_abc.py +++ b/openpype/hosts/blender/plugins/publish/extract_abc.py @@ -32,8 +32,9 @@ def process(self, instance): 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 096/121] 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 415cbb186c5..12d062d925e 100644 --- a/openpype/hosts/blender/plugins/publish/extract_abc.py +++ b/openpype/hosts/blender/plugins/publish/extract_abc.py @@ -4,7 +4,6 @@ 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 097/121] 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 bda026286d8..c09261705f6 100644 --- a/openpype/hosts/blender/plugins/create/create_blendScene.py +++ b/openpype/hosts/blender/plugins/create/create_blendScene.py @@ -8,6 +8,7 @@ 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 098/121] 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 57fccf92990..5a0026d862f 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -325,8 +325,6 @@ def set_instance_data( { "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 099/121] 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 f14023639f2..0929778d78c 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 b3d10090e3f..3a91b2d5ff5 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 c09261705f6..e1026282c0b 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 d624a7f05a0..2e2e6cec22c 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 93fb91a3242..16d227e50e4 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 8b5eaeb999f..2f3f61728b2 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 d80a1b07f03..b3329bcb3ba 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 bffa5696df2..7fb3e5eb006 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 3dd52395df5..940bcbea227 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 82284422499..d63b8d56ff2 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 100/121] 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 00000000000..f05a6b1f5a4 --- /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 101/121] 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 84e181cd6c0..8b1af5a0da4 100644 --- a/openpype/hosts/blender/plugins/load/load_blend.py +++ b/openpype/hosts/blender/plugins/load/load_blend.py @@ -4,11 +4,11 @@ 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 @@ def _post_process_layout(self, container, asset, representation): 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 102/121] 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 72c8f133621..3d4e634e9ea 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 103/121] 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 3d4e634e9ea..4685472213e 100644 --- a/openpype/hosts/blender/plugins/publish/collect_instance.py +++ b/openpype/hosts/blender/plugins/publish/collect_instance.py @@ -25,7 +25,6 @@ def process(self, instance): 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 104/121] 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 408e38e6cde..c1403d44b65 100644 --- a/openpype/hosts/blender/api/ops.py +++ b/openpype/hosts/blender/api/ops.py @@ -246,8 +246,24 @@ def execute(self, context): 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 @@ def execute(self, context): 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 105/121] 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 c1403d44b65..f4d96e563aa 100644 --- a/openpype/hosts/blender/api/ops.py +++ b/openpype/hosts/blender/api/ops.py @@ -261,7 +261,7 @@ def pull_to_front(window): 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 @@ def pull_to_front(window): 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 106/121] 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 ee43108594a..b386dd49d3a 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -6,7 +6,7 @@ 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 107/121] 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 44ca7582b98..00000000000 --- 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 108/121] 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 ffd423ad747..28c21c58113 100644 --- a/openpype/hosts/blender/plugins/create/create_workfile.py +++ b/openpype/hosts/blender/plugins/create/create_workfile.py @@ -74,13 +74,9 @@ def create(self): 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 109/121] 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 28c21c58113..92454347663 100644 --- a/openpype/hosts/blender/plugins/create/create_workfile.py +++ b/openpype/hosts/blender/plugins/create/create_workfile.py @@ -73,7 +73,6 @@ def create(self): 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 110/121] 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 6fbcdc63c39..712fbb2d14a 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 @@ def process(self, instance): 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 @@ def process(self, instance): 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 @@ def process(self, instance): 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 8fe8154fec7a74bc7b039457c9cf9e7c68c88eab Mon Sep 17 00:00:00 2001 From: Ynbot Date: Wed, 15 Nov 2023 03:25:41 +0000 Subject: [PATCH 111/121] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index 611fdc82cea..b7394c203d0 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.2" +__version__ = "3.17.6-nightly.3" From 72b1e759e7392fcc8f83b4f73ba3f7c56376eb8e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 15 Nov 2023 03:26:29 +0000 Subject: [PATCH 112/121] 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 e3777730075..86e3638ffe2 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-nightly.3 - 3.17.6-nightly.2 - 3.17.6-nightly.1 - 3.17.5 @@ -134,7 +135,6 @@ body: - 3.15.2-nightly.2 - 3.15.2-nightly.1 - 3.15.1 - - 3.15.1-nightly.6 validations: required: true - type: dropdown From 91e230a321d6946b612fb9476f8dbe56e179c14b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 15 Nov 2023 10:09:37 +0100 Subject: [PATCH 113/121] 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 3707ef97aae..53061c68858 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 @@ def _remove_container(self, container): 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 f6899843a39..1d73318f6ec 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 @@ def _selection_interface(self, session, event_values=None): "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 07a8ff433ed..8632f038b8e 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 @@ def set_next_task_statuses( 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 0aa0b9f9f54..d4dc53b6558 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 @@ def get_found_data(entity): 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 c731713c106..0efade9d8f3 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 @@ def launch(self, session, entities, event): 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 72a5efbcfee..e1df8e15376 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 @@ def interface(self, session, entities, event): 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 559de3a24d7..c198389b988 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 @@ def interface(self, session, entities, event): 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 dd68c75f846..250670f016f 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 @@ def interface(self, session, entities, event): 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 983c9223c66..cc6b99e8f88 100644 --- a/openpype/plugins/publish/cleanup_explicit.py +++ b/openpype/plugins/publish/cleanup_explicit.py @@ -58,21 +58,21 @@ def _remove_full_paths(self, full_paths): # 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 @@ def _remove_full_paths(self, full_paths): 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 @@ def _remove_empty_dirs(self, empty_dirpaths): 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 @@ def _remove_empty_dirs(self, empty_dirpaths): 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 a249b3acda7..6160b4f5c84 100644 --- a/openpype/plugins/publish/collect_rendered_files.py +++ b/openpype/plugins/publish/collect_rendered_files.py @@ -54,6 +54,8 @@ def _fill_staging_dir(self, data_object, anatomy): 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 @@ def _process_path(self, data, anatomy): 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 @@ def process(self, context): 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 dc8aab6ce4a..9a978ed286a 100644 --- a/openpype/plugins/publish/extract_burnin.py +++ b/openpype/plugins/publish/extract_burnin.py @@ -171,8 +171,6 @@ def main_process(self, instance): ).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 @@ def prepare_basic_data(self, instance): 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 dbf1b6c8a6b..faacb7af2ea 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -326,7 +326,6 @@ def _get_profile(self, instance): " | 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 db8a030dfa3..cd0f78530ae 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -143,7 +143,7 @@ def _get_outputs_per_representations(self, instance, profile_outputs): 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 0c12255d384..b154940469b 100644 --- a/openpype/plugins/publish/integrate_thumbnail.py +++ b/openpype/plugins/publish/integrate_thumbnail.py @@ -200,7 +200,7 @@ def _get_thumbnail_path_from_published(self, published_representations): 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 cf05327ce80..f9b48eebecb 100644 --- a/openpype/plugins/publish/integrate_thumbnail_ayon.py +++ b/openpype/plugins/publish/integrate_thumbnail_ayon.py @@ -137,7 +137,7 @@ def _get_instance_thumbnail_path(self, published_representations): 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 88d2dc82963..f26d86e6df1 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -352,7 +352,7 @@ def get_child_path(self, child_obj): 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 0209681e956..a25c22aa196 100644 --- a/openpype/settings/entities/dict_immutable_keys_entity.py +++ b/openpype/settings/entities/dict_immutable_keys_entity.py @@ -232,7 +232,7 @@ def get_child_path(self, child_obj): 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 e6d332b9adb..c11a7cf0593 100644 --- a/openpype/settings/entities/dict_mutable_keys_entity.py +++ b/openpype/settings/entities/dict_mutable_keys_entity.py @@ -284,7 +284,7 @@ def get_child_path(self, child_obj): 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 3b756e4ede3..c888cf3b78f 100644 --- a/openpype/settings/entities/item_entities.py +++ b/openpype/settings/entities/item_entities.py @@ -295,7 +295,7 @@ def get_child_path(self, child_obj): 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 5d6a64b3eaf..d9a18e01774 100644 --- a/openpype/settings/entities/list_entity.py +++ b/openpype/settings/entities/list_entity.py @@ -258,7 +258,7 @@ def get_child_path(self, child_obj): 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 f2e24fb522b..bd617c6b7c4 100644 --- a/openpype/settings/entities/root_entities.py +++ b/openpype/settings/entities/root_entities.py @@ -270,7 +270,7 @@ def get_child_path(self, child_entity): 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 114/121] 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 2c55e7c9516..a1f752605d9 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 @@ def RenderExecutable(self): } 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 @@ def RenderExecutable(self): 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 e9b81369cac..642608f9911 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 115/121] 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 712fbb2d14a..a705345edbd 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.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 @@ def process(self, instance): # 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 @@ def process(self, instance): 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 @@ def process(self, instance): 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 @@ def process(self, instance): ) 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 116/121] 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 5a0026d862f..7ac12b55493 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -282,11 +282,26 @@ def update_instances(self, update_list): 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 117/121] 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 7c49c837e9b..2ecaf87fce9 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -62,19 +62,6 @@ "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 20264c2cdfc..8b57c2e481f 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 dccb4ade78f..3409b4ec91d 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py @@ -12,6 +12,7 @@ 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 @@ def get_invalid(cls, instance): 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 @@ def repair(cls, instance): 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 @@ def repair(cls, instance): 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 5c95744876b..f6b3bfbbfd4 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 @@ 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 118/121] 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 58047d7e23a..d8826adc9ca 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 @@ def process(self, instance): @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 119/121] 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 69722b5e219..8895515b1a5 100644 --- a/openpype/tools/ayon_utils/models/__init__.py +++ b/openpype/tools/ayon_utils/models/__init__.py @@ -13,6 +13,7 @@ HIERARCHY_MODEL_SENDER, ) from .thumbnails import ThumbnailsModel +from .selection import HierarchyExpectedSelection __all__ = ( @@ -29,4 +30,6 @@ "HIERARCHY_MODEL_SENDER", "ThumbnailsModel", + + "HierarchyExpectedSelection", ) diff --git a/openpype/tools/ayon_utils/models/cache.py b/openpype/tools/ayon_utils/models/cache.py index 44b97e930df..221a14160ca 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 @@ def get(self, key): 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 00000000000..0ff239882b4 --- /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 f98bfcdf8a5..728433f929d 100644 --- a/openpype/tools/ayon_utils/widgets/projects_widget.py +++ b/openpype/tools/ayon_utils/widgets/projects_widget.py @@ -503,17 +503,6 @@ def set_current_context_project(self, project_name): 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 @@ def set_standard_filter_enabled(self, enabled): 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 ce399fd4c62..260f701d4b2 100644 --- a/openpype/tools/ayon_workfiles/abstract.py +++ b/openpype/tools/ayon_workfiles/abstract.py @@ -443,8 +443,11 @@ def project_settings(self): 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 @@ def get_project_entity(self): 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 @@ def get_folder_entity(self, folder_id): 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 @@ def get_selected_task_name(self): 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 @@ def expected_task_selected(self, folder_id, task_name): 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 @@ def go_to_current_context(self): # 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 @@ def get_folder_items(self, sender): 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 @@ def get_folder_items(self, sender): 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 @@ def get_task_items(self, folder_id, sender): 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 @@ def save_workfile_info(self, folder_id, task_id, filepath, note): 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 3784959cafb..fbe6df31551 100644 --- a/openpype/tools/ayon_workfiles/control.py +++ b/openpype/tools/ayon_workfiles/control.py @@ -16,93 +16,120 @@ ) 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 WorkfilesToolExpectedSelection(HierarchyExpectedSelection): + def __init__(self, controller): + super(WorkfilesToolExpectedSelection, self).__init__( + controller, + handle_project=False, + handle_folder=True, + handle_task=True, + ) -class ExpectedSelection: - def __init__(self): - self._folder_id = None - self._task_name = None 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 @@ def __init__(self, host=None): # 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 @@ def is_host_valid(self): 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 @@ def project_anatomy(self): 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 @@ def get_selected_task_id(self): 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 @@ def set_expected_selection( 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 @@ def go_to_current_context(self): ) # 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 @@ def fill_workarea_filepath( 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 @@ def save_workfile_info(self, folder_id, task_id, filepath, note): 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 @@ def refresh(self): 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 @@ def _get_event_context_data( 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 @@ def _save_as_workfile( ): # 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 @@ def _save_as_workfile( 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 @@ def _save_as_workfile( # 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 d906b9e7bdc..734cb08cb6c 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 a1d51525daf..00000000000 --- 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 ad034794d84..2f0896842d0 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 @@ def set_selected_folder(self, folder_id): 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 @@ def get_selected_task_name(self): 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 @@ def set_selected_task(self, folder_id, task_id, task_name): 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 @@ def set_selected_workfile_path(self, path): 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 @@ def set_selected_representation_id(self, representation_id): 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 4d989ed22c3..907b9b53836 100644 --- a/openpype/tools/ayon_workfiles/models/workfiles.py +++ b/openpype/tools/ayon_workfiles/models/workfiles.py @@ -148,7 +148,9 @@ def _get_base_data(self): 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 @@ def _get_folder_data(self, folder_id): 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 @@ def _prepare_fill_data(self, folder_id, task_id): 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 @@ def get_workarea_save_as_data(self, folder_id, task_id): 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 @@ def save_workfile_info(self, folder_id, task_id, filepath, note): ) 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 656ddf1dd82..16f0b6fce39 100644 --- a/openpype/tools/ayon_workfiles/widgets/files_widget.py +++ b/openpype/tools/ayon_workfiles/widgets/files_widget.py @@ -69,7 +69,7 @@ def __init__(self, controller, parent): 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 576cf18d730..704f7b2f39b 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 @@ def __init__(self, controller): 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 @@ def get_index_by_representation_id(self, representation_id): 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 @@ def _remove_empty_item(self): 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 @@ def _on_mouse_double_click(self, event): 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 @@ def _on_expected_selection_change(self, event): 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 3a8e90f9333..8eefd3cf812 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 @@ def __init__(self, controller): 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 @@ def get_index_by_filename(self, filename): 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 @@ def _remove_empty_item(self): 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 @@ def _on_duplicate_pressed(self): 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 @@ def _on_expected_selection_change(self, event): 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 b04f8e4098d..00000000000 --- 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 7f06576a000..5085f4701ec 100644 --- a/openpype/tools/ayon_workfiles/widgets/side_panel.py +++ b/openpype/tools/ayon_workfiles/widgets/side_panel.py @@ -66,7 +66,7 @@ def __init__(self, controller, parent): 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 04f5b286b19..00000000000 --- 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 6218d2dd06e..eb2f2bc1c72 100644 --- a/openpype/tools/ayon_workfiles/widgets/window.py +++ b/openpype/tools/ayon_workfiles/widgets/window.py @@ -5,32 +5,16 @@ 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 @@ def __init__(self, controller=None, parent=None): 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 @@ def __init__(self, controller=None, parent=None): 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 @@ def __init__(self, controller=None, parent=None): 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 @@ def _create_col_1_widget(self, controller, parent): 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 @@ def _create_col_1_widget(self, controller, parent): 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 @@ def ensure_visible(self, use_context=True, save=True, on_top=False): 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 @@ def _on_published_checkbox_changed(self): 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 @@ def _on_controller_refresh_finished(self): 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 120/121] 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 7ac12b55493..d7155a1b538 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -28,7 +28,7 @@ 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 @@ def create( 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 @@ def update_instances(self, update_list): "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 @@ def _load(self, 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 @@ def _load(self, # 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 0929778d78c..caaa72fe8d2 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -22,7 +22,7 @@ def create( ) # 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 4f5016d422a..331f6a8bdbf 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 8d1863d4d55..d7e82d1900c 100644 --- a/openpype/hosts/blender/plugins/load/load_abc.py +++ b/openpype/hosts/blender/plugins/load/load_abc.py @@ -137,9 +137,9 @@ def process_asset( 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 3447e67ebf8..f7d32f92a5e 100644 --- a/openpype/hosts/blender/plugins/load/load_action.py +++ b/openpype/hosts/blender/plugins/load/load_action.py @@ -7,7 +7,7 @@ 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 @@ 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 @@ def process_asset( 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 @@ def update(self, container: Dict, representation: Dict): 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 ac8f3633166..1e5bd39a320 100644 --- a/openpype/hosts/blender/plugins/load/load_audio.py +++ b/openpype/hosts/blender/plugins/load/load_audio.py @@ -42,9 +42,9 @@ def process_asset( 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 8b1af5a0da4..f437e667958 100644 --- a/openpype/hosts/blender/plugins/load/load_blend.py +++ b/openpype/hosts/blender/plugins/load/load_blend.py @@ -133,9 +133,9 @@ def process_asset( 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 2c955af9e82..6cc7f39d030 100644 --- a/openpype/hosts/blender/plugins/load/load_blendscene.py +++ b/openpype/hosts/blender/plugins/load/load_blendscene.py @@ -85,9 +85,9 @@ def process_asset( 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 05d3fb764dd..ecd6bb98f19 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 @@ def process_asset( 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 3cca6e7fd31..2d53d3e573f 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 @@ def process_asset( 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 e129ea6754f..8fce53a5d54 100644 --- a/openpype/hosts/blender/plugins/load/load_fbx.py +++ b/openpype/hosts/blender/plugins/load/load_fbx.py @@ -134,9 +134,9 @@ def process_asset( 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 a941c77a8ee..748ac619b64 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 @@ def process_asset(self, 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 c121f556333..8d3118d83b4 100644 --- a/openpype/hosts/blender/plugins/load/load_look.py +++ b/openpype/hosts/blender/plugins/load/load_look.py @@ -96,14 +96,14 @@ def process_asset( 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 121/121] 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 d7155a1b538..8d33187da33 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -298,7 +298,9 @@ def update_instances(self, update_list): "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 @@ def _load(self, # 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)