Merge branch 'develop' into fusion-new-publisher

This commit is contained in:
Ember Light 2023-03-04 10:06:26 +01:00 committed by GitHub
commit 6e134961ec
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
175 changed files with 2997 additions and 1370 deletions

View file

@ -702,6 +702,37 @@ class ClipLoader(LoaderPlugin):
_mapping = None
def apply_settings(cls, project_settings, system_settings):
plugin_type_settings = (
project_settings
.get("flame", {})
.get("load", {})
)
if not plugin_type_settings:
return
plugin_name = cls.__name__
plugin_settings = None
# Look for plugin settings in host specific settings
if plugin_name in plugin_type_settings:
plugin_settings = plugin_type_settings[plugin_name]
if not plugin_settings:
return
print(">>> We have preset for {}".format(plugin_name))
for option, value in plugin_settings.items():
if option == "enabled" and value is False:
print(" - is disabled by preset")
elif option == "representations":
continue
else:
print(" - setting `{}`: `{}`".format(option, value))
setattr(cls, option, value)
def get_colorspace(self, context):
"""Get colorspace name

View file

@ -4,6 +4,10 @@ import flame
from pprint import pformat
import openpype.hosts.flame.api as opfapi
from openpype.lib import StringTemplate
from openpype.lib.transcoding import (
VIDEO_EXTENSIONS,
IMAGE_EXTENSIONS
)
class LoadClip(opfapi.ClipLoader):
@ -14,7 +18,10 @@ class LoadClip(opfapi.ClipLoader):
"""
families = ["render2d", "source", "plate", "render", "review"]
representations = ["exr", "dpx", "jpg", "jpeg", "png", "h264"]
representations = ["*"]
extensions = set(
ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS)
)
label = "Load as clip"
order = -10

View file

@ -4,7 +4,10 @@ import flame
from pprint import pformat
import openpype.hosts.flame.api as opfapi
from openpype.lib import StringTemplate
from openpype.lib.transcoding import (
VIDEO_EXTENSIONS,
IMAGE_EXTENSIONS
)
class LoadClipBatch(opfapi.ClipLoader):
"""Load a subset to timeline as clip
@ -14,7 +17,10 @@ class LoadClipBatch(opfapi.ClipLoader):
"""
families = ["render2d", "source", "plate", "render", "review"]
representations = ["exr", "dpx", "jpg", "jpeg", "png", "h264"]
representations = ["*"]
extensions = set(
ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS)
)
label = "Load as clip to current batch"
order = -10

View file

@ -15,6 +15,7 @@ class FusionSetFrameRangeLoader(load.LoaderPlugin):
"pointcache",
"render"]
representations = ["*"]
extensions = {"*"}
label = "Set frame range"
order = 11

View file

@ -13,7 +13,8 @@ class FusionLoadAlembicMesh(load.LoaderPlugin):
"""Load Alembic mesh into Fusion"""
families = ["pointcache", "model"]
representations = ["abc"]
representations = ["*"]
extensions = {"abc"}
label = "Load alembic mesh"
order = -10

View file

@ -14,7 +14,8 @@ class FusionLoadFBXMesh(load.LoaderPlugin):
"""Load FBX mesh into Fusion"""
families = ["*"]
representations = ["fbx"]
representations = ["*"]
extensions = {"fbx"}
label = "Load FBX mesh"
order = -10

View file

@ -12,6 +12,10 @@ from openpype.hosts.fusion.api import (
get_current_comp,
comp_lock_and_undo_chunk
)
from openpype.lib.transcoding import (
IMAGE_EXTENSIONS,
VIDEO_EXTENSIONS
)
comp = get_current_comp()
@ -129,6 +133,9 @@ class FusionLoadSequence(load.LoaderPlugin):
families = ["imagesequence", "review", "render", "plate"]
representations = ["*"]
extensions = set(
ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS)
)
label = "Load sequence"
order = -10

View file

@ -20,8 +20,9 @@ class ImageSequenceLoader(load.LoaderPlugin):
Stores the imported asset in a container named after the asset.
"""
families = ["shot", "render", "image", "plate", "reference"]
representations = ["jpeg", "png", "jpg"]
families = ["shot", "render", "image", "plate", "reference", "review"]
representations = ["*"]
extensions = {"jpeg", "png", "jpg"}
def load(self, context, name=None, namespace=None, data=None):
"""Plugin entry point.

View file

