mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-02 00:44:52 +01:00
Merge branch 'develop' into bugfix/OP-3022-Look-publishing-and-srgb-colorspace-in-Maya-2022
This commit is contained in:
commit
db4f88d85b
60 changed files with 720 additions and 409 deletions
|
|
@ -1,7 +1,10 @@
|
|||
import os
|
||||
from openpype.pipeline import (
|
||||
load
|
||||
load,
|
||||
get_representation_path
|
||||
)
|
||||
from openpype.hosts.max.api.pipeline import containerise
|
||||
from openpype.hosts.max.api import lib
|
||||
|
||||
|
||||
class FbxLoader(load.LoaderPlugin):
|
||||
|
|
@ -36,14 +39,26 @@ importFile @"{filepath}" #noPrompt using:FBXIMP
|
|||
container_name = f"{name}_CON"
|
||||
|
||||
asset = rt.getNodeByName(f"{name}")
|
||||
# rename the container with "_CON"
|
||||
container = rt.container(name=container_name)
|
||||
asset.Parent = container
|
||||
|
||||
return container
|
||||
return containerise(
|
||||
name, [asset], context, loader=self.__class__.__name__)
|
||||
|
||||
def update(self, container, representation):
|
||||
from pymxs import runtime as rt
|
||||
|
||||
path = get_representation_path(representation)
|
||||
node = rt.getNodeByName(container["instance_node"])
|
||||
|
||||
fbx_objects = self.get_container_children(node)
|
||||
for fbx_object in fbx_objects:
|
||||
fbx_object.source = path
|
||||
|
||||
lib.imprint(container["instance_node"], {
|
||||
"representation": str(representation["_id"])
|
||||
})
|
||||
|
||||
def remove(self, container):
|
||||
from pymxs import runtime as rt
|
||||
|
||||
node = container["node"]
|
||||
node = rt.getNodeByName(container["instance_node"])
|
||||
rt.delete(node)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import os
|
||||
from openpype.pipeline import (
|
||||
load
|
||||
load, get_representation_path
|
||||
)
|
||||
from openpype.hosts.max.api.pipeline import containerise
|
||||
from openpype.hosts.max.api import lib
|
||||
|
||||
|
||||
class MaxSceneLoader(load.LoaderPlugin):
|
||||
|
|
@ -35,16 +37,26 @@ class MaxSceneLoader(load.LoaderPlugin):
|
|||
self.log.error("Something failed when loading.")
|
||||
|
||||
max_container = max_containers.pop()
|
||||
container_name = f"{name}_CON"
|
||||
# rename the container with "_CON"
|
||||
# get the original container
|
||||
container = rt.container(name=container_name)
|
||||
max_container.Parent = container
|
||||
|
||||
return container
|
||||
return containerise(
|
||||
name, [max_container], context, loader=self.__class__.__name__)
|
||||
|
||||
def update(self, container, representation):
|
||||
from pymxs import runtime as rt
|
||||
|
||||
path = get_representation_path(representation)
|
||||
node = rt.getNodeByName(container["instance_node"])
|
||||
|
||||
max_objects = self.get_container_children(node)
|
||||
for max_object in max_objects:
|
||||
max_object.source = path
|
||||
|
||||
lib.imprint(container["instance_node"], {
|
||||
"representation": str(representation["_id"])
|
||||
})
|
||||
|
||||
def remove(self, container):
|
||||
from pymxs import runtime as rt
|
||||
|
||||
node = container["node"]
|
||||
node = rt.getNodeByName(container["instance_node"])
|
||||
rt.delete(node)
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ importFile @"{file_path}" #noPrompt
|
|||
def remove(self, container):
|
||||
from pymxs import runtime as rt
|
||||
|
||||
node = container["node"]
|
||||
node = rt.getNodeByName(container["instance_node"])
|
||||
rt.delete(node)
|
||||
|
||||
@staticmethod
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ class ExtractReviewData(publish.Extractor):
|
|||
representations = instance.data.get("representations", [])
|
||||
|
||||
# review can be removed since `ProcessSubmittedJobOnFarm` will create
|
||||
# reviable representation if needed
|
||||
# reviewable representation if needed
|
||||
if (
|
||||
"render.farm" in instance.data["families"]
|
||||
and "review" in instance.data["families"]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Unreal Editor OpenPype host API."""
|
||||
|
||||
from .plugin import Loader
|
||||
from .plugin import (
|
||||
UnrealActorCreator,
|
||||
UnrealAssetCreator,
|
||||
Loader
|
||||
)
|
||||
|
||||
from .pipeline import (
|
||||
install,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
from typing import List
|
||||
from contextlib import contextmanager
|
||||
import semver
|
||||
import time
|
||||
|
||||
import pyblish.api
|
||||
|
||||
|
|
@ -16,13 +18,14 @@ from openpype.pipeline import (
|
|||
)
|
||||
from openpype.tools.utils import host_tools
|
||||
import openpype.hosts.unreal
|
||||
from openpype.host import HostBase, ILoadHost
|
||||
from openpype.host import HostBase, ILoadHost, IPublishHost
|
||||
|
||||
import unreal # noqa
|
||||
|
||||
|
||||
logger = logging.getLogger("openpype.hosts.unreal")
|
||||
|
||||
OPENPYPE_CONTAINERS = "OpenPypeContainers"
|
||||
CONTEXT_CONTAINER = "OpenPype/context.json"
|
||||
UNREAL_VERSION = semver.VersionInfo(
|
||||
*os.getenv("OPENPYPE_UNREAL_VERSION").split(".")
|
||||
)
|
||||
|
|
@ -35,7 +38,7 @@ CREATE_PATH = os.path.join(PLUGINS_DIR, "create")
|
|||
INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory")
|
||||
|
||||
|
||||
class UnrealHost(HostBase, ILoadHost):
|
||||
class UnrealHost(HostBase, ILoadHost, IPublishHost):
|
||||
"""Unreal host implementation.
|
||||
|
||||
For some time this class will re-use functions from module based
|
||||
|
|
@ -60,6 +63,32 @@ class UnrealHost(HostBase, ILoadHost):
|
|||
|
||||
show_tools_dialog()
|
||||
|
||||
def update_context_data(self, data, changes):
|
||||
content_path = unreal.Paths.project_content_dir()
|
||||
op_ctx = content_path + CONTEXT_CONTAINER
|
||||
attempts = 3
|
||||
for i in range(attempts):
|
||||
try:
|
||||
with open(op_ctx, "w+") as f:
|
||||
json.dump(data, f)
|
||||
break
|
||||
except IOError:
|
||||
if i == attempts - 1:
|
||||
raise Exception("Failed to write context data. Aborting.")
|
||||
unreal.log_warning("Failed to write context data. Retrying...")
|
||||
i += 1
|
||||
time.sleep(3)
|
||||
continue
|
||||
|
||||
def get_context_data(self):
|
||||
content_path = unreal.Paths.project_content_dir()
|
||||
op_ctx = content_path + CONTEXT_CONTAINER
|
||||
if not os.path.isfile(op_ctx):
|
||||
return {}
|
||||
with open(op_ctx, "r") as fp:
|
||||
data = json.load(fp)
|
||||
return data
|
||||
|
||||
|
||||
def install():
|
||||
"""Install Unreal configuration for OpenPype."""
|
||||
|
|
@ -133,6 +162,31 @@ def ls():
|
|||
yield data
|
||||
|
||||
|
||||
def ls_inst():
|
||||
ar = unreal.AssetRegistryHelpers.get_asset_registry()
|
||||
# UE 5.1 changed how class name is specified
|
||||
class_name = [
|
||||
"/Script/OpenPype",
|
||||
"OpenPypePublishInstance"
|
||||
] if (
|
||||
UNREAL_VERSION.major == 5
|
||||
and UNREAL_VERSION.minor > 0
|
||||
) else "OpenPypePublishInstance" # noqa
|
||||
instances = ar.get_assets_by_class(class_name, True)
|
||||
|
||||
# get_asset_by_class returns AssetData. To get all metadata we need to
|
||||
# load asset. get_tag_values() work only on metadata registered in
|
||||
# Asset Registry Project settings (and there is no way to set it with
|
||||
# python short of editing ini configuration file).
|
||||
for asset_data in instances:
|
||||
asset = asset_data.get_asset()
|
||||
data = unreal.EditorAssetLibrary.get_metadata_tag_values(asset)
|
||||
data["objectName"] = asset_data.asset_name
|
||||
data = cast_map_to_str_dict(data)
|
||||
|
||||
yield data
|
||||
|
||||
|
||||
def parse_container(container):
|
||||
"""To get data from container, AssetContainer must be loaded.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,245 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from abc import ABC
|
||||
import ast
|
||||
import collections
|
||||
import sys
|
||||
import six
|
||||
from abc import (
|
||||
ABC,
|
||||
ABCMeta,
|
||||
)
|
||||
|
||||
from openpype.pipeline import LoaderPlugin
|
||||
import unreal
|
||||
|
||||
from .pipeline import (
|
||||
create_publish_instance,
|
||||
imprint,
|
||||
ls_inst,
|
||||
UNREAL_VERSION
|
||||
)
|
||||
from openpype.lib import (
|
||||
BoolDef,
|
||||
UILabelDef
|
||||
)
|
||||
from openpype.pipeline import (
|
||||
Creator,
|
||||
LoaderPlugin,
|
||||
CreatorError,
|
||||
CreatedInstance
|
||||
)
|
||||
|
||||
|
||||
@six.add_metaclass(ABCMeta)
|
||||
class UnrealBaseCreator(Creator):
|
||||
"""Base class for Unreal creator plugins."""
|
||||
root = "/Game/OpenPype/PublishInstances"
|
||||
suffix = "_INS"
|
||||
|
||||
@staticmethod
|
||||
def cache_subsets(shared_data):
|
||||
"""Cache instances for Creators to shared data.
|
||||
|
||||
Create `unreal_cached_subsets` key when needed in shared data and
|
||||
fill it with all collected instances from the scene under its
|
||||
respective creator identifiers.
|
||||
|
||||
If legacy instances are detected in the scene, create
|
||||
`unreal_cached_legacy_subsets` there and fill it with
|
||||
all legacy subsets under family as a key.
|
||||
|
||||
Args:
|
||||
Dict[str, Any]: Shared data.
|
||||
|
||||
Return:
|
||||
Dict[str, Any]: Shared data dictionary.
|
||||
|
||||
"""
|
||||
if shared_data.get("unreal_cached_subsets") is None:
|
||||
unreal_cached_subsets = collections.defaultdict(list)
|
||||
unreal_cached_legacy_subsets = collections.defaultdict(list)
|
||||
for instance in ls_inst():
|
||||
creator_id = instance.get("creator_identifier")
|
||||
if creator_id:
|
||||
unreal_cached_subsets[creator_id].append(instance)
|
||||
else:
|
||||
family = instance.get("family")
|
||||
unreal_cached_legacy_subsets[family].append(instance)
|
||||
|
||||
shared_data["unreal_cached_subsets"] = unreal_cached_subsets
|
||||
shared_data["unreal_cached_legacy_subsets"] = (
|
||||
unreal_cached_legacy_subsets
|
||||
)
|
||||
return shared_data
|
||||
|
||||
def create(self, subset_name, instance_data, pre_create_data):
|
||||
try:
|
||||
instance_name = f"{subset_name}{self.suffix}"
|
||||
pub_instance = create_publish_instance(instance_name, self.root)
|
||||
|
||||
instance_data["subset"] = subset_name
|
||||
instance_data["instance_path"] = f"{self.root}/{instance_name}"
|
||||
|
||||
instance = CreatedInstance(
|
||||
self.family,
|
||||
subset_name,
|
||||
instance_data,
|
||||
self)
|
||||
self._add_instance_to_context(instance)
|
||||
|
||||
pub_instance.set_editor_property('add_external_assets', True)
|
||||
assets = pub_instance.get_editor_property('asset_data_external')
|
||||
|
||||
ar = unreal.AssetRegistryHelpers.get_asset_registry()
|
||||
|
||||
for member in pre_create_data.get("members", []):
|
||||
obj = ar.get_asset_by_object_path(member).get_asset()
|
||||
assets.add(obj)
|
||||
|
||||
imprint(f"{self.root}/{instance_name}", instance.data_to_store())
|
||||
|
||||
return instance
|
||||
|
||||
except Exception as er:
|
||||
six.reraise(
|
||||
CreatorError,
|
||||
CreatorError(f"Creator error: {er}"),
|
||||
sys.exc_info()[2])
|
||||
|
||||
def collect_instances(self):
|
||||
# cache instances if missing
|
||||
self.cache_subsets(self.collection_shared_data)
|
||||
for instance in self.collection_shared_data[
|
||||
"unreal_cached_subsets"].get(self.identifier, []):
|
||||
# Unreal saves metadata as string, so we need to convert it back
|
||||
instance['creator_attributes'] = ast.literal_eval(
|
||||
instance.get('creator_attributes', '{}'))
|
||||
instance['publish_attributes'] = ast.literal_eval(
|
||||
instance.get('publish_attributes', '{}'))
|
||||
created_instance = CreatedInstance.from_existing(instance, self)
|
||||
self._add_instance_to_context(created_instance)
|
||||
|
||||
def update_instances(self, update_list):
|
||||
for created_inst, changes in update_list:
|
||||
instance_node = created_inst.get("instance_path", "")
|
||||
|
||||
if not instance_node:
|
||||
unreal.log_warning(
|
||||
f"Instance node not found for {created_inst}")
|
||||
continue
|
||||
|
||||
new_values = {
|
||||
key: changes[key].new_value
|
||||
for key in changes.changed_keys
|
||||
}
|
||||
imprint(
|
||||
instance_node,
|
||||
new_values
|
||||
)
|
||||
|
||||
def remove_instances(self, instances):
|
||||
for instance in instances:
|
||||
instance_node = instance.data.get("instance_path", "")
|
||||
if instance_node:
|
||||
unreal.EditorAssetLibrary.delete_asset(instance_node)
|
||||
|
||||
self._remove_instance_from_context(instance)
|
||||
|
||||
|
||||
@six.add_metaclass(ABCMeta)
|
||||
class UnrealAssetCreator(UnrealBaseCreator):
|
||||
"""Base class for Unreal creator plugins based on assets."""
|
||||
|
||||
def create(self, subset_name, instance_data, pre_create_data):
|
||||
"""Create instance of the asset.
|
||||
|
||||
Args:
|
||||
subset_name (str): Name of the subset.
|
||||
instance_data (dict): Data for the instance.
|
||||
pre_create_data (dict): Data for the instance.
|
||||
|
||||
Returns:
|
||||
CreatedInstance: Created instance.
|
||||
"""
|
||||
try:
|
||||
# Check if instance data has members, filled by the plugin.
|
||||
# If not, use selection.
|
||||
if not pre_create_data.get("members"):
|
||||
pre_create_data["members"] = []
|
||||
|
||||
if pre_create_data.get("use_selection"):
|
||||
utilib = unreal.EditorUtilityLibrary
|
||||
sel_objects = utilib.get_selected_assets()
|
||||
pre_create_data["members"] = [
|
||||
a.get_path_name() for a in sel_objects]
|
||||
|
||||
super(UnrealAssetCreator, self).create(
|
||||
subset_name,
|
||||
instance_data,
|
||||
pre_create_data)
|
||||
|
||||
except Exception as er:
|
||||
six.reraise(
|
||||
CreatorError,
|
||||
CreatorError(f"Creator error: {er}"),
|
||||
sys.exc_info()[2])
|
||||
|
||||
def get_pre_create_attr_defs(self):
|
||||
return [
|
||||
BoolDef("use_selection", label="Use selection", default=True)
|
||||
]
|
||||
|
||||
|
||||
@six.add_metaclass(ABCMeta)
|
||||
class UnrealActorCreator(UnrealBaseCreator):
|
||||
"""Base class for Unreal creator plugins based on actors."""
|
||||
|
||||
def create(self, subset_name, instance_data, pre_create_data):
|
||||
"""Create instance of the asset.
|
||||
|
||||
Args:
|
||||
subset_name (str): Name of the subset.
|
||||
instance_data (dict): Data for the instance.
|
||||
pre_create_data (dict): Data for the instance.
|
||||
|
||||
Returns:
|
||||
CreatedInstance: Created instance.
|
||||
"""
|
||||
try:
|
||||
if UNREAL_VERSION.major == 5:
|
||||
world = unreal.UnrealEditorSubsystem().get_editor_world()
|
||||
else:
|
||||
world = unreal.EditorLevelLibrary.get_editor_world()
|
||||
|
||||
# Check if the level is saved
|
||||
if world.get_path_name().startswith("/Temp/"):
|
||||
raise CreatorError(
|
||||
"Level must be saved before creating instances.")
|
||||
|
||||
# Check if instance data has members, filled by the plugin.
|
||||
# If not, use selection.
|
||||
if not instance_data.get("members"):
|
||||
actor_subsystem = unreal.EditorActorSubsystem()
|
||||
sel_actors = actor_subsystem.get_selected_level_actors()
|
||||
selection = [a.get_path_name() for a in sel_actors]
|
||||
|
||||
instance_data["members"] = selection
|
||||
|
||||
instance_data["level"] = world.get_path_name()
|
||||
|
||||
super(UnrealActorCreator, self).create(
|
||||
subset_name,
|
||||
instance_data,
|
||||
pre_create_data)
|
||||
|
||||
except Exception as er:
|
||||
six.reraise(
|
||||
CreatorError,
|
||||
CreatorError(f"Creator error: {er}"),
|
||||
sys.exc_info()[2])
|
||||
|
||||
def get_pre_create_attr_defs(self):
|
||||
return [
|
||||
UILabelDef("Select actors to create instance from them.")
|
||||
]
|
||||
|
||||
|
||||
class Loader(LoaderPlugin, ABC):
|
||||
|
|
|
|||
|
|
@ -17,9 +17,8 @@ class ToolsBtnsWidget(QtWidgets.QWidget):
|
|||
def __init__(self, parent=None):
|
||||
super(ToolsBtnsWidget, self).__init__(parent)
|
||||
|
||||
create_btn = QtWidgets.QPushButton("Create...", self)
|
||||
load_btn = QtWidgets.QPushButton("Load...", self)
|
||||
publish_btn = QtWidgets.QPushButton("Publish...", self)
|
||||
publish_btn = QtWidgets.QPushButton("Publisher...", self)
|
||||
manage_btn = QtWidgets.QPushButton("Manage...", self)
|
||||
render_btn = QtWidgets.QPushButton("Render...", self)
|
||||
experimental_tools_btn = QtWidgets.QPushButton(
|
||||
|
|
@ -28,7 +27,6 @@ class ToolsBtnsWidget(QtWidgets.QWidget):
|
|||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.addWidget(create_btn, 0)
|
||||
layout.addWidget(load_btn, 0)
|
||||
layout.addWidget(publish_btn, 0)
|
||||
layout.addWidget(manage_btn, 0)
|
||||
|
|
@ -36,7 +34,6 @@ class ToolsBtnsWidget(QtWidgets.QWidget):
|
|||
layout.addWidget(experimental_tools_btn, 0)
|
||||
layout.addStretch(1)
|
||||
|
||||
create_btn.clicked.connect(self._on_create)
|
||||
load_btn.clicked.connect(self._on_load)
|
||||
publish_btn.clicked.connect(self._on_publish)
|
||||
manage_btn.clicked.connect(self._on_manage)
|
||||
|
|
@ -50,7 +47,7 @@ class ToolsBtnsWidget(QtWidgets.QWidget):
|
|||
self.tool_required.emit("loader")
|
||||
|
||||
def _on_publish(self):
|
||||
self.tool_required.emit("publish")
|
||||
self.tool_required.emit("publisher")
|
||||
|
||||
def _on_manage(self):
|
||||
self.tool_required.emit("sceneinventory")
|
||||
|
|
|
|||
|
|
@ -1,41 +1,38 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import unreal
|
||||
from unreal import EditorAssetLibrary as eal
|
||||
from unreal import EditorLevelLibrary as ell
|
||||
|
||||
from openpype.hosts.unreal.api.pipeline import instantiate
|
||||
from openpype.pipeline import LegacyCreator
|
||||
from openpype.pipeline import CreatorError
|
||||
from openpype.hosts.unreal.api.pipeline import UNREAL_VERSION
|
||||
from openpype.hosts.unreal.api.plugin import (
|
||||
UnrealAssetCreator,
|
||||
)
|
||||
|
||||
|
||||
class CreateCamera(LegacyCreator):
|
||||
"""Layout output for character rigs"""
|
||||
class CreateCamera(UnrealAssetCreator):
|
||||
"""Create Camera."""
|
||||
|
||||
name = "layoutMain"
|
||||
identifier = "io.openpype.creators.unreal.camera"
|
||||
label = "Camera"
|
||||
family = "camera"
|
||||
icon = "cubes"
|
||||
icon = "fa.camera"
|
||||
|
||||
root = "/Game/OpenPype/Instances"
|
||||
suffix = "_INS"
|
||||
def create(self, subset_name, instance_data, pre_create_data):
|
||||
if pre_create_data.get("use_selection"):
|
||||
sel_objects = unreal.EditorUtilityLibrary.get_selected_assets()
|
||||
selection = [a.get_path_name() for a in sel_objects]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CreateCamera, self).__init__(*args, **kwargs)
|
||||
if len(selection) != 1:
|
||||
raise CreatorError("Please select only one object.")
|
||||
|
||||
def process(self):
|
||||
data = self.data
|
||||
# Add the current level path to the metadata
|
||||
if UNREAL_VERSION.major == 5:
|
||||
world = unreal.UnrealEditorSubsystem().get_editor_world()
|
||||
else:
|
||||
world = unreal.EditorLevelLibrary.get_editor_world()
|
||||
|
||||
name = data["subset"]
|
||||
instance_data["level"] = world.get_path_name()
|
||||
|
||||
data["level"] = ell.get_editor_world().get_path_name()
|
||||
|
||||
if not eal.does_directory_exist(self.root):
|
||||
eal.make_directory(self.root)
|
||||
|
||||
factory = unreal.LevelSequenceFactoryNew()
|
||||
tools = unreal.AssetToolsHelpers().get_asset_tools()
|
||||
tools.create_asset(name, f"{self.root}/{name}", None, factory)
|
||||
|
||||
asset_name = f"{self.root}/{name}/{name}.{name}"
|
||||
|
||||
data["members"] = [asset_name]
|
||||
|
||||
instantiate(f"{self.root}", name, data, None, self.suffix)
|
||||
super(CreateCamera, self).create(
|
||||
subset_name,
|
||||
instance_data,
|
||||
pre_create_data)
|
||||
|
|
|
|||
|
|
@ -1,42 +1,13 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from unreal import EditorLevelLibrary
|
||||
|
||||
from openpype.pipeline import LegacyCreator
|
||||
from openpype.hosts.unreal.api.pipeline import instantiate
|
||||
from openpype.hosts.unreal.api.plugin import (
|
||||
UnrealActorCreator,
|
||||
)
|
||||
|
||||
|
||||
class CreateLayout(LegacyCreator):
|
||||
class CreateLayout(UnrealActorCreator):
|
||||
"""Layout output for character rigs."""
|
||||
|
||||
name = "layoutMain"
|
||||
identifier = "io.openpype.creators.unreal.layout"
|
||||
label = "Layout"
|
||||
family = "layout"
|
||||
icon = "cubes"
|
||||
|
||||
root = "/Game"
|
||||
suffix = "_INS"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CreateLayout, self).__init__(*args, **kwargs)
|
||||
|
||||
def process(self):
|
||||
data = self.data
|
||||
|
||||
name = 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]
|
||||
|
||||
data["level"] = EditorLevelLibrary.get_editor_world().get_path_name()
|
||||
|
||||
data["members"] = []
|
||||
|
||||
if (self.options or {}).get("useSelection"):
|
||||
# Set as members the selected actors
|
||||
for actor in EditorLevelLibrary.get_selected_level_actors():
|
||||
data["members"].append("{}.{}".format(
|
||||
actor.get_outer().get_name(), actor.get_name()))
|
||||
|
||||
instantiate(self.root, name, data, selection, self.suffix)
|
||||
|
|
|
|||
|
|
@ -1,56 +1,57 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Create look in Unreal."""
|
||||
import unreal # noqa
|
||||
from openpype.hosts.unreal.api import pipeline, plugin
|
||||
from openpype.pipeline import LegacyCreator
|
||||
import unreal
|
||||
|
||||
from openpype.pipeline import CreatorError
|
||||
from openpype.hosts.unreal.api.pipeline import (
|
||||
create_folder
|
||||
)
|
||||
from openpype.hosts.unreal.api.plugin import (
|
||||
UnrealAssetCreator
|
||||
)
|
||||
from openpype.lib import UILabelDef
|
||||
|
||||
|
||||
class CreateLook(LegacyCreator):
|
||||
class CreateLook(UnrealAssetCreator):
|
||||
"""Shader connections defining shape look."""
|
||||
|
||||
name = "unrealLook"
|
||||
label = "Unreal - Look"
|
||||
identifier = "io.openpype.creators.unreal.look"
|
||||
label = "Look"
|
||||
family = "look"
|
||||
icon = "paint-brush"
|
||||
|
||||
root = "/Game/Avalon/Assets"
|
||||
suffix = "_INS"
|
||||
def create(self, subset_name, instance_data, pre_create_data):
|
||||
# We need to set this to True for the parent class to work
|
||||
pre_create_data["use_selection"] = True
|
||||
sel_objects = unreal.EditorUtilityLibrary.get_selected_assets()
|
||||
selection = [a.get_path_name() for a in sel_objects]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CreateLook, self).__init__(*args, **kwargs)
|
||||
if len(selection) != 1:
|
||||
raise CreatorError("Please select only one asset.")
|
||||
|
||||
def process(self):
|
||||
name = self.data["subset"]
|
||||
selected_asset = selection[0]
|
||||
|
||||
selection = []
|
||||
if (self.options or {}).get("useSelection"):
|
||||
sel_objects = unreal.EditorUtilityLibrary.get_selected_assets()
|
||||
selection = [a.get_path_name() for a in sel_objects]
|
||||
look_directory = "/Game/OpenPype/Looks"
|
||||
|
||||
# Create the folder
|
||||
path = f"{self.root}/{self.data['asset']}"
|
||||
new_name = pipeline.create_folder(path, name)
|
||||
full_path = f"{path}/{new_name}"
|
||||
folder_name = create_folder(look_directory, subset_name)
|
||||
path = f"{look_directory}/{folder_name}"
|
||||
|
||||
instance_data["look"] = path
|
||||
|
||||
# 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')
|
||||
original_mesh = ar.get_asset_by_object_path(selected_asset).get_asset()
|
||||
materials = original_mesh.get_editor_property('static_materials')
|
||||
|
||||
self.data["members"] = []
|
||||
pre_create_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}"
|
||||
mat_name = material.get_editor_property('material_slot_name')
|
||||
object_path = f"{path}/{mat_name}.{mat_name}"
|
||||
unreal_object = unreal.EditorAssetLibrary.duplicate_loaded_asset(
|
||||
cube.get_asset(), object_path
|
||||
)
|
||||
|
|
@ -61,8 +62,16 @@ class CreateLook(LegacyCreator):
|
|||
unreal_object.add_material(
|
||||
material.get_editor_property('material_interface'))
|
||||
|
||||
self.data["members"].append(object_path)
|
||||
pre_create_data["members"].append(object_path)
|
||||
|
||||
unreal.EditorAssetLibrary.save_asset(object_path)
|
||||
|
||||
pipeline.imprint(f"{full_path}/{container_name}", self.data)
|
||||
super(CreateLook, self).create(
|
||||
subset_name,
|
||||
instance_data,
|
||||
pre_create_data)
|
||||
|
||||
def get_pre_create_attr_defs(self):
|
||||
return [
|
||||
UILabelDef("Select the asset from which to create the look.")
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,117 +1,138 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import unreal
|
||||
|
||||
from openpype.hosts.unreal.api import pipeline
|
||||
from openpype.pipeline import LegacyCreator
|
||||
from openpype.pipeline import CreatorError
|
||||
from openpype.hosts.unreal.api.pipeline import (
|
||||
get_subsequences
|
||||
)
|
||||
from openpype.hosts.unreal.api.plugin import (
|
||||
UnrealAssetCreator
|
||||
)
|
||||
from openpype.lib import UILabelDef
|
||||
|
||||
|
||||
class CreateRender(LegacyCreator):
|
||||
class CreateRender(UnrealAssetCreator):
|
||||
"""Create instance for sequence for rendering"""
|
||||
|
||||
name = "unrealRender"
|
||||
label = "Unreal - Render"
|
||||
identifier = "io.openpype.creators.unreal.render"
|
||||
label = "Render"
|
||||
family = "render"
|
||||
icon = "cube"
|
||||
asset_types = ["LevelSequence"]
|
||||
|
||||
root = "/Game/OpenPype/PublishInstances"
|
||||
suffix = "_INS"
|
||||
|
||||
def process(self):
|
||||
subset = self.data["subset"]
|
||||
icon = "eye"
|
||||
|
||||
def create(self, subset_name, instance_data, pre_create_data):
|
||||
ar = unreal.AssetRegistryHelpers.get_asset_registry()
|
||||
|
||||
# The asset name is the the third element of the path which contains
|
||||
# the map.
|
||||
# The index of the split path is 3 because the first element is an
|
||||
# empty string, as the path begins with "/Content".
|
||||
a = unreal.EditorUtilityLibrary.get_selected_assets()[0]
|
||||
asset_name = a.get_path_name().split("/")[3]
|
||||
sel_objects = unreal.EditorUtilityLibrary.get_selected_assets()
|
||||
selection = [
|
||||
a.get_path_name() for a in sel_objects
|
||||
if a.get_class().get_name() == "LevelSequence"]
|
||||
|
||||
# Get the master sequence and the master level.
|
||||
# There should be only one sequence and one level in the directory.
|
||||
filter = unreal.ARFilter(
|
||||
class_names=["LevelSequence"],
|
||||
package_paths=[f"/Game/OpenPype/{asset_name}"],
|
||||
recursive_paths=False)
|
||||
sequences = ar.get_assets(filter)
|
||||
ms = sequences[0].get_editor_property('object_path')
|
||||
filter = unreal.ARFilter(
|
||||
class_names=["World"],
|
||||
package_paths=[f"/Game/OpenPype/{asset_name}"],
|
||||
recursive_paths=False)
|
||||
levels = ar.get_assets(filter)
|
||||
ml = levels[0].get_editor_property('object_path')
|
||||
if not selection:
|
||||
raise CreatorError("Please select at least one Level Sequence.")
|
||||
|
||||
selection = []
|
||||
if (self.options or {}).get("useSelection"):
|
||||
sel_objects = unreal.EditorUtilityLibrary.get_selected_assets()
|
||||
selection = [
|
||||
a.get_path_name() for a in sel_objects
|
||||
if a.get_class().get_name() in self.asset_types]
|
||||
else:
|
||||
selection.append(self.data['sequence'])
|
||||
seq_data = None
|
||||
|
||||
unreal.log(f"selection: {selection}")
|
||||
for sel in selection:
|
||||
selected_asset = ar.get_asset_by_object_path(sel).get_asset()
|
||||
selected_asset_path = selected_asset.get_path_name()
|
||||
|
||||
path = f"{self.root}"
|
||||
unreal.EditorAssetLibrary.make_directory(path)
|
||||
# Check if the selected asset is a level sequence asset.
|
||||
if selected_asset.get_class().get_name() != "LevelSequence":
|
||||
unreal.log_warning(
|
||||
f"Skipping {selected_asset.get_name()}. It isn't a Level "
|
||||
"Sequence.")
|
||||
|
||||
ar = unreal.AssetRegistryHelpers.get_asset_registry()
|
||||
# The asset name is the third element of the path which
|
||||
# contains the map.
|
||||
# To take the asset name, we remove from the path the prefix
|
||||
# "/Game/OpenPype/" and then we split the path by "/".
|
||||
sel_path = selected_asset_path
|
||||
asset_name = sel_path.replace("/Game/OpenPype/", "").split("/")[0]
|
||||
|
||||
for a in selection:
|
||||
ms_obj = ar.get_asset_by_object_path(ms).get_asset()
|
||||
# Get the master sequence and the master level.
|
||||
# There should be only one sequence and one level in the directory.
|
||||
ar_filter = unreal.ARFilter(
|
||||
class_names=["LevelSequence"],
|
||||
package_paths=[f"/Game/OpenPype/{asset_name}"],
|
||||
recursive_paths=False)
|
||||
sequences = ar.get_assets(ar_filter)
|
||||
master_seq = sequences[0].get_asset().get_path_name()
|
||||
master_seq_obj = sequences[0].get_asset()
|
||||
ar_filter = unreal.ARFilter(
|
||||
class_names=["World"],
|
||||
package_paths=[f"/Game/OpenPype/{asset_name}"],
|
||||
recursive_paths=False)
|
||||
levels = ar.get_assets(ar_filter)
|
||||
master_lvl = levels[0].get_asset().get_path_name()
|
||||
|
||||
seq_data = None
|
||||
# If the selected asset is the master sequence, we get its data
|
||||
# and then we create the instance for the master sequence.
|
||||
# Otherwise, we cycle from the master sequence to find the selected
|
||||
# sequence and we get its data. This data will be used to create
|
||||
# the instance for the selected sequence. In particular,
|
||||
# we get the frame range of the selected sequence and its final
|
||||
# output path.
|
||||
master_seq_data = {
|
||||
"sequence": master_seq_obj,
|
||||
"output": f"{master_seq_obj.get_name()}",
|
||||
"frame_range": (
|
||||
master_seq_obj.get_playback_start(),
|
||||
master_seq_obj.get_playback_end())}
|
||||
|
||||
if a == ms:
|
||||
seq_data = {
|
||||
"sequence": ms_obj,
|
||||
"output": f"{ms_obj.get_name()}",
|
||||
"frame_range": (
|
||||
ms_obj.get_playback_start(), ms_obj.get_playback_end())
|
||||
}
|
||||
if selected_asset_path == master_seq:
|
||||
seq_data = master_seq_data
|
||||
else:
|
||||
seq_data_list = [{
|
||||
"sequence": ms_obj,
|
||||
"output": f"{ms_obj.get_name()}",
|
||||
"frame_range": (
|
||||
ms_obj.get_playback_start(), ms_obj.get_playback_end())
|
||||
}]
|
||||
seq_data_list = [master_seq_data]
|
||||
|
||||
for s in seq_data_list:
|
||||
subscenes = pipeline.get_subsequences(s.get('sequence'))
|
||||
for seq in seq_data_list:
|
||||
subscenes = get_subsequences(seq.get('sequence'))
|
||||
|
||||
for ss in subscenes:
|
||||
for sub_seq in subscenes:
|
||||
sub_seq_obj = sub_seq.get_sequence()
|
||||
curr_data = {
|
||||
"sequence": ss.get_sequence(),
|
||||
"output": (f"{s.get('output')}/"
|
||||
f"{ss.get_sequence().get_name()}"),
|
||||
"sequence": sub_seq_obj,
|
||||
"output": (f"{seq.get('output')}/"
|
||||
f"{sub_seq_obj.get_name()}"),
|
||||
"frame_range": (
|
||||
ss.get_start_frame(), ss.get_end_frame() - 1)
|
||||
}
|
||||
sub_seq.get_start_frame(),
|
||||
sub_seq.get_end_frame() - 1)}
|
||||
|
||||
if ss.get_sequence().get_path_name() == a:
|
||||
# If the selected asset is the current sub-sequence,
|
||||
# we get its data and we break the loop.
|
||||
# Otherwise, we add the current sub-sequence data to
|
||||
# the list of sequences to check.
|
||||
if sub_seq_obj.get_path_name() == selected_asset_path:
|
||||
seq_data = curr_data
|
||||
break
|
||||
|
||||
seq_data_list.append(curr_data)
|
||||
|
||||
# If we found the selected asset, we break the loop.
|
||||
if seq_data is not None:
|
||||
break
|
||||
|
||||
# If we didn't find the selected asset, we don't create the
|
||||
# instance.
|
||||
if not seq_data:
|
||||
unreal.log_warning(
|
||||
f"Skipping {selected_asset.get_name()}. It isn't a "
|
||||
"sub-sequence of the master sequence.")
|
||||
continue
|
||||
|
||||
d = self.data.copy()
|
||||
d["members"] = [a]
|
||||
d["sequence"] = a
|
||||
d["master_sequence"] = ms
|
||||
d["master_level"] = ml
|
||||
d["output"] = seq_data.get('output')
|
||||
d["frameStart"] = seq_data.get('frame_range')[0]
|
||||
d["frameEnd"] = seq_data.get('frame_range')[1]
|
||||
instance_data["members"] = [selected_asset_path]
|
||||
instance_data["sequence"] = selected_asset_path
|
||||
instance_data["master_sequence"] = master_seq
|
||||
instance_data["master_level"] = master_lvl
|
||||
instance_data["output"] = seq_data.get('output')
|
||||
instance_data["frameStart"] = seq_data.get('frame_range')[0]
|
||||
instance_data["frameEnd"] = seq_data.get('frame_range')[1]
|
||||
|
||||
container_name = f"{subset}{self.suffix}"
|
||||
pipeline.create_publish_instance(
|
||||
instance=container_name, path=path)
|
||||
pipeline.imprint(f"{path}/{container_name}", d)
|
||||
super(CreateRender, self).create(
|
||||
subset_name,
|
||||
instance_data,
|
||||
pre_create_data)
|
||||
|
||||
def get_pre_create_attr_defs(self):
|
||||
return [
|
||||
UILabelDef("Select the sequence to render.")
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,35 +1,13 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Create Static Meshes as FBX geometry."""
|
||||
import unreal # noqa
|
||||
from openpype.hosts.unreal.api.pipeline import (
|
||||
instantiate,
|
||||
from openpype.hosts.unreal.api.plugin import (
|
||||
UnrealAssetCreator,
|
||||
)
|
||||
from openpype.pipeline import LegacyCreator
|
||||
|
||||
|
||||
class CreateStaticMeshFBX(LegacyCreator):
|
||||
"""Static FBX geometry."""
|
||||
class CreateStaticMeshFBX(UnrealAssetCreator):
|
||||
"""Create Static Meshes as FBX geometry."""
|
||||
|
||||
name = "unrealStaticMeshMain"
|
||||
label = "Unreal - Static Mesh"
|
||||
identifier = "io.openpype.creators.unreal.staticmeshfbx"
|
||||
label = "Static Mesh (FBX)"
|
||||
family = "unrealStaticMesh"
|
||||
icon = "cube"
|
||||
asset_types = ["StaticMesh"]
|
||||
|
||||
root = "/Game"
|
||||
suffix = "_INS"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CreateStaticMeshFBX, 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]
|
||||
|
||||
unreal.log("selection: {}".format(selection))
|
||||
instantiate(self.root, name, self.data, selection, self.suffix)
|
||||
|
|
|
|||
|
|
@ -1,41 +1,31 @@
|
|||
"""Create UAsset."""
|
||||
# -*- coding: utf-8 -*-
|
||||
from pathlib import Path
|
||||
|
||||
import unreal
|
||||
|
||||
from openpype.hosts.unreal.api import pipeline
|
||||
from openpype.pipeline import LegacyCreator
|
||||
from openpype.pipeline import CreatorError
|
||||
from openpype.hosts.unreal.api.plugin import (
|
||||
UnrealAssetCreator,
|
||||
)
|
||||
|
||||
|
||||
class CreateUAsset(LegacyCreator):
|
||||
"""UAsset."""
|
||||
class CreateUAsset(UnrealAssetCreator):
|
||||
"""Create UAsset."""
|
||||
|
||||
name = "UAsset"
|
||||
identifier = "io.openpype.creators.unreal.uasset"
|
||||
label = "UAsset"
|
||||
family = "uasset"
|
||||
icon = "cube"
|
||||
|
||||
root = "/Game/OpenPype"
|
||||
suffix = "_INS"
|
||||
def create(self, subset_name, instance_data, pre_create_data):
|
||||
if pre_create_data.get("use_selection"):
|
||||
ar = unreal.AssetRegistryHelpers.get_asset_registry()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CreateUAsset, self).__init__(*args, **kwargs)
|
||||
|
||||
def process(self):
|
||||
ar = unreal.AssetRegistryHelpers.get_asset_registry()
|
||||
|
||||
subset = self.data["subset"]
|
||||
path = f"{self.root}/PublishInstances/"
|
||||
|
||||
unreal.EditorAssetLibrary.make_directory(path)
|
||||
|
||||
selection = []
|
||||
if (self.options or {}).get("useSelection"):
|
||||
sel_objects = unreal.EditorUtilityLibrary.get_selected_assets()
|
||||
selection = [a.get_path_name() for a in sel_objects]
|
||||
|
||||
if len(selection) != 1:
|
||||
raise RuntimeError("Please select only one object.")
|
||||
raise CreatorError("Please select only one object.")
|
||||
|
||||
obj = selection[0]
|
||||
|
||||
|
|
@ -43,19 +33,14 @@ class CreateUAsset(LegacyCreator):
|
|||
sys_path = unreal.SystemLibrary.get_system_path(asset)
|
||||
|
||||
if not sys_path:
|
||||
raise RuntimeError(
|
||||
raise CreatorError(
|
||||
f"{Path(obj).name} is not on the disk. Likely it needs to"
|
||||
"be saved first.")
|
||||
|
||||
if Path(sys_path).suffix != ".uasset":
|
||||
raise RuntimeError(f"{Path(sys_path).name} is not a UAsset.")
|
||||
raise CreatorError(f"{Path(sys_path).name} is not a UAsset.")
|
||||
|
||||
unreal.log("selection: {}".format(selection))
|
||||
container_name = f"{subset}{self.suffix}"
|
||||
pipeline.create_publish_instance(
|
||||
instance=container_name, path=path)
|
||||
|
||||
data = self.data.copy()
|
||||
data["members"] = selection
|
||||
|
||||
pipeline.imprint(f"{path}/{container_name}", data)
|
||||
super(CreateUAsset, self).create(
|
||||
subset_name,
|
||||
instance_data,
|
||||
pre_create_data)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
import unreal
|
||||
|
||||
import pyblish.api
|
||||
|
||||
|
||||
class CollectInstanceMembers(pyblish.api.InstancePlugin):
|
||||
"""
|
||||
Collect members of instance.
|
||||
|
||||
This collector will collect the assets for the families that support to
|
||||
have them included as External Data, and will add them to the instance
|
||||
as members.
|
||||
"""
|
||||
|
||||
order = pyblish.api.CollectorOrder + 0.1
|
||||
hosts = ["unreal"]
|
||||
families = ["camera", "look", "unrealStaticMesh", "uasset"]
|
||||
label = "Collect Instance Members"
|
||||
|
||||
def process(self, instance):
|
||||
"""Collect members of instance."""
|
||||
self.log.info("Collecting instance members")
|
||||
|
||||
ar = unreal.AssetRegistryHelpers.get_asset_registry()
|
||||
|
||||
inst_path = instance.data.get('instance_path')
|
||||
inst_name = instance.data.get('objectName')
|
||||
|
||||
pub_instance = ar.get_asset_by_object_path(
|
||||
f"{inst_path}.{inst_name}").get_asset()
|
||||
|
||||
if not pub_instance:
|
||||
self.log.error(f"{inst_path}.{inst_name}")
|
||||
raise RuntimeError(f"Instance {instance} not found.")
|
||||
|
||||
if not pub_instance.get_editor_property("add_external_assets"):
|
||||
# No external assets in the instance
|
||||
return
|
||||
|
||||
assets = pub_instance.get_editor_property('asset_data_external')
|
||||
|
||||
members = [asset.get_path_name() for asset in assets]
|
||||
|
||||
self.log.debug(f"Members: {members}")
|
||||
|
||||
instance.data["members"] = members
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Collect publishable instances in Unreal."""
|
||||
import ast
|
||||
import unreal # noqa
|
||||
import pyblish.api
|
||||
from openpype.hosts.unreal.api.pipeline import UNREAL_VERSION
|
||||
from openpype.pipeline.publish import KnownPublishError
|
||||
|
||||
|
||||
class CollectInstances(pyblish.api.ContextPlugin):
|
||||
"""Gather instances by OpenPypePublishInstance class
|
||||
|
||||
This collector finds all paths containing `OpenPypePublishInstance` class
|
||||
asset
|
||||
|
||||
Identifier:
|
||||
id (str): "pyblish.avalon.instance"
|
||||
|
||||
"""
|
||||
|
||||
label = "Collect Instances"
|
||||
order = pyblish.api.CollectorOrder - 0.1
|
||||
hosts = ["unreal"]
|
||||
|
||||
def process(self, context):
|
||||
|
||||
ar = unreal.AssetRegistryHelpers.get_asset_registry()
|
||||
class_name = [
|
||||
"/Script/OpenPype",
|
||||
"OpenPypePublishInstance"
|
||||
] if (
|
||||
UNREAL_VERSION.major == 5
|
||||
and UNREAL_VERSION.minor > 0
|
||||
) else "OpenPypePublishInstance" # noqa
|
||||
instance_containers = ar.get_assets_by_class(class_name, True)
|
||||
|
||||
for container_data in instance_containers:
|
||||
asset = container_data.get_asset()
|
||||
data = unreal.EditorAssetLibrary.get_metadata_tag_values(asset)
|
||||
data["objectName"] = container_data.asset_name
|
||||
# convert to strings
|
||||
data = {str(key): str(value) for (key, value) in data.items()}
|
||||
if not data.get("family"):
|
||||
raise KnownPublishError("instance has no family")
|
||||
|
||||
# content of container
|
||||
members = ast.literal_eval(data.get("members"))
|
||||
self.log.debug(members)
|
||||
self.log.debug(asset.get_path_name())
|
||||
# remove instance container
|
||||
self.log.info("Creating instance for {}".format(asset.get_name()))
|
||||
|
||||
instance = context.create_instance(asset.get_name())
|
||||
instance[:] = members
|
||||
|
||||
# Store the exact members of the object set
|
||||
instance.data["setMembers"] = members
|
||||
instance.data["families"] = [data.get("family")]
|
||||
instance.data["level"] = data.get("level")
|
||||
instance.data["parent"] = data.get("parent")
|
||||
|
||||
label = "{0} ({1})".format(asset.get_name()[:-4],
|
||||
data["asset"])
|
||||
|
||||
instance.data["label"] = label
|
||||
|
||||
instance.data.update(data)
|
||||
|
|
@ -3,10 +3,9 @@
|
|||
import os
|
||||
|
||||
import unreal
|
||||
from unreal import EditorAssetLibrary as eal
|
||||
from unreal import EditorLevelLibrary as ell
|
||||
|
||||
from openpype.pipeline import publish
|
||||
from openpype.hosts.unreal.api.pipeline import UNREAL_VERSION
|
||||
|
||||
|
||||
class ExtractCamera(publish.Extractor):
|
||||
|
|
@ -18,6 +17,8 @@ class ExtractCamera(publish.Extractor):
|
|||
optional = True
|
||||
|
||||
def process(self, instance):
|
||||
ar = unreal.AssetRegistryHelpers.get_asset_registry()
|
||||
|
||||
# Define extract output file path
|
||||
staging_dir = self.staging_dir(instance)
|
||||
fbx_filename = "{}.fbx".format(instance.name)
|
||||
|
|
@ -26,23 +27,54 @@ class ExtractCamera(publish.Extractor):
|
|||
self.log.info("Performing extraction..")
|
||||
|
||||
# Check if the loaded level is the same of the instance
|
||||
current_level = ell.get_editor_world().get_path_name()
|
||||
if UNREAL_VERSION.major == 5:
|
||||
world = unreal.UnrealEditorSubsystem().get_editor_world()
|
||||
else:
|
||||
world = unreal.EditorLevelLibrary.get_editor_world()
|
||||
current_level = world.get_path_name()
|
||||
assert current_level == instance.data.get("level"), \
|
||||
"Wrong level loaded"
|
||||
|
||||
for member in instance[:]:
|
||||
data = eal.find_asset_data(member)
|
||||
if data.asset_class == "LevelSequence":
|
||||
ar = unreal.AssetRegistryHelpers.get_asset_registry()
|
||||
sequence = ar.get_asset_by_object_path(member).get_asset()
|
||||
unreal.SequencerTools.export_fbx(
|
||||
ell.get_editor_world(),
|
||||
sequence,
|
||||
sequence.get_bindings(),
|
||||
unreal.FbxExportOption(),
|
||||
os.path.join(staging_dir, fbx_filename)
|
||||
)
|
||||
break
|
||||
for member in instance.data.get('members'):
|
||||
data = ar.get_asset_by_object_path(member)
|
||||
if UNREAL_VERSION.major == 5:
|
||||
is_level_sequence = (
|
||||
data.asset_class_path.asset_name == "LevelSequence")
|
||||
else:
|
||||
is_level_sequence = (data.asset_class == "LevelSequence")
|
||||
|
||||
if is_level_sequence:
|
||||
sequence = data.get_asset()
|
||||
if UNREAL_VERSION.major == 5 and UNREAL_VERSION.minor >= 1:
|
||||
params = unreal.SequencerExportFBXParams(
|
||||
world=world,
|
||||
root_sequence=sequence,
|
||||
sequence=sequence,
|
||||
bindings=sequence.get_bindings(),
|
||||
master_tracks=sequence.get_master_tracks(),
|
||||
fbx_file_name=os.path.join(staging_dir, fbx_filename)
|
||||
)
|
||||
unreal.SequencerTools.export_level_sequence_fbx(params)
|
||||
elif UNREAL_VERSION.major == 4 and UNREAL_VERSION.minor == 26:
|
||||
unreal.SequencerTools.export_fbx(
|
||||
world,
|
||||
sequence,
|
||||
sequence.get_bindings(),
|
||||
unreal.FbxExportOption(),
|
||||
os.path.join(staging_dir, fbx_filename)
|
||||
)
|
||||
else:
|
||||
# Unreal 5.0 or 4.27
|
||||
unreal.SequencerTools.export_level_sequence_fbx(
|
||||
world,
|
||||
sequence,
|
||||
sequence.get_bindings(),
|
||||
unreal.FbxExportOption(),
|
||||
os.path.join(staging_dir, fbx_filename)
|
||||
)
|
||||
|
||||
if not os.path.isfile(os.path.join(staging_dir, fbx_filename)):
|
||||
raise RuntimeError("Failed to extract camera")
|
||||
|
||||
if "representations" not in instance.data:
|
||||
instance.data["representations"] = []
|
||||
|
|
|
|||
|
|
@ -29,13 +29,13 @@ class ExtractLook(publish.Extractor):
|
|||
|
||||
for member in instance:
|
||||
asset = ar.get_asset_by_object_path(member)
|
||||
object = asset.get_asset()
|
||||
obj = asset.get_asset()
|
||||
|
||||
name = asset.get_editor_property('asset_name')
|
||||
|
||||
json_element = {'material': str(name)}
|
||||
|
||||
material_obj = object.get_editor_property('static_materials')[0]
|
||||
material_obj = obj.get_editor_property('static_materials')[0]
|
||||
material = material_obj.material_interface
|
||||
|
||||
base_color = mat_lib.get_material_property_input_node(
|
||||
|
|
|
|||
|
|
@ -22,7 +22,13 @@ class ExtractUAsset(publish.Extractor):
|
|||
staging_dir = self.staging_dir(instance)
|
||||
filename = "{}.uasset".format(instance.name)
|
||||
|
||||
obj = instance[0]
|
||||
members = instance.data.get("members", [])
|
||||
|
||||
if not members:
|
||||
raise RuntimeError("No members found in instance.")
|
||||
|
||||
# UAsset publishing supports only one member
|
||||
obj = members[0]
|
||||
|
||||
asset = ar.get_asset_by_object_path(obj).get_asset()
|
||||
sys_path = unreal.SystemLibrary.get_system_path(asset)
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ def import_filepath(filepath, module_name=None):
|
|||
|
||||
# Prepare module object where content of file will be parsed
|
||||
module = types.ModuleType(module_name)
|
||||
module.__file__ = filepath
|
||||
|
||||
if six.PY3:
|
||||
# Use loader so module has full specs
|
||||
|
|
@ -41,7 +42,6 @@ def import_filepath(filepath, module_name=None):
|
|||
# Execute content and store it to module object
|
||||
six.exec_(_stream.read(), module.__dict__)
|
||||
|
||||
module.__file__ = filepath
|
||||
return module
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -266,7 +266,8 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin):
|
|||
"PYBLISHPLUGINPATH",
|
||||
"NUKE_PATH",
|
||||
"TOOL_ENV",
|
||||
"FOUNDRY_LICENSE"
|
||||
"FOUNDRY_LICENSE",
|
||||
"OPENPYPE_SG_USER",
|
||||
]
|
||||
|
||||
# Add OpenPype version if we are running from build.
|
||||
|
|
|
|||
|
|
@ -139,7 +139,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
|
|||
"FTRACK_API_KEY",
|
||||
"FTRACK_SERVER",
|
||||
"AVALON_APP_NAME",
|
||||
"OPENPYPE_USERNAME"
|
||||
"OPENPYPE_USERNAME",
|
||||
"OPENPYPE_SG_USER",
|
||||
]
|
||||
|
||||
# Add OpenPype version if we are running from build.
|
||||
|
|
@ -194,7 +195,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
|
|||
metadata_path = os.path.join(output_dir, metadata_filename)
|
||||
|
||||
# Convert output dir to `{root}/rest/of/path/...` with Anatomy
|
||||
success, roothless_mtdt_p = self.anatomy.find_root_template_from_path(
|
||||
success, rootless_mtdt_p = self.anatomy.find_root_template_from_path(
|
||||
metadata_path)
|
||||
if not success:
|
||||
# `rootless_path` is not set to `output_dir` if none of roots match
|
||||
|
|
@ -202,9 +203,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
|
|||
"Could not find root path for remapping \"{}\"."
|
||||
" This may cause issues on farm."
|
||||
).format(output_dir))
|
||||
roothless_mtdt_p = metadata_path
|
||||
rootless_mtdt_p = metadata_path
|
||||
|
||||
return metadata_path, roothless_mtdt_p
|
||||
return metadata_path, rootless_mtdt_p
|
||||
|
||||
def _submit_deadline_post_job(self, instance, job, instances):
|
||||
"""Submit publish job to Deadline.
|
||||
|
|
@ -237,7 +238,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
|
|||
|
||||
# Transfer the environment from the original job to this dependent
|
||||
# job so they use the same environment
|
||||
metadata_path, roothless_metadata_path = \
|
||||
metadata_path, rootless_metadata_path = \
|
||||
self._create_metadata_path(instance)
|
||||
|
||||
environment = {
|
||||
|
|
@ -274,7 +275,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
|
|||
args = [
|
||||
"--headless",
|
||||
'publish',
|
||||
roothless_metadata_path,
|
||||
rootless_metadata_path,
|
||||
"--targets", "deadline",
|
||||
"--targets", "farm"
|
||||
]
|
||||
|
|
@ -411,7 +412,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
|
|||
assert fn is not None, "padding string wasn't found"
|
||||
# list of tuples (source, destination)
|
||||
staging = representation.get("stagingDir")
|
||||
staging = self.anatomy.fill_roots(staging)
|
||||
staging = self.anatomy.fill_root(staging)
|
||||
resource_files.append(
|
||||
(frame,
|
||||
os.path.join(staging,
|
||||
|
|
@ -588,7 +589,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
|
|||
host_name = os.environ.get("AVALON_APP", "")
|
||||
collections, remainders = clique.assemble(exp_files)
|
||||
|
||||
# create representation for every collected sequento ce
|
||||
# create representation for every collected sequence
|
||||
for collection in collections:
|
||||
ext = collection.tail.lstrip(".")
|
||||
preview = False
|
||||
|
|
@ -656,7 +657,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
|
|||
|
||||
self._solve_families(instance, preview)
|
||||
|
||||
# add reminders as representations
|
||||
# add remainders as representations
|
||||
for remainder in remainders:
|
||||
ext = remainder.split(".")[-1]
|
||||
|
||||
|
|
@ -676,7 +677,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
|
|||
"name": ext,
|
||||
"ext": ext,
|
||||
"files": os.path.basename(remainder),
|
||||
"stagingDir": os.path.dirname(remainder),
|
||||
"stagingDir": staging,
|
||||
}
|
||||
|
||||
preview = match_aov_pattern(
|
||||
|
|
@ -1060,7 +1061,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
|
|||
}
|
||||
publish_job.update({"ftrack": ftrack})
|
||||
|
||||
metadata_path, roothless_metadata_path = self._create_metadata_path(
|
||||
metadata_path, rootless_metadata_path = self._create_metadata_path(
|
||||
instance)
|
||||
|
||||
self.log.info("Writing json file: {}".format(metadata_path))
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ class ValidateExpectedFiles(pyblish.api.InstancePlugin):
|
|||
|
||||
for job_id in render_job_ids:
|
||||
job_info = self._get_job_info(job_id)
|
||||
frame_list = job_info["Props"]["Frames"]
|
||||
frame_list = job_info["Props"].get("Frames")
|
||||
if frame_list:
|
||||
all_frame_lists.extend(frame_list.split(','))
|
||||
|
||||
|
|
|
|||
|
|
@ -83,6 +83,11 @@ class CollectShotgridSession(pyblish.api.ContextPlugin):
|
|||
"login to shotgrid withing openpype Tray"
|
||||
)
|
||||
|
||||
# Set OPENPYPE_SG_USER with login so other deadline tasks can make
|
||||
# use of it
|
||||
self.log.info("Setting OPENPYPE_SG_USER to '%s'.", login)
|
||||
os.environ["OPENPYPE_SG_USER"] = login
|
||||
|
||||
session = shotgun_api3.Shotgun(
|
||||
base_url=shotgrid_url,
|
||||
script_name=shotgrid_script_name,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from openpype.pipeline.publish import get_publish_repre_path
|
|||
class IntegrateShotgridPublish(pyblish.api.InstancePlugin):
|
||||
"""
|
||||
Create published Files from representations and add it to version. If
|
||||
representation is tagged add shotgrid review, it will add it in
|
||||
representation is tagged as shotgrid review, it will add it in
|
||||
path to movie for a movie file or path to frame for an image sequence.
|
||||
"""
|
||||
|
||||
|
|
@ -27,11 +27,11 @@ class IntegrateShotgridPublish(pyblish.api.InstancePlugin):
|
|||
local_path = get_publish_repre_path(
|
||||
instance, representation, False
|
||||
)
|
||||
code = os.path.basename(local_path)
|
||||
|
||||
if representation.get("tags", []):
|
||||
continue
|
||||
|
||||
code = os.path.basename(local_path)
|
||||
published_file = self._find_existing_publish(
|
||||
code, context, shotgrid_version
|
||||
)
|
||||
|
|
|
|||
|
|
@ -37,9 +37,9 @@ class IntegrateShotgridVersion(pyblish.api.InstancePlugin):
|
|||
self.log.info("Use existing Shotgrid version: {}".format(version))
|
||||
|
||||
data_to_update = {}
|
||||
status = context.data.get("intent", {}).get("value")
|
||||
if status:
|
||||
data_to_update["sg_status_list"] = status
|
||||
intent = context.data.get("intent")
|
||||
if intent:
|
||||
data_to_update["sg_status_list"] = intent["value"]
|
||||
|
||||
for representation in instance.data.get("representations", []):
|
||||
local_path = get_publish_repre_path(
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import pyblish.api
|
|||
|
||||
from openpype.lib import (
|
||||
Logger,
|
||||
import_filepath,
|
||||
filter_profiles
|
||||
)
|
||||
from openpype.settings import (
|
||||
|
|
@ -301,12 +302,8 @@ def publish_plugins_discover(paths=None):
|
|||
if not mod_ext == ".py":
|
||||
continue
|
||||
|
||||
module = types.ModuleType(mod_name)
|
||||
module.__file__ = abspath
|
||||
|
||||
try:
|
||||
with open(abspath, "rb") as f:
|
||||
six.exec_(f.read(), module.__dict__)
|
||||
module = import_filepath(abspath, mod_name)
|
||||
|
||||
# Store reference to original module, to avoid
|
||||
# garbage collection from collecting it's global
|
||||
|
|
@ -683,6 +680,12 @@ def get_publish_repre_path(instance, repre, only_published=False):
|
|||
staging_dir = repre.get("stagingDir")
|
||||
if not staging_dir:
|
||||
staging_dir = get_instance_staging_dir(instance)
|
||||
|
||||
# Expand the staging dir path in case it's been stored with the root
|
||||
# template syntax
|
||||
anatomy = instance.context.data["anatomy"]
|
||||
staging_dir = anatomy.fill_root(staging_dir)
|
||||
|
||||
src_path = os.path.normpath(os.path.join(staging_dir, filename))
|
||||
if os.path.exists(src_path):
|
||||
return src_path
|
||||
|
|
|
|||
|
|
@ -23,4 +23,4 @@
|
|||
],
|
||||
"tools_env": [],
|
||||
"active": true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -255,4 +255,4 @@
|
|||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,4 +4,4 @@
|
|||
"darwin": "/Volumes/path",
|
||||
"linux": "/mnt/share/projects"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,4 +41,4 @@
|
|||
"Compositing": {
|
||||
"short_name": "comp"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,4 +66,4 @@
|
|||
"source": "source"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,4 +33,4 @@
|
|||
"create_first_version": false,
|
||||
"custom_templates": []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,4 +82,4 @@
|
|||
"active": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,4 +16,4 @@
|
|||
"anatomy_template_key_metadata": "render"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -163,4 +163,4 @@
|
|||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -496,4 +496,4 @@
|
|||
"farm_status_profiles": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,4 +17,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -607,4 +607,4 @@
|
|||
"linux": []
|
||||
},
|
||||
"project_environments": {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,4 +50,4 @@
|
|||
"skip_timelines_check": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,4 +97,4 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,4 +76,4 @@
|
|||
"active": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,4 +10,4 @@
|
|||
"note_status_shortname": "wfa"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,4 +5,4 @@
|
|||
"image_format": "exr",
|
||||
"multipass": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -533,4 +533,4 @@
|
|||
"profiles": []
|
||||
},
|
||||
"filters": {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,4 +67,4 @@
|
|||
"create_first_version": false,
|
||||
"custom_templates": []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,4 +27,4 @@
|
|||
"handleEnd": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,4 +4,4 @@
|
|||
"review": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,4 +19,4 @@
|
|||
"step": "step"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,4 +17,4 @@
|
|||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -304,4 +304,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -321,4 +321,4 @@
|
|||
"active": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -109,4 +109,4 @@
|
|||
"custom_templates": []
|
||||
},
|
||||
"filters": {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,4 +14,4 @@
|
|||
"project_setup": {
|
||||
"dev_mode": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -141,4 +141,4 @@
|
|||
"layer_name_regex": "(?P<layer>L[0-9]{3}_\\w+)_(?P<pass>.+)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1302,7 +1302,9 @@
|
|||
"variant_label": "Current",
|
||||
"use_python_2": false,
|
||||
"executables": {
|
||||
"windows": ["C:/Program Files/CelAction/CelAction2D Studio/CelAction2D.exe"],
|
||||
"windows": [
|
||||
"C:/Program Files/CelAction/CelAction2D Studio/CelAction2D.exe"
|
||||
],
|
||||
"darwin": [],
|
||||
"linux": []
|
||||
},
|
||||
|
|
@ -1365,4 +1367,4 @@
|
|||
}
|
||||
},
|
||||
"additional_apps": {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,4 +18,4 @@
|
|||
"production_version": "",
|
||||
"staging_version": "",
|
||||
"version_check_interval": 5
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -211,4 +211,4 @@
|
|||
"linux": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,4 +87,4 @@
|
|||
"renderman": "Pixar Renderman"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -440,8 +440,9 @@ class RootEntity(BaseItemEntity):
|
|||
os.makedirs(dirpath)
|
||||
|
||||
self.log.debug("Saving data to: {}\n{}".format(subpath, value))
|
||||
data = json.dumps(value, indent=4) + "\n"
|
||||
with open(output_path, "w") as file_stream:
|
||||
json.dump(value, file_stream, indent=4)
|
||||
file_stream.write(data)
|
||||
|
||||
dynamic_values_item = self.collect_dynamic_schema_entities()
|
||||
dynamic_values_item.save_values()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue