Merge pull request #328 from simonebarbieri/feature/blender-namespace-support

Feature/Blender namespace support
This commit is contained in:
Milan Kolar 2020-07-08 09:06:02 +02:00 committed by GitHub
commit 62ff1b298b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 241 additions and 158 deletions

View file

@ -14,12 +14,41 @@ def asset_name(
asset: str, subset: str, namespace: Optional[str] = None asset: str, subset: str, namespace: Optional[str] = None
) -> str: ) -> str:
"""Return a consistent name for an asset.""" """Return a consistent name for an asset."""
name = f"{asset}_{subset}" name = f"{asset}"
if namespace: if namespace:
name = f"{namespace}:{name}" name = f"{name}_{namespace}"
name = f"{name}_{subset}"
return name return name
def get_unique_number(
asset: str, subset: str
) -> str:
"""Return a unique number based on the asset name."""
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)
collections_names = [
c.name for c in loaded_assets
]
count = 1
name = f"{asset}_{count:0>2}_{subset}_CON"
while name in collections_names:
count += 1
name = f"{asset}_{count:0>2}_{subset}_CON"
return f"{count:0>2}"
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, def create_blender_context(active: Optional[bpy.types.Object] = None,
selected: Optional[bpy.types.Object] = None,): selected: Optional[bpy.types.Object] = None,):
"""Create a new Blender context. If an object is passed as """Create a new Blender context. If an object is passed as
@ -47,6 +76,25 @@ def create_blender_context(active: Optional[bpy.types.Object] = None,
raise Exception("Could not create a custom Blender context.") 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
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): class AssetLoader(api.Loader):
"""A basic AssetLoader for Blender """A basic AssetLoader for Blender

View file

