Camera handling between Blender and Unreal implemented

This commit is contained in:
Simone Barbieri 2021-09-07 15:51:36 +01:00
parent 80841fde06
commit 1d6b10bb1d
4 changed files with 306 additions and 10 deletions

View file

@ -3,11 +3,12 @@
import bpy
from avalon import api
from avalon.blender import lib
import openpype.hosts.blender.api.plugin
from avalon.blender import lib, ops
from avalon.blender.pipeline import AVALON_INSTANCES
from openpype.hosts.blender.api import plugin
class CreateCamera(openpype.hosts.blender.api.plugin.Creator):
class CreateCamera(plugin.Creator):
"""Polygonal static geometry"""
name = "cameraMain"
@ -16,17 +17,47 @@ class CreateCamera(openpype.hosts.blender.api.plugin.Creator):
icon = "video-camera"
def process(self):
""" Run the creator on Blender main thread"""
mti = ops.MainThreadItem(self._process)
ops.execute_in_main_thread(mti)
def process(self):
# Get Instance Containter or create it if it does not exist
instances = bpy.data.collections.get(AVALON_INSTANCES)
if not instances:
instances = bpy.data.collections.new(name=AVALON_INSTANCES)
bpy.context.scene.collection.children.link(instances)
# Create instance object
asset = self.data["asset"]
subset = self.data["subset"]
name = openpype.hosts.blender.api.plugin.asset_name(asset, subset)
collection = bpy.data.collections.new(name=name)
bpy.context.scene.collection.children.link(collection)
name = plugin.asset_name(asset, subset)
camera = bpy.data.cameras.new(subset)
camera_obj = bpy.data.objects.new(subset, camera)
instances.objects.link(camera_obj)
asset_group = bpy.data.objects.new(name=name, object_data=None)
asset_group.empty_display_type = 'SINGLE_ARROW'
instances.objects.link(asset_group)
self.data['task'] = api.Session.get('AVALON_TASK')
lib.imprint(collection, self.data)
print(f"self.data: {self.data}")
lib.imprint(asset_group, self.data)
if (self.options or {}).get("useSelection"):
for obj in lib.get_selection():
collection.objects.link(obj)
bpy.context.view_layer.objects.active = asset_group
selected = lib.get_selection()
for obj in selected:
obj.select_set(True)
selected.append(asset_group)
bpy.ops.object.parent_set(keep_transform=True)
else:
bpy.ops.object.select_all(action='DESELECT')
camera_obj.select_set(True)
asset_group.select_set(True)
bpy.context.view_layer.objects.active = asset_group
bpy.ops.object.parent_set(keep_transform=True)
return collection
return asset_group

View file

