From b1ee15b0b52904759458de4762e8483451f52948 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 24 Jun 2020 16:26:35 +0100 Subject: [PATCH 1/8] Model support for namespaces --- pype/hosts/blender/plugin.py | 22 ++++++++- pype/plugins/blender/load/load_model.py | 65 +++++++++++++------------ 2 files changed, 56 insertions(+), 31 deletions(-) diff --git a/pype/hosts/blender/plugin.py b/pype/hosts/blender/plugin.py index 77fce90d65..5a2596c9d8 100644 --- a/pype/hosts/blender/plugin.py +++ b/pype/hosts/blender/plugin.py @@ -5,7 +5,7 @@ from typing import Dict, List, Optional import bpy -from avalon import api +from avalon import api, blender VALID_EXTENSIONS = [".blend"] @@ -20,6 +20,26 @@ def asset_name( return name +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 + ) + if avalon_containers is None: + return "1" + collections_names = [ + c.name for c in avalon_containers.children + ] + count = 1 + name = f"{asset_name(asset, subset, str(count))}_CON" + while name in collections_names: + count += 1 + name = f"{asset_name(asset, subset, str(count))}_CON" + return str(count) + + 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 diff --git a/pype/plugins/blender/load/load_model.py b/pype/plugins/blender/load/load_model.py index 4a8f43cd48..b16f5a40b1 100644 --- a/pype/plugins/blender/load/load_model.py +++ b/pype/plugins/blender/load/load_model.py @@ -7,12 +7,12 @@ 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_model") -class BlendModelLoader(pype.hosts.blender.plugin.AssetLoader): +class BlendModelLoader(plugin.AssetLoader): """Load models from a .blend file. Because they come from a .blend file we can simply link the collection that @@ -30,16 +30,20 @@ class BlendModelLoader(pype.hosts.blender.plugin.AssetLoader): icon = "code-fork" color = "orange" - def _remove(self, objects, lib_container): - + def _remove(self, objects, container): for obj in objects: - + for material_slot in obj.material_slots: + bpy.data.materials.remove(material_slot.material) bpy.data.meshes.remove(obj.data) - bpy.data.collections.remove(bpy.data.collections[lib_container]) + 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): - relative = bpy.context.preferences.filepaths.use_relative_paths with bpy.data.libraries.load( libpath, link=True, relative=relative @@ -51,33 +55,26 @@ class BlendModelLoader(pype.hosts.blender.plugin.AssetLoader): scene.collection.children.link(bpy.data.collections[lib_container]) model_container = scene.collection.children[lib_container].make_local() - - objects_list = [] + model_container.name = container_name for obj in model_container.objects: - - obj = obj.make_local() - - obj.data.make_local() + self.prepare_data(obj, container_name) + self.prepare_data(obj.data, container_name) for material_slot in obj.material_slots: - - material_slot.material.make_local() + self.prepare_data(material_slot.material, container_name) if not obj.get(blender.pipeline.AVALON_PROPERTY): - obj[blender.pipeline.AVALON_PROPERTY] = dict() avalon_info = obj[blender.pipeline.AVALON_PROPERTY] avalon_info.update({"container_name": container_name}) - objects_list.append(obj) - model_container.pop(blender.pipeline.AVALON_PROPERTY) bpy.ops.object.select_all(action='DESELECT') - return objects_list + return model_container def process_asset( self, context: dict, name: str, namespace: Optional[str] = None, @@ -94,13 +91,18 @@ class BlendModelLoader(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 ) collection = bpy.data.collections.new(lib_container) - collection.name = container_name blender.pipeline.containerise_existing( collection, name, @@ -115,11 +117,13 @@ class BlendModelLoader(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(collection.objects) nodes.append(collection) @@ -162,7 +166,7 @@ class BlendModelLoader(pype.hosts.blender.plugin.AssetLoader): assert libpath.is_file(), ( f"The file doesn't exist: {libpath}" ) - assert extension in pype.hosts.blender.plugin.VALID_EXTENSIONS, ( + assert extension in plugin.VALID_EXTENSIONS, ( f"Unsupported file: {libpath}" ) @@ -171,6 +175,7 @@ class BlendModelLoader(pype.hosts.blender.plugin.AssetLoader): 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()) @@ -187,7 +192,7 @@ class BlendModelLoader(pype.hosts.blender.plugin.AssetLoader): logger.info("Library already loaded, not updating...") return - self._remove(objects, lib_container) + self._remove(objects, obj_container) objects_list = self._process( str(libpath), lib_container, collection.name) @@ -222,16 +227,16 @@ class BlendModelLoader(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) return True -class CacheModelLoader(pype.hosts.blender.plugin.AssetLoader): +class CacheModelLoader(plugin.AssetLoader): """Load cache models. Stores the imported asset in a collection named after the asset. @@ -267,7 +272,7 @@ class CacheModelLoader(pype.hosts.blender.plugin.AssetLoader): subset = context["subset"]["name"] # TODO (jasper): evaluate use of namespace which is 'alien' to Blender. lib_container = container_name = ( - pype.hosts.blender.plugin.asset_name(asset, subset, namespace) + plugin.asset_name(asset, subset, namespace) ) relative = bpy.context.preferences.filepaths.use_relative_paths From a0096d6cdf062c81fb1c4c9dc13e27aa49da73f6 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 25 Jun 2020 11:14:06 +0100 Subject: [PATCH 2/8] Rig support for namespaces --- pype/plugins/blender/load/load_model.py | 21 ++++----- pype/plugins/blender/load/load_rig.py | 60 +++++++++++++------------ 2 files changed, 41 insertions(+), 40 deletions(-) diff --git a/pype/plugins/blender/load/load_model.py b/pype/plugins/blender/load/load_model.py index b16f5a40b1..6f34b40e70 100644 --- a/pype/plugins/blender/load/load_model.py +++ b/pype/plugins/blender/load/load_model.py @@ -17,10 +17,6 @@ class BlendModelLoader(plugin.AssetLoader): Because they come from a .blend file we can simply link the collection that contains the model. There is no further need to 'containerise' it. - - Warning: - Loading the same asset more then once is not properly supported at the - moment. """ families = ["model"] @@ -102,16 +98,16 @@ class BlendModelLoader(plugin.AssetLoader): asset, subset, namespace ) - collection = bpy.data.collections.new(lib_container) + container = bpy.data.collections.new(lib_container) blender.pipeline.containerise_existing( - collection, + container, name, namespace, context, self.__class__.__name__, ) - container_metadata = collection.get( + container_metadata = container.get( blender.pipeline.AVALON_PROPERTY) container_metadata["libpath"] = libpath @@ -125,8 +121,8 @@ class BlendModelLoader(plugin.AssetLoader): # Save the list of objects in the metadata container container_metadata["objects"] = obj_container.all_objects - nodes = list(collection.objects) - nodes.append(collection) + nodes = list(container.objects) + nodes.append(container) self[:] = nodes return nodes @@ -148,7 +144,7 @@ class BlendModelLoader(plugin.AssetLoader): libpath = Path(api.get_representation_path(representation)) extension = libpath.suffix.lower() - logger.debug( + logger.info( "Container: %s\nRepresentation: %s", pformat(container, indent=2), pformat(representation, indent=2), @@ -194,11 +190,12 @@ class BlendModelLoader(plugin.AssetLoader): self._remove(objects, obj_container) - objects_list = self._process( + obj_container = self._process( str(libpath), lib_container, collection.name) # 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"]) diff --git a/pype/plugins/blender/load/load_rig.py b/pype/plugins/blender/load/load_rig.py index 3e53ff0363..41343e9c3a 100644 --- a/pype/plugins/blender/load/load_rig.py +++ b/pype/plugins/blender/load/load_rig.py @@ -7,20 +7,16 @@ 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_model") +logger = logging.getLogger("pype").getChild("blender").getChild("load_rig") -class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader): +class BlendRigLoader(plugin.AssetLoader): """Load rigs from a .blend file. Because they come from a .blend file we can simply link the collection that contains the model. There is no further need to 'containerise' it. - - Warning: - Loading the same asset more then once is not properly supported at the - moment. """ families = ["rig"] @@ -33,7 +29,6 @@ class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader): def _remove(self, objects, lib_container): for obj in objects: - if obj.type == 'ARMATURE': bpy.data.armatures.remove(obj.data) elif obj.type == 'MESH': @@ -44,8 +39,12 @@ class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader): bpy.data.collections.remove(bpy.data.collections[lib_container]) - def _process(self, libpath, lib_container, container_name, action): + 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): relative = bpy.context.preferences.filepaths.use_relative_paths with bpy.data.libraries.load( libpath, link=True, relative=relative @@ -57,6 +56,7 @@ class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader): scene.collection.children.link(bpy.data.collections[lib_container]) rig_container = scene.collection.children[lib_container].make_local() + rig_container.name = container_name meshes = [] armatures = [ @@ -65,15 +65,15 @@ class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader): objects_list = [] for child in rig_container.children: - child.make_local() + self.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: - obj = obj.make_local() - obj.data.make_local() + self.prepare_data(obj, container_name) + self.prepare_data(obj.data, container_name) if not obj.get(blender.pipeline.AVALON_PROPERTY): obj[blender.pipeline.AVALON_PROPERTY] = dict() @@ -84,13 +84,11 @@ class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader): if obj.type == 'ARMATURE' and action is not None: obj.animation_data.action = action - objects_list.append(obj) - rig_container.pop(blender.pipeline.AVALON_PROPERTY) bpy.ops.object.select_all(action='DESELECT') - return objects_list + return rig_container def process_asset( self, context: dict, name: str, namespace: Optional[str] = None, @@ -107,13 +105,17 @@ class BlendRigLoader(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, @@ -128,11 +130,13 @@ class BlendRigLoader(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, None) + 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) @@ -151,11 +155,9 @@ class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader): Warning: No nested collections are supported at the moment! """ - collection = bpy.data.collections.get( container["objectName"] ) - libpath = Path(api.get_representation_path(representation)) extension = libpath.suffix.lower() @@ -177,7 +179,7 @@ class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader): assert libpath.is_file(), ( f"The file doesn't exist: {libpath}" ) - assert extension in pype.hosts.blender.plugin.VALID_EXTENSIONS, ( + assert extension in plugin.VALID_EXTENSIONS, ( f"Unsupported file: {libpath}" ) @@ -186,6 +188,7 @@ class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader): 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()) @@ -208,13 +211,14 @@ class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader): action = armatures[0].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, action) # 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"]) @@ -246,9 +250,9 @@ class BlendRigLoader(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) From 8de72755c478b71f14f25529c1b52d4324d1d464 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Mon, 29 Jun 2020 17:21:54 +0100 Subject: [PATCH 3/8] Layout support for namespaces --- pype/hosts/blender/plugin.py | 30 ++++++- pype/plugins/blender/load/load_layout.py | 105 ++++++++++++----------- pype/plugins/blender/load/load_model.py | 51 +++++++---- pype/plugins/blender/load/load_rig.py | 66 ++++++++------ 4 files changed, 156 insertions(+), 96 deletions(-) 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) From 52e2d785c10a0fbf2271b654db58ea22a39f15e5 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Tue, 30 Jun 2020 11:35:24 +0100 Subject: [PATCH 4/8] Small improvements and Pep8 compliance --- pype/hosts/blender/plugin.py | 11 ++++++--- pype/plugins/blender/load/load_layout.py | 15 +++++------- pype/plugins/blender/load/load_model.py | 24 +++++++------------ pype/plugins/blender/load/load_rig.py | 30 ++++++++++-------------- 4 files changed, 35 insertions(+), 45 deletions(-) diff --git a/pype/hosts/blender/plugin.py b/pype/hosts/blender/plugin.py index eff41956e8..b0420fddfc 100644 --- a/pype/hosts/blender/plugin.py +++ b/pype/hosts/blender/plugin.py @@ -31,10 +31,8 @@ def asset_namespace( 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 loaded_assets + c.name for c in loaded_assets ] count = 1 name = f"{asset_name(asset, subset, str(count))}_CON" @@ -89,6 +87,13 @@ def get_parent_collection(collection): return None +def get_local_collection_with_name(name): + for collection in bpy.data.collections: + if collection.name == name and collection.library is None: + return collection + 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 5d3f7d92cf..e9e128bb7c 100644 --- a/pype/plugins/blender/load/load_layout.py +++ b/pype/plugins/blender/load/load_layout.py @@ -9,9 +9,6 @@ from avalon import api, blender import bpy import pype.hosts.blender.plugin as plugin -logger = logging.getLogger("pype").getChild( - "blender").getChild("load_layout") - class BlendLayoutLoader(plugin.AssetLoader): """Load layout from a .blend file.""" @@ -65,15 +62,15 @@ class BlendLayoutLoader(plugin.AssetLoader): 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 + obj for obj in container.objects if obj.type in objects_local_types ]) armatures.extend([ - obj for obj in container.objects + obj for obj in container.objects if obj.type == 'ARMATURE' ]) containers.extend(list(container.children)) @@ -175,7 +172,7 @@ class BlendLayoutLoader(plugin.AssetLoader): libpath = Path(api.get_representation_path(representation)) extension = libpath.suffix.lower() - logger.info( + self.log.info( "Container: %s\nRepresentation: %s", pformat(container, indent=2), pformat(representation, indent=2), @@ -210,13 +207,13 @@ class BlendLayoutLoader(plugin.AssetLoader): normalized_libpath = ( str(Path(bpy.path.abspath(str(libpath))).resolve()) ) - logger.debug( + self.log.debug( "normalized_collection_libpath:\n %s\nnormalized_libpath:\n %s", normalized_collection_libpath, normalized_libpath, ) if normalized_collection_libpath == normalized_libpath: - logger.info("Library already loaded, not updating...") + self.log.info("Library already loaded, not updating...") return actions = {} diff --git a/pype/plugins/blender/load/load_model.py b/pype/plugins/blender/load/load_model.py index 7b2ade4570..ad4b35eb03 100644 --- a/pype/plugins/blender/load/load_model.py +++ b/pype/plugins/blender/load/load_model.py @@ -9,8 +9,6 @@ from avalon import api, blender import bpy import pype.hosts.blender.plugin as plugin -logger = logging.getLogger("pype").getChild("blender").getChild("load_model") - class BlendModelLoader(plugin.AssetLoader): """Load models from a .blend file. @@ -145,7 +143,7 @@ class BlendModelLoader(plugin.AssetLoader): libpath = Path(api.get_representation_path(representation)) extension = libpath.suffix.lower() - logger.info( + self.log.info( "Container: %s\nRepresentation: %s", pformat(container, indent=2), pformat(representation, indent=2), @@ -172,11 +170,9 @@ class BlendModelLoader(plugin.AssetLoader): collection_libpath = collection_metadata["libpath"] lib_container = collection_metadata["lib_container"] - obj_container = [ - c for c in bpy.data.collections - if (c.name == collection_metadata["obj_container"].name and - c.library is None) - ][0] + obj_container = get_local_collection_with_name( + collection_metadata["obj_container"].name + ) objects = obj_container.all_objects container_name = obj_container.name @@ -187,13 +183,13 @@ class BlendModelLoader(plugin.AssetLoader): normalized_libpath = ( str(Path(bpy.path.abspath(str(libpath))).resolve()) ) - logger.debug( + self.log.debug( "normalized_collection_libpath:\n %s\nnormalized_libpath:\n %s", normalized_collection_libpath, normalized_libpath, ) if normalized_collection_libpath == normalized_libpath: - logger.info("Library already loaded, not updating...") + self.log.info("Library already loaded, not updating...") return parent = plugin.get_parent_collection(obj_container) @@ -234,11 +230,9 @@ class BlendModelLoader(plugin.AssetLoader): collection_metadata = collection.get( blender.pipeline.AVALON_PROPERTY) - obj_container = [ - c for c in bpy.data.collections - if (c.name == collection_metadata["obj_container"].name and - c.library is None) - ][0] + obj_container = get_local_collection_with_name( + collection_metadata["obj_container"].name + ) 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 7ac8a72993..860cae71ba 100644 --- a/pype/plugins/blender/load/load_rig.py +++ b/pype/plugins/blender/load/load_rig.py @@ -9,8 +9,6 @@ from avalon import api, blender import bpy import pype.hosts.blender.plugin as plugin -logger = logging.getLogger("pype").getChild("blender").getChild("load_rig") - class BlendRigLoader(plugin.AssetLoader): """Load rigs from a .blend file. @@ -39,7 +37,7 @@ class BlendRigLoader(plugin.AssetLoader): bpy.data.collections.remove(obj_container) def _process( - self, libpath, lib_container, container_name, + self, libpath, lib_container, container_name, action, parent_collection ): relative = bpy.context.preferences.filepaths.use_relative_paths @@ -52,7 +50,7 @@ class BlendRigLoader(plugin.AssetLoader): if parent is None: parent = bpy.context.scene.collection - + parent.children.link(bpy.data.collections[lib_container]) rig_container = parent.children[lib_container].make_local() @@ -60,7 +58,7 @@ class BlendRigLoader(plugin.AssetLoader): meshes = [] armatures = [ - obj for obj in rig_container.objects + obj for obj in rig_container.objects if obj.type == 'ARMATURE' ] @@ -161,7 +159,7 @@ class BlendRigLoader(plugin.AssetLoader): libpath = Path(api.get_representation_path(representation)) extension = libpath.suffix.lower() - logger.info( + self.log.info( "Container: %s\nRepresentation: %s", pformat(container, indent=2), pformat(representation, indent=2), @@ -188,11 +186,9 @@ class BlendRigLoader(plugin.AssetLoader): collection_libpath = collection_metadata["libpath"] lib_container = collection_metadata["lib_container"] - obj_container = [ - c for c in bpy.data.collections - if (c.name == collection_metadata["obj_container"].name and - c.library is None) - ][0] + obj_container = get_local_collection_with_name( + collection_metadata["obj_container"].name + ) objects = obj_container.all_objects container_name = obj_container.name @@ -203,13 +199,13 @@ class BlendRigLoader(plugin.AssetLoader): normalized_libpath = ( str(Path(bpy.path.abspath(str(libpath))).resolve()) ) - logger.debug( + self.log.debug( "normalized_collection_libpath:\n %s\nnormalized_libpath:\n %s", normalized_collection_libpath, normalized_libpath, ) if normalized_collection_libpath == normalized_libpath: - logger.info("Library already loaded, not updating...") + self.log.info("Library already loaded, not updating...") return # Get the armature of the rig @@ -259,11 +255,9 @@ class BlendRigLoader(plugin.AssetLoader): collection_metadata = collection.get( blender.pipeline.AVALON_PROPERTY) - obj_container = [ - c for c in bpy.data.collections - if (c.name == collection_metadata["obj_container"].name and - c.library is None) - ][0] + obj_container = get_local_collection_with_name( + collection_metadata["obj_container"].name + ) objects = obj_container.all_objects self._remove(objects, obj_container) From aa99cc14b8f341ed01c69a671d1d46051fd75d74 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Tue, 30 Jun 2020 11:42:27 +0100 Subject: [PATCH 5/8] Fix undefined function --- pype/plugins/blender/load/load_model.py | 4 ++-- pype/plugins/blender/load/load_rig.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pype/plugins/blender/load/load_model.py b/pype/plugins/blender/load/load_model.py index ad4b35eb03..0013ccb90a 100644 --- a/pype/plugins/blender/load/load_model.py +++ b/pype/plugins/blender/load/load_model.py @@ -170,7 +170,7 @@ class BlendModelLoader(plugin.AssetLoader): collection_libpath = collection_metadata["libpath"] lib_container = collection_metadata["lib_container"] - obj_container = get_local_collection_with_name( + obj_container = plugin.get_local_collection_with_name( collection_metadata["obj_container"].name ) objects = obj_container.all_objects @@ -230,7 +230,7 @@ class BlendModelLoader(plugin.AssetLoader): collection_metadata = collection.get( blender.pipeline.AVALON_PROPERTY) - obj_container = get_local_collection_with_name( + obj_container = plugin.get_local_collection_with_name( collection_metadata["obj_container"].name ) objects = obj_container.all_objects diff --git a/pype/plugins/blender/load/load_rig.py b/pype/plugins/blender/load/load_rig.py index 860cae71ba..d9e4495090 100644 --- a/pype/plugins/blender/load/load_rig.py +++ b/pype/plugins/blender/load/load_rig.py @@ -186,7 +186,7 @@ class BlendRigLoader(plugin.AssetLoader): collection_libpath = collection_metadata["libpath"] lib_container = collection_metadata["lib_container"] - obj_container = get_local_collection_with_name( + obj_container = plugin.get_local_collection_with_name( collection_metadata["obj_container"].name ) objects = obj_container.all_objects @@ -255,7 +255,7 @@ class BlendRigLoader(plugin.AssetLoader): collection_metadata = collection.get( blender.pipeline.AVALON_PROPERTY) - obj_container = get_local_collection_with_name( + obj_container = plugin.get_local_collection_with_name( collection_metadata["obj_container"].name ) objects = obj_container.all_objects From 2b58592d123695032887f2406fd0b285cfbc3f7e Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 2 Jul 2020 10:32:38 +0100 Subject: [PATCH 6/8] Supporting double digits namespace names --- pype/hosts/blender/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/hosts/blender/plugin.py b/pype/hosts/blender/plugin.py index b0420fddfc..f0791c8eb6 100644 --- a/pype/hosts/blender/plugin.py +++ b/pype/hosts/blender/plugin.py @@ -16,7 +16,7 @@ def asset_name( """Return a consistent name for an asset.""" name = f"{asset}_{subset}" if namespace: - name = f"{namespace}:{name}" + name = f"{namespace:0>2}:{name}" return name From 345601a79f9850ca83c47a890e1e5046fc7331af Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 2 Jul 2020 15:23:20 +0100 Subject: [PATCH 7/8] Changed namespace formulation --- pype/hosts/blender/plugin.py | 15 ++++++++------- pype/plugins/blender/load/load_layout.py | 6 ++++-- pype/plugins/blender/load/load_model.py | 6 ++++-- pype/plugins/blender/load/load_rig.py | 6 ++++-- 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/pype/hosts/blender/plugin.py b/pype/hosts/blender/plugin.py index f0791c8eb6..33cccd7d5e 100644 --- a/pype/hosts/blender/plugin.py +++ b/pype/hosts/blender/plugin.py @@ -14,16 +14,17 @@ def asset_name( asset: str, subset: str, namespace: Optional[str] = None ) -> str: """Return a consistent name for an asset.""" - name = f"{asset}_{subset}" + name = f"{asset}" if namespace: - name = f"{namespace:0>2}:{name}" + name = f"{name}_{namespace}" + name = f"{name}_{subset}" return name -def asset_namespace( +def get_unique_number( asset: str, subset: str ) -> str: - """Return a unique namespace based on the asset name.""" + """Return a unique number based on the asset name.""" avalon_containers = [ c for c in bpy.data.collections if c.name == 'AVALON_CONTAINERS' @@ -35,11 +36,11 @@ def asset_namespace( c.name for c in loaded_assets ] count = 1 - name = f"{asset_name(asset, subset, str(count))}_CON" + name = f"{asset}_{count:0>2}_{subset}_CON" while name in collections_names: count += 1 - name = f"{asset_name(asset, subset, str(count))}_CON" - return str(count) + name = f"{asset}_{count:0>2}_{subset}_CON" + return f"{count:0>2}" def prepare_data(data, container_name): diff --git a/pype/plugins/blender/load/load_layout.py b/pype/plugins/blender/load/load_layout.py index e9e128bb7c..cfab5a207b 100644 --- a/pype/plugins/blender/load/load_layout.py +++ b/pype/plugins/blender/load/load_layout.py @@ -118,14 +118,16 @@ class BlendLayoutLoader(plugin.AssetLoader): lib_container = plugin.asset_name( asset, subset ) - namespace = namespace or plugin.asset_namespace( + unique_number = plugin.get_unique_number( asset, subset ) + namespace = namespace or f"{asset}_{unique_number}" container_name = plugin.asset_name( - asset, subset, namespace + asset, subset, unique_number ) container = bpy.data.collections.new(lib_container) + container.name = container_name blender.pipeline.containerise_existing( container, name, diff --git a/pype/plugins/blender/load/load_model.py b/pype/plugins/blender/load/load_model.py index 0013ccb90a..ad9137a15d 100644 --- a/pype/plugins/blender/load/load_model.py +++ b/pype/plugins/blender/load/load_model.py @@ -90,14 +90,16 @@ class BlendModelLoader(plugin.AssetLoader): lib_container = plugin.asset_name( asset, subset ) - namespace = namespace or plugin.asset_namespace( + unique_number = plugin.get_unique_number( asset, subset ) + namespace = namespace or f"{asset}_{unique_number}" container_name = plugin.asset_name( - asset, subset, namespace + asset, subset, unique_number ) container = bpy.data.collections.new(lib_container) + container.name = container_name blender.pipeline.containerise_existing( container, name, diff --git a/pype/plugins/blender/load/load_rig.py b/pype/plugins/blender/load/load_rig.py index d9e4495090..e09a9cb92f 100644 --- a/pype/plugins/blender/load/load_rig.py +++ b/pype/plugins/blender/load/load_rig.py @@ -106,14 +106,16 @@ class BlendRigLoader(plugin.AssetLoader): lib_container = plugin.asset_name( asset, subset ) - namespace = namespace or plugin.asset_namespace( + unique_number = plugin.get_unique_number( asset, subset ) + namespace = namespace or f"{asset}_{unique_number}" container_name = plugin.asset_name( - asset, subset, namespace + asset, subset, unique_number ) container = bpy.data.collections.new(lib_container) + container.name = container_name blender.pipeline.containerise_existing( container, name, From 875962ed9b2440272bd95775650f13bcccdc364b Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 3 Jul 2020 09:27:43 +0100 Subject: [PATCH 8/8] Hound fixes --- pype/hosts/blender/plugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/hosts/blender/plugin.py b/pype/hosts/blender/plugin.py index 33cccd7d5e..ab53d49041 100644 --- a/pype/hosts/blender/plugin.py +++ b/pype/hosts/blender/plugin.py @@ -5,7 +5,7 @@ from typing import Dict, List, Optional import bpy -from avalon import api, blender +from avalon import api VALID_EXTENSIONS = [".blend"] @@ -26,7 +26,7 @@ def get_unique_number( ) -> str: """Return a unique number based on the asset name.""" avalon_containers = [ - c for c in bpy.data.collections + c for c in bpy.data.collections if c.name == 'AVALON_CONTAINERS' ] loaded_assets = []