diff --git a/pype/hosts/blender/plugin.py b/pype/hosts/blender/plugin.py index 5a2596c9d8..eff41956e8 100644 --- a/pype/hosts/blender/plugin.py +++ b/pype/hosts/blender/plugin.py @@ -24,13 +24,17 @@ def asset_namespace( asset: str, subset: str ) -> str: """Return a unique namespace based on the asset name.""" - avalon_containers = bpy.data.collections.get( - blender.pipeline.AVALON_CONTAINERS - ) + avalon_containers = [ + c for c in bpy.data.collections + if c.name == 'AVALON_CONTAINERS' + ] + loaded_assets = [] + for c in avalon_containers: + loaded_assets.extend(c.children) if avalon_containers is None: return "1" collections_names = [ - c.name for c in avalon_containers.children + c.name for c in loaded_assets ] count = 1 name = f"{asset_name(asset, subset, str(count))}_CON" @@ -40,6 +44,12 @@ def asset_namespace( return str(count) +def prepare_data(data, container_name): + name = data.name + data = data.make_local() + data.name = f"{name}:{container_name}" + + def create_blender_context(active: Optional[bpy.types.Object] = None, selected: Optional[bpy.types.Object] = None,): """Create a new Blender context. If an object is passed as @@ -67,6 +77,18 @@ def create_blender_context(active: Optional[bpy.types.Object] = None, raise Exception("Could not create a custom Blender context.") +def get_parent_collection(collection): + """Get the parent of the input collection""" + check_list = [bpy.context.scene.collection] + + for c in check_list: + if collection.name in c.children.keys(): + return c + check_list.extend(c.children) + + return None + + class AssetLoader(api.Loader): """A basic AssetLoader for Blender diff --git a/pype/plugins/blender/load/load_layout.py b/pype/plugins/blender/load/load_layout.py index 0c1032c4fb..5d3f7d92cf 100644 --- a/pype/plugins/blender/load/load_layout.py +++ b/pype/plugins/blender/load/load_layout.py @@ -7,20 +7,14 @@ from typing import Dict, List, Optional from avalon import api, blender import bpy -import pype.hosts.blender.plugin - +import pype.hosts.blender.plugin as plugin logger = logging.getLogger("pype").getChild( "blender").getChild("load_layout") -class BlendLayoutLoader(pype.hosts.blender.plugin.AssetLoader): - """Load animations from a .blend file. - - Warning: - Loading the same asset more then once is not properly supported at the - moment. - """ +class BlendLayoutLoader(plugin.AssetLoader): + """Load layout from a .blend file.""" families = ["layout"] representations = ["blend"] @@ -29,24 +23,25 @@ class BlendLayoutLoader(pype.hosts.blender.plugin.AssetLoader): icon = "code-fork" color = "orange" - def _remove(self, objects, lib_container): - + def _remove(self, objects, obj_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) + elif obj.type == 'CAMERA': + bpy.data.cameras.remove(obj.data) + elif obj.type == 'CURVE': + bpy.data.curves.remove(obj.data) - for element_container in bpy.data.collections[lib_container].children: + for element_container in obj_container.children: for child in element_container.children: bpy.data.collections.remove(child) bpy.data.collections.remove(element_container) - bpy.data.collections.remove(bpy.data.collections[lib_container]) + bpy.data.collections.remove(obj_container) def _process(self, libpath, lib_container, container_name, actions): - relative = bpy.context.preferences.filepaths.use_relative_paths with bpy.data.libraries.load( libpath, link=True, relative=relative @@ -58,26 +53,38 @@ class BlendLayoutLoader(pype.hosts.blender.plugin.AssetLoader): scene.collection.children.link(bpy.data.collections[lib_container]) layout_container = scene.collection.children[lib_container].make_local() + layout_container.name = container_name - meshes = [] + objects_local_types = ['MESH', 'CAMERA', 'CURVE'] + + objects = [] armatures = [] - objects_list = [] + containers = list(layout_container.children) - for element_container in layout_container.children: - element_container.make_local() - meshes.extend([obj for obj in element_container.objects if obj.type == 'MESH']) - armatures.extend([obj for obj in element_container.objects if obj.type == 'ARMATURE']) - for child in element_container.children: - child.make_local() - meshes.extend(child.objects) + for container in layout_container.children: + if container.name == blender.pipeline.AVALON_CONTAINERS: + containers.remove(container) + + for container in containers: + container.make_local() + objects.extend([ + obj for obj in container.objects + if obj.type in objects_local_types + ]) + armatures.extend([ + obj for obj in container.objects + if obj.type == 'ARMATURE' + ]) + containers.extend(list(container.children)) # 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() + for obj in objects + armatures: + obj.make_local() + if obj.data: + obj.data.make_local() if not obj.get(blender.pipeline.AVALON_PROPERTY): obj[blender.pipeline.AVALON_PROPERTY] = dict() @@ -85,18 +92,16 @@ class BlendLayoutLoader(pype.hosts.blender.plugin.AssetLoader): avalon_info = obj[blender.pipeline.AVALON_PROPERTY] avalon_info.update({"container_name": container_name}) - action = actions.get( obj.name, None ) + action = actions.get(obj.name, None) if obj.type == 'ARMATURE' and action is not None: obj.animation_data.action = action - objects_list.append(obj) - layout_container.pop(blender.pipeline.AVALON_PROPERTY) bpy.ops.object.select_all(action='DESELECT') - return objects_list + return layout_container def process_asset( self, context: dict, name: str, namespace: Optional[str] = None, @@ -113,13 +118,17 @@ class BlendLayoutLoader(pype.hosts.blender.plugin.AssetLoader): libpath = self.fname asset = context["asset"]["name"] subset = context["subset"]["name"] - lib_container = pype.hosts.blender.plugin.asset_name(asset, subset) - container_name = pype.hosts.blender.plugin.asset_name( + lib_container = plugin.asset_name( + asset, subset + ) + namespace = namespace or plugin.asset_namespace( + asset, subset + ) + container_name = plugin.asset_name( asset, subset, namespace ) container = bpy.data.collections.new(lib_container) - container.name = container_name blender.pipeline.containerise_existing( container, name, @@ -134,11 +143,13 @@ class BlendLayoutLoader(pype.hosts.blender.plugin.AssetLoader): container_metadata["libpath"] = libpath container_metadata["lib_container"] = lib_container - objects_list = self._process( + obj_container = self._process( libpath, lib_container, container_name, {}) + container_metadata["obj_container"] = obj_container + # Save the list of objects in the metadata container - container_metadata["objects"] = objects_list + container_metadata["objects"] = obj_container.all_objects nodes = list(container.objects) nodes.append(container) @@ -157,7 +168,6 @@ class BlendLayoutLoader(pype.hosts.blender.plugin.AssetLoader): Warning: No nested collections are supported at the moment! """ - collection = bpy.data.collections.get( container["objectName"] ) @@ -189,8 +199,11 @@ class BlendLayoutLoader(pype.hosts.blender.plugin.AssetLoader): collection_metadata = collection.get( blender.pipeline.AVALON_PROPERTY) - collection_libpath = collection_metadata["libpath"] + objects = collection_metadata["objects"] + lib_container = collection_metadata["lib_container"] + obj_container = collection_metadata["obj_container"] + normalized_collection_libpath = ( str(Path(bpy.path.abspath(collection_libpath)).resolve()) ) @@ -206,24 +219,20 @@ class BlendLayoutLoader(pype.hosts.blender.plugin.AssetLoader): logger.info("Library already loaded, not updating...") return - objects = collection_metadata["objects"] - lib_container = collection_metadata["lib_container"] - actions = {} for obj in objects: - if obj.type == 'ARMATURE': - actions[obj.name] = obj.animation_data.action - self._remove(objects, lib_container) + self._remove(objects, obj_container) - objects_list = self._process( + obj_container = self._process( str(libpath), lib_container, collection.name, actions) # Save the list of objects in the metadata container - collection_metadata["objects"] = objects_list + collection_metadata["obj_container"] = obj_container + collection_metadata["objects"] = obj_container.all_objects collection_metadata["libpath"] = str(libpath) collection_metadata["representation"] = str(representation["_id"]) @@ -255,9 +264,9 @@ class BlendLayoutLoader(pype.hosts.blender.plugin.AssetLoader): collection_metadata = collection.get( blender.pipeline.AVALON_PROPERTY) objects = collection_metadata["objects"] - lib_container = collection_metadata["lib_container"] + obj_container = collection_metadata["obj_container"] - self._remove(objects, lib_container) + self._remove(objects, obj_container) bpy.data.collections.remove(collection) diff --git a/pype/plugins/blender/load/load_model.py b/pype/plugins/blender/load/load_model.py index 6f34b40e70..7b2ade4570 100644 --- a/pype/plugins/blender/load/load_model.py +++ b/pype/plugins/blender/load/load_model.py @@ -34,31 +34,32 @@ class BlendModelLoader(plugin.AssetLoader): bpy.data.collections.remove(container) - def prepare_data(self, data, container_name): - name = data.name - data = data.make_local() - data.name = f"{name}:{container_name}" - - def _process(self, libpath, lib_container, container_name): + def _process( + self, libpath, lib_container, container_name, + parent_collection + ): 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 + parent = parent_collection - scene.collection.children.link(bpy.data.collections[lib_container]) + if parent is None: + parent = bpy.context.scene.collection - model_container = scene.collection.children[lib_container].make_local() + parent.children.link(bpy.data.collections[lib_container]) + + model_container = parent.children[lib_container].make_local() model_container.name = container_name for obj in model_container.objects: - self.prepare_data(obj, container_name) - self.prepare_data(obj.data, container_name) + plugin.prepare_data(obj, container_name) + plugin.prepare_data(obj.data, container_name) for material_slot in obj.material_slots: - self.prepare_data(material_slot.material, container_name) + plugin.prepare_data(material_slot.material, container_name) if not obj.get(blender.pipeline.AVALON_PROPERTY): obj[blender.pipeline.AVALON_PROPERTY] = dict() @@ -114,7 +115,7 @@ class BlendModelLoader(plugin.AssetLoader): container_metadata["lib_container"] = lib_container obj_container = self._process( - libpath, lib_container, container_name) + libpath, lib_container, container_name, None) container_metadata["obj_container"] = obj_container @@ -169,9 +170,16 @@ class BlendModelLoader(plugin.AssetLoader): collection_metadata = collection.get( blender.pipeline.AVALON_PROPERTY) collection_libpath = collection_metadata["libpath"] - objects = collection_metadata["objects"] lib_container = collection_metadata["lib_container"] - obj_container = collection_metadata["obj_container"] + + obj_container = [ + c for c in bpy.data.collections + if (c.name == collection_metadata["obj_container"].name and + c.library is None) + ][0] + objects = obj_container.all_objects + + container_name = obj_container.name normalized_collection_libpath = ( str(Path(bpy.path.abspath(collection_libpath)).resolve()) @@ -188,10 +196,12 @@ class BlendModelLoader(plugin.AssetLoader): logger.info("Library already loaded, not updating...") return + parent = plugin.get_parent_collection(obj_container) + self._remove(objects, obj_container) obj_container = self._process( - str(libpath), lib_container, collection.name) + str(libpath), lib_container, container_name, parent) # Save the list of objects in the metadata container collection_metadata["obj_container"] = obj_container @@ -223,8 +233,13 @@ class BlendModelLoader(plugin.AssetLoader): collection_metadata = collection.get( blender.pipeline.AVALON_PROPERTY) - objects = collection_metadata["objects"] - obj_container = collection_metadata["obj_container"] + + obj_container = [ + c for c in bpy.data.collections + if (c.name == collection_metadata["obj_container"].name and + c.library is None) + ][0] + objects = obj_container.all_objects self._remove(objects, obj_container) diff --git a/pype/plugins/blender/load/load_rig.py b/pype/plugins/blender/load/load_rig.py index 41343e9c3a..7ac8a72993 100644 --- a/pype/plugins/blender/load/load_rig.py +++ b/pype/plugins/blender/load/load_rig.py @@ -26,54 +26,54 @@ class BlendRigLoader(plugin.AssetLoader): icon = "code-fork" color = "orange" - def _remove(self, objects, lib_container): - + def _remove(self, objects, obj_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) - for child in bpy.data.collections[lib_container].children: + for child in obj_container.children: bpy.data.collections.remove(child) - bpy.data.collections.remove(bpy.data.collections[lib_container]) + bpy.data.collections.remove(obj_container) - def prepare_data(self, data, container_name): - name = data.name - data = data.make_local() - data.name = f"{name}:{container_name}" - - def _process(self, libpath, lib_container, container_name, action): + def _process( + self, libpath, lib_container, container_name, + action, parent_collection + ): 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 + parent = parent_collection - scene.collection.children.link(bpy.data.collections[lib_container]) + if parent is None: + parent = bpy.context.scene.collection + + parent.children.link(bpy.data.collections[lib_container]) - rig_container = scene.collection.children[lib_container].make_local() + rig_container = parent.children[lib_container].make_local() rig_container.name = container_name meshes = [] armatures = [ - obj for obj in rig_container.objects if obj.type == 'ARMATURE'] - - objects_list = [] + obj for obj in rig_container.objects + if obj.type == 'ARMATURE' + ] for child in rig_container.children: - self.prepare_data(child, container_name) - meshes.extend( child.objects ) + plugin.prepare_data(child, container_name) + meshes.extend(child.objects) # 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.prepare_data(obj, container_name) - self.prepare_data(obj.data, container_name) + plugin.prepare_data(obj, container_name) + plugin.prepare_data(obj.data, container_name) if not obj.get(blender.pipeline.AVALON_PROPERTY): obj[blender.pipeline.AVALON_PROPERTY] = dict() @@ -131,7 +131,7 @@ class BlendRigLoader(plugin.AssetLoader): container_metadata["lib_container"] = lib_container obj_container = self._process( - libpath, lib_container, container_name, None) + libpath, lib_container, container_name, None, None) container_metadata["obj_container"] = obj_container @@ -186,9 +186,16 @@ class BlendRigLoader(plugin.AssetLoader): collection_metadata = collection.get( blender.pipeline.AVALON_PROPERTY) collection_libpath = collection_metadata["libpath"] - objects = collection_metadata["objects"] lib_container = collection_metadata["lib_container"] - obj_container = collection_metadata["obj_container"] + + obj_container = [ + c for c in bpy.data.collections + if (c.name == collection_metadata["obj_container"].name and + c.library is None) + ][0] + objects = obj_container.all_objects + + container_name = obj_container.name normalized_collection_libpath = ( str(Path(bpy.path.abspath(collection_libpath)).resolve()) @@ -211,10 +218,12 @@ class BlendRigLoader(plugin.AssetLoader): action = armatures[0].animation_data.action + parent = plugin.get_parent_collection(obj_container) + self._remove(objects, obj_container) obj_container = self._process( - str(libpath), lib_container, collection.name, action) + str(libpath), lib_container, container_name, action, parent) # Save the list of objects in the metadata container collection_metadata["obj_container"] = obj_container @@ -249,8 +258,13 @@ class BlendRigLoader(plugin.AssetLoader): collection_metadata = collection.get( blender.pipeline.AVALON_PROPERTY) - objects = collection_metadata["objects"] - obj_container = collection_metadata["obj_container"] + + obj_container = [ + c for c in bpy.data.collections + if (c.name == collection_metadata["obj_container"].name and + c.library is None) + ][0] + objects = obj_container.all_objects self._remove(objects, obj_container)