@ -6,6 +6,10 @@ from openpype.pipeline import (
legacy_io,
get_representation_path,
)
from openpype.lib.transcoding import (
VIDEO_EXTENSIONS,
IMAGE_EXTENSIONS
)
import openpype.hosts.hiero.api as phiero
@ -17,7 +21,10 @@ class LoadClip(phiero.SequenceLoader):
"""
families = ["render2d", "source", "plate", "render", "review"]
representations = ["exr", "dpx", "jpg", "jpeg", "png", "h264"]
representations = ["*"]
extensions = set(
ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS)
)
label = "Load as clip"
order = -10
@ -34,6 +41,38 @@ class LoadClip(phiero.SequenceLoader):
clip_name_template = "{asset}_{subset}_{representation}"
def apply_settings(cls, project_settings, system_settings):
plugin_type_settings = (
project_settings
.get("hiero", {})
.get("load", {})
)
if not plugin_type_settings:
return
plugin_name = cls.__name__
plugin_settings = None
# Look for plugin settings in host specific settings
if plugin_name in plugin_type_settings:
plugin_settings = plugin_type_settings[plugin_name]
if not plugin_settings:
return
print(">>> We have preset for {}".format(plugin_name))
for option, value in plugin_settings.items():
if option == "enabled" and value is False:
print(" - is disabled by preset")
elif option == "representations":
continue
else:
print(" - setting `{}`: `{}`".format(option, value))
setattr(cls, option, value)
def load(self, context, name, namespace, options):
# add clip name template to options
options.update({

View file

@ -19,8 +19,9 @@ from openpype.lib import Logger
class LoadEffects(load.LoaderPlugin):
"""Loading colorspace soft effect exported from nukestudio"""
representations = ["effectJson"]
families = ["effect"]
representations = ["*"]
extension = {"json"}
label = "Load Effects"
order = 0

View file

@ -120,13 +120,9 @@ class CollectClipEffects(pyblish.api.InstancePlugin):
track = sitem.parentTrack().name()
# node serialization
node = sitem.node()
node_serialized = self.node_serialisation(node)
node_serialized = self.node_serialization(node)
node_name = sitem.name()
if "_" in node_name:
node_class = re.sub(r"(?:_)[_0-9]+", "", node_name) # more numbers
else:
node_class = re.sub(r"\d+", "", node_name) # one number
node_class = node.Class()
# collect timelineIn/Out
effect_t_in = int(sitem.timelineIn())
@ -148,7 +144,7 @@ class CollectClipEffects(pyblish.api.InstancePlugin):
"node": node_serialized
}}
def node_serialisation(self, node):
def node_serialization(self, node):
node_serialized = {}
# adding ignoring knob keys

View file

@ -4,7 +4,6 @@ import os
import sys
import platform
import uuid
import math
import re
import json
@ -15,7 +14,7 @@ from math import ceil
from six import string_types
from maya import cmds, mel
import maya.api.OpenMaya as om
from maya.api import OpenMaya
from openpype.client import (
get_project,
@ -403,9 +402,9 @@ def lsattrs(attrs):
"""
dep_fn = om.MFnDependencyNode()
dag_fn = om.MFnDagNode()
selection_list = om.MSelectionList()
dep_fn = OpenMaya.MFnDependencyNode()
dag_fn = OpenMaya.MFnDagNode()
selection_list = OpenMaya.MSelectionList()
first_attr = next(iter(attrs))
@ -419,7 +418,7 @@ def lsattrs(attrs):
matches = set()
for i in range(selection_list.length()):
node = selection_list.getDependNode(i)
if node.hasFn(om.MFn.kDagNode):
if node.hasFn(OpenMaya.MFn.kDagNode):
fn_node = dag_fn.setObject(node)
full_path_names = [path.fullPathName()
for path in fn_node.getAllPaths()]
@ -868,11 +867,11 @@ def maintained_selection_api():
Warning: This is *not* added to the undo stack.
"""
original = om.MGlobal.getActiveSelectionList()
original = OpenMaya.MGlobal.getActiveSelectionList()
try:
yield
finally:
om.MGlobal.setActiveSelectionList(original)
OpenMaya.MGlobal.setActiveSelectionList(original)
@contextlib.contextmanager
@ -1282,11 +1281,11 @@ def get_id(node):
if node is None:
return
sel = om.MSelectionList()
sel = OpenMaya.MSelectionList()
sel.add(node)
api_node = sel.getDependNode(0)
fn = om.MFnDependencyNode(api_node)
fn = OpenMaya.MFnDependencyNode(api_node)
if not fn.hasAttribute("cbId"):
return
@ -2064,13 +2063,8 @@ def set_scene_resolution(width, height, pixelAspect):
cmds.setAttr("%s.pixelAspect" % control_node, pixelAspect)
def reset_frame_range():
"""Set frame range to current asset"""
fps = convert_to_maya_fps(
float(legacy_io.Session.get("AVALON_FPS", 25))
)
set_scene_fps(fps)
def get_frame_range():
"""Get the current assets frame range and handles."""
# Set frame start/end
project_name = legacy_io.active_project()
@ -2097,8 +2091,26 @@ def reset_frame_range():
if handle_end is None:
handle_end = handles
frame_start -= int(handle_start)
frame_end += int(handle_end)
return {
"frameStart": frame_start,
"frameEnd": frame_end,
"handleStart": handle_start,
"handleEnd": handle_end
}
def reset_frame_range():
"""Set frame range to current asset"""
fps = convert_to_maya_fps(
float(legacy_io.Session.get("AVALON_FPS", 25))
)
set_scene_fps(fps)
frame_range = get_frame_range()
frame_start = frame_range["frameStart"] - int(frame_range["handleStart"])
frame_end = frame_range["frameEnd"] + int(frame_range["handleEnd"])
cmds.playbackOptions(minTime=frame_start)
cmds.playbackOptions(maxTime=frame_end)
@ -3341,15 +3353,15 @@ def iter_visible_nodes_in_range(nodes, start, end):
@memodict
def get_visibility_mplug(node):
"""Return api 2.0 MPlug with cached memoize decorator"""
sel = om.MSelectionList()
sel = OpenMaya.MSelectionList()
sel.add(node)
dag = sel.getDagPath(0)
return om.MFnDagNode(dag).findPlug("visibility", True)
return OpenMaya.MFnDagNode(dag).findPlug("visibility", True)
@contextlib.contextmanager
def dgcontext(mtime):
"""MDGContext context manager"""
context = om.MDGContext(mtime)
context = OpenMaya.MDGContext(mtime)
try:
previous = context.makeCurrent()
yield context
@ -3358,9 +3370,9 @@ def iter_visible_nodes_in_range(nodes, start, end):
# We skip the first frame as we already used that frame to check for
# overall visibilities. And end+1 to include the end frame.
scene_units = om.MTime.uiUnit()
scene_units = OpenMaya.MTime.uiUnit()
for frame in range(start + 1, end + 1):
mtime = om.MTime(frame, unit=scene_units)
mtime = OpenMaya.MTime(frame, unit=scene_units)
# Build little cache so we don't query the same MPlug's value
# again if it was checked on this frame and also is a dependency
@ -3509,3 +3521,87 @@ def write_xgen_file(data, filepath):
with open(filepath, "w") as f:
f.writelines(lines)
def get_color_management_preferences():
"""Get and resolve OCIO preferences."""
data = {
# Is color management enabled.
"enabled": cmds.colorManagementPrefs(
query=True, cmEnabled=True
),
"rendering_space": cmds.colorManagementPrefs(
query=True, renderingSpaceName=True
),
"output_transform": cmds.colorManagementPrefs(
query=True, outputTransformName=True
),
"output_transform_enabled": cmds.colorManagementPrefs(
query=True, outputTransformEnabled=True
),
"view_transform": cmds.colorManagementPrefs(
query=True, viewTransformName=True
)
}
# Split view and display from view_transform. view_transform comes in
# format of "{view} ({display})".
regex = re.compile(r"^(?P<view>.+) \((?P<display>.+)\)$")
match = regex.match(data["view_transform"])
data.update({
"display": match.group("display"),
"view": match.group("view")
})
# Get config absolute path.
path = cmds.colorManagementPrefs(
query=True, configFilePath=True
)
# The OCIO config supports a custom <MAYA_RESOURCES> token.
maya_resources_token = "<MAYA_RESOURCES>"
maya_resources_path = OpenMaya.MGlobal.getAbsolutePathToResources()
path = path.replace(maya_resources_token, maya_resources_path)
data["config"] = path
return data
def get_color_management_output_transform():
preferences = get_color_management_preferences()
colorspace = preferences["rendering_space"]
if preferences["output_transform_enabled"]:
colorspace = preferences["output_transform"]
return colorspace
def len_flattened(components):
"""Return the length of the list as if it was flattened.
Maya will return consecutive components as a single entry
when requesting with `maya.cmds.ls` without the `flatten`
flag. Though enabling `flatten` on a large list (e.g. millions)
will result in a slow result. This command will return the amount
of entries in a non-flattened list by parsing the result with
regex.
Args:
components (list): The non-flattened components.
Returns:
int: The amount of entries.
"""
assert isinstance(components, (list, tuple))
n = 0
pattern = re.compile(r"\[(\d+):(\d+)\]")
for c in components:
match = pattern.search(c)
if match:
start, end = match.groups()
n += int(end) - int(start) + 1
else:
n += 1
return n

View file

@ -46,6 +46,7 @@ import attr
from . import lib
from . import lib_rendersetup
from openpype.pipeline.colorspace import get_ocio_config_views
from maya import cmds, mel
@ -127,6 +128,7 @@ class RenderProduct(object):
"""
productName = attr.ib()
ext = attr.ib() # extension
colorspace = attr.ib() # colorspace
aov = attr.ib(default=None) # source aov
driver = attr.ib(default=None) # source driver
multipart = attr.ib(default=False) # multichannel file
@ -562,6 +564,9 @@ class RenderProductsArnold(ARenderProducts):
]
for ai_driver in ai_drivers:
colorspace = self._get_colorspace(
ai_driver + ".colorManagement"
)
# todo: check aiAOVDriver.prefix as it could have
# a custom path prefix set for this driver
@ -599,12 +604,15 @@ class RenderProductsArnold(ARenderProducts):
global_aov = self._get_attr(aov, "globalAov")
if global_aov:
for camera in cameras:
product = RenderProduct(productName=name,
ext=ext,
aov=aov_name,
driver=ai_driver,
multipart=self.multipart,
camera=camera)
product = RenderProduct(
productName=name,
ext=ext,
aov=aov_name,
driver=ai_driver,
multipart=self.multipart,
camera=camera,
colorspace=colorspace
)
products.append(product)
all_light_groups = self._get_attr(aov, "lightGroups")
@ -612,13 +620,16 @@ class RenderProductsArnold(ARenderProducts):
# All light groups is enabled. A single multipart
# Render Product
for camera in cameras:
product = RenderProduct(productName=name + "_lgroups",
ext=ext,
aov=aov_name,
driver=ai_driver,
# Always multichannel output
multipart=True,
camera=camera)
product = RenderProduct(
productName=name + "_lgroups",
ext=ext,
aov=aov_name,
driver=ai_driver,
# Always multichannel output
multipart=True,
camera=camera,
colorspace=colorspace
)
products.append(product)
else:
value = self._get_attr(aov, "lightGroupsList")
@ -634,12 +645,36 @@ class RenderProductsArnold(ARenderProducts):
aov=aov_name,
driver=ai_driver,
ext=ext,
camera=camera
camera=camera,
colorspace=colorspace
)
products.append(product)
return products
def _get_colorspace(self, attribute):
"""Resolve colorspace from Arnold settings."""
def _view_transform():
preferences = lib.get_color_management_preferences()
views_data = get_ocio_config_views(preferences["config"])
view_data = views_data[
"{}/{}".format(preferences["display"], preferences["view"])
]
return view_data["colorspace"]
def _raw():
preferences = lib.get_color_management_preferences()
return preferences["rendering_space"]
resolved_values = {
"Raw": _raw,
"Use View Transform": _view_transform,
# Default. Same as Maya Preferences.
"Use Output Transform": lib.get_color_management_output_transform
}
return resolved_values[self._get_attr(attribute)]()
def get_render_products(self):
"""Get all AOVs.
@ -668,11 +703,19 @@ class RenderProductsArnold(ARenderProducts):
]
default_ext = self._get_attr("defaultRenderGlobals.imfPluginKey")
beauty_products = [RenderProduct(
productName="beauty",
ext=default_ext,
driver="defaultArnoldDriver",
camera=camera) for camera in cameras]
colorspace = self._get_colorspace(
"defaultArnoldDriver.colorManagement"
)
beauty_products = [
RenderProduct(
productName="beauty",
ext=default_ext,
driver="defaultArnoldDriver",
camera=camera,
colorspace=colorspace
) for camera in cameras
]
# AOVs > Legacy > Maya Render View > Mode
aovs_enabled = bool(
self._get_attr("defaultArnoldRenderOptions.aovMode")
@ -825,6 +868,7 @@ class RenderProductsVray(ARenderProducts):
productName="",
ext=default_ext,
camera=camera,
colorspace=lib.get_color_management_output_transform(),
multipart=self.multipart
)
)
@ -880,10 +924,13 @@ class RenderProductsVray(ARenderProducts):
aov_name = self._get_vray_aov_name(aov)
for camera in cameras:
product = RenderProduct(productName=aov_name,
ext=default_ext,
aov=aov,
camera=camera)
product = RenderProduct(
productName=aov_name,
ext=default_ext,
aov=aov,
camera=camera,
colorspace=lib.get_color_management_output_transform()
)
products.append(product)
return products
@ -1367,7 +1414,12 @@ class RenderProductsMayaHardware(ARenderProducts):
products = []
for cam in self.get_renderable_cameras():
product = RenderProduct(productName="beauty", ext=ext, camera=cam)
product = RenderProduct(
productName="beauty",
ext=ext,
camera=cam,
colorspace=lib.get_color_management_output_transform()
)
products.append(product)
return products

View file

@ -23,7 +23,8 @@ class RenderSettings(object):
'vray': 'vraySettings.fileNamePrefix',
'arnold': 'defaultRenderGlobals.imageFilePrefix',
'renderman': 'rmanGlobals.imageFileFormat',
'redshift': 'defaultRenderGlobals.imageFilePrefix'
'redshift': 'defaultRenderGlobals.imageFilePrefix',
'mayahardware2': 'defaultRenderGlobals.imageFilePrefix'
}
_image_prefixes = {

View file

@ -13,6 +13,7 @@ class CreateAnimation(plugin.Creator):
icon = "male"
write_color_sets = False
write_face_sets = False
include_user_defined_attributes = False
def __init__(self, *args, **kwargs):
super(CreateAnimation, self).__init__(*args, **kwargs)
@ -47,3 +48,6 @@ class CreateAnimation(plugin.Creator):
# Default to write normals.
self.data["writeNormals"] = True
value = self.include_user_defined_attributes
self.data["includeUserDefinedAttributes"] = value

View file

@ -15,6 +15,7 @@ class CreatePointCache(plugin.Creator):
icon = "gears"
write_color_sets = False
write_face_sets = False
include_user_defined_attributes = False
def __init__(self, *args, **kwargs):
super(CreatePointCache, self).__init__(*args, **kwargs)
@ -33,6 +34,8 @@ class CreatePointCache(plugin.Creator):
self.data["refresh"] = False # Default to suspend refresh.
# Add options for custom attributes
value = self.include_user_defined_attributes
self.data["includeUserDefinedAttributes"] = value
self.data["attr"] = ""
self.data["attrPrefix"] = ""

View file

@ -25,16 +25,20 @@ class CreateReview(plugin.Creator):
"depth peeling",
"alpha cut"
]
useMayaTimeline = True
def __init__(self, *args, **kwargs):
super(CreateReview, self).__init__(*args, **kwargs)
# get basic animation data : start / end / handles / steps
data = OrderedDict(**self.data)
animation_data = lib.collect_animation_data(fps=True)
for key, value in animation_data.items():
# Option for using Maya or asset frame range in settings.
frame_range = lib.get_frame_range()
if self.useMayaTimeline:
frame_range = lib.collect_animation_data(fps=True)
for key, value in frame_range.items():
data[key] = value
data["fps"] = lib.collect_animation_data(fps=True)["fps"]
data["review_width"] = self.Width
data["review_height"] = self.Height
data["isolate"] = self.isolate

View file

@ -9,6 +9,9 @@ class CreateVrayProxy(plugin.Creator):
family = "vrayproxy"
icon = "gears"
vrmesh = True
alembic = True
def __init__(self, *args, **kwargs):
super(CreateVrayProxy, self).__init__(*args, **kwargs)
@ -18,3 +21,6 @@ class CreateVrayProxy(plugin.Creator):
# Write vertex colors
self.data["vertexColors"] = False
self.data["vrmesh"] = self.vrmesh
self.data["alembic"] = self.alembic

View file

@ -46,7 +46,6 @@ class CollectAnimationOutputGeometry(pyblish.api.InstancePlugin):
hierarchy = members + descendants
# Ignore certain node types (e.g. constraints)
ignore = cmds.ls(hierarchy, type=self.ignore_type, long=True)
if ignore:
@ -58,3 +57,18 @@ class CollectAnimationOutputGeometry(pyblish.api.InstancePlugin):
if instance.data.get("farm"):
instance.data["families"].append("publish.farm")
# Collect user defined attributes.
if not instance.data.get("includeUserDefinedAttributes", False):
return
user_defined_attributes = set()
for node in hierarchy:
attrs = cmds.listAttr(node, userDefined=True) or list()
shapes = cmds.listRelatives(node, shapes=True) or list()
for shape in shapes:
attrs.extend(cmds.listAttr(shape, userDefined=True) or list())
user_defined_attributes.update(attrs)
instance.data["userDefinedAttributes"] = list(user_defined_attributes)

View file

@ -137,6 +137,7 @@ class CollectInstances(pyblish.api.ContextPlugin):
# Create the instance
instance = context.create_instance(objset)
instance[:] = members_hierarchy
instance.data["objset"] = objset
# Store the exact members of the object set
instance.data["setMembers"] = members

View file

@ -42,3 +42,18 @@ class CollectPointcache(pyblish.api.InstancePlugin):
if proxy_set:
instance.remove(proxy_set)
instance.data["setMembers"].remove(proxy_set)
# Collect user defined attributes.
if not instance.data.get("includeUserDefinedAttributes", False):
return
user_defined_attributes = set()
for node in instance:
attrs = cmds.listAttr(node, userDefined=True) or list()
shapes = cmds.listRelatives(node, shapes=True) or list()
for shape in shapes:
attrs.extend(cmds.listAttr(shape, userDefined=True) or list())
user_defined_attributes.update(attrs)
instance.data["userDefinedAttributes"] = list(user_defined_attributes)

View file

@ -269,7 +269,7 @@ class CollectMayaRender(pyblish.api.ContextPlugin):
self.log.info(full_exp_files)
self.log.info("collecting layer: {}".format(layer_name))
# Get layer specific settings, might be overrides
colorspace_data = lib.get_color_management_preferences()
data = {
"subset": expected_layer_name,
"attachTo": attach_to,
@ -323,6 +323,9 @@ class CollectMayaRender(pyblish.api.ContextPlugin):
"renderSetupIncludeLights": render_instance.data.get(
"renderSetupIncludeLights"
),
"colorspaceConfig": colorspace_data["config"],
"colorspaceDisplay": colorspace_data["display"],
"colorspaceView": colorspace_data["view"],
"strict_error_checking": render_instance.data.get(
"strict_error_checking", True
)

View file

@ -9,10 +9,16 @@ class CollectVrayProxy(pyblish.api.InstancePlugin):
Add `pointcache` family for it.
"""
order = pyblish.api.CollectorOrder + 0.01
label = 'Collect Vray Proxy'
label = "Collect Vray Proxy"
families = ["vrayproxy"]
def process(self, instance):
"""Collector entry point."""
if not instance.data.get('families'):
instance.data["families"] = []
if instance.data.get("vrmesh"):
instance.data["families"].append("vrayproxy.vrmesh")
if instance.data.get("alembic"):
instance.data["families"].append("vrayproxy.alembic")