@ -7,20 +7,11 @@ from typing import Dict, List, Optional
from avalon import api, blender from avalon import api, blender
import bpy import bpy
import pype.hosts.blender.plugin import pype.hosts.blender.plugin as plugin
logger = logging.getLogger("pype").getChild( class BlendLayoutLoader(plugin.AssetLoader):
"blender").getChild("load_layout") """Load layout from a .blend file."""
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.
"""
families = ["layout"] families = ["layout"]
representations = ["blend"] representations = ["blend"]
@ -29,24 +20,25 @@ class BlendLayoutLoader(pype.hosts.blender.plugin.AssetLoader):
icon = "code-fork" icon = "code-fork"
color = "orange" color = "orange"
def _remove(self, objects, lib_container): def _remove(self, objects, obj_container):
for obj in objects: for obj in objects:
if obj.type == 'ARMATURE': if obj.type == 'ARMATURE':
bpy.data.armatures.remove(obj.data) bpy.data.armatures.remove(obj.data)
elif obj.type == 'MESH': elif obj.type == 'MESH':
bpy.data.meshes.remove(obj.data) 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: for child in element_container.children:
bpy.data.collections.remove(child) bpy.data.collections.remove(child)
bpy.data.collections.remove(element_container) 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): def _process(self, libpath, lib_container, container_name, actions):
relative = bpy.context.preferences.filepaths.use_relative_paths relative = bpy.context.preferences.filepaths.use_relative_paths
with bpy.data.libraries.load( with bpy.data.libraries.load(
libpath, link=True, relative=relative libpath, link=True, relative=relative
@ -58,26 +50,38 @@ class BlendLayoutLoader(pype.hosts.blender.plugin.AssetLoader):
scene.collection.children.link(bpy.data.collections[lib_container]) scene.collection.children.link(bpy.data.collections[lib_container])
layout_container = scene.collection.children[lib_container].make_local() layout_container = scene.collection.children[lib_container].make_local()
layout_container.name = container_name
meshes = [] objects_local_types = ['MESH', 'CAMERA', 'CURVE']
objects = []
armatures = [] armatures = []
objects_list = [] containers = list(layout_container.children)
for element_container in layout_container.children: for container in layout_container.children:
element_container.make_local() if container.name == blender.pipeline.AVALON_CONTAINERS:
meshes.extend([obj for obj in element_container.objects if obj.type == 'MESH']) containers.remove(container)
armatures.extend([obj for obj in element_container.objects if obj.type == 'ARMATURE'])
for child in element_container.children: for container in containers:
child.make_local() container.make_local()
meshes.extend(child.objects) 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. # Link meshes first, then armatures.
# The armature is unparented for all the non-local meshes, # The armature is unparented for all the non-local meshes,
# when it is made local. # when it is made local.
for obj in meshes + armatures: for obj in objects + armatures:
obj = obj.make_local() obj.make_local()
obj.data.make_local() if obj.data:
obj.data.make_local()
if not obj.get(blender.pipeline.AVALON_PROPERTY): if not obj.get(blender.pipeline.AVALON_PROPERTY):
obj[blender.pipeline.AVALON_PROPERTY] = dict() obj[blender.pipeline.AVALON_PROPERTY] = dict()
@ -85,18 +89,16 @@ class BlendLayoutLoader(pype.hosts.blender.plugin.AssetLoader):
avalon_info = obj[blender.pipeline.AVALON_PROPERTY] avalon_info = obj[blender.pipeline.AVALON_PROPERTY]
avalon_info.update({"container_name": container_name}) 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: if obj.type == 'ARMATURE' and action is not None:
obj.animation_data.action = action obj.animation_data.action = action
objects_list.append(obj)
layout_container.pop(blender.pipeline.AVALON_PROPERTY) layout_container.pop(blender.pipeline.AVALON_PROPERTY)
bpy.ops.object.select_all(action='DESELECT') bpy.ops.object.select_all(action='DESELECT')
return objects_list return layout_container
def process_asset( def process_asset(
self, context: dict, name: str, namespace: Optional[str] = None, self, context: dict, name: str, namespace: Optional[str] = None,
@ -113,9 +115,15 @@ class BlendLayoutLoader(pype.hosts.blender.plugin.AssetLoader):
libpath = self.fname libpath = self.fname
asset = context["asset"]["name"] asset = context["asset"]["name"]
subset = context["subset"]["name"] subset = context["subset"]["name"]
lib_container = pype.hosts.blender.plugin.asset_name(asset, subset) lib_container = plugin.asset_name(
container_name = pype.hosts.blender.plugin.asset_name( asset, subset
asset, subset, namespace )
unique_number = plugin.get_unique_number(
asset, subset
)
namespace = namespace or f"{asset}_{unique_number}"
container_name = plugin.asset_name(
asset, subset, unique_number
) )
container = bpy.data.collections.new(lib_container) container = bpy.data.collections.new(lib_container)
@ -134,11 +142,13 @@ class BlendLayoutLoader(pype.hosts.blender.plugin.AssetLoader):
container_metadata["libpath"] = libpath container_metadata["libpath"] = libpath
container_metadata["lib_container"] = lib_container container_metadata["lib_container"] = lib_container
objects_list = self._process( obj_container = self._process(
libpath, lib_container, container_name, {}) libpath, lib_container, container_name, {})
container_metadata["obj_container"] = obj_container
# Save the list of objects in the metadata 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 = list(container.objects)
nodes.append(container) nodes.append(container)
@ -157,7 +167,6 @@ class BlendLayoutLoader(pype.hosts.blender.plugin.AssetLoader):
Warning: Warning:
No nested collections are supported at the moment! No nested collections are supported at the moment!
""" """
collection = bpy.data.collections.get( collection = bpy.data.collections.get(
container["objectName"] container["objectName"]
) )
@ -165,7 +174,7 @@ class BlendLayoutLoader(pype.hosts.blender.plugin.AssetLoader):
libpath = Path(api.get_representation_path(representation)) libpath = Path(api.get_representation_path(representation))
extension = libpath.suffix.lower() extension = libpath.suffix.lower()
logger.info( self.log.info(
"Container: %s\nRepresentation: %s", "Container: %s\nRepresentation: %s",
pformat(container, indent=2), pformat(container, indent=2),
pformat(representation, indent=2), pformat(representation, indent=2),
@ -189,41 +198,40 @@ class BlendLayoutLoader(pype.hosts.blender.plugin.AssetLoader):
collection_metadata = collection.get( collection_metadata = collection.get(
blender.pipeline.AVALON_PROPERTY) blender.pipeline.AVALON_PROPERTY)
collection_libpath = collection_metadata["libpath"] collection_libpath = collection_metadata["libpath"]
objects = collection_metadata["objects"]
lib_container = collection_metadata["lib_container"]
obj_container = collection_metadata["obj_container"]
normalized_collection_libpath = ( normalized_collection_libpath = (
str(Path(bpy.path.abspath(collection_libpath)).resolve()) str(Path(bpy.path.abspath(collection_libpath)).resolve())
) )
normalized_libpath = ( normalized_libpath = (
str(Path(bpy.path.abspath(str(libpath))).resolve()) 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:\n %s\nnormalized_libpath:\n %s",
normalized_collection_libpath, normalized_collection_libpath,
normalized_libpath, normalized_libpath,
) )
if 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 return
objects = collection_metadata["objects"]
lib_container = collection_metadata["lib_container"]
actions = {} actions = {}
for obj in objects: for obj in objects:
if obj.type == 'ARMATURE': if obj.type == 'ARMATURE':
actions[obj.name] = obj.animation_data.action 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) str(libpath), lib_container, collection.name, actions)
# Save the list of objects in the metadata container # 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["libpath"] = str(libpath)
collection_metadata["representation"] = str(representation["_id"]) collection_metadata["representation"] = str(representation["_id"])
@ -255,9 +263,9 @@ class BlendLayoutLoader(pype.hosts.blender.plugin.AssetLoader):
collection_metadata = collection.get( collection_metadata = collection.get(
blender.pipeline.AVALON_PROPERTY) blender.pipeline.AVALON_PROPERTY)
objects = collection_metadata["objects"] 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) bpy.data.collections.remove(collection)

View file

@ -7,20 +7,14 @@ from typing import Dict, List, Optional
from avalon import api, blender from avalon import api, blender
import bpy 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. """Load models from a .blend file.
Because they come from a .blend file we can simply link the collection that 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. 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"] families = ["model"]
@ -30,54 +24,52 @@ class BlendModelLoader(pype.hosts.blender.plugin.AssetLoader):
icon = "code-fork" icon = "code-fork"
color = "orange" color = "orange"
def _remove(self, objects, lib_container): def _remove(self, objects, container):
for obj in objects: 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.meshes.remove(obj.data)
bpy.data.collections.remove(bpy.data.collections[lib_container]) bpy.data.collections.remove(container)
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 relative = bpy.context.preferences.filepaths.use_relative_paths
with bpy.data.libraries.load( with bpy.data.libraries.load(
libpath, link=True, relative=relative libpath, link=True, relative=relative
) as (_, data_to): ) as (_, data_to):
data_to.collections = [lib_container] 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])
objects_list = [] model_container = parent.children[lib_container].make_local()
model_container.name = container_name
for obj in model_container.objects: for obj in model_container.objects:
plugin.prepare_data(obj, container_name)
obj = obj.make_local() plugin.prepare_data(obj.data, container_name)
obj.data.make_local()
for material_slot in obj.material_slots: for material_slot in obj.material_slots:
plugin.prepare_data(material_slot.material, container_name)
material_slot.material.make_local()
if not obj.get(blender.pipeline.AVALON_PROPERTY): if not obj.get(blender.pipeline.AVALON_PROPERTY):
obj[blender.pipeline.AVALON_PROPERTY] = dict() obj[blender.pipeline.AVALON_PROPERTY] = dict()
avalon_info = obj[blender.pipeline.AVALON_PROPERTY] avalon_info = obj[blender.pipeline.AVALON_PROPERTY]
avalon_info.update({"container_name": container_name}) avalon_info.update({"container_name": container_name})
objects_list.append(obj)
model_container.pop(blender.pipeline.AVALON_PROPERTY) model_container.pop(blender.pipeline.AVALON_PROPERTY)
bpy.ops.object.select_all(action='DESELECT') bpy.ops.object.select_all(action='DESELECT')
return objects_list return model_container
def process_asset( def process_asset(
self, context: dict, name: str, namespace: Optional[str] = None, self, context: dict, name: str, namespace: Optional[str] = None,
@ -94,35 +86,44 @@ class BlendModelLoader(pype.hosts.blender.plugin.AssetLoader):
libpath = self.fname libpath = self.fname
asset = context["asset"]["name"] asset = context["asset"]["name"]
subset = context["subset"]["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 asset, subset
)
unique_number = plugin.get_unique_number(
asset, subset
)
namespace = namespace or f"{asset}_{unique_number}"
container_name = plugin.asset_name(
asset, subset, unique_number
) )
collection = bpy.data.collections.new(lib_container) container = bpy.data.collections.new(lib_container)
collection.name = container_name container.name = container_name
blender.pipeline.containerise_existing( blender.pipeline.containerise_existing(
collection, container,
name, name,
namespace, namespace,
context, context,
self.__class__.__name__, self.__class__.__name__,
) )
container_metadata = collection.get( container_metadata = container.get(
blender.pipeline.AVALON_PROPERTY) blender.pipeline.AVALON_PROPERTY)
container_metadata["libpath"] = libpath container_metadata["libpath"] = libpath
container_metadata["lib_container"] = lib_container container_metadata["lib_container"] = lib_container
objects_list = self._process( obj_container = self._process(
libpath, lib_container, container_name) libpath, lib_container, container_name, None)
container_metadata["obj_container"] = obj_container
# Save the list of objects in the metadata 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 = list(container.objects)
nodes.append(collection) nodes.append(container)
self[:] = nodes self[:] = nodes
return nodes return nodes
@ -144,7 +145,7 @@ class BlendModelLoader(pype.hosts.blender.plugin.AssetLoader):
libpath = Path(api.get_representation_path(representation)) libpath = Path(api.get_representation_path(representation))
extension = libpath.suffix.lower() extension = libpath.suffix.lower()
logger.debug( self.log.info(
"Container: %s\nRepresentation: %s", "Container: %s\nRepresentation: %s",
pformat(container, indent=2), pformat(container, indent=2),
pformat(representation, indent=2), pformat(representation, indent=2),
@ -162,38 +163,47 @@ class BlendModelLoader(pype.hosts.blender.plugin.AssetLoader):
assert libpath.is_file(), ( assert libpath.is_file(), (
f"The file doesn't exist: {libpath}" 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}" f"Unsupported file: {libpath}"
) )
collection_metadata = collection.get( collection_metadata = collection.get(
blender.pipeline.AVALON_PROPERTY) blender.pipeline.AVALON_PROPERTY)
collection_libpath = collection_metadata["libpath"] collection_libpath = collection_metadata["libpath"]
objects = collection_metadata["objects"]
lib_container = collection_metadata["lib_container"] lib_container = collection_metadata["lib_container"]
obj_container = plugin.get_local_collection_with_name(
collection_metadata["obj_container"].name
)
objects = obj_container.all_objects
container_name = obj_container.name
normalized_collection_libpath = ( normalized_collection_libpath = (
str(Path(bpy.path.abspath(collection_libpath)).resolve()) str(Path(bpy.path.abspath(collection_libpath)).resolve())
) )
normalized_libpath = ( normalized_libpath = (
str(Path(bpy.path.abspath(str(libpath))).resolve()) 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:\n %s\nnormalized_libpath:\n %s",
normalized_collection_libpath, normalized_collection_libpath,
normalized_libpath, normalized_libpath,
) )
if 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 return
self._remove(objects, lib_container) parent = plugin.get_parent_collection(obj_container)
objects_list = self._process( self._remove(objects, obj_container)
str(libpath), lib_container, collection.name)
obj_container = self._process(
str(libpath), lib_container, container_name, parent)
# Save the list of objects in the metadata container # 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["libpath"] = str(libpath)
collection_metadata["representation"] = str(representation["_id"]) collection_metadata["representation"] = str(representation["_id"])
@ -221,17 +231,20 @@ class BlendModelLoader(pype.hosts.blender.plugin.AssetLoader):
collection_metadata = collection.get( collection_metadata = collection.get(
blender.pipeline.AVALON_PROPERTY) blender.pipeline.AVALON_PROPERTY)
objects = collection_metadata["objects"]
lib_container = collection_metadata["lib_container"]
self._remove(objects, lib_container) obj_container = plugin.get_local_collection_with_name(
collection_metadata["obj_container"].name
)
objects = obj_container.all_objects
self._remove(objects, obj_container)
bpy.data.collections.remove(collection) bpy.data.collections.remove(collection)
return True return True
class CacheModelLoader(pype.hosts.blender.plugin.AssetLoader): class CacheModelLoader(plugin.AssetLoader):
"""Load cache models. """Load cache models.
Stores the imported asset in a collection named after the asset. Stores the imported asset in a collection named after the asset.
@ -267,7 +280,7 @@ class CacheModelLoader(pype.hosts.blender.plugin.AssetLoader):
subset = context["subset"]["name"] subset = context["subset"]["name"]
# TODO (jasper): evaluate use of namespace which is 'alien' to Blender. # TODO (jasper): evaluate use of namespace which is 'alien' to Blender.
lib_container = container_name = ( 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 relative = bpy.context.preferences.filepaths.use_relative_paths

View file

@ -7,20 +7,14 @@ from typing import Dict, List, Optional
from avalon import api, blender from avalon import api, blender
import bpy import bpy
import pype.hosts.blender.plugin import pype.hosts.blender.plugin as plugin
logger = logging.getLogger("pype").getChild("blender").getChild("load_model")
class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader): class BlendRigLoader(plugin.AssetLoader):
"""Load rigs from a .blend file. """Load rigs from a .blend file.
Because they come from a .blend file we can simply link the collection that 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. 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"] families = ["rig"]
@ -30,50 +24,54 @@ class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader):
icon = "code-fork" icon = "code-fork"
color = "orange" color = "orange"
def _remove(self, objects, lib_container): def _remove(self, objects, obj_container):
for obj in objects: for obj in objects:
if obj.type == 'ARMATURE': if obj.type == 'ARMATURE':
bpy.data.armatures.remove(obj.data) bpy.data.armatures.remove(obj.data)
elif obj.type == 'MESH': elif obj.type == 'MESH':
bpy.data.meshes.remove(obj.data) 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(child)
bpy.data.collections.remove(bpy.data.collections[lib_container]) bpy.data.collections.remove(obj_container)
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 relative = bpy.context.preferences.filepaths.use_relative_paths
with bpy.data.libraries.load( with bpy.data.libraries.load(
libpath, link=True, relative=relative libpath, link=True, relative=relative
) as (_, data_to): ) as (_, data_to):
data_to.collections = [lib_container] 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
rig_container = scene.collection.children[lib_container].make_local() parent.children.link(bpy.data.collections[lib_container])
rig_container = parent.children[lib_container].make_local()
rig_container.name = container_name
meshes = [] meshes = []
armatures = [ armatures = [
obj for obj in rig_container.objects if obj.type == 'ARMATURE'] obj for obj in rig_container.objects
if obj.type == 'ARMATURE'
objects_list = [] ]
for child in rig_container.children: for child in rig_container.children:
child.make_local() plugin.prepare_data(child, container_name)
meshes.extend( child.objects ) meshes.extend(child.objects)
# Link meshes first, then armatures. # Link meshes first, then armatures.
# The armature is unparented for all the non-local meshes, # The armature is unparented for all the non-local meshes,
# when it is made local. # when it is made local.
for obj in meshes + armatures: for obj in meshes + armatures:
obj = obj.make_local() plugin.prepare_data(obj, container_name)
obj.data.make_local() plugin.prepare_data(obj.data, container_name)
if not obj.get(blender.pipeline.AVALON_PROPERTY): if not obj.get(blender.pipeline.AVALON_PROPERTY):
obj[blender.pipeline.AVALON_PROPERTY] = dict() obj[blender.pipeline.AVALON_PROPERTY] = dict()
@ -84,13 +82,11 @@ class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader):
if obj.type == 'ARMATURE' and action is not None: if obj.type == 'ARMATURE' and action is not None:
obj.animation_data.action = action obj.animation_data.action = action
objects_list.append(obj)
rig_container.pop(blender.pipeline.AVALON_PROPERTY) rig_container.pop(blender.pipeline.AVALON_PROPERTY)
bpy.ops.object.select_all(action='DESELECT') bpy.ops.object.select_all(action='DESELECT')
return objects_list return rig_container
def process_asset( def process_asset(
self, context: dict, name: str, namespace: Optional[str] = None, self, context: dict, name: str, namespace: Optional[str] = None,
@ -107,9 +103,15 @@ class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader):
libpath = self.fname libpath = self.fname
asset = context["asset"]["name"] asset = context["asset"]["name"]
subset = context["subset"]["name"] subset = context["subset"]["name"]
lib_container = pype.hosts.blender.plugin.asset_name(asset, subset) lib_container = plugin.asset_name(
container_name = pype.hosts.blender.plugin.asset_name( asset, subset
asset, subset, namespace )
unique_number = plugin.get_unique_number(
asset, subset
)
namespace = namespace or f"{asset}_{unique_number}"
container_name = plugin.asset_name(
asset, subset, unique_number
) )
container = bpy.data.collections.new(lib_container) container = bpy.data.collections.new(lib_container)
@ -128,11 +130,13 @@ class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader):
container_metadata["libpath"] = libpath container_metadata["libpath"] = libpath
container_metadata["lib_container"] = lib_container container_metadata["lib_container"] = lib_container
objects_list = self._process( obj_container = self._process(
libpath, lib_container, container_name, None) libpath, lib_container, container_name, None, None)
container_metadata["obj_container"] = obj_container
# Save the list of objects in the metadata 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 = list(container.objects)
nodes.append(container) nodes.append(container)
@ -151,15 +155,13 @@ class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader):
Warning: Warning:
No nested collections are supported at the moment! No nested collections are supported at the moment!
""" """
collection = bpy.data.collections.get( collection = bpy.data.collections.get(
container["objectName"] container["objectName"]
) )
libpath = Path(api.get_representation_path(representation)) libpath = Path(api.get_representation_path(representation))
extension = libpath.suffix.lower() extension = libpath.suffix.lower()
logger.info( self.log.info(
"Container: %s\nRepresentation: %s", "Container: %s\nRepresentation: %s",
pformat(container, indent=2), pformat(container, indent=2),
pformat(representation, indent=2), pformat(representation, indent=2),
@ -177,29 +179,35 @@ class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader):
assert libpath.is_file(), ( assert libpath.is_file(), (
f"The file doesn't exist: {libpath}" 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}" f"Unsupported file: {libpath}"
) )
collection_metadata = collection.get( collection_metadata = collection.get(
blender.pipeline.AVALON_PROPERTY) blender.pipeline.AVALON_PROPERTY)
collection_libpath = collection_metadata["libpath"] collection_libpath = collection_metadata["libpath"]
objects = collection_metadata["objects"]
lib_container = collection_metadata["lib_container"] lib_container = collection_metadata["lib_container"]
obj_container = plugin.get_local_collection_with_name(
collection_metadata["obj_container"].name
)
objects = obj_container.all_objects
container_name = obj_container.name
normalized_collection_libpath = ( normalized_collection_libpath = (
str(Path(bpy.path.abspath(collection_libpath)).resolve()) str(Path(bpy.path.abspath(collection_libpath)).resolve())
) )
normalized_libpath = ( normalized_libpath = (
str(Path(bpy.path.abspath(str(libpath))).resolve()) 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:\n %s\nnormalized_libpath:\n %s",
normalized_collection_libpath, normalized_collection_libpath,
normalized_libpath, normalized_libpath,
) )
if 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 return
# Get the armature of the rig # Get the armature of the rig
@ -208,13 +216,16 @@ class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader):
action = armatures[0].animation_data.action action = armatures[0].animation_data.action
self._remove(objects, lib_container) parent = plugin.get_parent_collection(obj_container)
objects_list = self._process( self._remove(objects, obj_container)
str(libpath), lib_container, collection.name, action)
obj_container = self._process(
str(libpath), lib_container, container_name, action, parent)
# Save the list of objects in the metadata container # 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["libpath"] = str(libpath)
collection_metadata["representation"] = str(representation["_id"]) collection_metadata["representation"] = str(representation["_id"])
@ -245,10 +256,13 @@ class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader):
collection_metadata = collection.get( collection_metadata = collection.get(
blender.pipeline.AVALON_PROPERTY) blender.pipeline.AVALON_PROPERTY)
objects = collection_metadata["objects"]
lib_container = collection_metadata["lib_container"]
self._remove(objects, lib_container) obj_container = plugin.get_local_collection_with_name(
collection_metadata["obj_container"].name
)
objects = obj_container.all_objects
self._remove(objects, obj_container)
bpy.data.collections.remove(collection) bpy.data.collections.remove(collection)