@ -12,6 +12,7 @@ from avalon.blender.pipeline import AVALON_CONTAINERS
from avalon.blender.pipeline import AVALON_CONTAINER_ID
from avalon.blender.pipeline import AVALON_PROPERTY
from avalon.blender.pipeline import AVALON_INSTANCES
from openpype import lib
from openpype.hosts.blender.api import plugin
@ -59,6 +60,8 @@ class JsonLayoutLoader(plugin.AssetLoader):
return None
def _process(self, libpath, asset, asset_group, actions):
print(f"asset: {asset}")
bpy.ops.object.select_all(action='DESELECT')
with open(libpath, "r") as fp:
@ -103,6 +106,21 @@ class JsonLayoutLoader(plugin.AssetLoader):
options=options
)
# Create the camera asset and the camera instance
creator_plugin = lib.get_creator_by_name("CreateCamera")
if not creator_plugin:
raise ValueError("Creator plugin \"CreateCamera\" was "
"not found.")
api.create(
creator_plugin,
name="camera",
# name=f"{unique_number}_{subset}_animation",
asset=asset,
options={"useSelection": False}
# data={"dependencies": str(context["representation"]["_id"])}
)
def process_asset(self,
context: dict,
name: str,

View file

@ -0,0 +1,72 @@
import os
from openpype import api
from openpype.hosts.blender.api import plugin
import bpy
class ExtractFBX(api.Extractor):
"""Extract as FBX."""
label = "Extract FBX"
hosts = ["blender"]
families = ["camera"]
optional = True
def process(self, instance):
# Define extract output file path
stagingdir = self.staging_dir(instance)
filename = f"{instance.name}.fbx"
filepath = os.path.join(stagingdir, filename)
# Perform extraction
self.log.info("Performing extraction..")
bpy.ops.object.select_all(action='DESELECT')
selected = []
camera = None
for obj in instance:
if obj.type == "CAMERA":
obj.select_set(True)
selected.append(obj)
camera = obj
break
assert camera, "No camera found"
context = plugin.create_blender_context(
active=camera, selected=selected)
scale_length = bpy.context.scene.unit_settings.scale_length
bpy.context.scene.unit_settings.scale_length = 0.01
# We export the fbx
bpy.ops.export_scene.fbx(
context,
filepath=filepath,
use_active_collection=False,
use_selection=True,
object_types={'CAMERA'}
)
bpy.context.scene.unit_settings.scale_length = scale_length
bpy.ops.object.select_all(action='DESELECT')
if "representations" not in instance.data:
instance.data["representations"] = []
representation = {
'name': 'fbx',
'ext': 'fbx',
'files': filename,
"stagingDir": stagingdir,
}
instance.data["representations"].append(representation)
self.log.info("Extracted instance '%s' to: %s",
instance.name, representation)

View file

@ -0,0 +1,175 @@
import os
from avalon import api, pipeline
from avalon.unreal import lib
from avalon.unreal import pipeline as unreal_pipeline
import unreal
class CameraLoader(api.Loader):
"""Load Unreal StaticMesh from FBX"""
families = ["camera"]
label = "Load Camera"
representations = ["fbx"]
icon = "cube"
color = "orange"
def load(self, context, name, namespace, data):
"""
Load and containerise representation into Content Browser.
This is two step process. First, import FBX to temporary path and
then call `containerise()` on it - this moves all content to new
directory and then it will create AssetContainer there and imprint it
with metadata. This will mark this path as container.
Args:
context (dict): application context
name (str): subset name
namespace (str): in Unreal this is basically path to container.
This is not passed here, so namespace is set
by `containerise()` because only then we know
real path.
data (dict): Those would be data to be imprinted. This is not used
now, data are imprinted by `containerise()`.
Returns:
list(str): list of container content
"""
# Create directory for asset and avalon container
root = "/Game/Avalon/Assets"
asset = context.get('asset').get('name')
suffix = "_CON"
if asset:
asset_name = "{}_{}".format(asset, name)
else:
asset_name = "{}".format(name)
tools = unreal.AssetToolsHelpers().get_asset_tools()
unique_number = 1
if unreal.EditorAssetLibrary.does_directory_exist(f"{root}/{asset}"):
asset_content = unreal.EditorAssetLibrary.list_assets(
f"{root}/{asset}", recursive=False, include_folder=True
)
# Get highest number to make a unique name
folders = [a for a in asset_content
if a[-1] == "/" and f"{name}_" in a]
f_numbers = []
for f in folders:
# Get number from folder name. Splits the string by "_" and
# removes the last element (which is a "/").
f_numbers.append(int(f.split("_")[-1][:-1]))
f_numbers.sort()
unique_number = f_numbers[-1] + 1
asset_dir, container_name = tools.create_unique_asset_name(
f"{root}/{asset}/{name}_{unique_number:02d}", suffix="")
container_name += suffix
unreal.EditorAssetLibrary.make_directory(asset_dir)
sequence = tools.create_asset(
asset_name=asset_name,
package_path=asset_dir,
asset_class=unreal.LevelSequence,
factory=unreal.LevelSequenceFactoryNew()
)
settings = unreal.MovieSceneUserImportFBXSettings()
unreal.SequencerTools.import_fbx(
unreal.EditorLevelLibrary.get_editor_world(),
sequence,
sequence.get_bindings(),
settings,
self.fname
)
# Create Asset Container
lib.create_avalon_container(container=container_name, path=asset_dir)
data = {
"schema": "openpype:container-2.0",
"id": pipeline.AVALON_CONTAINER_ID,
"asset": asset,
"namespace": asset_dir,
"container_name": container_name,
"asset_name": asset_name,
"loader": str(self.__class__.__name__),
"representation": context["representation"]["_id"],
"parent": context["representation"]["parent"],
"family": context["representation"]["context"]["family"]
}
unreal_pipeline.imprint(
"{}/{}".format(asset_dir, container_name), data)
asset_content = unreal.EditorAssetLibrary.list_assets(
asset_dir, recursive=True, include_folder=True
)
for a in asset_content:
unreal.EditorAssetLibrary.save_asset(a)
return asset_content
def update(self, container, representation):
path = container["namespace"]
ar = unreal.AssetRegistryHelpers.get_asset_registry()
tools = unreal.AssetToolsHelpers().get_asset_tools()
asset_content = unreal.EditorAssetLibrary.list_assets(
path, recursive=False, include_folder=False
)
asset_name = ""
for a in asset_content:
asset = ar.get_asset_by_object_path(a)
if a.endswith("_CON"):
loaded_asset = unreal.EditorAssetLibrary.load_asset(a)
unreal.EditorAssetLibrary.set_metadata_tag(
loaded_asset, "representation", str(representation["_id"])
)
unreal.EditorAssetLibrary.set_metadata_tag(
loaded_asset, "parent", str(representation["parent"])
)
asset_name = unreal.EditorAssetLibrary.get_metadata_tag(
loaded_asset, "asset_name"
)
elif asset.asset_class == "LevelSequence":
unreal.EditorAssetLibrary.delete_asset(a)
sequence = tools.create_asset(
asset_name=asset_name,
package_path=path,
asset_class=unreal.LevelSequence,
factory=unreal.LevelSequenceFactoryNew()
)
settings = unreal.MovieSceneUserImportFBXSettings()
unreal.SequencerTools.import_fbx(
unreal.EditorLevelLibrary.get_editor_world(),
sequence,
sequence.get_bindings(),
settings,
str(representation["data"]["path"])
)
def remove(self, container):
path = container["namespace"]
parent_path = os.path.dirname(path)
unreal.EditorAssetLibrary.delete_directory(path)
asset_content = unreal.EditorAssetLibrary.list_assets(
parent_path, recursive=False, include_folder=True
)
if len(asset_content) == 0:
unreal.EditorAssetLibrary.delete_directory(parent_path)