View file

@ -1,5 +1,4 @@
import os
import copy
from maya import cmds
@ -10,7 +9,6 @@ from openpype.hosts.maya.api.lib import (
maintained_selection,
iter_visible_nodes_in_range
)
from openpype.lib import StringTemplate
class ExtractAlembic(publish.Extractor):
@ -25,7 +23,7 @@ class ExtractAlembic(publish.Extractor):
label = "Extract Pointcache (Alembic)"
hosts = ["maya"]
families = ["pointcache", "model", "vrayproxy"]
families = ["pointcache", "model", "vrayproxy.alembic"]
targets = ["local", "remote"]
def process(self, instance):
@ -41,6 +39,7 @@ class ExtractAlembic(publish.Extractor):
attrs = instance.data.get("attr", "").split(";")
attrs = [value for value in attrs if value.strip()]
attrs += instance.data.get("userDefinedAttributes", [])
attrs += ["cbId"]
attr_prefixes = instance.data.get("attrPrefix", "").split(";")
@ -115,6 +114,7 @@ class ExtractAlembic(publish.Extractor):
# Extract proxy.
if not instance.data.get("proxy"):
self.log.info("No proxy nodes found. Skipping proxy extraction.")
return
path = path.replace(".abc", "_proxy.abc")
@ -134,26 +134,14 @@ class ExtractAlembic(publish.Extractor):
**options
)
template_data = copy.deepcopy(instance.data["anatomyData"])
template_data.update({"ext": "abc"})
templates = instance.context.data["anatomy"].templates["publish"]
published_filename_without_extension = StringTemplate(
templates["file"]
).format(template_data).replace(".abc", "_proxy")
transfers = []
destination = os.path.join(
instance.data["resourcesDir"],
filename.replace(
filename.split(".")[0],
published_filename_without_extension
)
)
transfers.append((path, destination))
for source, destination in transfers:
self.log.debug("Transfer: {} > {}".format(source, destination))
instance.data["transfers"] = transfers
representation = {
"name": "proxy",
"ext": "abc",
"files": os.path.basename(path),
"stagingDir": dirname,
"outputName": "proxy"
}
instance.data["representations"].append(representation)
def get_members_and_roots(self, instance):
return instance[:], instance.data.get("setMembers")

View file

@ -16,7 +16,7 @@ class ExtractVRayProxy(publish.Extractor):
label = "VRay Proxy (.vrmesh)"
hosts = ["maya"]
families = ["vrayproxy"]
families = ["vrayproxy.vrmesh"]
def process(self, instance):

View file

