Separate blendscene loader from blend loader

This commit is contained in:
Simone Barbieri 2023-10-31 10:49:58 +00:00
parent a180a276a3
commit fd30a0426c
2 changed files with 216 additions and 1 deletions

View file

@ -20,7 +20,7 @@ from openpype.hosts.blender.api.pipeline import (
class BlendLoader(plugin.AssetLoader):
"""Load assets from a .blend file."""
families = ["model", "rig", "layout", "camera", "blendScene"]
families = ["model", "rig", "layout", "camera"]
representations = ["blend"]
label = "Append Blend"

View file

@ -0,0 +1,215 @@
from typing import Dict, List, Optional
from pathlib import Path
import bpy
from openpype.pipeline import (
get_representation_path,
AVALON_CONTAINER_ID,
)
from openpype.pipeline.create import get_legacy_creator_by_name
from openpype.hosts.blender.api import plugin
from openpype.hosts.blender.api.lib import imprint
from openpype.hosts.blender.api.pipeline import (
AVALON_CONTAINERS,
AVALON_PROPERTY,
)
class BlendSceneLoader(plugin.AssetLoader):
"""Load assets from a .blend file."""
families = ["blendScene"]
representations = ["blend"]
label = "Append Blend"
icon = "code-fork"
color = "orange"
@staticmethod
def _get_asset_container(collections):
for coll in collections:
parents = [c for c in collections if c.user_of_id(coll)]
if coll.get(AVALON_PROPERTY) and not parents:
return coll
return None
def _process_data(self, libpath, group_name, family):
# Append all the data from the .blend file
with bpy.data.libraries.load(
libpath, link=False, relative=False
) as (data_from, data_to):
for attr in dir(data_to):
setattr(data_to, attr, getattr(data_from, attr))
members = []
# Rename the object to add the asset name
for attr in dir(data_to):
for data in getattr(data_to, attr):
data.name = f"{group_name}:{data.name}"
members.append(data)
container = self._get_asset_container(
data_to.collections)
assert container, "No asset group found"
container.name = group_name
# Link the group to the scene
bpy.context.scene.collection.children.link(container)
# Remove the library from the blend file
library = bpy.data.libraries.get(bpy.path.basename(libpath))
bpy.data.libraries.remove(library)
return container, members
def process_asset(
self, context: dict, name: str, namespace: Optional[str] = None,
options: Optional[Dict] = None
) -> Optional[List]:
"""
Arguments:
name: Use pre-defined name
namespace: Use pre-defined namespace
context: Full parenthood of representation to load
options: Additional settings dictionary
"""
libpath = self.filepath_from_context(context)
asset = context["asset"]["name"]
subset = context["subset"]["name"]
try:
family = context["representation"]["context"]["family"]
except ValueError:
family = "model"
asset_name = plugin.asset_name(asset, subset)
unique_number = plugin.get_unique_number(asset, subset)
group_name = plugin.asset_name(asset, subset, unique_number)
namespace = namespace or f"{asset}_{unique_number}"
avalon_container = bpy.data.collections.get(AVALON_CONTAINERS)
if not avalon_container:
avalon_container = bpy.data.collections.new(name=AVALON_CONTAINERS)
bpy.context.scene.collection.children.link(avalon_container)
container, members = self._process_data(libpath, group_name, family)
avalon_container.children.link(container)
data = {
"schema": "openpype:container-2.0",
"id": AVALON_CONTAINER_ID,
"name": name,
"namespace": namespace or '',
"loader": str(self.__class__.__name__),
"representation": str(context["representation"]["_id"]),
"libpath": libpath,
"asset_name": asset_name,
"parent": str(context["representation"]["parent"]),
"family": context["representation"]["context"]["family"],
"objectName": group_name,
"members": members,
}
container[AVALON_PROPERTY] = data
objects = [
obj for obj in bpy.data.objects
if obj.name.startswith(f"{group_name}:")
]
self[:] = objects
return objects
def exec_update(self, container: Dict, representation: Dict):
"""
Update the loaded asset.
"""
group_name = container["objectName"]
asset_group = bpy.data.collections.get(group_name)
libpath = Path(get_representation_path(representation)).as_posix()
assert asset_group, (
f"The asset is not loaded: {container['objectName']}"
)
collection_parents = {}
members = asset_group.get(AVALON_PROPERTY).get("members", [])
loaded_collections = {c for c in bpy.data.collections if c in members}
loaded_collections.add(bpy.data.collections.get(AVALON_CONTAINERS))
for member in members:
if isinstance(member, bpy.types.Object):
member_parents = set(member.users_collection)
elif isinstance(member, bpy.types.Collection):
member_parents = {
c for c in bpy.data.collections if c.user_of_id(member)}
else:
continue
member_parents = member_parents.difference(loaded_collections)
if member_parents:
collection_parents[member.name] = list(member_parents)
old_data = dict(asset_group.get(AVALON_PROPERTY))
self.exec_remove(container)
family = container["family"]
asset_group, members = self._process_data(libpath, group_name, family)
for member in members:
if member.name in collection_parents:
for parent in collection_parents[member.name]:
if isinstance(member, bpy.types.Object):
parent.objects.link(member)
elif isinstance(member, bpy.types.Collection):
parent.children.link(member)
avalon_container = bpy.data.collections.get(AVALON_CONTAINERS)
avalon_container.children.link(asset_group)
# Restore the old data, but reset memebers, as they don't exist anymore
# This avoids a crash, because the memory addresses of those members
# are not valid anymore
old_data["members"] = []
asset_group[AVALON_PROPERTY] = old_data
new_data = {
"libpath": libpath,
"representation": str(representation["_id"]),
"parent": str(representation["parent"]),
"members": members,
}
imprint(asset_group, new_data)
def exec_remove(self, container: Dict) -> bool:
"""
Remove an existing container from a Blender scene.
"""
group_name = container["objectName"]
asset_group = bpy.data.collections.get(group_name)
attrs = [
attr for attr in dir(bpy.data)
if isinstance(
getattr(bpy.data, attr),
bpy.types.bpy_prop_collection
)
]
members = asset_group.get(AVALON_PROPERTY).get("members", [])
for attr in attrs:
for data in getattr(bpy.data, attr):
if data in members:
# Skip the asset group
if data == asset_group:
continue
getattr(bpy.data, attr).remove(data)
bpy.data.collections.remove(asset_group)