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

Commit

Permalink
Merge pull request #1562 from simonebarbieri/feature/unreal-blender-m…
Browse files Browse the repository at this point in the history
…aterials
  • Loading branch information
mkolar authored Jun 1, 2021
2 parents 281697c + 1ead13b commit ebfa0bb
Show file tree
Hide file tree
Showing 4 changed files with 419 additions and 0 deletions.
218 changes: 218 additions & 0 deletions openpype/hosts/blender/plugins/load/load_look.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
"""Load a model asset in Blender."""

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

import os
import json
import bpy

from avalon import api, blender
import openpype.hosts.blender.api.plugin as plugin


class BlendLookLoader(plugin.AssetLoader):
"""Load models 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.
"""

families = ["look"]
representations = ["json"]

label = "Load Look"
icon = "code-fork"
color = "orange"

def get_all_children(self, obj):
children = list(obj.children)

for child in children:
children.extend(child.children)

return children

def _process(self, libpath, container_name, objects):
with open(libpath, "r") as fp:
data = json.load(fp)

path = os.path.dirname(libpath)
materials_path = f"{path}/resources"

materials = []

for entry in data:
file = entry.get('fbx_filename')
if file is None:
continue

bpy.ops.import_scene.fbx(filepath=f"{materials_path}/{file}")

mesh = [o for o in bpy.context.scene.objects if o.select_get()][0]
material = mesh.data.materials[0]
material.name = f"{material.name}:{container_name}"

texture_file = entry.get('tga_filename')
if texture_file:
node_tree = material.node_tree
pbsdf = node_tree.nodes['Principled BSDF']
base_color = pbsdf.inputs[0]
tex_node = base_color.links[0].from_node
tex_node.image.filepath = f"{materials_path}/{texture_file}"

materials.append(material)

for obj in objects:
for child in self.get_all_children(obj):
mesh_name = child.name.split(':')[0]
if mesh_name == material.name.split(':')[0]:
child.data.materials.clear()
child.data.materials.append(material)
break

bpy.data.objects.remove(mesh)

return materials, objects

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 = plugin.asset_name(
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
)

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

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

metadata["libpath"] = libpath
metadata["lib_container"] = lib_container

selected = [o for o in bpy.context.scene.objects if o.select_get()]

materials, objects = self._process(libpath, container_name, selected)

# Save the list of imported materials in the metadata container
metadata["objects"] = objects
metadata["materials"] = materials

metadata["parent"] = str(context["representation"]["parent"])
metadata["family"] = context["representation"]["context"]["family"]

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

def update(self, container: Dict, representation: Dict):
collection = bpy.data.collections.get(container["objectName"])
libpath = Path(api.get_representation_path(representation))
extension = libpath.suffix.lower()

self.log.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 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())
)
self.log.debug(
"normalized_collection_libpath:\n %s\nnormalized_libpath:\n %s",
normalized_collection_libpath,
normalized_libpath,
)
if normalized_collection_libpath == normalized_libpath:
self.log.info("Library already loaded, not updating...")
return

for obj in collection_metadata['objects']:
for child in self.get_all_children(obj):
child.data.materials.clear()

for material in collection_metadata['materials']:
bpy.data.materials.remove(material)

namespace = collection_metadata['namespace']
name = collection_metadata['name']

container_name = f"{namespace}_{name}"

materials, objects = self._process(
libpath, container_name, collection_metadata['objects'])

collection_metadata["objects"] = objects
collection_metadata["materials"] = materials
collection_metadata["libpath"] = str(libpath)
collection_metadata["representation"] = str(representation["_id"])

def remove(self, container: Dict) -> bool:
collection = bpy.data.collections.get(container["objectName"])
if not collection:
return False

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

for obj in collection_metadata['objects']:
for child in self.get_all_children(obj):
child.data.materials.clear()

for material in collection_metadata['materials']:
bpy.data.materials.remove(material)

bpy.data.collections.remove(collection)

return True
15 changes: 15 additions & 0 deletions openpype/hosts/blender/plugins/publish/extract_fbx.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ def process(self, instance):
# We set the scale of the scene for the export
scene.unit_settings.scale_length = 0.01

new_materials = []

for obj in collections[0].all_objects:
if obj.type == 'MESH':
mat = bpy.data.materials.new(obj.name)
obj.data.materials.append(mat)
new_materials.append(mat)

# We export the fbx
bpy.ops.export_scene.fbx(
filepath=filepath,
Expand All @@ -66,6 +74,13 @@ def process(self, instance):

scene.unit_settings.scale_length = old_scale

for mat in new_materials:
bpy.data.materials.remove(mat)

for obj in collections[0].all_objects:
if obj.type == 'MESH':
obj.data.materials.pop()

if "representations" not in instance.data:
instance.data["representations"] = []

Expand Down
66 changes: 66 additions & 0 deletions openpype/hosts/unreal/plugins/create/create_look.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import unreal
from openpype.hosts.unreal.api.plugin import Creator
from avalon.unreal import pipeline


class CreateLook(Creator):
"""Shader connections defining shape look"""

name = "unrealLook"
label = "Unreal - Look"
family = "look"
icon = "paint-brush"

root = "/Game/Avalon/Assets"
suffix = "_INS"

def __init__(self, *args, **kwargs):
super(CreateLook, self).__init__(*args, **kwargs)

def process(self):
name = self.data["subset"]

selection = []
if (self.options or {}).get("useSelection"):
sel_objects = unreal.EditorUtilityLibrary.get_selected_assets()
selection = [a.get_path_name() for a in sel_objects]

# Create the folder
path = f"{self.root}/{self.data['asset']}"
new_name = pipeline.create_folder(path, name)
full_path = f"{path}/{new_name}"

# Create a new cube static mesh
ar = unreal.AssetRegistryHelpers.get_asset_registry()
cube = ar.get_asset_by_object_path("/Engine/BasicShapes/Cube.Cube")

# Create the avalon publish instance object
container_name = f"{name}{self.suffix}"
pipeline.create_publish_instance(
instance=container_name, path=full_path)

# Get the mesh of the selected object
original_mesh = ar.get_asset_by_object_path(selection[0]).get_asset()
materials = original_mesh.get_editor_property('materials')

self.data["members"] = []

# Add the materials to the cube
for material in materials:
name = material.get_editor_property('material_slot_name')
object_path = f"{full_path}/{name}.{name}"
object = unreal.EditorAssetLibrary.duplicate_loaded_asset(
cube.get_asset(), object_path
)

# Remove the default material of the cube object
object.get_editor_property('static_materials').pop()

object.add_material(
material.get_editor_property('material_interface'))

self.data["members"].append(object_path)

unreal.EditorAssetLibrary.save_asset(object_path)

pipeline.imprint(f"{full_path}/{container_name}", self.data)
Loading

0 comments on commit ebfa0bb

Please sign in to comment.