@ -57,6 +57,10 @@ class ValidateFrameRange(pyblish.api.InstancePlugin):
inst_start = int(instance.data.get("frameStartHandle"))
inst_end = int(instance.data.get("frameEndHandle"))
inst_frame_start = int(instance.data.get("frameStart"))
inst_frame_end = int(instance.data.get("frameEnd"))
inst_handle_start = int(instance.data.get("handleStart"))
inst_handle_end = int(instance.data.get("handleEnd"))
# basic sanity checks
assert frame_start_handle <= frame_end_handle, (
@ -69,24 +73,37 @@ class ValidateFrameRange(pyblish.api.InstancePlugin):
if [ef for ef in self.exclude_families
if instance.data["family"] in ef]:
return
if(inst_start != frame_start_handle):
if (inst_start != frame_start_handle):
errors.append("Instance start frame [ {} ] doesn't "
"match the one set on instance [ {} ]: "
"match the one set on asset [ {} ]: "
"{}/{}/{}/{} (handle/start/end/handle)".format(
inst_start,
frame_start_handle,
handle_start, frame_start, frame_end, handle_end
))
if(inst_end != frame_end_handle):
if (inst_end != frame_end_handle):
errors.append("Instance end frame [ {} ] doesn't "
"match the one set on instance [ {} ]: "
"match the one set on asset [ {} ]: "
"{}/{}/{}/{} (handle/start/end/handle)".format(
inst_end,
frame_end_handle,
handle_start, frame_start, frame_end, handle_end
))
checks = {
"frame start": (frame_start, inst_frame_start),
"frame end": (frame_end, inst_frame_end),
"handle start": (handle_start, inst_handle_start),
"handle end": (handle_end, inst_handle_end)
}
for label, values in checks.items():
if values[0] != values[1]:
errors.append(
"{} on instance ({}) does not match with the asset "
"({}).".format(label.title(), values[1], values[0])
)
for e in errors:
self.log.error(e)

View file

@ -0,0 +1,60 @@
from maya import cmds
import pyblish.api
from openpype.pipeline.publish import (
ValidateContentsOrder, PublishValidationError, RepairAction
)
from openpype.pipeline import discover_legacy_creator_plugins
from openpype.hosts.maya.api.lib import imprint
class ValidateInstanceAttributes(pyblish.api.InstancePlugin):
"""Validate Instance Attributes.
New attributes can be introduced as new features come in. Old instances
will need to be updated with these attributes for the documentation to make
sense, and users do not have to recreate the instances.
"""
order = ValidateContentsOrder
hosts = ["maya"]
families = ["*"]
label = "Instance Attributes"
plugins_by_family = {
p.family: p for p in discover_legacy_creator_plugins()
}
actions = [RepairAction]
@classmethod
def get_missing_attributes(self, instance):
plugin = self.plugins_by_family[instance.data["family"]]
subset = instance.data["subset"]
asset = instance.data["asset"]
objset = instance.data["objset"]
missing_attributes = {}
for key, value in plugin(subset, asset).data.items():
if not cmds.objExists("{}.{}".format(objset, key)):
missing_attributes[key] = value
return missing_attributes
def process(self, instance):
objset = instance.data.get("objset")
if objset is None:
self.log.debug(
"Skipping {} because no objectset found.".format(instance)
)
return
missing_attributes = self.get_missing_attributes(instance)
if missing_attributes:
raise PublishValidationError(
"Missing attributes on {}:\n{}".format(
objset, missing_attributes
)
)
@classmethod
def repair(cls, instance):
imprint(instance.data["objset"], cls.get_missing_attributes(instance))

View file

@ -0,0 +1,54 @@
from maya import cmds
import pyblish.api
import openpype.hosts.maya.api.action
from openpype.pipeline.publish import (
RepairAction,
ValidateMeshOrder
)
class ValidateMeshEmpty(pyblish.api.InstancePlugin):
"""Validate meshes have some vertices.
Its possible to have meshes without any vertices. To replicate
this issue, delete all faces/polygons then all edges.
"""
order = ValidateMeshOrder
hosts = ["maya"]
families = ["model"]
label = "Mesh Empty"
actions = [
openpype.hosts.maya.api.action.SelectInvalidAction, RepairAction
]
@classmethod
def repair(cls, instance):
invalid = cls.get_invalid(instance)
for node in invalid:
cmds.delete(node)
@classmethod
def get_invalid(cls, instance):
invalid = []
meshes = cmds.ls(instance, type="mesh", long=True)
for mesh in meshes:
num_vertices = cmds.polyEvaluate(mesh, vertex=True)
if num_vertices == 0:
cls.log.warning(
"\"{}\" does not have any vertices.".format(mesh)
)
invalid.append(mesh)
return invalid
def process(self, instance):
invalid = self.get_invalid(instance)
if invalid:
raise RuntimeError(
"Meshes found in instance without any vertices: %s" % invalid
)

View file

@ -1,39 +1,9 @@
import re
from maya import cmds
import pyblish.api
import openpype.hosts.maya.api.action
from openpype.pipeline.publish import ValidateMeshOrder
def len_flattened(components):
"""Return the length of the list as if it was flattened.
Maya will return consecutive components as a single entry
when requesting with `maya.cmds.ls` without the `flatten`
flag. Though enabling `flatten` on a large list (e.g. millions)
will result in a slow result. This command will return the amount
of entries in a non-flattened list by parsing the result with
regex.
Args:
components (list): The non-flattened components.
Returns:
int: The amount of entries.
"""
assert isinstance(components, (list, tuple))
n = 0
for c in components:
match = re.search("\[([0-9]+):([0-9]+)\]", c)
if match:
start, end = match.groups()
n += int(end) - int(start) + 1
else:
n += 1
return n
from openpype.hosts.maya.api.lib import len_flattened
class ValidateMeshHasUVs(pyblish.api.InstancePlugin):
@ -57,6 +27,15 @@ class ValidateMeshHasUVs(pyblish.api.InstancePlugin):
invalid = []
for node in cmds.ls(instance, type='mesh'):
num_vertices = cmds.polyEvaluate(node, vertex=True)
if num_vertices == 0:
cls.log.warning(
"Skipping \"{}\", cause it does not have any "
"vertices.".format(node)
)
continue
uv = cmds.polyEvaluate(node, uv=True)
if uv == 0:

View file

@ -28,7 +28,10 @@ class ValidateMeshNonZeroEdgeLength(pyblish.api.InstancePlugin):
@classmethod
def get_invalid(cls, instance):
"""Return the invalid edges.
Also see: http://help.autodesk.com/view/MAYAUL/2015/ENU/?guid=Mesh__Cleanup
Also see:
http://help.autodesk.com/view/MAYAUL/2015/ENU/?guid=Mesh__Cleanup
"""
@ -36,8 +39,21 @@ class ValidateMeshNonZeroEdgeLength(pyblish.api.InstancePlugin):
if not meshes:
return list()
valid_meshes = []
for mesh in meshes:
num_vertices = cmds.polyEvaluate(mesh, vertex=True)
if num_vertices == 0:
cls.log.warning(
"Skipping \"{}\", cause it does not have any "
"vertices.".format(mesh)
)
continue
valid_meshes.append(mesh)
# Get all edges
edges = ['{0}.e[*]'.format(node) for node in meshes]
edges = ['{0}.e[*]'.format(node) for node in valid_meshes]
# Filter by constraint on edge length
invalid = lib.polyConstraint(edges,

View file

@ -1,5 +1,3 @@
import re
from maya import cmds
import pyblish.api
@ -8,37 +6,7 @@ from openpype.pipeline.publish import (
RepairAction,
ValidateMeshOrder,
)
def len_flattened(components):
"""Return the length of the list as if it was flattened.
Maya will return consecutive components as a single entry
when requesting with `maya.cmds.ls` without the `flatten`
flag. Though enabling `flatten` on a large list (e.g. millions)
will result in a slow result. This command will return the amount
of entries in a non-flattened list by parsing the result with
regex.
Args:
components (list): The non-flattened components.
Returns:
int: The amount of entries.
"""
assert isinstance(components, (list, tuple))
n = 0
pattern = re.compile(r"\[(\d+):(\d+)\]")
for c in components:
match = pattern.search(c)
if match:
start, end = match.groups()
n += int(end) - int(start) + 1
else:
n += 1
return n
from openpype.hosts.maya.api.lib import len_flattened
class ValidateMeshVerticesHaveEdges(pyblish.api.InstancePlugin):
@ -87,6 +55,13 @@ class ValidateMeshVerticesHaveEdges(pyblish.api.InstancePlugin):
for mesh in meshes:
num_vertices = cmds.polyEvaluate(mesh, vertex=True)
if num_vertices == 0:
cls.log.warning(
"Skipping \"{}\", cause it does not have any "
"vertices.".format(mesh)
)
continue
# Vertices from all edges
edges = "%s.e[*]" % mesh
vertices = cmds.polyListComponentConversion(edges, toVertex=True)

View file

@ -2,9 +2,11 @@
"""Validate model nodes names."""
import os
import re
from maya import cmds
import pyblish.api
import platform
from maya import cmds
import pyblish.api
from openpype.pipeline import legacy_io
from openpype.pipeline.publish import ValidateContentsOrder
import openpype.hosts.maya.api.action
@ -44,7 +46,7 @@ class ValidateModelName(pyblish.api.InstancePlugin):
if not cmds.ls(child, transforms=True):
return False
return True
except:
except Exception:
return False
invalid = []
@ -94,9 +96,10 @@ class ValidateModelName(pyblish.api.InstancePlugin):
# load shader list file as utf-8
shaders = []
if not use_db:
if cls.material_file:
if os.path.isfile(cls.material_file):
shader_file = open(cls.material_file, "r")
material_file = cls.material_file[platform.system().lower()]
if material_file:
if os.path.isfile(material_file):
shader_file = open(material_file, "r")
shaders = shader_file.readlines()
shader_file.close()
else:
@ -113,7 +116,7 @@ class ValidateModelName(pyblish.api.InstancePlugin):
shader_file.close()
# strip line endings from list
shaders = map(lambda s: s.rstrip(), shaders)
shaders = [s.rstrip() for s in shaders if s.rstrip()]
# compile regex for testing names
regex = cls.regex

View file

@ -1,27 +1,31 @@
import pyblish.api
from openpype.pipeline import KnownPublishError
class ValidateVrayProxy(pyblish.api.InstancePlugin):
order = pyblish.api.ValidatorOrder
label = 'VRay Proxy Settings'
hosts = ['maya']
families = ['studio.vrayproxy']
label = "VRay Proxy Settings"
hosts = ["maya"]
families = ["vrayproxy"]
def process(self, instance):
invalid = self.get_invalid(instance)
if invalid:
raise RuntimeError("'%s' has invalid settings for VRay Proxy "
"export!" % instance.name)
@classmethod
def get_invalid(cls, instance):
data = instance.data
if not data["setMembers"]:
cls.log.error("'%s' is empty! This is a bug" % instance.name)
raise KnownPublishError(
"'%s' is empty! This is a bug" % instance.name
)
if data["animation"]:
if data["frameEnd"] < data["frameStart"]:
cls.log.error("End frame is smaller than start frame")
raise KnownPublishError(
"End frame is smaller than start frame"
)
if not data["vrmesh"] and not data["alembic"]:
raise KnownPublishError(
"Both vrmesh and alembic are off. Needs at least one to"
" publish."
)

View file

@ -7,12 +7,13 @@ from openpype.hosts.maya.api import MayaHost
from maya import cmds
host = MayaHost()
install_host(host)
print("Starting OpenPype usersetup...")
# Open Workfile Post Initialization.
key = "OPENPYPE_OPEN_WORKFILE_POST_INITIALIZATION"
if bool(int(os.environ.get(key, "0"))):

View file

@ -1264,7 +1264,7 @@ def convert_to_valid_instaces():
creator_attr["farm_chunk"] = (
node["deadlineChunkSize"].value())
if "deadlineConcurrentTasks" in node.knobs():
creator_attr["farm_concurency"] = (
creator_attr["farm_concurrency"] = (
node["deadlineConcurrentTasks"].value())
_remove_old_knobs(node)

View file

@ -6,10 +6,7 @@ from openpype.pipeline import (
CreatedInstance
)
from openpype.lib import (
BoolDef,
NumberDef,
UISeparatorDef,
UILabelDef
BoolDef
)
from openpype.hosts.nuke import api as napi
@ -49,33 +46,6 @@ class CreateWritePrerender(napi.NukeWriteCreator):
self._get_render_target_enum(),
self._get_reviewable_bool()
]
if "farm_rendering" in self.instance_attributes:
attr_defs.extend([
UISeparatorDef(),
UILabelDef("Farm rendering attributes"),
BoolDef("suspended_publish", label="Suspended publishing"),
NumberDef(
"farm_priority",
label="Priority",
minimum=1,
maximum=99,
default=50
),
NumberDef(
"farm_chunk",
label="Chunk size",
minimum=1,
maximum=99,
default=10
),
NumberDef(
"farm_concurency",
label="Concurent tasks",
minimum=1,
maximum=10,
default=1
)
])
return attr_defs
def create_instance_node(self, subset_name, instance_data):

View file

@ -6,10 +6,7 @@ from openpype.pipeline import (
CreatedInstance
)
from openpype.lib import (
BoolDef,
NumberDef,
UISeparatorDef,
UILabelDef
BoolDef
)
from openpype.hosts.nuke import api as napi
@ -46,33 +43,6 @@ class CreateWriteRender(napi.NukeWriteCreator):
self._get_render_target_enum(),
self._get_reviewable_bool()
]
if "farm_rendering" in self.instance_attributes:
attr_defs.extend([
UISeparatorDef(),
UILabelDef("Farm rendering attributes"),
BoolDef("suspended_publish", label="Suspended publishing"),
NumberDef(
"farm_priority",
label="Priority",
minimum=1,
maximum=99,
default=50
),
NumberDef(
"farm_chunk",
label="Chunk size",
minimum=1,
maximum=99,
default=10
),
NumberDef(
"farm_concurency",
label="Concurent tasks",
minimum=1,
maximum=10,
default=1
)
])
return attr_defs
def create_instance_node(self, subset_name, instance_data):

View file

@ -17,6 +17,7 @@ class SetFrameRangeLoader(load.LoaderPlugin):
"yeticache",
"pointcache"]
representations = ["*"]
extension = {"*"}
label = "Set frame range"
order = 11

