mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 08:24:53 +01:00
Merge pull request #328 from simonebarbieri/feature/blender-namespace-support
Feature/Blender namespace support
This commit is contained in:
commit
62ff1b298b
4 changed files with 241 additions and 158 deletions
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue