From 507adfa9368f24d9eaff47e5d386cad08671ab9e Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Mon, 5 Jun 2023 18:39:32 +0200 Subject: [PATCH 001/101] Refactor: Blender new publisher --- openpype/hosts/blender/api/ops.py | 6 ++++- openpype/hosts/blender/api/plugin.py | 25 ++++++++++++++++--- .../blender/plugins/create/create_action.py | 2 +- .../plugins/create/create_animation.py | 2 +- .../blender/plugins/create/create_camera.py | 2 +- .../blender/plugins/create/create_layout.py | 2 +- .../blender/plugins/create/create_model.py | 2 +- .../plugins/create/create_pointcache.py | 2 +- .../blender/plugins/create/create_review.py | 2 +- .../blender/plugins/create/create_rig.py | 2 +- .../blender/plugins/publish/extract_blend.py | 5 +++- 11 files changed, 39 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/blender/api/ops.py b/openpype/hosts/blender/api/ops.py index 208c11cfe8..22c590d4bd 100644 --- a/openpype/hosts/blender/api/ops.py +++ b/openpype/hosts/blender/api/ops.py @@ -275,6 +275,10 @@ class LaunchCreator(LaunchQtApp): def before_window_show(self): self._window.refresh() + def execute(self, context): + host_tools.show_publisher(tab="create") + return {"FINISHED"} + class LaunchLoader(LaunchQtApp): """Launch Avalon Loader.""" @@ -299,7 +303,7 @@ class LaunchPublisher(LaunchQtApp): bl_label = "Publish..." def execute(self, context): - host_tools.show_publish() + host_tools.show_publisher(tab="publish") return {"FINISHED"} diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index fb87d08cce..221c8d8936 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -6,7 +6,8 @@ from typing import Dict, List, Optional import bpy from openpype.pipeline import ( - LegacyCreator, + Creator, + CreatedInstance, LoaderPlugin, ) from .pipeline import AVALON_CONTAINERS @@ -134,10 +135,11 @@ def deselect_all(): bpy.context.view_layer.objects.active = active -class Creator(LegacyCreator): - """Base class for Creator plug-ins.""" +class BlenderCreator(Creator): + """Base class for Blender Creator plug-ins.""" defaults = ['Main'] + # Deprecated? def process(self): collection = bpy.data.collections.new(name=self.data["subset"]) bpy.context.scene.collection.children.link(collection) @@ -150,6 +152,23 @@ class Creator(LegacyCreator): return collection + def create( + self, subset_name: str, instance_data: dict, pre_create_data: dict + ): + """Override abstract method from Creator. + Create new instance and store it. + + Args: + subset_name(str): Subset name of created instance. + instance_data(dict): Base data for instance. + pre_create_data(dict): Data based on pre creation attributes. + Those may affect how creator works. + """ + instance = CreatedInstance( + self.family, subset_name, instance_data + ) + + class Loader(LoaderPlugin): """Base class for Loader plug-ins.""" diff --git a/openpype/hosts/blender/plugins/create/create_action.py b/openpype/hosts/blender/plugins/create/create_action.py index 0203ba74c0..effbccd430 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -7,7 +7,7 @@ import openpype.hosts.blender.api.plugin from openpype.hosts.blender.api import lib -class CreateAction(openpype.hosts.blender.api.plugin.Creator): +class CreateAction(openpype.hosts.blender.api.plugin.BlenderCreator): """Action output for character rigs""" name = "actionMain" diff --git a/openpype/hosts/blender/plugins/create/create_animation.py b/openpype/hosts/blender/plugins/create/create_animation.py index bc2840952b..1b9bbcacd9 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -7,7 +7,7 @@ from openpype.hosts.blender.api import plugin, lib, ops from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES -class CreateAnimation(plugin.Creator): +class CreateAnimation(plugin.BlenderCreator): """Animation output for character rigs""" name = "animationMain" diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index 7a770a3e77..c72f2b92ff 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -7,7 +7,7 @@ from openpype.hosts.blender.api import plugin, lib, ops from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES -class CreateCamera(plugin.Creator): +class CreateCamera(plugin.BlenderCreator): """Polygonal static geometry""" name = "cameraMain" diff --git a/openpype/hosts/blender/plugins/create/create_layout.py b/openpype/hosts/blender/plugins/create/create_layout.py index 73ed683256..ba75df6735 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -7,7 +7,7 @@ from openpype.hosts.blender.api import plugin, lib, ops from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES -class CreateLayout(plugin.Creator): +class CreateLayout(plugin.BlenderCreator): """Layout output for character rigs""" name = "layoutMain" diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index 51fc6683f6..a7e71622ea 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -7,7 +7,7 @@ from openpype.hosts.blender.api import plugin, lib, ops from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES -class CreateModel(plugin.Creator): +class CreateModel(plugin.BlenderCreator): """Polygonal static geometry""" name = "modelMain" diff --git a/openpype/hosts/blender/plugins/create/create_pointcache.py b/openpype/hosts/blender/plugins/create/create_pointcache.py index 65cf18472d..0555d956de 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -7,7 +7,7 @@ from openpype.hosts.blender.api import plugin, lib, ops from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES -class CreatePointcache(plugin.Creator): +class CreatePointcache(plugin.BlenderCreator): """Polygonal static geometry""" name = "pointcacheMain" diff --git a/openpype/hosts/blender/plugins/create/create_review.py b/openpype/hosts/blender/plugins/create/create_review.py index 914f249891..58c26e0324 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -7,7 +7,7 @@ from openpype.hosts.blender.api import plugin, lib, ops from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES -class CreateReview(plugin.Creator): +class CreateReview(plugin.BlenderCreator): """Single baked camera""" name = "reviewDefault" diff --git a/openpype/hosts/blender/plugins/create/create_rig.py b/openpype/hosts/blender/plugins/create/create_rig.py index 08cc46ee3e..7a0393f0ba 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -7,7 +7,7 @@ from openpype.hosts.blender.api import plugin, lib, ops from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES -class CreateRig(plugin.Creator): +class CreateRig(plugin.BlenderCreator): """Artist-friendly rig with controls to direct motion""" name = "rigMain" diff --git a/openpype/hosts/blender/plugins/publish/extract_blend.py b/openpype/hosts/blender/plugins/publish/extract_blend.py index d4f26b4f3c..fba9a861a0 100644 --- a/openpype/hosts/blender/plugins/publish/extract_blend.py +++ b/openpype/hosts/blender/plugins/publish/extract_blend.py @@ -5,7 +5,7 @@ import bpy from openpype.pipeline import publish -class ExtractBlend(publish.Extractor): +class ExtractBlend(publish.Extractor, publish.OptionalPyblishPluginMixin): """Extract a blend file.""" label = "Extract Blend" @@ -16,6 +16,9 @@ class ExtractBlend(publish.Extractor): def process(self, instance): # Define extract output file path + if not self.is_active(instance.data): + return + stagingdir = self.staging_dir(instance) filename = f"{instance.name}.blend" filepath = os.path.join(stagingdir, filename) From e06dfbb8e8dbf1a92f2164a0b929968384d1aaff Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Tue, 6 Jun 2023 16:09:09 +0200 Subject: [PATCH 002/101] draft implementation for blender creator refactor --- openpype/hosts/blender/api/plugin.py | 75 ++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 221c8d8936..39d0f5e662 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -152,6 +152,29 @@ class BlenderCreator(Creator): return collection + @staticmethod + def cache_subsets(shared_data): + """Cache instances for Creators shared data. + + Create `blender_cached_subsets` key when needed in shared data and + fill it with all collected instances from the scene under its + respective creator identifiers. + + If legacy instances are detected in the scene, create + `blender_cached_legacy_subsets` key and fill it with + all legacy subsets from this family as a value. # key or value? + + Args: + shared_data(Dict[str, Any]): Shared data. + + Return: + Dict[str, Any]: Shared data with cached subsets. + """ + if not shared_data.get('blender_cached_subsets'): + cache = {} + cache_legacy = {} + + def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): @@ -168,6 +191,58 @@ class BlenderCreator(Creator): self.family, subset_name, instance_data ) + collection = bpy.data.collections.new(name=self.data['subset']) + bpy.context.scene.collection.children.link(collection) + + if (self.options or {}).get("useSelection"): + for obj in get_selection(): + collection.objects.link(obj) + + + def collect_instances(self): + """Override abstract method from BaseCreator. + Collect existing instances related to this creator plugin.""" + for ( + instance_data in self.cache_subsets( + self.collection_shared_data + ).get('blender_cached_subsets') + ): + # Process only instances that were created by this creator + creator_id = instance_data.get('creator_identifier') + + if creator_id == self.identifier: + # Create instance object from existing data + instance = CreatedInstance.from_existing( + instance_data, self + ) + + # Add instance to create context + self.add_instance_to_context(instance) + + + def update_instances(self, update_list): + """Override abstract method from BaseCreator. + Store changes of existing instances so they can be recollected. + + Args: + update_list(List[UpdateData]): Changed instances + and their changes, as a list of tuples.""" + for created_instance, _changes in update_list: + data = created_instance.data_to_store() + + # TODO + + + def remove_instances(self, instances: List[CreatedInstance]): + """Override abstract method from BaseCreator. + Method called when instances are removed. + + Args: + instance(List[CreatedInstance]): Instance objects to remove. + """ + for instance in instances: + self._remove_instance_from_context(instance) + class Loader(LoaderPlugin): """Base class for Loader plug-ins.""" From 3c198694a9be7dace95e5e027b604f23e3f2bdfe Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Tue, 6 Jun 2023 18:03:02 +0200 Subject: [PATCH 003/101] blender creator cache subsets --- openpype/hosts/blender/api/plugin.py | 59 +++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 39d0f5e662..9a982c45e7 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -10,7 +10,11 @@ from openpype.pipeline import ( CreatedInstance, LoaderPlugin, ) -from .pipeline import AVALON_CONTAINERS +from .pipeline import ( + AVALON_CONTAINERS, + AVALON_INSTANCES, + AVALON_PROPERTY, +) from .ops import ( MainThreadItem, execute_in_main_thread @@ -174,6 +178,44 @@ class BlenderCreator(Creator): cache = {} cache_legacy = {} + avalon_instances = bpy.data.collections.get(AVALON_INSTANCES) + if avalon_instances: + for obj in bpy.data.collections.get(AVALON_INSTANCES).objects: + avalon_prop = obj.get(AVALON_PROPERTY, {}) + if avalon_prop.get('id') == 'pyblish.avalon.instance': + creator_id = avalon_prop.get('creator_identifier') + + if creator_id: + # Creator instance + cache.setdefault(creator_id, []).append( + avalon_prop + ) + else: + family = avalon_prop.get('family') + if family: + # Legacy creator instance + cache_legacy.setdefault(family, []).append( + avalon_prop + ) + + for col in bpy.data.collections: + avalon_prop = col.get(AVALON_PROPERTY, {}) + if avalon_prop.get('id') == 'pyblish.avalon.instance': + creaor_id = avalon_prop.get('creator_identifier') + + if creator_id: + # Creator instance + cache.setdefault(creator_id, []).append(avalon_prop) + else: + family = avalon_prop.get('family') + if family: + cache_legacy.setdefault(family, []) + if family: + # Legacy creator instance + cache_legacy.setdefault(family, []).append( + avalon_prop + ) + def create( self, subset_name: str, instance_data: dict, pre_create_data: dict @@ -202,11 +244,16 @@ class BlenderCreator(Creator): def collect_instances(self): """Override abstract method from BaseCreator. Collect existing instances related to this creator plugin.""" - for ( - instance_data in self.cache_subsets( - self.collection_shared_data - ).get('blender_cached_subsets') - ): + + # Cache subsets in shared data + self.cache_subsets(self.collection_shared_data) + + # Get cached subsets + cached_subsets = self.collection_shared_data.get('blender_cached_subsets') + if not cached_subsets: + return + + for instance_data in cached_subsets: # Process only instances that were created by this creator creator_id = instance_data.get('creator_identifier') From d4c030e77d38b7db3fc50c98d0d43ca6d6beae8e Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Fri, 16 Jun 2023 11:16:42 +0200 Subject: [PATCH 004/101] Add identifiers to blender creators. Fix wrong method name --- openpype/hosts/blender/api/plugin.py | 2 +- openpype/hosts/blender/plugins/create/create_action.py | 1 + openpype/hosts/blender/plugins/create/create_animation.py | 1 + openpype/hosts/blender/plugins/create/create_camera.py | 1 + openpype/hosts/blender/plugins/create/create_layout.py | 1 + openpype/hosts/blender/plugins/create/create_model.py | 1 + openpype/hosts/blender/plugins/create/create_pointcache.py | 1 + openpype/hosts/blender/plugins/create/create_review.py | 1 + openpype/hosts/blender/plugins/create/create_rig.py | 1 + 9 files changed, 9 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 9a982c45e7..c13af363c5 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -264,7 +264,7 @@ class BlenderCreator(Creator): ) # Add instance to create context - self.add_instance_to_context(instance) + self._add_instance_to_context(instance) def update_instances(self, update_list): diff --git a/openpype/hosts/blender/plugins/create/create_action.py b/openpype/hosts/blender/plugins/create/create_action.py index effbccd430..5f4ded3688 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -10,6 +10,7 @@ from openpype.hosts.blender.api import lib class CreateAction(openpype.hosts.blender.api.plugin.BlenderCreator): """Action output for character rigs""" + identifier = "io.openpype.creators.blender.action" name = "actionMain" label = "Action" family = "action" diff --git a/openpype/hosts/blender/plugins/create/create_animation.py b/openpype/hosts/blender/plugins/create/create_animation.py index 1b9bbcacd9..277c588610 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -10,6 +10,7 @@ from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES class CreateAnimation(plugin.BlenderCreator): """Animation output for character rigs""" + identifier = "io.openpype.creators.blender.animation" name = "animationMain" label = "Animation" family = "animation" diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index c72f2b92ff..9086c44c5f 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -10,6 +10,7 @@ from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES class CreateCamera(plugin.BlenderCreator): """Polygonal static geometry""" + identifier = "io.openpype.creators.blender.camera" name = "cameraMain" label = "Camera" family = "camera" diff --git a/openpype/hosts/blender/plugins/create/create_layout.py b/openpype/hosts/blender/plugins/create/create_layout.py index ba75df6735..ae567e6495 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -10,6 +10,7 @@ from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES class CreateLayout(plugin.BlenderCreator): """Layout output for character rigs""" + identifier = "io.openpype.creators.blender.layout" name = "layoutMain" label = "Layout" family = "layout" diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index a7e71622ea..46196ab383 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -10,6 +10,7 @@ from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES class CreateModel(plugin.BlenderCreator): """Polygonal static geometry""" + identifier = "io.openpype.creators.blender.model" name = "modelMain" label = "Model" family = "model" diff --git a/openpype/hosts/blender/plugins/create/create_pointcache.py b/openpype/hosts/blender/plugins/create/create_pointcache.py index 0555d956de..4c434202c7 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -10,6 +10,7 @@ from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES class CreatePointcache(plugin.BlenderCreator): """Polygonal static geometry""" + identifier = "io.openpype.creators.blender.pointcache" name = "pointcacheMain" label = "Point Cache" family = "pointcache" diff --git a/openpype/hosts/blender/plugins/create/create_review.py b/openpype/hosts/blender/plugins/create/create_review.py index 58c26e0324..87774aed7a 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -10,6 +10,7 @@ from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES class CreateReview(plugin.BlenderCreator): """Single baked camera""" + identifier = "io.openpype.creators.blender.review" name = "reviewDefault" label = "Review" family = "review" diff --git a/openpype/hosts/blender/plugins/create/create_rig.py b/openpype/hosts/blender/plugins/create/create_rig.py index 7a0393f0ba..84924a659b 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -10,6 +10,7 @@ from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES class CreateRig(plugin.BlenderCreator): """Artist-friendly rig with controls to direct motion""" + identifier = "io.openpype.creators.blender.rig" name = "rigMain" label = "Rig" family = "rig" From efa294defcc625acdd36e7948bde3f75b15beb88 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Fri, 16 Jun 2023 17:54:28 +0200 Subject: [PATCH 005/101] fixed wrong variable name --- openpype/hosts/blender/api/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index c13af363c5..02436e3583 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -201,7 +201,7 @@ class BlenderCreator(Creator): for col in bpy.data.collections: avalon_prop = col.get(AVALON_PROPERTY, {}) if avalon_prop.get('id') == 'pyblish.avalon.instance': - creaor_id = avalon_prop.get('creator_identifier') + creator_id = avalon_prop.get('creator_identifier') if creator_id: # Creator instance From b681173dc5b2ee34862ffa41dbc3fc452c682a56 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Mon, 3 Jul 2023 17:44:36 +0200 Subject: [PATCH 006/101] draft blenderhost class implementation --- openpype/hosts/blender/api/__init__.py | 2 + openpype/hosts/blender/api/pipeline.py | 41 ++++++++++++++++++- .../blender/blender_addon/startup/init.py | 4 +- 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/blender/api/__init__.py b/openpype/hosts/blender/api/__init__.py index e15f1193a5..ce2b444997 100644 --- a/openpype/hosts/blender/api/__init__.py +++ b/openpype/hosts/blender/api/__init__.py @@ -10,6 +10,7 @@ from .pipeline import ( ls, publish, containerise, + BlenderHost, ) from .plugin import ( @@ -47,6 +48,7 @@ __all__ = [ "ls", "publish", "containerise", + "BlenderHost", "Creator", "Loader", diff --git a/openpype/hosts/blender/api/pipeline.py b/openpype/hosts/blender/api/pipeline.py index 84af0904f0..935981da86 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -6,10 +6,14 @@ from typing import Callable, Dict, Iterator, List, Optional import bpy from . import lib -from . import ops +from . import ops, properties import pyblish.api +from openpype.host import( + HostBase, + IPublishHost, +) from openpype.client import get_asset_by_name from openpype.pipeline import ( schema, @@ -47,6 +51,39 @@ IS_HEADLESS = bpy.app.background log = Logger.get_logger(__name__) +class BlenderHost(HostBase, IPublishHost): + name = "blender" + + def install(self): + """Override install method from HostBase. + Install Blender host functionality.""" + install() + + def ls(self) -> Iterator: + """List containers from active Blender scene.""" + return ls() + + def get_context_data(self): + """Override abstract method from IPublishHost. + Get global data related to creation-publishing from workfile. + + Returns: + dict: Context data stored using 'update_context_data'. + """ + return bpy.context.scene.openpype_context + + def update_context_data(self, data, changes): + """Override abstract method from IPublishHost. + Store global context data to workfile. + + Args: + data (dict): New data as are. + changes (dict): Only data that has been changed. Each value has + tuple with '(, )' value. + """ + bpy.context.scene.openpype_context.update(data) + + def pype_excepthook_handler(*args): traceback.print_exception(*args) @@ -72,6 +109,7 @@ def install(): if not IS_HEADLESS: ops.register() + properties.register() def uninstall(): @@ -86,6 +124,7 @@ def uninstall(): if not IS_HEADLESS: ops.unregister() + properties.unregister() def show_message(title, message): diff --git a/openpype/hosts/blender/blender_addon/startup/init.py b/openpype/hosts/blender/blender_addon/startup/init.py index 8dbff8a91d..603691675d 100644 --- a/openpype/hosts/blender/blender_addon/startup/init.py +++ b/openpype/hosts/blender/blender_addon/startup/init.py @@ -1,9 +1,9 @@ from openpype.pipeline import install_host -from openpype.hosts.blender import api +from openpype.hosts.blender.api import BlenderHost def register(): - install_host(api) + install_host(BlenderHost()) def unregister(): From 3b7ed1d71f86f6bd04029cc6ec0e369aff94c365 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Tue, 4 Jul 2023 10:55:04 +0200 Subject: [PATCH 007/101] Fix update_container_data error --- openpype/hosts/blender/api/pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/blender/api/pipeline.py b/openpype/hosts/blender/api/pipeline.py index 935981da86..968c70089b 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -81,7 +81,7 @@ class BlenderHost(HostBase, IPublishHost): changes (dict): Only data that has been changed. Each value has tuple with '(, )' value. """ - bpy.context.scene.openpype_context.update(data) + bpy.context.scene.openpype_context |= data def pype_excepthook_handler(*args): From 7bdbf0252211167c065aa8f3e622d48e725c0bf8 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Tue, 4 Jul 2023 16:07:44 +0200 Subject: [PATCH 008/101] Added properties to blender host --- openpype/hosts/blender/api/properties.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 openpype/hosts/blender/api/properties.py diff --git a/openpype/hosts/blender/api/properties.py b/openpype/hosts/blender/api/properties.py new file mode 100644 index 0000000000..ffc1dea733 --- /dev/null +++ b/openpype/hosts/blender/api/properties.py @@ -0,0 +1,23 @@ +import bpy +from bpy.utils import register_classes_factory + +class OpenpypeContext(bpy.types.PropertyGroup): + pass + +classes = [OpenpypeContext] + +factory_register, factory_unregister = register_classes_factory(classes) + +def register(): + """Register the properties.""" + factory_register() + + bpy.types.Scene.openpype_context = bpy.props.CollectionProperty( + name="OpenPype Context", type=OpenpypeContext, options={"HIDDEN"} + ) + +def unregister(): + """Unregister the properties.""" + factory_unregister() + + del bpy.types.Scene.openpype_context From d9960e84d44dab94ac5fec6bf45315ce79ab5d37 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Tue, 4 Jul 2023 17:54:40 +0200 Subject: [PATCH 009/101] Fixed errors during create --- openpype/hosts/blender/api/pipeline.py | 2 +- openpype/hosts/blender/api/plugin.py | 6 +++--- openpype/hosts/blender/api/properties.py | 9 +++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/blender/api/pipeline.py b/openpype/hosts/blender/api/pipeline.py index 968c70089b..935981da86 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -81,7 +81,7 @@ class BlenderHost(HostBase, IPublishHost): changes (dict): Only data that has been changed. Each value has tuple with '(, )' value. """ - bpy.context.scene.openpype_context |= data + bpy.context.scene.openpype_context.update(data) def pype_excepthook_handler(*args): diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 02436e3583..91068244c5 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -230,13 +230,13 @@ class BlenderCreator(Creator): Those may affect how creator works. """ instance = CreatedInstance( - self.family, subset_name, instance_data + self.family, subset_name, instance_data, self ) - collection = bpy.data.collections.new(name=self.data['subset']) + collection = bpy.data.collections.new(name=subset_name) bpy.context.scene.collection.children.link(collection) - if (self.options or {}).get("useSelection"): + if pre_create_data.get("useSelection"): for obj in get_selection(): collection.objects.link(obj) diff --git a/openpype/hosts/blender/api/properties.py b/openpype/hosts/blender/api/properties.py index ffc1dea733..c6b5ffe011 100644 --- a/openpype/hosts/blender/api/properties.py +++ b/openpype/hosts/blender/api/properties.py @@ -4,7 +4,7 @@ from bpy.utils import register_classes_factory class OpenpypeContext(bpy.types.PropertyGroup): pass -classes = [OpenpypeContext] +classes = [] # [OpenpypeContext] factory_register, factory_unregister = register_classes_factory(classes) @@ -12,9 +12,10 @@ def register(): """Register the properties.""" factory_register() - bpy.types.Scene.openpype_context = bpy.props.CollectionProperty( - name="OpenPype Context", type=OpenpypeContext, options={"HIDDEN"} - ) + bpy.types.Scene.openpype_context = {} + # bpy.types.Scene.openpype_context = bpy.props.CollectionProperty( + # name="OpenPype Context", type=OpenpypeContext, options={"HIDDEN"} + # ) def unregister(): """Unregister the properties.""" From cf66d12ef20feb2e1dcf326d4888434a3c506bdb Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Wed, 5 Jul 2023 17:36:42 +0200 Subject: [PATCH 010/101] Fixes to base creator, creators compatibility --- openpype/hosts/blender/api/plugin.py | 3 + .../blender/plugins/create/create_action.py | 27 +++++++++ .../plugins/create/create_animation.py | 47 +++++++++++++++- .../blender/plugins/create/create_camera.py | 56 ++++++++++++++++++- .../blender/plugins/create/create_layout.py | 45 ++++++++++++++- .../blender/plugins/create/create_model.py | 45 ++++++++++++++- .../plugins/create/create_pointcache.py | 22 ++++++++ .../blender/plugins/create/create_review.py | 43 +++++++++++++- .../blender/plugins/create/create_rig.py | 45 ++++++++++++++- 9 files changed, 315 insertions(+), 18 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 91068244c5..153897cb9a 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -216,6 +216,8 @@ class BlenderCreator(Creator): avalon_prop ) + return shared_data + def create( self, subset_name: str, instance_data: dict, pre_create_data: dict @@ -232,6 +234,7 @@ class BlenderCreator(Creator): instance = CreatedInstance( self.family, subset_name, instance_data, self ) + self._add_instance_to_context(instance) collection = bpy.data.collections.new(name=subset_name) bpy.context.scene.collection.children.link(collection) diff --git a/openpype/hosts/blender/plugins/create/create_action.py b/openpype/hosts/blender/plugins/create/create_action.py index 5f4ded3688..32e924d758 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -16,6 +16,33 @@ class CreateAction(openpype.hosts.blender.api.plugin.BlenderCreator): family = "action" icon = "male" + def create( + self, subset_name: str, instance_data: dict, pre_create_data: dict + ): + + name = openpype.hosts.blender.api.plugin.asset_name( + instance_data["asset"], subset_name + ) + collection = bpy.data.collections.new(name=name) + bpy.context.scene.collection.children.link(collection) + instance_data['task'] = get_current_task_name() + lib.imprint(collection, instance_data) + + if pre_create_data.get("useSelection"): + for obj in lib.get_selection(): + if (obj.animation_data is not None + and obj.animation_data.action is not None): + + empty_obj = bpy.data.objects.new(name=name, + object_data=None) + empty_obj.animation_data_create() + empty_obj.animation_data.action = obj.animation_data.action + empty_obj.animation_data.action.name = name + collection.objects.link(empty_obj) + + return collection + + # Deprecated def process(self): asset = self.data["asset"] diff --git a/openpype/hosts/blender/plugins/create/create_animation.py b/openpype/hosts/blender/plugins/create/create_animation.py index 277c588610..12478fd7e5 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -16,12 +16,53 @@ class CreateAnimation(plugin.BlenderCreator): family = "animation" icon = "male" - def process(self): + def create( + self, subset_name: str, instance_data: dict, pre_create_data: dict + ): """ Run the creator on Blender main thread""" - mti = ops.MainThreadItem(self._process) + mti = ops.MainThreadItem( + self._process, subset_name, instance_data, pre_create_data + ) ops.execute_in_main_thread(mti) - def _process(self): + def _process( + self, subset_name: str, instance_data: dict, pre_create_data: dict + ): + # Get Instance Container or create it if it does not exist + instances = bpy.data.collections.get(AVALON_INSTANCES) + if not instances: + instances = bpy.data.collections.new(name=AVALON_INSTANCES) + bpy.context.scene.collection.children.link(instances) + + # Create instance object + # name = self.name + # if not name: + name = plugin.asset_name(instance_data["asset"], subset_name) + # asset_group = bpy.data.objects.new(name=name, object_data=None) + # asset_group.empty_display_type = 'SINGLE_ARROW' + asset_group = bpy.data.collections.new(name=name) + instances.children.link(asset_group) + instance_data['task'] = get_current_task_name() + lib.imprint(asset_group, instance_data) + + if pre_create_data.get("useSelection"): + selected = lib.get_selection() + for obj in selected: + asset_group.objects.link(obj) + elif pre_create_data.get("asset_group"): + obj = (self.options or {}).get("asset_group") + asset_group.objects.link(obj) + + return asset_group + + # Deprecated + def process(self): + """ Run the creator on Blender main thread""" + mti = ops.MainThreadItem(self._process_legacy) + ops.execute_in_main_thread(mti) + + # Deprecated + def _process_legacy(self): # Get Instance Container or create it if it does not exist instances = bpy.data.collections.get(AVALON_INSTANCES) if not instances: diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index 9086c44c5f..6b9d1b7f73 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -16,12 +16,62 @@ class CreateCamera(plugin.BlenderCreator): family = "camera" icon = "video-camera" - def process(self): + def create( + self, subset_name: str, instance_data: dict, pre_create_data: dict + ): """ Run the creator on Blender main thread""" - mti = ops.MainThreadItem(self._process) + mti = ops.MainThreadItem( + self._process, subset_name, instance_data, pre_create_data + ) ops.execute_in_main_thread(mti) - def _process(self): + def _process( + self, subset_name: str, instance_data: dict, pre_create_data: dict + ): + # Get Instance Container or create it if it does not exist + instances = bpy.data.collections.get(AVALON_INSTANCES) + if not instances: + instances = bpy.data.collections.new(name=AVALON_INSTANCES) + bpy.context.scene.collection.children.link(instances) + + # Create instance object + name = plugin.asset_name(instance_data["asset"], subset_name) + + asset_group = bpy.data.objects.new(name=name, object_data=None) + asset_group.empty_display_type = 'SINGLE_ARROW' + instances.objects.link(asset_group) + instance_data['task'] = get_current_task_name() + lib.imprint(asset_group, instance_data) + + if pre_create_data.get("useSelection"): + bpy.context.view_layer.objects.active = asset_group + selected = lib.get_selection() + for obj in selected: + obj.select_set(True) + selected.append(asset_group) + bpy.ops.object.parent_set(keep_transform=True) + else: + plugin.deselect_all() + camera = bpy.data.cameras.new(subset_name) + camera_obj = bpy.data.objects.new(subset_name, camera) + + instances.objects.link(camera_obj) + + camera_obj.select_set(True) + asset_group.select_set(True) + bpy.context.view_layer.objects.active = asset_group + bpy.ops.object.parent_set(keep_transform=True) + + return asset_group + + # Deprecated + def process(self): + """ Run the creator on Blender main thread""" + mti = ops.MainThreadItem(self._process_legacy) + ops.execute_in_main_thread(mti) + + # Deprecated + def _process_legacy(self): # Get Instance Container or create it if it does not exist instances = bpy.data.collections.get(AVALON_INSTANCES) if not instances: diff --git a/openpype/hosts/blender/plugins/create/create_layout.py b/openpype/hosts/blender/plugins/create/create_layout.py index ae567e6495..24aa277349 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -16,12 +16,51 @@ class CreateLayout(plugin.BlenderCreator): family = "layout" icon = "cubes" - def process(self): + def create( + self, subset_name: str, instance_data: dict, pre_create_data: dict + ): """ Run the creator on Blender main thread""" - mti = ops.MainThreadItem(self._process) + mti = ops.MainThreadItem( + self._process, subset_name, instance_data, pre_create_data + ) ops.execute_in_main_thread(mti) - def _process(self): + def _process( + self, subset_name: str, instance_data: dict, pre_create_data: dict + ): + # Get Instance Container or create it if it does not exist + instances = bpy.data.collections.get(AVALON_INSTANCES) + if not instances: + instances = bpy.data.collections.new(name=AVALON_INSTANCES) + bpy.context.scene.collection.children.link(instances) + + # Create instance object + name = plugin.asset_name(instance_data["asset"], subset_name) + asset_group = bpy.data.objects.new(name=name, object_data=None) + asset_group.empty_display_type = 'SINGLE_ARROW' + instances.objects.link(asset_group) + instance_data['task'] = get_current_task_name() + lib.imprint(asset_group, instance_data) + + # Add selected objects to instance + if pre_create_data.get("useSelection"): + bpy.context.view_layer.objects.active = asset_group + selected = lib.get_selection() + for obj in selected: + obj.select_set(True) + selected.append(asset_group) + bpy.ops.object.parent_set(keep_transform=True) + + return asset_group + + # Deprecated + def process(self): + """ Run the creator on Blender main thread""" + mti = ops.MainThreadItem(self._process_legacy) + ops.execute_in_main_thread(mti) + + # Deprecated + def _process_legacy(self): # Get Instance Container or create it if it does not exist instances = bpy.data.collections.get(AVALON_INSTANCES) if not instances: diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index 46196ab383..2e713dd661 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -16,12 +16,51 @@ class CreateModel(plugin.BlenderCreator): family = "model" icon = "cube" - def process(self): + def create( + self, subset_name: str, instance_data: dict, pre_create_data: dict + ): """ Run the creator on Blender main thread""" - mti = ops.MainThreadItem(self._process) + mti = ops.MainThreadItem( + self._process, subset_name, instance_data, pre_create_data + ) ops.execute_in_main_thread(mti) - def _process(self): + def _process( + self, subset_name: str, instance_data: dict, pre_create_data: dict + ): + # Get Instance Container or create it if it does not exist + instances = bpy.data.collections.get(AVALON_INSTANCES) + if not instances: + instances = bpy.data.collections.new(name=AVALON_INSTANCES) + bpy.context.scene.collection.children.link(instances) + + # Create instance object + name = plugin.asset_name(instance_data["asset"], subset_name) + asset_group = bpy.data.objects.new(name=name, object_data=None) + asset_group.empty_display_type = 'SINGLE_ARROW' + instances.objects.link(asset_group) + instance_data['task'] = get_current_task_name() + lib.imprint(asset_group, instance_data) + + # Add selected objects to instance + if pre_create_data.get("useSelection"): + bpy.context.view_layer.objects.active = asset_group + selected = lib.get_selection() + for obj in selected: + obj.select_set(True) + selected.append(asset_group) + bpy.ops.object.parent_set(keep_transform=True) + + return asset_group + + # Deprecated + def process(self): + """ Run the creator on Blender main thread""" + mti = ops.MainThreadItem(self._process_legacy) + ops.execute_in_main_thread(mti) + + # Deprecated + def _process_legacy(self): # Get Instance Container or create it if it does not exist instances = bpy.data.collections.get(AVALON_INSTANCES) if not instances: diff --git a/openpype/hosts/blender/plugins/create/create_pointcache.py b/openpype/hosts/blender/plugins/create/create_pointcache.py index 4c434202c7..5932315bc8 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -16,6 +16,28 @@ class CreatePointcache(plugin.BlenderCreator): family = "pointcache" icon = "gears" + def create( + self, subset_name: str, instance_data: dict, pre_create_data: dict + ): + + name = openpype.hosts.blender.api.plugin.asset_name( + instance_data["asset"], subset_name + ) + collection = bpy.data.collections.new(name=name) + bpy.context.scene.collection.children.link(collection) + instance_data['task'] = get_current_task_name() + lib.imprint(collection, instance_data) + + if pre_create_data.get("useSelection"): + objects = lib.get_selection() + for obj in objects: + collection.objects.link(obj) + if obj.type == 'EMPTY': + objects.extend(obj.children) + + return collection + + # Deprecated def process(self): """ Run the creator on Blender main thread""" mti = ops.MainThreadItem(self._process) diff --git a/openpype/hosts/blender/plugins/create/create_review.py b/openpype/hosts/blender/plugins/create/create_review.py index 87774aed7a..4b5cfb4c35 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -16,12 +16,49 @@ class CreateReview(plugin.BlenderCreator): family = "review" icon = "video-camera" - def process(self): + def create( + self, subset_name: str, instance_data: dict, pre_create_data: dict + ): """ Run the creator on Blender main thread""" - mti = ops.MainThreadItem(self._process) + mti = ops.MainThreadItem( + self._process, subset_name, instance_data, pre_create_data + ) ops.execute_in_main_thread(mti) - def _process(self): + def _process( + self, subset_name: str, instance_data: dict, pre_create_data: dict + ): + # Get Instance Container or create it if it does not exist + instances = bpy.data.collections.get(AVALON_INSTANCES) + if not instances: + instances = bpy.data.collections.new(name=AVALON_INSTANCES) + bpy.context.scene.collection.children.link(instances) + + # Create instance object + name = plugin.asset_name(instance_data["asset"], subset_name) + asset_group = bpy.data.collections.new(name=name) + instances.children.link(asset_group) + instance_data['task'] = get_current_task_name() + lib.imprint(asset_group, instance_data) + + if pre_create_data.get("useSelection"): + selected = lib.get_selection() + for obj in selected: + asset_group.objects.link(obj) + elif pre_create_data.get("asset_group"): + obj = (self.options or {}).get("asset_group") + asset_group.objects.link(obj) + + return asset_group + + # Deprecated + def process(self): + """ Run the creator on Blender main thread""" + mti = ops.MainThreadItem(self._process_legacy) + ops.execute_in_main_thread(mti) + + # Deprecated + def _process_legacy(self): # Get Instance Container or create it if it does not exist instances = bpy.data.collections.get(AVALON_INSTANCES) if not instances: diff --git a/openpype/hosts/blender/plugins/create/create_rig.py b/openpype/hosts/blender/plugins/create/create_rig.py index 84924a659b..5ebf952d4a 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -16,12 +16,51 @@ class CreateRig(plugin.BlenderCreator): family = "rig" icon = "wheelchair" - def process(self): + def create( + self, subset_name: str, instance_data: dict, pre_create_data: dict + ): """ Run the creator on Blender main thread""" - mti = ops.MainThreadItem(self._process) + mti = ops.MainThreadItem( + self._process, subset_name, instance_data, pre_create_data + ) ops.execute_in_main_thread(mti) - def _process(self): + def _process( + self, subset_name: str, instance_data: dict, pre_create_data: dict + ): + # Get Instance Container or create it if it does not exist + instances = bpy.data.collections.get(AVALON_INSTANCES) + if not instances: + instances = bpy.data.collections.new(name=AVALON_INSTANCES) + bpy.context.scene.collection.children.link(instances) + + # Create instance object + name = plugin.asset_name(instance_data["asset"], subset_name) + asset_group = bpy.data.objects.new(name=name, object_data=None) + asset_group.empty_display_type = 'SINGLE_ARROW' + instances.objects.link(asset_group) + instance_data['task'] = get_current_task_name() + lib.imprint(asset_group, instance_data) + + # Add selected objects to instance + if pre_create_data.get("useSelection"): + bpy.context.view_layer.objects.active = asset_group + selected = lib.get_selection() + for obj in selected: + obj.select_set(True) + selected.append(asset_group) + bpy.ops.object.parent_set(keep_transform=True) + + return asset_group + + # Deprecated + def process(self): + """ Run the creator on Blender main thread""" + mti = ops.MainThreadItem(self._process_legacy) + ops.execute_in_main_thread(mti) + + # Deprecated + def _process_legacy(self): # Get Instance Container or create it if it does not exist instances = bpy.data.collections.get(AVALON_INSTANCES) if not instances: From e0e7c965856795776e86cfab83a582b2fd4c134d Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Mon, 10 Jul 2023 14:59:31 +0200 Subject: [PATCH 011/101] blender creator instances added to context --- openpype/hosts/blender/plugins/create/create_action.py | 5 ++++- openpype/hosts/blender/plugins/create/create_animation.py | 6 +++++- openpype/hosts/blender/plugins/create/create_camera.py | 6 +++++- openpype/hosts/blender/plugins/create/create_layout.py | 6 +++++- openpype/hosts/blender/plugins/create/create_model.py | 6 +++++- openpype/hosts/blender/plugins/create/create_pointcache.py | 7 +++++-- openpype/hosts/blender/plugins/create/create_review.py | 6 +++++- openpype/hosts/blender/plugins/create/create_rig.py | 6 +++++- 8 files changed, 39 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/blender/plugins/create/create_action.py b/openpype/hosts/blender/plugins/create/create_action.py index 32e924d758..6c2c6d98ce 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -2,7 +2,7 @@ import bpy -from openpype.pipeline import get_current_task_name +from openpype.pipeline import get_current_task_name, CreatedInstance import openpype.hosts.blender.api.plugin from openpype.hosts.blender.api import lib @@ -19,6 +19,9 @@ class CreateAction(openpype.hosts.blender.api.plugin.BlenderCreator): def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): + self._add_instance_to_context( + CreatedInstance(self.family, subset_name, instance_data, self) + ) name = openpype.hosts.blender.api.plugin.asset_name( instance_data["asset"], subset_name diff --git a/openpype/hosts/blender/plugins/create/create_animation.py b/openpype/hosts/blender/plugins/create/create_animation.py index 12478fd7e5..ec77569889 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -2,7 +2,7 @@ import bpy -from openpype.pipeline import get_current_task_name +from openpype.pipeline import get_current_task_name, CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES @@ -20,6 +20,10 @@ class CreateAnimation(plugin.BlenderCreator): self, subset_name: str, instance_data: dict, pre_create_data: dict ): """ Run the creator on Blender main thread""" + self._add_instance_to_context( + CreatedInstance(self.family, subset_name, instance_data, self) + ) + mti = ops.MainThreadItem( self._process, subset_name, instance_data, pre_create_data ) diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index 6b9d1b7f73..55ea07d90f 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -2,7 +2,7 @@ import bpy -from openpype.pipeline import get_current_task_name +from openpype.pipeline import get_current_task_name, CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES @@ -20,6 +20,10 @@ class CreateCamera(plugin.BlenderCreator): self, subset_name: str, instance_data: dict, pre_create_data: dict ): """ Run the creator on Blender main thread""" + self._add_instance_to_context( + CreatedInstance(self.family, subset_name, instance_data, self) + ) + mti = ops.MainThreadItem( self._process, subset_name, instance_data, pre_create_data ) diff --git a/openpype/hosts/blender/plugins/create/create_layout.py b/openpype/hosts/blender/plugins/create/create_layout.py index 24aa277349..60812d7fc9 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -2,7 +2,7 @@ import bpy -from openpype.pipeline import get_current_task_name +from openpype.pipeline import get_current_task_name, CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES @@ -20,6 +20,10 @@ class CreateLayout(plugin.BlenderCreator): self, subset_name: str, instance_data: dict, pre_create_data: dict ): """ Run the creator on Blender main thread""" + self._add_instance_to_context( + CreatedInstance(self.family, subset_name, instance_data, self) + ) + mti = ops.MainThreadItem( self._process, subset_name, instance_data, pre_create_data ) diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index 2e713dd661..59bbae5bc4 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -2,7 +2,7 @@ import bpy -from openpype.pipeline import get_current_task_name +from openpype.pipeline import get_current_task_name, CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES @@ -20,6 +20,10 @@ class CreateModel(plugin.BlenderCreator): self, subset_name: str, instance_data: dict, pre_create_data: dict ): """ Run the creator on Blender main thread""" + self._add_instance_to_context( + CreatedInstance(self.family, subset_name, instance_data, self) + ) + mti = ops.MainThreadItem( self._process, subset_name, instance_data, pre_create_data ) diff --git a/openpype/hosts/blender/plugins/create/create_pointcache.py b/openpype/hosts/blender/plugins/create/create_pointcache.py index 5932315bc8..bb843c5a22 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -2,7 +2,7 @@ import bpy -from openpype.pipeline import get_current_task_name +from openpype.pipeline import get_current_task_name, CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES @@ -19,8 +19,11 @@ class CreatePointcache(plugin.BlenderCreator): def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): + self._add_instance_to_context( + CreatedInstance(self.family, subset_name, instance_data, self) + ) - name = openpype.hosts.blender.api.plugin.asset_name( + name = plugin.asset_name( instance_data["asset"], subset_name ) collection = bpy.data.collections.new(name=name) diff --git a/openpype/hosts/blender/plugins/create/create_review.py b/openpype/hosts/blender/plugins/create/create_review.py index 4b5cfb4c35..1191fb95f1 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -2,7 +2,7 @@ import bpy -from openpype.pipeline import get_current_task_name +from openpype.pipeline import get_current_task_name, CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES @@ -20,6 +20,10 @@ class CreateReview(plugin.BlenderCreator): self, subset_name: str, instance_data: dict, pre_create_data: dict ): """ Run the creator on Blender main thread""" + self._add_instance_to_context( + CreatedInstance(self.family, subset_name, instance_data, self) + ) + mti = ops.MainThreadItem( self._process, subset_name, instance_data, pre_create_data ) diff --git a/openpype/hosts/blender/plugins/create/create_rig.py b/openpype/hosts/blender/plugins/create/create_rig.py index 5ebf952d4a..752f3f7b18 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -2,7 +2,7 @@ import bpy -from openpype.pipeline import get_current_task_name +from openpype.pipeline import get_current_task_name, CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES @@ -20,6 +20,10 @@ class CreateRig(plugin.BlenderCreator): self, subset_name: str, instance_data: dict, pre_create_data: dict ): """ Run the creator on Blender main thread""" + self._add_instance_to_context( + CreatedInstance(self.family, subset_name, instance_data, self) + ) + mti = ops.MainThreadItem( self._process, subset_name, instance_data, pre_create_data ) From 6eae2c264d57b4ed5395de03950e09bc54ae7acb Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Wed, 12 Jul 2023 15:54:41 +0200 Subject: [PATCH 012/101] Add dict to imprint, type hints in blenderhost, imprint in create --- openpype/hosts/blender/api/lib.py | 2 +- openpype/hosts/blender/api/pipeline.py | 4 ++-- openpype/hosts/blender/api/plugin.py | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/blender/api/lib.py b/openpype/hosts/blender/api/lib.py index 9bb560c364..76bef09ceb 100644 --- a/openpype/hosts/blender/api/lib.py +++ b/openpype/hosts/blender/api/lib.py @@ -188,7 +188,7 @@ def imprint(node: bpy.types.bpy_struct_meta_idprop, data: Dict): # Support values evaluated at imprint value = value() - if not isinstance(value, (int, float, bool, str, list)): + if not isinstance(value, (int, float, bool, str, list, dict)): raise TypeError(f"Unsupported type: {type(value)}") imprint_data[key] = value diff --git a/openpype/hosts/blender/api/pipeline.py b/openpype/hosts/blender/api/pipeline.py index 935981da86..4c06b1959e 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -63,7 +63,7 @@ class BlenderHost(HostBase, IPublishHost): """List containers from active Blender scene.""" return ls() - def get_context_data(self): + def get_context_data(self) -> dict: """Override abstract method from IPublishHost. Get global data related to creation-publishing from workfile. @@ -72,7 +72,7 @@ class BlenderHost(HostBase, IPublishHost): """ return bpy.context.scene.openpype_context - def update_context_data(self, data, changes): + def update_context_data(self, data: dict, changes: dict): """Override abstract method from IPublishHost. Store global context data to workfile. diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 153897cb9a..fe1e4ce81c 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -238,6 +238,7 @@ class BlenderCreator(Creator): collection = bpy.data.collections.new(name=subset_name) bpy.context.scene.collection.children.link(collection) + imprint(collection, instance_data) if pre_create_data.get("useSelection"): for obj in get_selection(): From 7617d1e0ad63042d39a3c626f6e614100d2bd879 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Wed, 19 Jul 2023 20:28:45 +0200 Subject: [PATCH 013/101] Set shared data, edit loop in cached_subsets --- openpype/hosts/blender/api/plugin.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index fe1e4ce81c..97a3dfa6a6 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -216,6 +216,9 @@ class BlenderCreator(Creator): avalon_prop ) + shared_data["blender_cached_subsets"] = cache + shared_data["blender_cached_legacy_subsets"] = cache_legacy + return shared_data @@ -257,14 +260,17 @@ class BlenderCreator(Creator): if not cached_subsets: return - for instance_data in cached_subsets: + for instance_data in cached_subsets.get(self.identifier, []): # Process only instances that were created by this creator + data = dict() + for key, value in instance_data.items(): + data[key] = value creator_id = instance_data.get('creator_identifier') if creator_id == self.identifier: # Create instance object from existing data instance = CreatedInstance.from_existing( - instance_data, self + data, self ) # Add instance to create context From aeebd78cc71fb3edbe51157c60da849f62e727f3 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Thu, 20 Jul 2023 15:31:23 +0200 Subject: [PATCH 014/101] Collect instances functional --- openpype/hosts/blender/api/plugin.py | 4 ++-- openpype/hosts/blender/plugins/create/create_action.py | 3 +++ openpype/hosts/blender/plugins/create/create_animation.py | 3 +++ openpype/hosts/blender/plugins/create/create_camera.py | 3 +++ openpype/hosts/blender/plugins/create/create_layout.py | 3 +++ openpype/hosts/blender/plugins/create/create_model.py | 3 +++ openpype/hosts/blender/plugins/create/create_pointcache.py | 3 +++ openpype/hosts/blender/plugins/create/create_review.py | 3 +++ openpype/hosts/blender/plugins/create/create_rig.py | 3 +++ 9 files changed, 26 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 97a3dfa6a6..5dd352ff6c 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -262,10 +262,10 @@ class BlenderCreator(Creator): for instance_data in cached_subsets.get(self.identifier, []): # Process only instances that were created by this creator - data = dict() + data = {} for key, value in instance_data.items(): data[key] = value - creator_id = instance_data.get('creator_identifier') + creator_id = data.get('creator_identifier') if creator_id == self.identifier: # Create instance object from existing data diff --git a/openpype/hosts/blender/plugins/create/create_action.py b/openpype/hosts/blender/plugins/create/create_action.py index 6c2c6d98ce..2c0d248994 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -29,6 +29,9 @@ class CreateAction(openpype.hosts.blender.api.plugin.BlenderCreator): collection = bpy.data.collections.new(name=name) bpy.context.scene.collection.children.link(collection) instance_data['task'] = get_current_task_name() + instance_data['id'] = 'pyblish.avalon.instance' + instance_data['creator_identifier'] = self.identifier + instance_data['label'] = self.label lib.imprint(collection, instance_data) if pre_create_data.get("useSelection"): diff --git a/openpype/hosts/blender/plugins/create/create_animation.py b/openpype/hosts/blender/plugins/create/create_animation.py index ec77569889..538c0455ac 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -47,6 +47,9 @@ class CreateAnimation(plugin.BlenderCreator): asset_group = bpy.data.collections.new(name=name) instances.children.link(asset_group) instance_data['task'] = get_current_task_name() + instance_data['id'] = 'pyblish.avalon.instance' + instance_data['creator_identifier'] = self.identifier + instance_data['label'] = self.label lib.imprint(asset_group, instance_data) if pre_create_data.get("useSelection"): diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index 55ea07d90f..ae85273353 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -45,6 +45,9 @@ class CreateCamera(plugin.BlenderCreator): asset_group.empty_display_type = 'SINGLE_ARROW' instances.objects.link(asset_group) instance_data['task'] = get_current_task_name() + instance_data['id'] = 'pyblish.avalon.instance' + instance_data['creator_identifier'] = self.identifier + instance_data['label'] = self.label lib.imprint(asset_group, instance_data) if pre_create_data.get("useSelection"): diff --git a/openpype/hosts/blender/plugins/create/create_layout.py b/openpype/hosts/blender/plugins/create/create_layout.py index 60812d7fc9..cce422b229 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -44,6 +44,9 @@ class CreateLayout(plugin.BlenderCreator): asset_group.empty_display_type = 'SINGLE_ARROW' instances.objects.link(asset_group) instance_data['task'] = get_current_task_name() + instance_data['id'] = 'pyblish.avalon.instance' + instance_data['creator_identifier'] = self.identifier + instance_data['label'] = self.label lib.imprint(asset_group, instance_data) # Add selected objects to instance diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index 59bbae5bc4..57f9e79aa1 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -44,6 +44,9 @@ class CreateModel(plugin.BlenderCreator): asset_group.empty_display_type = 'SINGLE_ARROW' instances.objects.link(asset_group) instance_data['task'] = get_current_task_name() + instance_data['id'] = 'pyblish.avalon.instance' + instance_data['creator_identifier'] = self.identifier + instance_data['label'] = self.label lib.imprint(asset_group, instance_data) # Add selected objects to instance diff --git a/openpype/hosts/blender/plugins/create/create_pointcache.py b/openpype/hosts/blender/plugins/create/create_pointcache.py index bb843c5a22..f95f79ae78 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -29,6 +29,9 @@ class CreatePointcache(plugin.BlenderCreator): collection = bpy.data.collections.new(name=name) bpy.context.scene.collection.children.link(collection) instance_data['task'] = get_current_task_name() + instance_data['id'] = 'pyblish.avalon.instance' + instance_data['creator_identifier'] = self.identifier + instance_data['label'] = self.label lib.imprint(collection, instance_data) if pre_create_data.get("useSelection"): diff --git a/openpype/hosts/blender/plugins/create/create_review.py b/openpype/hosts/blender/plugins/create/create_review.py index 1191fb95f1..8472600c2f 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -43,6 +43,9 @@ class CreateReview(plugin.BlenderCreator): asset_group = bpy.data.collections.new(name=name) instances.children.link(asset_group) instance_data['task'] = get_current_task_name() + instance_data['id'] = 'pyblish.avalon.instance' + instance_data['creator_identifier'] = self.identifier + instance_data['label'] = self.label lib.imprint(asset_group, instance_data) if pre_create_data.get("useSelection"): diff --git a/openpype/hosts/blender/plugins/create/create_rig.py b/openpype/hosts/blender/plugins/create/create_rig.py index 752f3f7b18..a60f2a72ee 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -44,6 +44,9 @@ class CreateRig(plugin.BlenderCreator): asset_group.empty_display_type = 'SINGLE_ARROW' instances.objects.link(asset_group) instance_data['task'] = get_current_task_name() + instance_data['id'] = 'pyblish.avalon.instance' + instance_data['creator_identifier'] = self.identifier + instance_data['label'] = self.label lib.imprint(asset_group, instance_data) # Add selected objects to instance From 4d03c1c0e60b8e32248a4bb96a326c7c3a3762fb Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Thu, 20 Jul 2023 18:26:59 +0200 Subject: [PATCH 015/101] Set subset in inst data, use update to set it --- .../hosts/blender/plugins/create/create_action.py | 13 +++++++++---- .../blender/plugins/create/create_animation.py | 13 +++++++++---- .../hosts/blender/plugins/create/create_camera.py | 13 +++++++++---- .../hosts/blender/plugins/create/create_layout.py | 13 +++++++++---- .../hosts/blender/plugins/create/create_model.py | 13 +++++++++---- .../blender/plugins/create/create_pointcache.py | 13 +++++++++---- .../hosts/blender/plugins/create/create_review.py | 13 +++++++++---- openpype/hosts/blender/plugins/create/create_rig.py | 13 +++++++++---- 8 files changed, 72 insertions(+), 32 deletions(-) diff --git a/openpype/hosts/blender/plugins/create/create_action.py b/openpype/hosts/blender/plugins/create/create_action.py index 2c0d248994..6951e86c46 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -28,10 +28,15 @@ class CreateAction(openpype.hosts.blender.api.plugin.BlenderCreator): ) collection = bpy.data.collections.new(name=name) bpy.context.scene.collection.children.link(collection) - instance_data['task'] = get_current_task_name() - instance_data['id'] = 'pyblish.avalon.instance' - instance_data['creator_identifier'] = self.identifier - instance_data['label'] = self.label + instance_data.update( + { + "id": "pyblish.avalon.instance", + "creator_identifier": self.identifier, + "label": self.label, + "task": get_current_task_name(), + "subset": subset_name, + } + ) lib.imprint(collection, instance_data) if pre_create_data.get("useSelection"): diff --git a/openpype/hosts/blender/plugins/create/create_animation.py b/openpype/hosts/blender/plugins/create/create_animation.py index 538c0455ac..9e7dfbaf84 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -46,10 +46,15 @@ class CreateAnimation(plugin.BlenderCreator): # asset_group.empty_display_type = 'SINGLE_ARROW' asset_group = bpy.data.collections.new(name=name) instances.children.link(asset_group) - instance_data['task'] = get_current_task_name() - instance_data['id'] = 'pyblish.avalon.instance' - instance_data['creator_identifier'] = self.identifier - instance_data['label'] = self.label + instance_data.update( + { + "id": "pyblish.avalon.instance", + "creator_identifier": self.identifier, + "label": self.label, + "task": get_current_task_name(), + "subset": subset_name, + } + ) lib.imprint(asset_group, instance_data) if pre_create_data.get("useSelection"): diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index ae85273353..c5987779c0 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -44,10 +44,15 @@ class CreateCamera(plugin.BlenderCreator): asset_group = bpy.data.objects.new(name=name, object_data=None) asset_group.empty_display_type = 'SINGLE_ARROW' instances.objects.link(asset_group) - instance_data['task'] = get_current_task_name() - instance_data['id'] = 'pyblish.avalon.instance' - instance_data['creator_identifier'] = self.identifier - instance_data['label'] = self.label + instance_data.update( + { + "id": "pyblish.avalon.instance", + "creator_identifier": self.identifier, + "label": self.label, + "task": get_current_task_name(), + "subset": subset_name, + } + ) lib.imprint(asset_group, instance_data) if pre_create_data.get("useSelection"): diff --git a/openpype/hosts/blender/plugins/create/create_layout.py b/openpype/hosts/blender/plugins/create/create_layout.py index cce422b229..cb61799f4a 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -43,10 +43,15 @@ class CreateLayout(plugin.BlenderCreator): asset_group = bpy.data.objects.new(name=name, object_data=None) asset_group.empty_display_type = 'SINGLE_ARROW' instances.objects.link(asset_group) - instance_data['task'] = get_current_task_name() - instance_data['id'] = 'pyblish.avalon.instance' - instance_data['creator_identifier'] = self.identifier - instance_data['label'] = self.label + instance_data.update( + { + "id": "pyblish.avalon.instance", + "creator_identifier": self.identifier, + "label": self.label, + "task": get_current_task_name(), + "subset": subset_name, + } + ) lib.imprint(asset_group, instance_data) # Add selected objects to instance diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index 57f9e79aa1..ccf3668d98 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -43,10 +43,15 @@ class CreateModel(plugin.BlenderCreator): asset_group = bpy.data.objects.new(name=name, object_data=None) asset_group.empty_display_type = 'SINGLE_ARROW' instances.objects.link(asset_group) - instance_data['task'] = get_current_task_name() - instance_data['id'] = 'pyblish.avalon.instance' - instance_data['creator_identifier'] = self.identifier - instance_data['label'] = self.label + instance_data.update( + { + "id": "pyblish.avalon.instance", + "creator_identifier": self.identifier, + "label": self.label, + "task": get_current_task_name(), + "subset": subset_name, + } + ) lib.imprint(asset_group, instance_data) # Add selected objects to instance diff --git a/openpype/hosts/blender/plugins/create/create_pointcache.py b/openpype/hosts/blender/plugins/create/create_pointcache.py index f95f79ae78..e27cb22389 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -28,10 +28,15 @@ class CreatePointcache(plugin.BlenderCreator): ) collection = bpy.data.collections.new(name=name) bpy.context.scene.collection.children.link(collection) - instance_data['task'] = get_current_task_name() - instance_data['id'] = 'pyblish.avalon.instance' - instance_data['creator_identifier'] = self.identifier - instance_data['label'] = self.label + instance_data.update( + { + "id": "pyblish.avalon.instance", + "creator_identifier": self.identifier, + "label": self.label, + "task": get_current_task_name(), + "subset": subset_name, + } + ) lib.imprint(collection, instance_data) if pre_create_data.get("useSelection"): diff --git a/openpype/hosts/blender/plugins/create/create_review.py b/openpype/hosts/blender/plugins/create/create_review.py index 8472600c2f..afeaea951b 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -42,10 +42,15 @@ class CreateReview(plugin.BlenderCreator): name = plugin.asset_name(instance_data["asset"], subset_name) asset_group = bpy.data.collections.new(name=name) instances.children.link(asset_group) - instance_data['task'] = get_current_task_name() - instance_data['id'] = 'pyblish.avalon.instance' - instance_data['creator_identifier'] = self.identifier - instance_data['label'] = self.label + instance_data.update( + { + "id": "pyblish.avalon.instance", + "creator_identifier": self.identifier, + "label": self.label, + "task": get_current_task_name(), + "subset": subset_name, + } + ) lib.imprint(asset_group, instance_data) if pre_create_data.get("useSelection"): diff --git a/openpype/hosts/blender/plugins/create/create_rig.py b/openpype/hosts/blender/plugins/create/create_rig.py index a60f2a72ee..2db766f7ed 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -43,10 +43,15 @@ class CreateRig(plugin.BlenderCreator): asset_group = bpy.data.objects.new(name=name, object_data=None) asset_group.empty_display_type = 'SINGLE_ARROW' instances.objects.link(asset_group) - instance_data['task'] = get_current_task_name() - instance_data['id'] = 'pyblish.avalon.instance' - instance_data['creator_identifier'] = self.identifier - instance_data['label'] = self.label + instance_data.update( + { + "id": "pyblish.avalon.instance", + "creator_identifier": self.identifier, + "label": self.label, + "task": get_current_task_name(), + "subset": subset_name, + } + ) lib.imprint(asset_group, instance_data) # Add selected objects to instance From 8236e46ca93b562feb66df51a81ec74c6785586f Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Mon, 31 Jul 2023 17:16:40 +0200 Subject: [PATCH 016/101] implement IWorkfileHost for Blender host --- openpype/hosts/blender/api/pipeline.py | 70 +++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/blender/api/pipeline.py b/openpype/hosts/blender/api/pipeline.py index 4c06b1959e..c3f8f06694 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -12,6 +12,7 @@ import pyblish.api from openpype.host import( HostBase, + IWorkfileHost, IPublishHost, ) from openpype.client import get_asset_by_name @@ -33,6 +34,14 @@ from openpype.lib import ( ) import openpype.hosts.blender from openpype.settings import get_project_settings +from .workio import ( + open_file, + save_file, + current_file, + has_unsaved_changes, + file_extensions, + work_root, +) HOST_DIR = os.path.dirname(os.path.abspath(openpype.hosts.blender.__file__)) @@ -51,7 +60,7 @@ IS_HEADLESS = bpy.app.background log = Logger.get_logger(__name__) -class BlenderHost(HostBase, IPublishHost): +class BlenderHost(HostBase, IWorkfileHost, IPublishHost): name = "blender" def install(self): @@ -63,6 +72,65 @@ class BlenderHost(HostBase, IPublishHost): """List containers from active Blender scene.""" return ls() + def get_workfile_extensions(self) -> List[str]: + """Override get_workfile_extensions method from IWorkfileHost. + Get workfile possible extensions. + + Returns: + List[str]: Workfile extensions. + """ + return file_extensions() + + def save_workfile(self, dst_path: str = None): + """Override save_workfile method from IWorkfileHost. + Save currently opened workfile. + + Args: + dst_path (str): Where the current scene should be saved. Or use + current path if `None` is passed. + """ + save_file(dst_path if dst_path else bpy.data.filepath) + + def open_workfile(self, filepath: str): + """Override open_workfile method from IWorkfileHost. + Open workfile at specified filepath in the host. + + Args: + filepath (str): Path to workfile. + """ + open_file(filepath) + + def get_current_workfile(self) -> str: + """Override get_current_workfile method from IWorkfileHost. + Retrieve currently opened workfile path. + + Returns: + str: Path to currently opened workfile. + """ + return current_file() + + def workfile_has_unsaved_changes(self) -> bool: + """Override wokfile_has_unsaved_changes method from IWorkfileHost. + Returns True if opened workfile has no unsaved changes. + + Returns: + bool: True if scene is saved and False if it has unsaved + modifications. + """ + return has_unsaved_changes() + + def work_root(self, session) -> str: + """Override work_root method from IWorkfileHost. + Modify workdir per host. + + Args: + session (dict): Session context data. + + Returns: + str: Path to new workdir. + """ + return work_root(session) + def get_context_data(self) -> dict: """Override abstract method from IPublishHost. Get global data related to creation-publishing from workfile. From e9d022bab37f67391267788c3de940c1db0f955d Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Wed, 2 Aug 2023 16:13:34 +0200 Subject: [PATCH 017/101] Handling optional plugins --- openpype/hosts/blender/plugins/publish/extract_abc.py | 2 +- .../blender/plugins/publish/extract_abc_animation.py | 5 ++++- .../blender/plugins/publish/extract_blend_animation.py | 5 ++++- .../blender/plugins/publish/extract_camera_abc.py | 2 +- .../blender/plugins/publish/extract_camera_fbx.py | 2 +- openpype/hosts/blender/plugins/publish/extract_fbx.py | 2 +- .../blender/plugins/publish/extract_fbx_animation.py | 5 ++++- .../hosts/blender/plugins/publish/extract_layout.py | 2 +- .../hosts/blender/plugins/publish/extract_playblast.py | 2 +- .../plugins/publish/increment_workfile_version.py | 6 +++++- .../blender/plugins/publish/integrate_animation.py | 6 +++++- .../blender/plugins/publish/validate_mesh_has_uv.py | 10 ++++++++-- .../blender/plugins/publish/validate_object_mode.py | 6 +++++- 13 files changed, 41 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/extract_abc.py b/openpype/hosts/blender/plugins/publish/extract_abc.py index 7b6c4d7ae7..5af8104344 100644 --- a/openpype/hosts/blender/plugins/publish/extract_abc.py +++ b/openpype/hosts/blender/plugins/publish/extract_abc.py @@ -7,7 +7,7 @@ from openpype.hosts.blender.api import plugin from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY -class ExtractABC(publish.Extractor): +class ExtractABC(publish.Extractor, publish.OptionalPyblishPluginMixin): """Extract as ABC.""" label = "Extract ABC" diff --git a/openpype/hosts/blender/plugins/publish/extract_abc_animation.py b/openpype/hosts/blender/plugins/publish/extract_abc_animation.py index 44b2ba3761..0b6b93b7a5 100644 --- a/openpype/hosts/blender/plugins/publish/extract_abc_animation.py +++ b/openpype/hosts/blender/plugins/publish/extract_abc_animation.py @@ -6,7 +6,10 @@ from openpype.pipeline import publish from openpype.hosts.blender.api import plugin -class ExtractAnimationABC(publish.Extractor): +class ExtractAnimationABC( + publish.Extractor, + publish.OptionalPyblishPluginMixin, +): """Extract as ABC.""" label = "Extract Animation ABC" diff --git a/openpype/hosts/blender/plugins/publish/extract_blend_animation.py b/openpype/hosts/blender/plugins/publish/extract_blend_animation.py index 477411b73d..3a5b788c9e 100644 --- a/openpype/hosts/blender/plugins/publish/extract_blend_animation.py +++ b/openpype/hosts/blender/plugins/publish/extract_blend_animation.py @@ -5,7 +5,10 @@ import bpy from openpype.pipeline import publish -class ExtractBlendAnimation(publish.Extractor): +class ExtractBlendAnimation( + publish.Extractor, + publish.OptionalPyblishPluginMixin, +): """Extract a blend file.""" label = "Extract Blend" diff --git a/openpype/hosts/blender/plugins/publish/extract_camera_abc.py b/openpype/hosts/blender/plugins/publish/extract_camera_abc.py index 036be7bf3c..2a327f4d65 100644 --- a/openpype/hosts/blender/plugins/publish/extract_camera_abc.py +++ b/openpype/hosts/blender/plugins/publish/extract_camera_abc.py @@ -7,7 +7,7 @@ from openpype.hosts.blender.api import plugin from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY -class ExtractCameraABC(publish.Extractor): +class ExtractCameraABC(publish.Extractor, publish.OptionalPyblishPluginMixin): """Extract camera as ABC.""" label = "Extract Camera (ABC)" diff --git a/openpype/hosts/blender/plugins/publish/extract_camera_fbx.py b/openpype/hosts/blender/plugins/publish/extract_camera_fbx.py index 315994140e..8e5e4d37d4 100644 --- a/openpype/hosts/blender/plugins/publish/extract_camera_fbx.py +++ b/openpype/hosts/blender/plugins/publish/extract_camera_fbx.py @@ -6,7 +6,7 @@ from openpype.pipeline import publish from openpype.hosts.blender.api import plugin -class ExtractCamera(publish.Extractor): +class ExtractCamera(publish.Extractor, publish.OptionalPyblishPluginMixin): """Extract as the camera as FBX.""" label = "Extract Camera (FBX)" diff --git a/openpype/hosts/blender/plugins/publish/extract_fbx.py b/openpype/hosts/blender/plugins/publish/extract_fbx.py index 0ad797c226..8ace6a43a7 100644 --- a/openpype/hosts/blender/plugins/publish/extract_fbx.py +++ b/openpype/hosts/blender/plugins/publish/extract_fbx.py @@ -7,7 +7,7 @@ from openpype.hosts.blender.api import plugin from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY -class ExtractFBX(publish.Extractor): +class ExtractFBX(publish.Extractor, publish.OptionalPyblishPluginMixin): """Extract as FBX.""" label = "Extract FBX" diff --git a/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py b/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py index 062b42e99d..04f50f8207 100644 --- a/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py @@ -10,7 +10,10 @@ from openpype.hosts.blender.api import plugin from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY -class ExtractAnimationFBX(publish.Extractor): +class ExtractAnimationFBX( + publish.Extractor, + publish.OptionalPyblishPluginMixin, +): """Extract as animation.""" label = "Extract FBX" diff --git a/openpype/hosts/blender/plugins/publish/extract_layout.py b/openpype/hosts/blender/plugins/publish/extract_layout.py index f2d04f1178..8445560bba 100644 --- a/openpype/hosts/blender/plugins/publish/extract_layout.py +++ b/openpype/hosts/blender/plugins/publish/extract_layout.py @@ -11,7 +11,7 @@ from openpype.hosts.blender.api import plugin from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY -class ExtractLayout(publish.Extractor): +class ExtractLayout(publish.Extractor, publish.OptionalPyblishPluginMixin): """Extract a layout.""" label = "Extract Layout" diff --git a/openpype/hosts/blender/plugins/publish/extract_playblast.py b/openpype/hosts/blender/plugins/publish/extract_playblast.py index 196e75b8cc..82ddbc1fc2 100644 --- a/openpype/hosts/blender/plugins/publish/extract_playblast.py +++ b/openpype/hosts/blender/plugins/publish/extract_playblast.py @@ -9,7 +9,7 @@ from openpype.hosts.blender.api import capture from openpype.hosts.blender.api.lib import maintained_time -class ExtractPlayblast(publish.Extractor): +class ExtractPlayblast(publish.Extractor, publish.OptionalPyblishPluginMixin): """ Extract viewport playblast. diff --git a/openpype/hosts/blender/plugins/publish/increment_workfile_version.py b/openpype/hosts/blender/plugins/publish/increment_workfile_version.py index 6ace14d77c..176668f366 100644 --- a/openpype/hosts/blender/plugins/publish/increment_workfile_version.py +++ b/openpype/hosts/blender/plugins/publish/increment_workfile_version.py @@ -1,8 +1,12 @@ import pyblish.api +from openpype.pipeline.publish import OptionalPyblishPluginMixin from openpype.hosts.blender.api.workio import save_file -class IncrementWorkfileVersion(pyblish.api.ContextPlugin): +class IncrementWorkfileVersion( + pyblish.api.ContextPlugin, + OptionalPyblishPluginMixin +): """Increment current workfile version.""" order = pyblish.api.IntegratorOrder + 0.9 diff --git a/openpype/hosts/blender/plugins/publish/integrate_animation.py b/openpype/hosts/blender/plugins/publish/integrate_animation.py index d9a85bc79b..b7e5423fa8 100644 --- a/openpype/hosts/blender/plugins/publish/integrate_animation.py +++ b/openpype/hosts/blender/plugins/publish/integrate_animation.py @@ -1,9 +1,13 @@ import json import pyblish.api +from openpype.pipeline.publish import OptionalPyblishPluginMixin -class IntegrateAnimation(pyblish.api.InstancePlugin): +class IntegrateAnimation( + pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin, +): """Generate a JSON file for animation.""" label = "Integrate Animation" diff --git a/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py b/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py index edf47193be..687371b362 100644 --- a/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py +++ b/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py @@ -4,11 +4,17 @@ import bpy import pyblish.api -from openpype.pipeline.publish import ValidateContentsOrder +from openpype.pipeline.publish import ( + ValidateContentsOrder, + OptionalPyblishPluginMixin, +) import openpype.hosts.blender.api.action -class ValidateMeshHasUvs(pyblish.api.InstancePlugin): +class ValidateMeshHasUvs( + pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin, +): """Validate that the current mesh has UV's.""" order = ValidateContentsOrder diff --git a/openpype/hosts/blender/plugins/publish/validate_object_mode.py b/openpype/hosts/blender/plugins/publish/validate_object_mode.py index ac60e00f89..d8d2e3c8bf 100644 --- a/openpype/hosts/blender/plugins/publish/validate_object_mode.py +++ b/openpype/hosts/blender/plugins/publish/validate_object_mode.py @@ -3,10 +3,14 @@ from typing import List import bpy import pyblish.api +from openpype.pipeline.publish import OptionalPyblishPluginMixin import openpype.hosts.blender.api.action -class ValidateObjectIsInObjectMode(pyblish.api.InstancePlugin): +class ValidateObjectIsInObjectMode( + pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin, +): """Validate that the objects in the instance are in Object Mode.""" order = pyblish.api.ValidatorOrder - 0.01 From 656a42dd525317f83b22a232f8dc68756d2c389a Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Wed, 9 Aug 2023 11:45:58 +0200 Subject: [PATCH 018/101] Handling instance noce --- openpype/hosts/blender/api/plugin.py | 13 ++++++++++--- .../hosts/blender/plugins/create/create_action.py | 8 ++++++++ .../blender/plugins/create/create_animation.py | 6 ++++++ .../hosts/blender/plugins/create/create_camera.py | 6 ++++++ .../hosts/blender/plugins/create/create_layout.py | 6 ++++++ .../hosts/blender/plugins/create/create_model.py | 6 ++++++ .../blender/plugins/create/create_pointcache.py | 6 ++++++ .../hosts/blender/plugins/create/create_review.py | 6 ++++++ openpype/hosts/blender/plugins/create/create_rig.py | 6 ++++++ 9 files changed, 60 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 5dd352ff6c..fe0d53f84e 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -234,13 +234,20 @@ class BlenderCreator(Creator): pre_create_data(dict): Data based on pre creation attributes. Those may affect how creator works. """ + collection = bpy.data.collections.new(name=subset_name) + bpy.context.scene.collection.children.link(collection) + + instance_node = {} + for key, value in collection.items(): + instance_node[key] = value + + instance_data["instance_node"] = instance_node + instance = CreatedInstance( self.family, subset_name, instance_data, self ) self._add_instance_to_context(instance) - collection = bpy.data.collections.new(name=subset_name) - bpy.context.scene.collection.children.link(collection) imprint(collection, instance_data) if pre_create_data.get("useSelection"): @@ -287,7 +294,7 @@ class BlenderCreator(Creator): for created_instance, _changes in update_list: data = created_instance.data_to_store() - # TODO + imprint(data.get("instance_node", {}), data) def remove_instances(self, instances: List[CreatedInstance]): diff --git a/openpype/hosts/blender/plugins/create/create_action.py b/openpype/hosts/blender/plugins/create/create_action.py index 6951e86c46..a43258082c 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -28,6 +28,11 @@ class CreateAction(openpype.hosts.blender.api.plugin.BlenderCreator): ) collection = bpy.data.collections.new(name=name) bpy.context.scene.collection.children.link(collection) + + instance_node = {} + for key, value in collection.items(): + instance_node[key] = value + instance_data.update( { "id": "pyblish.avalon.instance", @@ -35,8 +40,11 @@ class CreateAction(openpype.hosts.blender.api.plugin.BlenderCreator): "label": self.label, "task": get_current_task_name(), "subset": subset_name, + "instance_node": instance_node, } ) + from pprint import pprint + pprint(instance_data) lib.imprint(collection, instance_data) if pre_create_data.get("useSelection"): diff --git a/openpype/hosts/blender/plugins/create/create_animation.py b/openpype/hosts/blender/plugins/create/create_animation.py index 9e7dfbaf84..842292f0f9 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -46,6 +46,11 @@ class CreateAnimation(plugin.BlenderCreator): # asset_group.empty_display_type = 'SINGLE_ARROW' asset_group = bpy.data.collections.new(name=name) instances.children.link(asset_group) + + instance_node = {} + for key, value in asset_group.items(): + instance_node[key] = value + instance_data.update( { "id": "pyblish.avalon.instance", @@ -53,6 +58,7 @@ class CreateAnimation(plugin.BlenderCreator): "label": self.label, "task": get_current_task_name(), "subset": subset_name, + "instance_node": instance_node, } ) lib.imprint(asset_group, instance_data) diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index c5987779c0..8360abbc7d 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -44,6 +44,11 @@ class CreateCamera(plugin.BlenderCreator): asset_group = bpy.data.objects.new(name=name, object_data=None) asset_group.empty_display_type = 'SINGLE_ARROW' instances.objects.link(asset_group) + + instance_node = {} + for key, value in asset_group.items(): + instance_node[key] = value + instance_data.update( { "id": "pyblish.avalon.instance", @@ -51,6 +56,7 @@ class CreateCamera(plugin.BlenderCreator): "label": self.label, "task": get_current_task_name(), "subset": subset_name, + "instance_node": instance_node, } ) lib.imprint(asset_group, instance_data) diff --git a/openpype/hosts/blender/plugins/create/create_layout.py b/openpype/hosts/blender/plugins/create/create_layout.py index cb61799f4a..b4b127f32c 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -43,6 +43,11 @@ class CreateLayout(plugin.BlenderCreator): asset_group = bpy.data.objects.new(name=name, object_data=None) asset_group.empty_display_type = 'SINGLE_ARROW' instances.objects.link(asset_group) + + instance_node = {} + for key, value in asset_group.items(): + instance_node[key] = value + instance_data.update( { "id": "pyblish.avalon.instance", @@ -50,6 +55,7 @@ class CreateLayout(plugin.BlenderCreator): "label": self.label, "task": get_current_task_name(), "subset": subset_name, + "instance_node": instance_node, } ) lib.imprint(asset_group, instance_data) diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index ccf3668d98..5cb2de0fae 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -43,6 +43,11 @@ class CreateModel(plugin.BlenderCreator): asset_group = bpy.data.objects.new(name=name, object_data=None) asset_group.empty_display_type = 'SINGLE_ARROW' instances.objects.link(asset_group) + + instance_node = {} + for key, value in asset_group.items(): + instance_node[key] = value + instance_data.update( { "id": "pyblish.avalon.instance", @@ -50,6 +55,7 @@ class CreateModel(plugin.BlenderCreator): "label": self.label, "task": get_current_task_name(), "subset": subset_name, + "instance_node": instance_node, } ) lib.imprint(asset_group, instance_data) diff --git a/openpype/hosts/blender/plugins/create/create_pointcache.py b/openpype/hosts/blender/plugins/create/create_pointcache.py index e27cb22389..a95ae547c8 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -28,6 +28,11 @@ class CreatePointcache(plugin.BlenderCreator): ) collection = bpy.data.collections.new(name=name) bpy.context.scene.collection.children.link(collection) + + instance_node = {} + for key, value in collection.items(): + instance_node[key] = value + instance_data.update( { "id": "pyblish.avalon.instance", @@ -35,6 +40,7 @@ class CreatePointcache(plugin.BlenderCreator): "label": self.label, "task": get_current_task_name(), "subset": subset_name, + "instance_node": instance_node, } ) lib.imprint(collection, instance_data) diff --git a/openpype/hosts/blender/plugins/create/create_review.py b/openpype/hosts/blender/plugins/create/create_review.py index afeaea951b..1bcafbc265 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -42,6 +42,11 @@ class CreateReview(plugin.BlenderCreator): name = plugin.asset_name(instance_data["asset"], subset_name) asset_group = bpy.data.collections.new(name=name) instances.children.link(asset_group) + + instance_node = {} + for key, value in asset_group.items(): + instance_node[key] = value + instance_data.update( { "id": "pyblish.avalon.instance", @@ -49,6 +54,7 @@ class CreateReview(plugin.BlenderCreator): "label": self.label, "task": get_current_task_name(), "subset": subset_name, + "instance_node": instance_node, } ) lib.imprint(asset_group, instance_data) diff --git a/openpype/hosts/blender/plugins/create/create_rig.py b/openpype/hosts/blender/plugins/create/create_rig.py index 2db766f7ed..e93f4a171f 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -43,6 +43,11 @@ class CreateRig(plugin.BlenderCreator): asset_group = bpy.data.objects.new(name=name, object_data=None) asset_group.empty_display_type = 'SINGLE_ARROW' instances.objects.link(asset_group) + + instance_node = {} + for key, value in asset_group.items(): + instance_node[key] = value + instance_data.update( { "id": "pyblish.avalon.instance", @@ -50,6 +55,7 @@ class CreateRig(plugin.BlenderCreator): "label": self.label, "task": get_current_task_name(), "subset": subset_name, + "instance_node": instance_node, } ) lib.imprint(asset_group, instance_data) From 5f7847c6973d378779e3b27a0ef25ed3fa5a9e1c Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Wed, 9 Aug 2023 12:04:06 +0200 Subject: [PATCH 019/101] Removed useless pprint --- openpype/hosts/blender/plugins/create/create_action.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/blender/plugins/create/create_action.py b/openpype/hosts/blender/plugins/create/create_action.py index a43258082c..ee7ca092b5 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -43,8 +43,6 @@ class CreateAction(openpype.hosts.blender.api.plugin.BlenderCreator): "instance_node": instance_node, } ) - from pprint import pprint - pprint(instance_data) lib.imprint(collection, instance_data) if pre_create_data.get("useSelection"): From f52c9f0a38de8c9d3f50d6a1a721f20afdfa1b5f Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Thu, 10 Aug 2023 10:37:46 +0200 Subject: [PATCH 020/101] Enhance instance_node, wip remove_inst, rm extra empty lines --- openpype/hosts/blender/api/plugin.py | 22 ++++++++++++++----- .../blender/plugins/create/create_action.py | 8 ++++--- .../plugins/create/create_animation.py | 12 ++++++---- .../blender/plugins/create/create_camera.py | 12 ++++++---- .../blender/plugins/create/create_layout.py | 12 ++++++---- .../blender/plugins/create/create_model.py | 12 ++++++---- .../plugins/create/create_pointcache.py | 8 ++++--- .../blender/plugins/create/create_review.py | 12 ++++++---- .../blender/plugins/create/create_rig.py | 12 ++++++---- 9 files changed, 75 insertions(+), 35 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index fe0d53f84e..9967f9479c 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -155,7 +155,6 @@ class BlenderCreator(Creator): return collection - @staticmethod def cache_subsets(shared_data): """Cache instances for Creators shared data. @@ -221,7 +220,6 @@ class BlenderCreator(Creator): return shared_data - def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): @@ -254,7 +252,6 @@ class BlenderCreator(Creator): for obj in get_selection(): collection.objects.link(obj) - def collect_instances(self): """Override abstract method from BaseCreator. Collect existing instances related to this creator plugin.""" @@ -283,7 +280,6 @@ class BlenderCreator(Creator): # Add instance to create context self._add_instance_to_context(instance) - def update_instances(self, update_list): """Override abstract method from BaseCreator. Store changes of existing instances so they can be recollected. @@ -296,7 +292,6 @@ class BlenderCreator(Creator): imprint(data.get("instance_node", {}), data) - def remove_instances(self, instances: List[CreatedInstance]): """Override abstract method from BaseCreator. Method called when instances are removed. @@ -305,6 +300,23 @@ class BlenderCreator(Creator): instance(List[CreatedInstance]): Instance objects to remove. """ for instance in instances: + outliner_entity = instance.data.get("instance_node", {}).get( + "datablock" + ) + if not outliner_entity: + continue + + if isinstance(outliner_entity, bpy.types.Collection): + for children in outliner_entity.children_recursive: + if isinstance(children, bpy.types.Collection): + bpy.data.collections.remove(children) + else: + bpy.data.objects.remove(children) + + bpy.data.collections.remove(outliner_entity) + elif isinstance(outliner_entity, bpy.types.Object): + bpy.data.objects.remove(outliner_entity) + self._remove_instance_from_context(instance) diff --git a/openpype/hosts/blender/plugins/create/create_action.py b/openpype/hosts/blender/plugins/create/create_action.py index ee7ca092b5..7404e7e037 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -5,6 +5,7 @@ import bpy from openpype.pipeline import get_current_task_name, CreatedInstance import openpype.hosts.blender.api.plugin from openpype.hosts.blender.api import lib +from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY class CreateAction(openpype.hosts.blender.api.plugin.BlenderCreator): @@ -29,9 +30,10 @@ class CreateAction(openpype.hosts.blender.api.plugin.BlenderCreator): collection = bpy.data.collections.new(name=name) bpy.context.scene.collection.children.link(collection) - instance_node = {} - for key, value in collection.items(): - instance_node[key] = value + collection[AVALON_PROPERTY] = instance_node = { + "name": collection.name, + "datablock": collection, + } instance_data.update( { diff --git a/openpype/hosts/blender/plugins/create/create_animation.py b/openpype/hosts/blender/plugins/create/create_animation.py index 842292f0f9..c2f24250e1 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -4,7 +4,10 @@ import bpy from openpype.pipeline import get_current_task_name, CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops -from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES +from openpype.hosts.blender.api.pipeline import ( + AVALON_INSTANCES, + AVALON_PROPERTY, +) class CreateAnimation(plugin.BlenderCreator): @@ -47,9 +50,10 @@ class CreateAnimation(plugin.BlenderCreator): asset_group = bpy.data.collections.new(name=name) instances.children.link(asset_group) - instance_node = {} - for key, value in asset_group.items(): - instance_node[key] = value + asset_group[AVALON_PROPERTY] = instance_node = { + "name": asset_group.name, + "datablock": asset_group, + } instance_data.update( { diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index 8360abbc7d..a83124cbe7 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -4,7 +4,10 @@ import bpy from openpype.pipeline import get_current_task_name, CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops -from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES +from openpype.hosts.blender.api.pipeline import ( + AVALON_INSTANCES, + AVALON_PROPERTY, +) class CreateCamera(plugin.BlenderCreator): @@ -45,9 +48,10 @@ class CreateCamera(plugin.BlenderCreator): asset_group.empty_display_type = 'SINGLE_ARROW' instances.objects.link(asset_group) - instance_node = {} - for key, value in asset_group.items(): - instance_node[key] = value + asset_group[AVALON_PROPERTY] = instance_node = { + "name": asset_group.name, + "datablock": asset_group, + } instance_data.update( { diff --git a/openpype/hosts/blender/plugins/create/create_layout.py b/openpype/hosts/blender/plugins/create/create_layout.py index b4b127f32c..4fb76ef41e 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -4,7 +4,10 @@ import bpy from openpype.pipeline import get_current_task_name, CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops -from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES +from openpype.hosts.blender.api.pipeline import ( + AVALON_INSTANCES, + AVALON_PROPERTY, +) class CreateLayout(plugin.BlenderCreator): @@ -44,9 +47,10 @@ class CreateLayout(plugin.BlenderCreator): asset_group.empty_display_type = 'SINGLE_ARROW' instances.objects.link(asset_group) - instance_node = {} - for key, value in asset_group.items(): - instance_node[key] = value + asset_group[AVALON_PROPERTY] = instance_node = { + "name": asset_group.name, + "datablock": asset_group, + } instance_data.update( { diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index 5cb2de0fae..45f1d66ad9 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -4,7 +4,10 @@ import bpy from openpype.pipeline import get_current_task_name, CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops -from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES +from openpype.hosts.blender.api.pipeline import ( + AVALON_INSTANCES, + AVALON_PROPERTY, +) class CreateModel(plugin.BlenderCreator): @@ -44,9 +47,10 @@ class CreateModel(plugin.BlenderCreator): asset_group.empty_display_type = 'SINGLE_ARROW' instances.objects.link(asset_group) - instance_node = {} - for key, value in asset_group.items(): - instance_node[key] = value + asset_group[AVALON_PROPERTY] = instance_node = { + "name": asset_group.name, + "datablock": asset_group, + } instance_data.update( { diff --git a/openpype/hosts/blender/plugins/create/create_pointcache.py b/openpype/hosts/blender/plugins/create/create_pointcache.py index a95ae547c8..7aa3b22466 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -5,6 +5,7 @@ import bpy from openpype.pipeline import get_current_task_name, CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES +from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY class CreatePointcache(plugin.BlenderCreator): @@ -29,9 +30,10 @@ class CreatePointcache(plugin.BlenderCreator): collection = bpy.data.collections.new(name=name) bpy.context.scene.collection.children.link(collection) - instance_node = {} - for key, value in collection.items(): - instance_node[key] = value + collection[AVALON_PROPERTY] = instance_node = { + "name": collection.name, + "datablock": collection, + } instance_data.update( { diff --git a/openpype/hosts/blender/plugins/create/create_review.py b/openpype/hosts/blender/plugins/create/create_review.py index 1bcafbc265..a6ca5b1b92 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -4,7 +4,10 @@ import bpy from openpype.pipeline import get_current_task_name, CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops -from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES +from openpype.hosts.blender.api.pipeline import ( + AVALON_INSTANCES, + AVALON_PROPERTY, +) class CreateReview(plugin.BlenderCreator): @@ -43,9 +46,10 @@ class CreateReview(plugin.BlenderCreator): asset_group = bpy.data.collections.new(name=name) instances.children.link(asset_group) - instance_node = {} - for key, value in asset_group.items(): - instance_node[key] = value + asset_group[AVALON_PROPERTY] = instance_node = { + "name": asset_group.name, + "datablock": asset_group, + } instance_data.update( { diff --git a/openpype/hosts/blender/plugins/create/create_rig.py b/openpype/hosts/blender/plugins/create/create_rig.py index e93f4a171f..f7c99b0b03 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -4,7 +4,10 @@ import bpy from openpype.pipeline import get_current_task_name, CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops -from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES +from openpype.hosts.blender.api.pipeline import ( + AVALON_INSTANCES, + AVALON_PROPERTY, +) class CreateRig(plugin.BlenderCreator): @@ -44,9 +47,10 @@ class CreateRig(plugin.BlenderCreator): asset_group.empty_display_type = 'SINGLE_ARROW' instances.objects.link(asset_group) - instance_node = {} - for key, value in asset_group.items(): - instance_node[key] = value + asset_group[AVALON_PROPERTY] = instance_node = { + "name": asset_group.name, + "datablock": asset_group, + } instance_data.update( { From 371d7405ec10257fe52c1259c383604862b080f7 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Thu, 10 Aug 2023 11:19:05 +0200 Subject: [PATCH 021/101] Apply creator change to bl base creator --- openpype/hosts/blender/api/plugin.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 9967f9479c..060d229eb5 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -235,9 +235,10 @@ class BlenderCreator(Creator): collection = bpy.data.collections.new(name=subset_name) bpy.context.scene.collection.children.link(collection) - instance_node = {} - for key, value in collection.items(): - instance_node[key] = value + collection["instance_node"] = instance_node = { + "name": collection.name, + "datablock": collection, + } instance_data["instance_node"] = instance_node From e832cf59ccb2bbf6a436184f1143ebf53231c5df Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Tue, 12 Sep 2023 15:49:33 +0200 Subject: [PATCH 022/101] Fix Cannot pickle object error --- openpype/hosts/blender/api/plugin.py | 5 +---- openpype/hosts/blender/plugins/create/create_action.py | 1 - openpype/hosts/blender/plugins/create/create_animation.py | 1 - openpype/hosts/blender/plugins/create/create_camera.py | 1 - openpype/hosts/blender/plugins/create/create_layout.py | 1 - openpype/hosts/blender/plugins/create/create_model.py | 1 - openpype/hosts/blender/plugins/create/create_pointcache.py | 1 - openpype/hosts/blender/plugins/create/create_review.py | 1 - openpype/hosts/blender/plugins/create/create_rig.py | 1 - 9 files changed, 1 insertion(+), 12 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 060d229eb5..73d8fc0ed5 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -237,7 +237,6 @@ class BlenderCreator(Creator): collection["instance_node"] = instance_node = { "name": collection.name, - "datablock": collection, } instance_data["instance_node"] = instance_node @@ -267,9 +266,7 @@ class BlenderCreator(Creator): for instance_data in cached_subsets.get(self.identifier, []): # Process only instances that were created by this creator - data = {} - for key, value in instance_data.items(): - data[key] = value + data = instance_data.to_dict() creator_id = data.get('creator_identifier') if creator_id == self.identifier: diff --git a/openpype/hosts/blender/plugins/create/create_action.py b/openpype/hosts/blender/plugins/create/create_action.py index 7404e7e037..d766fce038 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -32,7 +32,6 @@ class CreateAction(openpype.hosts.blender.api.plugin.BlenderCreator): collection[AVALON_PROPERTY] = instance_node = { "name": collection.name, - "datablock": collection, } instance_data.update( diff --git a/openpype/hosts/blender/plugins/create/create_animation.py b/openpype/hosts/blender/plugins/create/create_animation.py index c2f24250e1..88ae9e5996 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -52,7 +52,6 @@ class CreateAnimation(plugin.BlenderCreator): asset_group[AVALON_PROPERTY] = instance_node = { "name": asset_group.name, - "datablock": asset_group, } instance_data.update( diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index a83124cbe7..026b5739d6 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -50,7 +50,6 @@ class CreateCamera(plugin.BlenderCreator): asset_group[AVALON_PROPERTY] = instance_node = { "name": asset_group.name, - "datablock": asset_group, } instance_data.update( diff --git a/openpype/hosts/blender/plugins/create/create_layout.py b/openpype/hosts/blender/plugins/create/create_layout.py index 4fb76ef41e..f46ae58a43 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -49,7 +49,6 @@ class CreateLayout(plugin.BlenderCreator): asset_group[AVALON_PROPERTY] = instance_node = { "name": asset_group.name, - "datablock": asset_group, } instance_data.update( diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index 45f1d66ad9..069b78626b 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -49,7 +49,6 @@ class CreateModel(plugin.BlenderCreator): asset_group[AVALON_PROPERTY] = instance_node = { "name": asset_group.name, - "datablock": asset_group, } instance_data.update( diff --git a/openpype/hosts/blender/plugins/create/create_pointcache.py b/openpype/hosts/blender/plugins/create/create_pointcache.py index 7aa3b22466..3054b81ef5 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -32,7 +32,6 @@ class CreatePointcache(plugin.BlenderCreator): collection[AVALON_PROPERTY] = instance_node = { "name": collection.name, - "datablock": collection, } instance_data.update( diff --git a/openpype/hosts/blender/plugins/create/create_review.py b/openpype/hosts/blender/plugins/create/create_review.py index a6ca5b1b92..10a96c94fd 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -48,7 +48,6 @@ class CreateReview(plugin.BlenderCreator): asset_group[AVALON_PROPERTY] = instance_node = { "name": asset_group.name, - "datablock": asset_group, } instance_data.update( diff --git a/openpype/hosts/blender/plugins/create/create_rig.py b/openpype/hosts/blender/plugins/create/create_rig.py index f7c99b0b03..8daffe638d 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -49,7 +49,6 @@ class CreateRig(plugin.BlenderCreator): asset_group[AVALON_PROPERTY] = instance_node = { "name": asset_group.name, - "datablock": asset_group, } instance_data.update( From 78b33370dc3418083d323d465332ff28aa32fbeb Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Mon, 18 Sep 2023 17:59:33 +0200 Subject: [PATCH 023/101] Fix collectreview --- openpype/hosts/blender/plugins/publish/collect_review.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/blender/plugins/publish/collect_review.py b/openpype/hosts/blender/plugins/publish/collect_review.py index 3bf2e39e24..66a3d7b5e8 100644 --- a/openpype/hosts/blender/plugins/publish/collect_review.py +++ b/openpype/hosts/blender/plugins/publish/collect_review.py @@ -16,10 +16,14 @@ class CollectReview(pyblish.api.InstancePlugin): self.log.debug(f"instance: {instance}") + datablock = bpy.data.collections.get( + instance.data.get("instance_node", {}).get("name", "") + ) + # get cameras cameras = [ obj - for obj in instance + for obj in datablock.all_objects if isinstance(obj, bpy.types.Object) and obj.type == "CAMERA" ] From a12d599b442540a46b88212fcdc873ad1c85db46 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Wed, 25 Oct 2023 14:04:00 +0200 Subject: [PATCH 024/101] change creator labels --- openpype/hosts/blender/plugins/create/create_action.py | 2 +- openpype/hosts/blender/plugins/create/create_animation.py | 2 +- openpype/hosts/blender/plugins/create/create_camera.py | 2 +- openpype/hosts/blender/plugins/create/create_layout.py | 2 +- openpype/hosts/blender/plugins/create/create_model.py | 2 +- openpype/hosts/blender/plugins/create/create_pointcache.py | 2 +- openpype/hosts/blender/plugins/create/create_review.py | 2 +- openpype/hosts/blender/plugins/create/create_rig.py | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/blender/plugins/create/create_action.py b/openpype/hosts/blender/plugins/create/create_action.py index d766fce038..e7b689c54e 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -38,7 +38,7 @@ class CreateAction(openpype.hosts.blender.api.plugin.BlenderCreator): { "id": "pyblish.avalon.instance", "creator_identifier": self.identifier, - "label": self.label, + "label": subset_name, "task": get_current_task_name(), "subset": subset_name, "instance_node": instance_node, diff --git a/openpype/hosts/blender/plugins/create/create_animation.py b/openpype/hosts/blender/plugins/create/create_animation.py index 88ae9e5996..8b4214ceda 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -58,7 +58,7 @@ class CreateAnimation(plugin.BlenderCreator): { "id": "pyblish.avalon.instance", "creator_identifier": self.identifier, - "label": self.label, + "label": subset_name, "task": get_current_task_name(), "subset": subset_name, "instance_node": instance_node, diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index 026b5739d6..4747e50b2e 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -56,7 +56,7 @@ class CreateCamera(plugin.BlenderCreator): { "id": "pyblish.avalon.instance", "creator_identifier": self.identifier, - "label": self.label, + "label": subset_name, "task": get_current_task_name(), "subset": subset_name, "instance_node": instance_node, diff --git a/openpype/hosts/blender/plugins/create/create_layout.py b/openpype/hosts/blender/plugins/create/create_layout.py index f46ae58a43..0c97d57af3 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -55,7 +55,7 @@ class CreateLayout(plugin.BlenderCreator): { "id": "pyblish.avalon.instance", "creator_identifier": self.identifier, - "label": self.label, + "label": subset_name, "task": get_current_task_name(), "subset": subset_name, "instance_node": instance_node, diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index 069b78626b..3c8e9c4900 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -55,7 +55,7 @@ class CreateModel(plugin.BlenderCreator): { "id": "pyblish.avalon.instance", "creator_identifier": self.identifier, - "label": self.label, + "label": subset_name, "task": get_current_task_name(), "subset": subset_name, "instance_node": instance_node, diff --git a/openpype/hosts/blender/plugins/create/create_pointcache.py b/openpype/hosts/blender/plugins/create/create_pointcache.py index 3054b81ef5..a40bd5af61 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -38,7 +38,7 @@ class CreatePointcache(plugin.BlenderCreator): { "id": "pyblish.avalon.instance", "creator_identifier": self.identifier, - "label": self.label, + "label": subset_name, "task": get_current_task_name(), "subset": subset_name, "instance_node": instance_node, diff --git a/openpype/hosts/blender/plugins/create/create_review.py b/openpype/hosts/blender/plugins/create/create_review.py index 10a96c94fd..8c9a8d5927 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -54,7 +54,7 @@ class CreateReview(plugin.BlenderCreator): { "id": "pyblish.avalon.instance", "creator_identifier": self.identifier, - "label": self.label, + "label": subset_name, "task": get_current_task_name(), "subset": subset_name, "instance_node": instance_node, diff --git a/openpype/hosts/blender/plugins/create/create_rig.py b/openpype/hosts/blender/plugins/create/create_rig.py index 8daffe638d..110a9f5c8e 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -55,7 +55,7 @@ class CreateRig(plugin.BlenderCreator): { "id": "pyblish.avalon.instance", "creator_identifier": self.identifier, - "label": self.label, + "label": subset_name, "task": get_current_task_name(), "subset": subset_name, "instance_node": instance_node, From cb4dd2559b9d51469870a3962cf69435f7d290b4 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Thu, 26 Oct 2023 10:44:34 +0200 Subject: [PATCH 025/101] wip create blend scene create render --- .../plugins/create/create_blendScene.py | 41 +++++++++++++++---- .../blender/plugins/create/create_render.py | 35 ++++++++++++---- 2 files changed, 59 insertions(+), 17 deletions(-) diff --git a/openpype/hosts/blender/plugins/create/create_blendScene.py b/openpype/hosts/blender/plugins/create/create_blendScene.py index 63bcf212ff..ee8e52d3c5 100644 --- a/openpype/hosts/blender/plugins/create/create_blendScene.py +++ b/openpype/hosts/blender/plugins/create/create_blendScene.py @@ -4,7 +4,10 @@ import bpy from openpype.pipeline import get_current_task_name from openpype.hosts.blender.api import plugin, lib, ops -from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES +from openpype.hosts.blender.api.pipeline import ( + AVALON_INSTANCES, + AVALON_PROPERTY, +) class CreateBlendScene(plugin.Creator): @@ -15,12 +18,18 @@ class CreateBlendScene(plugin.Creator): family = "blendScene" icon = "cubes" - def process(self): + def create( + self, subset_name: str, instance_data: dict, pre_create_data: dict + ): """ Run the creator on Blender main thread""" - mti = ops.MainThreadItem(self._process) + mti = ops.MainThreadItem( + self._process, subset_name, instance_data, pre_create_data + ) ops.execute_in_main_thread(mti) - def _process(self): + def _process( + self, subset_name: str, instance_data: dict, pre_create_data: dict + ): # Get Instance Container or create it if it does not exist instances = bpy.data.collections.get(AVALON_INSTANCES) if not instances: @@ -28,14 +37,28 @@ class CreateBlendScene(plugin.Creator): bpy.context.scene.collection.children.link(instances) # Create instance object - asset = self.data["asset"] - subset = self.data["subset"] - name = plugin.asset_name(asset, subset) + asset = instance_data.get("asset") + name = plugin.asset_name(asset, subset_name) asset_group = bpy.data.objects.new(name=name, object_data=None) asset_group.empty_display_type = 'SINGLE_ARROW' instances.objects.link(asset_group) - self.data['task'] = get_current_task_name() - lib.imprint(asset_group, self.data) + + asset_group[AVALON_PROPERTY] = instance_node = { + "name": asset_group.name + } + + instance_data.update( + { + "id": "publish.avalon.instance", + "creator_identifier": self.identifier, + "label": subset_name, + "task": get_current_task_name(), + "subset": subset_name, + "instance_node": instance_node, + } + ) + + lib.imprint(asset_group, instance_data) # Add selected objects to instance if (self.options or {}).get("useSelection"): diff --git a/openpype/hosts/blender/plugins/create/create_render.py b/openpype/hosts/blender/plugins/create/create_render.py index f938a21808..ab3119b32e 100644 --- a/openpype/hosts/blender/plugins/create/create_render.py +++ b/openpype/hosts/blender/plugins/create/create_render.py @@ -4,10 +4,13 @@ import bpy from openpype.pipeline import get_current_task_name from openpype.hosts.blender.api import plugin, lib from openpype.hosts.blender.api.render_lib import prepare_rendering -from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES +from openpype.hosts.blender.api.pipeline import ( + AVALON_INSTANCES, + AVALON_PROPERTY, +) -class CreateRenderlayer(plugin.Creator): +class CreateRenderlayer(plugin.BlenderCreator): """Single baked camera""" name = "renderingMain" @@ -15,7 +18,9 @@ class CreateRenderlayer(plugin.Creator): family = "render" icon = "eye" - def process(self): + def create( + self, subset_name: str, instance_data: dict, pre_create_data: dict + ): # Get Instance Container or create it if it does not exist instances = bpy.data.collections.get(AVALON_INSTANCES) if not instances: @@ -23,15 +28,29 @@ class CreateRenderlayer(plugin.Creator): bpy.context.scene.collection.children.link(instances) # Create instance object - asset = self.data["asset"] - subset = self.data["subset"] - name = plugin.asset_name(asset, subset) + asset = instance_data.get("asset") + name = plugin.asset_name(asset, subset_name) asset_group = bpy.data.collections.new(name=name) try: instances.children.link(asset_group) - self.data['task'] = get_current_task_name() - lib.imprint(asset_group, self.data) + + asset_group[AVALON_PROPERTY] = instance_node = { + "name": asset_group.name + } + + instance_data.update( + { + "id": "pyblish.avalon.instance", + "creator_identifier": self.identifier, + "label": subset_name, + "task": get_current_task_name(), + "subset": subset_name, + "instance_node": instance_node, + } + ) + + lib.imprint(asset_group, instance_data) prepare_rendering(asset_group) except Exception: From ca2ff805910510a6ecf75e0ae233b8b818665924 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 26 Oct 2023 12:43:13 +0200 Subject: [PATCH 026/101] nuke: updating colorspace defaults --- .../defaults/project_settings/nuke.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index 1cadedd797..20df0ad5c2 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -19,16 +19,16 @@ "rules": {} }, "viewer": { - "viewerProcess": "sRGB" + "viewerProcess": "sRGB (default)" }, "baking": { - "viewerProcess": "rec709" + "viewerProcess": "rec709 (default)" }, "workfile": { - "colorManagement": "Nuke", + "colorManagement": "OCIO", "OCIO_config": "nuke-default", - "workingSpaceLUT": "linear", - "monitorLut": "sRGB" + "workingSpaceLUT": "scene_linear", + "monitorLut": "sRGB (default)" }, "nodes": { "requiredNodes": [ @@ -76,7 +76,7 @@ { "type": "text", "name": "colorspace", - "value": "linear" + "value": "scene_linear" }, { "type": "bool", @@ -129,7 +129,7 @@ { "type": "text", "name": "colorspace", - "value": "linear" + "value": "scene_linear" }, { "type": "bool", @@ -177,7 +177,7 @@ { "type": "text", "name": "colorspace", - "value": "sRGB" + "value": "texture_paint" }, { "type": "bool", @@ -193,7 +193,7 @@ "inputs": [ { "regex": "(beauty).*(?=.exr)", - "colorspace": "linear" + "colorspace": "scene_linear" } ] } From ae2c4bd5548c6ba81e7937320b0b91554ea614cd Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 26 Oct 2023 17:09:34 +0200 Subject: [PATCH 027/101] nuke: aligning server addon settings with openpype --- server_addon/nuke/server/settings/imageio.py | 16 ++++++++-------- server_addon/nuke/server/version.py | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/server_addon/nuke/server/settings/imageio.py b/server_addon/nuke/server/settings/imageio.py index 15ccd4e89a..19ad5ff24a 100644 --- a/server_addon/nuke/server/settings/imageio.py +++ b/server_addon/nuke/server/settings/imageio.py @@ -213,16 +213,16 @@ class ImageIOSettings(BaseSettingsModel): DEFAULT_IMAGEIO_SETTINGS = { "viewer": { - "viewerProcess": "sRGB" + "viewerProcess": "sRGB (default)" }, "baking": { - "viewerProcess": "rec709" + "viewerProcess": "rec709 (default)" }, "workfile": { - "color_management": "Nuke", + "color_management": "OCIO", "native_ocio_config": "nuke-default", - "working_space": "linear", - "thumbnail_space": "sRGB", + "working_space": "scene_linear", + "thumbnail_space": "sRGB (default)", }, "nodes": { "required_nodes": [ @@ -269,7 +269,7 @@ DEFAULT_IMAGEIO_SETTINGS = { { "type": "text", "name": "colorspace", - "text": "linear" + "text": "scene_linear" }, { "type": "boolean", @@ -321,7 +321,7 @@ DEFAULT_IMAGEIO_SETTINGS = { { "type": "text", "name": "colorspace", - "text": "linear" + "text": "scene_linear" }, { "type": "boolean", @@ -368,7 +368,7 @@ DEFAULT_IMAGEIO_SETTINGS = { { "type": "text", "name": "colorspace", - "text": "sRGB" + "text": "texture_paint" }, { "type": "boolean", diff --git a/server_addon/nuke/server/version.py b/server_addon/nuke/server/version.py index bbab0242f6..1276d0254f 100644 --- a/server_addon/nuke/server/version.py +++ b/server_addon/nuke/server/version.py @@ -1 +1 @@ -__version__ = "0.1.4" +__version__ = "0.1.5" From 4f658d2f51cd3357a3d31ba7d607354debabaa19 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Fri, 27 Oct 2023 16:05:01 +0200 Subject: [PATCH 028/101] update blender creator --- openpype/hosts/blender/api/plugin.py | 14 ++++++++++++-- .../blender/plugins/create/create_blendScene.py | 2 +- .../hosts/blender/plugins/create/create_model.py | 2 +- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 73d8fc0ed5..dbd9f25d68 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -9,6 +9,7 @@ from openpype.pipeline import ( Creator, CreatedInstance, LoaderPlugin, + get_current_task_name, ) from .pipeline import ( AVALON_CONTAINERS, @@ -235,11 +236,20 @@ class BlenderCreator(Creator): collection = bpy.data.collections.new(name=subset_name) bpy.context.scene.collection.children.link(collection) - collection["instance_node"] = instance_node = { + collection[AVALON_PROPERTY] = instance_node = { "name": collection.name, } - instance_data["instance_node"] = instance_node + instance_data.update( + { + "id": "pyblish.avalon.instance", + "creator_identifier": self.identifier, + "label": subset_name, + "task": get_current_task_name(), + "subset": subset_name, + "instance_node": instance_node, + } + ) instance = CreatedInstance( self.family, subset_name, instance_data, self diff --git a/openpype/hosts/blender/plugins/create/create_blendScene.py b/openpype/hosts/blender/plugins/create/create_blendScene.py index ee8e52d3c5..23ff991654 100644 --- a/openpype/hosts/blender/plugins/create/create_blendScene.py +++ b/openpype/hosts/blender/plugins/create/create_blendScene.py @@ -21,7 +21,7 @@ class CreateBlendScene(plugin.Creator): def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): - """ Run the creator on Blender main thread""" + """Run the creator on Blender main thread.""" mti = ops.MainThreadItem( self._process, subset_name, instance_data, pre_create_data ) diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index 3c8e9c4900..761d9fca9f 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -22,7 +22,7 @@ class CreateModel(plugin.BlenderCreator): def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): - """ Run the creator on Blender main thread""" + """Run the creator on Blender main thread.""" self._add_instance_to_context( CreatedInstance(self.family, subset_name, instance_data, self) ) From 59bb86dc3337cb340d3ed9bf1c842167f98fa6b0 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Thu, 2 Nov 2023 11:42:39 +0100 Subject: [PATCH 029/101] Rename BlenderCreator into BaseCreator --- openpype/hosts/blender/api/plugin.py | 2 +- openpype/hosts/blender/plugins/create/create_action.py | 2 +- openpype/hosts/blender/plugins/create/create_animation.py | 2 +- openpype/hosts/blender/plugins/create/create_camera.py | 2 +- openpype/hosts/blender/plugins/create/create_layout.py | 2 +- openpype/hosts/blender/plugins/create/create_model.py | 2 +- openpype/hosts/blender/plugins/create/create_pointcache.py | 2 +- openpype/hosts/blender/plugins/create/create_render.py | 2 +- openpype/hosts/blender/plugins/create/create_review.py | 2 +- openpype/hosts/blender/plugins/create/create_rig.py | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index dbd9f25d68..3ddc375670 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -140,7 +140,7 @@ def deselect_all(): bpy.context.view_layer.objects.active = active -class BlenderCreator(Creator): +class BaseCreator(Creator): """Base class for Blender Creator plug-ins.""" defaults = ['Main'] diff --git a/openpype/hosts/blender/plugins/create/create_action.py b/openpype/hosts/blender/plugins/create/create_action.py index e7b689c54e..7d00aa1dcb 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -8,7 +8,7 @@ from openpype.hosts.blender.api import lib from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY -class CreateAction(openpype.hosts.blender.api.plugin.BlenderCreator): +class CreateAction(openpype.hosts.blender.api.plugin.BaseCreator): """Action output for character rigs""" identifier = "io.openpype.creators.blender.action" diff --git a/openpype/hosts/blender/plugins/create/create_animation.py b/openpype/hosts/blender/plugins/create/create_animation.py index 8b4214ceda..6cfd054e74 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -10,7 +10,7 @@ from openpype.hosts.blender.api.pipeline import ( ) -class CreateAnimation(plugin.BlenderCreator): +class CreateAnimation(plugin.BaseCreator): """Animation output for character rigs""" identifier = "io.openpype.creators.blender.animation" diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index 4747e50b2e..5d9682e575 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -10,7 +10,7 @@ from openpype.hosts.blender.api.pipeline import ( ) -class CreateCamera(plugin.BlenderCreator): +class CreateCamera(plugin.BaseCreator): """Polygonal static geometry""" identifier = "io.openpype.creators.blender.camera" diff --git a/openpype/hosts/blender/plugins/create/create_layout.py b/openpype/hosts/blender/plugins/create/create_layout.py index 0c97d57af3..ed47b0632f 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -10,7 +10,7 @@ from openpype.hosts.blender.api.pipeline import ( ) -class CreateLayout(plugin.BlenderCreator): +class CreateLayout(plugin.BaseCreator): """Layout output for character rigs""" identifier = "io.openpype.creators.blender.layout" diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index 761d9fca9f..949fae0f76 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -10,7 +10,7 @@ from openpype.hosts.blender.api.pipeline import ( ) -class CreateModel(plugin.BlenderCreator): +class CreateModel(plugin.BaseCreator): """Polygonal static geometry""" identifier = "io.openpype.creators.blender.model" diff --git a/openpype/hosts/blender/plugins/create/create_pointcache.py b/openpype/hosts/blender/plugins/create/create_pointcache.py index a40bd5af61..2ad12caa9c 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -8,7 +8,7 @@ from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY -class CreatePointcache(plugin.BlenderCreator): +class CreatePointcache(plugin.BaseCreator): """Polygonal static geometry""" identifier = "io.openpype.creators.blender.pointcache" diff --git a/openpype/hosts/blender/plugins/create/create_render.py b/openpype/hosts/blender/plugins/create/create_render.py index ab3119b32e..45570f3491 100644 --- a/openpype/hosts/blender/plugins/create/create_render.py +++ b/openpype/hosts/blender/plugins/create/create_render.py @@ -10,7 +10,7 @@ from openpype.hosts.blender.api.pipeline import ( ) -class CreateRenderlayer(plugin.BlenderCreator): +class CreateRenderlayer(plugin.BaseCreator): """Single baked camera""" name = "renderingMain" diff --git a/openpype/hosts/blender/plugins/create/create_review.py b/openpype/hosts/blender/plugins/create/create_review.py index 8c9a8d5927..e8b893b4c0 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -10,7 +10,7 @@ from openpype.hosts.blender.api.pipeline import ( ) -class CreateReview(plugin.BlenderCreator): +class CreateReview(plugin.BaseCreator): """Single baked camera""" identifier = "io.openpype.creators.blender.review" diff --git a/openpype/hosts/blender/plugins/create/create_rig.py b/openpype/hosts/blender/plugins/create/create_rig.py index 110a9f5c8e..6223e64174 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -10,7 +10,7 @@ from openpype.hosts.blender.api.pipeline import ( ) -class CreateRig(plugin.BlenderCreator): +class CreateRig(plugin.BaseCreator): """Artist-friendly rig with controls to direct motion""" identifier = "io.openpype.creators.blender.rig" From 976ff308e97abed1efd6b87f09c477ad16c8f329 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Thu, 2 Nov 2023 16:18:51 +0100 Subject: [PATCH 030/101] Add Create workfile plugin --- .../blender/plugins/create/create_workfile.py | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 openpype/hosts/blender/plugins/create/create_workfile.py diff --git a/openpype/hosts/blender/plugins/create/create_workfile.py b/openpype/hosts/blender/plugins/create/create_workfile.py new file mode 100644 index 0000000000..d1529f75f6 --- /dev/null +++ b/openpype/hosts/blender/plugins/create/create_workfile.py @@ -0,0 +1,100 @@ +import bpy + +from openpype.pipeline import CreatedInstance, AutoCreator +from openpype.client import get_asset_by_name +from openpype.hosts.blender.api.plugin import BaseCreator +from openpype.hosts.blender.api.lib import imprint +from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY + + +class CreateWorkfile(BaseCreator, AutoCreator): + """Workfile auto-creator.""" + identifier = "io.openpype.creators.blender.workfile" + label = "Workfile" + family = "workfile" + icon = "fa5.file" + + def create(self): + """Create workfile instances.""" + current_instance = next( + ( + instance for instance in self.create_context.instances + if instance.creator_identifier == self.identifier + ), + None, + ) + + project_name = self.project_name + asset_name = self.create_context.get_current_asset_name() + task_name = self.create_context.get_current_task_name() + host_name = self.create_context.host_name + + if not current_instance: + asset_doc = get_asset_by_name(project_name, asset_name) + subset_name = self.get_subset_name( + task_name, task_name, asset_doc, project_name, host_name + ) + data = { + "asset": asset_name, + "task": task_name, + "variant": task_name, + } + data.update( + self.get_dynamic_data( + task_name, + task_name, + asset_doc, + project_name, + host_name, + current_instance, + ) + ) + self.log.info("Auto-creating workfile instance...") + current_instance = CreatedInstance( + self.family, subset_name, data, self + ) + self._add_instance_to_context(current_instance) + elif ( + current_instance["asset"] != asset_name + or current_instance["task"] != task_name + ): + # Update instance context if it's different + asset_doc = get_asset_by_name(project_name, asset_name) + subset_name = self.get_subset_name( + task_name, task_name, asset_doc, project_name, host_name + ) + + current_instance.update( + { + "asset": asset_name, + "task": task_name, + "subset": subset_name, + } + ) + + def collect_instances(self): + """Collect workfile instances.""" + self.cache_subsets(self.collection_shared_data) + cached_subsets = self.collection_shared_data["blender_cached_subsets"] + for node in cached_subsets.get(self.identifier, []): + created_instance = CreatedInstance.from_existing( + self.read_instance_node(node), self + ) + self._add_instance_to_context(created_instance) + + def update_instances(self, update_list): + """Update workfile instances.""" + for created_inst, _changes in update_list: + data = created_inst.data_to_store() + node = data.get("instance_node") + if not node: + task_name = self.create_context.get_current_task_name() + + bpy.context.scene[AVALON_PROPERTY] = node = { + "name": f"workfile{task_name}" + } + + created_inst["instance_node"] = node + data = created_inst.data_to_store() + + imprint(node, data) From 10aea1088a68f49218255e4988febe4bc1f0f462 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Fri, 3 Nov 2023 17:06:44 +0100 Subject: [PATCH 031/101] Add collect workfile to blender host --- .../plugins/publish/collect_workfile.py | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 openpype/hosts/blender/plugins/publish/collect_workfile.py diff --git a/openpype/hosts/blender/plugins/publish/collect_workfile.py b/openpype/hosts/blender/plugins/publish/collect_workfile.py new file mode 100644 index 0000000000..e431405e80 --- /dev/null +++ b/openpype/hosts/blender/plugins/publish/collect_workfile.py @@ -0,0 +1,38 @@ +from pathlib import Path + +import bpy +from pyblish.api import InstancePlugin, CollectorOrder + + +class CollectWorkfile(InstancePlugin): + """Inject workfile data into its instance.""" + + order = CollectorOrder + label = "Collect Workfile" + hosts = ["blender"] + families = ["workfile"] + + def process(self, instance): + """Process collector.""" + + context = instance.context + filepath = Path(context.data["currentFile"]) + ext = filepath.suffix + + instance.data.update( + { + "setMembers": [filepath.as_posix()], + "frameStart": context.data.get("frameStart", 1), + "frameEnd": context.data.get("frameEnd", 1), + "handleStart": context.data.get("handleStart", 1), + "handledEnd": context.data.get("handleEnd", 1), + "representations": [ + { + "name": ext.lstrip("."), + "ext": ext, + "files": filepath.name, # TODO resources + "stagingDir": filepath.parent, + } + ], + } + ) From 93101debd87d1d72cbeec69f2131f093f06c27a7 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Tue, 7 Nov 2023 10:23:55 +0100 Subject: [PATCH 032/101] collect workfile remove unused import and comment --- openpype/hosts/blender/plugins/publish/collect_workfile.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/collect_workfile.py b/openpype/hosts/blender/plugins/publish/collect_workfile.py index e431405e80..01c033084b 100644 --- a/openpype/hosts/blender/plugins/publish/collect_workfile.py +++ b/openpype/hosts/blender/plugins/publish/collect_workfile.py @@ -1,6 +1,5 @@ from pathlib import Path -import bpy from pyblish.api import InstancePlugin, CollectorOrder @@ -30,7 +29,7 @@ class CollectWorkfile(InstancePlugin): { "name": ext.lstrip("."), "ext": ext, - "files": filepath.name, # TODO resources + "files": filepath.name, "stagingDir": filepath.parent, } ], From cf356e7ecd66521f91996024bac4e911a4ce09ac Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Tue, 7 Nov 2023 16:00:50 +0100 Subject: [PATCH 033/101] add create render identifier --- openpype/hosts/blender/plugins/create/create_render.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/blender/plugins/create/create_render.py b/openpype/hosts/blender/plugins/create/create_render.py index 45570f3491..e036ae7df3 100644 --- a/openpype/hosts/blender/plugins/create/create_render.py +++ b/openpype/hosts/blender/plugins/create/create_render.py @@ -13,6 +13,7 @@ from openpype.hosts.blender.api.pipeline import ( class CreateRenderlayer(plugin.BaseCreator): """Single baked camera""" + identifier = "io.openpype.creators.blender.render" name = "renderingMain" label = "Render" family = "render" @@ -28,8 +29,7 @@ class CreateRenderlayer(plugin.BaseCreator): bpy.context.scene.collection.children.link(instances) # Create instance object - asset = instance_data.get("asset") - name = plugin.asset_name(asset, subset_name) + name = plugin.asset_name(instance_data.get("asset"), subset_name) asset_group = bpy.data.collections.new(name=name) try: From 2f23b83481de1d7cad5a5139ff0955091e980da0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 8 Nov 2023 13:38:45 +0100 Subject: [PATCH 034/101] traypublisher: failed validator in editorial not necessary --- .../traypublisher/plugins/publish/validate_frame_ranges.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py b/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py index 09de2d8db2..7a5a3c7fc1 100644 --- a/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py +++ b/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py @@ -30,12 +30,17 @@ class ValidateFrameRange(OptionalPyblishPluginMixin, if not self.is_active(instance.data): return + # Skip the instance if does not have asset entity in database + asset_doc = instance.data.get("assetEntity") + if not asset_doc: + self.log.warning("No asset data found, skipping.") + return + if (self.skip_timelines_check and any(re.search(pattern, instance.data["task"]) for pattern in self.skip_timelines_check)): self.log.info("Skipping for {} task".format(instance.data["task"])) - asset_doc = instance.data["assetEntity"] asset_data = asset_doc["data"] frame_start = asset_data["frameStart"] frame_end = asset_data["frameEnd"] From eda0afc26052f1ac1d5a6088aed71db94e8c3010 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 8 Nov 2023 14:27:47 +0100 Subject: [PATCH 035/101] traypublisher: adding exceptions for editorial instances --- .../traypublisher/plugins/create/create_editorial.py | 2 ++ .../plugins/publish/collect_sequence_frame_data.py | 6 ++++++ .../plugins/publish/validate_frame_ranges.py | 8 ++++---- openpype/plugins/publish/collect_resources_path.py | 6 ++++++ 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index 8640500b18..a2746f115f 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -701,6 +701,8 @@ or updating already created. Publishing will create OTIO file. # parent time properties "trackStartFrame": track_start_frame, "timelineOffset": timeline_offset, + "isEditorial": True, + # creator_attributes "creator_attributes": creator_attributes } diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_sequence_frame_data.py b/openpype/hosts/traypublisher/plugins/publish/collect_sequence_frame_data.py index db70d4fe0a..92cedf6b5b 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_sequence_frame_data.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_sequence_frame_data.py @@ -27,6 +27,12 @@ class CollectSequenceFrameData( if not self.is_active(instance.data): return + # editorial would fail since they might not be in database yet + is_editorial = instance.data.get("isEditorial") + if is_editorial: + self.log.debug("Instance is Editorial. Skipping.") + return + frame_data = self.get_frame_data_from_repre_sequence(instance) if not frame_data: diff --git a/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py b/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py index 7a5a3c7fc1..4977a13374 100644 --- a/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py +++ b/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py @@ -30,10 +30,10 @@ class ValidateFrameRange(OptionalPyblishPluginMixin, if not self.is_active(instance.data): return - # Skip the instance if does not have asset entity in database - asset_doc = instance.data.get("assetEntity") - if not asset_doc: - self.log.warning("No asset data found, skipping.") + # editorial would fail since they might not be in database yet + is_editorial = instance.data.get("isEditorial") + if is_editorial: + self.log.debug("Instance is Editorial. Skipping.") return if (self.skip_timelines_check and diff --git a/openpype/plugins/publish/collect_resources_path.py b/openpype/plugins/publish/collect_resources_path.py index cfb4d63c1b..14c13310df 100644 --- a/openpype/plugins/publish/collect_resources_path.py +++ b/openpype/plugins/publish/collect_resources_path.py @@ -68,6 +68,12 @@ class CollectResourcesPath(pyblish.api.InstancePlugin): ] def process(self, instance): + # editorial would fail since they might not be in database yet + is_editorial = instance.data.get("isEditorial") + if is_editorial: + self.log.debug("Instance is Editorial. Skipping.") + return + anatomy = instance.context.data["anatomy"] template_data = copy.deepcopy(instance.data["anatomyData"]) From 7cd98fe9033c17b4897208ab7c54bda5e7e53c45 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Thu, 9 Nov 2023 15:52:36 +0100 Subject: [PATCH 036/101] Remove deprecated code, use avalon instances in basecreator --- openpype/hosts/blender/api/plugin.py | 25 +++++----- .../blender/plugins/create/create_action.py | 25 ---------- .../plugins/create/create_animation.py | 37 -------------- .../blender/plugins/create/create_camera.py | 49 ------------------- .../blender/plugins/create/create_layout.py | 37 -------------- .../blender/plugins/create/create_model.py | 37 -------------- .../plugins/create/create_pointcache.py | 36 -------------- .../blender/plugins/create/create_review.py | 33 ------------- .../blender/plugins/create/create_rig.py | 37 -------------- 9 files changed, 11 insertions(+), 305 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 3ddc375670..629cb4dac9 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -144,18 +144,6 @@ class BaseCreator(Creator): """Base class for Blender Creator plug-ins.""" defaults = ['Main'] - # Deprecated? - def process(self): - collection = bpy.data.collections.new(name=self.data["subset"]) - bpy.context.scene.collection.children.link(collection) - imprint(collection, self.data) - - if (self.options or {}).get("useSelection"): - for obj in get_selection(): - collection.objects.link(obj) - - return collection - @staticmethod def cache_subsets(shared_data): """Cache instances for Creators shared data. @@ -233,8 +221,17 @@ class BaseCreator(Creator): pre_create_data(dict): Data based on pre creation attributes. Those may affect how creator works. """ - collection = bpy.data.collections.new(name=subset_name) - bpy.context.scene.collection.children.link(collection) + # Get Instance Container or create it if it does not exist + instances = bpy.data.collections.get(AVALON_INSTANCES) + if not instances: + instances = bpy.data.collections.new(name=AVALON_INSTANCES) + bpy.context.scene.collection.children.link(instances) + + # Create instance collection + collection = bpy.data.collections.new( + name=asset_name(instance_data["asset"], subset_name) + ) + instances.children.link(collection) collection[AVALON_PROPERTY] = instance_node = { "name": collection.name, diff --git a/openpype/hosts/blender/plugins/create/create_action.py b/openpype/hosts/blender/plugins/create/create_action.py index 7d00aa1dcb..9267fc0765 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -59,28 +59,3 @@ class CreateAction(openpype.hosts.blender.api.plugin.BaseCreator): collection.objects.link(empty_obj) return collection - - # Deprecated - def process(self): - - asset = self.data["asset"] - subset = self.data["subset"] - name = openpype.hosts.blender.api.plugin.asset_name(asset, subset) - collection = bpy.data.collections.new(name=name) - bpy.context.scene.collection.children.link(collection) - self.data['task'] = get_current_task_name() - lib.imprint(collection, self.data) - - if (self.options or {}).get("useSelection"): - for obj in lib.get_selection(): - if (obj.animation_data is not None - and obj.animation_data.action is not None): - - empty_obj = bpy.data.objects.new(name=name, - object_data=None) - empty_obj.animation_data_create() - empty_obj.animation_data.action = obj.animation_data.action - empty_obj.animation_data.action.name = name - collection.objects.link(empty_obj) - - return collection diff --git a/openpype/hosts/blender/plugins/create/create_animation.py b/openpype/hosts/blender/plugins/create/create_animation.py index 6cfd054e74..89567061b6 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -75,40 +75,3 @@ class CreateAnimation(plugin.BaseCreator): asset_group.objects.link(obj) return asset_group - - # Deprecated - def process(self): - """ Run the creator on Blender main thread""" - mti = ops.MainThreadItem(self._process_legacy) - ops.execute_in_main_thread(mti) - - # Deprecated - def _process_legacy(self): - # Get Instance Container or create it if it does not exist - instances = bpy.data.collections.get(AVALON_INSTANCES) - if not instances: - instances = bpy.data.collections.new(name=AVALON_INSTANCES) - bpy.context.scene.collection.children.link(instances) - - # Create instance object - # name = self.name - # if not name: - asset = self.data["asset"] - subset = self.data["subset"] - name = plugin.asset_name(asset, subset) - # asset_group = bpy.data.objects.new(name=name, object_data=None) - # asset_group.empty_display_type = 'SINGLE_ARROW' - asset_group = bpy.data.collections.new(name=name) - instances.children.link(asset_group) - self.data['task'] = get_current_task_name() - lib.imprint(asset_group, self.data) - - if (self.options or {}).get("useSelection"): - selected = lib.get_selection() - for obj in selected: - asset_group.objects.link(obj) - elif (self.options or {}).get("asset_group"): - obj = (self.options or {}).get("asset_group") - asset_group.objects.link(obj) - - return asset_group diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index 5d9682e575..125514ae01 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -84,52 +84,3 @@ class CreateCamera(plugin.BaseCreator): bpy.ops.object.parent_set(keep_transform=True) return asset_group - - # Deprecated - def process(self): - """ Run the creator on Blender main thread""" - mti = ops.MainThreadItem(self._process_legacy) - ops.execute_in_main_thread(mti) - - # Deprecated - def _process_legacy(self): - # Get Instance Container or create it if it does not exist - instances = bpy.data.collections.get(AVALON_INSTANCES) - if not instances: - instances = bpy.data.collections.new(name=AVALON_INSTANCES) - bpy.context.scene.collection.children.link(instances) - - # Create instance object - asset = self.data["asset"] - subset = self.data["subset"] - name = plugin.asset_name(asset, subset) - - asset_group = bpy.data.objects.new(name=name, object_data=None) - asset_group.empty_display_type = 'SINGLE_ARROW' - instances.objects.link(asset_group) - self.data['task'] = get_current_task_name() - print(f"self.data: {self.data}") - lib.imprint(asset_group, self.data) - - if (self.options or {}).get("useSelection"): - bpy.context.view_layer.objects.active = asset_group - selected = lib.get_selection() - for obj in selected: - if obj.parent in selected: - obj.select_set(False) - continue - selected.append(asset_group) - bpy.ops.object.parent_set(keep_transform=True) - else: - plugin.deselect_all() - camera = bpy.data.cameras.new(subset) - camera_obj = bpy.data.objects.new(subset, camera) - - instances.objects.link(camera_obj) - - camera_obj.select_set(True) - asset_group.select_set(True) - bpy.context.view_layer.objects.active = asset_group - bpy.ops.object.parent_set(keep_transform=True) - - return asset_group diff --git a/openpype/hosts/blender/plugins/create/create_layout.py b/openpype/hosts/blender/plugins/create/create_layout.py index ed47b0632f..a6c7053cb2 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -73,40 +73,3 @@ class CreateLayout(plugin.BaseCreator): bpy.ops.object.parent_set(keep_transform=True) return asset_group - - # Deprecated - def process(self): - """ Run the creator on Blender main thread""" - mti = ops.MainThreadItem(self._process_legacy) - ops.execute_in_main_thread(mti) - - # Deprecated - def _process_legacy(self): - # Get Instance Container or create it if it does not exist - instances = bpy.data.collections.get(AVALON_INSTANCES) - if not instances: - instances = bpy.data.collections.new(name=AVALON_INSTANCES) - bpy.context.scene.collection.children.link(instances) - - # Create instance object - asset = self.data["asset"] - subset = self.data["subset"] - name = plugin.asset_name(asset, subset) - asset_group = bpy.data.objects.new(name=name, object_data=None) - asset_group.empty_display_type = 'SINGLE_ARROW' - instances.objects.link(asset_group) - self.data['task'] = get_current_task_name() - lib.imprint(asset_group, self.data) - - # Add selected objects to instance - if (self.options or {}).get("useSelection"): - bpy.context.view_layer.objects.active = asset_group - selected = lib.get_selection() - for obj in selected: - if obj.parent in selected: - obj.select_set(False) - continue - selected.append(asset_group) - bpy.ops.object.parent_set(keep_transform=True) - - return asset_group diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index 949fae0f76..8beb8025c4 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -73,40 +73,3 @@ class CreateModel(plugin.BaseCreator): bpy.ops.object.parent_set(keep_transform=True) return asset_group - - # Deprecated - def process(self): - """ Run the creator on Blender main thread""" - mti = ops.MainThreadItem(self._process_legacy) - ops.execute_in_main_thread(mti) - - # Deprecated - def _process_legacy(self): - # Get Instance Container or create it if it does not exist - instances = bpy.data.collections.get(AVALON_INSTANCES) - if not instances: - instances = bpy.data.collections.new(name=AVALON_INSTANCES) - bpy.context.scene.collection.children.link(instances) - - # Create instance object - asset = self.data["asset"] - subset = self.data["subset"] - name = plugin.asset_name(asset, subset) - asset_group = bpy.data.objects.new(name=name, object_data=None) - asset_group.empty_display_type = 'SINGLE_ARROW' - instances.objects.link(asset_group) - self.data['task'] = get_current_task_name() - lib.imprint(asset_group, self.data) - - # Add selected objects to instance - if (self.options or {}).get("useSelection"): - bpy.context.view_layer.objects.active = asset_group - selected = lib.get_selection() - for obj in selected: - if obj.parent in selected: - obj.select_set(False) - continue - selected.append(asset_group) - bpy.ops.object.parent_set(keep_transform=True) - - return asset_group diff --git a/openpype/hosts/blender/plugins/create/create_pointcache.py b/openpype/hosts/blender/plugins/create/create_pointcache.py index 2ad12caa9c..aa8b297d16 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -54,39 +54,3 @@ class CreatePointcache(plugin.BaseCreator): objects.extend(obj.children) return collection - - # Deprecated - def process(self): - """ Run the creator on Blender main thread""" - mti = ops.MainThreadItem(self._process) - ops.execute_in_main_thread(mti) - - def _process(self): - # Get Instance Container or create it if it does not exist - instances = bpy.data.collections.get(AVALON_INSTANCES) - if not instances: - instances = bpy.data.collections.new(name=AVALON_INSTANCES) - bpy.context.scene.collection.children.link(instances) - - # Create instance object - asset = self.data["asset"] - subset = self.data["subset"] - name = plugin.asset_name(asset, subset) - asset_group = bpy.data.objects.new(name=name, object_data=None) - asset_group.empty_display_type = 'SINGLE_ARROW' - instances.objects.link(asset_group) - self.data['task'] = get_current_task_name() - lib.imprint(asset_group, self.data) - - # Add selected objects to instance - if (self.options or {}).get("useSelection"): - bpy.context.view_layer.objects.active = asset_group - selected = lib.get_selection() - for obj in selected: - if obj.parent in selected: - obj.select_set(False) - continue - selected.append(asset_group) - bpy.ops.object.parent_set(keep_transform=True) - - return asset_group diff --git a/openpype/hosts/blender/plugins/create/create_review.py b/openpype/hosts/blender/plugins/create/create_review.py index e8b893b4c0..13fa3b621f 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -71,36 +71,3 @@ class CreateReview(plugin.BaseCreator): asset_group.objects.link(obj) return asset_group - - # Deprecated - def process(self): - """ Run the creator on Blender main thread""" - mti = ops.MainThreadItem(self._process_legacy) - ops.execute_in_main_thread(mti) - - # Deprecated - def _process_legacy(self): - # Get Instance Container or create it if it does not exist - instances = bpy.data.collections.get(AVALON_INSTANCES) - if not instances: - instances = bpy.data.collections.new(name=AVALON_INSTANCES) - bpy.context.scene.collection.children.link(instances) - - # Create instance object - asset = self.data["asset"] - subset = self.data["subset"] - name = plugin.asset_name(asset, subset) - asset_group = bpy.data.collections.new(name=name) - instances.children.link(asset_group) - self.data['task'] = get_current_task_name() - lib.imprint(asset_group, self.data) - - if (self.options or {}).get("useSelection"): - selected = lib.get_selection() - for obj in selected: - asset_group.objects.link(obj) - elif (self.options or {}).get("asset_group"): - obj = (self.options or {}).get("asset_group") - asset_group.objects.link(obj) - - return asset_group diff --git a/openpype/hosts/blender/plugins/create/create_rig.py b/openpype/hosts/blender/plugins/create/create_rig.py index 6223e64174..6682162f4b 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -73,40 +73,3 @@ class CreateRig(plugin.BaseCreator): bpy.ops.object.parent_set(keep_transform=True) return asset_group - - # Deprecated - def process(self): - """ Run the creator on Blender main thread""" - mti = ops.MainThreadItem(self._process_legacy) - ops.execute_in_main_thread(mti) - - # Deprecated - def _process_legacy(self): - # Get Instance Container or create it if it does not exist - instances = bpy.data.collections.get(AVALON_INSTANCES) - if not instances: - instances = bpy.data.collections.new(name=AVALON_INSTANCES) - bpy.context.scene.collection.children.link(instances) - - # Create instance object - asset = self.data["asset"] - subset = self.data["subset"] - name = plugin.asset_name(asset, subset) - asset_group = bpy.data.objects.new(name=name, object_data=None) - asset_group.empty_display_type = 'SINGLE_ARROW' - instances.objects.link(asset_group) - self.data['task'] = get_current_task_name() - lib.imprint(asset_group, self.data) - - # Add selected objects to instance - if (self.options or {}).get("useSelection"): - bpy.context.view_layer.objects.active = asset_group - selected = lib.get_selection() - for obj in selected: - if obj.parent in selected: - obj.select_set(False) - continue - selected.append(asset_group) - bpy.ops.object.parent_set(keep_transform=True) - - return asset_group From 01be65d283a531ad8eae43beca9d3637bf7221f2 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Fri, 10 Nov 2023 16:18:28 +0100 Subject: [PATCH 037/101] Reduce redundancy in create action, animation, pointcache, render and review --- openpype/hosts/blender/api/plugin.py | 16 ++++--- .../blender/plugins/create/create_action.py | 34 +++----------- .../plugins/create/create_animation.py | 45 +++---------------- .../plugins/create/create_pointcache.py | 34 ++------------ .../blender/plugins/create/create_render.py | 43 +++--------------- .../blender/plugins/create/create_review.py | 42 +++-------------- 6 files changed, 41 insertions(+), 173 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 629cb4dac9..aef891bd83 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -237,6 +237,7 @@ class BaseCreator(Creator): "name": collection.name, } + # Set instance data instance_data.update( { "id": "pyblish.avalon.instance", @@ -248,16 +249,15 @@ class BaseCreator(Creator): } ) - instance = CreatedInstance( - self.family, subset_name, instance_data, self + self._add_instance_to_context( + CreatedInstance( + self.family, subset_name, instance_data, self + ) ) - self._add_instance_to_context(instance) imprint(collection, instance_data) - if pre_create_data.get("useSelection"): - for obj in get_selection(): - collection.objects.link(obj) + return collection def collect_instances(self): """Override abstract method from BaseCreator. @@ -267,7 +267,9 @@ class BaseCreator(Creator): self.cache_subsets(self.collection_shared_data) # Get cached subsets - cached_subsets = self.collection_shared_data.get('blender_cached_subsets') + cached_subsets = self.collection_shared_data.get( + "blender_cached_subsets" + ) if not cached_subsets: return diff --git a/openpype/hosts/blender/plugins/create/create_action.py b/openpype/hosts/blender/plugins/create/create_action.py index 9267fc0765..95bd42682c 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -2,13 +2,11 @@ import bpy -from openpype.pipeline import get_current_task_name, CreatedInstance -import openpype.hosts.blender.api.plugin +from openpype.hosts.blender.api.plugin import BaseCreator, asset_name from openpype.hosts.blender.api import lib -from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY -class CreateAction(openpype.hosts.blender.api.plugin.BaseCreator): +class CreateAction(BaseCreator): """Action output for character rigs""" identifier = "io.openpype.creators.blender.action" @@ -20,31 +18,13 @@ class CreateAction(openpype.hosts.blender.api.plugin.BaseCreator): def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): - self._add_instance_to_context( - CreatedInstance(self.family, subset_name, instance_data, self) + # Run parent create method + collection = super().create( + subset_name, instance_data, pre_create_data ) - name = openpype.hosts.blender.api.plugin.asset_name( - instance_data["asset"], subset_name - ) - collection = bpy.data.collections.new(name=name) - bpy.context.scene.collection.children.link(collection) - - collection[AVALON_PROPERTY] = instance_node = { - "name": collection.name, - } - - instance_data.update( - { - "id": "pyblish.avalon.instance", - "creator_identifier": self.identifier, - "label": subset_name, - "task": get_current_task_name(), - "subset": subset_name, - "instance_node": instance_node, - } - ) - lib.imprint(collection, instance_data) + # Get instance name + name = asset_name(instance_data["asset"], subset_name) if pre_create_data.get("useSelection"): for obj in lib.get_selection(): diff --git a/openpype/hosts/blender/plugins/create/create_animation.py b/openpype/hosts/blender/plugins/create/create_animation.py index 89567061b6..3c70ae1bd0 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -1,13 +1,8 @@ """Create an animation asset.""" -import bpy -from openpype.pipeline import get_current_task_name, CreatedInstance +from openpype.pipeline import CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops -from openpype.hosts.blender.api.pipeline import ( - AVALON_INSTANCES, - AVALON_PROPERTY, -) class CreateAnimation(plugin.BaseCreator): @@ -35,43 +30,17 @@ class CreateAnimation(plugin.BaseCreator): def _process( self, subset_name: str, instance_data: dict, pre_create_data: dict ): - # Get Instance Container or create it if it does not exist - instances = bpy.data.collections.get(AVALON_INSTANCES) - if not instances: - instances = bpy.data.collections.new(name=AVALON_INSTANCES) - bpy.context.scene.collection.children.link(instances) - - # Create instance object - # name = self.name - # if not name: - name = plugin.asset_name(instance_data["asset"], subset_name) - # asset_group = bpy.data.objects.new(name=name, object_data=None) - # asset_group.empty_display_type = 'SINGLE_ARROW' - asset_group = bpy.data.collections.new(name=name) - instances.children.link(asset_group) - - asset_group[AVALON_PROPERTY] = instance_node = { - "name": asset_group.name, - } - - instance_data.update( - { - "id": "pyblish.avalon.instance", - "creator_identifier": self.identifier, - "label": subset_name, - "task": get_current_task_name(), - "subset": subset_name, - "instance_node": instance_node, - } + # Run parent create method + collection = super().create( + subset_name, instance_data, pre_create_data ) - lib.imprint(asset_group, instance_data) if pre_create_data.get("useSelection"): selected = lib.get_selection() for obj in selected: - asset_group.objects.link(obj) + collection.objects.link(obj) elif pre_create_data.get("asset_group"): obj = (self.options or {}).get("asset_group") - asset_group.objects.link(obj) + collection.objects.link(obj) - return asset_group + return collection diff --git a/openpype/hosts/blender/plugins/create/create_pointcache.py b/openpype/hosts/blender/plugins/create/create_pointcache.py index aa8b297d16..d823193249 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -1,11 +1,6 @@ """Create a pointcache asset.""" -import bpy - -from openpype.pipeline import get_current_task_name, CreatedInstance -from openpype.hosts.blender.api import plugin, lib, ops -from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES -from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY +from openpype.hosts.blender.api import plugin, lib class CreatePointcache(plugin.BaseCreator): @@ -20,32 +15,11 @@ class CreatePointcache(plugin.BaseCreator): def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): - self._add_instance_to_context( - CreatedInstance(self.family, subset_name, instance_data, self) + # Run parent create method + collection = super().create( + subset_name, instance_data, pre_create_data ) - name = plugin.asset_name( - instance_data["asset"], subset_name - ) - collection = bpy.data.collections.new(name=name) - bpy.context.scene.collection.children.link(collection) - - collection[AVALON_PROPERTY] = instance_node = { - "name": collection.name, - } - - instance_data.update( - { - "id": "pyblish.avalon.instance", - "creator_identifier": self.identifier, - "label": subset_name, - "task": get_current_task_name(), - "subset": subset_name, - "instance_node": instance_node, - } - ) - lib.imprint(collection, instance_data) - if pre_create_data.get("useSelection"): objects = lib.get_selection() for obj in objects: diff --git a/openpype/hosts/blender/plugins/create/create_render.py b/openpype/hosts/blender/plugins/create/create_render.py index e036ae7df3..0e5a284caf 100644 --- a/openpype/hosts/blender/plugins/create/create_render.py +++ b/openpype/hosts/blender/plugins/create/create_render.py @@ -1,13 +1,8 @@ """Create render.""" import bpy -from openpype.pipeline import get_current_task_name -from openpype.hosts.blender.api import plugin, lib +from openpype.hosts.blender.api import plugin from openpype.hosts.blender.api.render_lib import prepare_rendering -from openpype.hosts.blender.api.pipeline import ( - AVALON_INSTANCES, - AVALON_PROPERTY, -) class CreateRenderlayer(plugin.BaseCreator): @@ -22,40 +17,16 @@ class CreateRenderlayer(plugin.BaseCreator): def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): - # Get Instance Container or create it if it does not exist - instances = bpy.data.collections.get(AVALON_INSTANCES) - if not instances: - instances = bpy.data.collections.new(name=AVALON_INSTANCES) - bpy.context.scene.collection.children.link(instances) - - # Create instance object - name = plugin.asset_name(instance_data.get("asset"), subset_name) - asset_group = bpy.data.collections.new(name=name) - try: - instances.children.link(asset_group) - - asset_group[AVALON_PROPERTY] = instance_node = { - "name": asset_group.name - } - - instance_data.update( - { - "id": "pyblish.avalon.instance", - "creator_identifier": self.identifier, - "label": subset_name, - "task": get_current_task_name(), - "subset": subset_name, - "instance_node": instance_node, - } + # Run parent create method + collection = super().create( + subset_name, instance_data, pre_create_data ) - lib.imprint(asset_group, instance_data) - - prepare_rendering(asset_group) + prepare_rendering(collection) except Exception: # Remove the instance if there was an error - bpy.data.collections.remove(asset_group) + bpy.data.collections.remove(collection) raise # TODO: this is undesiderable, but it's the only way to be sure that @@ -69,4 +40,4 @@ class CreateRenderlayer(plugin.BaseCreator): # now it is to force the file to be saved. bpy.ops.wm.save_as_mainfile(filepath=bpy.data.filepath) - return asset_group + return collection diff --git a/openpype/hosts/blender/plugins/create/create_review.py b/openpype/hosts/blender/plugins/create/create_review.py index 13fa3b621f..e35e405ee1 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -1,13 +1,7 @@ """Create review.""" -import bpy - -from openpype.pipeline import get_current_task_name, CreatedInstance +from openpype.pipeline import CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops -from openpype.hosts.blender.api.pipeline import ( - AVALON_INSTANCES, - AVALON_PROPERTY, -) class CreateReview(plugin.BaseCreator): @@ -35,39 +29,17 @@ class CreateReview(plugin.BaseCreator): def _process( self, subset_name: str, instance_data: dict, pre_create_data: dict ): - # Get Instance Container or create it if it does not exist - instances = bpy.data.collections.get(AVALON_INSTANCES) - if not instances: - instances = bpy.data.collections.new(name=AVALON_INSTANCES) - bpy.context.scene.collection.children.link(instances) - - # Create instance object - name = plugin.asset_name(instance_data["asset"], subset_name) - asset_group = bpy.data.collections.new(name=name) - instances.children.link(asset_group) - - asset_group[AVALON_PROPERTY] = instance_node = { - "name": asset_group.name, - } - - instance_data.update( - { - "id": "pyblish.avalon.instance", - "creator_identifier": self.identifier, - "label": subset_name, - "task": get_current_task_name(), - "subset": subset_name, - "instance_node": instance_node, - } + # Run parent create method + collection = super().create( + subset_name, instance_data, pre_create_data ) - lib.imprint(asset_group, instance_data) if pre_create_data.get("useSelection"): selected = lib.get_selection() for obj in selected: - asset_group.objects.link(obj) + collection.objects.link(obj) elif pre_create_data.get("asset_group"): obj = (self.options or {}).get("asset_group") - asset_group.objects.link(obj) + collection.objects.link(obj) - return asset_group + return collection From c614522b4c3df1f80b1151e7e7a59dee4108c05f Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Fri, 10 Nov 2023 17:16:55 +0100 Subject: [PATCH 038/101] Fix animation and review duplication in publisher UI --- .../blender/plugins/create/create_action.py | 2 +- .../plugins/create/create_animation.py | 20 +++---------------- .../plugins/create/create_blendScene.py | 2 +- .../blender/plugins/create/create_camera.py | 4 ++-- .../blender/plugins/create/create_layout.py | 4 ++-- .../blender/plugins/create/create_model.py | 2 +- .../plugins/create/create_pointcache.py | 2 +- .../blender/plugins/create/create_render.py | 2 +- .../blender/plugins/create/create_review.py | 19 +++--------------- .../blender/plugins/create/create_rig.py | 4 ++-- 10 files changed, 17 insertions(+), 44 deletions(-) diff --git a/openpype/hosts/blender/plugins/create/create_action.py b/openpype/hosts/blender/plugins/create/create_action.py index 95bd42682c..ac425dff74 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -7,7 +7,7 @@ from openpype.hosts.blender.api import lib class CreateAction(BaseCreator): - """Action output for character rigs""" + """Action output for character rigs.""" identifier = "io.openpype.creators.blender.action" name = "actionMain" diff --git a/openpype/hosts/blender/plugins/create/create_animation.py b/openpype/hosts/blender/plugins/create/create_animation.py index 3c70ae1bd0..f7cb9f88aa 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -1,12 +1,10 @@ """Create an animation asset.""" - -from openpype.pipeline import CreatedInstance -from openpype.hosts.blender.api import plugin, lib, ops +from openpype.hosts.blender.api import plugin, lib class CreateAnimation(plugin.BaseCreator): - """Animation output for character rigs""" + """Animation output for character rigs.""" identifier = "io.openpype.creators.blender.animation" name = "animationMain" @@ -17,19 +15,7 @@ class CreateAnimation(plugin.BaseCreator): def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): - """ Run the creator on Blender main thread""" - self._add_instance_to_context( - CreatedInstance(self.family, subset_name, instance_data, self) - ) - - mti = ops.MainThreadItem( - self._process, subset_name, instance_data, pre_create_data - ) - ops.execute_in_main_thread(mti) - - def _process( - self, subset_name: str, instance_data: dict, pre_create_data: dict - ): + """Run the creator on Blender main thread.""" # Run parent create method collection = super().create( subset_name, instance_data, pre_create_data diff --git a/openpype/hosts/blender/plugins/create/create_blendScene.py b/openpype/hosts/blender/plugins/create/create_blendScene.py index 23ff991654..df3a70f199 100644 --- a/openpype/hosts/blender/plugins/create/create_blendScene.py +++ b/openpype/hosts/blender/plugins/create/create_blendScene.py @@ -11,7 +11,7 @@ from openpype.hosts.blender.api.pipeline import ( class CreateBlendScene(plugin.Creator): - """Generic group of assets""" + """Generic group of assets.""" name = "blendScene" label = "Blender Scene" diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index 125514ae01..a42a9d6ef0 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -11,7 +11,7 @@ from openpype.hosts.blender.api.pipeline import ( class CreateCamera(plugin.BaseCreator): - """Polygonal static geometry""" + """Polygonal static geometry.""" identifier = "io.openpype.creators.blender.camera" name = "cameraMain" @@ -22,7 +22,7 @@ class CreateCamera(plugin.BaseCreator): def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): - """ Run the creator on Blender main thread""" + """Run the creator on Blender main thread.""" self._add_instance_to_context( CreatedInstance(self.family, subset_name, instance_data, self) ) diff --git a/openpype/hosts/blender/plugins/create/create_layout.py b/openpype/hosts/blender/plugins/create/create_layout.py index a6c7053cb2..ef1822dedf 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -11,7 +11,7 @@ from openpype.hosts.blender.api.pipeline import ( class CreateLayout(plugin.BaseCreator): - """Layout output for character rigs""" + """Layout output for character rigs.""" identifier = "io.openpype.creators.blender.layout" name = "layoutMain" @@ -22,7 +22,7 @@ class CreateLayout(plugin.BaseCreator): def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): - """ Run the creator on Blender main thread""" + """Run the creator on Blender main thread.""" self._add_instance_to_context( CreatedInstance(self.family, subset_name, instance_data, self) ) diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index 8beb8025c4..e4937f989e 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -11,7 +11,7 @@ from openpype.hosts.blender.api.pipeline import ( class CreateModel(plugin.BaseCreator): - """Polygonal static geometry""" + """Polygonal static geometry.""" identifier = "io.openpype.creators.blender.model" name = "modelMain" diff --git a/openpype/hosts/blender/plugins/create/create_pointcache.py b/openpype/hosts/blender/plugins/create/create_pointcache.py index d823193249..d05b3adb04 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -4,7 +4,7 @@ from openpype.hosts.blender.api import plugin, lib class CreatePointcache(plugin.BaseCreator): - """Polygonal static geometry""" + """Polygonal static geometry.""" identifier = "io.openpype.creators.blender.pointcache" name = "pointcacheMain" diff --git a/openpype/hosts/blender/plugins/create/create_render.py b/openpype/hosts/blender/plugins/create/create_render.py index 0e5a284caf..bffa5696df 100644 --- a/openpype/hosts/blender/plugins/create/create_render.py +++ b/openpype/hosts/blender/plugins/create/create_render.py @@ -6,7 +6,7 @@ from openpype.hosts.blender.api.render_lib import prepare_rendering class CreateRenderlayer(plugin.BaseCreator): - """Single baked camera""" + """Single baked camera.""" identifier = "io.openpype.creators.blender.render" name = "renderingMain" diff --git a/openpype/hosts/blender/plugins/create/create_review.py b/openpype/hosts/blender/plugins/create/create_review.py index e35e405ee1..91333b7741 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -1,11 +1,10 @@ """Create review.""" -from openpype.pipeline import CreatedInstance -from openpype.hosts.blender.api import plugin, lib, ops +from openpype.hosts.blender.api import plugin, lib class CreateReview(plugin.BaseCreator): - """Single baked camera""" + """Single baked camera.""" identifier = "io.openpype.creators.blender.review" name = "reviewDefault" @@ -16,19 +15,7 @@ class CreateReview(plugin.BaseCreator): def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): - """ Run the creator on Blender main thread""" - self._add_instance_to_context( - CreatedInstance(self.family, subset_name, instance_data, self) - ) - - mti = ops.MainThreadItem( - self._process, subset_name, instance_data, pre_create_data - ) - ops.execute_in_main_thread(mti) - - def _process( - self, subset_name: str, instance_data: dict, pre_create_data: dict - ): + """Run the creator on Blender main thread.""" # Run parent create method collection = super().create( subset_name, instance_data, pre_create_data diff --git a/openpype/hosts/blender/plugins/create/create_rig.py b/openpype/hosts/blender/plugins/create/create_rig.py index 6682162f4b..b54cc73d4a 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -11,7 +11,7 @@ from openpype.hosts.blender.api.pipeline import ( class CreateRig(plugin.BaseCreator): - """Artist-friendly rig with controls to direct motion""" + """Artist-friendly rig with controls to direct motion.""" identifier = "io.openpype.creators.blender.rig" name = "rigMain" @@ -22,7 +22,7 @@ class CreateRig(plugin.BaseCreator): def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): - """ Run the creator on Blender main thread""" + """Run the creator on Blender main thread.""" self._add_instance_to_context( CreatedInstance(self.family, subset_name, instance_data, self) ) From 62fa3304178fc0d9d14bf4f7b5a58e2dee1c5680 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Mon, 13 Nov 2023 11:58:47 +0100 Subject: [PATCH 039/101] Add set_instance_data method to reduce redundancy --- openpype/hosts/blender/api/plugin.py | 41 +++++++++++++------ .../plugins/create/create_blendScene.py | 12 +----- .../blender/plugins/create/create_camera.py | 11 +---- .../blender/plugins/create/create_layout.py | 11 +---- .../blender/plugins/create/create_model.py | 11 +---- .../blender/plugins/create/create_rig.py | 11 +---- 6 files changed, 34 insertions(+), 63 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index aef891bd83..e9683bee3a 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -217,7 +217,7 @@ class BaseCreator(Creator): Args: subset_name(str): Subset name of created instance. - instance_data(dict): Base data for instance. + instance_data(dict): Instance base data. pre_create_data(dict): Data based on pre creation attributes. Those may affect how creator works. """ @@ -237,17 +237,7 @@ class BaseCreator(Creator): "name": collection.name, } - # Set instance data - instance_data.update( - { - "id": "pyblish.avalon.instance", - "creator_identifier": self.identifier, - "label": subset_name, - "task": get_current_task_name(), - "subset": subset_name, - "instance_node": instance_node, - } - ) + self.set_instance_data(subset_name, instance_data, instance_node) self._add_instance_to_context( CreatedInstance( @@ -326,6 +316,33 @@ class BaseCreator(Creator): self._remove_instance_from_context(instance) + def set_instance_data( + self, + subset_name: str, + instance_data: dict, + instance_node: bpy.types.ID, + ): + """Fill instance data with required items. + + Args: + subset_name(str): Subset name of created instance. + instance_data(dict): Instance base data. + instance_node(bpy.types.ID): Instance node in blender scene. + """ + if not instance_data: + instance_data = {} + + instance_data.update( + { + "id": "pyblish.avalon.instance", + "creator_identifier": self.identifier, + "label": subset_name, + "task": get_current_task_name(), + "subset": subset_name, + "instance_node": instance_node, + } + ) + class Loader(LoaderPlugin): """Base class for Loader plug-ins.""" diff --git a/openpype/hosts/blender/plugins/create/create_blendScene.py b/openpype/hosts/blender/plugins/create/create_blendScene.py index df3a70f199..0773c4dae3 100644 --- a/openpype/hosts/blender/plugins/create/create_blendScene.py +++ b/openpype/hosts/blender/plugins/create/create_blendScene.py @@ -47,17 +47,7 @@ class CreateBlendScene(plugin.Creator): "name": asset_group.name } - instance_data.update( - { - "id": "publish.avalon.instance", - "creator_identifier": self.identifier, - "label": subset_name, - "task": get_current_task_name(), - "subset": subset_name, - "instance_node": instance_node, - } - ) - + self.set_instance_data(subset_name, instance_data, instance_node) lib.imprint(asset_group, instance_data) # Add selected objects to instance diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index a42a9d6ef0..8dbba229a9 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -52,16 +52,7 @@ class CreateCamera(plugin.BaseCreator): "name": asset_group.name, } - instance_data.update( - { - "id": "pyblish.avalon.instance", - "creator_identifier": self.identifier, - "label": subset_name, - "task": get_current_task_name(), - "subset": subset_name, - "instance_node": instance_node, - } - ) + self.set_instance_data(subset_name, instance_data, instance_node) lib.imprint(asset_group, instance_data) if pre_create_data.get("useSelection"): diff --git a/openpype/hosts/blender/plugins/create/create_layout.py b/openpype/hosts/blender/plugins/create/create_layout.py index ef1822dedf..a9bf115ea9 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -51,16 +51,7 @@ class CreateLayout(plugin.BaseCreator): "name": asset_group.name, } - instance_data.update( - { - "id": "pyblish.avalon.instance", - "creator_identifier": self.identifier, - "label": subset_name, - "task": get_current_task_name(), - "subset": subset_name, - "instance_node": instance_node, - } - ) + self.set_instance_data(subset_name, instance_data, instance_node) lib.imprint(asset_group, instance_data) # Add selected objects to instance diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index e4937f989e..3501071e8f 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -51,16 +51,7 @@ class CreateModel(plugin.BaseCreator): "name": asset_group.name, } - instance_data.update( - { - "id": "pyblish.avalon.instance", - "creator_identifier": self.identifier, - "label": subset_name, - "task": get_current_task_name(), - "subset": subset_name, - "instance_node": instance_node, - } - ) + self.set_instance_data(subset_name, instance_data, instance_node) lib.imprint(asset_group, instance_data) # Add selected objects to instance diff --git a/openpype/hosts/blender/plugins/create/create_rig.py b/openpype/hosts/blender/plugins/create/create_rig.py index b54cc73d4a..b5b61b3971 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -51,16 +51,7 @@ class CreateRig(plugin.BaseCreator): "name": asset_group.name, } - instance_data.update( - { - "id": "pyblish.avalon.instance", - "creator_identifier": self.identifier, - "label": subset_name, - "task": get_current_task_name(), - "subset": subset_name, - "instance_node": instance_node, - } - ) + self.set_instance_data(subset_name, instance_data, instance_node) lib.imprint(asset_group, instance_data) # Add selected objects to instance From f906d05c7305f123d8e037bd02e3d5742103883f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 13 Nov 2023 16:56:47 +0100 Subject: [PATCH 040/101] fusion: removing hardcoded template name for saver --- openpype/hosts/fusion/plugins/create/create_saver.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/fusion/plugins/create/create_saver.py b/openpype/hosts/fusion/plugins/create/create_saver.py index 2dc48f4b60..e8ba2880a4 100644 --- a/openpype/hosts/fusion/plugins/create/create_saver.py +++ b/openpype/hosts/fusion/plugins/create/create_saver.py @@ -149,9 +149,13 @@ class CreateSaver(NewCreator): # get frame padding from anatomy templates anatomy = Anatomy() - frame_padding = int( - anatomy.templates["render"].get("frame_padding", 4) - ) + render_anatomy_template = anatomy.templates.get("render") + if render_anatomy_template: + frame_padding = int( + render_anatomy_template.get("frame_padding", 4) + ) + else: + frame_padding = int(anatomy.templates.get("frame_padding", 4)) # Subset change detected workdir = os.path.normpath(legacy_io.Session["AVALON_WORKDIR"]) From 8d58b284fc5a6fac5aeb535c3adcfabde4f7ff54 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Mon, 13 Nov 2023 17:21:28 +0100 Subject: [PATCH 041/101] Remove unused dependency, fix collect_render --- openpype/hosts/blender/api/plugin.py | 5 +---- openpype/hosts/blender/plugins/publish/collect_render.py | 4 +++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index e9683bee3a..4bb489dca2 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -20,10 +20,7 @@ from .ops import ( MainThreadItem, execute_in_main_thread ) -from .lib import ( - imprint, - get_selection -) +from .lib import imprint VALID_EXTENSIONS = [".blend", ".json", ".abc", ".fbx"] diff --git a/openpype/hosts/blender/plugins/publish/collect_render.py b/openpype/hosts/blender/plugins/publish/collect_render.py index 92e2473a95..578ac36ed3 100644 --- a/openpype/hosts/blender/plugins/publish/collect_render.py +++ b/openpype/hosts/blender/plugins/publish/collect_render.py @@ -73,7 +73,9 @@ class CollectBlenderRender(pyblish.api.InstancePlugin): def process(self, instance): context = instance.context - render_data = bpy.data.collections[str(instance)].get("render_data") + render_data = bpy.data.collections[ + instance.data["instance_node"]["name"] + ].get("render_data") assert render_data, "No render data found." From 51f1b14fd679f7df16f9ec8c21dad24725f10e43 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 13 Nov 2023 23:20:09 +0100 Subject: [PATCH 042/101] Fix optional states --- .../blender/plugins/publish/extract_abc.py | 3 ++ .../plugins/publish/extract_abc_animation.py | 3 ++ .../blender/plugins/publish/extract_blend.py | 3 ++ .../publish/extract_blend_animation.py | 3 ++ .../plugins/publish/extract_camera_abc.py | 3 ++ .../plugins/publish/extract_camera_fbx.py | 3 ++ .../blender/plugins/publish/extract_fbx.py | 3 ++ .../plugins/publish/extract_fbx_animation.py | 3 ++ .../blender/plugins/publish/extract_layout.py | 3 ++ .../plugins/publish/extract_playblast.py | 3 +- .../publish/increment_workfile_version.py | 2 ++ .../publish/validate_camera_zero_keyframe.py | 3 ++ .../plugins/publish/validate_file_saved.py | 30 +++++++++++++++++-- .../plugins/publish/validate_mesh_has_uv.py | 3 ++ .../validate_mesh_no_negative_scale.py | 11 +++++-- .../publish/validate_no_colons_in_name.py | 11 +++++-- .../plugins/publish/validate_object_mode.py | 3 ++ .../publish/validate_render_camera_is_set.py | 8 ++++- .../publish/validate_transform_zero.py | 11 +++++-- 19 files changed, 102 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/extract_abc.py b/openpype/hosts/blender/plugins/publish/extract_abc.py index 5af8104344..61ff17e441 100644 --- a/openpype/hosts/blender/plugins/publish/extract_abc.py +++ b/openpype/hosts/blender/plugins/publish/extract_abc.py @@ -15,6 +15,9 @@ class ExtractABC(publish.Extractor, publish.OptionalPyblishPluginMixin): families = ["pointcache"] def process(self, instance): + if not self.is_active(instance.data): + return + # Define extract output file path stagingdir = self.staging_dir(instance) filename = f"{instance.name}.abc" diff --git a/openpype/hosts/blender/plugins/publish/extract_abc_animation.py b/openpype/hosts/blender/plugins/publish/extract_abc_animation.py index 0b6b93b7a5..7ec10ed6c4 100644 --- a/openpype/hosts/blender/plugins/publish/extract_abc_animation.py +++ b/openpype/hosts/blender/plugins/publish/extract_abc_animation.py @@ -18,6 +18,9 @@ class ExtractAnimationABC( optional = True def process(self, instance): + if not self.is_active(instance.data): + return + # Define extract output file path stagingdir = self.staging_dir(instance) filename = f"{instance.name}.abc" diff --git a/openpype/hosts/blender/plugins/publish/extract_blend.py b/openpype/hosts/blender/plugins/publish/extract_blend.py index fba9a861a0..37842ed90f 100644 --- a/openpype/hosts/blender/plugins/publish/extract_blend.py +++ b/openpype/hosts/blender/plugins/publish/extract_blend.py @@ -14,6 +14,9 @@ class ExtractBlend(publish.Extractor, publish.OptionalPyblishPluginMixin): optional = True def process(self, instance): + if not self.is_active(instance.data): + return + # Define extract output file path if not self.is_active(instance.data): diff --git a/openpype/hosts/blender/plugins/publish/extract_blend_animation.py b/openpype/hosts/blender/plugins/publish/extract_blend_animation.py index 3a5b788c9e..84e1e7d602 100644 --- a/openpype/hosts/blender/plugins/publish/extract_blend_animation.py +++ b/openpype/hosts/blender/plugins/publish/extract_blend_animation.py @@ -17,6 +17,9 @@ class ExtractBlendAnimation( optional = True def process(self, instance): + if not self.is_active(instance.data): + return + # Define extract output file path stagingdir = self.staging_dir(instance) diff --git a/openpype/hosts/blender/plugins/publish/extract_camera_abc.py b/openpype/hosts/blender/plugins/publish/extract_camera_abc.py index 2a327f4d65..1c0c033364 100644 --- a/openpype/hosts/blender/plugins/publish/extract_camera_abc.py +++ b/openpype/hosts/blender/plugins/publish/extract_camera_abc.py @@ -16,6 +16,9 @@ class ExtractCameraABC(publish.Extractor, publish.OptionalPyblishPluginMixin): optional = True def process(self, instance): + if not self.is_active(instance.data): + return + # Define extract output file path stagingdir = self.staging_dir(instance) filename = f"{instance.name}.abc" diff --git a/openpype/hosts/blender/plugins/publish/extract_camera_fbx.py b/openpype/hosts/blender/plugins/publish/extract_camera_fbx.py index 8e5e4d37d4..8e5cc6f651 100644 --- a/openpype/hosts/blender/plugins/publish/extract_camera_fbx.py +++ b/openpype/hosts/blender/plugins/publish/extract_camera_fbx.py @@ -15,6 +15,9 @@ class ExtractCamera(publish.Extractor, publish.OptionalPyblishPluginMixin): optional = True def process(self, instance): + if not self.is_active(instance.data): + return + # Define extract output file path stagingdir = self.staging_dir(instance) filename = f"{instance.name}.fbx" diff --git a/openpype/hosts/blender/plugins/publish/extract_fbx.py b/openpype/hosts/blender/plugins/publish/extract_fbx.py index 8ace6a43a7..07c35207d8 100644 --- a/openpype/hosts/blender/plugins/publish/extract_fbx.py +++ b/openpype/hosts/blender/plugins/publish/extract_fbx.py @@ -16,6 +16,9 @@ class ExtractFBX(publish.Extractor, publish.OptionalPyblishPluginMixin): optional = True def process(self, instance): + if not self.is_active(instance.data): + return + # Define extract output file path stagingdir = self.staging_dir(instance) filename = f"{instance.name}.fbx" diff --git a/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py b/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py index 04f50f8207..5a8381dbc3 100644 --- a/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py @@ -22,6 +22,9 @@ class ExtractAnimationFBX( optional = True def process(self, instance): + if not self.is_active(instance.data): + return + # Define extract output file path stagingdir = self.staging_dir(instance) diff --git a/openpype/hosts/blender/plugins/publish/extract_layout.py b/openpype/hosts/blender/plugins/publish/extract_layout.py index 8445560bba..576c2c33fc 100644 --- a/openpype/hosts/blender/plugins/publish/extract_layout.py +++ b/openpype/hosts/blender/plugins/publish/extract_layout.py @@ -113,6 +113,9 @@ class ExtractLayout(publish.Extractor, publish.OptionalPyblishPluginMixin): return None, n def process(self, instance): + if not self.is_active(instance.data): + return + # Define extract output file path stagingdir = self.staging_dir(instance) diff --git a/openpype/hosts/blender/plugins/publish/extract_playblast.py b/openpype/hosts/blender/plugins/publish/extract_playblast.py index 82ddbc1fc2..07b51becce 100644 --- a/openpype/hosts/blender/plugins/publish/extract_playblast.py +++ b/openpype/hosts/blender/plugins/publish/extract_playblast.py @@ -24,7 +24,8 @@ class ExtractPlayblast(publish.Extractor, publish.OptionalPyblishPluginMixin): order = pyblish.api.ExtractorOrder + 0.01 def process(self, instance): - self.log.info("Extracting capture..") + if not self.is_active(instance.data): + return self.log.info(instance.data) diff --git a/openpype/hosts/blender/plugins/publish/increment_workfile_version.py b/openpype/hosts/blender/plugins/publish/increment_workfile_version.py index 176668f366..8e522977e4 100644 --- a/openpype/hosts/blender/plugins/publish/increment_workfile_version.py +++ b/openpype/hosts/blender/plugins/publish/increment_workfile_version.py @@ -17,6 +17,8 @@ class IncrementWorkfileVersion( "pointcache", "render"] def process(self, context): + if not self.is_active(context.data): + return assert all(result["success"] for result in context.data["results"]), ( "Publishing not successful so version is not increased.") diff --git a/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py b/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py index 48c267fd18..f109d9c3ce 100644 --- a/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py +++ b/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py @@ -40,6 +40,9 @@ class ValidateCameraZeroKeyframe(pyblish.api.InstancePlugin): return invalid def process(self, instance): + if not self.is_active(instance.data): + return + invalid = self.get_invalid(instance) if invalid: raise RuntimeError( diff --git a/openpype/hosts/blender/plugins/publish/validate_file_saved.py b/openpype/hosts/blender/plugins/publish/validate_file_saved.py index e191585c55..44b33a573a 100644 --- a/openpype/hosts/blender/plugins/publish/validate_file_saved.py +++ b/openpype/hosts/blender/plugins/publish/validate_file_saved.py @@ -2,8 +2,24 @@ import bpy import pyblish.api +from openpype.pipeline.publish import ( + OptionalPyblishPluginMixin, + PublishValidationError +) -class ValidateFileSaved(pyblish.api.InstancePlugin): + +class SaveWorkfileAction(pyblish.api.Action): + """Save Workfile.""" + label = "Save Workfile" + on = "failed" + icon = "save" + + def process(self, context, plugin): + bpy.ops.wm.avalon_workfiles() + + +class ValidateFileSaved(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Validate that the workfile has been saved.""" order = pyblish.api.ValidatorOrder - 0.01 @@ -11,10 +27,20 @@ class ValidateFileSaved(pyblish.api.InstancePlugin): label = "Validate File Saved" optional = False exclude_families = [] + actions = [SaveWorkfileAction] def process(self, instance): + if not self.is_active(instance.data): + return + + if not instance.context.data["currentFile"]: + # File has not been saved at all and has no filename + raise PublishValidationError( + "Current file is empty. Save the file before continuing." + ) + if [ef for ef in self.exclude_families if instance.data["family"] in ef]: return if bpy.data.is_dirty: - raise RuntimeError("Workfile is not saved.") + raise PublishValidationError("Workfile has unsaved changes.") diff --git a/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py b/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py index 687371b362..c2d63685e2 100644 --- a/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py +++ b/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py @@ -55,6 +55,9 @@ class ValidateMeshHasUvs( return invalid def process(self, instance): + if not self.is_active(instance.data): + return + invalid = self.get_invalid(instance) if invalid: raise RuntimeError( diff --git a/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py b/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py index 618feb95c1..737fd80798 100644 --- a/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py +++ b/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py @@ -4,11 +4,15 @@ import bpy import pyblish.api -from openpype.pipeline.publish import ValidateContentsOrder +from openpype.pipeline.publish import ( + ValidateContentsOrder, + OptionalPyblishPluginMixin, +) import openpype.hosts.blender.api.action -class ValidateMeshNoNegativeScale(pyblish.api.Validator): +class ValidateMeshNoNegativeScale(pyblish.api.Validator, + OptionalPyblishPluginMixin): """Ensure that meshes don't have a negative scale.""" order = ValidateContentsOrder @@ -27,6 +31,9 @@ class ValidateMeshNoNegativeScale(pyblish.api.Validator): return invalid def process(self, instance): + if not self.is_active(instance.data): + return + invalid = self.get_invalid(instance) if invalid: raise RuntimeError( diff --git a/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py b/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py index 1a98ec4c1d..fbaf40e91f 100644 --- a/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py +++ b/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py @@ -5,10 +5,14 @@ import bpy import pyblish.api import openpype.hosts.blender.api.action -from openpype.pipeline.publish import ValidateContentsOrder +from openpype.pipeline.publish import ( + ValidateContentsOrder, + OptionalPyblishPluginMixin, +) -class ValidateNoColonsInName(pyblish.api.InstancePlugin): +class ValidateNoColonsInName(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """There cannot be colons in names Object or bone names cannot include colons. Other software do not @@ -36,6 +40,9 @@ class ValidateNoColonsInName(pyblish.api.InstancePlugin): return invalid def process(self, instance): + if not self.is_active(instance.data): + return + invalid = self.get_invalid(instance) if invalid: raise RuntimeError( diff --git a/openpype/hosts/blender/plugins/publish/validate_object_mode.py b/openpype/hosts/blender/plugins/publish/validate_object_mode.py index d8d2e3c8bf..19e8d1bc8d 100644 --- a/openpype/hosts/blender/plugins/publish/validate_object_mode.py +++ b/openpype/hosts/blender/plugins/publish/validate_object_mode.py @@ -29,6 +29,9 @@ class ValidateObjectIsInObjectMode( return invalid def process(self, instance): + if not self.is_active(instance.data): + return + invalid = self.get_invalid(instance) if invalid: raise RuntimeError( diff --git a/openpype/hosts/blender/plugins/publish/validate_render_camera_is_set.py b/openpype/hosts/blender/plugins/publish/validate_render_camera_is_set.py index ba3a796f35..0207b0fd09 100644 --- a/openpype/hosts/blender/plugins/publish/validate_render_camera_is_set.py +++ b/openpype/hosts/blender/plugins/publish/validate_render_camera_is_set.py @@ -2,8 +2,11 @@ import bpy import pyblish.api +from openpype.pipeline.publish import OptionalPyblishPluginMixin -class ValidateRenderCameraIsSet(pyblish.api.InstancePlugin): + +class ValidateRenderCameraIsSet(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Validate that there is a camera set as active for rendering.""" order = pyblish.api.ValidatorOrder @@ -13,5 +16,8 @@ class ValidateRenderCameraIsSet(pyblish.api.InstancePlugin): optional = False def process(self, instance): + if not self.is_active(instance.data): + return + if not bpy.context.scene.camera: raise RuntimeError("No camera is active for rendering.") diff --git a/openpype/hosts/blender/plugins/publish/validate_transform_zero.py b/openpype/hosts/blender/plugins/publish/validate_transform_zero.py index 66ef731e6e..3c68e61277 100644 --- a/openpype/hosts/blender/plugins/publish/validate_transform_zero.py +++ b/openpype/hosts/blender/plugins/publish/validate_transform_zero.py @@ -6,10 +6,14 @@ import bpy import pyblish.api import openpype.hosts.blender.api.action -from openpype.pipeline.publish import ValidateContentsOrder +from openpype.pipeline.publish import ( + ValidateContentsOrder, + OptionalPyblishPluginMixin, +) -class ValidateTransformZero(pyblish.api.InstancePlugin): +class ValidateTransformZero(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Transforms can't have any values To solve this issue, try freezing the transforms. So long @@ -38,6 +42,9 @@ class ValidateTransformZero(pyblish.api.InstancePlugin): return invalid def process(self, instance): + if not self.is_active(instance.data): + return + invalid = self.get_invalid(instance) if invalid: raise RuntimeError( From 7a0fd92481dcbb36e75bb564a1d0f951804f34c0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 13 Nov 2023 23:23:08 +0100 Subject: [PATCH 043/101] Remove legacy instance collecting (since it resulted in duplicate instances) + fix workfile publishing --- .../plugins/publish/collect_current_file.py | 57 --------------- .../plugins/publish/collect_instances.py | 70 ------------------- .../plugins/publish/collect_workfile.py | 2 +- 3 files changed, 1 insertion(+), 128 deletions(-) delete mode 100644 openpype/hosts/blender/plugins/publish/collect_instances.py diff --git a/openpype/hosts/blender/plugins/publish/collect_current_file.py b/openpype/hosts/blender/plugins/publish/collect_current_file.py index c2d8a96a18..91c88f2e28 100644 --- a/openpype/hosts/blender/plugins/publish/collect_current_file.py +++ b/openpype/hosts/blender/plugins/publish/collect_current_file.py @@ -1,72 +1,15 @@ -import os -import bpy - import pyblish.api -from openpype.pipeline import get_current_task_name, get_current_asset_name from openpype.hosts.blender.api import workio -class SaveWorkfiledAction(pyblish.api.Action): - """Save Workfile.""" - label = "Save Workfile" - on = "failed" - icon = "save" - - def process(self, context, plugin): - bpy.ops.wm.avalon_workfiles() - - class CollectBlenderCurrentFile(pyblish.api.ContextPlugin): """Inject the current working file into context""" order = pyblish.api.CollectorOrder - 0.5 label = "Blender Current File" hosts = ["blender"] - actions = [SaveWorkfiledAction] def process(self, context): """Inject the current working file""" current_file = workio.current_file() - context.data["currentFile"] = current_file - - assert current_file, ( - "Current file is empty. Save the file before continuing." - ) - - folder, file = os.path.split(current_file) - filename, ext = os.path.splitext(file) - - task = get_current_task_name() - - data = {} - - # create instance - instance = context.create_instance(name=filename) - subset = "workfile" + task.capitalize() - - data.update({ - "subset": subset, - "asset": get_current_asset_name(), - "label": subset, - "publish": True, - "family": "workfile", - "families": ["workfile"], - "setMembers": [current_file], - "frameStart": bpy.context.scene.frame_start, - "frameEnd": bpy.context.scene.frame_end, - }) - - data["representations"] = [{ - "name": ext.lstrip("."), - "ext": ext.lstrip("."), - "files": file, - "stagingDir": folder, - }] - - instance.data.update(data) - - self.log.info("Collected instance: {}".format(file)) - self.log.info("Scene path: {}".format(current_file)) - self.log.info("staging Dir: {}".format(folder)) - self.log.info("subset: {}".format(subset)) diff --git a/openpype/hosts/blender/plugins/publish/collect_instances.py b/openpype/hosts/blender/plugins/publish/collect_instances.py deleted file mode 100644 index ad2ce54147..0000000000 --- a/openpype/hosts/blender/plugins/publish/collect_instances.py +++ /dev/null @@ -1,70 +0,0 @@ -import json -from typing import Generator - -import bpy - -import pyblish.api -from openpype.hosts.blender.api.pipeline import ( - AVALON_INSTANCES, - AVALON_PROPERTY, -) - - -class CollectInstances(pyblish.api.ContextPlugin): - """Collect the data of a model.""" - - hosts = ["blender"] - label = "Collect Instances" - order = pyblish.api.CollectorOrder - - @staticmethod - def get_asset_groups() -> Generator: - """Return all instances that are empty objects asset groups. - """ - instances = bpy.data.collections.get(AVALON_INSTANCES) - for obj in list(instances.objects) + list(instances.children): - avalon_prop = obj.get(AVALON_PROPERTY) or {} - if avalon_prop.get('id') == 'pyblish.avalon.instance': - yield obj - - @staticmethod - def create_instance(context, group): - avalon_prop = group[AVALON_PROPERTY] - asset = avalon_prop['asset'] - family = avalon_prop['family'] - subset = avalon_prop['subset'] - task = avalon_prop['task'] - name = f"{asset}_{subset}" - return context.create_instance( - name=name, - family=family, - families=[family], - subset=subset, - asset=asset, - task=task, - ) - - def process(self, context): - """Collect the models from the current Blender scene.""" - asset_groups = self.get_asset_groups() - - for group in asset_groups: - instance = self.create_instance(context, group) - members = [] - if isinstance(group, bpy.types.Collection): - members = list(group.objects) - family = instance.data["family"] - if family == "animation": - for obj in group.objects: - if obj.type == 'EMPTY' and obj.get(AVALON_PROPERTY): - members.extend( - child for child in obj.children - if child.type == 'ARMATURE') - else: - members = group.children_recursive - - members.append(group) - instance[:] = members - self.log.debug(json.dumps(instance.data, indent=4)) - for obj in instance: - self.log.debug(obj) diff --git a/openpype/hosts/blender/plugins/publish/collect_workfile.py b/openpype/hosts/blender/plugins/publish/collect_workfile.py index 01c033084b..6561c89605 100644 --- a/openpype/hosts/blender/plugins/publish/collect_workfile.py +++ b/openpype/hosts/blender/plugins/publish/collect_workfile.py @@ -28,7 +28,7 @@ class CollectWorkfile(InstancePlugin): "representations": [ { "name": ext.lstrip("."), - "ext": ext, + "ext": ext.lstrip("."), "files": filepath.name, "stagingDir": filepath.parent, } From 60486c2d9f07775d6dcd41f34fa7434d7120d84a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 13 Nov 2023 23:36:26 +0100 Subject: [PATCH 044/101] Tweak logging levels for artist-facing reports + some log message cosmetics --- openpype/hosts/blender/plugins/publish/collect_render.py | 4 ++-- openpype/hosts/blender/plugins/publish/extract_abc.py | 6 +++--- .../blender/plugins/publish/extract_abc_animation.py | 6 +++--- openpype/hosts/blender/plugins/publish/extract_blend.py | 6 +++--- .../blender/plugins/publish/extract_blend_animation.py | 6 +++--- .../hosts/blender/plugins/publish/extract_camera_abc.py | 6 +++--- .../hosts/blender/plugins/publish/extract_camera_fbx.py | 6 +++--- openpype/hosts/blender/plugins/publish/extract_fbx.py | 6 +++--- .../blender/plugins/publish/extract_fbx_animation.py | 8 ++++---- openpype/hosts/blender/plugins/publish/extract_layout.py | 8 ++++---- .../hosts/blender/plugins/publish/extract_playblast.py | 6 ++---- .../hosts/blender/plugins/publish/extract_thumbnail.py | 6 +++--- .../blender/plugins/publish/increment_workfile_version.py | 2 +- .../hosts/blender/plugins/publish/integrate_animation.py | 2 +- 14 files changed, 38 insertions(+), 40 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/collect_render.py b/openpype/hosts/blender/plugins/publish/collect_render.py index 578ac36ed3..48b03256ae 100644 --- a/openpype/hosts/blender/plugins/publish/collect_render.py +++ b/openpype/hosts/blender/plugins/publish/collect_render.py @@ -79,7 +79,7 @@ class CollectBlenderRender(pyblish.api.InstancePlugin): assert render_data, "No render data found." - self.log.info(f"render_data: {dict(render_data)}") + self.log.debug(f"render_data: {dict(render_data)}") render_product = render_data.get("render_product") aov_file_product = render_data.get("aov_file_product") @@ -122,4 +122,4 @@ class CollectBlenderRender(pyblish.api.InstancePlugin): "renderProducts": colorspace.ARenderProduct(), }) - self.log.info(f"data: {instance.data}") + self.log.debug(f"data: {instance.data}") diff --git a/openpype/hosts/blender/plugins/publish/extract_abc.py b/openpype/hosts/blender/plugins/publish/extract_abc.py index 61ff17e441..1602c4d266 100644 --- a/openpype/hosts/blender/plugins/publish/extract_abc.py +++ b/openpype/hosts/blender/plugins/publish/extract_abc.py @@ -24,7 +24,7 @@ class ExtractABC(publish.Extractor, publish.OptionalPyblishPluginMixin): filepath = os.path.join(stagingdir, filename) # Perform extraction - self.log.info("Performing extraction..") + self.log.debug("Performing extraction..") plugin.deselect_all() @@ -62,8 +62,8 @@ class ExtractABC(publish.Extractor, publish.OptionalPyblishPluginMixin): } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s", - instance.name, representation) + self.log.debug("Extracted instance '%s' to: %s", + instance.name, representation) class ExtractModelABC(ExtractABC): diff --git a/openpype/hosts/blender/plugins/publish/extract_abc_animation.py b/openpype/hosts/blender/plugins/publish/extract_abc_animation.py index 7ec10ed6c4..0ed2819407 100644 --- a/openpype/hosts/blender/plugins/publish/extract_abc_animation.py +++ b/openpype/hosts/blender/plugins/publish/extract_abc_animation.py @@ -27,7 +27,7 @@ class ExtractAnimationABC( filepath = os.path.join(stagingdir, filename) # Perform extraction - self.log.info("Performing extraction..") + self.log.debug("Performing extraction..") plugin.deselect_all() @@ -72,5 +72,5 @@ class ExtractAnimationABC( } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s", - instance.name, representation) + self.log.debug("Extracted instance '%s' to: %s", + instance.name, representation) diff --git a/openpype/hosts/blender/plugins/publish/extract_blend.py b/openpype/hosts/blender/plugins/publish/extract_blend.py index 37842ed90f..884169f7f1 100644 --- a/openpype/hosts/blender/plugins/publish/extract_blend.py +++ b/openpype/hosts/blender/plugins/publish/extract_blend.py @@ -27,7 +27,7 @@ class ExtractBlend(publish.Extractor, publish.OptionalPyblishPluginMixin): filepath = os.path.join(stagingdir, filename) # Perform extraction - self.log.info("Performing extraction..") + self.log.debug("Performing extraction..") data_blocks = set() @@ -58,5 +58,5 @@ class ExtractBlend(publish.Extractor, publish.OptionalPyblishPluginMixin): } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s", - instance.name, representation) + self.log.debug("Extracted instance '%s' to: %s", + instance.name, representation) diff --git a/openpype/hosts/blender/plugins/publish/extract_blend_animation.py b/openpype/hosts/blender/plugins/publish/extract_blend_animation.py index 84e1e7d602..2e0de9317e 100644 --- a/openpype/hosts/blender/plugins/publish/extract_blend_animation.py +++ b/openpype/hosts/blender/plugins/publish/extract_blend_animation.py @@ -27,7 +27,7 @@ class ExtractBlendAnimation( filepath = os.path.join(stagingdir, filename) # Perform extraction - self.log.info("Performing extraction..") + self.log.debug("Performing extraction..") data_blocks = set() @@ -56,5 +56,5 @@ class ExtractBlendAnimation( } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s", - instance.name, representation) + self.log.debug("Extracted instance '%s' to: %s", + instance.name, representation) diff --git a/openpype/hosts/blender/plugins/publish/extract_camera_abc.py b/openpype/hosts/blender/plugins/publish/extract_camera_abc.py index 1c0c033364..9db1505d37 100644 --- a/openpype/hosts/blender/plugins/publish/extract_camera_abc.py +++ b/openpype/hosts/blender/plugins/publish/extract_camera_abc.py @@ -25,7 +25,7 @@ class ExtractCameraABC(publish.Extractor, publish.OptionalPyblishPluginMixin): filepath = os.path.join(stagingdir, filename) # Perform extraction - self.log.info("Performing extraction..") + self.log.debug("Performing extraction..") plugin.deselect_all() @@ -67,5 +67,5 @@ class ExtractCameraABC(publish.Extractor, publish.OptionalPyblishPluginMixin): } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s", - instance.name, representation) + self.log.debug("Extracted instance '%s' to: %s", + instance.name, representation) diff --git a/openpype/hosts/blender/plugins/publish/extract_camera_fbx.py b/openpype/hosts/blender/plugins/publish/extract_camera_fbx.py index 8e5cc6f651..9bbcf047cc 100644 --- a/openpype/hosts/blender/plugins/publish/extract_camera_fbx.py +++ b/openpype/hosts/blender/plugins/publish/extract_camera_fbx.py @@ -24,7 +24,7 @@ class ExtractCamera(publish.Extractor, publish.OptionalPyblishPluginMixin): filepath = os.path.join(stagingdir, filename) # Perform extraction - self.log.info("Performing extraction..") + self.log.debug("Performing extraction..") plugin.deselect_all() @@ -76,5 +76,5 @@ class ExtractCamera(publish.Extractor, publish.OptionalPyblishPluginMixin): } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s", - instance.name, representation) + self.log.debug("Extracted instance '%s' to: %s", + instance.name, representation) diff --git a/openpype/hosts/blender/plugins/publish/extract_fbx.py b/openpype/hosts/blender/plugins/publish/extract_fbx.py index 07c35207d8..fae438158a 100644 --- a/openpype/hosts/blender/plugins/publish/extract_fbx.py +++ b/openpype/hosts/blender/plugins/publish/extract_fbx.py @@ -25,7 +25,7 @@ class ExtractFBX(publish.Extractor, publish.OptionalPyblishPluginMixin): filepath = os.path.join(stagingdir, filename) # Perform extraction - self.log.info("Performing extraction..") + self.log.debug("Performing extraction..") plugin.deselect_all() @@ -87,5 +87,5 @@ class ExtractFBX(publish.Extractor, publish.OptionalPyblishPluginMixin): } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s", - instance.name, representation) + self.log.debug("Extracted instance '%s' to: %s", + instance.name, representation) diff --git a/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py b/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py index 5a8381dbc3..ba584ac99a 100644 --- a/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py @@ -29,7 +29,7 @@ class ExtractAnimationFBX( stagingdir = self.staging_dir(instance) # Perform extraction - self.log.info("Performing extraction..") + self.log.debug("Performing extraction..") # The first collection object in the instance is taken, as there # should be only one that contains the asset group. @@ -62,7 +62,7 @@ class ExtractAnimationFBX( starting_frames.append(curr_frame_range[0]) ending_frames.append(curr_frame_range[1]) else: - self.log.info("Object have no animation.") + self.log.info("Object has no animation.") return asset_group_name = asset_group.name @@ -164,5 +164,5 @@ class ExtractAnimationFBX( instance.data["representations"].append(fbx_representation) instance.data["representations"].append(json_representation) - self.log.info("Extracted instance '{}' to: {}".format( - instance.name, fbx_representation)) + self.log.debug("Extracted instance '{}' to: {}".format( + instance.name, fbx_representation)) diff --git a/openpype/hosts/blender/plugins/publish/extract_layout.py b/openpype/hosts/blender/plugins/publish/extract_layout.py index 576c2c33fc..d0adac6edb 100644 --- a/openpype/hosts/blender/plugins/publish/extract_layout.py +++ b/openpype/hosts/blender/plugins/publish/extract_layout.py @@ -45,7 +45,7 @@ class ExtractLayout(publish.Extractor, publish.OptionalPyblishPluginMixin): starting_frames.append(curr_frame_range[0]) ending_frames.append(curr_frame_range[1]) else: - self.log.info("Object have no animation.") + self.log.info("Object has no animation.") continue asset_group_name = asset.name @@ -120,7 +120,7 @@ class ExtractLayout(publish.Extractor, publish.OptionalPyblishPluginMixin): stagingdir = self.staging_dir(instance) # Perform extraction - self.log.info("Performing extraction..") + self.log.debug("Performing extraction..") if "representations" not in instance.data: instance.data["representations"] = [] @@ -248,5 +248,5 @@ class ExtractLayout(publish.Extractor, publish.OptionalPyblishPluginMixin): } instance.data["representations"].append(fbx_representation) - self.log.info("Extracted instance '%s' to: %s", - instance.name, json_representation) + self.log.debug("Extracted instance '%s' to: %s", + instance.name, json_representation) diff --git a/openpype/hosts/blender/plugins/publish/extract_playblast.py b/openpype/hosts/blender/plugins/publish/extract_playblast.py index 07b51becce..696cf85089 100644 --- a/openpype/hosts/blender/plugins/publish/extract_playblast.py +++ b/openpype/hosts/blender/plugins/publish/extract_playblast.py @@ -27,8 +27,6 @@ class ExtractPlayblast(publish.Extractor, publish.OptionalPyblishPluginMixin): if not self.is_active(instance.data): return - self.log.info(instance.data) - # get scene fps fps = instance.data.get("fps") if fps is None: @@ -56,7 +54,7 @@ class ExtractPlayblast(publish.Extractor, publish.OptionalPyblishPluginMixin): filename = instance.name path = os.path.join(stagingdir, filename) - self.log.info(f"Outputting images to {path}") + self.log.debug(f"Outputting images to {path}") project_settings = instance.context.data["project_settings"]["blender"] presets = project_settings["publish"]["ExtractPlayblast"]["presets"] @@ -101,7 +99,7 @@ class ExtractPlayblast(publish.Extractor, publish.OptionalPyblishPluginMixin): frame_collection = collections[0] - self.log.info(f"We found collection of interest {frame_collection}") + self.log.debug(f"We found collection of interest {frame_collection}") instance.data.setdefault("representations", []) diff --git a/openpype/hosts/blender/plugins/publish/extract_thumbnail.py b/openpype/hosts/blender/plugins/publish/extract_thumbnail.py index 65c3627375..52e5d98fc4 100644 --- a/openpype/hosts/blender/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/blender/plugins/publish/extract_thumbnail.py @@ -24,13 +24,13 @@ class ExtractThumbnail(publish.Extractor): presets = {} def process(self, instance): - self.log.info("Extracting capture..") + self.log.debug("Extracting capture..") stagingdir = self.staging_dir(instance) filename = instance.name path = os.path.join(stagingdir, filename) - self.log.info(f"Outputting images to {path}") + self.log.debug(f"Outputting images to {path}") camera = instance.data.get("review_camera", "AUTO") start = instance.data.get("frameStart", bpy.context.scene.frame_start) @@ -61,7 +61,7 @@ class ExtractThumbnail(publish.Extractor): thumbnail = os.path.basename(self._fix_output_path(path)) - self.log.info(f"thumbnail: {thumbnail}") + self.log.debug(f"thumbnail: {thumbnail}") instance.data.setdefault("representations", []) diff --git a/openpype/hosts/blender/plugins/publish/increment_workfile_version.py b/openpype/hosts/blender/plugins/publish/increment_workfile_version.py index 8e522977e4..7e33fd53fa 100644 --- a/openpype/hosts/blender/plugins/publish/increment_workfile_version.py +++ b/openpype/hosts/blender/plugins/publish/increment_workfile_version.py @@ -29,4 +29,4 @@ class IncrementWorkfileVersion( save_file(filepath, copy=False) - self.log.info('Incrementing script version') + self.log.debug('Incrementing blender workfile version') diff --git a/openpype/hosts/blender/plugins/publish/integrate_animation.py b/openpype/hosts/blender/plugins/publish/integrate_animation.py index b7e5423fa8..623da9c585 100644 --- a/openpype/hosts/blender/plugins/publish/integrate_animation.py +++ b/openpype/hosts/blender/plugins/publish/integrate_animation.py @@ -17,7 +17,7 @@ class IntegrateAnimation( families = ["setdress"] def process(self, instance): - self.log.info("Integrate Animation") + self.log.debug("Integrate Animation") representation = instance.data.get('representations')[0] json_path = representation.get('publishedFiles')[0] From ae6c810ba54091d5157dd6d656589377da7f2446 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 13 Nov 2023 23:38:21 +0100 Subject: [PATCH 045/101] Raise PublishValidationError from validators --- .../plugins/publish/validate_camera_zero_keyframe.py | 7 +++++-- .../blender/plugins/publish/validate_deadline_publish.py | 2 +- .../hosts/blender/plugins/publish/validate_mesh_has_uv.py | 3 ++- .../plugins/publish/validate_mesh_no_negative_scale.py | 3 ++- .../blender/plugins/publish/validate_no_colons_in_name.py | 3 ++- .../hosts/blender/plugins/publish/validate_object_mode.py | 7 +++++-- .../plugins/publish/validate_render_camera_is_set.py | 7 +++++-- .../blender/plugins/publish/validate_transform_zero.py | 3 ++- 8 files changed, 24 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py b/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py index f109d9c3ce..65697cb86d 100644 --- a/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py +++ b/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py @@ -5,7 +5,10 @@ import bpy import pyblish.api import openpype.hosts.blender.api.action -from openpype.pipeline.publish import ValidateContentsOrder +from openpype.pipeline.publish import ( + ValidateContentsOrder, + PublishValidationError +) class ValidateCameraZeroKeyframe(pyblish.api.InstancePlugin): @@ -45,6 +48,6 @@ class ValidateCameraZeroKeyframe(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: - raise RuntimeError( + raise PublishValidationError( f"Camera must have a keyframe at frame 0: {invalid}" ) diff --git a/openpype/hosts/blender/plugins/publish/validate_deadline_publish.py b/openpype/hosts/blender/plugins/publish/validate_deadline_publish.py index 14220b5c9c..58047d7e23 100644 --- a/openpype/hosts/blender/plugins/publish/validate_deadline_publish.py +++ b/openpype/hosts/blender/plugins/publish/validate_deadline_publish.py @@ -36,7 +36,7 @@ class ValidateDeadlinePublish(pyblish.api.InstancePlugin, "Render output folder " "doesn't match the blender scene name! " "Use Repair action to " - "fix the folder file path.." + "fix the folder file path." ) @classmethod diff --git a/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py b/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py index c2d63685e2..dd955dc5da 100644 --- a/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py +++ b/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py @@ -7,6 +7,7 @@ import pyblish.api from openpype.pipeline.publish import ( ValidateContentsOrder, OptionalPyblishPluginMixin, + PublishValidationError ) import openpype.hosts.blender.api.action @@ -60,6 +61,6 @@ class ValidateMeshHasUvs( invalid = self.get_invalid(instance) if invalid: - raise RuntimeError( + raise PublishValidationError( f"Meshes found in instance without valid UV's: {invalid}" ) diff --git a/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py b/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py index 737fd80798..a498a3b4cb 100644 --- a/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py +++ b/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py @@ -7,6 +7,7 @@ import pyblish.api from openpype.pipeline.publish import ( ValidateContentsOrder, OptionalPyblishPluginMixin, + PublishValidationError ) import openpype.hosts.blender.api.action @@ -36,6 +37,6 @@ class ValidateMeshNoNegativeScale(pyblish.api.Validator, invalid = self.get_invalid(instance) if invalid: - raise RuntimeError( + raise PublishValidationError( f"Meshes found in instance with negative scale: {invalid}" ) diff --git a/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py b/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py index fbaf40e91f..17119f8d88 100644 --- a/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py +++ b/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py @@ -8,6 +8,7 @@ import openpype.hosts.blender.api.action from openpype.pipeline.publish import ( ValidateContentsOrder, OptionalPyblishPluginMixin, + PublishValidationError ) @@ -45,6 +46,6 @@ class ValidateNoColonsInName(pyblish.api.InstancePlugin, invalid = self.get_invalid(instance) if invalid: - raise RuntimeError( + raise PublishValidationError( f"Objects found with colon in name: {invalid}" ) diff --git a/openpype/hosts/blender/plugins/publish/validate_object_mode.py b/openpype/hosts/blender/plugins/publish/validate_object_mode.py index 19e8d1bc8d..3b6f29a79e 100644 --- a/openpype/hosts/blender/plugins/publish/validate_object_mode.py +++ b/openpype/hosts/blender/plugins/publish/validate_object_mode.py @@ -3,7 +3,10 @@ from typing import List import bpy import pyblish.api -from openpype.pipeline.publish import OptionalPyblishPluginMixin +from openpype.pipeline.publish import ( + OptionalPyblishPluginMixin, + PublishValidationError +) import openpype.hosts.blender.api.action @@ -34,6 +37,6 @@ class ValidateObjectIsInObjectMode( invalid = self.get_invalid(instance) if invalid: - raise RuntimeError( + raise PublishValidationError( f"Object found in instance is not in Object Mode: {invalid}" ) diff --git a/openpype/hosts/blender/plugins/publish/validate_render_camera_is_set.py b/openpype/hosts/blender/plugins/publish/validate_render_camera_is_set.py index 0207b0fd09..86d1fcc681 100644 --- a/openpype/hosts/blender/plugins/publish/validate_render_camera_is_set.py +++ b/openpype/hosts/blender/plugins/publish/validate_render_camera_is_set.py @@ -2,7 +2,10 @@ import bpy import pyblish.api -from openpype.pipeline.publish import OptionalPyblishPluginMixin +from openpype.pipeline.publish import ( + OptionalPyblishPluginMixin, + PublishValidationError +) class ValidateRenderCameraIsSet(pyblish.api.InstancePlugin, @@ -20,4 +23,4 @@ class ValidateRenderCameraIsSet(pyblish.api.InstancePlugin, return if not bpy.context.scene.camera: - raise RuntimeError("No camera is active for rendering.") + raise PublishValidationError("No camera is active for rendering.") diff --git a/openpype/hosts/blender/plugins/publish/validate_transform_zero.py b/openpype/hosts/blender/plugins/publish/validate_transform_zero.py index 3c68e61277..5270a50888 100644 --- a/openpype/hosts/blender/plugins/publish/validate_transform_zero.py +++ b/openpype/hosts/blender/plugins/publish/validate_transform_zero.py @@ -9,6 +9,7 @@ import openpype.hosts.blender.api.action from openpype.pipeline.publish import ( ValidateContentsOrder, OptionalPyblishPluginMixin, + PublishValidationError ) @@ -47,7 +48,7 @@ class ValidateTransformZero(pyblish.api.InstancePlugin, invalid = self.get_invalid(instance) if invalid: - raise RuntimeError( + raise PublishValidationError( "Object found in instance has not" f" transform to zero: {invalid}" ) From 1e7ef83b188b9c415cb403e702f93c380d0afdce Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 13 Nov 2023 23:53:17 +0100 Subject: [PATCH 046/101] Validate workfile saved once; not per instance - refactored to ContextPlugin --- .../plugins/publish/validate_file_saved.py | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/validate_file_saved.py b/openpype/hosts/blender/plugins/publish/validate_file_saved.py index 44b33a573a..442f856e05 100644 --- a/openpype/hosts/blender/plugins/publish/validate_file_saved.py +++ b/openpype/hosts/blender/plugins/publish/validate_file_saved.py @@ -18,7 +18,7 @@ class SaveWorkfileAction(pyblish.api.Action): bpy.ops.wm.avalon_workfiles() -class ValidateFileSaved(pyblish.api.InstancePlugin, +class ValidateFileSaved(pyblish.api.ContextPlugin, OptionalPyblishPluginMixin): """Validate that the workfile has been saved.""" @@ -29,18 +29,33 @@ class ValidateFileSaved(pyblish.api.InstancePlugin, exclude_families = [] actions = [SaveWorkfileAction] - def process(self, instance): - if not self.is_active(instance.data): + def process(self, context): + if not self.is_active(context.data): return - if not instance.context.data["currentFile"]: + if not context.data["currentFile"]: # File has not been saved at all and has no filename raise PublishValidationError( "Current file is empty. Save the file before continuing." ) - if [ef for ef in self.exclude_families - if instance.data["family"] in ef]: + # Do not validate workfile has unsaved changes if only instances + # present of families that should be excluded + families = { + instance.data["family"] for instance in context + # Consider only enabled instances + if instance.data.get("publish", True) + and instance.data.get("active", True) + } + + def is_excluded(family): + return any(family in exclude_family + for exclude_family in self.exclude_families) + + if all(is_excluded(family) for family in families): + self.log.debug("Only excluded families found, skipping workfile " + "unsaved changes validation..") return + if bpy.data.is_dirty: raise PublishValidationError("Workfile has unsaved changes.") From b8255c6ee3e79d5a9d4466437ed6ec5628edf382 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 13 Nov 2023 23:53:44 +0100 Subject: [PATCH 047/101] Simplify collecting instances (re-use logic) --- openpype/hosts/blender/api/plugin.py | 55 +++++++++++----------------- 1 file changed, 22 insertions(+), 33 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 4bb489dca2..1114d136ce 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -1,5 +1,6 @@ """Shared functionality for pipeline plugins for Blender.""" +import itertools from pathlib import Path from typing import Dict, List, Optional @@ -164,42 +165,30 @@ class BaseCreator(Creator): cache_legacy = {} avalon_instances = bpy.data.collections.get(AVALON_INSTANCES) - if avalon_instances: - for obj in bpy.data.collections.get(AVALON_INSTANCES).objects: - avalon_prop = obj.get(AVALON_PROPERTY, {}) - if avalon_prop.get('id') == 'pyblish.avalon.instance': - creator_id = avalon_prop.get('creator_identifier') + avalon_instance_objs = ( + avalon_instances.objects if avalon_instances else [] + ) - if creator_id: - # Creator instance - cache.setdefault(creator_id, []).append( - avalon_prop - ) - else: - family = avalon_prop.get('family') - if family: - # Legacy creator instance - cache_legacy.setdefault(family, []).append( - avalon_prop - ) + for obj_or_col in itertools.chain( + avalon_instance_objs, + bpy.data.collections + ): + avalon_prop = obj_or_col.get(AVALON_PROPERTY, {}) + if not avalon_prop: + continue - for col in bpy.data.collections: - avalon_prop = col.get(AVALON_PROPERTY, {}) - if avalon_prop.get('id') == 'pyblish.avalon.instance': - creator_id = avalon_prop.get('creator_identifier') + if avalon_prop.get('id') != 'pyblish.avalon.instance': + continue - if creator_id: - # Creator instance - cache.setdefault(creator_id, []).append(avalon_prop) - else: - family = avalon_prop.get('family') - if family: - cache_legacy.setdefault(family, []) - if family: - # Legacy creator instance - cache_legacy.setdefault(family, []).append( - avalon_prop - ) + creator_id = avalon_prop.get('creator_identifier') + if creator_id: + # Creator instance + cache.setdefault(creator_id, []).append(avalon_prop) + else: + family = avalon_prop.get('family') + if family: + # Legacy creator instance + cache_legacy.setdefault(family, []).append(avalon_prop) shared_data["blender_cached_subsets"] = cache shared_data["blender_cached_legacy_subsets"] = cache_legacy From 66981669545046e14551250b14a195132a231887 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 00:24:57 +0100 Subject: [PATCH 048/101] Refactor to correct name --- .../hosts/blender/plugins/publish/validate_instance_empty.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/blender/plugins/publish/validate_instance_empty.py b/openpype/hosts/blender/plugins/publish/validate_instance_empty.py index 3ebc6515d3..7669d76943 100644 --- a/openpype/hosts/blender/plugins/publish/validate_instance_empty.py +++ b/openpype/hosts/blender/plugins/publish/validate_instance_empty.py @@ -13,7 +13,7 @@ class ValidateInstanceEmpty(pyblish.api.InstancePlugin): optional = False def process(self, instance): - asset_group = instance.data["instance_group"] + asset_group = instance.data["instance_node"] if isinstance(asset_group, bpy.types.Collection): if not (asset_group.objects or asset_group.children): From 871bf194da2e7743363697032a984c41a2e54fc4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 00:36:33 +0100 Subject: [PATCH 049/101] Avoid double space in log message --- openpype/plugins/publish/collect_comment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/collect_comment.py b/openpype/plugins/publish/collect_comment.py index 9f41e37f22..38d61a7071 100644 --- a/openpype/plugins/publish/collect_comment.py +++ b/openpype/plugins/publish/collect_comment.py @@ -103,10 +103,10 @@ class CollectComment( instance.data["comment"] = instance_comment if instance_comment: - msg_end = " has comment set to: \"{}\"".format( + msg_end = "has comment set to: \"{}\"".format( instance_comment) else: - msg_end = " does not have set comment" + msg_end = "does not have set comment" self.log.debug("Instance {} {}".format(instance_label, msg_end)) def cleanup_comment(self, comment): From 3d10bfdf0916d9dd74db3ad5e1edf655fdbd3478 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 00:37:13 +0100 Subject: [PATCH 050/101] Cosmetics --- openpype/hosts/blender/api/plugin.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index e126511fd6..0399bf2e50 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -256,19 +256,16 @@ class BaseCreator(Creator): if not cached_subsets: return + # Process only instances that were created by this creator for instance_data in cached_subsets.get(self.identifier, []): - # Process only instances that were created by this creator - data = instance_data.to_dict() - creator_id = data.get('creator_identifier') + # Create instance object from existing data + instance = CreatedInstance.from_existing( + instance_data=instance_data.to_dict(), + creator=self + ) - if creator_id == self.identifier: - # Create instance object from existing data - instance = CreatedInstance.from_existing( - data, self - ) - - # Add instance to create context - self._add_instance_to_context(instance) + # Add instance to create context + self._add_instance_to_context(instance) def update_instances(self, update_list): """Override abstract method from BaseCreator. From 8575c4843caf7074dbdc5376076d4ee805e2fb7b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 02:25:42 +0100 Subject: [PATCH 051/101] Fix storing instance and context data correctly, fix removing instances + refactor instance nodes --- openpype/hosts/blender/api/ops.py | 8 ++ openpype/hosts/blender/api/pipeline.py | 7 +- openpype/hosts/blender/api/plugin.py | 89 ++++++++++--------- .../blender/plugins/create/create_action.py | 9 +- .../plugins/create/create_animation.py | 2 +- .../plugins/create/create_blendScene.py | 39 ++------ .../blender/plugins/create/create_camera.py | 41 ++------- .../blender/plugins/create/create_layout.py | 37 ++------ .../blender/plugins/create/create_model.py | 40 ++------- .../plugins/create/create_pointcache.py | 2 +- .../blender/plugins/create/create_review.py | 3 +- .../blender/plugins/create/create_rig.py | 39 ++------ .../blender/plugins/create/create_workfile.py | 52 ++++++----- .../hosts/blender/plugins/load/load_blend.py | 1 + .../blender/plugins/load/load_layout_json.py | 1 + .../blender/plugins/publish/collect_render.py | 5 +- .../blender/plugins/publish/collect_review.py | 4 +- .../publish/validate_instance_empty.py | 2 +- 18 files changed, 140 insertions(+), 241 deletions(-) diff --git a/openpype/hosts/blender/api/ops.py b/openpype/hosts/blender/api/ops.py index 22c590d4bd..c617144b6f 100644 --- a/openpype/hosts/blender/api/ops.py +++ b/openpype/hosts/blender/api/ops.py @@ -31,6 +31,14 @@ PREVIEW_COLLECTIONS: Dict = dict() TIMER_INTERVAL: float = 0.01 if platform.system() == "Windows" else 0.1 +def execute_function_in_main_thread(f): + """Decorator to move a function call into main thread items""" + def wrapper(*args, **kwargs): + mti = MainThreadItem(f, *args, **kwargs) + execute_in_main_thread(mti) + return wrapper + + class BlenderApplication(QtWidgets.QApplication): _instance = None blender_windows = {} diff --git a/openpype/hosts/blender/api/pipeline.py b/openpype/hosts/blender/api/pipeline.py index c3f8f06694..9ac59f5620 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -138,7 +138,10 @@ class BlenderHost(HostBase, IWorkfileHost, IPublishHost): Returns: dict: Context data stored using 'update_context_data'. """ - return bpy.context.scene.openpype_context + property = bpy.context.scene.get(AVALON_PROPERTY) + if property: + return property.to_dict() + return {} def update_context_data(self, data: dict, changes: dict): """Override abstract method from IPublishHost. @@ -149,7 +152,7 @@ class BlenderHost(HostBase, IWorkfileHost, IPublishHost): changes (dict): Only data that has been changed. Each value has tuple with '(, )' value. """ - bpy.context.scene.openpype_context.update(data) + bpy.context.scene[AVALON_PROPERTY] = data def pype_excepthook_handler(*args): diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 0399bf2e50..d1cefe0a62 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -12,6 +12,8 @@ from openpype.pipeline import ( LoaderPlugin, get_current_task_name, ) +from openpype.lib import BoolDef + from .pipeline import ( AVALON_CONTAINERS, AVALON_INSTANCES, @@ -149,6 +151,8 @@ class BaseCreator(Creator): """Base class for Blender Creator plug-ins.""" defaults = ['Main'] + create_as_asset_group = False + @staticmethod def cache_subsets(shared_data): """Cache instances for Creators shared data. @@ -190,12 +194,12 @@ class BaseCreator(Creator): creator_id = avalon_prop.get('creator_identifier') if creator_id: # Creator instance - cache.setdefault(creator_id, []).append(avalon_prop) + cache.setdefault(creator_id, []).append(obj_or_col) else: family = avalon_prop.get('family') if family: # Legacy creator instance - cache_legacy.setdefault(family, []).append(avalon_prop) + cache_legacy.setdefault(family, []).append(obj_or_col) shared_data["blender_cached_subsets"] = cache shared_data["blender_cached_legacy_subsets"] = cache_legacy @@ -220,27 +224,29 @@ class BaseCreator(Creator): instances = bpy.data.collections.new(name=AVALON_INSTANCES) bpy.context.scene.collection.children.link(instances) - # Create instance collection - collection = bpy.data.collections.new( - name=asset_name(instance_data["asset"], subset_name) + # Create asset group + name = asset_name(instance_data["asset"], subset_name) + if self.create_as_asset_group: + # Create instance as empty + instance_node = bpy.data.objects.new(name=name, object_data=None) + instance_node.empty_display_type = 'SINGLE_ARROW' + instances.objects.link(instance_node) + else: + # Create instance collection + instance_node = bpy.data.collections.new(name=name) + instances.children.link(instance_node) + + self.set_instance_data(subset_name, instance_data) + + instance = CreatedInstance( + self.family, subset_name, instance_data, self ) - instances.children.link(collection) + instance.transient_data["instance_node"] = instance_node + self._add_instance_to_context(instance) - collection[AVALON_PROPERTY] = instance_node = { - "name": collection.name, - } + imprint(instance_node, instance_data) - self.set_instance_data(subset_name, instance_data, instance_node) - - self._add_instance_to_context( - CreatedInstance( - self.family, subset_name, instance_data, self - ) - ) - - imprint(collection, instance_data) - - return collection + return instance_node def collect_instances(self): """Override abstract method from BaseCreator. @@ -257,12 +263,14 @@ class BaseCreator(Creator): return # Process only instances that were created by this creator - for instance_data in cached_subsets.get(self.identifier, []): + for instance_node in cached_subsets.get(self.identifier, []): + property = instance_node.get(AVALON_PROPERTY) # Create instance object from existing data instance = CreatedInstance.from_existing( - instance_data=instance_data.to_dict(), + instance_data=property.to_dict(), creator=self ) + instance.transient_data["instance_node"] = instance_node # Add instance to create context self._add_instance_to_context(instance) @@ -276,41 +284,32 @@ class BaseCreator(Creator): and their changes, as a list of tuples.""" for created_instance, _changes in update_list: data = created_instance.data_to_store() - - imprint(data.get("instance_node", {}), data) + node = created_instance.transient_data["instance_node"] + if node: + imprint(node, data) def remove_instances(self, instances: List[CreatedInstance]): - """Override abstract method from BaseCreator. - Method called when instances are removed. - Args: - instance(List[CreatedInstance]): Instance objects to remove. - """ for instance in instances: - outliner_entity = instance.data.get("instance_node", {}).get( - "datablock" - ) - if not outliner_entity: - continue + node = instance.transient_data["instance_node"] - if isinstance(outliner_entity, bpy.types.Collection): - for children in outliner_entity.children_recursive: + if isinstance(node, bpy.types.Collection): + for children in node.children_recursive: if isinstance(children, bpy.types.Collection): bpy.data.collections.remove(children) else: bpy.data.objects.remove(children) - bpy.data.collections.remove(outliner_entity) - elif isinstance(outliner_entity, bpy.types.Object): - bpy.data.objects.remove(outliner_entity) + bpy.data.collections.remove(node) + elif isinstance(node, bpy.types.Object): + bpy.data.objects.remove(node) self._remove_instance_from_context(instance) def set_instance_data( self, subset_name: str, - instance_data: dict, - instance_node: bpy.types.ID, + instance_data: dict ): """Fill instance data with required items. @@ -329,10 +328,16 @@ class BaseCreator(Creator): "label": subset_name, "task": get_current_task_name(), "subset": subset_name, - "instance_node": instance_node, } ) + def get_pre_create_attr_defs(self): + return [ + BoolDef("use_selection", + label="Use selection", + default=True) + ] + class Loader(LoaderPlugin): """Base class for Loader plug-ins.""" diff --git a/openpype/hosts/blender/plugins/create/create_action.py b/openpype/hosts/blender/plugins/create/create_action.py index ac425dff74..f14023639f 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -2,11 +2,10 @@ import bpy -from openpype.hosts.blender.api.plugin import BaseCreator, asset_name -from openpype.hosts.blender.api import lib +from openpype.hosts.blender.api import lib, plugin -class CreateAction(BaseCreator): +class CreateAction(plugin.BaseCreator): """Action output for character rigs.""" identifier = "io.openpype.creators.blender.action" @@ -24,9 +23,9 @@ class CreateAction(BaseCreator): ) # Get instance name - name = asset_name(instance_data["asset"], subset_name) + name = plugin.asset_name(instance_data["asset"], subset_name) - if pre_create_data.get("useSelection"): + if pre_create_data.get("use_selection"): for obj in lib.get_selection(): if (obj.animation_data is not None and obj.animation_data.action is not None): diff --git a/openpype/hosts/blender/plugins/create/create_animation.py b/openpype/hosts/blender/plugins/create/create_animation.py index f7cb9f88aa..6f0cabb259 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -21,7 +21,7 @@ class CreateAnimation(plugin.BaseCreator): subset_name, instance_data, pre_create_data ) - if pre_create_data.get("useSelection"): + if pre_create_data.get("use_selection"): selected = lib.get_selection() for obj in selected: collection.objects.link(obj) diff --git a/openpype/hosts/blender/plugins/create/create_blendScene.py b/openpype/hosts/blender/plugins/create/create_blendScene.py index 1dc8f44a63..f1090ae397 100644 --- a/openpype/hosts/blender/plugins/create/create_blendScene.py +++ b/openpype/hosts/blender/plugins/create/create_blendScene.py @@ -23,42 +23,17 @@ class CreateBlendScene(plugin.Creator): def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): - """Run the creator on Blender main thread.""" - mti = ops.MainThreadItem( - self._process, subset_name, instance_data, pre_create_data - ) - ops.execute_in_main_thread(mti) - def _process( - self, subset_name: str, instance_data: dict, pre_create_data: dict - ): - # Get Instance Container or create it if it does not exist - instances = bpy.data.collections.get(AVALON_INSTANCES) - if not instances: - instances = bpy.data.collections.new(name=AVALON_INSTANCES) - bpy.context.scene.collection.children.link(instances) + instance_node = super().create(subset_name, + instance_data, + pre_create_data) - # Create instance object - asset = instance_data.get("asset") - name = plugin.asset_name(asset, subset_name) - - # Create the new asset group as collection - asset_group = bpy.data.collections.new(name=name) - instances.children.link(asset_group) - asset_group[AVALON_PROPERTY] = instance_node = { - "name": asset_group.name - } - - self.set_instance_data(subset_name, instance_data, instance_node) - lib.imprint(asset_group, instance_data) - - if (self.options or {}).get("useSelection"): + if pre_create_data.get("use_selection"): selection = lib.get_selection(include_collections=True) - for data in selection: if isinstance(data, bpy.types.Collection): - asset_group.children.link(data) + instance_node.children.link(data) elif isinstance(data, bpy.types.Object): - asset_group.objects.link(data) + instance_node.objects.link(data) - return asset_group + return instance_node diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index 8dbba229a9..d30c60ba21 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -2,7 +2,7 @@ import bpy -from openpype.pipeline import get_current_task_name, CreatedInstance +from openpype.pipeline import CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops from openpype.hosts.blender.api.pipeline import ( AVALON_INSTANCES, @@ -19,43 +19,19 @@ class CreateCamera(plugin.BaseCreator): family = "camera" icon = "video-camera" + create_as_asset_group = True + + @ops.execute_function_in_main_thread def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): """Run the creator on Blender main thread.""" - self._add_instance_to_context( - CreatedInstance(self.family, subset_name, instance_data, self) - ) - mti = ops.MainThreadItem( - self._process, subset_name, instance_data, pre_create_data - ) - ops.execute_in_main_thread(mti) + asset_group = super().create(subset_name, + instance_data, + pre_create_data) - def _process( - self, subset_name: str, instance_data: dict, pre_create_data: dict - ): - # Get Instance Container or create it if it does not exist - instances = bpy.data.collections.get(AVALON_INSTANCES) - if not instances: - instances = bpy.data.collections.new(name=AVALON_INSTANCES) - bpy.context.scene.collection.children.link(instances) - - # Create instance object - name = plugin.asset_name(instance_data["asset"], subset_name) - - asset_group = bpy.data.objects.new(name=name, object_data=None) - asset_group.empty_display_type = 'SINGLE_ARROW' - instances.objects.link(asset_group) - - asset_group[AVALON_PROPERTY] = instance_node = { - "name": asset_group.name, - } - - self.set_instance_data(subset_name, instance_data, instance_node) - lib.imprint(asset_group, instance_data) - - if pre_create_data.get("useSelection"): + if pre_create_data.get("use_selection"): bpy.context.view_layer.objects.active = asset_group selected = lib.get_selection() for obj in selected: @@ -67,6 +43,7 @@ class CreateCamera(plugin.BaseCreator): camera = bpy.data.cameras.new(subset_name) camera_obj = bpy.data.objects.new(subset_name, camera) + instances = bpy.data.collections.get(AVALON_INSTANCES) instances.objects.link(camera_obj) camera_obj.select_set(True) diff --git a/openpype/hosts/blender/plugins/create/create_layout.py b/openpype/hosts/blender/plugins/create/create_layout.py index a9bf115ea9..f569a81c29 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -19,43 +19,18 @@ class CreateLayout(plugin.BaseCreator): family = "layout" icon = "cubes" + create_as_asset_group = True + def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): - """Run the creator on Blender main thread.""" - self._add_instance_to_context( - CreatedInstance(self.family, subset_name, instance_data, self) - ) - mti = ops.MainThreadItem( - self._process, subset_name, instance_data, pre_create_data - ) - ops.execute_in_main_thread(mti) - - def _process( - self, subset_name: str, instance_data: dict, pre_create_data: dict - ): - # Get Instance Container or create it if it does not exist - instances = bpy.data.collections.get(AVALON_INSTANCES) - if not instances: - instances = bpy.data.collections.new(name=AVALON_INSTANCES) - bpy.context.scene.collection.children.link(instances) - - # Create instance object - name = plugin.asset_name(instance_data["asset"], subset_name) - asset_group = bpy.data.objects.new(name=name, object_data=None) - asset_group.empty_display_type = 'SINGLE_ARROW' - instances.objects.link(asset_group) - - asset_group[AVALON_PROPERTY] = instance_node = { - "name": asset_group.name, - } - - self.set_instance_data(subset_name, instance_data, instance_node) - lib.imprint(asset_group, instance_data) + asset_group = super().create(subset_name, + instance_data, + pre_create_data) # Add selected objects to instance - if pre_create_data.get("useSelection"): + if pre_create_data.get("use_selection"): bpy.context.view_layer.objects.active = asset_group selected = lib.get_selection() for obj in selected: diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index 3501071e8f..9774d4bec3 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -19,48 +19,24 @@ class CreateModel(plugin.BaseCreator): family = "model" icon = "cube" + create_as_asset_group = True + + @ops.execute_function_in_main_thread def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): - """Run the creator on Blender main thread.""" - self._add_instance_to_context( - CreatedInstance(self.family, subset_name, instance_data, self) - ) - - mti = ops.MainThreadItem( - self._process, subset_name, instance_data, pre_create_data - ) - ops.execute_in_main_thread(mti) - - def _process( - self, subset_name: str, instance_data: dict, pre_create_data: dict - ): - # Get Instance Container or create it if it does not exist - instances = bpy.data.collections.get(AVALON_INSTANCES) - if not instances: - instances = bpy.data.collections.new(name=AVALON_INSTANCES) - bpy.context.scene.collection.children.link(instances) - - # Create instance object - name = plugin.asset_name(instance_data["asset"], subset_name) - asset_group = bpy.data.objects.new(name=name, object_data=None) - asset_group.empty_display_type = 'SINGLE_ARROW' - instances.objects.link(asset_group) - - asset_group[AVALON_PROPERTY] = instance_node = { - "name": asset_group.name, - } - - self.set_instance_data(subset_name, instance_data, instance_node) - lib.imprint(asset_group, instance_data) + asset_group = super().create(subset_name, + instance_data, + pre_create_data) # Add selected objects to instance - if pre_create_data.get("useSelection"): + if pre_create_data.get("use_selection"): bpy.context.view_layer.objects.active = asset_group selected = lib.get_selection() for obj in selected: obj.select_set(True) selected.append(asset_group) + bpy.ops.object.parent_set(keep_transform=True) return asset_group diff --git a/openpype/hosts/blender/plugins/create/create_pointcache.py b/openpype/hosts/blender/plugins/create/create_pointcache.py index d05b3adb04..d80a1b07f0 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -20,7 +20,7 @@ class CreatePointcache(plugin.BaseCreator): subset_name, instance_data, pre_create_data ) - if pre_create_data.get("useSelection"): + if pre_create_data.get("use_selection"): objects = lib.get_selection() for obj in objects: collection.objects.link(obj) diff --git a/openpype/hosts/blender/plugins/create/create_review.py b/openpype/hosts/blender/plugins/create/create_review.py index 91333b7741..2d521c4255 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -21,11 +21,12 @@ class CreateReview(plugin.BaseCreator): subset_name, instance_data, pre_create_data ) - if pre_create_data.get("useSelection"): + if pre_create_data.get("use_selection"): selected = lib.get_selection() for obj in selected: collection.objects.link(obj) elif pre_create_data.get("asset_group"): + # TODO: What is the intended behavior for this? obj = (self.options or {}).get("asset_group") collection.objects.link(obj) diff --git a/openpype/hosts/blender/plugins/create/create_rig.py b/openpype/hosts/blender/plugins/create/create_rig.py index b5b61b3971..87f0124164 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -19,43 +19,18 @@ class CreateRig(plugin.BaseCreator): family = "rig" icon = "wheelchair" + create_as_asset_group = True + + @ops.execute_function_in_main_thread def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): - """Run the creator on Blender main thread.""" - self._add_instance_to_context( - CreatedInstance(self.family, subset_name, instance_data, self) - ) - - mti = ops.MainThreadItem( - self._process, subset_name, instance_data, pre_create_data - ) - ops.execute_in_main_thread(mti) - - def _process( - self, subset_name: str, instance_data: dict, pre_create_data: dict - ): - # Get Instance Container or create it if it does not exist - instances = bpy.data.collections.get(AVALON_INSTANCES) - if not instances: - instances = bpy.data.collections.new(name=AVALON_INSTANCES) - bpy.context.scene.collection.children.link(instances) - - # Create instance object - name = plugin.asset_name(instance_data["asset"], subset_name) - asset_group = bpy.data.objects.new(name=name, object_data=None) - asset_group.empty_display_type = 'SINGLE_ARROW' - instances.objects.link(asset_group) - - asset_group[AVALON_PROPERTY] = instance_node = { - "name": asset_group.name, - } - - self.set_instance_data(subset_name, instance_data, instance_node) - lib.imprint(asset_group, instance_data) + asset_group = super().create(subset_name, + instance_data, + pre_create_data) # Add selected objects to instance - if pre_create_data.get("useSelection"): + if pre_create_data.get("use_selection"): bpy.context.view_layer.objects.active = asset_group selected = lib.get_selection() for obj in selected: diff --git a/openpype/hosts/blender/plugins/create/create_workfile.py b/openpype/hosts/blender/plugins/create/create_workfile.py index d1529f75f6..8f5adb5583 100644 --- a/openpype/hosts/blender/plugins/create/create_workfile.py +++ b/openpype/hosts/blender/plugins/create/create_workfile.py @@ -3,8 +3,10 @@ import bpy from openpype.pipeline import CreatedInstance, AutoCreator from openpype.client import get_asset_by_name from openpype.hosts.blender.api.plugin import BaseCreator -from openpype.hosts.blender.api.lib import imprint -from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY +from openpype.hosts.blender.api.pipeline import ( + AVALON_PROPERTY, + AVALON_CONTAINERS +) class CreateWorkfile(BaseCreator, AutoCreator): @@ -53,6 +55,8 @@ class CreateWorkfile(BaseCreator, AutoCreator): current_instance = CreatedInstance( self.family, subset_name, data, self ) + instance_node = bpy.data.collections.get(AVALON_CONTAINERS, {}) + current_instance.transient_data["instance_node"] = instance_node self._add_instance_to_context(current_instance) elif ( current_instance["asset"] != asset_name @@ -73,28 +77,30 @@ class CreateWorkfile(BaseCreator, AutoCreator): ) def collect_instances(self): - """Collect workfile instances.""" - self.cache_subsets(self.collection_shared_data) - cached_subsets = self.collection_shared_data["blender_cached_subsets"] - for node in cached_subsets.get(self.identifier, []): - created_instance = CreatedInstance.from_existing( - self.read_instance_node(node), self - ) - self._add_instance_to_context(created_instance) - def update_instances(self, update_list): - """Update workfile instances.""" - for created_inst, _changes in update_list: - data = created_inst.data_to_store() - node = data.get("instance_node") - if not node: - task_name = self.create_context.get_current_task_name() + print("Collecting!") + instance_node = bpy.data.collections.get(AVALON_CONTAINERS) + if not instance_node: + return + print(instance_node) + property = instance_node.get(AVALON_PROPERTY) + if not property: + return + print(property) - bpy.context.scene[AVALON_PROPERTY] = node = { - "name": f"workfile{task_name}" - } + # Create instance object from existing data + instance = CreatedInstance.from_existing( + instance_data=property.to_dict(), + creator=self + ) + instance.transient_data["instance_node"] = instance_node - created_inst["instance_node"] = node - data = created_inst.data_to_store() + # Add instance to create context + self._add_instance_to_context(instance) - imprint(node, data) + def remove_instances(self, instances): + for instance in instances: + node = instance.transient_data["instance_node"] + del node[AVALON_PROPERTY] + + self._remove_instance_from_context(instance) diff --git a/openpype/hosts/blender/plugins/load/load_blend.py b/openpype/hosts/blender/plugins/load/load_blend.py index f7bbc630de..1804de29b6 100644 --- a/openpype/hosts/blender/plugins/load/load_blend.py +++ b/openpype/hosts/blender/plugins/load/load_blend.py @@ -60,6 +60,7 @@ class BlendLoader(plugin.AssetLoader): for rig in rigs: creator_plugin = get_legacy_creator_by_name("CreateAnimation") + # TODO: Refactor legacy create usage to new style creators legacy_create( creator_plugin, name=rig.name.split(':')[-1] + "_animation", diff --git a/openpype/hosts/blender/plugins/load/load_layout_json.py b/openpype/hosts/blender/plugins/load/load_layout_json.py index 81683b8de8..a941c77a8e 100644 --- a/openpype/hosts/blender/plugins/load/load_layout_json.py +++ b/openpype/hosts/blender/plugins/load/load_layout_json.py @@ -123,6 +123,7 @@ class JsonLayoutLoader(plugin.AssetLoader): # raise ValueError("Creator plugin \"CreateCamera\" was " # "not found.") + # TODO: Refactor legacy create usage to new style creators # legacy_create( # creator_plugin, # name="camera", diff --git a/openpype/hosts/blender/plugins/publish/collect_render.py b/openpype/hosts/blender/plugins/publish/collect_render.py index 48b03256ae..00faf85aed 100644 --- a/openpype/hosts/blender/plugins/publish/collect_render.py +++ b/openpype/hosts/blender/plugins/publish/collect_render.py @@ -73,9 +73,8 @@ class CollectBlenderRender(pyblish.api.InstancePlugin): def process(self, instance): context = instance.context - render_data = bpy.data.collections[ - instance.data["instance_node"]["name"] - ].get("render_data") + instance_node = instance.data["transientData"]["instance_node"] + render_data = instance_node.get("render_data") assert render_data, "No render data found." diff --git a/openpype/hosts/blender/plugins/publish/collect_review.py b/openpype/hosts/blender/plugins/publish/collect_review.py index 4889c66be3..2c077398da 100644 --- a/openpype/hosts/blender/plugins/publish/collect_review.py +++ b/openpype/hosts/blender/plugins/publish/collect_review.py @@ -16,9 +16,7 @@ class CollectReview(pyblish.api.InstancePlugin): self.log.debug(f"instance: {instance}") - datablock = bpy.data.collections.get( - instance.data.get("instance_node", {}).get("name", "") - ) + datablock = instance.data["transientData"]["instance_node"] # get cameras cameras = [ diff --git a/openpype/hosts/blender/plugins/publish/validate_instance_empty.py b/openpype/hosts/blender/plugins/publish/validate_instance_empty.py index 7669d76943..6845c29b37 100644 --- a/openpype/hosts/blender/plugins/publish/validate_instance_empty.py +++ b/openpype/hosts/blender/plugins/publish/validate_instance_empty.py @@ -13,7 +13,7 @@ class ValidateInstanceEmpty(pyblish.api.InstancePlugin): optional = False def process(self, instance): - asset_group = instance.data["instance_node"] + asset_group = instance.data["transientData"]["instance_node"] if isinstance(asset_group, bpy.types.Collection): if not (asset_group.objects or asset_group.children): From bf49ffea16a6c83c0a7ba099143b3f35ca068d85 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 02:33:26 +0100 Subject: [PATCH 052/101] Hound --- openpype/hosts/blender/api/properties.py | 5 +++++ openpype/hosts/blender/plugins/create/create_blendScene.py | 7 +------ openpype/hosts/blender/plugins/create/create_camera.py | 6 +----- openpype/hosts/blender/plugins/create/create_layout.py | 7 +------ openpype/hosts/blender/plugins/create/create_model.py | 6 ------ openpype/hosts/blender/plugins/create/create_rig.py | 5 ----- 6 files changed, 8 insertions(+), 28 deletions(-) diff --git a/openpype/hosts/blender/api/properties.py b/openpype/hosts/blender/api/properties.py index c6b5ffe011..44ca7582b9 100644 --- a/openpype/hosts/blender/api/properties.py +++ b/openpype/hosts/blender/api/properties.py @@ -1,13 +1,17 @@ import bpy from bpy.utils import register_classes_factory + class OpenpypeContext(bpy.types.PropertyGroup): pass + classes = [] # [OpenpypeContext] + factory_register, factory_unregister = register_classes_factory(classes) + def register(): """Register the properties.""" factory_register() @@ -17,6 +21,7 @@ def register(): # name="OpenPype Context", type=OpenpypeContext, options={"HIDDEN"} # ) + def unregister(): """Unregister the properties.""" factory_unregister() diff --git a/openpype/hosts/blender/plugins/create/create_blendScene.py b/openpype/hosts/blender/plugins/create/create_blendScene.py index f1090ae397..c580cdfe46 100644 --- a/openpype/hosts/blender/plugins/create/create_blendScene.py +++ b/openpype/hosts/blender/plugins/create/create_blendScene.py @@ -2,12 +2,7 @@ import bpy -from openpype.pipeline import get_current_task_name -from openpype.hosts.blender.api import plugin, lib, ops -from openpype.hosts.blender.api.pipeline import ( - AVALON_INSTANCES, - AVALON_PROPERTY, -) +from openpype.hosts.blender.api import plugin, lib class CreateBlendScene(plugin.Creator): diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index d30c60ba21..13075f718e 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -2,12 +2,8 @@ import bpy -from openpype.pipeline import CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops -from openpype.hosts.blender.api.pipeline import ( - AVALON_INSTANCES, - AVALON_PROPERTY, -) +from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES class CreateCamera(plugin.BaseCreator): diff --git a/openpype/hosts/blender/plugins/create/create_layout.py b/openpype/hosts/blender/plugins/create/create_layout.py index f569a81c29..05d11bf315 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -2,12 +2,7 @@ import bpy -from openpype.pipeline import get_current_task_name, CreatedInstance -from openpype.hosts.blender.api import plugin, lib, ops -from openpype.hosts.blender.api.pipeline import ( - AVALON_INSTANCES, - AVALON_PROPERTY, -) +from openpype.hosts.blender.api import plugin, lib class CreateLayout(plugin.BaseCreator): diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index 9774d4bec3..9f1367ebb2 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -2,13 +2,7 @@ import bpy -from openpype.pipeline import get_current_task_name, CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops -from openpype.hosts.blender.api.pipeline import ( - AVALON_INSTANCES, - AVALON_PROPERTY, -) - class CreateModel(plugin.BaseCreator): """Polygonal static geometry.""" diff --git a/openpype/hosts/blender/plugins/create/create_rig.py b/openpype/hosts/blender/plugins/create/create_rig.py index 87f0124164..c7ab9b81e1 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -2,12 +2,7 @@ import bpy -from openpype.pipeline import get_current_task_name, CreatedInstance from openpype.hosts.blender.api import plugin, lib, ops -from openpype.hosts.blender.api.pipeline import ( - AVALON_INSTANCES, - AVALON_PROPERTY, -) class CreateRig(plugin.BaseCreator): From e67ecd4065ffa40e4b563762a969d4650300e694 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 02:34:44 +0100 Subject: [PATCH 053/101] Hound --- openpype/hosts/blender/api/pipeline.py | 2 +- openpype/hosts/blender/plugins/create/create_model.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/blender/api/pipeline.py b/openpype/hosts/blender/api/pipeline.py index 9ac59f5620..b9f802d221 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -10,7 +10,7 @@ from . import ops, properties import pyblish.api -from openpype.host import( +from openpype.host import ( HostBase, IWorkfileHost, IPublishHost, diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index 9f1367ebb2..7047304014 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -4,6 +4,7 @@ import bpy from openpype.hosts.blender.api import plugin, lib, ops + class CreateModel(plugin.BaseCreator): """Polygonal static geometry.""" From b16bc2a80e85282855460c93906783ae46a96577 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 02:43:21 +0100 Subject: [PATCH 054/101] Remove debugging prints --- openpype/hosts/blender/plugins/create/create_workfile.py | 4 +--- openpype/hosts/blender/plugins/load/load_blend.py | 3 --- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/openpype/hosts/blender/plugins/create/create_workfile.py b/openpype/hosts/blender/plugins/create/create_workfile.py index 8f5adb5583..4dda1a31cc 100644 --- a/openpype/hosts/blender/plugins/create/create_workfile.py +++ b/openpype/hosts/blender/plugins/create/create_workfile.py @@ -78,15 +78,13 @@ class CreateWorkfile(BaseCreator, AutoCreator): def collect_instances(self): - print("Collecting!") instance_node = bpy.data.collections.get(AVALON_CONTAINERS) if not instance_node: return - print(instance_node) + property = instance_node.get(AVALON_PROPERTY) if not property: return - print(property) # Create instance object from existing data instance = CreatedInstance.from_existing( diff --git a/openpype/hosts/blender/plugins/load/load_blend.py b/openpype/hosts/blender/plugins/load/load_blend.py index 1804de29b6..84e181cd6c 100644 --- a/openpype/hosts/blender/plugins/load/load_blend.py +++ b/openpype/hosts/blender/plugins/load/load_blend.py @@ -91,7 +91,6 @@ class BlendLoader(plugin.AssetLoader): members.append(data) container = self._get_asset_container(data_to.objects) - print(container) assert container, "No asset group found" container.name = group_name @@ -105,8 +104,6 @@ class BlendLoader(plugin.AssetLoader): print(obj) bpy.context.scene.collection.objects.link(obj) - print("") - # Remove the library from the blend file library = bpy.data.libraries.get(bpy.path.basename(libpath)) bpy.data.libraries.remove(library) From 6e89f3301470029ae996b02cb784b66ccfcd6986 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 08:50:03 +0100 Subject: [PATCH 055/101] Clarify Workfile instance data persistence --- openpype/hosts/blender/plugins/create/create_workfile.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/blender/plugins/create/create_workfile.py b/openpype/hosts/blender/plugins/create/create_workfile.py index 4dda1a31cc..ffd423ad74 100644 --- a/openpype/hosts/blender/plugins/create/create_workfile.py +++ b/openpype/hosts/blender/plugins/create/create_workfile.py @@ -10,7 +10,13 @@ from openpype.hosts.blender.api.pipeline import ( class CreateWorkfile(BaseCreator, AutoCreator): - """Workfile auto-creator.""" + """Workfile auto-creator. + + The workfile instance stores its data on the `AVALON_CONTAINERS` collection + as custom attributes, because unlike other instances it doesn't have an + instance node of its own. + + """ identifier = "io.openpype.creators.blender.workfile" label = "Workfile" family = "workfile" From a2a47787db707215d3597e53cf4dc33857a96219 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 09:12:09 +0100 Subject: [PATCH 056/101] Avoid need for executing in main thread by just not using `bpy.ops` functions. I also skipped the "keep_transform" part of the functionality since it's redundant because the created asset group is always at origin by default, and thus needs no matrix inverse computations. --- .../blender/plugins/create/create_camera.py | 16 +++++----------- .../blender/plugins/create/create_layout.py | 7 ++----- .../hosts/blender/plugins/create/create_model.py | 11 +++-------- .../hosts/blender/plugins/create/create_rig.py | 10 +++------- 4 files changed, 13 insertions(+), 31 deletions(-) diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index 13075f718e..b0c0fc28db 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -2,7 +2,7 @@ import bpy -from openpype.hosts.blender.api import plugin, lib, ops +from openpype.hosts.blender.api import plugin, lib from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES @@ -17,7 +17,6 @@ class CreateCamera(plugin.BaseCreator): create_as_asset_group = True - @ops.execute_function_in_main_thread def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): @@ -27,13 +26,10 @@ class CreateCamera(plugin.BaseCreator): instance_data, pre_create_data) + bpy.context.view_layer.objects.active = asset_group if pre_create_data.get("use_selection"): - bpy.context.view_layer.objects.active = asset_group - selected = lib.get_selection() - for obj in selected: - obj.select_set(True) - selected.append(asset_group) - bpy.ops.object.parent_set(keep_transform=True) + for obj in lib.get_selection(): + obj.parent = asset_group else: plugin.deselect_all() camera = bpy.data.cameras.new(subset_name) @@ -42,9 +38,7 @@ class CreateCamera(plugin.BaseCreator): instances = bpy.data.collections.get(AVALON_INSTANCES) instances.objects.link(camera_obj) - camera_obj.select_set(True) - asset_group.select_set(True) bpy.context.view_layer.objects.active = asset_group - bpy.ops.object.parent_set(keep_transform=True) + camera_obj.parent = asset_group return asset_group diff --git a/openpype/hosts/blender/plugins/create/create_layout.py b/openpype/hosts/blender/plugins/create/create_layout.py index 05d11bf315..93fb91a324 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -27,10 +27,7 @@ class CreateLayout(plugin.BaseCreator): # Add selected objects to instance if pre_create_data.get("use_selection"): bpy.context.view_layer.objects.active = asset_group - selected = lib.get_selection() - for obj in selected: - obj.select_set(True) - selected.append(asset_group) - bpy.ops.object.parent_set(keep_transform=True) + for obj in lib.get_selection(): + obj.parent = asset_group return asset_group diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index 7047304014..8b5eaeb999 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -2,7 +2,7 @@ import bpy -from openpype.hosts.blender.api import plugin, lib, ops +from openpype.hosts.blender.api import plugin, lib class CreateModel(plugin.BaseCreator): @@ -16,7 +16,6 @@ class CreateModel(plugin.BaseCreator): create_as_asset_group = True - @ops.execute_function_in_main_thread def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): @@ -27,11 +26,7 @@ class CreateModel(plugin.BaseCreator): # Add selected objects to instance if pre_create_data.get("use_selection"): bpy.context.view_layer.objects.active = asset_group - selected = lib.get_selection() - for obj in selected: - obj.select_set(True) - selected.append(asset_group) - - bpy.ops.object.parent_set(keep_transform=True) + for obj in lib.get_selection(): + obj.parent = asset_group return asset_group diff --git a/openpype/hosts/blender/plugins/create/create_rig.py b/openpype/hosts/blender/plugins/create/create_rig.py index c7ab9b81e1..8228442249 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -2,7 +2,7 @@ import bpy -from openpype.hosts.blender.api import plugin, lib, ops +from openpype.hosts.blender.api import plugin, lib class CreateRig(plugin.BaseCreator): @@ -16,7 +16,6 @@ class CreateRig(plugin.BaseCreator): create_as_asset_group = True - @ops.execute_function_in_main_thread def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): @@ -27,10 +26,7 @@ class CreateRig(plugin.BaseCreator): # Add selected objects to instance if pre_create_data.get("use_selection"): bpy.context.view_layer.objects.active = asset_group - selected = lib.get_selection() - for obj in selected: - obj.select_set(True) - selected.append(asset_group) - bpy.ops.object.parent_set(keep_transform=True) + for obj in lib.get_selection(): + obj.parent = asset_group return asset_group From dcfdb25217bf3c72bcad16500582c4a984ad6892 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 10:52:54 +0100 Subject: [PATCH 057/101] Implement `ILoadHost` + refactor `host.ls()` to `host.get_containers()` --- openpype/hosts/blender/api/pipeline.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/blender/api/pipeline.py b/openpype/hosts/blender/api/pipeline.py index b9f802d221..ee43108594 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -14,6 +14,7 @@ from openpype.host import ( HostBase, IWorkfileHost, IPublishHost, + ILoadHost ) from openpype.client import get_asset_by_name from openpype.pipeline import ( @@ -60,7 +61,7 @@ IS_HEADLESS = bpy.app.background log = Logger.get_logger(__name__) -class BlenderHost(HostBase, IWorkfileHost, IPublishHost): +class BlenderHost(HostBase, IWorkfileHost, IPublishHost, ILoadHost): name = "blender" def install(self): @@ -68,7 +69,7 @@ class BlenderHost(HostBase, IWorkfileHost, IPublishHost): Install Blender host functionality.""" install() - def ls(self) -> Iterator: + def get_containers(self) -> Iterator: """List containers from active Blender scene.""" return ls() From 28c83c3bb63085b87d861d23644bea2871e7b73b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 11:09:34 +0100 Subject: [PATCH 058/101] Remove duplicated code Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/blender/plugins/publish/extract_blend.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/extract_blend.py b/openpype/hosts/blender/plugins/publish/extract_blend.py index 991cd040de..a1b49dcd8f 100644 --- a/openpype/hosts/blender/plugins/publish/extract_blend.py +++ b/openpype/hosts/blender/plugins/publish/extract_blend.py @@ -19,9 +19,6 @@ class ExtractBlend(publish.Extractor, publish.OptionalPyblishPluginMixin): # Define extract output file path - if not self.is_active(instance.data): - return - stagingdir = self.staging_dir(instance) filename = f"{instance.name}.blend" filepath = os.path.join(stagingdir, filename) From 6b59b718238f51ec1aef7be40b804b842adf6062 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 11:42:12 +0100 Subject: [PATCH 059/101] Re-add collector to collect instance members --- .../plugins/publish/collect_instance.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 openpype/hosts/blender/plugins/publish/collect_instance.py diff --git a/openpype/hosts/blender/plugins/publish/collect_instance.py b/openpype/hosts/blender/plugins/publish/collect_instance.py new file mode 100644 index 0000000000..f8fc038347 --- /dev/null +++ b/openpype/hosts/blender/plugins/publish/collect_instance.py @@ -0,0 +1,43 @@ +import bpy + +import pyblish.api + +from openpype.pipeline.publish import KnownPublishError +from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY + + +class CollectBlenderInstanceData(pyblish.api.InstancePlugin): + """Validator to verify that the instance is not empty""" + + order = pyblish.api.CollectorOrder + hosts = ["blender"] + families = ["model", "pointcache", "rig", "camera" "layout", "blendScene"] + label = "Collect Instance" + + def process(self, instance): + instance_node = instance.data["transientData"]["instance_node"] + + # Collect members of the instance + members = [instance_node] + if isinstance(instance_node, bpy.types.Collection): + members.extend(instance_node.objects) + members.extend(instance_node.children) + + # Special case for animation instances, include armatures + # TODO: Does this still work as intended? + if instance.data["family"] == "animation": + for obj in instance_node.objects: + if obj.type == 'EMPTY' and obj.get(AVALON_PROPERTY): + members.extend( + child for child in obj.children + if child.type == 'ARMATURE' + ) + elif isinstance(instance_node, bpy.types.Object): + members.extend(instance_node.children_recursive) + else: + raise KnownPublishError( + f"Unsupported instance node type '{type(instance_node)}' " + f"for instance '{instance}'" + ) + + instance[:] = members From 19b7abb8ff3def802d3cf9074cd77a20c77b12cb Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 11:47:20 +0100 Subject: [PATCH 060/101] Label cosmetics --- openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py b/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py index dd955dc5da..060bccbd04 100644 --- a/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py +++ b/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py @@ -21,7 +21,7 @@ class ValidateMeshHasUvs( order = ValidateContentsOrder hosts = ["blender"] families = ["model"] - label = "Mesh Has UV's" + label = "Mesh Has UVs" actions = [openpype.hosts.blender.api.action.SelectInvalidAction] optional = True From b8f361f75d2e7f5d4c3097ba1bd67c095b37f3b2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 11:55:14 +0100 Subject: [PATCH 061/101] Refactor Validate Instance Empty + raise PublishValidationError --- .../plugins/publish/validate_instance_empty.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/validate_instance_empty.py b/openpype/hosts/blender/plugins/publish/validate_instance_empty.py index 6845c29b37..51a1dcf6ca 100644 --- a/openpype/hosts/blender/plugins/publish/validate_instance_empty.py +++ b/openpype/hosts/blender/plugins/publish/validate_instance_empty.py @@ -1,6 +1,5 @@ -import bpy - import pyblish.api +from openpype.pipeline.publish import PublishValidationError class ValidateInstanceEmpty(pyblish.api.InstancePlugin): @@ -13,11 +12,8 @@ class ValidateInstanceEmpty(pyblish.api.InstancePlugin): optional = False def process(self, instance): - asset_group = instance.data["transientData"]["instance_node"] - - if isinstance(asset_group, bpy.types.Collection): - if not (asset_group.objects or asset_group.children): - raise RuntimeError(f"Instance {instance.name} is empty.") - elif isinstance(asset_group, bpy.types.Object): - if not asset_group.children: - raise RuntimeError(f"Instance {instance.name} is empty.") + # Members are collected by `collect_instance` so we only need to check + # whether any member is included. The instance node will be included + # as a member as well, hence we will check for at least 2 members + if len(instance) < 2: + raise PublishValidationError(f"Instance {instance.name} is empty.") From 3f2bd4c001a8a35dc5f5f510074b170ceda2fbc7 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 11:56:08 +0100 Subject: [PATCH 062/101] Improve object name readability in reports --- .../blender/plugins/publish/validate_camera_zero_keyframe.py | 3 ++- .../plugins/publish/validate_mesh_no_negative_scale.py | 3 ++- .../blender/plugins/publish/validate_no_colons_in_name.py | 3 ++- .../hosts/blender/plugins/publish/validate_object_mode.py | 3 ++- .../hosts/blender/plugins/publish/validate_transform_zero.py | 5 +++-- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py b/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py index 65697cb86d..ee0a0e4dc9 100644 --- a/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py +++ b/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py @@ -48,6 +48,7 @@ class ValidateCameraZeroKeyframe(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: + names = ", ".join(obj.name for obj in invalid) raise PublishValidationError( - f"Camera must have a keyframe at frame 0: {invalid}" + f"Camera must have a keyframe at frame 0: {names}" ) diff --git a/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py b/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py index a498a3b4cb..7f77bbe38c 100644 --- a/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py +++ b/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py @@ -37,6 +37,7 @@ class ValidateMeshNoNegativeScale(pyblish.api.Validator, invalid = self.get_invalid(instance) if invalid: + names = ", ".join(obj.name for obj in invalid) raise PublishValidationError( - f"Meshes found in instance with negative scale: {invalid}" + f"Meshes found in instance with negative scale: {names}" ) diff --git a/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py b/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py index 17119f8d88..caf555b535 100644 --- a/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py +++ b/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py @@ -46,6 +46,7 @@ class ValidateNoColonsInName(pyblish.api.InstancePlugin, invalid = self.get_invalid(instance) if invalid: + names = ", ".join(obj.name for obj in invalid) raise PublishValidationError( - f"Objects found with colon in name: {invalid}" + f"Objects found with colon in name: {names}" ) diff --git a/openpype/hosts/blender/plugins/publish/validate_object_mode.py b/openpype/hosts/blender/plugins/publish/validate_object_mode.py index 3b6f29a79e..ab5f4bb467 100644 --- a/openpype/hosts/blender/plugins/publish/validate_object_mode.py +++ b/openpype/hosts/blender/plugins/publish/validate_object_mode.py @@ -37,6 +37,7 @@ class ValidateObjectIsInObjectMode( invalid = self.get_invalid(instance) if invalid: + names = ", ".join(obj.name for obj in invalid) raise PublishValidationError( - f"Object found in instance is not in Object Mode: {invalid}" + f"Object found in instance is not in Object Mode: {names}" ) diff --git a/openpype/hosts/blender/plugins/publish/validate_transform_zero.py b/openpype/hosts/blender/plugins/publish/validate_transform_zero.py index 5270a50888..1fb9535ee4 100644 --- a/openpype/hosts/blender/plugins/publish/validate_transform_zero.py +++ b/openpype/hosts/blender/plugins/publish/validate_transform_zero.py @@ -48,7 +48,8 @@ class ValidateTransformZero(pyblish.api.InstancePlugin, invalid = self.get_invalid(instance) if invalid: + names = ", ".join(obj.name for obj in invalid) raise PublishValidationError( - "Object found in instance has not" - f" transform to zero: {invalid}" + "Objects found in instance which do not" + f" have transform set to zero: {names}" ) From f12b4106717be573f6149f52b2419cbcaaaf274b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 12:01:41 +0100 Subject: [PATCH 063/101] Clean up some todos --- openpype/hosts/blender/api/ops.py | 1 - openpype/hosts/blender/api/plugin.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/blender/api/ops.py b/openpype/hosts/blender/api/ops.py index c617144b6f..408e38e6cd 100644 --- a/openpype/hosts/blender/api/ops.py +++ b/openpype/hosts/blender/api/ops.py @@ -428,7 +428,6 @@ class TOPBAR_MT_avalon(bpy.types.Menu): layout.operator(SetResolution.bl_idname, text="Set Resolution") layout.separator() layout.operator(LaunchWorkFiles.bl_idname, text="Work Files...") - # TODO (jasper): maybe add 'Reload Pipeline' def draw_avalon_menu(self, context): diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index d1cefe0a62..57fccf9299 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -430,7 +430,7 @@ class AssetLoader(LoaderPlugin): namespace: Use pre-defined namespace options: Additional settings dictionary """ - # TODO (jasper): make it possible to add the asset several times by + # TODO: make it possible to add the asset several times by # just re-using the collection filepath = self.filepath_from_context(context) assert Path(filepath).exists(), f"{filepath} doesn't exist." From 9419fe5d51a23b9378394638eb91b9212a4dc61d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 12:02:20 +0100 Subject: [PATCH 064/101] Clarify what `asset_group` pre_create_data intends to do, because it is not an Attribute Definition on the Creator --- openpype/hosts/blender/plugins/create/create_animation.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/blender/plugins/create/create_animation.py b/openpype/hosts/blender/plugins/create/create_animation.py index 6f0cabb259..b3d10090e3 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -15,7 +15,6 @@ class CreateAnimation(plugin.BaseCreator): def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): - """Run the creator on Blender main thread.""" # Run parent create method collection = super().create( subset_name, instance_data, pre_create_data @@ -26,7 +25,9 @@ class CreateAnimation(plugin.BaseCreator): for obj in selected: collection.objects.link(obj) elif pre_create_data.get("asset_group"): - obj = (self.options or {}).get("asset_group") + # Use for Load Blend automated creation of animation instances + # upon loading rig files + obj = pre_create_data.get("asset_group") collection.objects.link(obj) return collection From a450eab0ce67b7c2bce903b2008776f7863b401b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 12:02:46 +0100 Subject: [PATCH 065/101] Remove "asset_group" functionality in CreateReview, since it's unused there --- openpype/hosts/blender/plugins/create/create_review.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/openpype/hosts/blender/plugins/create/create_review.py b/openpype/hosts/blender/plugins/create/create_review.py index 2d521c4255..3dd52395df 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -15,7 +15,6 @@ class CreateReview(plugin.BaseCreator): def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): - """Run the creator on Blender main thread.""" # Run parent create method collection = super().create( subset_name, instance_data, pre_create_data @@ -25,9 +24,5 @@ class CreateReview(plugin.BaseCreator): selected = lib.get_selection() for obj in selected: collection.objects.link(obj) - elif pre_create_data.get("asset_group"): - # TODO: What is the intended behavior for this? - obj = (self.options or {}).get("asset_group") - collection.objects.link(obj) return collection From 8e6c533ee6dbabd99b322336196e976806a676c0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 12:03:16 +0100 Subject: [PATCH 066/101] Remove redundant docstring - since `create` now isn't off-loaded to 'main thread' anymore --- openpype/hosts/blender/plugins/create/create_camera.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index b0c0fc28db..d624a7f05a 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -20,7 +20,6 @@ class CreateCamera(plugin.BaseCreator): def create( self, subset_name: str, instance_data: dict, pre_create_data: dict ): - """Run the creator on Blender main thread.""" asset_group = super().create(subset_name, instance_data, From 4d1c887ece74c6dc638bf9df250d8e39cd2e2ee9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 12:07:08 +0100 Subject: [PATCH 067/101] Fix missing class inheritance --- .../plugins/publish/validate_camera_zero_keyframe.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py b/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py index ee0a0e4dc9..9b6e513897 100644 --- a/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py +++ b/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py @@ -7,11 +7,13 @@ import pyblish.api import openpype.hosts.blender.api.action from openpype.pipeline.publish import ( ValidateContentsOrder, - PublishValidationError + PublishValidationError, + OptionalPyblishPluginMixin ) -class ValidateCameraZeroKeyframe(pyblish.api.InstancePlugin): +class ValidateCameraZeroKeyframe(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Camera must have a keyframe at frame 0. Unreal shifts the first keyframe to frame 0. Forcing the camera to have From 7acc5f9dd61a73841363a9a1000052ddc6f88e7f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 12:09:24 +0100 Subject: [PATCH 068/101] Collect camera instance members --- openpype/hosts/blender/plugins/publish/collect_instance.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/blender/plugins/publish/collect_instance.py b/openpype/hosts/blender/plugins/publish/collect_instance.py index f8fc038347..b170e3d06f 100644 --- a/openpype/hosts/blender/plugins/publish/collect_instance.py +++ b/openpype/hosts/blender/plugins/publish/collect_instance.py @@ -11,7 +11,8 @@ class CollectBlenderInstanceData(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder hosts = ["blender"] - families = ["model", "pointcache", "rig", "camera" "layout", "blendScene"] + families = ["model", "pointcache", "rig", "camera" "layout", "blendScene", + "camera"] label = "Collect Instance" def process(self, instance): From 53bcf0e17cc841c901a887e8c09619aacab2d4d4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 12:17:06 +0100 Subject: [PATCH 069/101] Use instance node explicitly as the 'asset group' --- openpype/hosts/blender/plugins/publish/extract_abc.py | 9 +++------ .../blender/plugins/publish/extract_abc_animation.py | 2 +- .../hosts/blender/plugins/publish/extract_camera_abc.py | 7 +------ openpype/hosts/blender/plugins/publish/extract_fbx.py | 6 ++---- .../blender/plugins/publish/extract_fbx_animation.py | 9 +-------- 5 files changed, 8 insertions(+), 25 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/extract_abc.py b/openpype/hosts/blender/plugins/publish/extract_abc.py index 1602c4d266..b0cd6d0d32 100644 --- a/openpype/hosts/blender/plugins/publish/extract_abc.py +++ b/openpype/hosts/blender/plugins/publish/extract_abc.py @@ -28,18 +28,15 @@ class ExtractABC(publish.Extractor, publish.OptionalPyblishPluginMixin): plugin.deselect_all() - selected = [] - active = None + asset_group = instance.data["transientData"]["instance_node"] + selected = [] for obj in instance: obj.select_set(True) selected.append(obj) - # Set as active the asset group - if obj.get(AVALON_PROPERTY): - active = obj context = plugin.create_blender_context( - active=active, selected=selected) + active=asset_group, selected=selected) with bpy.context.temp_override(**context): # We export the abc diff --git a/openpype/hosts/blender/plugins/publish/extract_abc_animation.py b/openpype/hosts/blender/plugins/publish/extract_abc_animation.py index 0ed2819407..1c23fc8acb 100644 --- a/openpype/hosts/blender/plugins/publish/extract_abc_animation.py +++ b/openpype/hosts/blender/plugins/publish/extract_abc_animation.py @@ -32,7 +32,7 @@ class ExtractAnimationABC( plugin.deselect_all() selected = [] - asset_group = None + asset_group = instance.data["transientData"]["instance_node"] objects = [] for obj in instance: diff --git a/openpype/hosts/blender/plugins/publish/extract_camera_abc.py b/openpype/hosts/blender/plugins/publish/extract_camera_abc.py index 9db1505d37..9d0b7f132b 100644 --- a/openpype/hosts/blender/plugins/publish/extract_camera_abc.py +++ b/openpype/hosts/blender/plugins/publish/extract_camera_abc.py @@ -29,12 +29,7 @@ class ExtractCameraABC(publish.Extractor, publish.OptionalPyblishPluginMixin): plugin.deselect_all() - asset_group = None - for obj in instance: - if obj.get(AVALON_PROPERTY): - asset_group = obj - break - assert asset_group, "No asset group found" + asset_group = instance.data["transientData"]["instance_node"] # Need to cast to list because children is a tuple selected = list(asset_group.children) diff --git a/openpype/hosts/blender/plugins/publish/extract_fbx.py b/openpype/hosts/blender/plugins/publish/extract_fbx.py index fae438158a..0ba82eca4e 100644 --- a/openpype/hosts/blender/plugins/publish/extract_fbx.py +++ b/openpype/hosts/blender/plugins/publish/extract_fbx.py @@ -29,14 +29,12 @@ class ExtractFBX(publish.Extractor, publish.OptionalPyblishPluginMixin): plugin.deselect_all() - selected = [] - asset_group = None + asset_group = instance.data["transientData"]["instance_node"] + selected = [] for obj in instance: obj.select_set(True) selected.append(obj) - if obj.get(AVALON_PROPERTY): - asset_group = obj context = plugin.create_blender_context( active=asset_group, selected=selected) diff --git a/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py b/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py index ba584ac99a..6fbcdc63c3 100644 --- a/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py @@ -31,14 +31,7 @@ class ExtractAnimationFBX( # Perform extraction self.log.debug("Performing extraction..") - # The first collection object in the instance is taken, as there - # should be only one that contains the asset group. - collection = [ - obj for obj in instance if type(obj) is bpy.types.Collection][0] - - # Again, the first object in the collection is taken , as there - # should be only the asset group in the collection. - asset_group = collection.objects[0] + asset_group = instance.data["transientData"]["instance_node"] armature = [ obj for obj in asset_group.children if obj.type == 'ARMATURE'][0] From 2b5c20e6e0beaf56767a000c09d09c8860d45d08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Tue, 14 Nov 2023 12:49:46 +0100 Subject: [PATCH 070/101] Update openpype/hosts/fusion/plugins/create/create_saver.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/fusion/plugins/create/create_saver.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/openpype/hosts/fusion/plugins/create/create_saver.py b/openpype/hosts/fusion/plugins/create/create_saver.py index e8ba2880a4..ecf36abdd2 100644 --- a/openpype/hosts/fusion/plugins/create/create_saver.py +++ b/openpype/hosts/fusion/plugins/create/create_saver.py @@ -149,13 +149,7 @@ class CreateSaver(NewCreator): # get frame padding from anatomy templates anatomy = Anatomy() - render_anatomy_template = anatomy.templates.get("render") - if render_anatomy_template: - frame_padding = int( - render_anatomy_template.get("frame_padding", 4) - ) - else: - frame_padding = int(anatomy.templates.get("frame_padding", 4)) + frame_padding = anatomy.templates["frame_padding"] # Subset change detected workdir = os.path.normpath(legacy_io.Session["AVALON_WORKDIR"]) From 162394a56cfd3f62756603dd821596c478191747 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 15:17:55 +0100 Subject: [PATCH 071/101] Fix typo, missing comment *facepalm* --- openpype/hosts/blender/plugins/publish/collect_instance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/blender/plugins/publish/collect_instance.py b/openpype/hosts/blender/plugins/publish/collect_instance.py index b170e3d06f..72c8f13362 100644 --- a/openpype/hosts/blender/plugins/publish/collect_instance.py +++ b/openpype/hosts/blender/plugins/publish/collect_instance.py @@ -11,7 +11,7 @@ class CollectBlenderInstanceData(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder hosts = ["blender"] - families = ["model", "pointcache", "rig", "camera" "layout", "blendScene", + families = ["model", "pointcache", "rig", "camera", "layout", "blendScene", "camera"] label = "Collect Instance" From 8d57f5f3ef7224d4bae5920ff1e82bde93340f2b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 15:23:45 +0100 Subject: [PATCH 072/101] Fix extract layout --- .../hosts/blender/plugins/publish/extract_layout.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/blender/plugins/publish/extract_layout.py b/openpype/hosts/blender/plugins/publish/extract_layout.py index d0adac6edb..7e8ca7a250 100644 --- a/openpype/hosts/blender/plugins/publish/extract_layout.py +++ b/openpype/hosts/blender/plugins/publish/extract_layout.py @@ -128,13 +128,22 @@ class ExtractLayout(publish.Extractor, publish.OptionalPyblishPluginMixin): json_data = [] fbx_files = [] - asset_group = bpy.data.objects[str(instance)] + asset_group = instance.data["transientData"]["instance_node"] fbx_count = 0 project_name = instance.context.data["projectEntity"]["name"] for asset in asset_group.children: metadata = asset.get(AVALON_PROPERTY) + if not metadata: + # Avoid erroring directly if there's just invalid data + # inside the instance + # TODO: This should actually be validated in a validator + self.log.warning( + f"Found content in layout that is not a loaded " + f"asset, skipping: {asset.name_full}" + ) + continue version_id = metadata["parent"] family = metadata["family"] From d1aaefab9c916da3e2ad94a77792489c1300f60b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 15:24:20 +0100 Subject: [PATCH 073/101] Improve comment grammar --- openpype/hosts/blender/plugins/publish/extract_layout.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/extract_layout.py b/openpype/hosts/blender/plugins/publish/extract_layout.py index 7e8ca7a250..73d92961bc 100644 --- a/openpype/hosts/blender/plugins/publish/extract_layout.py +++ b/openpype/hosts/blender/plugins/publish/extract_layout.py @@ -136,8 +136,8 @@ class ExtractLayout(publish.Extractor, publish.OptionalPyblishPluginMixin): for asset in asset_group.children: metadata = asset.get(AVALON_PROPERTY) if not metadata: - # Avoid erroring directly if there's just invalid data - # inside the instance + # Avoid raising error directly if there's just invalid data + # inside the instance; better to log it to the artist # TODO: This should actually be validated in a validator self.log.warning( f"Found content in layout that is not a loaded " From fa5638d06bb73d4f327d3de120718b72d70c6b9f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 15:33:59 +0100 Subject: [PATCH 074/101] Fix parent class --- openpype/hosts/blender/plugins/create/create_blendScene.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/blender/plugins/create/create_blendScene.py b/openpype/hosts/blender/plugins/create/create_blendScene.py index c580cdfe46..bda026286d 100644 --- a/openpype/hosts/blender/plugins/create/create_blendScene.py +++ b/openpype/hosts/blender/plugins/create/create_blendScene.py @@ -5,7 +5,7 @@ import bpy from openpype.hosts.blender.api import plugin, lib -class CreateBlendScene(plugin.Creator): +class CreateBlendScene(plugin.BaseCreator): """Generic group of assets.""" name = "blendScene" From 0a714778736118d78c6f5c7a3660c7828de26d26 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 15:42:29 +0100 Subject: [PATCH 075/101] Allow to fall back to a window from the window manager --- openpype/hosts/blender/api/lib.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/blender/api/lib.py b/openpype/hosts/blender/api/lib.py index 5d8c93dc49..e80ed61bc8 100644 --- a/openpype/hosts/blender/api/lib.py +++ b/openpype/hosts/blender/api/lib.py @@ -278,9 +278,11 @@ def get_selected_collections(): list: A list of `bpy.types.Collection` objects that are currently selected in the outliner. """ + window = bpy.context.window or bpy.context.window_manager.windows[0] + try: area = next( - area for area in bpy.context.window.screen.areas + area for area in window.screen.areas if area.type == 'OUTLINER') region = next( region for region in area.regions @@ -290,10 +292,10 @@ def get_selected_collections(): "must be in the main Blender window.") from e with bpy.context.temp_override( - window=bpy.context.window, + window=window, area=area, region=region, - screen=bpy.context.window.screen + screen=window.screen ): ids = bpy.context.selected_ids From 63ff830d12cdfaf8ed4cee4e681f5c5e650cc76a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 15:50:07 +0100 Subject: [PATCH 076/101] Ignore collections in instance, only select objects --- openpype/hosts/blender/plugins/publish/extract_abc.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/extract_abc.py b/openpype/hosts/blender/plugins/publish/extract_abc.py index b0cd6d0d32..415cbb186c 100644 --- a/openpype/hosts/blender/plugins/publish/extract_abc.py +++ b/openpype/hosts/blender/plugins/publish/extract_abc.py @@ -32,8 +32,9 @@ class ExtractABC(publish.Extractor, publish.OptionalPyblishPluginMixin): selected = [] for obj in instance: - obj.select_set(True) - selected.append(obj) + if isinstance(obj, bpy.types.Object): + obj.select_set(True) + selected.append(obj) context = plugin.create_blender_context( active=asset_group, selected=selected) From 00d5b90568e6a35df49abe3bc8129a4cd6ab7a4a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 15:50:23 +0100 Subject: [PATCH 077/101] Remove unused import --- openpype/hosts/blender/plugins/publish/extract_abc.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/blender/plugins/publish/extract_abc.py b/openpype/hosts/blender/plugins/publish/extract_abc.py index 415cbb186c..12d062d925 100644 --- a/openpype/hosts/blender/plugins/publish/extract_abc.py +++ b/openpype/hosts/blender/plugins/publish/extract_abc.py @@ -4,7 +4,6 @@ import bpy from openpype.pipeline import publish from openpype.hosts.blender.api import plugin -from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY class ExtractABC(publish.Extractor, publish.OptionalPyblishPluginMixin): From f281da5784f79cacda9730c5b211a77e48bfeb2b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 16:01:30 +0100 Subject: [PATCH 078/101] Fix missing identifier --- openpype/hosts/blender/plugins/create/create_blendScene.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/blender/plugins/create/create_blendScene.py b/openpype/hosts/blender/plugins/create/create_blendScene.py index bda026286d..c09261705f 100644 --- a/openpype/hosts/blender/plugins/create/create_blendScene.py +++ b/openpype/hosts/blender/plugins/create/create_blendScene.py @@ -8,6 +8,7 @@ from openpype.hosts.blender.api import plugin, lib class CreateBlendScene(plugin.BaseCreator): """Generic group of assets.""" + identifier = "io.openpype.creators.blender.blendscene" name = "blendScene" label = "Blender Scene" family = "blendScene" From 0556986a3e39c45be8c923f18a87981c17163c3a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 16:40:29 +0100 Subject: [PATCH 079/101] Remove redundant data --- openpype/hosts/blender/api/plugin.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 57fccf9299..5a0026d862 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -325,8 +325,6 @@ class BaseCreator(Creator): { "id": "pyblish.avalon.instance", "creator_identifier": self.identifier, - "label": subset_name, - "task": get_current_task_name(), "subset": subset_name, } ) From 9d6352c68dc69383534ab4a0757751a7829d7a01 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 16:43:04 +0100 Subject: [PATCH 080/101] Remove `name` attributes on Creator that are irrelevant to new-style creators --- openpype/hosts/blender/plugins/create/create_action.py | 1 - openpype/hosts/blender/plugins/create/create_animation.py | 1 - openpype/hosts/blender/plugins/create/create_blendScene.py | 1 - openpype/hosts/blender/plugins/create/create_camera.py | 1 - openpype/hosts/blender/plugins/create/create_layout.py | 1 - openpype/hosts/blender/plugins/create/create_model.py | 1 - openpype/hosts/blender/plugins/create/create_pointcache.py | 1 - openpype/hosts/blender/plugins/create/create_render.py | 1 - openpype/hosts/blender/plugins/create/create_review.py | 1 - openpype/hosts/blender/plugins/create/create_rig.py | 1 - 10 files changed, 10 deletions(-) diff --git a/openpype/hosts/blender/plugins/create/create_action.py b/openpype/hosts/blender/plugins/create/create_action.py index f14023639f..0929778d78 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -9,7 +9,6 @@ class CreateAction(plugin.BaseCreator): """Action output for character rigs.""" identifier = "io.openpype.creators.blender.action" - name = "actionMain" label = "Action" family = "action" icon = "male" diff --git a/openpype/hosts/blender/plugins/create/create_animation.py b/openpype/hosts/blender/plugins/create/create_animation.py index b3d10090e3..3a91b2d5ff 100644 --- a/openpype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -7,7 +7,6 @@ class CreateAnimation(plugin.BaseCreator): """Animation output for character rigs.""" identifier = "io.openpype.creators.blender.animation" - name = "animationMain" label = "Animation" family = "animation" icon = "male" diff --git a/openpype/hosts/blender/plugins/create/create_blendScene.py b/openpype/hosts/blender/plugins/create/create_blendScene.py index c09261705f..e1026282c0 100644 --- a/openpype/hosts/blender/plugins/create/create_blendScene.py +++ b/openpype/hosts/blender/plugins/create/create_blendScene.py @@ -9,7 +9,6 @@ class CreateBlendScene(plugin.BaseCreator): """Generic group of assets.""" identifier = "io.openpype.creators.blender.blendscene" - name = "blendScene" label = "Blender Scene" family = "blendScene" icon = "cubes" diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index d624a7f05a..2e2e6cec22 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -10,7 +10,6 @@ class CreateCamera(plugin.BaseCreator): """Polygonal static geometry.""" identifier = "io.openpype.creators.blender.camera" - name = "cameraMain" label = "Camera" family = "camera" icon = "video-camera" diff --git a/openpype/hosts/blender/plugins/create/create_layout.py b/openpype/hosts/blender/plugins/create/create_layout.py index 93fb91a324..16d227e50e 100644 --- a/openpype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -9,7 +9,6 @@ class CreateLayout(plugin.BaseCreator): """Layout output for character rigs.""" identifier = "io.openpype.creators.blender.layout" - name = "layoutMain" label = "Layout" family = "layout" icon = "cubes" diff --git a/openpype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py index 8b5eaeb999..2f3f61728b 100644 --- a/openpype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -9,7 +9,6 @@ class CreateModel(plugin.BaseCreator): """Polygonal static geometry.""" identifier = "io.openpype.creators.blender.model" - name = "modelMain" label = "Model" family = "model" icon = "cube" diff --git a/openpype/hosts/blender/plugins/create/create_pointcache.py b/openpype/hosts/blender/plugins/create/create_pointcache.py index d80a1b07f0..b3329bcb3b 100644 --- a/openpype/hosts/blender/plugins/create/create_pointcache.py +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -7,7 +7,6 @@ class CreatePointcache(plugin.BaseCreator): """Polygonal static geometry.""" identifier = "io.openpype.creators.blender.pointcache" - name = "pointcacheMain" label = "Point Cache" family = "pointcache" icon = "gears" diff --git a/openpype/hosts/blender/plugins/create/create_render.py b/openpype/hosts/blender/plugins/create/create_render.py index bffa5696df..7fb3e5eb00 100644 --- a/openpype/hosts/blender/plugins/create/create_render.py +++ b/openpype/hosts/blender/plugins/create/create_render.py @@ -9,7 +9,6 @@ class CreateRenderlayer(plugin.BaseCreator): """Single baked camera.""" identifier = "io.openpype.creators.blender.render" - name = "renderingMain" label = "Render" family = "render" icon = "eye" diff --git a/openpype/hosts/blender/plugins/create/create_review.py b/openpype/hosts/blender/plugins/create/create_review.py index 3dd52395df..940bcbea22 100644 --- a/openpype/hosts/blender/plugins/create/create_review.py +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -7,7 +7,6 @@ class CreateReview(plugin.BaseCreator): """Single baked camera.""" identifier = "io.openpype.creators.blender.review" - name = "reviewDefault" label = "Review" family = "review" icon = "video-camera" diff --git a/openpype/hosts/blender/plugins/create/create_rig.py b/openpype/hosts/blender/plugins/create/create_rig.py index 8228442249..d63b8d56ff 100644 --- a/openpype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -9,7 +9,6 @@ class CreateRig(plugin.BaseCreator): """Artist-friendly rig with controls to direct motion.""" identifier = "io.openpype.creators.blender.rig" - name = "rigMain" label = "Rig" family = "rig" icon = "wheelchair" From d077b7526f289a992bb89f53e589f8909950e556 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 16:57:25 +0100 Subject: [PATCH 081/101] Add legacy instance conversion --- .../blender/plugins/create/convert_legacy.py | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 openpype/hosts/blender/plugins/create/convert_legacy.py diff --git a/openpype/hosts/blender/plugins/create/convert_legacy.py b/openpype/hosts/blender/plugins/create/convert_legacy.py new file mode 100644 index 0000000000..f05a6b1f5a --- /dev/null +++ b/openpype/hosts/blender/plugins/create/convert_legacy.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +"""Converter for legacy Houdini subsets.""" +from openpype.pipeline.create.creator_plugins import SubsetConvertorPlugin +from openpype.hosts.blender.api.lib import imprint + + +class BlenderLegacyConvertor(SubsetConvertorPlugin): + """Find and convert any legacy subsets in the scene. + + This Converter will find all legacy subsets in the scene and will + transform them to the current system. Since the old subsets doesn't + retain any information about their original creators, the only mapping + we can do is based on their families. + + Its limitation is that you can have multiple creators creating subset + of the same family and there is no way to handle it. This code should + nevertheless cover all creators that came with OpenPype. + + """ + identifier = "io.openpype.creators.blender.legacy" + family_to_id = { + "action": "io.openpype.creators.blender.action", + "camera": "io.openpype.creators.blender.camera", + "animation": "io.openpype.creators.blender.animation", + "blendScene": "io.openpype.creators.blender.blendscene", + "layout": "io.openpype.creators.blender.layout", + "model": "io.openpype.creators.blender.model", + "pointcache": "io.openpype.creators.blender.pointcache", + "render": "io.openpype.creators.blender.render", + "review": "io.openpype.creators.blender.review", + "rig": "io.openpype.creators.blender.rig", + } + + def __init__(self, *args, **kwargs): + super(BlenderLegacyConvertor, self).__init__(*args, **kwargs) + self.legacy_subsets = {} + + def find_instances(self): + """Find legacy subsets in the scene. + + Legacy subsets are the ones that doesn't have `creator_identifier` + parameter on them. + + This is using cached entries done in + :py:meth:`~BaseCreator.cache_subsets()` + + """ + self.legacy_subsets = self.collection_shared_data.get( + "blender_cached_legacy_subsets") + if not self.legacy_subsets: + return + self.add_convertor_item( + "Found {} incompatible subset{}".format( + len(self.legacy_subsets), + "s" if len(self.legacy_subsets) > 1 else "" + ) + ) + + def convert(self): + """Convert all legacy subsets to current. + + It is enough to add `creator_identifier` and `instance_node`. + + """ + if not self.legacy_subsets: + return + + for family, instance_nodes in self.legacy_subsets.items(): + if family in self.family_to_id: + for instance_node in instance_nodes: + creator_identifier = self.family_to_id[family] + self.log.info( + "Converting {} to {}".format(instance_node.name, + creator_identifier) + ) + imprint(instance_node, data={ + "creator_identifier": creator_identifier + }) From 1fbe1b3163aa296bb9a007a3c95e61b24b55306d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 17:11:46 +0100 Subject: [PATCH 082/101] Refactor load layout to create new-style creator animation instances --- .../hosts/blender/plugins/load/load_blend.py | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/blender/plugins/load/load_blend.py b/openpype/hosts/blender/plugins/load/load_blend.py index 84e181cd6c..8b1af5a0da 100644 --- a/openpype/hosts/blender/plugins/load/load_blend.py +++ b/openpype/hosts/blender/plugins/load/load_blend.py @@ -4,11 +4,11 @@ from pathlib import Path import bpy from openpype.pipeline import ( - legacy_create, get_representation_path, AVALON_CONTAINER_ID, + registered_host ) -from openpype.pipeline.create import get_legacy_creator_by_name +from openpype.pipeline.create import CreateContext from openpype.hosts.blender.api import plugin from openpype.hosts.blender.api.lib import imprint from openpype.hosts.blender.api.pipeline import ( @@ -57,20 +57,21 @@ class BlendLoader(plugin.AssetLoader): obj.get(AVALON_PROPERTY).get('family') == 'rig' ) ] + if not rigs: + return + + # Create animation instances for each rig + creator_identifier = "io.openpype.creators.blender.animation" + host = registered_host() + create_context = CreateContext(host) for rig in rigs: - creator_plugin = get_legacy_creator_by_name("CreateAnimation") - # TODO: Refactor legacy create usage to new style creators - legacy_create( - creator_plugin, - name=rig.name.split(':')[-1] + "_animation", - asset=asset, - options={ - "useSelection": False, + create_context.create( + creator_identifier=creator_identifier, + variant=rig.name.split(':')[-1], + pre_create_data={ + "use_selection": False, "asset_group": rig - }, - data={ - "dependencies": representation } ) From b04ae30b821c8a26d68eedf62864100e2fd04989 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 17:13:46 +0100 Subject: [PATCH 083/101] Collect animation members --- openpype/hosts/blender/plugins/publish/collect_instance.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/collect_instance.py b/openpype/hosts/blender/plugins/publish/collect_instance.py index 72c8f13362..3d4e634e9e 100644 --- a/openpype/hosts/blender/plugins/publish/collect_instance.py +++ b/openpype/hosts/blender/plugins/publish/collect_instance.py @@ -11,8 +11,8 @@ class CollectBlenderInstanceData(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder hosts = ["blender"] - families = ["model", "pointcache", "rig", "camera", "layout", "blendScene", - "camera"] + families = ["model", "pointcache", "animation", "rig", "camera", "layout", + "blendScene"] label = "Collect Instance" def process(self, instance): From d1c3eb83cfe1d7bb485d88914b42cdc0640aba68 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 17:17:35 +0100 Subject: [PATCH 084/101] Remove todo --- openpype/hosts/blender/plugins/publish/collect_instance.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/blender/plugins/publish/collect_instance.py b/openpype/hosts/blender/plugins/publish/collect_instance.py index 3d4e634e9e..4685472213 100644 --- a/openpype/hosts/blender/plugins/publish/collect_instance.py +++ b/openpype/hosts/blender/plugins/publish/collect_instance.py @@ -25,7 +25,6 @@ class CollectBlenderInstanceData(pyblish.api.InstancePlugin): members.extend(instance_node.children) # Special case for animation instances, include armatures - # TODO: Does this still work as intended? if instance.data["family"] == "animation": for obj in instance_node.objects: if obj.type == 'EMPTY' and obj.get(AVALON_PROPERTY): From 78bcd7ce74a67299c3a389209210f200a22e3654 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 17:37:53 +0100 Subject: [PATCH 085/101] Pull window to front when menu entries are clicked; no more searching for minimized windows --- openpype/hosts/blender/api/ops.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/openpype/hosts/blender/api/ops.py b/openpype/hosts/blender/api/ops.py index 408e38e6cd..c1403d44b6 100644 --- a/openpype/hosts/blender/api/ops.py +++ b/openpype/hosts/blender/api/ops.py @@ -246,8 +246,24 @@ class LaunchQtApp(bpy.types.Operator): self.before_window_show() + def pull_to_front(window): + """Pull window forward to screen. + + If Window is minimized this will un-minimize, then it can be raised + and activated to the front. + """ + window.setWindowState( + (window.windowState() & ~QtCore.Qt.WindowMinimized) | + QtCore.Qt.WindowActive + ) + window.raise_() + window.activateWindow() + if isinstance(self._window, ModuleType): self._window.show() + pull_to_front(self.window) + + # Pull window to the front window = None if hasattr(self._window, "window"): window = self._window.window @@ -262,6 +278,7 @@ class LaunchQtApp(bpy.types.Operator): on_top_flags = origin_flags | QtCore.Qt.WindowStaysOnTopHint self._window.setWindowFlags(on_top_flags) self._window.show() + pull_to_front(self.window) # if on_top_flags != origin_flags: # self._window.setWindowFlags(origin_flags) From d42d57133b4981b6c9780ee0e2167daf3be42308 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 17:39:22 +0100 Subject: [PATCH 086/101] Fix typo --- openpype/hosts/blender/api/ops.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/blender/api/ops.py b/openpype/hosts/blender/api/ops.py index c1403d44b6..f4d96e563a 100644 --- a/openpype/hosts/blender/api/ops.py +++ b/openpype/hosts/blender/api/ops.py @@ -261,7 +261,7 @@ class LaunchQtApp(bpy.types.Operator): if isinstance(self._window, ModuleType): self._window.show() - pull_to_front(self.window) + pull_to_front(self._window) # Pull window to the front window = None @@ -278,7 +278,7 @@ class LaunchQtApp(bpy.types.Operator): on_top_flags = origin_flags | QtCore.Qt.WindowStaysOnTopHint self._window.setWindowFlags(on_top_flags) self._window.show() - pull_to_front(self.window) + pull_to_front(self._window) # if on_top_flags != origin_flags: # self._window.setWindowFlags(origin_flags) From 6398dbf1c46dd3dd921995886703a64146a0cad3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 17:49:13 +0100 Subject: [PATCH 087/101] Remove unused properties --- openpype/hosts/blender/api/pipeline.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openpype/hosts/blender/api/pipeline.py b/openpype/hosts/blender/api/pipeline.py index ee43108594..b386dd49d3 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -6,7 +6,7 @@ from typing import Callable, Dict, Iterator, List, Optional import bpy from . import lib -from . import ops, properties +from . import ops import pyblish.api @@ -181,7 +181,6 @@ def install(): if not IS_HEADLESS: ops.register() - properties.register() def uninstall(): @@ -196,7 +195,6 @@ def uninstall(): if not IS_HEADLESS: ops.unregister() - properties.unregister() def show_message(title, message): From 31f24f8cc2436eda1930c6affcd92d376d8b13cf Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 17:50:03 +0100 Subject: [PATCH 088/101] Remove unused properties lib --- openpype/hosts/blender/api/properties.py | 29 ------------------------ 1 file changed, 29 deletions(-) delete mode 100644 openpype/hosts/blender/api/properties.py diff --git a/openpype/hosts/blender/api/properties.py b/openpype/hosts/blender/api/properties.py deleted file mode 100644 index 44ca7582b9..0000000000 --- a/openpype/hosts/blender/api/properties.py +++ /dev/null @@ -1,29 +0,0 @@ -import bpy -from bpy.utils import register_classes_factory - - -class OpenpypeContext(bpy.types.PropertyGroup): - pass - - -classes = [] # [OpenpypeContext] - - -factory_register, factory_unregister = register_classes_factory(classes) - - -def register(): - """Register the properties.""" - factory_register() - - bpy.types.Scene.openpype_context = {} - # bpy.types.Scene.openpype_context = bpy.props.CollectionProperty( - # name="OpenPype Context", type=OpenpypeContext, options={"HIDDEN"} - # ) - - -def unregister(): - """Unregister the properties.""" - factory_unregister() - - del bpy.types.Scene.openpype_context From 45699444ef8d4e466d3fed4652d26b4d1c55033a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 18:03:08 +0100 Subject: [PATCH 089/101] Fix setting data - `current_instance` is not a `dict` --- .../hosts/blender/plugins/create/create_workfile.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/blender/plugins/create/create_workfile.py b/openpype/hosts/blender/plugins/create/create_workfile.py index ffd423ad74..28c21c5811 100644 --- a/openpype/hosts/blender/plugins/create/create_workfile.py +++ b/openpype/hosts/blender/plugins/create/create_workfile.py @@ -74,13 +74,9 @@ class CreateWorkfile(BaseCreator, AutoCreator): task_name, task_name, asset_doc, project_name, host_name ) - current_instance.update( - { - "asset": asset_name, - "task": task_name, - "subset": subset_name, - } - ) + current_instance["asset"] = asset_name + current_instance["task"] = task_name + current_instance["subset"] = subset_name def collect_instances(self): From 478afb7f4899189dab0e3a99b0c83dd8e01f7d67 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 18:04:57 +0100 Subject: [PATCH 090/101] Cosmetics; match code of other hosts more --- openpype/hosts/blender/plugins/create/create_workfile.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/blender/plugins/create/create_workfile.py b/openpype/hosts/blender/plugins/create/create_workfile.py index 28c21c5811..9245434766 100644 --- a/openpype/hosts/blender/plugins/create/create_workfile.py +++ b/openpype/hosts/blender/plugins/create/create_workfile.py @@ -73,7 +73,6 @@ class CreateWorkfile(BaseCreator, AutoCreator): subset_name = self.get_subset_name( task_name, task_name, asset_doc, project_name, host_name ) - current_instance["asset"] = asset_name current_instance["task"] = task_name current_instance["subset"] = subset_name From d148671d02563ce11413e7cb4fd010f9fb3e41f1 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2023 20:56:00 +0100 Subject: [PATCH 091/101] Fix finding ARMATURE objects in asset group Collection --- .../plugins/publish/extract_fbx_animation.py | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py b/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py index 6fbcdc63c3..712fbb2d14 100644 --- a/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py @@ -33,8 +33,27 @@ class ExtractAnimationFBX( asset_group = instance.data["transientData"]["instance_node"] - armature = [ - obj for obj in asset_group.children if obj.type == 'ARMATURE'][0] + # Get objects in this collection (but not in children collections) + # and for those objects include the children hierarchy + # TODO: Would it make more sense for the Collect Instance collector + # to also always retrieve all the children? + objects = set(asset_group.objects) + for obj in list(objects): + objects.update(obj.children_recursive) + + # Find all armatures among the objects, assume to find only one + armatures = [obj for obj in objects if obj.type == "ARMATURE"] + if not armatures: + raise RuntimeError( + f"Unable to find ARMATURE in collection: " + f"{asset_group.name}" + ) + elif len(armatures) > 1: + self.log.warning( + "Found more than one ARMATURE, using " + f"only first of: {armatures}" + ) + armature = armatures[0] object_action_pairs = [] original_actions = [] @@ -43,9 +62,6 @@ class ExtractAnimationFBX( ending_frames = [] # For each armature, we make a copy of the current action - curr_action = None - copy_action = None - if armature.animation_data and armature.animation_data.action: curr_action = armature.animation_data.action copy_action = curr_action.copy() @@ -55,7 +71,10 @@ class ExtractAnimationFBX( starting_frames.append(curr_frame_range[0]) ending_frames.append(curr_frame_range[1]) else: - self.log.info("Object has no animation.") + self.log.info( + f"Armature '{armature.name}' has no animation, " + f"skipping FBX animation extraction for {instance}." + ) return asset_group_name = asset_group.name From 72b1e759e7392fcc8f83b4f73ba3f7c56376eb8e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 15 Nov 2023 03:26:29 +0000 Subject: [PATCH 092/101] 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 e377773007..86e3638ffe 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 093/101] Enhancement: Some publish logs cosmetics (#5917) * Write logs in sorted order for better logs * Typo in log message * More descriptive log * Skip logging profile since `filter_profiles` already logs "Profile selected: {profile}" * Fix grammar * Fix grammar * Improve logged information --- .../plugins/load/load_reference_image.py | 2 +- .../action_tranfer_hierarchical_values.py | 2 +- .../event_next_task_update.py | 2 +- .../event_sync_to_avalon.py | 2 +- .../action_component_open.py | 2 +- .../action_delete_asset.py | 2 +- .../event_handlers_user/action_delivery.py | 2 +- .../event_handlers_user/action_job_killer.py | 2 +- openpype/plugins/publish/cleanup_explicit.py | 26 +++++++++++-------- .../plugins/publish/collect_rendered_files.py | 5 ++-- openpype/plugins/publish/extract_burnin.py | 4 +-- .../publish/extract_color_transcode.py | 1 - openpype/plugins/publish/extract_review.py | 2 +- .../plugins/publish/integrate_thumbnail.py | 2 +- .../publish/integrate_thumbnail_ayon.py | 2 +- .../settings/entities/dict_conditional.py | 2 +- .../entities/dict_immutable_keys_entity.py | 2 +- .../entities/dict_mutable_keys_entity.py | 2 +- openpype/settings/entities/item_entities.py | 2 +- openpype/settings/entities/list_entity.py | 2 +- openpype/settings/entities/root_entities.py | 2 +- 21 files changed, 36 insertions(+), 34 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/load/load_reference_image.py b/openpype/hosts/tvpaint/plugins/load/load_reference_image.py index 3707ef97aa..53061c6885 100644 --- a/openpype/hosts/tvpaint/plugins/load/load_reference_image.py +++ b/openpype/hosts/tvpaint/plugins/load/load_reference_image.py @@ -190,7 +190,7 @@ class LoadImage(plugin.Loader): if pop_idx is None: self.log.warning( - "Didn't found container in workfile containers. {}".format( + "Didn't find container in workfile containers. {}".format( container ) ) diff --git a/openpype/modules/ftrack/event_handlers_server/action_tranfer_hierarchical_values.py b/openpype/modules/ftrack/event_handlers_server/action_tranfer_hierarchical_values.py index f6899843a3..1d73318f6e 100644 --- a/openpype/modules/ftrack/event_handlers_server/action_tranfer_hierarchical_values.py +++ b/openpype/modules/ftrack/event_handlers_server/action_tranfer_hierarchical_values.py @@ -66,7 +66,7 @@ class TransferHierarchicalValues(ServerAction): "items": [{ "type": "label", "value": ( - "Didn't found custom attributes" + "Didn't find custom attributes" " that can be transferred." ) }] diff --git a/openpype/modules/ftrack/event_handlers_server/event_next_task_update.py b/openpype/modules/ftrack/event_handlers_server/event_next_task_update.py index 07a8ff433e..8632f038b8 100644 --- a/openpype/modules/ftrack/event_handlers_server/event_next_task_update.py +++ b/openpype/modules/ftrack/event_handlers_server/event_next_task_update.py @@ -257,7 +257,7 @@ class NextTaskUpdate(BaseEvent): new_task_name = mapping.get(old_status_name) if not new_task_name: self.log.debug( - "Didn't found mapping for status \"{}\".".format( + "Didn't find mapping for status \"{}\".".format( task_status["name"] ) ) diff --git a/openpype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py b/openpype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py index 0aa0b9f9f5..d4dc53b655 100644 --- a/openpype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py +++ b/openpype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py @@ -387,7 +387,7 @@ class SyncToAvalonEvent(BaseEvent): if not data: # TODO logging self.log.warning( - "Didn't found entity by key/value \"{}\" / \"{}\"".format( + "Didn't find entity by key/value \"{}\" / \"{}\"".format( key, value ) ) diff --git a/openpype/modules/ftrack/event_handlers_user/action_component_open.py b/openpype/modules/ftrack/event_handlers_user/action_component_open.py index c731713c10..0efade9d8f 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_component_open.py +++ b/openpype/modules/ftrack/event_handlers_user/action_component_open.py @@ -51,7 +51,7 @@ class ComponentOpen(BaseAction): else: return { 'success': False, - 'message': "Didn't found file: " + fpath + 'message': "Didn't find file: " + fpath } return { diff --git a/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py b/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py index 72a5efbcfe..e1df8e1537 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py +++ b/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py @@ -169,7 +169,7 @@ class DeleteAssetSubset(BaseAction): return { "success": True, "message": ( - "Didn't found entities in avalon." + "Didn't find entities in avalon." " You can use Ftrack's Delete button for the selection." ) } diff --git a/openpype/modules/ftrack/event_handlers_user/action_delivery.py b/openpype/modules/ftrack/event_handlers_user/action_delivery.py index 559de3a24d..c198389b98 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_delivery.py +++ b/openpype/modules/ftrack/event_handlers_user/action_delivery.py @@ -61,7 +61,7 @@ class Delivery(BaseAction): return { "success": False, "message": ( - "Didn't found project \"{}\" in avalon." + "Didn't find project \"{}\" in avalon." ).format(project_name) } diff --git a/openpype/modules/ftrack/event_handlers_user/action_job_killer.py b/openpype/modules/ftrack/event_handlers_user/action_job_killer.py index dd68c75f84..250670f016 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_job_killer.py +++ b/openpype/modules/ftrack/event_handlers_user/action_job_killer.py @@ -29,7 +29,7 @@ class JobKiller(BaseAction): if not jobs: return { "success": True, - "message": "Didn't found any running jobs" + "message": "Didn't find any running jobs" } # Collect user ids from jobs diff --git a/openpype/plugins/publish/cleanup_explicit.py b/openpype/plugins/publish/cleanup_explicit.py index 983c9223c6..cc6b99e8f8 100644 --- a/openpype/plugins/publish/cleanup_explicit.py +++ b/openpype/plugins/publish/cleanup_explicit.py @@ -58,21 +58,21 @@ class ExplicitCleanUp(pyblish.api.ContextPlugin): # Store failed paths with exception failed = [] # Store removed filepaths for logging - succeded_files = set() + succeeded_files = set() # Remove file by file for filepath in filepaths: try: os.remove(filepath) - succeded_files.add(filepath) + succeeded_files.add(filepath) except Exception as exc: failed.append((filepath, exc)) - if succeded_files: + if succeeded_files: self.log.info( - "Removed files:\n{}".format("\n".join(succeded_files)) + "Removed files:\n{}".format("\n".join(sorted(succeeded_files))) ) - # Delete folders with it's content + # Delete folders with its content succeeded = set() for dirpath in dirpaths: # Check if directory still exists @@ -87,17 +87,21 @@ class ExplicitCleanUp(pyblish.api.ContextPlugin): if succeeded: self.log.info( - "Removed directories:\n{}".format("\n".join(succeeded)) + "Removed directories:\n{}".format( + "\n".join(sorted(succeeded)) + ) ) - # Prepare lines for report of failed removements + # Prepare lines for report of failed removals lines = [] for filepath, exc in failed: lines.append("{}: {}".format(filepath, str(exc))) if lines: self.log.warning( - "Failed to remove filepaths:\n{}".format("\n".join(lines)) + "Failed to remove filepaths:\n{}".format( + "\n".join(sorted(lines)) + ) ) def _remove_empty_dirs(self, empty_dirpaths): @@ -134,8 +138,8 @@ class ExplicitCleanUp(pyblish.api.ContextPlugin): if to_skip_dirpaths: self.log.debug( - "Skipped directories because contain files:\n{}".format( - "\n".join(to_skip_dirpaths) + "Skipped directories because they contain files:\n{}".format( + "\n".join(sorted(to_skip_dirpaths)) ) ) @@ -147,6 +151,6 @@ class ExplicitCleanUp(pyblish.api.ContextPlugin): if to_delete_dirpaths: self.log.debug( "Deleted empty directories:\n{}".format( - "\n".join(to_delete_dirpaths) + "\n".join(sorted(to_delete_dirpaths)) ) ) diff --git a/openpype/plugins/publish/collect_rendered_files.py b/openpype/plugins/publish/collect_rendered_files.py index a249b3acda..6160b4f5c8 100644 --- a/openpype/plugins/publish/collect_rendered_files.py +++ b/openpype/plugins/publish/collect_rendered_files.py @@ -54,6 +54,8 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): staging_dir = data_object.get("stagingDir") if staging_dir: data_object["stagingDir"] = anatomy.fill_root(staging_dir) + self.log.debug("Filling stagingDir with root to: %s", + data_object["stagingDir"]) def _process_path(self, data, anatomy): """Process data of a single JSON publish metadata file. @@ -108,7 +110,6 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): instance = self._context.create_instance( instance_data.get("subset") ) - self.log.debug("Filling stagingDir...") self._fill_staging_dir(instance_data, anatomy) instance.data.update(instance_data) @@ -161,7 +162,7 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): anatomy.project_name )) - self.log.debug("anatomy: {}".format(anatomy.roots)) + self.log.debug("Anatomy roots: {}".format(anatomy.roots)) try: session_is_set = False for path in paths: diff --git a/openpype/plugins/publish/extract_burnin.py b/openpype/plugins/publish/extract_burnin.py index dc8aab6ce4..9a978ed286 100644 --- a/openpype/plugins/publish/extract_burnin.py +++ b/openpype/plugins/publish/extract_burnin.py @@ -171,8 +171,6 @@ class ExtractBurnin(publish.Extractor): ).format(host_name, family, task_name, task_type, subset)) return - self.log.debug("profile: {}".format(profile)) - # Pre-filter burnin definitions by instance families burnin_defs = self.filter_burnins_defs(profile, instance) if not burnin_defs: @@ -450,7 +448,7 @@ class ExtractBurnin(publish.Extractor): filling burnin strings. `temp_data` are for repre pre-process preparation. """ - self.log.debug("Prepring basic data for burnins") + self.log.debug("Preparing basic data for burnins") context = instance.context version = instance.data.get("version") diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index dbf1b6c8a6..faacb7af2e 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -326,7 +326,6 @@ class ExtractOIIOTranscode(publish.Extractor): " | Task type \"{}\" | Subset \"{}\" " ).format(host_name, family, task_name, task_type, subset)) - self.log.debug("profile: {}".format(profile)) return profile def _repre_is_valid(self, repre): diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index db8a030dfa..cd0f78530a 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -143,7 +143,7 @@ class ExtractReview(pyblish.api.InstancePlugin): custom_tags = repre.get("custom_tags") if "review" not in tags: self.log.debug(( - "Repre: {} - Didn't found \"review\" in tags. Skipping" + "Repre: {} - Didn't find \"review\" in tags. Skipping" ).format(repre_name)) continue diff --git a/openpype/plugins/publish/integrate_thumbnail.py b/openpype/plugins/publish/integrate_thumbnail.py index 0c12255d38..b154940469 100644 --- a/openpype/plugins/publish/integrate_thumbnail.py +++ b/openpype/plugins/publish/integrate_thumbnail.py @@ -200,7 +200,7 @@ class IntegrateThumbnails(pyblish.api.ContextPlugin): if thumb_repre_doc is None: self.log.debug( - "There is not representation with name \"thumbnail\"" + "There is no representation with name \"thumbnail\"" ) return None diff --git a/openpype/plugins/publish/integrate_thumbnail_ayon.py b/openpype/plugins/publish/integrate_thumbnail_ayon.py index cf05327ce8..f9b48eebec 100644 --- a/openpype/plugins/publish/integrate_thumbnail_ayon.py +++ b/openpype/plugins/publish/integrate_thumbnail_ayon.py @@ -137,7 +137,7 @@ class IntegrateThumbnailsAYON(pyblish.api.ContextPlugin): if thumb_repre_doc is None: self.log.debug( - "There is not representation with name \"thumbnail\"" + "There is no representation with name \"thumbnail\"" ) return None diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 88d2dc8296..f26d86e6df 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -352,7 +352,7 @@ class DictConditionalEntity(ItemEntity): break if result_key is None: - raise ValueError("Didn't found child {}".format(child_obj)) + raise ValueError("Didn't find child {}".format(child_obj)) return "/".join([self.path, result_key]) diff --git a/openpype/settings/entities/dict_immutable_keys_entity.py b/openpype/settings/entities/dict_immutable_keys_entity.py index 0209681e95..a25c22aa19 100644 --- a/openpype/settings/entities/dict_immutable_keys_entity.py +++ b/openpype/settings/entities/dict_immutable_keys_entity.py @@ -232,7 +232,7 @@ class DictImmutableKeysEntity(ItemEntity): break if result_key is None: - raise ValueError("Didn't found child {}".format(child_obj)) + raise ValueError("Didn't find child {}".format(child_obj)) return "/".join([self.path, result_key]) diff --git a/openpype/settings/entities/dict_mutable_keys_entity.py b/openpype/settings/entities/dict_mutable_keys_entity.py index e6d332b9ad..c11a7cf059 100644 --- a/openpype/settings/entities/dict_mutable_keys_entity.py +++ b/openpype/settings/entities/dict_mutable_keys_entity.py @@ -284,7 +284,7 @@ class DictMutableKeysEntity(EndpointEntity): break if result_key is None: - raise ValueError("Didn't found child {}".format(child_obj)) + raise ValueError("Didn't find child {}".format(child_obj)) return "/".join([self.path, result_key]) diff --git a/openpype/settings/entities/item_entities.py b/openpype/settings/entities/item_entities.py index 3b756e4ede..c888cf3b78 100644 --- a/openpype/settings/entities/item_entities.py +++ b/openpype/settings/entities/item_entities.py @@ -295,7 +295,7 @@ class ListStrictEntity(ItemEntity): break if result_idx is None: - raise ValueError("Didn't found child {}".format(child_obj)) + raise ValueError("Didn't find child {}".format(child_obj)) return "/".join([self.path, str(result_idx)]) diff --git a/openpype/settings/entities/list_entity.py b/openpype/settings/entities/list_entity.py index 5d6a64b3ea..d9a18e0177 100644 --- a/openpype/settings/entities/list_entity.py +++ b/openpype/settings/entities/list_entity.py @@ -258,7 +258,7 @@ class ListEntity(EndpointEntity): break if result_idx is None: - raise ValueError("Didn't found child {}".format(child_obj)) + raise ValueError("Didn't find child {}".format(child_obj)) return "/".join([self.path, str(result_idx)]) diff --git a/openpype/settings/entities/root_entities.py b/openpype/settings/entities/root_entities.py index f2e24fb522..bd617c6b7c 100644 --- a/openpype/settings/entities/root_entities.py +++ b/openpype/settings/entities/root_entities.py @@ -270,7 +270,7 @@ class RootEntity(BaseItemEntity): for key, _child_entity in self.non_gui_children.items(): if _child_entity is child_entity: return key - raise ValueError("Didn't found child {}".format(child_entity)) + raise ValueError("Didn't find child {}".format(child_entity)) @property def value(self): From 4d48a6981bb77c93c7f99e386bb6a82ab4603239 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 15 Nov 2023 11:25:47 +0100 Subject: [PATCH 094/101] Bugfix: Ayon Deadline env vars + error message on no executable found (#5815) * Fix ingesting env vars correctly for `DeadlinePlugin.RunProcess` * Fix error message reporting `;` between each character of the string * Cosmetics * Use `SetEnvironmentVariable` instead of `SetProcessEnvironmentVariable` because it's a Simple Plugin See: https://docs.thinkboxsoftware.com/products/deadline/10.1/1_User%20Manual/manual/environment.html#job-rendering --- .../deadline/repository/custom/plugins/Ayon/Ayon.py | 12 ++++++------ .../repository/custom/plugins/GlobalJobPreLoad.py | 3 +++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/openpype/modules/deadline/repository/custom/plugins/Ayon/Ayon.py b/openpype/modules/deadline/repository/custom/plugins/Ayon/Ayon.py index 2c55e7c951..a1f752605d 100644 --- a/openpype/modules/deadline/repository/custom/plugins/Ayon/Ayon.py +++ b/openpype/modules/deadline/repository/custom/plugins/Ayon/Ayon.py @@ -85,7 +85,7 @@ class AyonDeadlinePlugin(DeadlinePlugin): } for env, val in environment.items(): - self.SetProcessEnvironmentVariable(env, val) + self.SetEnvironmentVariable(env, val) exe_list = self.GetConfigEntry("AyonExecutable") # clean '\ ' for MacOS pasting @@ -101,11 +101,11 @@ class AyonDeadlinePlugin(DeadlinePlugin): if exe == "": self.FailRender( - "Ayon executable was not found " + - "in the semicolon separated list " + - "\"" + ";".join(exe_list) + "\". " + - "The path to the render executable can be configured " + - "from the Plugin Configuration in the Deadline Monitor.") + "Ayon executable was not found in the semicolon separated " + "list: \"{}\". The path to the render executable can be " + "configured from the Plugin Configuration in the Deadline " + "Monitor.".format(exe_list) + ) return exe def RenderArgument(self): diff --git a/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py b/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py index e9b81369ca..642608f991 100644 --- a/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py +++ b/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py @@ -495,7 +495,10 @@ def inject_ayon_environment(deadlinePlugin): "AYON_BUNDLE_NAME": ayon_bundle_name, } for env, val in environment.items(): + # Add the env var for the Render Plugin that is about to render deadlinePlugin.SetEnvironmentVariable(env, val) + # Add the env var for current calls to `DeadlinePlugin.RunProcess` + deadlinePlugin.SetProcessEnvironmentVariable(env, val) args_str = subprocess.list2cmdline(args) print(">>> Executing: {} {}".format(exe, args_str)) From 480c37533b8ce0e542e7b052ae28ff4d7a775acb Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 15 Nov 2023 12:43:19 +0100 Subject: [PATCH 095/101] Fix extract animation - `asset_group` is a Collection and can't be selected, and thus not exported from so we now take the 'root' node in that collection --- .../plugins/publish/extract_fbx_animation.py | 49 +++++++++++++++++-- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py b/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py index 712fbb2d14..a705345edb 100644 --- a/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py @@ -10,6 +10,37 @@ from openpype.hosts.blender.api import plugin from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY +def get_all_parents(obj): + """Get all recursive parents of object""" + result = [] + while True: + obj = obj.parent + if not obj: + break + result.append(obj) + return result + + +def get_highest_root(objects): + # Get the highest object that is also in the collection + included_objects = {obj.name_full for obj in objects} + num_parents_to_obj = {} + for obj in objects: + if isinstance(obj, bpy.types.Object): + parents = get_all_parents(obj) + # included parents + parents = [parent for parent in parents if + parent.name_full in included_objects] + if not parents: + # A node without parents must be a highest root + return obj + + num_parents_to_obj.setdefault(len(parents), obj) + + minimum_parent = min(num_parents_to_obj) + return num_parents_to_obj[minimum_parent] + + class ExtractAnimationFBX( publish.Extractor, publish.OptionalPyblishPluginMixin, @@ -38,6 +69,11 @@ class ExtractAnimationFBX( # TODO: Would it make more sense for the Collect Instance collector # to also always retrieve all the children? objects = set(asset_group.objects) + + # From the direct children of the collection find the 'root' node + # that we want to export - it is the 'highest' node in a hierarchy + root = get_highest_root(objects) + for obj in list(objects): objects.update(obj.children_recursive) @@ -78,8 +114,13 @@ class ExtractAnimationFBX( return asset_group_name = asset_group.name - asset_group.name = asset_group.get(AVALON_PROPERTY).get("asset_name") + asset_name = asset_group.get(AVALON_PROPERTY).get("asset_name") + if asset_name: + # Rename for the export; this data is only present when loaded + # from a JSON Layout (layout family) + asset_group.name = asset_name + # Remove : from the armature name for the export armature_name = armature.name original_name = armature_name.split(':')[1] armature.name = original_name @@ -102,13 +143,13 @@ class ExtractAnimationFBX( for obj in bpy.data.objects: obj.select_set(False) - asset_group.select_set(True) + root.select_set(True) armature.select_set(True) fbx_filename = f"{instance.name}_{armature.name}.fbx" filepath = os.path.join(stagingdir, fbx_filename) override = plugin.create_blender_context( - active=asset_group, selected=[asset_group, armature]) + active=root, selected=[root, armature]) bpy.ops.export_scene.fbx( override, filepath=filepath, @@ -122,7 +163,7 @@ class ExtractAnimationFBX( ) armature.name = armature_name asset_group.name = asset_group_name - asset_group.select_set(False) + root.select_set(True) armature.select_set(False) # We delete the baked action and set the original one back From dd54866fe0925d606cb10be0b70d42e50cae2ad7 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 15 Nov 2023 13:08:59 +0100 Subject: [PATCH 096/101] Rename instance node if asset or subset changed to match with new name --- openpype/hosts/blender/api/plugin.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 5a0026d862..7ac12b5549 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -282,11 +282,26 @@ class BaseCreator(Creator): Args: update_list(List[UpdateData]): Changed instances and their changes, as a list of tuples.""" - for created_instance, _changes in update_list: + for created_instance, changes in update_list: data = created_instance.data_to_store() node = created_instance.transient_data["instance_node"] - if node: - imprint(node, data) + if not node: + # We can't update if we don't know the node + self.log.error( + f"Unable to update instance {created_instance} " + f"without instance node." + ) + return + + # Rename the instance node in the scene if subset or asset changed + if ( + "subset" in changes.changed_keys + or "asset" in changes.changed_keys + ): + name = asset_name(asset=data["asset"], subset=data["subset"]) + node.name = name + + imprint(node, data) def remove_instances(self, instances: List[CreatedInstance]): From 5d10f7f2dd0e7668a14ff7630ddf97b85d42e0cb Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 15 Nov 2023 13:15:50 +0100 Subject: [PATCH 097/101] Remove global `RENDER_ATTRS`, refactor to a `RenderSettings.get_padding_attr` method (#5801) - Partial cleanup extracted from #3880 --- openpype/hosts/maya/api/lib.py | 13 ------------- openpype/hosts/maya/api/lib_rendersettings.py | 8 ++++++++ .../plugins/publish/validate_rendersettings.py | 17 +++++++---------- .../plugins/publish/submit_maya_muster.py | 5 ++--- 4 files changed, 17 insertions(+), 26 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 7c49c837e9..2ecaf87fce 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -62,19 +62,6 @@ SHAPE_ATTRS = {"castsShadows", "doubleSided", "opposite"} -RENDER_ATTRS = {"vray": { - "node": "vraySettings", - "prefix": "fileNamePrefix", - "padding": "fileNamePadding", - "ext": "imageFormatStr" -}, - "default": { - "node": "defaultRenderGlobals", - "prefix": "imageFilePrefix", - "padding": "extensionPadding" -} -} - DEFAULT_MATRIX = [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, diff --git a/openpype/hosts/maya/api/lib_rendersettings.py b/openpype/hosts/maya/api/lib_rendersettings.py index 20264c2cdf..8b57c2e481 100644 --- a/openpype/hosts/maya/api/lib_rendersettings.py +++ b/openpype/hosts/maya/api/lib_rendersettings.py @@ -33,6 +33,14 @@ class RenderSettings(object): def get_image_prefix_attr(cls, renderer): return cls._image_prefix_nodes[renderer] + @staticmethod + def get_padding_attr(renderer): + """Return attribute for renderer that defines frame padding amount""" + if renderer == "vray": + return "vraySettings.fileNamePadding" + else: + return "defaultRenderGlobals.extensionPadding" + def __init__(self, project_settings=None): if not project_settings: project_settings = get_project_settings( diff --git a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py index dccb4ade78..3409b4ec91 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py @@ -12,6 +12,7 @@ from openpype.pipeline.publish import ( PublishValidationError, ) from openpype.hosts.maya.api import lib +from openpype.hosts.maya.api.lib_rendersettings import RenderSettings def convert_to_int_or_float(string_value): @@ -129,13 +130,13 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): layer = instance.data['renderlayer'] cameras = instance.data.get("cameras", []) - # Get the node attributes for current renderer - attrs = lib.RENDER_ATTRS.get(renderer, lib.RENDER_ATTRS['default']) # Prefix attribute can return None when a value was never set prefix = lib.get_attr_in_layer(cls.ImagePrefixes[renderer], layer=layer) or "" - padding = lib.get_attr_in_layer("{node}.{padding}".format(**attrs), - layer=layer) + padding = lib.get_attr_in_layer( + attr=RenderSettings.get_padding_attr(renderer), + layer=layer + ) anim_override = lib.get_attr_in_layer("defaultRenderGlobals.animation", layer=layer) @@ -372,8 +373,6 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): lib.set_attribute(data["attribute"], data["values"][0], node) with lib.renderlayer(layer_node): - default = lib.RENDER_ATTRS['default'] - render_attrs = lib.RENDER_ATTRS.get(renderer, default) # Repair animation must be enabled cmds.setAttr("defaultRenderGlobals.animation", True) @@ -391,15 +390,13 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): default_prefix = default_prefix.replace(variant, "") if renderer != "renderman": - node = render_attrs["node"] - prefix_attr = render_attrs["prefix"] - + prefix_attr = RenderSettings.get_image_prefix_attr(renderer) fname_prefix = default_prefix cmds.setAttr("{}.{}".format(node, prefix_attr), fname_prefix, type="string") # Repair padding - padding_attr = render_attrs["padding"] + padding_attr = RenderSettings.get_padding_attr(renderer) cmds.setAttr("{}.{}".format(node, padding_attr), cls.DEFAULT_PADDING) else: diff --git a/openpype/modules/muster/plugins/publish/submit_maya_muster.py b/openpype/modules/muster/plugins/publish/submit_maya_muster.py index 5c95744876..f6b3bfbbfd 100644 --- a/openpype/modules/muster/plugins/publish/submit_maya_muster.py +++ b/openpype/modules/muster/plugins/publish/submit_maya_muster.py @@ -10,6 +10,7 @@ from maya import cmds import pyblish.api from openpype.lib import requests_post from openpype.hosts.maya.api import lib +from openpype.hosts.maya.api.lib_rendersettings import RenderSettings from openpype.pipeline import legacy_io from openpype.settings import get_system_settings @@ -68,10 +69,8 @@ def get_renderer_variables(renderlayer=None): """ renderer = lib.get_renderer(renderlayer or lib.get_current_renderlayer()) - render_attrs = lib.RENDER_ATTRS.get(renderer, lib.RENDER_ATTRS["default"]) - padding = cmds.getAttr("{}.{}".format(render_attrs["node"], - render_attrs["padding"])) + padding = cmds.getAttr(RenderSettings.get_padding_attr(renderer)) filename_0 = cmds.renderSettings(fullPath=True, firstImageName=True)[0] From 025c114de854688bbf99e60a3d8fe3843c05025c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 15 Nov 2023 16:40:46 +0100 Subject: [PATCH 098/101] Fix repair --- .../hosts/blender/plugins/publish/validate_deadline_publish.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/blender/plugins/publish/validate_deadline_publish.py b/openpype/hosts/blender/plugins/publish/validate_deadline_publish.py index 58047d7e23..d8826adc9c 100644 --- a/openpype/hosts/blender/plugins/publish/validate_deadline_publish.py +++ b/openpype/hosts/blender/plugins/publish/validate_deadline_publish.py @@ -41,7 +41,7 @@ class ValidateDeadlinePublish(pyblish.api.InstancePlugin, @classmethod def repair(cls, instance): - container = bpy.data.collections[str(instance)] + container = instance.data["transientData"]["instance_node"] prepare_rendering(container) bpy.ops.wm.save_as_mainfile(filepath=bpy.data.filepath) cls.log.debug("Reset the render output folder...") From 23291ac53c1fcc00ed603ecdd3d23897b1c844e0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 15 Nov 2023 18:35:54 +0100 Subject: [PATCH 099/101] AYON workfiles tools: Revisit workfiles tool (#5897) * implemented base hierarchy expected selection model * use existing models and widgets in ayon workfiles tool * move private method under public method * added more methods to cache * reset models during controller reset * create workfile info all the time --- openpype/tools/ayon_utils/models/__init__.py | 3 + openpype/tools/ayon_utils/models/cache.py | 49 +- openpype/tools/ayon_utils/models/selection.py | 179 ++++++++ .../ayon_utils/widgets/projects_widget.py | 22 +- openpype/tools/ayon_workfiles/abstract.py | 48 +- openpype/tools/ayon_workfiles/control.py | 224 ++++++---- .../tools/ayon_workfiles/models/__init__.py | 2 - .../tools/ayon_workfiles/models/hierarchy.py | 236 ---------- .../tools/ayon_workfiles/models/selection.py | 23 +- .../tools/ayon_workfiles/models/workfiles.py | 24 +- .../ayon_workfiles/widgets/files_widget.py | 2 +- .../widgets/files_widget_published.py | 34 +- .../widgets/files_widget_workarea.py | 22 +- .../ayon_workfiles/widgets/folders_widget.py | 324 -------------- .../ayon_workfiles/widgets/side_panel.py | 2 +- .../ayon_workfiles/widgets/tasks_widget.py | 420 ------------------ .../tools/ayon_workfiles/widgets/window.py | 57 +-- 17 files changed, 501 insertions(+), 1170 deletions(-) create mode 100644 openpype/tools/ayon_utils/models/selection.py delete mode 100644 openpype/tools/ayon_workfiles/models/hierarchy.py delete mode 100644 openpype/tools/ayon_workfiles/widgets/folders_widget.py delete mode 100644 openpype/tools/ayon_workfiles/widgets/tasks_widget.py diff --git a/openpype/tools/ayon_utils/models/__init__.py b/openpype/tools/ayon_utils/models/__init__.py index 69722b5e21..8895515b1a 100644 --- a/openpype/tools/ayon_utils/models/__init__.py +++ b/openpype/tools/ayon_utils/models/__init__.py @@ -13,6 +13,7 @@ from .hierarchy import ( HIERARCHY_MODEL_SENDER, ) from .thumbnails import ThumbnailsModel +from .selection import HierarchyExpectedSelection __all__ = ( @@ -29,4 +30,6 @@ __all__ = ( "HIERARCHY_MODEL_SENDER", "ThumbnailsModel", + + "HierarchyExpectedSelection", ) diff --git a/openpype/tools/ayon_utils/models/cache.py b/openpype/tools/ayon_utils/models/cache.py index 44b97e930d..221a14160c 100644 --- a/openpype/tools/ayon_utils/models/cache.py +++ b/openpype/tools/ayon_utils/models/cache.py @@ -81,11 +81,11 @@ class NestedCacheItem: """Helper for cached items stored in nested structure. Example: - >>> cache = NestedCacheItem(levels=2) + >>> cache = NestedCacheItem(levels=2, default_factory=lambda: 0) >>> cache["a"]["b"].is_valid False >>> cache["a"]["b"].get_data() - None + 0 >>> cache["a"]["b"] = 1 >>> cache["a"]["b"].is_valid True @@ -167,8 +167,51 @@ class NestedCacheItem: return self[key] + def cached_count(self): + """Amount of cached items. + + Returns: + int: Amount of cached items. + """ + + return len(self._data_by_key) + + def clear_key(self, key): + """Clear cached item by key. + + Args: + key (str): Key of the cache item. + """ + + self._data_by_key.pop(key, None) + + def clear_invalid(self): + """Clear all invalid cache items. + + Note: + To clear all cache items use 'reset'. + """ + + changed = {} + children_are_nested = self._levels > 1 + for key, cache in tuple(self._data_by_key.items()): + if children_are_nested: + output = cache.clear_invalid() + if output: + changed[key] = output + if not cache.cached_count(): + self._data_by_key.pop(key) + elif not cache.is_valid: + changed[key] = cache.get_data() + self._data_by_key.pop(key) + return changed + def reset(self): - """Reset cache.""" + """Reset cache. + + Note: + To clear only invalid cache items use 'clear_invalid'. + """ self._data_by_key = {} diff --git a/openpype/tools/ayon_utils/models/selection.py b/openpype/tools/ayon_utils/models/selection.py new file mode 100644 index 0000000000..0ff239882b --- /dev/null +++ b/openpype/tools/ayon_utils/models/selection.py @@ -0,0 +1,179 @@ +class _ExampleController: + def emit_event(self, topic, data, **kwargs): + pass + + +class HierarchyExpectedSelection: + """Base skeleton of expected selection model. + + Expected selection model holds information about which entities should be + selected. The order of selection is very important as change of project + will affect what folders are available in folders UI and so on. Because + of that should expected selection model know what is current entity + to select. + + If any of 'handle_project', 'handle_folder' or 'handle_task' is set to + 'False' expected selection data won't contain information about the + entity type at all. Also if project is not handled then it is not + necessary to call 'expected_project_selected'. Same goes for folder and + task. + + Model is triggering event with 'expected_selection_changed' topic and + data > data structure is matching 'get_expected_selection_data' method. + + Questions: + Require '_ExampleController' as abstraction? + + Args: + controller (Any): Controller object. ('_ExampleController') + handle_project (bool): Project can be considered as can have expected + selection. + handle_folder (bool): Folder can be considered as can have expected + selection. + handle_task (bool): Task can be considered as can have expected + selection. + """ + + def __init__( + self, + controller, + handle_project=True, + handle_folder=True, + handle_task=True + ): + self._project_name = None + self._folder_id = None + self._task_name = None + + self._project_selected = True + self._folder_selected = True + self._task_selected = True + + self._controller = controller + + self._handle_project = handle_project + self._handle_folder = handle_folder + self._handle_task = handle_task + + def set_expected_selection( + self, + project_name=None, + folder_id=None, + task_name=None + ): + """Sets expected selection. + + Args: + project_name (Optional[str]): Project name. + folder_id (Optional[str]): Folder id. + task_name (Optional[str]): Task name. + """ + + self._project_name = project_name + self._folder_id = folder_id + self._task_name = task_name + + self._project_selected = not self._handle_project + self._folder_selected = not self._handle_folder + self._task_selected = not self._handle_task + self._emit_change() + + def get_expected_selection_data(self): + project_current = False + folder_current = False + task_current = False + if not self._project_selected: + project_current = True + elif not self._folder_selected: + folder_current = True + elif not self._task_selected: + task_current = True + data = {} + if self._handle_project: + data["project"] = { + "name": self._project_name, + "current": project_current, + "selected": self._project_selected, + } + if self._handle_folder: + data["folder"] = { + "id": self._folder_id, + "current": folder_current, + "selected": self._folder_selected, + } + if self._handle_task: + data["task"] = { + "name": self._task_name, + "current": task_current, + "selected": self._task_selected, + } + + return data + + def is_expected_project_selected(self, project_name): + if not self._handle_project: + return True + return project_name == self._project_name and self._project_selected + + def is_expected_folder_selected(self, folder_id): + if not self._handle_folder: + return True + return folder_id == self._folder_id and self._folder_selected + + def expected_project_selected(self, project_name): + """UI selected requested project. + + Other entity types can be requested for selection. + + Args: + project_name (str): Name of project. + """ + + if project_name != self._project_name: + return False + self._project_selected = True + self._emit_change() + return True + + def expected_folder_selected(self, folder_id): + """UI selected requested folder. + + Other entity types can be requested for selection. + + Args: + folder_id (str): Folder id. + """ + + if folder_id != self._folder_id: + return False + self._folder_selected = True + self._emit_change() + return True + + def expected_task_selected(self, folder_id, task_name): + """UI selected requested task. + + Other entity types can be requested for selection. + + Because task name is not unique across project a folder id is also + required to confirm the right task has been selected. + + Args: + folder_id (str): Folder id. + task_name (str): Task name. + """ + + if self._folder_id != folder_id: + return False + + if task_name != self._task_name: + return False + self._task_selected = True + self._emit_change() + return True + + def _emit_change(self): + self._controller.emit_event( + "expected_selection_changed", + self.get_expected_selection_data(), + ) diff --git a/openpype/tools/ayon_utils/widgets/projects_widget.py b/openpype/tools/ayon_utils/widgets/projects_widget.py index f98bfcdf8a..728433f929 100644 --- a/openpype/tools/ayon_utils/widgets/projects_widget.py +++ b/openpype/tools/ayon_utils/widgets/projects_widget.py @@ -503,17 +503,6 @@ class ProjectsCombobox(QtWidgets.QWidget): self._projects_model.set_current_context_project(project_name) self._projects_proxy_model.invalidateFilter() - def _update_select_item_visiblity(self, **kwargs): - if not self._select_item_visible: - return - if "project_name" not in kwargs: - project_name = self.get_selected_project_name() - else: - project_name = kwargs.get("project_name") - - # Hide the item if a project is selected - self._projects_model.set_selected_project(project_name) - def set_select_item_visible(self, visible): self._select_item_visible = visible self._projects_model.set_select_item_visible(visible) @@ -534,6 +523,17 @@ class ProjectsCombobox(QtWidgets.QWidget): def set_library_filter_enabled(self, enabled): return self._projects_proxy_model.set_library_filter_enabled(enabled) + def _update_select_item_visiblity(self, **kwargs): + if not self._select_item_visible: + return + if "project_name" not in kwargs: + project_name = self.get_selected_project_name() + else: + project_name = kwargs.get("project_name") + + # Hide the item if a project is selected + self._projects_model.set_selected_project(project_name) + def _on_current_index_changed(self, idx): if not self._listen_selection_change: return diff --git a/openpype/tools/ayon_workfiles/abstract.py b/openpype/tools/ayon_workfiles/abstract.py index ce399fd4c6..260f701d4b 100644 --- a/openpype/tools/ayon_workfiles/abstract.py +++ b/openpype/tools/ayon_workfiles/abstract.py @@ -443,8 +443,11 @@ class AbstractWorkfilesBackend(AbstractWorkfilesCommon): pass @abstractmethod - def get_project_entity(self): - """Get current project entity. + def get_project_entity(self, project_name): + """Get project entity by name. + + Args: + project_name (str): Project name. Returns: dict[str, Any]: Project entity data. @@ -453,10 +456,11 @@ class AbstractWorkfilesBackend(AbstractWorkfilesCommon): pass @abstractmethod - def get_folder_entity(self, folder_id): + def get_folder_entity(self, project_name, folder_id): """Get folder entity by id. Args: + project_name (str): Project name. folder_id (str): Folder id. Returns: @@ -466,10 +470,11 @@ class AbstractWorkfilesBackend(AbstractWorkfilesCommon): pass @abstractmethod - def get_task_entity(self, task_id): + def get_task_entity(self, project_name, task_id): """Get task entity by id. Args: + project_name (str): Project name. task_id (str): Task id. Returns: @@ -574,12 +579,10 @@ class AbstractWorkfilesFrontend(AbstractWorkfilesCommon): pass @abstractmethod - def set_selected_task(self, folder_id, task_id, task_name): + def set_selected_task(self, task_id, task_name): """Change selected task. Args: - folder_id (Union[str, None]): Folder id or None if no folder - is selected. task_id (Union[str, None]): Task id or None if no task is selected. task_name (Union[str, None]): Task name or None if no task @@ -711,21 +714,27 @@ class AbstractWorkfilesFrontend(AbstractWorkfilesCommon): pass @abstractmethod - def expected_representation_selected(self, representation_id): + def expected_representation_selected( + self, folder_id, task_name, representation_id + ): """Expected representation was selected in UI. Args: + folder_id (str): Folder id under which representation is. + task_name (str): Task name under which representation is. representation_id (str): Representation id which was selected. """ pass @abstractmethod - def expected_workfile_selected(self, workfile_path): + def expected_workfile_selected(self, folder_id, task_name, workfile_name): """Expected workfile was selected in UI. Args: - workfile_path (str): Workfile path which was selected. + folder_id (str): Folder id under which workfile is. + task_name (str): Task name under which workfile is. + workfile_name (str): Workfile filename which was selected. """ pass @@ -738,7 +747,7 @@ class AbstractWorkfilesFrontend(AbstractWorkfilesCommon): # Model functions @abstractmethod - def get_folder_items(self, sender): + def get_folder_items(self, project_name, sender): """Folder items to visualize project hierarchy. This function may trigger events 'folders.refresh.started' and @@ -746,6 +755,7 @@ class AbstractWorkfilesFrontend(AbstractWorkfilesCommon): That may help to avoid re-refresh of folder items in UI elements. Args: + project_name (str): Project name for which are folders requested. sender (str): Who requested folder items. Returns: @@ -756,7 +766,7 @@ class AbstractWorkfilesFrontend(AbstractWorkfilesCommon): pass @abstractmethod - def get_task_items(self, folder_id, sender): + def get_task_items(self, project_name, folder_id, sender): """Task items. This function may trigger events 'tasks.refresh.started' and @@ -764,6 +774,7 @@ class AbstractWorkfilesFrontend(AbstractWorkfilesCommon): That may help to avoid re-refresh of task items in UI elements. Args: + project_name (str): Project name for which are tasks requested. folder_id (str): Folder ID for which are tasks requested. sender (str): Who requested folder items. @@ -892,22 +903,25 @@ class AbstractWorkfilesFrontend(AbstractWorkfilesCommon): At this moment the only information which can be saved about workfile is 'note'. + When 'note' is 'None' it is only validated if workfile info exists, + and if not then creates one with empty note. + Args: folder_id (str): Folder id. task_id (str): Task id. filepath (str): Workfile path. - note (str): Note. + note (Union[str, None]): Note. """ pass # General commands @abstractmethod - def refresh(self): - """Refresh everything, models, ui etc. + def reset(self): + """Reset everything, models, ui etc. - Triggers 'controller.refresh.started' event at the beginning and - 'controller.refresh.finished' at the end. + Triggers 'controller.reset.started' event at the beginning and + 'controller.reset.finished' at the end. """ pass diff --git a/openpype/tools/ayon_workfiles/control.py b/openpype/tools/ayon_workfiles/control.py index 3784959caf..fbe6df3155 100644 --- a/openpype/tools/ayon_workfiles/control.py +++ b/openpype/tools/ayon_workfiles/control.py @@ -16,93 +16,120 @@ from openpype.pipeline.context_tools import ( ) from openpype.pipeline.workfile import create_workdir_extra_folders +from openpype.tools.ayon_utils.models import ( + HierarchyModel, + HierarchyExpectedSelection, + ProjectsModel, +) + from .abstract import ( AbstractWorkfilesFrontend, AbstractWorkfilesBackend, ) -from .models import SelectionModel, EntitiesModel, WorkfilesModel +from .models import SelectionModel, WorkfilesModel -class ExpectedSelection: - def __init__(self): - self._folder_id = None - self._task_name = None +class WorkfilesToolExpectedSelection(HierarchyExpectedSelection): + def __init__(self, controller): + super(WorkfilesToolExpectedSelection, self).__init__( + controller, + handle_project=False, + handle_folder=True, + handle_task=True, + ) + self._workfile_name = None self._representation_id = None - self._folder_selected = True - self._task_selected = True - self._workfile_name_selected = True - self._representation_id_selected = True + + self._workfile_selected = True + self._representation_selected = True def set_expected_selection( self, - folder_id, - task_name, + project_name=None, + folder_id=None, + task_name=None, workfile_name=None, - representation_id=None + representation_id=None, ): - self._folder_id = folder_id - self._task_name = task_name self._workfile_name = workfile_name self._representation_id = representation_id - self._folder_selected = False - self._task_selected = False - self._workfile_name_selected = workfile_name is None - self._representation_id_selected = representation_id is None + + self._workfile_selected = False + self._representation_selected = False + + super(WorkfilesToolExpectedSelection, self).set_expected_selection( + project_name, + folder_id, + task_name, + ) def get_expected_selection_data(self): - return { - "folder_id": self._folder_id, - "task_name": self._task_name, - "workfile_name": self._workfile_name, - "representation_id": self._representation_id, - "folder_selected": self._folder_selected, - "task_selected": self._task_selected, - "workfile_name_selected": self._workfile_name_selected, - "representation_id_selected": self._representation_id_selected, + data = super( + WorkfilesToolExpectedSelection, self + ).get_expected_selection_data() + + _is_current = ( + self._project_selected + and self._folder_selected + and self._task_selected + ) + workfile_is_current = False + repre_is_current = False + if _is_current: + workfile_is_current = not self._workfile_selected + repre_is_current = not self._representation_selected + + data["workfile"] = { + "name": self._workfile_name, + "current": workfile_is_current, + "selected": self._workfile_selected, } + data["representation"] = { + "id": self._representation_id, + "current": repre_is_current, + "selected": self._workfile_selected, + } + return data - def is_expected_folder_selected(self, folder_id): - return folder_id == self._folder_id and self._folder_selected + def is_expected_workfile_selected(self, workfile_name): + return ( + workfile_name == self._workfile_name + and self._workfile_selected + ) - def is_expected_task_selected(self, folder_id, task_name): - if not self.is_expected_folder_selected(folder_id): - return False - return task_name == self._task_name and self._task_selected + def is_expected_representation_selected(self, representation_id): + return ( + representation_id == self._representation_id + and self._representation_selected + ) - def expected_folder_selected(self, folder_id): + def expected_workfile_selected(self, folder_id, task_name, workfile_name): if folder_id != self._folder_id: return False - self._folder_selected = True - return True - - def expected_task_selected(self, folder_id, task_name): - if not self.is_expected_folder_selected(folder_id): - return False if task_name != self._task_name: return False - self._task_selected = True - return True - - def expected_workfile_selected(self, folder_id, task_name, workfile_name): - if not self.is_expected_task_selected(folder_id, task_name): - return False - if workfile_name != self._workfile_name: return False - self._workfile_name_selected = True + self._workfile_selected = True + self._emit_change() return True def expected_representation_selected( self, folder_id, task_name, representation_id ): - if not self.is_expected_task_selected(folder_id, task_name): + if folder_id != self._folder_id: return False + + if task_name != self._task_name: + return False + if representation_id != self._representation_id: return False - self._representation_id_selected = True + self._representation_selected = True + self._emit_change() return True @@ -136,9 +163,9 @@ class BaseWorkfileController( # Expected selected folder and task self._expected_selection = self._create_expected_selection_obj() - self._selection_model = self._create_selection_model() - self._entities_model = self._create_entities_model() + self._projects_model = self._create_projects_model() + self._hierarchy_model = self._create_hierarchy_model() self._workfiles_model = self._create_workfiles_model() @property @@ -151,13 +178,16 @@ class BaseWorkfileController( return self._host_is_valid def _create_expected_selection_obj(self): - return ExpectedSelection() + return WorkfilesToolExpectedSelection(self) + + def _create_projects_model(self): + return ProjectsModel(self) def _create_selection_model(self): return SelectionModel(self) - def _create_entities_model(self): - return EntitiesModel(self) + def _create_hierarchy_model(self): + return HierarchyModel(self) def _create_workfiles_model(self): return WorkfilesModel(self) @@ -193,14 +223,17 @@ class BaseWorkfileController( self._project_anatomy = Anatomy(self.get_current_project_name()) return self._project_anatomy - def get_project_entity(self): - return self._entities_model.get_project_entity() + def get_project_entity(self, project_name): + return self._projects_model.get_project_entity( + project_name) - def get_folder_entity(self, folder_id): - return self._entities_model.get_folder_entity(folder_id) + def get_folder_entity(self, project_name, folder_id): + return self._hierarchy_model.get_folder_entity( + project_name, folder_id) - def get_task_entity(self, task_id): - return self._entities_model.get_task_entity(task_id) + def get_task_entity(self, project_name, task_id): + return self._hierarchy_model.get_task_entity( + project_name, task_id) # --------------------------------- # Implementation of abstract methods @@ -293,9 +326,8 @@ class BaseWorkfileController( def get_selected_task_name(self): return self._selection_model.get_selected_task_name() - def set_selected_task(self, folder_id, task_id, task_name): - return self._selection_model.set_selected_task( - folder_id, task_id, task_name) + def set_selected_task(self, task_id, task_name): + return self._selection_model.set_selected_task(task_id, task_name) def get_selected_workfile_path(self): return self._selection_model.get_selected_workfile_path() @@ -318,7 +350,11 @@ class BaseWorkfileController( representation_id=None ): self._expected_selection.set_expected_selection( - folder_id, task_name, workfile_name, representation_id + self.get_current_project_name(), + folder_id, + task_name, + workfile_name, + representation_id ) self._trigger_expected_selection_changed() @@ -355,11 +391,13 @@ class BaseWorkfileController( ) # Model functions - def get_folder_items(self, sender): - return self._entities_model.get_folder_items(sender) + def get_folder_items(self, project_name, sender=None): + return self._hierarchy_model.get_folder_items(project_name, sender) - def get_task_items(self, folder_id, sender): - return self._entities_model.get_tasks_items(folder_id, sender) + def get_task_items(self, project_name, folder_id, sender=None): + return self._hierarchy_model.get_task_items( + project_name, folder_id, sender + ) def get_workarea_dir_by_context(self, folder_id, task_id): return self._workfiles_model.get_workarea_dir_by_context( @@ -394,7 +432,9 @@ class BaseWorkfileController( def get_published_file_items(self, folder_id, task_id): task_name = None if task_id: - task = self.get_task_entity(task_id) + task = self.get_task_entity( + self.get_current_project_name(), task_id + ) task_name = task.get("name") return self._workfiles_model.get_published_file_items( @@ -410,21 +450,27 @@ class BaseWorkfileController( folder_id, task_id, filepath, note ) - def refresh(self): + def reset(self): if not self._host_is_valid: - self._emit_event("controller.refresh.started") - self._emit_event("controller.refresh.finished") + self._emit_event("controller.reset.started") + self._emit_event("controller.reset.finished") return expected_folder_id = self.get_selected_folder_id() expected_task_name = self.get_selected_task_name() + expected_work_path = self.get_selected_workfile_path() + expected_repre_id = self.get_selected_representation_id() + expected_work_name = None + if expected_work_path: + expected_work_name = os.path.basename(expected_work_path) - self._emit_event("controller.refresh.started") + self._emit_event("controller.reset.started") context = self._get_host_current_context() project_name = context["project_name"] folder_name = context["asset_name"] task_name = context["task_name"] + current_file = self.get_current_workfile() folder_id = None if folder_name: folder = ayon_api.get_folder_by_name(project_name, folder_name) @@ -439,18 +485,25 @@ class BaseWorkfileController( self._current_folder_id = folder_id self._current_task_name = task_name + self._projects_model.reset() + self._hierarchy_model.reset() + if not expected_folder_id: expected_folder_id = folder_id expected_task_name = task_name + if current_file: + expected_work_name = os.path.basename(current_file) + + self._emit_event("controller.reset.finished") self._expected_selection.set_expected_selection( - expected_folder_id, expected_task_name + project_name, + expected_folder_id, + expected_task_name, + expected_work_name, + expected_repre_id, ) - self._entities_model.refresh() - - self._emit_event("controller.refresh.finished") - # Controller actions def open_workfile(self, folder_id, task_id, filepath): self._emit_event("open_workfile.started") @@ -579,9 +632,9 @@ class BaseWorkfileController( self, project_name, folder_id, task_id, folder=None, task=None ): if folder is None: - folder = self.get_folder_entity(folder_id) + folder = self.get_folder_entity(project_name, folder_id) if task is None: - task = self.get_task_entity(task_id) + task = self.get_task_entity(project_name, task_id) # NOTE keys should be OpenPype compatible return { "project_name": project_name, @@ -633,8 +686,8 @@ class BaseWorkfileController( ): # Trigger before save event project_name = self.get_current_project_name() - folder = self.get_folder_entity(folder_id) - task = self.get_task_entity(task_id) + folder = self.get_folder_entity(project_name, folder_id) + task = self.get_task_entity(project_name, task_id) task_name = task["name"] # QUESTION should the data be different for 'before' and 'after'? @@ -674,6 +727,9 @@ class BaseWorkfileController( else: self._host_save_workfile(dst_filepath) + # Make sure workfile info exists + self.save_workfile_info(folder_id, task_id, dst_filepath, None) + # Create extra folders create_workdir_extra_folders( workdir, @@ -685,4 +741,4 @@ class BaseWorkfileController( # Trigger after save events emit_event("workfile.save.after", event_data, source="workfiles.tool") - self.refresh() + self.reset() diff --git a/openpype/tools/ayon_workfiles/models/__init__.py b/openpype/tools/ayon_workfiles/models/__init__.py index d906b9e7bd..734cb08cb6 100644 --- a/openpype/tools/ayon_workfiles/models/__init__.py +++ b/openpype/tools/ayon_workfiles/models/__init__.py @@ -1,10 +1,8 @@ -from .hierarchy import EntitiesModel from .selection import SelectionModel from .workfiles import WorkfilesModel __all__ = ( "SelectionModel", - "EntitiesModel", "WorkfilesModel", ) diff --git a/openpype/tools/ayon_workfiles/models/hierarchy.py b/openpype/tools/ayon_workfiles/models/hierarchy.py deleted file mode 100644 index a1d51525da..0000000000 --- a/openpype/tools/ayon_workfiles/models/hierarchy.py +++ /dev/null @@ -1,236 +0,0 @@ -"""Hierarchy model that handles folders and tasks. - -The model can be extracted for common usage. In that case it will be required -to add more handling of project name changes. -""" - -import time -import collections -import contextlib - -import ayon_api - -from openpype.tools.ayon_workfiles.abstract import ( - FolderItem, - TaskItem, -) - - -def _get_task_items_from_tasks(tasks): - """ - - Returns: - TaskItem: Task item. - """ - - output = [] - for task in tasks: - folder_id = task["folderId"] - output.append(TaskItem( - task["id"], - task["name"], - task["type"], - folder_id, - None, - None - )) - return output - - -def _get_folder_item_from_hierarchy_item(item): - return FolderItem( - item["id"], - item["parentId"], - item["name"], - item["label"], - None, - None, - ) - - -class CacheItem: - def __init__(self, lifetime=120): - self._lifetime = lifetime - self._last_update = None - self._data = None - - @property - def is_valid(self): - if self._last_update is None: - return False - - return (time.time() - self._last_update) < self._lifetime - - def set_invalid(self, data=None): - self._last_update = None - self._data = data - - def get_data(self): - return self._data - - def update_data(self, data): - self._data = data - self._last_update = time.time() - - -class EntitiesModel(object): - event_source = "entities.model" - - def __init__(self, controller): - project_cache = CacheItem() - project_cache.set_invalid({}) - folders_cache = CacheItem() - folders_cache.set_invalid({}) - self._project_cache = project_cache - self._folders_cache = folders_cache - self._tasks_cache = {} - - self._folders_by_id = {} - self._tasks_by_id = {} - - self._folders_refreshing = False - self._tasks_refreshing = set() - self._controller = controller - - def reset(self): - self._project_cache.set_invalid({}) - self._folders_cache.set_invalid({}) - self._tasks_cache = {} - - self._folders_by_id = {} - self._tasks_by_id = {} - - def refresh(self): - self._refresh_folders_cache() - - def get_project_entity(self): - if not self._project_cache.is_valid: - project_name = self._controller.get_current_project_name() - project_entity = ayon_api.get_project(project_name) - self._project_cache.update_data(project_entity) - return self._project_cache.get_data() - - def get_folder_items(self, sender): - if not self._folders_cache.is_valid: - self._refresh_folders_cache(sender) - return self._folders_cache.get_data() - - def get_tasks_items(self, folder_id, sender): - if not folder_id: - return [] - - task_cache = self._tasks_cache.get(folder_id) - if task_cache is None or not task_cache.is_valid: - self._refresh_tasks_cache(folder_id, sender) - task_cache = self._tasks_cache.get(folder_id) - return task_cache.get_data() - - def get_folder_entity(self, folder_id): - if folder_id not in self._folders_by_id: - entity = None - if folder_id: - project_name = self._controller.get_current_project_name() - entity = ayon_api.get_folder_by_id(project_name, folder_id) - self._folders_by_id[folder_id] = entity - return self._folders_by_id[folder_id] - - def get_task_entity(self, task_id): - if task_id not in self._tasks_by_id: - entity = None - if task_id: - project_name = self._controller.get_current_project_name() - entity = ayon_api.get_task_by_id(project_name, task_id) - self._tasks_by_id[task_id] = entity - return self._tasks_by_id[task_id] - - @contextlib.contextmanager - def _folder_refresh_event_manager(self, project_name, sender): - self._folders_refreshing = True - self._controller.emit_event( - "folders.refresh.started", - {"project_name": project_name, "sender": sender}, - self.event_source - ) - try: - yield - - finally: - self._controller.emit_event( - "folders.refresh.finished", - {"project_name": project_name, "sender": sender}, - self.event_source - ) - self._folders_refreshing = False - - @contextlib.contextmanager - def _task_refresh_event_manager( - self, project_name, folder_id, sender - ): - self._tasks_refreshing.add(folder_id) - self._controller.emit_event( - "tasks.refresh.started", - { - "project_name": project_name, - "folder_id": folder_id, - "sender": sender, - }, - self.event_source - ) - try: - yield - - finally: - self._controller.emit_event( - "tasks.refresh.finished", - { - "project_name": project_name, - "folder_id": folder_id, - "sender": sender, - }, - self.event_source - ) - self._tasks_refreshing.discard(folder_id) - - def _refresh_folders_cache(self, sender=None): - if self._folders_refreshing: - return - project_name = self._controller.get_current_project_name() - with self._folder_refresh_event_manager(project_name, sender): - folder_items = self._query_folders(project_name) - self._folders_cache.update_data(folder_items) - - def _query_folders(self, project_name): - hierarchy = ayon_api.get_folders_hierarchy(project_name) - - folder_items = {} - hierachy_queue = collections.deque(hierarchy["hierarchy"]) - while hierachy_queue: - item = hierachy_queue.popleft() - folder_item = _get_folder_item_from_hierarchy_item(item) - folder_items[folder_item.entity_id] = folder_item - hierachy_queue.extend(item["children"] or []) - return folder_items - - def _refresh_tasks_cache(self, folder_id, sender=None): - if folder_id in self._tasks_refreshing: - return - - project_name = self._controller.get_current_project_name() - with self._task_refresh_event_manager( - project_name, folder_id, sender - ): - cache_item = self._tasks_cache.get(folder_id) - if cache_item is None: - cache_item = CacheItem() - self._tasks_cache[folder_id] = cache_item - - task_items = self._query_tasks(project_name, folder_id) - cache_item.update_data(task_items) - - def _query_tasks(self, project_name, folder_id): - tasks = list(ayon_api.get_tasks( - project_name, - folder_ids=[folder_id], - fields={"id", "name", "label", "folderId", "type"} - )) - return _get_task_items_from_tasks(tasks) diff --git a/openpype/tools/ayon_workfiles/models/selection.py b/openpype/tools/ayon_workfiles/models/selection.py index ad034794d8..2f0896842d 100644 --- a/openpype/tools/ayon_workfiles/models/selection.py +++ b/openpype/tools/ayon_workfiles/models/selection.py @@ -4,7 +4,7 @@ class SelectionModel(object): Triggering events: - "selection.folder.changed" - "selection.task.changed" - - "workarea.selection.changed" + - "selection.workarea.changed" - "selection.representation.changed" """ @@ -29,7 +29,10 @@ class SelectionModel(object): self._folder_id = folder_id self._controller.emit_event( "selection.folder.changed", - {"folder_id": folder_id}, + { + "project_name": self._controller.get_current_project_name(), + "folder_id": folder_id + }, self.event_source ) @@ -39,10 +42,7 @@ class SelectionModel(object): def get_selected_task_id(self): return self._task_id - def set_selected_task(self, folder_id, task_id, task_name): - if folder_id != self._folder_id: - self.set_selected_folder(folder_id) - + def set_selected_task(self, task_id, task_name): if task_id == self._task_id: return @@ -51,7 +51,8 @@ class SelectionModel(object): self._controller.emit_event( "selection.task.changed", { - "folder_id": folder_id, + "project_name": self._controller.get_current_project_name(), + "folder_id": self._folder_id, "task_name": task_name, "task_id": task_id }, @@ -67,8 +68,9 @@ class SelectionModel(object): self._workfile_path = path self._controller.emit_event( - "workarea.selection.changed", + "selection.workarea.changed", { + "project_name": self._controller.get_current_project_name(), "path": path, "folder_id": self._folder_id, "task_name": self._task_name, @@ -86,6 +88,9 @@ class SelectionModel(object): self._representation_id = representation_id self._controller.emit_event( "selection.representation.changed", - {"representation_id": representation_id}, + { + "project_name": self._controller.get_current_project_name(), + "representation_id": representation_id, + }, self.event_source ) diff --git a/openpype/tools/ayon_workfiles/models/workfiles.py b/openpype/tools/ayon_workfiles/models/workfiles.py index 4d989ed22c..907b9b5383 100644 --- a/openpype/tools/ayon_workfiles/models/workfiles.py +++ b/openpype/tools/ayon_workfiles/models/workfiles.py @@ -148,7 +148,9 @@ class WorkareaModel: def _get_folder_data(self, folder_id): fill_data = self._fill_data_by_folder_id.get(folder_id) if fill_data is None: - folder = self._controller.get_folder_entity(folder_id) + folder = self._controller.get_folder_entity( + self.project_name, folder_id + ) fill_data = get_folder_template_data(folder) self._fill_data_by_folder_id[folder_id] = fill_data return copy.deepcopy(fill_data) @@ -156,7 +158,9 @@ class WorkareaModel: def _get_task_data(self, project_entity, folder_id, task_id): task_data = self._task_data_by_folder_id.setdefault(folder_id, {}) if task_id not in task_data: - task = self._controller.get_task_entity(task_id) + task = self._controller.get_task_entity( + self.project_name, task_id + ) if task: task_data[task_id] = get_task_template_data( project_entity, task) @@ -167,8 +171,9 @@ class WorkareaModel: return {} base_data = self._get_base_data() + project_name = base_data["project"]["name"] folder_data = self._get_folder_data(folder_id) - project_entity = self._controller.get_project_entity() + project_entity = self._controller.get_project_entity(project_name) task_data = self._get_task_data(project_entity, folder_id, task_id) base_data.update(folder_data) @@ -292,9 +297,13 @@ class WorkareaModel: folder = None task = None if folder_id: - folder = self._controller.get_folder_entity(folder_id) + folder = self._controller.get_folder_entity( + self.project_name, folder_id + ) if task_id: - task = self._controller.get_task_entity(task_id) + task = self._controller.get_task_entity( + self.project_name, task_id + ) if not folder or not task: return { @@ -491,10 +500,13 @@ class WorkfileEntitiesModel: ) if not workfile_info: self._cache[identifier] = self._create_workfile_info_entity( - task_id, rootless_path, note) + task_id, rootless_path, note or "") self._items.pop(identifier, None) return + if note is None: + return + new_workfile_info = copy.deepcopy(workfile_info) attrib = new_workfile_info.setdefault("attrib", {}) attrib["description"] = note diff --git a/openpype/tools/ayon_workfiles/widgets/files_widget.py b/openpype/tools/ayon_workfiles/widgets/files_widget.py index 656ddf1dd8..16f0b6fce3 100644 --- a/openpype/tools/ayon_workfiles/widgets/files_widget.py +++ b/openpype/tools/ayon_workfiles/widgets/files_widget.py @@ -69,7 +69,7 @@ class FilesWidget(QtWidgets.QWidget): main_layout.addWidget(btns_widget, 0) controller.register_event_callback( - "workarea.selection.changed", + "selection.workarea.changed", self._on_workarea_path_changed ) controller.register_event_callback( diff --git a/openpype/tools/ayon_workfiles/widgets/files_widget_published.py b/openpype/tools/ayon_workfiles/widgets/files_widget_published.py index 576cf18d73..704f7b2f39 100644 --- a/openpype/tools/ayon_workfiles/widgets/files_widget_published.py +++ b/openpype/tools/ayon_workfiles/widgets/files_widget_published.py @@ -59,14 +59,6 @@ class PublishedFilesModel(QtGui.QStandardItemModel): self._add_empty_item() - def _clear_items(self): - self._remove_missing_context_item() - self._remove_empty_item() - if self._items_by_id: - root = self.invisibleRootItem() - root.removeRows(0, root.rowCount()) - self._items_by_id = {} - def set_published_mode(self, published_mode): if self._published_mode == published_mode: return @@ -89,6 +81,18 @@ class PublishedFilesModel(QtGui.QStandardItemModel): return QtCore.QModelIndex() return self.indexFromItem(item) + def refresh(self): + if self._published_mode: + self._fill_items() + + def _clear_items(self): + self._remove_missing_context_item() + self._remove_empty_item() + if self._items_by_id: + root = self.invisibleRootItem() + root.removeRows(0, root.rowCount()) + self._items_by_id = {} + def _get_missing_context_item(self): if self._missing_context_item is None: message = "Select folder" @@ -149,7 +153,6 @@ class PublishedFilesModel(QtGui.QStandardItemModel): def _on_folder_changed(self, event): self._last_folder_id = event["folder_id"] - self._last_task_id = None if self._context_select_mode: return @@ -356,14 +359,13 @@ class PublishedFilesWidget(QtWidgets.QWidget): self.save_as_requested.emit() def _on_expected_selection_change(self, event): - if ( - event["representation_id_selected"] - or not event["folder_selected"] - or (event["task_name"] and not event["task_selected"]) - ): + repre_info = event["representation"] + if not repre_info["current"]: return - representation_id = event["representation_id"] + self._model.refresh() + + representation_id = repre_info["id"] selected_repre_id = self.get_selected_repre_id() if ( representation_id is not None @@ -376,5 +378,5 @@ class PublishedFilesWidget(QtWidgets.QWidget): self._view.setCurrentIndex(proxy_index) self._controller.expected_representation_selected( - event["folder_id"], event["task_name"], representation_id + event["folder"]["id"], event["task"]["name"], representation_id ) diff --git a/openpype/tools/ayon_workfiles/widgets/files_widget_workarea.py b/openpype/tools/ayon_workfiles/widgets/files_widget_workarea.py index 3a8e90f933..8eefd3cf81 100644 --- a/openpype/tools/ayon_workfiles/widgets/files_widget_workarea.py +++ b/openpype/tools/ayon_workfiles/widgets/files_widget_workarea.py @@ -28,6 +28,10 @@ class WorkAreaFilesModel(QtGui.QStandardItemModel): self.setHeaderData(0, QtCore.Qt.Horizontal, "Name") self.setHeaderData(1, QtCore.Qt.Horizontal, "Date Modified") + controller.register_event_callback( + "selection.folder.changed", + self._on_folder_changed + ) controller.register_event_callback( "selection.task.changed", self._on_task_changed @@ -63,6 +67,10 @@ class WorkAreaFilesModel(QtGui.QStandardItemModel): return QtCore.QModelIndex() return self.indexFromItem(item) + def refresh(self): + if not self._published_mode: + self._fill_items() + def _get_missing_context_item(self): if self._missing_context_item is None: message = "Select folder and task" @@ -129,6 +137,11 @@ class WorkAreaFilesModel(QtGui.QStandardItemModel): root_item.takeRow(self._empty_root_item.row()) self._empty_item_used = False + def _on_folder_changed(self, event): + self._selected_folder_id = event["folder_id"] + if not self._published_mode: + self._fill_items() + def _on_task_changed(self, event): self._selected_folder_id = event["folder_id"] self._selected_task_id = event["task_id"] @@ -362,10 +375,13 @@ class WorkAreaFilesWidget(QtWidgets.QWidget): self.duplicate_requested.emit() def _on_expected_selection_change(self, event): - if event["workfile_name_selected"]: + workfile_info = event["workfile"] + if not workfile_info["current"]: return - workfile_name = event["workfile_name"] + self._model.refresh() + + workfile_name = workfile_info["name"] if ( workfile_name is not None and workfile_name != self._get_selected_info()["filename"] @@ -376,5 +392,5 @@ class WorkAreaFilesWidget(QtWidgets.QWidget): self._view.setCurrentIndex(proxy_index) self._controller.expected_workfile_selected( - event["folder_id"], event["task_name"], workfile_name + event["folder"]["id"], event["task"]["name"], workfile_name ) diff --git a/openpype/tools/ayon_workfiles/widgets/folders_widget.py b/openpype/tools/ayon_workfiles/widgets/folders_widget.py deleted file mode 100644 index b04f8e4098..0000000000 --- a/openpype/tools/ayon_workfiles/widgets/folders_widget.py +++ /dev/null @@ -1,324 +0,0 @@ -import uuid -import collections - -import qtawesome -from qtpy import QtWidgets, QtGui, QtCore - -from openpype.tools.utils import ( - RecursiveSortFilterProxyModel, - DeselectableTreeView, -) - -from .constants import ITEM_ID_ROLE, ITEM_NAME_ROLE - -SENDER_NAME = "qt_folders_model" - - -class FoldersRefreshThread(QtCore.QThread): - """Thread for refreshing folders. - - Call controller to get folders and emit signal when finished. - - Args: - controller (AbstractWorkfilesFrontend): The control object. - """ - - refresh_finished = QtCore.Signal(str) - - def __init__(self, controller): - super(FoldersRefreshThread, self).__init__() - self._id = uuid.uuid4().hex - self._controller = controller - self._result = None - - @property - def id(self): - """Thread id. - - Returns: - str: Unique id of the thread. - """ - - return self._id - - def run(self): - self._result = self._controller.get_folder_items(SENDER_NAME) - self.refresh_finished.emit(self.id) - - def get_result(self): - return self._result - - -class FoldersModel(QtGui.QStandardItemModel): - """Folders model which cares about refresh of folders. - - Args: - controller (AbstractWorkfilesFrontend): The control object. - """ - - refreshed = QtCore.Signal() - - def __init__(self, controller): - super(FoldersModel, self).__init__() - - self._controller = controller - self._items_by_id = {} - self._parent_id_by_id = {} - - self._refresh_threads = {} - self._current_refresh_thread = None - - self._has_content = False - self._is_refreshing = False - - @property - def is_refreshing(self): - """Model is refreshing. - - Returns: - bool: True if model is refreshing. - """ - return self._is_refreshing - - @property - def has_content(self): - """Has at least one folder. - - Returns: - bool: True if model has at least one folder. - """ - - return self._has_content - - def clear(self): - self._items_by_id = {} - self._parent_id_by_id = {} - self._has_content = False - super(FoldersModel, self).clear() - - def get_index_by_id(self, item_id): - """Get index by folder id. - - Returns: - QtCore.QModelIndex: Index of the folder. Can be invalid if folder - is not available. - """ - item = self._items_by_id.get(item_id) - if item is None: - return QtCore.QModelIndex() - return self.indexFromItem(item) - - def refresh(self): - """Refresh folders items. - - Refresh start thread because it can cause that controller can - start query from database if folders are not cached. - """ - - self._is_refreshing = True - - thread = FoldersRefreshThread(self._controller) - self._current_refresh_thread = thread.id - self._refresh_threads[thread.id] = thread - thread.refresh_finished.connect(self._on_refresh_thread) - thread.start() - - def _on_refresh_thread(self, thread_id): - """Callback when refresh thread is finished. - - Technically can be running multiple refresh threads at the same time, - to avoid using values from wrong thread, we check if thread id is - current refresh thread id. - - Folders are stored by id. - - Args: - thread_id (str): Thread id. - """ - - thread = self._refresh_threads.pop(thread_id) - if thread_id != self._current_refresh_thread: - return - - folder_items_by_id = thread.get_result() - if not folder_items_by_id: - if folder_items_by_id is not None: - self.clear() - self._is_refreshing = False - return - - self._has_content = True - - folder_ids = set(folder_items_by_id) - ids_to_remove = set(self._items_by_id) - folder_ids - - folder_items_by_parent = collections.defaultdict(list) - for folder_item in folder_items_by_id.values(): - folder_items_by_parent[folder_item.parent_id].append(folder_item) - - hierarchy_queue = collections.deque() - hierarchy_queue.append(None) - - while hierarchy_queue: - parent_id = hierarchy_queue.popleft() - folder_items = folder_items_by_parent[parent_id] - if parent_id is None: - parent_item = self.invisibleRootItem() - else: - parent_item = self._items_by_id[parent_id] - - new_items = [] - for folder_item in folder_items: - item_id = folder_item.entity_id - item = self._items_by_id.get(item_id) - if item is None: - is_new = True - item = QtGui.QStandardItem() - item.setEditable(False) - else: - is_new = self._parent_id_by_id[item_id] != parent_id - - icon = qtawesome.icon( - folder_item.icon_name, - color=folder_item.icon_color, - ) - item.setData(item_id, ITEM_ID_ROLE) - item.setData(folder_item.name, ITEM_NAME_ROLE) - item.setData(folder_item.label, QtCore.Qt.DisplayRole) - item.setData(icon, QtCore.Qt.DecorationRole) - if is_new: - new_items.append(item) - self._items_by_id[item_id] = item - self._parent_id_by_id[item_id] = parent_id - - hierarchy_queue.append(item_id) - - if new_items: - parent_item.appendRows(new_items) - - for item_id in ids_to_remove: - item = self._items_by_id[item_id] - parent_id = self._parent_id_by_id[item_id] - if parent_id is None: - parent_item = self.invisibleRootItem() - else: - parent_item = self._items_by_id[parent_id] - parent_item.takeChild(item.row()) - - for item_id in ids_to_remove: - self._items_by_id.pop(item_id) - self._parent_id_by_id.pop(item_id) - - self._is_refreshing = False - self.refreshed.emit() - - -class FoldersWidget(QtWidgets.QWidget): - """Folders widget. - - Widget that handles folders view, model and selection. - - Args: - controller (AbstractWorkfilesFrontend): The control object. - parent (QtWidgets.QWidget): The parent widget. - """ - - def __init__(self, controller, parent): - super(FoldersWidget, self).__init__(parent) - - folders_view = DeselectableTreeView(self) - folders_view.setHeaderHidden(True) - - folders_model = FoldersModel(controller) - folders_proxy_model = RecursiveSortFilterProxyModel() - folders_proxy_model.setSourceModel(folders_model) - - folders_view.setModel(folders_proxy_model) - - main_layout = QtWidgets.QHBoxLayout(self) - main_layout.setContentsMargins(0, 0, 0, 0) - main_layout.addWidget(folders_view, 1) - - controller.register_event_callback( - "folders.refresh.finished", - self._on_folders_refresh_finished - ) - controller.register_event_callback( - "controller.refresh.finished", - self._on_controller_refresh - ) - controller.register_event_callback( - "expected_selection_changed", - self._on_expected_selection_change - ) - - selection_model = folders_view.selectionModel() - selection_model.selectionChanged.connect(self._on_selection_change) - - folders_model.refreshed.connect(self._on_model_refresh) - - self._controller = controller - self._folders_view = folders_view - self._folders_model = folders_model - self._folders_proxy_model = folders_proxy_model - - self._expected_selection = None - - def set_name_filter(self, name): - self._folders_proxy_model.setFilterFixedString(name) - - def _clear(self): - self._folders_model.clear() - - def _on_folders_refresh_finished(self, event): - if event["sender"] != SENDER_NAME: - self._folders_model.refresh() - - def _on_controller_refresh(self): - self._update_expected_selection() - - def _update_expected_selection(self, expected_data=None): - if expected_data is None: - expected_data = self._controller.get_expected_selection_data() - - # We're done - if expected_data["folder_selected"]: - return - - folder_id = expected_data["folder_id"] - self._expected_selection = folder_id - if not self._folders_model.is_refreshing: - self._set_expected_selection() - - def _set_expected_selection(self): - folder_id = self._expected_selection - self._expected_selection = None - if ( - folder_id is not None - and folder_id != self._get_selected_item_id() - ): - index = self._folders_model.get_index_by_id(folder_id) - if index.isValid(): - proxy_index = self._folders_proxy_model.mapFromSource(index) - self._folders_view.setCurrentIndex(proxy_index) - self._controller.expected_folder_selected(folder_id) - - def _on_model_refresh(self): - if self._expected_selection: - self._set_expected_selection() - self._folders_proxy_model.sort(0) - - def _on_expected_selection_change(self, event): - self._update_expected_selection(event.data) - - def _get_selected_item_id(self): - selection_model = self._folders_view.selectionModel() - for index in selection_model.selectedIndexes(): - item_id = index.data(ITEM_ID_ROLE) - if item_id is not None: - return item_id - return None - - def _on_selection_change(self): - item_id = self._get_selected_item_id() - self._controller.set_selected_folder(item_id) diff --git a/openpype/tools/ayon_workfiles/widgets/side_panel.py b/openpype/tools/ayon_workfiles/widgets/side_panel.py index 7f06576a00..5085f4701e 100644 --- a/openpype/tools/ayon_workfiles/widgets/side_panel.py +++ b/openpype/tools/ayon_workfiles/widgets/side_panel.py @@ -66,7 +66,7 @@ class SidePanelWidget(QtWidgets.QWidget): btn_note_save.clicked.connect(self._on_save_click) controller.register_event_callback( - "workarea.selection.changed", self._on_selection_change + "selection.workarea.changed", self._on_selection_change ) self._details_input = details_input diff --git a/openpype/tools/ayon_workfiles/widgets/tasks_widget.py b/openpype/tools/ayon_workfiles/widgets/tasks_widget.py deleted file mode 100644 index 04f5b286b1..0000000000 --- a/openpype/tools/ayon_workfiles/widgets/tasks_widget.py +++ /dev/null @@ -1,420 +0,0 @@ -import uuid -import qtawesome -from qtpy import QtWidgets, QtGui, QtCore - -from openpype.style import get_disabled_entity_icon_color -from openpype.tools.utils import DeselectableTreeView - -from .constants import ( - ITEM_NAME_ROLE, - ITEM_ID_ROLE, - PARENT_ID_ROLE, -) - -SENDER_NAME = "qt_tasks_model" - - -class RefreshThread(QtCore.QThread): - """Thread for refreshing tasks. - - Call controller to get tasks and emit signal when finished. - - Args: - controller (AbstractWorkfilesFrontend): The control object. - folder_id (str): Folder id. - """ - - refresh_finished = QtCore.Signal(str) - - def __init__(self, controller, folder_id): - super(RefreshThread, self).__init__() - self._id = uuid.uuid4().hex - self._controller = controller - self._folder_id = folder_id - self._result = None - - @property - def id(self): - return self._id - - def run(self): - self._result = self._controller.get_task_items( - self._folder_id, SENDER_NAME) - self.refresh_finished.emit(self.id) - - def get_result(self): - return self._result - - -class TasksModel(QtGui.QStandardItemModel): - """Tasks model which cares about refresh of tasks by folder id. - - Args: - controller (AbstractWorkfilesFrontend): The control object. - """ - - refreshed = QtCore.Signal() - - def __init__(self, controller): - super(TasksModel, self).__init__() - - self._controller = controller - - self._items_by_name = {} - self._has_content = False - self._is_refreshing = False - - self._invalid_selection_item_used = False - self._invalid_selection_item = None - self._empty_tasks_item_used = False - self._empty_tasks_item = None - - self._last_folder_id = None - - self._refresh_threads = {} - self._current_refresh_thread = None - - # Initial state - self._add_invalid_selection_item() - - def clear(self): - self._items_by_name = {} - self._has_content = False - self._remove_invalid_items() - super(TasksModel, self).clear() - - def refresh(self, folder_id): - """Refresh tasks for folder. - - Args: - folder_id (Union[str, None]): Folder id. - """ - - self._refresh(folder_id) - - def get_index_by_name(self, task_name): - """Find item by name and return its index. - - Returns: - QtCore.QModelIndex: Index of item. Is invalid if task is not - found by name. - """ - - item = self._items_by_name.get(task_name) - if item is None: - return QtCore.QModelIndex() - return self.indexFromItem(item) - - def get_last_folder_id(self): - """Get last refreshed folder id. - - Returns: - Union[str, None]: Folder id. - """ - - return self._last_folder_id - - def _get_invalid_selection_item(self): - if self._invalid_selection_item is None: - item = QtGui.QStandardItem("Select a folder") - item.setFlags(QtCore.Qt.NoItemFlags) - icon = qtawesome.icon( - "fa.times", - color=get_disabled_entity_icon_color() - ) - item.setData(icon, QtCore.Qt.DecorationRole) - self._invalid_selection_item = item - return self._invalid_selection_item - - def _get_empty_task_item(self): - if self._empty_tasks_item is None: - item = QtGui.QStandardItem("No task") - icon = qtawesome.icon( - "fa.exclamation-circle", - color=get_disabled_entity_icon_color() - ) - item.setData(icon, QtCore.Qt.DecorationRole) - item.setFlags(QtCore.Qt.NoItemFlags) - self._empty_tasks_item = item - return self._empty_tasks_item - - def _add_invalid_item(self, item): - self.clear() - root_item = self.invisibleRootItem() - root_item.appendRow(item) - - def _remove_invalid_item(self, item): - root_item = self.invisibleRootItem() - root_item.takeRow(item.row()) - - def _remove_invalid_items(self): - self._remove_invalid_selection_item() - self._remove_empty_task_item() - - def _add_invalid_selection_item(self): - if not self._invalid_selection_item_used: - self._add_invalid_item(self._get_invalid_selection_item()) - self._invalid_selection_item_used = True - - def _remove_invalid_selection_item(self): - if self._invalid_selection_item: - self._remove_invalid_item(self._get_invalid_selection_item()) - self._invalid_selection_item_used = False - - def _add_empty_task_item(self): - if not self._empty_tasks_item_used: - self._add_invalid_item(self._get_empty_task_item()) - self._empty_tasks_item_used = True - - def _remove_empty_task_item(self): - if self._empty_tasks_item_used: - self._remove_invalid_item(self._get_empty_task_item()) - self._empty_tasks_item_used = False - - def _refresh(self, folder_id): - self._is_refreshing = True - self._last_folder_id = folder_id - if not folder_id: - self._add_invalid_selection_item() - self._current_refresh_thread = None - self._is_refreshing = False - self.refreshed.emit() - return - - thread = RefreshThread(self._controller, folder_id) - self._current_refresh_thread = thread.id - self._refresh_threads[thread.id] = thread - thread.refresh_finished.connect(self._on_refresh_thread) - thread.start() - - def _on_refresh_thread(self, thread_id): - """Callback when refresh thread is finished. - - Technically can be running multiple refresh threads at the same time, - to avoid using values from wrong thread, we check if thread id is - current refresh thread id. - - Tasks are stored by name, so if a folder has same task name as - previously selected folder it keeps the selection. - - Args: - thread_id (str): Thread id. - """ - - thread = self._refresh_threads.pop(thread_id) - if thread_id != self._current_refresh_thread: - return - - task_items = thread.get_result() - # Task items are refreshed - if task_items is None: - return - - # No tasks are available on folder - if not task_items: - self._add_empty_task_item() - return - self._remove_invalid_items() - - new_items = [] - new_names = set() - for task_item in task_items: - name = task_item.name - new_names.add(name) - item = self._items_by_name.get(name) - if item is None: - item = QtGui.QStandardItem() - item.setEditable(False) - new_items.append(item) - self._items_by_name[name] = item - - # TODO cache locally - icon = qtawesome.icon( - task_item.icon_name, - color=task_item.icon_color, - ) - item.setData(task_item.label, QtCore.Qt.DisplayRole) - item.setData(name, ITEM_NAME_ROLE) - item.setData(task_item.id, ITEM_ID_ROLE) - item.setData(task_item.parent_id, PARENT_ID_ROLE) - item.setData(icon, QtCore.Qt.DecorationRole) - - root_item = self.invisibleRootItem() - - for name in set(self._items_by_name) - new_names: - item = self._items_by_name.pop(name) - root_item.removeRow(item.row()) - - if new_items: - root_item.appendRows(new_items) - - self._has_content = root_item.rowCount() > 0 - self._is_refreshing = False - self.refreshed.emit() - - @property - def is_refreshing(self): - """Model is refreshing. - - Returns: - bool: Model is refreshing - """ - - return self._is_refreshing - - @property - def has_content(self): - """Model has content. - - Returns: - bools: Have at least one task. - """ - - return self._has_content - - def headerData(self, section, orientation, role): - # Show nice labels in the header - if ( - role == QtCore.Qt.DisplayRole - and orientation == QtCore.Qt.Horizontal - ): - if section == 0: - return "Tasks" - - return super(TasksModel, self).headerData( - section, orientation, role - ) - - -class TasksWidget(QtWidgets.QWidget): - """Tasks widget. - - Widget that handles tasks view, model and selection. - - Args: - controller (AbstractWorkfilesFrontend): Workfiles controller. - """ - - def __init__(self, controller, parent): - super(TasksWidget, self).__init__(parent) - - tasks_view = DeselectableTreeView(self) - tasks_view.setIndentation(0) - - tasks_model = TasksModel(controller) - tasks_proxy_model = QtCore.QSortFilterProxyModel() - tasks_proxy_model.setSourceModel(tasks_model) - - tasks_view.setModel(tasks_proxy_model) - - main_layout = QtWidgets.QHBoxLayout(self) - main_layout.setContentsMargins(0, 0, 0, 0) - main_layout.addWidget(tasks_view, 1) - - controller.register_event_callback( - "tasks.refresh.finished", - self._on_tasks_refresh_finished - ) - controller.register_event_callback( - "selection.folder.changed", - self._folder_selection_changed - ) - controller.register_event_callback( - "expected_selection_changed", - self._on_expected_selection_change - ) - - selection_model = tasks_view.selectionModel() - selection_model.selectionChanged.connect(self._on_selection_change) - - tasks_model.refreshed.connect(self._on_tasks_model_refresh) - - self._controller = controller - self._tasks_view = tasks_view - self._tasks_model = tasks_model - self._tasks_proxy_model = tasks_proxy_model - - self._selected_folder_id = None - - self._expected_selection_data = None - - def _clear(self): - self._tasks_model.clear() - - def _on_tasks_refresh_finished(self, event): - """Tasks were refreshed in controller. - - Ignore if refresh was triggered by tasks model, or refreshed folder is - not the same as currently selected folder. - - Args: - event (Event): Event object. - """ - - # Refresh only if current folder id is the same - if ( - event["sender"] == SENDER_NAME - or event["folder_id"] != self._selected_folder_id - ): - return - self._tasks_model.refresh(self._selected_folder_id) - - def _folder_selection_changed(self, event): - self._selected_folder_id = event["folder_id"] - self._tasks_model.refresh(self._selected_folder_id) - - def _on_tasks_model_refresh(self): - if not self._set_expected_selection(): - self._on_selection_change() - self._tasks_proxy_model.sort(0) - - def _set_expected_selection(self): - if self._expected_selection_data is None: - return False - folder_id = self._expected_selection_data["folder_id"] - task_name = self._expected_selection_data["task_name"] - self._expected_selection_data = None - model_folder_id = self._tasks_model.get_last_folder_id() - if folder_id != model_folder_id: - return False - if task_name is not None: - index = self._tasks_model.get_index_by_name(task_name) - if index.isValid(): - proxy_index = self._tasks_proxy_model.mapFromSource(index) - self._tasks_view.setCurrentIndex(proxy_index) - self._controller.expected_task_selected(folder_id, task_name) - return True - - def _on_expected_selection_change(self, event): - if event["task_selected"] or not event["folder_selected"]: - return - - model_folder_id = self._tasks_model.get_last_folder_id() - folder_id = event["folder_id"] - self._expected_selection_data = { - "task_name": event["task_name"], - "folder_id": folder_id, - } - - if folder_id != model_folder_id or self._tasks_model.is_refreshing: - return - self._set_expected_selection() - - def _get_selected_item_ids(self): - selection_model = self._tasks_view.selectionModel() - for index in selection_model.selectedIndexes(): - task_id = index.data(ITEM_ID_ROLE) - task_name = index.data(ITEM_NAME_ROLE) - parent_id = index.data(PARENT_ID_ROLE) - if task_name is not None: - return parent_id, task_id, task_name - return self._selected_folder_id, None, None - - def _on_selection_change(self): - # Don't trigger task change during refresh - # - a task was deselected if that happens - # - can cause crash triggered during tasks refreshing - if self._tasks_model.is_refreshing: - return - parent_id, task_id, task_name = self._get_selected_item_ids() - self._controller.set_selected_task(parent_id, task_id, task_name) diff --git a/openpype/tools/ayon_workfiles/widgets/window.py b/openpype/tools/ayon_workfiles/widgets/window.py index 6218d2dd06..eb2f2bc1c7 100644 --- a/openpype/tools/ayon_workfiles/widgets/window.py +++ b/openpype/tools/ayon_workfiles/widgets/window.py @@ -5,32 +5,16 @@ from openpype.tools.utils import ( PlaceholderLineEdit, MessageOverlayObject, ) -from openpype.tools.utils.lib import get_qta_icon_by_name_and_color +from openpype.tools.ayon_utils.widgets import FoldersWidget, TasksWidget from openpype.tools.ayon_workfiles.control import BaseWorkfileController +from openpype.tools.utils import GoToCurrentButton, RefreshButton from .side_panel import SidePanelWidget -from .folders_widget import FoldersWidget -from .tasks_widget import TasksWidget from .files_widget import FilesWidget from .utils import BaseOverlayFrame -# TODO move to utils -# from openpype.tools.utils.lib import ( -# get_refresh_icon, get_go_to_current_icon) -def get_refresh_icon(): - return get_qta_icon_by_name_and_color( - "fa.refresh", style.get_default_tools_icon_color() - ) - - -def get_go_to_current_icon(): - return get_qta_icon_by_name_and_color( - "fa.arrow-down", style.get_default_tools_icon_color() - ) - - class InvalidHostOverlay(BaseOverlayFrame): def __init__(self, parent): super(InvalidHostOverlay, self).__init__(parent) @@ -80,7 +64,7 @@ class WorkfilesToolWindow(QtWidgets.QWidget): self._default_window_flags = flags - self._folder_widget = None + self._folders_widget = None self._folder_filter_input = None self._files_widget = None @@ -100,7 +84,9 @@ class WorkfilesToolWindow(QtWidgets.QWidget): home_body_widget = QtWidgets.QWidget(home_page_widget) col_1_widget = self._create_col_1_widget(controller, parent) - tasks_widget = TasksWidget(controller, home_body_widget) + tasks_widget = TasksWidget( + controller, home_body_widget, handle_expected_selection=True + ) col_3_widget = self._create_col_3_widget(controller, home_body_widget) side_panel = SidePanelWidget(controller, home_body_widget) @@ -151,11 +137,11 @@ class WorkfilesToolWindow(QtWidgets.QWidget): self._on_open_finished ) controller.register_event_callback( - "controller.refresh.started", + "controller.reset.started", self._on_controller_refresh_started, ) controller.register_event_callback( - "controller.refresh.finished", + "controller.reset.finished", self._on_controller_refresh_finished, ) @@ -188,19 +174,12 @@ class WorkfilesToolWindow(QtWidgets.QWidget): folder_filter_input = PlaceholderLineEdit(header_widget) folder_filter_input.setPlaceholderText("Filter folders..") - go_to_current_btn = QtWidgets.QPushButton(header_widget) - go_to_current_btn.setIcon(get_go_to_current_icon()) - go_to_current_btn_sp = go_to_current_btn.sizePolicy() - go_to_current_btn_sp.setVerticalPolicy(QtWidgets.QSizePolicy.Minimum) - go_to_current_btn.setSizePolicy(go_to_current_btn_sp) + go_to_current_btn = GoToCurrentButton(header_widget) + refresh_btn = RefreshButton(header_widget) - refresh_btn = QtWidgets.QPushButton(header_widget) - refresh_btn.setIcon(get_refresh_icon()) - refresh_btn_sp = refresh_btn.sizePolicy() - refresh_btn_sp.setVerticalPolicy(QtWidgets.QSizePolicy.Minimum) - refresh_btn.setSizePolicy(refresh_btn_sp) - - folder_widget = FoldersWidget(controller, col_widget) + folder_widget = FoldersWidget( + controller, col_widget, handle_expected_selection=True + ) header_layout = QtWidgets.QHBoxLayout(header_widget) header_layout.setContentsMargins(0, 0, 0, 0) @@ -218,7 +197,7 @@ class WorkfilesToolWindow(QtWidgets.QWidget): refresh_btn.clicked.connect(self._on_refresh_clicked) self._folder_filter_input = folder_filter_input - self._folder_widget = folder_widget + self._folders_widget = folder_widget return col_widget @@ -300,7 +279,7 @@ class WorkfilesToolWindow(QtWidgets.QWidget): def refresh(self): """Trigger refresh of workfiles tool controller.""" - self._controller.refresh() + self._controller.reset() def showEvent(self, event): super(WorkfilesToolWindow, self).showEvent(event) @@ -338,7 +317,7 @@ class WorkfilesToolWindow(QtWidgets.QWidget): self._side_panel.set_published_mode(published_mode) def _on_folder_filter_change(self, text): - self._folder_widget.set_name_filter(text) + self._folders_widget.set_name_filter(text) def _on_go_to_current_clicked(self): self._controller.go_to_current_context() @@ -357,6 +336,10 @@ class WorkfilesToolWindow(QtWidgets.QWidget): if not self._host_is_valid: return + self._folders_widget.set_project_name( + self._controller.get_current_project_name() + ) + def _on_save_as_finished(self, event): if event["failed"]: self._overlay_messages_widget.add_message( From e340b9c7bc772d41c272bdbb4dfd42321c03df5d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Nov 2023 10:57:09 +0100 Subject: [PATCH 100/101] rename function 'asset_name' to 'prepare_scene_name' --- openpype/hosts/blender/api/plugin.py | 10 +++++----- openpype/hosts/blender/plugins/create/create_action.py | 2 +- openpype/hosts/blender/plugins/load/import_workfile.py | 2 +- openpype/hosts/blender/plugins/load/load_abc.py | 4 ++-- openpype/hosts/blender/plugins/load/load_action.py | 10 +++++----- openpype/hosts/blender/plugins/load/load_audio.py | 4 ++-- openpype/hosts/blender/plugins/load/load_blend.py | 4 ++-- openpype/hosts/blender/plugins/load/load_blendscene.py | 4 ++-- openpype/hosts/blender/plugins/load/load_camera_abc.py | 4 ++-- openpype/hosts/blender/plugins/load/load_camera_fbx.py | 4 ++-- openpype/hosts/blender/plugins/load/load_fbx.py | 4 ++-- .../hosts/blender/plugins/load/load_layout_json.py | 4 ++-- openpype/hosts/blender/plugins/load/load_look.py | 4 ++-- 13 files changed, 30 insertions(+), 30 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 7ac12b5549..d7155a1b53 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -28,7 +28,7 @@ from .lib import imprint VALID_EXTENSIONS = [".blend", ".json", ".abc", ".fbx"] -def asset_name( +def prepare_scene_name( asset: str, subset: str, namespace: Optional[str] = None ) -> str: """Return a consistent name for an asset.""" @@ -225,7 +225,7 @@ class BaseCreator(Creator): bpy.context.scene.collection.children.link(instances) # Create asset group - name = asset_name(instance_data["asset"], subset_name) + name = prepare_scene_name(instance_data["asset"], subset_name) if self.create_as_asset_group: # Create instance as empty instance_node = bpy.data.objects.new(name=name, object_data=None) @@ -298,7 +298,7 @@ class BaseCreator(Creator): "subset" in changes.changed_keys or "asset" in changes.changed_keys ): - name = asset_name(asset=data["asset"], subset=data["subset"]) + name = prepare_scene_name(asset=data["asset"], subset=data["subset"]) node.name = name imprint(node, data) @@ -454,7 +454,7 @@ class AssetLoader(LoaderPlugin): asset, subset ) namespace = namespace or f"{asset}_{unique_number}" - name = name or asset_name( + name = name or prepare_scene_name( asset, subset, unique_number ) @@ -483,7 +483,7 @@ class AssetLoader(LoaderPlugin): # asset = context["asset"]["name"] # subset = context["subset"]["name"] - # instance_name = asset_name(asset, subset, unique_number) + '_CON' + # instance_name = prepare_scene_name(asset, subset, unique_number) + '_CON' # return self._get_instance_collection(instance_name, nodes) diff --git a/openpype/hosts/blender/plugins/create/create_action.py b/openpype/hosts/blender/plugins/create/create_action.py index 0929778d78..caaa72fe8d 100644 --- a/openpype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -22,7 +22,7 @@ class CreateAction(plugin.BaseCreator): ) # Get instance name - name = plugin.asset_name(instance_data["asset"], subset_name) + name = plugin.prepare_scene_name(instance_data["asset"], subset_name) if pre_create_data.get("use_selection"): for obj in lib.get_selection(): diff --git a/openpype/hosts/blender/plugins/load/import_workfile.py b/openpype/hosts/blender/plugins/load/import_workfile.py index 4f5016d422..331f6a8bdb 100644 --- a/openpype/hosts/blender/plugins/load/import_workfile.py +++ b/openpype/hosts/blender/plugins/load/import_workfile.py @@ -7,7 +7,7 @@ def append_workfile(context, fname, do_import): asset = context['asset']['name'] subset = context['subset']['name'] - group_name = plugin.asset_name(asset, subset) + group_name = plugin.prepare_scene_name(asset, subset) # We need to preserve the original names of the scenes, otherwise, # if there are duplicate names in the current workfile, the imported diff --git a/openpype/hosts/blender/plugins/load/load_abc.py b/openpype/hosts/blender/plugins/load/load_abc.py index 8d1863d4d5..d7e82d1900 100644 --- a/openpype/hosts/blender/plugins/load/load_abc.py +++ b/openpype/hosts/blender/plugins/load/load_abc.py @@ -137,9 +137,9 @@ class CacheModelLoader(plugin.AssetLoader): asset = context["asset"]["name"] subset = context["subset"]["name"] - asset_name = plugin.asset_name(asset, subset) + asset_name = plugin.prepare_scene_name(asset, subset) unique_number = plugin.get_unique_number(asset, subset) - group_name = plugin.asset_name(asset, subset, unique_number) + group_name = plugin.prepare_scene_name(asset, subset, unique_number) namespace = namespace or f"{asset}_{unique_number}" containers = bpy.data.collections.get(AVALON_CONTAINERS) diff --git a/openpype/hosts/blender/plugins/load/load_action.py b/openpype/hosts/blender/plugins/load/load_action.py index 3447e67ebf..f7d32f92a5 100644 --- a/openpype/hosts/blender/plugins/load/load_action.py +++ b/openpype/hosts/blender/plugins/load/load_action.py @@ -7,7 +7,7 @@ from typing import Dict, List, Optional import bpy from openpype.pipeline import get_representation_path -import openpype.hosts.blender.api.plugin +from openpype.hosts.blender.api import plugin from openpype.hosts.blender.api.pipeline import ( containerise_existing, AVALON_PROPERTY, @@ -16,7 +16,7 @@ from openpype.hosts.blender.api.pipeline import ( logger = logging.getLogger("openpype").getChild("blender").getChild("load_action") -class BlendActionLoader(openpype.hosts.blender.api.plugin.AssetLoader): +class BlendActionLoader(plugin.AssetLoader): """Load action from a .blend file. Warning: @@ -46,8 +46,8 @@ class BlendActionLoader(openpype.hosts.blender.api.plugin.AssetLoader): libpath = self.filepath_from_context(context) asset = context["asset"]["name"] subset = context["subset"]["name"] - lib_container = openpype.hosts.blender.api.plugin.asset_name(asset, subset) - container_name = openpype.hosts.blender.api.plugin.asset_name( + lib_container = plugin.prepare_scene_name(asset, subset) + container_name = plugin.prepare_scene_name( asset, subset, namespace ) @@ -152,7 +152,7 @@ class BlendActionLoader(openpype.hosts.blender.api.plugin.AssetLoader): assert libpath.is_file(), ( f"The file doesn't exist: {libpath}" ) - assert extension in openpype.hosts.blender.api.plugin.VALID_EXTENSIONS, ( + assert extension in plugin.VALID_EXTENSIONS, ( f"Unsupported file: {libpath}" ) diff --git a/openpype/hosts/blender/plugins/load/load_audio.py b/openpype/hosts/blender/plugins/load/load_audio.py index ac8f363316..1e5bd39a32 100644 --- a/openpype/hosts/blender/plugins/load/load_audio.py +++ b/openpype/hosts/blender/plugins/load/load_audio.py @@ -42,9 +42,9 @@ class AudioLoader(plugin.AssetLoader): asset = context["asset"]["name"] subset = context["subset"]["name"] - asset_name = plugin.asset_name(asset, subset) + asset_name = plugin.prepare_scene_name(asset, subset) unique_number = plugin.get_unique_number(asset, subset) - group_name = plugin.asset_name(asset, subset, unique_number) + group_name = plugin.prepare_scene_name(asset, subset, unique_number) namespace = namespace or f"{asset}_{unique_number}" avalon_container = bpy.data.collections.get(AVALON_CONTAINERS) diff --git a/openpype/hosts/blender/plugins/load/load_blend.py b/openpype/hosts/blender/plugins/load/load_blend.py index 8b1af5a0da..f437e66795 100644 --- a/openpype/hosts/blender/plugins/load/load_blend.py +++ b/openpype/hosts/blender/plugins/load/load_blend.py @@ -133,9 +133,9 @@ class BlendLoader(plugin.AssetLoader): representation = str(context["representation"]["_id"]) - asset_name = plugin.asset_name(asset, subset) + asset_name = plugin.prepare_scene_name(asset, subset) unique_number = plugin.get_unique_number(asset, subset) - group_name = plugin.asset_name(asset, subset, unique_number) + group_name = plugin.prepare_scene_name(asset, subset, unique_number) namespace = namespace or f"{asset}_{unique_number}" avalon_container = bpy.data.collections.get(AVALON_CONTAINERS) diff --git a/openpype/hosts/blender/plugins/load/load_blendscene.py b/openpype/hosts/blender/plugins/load/load_blendscene.py index 2c955af9e8..6cc7f39d03 100644 --- a/openpype/hosts/blender/plugins/load/load_blendscene.py +++ b/openpype/hosts/blender/plugins/load/load_blendscene.py @@ -85,9 +85,9 @@ class BlendSceneLoader(plugin.AssetLoader): except ValueError: family = "model" - asset_name = plugin.asset_name(asset, subset) + asset_name = plugin.prepare_scene_name(asset, subset) unique_number = plugin.get_unique_number(asset, subset) - group_name = plugin.asset_name(asset, subset, unique_number) + group_name = plugin.prepare_scene_name(asset, subset, unique_number) namespace = namespace or f"{asset}_{unique_number}" avalon_container = bpy.data.collections.get(AVALON_CONTAINERS) diff --git a/openpype/hosts/blender/plugins/load/load_camera_abc.py b/openpype/hosts/blender/plugins/load/load_camera_abc.py index 05d3fb764d..ecd6bb98f1 100644 --- a/openpype/hosts/blender/plugins/load/load_camera_abc.py +++ b/openpype/hosts/blender/plugins/load/load_camera_abc.py @@ -87,9 +87,9 @@ class AbcCameraLoader(plugin.AssetLoader): asset = context["asset"]["name"] subset = context["subset"]["name"] - asset_name = plugin.asset_name(asset, subset) + asset_name = plugin.prepare_scene_name(asset, subset) unique_number = plugin.get_unique_number(asset, subset) - group_name = plugin.asset_name(asset, subset, unique_number) + group_name = plugin.prepare_scene_name(asset, subset, unique_number) namespace = namespace or f"{asset}_{unique_number}" avalon_container = bpy.data.collections.get(AVALON_CONTAINERS) diff --git a/openpype/hosts/blender/plugins/load/load_camera_fbx.py b/openpype/hosts/blender/plugins/load/load_camera_fbx.py index 3cca6e7fd3..2d53d3e573 100644 --- a/openpype/hosts/blender/plugins/load/load_camera_fbx.py +++ b/openpype/hosts/blender/plugins/load/load_camera_fbx.py @@ -90,9 +90,9 @@ class FbxCameraLoader(plugin.AssetLoader): asset = context["asset"]["name"] subset = context["subset"]["name"] - asset_name = plugin.asset_name(asset, subset) + asset_name = plugin.prepare_scene_name(asset, subset) unique_number = plugin.get_unique_number(asset, subset) - group_name = plugin.asset_name(asset, subset, unique_number) + group_name = plugin.prepare_scene_name(asset, subset, unique_number) namespace = namespace or f"{asset}_{unique_number}" avalon_container = bpy.data.collections.get(AVALON_CONTAINERS) diff --git a/openpype/hosts/blender/plugins/load/load_fbx.py b/openpype/hosts/blender/plugins/load/load_fbx.py index e129ea6754..8fce53a5d5 100644 --- a/openpype/hosts/blender/plugins/load/load_fbx.py +++ b/openpype/hosts/blender/plugins/load/load_fbx.py @@ -134,9 +134,9 @@ class FbxModelLoader(plugin.AssetLoader): asset = context["asset"]["name"] subset = context["subset"]["name"] - asset_name = plugin.asset_name(asset, subset) + asset_name = plugin.prepare_scene_name(asset, subset) unique_number = plugin.get_unique_number(asset, subset) - group_name = plugin.asset_name(asset, subset, unique_number) + group_name = plugin.prepare_scene_name(asset, subset, unique_number) namespace = namespace or f"{asset}_{unique_number}" avalon_container = bpy.data.collections.get(AVALON_CONTAINERS) diff --git a/openpype/hosts/blender/plugins/load/load_layout_json.py b/openpype/hosts/blender/plugins/load/load_layout_json.py index a941c77a8e..748ac619b6 100644 --- a/openpype/hosts/blender/plugins/load/load_layout_json.py +++ b/openpype/hosts/blender/plugins/load/load_layout_json.py @@ -149,9 +149,9 @@ class JsonLayoutLoader(plugin.AssetLoader): asset = context["asset"]["name"] subset = context["subset"]["name"] - asset_name = plugin.asset_name(asset, subset) + asset_name = plugin.prepare_scene_name(asset, subset) unique_number = plugin.get_unique_number(asset, subset) - group_name = plugin.asset_name(asset, subset, unique_number) + group_name = plugin.prepare_scene_name(asset, subset, unique_number) namespace = namespace or f"{asset}_{unique_number}" avalon_container = bpy.data.collections.get(AVALON_CONTAINERS) diff --git a/openpype/hosts/blender/plugins/load/load_look.py b/openpype/hosts/blender/plugins/load/load_look.py index c121f55633..8d3118d83b 100644 --- a/openpype/hosts/blender/plugins/load/load_look.py +++ b/openpype/hosts/blender/plugins/load/load_look.py @@ -96,14 +96,14 @@ class BlendLookLoader(plugin.AssetLoader): asset = context["asset"]["name"] subset = context["subset"]["name"] - lib_container = plugin.asset_name( + lib_container = plugin.prepare_scene_name( asset, subset ) unique_number = plugin.get_unique_number( asset, subset ) namespace = namespace or f"{asset}_{unique_number}" - container_name = plugin.asset_name( + container_name = plugin.prepare_scene_name( asset, subset, unique_number ) From e8f7f146ab670e5c21a8374143456858df80260b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Nov 2023 11:02:29 +0100 Subject: [PATCH 101/101] formatting fix --- openpype/hosts/blender/api/plugin.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index d7155a1b53..8d33187da3 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -298,7 +298,9 @@ class BaseCreator(Creator): "subset" in changes.changed_keys or "asset" in changes.changed_keys ): - name = prepare_scene_name(asset=data["asset"], subset=data["subset"]) + name = prepare_scene_name( + asset=data["asset"], subset=data["subset"] + ) node.name = name imprint(node, data) @@ -483,7 +485,9 @@ class AssetLoader(LoaderPlugin): # asset = context["asset"]["name"] # subset = context["subset"]["name"] - # instance_name = prepare_scene_name(asset, subset, unique_number) + '_CON' + # instance_name = prepare_scene_name( + # asset, subset, unique_number + # ) + '_CON' # return self._get_instance_collection(instance_name, nodes)