View file

@ -25,8 +25,9 @@ from openpype.hosts.nuke.api import containerise, update_container
class LoadBackdropNodes(load.LoaderPlugin):
"""Loading Published Backdrop nodes (workfile, nukenodes)"""
representations = ["nk"]
families = ["workfile", "nukenodes"]
representations = ["*"]
extension = {"nk"}
label = "Import Nuke Nodes"
order = 0

View file

@ -25,7 +25,8 @@ class AlembicCameraLoader(load.LoaderPlugin):
"""
families = ["camera"]
representations = ["abc"]
representations = ["*"]
extension = {"abc"}
label = "Load Alembic Camera"
icon = "camera"

View file

@ -21,6 +21,10 @@ from openpype.hosts.nuke.api import (
viewer_update_and_undo_stop,
colorspace_exists_on_node
)
from openpype.lib.transcoding import (
VIDEO_EXTENSIONS,
IMAGE_EXTENSIONS
)
from openpype.hosts.nuke.api import plugin
@ -38,13 +42,10 @@ class LoadClip(plugin.NukeLoader):
"prerender",
"review"
]
representations = [
"exr",
"dpx",
"mov",
"review",
"mp4"
]
representations = ["*"]
extensions = set(
ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS)
)
label = "Load Clip"
order = -20
@ -81,17 +82,17 @@ class LoadClip(plugin.NukeLoader):
@classmethod
def get_representations(cls):
return (
cls.representations
+ cls._representations
+ plugin.get_review_presets_config()
)
return cls._representations or cls.representations
def load(self, context, name, namespace, options):
"""Load asset via database
"""
representation = context["representation"]
# reste container id so it is always unique for each instance
# reset container id so it is always unique for each instance
self.reset_container_id()
self.log.warning(self.extensions)
is_sequence = len(representation["files"]) > 1
if is_sequence:

View file

@ -22,8 +22,9 @@ from openpype.hosts.nuke.api import (
class LoadEffects(load.LoaderPlugin):
"""Loading colorspace soft effect exported from nukestudio"""
representations = ["effectJson"]
families = ["effect"]
representations = ["*"]
extension = {"json"}
label = "Load Effects - nodes"
order = 0

View file

@ -23,8 +23,9 @@ from openpype.hosts.nuke.api import (
class LoadEffectsInputProcess(load.LoaderPlugin):
"""Loading colorspace soft effect exported from nukestudio"""
representations = ["effectJson"]
families = ["effect"]
representations = ["*"]
extension = {"json"}
label = "Load Effects - Input Process"
order = 0

View file

@ -24,8 +24,9 @@ from openpype.hosts.nuke.api import (
class LoadGizmo(load.LoaderPlugin):
"""Loading nuke Gizmo"""
representations = ["gizmo"]
families = ["gizmo"]
representations = ["*"]
extension = {"gizmo"}
label = "Load Gizmo"
order = 0

View file

@ -26,8 +26,9 @@ from openpype.hosts.nuke.api import (
class LoadGizmoInputProcess(load.LoaderPlugin):
"""Loading colorspace soft effect exported from nukestudio"""
representations = ["gizmo"]
families = ["gizmo"]
representations = ["*"]
extension = {"gizmo"}
label = "Load Gizmo - Input Process"
order = 0

View file

@ -19,6 +19,9 @@ from openpype.hosts.nuke.api import (
update_container,
viewer_update_and_undo_stop
)
from openpype.lib.transcoding import (
IMAGE_EXTENSIONS
)
class LoadImage(load.LoaderPlugin):
@ -33,7 +36,10 @@ class LoadImage(load.LoaderPlugin):
"review",
"image"
]
representations = ["exr", "dpx", "jpg", "jpeg", "png", "psd", "tiff"]
representations = ["*"]
extensions = set(
ext.lstrip(".") for ext in IMAGE_EXTENSIONS
)
label = "Load Image"
order = -10
@ -58,7 +64,7 @@ class LoadImage(load.LoaderPlugin):
@classmethod
def get_representations(cls):
return cls.representations + cls._representations
return cls._representations or cls.representations
def load(self, context, name, namespace, options):
self.log.info("__ options: `{}`".format(options))

View file

@ -8,7 +8,9 @@ class MatchmoveLoader(load.LoaderPlugin):
"""
families = ["matchmove"]
representations = ["py"]
representations = ["*"]
extension = {"py"}
defaults = ["Camera", "Object"]
label = "Run matchmove script"

View file

