Skip to content
This repository has been archived by the owner on Sep 20, 2024. It is now read-only.

Layout asset for Blender #226

Merged
merged 1 commit into from
Jun 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions pype/plugins/blender/create/create_layout.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""Create a layout asset."""

import bpy

from avalon import api
from avalon.blender import Creator, lib
import pype.hosts.blender.plugin


class CreateLayout(Creator):
"""Layout output for character rigs"""

name = "layoutMain"
label = "Layout"
family = "layout"
icon = "cubes"

def process(self):

asset = self.data["asset"]
subset = self.data["subset"]
name = pype.hosts.blender.plugin.asset_name(asset, subset)
collection = bpy.data.collections.new(name=name)
bpy.context.scene.collection.children.link(collection)
self.data['task'] = api.Session.get('AVALON_TASK')
lib.imprint(collection, self.data)

# Add the rig object and all the children meshes to
# a set and link them all at the end to avoid duplicates.
# Blender crashes if trying to link an object that is already linked.
# This links automatically the children meshes if they were not
# selected, and doesn't link them twice if they, insted,
# were manually selected by the user.
objects_to_link = set()

if (self.options or {}).get("useSelection"):

for obj in lib.get_selection():

objects_to_link.add(obj)

if obj.type == 'ARMATURE':

for subobj in obj.children:

objects_to_link.add(subobj)

for obj in objects_to_link:

collection.objects.link(obj)

return collection
259 changes: 259 additions & 0 deletions pype/plugins/blender/load/load_layout.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
"""Load a layout in Blender."""

import logging
from pathlib import Path
from pprint import pformat
from typing import Dict, List, Optional

from avalon import api, blender
import bpy
import pype.hosts.blender.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.
"""

families = ["layout"]
representations = ["blend"]

label = "Link Layout"
icon = "code-fork"
color = "orange"

@staticmethod
def _remove(self, objects, lib_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)

bpy.data.collections.remove(bpy.data.collections[lib_container])

@staticmethod
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
) as (_, data_to):
data_to.collections = [lib_container]

scene = bpy.context.scene

scene.collection.children.link(bpy.data.collections[lib_container])

layout_container = scene.collection.children[lib_container].make_local()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

line too long (80 > 79 characters)


meshes = [
obj for obj in layout_container.objects if obj.type == 'MESH']
armatures = [
obj for obj in layout_container.objects if obj.type == 'ARMATURE']

objects_list = []

# 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()

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})

action = actions.get( obj.name, None )
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whitespace after '('
whitespace before ')'


if obj.type == 'ARMATURE' and action is not None:

obj.animation_data.action = action

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blank line contains whitespace

objects_list.append(obj)

layout_container.pop(blender.pipeline.AVALON_PROPERTY)

bpy.ops.object.select_all(action='DESELECT')

return objects_list

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.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(
asset, subset, namespace
)

container = bpy.data.collections.new(lib_container)
container.name = container_name
blender.pipeline.containerise_existing(
container,
name,
namespace,
context,
self.__class__.__name__,
)

container_metadata = container.get(
blender.pipeline.AVALON_PROPERTY)

container_metadata["libpath"] = libpath
container_metadata["lib_container"] = lib_container

objects_list = self._process(
self, libpath, lib_container, container_name, {})

# Save the list of objects in the metadata container
container_metadata["objects"] = objects_list

nodes = list(container.objects)
nodes.append(container)
self[:] = nodes
return nodes

def update(self, container: Dict, representation: Dict):
"""Update the loaded asset.

This will remove all objects of the current collection, load the new
ones and add them to the collection.
If the objects of the collection are used in another collection they
will not be removed, only unlinked. Normally this should not be the
case though.

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()

logger.info(
"Container: %s\nRepresentation: %s",
pformat(container, indent=2),
pformat(representation, indent=2),
)

assert collection, (
f"The asset is not loaded: {container['objectName']}"
)
assert not (collection.children), (
"Nested collections are not supported."
)
assert libpath, (
"No existing library file found for {container['objectName']}"
)
assert libpath.is_file(), (
f"The file doesn't exist: {libpath}"
)
assert extension in pype.hosts.blender.plugin.VALID_EXTENSIONS, (
f"Unsupported file: {libpath}"
)

collection_metadata = collection.get(
blender.pipeline.AVALON_PROPERTY)

collection_libpath = collection_metadata["libpath"]
normalized_collection_libpath = (
str(Path(bpy.path.abspath(collection_libpath)).resolve())
)
normalized_libpath = (
str(Path(bpy.path.abspath(str(libpath))).resolve())
)
logger.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...")
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(self, objects, lib_container)

objects_list = self._process(
self, str(libpath), lib_container, collection.name, actions)

# Save the list of objects in the metadata container
collection_metadata["objects"] = objects_list
collection_metadata["libpath"] = str(libpath)
collection_metadata["representation"] = str(representation["_id"])

bpy.ops.object.select_all(action='DESELECT')

def remove(self, container: Dict) -> bool:
"""Remove an existing container from a Blender scene.

Arguments:
container (avalon-core:container-1.0): Container to remove,
from `host.ls()`.

Returns:
bool: Whether the container was deleted.

Warning:
No nested collections are supported at the moment!
"""

collection = bpy.data.collections.get(
container["objectName"]
)
if not collection:
return False
assert not (collection.children), (
"Nested collections are not supported."
)

collection_metadata = collection.get(
blender.pipeline.AVALON_PROPERTY)
objects = collection_metadata["objects"]
lib_container = collection_metadata["lib_container"]

self._remove(self, objects, lib_container)

bpy.data.collections.remove(collection)

return True
2 changes: 1 addition & 1 deletion pype/plugins/blender/publish/extract_blend.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class ExtractBlend(pype.api.Extractor):

label = "Extract Blend"
hosts = ["blender"]
families = ["animation", "model", "rig", "action"]
families = ["animation", "model", "rig", "action", "layout"]
optional = True

def process(self, instance):
Expand Down