From 5bf25ffd3ee322e16c508bda88322faafa198e04 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 11 Mar 2020 12:39:25 +0000 Subject: [PATCH] Bug fixing and code optimization --- pype/plugins/blender/create/create_rig.py | 63 ++++++- pype/plugins/blender/load/load_action.py | 6 +- pype/plugins/blender/load/load_animation.py | 161 ++++++++---------- pype/plugins/blender/load/load_model.py | 146 +++++++---------- pype/plugins/blender/load/load_rig.py | 172 ++++++++------------ 5 files changed, 262 insertions(+), 286 deletions(-) diff --git a/pype/plugins/blender/create/create_rig.py b/pype/plugins/blender/create/create_rig.py index 5d83fafdd3..f630c63966 100644 --- a/pype/plugins/blender/create/create_rig.py +++ b/pype/plugins/blender/create/create_rig.py @@ -14,6 +14,23 @@ class CreateRig(Creator): family = "rig" icon = "wheelchair" + # @staticmethod + # def _find_layer_collection(self, layer_collection, collection): + + # found = None + + # if (layer_collection.collection == collection): + + # return layer_collection + + # for layer in layer_collection.children: + + # found = self._find_layer_collection(layer, collection) + + # if found: + + # return found + def process(self): import pype.blender @@ -25,8 +42,52 @@ class CreateRig(Creator): self.data['task'] = api.Session.get('AVALON_TASK') lib.imprint(collection, self.data) + # Add the rig object and all the children meshes to + # a set and link them all at the end to avoid duplicates. + # Blender crashes if trying to link an object that is already linked. + # This links automatically the children meshes if they were not + # selected, and doesn't link them twice if they, insted, + # were manually selected by the user. + objects_to_link = set() + if (self.options or {}).get("useSelection"): + for obj in lib.get_selection(): - collection.objects.link(obj) + + objects_to_link.add( obj ) + + if obj.type == 'ARMATURE': + + for subobj in obj.children: + + objects_to_link.add( subobj ) + + # Create a new collection and link the widgets that + # the rig uses. + # custom_shapes = set() + + # for posebone in obj.pose.bones: + + # if posebone.custom_shape is not None: + + # custom_shapes.add( posebone.custom_shape ) + + # if len( custom_shapes ) > 0: + + # widgets_collection = bpy.data.collections.new(name="Widgets") + + # collection.children.link(widgets_collection) + + # for custom_shape in custom_shapes: + + # widgets_collection.objects.link( custom_shape ) + + # layer_collection = self._find_layer_collection(bpy.context.view_layer.layer_collection, widgets_collection) + + # layer_collection.exclude = True + + for obj in objects_to_link: + + collection.objects.link(obj) return collection diff --git a/pype/plugins/blender/load/load_action.py b/pype/plugins/blender/load/load_action.py index 6094f712ae..747bcd47f5 100644 --- a/pype/plugins/blender/load/load_action.py +++ b/pype/plugins/blender/load/load_action.py @@ -13,7 +13,7 @@ from avalon import api logger = logging.getLogger("pype").getChild("blender").getChild("load_action") -class BlendAnimationLoader(pype.blender.AssetLoader): +class BlendActionLoader(pype.blender.AssetLoader): """Load action from a .blend file. Warning: @@ -47,7 +47,6 @@ class BlendAnimationLoader(pype.blender.AssetLoader): container_name = pype.blender.plugin.asset_name( asset, subset, namespace ) - relative = bpy.context.preferences.filepaths.use_relative_paths container = bpy.data.collections.new(lib_container) container.name = container_name @@ -65,6 +64,7 @@ class BlendAnimationLoader(pype.blender.AssetLoader): container_metadata["libpath"] = libpath container_metadata["lib_container"] = lib_container + relative = bpy.context.preferences.filepaths.use_relative_paths with bpy.data.libraries.load( libpath, link=True, relative=relative ) as (_, data_to): @@ -85,8 +85,6 @@ class BlendAnimationLoader(pype.blender.AssetLoader): obj = obj.make_local() - # obj.data.make_local() - if obj.animation_data is not None and obj.animation_data.action is not None: obj.animation_data.action.make_local() diff --git a/pype/plugins/blender/load/load_animation.py b/pype/plugins/blender/load/load_animation.py index c6d18fb1a9..0610517b67 100644 --- a/pype/plugins/blender/load/load_animation.py +++ b/pype/plugins/blender/load/load_animation.py @@ -28,43 +28,22 @@ class BlendAnimationLoader(pype.blender.AssetLoader): icon = "code-fork" color = "orange" - def process_asset( - self, context: dict, name: str, namespace: Optional[str] = None, - options: Optional[Dict] = None - ) -> Optional[List]: - """ - Arguments: - name: Use pre-defined name - namespace: Use pre-defined namespace - context: Full parenthood of representation to load - options: Additional settings dictionary - """ + @staticmethod + def _remove(self, objects, lib_container): + + for obj in objects: + + if obj.type == 'ARMATURE': + bpy.data.armatures.remove(obj.data) + elif obj.type == 'MESH': + bpy.data.meshes.remove(obj.data) + + bpy.data.collections.remove(bpy.data.collections[lib_container]) + + @staticmethod + def _process(self, libpath, lib_container, container_name): - libpath = self.fname - asset = context["asset"]["name"] - subset = context["subset"]["name"] - lib_container = pype.blender.plugin.asset_name(asset, subset) - container_name = pype.blender.plugin.asset_name( - asset, subset, namespace - ) relative = bpy.context.preferences.filepaths.use_relative_paths - - container = bpy.data.collections.new(lib_container) - container.name = container_name - avalon.blender.pipeline.containerise_existing( - container, - name, - namespace, - context, - self.__class__.__name__, - ) - - container_metadata = container.get( - avalon.blender.pipeline.AVALON_PROPERTY) - - container_metadata["libpath"] = libpath - container_metadata["lib_container"] = lib_container - with bpy.data.libraries.load( libpath, link=True, relative=relative ) as (_, data_to): @@ -77,8 +56,9 @@ class BlendAnimationLoader(pype.blender.AssetLoader): animation_container = scene.collection.children[lib_container].make_local() meshes = [obj for obj in animation_container.objects if obj.type == 'MESH'] - armatures = [ - obj for obj in animation_container.objects if obj.type == 'ARMATURE'] + armatures = [obj for obj in animation_container.objects if obj.type == 'ARMATURE'] + + # Should check if there is only an armature? objects_list = [] @@ -106,11 +86,51 @@ class BlendAnimationLoader(pype.blender.AssetLoader): animation_container.pop( avalon.blender.pipeline.AVALON_PROPERTY ) + bpy.ops.object.select_all(action='DESELECT') + + return objects_list + + def process_asset( + self, context: dict, name: str, namespace: Optional[str] = None, + options: Optional[Dict] = None + ) -> Optional[List]: + """ + Arguments: + name: Use pre-defined name + namespace: Use pre-defined namespace + context: Full parenthood of representation to load + options: Additional settings dictionary + """ + + libpath = self.fname + asset = context["asset"]["name"] + subset = context["subset"]["name"] + lib_container = pype.blender.plugin.asset_name(asset, subset) + container_name = pype.blender.plugin.asset_name( + asset, subset, namespace + ) + + container = bpy.data.collections.new(lib_container) + container.name = container_name + avalon.blender.pipeline.containerise_existing( + container, + name, + namespace, + context, + self.__class__.__name__, + ) + + container_metadata = container.get( + avalon.blender.pipeline.AVALON_PROPERTY) + + container_metadata["libpath"] = libpath + container_metadata["lib_container"] = lib_container + + objects_list = self._process(self, libpath, lib_container, container_name) + # Save the list of objects in the metadata container container_metadata["objects"] = objects_list - bpy.ops.object.select_all(action='DESELECT') - nodes = list(container.objects) nodes.append(container) self[:] = nodes @@ -177,59 +197,16 @@ class BlendAnimationLoader(pype.blender.AssetLoader): logger.info("Library already loaded, not updating...") return - # Get the armature of the rig - armatures = [obj for obj in collection_metadata["objects"] - if obj.type == 'ARMATURE'] - assert(len(armatures) == 1) - - for obj in collection_metadata["objects"]: - - if obj.type == 'ARMATURE': - bpy.data.armatures.remove(obj.data) - elif obj.type == 'MESH': - bpy.data.meshes.remove(obj.data) - + objects = collection_metadata["objects"] lib_container = collection_metadata["lib_container"] - bpy.data.collections.remove(bpy.data.collections[lib_container]) - - relative = bpy.context.preferences.filepaths.use_relative_paths - with bpy.data.libraries.load( - str(libpath), link=True, relative=relative - ) as (_, data_to): - data_to.collections = [lib_container] - - scene = bpy.context.scene - - scene.collection.children.link(bpy.data.collections[lib_container]) - - animation_container = scene.collection.children[lib_container].make_local() - - meshes = [obj for obj in animation_container.objects if obj.type == 'MESH'] - armatures = [ - obj for obj in animation_container.objects if obj.type == 'ARMATURE'] - objects_list = [] - + # Get the armature of the rig + armatures = [obj for obj in objects if obj.type == 'ARMATURE'] assert(len(armatures) == 1) - # Link meshes first, then armatures. - # The armature is unparented for all the non-local meshes, - # when it is made local. - for obj in meshes + armatures: + self._remove(self, objects, lib_container) - obj = obj.make_local() - - obj.data.make_local() - - if not obj.get(avalon.blender.pipeline.AVALON_PROPERTY): - - obj[avalon.blender.pipeline.AVALON_PROPERTY] = dict() - - avalon_info = obj[avalon.blender.pipeline.AVALON_PROPERTY] - avalon_info.update({"container_name": collection.name}) - objects_list.append(obj) - - animation_container.pop( avalon.blender.pipeline.AVALON_PROPERTY ) + objects_list = self._process(self, str(libpath), lib_container, collection.name) # Save the list of objects in the metadata container collection_metadata["objects"] = objects_list @@ -266,14 +243,8 @@ class BlendAnimationLoader(pype.blender.AssetLoader): objects = collection_metadata["objects"] lib_container = collection_metadata["lib_container"] - for obj in objects: - - if obj.type == 'ARMATURE': - bpy.data.armatures.remove(obj.data) - elif obj.type == 'MESH': - bpy.data.meshes.remove(obj.data) - - bpy.data.collections.remove(bpy.data.collections[lib_container]) + self._remove(self, objects, lib_container) + bpy.data.collections.remove(collection) return True diff --git a/pype/plugins/blender/load/load_model.py b/pype/plugins/blender/load/load_model.py index 8ba8c5cfc8..10904a1f7b 100644 --- a/pype/plugins/blender/load/load_model.py +++ b/pype/plugins/blender/load/load_model.py @@ -31,43 +31,19 @@ class BlendModelLoader(pype.blender.AssetLoader): icon = "code-fork" color = "orange" - def process_asset( - self, context: dict, name: str, namespace: Optional[str] = None, - options: Optional[Dict] = None - ) -> Optional[List]: - """ - Arguments: - name: Use pre-defined name - namespace: Use pre-defined namespace - context: Full parenthood of representation to load - options: Additional settings dictionary - """ + @staticmethod + def _remove(self, objects, lib_container): + + for obj in objects: + + bpy.data.meshes.remove(obj.data) + + bpy.data.collections.remove(bpy.data.collections[lib_container]) + + @staticmethod + def _process(self, libpath, lib_container, container_name): - libpath = self.fname - asset = context["asset"]["name"] - subset = context["subset"]["name"] - lib_container = pype.blender.plugin.asset_name(asset, subset) - container_name = pype.blender.plugin.asset_name( - asset, subset, namespace - ) relative = bpy.context.preferences.filepaths.use_relative_paths - - container = bpy.data.collections.new(lib_container) - container.name = container_name - avalon.blender.pipeline.containerise_existing( - container, - name, - namespace, - context, - self.__class__.__name__, - ) - - container_metadata = container.get( - avalon.blender.pipeline.AVALON_PROPERTY) - - container_metadata["libpath"] = libpath - container_metadata["lib_container"] = lib_container - with bpy.data.libraries.load( libpath, link=True, relative=relative ) as (_, data_to): @@ -102,13 +78,53 @@ class BlendModelLoader(pype.blender.AssetLoader): model_container.pop( avalon.blender.pipeline.AVALON_PROPERTY ) + bpy.ops.object.select_all(action='DESELECT') + + return objects_list + + def process_asset( + self, context: dict, name: str, namespace: Optional[str] = None, + options: Optional[Dict] = None + ) -> Optional[List]: + """ + Arguments: + name: Use pre-defined name + namespace: Use pre-defined namespace + context: Full parenthood of representation to load + options: Additional settings dictionary + """ + + libpath = self.fname + asset = context["asset"]["name"] + subset = context["subset"]["name"] + lib_container = pype.blender.plugin.asset_name(asset, subset) + container_name = pype.blender.plugin.asset_name( + asset, subset, namespace + ) + + collection = bpy.data.collections.new(lib_container) + collection.name = container_name + avalon.blender.pipeline.containerise_existing( + collection, + name, + namespace, + context, + self.__class__.__name__, + ) + + container_metadata = collection.get( + avalon.blender.pipeline.AVALON_PROPERTY) + + container_metadata["libpath"] = libpath + container_metadata["lib_container"] = lib_container + + objects_list = self._process(self, libpath, lib_container, container_name) + # Save the list of objects in the metadata container container_metadata["objects"] = objects_list - bpy.ops.object.select_all(action='DESELECT') - - nodes = list(container.objects) - nodes.append(container) + nodes = list(collection.objects) + nodes.append(collection) self[:] = nodes return nodes @@ -154,8 +170,10 @@ class BlendModelLoader(pype.blender.AssetLoader): collection_metadata = collection.get( avalon.blender.pipeline.AVALON_PROPERTY) - collection_libpath = collection_metadata["libpath"] + objects = collection_metadata["objects"] + lib_container = collection_metadata["lib_container"] + normalized_collection_libpath = ( str(Path(bpy.path.abspath(collection_libpath)).resolve()) ) @@ -171,54 +189,15 @@ class BlendModelLoader(pype.blender.AssetLoader): logger.info("Library already loaded, not updating...") return - for obj in collection_metadata["objects"]: + self._remove(self, objects, lib_container) - bpy.data.meshes.remove(obj.data) - - lib_container = collection_metadata["lib_container"] - - bpy.data.collections.remove(bpy.data.collections[lib_container]) - - relative = bpy.context.preferences.filepaths.use_relative_paths - with bpy.data.libraries.load( - str(libpath), link=True, relative=relative - ) as (_, data_to): - data_to.collections = [lib_container] - - scene = bpy.context.scene - - scene.collection.children.link(bpy.data.collections[lib_container]) - - model_container = scene.collection.children[lib_container].make_local() - - objects_list = [] - - # Link meshes first, then armatures. - # The armature is unparented for all the non-local meshes, - # when it is made local. - for obj in model_container.objects: - - obj = obj.make_local() - - obj.data.make_local() - - if not obj.get(avalon.blender.pipeline.AVALON_PROPERTY): - - obj[avalon.blender.pipeline.AVALON_PROPERTY] = dict() - - avalon_info = obj[avalon.blender.pipeline.AVALON_PROPERTY] - avalon_info.update({"container_name": collection.name}) - objects_list.append(obj) - - model_container.pop( avalon.blender.pipeline.AVALON_PROPERTY ) + objects_list = self._process(self, str(libpath), lib_container, collection.name) # Save the list of objects in the metadata container collection_metadata["objects"] = objects_list collection_metadata["libpath"] = str(libpath) collection_metadata["representation"] = str(representation["_id"]) - bpy.ops.object.select_all(action='DESELECT') - def remove(self, container: Dict) -> bool: """Remove an existing container from a Blender scene. @@ -246,11 +225,8 @@ class BlendModelLoader(pype.blender.AssetLoader): objects = collection_metadata["objects"] lib_container = collection_metadata["lib_container"] - for obj in objects: + self._remove(self, objects, lib_container) - bpy.data.meshes.remove(obj.data) - - bpy.data.collections.remove(bpy.data.collections[lib_container]) bpy.data.collections.remove(collection) return True diff --git a/pype/plugins/blender/load/load_rig.py b/pype/plugins/blender/load/load_rig.py index c19717cd82..dcb70da6d8 100644 --- a/pype/plugins/blender/load/load_rig.py +++ b/pype/plugins/blender/load/load_rig.py @@ -30,6 +30,68 @@ class BlendRigLoader(pype.blender.AssetLoader): label = "Link Rig" icon = "code-fork" color = "orange" + + @staticmethod + def _remove(self, objects, lib_container): + + for obj in objects: + + if obj.type == 'ARMATURE': + bpy.data.armatures.remove(obj.data) + elif obj.type == 'MESH': + bpy.data.meshes.remove(obj.data) + + bpy.data.collections.remove(bpy.data.collections[lib_container]) + + @staticmethod + def _process(self, libpath, lib_container, container_name, action): + + relative = bpy.context.preferences.filepaths.use_relative_paths + with bpy.data.libraries.load( + libpath, link=True, relative=relative + ) as (_, data_to): + data_to.collections = [lib_container] + + scene = bpy.context.scene + + scene.collection.children.link(bpy.data.collections[lib_container]) + + rig_container = scene.collection.children[lib_container].make_local() + + meshes = [obj for obj in rig_container.objects if obj.type == 'MESH'] + armatures = [obj for obj in rig_container.objects if obj.type == 'ARMATURE'] + + objects_list = [] + + assert(len(armatures) == 1) + + # Link meshes first, then armatures. + # The armature is unparented for all the non-local meshes, + # when it is made local. + for obj in meshes + armatures: + + obj = obj.make_local() + + obj.data.make_local() + + if not obj.get(avalon.blender.pipeline.AVALON_PROPERTY): + + obj[avalon.blender.pipeline.AVALON_PROPERTY] = dict() + + avalon_info = obj[avalon.blender.pipeline.AVALON_PROPERTY] + avalon_info.update({"container_name": container_name}) + + if obj.type == 'ARMATURE' and action is not None: + + obj.animation_data.action = action + + objects_list.append(obj) + + rig_container.pop( avalon.blender.pipeline.AVALON_PROPERTY ) + + bpy.ops.object.select_all(action='DESELECT') + + return objects_list def process_asset( self, context: dict, name: str, namespace: Optional[str] = None, @@ -50,7 +112,6 @@ class BlendRigLoader(pype.blender.AssetLoader): container_name = pype.blender.plugin.asset_name( asset, subset, namespace ) - relative = bpy.context.preferences.filepaths.use_relative_paths container = bpy.data.collections.new(lib_container) container.name = container_name @@ -68,48 +129,11 @@ class BlendRigLoader(pype.blender.AssetLoader): container_metadata["libpath"] = libpath container_metadata["lib_container"] = lib_container - with bpy.data.libraries.load( - libpath, link=True, relative=relative - ) as (_, data_to): - data_to.collections = [lib_container] - - scene = bpy.context.scene - - scene.collection.children.link(bpy.data.collections[lib_container]) - - rig_container = scene.collection.children[lib_container].make_local() - - meshes = [obj for obj in rig_container.objects if obj.type == 'MESH'] - armatures = [ - obj for obj in rig_container.objects if obj.type == 'ARMATURE'] - - objects_list = [] - - # Link meshes first, then armatures. - # The armature is unparented for all the non-local meshes, - # when it is made local. - for obj in meshes + armatures: - - obj = obj.make_local() - - obj.data.make_local() - - if not obj.get(avalon.blender.pipeline.AVALON_PROPERTY): - - obj[avalon.blender.pipeline.AVALON_PROPERTY] = dict() - - avalon_info = obj[avalon.blender.pipeline.AVALON_PROPERTY] - avalon_info.update({"container_name": container_name}) - - objects_list.append(obj) - - rig_container.pop( avalon.blender.pipeline.AVALON_PROPERTY ) + objects_list = self._process(self, libpath, lib_container, container_name, None) # Save the list of objects in the metadata container container_metadata["objects"] = objects_list - bpy.ops.object.select_all(action='DESELECT') - nodes = list(container.objects) nodes.append(container) self[:] = nodes @@ -159,8 +183,10 @@ class BlendRigLoader(pype.blender.AssetLoader): collection_metadata = collection.get( avalon.blender.pipeline.AVALON_PROPERTY) - collection_libpath = collection_metadata["libpath"] + objects = collection_metadata["objects"] + lib_container = collection_metadata["lib_container"] + normalized_collection_libpath = ( str(Path(bpy.path.abspath(collection_libpath)).resolve()) ) @@ -177,64 +203,14 @@ class BlendRigLoader(pype.blender.AssetLoader): return # Get the armature of the rig - armatures = [obj for obj in collection_metadata["objects"] - if obj.type == 'ARMATURE'] + armatures = [obj for obj in objects if obj.type == 'ARMATURE'] assert(len(armatures) == 1) action = armatures[0].animation_data.action - for obj in collection_metadata["objects"]: + self._remove(self, objects, lib_container) - if obj.type == 'ARMATURE': - bpy.data.armatures.remove(obj.data) - elif obj.type == 'MESH': - bpy.data.meshes.remove(obj.data) - - lib_container = collection_metadata["lib_container"] - - bpy.data.collections.remove(bpy.data.collections[lib_container]) - - relative = bpy.context.preferences.filepaths.use_relative_paths - with bpy.data.libraries.load( - str(libpath), link=True, relative=relative - ) as (_, data_to): - data_to.collections = [lib_container] - - scene = bpy.context.scene - - scene.collection.children.link(bpy.data.collections[lib_container]) - - rig_container = scene.collection.children[lib_container].make_local() - - meshes = [obj for obj in rig_container.objects if obj.type == 'MESH'] - armatures = [ - obj for obj in rig_container.objects if obj.type == 'ARMATURE'] - objects_list = [] - - assert(len(armatures) == 1) - - # Link meshes first, then armatures. - # The armature is unparented for all the non-local meshes, - # when it is made local. - for obj in meshes + armatures: - - obj = obj.make_local() - - obj.data.make_local() - - if not obj.get(avalon.blender.pipeline.AVALON_PROPERTY): - - obj[avalon.blender.pipeline.AVALON_PROPERTY] = dict() - - avalon_info = obj[avalon.blender.pipeline.AVALON_PROPERTY] - avalon_info.update({"container_name": collection.name}) - objects_list.append(obj) - - if obj.type == 'ARMATURE' and action is not None: - - obj.animation_data.action = action - - rig_container.pop( avalon.blender.pipeline.AVALON_PROPERTY ) + objects_list = self._process(self, str(libpath), lib_container, collection.name, action) # Save the list of objects in the metadata container collection_metadata["objects"] = objects_list @@ -271,14 +247,8 @@ class BlendRigLoader(pype.blender.AssetLoader): objects = collection_metadata["objects"] lib_container = collection_metadata["lib_container"] - for obj in objects: - - if obj.type == 'ARMATURE': - bpy.data.armatures.remove(obj.data) - elif obj.type == 'MESH': - bpy.data.meshes.remove(obj.data) - - bpy.data.collections.remove(bpy.data.collections[lib_container]) + self._remove(self, objects, lib_container) + bpy.data.collections.remove(collection) return True