@ -23,7 +23,8 @@ class AlembicModelLoader(load.LoaderPlugin):
"""
families = ["model", "pointcache", "animation"]
representations = ["abc"]
representations = ["*"]
extension = {"abc"}
label = "Load Alembic"
icon = "cube"

View file

@ -20,8 +20,9 @@ from openpype.hosts.nuke.api import (
class LinkAsGroup(load.LoaderPlugin):
"""Copy the published file to be pasted at the desired location"""
representations = ["nk"]
families = ["workfile", "nukenodes"]
representations = ["*"]
extension = {"nk"}
label = "Load Precomp"
order = 0

View file

@ -132,14 +132,14 @@ class CollectNukeWrites(pyblish.api.InstancePlugin):
self.log.info("Publishing rendered frames ...")
elif render_target == "farm":
farm_priority = creator_attributes.get("farm_priority")
farm_chunk = creator_attributes.get("farm_chunk")
farm_concurency = creator_attributes.get("farm_concurency")
instance.data.update({
"deadlineChunkSize": farm_chunk or 1,
"deadlinePriority": farm_priority or 50,
"deadlineConcurrentTasks": farm_concurency or 0
})
farm_keys = ["farm_chunk", "farm_priority", "farm_concurrency"]
for key in farm_keys:
# Skip if key is not in creator attributes
if key not in creator_attributes:
continue
# Add farm attributes to instance
instance.data[key] = creator_attributes[key]
# Farm rendering
instance.data["transfer"] = False
instance.data["farm"] = True

View file

@ -14,7 +14,10 @@ from openpype.hosts.resolve.api.pipeline import (
containerise,
update_container,
)
from openpype.lib.transcoding import (
VIDEO_EXTENSIONS,
IMAGE_EXTENSIONS
)
class LoadClip(plugin.TimelineItemLoader):
"""Load a subset to timeline as clip
@ -24,7 +27,11 @@ class LoadClip(plugin.TimelineItemLoader):
"""
families = ["render2d", "source", "plate", "render", "review"]
representations = ["exr", "dpx", "jpg", "jpeg", "png", "h264", "mov"]
representations = ["*"]
extensions = set(
ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS)
)
label = "Load as clip"
order = -10

View file

@ -17,9 +17,10 @@ class UnrealAddon(OpenPypeModule, IHostAddon):
ue_plugin = "UE_5.0" if app.name[:1] == "5" else "UE_4.7"
unreal_plugin_path = os.path.join(
UNREAL_ROOT_DIR, "integration", ue_plugin
UNREAL_ROOT_DIR, "integration", ue_plugin, "OpenPype"
)
if not env.get("OPENPYPE_UNREAL_PLUGIN"):
if not env.get("OPENPYPE_UNREAL_PLUGIN") or \
env.get("OPENPYPE_UNREAL_PLUGIN") != unreal_plugin_path:
env["OPENPYPE_UNREAL_PLUGIN"] = unreal_plugin_path
# Set default environments if are not set via settings

View file

@ -79,9 +79,9 @@ class UnrealPrelaunchHook(PreLaunchHook):
unreal_project_name = os.path.splitext(unreal_project_filename)[0]
# Unreal is sensitive about project names longer then 20 chars
if len(unreal_project_name) > 20:
self.log.warning((
f"Project name exceed 20 characters ({unreal_project_name})!"
))
raise ApplicationLaunchFailed(
f"Project name exceeds 20 characters ({unreal_project_name})!"
)
# Unreal doesn't accept non alphabet characters at the start
# of the project name. This is because project name is then used
@ -119,29 +119,34 @@ class UnrealPrelaunchHook(PreLaunchHook):
f"detected [ {engine_version} ]"
))
ue_path = unreal_lib.get_editor_executable_path(
ue_path = unreal_lib.get_editor_exe_path(
Path(detected[engine_version]), engine_version)
self.launch_context.launch_args = [ue_path.as_posix()]
project_path.mkdir(parents=True, exist_ok=True)
# Set "OPENPYPE_UNREAL_PLUGIN" to current process environment for
# execution of `create_unreal_project`
if self.launch_context.env.get("OPENPYPE_UNREAL_PLUGIN"):
self.log.info((
f"{self.signature} using OpenPype plugin from "
f"{self.launch_context.env.get('OPENPYPE_UNREAL_PLUGIN')}"
))
env_key = "OPENPYPE_UNREAL_PLUGIN"
if self.launch_context.env.get(env_key):
os.environ[env_key] = self.launch_context.env[env_key]
engine_path = detected[engine_version]
unreal_lib.try_installing_plugin(Path(engine_path), os.environ)
project_file = project_path / unreal_project_filename
if not project_file.is_file():
engine_path = detected[engine_version]
self.log.info((
f"{self.signature} creating unreal "
f"project [ {unreal_project_name} ]"
))
# Set "OPENPYPE_UNREAL_PLUGIN" to current process environment for
# execution of `create_unreal_project`
if self.launch_context.env.get("OPENPYPE_UNREAL_PLUGIN"):
self.log.info((
f"{self.signature} using OpenPype plugin from "
f"{self.launch_context.env.get('OPENPYPE_UNREAL_PLUGIN')}"
))
env_key = "OPENPYPE_UNREAL_PLUGIN"
if self.launch_context.env.get(env_key):
os.environ[env_key] = self.launch_context.env[env_key]
unreal_lib.create_unreal_project(
unreal_project_name,

View file

@ -0,0 +1,8 @@
/Saved
/DerivedDataCache
/Intermediate
/Content
/Config
/Binaries
/.idea
/.vs

View file

@ -0,0 +1,12 @@
{
"FileVersion": 3,
"EngineAssociation": "4.27",
"Category": "",
"Description": "",
"Plugins": [
{
"Name": "OpenPype",
"Enabled": true
}
]
}

View file

@ -0,0 +1,8 @@
[FilterPlugin]
; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and
; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively.
;
; Examples:
; /README.txt
; /Extras/...
; /Binaries/ThirdParty/*.dll

View file

@ -10,10 +10,9 @@
"DocsURL": "https://openpype.io/docs/artist_hosts_unreal",
"MarketplaceURL": "",
"SupportURL": "https://pype.club/",
"EngineVersion": "4.27",
"CanContainContent": true,
"IsBetaVersion": true,
"IsExperimentalVersion": false,
"Installed": false,
"Installed": true,
"Modules": [
{
"Name": "OpenPype",

View file

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Before After
Before After

View file

@ -1,4 +1,4 @@
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
// Copyright 2023, Ayon, All rights reserved.
using UnrealBuildTool;
@ -6,8 +6,8 @@ public class OpenPype : ModuleRules
{
public OpenPype(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicIncludePaths.AddRange(
new string[] {
// ... add public include paths required here ...
@ -34,6 +34,7 @@ public class OpenPype : ModuleRules
PrivateDependencyModuleNames.AddRange(
new string[]
{
"GameProjectGeneration",
"Projects",
"InputCore",
"UnrealEd",

View file

@ -0,0 +1,141 @@
// Copyright 2023, Ayon, All rights reserved.
#include "Commandlets/Implementations/OPGenerateProjectCommandlet.h"
#include "Editor.h"
#include "GameProjectUtils.h"
#include "OPConstants.h"
#include "Commandlets/OPActionResult.h"
#include "ProjectDescriptor.h"
int32 UOPGenerateProjectCommandlet::Main(const FString& CommandLineParams)
{
//Parses command line parameters & creates structure FProjectInformation
const FOPGenerateProjectParams ParsedParams = FOPGenerateProjectParams(CommandLineParams);
ProjectInformation = ParsedParams.GenerateUEProjectInformation();
//Creates .uproject & other UE files
EVALUATE_OP_ACTION_RESULT(TryCreateProject());
//Loads created .uproject
EVALUATE_OP_ACTION_RESULT(TryLoadProjectDescriptor());
//Adds needed plugin to .uproject
AttachPluginsToProjectDescriptor();
//Saves .uproject
EVALUATE_OP_ACTION_RESULT(TrySave());
//When we are here, there should not be problems in generating Unreal Project for OpenPype
return 0;
}
FOPGenerateProjectParams::FOPGenerateProjectParams(): FOPGenerateProjectParams("")
{
}
FOPGenerateProjectParams::FOPGenerateProjectParams(const FString& CommandLineParams): CommandLineParams(
CommandLineParams)
{
UCommandlet::ParseCommandLine(*CommandLineParams, Tokens, Switches);
}
FProjectInformation FOPGenerateProjectParams::GenerateUEProjectInformation() const
{
FProjectInformation ProjectInformation = FProjectInformation();
ProjectInformation.ProjectFilename = GetProjectFileName();
ProjectInformation.bShouldGenerateCode = IsSwitchPresent("GenerateCode");
return ProjectInformation;
}
FString FOPGenerateProjectParams::TryGetToken(const int32 Index) const
{
return Tokens.IsValidIndex(Index) ? Tokens[Index] : "";
}
FString FOPGenerateProjectParams::GetProjectFileName() const
{
return TryGetToken(0);
}
bool FOPGenerateProjectParams::IsSwitchPresent(const FString& Switch) const
{
return INDEX_NONE != Switches.IndexOfByPredicate([&Switch](const FString& Item) -> bool
{
return Item.Equals(Switch);
}
);
}
UOPGenerateProjectCommandlet::UOPGenerateProjectCommandlet()
{
LogToConsole = true;
}
FOP_ActionResult UOPGenerateProjectCommandlet::TryCreateProject() const
{
FText FailReason;
FText FailLog;
TArray<FString> OutCreatedFiles;
if (!GameProjectUtils::CreateProject(ProjectInformation, FailReason, FailLog, &OutCreatedFiles))
return FOP_ActionResult(EOP_ActionResult::ProjectNotCreated, FailReason);
return FOP_ActionResult();
}
FOP_ActionResult UOPGenerateProjectCommandlet::TryLoadProjectDescriptor()
{
FText FailReason;
const bool bLoaded = ProjectDescriptor.Load(ProjectInformation.ProjectFilename, FailReason);
return FOP_ActionResult(bLoaded ? EOP_ActionResult::Ok : EOP_ActionResult::ProjectNotLoaded, FailReason);
}
void UOPGenerateProjectCommandlet::AttachPluginsToProjectDescriptor()
{
FPluginReferenceDescriptor OPPluginDescriptor;
OPPluginDescriptor.bEnabled = true;
OPPluginDescriptor.Name = OPConstants::OP_PluginName;
ProjectDescriptor.Plugins.Add(OPPluginDescriptor);
FPluginReferenceDescriptor PythonPluginDescriptor;
PythonPluginDescriptor.bEnabled = true;
PythonPluginDescriptor.Name = OPConstants::PythonScript_PluginName;
ProjectDescriptor.Plugins.Add(PythonPluginDescriptor);
FPluginReferenceDescriptor SequencerScriptingPluginDescriptor;
SequencerScriptingPluginDescriptor.bEnabled = true;
SequencerScriptingPluginDescriptor.Name = OPConstants::SequencerScripting_PluginName;
ProjectDescriptor.Plugins.Add(SequencerScriptingPluginDescriptor);
FPluginReferenceDescriptor MovieRenderPipelinePluginDescriptor;
MovieRenderPipelinePluginDescriptor.bEnabled = true;
MovieRenderPipelinePluginDescriptor.Name = OPConstants::MovieRenderPipeline_PluginName;
ProjectDescriptor.Plugins.Add(MovieRenderPipelinePluginDescriptor);
FPluginReferenceDescriptor EditorScriptingPluginDescriptor;
EditorScriptingPluginDescriptor.bEnabled = true;
EditorScriptingPluginDescriptor.Name = OPConstants::EditorScriptingUtils_PluginName;
ProjectDescriptor.Plugins.Add(EditorScriptingPluginDescriptor);
}
FOP_ActionResult UOPGenerateProjectCommandlet::TrySave()
{
FText FailReason;
const bool bSaved = ProjectDescriptor.Save(ProjectInformation.ProjectFilename, FailReason);
return FOP_ActionResult(bSaved ? EOP_ActionResult::Ok : EOP_ActionResult::ProjectNotSaved, FailReason);
}
FOPGenerateProjectParams UOPGenerateProjectCommandlet::ParseParameters(const FString& Params) const
{
FOPGenerateProjectParams ParamsResult;
TArray<FString> Tokens, Switches;
ParseCommandLine(*Params, Tokens, Switches);
return ParamsResult;
}

View file

@ -0,0 +1,41 @@
// Copyright 2023, Ayon, All rights reserved.
#include "Commandlets/OPActionResult.h"
#include "Logging/OP_Log.h"
EOP_ActionResult::Type& FOP_ActionResult::GetStatus()
{
return Status;
}
FText& FOP_ActionResult::GetReason()
{
return Reason;
}
FOP_ActionResult::FOP_ActionResult():Status(EOP_ActionResult::Type::Ok)
{
}
FOP_ActionResult::FOP_ActionResult(const EOP_ActionResult::Type& InEnum):Status(InEnum)
{
TryLog();
}
FOP_ActionResult::FOP_ActionResult(const EOP_ActionResult::Type& InEnum, const FText& InReason):Status(InEnum), Reason(InReason)
{
TryLog();
};
bool FOP_ActionResult::IsProblem() const
{
return Status != EOP_ActionResult::Ok;
}
void FOP_ActionResult::TryLog() const
{
if(IsProblem())
UE_LOG(LogCommandletOPGenerateProject, Error, TEXT("%s"), *Reason.ToString());
}

View file

@ -0,0 +1 @@
#include "Logging/OP_Log.h"

View file

@ -1,3 +1,4 @@
// Copyright 2023, Ayon, All rights reserved.
#include "OpenPype.h"
#include "ISettingsContainer.h"
@ -16,32 +17,34 @@ static const FName OpenPypeTabName("OpenPype");
// This function is triggered when the plugin is staring up
void FOpenPypeModule::StartupModule()
{
FOpenPypeStyle::Initialize();
FOpenPypeStyle::SetIcon("Logo", "openpype40");
if (!IsRunningCommandlet()) {
FOpenPypeStyle::Initialize();
FOpenPypeStyle::SetIcon("Logo", "openpype40");
// Create the Extender that will add content to the menu
FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor");
// Create the Extender that will add content to the menu
FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor");
TSharedPtr<FExtender> MenuExtender = MakeShareable(new FExtender());
TSharedPtr<FExtender> ToolbarExtender = MakeShareable(new FExtender());
TSharedPtr<FExtender> MenuExtender = MakeShareable(new FExtender());
TSharedPtr<FExtender> ToolbarExtender = MakeShareable(new FExtender());
MenuExtender->AddMenuExtension(
"LevelEditor",
EExtensionHook::After,
NULL,
FMenuExtensionDelegate::CreateRaw(this, &FOpenPypeModule::AddMenuEntry)
);
ToolbarExtender->AddToolBarExtension(
"Settings",
EExtensionHook::After,
NULL,
FToolBarExtensionDelegate::CreateRaw(this, &FOpenPypeModule::AddToobarEntry));
MenuExtender->AddMenuExtension(
"LevelEditor",
EExtensionHook::After,
NULL,
FMenuExtensionDelegate::CreateRaw(this, &FOpenPypeModule::AddMenuEntry)
);
ToolbarExtender->AddToolBarExtension(
"Settings",
EExtensionHook::After,
NULL,
FToolBarExtensionDelegate::CreateRaw(this, &FOpenPypeModule::AddToobarEntry));
LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MenuExtender);
LevelEditorModule.GetToolBarExtensibilityManager()->AddExtender(ToolbarExtender);
LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MenuExtender);
LevelEditorModule.GetToolBarExtensibilityManager()->AddExtender(ToolbarExtender);
RegisterSettings();
RegisterSettings();
}
}
void FOpenPypeModule::ShutdownModule()

View file

@ -1,3 +1,4 @@
// Copyright 2023, Ayon, All rights reserved.
#include "OpenPypeLib.h"
#include "AssetViewUtils.h"

View file

@ -1,11 +1,12 @@
// Copyright 2023, Ayon, All rights reserved.
#pragma once
#include "OpenPypePublishInstance.h"
#include "AssetRegistryModule.h"
#include "NotificationManager.h"
#include "OpenPypeLib.h"
#include "OpenPypeSettings.h"
#include "SNotificationList.h"
#include "Framework/Notifications/NotificationManager.h"
#include "Widgets/Notifications/SNotificationList.h"
//Moves all the invalid pointers to the end to prepare them for the shrinking
#define REMOVE_INVALID_ENTRIES(VAR) VAR.CompactStable(); \

View file

@ -1,3 +1,4 @@
// Copyright 2023, Ayon, All rights reserved.
#include "OpenPypePublishInstanceFactory.h"
#include "OpenPypePublishInstance.h"

View file

@ -1,3 +1,4 @@
// Copyright 2023, Ayon, All rights reserved.
#include "OpenPypePythonBridge.h"
UOpenPypePythonBridge* UOpenPypePythonBridge::Get()

View file

@ -1,9 +1,8 @@
// Fill out your copyright notice in the Description page of Project Settings.
// Copyright 2023, Ayon, All rights reserved.
#include "OpenPypeSettings.h"
#include "IPluginManager.h"
#include "UObjectGlobals.h"
#include "Interfaces/IPluginManager.h"
/**
* Mainly is used for initializing default values if the DefaultOpenPypeSettings.ini file does not exist in the saved config

View file

@ -1,3 +1,4 @@
// Copyright 2023, Ayon, All rights reserved.
#include "OpenPypeStyle.h"
#include "Framework/Application/SlateApplication.h"
#include "Styling/SlateStyle.h"
@ -43,7 +44,7 @@ const FVector2D Icon40x40(40.0f, 40.0f);
TUniquePtr< FSlateStyleSet > FOpenPypeStyle::Create()
{
TUniquePtr< FSlateStyleSet > Style = MakeUnique<FSlateStyleSet>(GetStyleSetName());
Style->SetContentRoot(FPaths::ProjectPluginsDir() / TEXT("OpenPype/Resources"));
Style->SetContentRoot(FPaths::EnginePluginsDir() / TEXT("Marketplace/OpenPype/Resources"));
return Style;
}
@ -66,5 +67,4 @@ const ISlateStyle& FOpenPypeStyle::Get()
{
check(OpenPypeStyleInstance);
return *OpenPypeStyleInstance;
return *OpenPypeStyleInstance;
}

View file

@ -0,0 +1,60 @@
// Copyright 2023, Ayon, All rights reserved.
#pragma once
#include "GameProjectUtils.h"
#include "Commandlets/OPActionResult.h"
#include "ProjectDescriptor.h"
#include "Commandlets/Commandlet.h"
#include "OPGenerateProjectCommandlet.generated.h"
struct FProjectDescriptor;
struct FProjectInformation;
/**
* @brief Structure which parses command line parameters and generates FProjectInformation
*/
USTRUCT()
struct FOPGenerateProjectParams
{
GENERATED_BODY()
private:
FString CommandLineParams;
TArray<FString> Tokens;
TArray<FString> Switches;
public:
FOPGenerateProjectParams();
FOPGenerateProjectParams(const FString& CommandLineParams);
FProjectInformation GenerateUEProjectInformation() const;
private:
FString TryGetToken(const int32 Index) const;
FString GetProjectFileName() const;
bool IsSwitchPresent(const FString& Switch) const;
};
UCLASS()
class OPENPYPE_API UOPGenerateProjectCommandlet : public UCommandlet
{
GENERATED_BODY()
private:
FProjectInformation ProjectInformation;
FProjectDescriptor ProjectDescriptor;
public:
UOPGenerateProjectCommandlet();
virtual int32 Main(const FString& CommandLineParams) override;
private:
FOPGenerateProjectParams ParseParameters(const FString& Params) const;
FOP_ActionResult TryCreateProject() const;
FOP_ActionResult TryLoadProjectDescriptor();
void AttachPluginsToProjectDescriptor();
FOP_ActionResult TrySave();
};

View file

@ -0,0 +1,83 @@
// Copyright 2023, Ayon, All rights reserved.
#pragma once
#include "CoreMinimal.h"
#include "OPActionResult.generated.h"
/**
* @brief This macro returns error code when is problem or does nothing when there is no problem.
* @param ActionResult FOP_ActionResult structure
*/
#define EVALUATE_OP_ACTION_RESULT(ActionResult) \
if(ActionResult.IsProblem()) \
return ActionResult.GetStatus();
/**
* @brief This enum values are humanly readable mapping of error codes.
* Here should be all error codes to be possible find what went wrong.
* TODO: In the future should exists an web document where is mapped error code & what problem occured & how to repair it...
*/
UENUM()
namespace EOP_ActionResult
{
enum Type
{
Ok,
ProjectNotCreated,
ProjectNotLoaded,
ProjectNotSaved,
//....Here insert another values
//Do not remove!
//Usable for looping through enum values
__Last UMETA(Hidden)
};
}
/**
* @brief This struct holds action result enum and optionally reason of fail
*/
USTRUCT()
struct FOP_ActionResult
{
GENERATED_BODY()
public:
/** @brief Default constructor usable when there is no problem */
FOP_ActionResult();
/**
* @brief This constructor initializes variables & attempts to log when is error
* @param InEnum Status
*/
FOP_ActionResult(const EOP_ActionResult::Type& InEnum);
/**
* @brief This constructor initializes variables & attempts to log when is error
* @param InEnum Status
* @param InReason Reason of potential fail
*/
FOP_ActionResult(const EOP_ActionResult::Type& InEnum, const FText& InReason);
private:
/** @brief Action status */
EOP_ActionResult::Type Status;
/** @brief Optional reason of fail */
FText Reason;
public:
/**
* @brief Checks if there is problematic state
* @return true when status is not equal to EOP_ActionResult::Ok
*/
bool IsProblem() const;
EOP_ActionResult::Type& GetStatus();
FText& GetReason();
private:
void TryLog() const;
};

View file

@ -0,0 +1,4 @@
// Copyright 2023, Ayon, All rights reserved.
#pragma once
DEFINE_LOG_CATEGORY_STATIC(LogCommandletOPGenerateProject, Log, All);

View file

@ -0,0 +1,13 @@
// Copyright 2023, Ayon, All rights reserved.
#pragma once
namespace OPConstants
{
const FString OP_PluginName = "OpenPype";
const FString PythonScript_PluginName = "PythonScriptPlugin";
const FString SequencerScripting_PluginName = "SequencerScripting";
const FString MovieRenderPipeline_PluginName = "MovieRenderPipeline";
const FString EditorScriptingUtils_PluginName = "EditorScriptingUtilities";
}

View file

@ -1,4 +1,4 @@
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
// Copyright 2023, Ayon, All rights reserved.
#pragma once

View file

@ -1,3 +1,4 @@
// Copyright 2023, Ayon, All rights reserved.
#pragma once
#include "Engine.h"
@ -16,7 +17,7 @@ public:
*
* @return - Set of UObjects. Careful! They are returning raw pointers. Seems like an issue in UE5
*/
UFUNCTION(BlueprintCallable, BlueprintPure)
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python")
TSet<UObject*> GetInternalAssets() const
{
//For some reason it can only return Raw Pointers? Seems like an issue which they haven't fixed.
@ -33,7 +34,7 @@ public:
*
* @return - TSet of assets (UObjects). Careful! They are returning raw pointers. Seems like an issue in UE5
*/
UFUNCTION(BlueprintCallable, BlueprintPure)
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python")
TSet<UObject*> GetExternalAssets() const
{
//For some reason it can only return Raw Pointers? Seems like an issue which they haven't fixed.
@ -53,7 +54,7 @@ public:
*
* @attention If the bAddExternalAssets variable is false, external assets won't be included!
*/
UFUNCTION(BlueprintCallable, BlueprintPure)
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python")
TSet<UObject*> GetAllAssets() const
{
const TSet<TSoftObjectPtr<UObject>>& IteratedSet = bAddExternalAssets

View file

@ -1,3 +1,4 @@
// Copyright 2023, Ayon, All rights reserved.
#pragma once
#include "Engine.h"
#include "OpenPypePythonBridge.generated.h"

View file

@ -1,9 +1,8 @@
// Fill out your copyright notice in the Description page of Project Settings.
// Copyright 2023, Ayon, All rights reserved.
#pragma once
#include "CoreMinimal.h"
#include "Object.h"
#include "OpenPypeSettings.generated.h"
#define OPENPYPE_SETTINGS_FILEPATH IPluginManager::Get().FindPlugin("OpenPype")->GetBaseDir() / TEXT("Config") / TEXT("DefaultOpenPypeSettings.ini")

View file

@ -1,115 +0,0 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "AssetContainer.h"
#include "AssetRegistryModule.h"
#include "Misc/PackageName.h"
#include "Engine.h"
#include "Containers/UnrealString.h"
UAssetContainer::UAssetContainer(const FObjectInitializer& ObjectInitializer)
: UAssetUserData(ObjectInitializer)
{
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
FString path = UAssetContainer::GetPathName();
UE_LOG(LogTemp, Warning, TEXT("UAssetContainer %s"), *path);
FARFilter Filter;
Filter.PackagePaths.Add(FName(*path));
AssetRegistryModule.Get().OnAssetAdded().AddUObject(this, &UAssetContainer::OnAssetAdded);
AssetRegistryModule.Get().OnAssetRemoved().AddUObject(this, &UAssetContainer::OnAssetRemoved);
AssetRegistryModule.Get().OnAssetRenamed().AddUObject(this, &UAssetContainer::OnAssetRenamed);
}
void UAssetContainer::OnAssetAdded(const FAssetData& AssetData)
{
TArray<FString> split;
// get directory of current container
FString selfFullPath = UAssetContainer::GetPathName();
FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath);
// get asset path and class
FString assetPath = AssetData.GetFullName();
FString assetFName = AssetData.AssetClass.ToString();
// split path
assetPath.ParseIntoArray(split, TEXT(" "), true);
FString assetDir = FPackageName::GetLongPackagePath(*split[1]);
// take interest only in paths starting with path of current container
if (assetDir.StartsWith(*selfDir))
{
// exclude self
if (assetFName != "AssetContainer")
{
assets.Add(assetPath);
assetsData.Add(AssetData);
UE_LOG(LogTemp, Log, TEXT("%s: asset added to %s"), *selfFullPath, *selfDir);
}
}
}
void UAssetContainer::OnAssetRemoved(const FAssetData& AssetData)
{
TArray<FString> split;
// get directory of current container
FString selfFullPath = UAssetContainer::GetPathName();
FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath);
// get asset path and class
FString assetPath = AssetData.GetFullName();
FString assetFName = AssetData.AssetClass.ToString();
// split path
assetPath.ParseIntoArray(split, TEXT(" "), true);
FString assetDir = FPackageName::GetLongPackagePath(*split[1]);
// take interest only in paths starting with path of current container
FString path = UAssetContainer::GetPathName();
FString lpp = FPackageName::GetLongPackagePath(*path);
if (assetDir.StartsWith(*selfDir))
{
// exclude self
if (assetFName != "AssetContainer")
{
// UE_LOG(LogTemp, Warning, TEXT("%s: asset removed"), *lpp);
assets.Remove(assetPath);
assetsData.Remove(AssetData);
}
}
}
void UAssetContainer::OnAssetRenamed(const FAssetData& AssetData, const FString& str)
{
TArray<FString> split;
// get directory of current container
FString selfFullPath = UAssetContainer::GetPathName();
FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath);
// get asset path and class
FString assetPath = AssetData.GetFullName();
FString assetFName = AssetData.AssetClass.ToString();
// split path
assetPath.ParseIntoArray(split, TEXT(" "), true);
FString assetDir = FPackageName::GetLongPackagePath(*split[1]);
if (assetDir.StartsWith(*selfDir))
{
// exclude self
if (assetFName != "AssetContainer")
{
assets.Remove(str);
assets.Add(assetPath);
assetsData.Remove(AssetData);
// UE_LOG(LogTemp, Warning, TEXT("%s: asset renamed %s"), *lpp, *str);
}
}
}

View file

@ -1,39 +0,0 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "Engine/AssetUserData.h"
#include "AssetData.h"
#include "AssetContainer.generated.h"
/**
*
*/
UCLASS(Blueprintable)
class OPENPYPE_API UAssetContainer : public UAssetUserData
{
GENERATED_BODY()
public:
UAssetContainer(const FObjectInitializer& ObjectInitalizer);
// ~UAssetContainer();
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TArray<FString> assets;
// There seems to be no reflection option to expose array of FAssetData
/*
UPROPERTY(Transient, BlueprintReadOnly, Category = "Python", meta=(DisplayName="Assets Data"))
TArray<FAssetData> assetsData;
*/
private:
TArray<FAssetData> assetsData;
void OnAssetAdded(const FAssetData& AssetData);
void OnAssetRemoved(const FAssetData& AssetData);
void OnAssetRenamed(const FAssetData& AssetData, const FString& str);
};

View file

@ -1,21 +0,0 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Factories/Factory.h"
#include "AssetContainerFactory.generated.h"
/**
*
*/
UCLASS()
class OPENPYPE_API UAssetContainerFactory : public UFactory
{
GENERATED_BODY()
public:
UAssetContainerFactory(const FObjectInitializer& ObjectInitializer);
virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override;
virtual bool ShouldShowInNewMenu() const override;
};

View file

@ -0,0 +1,41 @@
# Prerequisites
*.d
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
*.smod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
/Saved
/DerivedDataCache
/Intermediate
/Binaries
/Content
/Config
/.idea
/.vs

View file

@ -0,0 +1,20 @@
{
"FileVersion": 3,
"EngineAssociation": "5.0",
"Category": "",
"Description": "",
"Plugins": [
{
"Name": "ModelingToolsEditorMode",
"Enabled": true,
"TargetAllowList": [
"Editor"
]
},
{
"Name": "OpenPype",
"Enabled": true,
"Type": "Editor"
}
]
}

View file

@ -0,0 +1,8 @@
[FilterPlugin]
; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and
; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively.
;
; Examples:
; /README.txt
; /Extras/...
; /Binaries/ThirdParty/*.dll

View file

@ -11,9 +11,9 @@
"MarketplaceURL": "",
"SupportURL": "https://pype.club/",
"CanContainContent": true,
"IsBetaVersion": true,
"EngineVersion": "5.0",
"IsExperimentalVersion": false,
"Installed": false,
"Installed": true,
"Modules": [
{
"Name": "OpenPype",

View file

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Before After
Before After

Some files were not shown because too many files have changed in this diff Show more