Merge branch 'develop' into bugfix/OP-3191_Flame-publishing-bugs
71
CHANGELOG.md
|
|
@ -1,8 +1,24 @@
|
|||
# Changelog
|
||||
|
||||
## [3.10.0-nightly.3](https://github.com/pypeclub/OpenPype/tree/HEAD)
|
||||
## [3.10.1-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD)
|
||||
|
||||
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.9.8...HEAD)
|
||||
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.10.0...HEAD)
|
||||
|
||||
**🚀 Enhancements**
|
||||
|
||||
- TVPaint: Init file for TVPaint worker also handle guideline images [\#3250](https://github.com/pypeclub/OpenPype/pull/3250)
|
||||
- Support for Unreal 5 [\#3122](https://github.com/pypeclub/OpenPype/pull/3122)
|
||||
|
||||
**🐛 Bug fixes**
|
||||
|
||||
- Unreal: Fix Camera Loading if Layout is missing [\#3255](https://github.com/pypeclub/OpenPype/pull/3255)
|
||||
- Unreal: Fixed Animation loading in UE5 [\#3240](https://github.com/pypeclub/OpenPype/pull/3240)
|
||||
- Unreal: Fixed Render creation in UE5 [\#3239](https://github.com/pypeclub/OpenPype/pull/3239)
|
||||
- Unreal: Fixed Camera loading in UE5 [\#3238](https://github.com/pypeclub/OpenPype/pull/3238)
|
||||
|
||||
## [3.10.0](https://github.com/pypeclub/OpenPype/tree/3.10.0) (2022-05-26)
|
||||
|
||||
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.10.0-nightly.6...3.10.0)
|
||||
|
||||
**🆕 New features**
|
||||
|
||||
|
|
@ -10,11 +26,17 @@
|
|||
- General: Creator plugins from addons can be registered [\#3179](https://github.com/pypeclub/OpenPype/pull/3179)
|
||||
- Ftrack: Single image reviewable [\#3157](https://github.com/pypeclub/OpenPype/pull/3157)
|
||||
- Nuke: Expose write attributes to settings [\#3123](https://github.com/pypeclub/OpenPype/pull/3123)
|
||||
- Hiero: Initial frame publish support [\#3106](https://github.com/pypeclub/OpenPype/pull/3106)
|
||||
|
||||
**🚀 Enhancements**
|
||||
|
||||
- Maya: FBX camera export [\#3253](https://github.com/pypeclub/OpenPype/pull/3253)
|
||||
- General: updating common vendor `scriptmenu` to 1.5.2 [\#3246](https://github.com/pypeclub/OpenPype/pull/3246)
|
||||
- Project Manager: Allow to paste Tasks into multiple assets at the same time [\#3226](https://github.com/pypeclub/OpenPype/pull/3226)
|
||||
- Project manager: Sped up project load [\#3216](https://github.com/pypeclub/OpenPype/pull/3216)
|
||||
- Loader UI: Speed issues of loader with sync server [\#3199](https://github.com/pypeclub/OpenPype/pull/3199)
|
||||
- Looks: add basic support for Renderman [\#3190](https://github.com/pypeclub/OpenPype/pull/3190)
|
||||
- Maya: added clean\_import option to Import loader [\#3181](https://github.com/pypeclub/OpenPype/pull/3181)
|
||||
- Add the scripts menu definition to nuke [\#3168](https://github.com/pypeclub/OpenPype/pull/3168)
|
||||
- Maya: add maya 2023 to default applications [\#3167](https://github.com/pypeclub/OpenPype/pull/3167)
|
||||
- Compressed bgeo publishing in SAP and Houdini loader [\#3153](https://github.com/pypeclub/OpenPype/pull/3153)
|
||||
- General: Add 'dataclasses' to required python modules [\#3149](https://github.com/pypeclub/OpenPype/pull/3149)
|
||||
|
|
@ -25,15 +47,19 @@
|
|||
- General: Simplified OP modules/addons import [\#3137](https://github.com/pypeclub/OpenPype/pull/3137)
|
||||
- Terminal: Tweak coloring of TrayModuleManager logging enabled states [\#3133](https://github.com/pypeclub/OpenPype/pull/3133)
|
||||
- General: Cleanup some Loader docstrings [\#3131](https://github.com/pypeclub/OpenPype/pull/3131)
|
||||
- Nuke: render instance with subset name filtered overrides [\#3117](https://github.com/pypeclub/OpenPype/pull/3117)
|
||||
- Unreal: Layout and Camera update and remove functions reimplemented and improvements [\#3116](https://github.com/pypeclub/OpenPype/pull/3116)
|
||||
- Settings: Remove environment groups from settings [\#3115](https://github.com/pypeclub/OpenPype/pull/3115)
|
||||
- TVPaint: Match renderlayer key with other hosts [\#3110](https://github.com/pypeclub/OpenPype/pull/3110)
|
||||
- Ftrack: AssetVersion status on publish [\#3108](https://github.com/pypeclub/OpenPype/pull/3108)
|
||||
- Tray publisher: Simple families from settings [\#3105](https://github.com/pypeclub/OpenPype/pull/3105)
|
||||
|
||||
**🐛 Bug fixes**
|
||||
|
||||
- nuke: use framerange issue [\#3254](https://github.com/pypeclub/OpenPype/pull/3254)
|
||||
- Ftrack: Chunk sizes for queries has minimal condition [\#3244](https://github.com/pypeclub/OpenPype/pull/3244)
|
||||
- Maya: renderman displays needs to be filtered [\#3242](https://github.com/pypeclub/OpenPype/pull/3242)
|
||||
- Ftrack: Validate that the user exists on ftrack [\#3237](https://github.com/pypeclub/OpenPype/pull/3237)
|
||||
- Maya: Fix support for multiple resolutions [\#3236](https://github.com/pypeclub/OpenPype/pull/3236)
|
||||
- TVPaint: Look for more groups than 12 [\#3228](https://github.com/pypeclub/OpenPype/pull/3228)
|
||||
- Hiero: debugging frame range and other 3.10 [\#3222](https://github.com/pypeclub/OpenPype/pull/3222)
|
||||
- Project Manager: Fix persistent editors on project change [\#3218](https://github.com/pypeclub/OpenPype/pull/3218)
|
||||
- Deadline: instance data overwrite fix [\#3214](https://github.com/pypeclub/OpenPype/pull/3214)
|
||||
- Ftrack: Push hierarchical attributes action works [\#3210](https://github.com/pypeclub/OpenPype/pull/3210)
|
||||
- Standalone Publisher: Always create new representation for thumbnail [\#3203](https://github.com/pypeclub/OpenPype/pull/3203)
|
||||
- Photoshop: skip collector when automatic testing [\#3202](https://github.com/pypeclub/OpenPype/pull/3202)
|
||||
- Nuke: render/workfile version sync doesn't work on farm [\#3185](https://github.com/pypeclub/OpenPype/pull/3185)
|
||||
|
|
@ -47,20 +73,21 @@
|
|||
- Ftrack: Action delete old versions formatting works [\#3152](https://github.com/pypeclub/OpenPype/pull/3152)
|
||||
- Deadline: fix the output directory [\#3144](https://github.com/pypeclub/OpenPype/pull/3144)
|
||||
- General: New Session schema [\#3141](https://github.com/pypeclub/OpenPype/pull/3141)
|
||||
- General: Missing version on headless mode crash properly [\#3136](https://github.com/pypeclub/OpenPype/pull/3136)
|
||||
- TVPaint: Composite layers in reversed order [\#3135](https://github.com/pypeclub/OpenPype/pull/3135)
|
||||
- Nuke: fixing default settings for workfile builder loaders [\#3120](https://github.com/pypeclub/OpenPype/pull/3120)
|
||||
- Nuke: fix anatomy imageio regex default [\#3119](https://github.com/pypeclub/OpenPype/pull/3119)
|
||||
- General: Python 3 compatibility in queries [\#3112](https://github.com/pypeclub/OpenPype/pull/3112)
|
||||
- General: Collect loaded versions skips not existing representations [\#3095](https://github.com/pypeclub/OpenPype/pull/3095)
|
||||
|
||||
**🔀 Refactored code**
|
||||
|
||||
- Avalon repo removed from Jobs workflow [\#3193](https://github.com/pypeclub/OpenPype/pull/3193)
|
||||
- General: Remove remaining imports from avalon [\#3130](https://github.com/pypeclub/OpenPype/pull/3130)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Harmony: message length in 21.1 [\#3257](https://github.com/pypeclub/OpenPype/pull/3257)
|
||||
- Harmony: 21.1 fix [\#3249](https://github.com/pypeclub/OpenPype/pull/3249)
|
||||
- Maya: added jpg to filter for Image Plane Loader [\#3223](https://github.com/pypeclub/OpenPype/pull/3223)
|
||||
- Webpublisher: replace space by underscore in subset names [\#3160](https://github.com/pypeclub/OpenPype/pull/3160)
|
||||
- StandalonePublisher: removed Extract Background plugins [\#3093](https://github.com/pypeclub/OpenPype/pull/3093)
|
||||
|
||||
## [3.9.8](https://github.com/pypeclub/OpenPype/tree/3.9.8) (2022-05-19)
|
||||
|
||||
|
|
@ -69,6 +96,7 @@
|
|||
**🚀 Enhancements**
|
||||
|
||||
- nuke: generate publishing nodes inside render group node [\#3206](https://github.com/pypeclub/OpenPype/pull/3206)
|
||||
- Loader UI: Speed issues of loader with sync server [\#3200](https://github.com/pypeclub/OpenPype/pull/3200)
|
||||
- Backport of fix for attaching renders to subsets [\#3195](https://github.com/pypeclub/OpenPype/pull/3195)
|
||||
|
||||
**🐛 Bug fixes**
|
||||
|
|
@ -114,31 +142,14 @@
|
|||
|
||||
- Nuke: render instance with subset name filtered overrides \(3.9.x\) [\#3125](https://github.com/pypeclub/OpenPype/pull/3125)
|
||||
|
||||
**🚀 Enhancements**
|
||||
|
||||
- TVPaint: Match renderlayer key with other hosts [\#3109](https://github.com/pypeclub/OpenPype/pull/3109)
|
||||
|
||||
**🐛 Bug fixes**
|
||||
|
||||
- General: Missing version on headless mode crash properly [\#3136](https://github.com/pypeclub/OpenPype/pull/3136)
|
||||
- TVPaint: Composite layers in reversed order [\#3134](https://github.com/pypeclub/OpenPype/pull/3134)
|
||||
- General: Python 3 compatibility in queries [\#3111](https://github.com/pypeclub/OpenPype/pull/3111)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Ftrack: AssetVersion status on publish [\#3114](https://github.com/pypeclub/OpenPype/pull/3114)
|
||||
- renderman support for 3.9.x [\#3107](https://github.com/pypeclub/OpenPype/pull/3107)
|
||||
|
||||
## [3.9.5](https://github.com/pypeclub/OpenPype/tree/3.9.5) (2022-04-25)
|
||||
|
||||
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.10.0-nightly.2...3.9.5)
|
||||
|
||||
**🐛 Bug fixes**
|
||||
|
||||
- Ftrack: Update Create Folders action [\#3092](https://github.com/pypeclub/OpenPype/pull/3092)
|
||||
- General: Extract review sequence is not converted with same names [\#3075](https://github.com/pypeclub/OpenPype/pull/3075)
|
||||
- Webpublisher: Use variant value [\#3072](https://github.com/pypeclub/OpenPype/pull/3072)
|
||||
|
||||
## [3.9.4](https://github.com/pypeclub/OpenPype/tree/3.9.4) (2022-04-15)
|
||||
|
||||
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.9.4-nightly.2...3.9.4)
|
||||
|
|
|
|||
|
|
@ -35,7 +35,11 @@ function Client() {
|
|||
self.pack = function(num) {
|
||||
var ascii='';
|
||||
for (var i = 3; i >= 0; i--) {
|
||||
ascii += String.fromCharCode((num >> (8 * i)) & 255);
|
||||
var hex = ((num >> (8 * i)) & 255).toString(16);
|
||||
if (hex.length < 2){
|
||||
ascii += "0";
|
||||
}
|
||||
ascii += hex;
|
||||
}
|
||||
return ascii;
|
||||
};
|
||||
|
|
@ -279,19 +283,22 @@ function Client() {
|
|||
};
|
||||
|
||||
self._send = function(message) {
|
||||
var data = new QByteArray();
|
||||
var outstr = new QDataStream(data, QIODevice.WriteOnly);
|
||||
outstr.writeInt(0);
|
||||
data.append('UTF-8');
|
||||
outstr.device().seek(0);
|
||||
outstr.writeInt(data.size() - 4);
|
||||
var codec = QTextCodec.codecForUtfText(data);
|
||||
var msg = codec.fromUnicode(message);
|
||||
var l = msg.size();
|
||||
var coded = new QByteArray('AH').append(self.pack(l));
|
||||
coded = coded.append(msg);
|
||||
self.socket.write(new QByteArray(coded));
|
||||
self.logDebug('Sent.');
|
||||
/** Harmony 21.1 doesn't have QDataStream anymore.
|
||||
|
||||
This means we aren't able to write bytes into QByteArray so we had
|
||||
modify how content lenght is sent do the server.
|
||||
Content lenght is sent as string of 8 char convertible into integer
|
||||
(instead of 0x00000001[4 bytes] > "000000001"[8 bytes]) */
|
||||
var codec_name = new QByteArray().append("UTF-8");
|
||||
|
||||
var codec = QTextCodec.codecForName(codec_name);
|
||||
var msg = codec.fromUnicode(message);
|
||||
var l = msg.size();
|
||||
var header = new QByteArray().append('AH').append(self.pack(l));
|
||||
var coded = msg.prepend(header);
|
||||
|
||||
self.socket.write(coded);
|
||||
self.logDebug('Sent.');
|
||||
};
|
||||
|
||||
self.waitForLock = function() {
|
||||
|
|
@ -351,7 +358,14 @@ function start() {
|
|||
app.avalonClient = new Client();
|
||||
app.avalonClient.socket.connectToHost(host, port);
|
||||
}
|
||||
var menuBar = QApplication.activeWindow().menuBar();
|
||||
var mainWindow = null;
|
||||
var widgets = QApplication.topLevelWidgets();
|
||||
for (var i = 0 ; i < widgets.length; i++) {
|
||||
if (widgets[i] instanceof QMainWindow){
|
||||
mainWindow = widgets[i];
|
||||
}
|
||||
}
|
||||
var menuBar = mainWindow.menuBar();
|
||||
var actions = menuBar.actions();
|
||||
app.avalonMenu = null;
|
||||
|
||||
|
|
|
|||
|
|
@ -88,21 +88,25 @@ class Server(threading.Thread):
|
|||
"""
|
||||
current_time = time.time()
|
||||
while True:
|
||||
|
||||
self.log.info("wait ttt")
|
||||
# Receive the data in small chunks and retransmit it
|
||||
request = None
|
||||
header = self.connection.recv(6)
|
||||
header = self.connection.recv(10)
|
||||
if len(header) == 0:
|
||||
# null data received, socket is closing.
|
||||
self.log.info(f"[{self.timestamp()}] Connection closing.")
|
||||
break
|
||||
|
||||
if header[0:2] != b"AH":
|
||||
self.log.error("INVALID HEADER")
|
||||
length = struct.unpack(">I", header[2:])[0]
|
||||
content_length_str = header[2:].decode()
|
||||
|
||||
length = int(content_length_str, 16)
|
||||
data = self.connection.recv(length)
|
||||
while (len(data) < length):
|
||||
# we didn't received everything in first try, lets wait for
|
||||
# all data.
|
||||
self.log.info("loop")
|
||||
time.sleep(0.1)
|
||||
if self.connection is None:
|
||||
self.log.error(f"[{self.timestamp()}] "
|
||||
|
|
@ -113,7 +117,7 @@ class Server(threading.Thread):
|
|||
break
|
||||
|
||||
data += self.connection.recv(length - len(data))
|
||||
|
||||
self.log.debug("data:: {} {}".format(data, type(data)))
|
||||
self.received += data.decode("utf-8")
|
||||
pretty = self._pretty(self.received)
|
||||
self.log.debug(
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
Host specific functions where host api is connected
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
from copy import deepcopy
|
||||
import os
|
||||
import re
|
||||
|
|
@ -171,7 +170,7 @@ def get_track_items(
|
|||
|
||||
# get selected track items or all in active sequence
|
||||
if selection:
|
||||
with contextlib.suppress(AttributeError):
|
||||
try:
|
||||
for track_item in selection:
|
||||
log.info("___ track_item: {}".format(track_item))
|
||||
# make sure only trackitems are selected
|
||||
|
|
@ -188,6 +187,8 @@ def get_track_items(
|
|||
):
|
||||
log.info("___ valid trackitem: {}".format(track_item))
|
||||
return_list.append(track_item)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
# collect all available active sequence track items
|
||||
if not return_list:
|
||||
|
|
|
|||
|
|
@ -1093,6 +1093,11 @@ class RenderProductsRenderman(ARenderProducts):
|
|||
if not enabled:
|
||||
continue
|
||||
|
||||
# Skip display types not producing any file output.
|
||||
# Is there a better way to do it?
|
||||
if not display_types.get(display["driverNode"]["type"]):
|
||||
continue
|
||||
|
||||
aov_name = name
|
||||
if aov_name == "rmanDefaultDisplay":
|
||||
aov_name = "beauty"
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ class ImagePlaneLoader(load.LoaderPlugin):
|
|||
|
||||
families = ["image", "plate", "render"]
|
||||
label = "Load imagePlane"
|
||||
representations = ["mov", "exr", "preview", "png"]
|
||||
representations = ["mov", "exr", "preview", "png", "jpg"]
|
||||
icon = "image"
|
||||
color = "orange"
|
||||
|
||||
|
|
|
|||
20
openpype/hosts/maya/plugins/publish/collect_fbx_camera.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from maya import cmds # noqa
|
||||
import pyblish.api
|
||||
|
||||
|
||||
class CollectFbxCamera(pyblish.api.InstancePlugin):
|
||||
"""Collect Camera for FBX export."""
|
||||
|
||||
order = pyblish.api.CollectorOrder + 0.2
|
||||
label = "Collect Camera for FBX export"
|
||||
families = ["camera"]
|
||||
|
||||
def process(self, instance):
|
||||
if not instance.data.get("families"):
|
||||
instance.data["families"] = []
|
||||
|
||||
if "fbx" not in instance.data["families"]:
|
||||
instance.data["families"].append("fbx")
|
||||
|
||||
instance.data["cameras"] = True
|
||||
|
|
@ -22,10 +22,46 @@ RENDERER_NODE_TYPES = [
|
|||
# redshift
|
||||
"RedshiftMeshParameters"
|
||||
]
|
||||
|
||||
SHAPE_ATTRS = set(SHAPE_ATTRS)
|
||||
|
||||
|
||||
def get_pxr_multitexture_file_attrs(node):
|
||||
attrs = []
|
||||
for i in range(9):
|
||||
if cmds.attributeQuery("filename{}".format(i), node=node, ex=True):
|
||||
file = cmds.getAttr("{}.filename{}".format(node, i))
|
||||
if file:
|
||||
attrs.append("filename{}".format(i))
|
||||
return attrs
|
||||
|
||||
|
||||
FILE_NODES = {
|
||||
"file": "fileTextureName",
|
||||
|
||||
"aiImage": "filename",
|
||||
|
||||
"RedshiftNormalMap": "text0",
|
||||
|
||||
"PxrBump": "filename",
|
||||
"PxrNormalMap": "filename",
|
||||
"PxrMultiTexture": get_pxr_multitexture_file_attrs,
|
||||
"PxrPtexture": "filename",
|
||||
"PxrTexture": "filename"
|
||||
}
|
||||
|
||||
|
||||
def get_attributes(dictionary, attr, node=None):
|
||||
# type: (dict, str, str) -> list
|
||||
if callable(dictionary[attr]):
|
||||
val = dictionary[attr](node)
|
||||
else:
|
||||
val = dictionary.get(attr, [])
|
||||
|
||||
if not isinstance(val, list):
|
||||
return [val]
|
||||
return val
|
||||
|
||||
|
||||
def get_look_attrs(node):
|
||||
"""Returns attributes of a node that are important for the look.
|
||||
|
||||
|
|
@ -51,15 +87,14 @@ def get_look_attrs(node):
|
|||
if cmds.objectType(node, isAType="shape"):
|
||||
attrs = cmds.listAttr(node, changedSinceFileOpen=True) or []
|
||||
for attr in attrs:
|
||||
if attr in SHAPE_ATTRS:
|
||||
if attr in SHAPE_ATTRS or \
|
||||
attr not in SHAPE_ATTRS and attr.startswith('ai'):
|
||||
result.append(attr)
|
||||
elif attr.startswith('ai'):
|
||||
result.append(attr)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def node_uses_image_sequence(node):
|
||||
def node_uses_image_sequence(node, node_path):
|
||||
# type: (str) -> bool
|
||||
"""Return whether file node uses an image sequence or single image.
|
||||
|
||||
Determine if a node uses an image sequence or just a single image,
|
||||
|
|
@ -74,12 +109,15 @@ def node_uses_image_sequence(node):
|
|||
"""
|
||||
|
||||
# useFrameExtension indicates an explicit image sequence
|
||||
node_path = get_file_node_path(node).lower()
|
||||
|
||||
# The following tokens imply a sequence
|
||||
patterns = ["<udim>", "<tile>", "<uvtile>", "u<u>_v<v>", "<frame0"]
|
||||
patterns = ["<udim>", "<tile>", "<uvtile>",
|
||||
"u<u>_v<v>", "<frame0", "<f4>"]
|
||||
try:
|
||||
use_frame_extension = cmds.getAttr('%s.useFrameExtension' % node)
|
||||
except ValueError:
|
||||
use_frame_extension = False
|
||||
|
||||
return (cmds.getAttr('%s.useFrameExtension' % node) or
|
||||
return (use_frame_extension or
|
||||
any(pattern in node_path for pattern in patterns))
|
||||
|
||||
|
||||
|
|
@ -137,14 +175,15 @@ def seq_to_glob(path):
|
|||
return path
|
||||
|
||||
|
||||
def get_file_node_path(node):
|
||||
def get_file_node_paths(node):
|
||||
# type: (str) -> list
|
||||
"""Get the file path used by a Maya file node.
|
||||
|
||||
Args:
|
||||
node (str): Name of the Maya file node
|
||||
|
||||
Returns:
|
||||
str: the file path in use
|
||||
list: the file paths in use
|
||||
|
||||
"""
|
||||
# if the path appears to be sequence, use computedFileTextureNamePattern,
|
||||
|
|
@ -163,15 +202,20 @@ def get_file_node_path(node):
|
|||
"<uvtile>"]
|
||||
lower = texture_pattern.lower()
|
||||
if any(pattern in lower for pattern in patterns):
|
||||
return texture_pattern
|
||||
return [texture_pattern]
|
||||
|
||||
if cmds.nodeType(node) == 'aiImage':
|
||||
return cmds.getAttr('{0}.filename'.format(node))
|
||||
if cmds.nodeType(node) == 'RedshiftNormalMap':
|
||||
return cmds.getAttr('{}.tex0'.format(node))
|
||||
try:
|
||||
file_attributes = get_attributes(
|
||||
FILE_NODES, cmds.nodeType(node), node)
|
||||
except AttributeError:
|
||||
file_attributes = "fileTextureName"
|
||||
|
||||
# otherwise use fileTextureName
|
||||
return cmds.getAttr('{0}.fileTextureName'.format(node))
|
||||
files = []
|
||||
for file_attr in file_attributes:
|
||||
if cmds.attributeQuery(file_attr, node=node, exists=True):
|
||||
files.append(cmds.getAttr("{}.{}".format(node, file_attr)))
|
||||
|
||||
return files
|
||||
|
||||
|
||||
def get_file_node_files(node):
|
||||
|
|
@ -185,16 +229,21 @@ def get_file_node_files(node):
|
|||
list: List of full file paths.
|
||||
|
||||
"""
|
||||
paths = get_file_node_paths(node)
|
||||
sequences = []
|
||||
replaces = []
|
||||
for index, path in enumerate(paths):
|
||||
if node_uses_image_sequence(node, path):
|
||||
glob_pattern = seq_to_glob(path)
|
||||
sequences.extend(glob.glob(glob_pattern))
|
||||
replaces.append(index)
|
||||
|
||||
path = get_file_node_path(node)
|
||||
path = cmds.workspace(expandName=path)
|
||||
if node_uses_image_sequence(node):
|
||||
glob_pattern = seq_to_glob(path)
|
||||
return glob.glob(glob_pattern)
|
||||
elif os.path.exists(path):
|
||||
return [path]
|
||||
else:
|
||||
return []
|
||||
for index in replaces:
|
||||
paths.pop(index)
|
||||
|
||||
paths.extend(sequences)
|
||||
|
||||
return [p for p in paths if os.path.exists(p)]
|
||||
|
||||
|
||||
class CollectLook(pyblish.api.InstancePlugin):
|
||||
|
|
@ -238,13 +287,13 @@ class CollectLook(pyblish.api.InstancePlugin):
|
|||
"for %s" % instance.data['name'])
|
||||
|
||||
# Discover related object sets
|
||||
self.log.info("Gathering sets..")
|
||||
self.log.info("Gathering sets ...")
|
||||
sets = self.collect_sets(instance)
|
||||
|
||||
# Lookup set (optimization)
|
||||
instance_lookup = set(cmds.ls(instance, long=True))
|
||||
|
||||
self.log.info("Gathering set relations..")
|
||||
self.log.info("Gathering set relations ...")
|
||||
# Ensure iteration happen in a list so we can remove keys from the
|
||||
# dict within the loop
|
||||
|
||||
|
|
@ -326,7 +375,10 @@ class CollectLook(pyblish.api.InstancePlugin):
|
|||
"volumeShader",
|
||||
"displacementShader",
|
||||
"aiSurfaceShader",
|
||||
"aiVolumeShader"]
|
||||
"aiVolumeShader",
|
||||
"rman__surface",
|
||||
"rman__displacement"
|
||||
]
|
||||
if look_sets:
|
||||
materials = []
|
||||
|
||||
|
|
@ -374,15 +426,17 @@ class CollectLook(pyblish.api.InstancePlugin):
|
|||
or []
|
||||
)
|
||||
|
||||
files = cmds.ls(history, type="file", long=True)
|
||||
files.extend(cmds.ls(history, type="aiImage", long=True))
|
||||
files.extend(cmds.ls(history, type="RedshiftNormalMap", long=True))
|
||||
all_supported_nodes = FILE_NODES.keys()
|
||||
files = []
|
||||
for node_type in all_supported_nodes:
|
||||
files.extend(cmds.ls(history, type=node_type, long=True))
|
||||
|
||||
self.log.info("Collected file nodes:\n{}".format(files))
|
||||
# Collect textures if any file nodes are found
|
||||
instance.data["resources"] = []
|
||||
for n in files:
|
||||
instance.data["resources"].append(self.collect_resource(n))
|
||||
for res in self.collect_resources(n):
|
||||
instance.data["resources"].append(res)
|
||||
|
||||
self.log.info("Collected resources: {}".format(instance.data["resources"]))
|
||||
|
||||
|
|
@ -502,7 +556,7 @@ class CollectLook(pyblish.api.InstancePlugin):
|
|||
|
||||
return attributes
|
||||
|
||||
def collect_resource(self, node):
|
||||
def collect_resources(self, node):
|
||||
"""Collect the link to the file(s) used (resource)
|
||||
Args:
|
||||
node (str): name of the node
|
||||
|
|
@ -510,68 +564,69 @@ class CollectLook(pyblish.api.InstancePlugin):
|
|||
Returns:
|
||||
dict
|
||||
"""
|
||||
|
||||
self.log.debug("processing: {}".format(node))
|
||||
if cmds.nodeType(node) not in ["file", "aiImage", "RedshiftNormalMap"]:
|
||||
all_supported_nodes = FILE_NODES.keys()
|
||||
if cmds.nodeType(node) not in all_supported_nodes:
|
||||
self.log.error(
|
||||
"Unsupported file node: {}".format(cmds.nodeType(node)))
|
||||
raise AssertionError("Unsupported file node")
|
||||
|
||||
if cmds.nodeType(node) == 'file':
|
||||
self.log.debug(" - file node")
|
||||
attribute = "{}.fileTextureName".format(node)
|
||||
computed_attribute = "{}.computedFileTextureNamePattern".format(node)
|
||||
elif cmds.nodeType(node) == 'aiImage':
|
||||
self.log.debug("aiImage node")
|
||||
attribute = "{}.filename".format(node)
|
||||
computed_attribute = attribute
|
||||
elif cmds.nodeType(node) == 'RedshiftNormalMap':
|
||||
self.log.debug("RedshiftNormalMap node")
|
||||
attribute = "{}.tex0".format(node)
|
||||
computed_attribute = attribute
|
||||
self.log.debug(" - got {}".format(cmds.nodeType(node)))
|
||||
|
||||
source = cmds.getAttr(attribute)
|
||||
self.log.info(" - file source: {}".format(source))
|
||||
color_space_attr = "{}.colorSpace".format(node)
|
||||
try:
|
||||
color_space = cmds.getAttr(color_space_attr)
|
||||
except ValueError:
|
||||
# node doesn't have colorspace attribute
|
||||
color_space = "Raw"
|
||||
# Compare with the computed file path, e.g. the one with the <UDIM>
|
||||
# pattern in it, to generate some logging information about this
|
||||
# difference
|
||||
# computed_attribute = "{}.computedFileTextureNamePattern".format(node)
|
||||
computed_source = cmds.getAttr(computed_attribute)
|
||||
if source != computed_source:
|
||||
self.log.debug("Detected computed file pattern difference "
|
||||
"from original pattern: {0} "
|
||||
"({1} -> {2})".format(node,
|
||||
source,
|
||||
computed_source))
|
||||
attributes = get_attributes(FILE_NODES, cmds.nodeType(node), node)
|
||||
for attribute in attributes:
|
||||
source = cmds.getAttr("{}.{}".format(
|
||||
node,
|
||||
attribute
|
||||
))
|
||||
computed_attribute = "{}.{}".format(node, attribute)
|
||||
if attribute == "fileTextureName":
|
||||
computed_attribute = node + ".computedFileTextureNamePattern"
|
||||
|
||||
# We replace backslashes with forward slashes because V-Ray
|
||||
# can't handle the UDIM files with the backslashes in the
|
||||
# paths as the computed patterns
|
||||
source = source.replace("\\", "/")
|
||||
self.log.info(" - file source: {}".format(source))
|
||||
color_space_attr = "{}.colorSpace".format(node)
|
||||
try:
|
||||
color_space = cmds.getAttr(color_space_attr)
|
||||
except ValueError:
|
||||
# node doesn't have colorspace attribute
|
||||
color_space = "Raw"
|
||||
# Compare with the computed file path, e.g. the one with
|
||||
# the <UDIM> pattern in it, to generate some logging information
|
||||
# about this difference
|
||||
computed_source = cmds.getAttr(computed_attribute)
|
||||
if source != computed_source:
|
||||
self.log.debug("Detected computed file pattern difference "
|
||||
"from original pattern: {0} "
|
||||
"({1} -> {2})".format(node,
|
||||
source,
|
||||
computed_source))
|
||||
|
||||
files = get_file_node_files(node)
|
||||
if len(files) == 0:
|
||||
self.log.error("No valid files found from node `%s`" % node)
|
||||
# We replace backslashes with forward slashes because V-Ray
|
||||
# can't handle the UDIM files with the backslashes in the
|
||||
# paths as the computed patterns
|
||||
source = source.replace("\\", "/")
|
||||
|
||||
self.log.info("collection of resource done:")
|
||||
self.log.info(" - node: {}".format(node))
|
||||
self.log.info(" - attribute: {}".format(attribute))
|
||||
self.log.info(" - source: {}".format(source))
|
||||
self.log.info(" - file: {}".format(files))
|
||||
self.log.info(" - color space: {}".format(color_space))
|
||||
files = get_file_node_files(node)
|
||||
if len(files) == 0:
|
||||
self.log.error("No valid files found from node `%s`" % node)
|
||||
|
||||
# Define the resource
|
||||
return {"node": node,
|
||||
"attribute": attribute,
|
||||
self.log.info("collection of resource done:")
|
||||
self.log.info(" - node: {}".format(node))
|
||||
self.log.info(" - attribute: {}".format(attribute))
|
||||
self.log.info(" - source: {}".format(source))
|
||||
self.log.info(" - file: {}".format(files))
|
||||
self.log.info(" - color space: {}".format(color_space))
|
||||
|
||||
# Define the resource
|
||||
yield {
|
||||
"node": node,
|
||||
# here we are passing not only attribute, but with node again
|
||||
# this should be simplified and changed extractor.
|
||||
"attribute": "{}.{}".format(node, attribute),
|
||||
"source": source, # required for resources
|
||||
"files": files,
|
||||
"color_space": color_space} # required for resources
|
||||
"color_space": color_space
|
||||
} # required for resources
|
||||
|
||||
|
||||
class CollectModelRenderSets(CollectLook):
|
||||
|
|
|
|||
|
|
@ -339,9 +339,15 @@ class CollectMayaRender(pyblish.api.ContextPlugin):
|
|||
"source": filepath,
|
||||
"expectedFiles": full_exp_files,
|
||||
"publishRenderMetadataFolder": common_publish_meta_path,
|
||||
"resolutionWidth": cmds.getAttr("defaultResolution.width"),
|
||||
"resolutionHeight": cmds.getAttr("defaultResolution.height"),
|
||||
"pixelAspect": cmds.getAttr("defaultResolution.pixelAspect"),
|
||||
"resolutionWidth": lib.get_attr_in_layer(
|
||||
"defaultResolution.height", layer=layer_name
|
||||
),
|
||||
"resolutionHeight": lib.get_attr_in_layer(
|
||||
"defaultResolution.width", layer=layer_name
|
||||
),
|
||||
"pixelAspect": lib.get_attr_in_layer(
|
||||
"defaultResolution.pixelAspect", layer=layer_name
|
||||
),
|
||||
"tileRendering": render_instance.data.get("tileRendering") or False, # noqa: E501
|
||||
"tilesX": render_instance.data.get("tilesX") or 2,
|
||||
"tilesY": render_instance.data.get("tilesY") or 2,
|
||||
|
|
|
|||
|
|
@ -124,9 +124,15 @@ class CollectVrayScene(pyblish.api.InstancePlugin):
|
|||
# Add source to allow tracing back to the scene from
|
||||
# which was submitted originally
|
||||
"source": context.data["currentFile"].replace("\\", "/"),
|
||||
"resolutionWidth": cmds.getAttr("defaultResolution.width"),
|
||||
"resolutionHeight": cmds.getAttr("defaultResolution.height"),
|
||||
"pixelAspect": cmds.getAttr("defaultResolution.pixelAspect"),
|
||||
"resolutionWidth": lib.get_attr_in_layer(
|
||||
"defaultResolution.height", layer=layer_name
|
||||
),
|
||||
"resolutionHeight": lib.get_attr_in_layer(
|
||||
"defaultResolution.width", layer=layer_name
|
||||
),
|
||||
"pixelAspect": lib.get_attr_in_layer(
|
||||
"defaultResolution.pixelAspect", layer=layer_name
|
||||
),
|
||||
"priority": instance.data.get("priority"),
|
||||
"useMultipleSceneFiles": instance.data.get(
|
||||
"vraySceneMultipleFiles")
|
||||
|
|
|
|||
|
|
@ -372,10 +372,12 @@ class ExtractLook(openpype.api.Extractor):
|
|||
|
||||
if mode == COPY:
|
||||
transfers.append((source, destination))
|
||||
self.log.info('copying')
|
||||
self.log.info('file will be copied {} -> {}'.format(
|
||||
source, destination))
|
||||
elif mode == HARDLINK:
|
||||
hardlinks.append((source, destination))
|
||||
self.log.info('hardlinking')
|
||||
self.log.info('file will be hardlinked {} -> {}'.format(
|
||||
source, destination))
|
||||
|
||||
# Store the hashes from hash to destination to include in the
|
||||
# database
|
||||
|
|
|
|||
|
|
@ -373,7 +373,7 @@ def add_write_node_legacy(name, **kwarg):
|
|||
Returns:
|
||||
node (obj): nuke write node
|
||||
"""
|
||||
frame_range = kwarg.get("use_range_limit", None)
|
||||
use_range_limit = kwarg.get("use_range_limit", None)
|
||||
|
||||
w = nuke.createNode(
|
||||
"Write",
|
||||
|
|
@ -391,10 +391,10 @@ def add_write_node_legacy(name, **kwarg):
|
|||
log.debug(e)
|
||||
continue
|
||||
|
||||
if frame_range:
|
||||
if use_range_limit:
|
||||
w["use_limit"].setValue(True)
|
||||
w["first"].setValue(frame_range[0])
|
||||
w["last"].setValue(frame_range[1])
|
||||
w["first"].setValue(kwarg["frame_range"][0])
|
||||
w["last"].setValue(kwarg["frame_range"][1])
|
||||
|
||||
return w
|
||||
|
||||
|
|
@ -409,7 +409,7 @@ def add_write_node(name, file_path, knobs, **kwarg):
|
|||
Returns:
|
||||
node (obj): nuke write node
|
||||
"""
|
||||
frame_range = kwarg.get("use_range_limit", None)
|
||||
use_range_limit = kwarg.get("use_range_limit", None)
|
||||
|
||||
w = nuke.createNode(
|
||||
"Write",
|
||||
|
|
@ -420,10 +420,10 @@ def add_write_node(name, file_path, knobs, **kwarg):
|
|||
# finally add knob overrides
|
||||
set_node_knobs_from_settings(w, knobs, **kwarg)
|
||||
|
||||
if frame_range:
|
||||
if use_range_limit:
|
||||
w["use_limit"].setValue(True)
|
||||
w["first"].setValue(frame_range[0])
|
||||
w["last"].setValue(frame_range[1])
|
||||
w["first"].setValue(kwarg["frame_range"][0])
|
||||
w["last"].setValue(kwarg["frame_range"][1])
|
||||
|
||||
return w
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@ from .lib import (
|
|||
maintained_selection,
|
||||
set_avalon_knob_data,
|
||||
add_publish_knob,
|
||||
get_nuke_imageio_settings
|
||||
get_nuke_imageio_settings,
|
||||
set_node_knobs_from_settings
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -497,16 +498,7 @@ class ExporterReviewMov(ExporterReview):
|
|||
add_tags.append("reformated")
|
||||
|
||||
rf_node = nuke.createNode("Reformat")
|
||||
for kn_conf in reformat_node_config:
|
||||
_type = kn_conf["type"]
|
||||
k_name = str(kn_conf["name"])
|
||||
k_value = kn_conf["value"]
|
||||
|
||||
# to remove unicode as nuke doesn't like it
|
||||
if _type == "string":
|
||||
k_value = str(kn_conf["value"])
|
||||
|
||||
rf_node[k_name].setValue(k_value)
|
||||
set_node_knobs_from_settings(rf_node, reformat_node_config)
|
||||
|
||||
# connect
|
||||
rf_node.setInput(0, self.previous_node)
|
||||
|
|
|
|||
|
|
@ -27,6 +27,10 @@ class CreateWritePrerender(plugin.AbstractWriteRender):
|
|||
# add fpath_template
|
||||
write_data["fpath_template"] = self.fpath_template
|
||||
write_data["use_range_limit"] = self.use_range_limit
|
||||
write_data["frame_range"] = (
|
||||
nuke.root()["first_frame"].value(),
|
||||
nuke.root()["last_frame"].value()
|
||||
)
|
||||
|
||||
if not self.is_legacy():
|
||||
return create_write_node(
|
||||
|
|
|
|||
|
|
@ -15,13 +15,13 @@ from openpype.hosts.nuke.api import (
|
|||
|
||||
class AlembicModelLoader(load.LoaderPlugin):
|
||||
"""
|
||||
This will load alembic model into script.
|
||||
This will load alembic model or anim into script.
|
||||
"""
|
||||
|
||||
families = ["model"]
|
||||
families = ["model", "pointcache", "animation"]
|
||||
representations = ["abc"]
|
||||
|
||||
label = "Load Alembic Model"
|
||||
label = "Load Alembic"
|
||||
icon = "cube"
|
||||
color = "orange"
|
||||
node_color = "0x4ecd91ff"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import nuke
|
||||
import os
|
||||
|
||||
from openpype.api import Logger
|
||||
from openpype.pipeline import install_host
|
||||
|
|
@ -9,6 +10,7 @@ from openpype.hosts.nuke.api.lib import (
|
|||
WorkfileSettings,
|
||||
dirmap_file_name_filter
|
||||
)
|
||||
from openpype.settings import get_project_settings
|
||||
|
||||
log = Logger.get_logger(__name__)
|
||||
|
||||
|
|
@ -28,3 +30,32 @@ nuke.addOnScriptLoad(WorkfileSettings().set_context_settings)
|
|||
nuke.addFilenameFilter(dirmap_file_name_filter)
|
||||
|
||||
log.info('Automatic syncing of write file knob to script version')
|
||||
|
||||
|
||||
def add_scripts_menu():
|
||||
try:
|
||||
from scriptsmenu import launchfornuke
|
||||
except ImportError:
|
||||
log.warning(
|
||||
"Skipping studio.menu install, because "
|
||||
"'scriptsmenu' module seems unavailable."
|
||||
)
|
||||
return
|
||||
|
||||
# load configuration of custom menu
|
||||
project_settings = get_project_settings(os.getenv("AVALON_PROJECT"))
|
||||
config = project_settings["nuke"]["scriptsmenu"]["definition"]
|
||||
_menu = project_settings["nuke"]["scriptsmenu"]["name"]
|
||||
|
||||
if not config:
|
||||
log.warning("Skipping studio menu, no definition found.")
|
||||
return
|
||||
|
||||
# run the launcher for Maya menu
|
||||
studio_menu = launchfornuke.main(title=_menu.title())
|
||||
|
||||
# apply configuration
|
||||
studio_menu.build_from_configuration(studio_menu, config)
|
||||
|
||||
|
||||
add_scripts_menu()
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ class CollectColorCodedInstances(pyblish.api.ContextPlugin):
|
|||
"variant": variant,
|
||||
"family": resolved_family,
|
||||
"task": task_name,
|
||||
"layer": layer.name
|
||||
"layer": layer.clean_name
|
||||
}
|
||||
|
||||
subset = resolved_subset_template.format(
|
||||
|
|
|
|||
|
|
@ -165,12 +165,12 @@ def parse_group_data(data):
|
|||
if not group_raw:
|
||||
continue
|
||||
|
||||
parts = group_raw.split(" ")
|
||||
parts = group_raw.split("|")
|
||||
# Check for length and concatenate 2 last items until length match
|
||||
# - this happens if name contain spaces
|
||||
while len(parts) > 6:
|
||||
last_item = parts.pop(-1)
|
||||
parts[-1] = " ".join([parts[-1], last_item])
|
||||
parts[-1] = "|".join([parts[-1], last_item])
|
||||
clip_id, group_id, red, green, blue, name = parts
|
||||
|
||||
group = {
|
||||
|
|
@ -201,11 +201,16 @@ def get_groups_data(communicator=None):
|
|||
george_script_lines = (
|
||||
# Variable containing full path to output file
|
||||
"output_path = \"{}\"".format(output_filepath),
|
||||
"loop = 1",
|
||||
"FOR idx = 1 TO 12",
|
||||
"empty = 0",
|
||||
# Loop over 100 groups
|
||||
"FOR idx = 1 TO 100",
|
||||
# Receive information about groups
|
||||
"tv_layercolor \"getcolor\" 0 idx",
|
||||
"tv_writetextfile \"strict\" \"append\" '\"'output_path'\"' result",
|
||||
"END"
|
||||
"PARSE result clip_id group_index c_red c_green c_blue group_name",
|
||||
# Create and add line to output file
|
||||
"line = clip_id'|'group_index'|'c_red'|'c_green'|'c_blue'|'group_name",
|
||||
"tv_writetextfile \"strict\" \"append\" '\"'output_path'\"' line",
|
||||
"END",
|
||||
)
|
||||
george_script = "\n".join(george_script_lines)
|
||||
execute_george_through_file(george_script, communicator)
|
||||
|
|
|
|||
|
|
@ -1,15 +1,19 @@
|
|||
import os
|
||||
import openpype.hosts
|
||||
from openpype.lib.applications import Application
|
||||
|
||||
|
||||
def add_implementation_envs(env, _app):
|
||||
def add_implementation_envs(env: dict, _app: Application) -> None:
|
||||
"""Modify environments to contain all required for implementation."""
|
||||
# Set OPENPYPE_UNREAL_PLUGIN required for Unreal implementation
|
||||
|
||||
ue_plugin = "UE_5.0" if _app.name[:1] == "5" else "UE_4.7"
|
||||
unreal_plugin_path = os.path.join(
|
||||
os.path.dirname(os.path.abspath(openpype.hosts.__file__)),
|
||||
"unreal", "integration"
|
||||
"unreal", "integration", ue_plugin
|
||||
)
|
||||
env["OPENPYPE_UNREAL_PLUGIN"] = unreal_plugin_path
|
||||
if not env.get("OPENPYPE_UNREAL_PLUGIN"):
|
||||
env["OPENPYPE_UNREAL_PLUGIN"] = unreal_plugin_path
|
||||
|
||||
# Set default environments if are not set via settings
|
||||
defaults = {
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ class UnrealPrelaunchHook(PreLaunchHook):
|
|||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.signature = "( {} )".format(self.__class__.__name__)
|
||||
self.signature = f"( {self.__class__.__name__} )"
|
||||
|
||||
def _get_work_filename(self):
|
||||
# Use last workfile if was found
|
||||
|
|
@ -71,7 +71,7 @@ class UnrealPrelaunchHook(PreLaunchHook):
|
|||
if int(engine_version.split(".")[0]) < 4 and \
|
||||
int(engine_version.split(".")[1]) < 26:
|
||||
raise ApplicationLaunchFailed((
|
||||
f"{self.signature} Old unsupported version of UE4 "
|
||||
f"{self.signature} Old unsupported version of UE "
|
||||
f"detected - {engine_version}"))
|
||||
except ValueError:
|
||||
# there can be string in minor version and in that case
|
||||
|
|
@ -99,18 +99,19 @@ class UnrealPrelaunchHook(PreLaunchHook):
|
|||
f"character ({unreal_project_name}). Appending 'P'"
|
||||
))
|
||||
unreal_project_name = f"P{unreal_project_name}"
|
||||
unreal_project_filename = f'{unreal_project_name}.uproject'
|
||||
|
||||
project_path = Path(os.path.join(workdir, unreal_project_name))
|
||||
|
||||
self.log.info((
|
||||
f"{self.signature} requested UE4 version: "
|
||||
f"{self.signature} requested UE version: "
|
||||
f"[ {engine_version} ]"
|
||||
))
|
||||
|
||||
detected = unreal_lib.get_engine_versions(self.launch_context.env)
|
||||
detected_str = ', '.join(detected.keys()) or 'none'
|
||||
self.log.info((
|
||||
f"{self.signature} detected UE4 versions: "
|
||||
f"{self.signature} detected UE versions: "
|
||||
f"[ {detected_str} ]"
|
||||
))
|
||||
if not detected:
|
||||
|
|
@ -123,10 +124,10 @@ class UnrealPrelaunchHook(PreLaunchHook):
|
|||
f"detected [ {engine_version} ]"
|
||||
))
|
||||
|
||||
ue4_path = unreal_lib.get_editor_executable_path(
|
||||
Path(detected[engine_version]))
|
||||
ue_path = unreal_lib.get_editor_executable_path(
|
||||
Path(detected[engine_version]), engine_version)
|
||||
|
||||
self.launch_context.launch_args = [ue4_path.as_posix()]
|
||||
self.launch_context.launch_args = [ue_path.as_posix()]
|
||||
project_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
project_file = project_path / unreal_project_filename
|
||||
|
|
@ -138,6 +139,11 @@ class UnrealPrelaunchHook(PreLaunchHook):
|
|||
))
|
||||
# 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]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# OpenPype Unreal Integration plugin
|
||||
# OpenPype Unreal Integration plugin - UE 4.x
|
||||
|
||||
This is plugin for Unreal Editor, creating menu for [OpenPype](https://github.com/getavalon) tools to run.
|
||||
|
||||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 84 KiB |
35
openpype/hosts/unreal/integration/UE_5.0/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# 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
|
||||
|
||||
/Binaries
|
||||
/Intermediate
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import unreal
|
||||
|
||||
openpype_detected = True
|
||||
try:
|
||||
from openpype.pipeline import install_host
|
||||
from openpype.hosts.unreal import api as openpype_host
|
||||
except ImportError as exc:
|
||||
openpype_host = None
|
||||
openpype_detected = False
|
||||
unreal.log_error("OpenPype: cannot load OpenPype [ {} ]".format(exc))
|
||||
|
||||
if openpype_detected:
|
||||
install_host(openpype_host)
|
||||
|
||||
|
||||
@unreal.uclass()
|
||||
class OpenPypeIntegration(unreal.OpenPypePythonBridge):
|
||||
@unreal.ufunction(override=True)
|
||||
def RunInPython_Popup(self):
|
||||
unreal.log_warning("OpenPype: showing tools popup")
|
||||
if openpype_detected:
|
||||
openpype_host.show_tools_popup()
|
||||
|
||||
@unreal.ufunction(override=True)
|
||||
def RunInPython_Dialog(self):
|
||||
unreal.log_warning("OpenPype: showing tools dialog")
|
||||
if openpype_detected:
|
||||
openpype_host.show_tools_dialog()
|
||||
24
openpype/hosts/unreal/integration/UE_5.0/OpenPype.uplugin
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"FileVersion": 3,
|
||||
"Version": 1,
|
||||
"VersionName": "1.0",
|
||||
"FriendlyName": "OpenPype",
|
||||
"Description": "OpenPype Integration",
|
||||
"Category": "OpenPype.Integration",
|
||||
"CreatedBy": "Ondrej Samohel",
|
||||
"CreatedByURL": "https://openpype.io",
|
||||
"DocsURL": "https://openpype.io/docs/artist_hosts_unreal",
|
||||
"MarketplaceURL": "",
|
||||
"SupportURL": "https://pype.club/",
|
||||
"CanContainContent": true,
|
||||
"IsBetaVersion": true,
|
||||
"IsExperimentalVersion": false,
|
||||
"Installed": false,
|
||||
"Modules": [
|
||||
{
|
||||
"Name": "OpenPype",
|
||||
"Type": "Editor",
|
||||
"LoadingPhase": "Default"
|
||||
}
|
||||
]
|
||||
}
|
||||
11
openpype/hosts/unreal/integration/UE_5.0/README.md
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# OpenPype Unreal Integration plugin - UE 5.x
|
||||
|
||||
This is plugin for Unreal Editor, creating menu for [OpenPype](https://github.com/getavalon) tools to run.
|
||||
|
||||
## How does this work
|
||||
|
||||
Plugin is creating basic menu items in **Window/OpenPype** section of Unreal Editor main menu and a button
|
||||
on the main toolbar with associated menu. Clicking on those menu items is calling callbacks that are
|
||||
declared in C++ but needs to be implemented during Unreal Editor
|
||||
startup in `Plugins/OpenPype/Content/Python/init_unreal.py` - this should be executed by Unreal Editor
|
||||
automatically.
|
||||
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
|
@ -0,0 +1,59 @@
|
|||
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class OpenPype : ModuleRules
|
||||
{
|
||||
public OpenPype(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
PublicIncludePaths.AddRange(
|
||||
new string[] {
|
||||
// ... add public include paths required here ...
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
PrivateIncludePaths.AddRange(
|
||||
new string[] {
|
||||
// ... add other private include paths required here ...
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Core",
|
||||
// ... add other public dependencies that you statically link with here ...
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Projects",
|
||||
"InputCore",
|
||||
"EditorFramework",
|
||||
"UnrealEd",
|
||||
"ToolMenus",
|
||||
"LevelEditor",
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
// ... add private dependencies that you statically link with here ...
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
DynamicallyLoadedModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
// ... add any modules that your module loads dynamically here ...
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
#include "AssetContainerFactory.h"
|
||||
#include "AssetContainer.h"
|
||||
|
||||
UAssetContainerFactory::UAssetContainerFactory(const FObjectInitializer& ObjectInitializer)
|
||||
: UFactory(ObjectInitializer)
|
||||
{
|
||||
SupportedClass = UAssetContainer::StaticClass();
|
||||
bCreateNew = false;
|
||||
bEditorImport = true;
|
||||
}
|
||||
|
||||
UObject* UAssetContainerFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
|
||||
{
|
||||
UAssetContainer* AssetContainer = NewObject<UAssetContainer>(InParent, Class, Name, Flags);
|
||||
return AssetContainer;
|
||||
}
|
||||
|
||||
bool UAssetContainerFactory::ShouldShowInNewMenu() const {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
#include "OpenPype.h"
|
||||
#include "OpenPypeStyle.h"
|
||||
#include "OpenPypeCommands.h"
|
||||
#include "OpenPypePythonBridge.h"
|
||||
#include "LevelEditor.h"
|
||||
#include "Misc/MessageDialog.h"
|
||||
#include "ToolMenus.h"
|
||||
|
||||
|
||||
static const FName OpenPypeTabName("OpenPype");
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FOpenPypeModule"
|
||||
|
||||
// This function is triggered when the plugin is staring up
|
||||
void FOpenPypeModule::StartupModule()
|
||||
{
|
||||
FOpenPypeStyle::Initialize();
|
||||
FOpenPypeStyle::ReloadTextures();
|
||||
FOpenPypeCommands::Register();
|
||||
|
||||
PluginCommands = MakeShareable(new FUICommandList);
|
||||
|
||||
PluginCommands->MapAction(
|
||||
FOpenPypeCommands::Get().OpenPypeTools,
|
||||
FExecuteAction::CreateRaw(this, &FOpenPypeModule::MenuPopup),
|
||||
FCanExecuteAction());
|
||||
PluginCommands->MapAction(
|
||||
FOpenPypeCommands::Get().OpenPypeToolsDialog,
|
||||
FExecuteAction::CreateRaw(this, &FOpenPypeModule::MenuDialog),
|
||||
FCanExecuteAction());
|
||||
|
||||
UToolMenus::RegisterStartupCallback(FSimpleMulticastDelegate::FDelegate::CreateRaw(this, &FOpenPypeModule::RegisterMenus));
|
||||
}
|
||||
|
||||
void FOpenPypeModule::ShutdownModule()
|
||||
{
|
||||
UToolMenus::UnRegisterStartupCallback(this);
|
||||
|
||||
UToolMenus::UnregisterOwner(this);
|
||||
|
||||
FOpenPypeStyle::Shutdown();
|
||||
|
||||
FOpenPypeCommands::Unregister();
|
||||
}
|
||||
|
||||
void FOpenPypeModule::RegisterMenus()
|
||||
{
|
||||
// Owner will be used for cleanup in call to UToolMenus::UnregisterOwner
|
||||
FToolMenuOwnerScoped OwnerScoped(this);
|
||||
|
||||
{
|
||||
UToolMenu* Menu = UToolMenus::Get()->ExtendMenu("LevelEditor.MainMenu.Tools");
|
||||
{
|
||||
// FToolMenuSection& Section = Menu->FindOrAddSection("OpenPype");
|
||||
FToolMenuSection& Section = Menu->AddSection(
|
||||
"OpenPype",
|
||||
TAttribute<FText>(FText::FromString("OpenPype")),
|
||||
FToolMenuInsert("Programming", EToolMenuInsertType::Before)
|
||||
);
|
||||
Section.AddMenuEntryWithCommandList(FOpenPypeCommands::Get().OpenPypeTools, PluginCommands);
|
||||
Section.AddMenuEntryWithCommandList(FOpenPypeCommands::Get().OpenPypeToolsDialog, PluginCommands);
|
||||
}
|
||||
UToolMenu* ToolbarMenu = UToolMenus::Get()->ExtendMenu("LevelEditor.LevelEditorToolBar.PlayToolBar");
|
||||
{
|
||||
FToolMenuSection& Section = ToolbarMenu->FindOrAddSection("PluginTools");
|
||||
{
|
||||
FToolMenuEntry& Entry = Section.AddEntry(FToolMenuEntry::InitToolBarButton(FOpenPypeCommands::Get().OpenPypeTools));
|
||||
Entry.SetCommandList(PluginCommands);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void FOpenPypeModule::MenuPopup() {
|
||||
UOpenPypePythonBridge* bridge = UOpenPypePythonBridge::Get();
|
||||
bridge->RunInPython_Popup();
|
||||
}
|
||||
|
||||
void FOpenPypeModule::MenuDialog() {
|
||||
UOpenPypePythonBridge* bridge = UOpenPypePythonBridge::Get();
|
||||
bridge->RunInPython_Dialog();
|
||||
}
|
||||
|
||||
IMPLEMENT_MODULE(FOpenPypeModule, OpenPype)
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "OpenPypeCommands.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FOpenPypeModule"
|
||||
|
||||
void FOpenPypeCommands::RegisterCommands()
|
||||
{
|
||||
UI_COMMAND(OpenPypeTools, "OpenPype Tools", "Pipeline tools", EUserInterfaceActionType::Button, FInputChord());
|
||||
UI_COMMAND(OpenPypeToolsDialog, "OpenPype Tools Dialog", "Pipeline tools dialog", EUserInterfaceActionType::Button, FInputChord());
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
#include "OpenPypeLib.h"
|
||||
#include "Misc/Paths.h"
|
||||
#include "Misc/ConfigCacheIni.h"
|
||||
#include "UObject/UnrealType.h"
|
||||
|
||||
/**
|
||||
* Sets color on folder icon on given path
|
||||
* @param InPath - path to folder
|
||||
* @param InFolderColor - color of the folder
|
||||
* @warning This color will appear only after Editor restart. Is there a better way?
|
||||
*/
|
||||
|
||||
void UOpenPypeLib::CSetFolderColor(FString FolderPath, FLinearColor FolderColor, bool bForceAdd)
|
||||
{
|
||||
auto SaveColorInternal = [](FString InPath, FLinearColor InFolderColor)
|
||||
{
|
||||
// Saves the color of the folder to the config
|
||||
if (FPaths::FileExists(GEditorPerProjectIni))
|
||||
{
|
||||
GConfig->SetString(TEXT("PathColor"), *InPath, *InFolderColor.ToString(), GEditorPerProjectIni);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
SaveColorInternal(FolderPath, FolderColor);
|
||||
|
||||
}
|
||||
/**
|
||||
* Returns all poperties on given object
|
||||
* @param cls - class
|
||||
* @return TArray of properties
|
||||
*/
|
||||
TArray<FString> UOpenPypeLib::GetAllProperties(UClass* cls)
|
||||
{
|
||||
TArray<FString> Ret;
|
||||
if (cls != nullptr)
|
||||
{
|
||||
for (TFieldIterator<FProperty> It(cls); It; ++It)
|
||||
{
|
||||
FProperty* Property = *It;
|
||||
if (Property->HasAnyPropertyFlags(EPropertyFlags::CPF_Edit))
|
||||
{
|
||||
Ret.Add(Property->GetName());
|
||||
}
|
||||
}
|
||||
}
|
||||
return Ret;
|
||||
}
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
#pragma once
|
||||
|
||||
#include "OpenPypePublishInstance.h"
|
||||
#include "AssetRegistryModule.h"
|
||||
|
||||
|
||||
UOpenPypePublishInstance::UOpenPypePublishInstance(const FObjectInitializer& ObjectInitializer)
|
||||
: UObject(ObjectInitializer)
|
||||
{
|
||||
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
|
||||
FString path = UOpenPypePublishInstance::GetPathName();
|
||||
FARFilter Filter;
|
||||
Filter.PackagePaths.Add(FName(*path));
|
||||
|
||||
AssetRegistryModule.Get().OnAssetAdded().AddUObject(this, &UOpenPypePublishInstance::OnAssetAdded);
|
||||
AssetRegistryModule.Get().OnAssetRemoved().AddUObject(this, &UOpenPypePublishInstance::OnAssetRemoved);
|
||||
AssetRegistryModule.Get().OnAssetRenamed().AddUObject(this, &UOpenPypePublishInstance::OnAssetRenamed);
|
||||
}
|
||||
|
||||
void UOpenPypePublishInstance::OnAssetAdded(const FAssetData& AssetData)
|
||||
{
|
||||
TArray<FString> split;
|
||||
|
||||
// get directory of current container
|
||||
FString selfFullPath = UOpenPypePublishInstance::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 != "OpenPypePublishInstance")
|
||||
{
|
||||
assets.Add(assetPath);
|
||||
UE_LOG(LogTemp, Log, TEXT("%s: asset added to %s"), *selfFullPath, *selfDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UOpenPypePublishInstance::OnAssetRemoved(const FAssetData& AssetData)
|
||||
{
|
||||
TArray<FString> split;
|
||||
|
||||
// get directory of current container
|
||||
FString selfFullPath = UOpenPypePublishInstance::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 = UOpenPypePublishInstance::GetPathName();
|
||||
FString lpp = FPackageName::GetLongPackagePath(*path);
|
||||
|
||||
if (assetDir.StartsWith(*selfDir))
|
||||
{
|
||||
// exclude self
|
||||
if (assetFName != "OpenPypePublishInstance")
|
||||
{
|
||||
// UE_LOG(LogTemp, Warning, TEXT("%s: asset removed"), *lpp);
|
||||
assets.Remove(assetPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UOpenPypePublishInstance::OnAssetRenamed(const FAssetData& AssetData, const FString& str)
|
||||
{
|
||||
TArray<FString> split;
|
||||
|
||||
// get directory of current container
|
||||
FString selfFullPath = UOpenPypePublishInstance::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);
|
||||
// UE_LOG(LogTemp, Warning, TEXT("%s: asset renamed %s"), *lpp, *str);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
#include "OpenPypePublishInstanceFactory.h"
|
||||
#include "OpenPypePublishInstance.h"
|
||||
|
||||
UOpenPypePublishInstanceFactory::UOpenPypePublishInstanceFactory(const FObjectInitializer& ObjectInitializer)
|
||||
: UFactory(ObjectInitializer)
|
||||
{
|
||||
SupportedClass = UOpenPypePublishInstance::StaticClass();
|
||||
bCreateNew = false;
|
||||
bEditorImport = true;
|
||||
}
|
||||
|
||||
UObject* UOpenPypePublishInstanceFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
|
||||
{
|
||||
UOpenPypePublishInstance* OpenPypePublishInstance = NewObject<UOpenPypePublishInstance>(InParent, Class, Name, Flags);
|
||||
return OpenPypePublishInstance;
|
||||
}
|
||||
|
||||
bool UOpenPypePublishInstanceFactory::ShouldShowInNewMenu() const {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
#include "OpenPypePythonBridge.h"
|
||||
|
||||
UOpenPypePythonBridge* UOpenPypePythonBridge::Get()
|
||||
{
|
||||
TArray<UClass*> OpenPypePythonBridgeClasses;
|
||||
GetDerivedClasses(UOpenPypePythonBridge::StaticClass(), OpenPypePythonBridgeClasses);
|
||||
int32 NumClasses = OpenPypePythonBridgeClasses.Num();
|
||||
if (NumClasses > 0)
|
||||
{
|
||||
return Cast<UOpenPypePythonBridge>(OpenPypePythonBridgeClasses[NumClasses - 1]->GetDefaultObject());
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
#include "OpenPype.h"
|
||||
#include "OpenPypeStyle.h"
|
||||
#include "Framework/Application/SlateApplication.h"
|
||||
#include "Styling/SlateStyleRegistry.h"
|
||||
#include "Slate/SlateGameResources.h"
|
||||
#include "Interfaces/IPluginManager.h"
|
||||
#include "Styling/SlateStyleMacros.h"
|
||||
|
||||
#define RootToContentDir Style->RootToContentDir
|
||||
|
||||
TSharedPtr<FSlateStyleSet> FOpenPypeStyle::OpenPypeStyleInstance = nullptr;
|
||||
|
||||
void FOpenPypeStyle::Initialize()
|
||||
{
|
||||
if (!OpenPypeStyleInstance.IsValid())
|
||||
{
|
||||
OpenPypeStyleInstance = Create();
|
||||
FSlateStyleRegistry::RegisterSlateStyle(*OpenPypeStyleInstance);
|
||||
}
|
||||
}
|
||||
|
||||
void FOpenPypeStyle::Shutdown()
|
||||
{
|
||||
FSlateStyleRegistry::UnRegisterSlateStyle(*OpenPypeStyleInstance);
|
||||
ensure(OpenPypeStyleInstance.IsUnique());
|
||||
OpenPypeStyleInstance.Reset();
|
||||
}
|
||||
|
||||
FName FOpenPypeStyle::GetStyleSetName()
|
||||
{
|
||||
static FName StyleSetName(TEXT("OpenPypeStyle"));
|
||||
return StyleSetName;
|
||||
}
|
||||
|
||||
const FVector2D Icon16x16(16.0f, 16.0f);
|
||||
const FVector2D Icon20x20(20.0f, 20.0f);
|
||||
const FVector2D Icon40x40(40.0f, 40.0f);
|
||||
|
||||
TSharedRef< FSlateStyleSet > FOpenPypeStyle::Create()
|
||||
{
|
||||
TSharedRef< FSlateStyleSet > Style = MakeShareable(new FSlateStyleSet("OpenPypeStyle"));
|
||||
Style->SetContentRoot(IPluginManager::Get().FindPlugin("OpenPype")->GetBaseDir() / TEXT("Resources"));
|
||||
|
||||
Style->Set("OpenPype.OpenPypeTools", new IMAGE_BRUSH(TEXT("openpype40"), Icon40x40));
|
||||
Style->Set("OpenPype.OpenPypeToolsDialog", new IMAGE_BRUSH(TEXT("openpype40"), Icon40x40));
|
||||
|
||||
return Style;
|
||||
}
|
||||
|
||||
void FOpenPypeStyle::ReloadTextures()
|
||||
{
|
||||
if (FSlateApplication::IsInitialized())
|
||||
{
|
||||
FSlateApplication::Get().GetRenderer()->ReloadTextureResources();
|
||||
}
|
||||
}
|
||||
|
||||
const ISlateStyle& FOpenPypeStyle::Get()
|
||||
{
|
||||
return *OpenPypeStyleInstance;
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
// 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);
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
// 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;
|
||||
};
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
|
||||
class FOpenPypeModule : public IModuleInterface
|
||||
{
|
||||
public:
|
||||
virtual void StartupModule() override;
|
||||
virtual void ShutdownModule() override;
|
||||
|
||||
private:
|
||||
void RegisterMenus();
|
||||
|
||||
void MenuPopup();
|
||||
void MenuDialog();
|
||||
|
||||
private:
|
||||
TSharedPtr<class FUICommandList> PluginCommands;
|
||||
};
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Framework/Commands/Commands.h"
|
||||
#include "OpenPypeStyle.h"
|
||||
|
||||
class FOpenPypeCommands : public TCommands<FOpenPypeCommands>
|
||||
{
|
||||
public:
|
||||
|
||||
FOpenPypeCommands()
|
||||
: TCommands<FOpenPypeCommands>(TEXT("OpenPype"), NSLOCTEXT("Contexts", "OpenPype", "OpenPype Tools"), NAME_None, FOpenPypeStyle::GetStyleSetName())
|
||||
{
|
||||
}
|
||||
|
||||
// TCommands<> interface
|
||||
virtual void RegisterCommands() override;
|
||||
|
||||
public:
|
||||
TSharedPtr< FUICommandInfo > OpenPypeTools;
|
||||
TSharedPtr< FUICommandInfo > OpenPypeToolsDialog;
|
||||
};
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
#pragma once
|
||||
|
||||
#include "Engine.h"
|
||||
#include "OpenPypeLib.generated.h"
|
||||
|
||||
|
||||
UCLASS(Blueprintable)
|
||||
class OPENPYPE_API UOpenPypeLib : public UObject
|
||||
{
|
||||
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UFUNCTION(BlueprintCallable, Category = Python)
|
||||
static void CSetFolderColor(FString FolderPath, FLinearColor FolderColor, bool bForceAdd);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = Python)
|
||||
static TArray<FString> GetAllProperties(UClass* cls);
|
||||
};
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
#include "Engine.h"
|
||||
#include "OpenPypePublishInstance.generated.h"
|
||||
|
||||
|
||||
UCLASS(Blueprintable)
|
||||
class OPENPYPE_API UOpenPypePublishInstance : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UOpenPypePublishInstance(const FObjectInitializer& ObjectInitalizer);
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly)
|
||||
TArray<FString> assets;
|
||||
private:
|
||||
void OnAssetAdded(const FAssetData& AssetData);
|
||||
void OnAssetRemoved(const FAssetData& AssetData);
|
||||
void OnAssetRenamed(const FAssetData& AssetData, const FString& str);
|
||||
};
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Factories/Factory.h"
|
||||
#include "OpenPypePublishInstanceFactory.generated.h"
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
UCLASS()
|
||||
class OPENPYPE_API UOpenPypePublishInstanceFactory : public UFactory
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UOpenPypePublishInstanceFactory(const FObjectInitializer& ObjectInitializer);
|
||||
virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override;
|
||||
virtual bool ShouldShowInNewMenu() const override;
|
||||
};
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
#pragma once
|
||||
#include "Engine.h"
|
||||
#include "OpenPypePythonBridge.generated.h"
|
||||
|
||||
UCLASS(Blueprintable)
|
||||
class UOpenPypePythonBridge : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UFUNCTION(BlueprintCallable, Category = Python)
|
||||
static UOpenPypePythonBridge* Get();
|
||||
|
||||
UFUNCTION(BlueprintImplementableEvent, Category = Python)
|
||||
void RunInPython_Popup() const;
|
||||
|
||||
UFUNCTION(BlueprintImplementableEvent, Category = Python)
|
||||
void RunInPython_Dialog() const;
|
||||
|
||||
};
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
#pragma once
|
||||
#include "CoreMinimal.h"
|
||||
#include "Styling/SlateStyle.h"
|
||||
|
||||
class FOpenPypeStyle
|
||||
{
|
||||
public:
|
||||
static void Initialize();
|
||||
static void Shutdown();
|
||||
static void ReloadTextures();
|
||||
static const ISlateStyle& Get();
|
||||
static FName GetStyleSetName();
|
||||
|
||||
|
||||
private:
|
||||
static TSharedRef< class FSlateStyleSet > Create();
|
||||
static TSharedPtr< class FSlateStyleSet > OpenPypeStyleInstance;
|
||||
};
|
||||
|
|
@ -70,19 +70,22 @@ def get_engine_versions(env=None):
|
|||
return OrderedDict()
|
||||
|
||||
|
||||
def get_editor_executable_path(engine_path: Path) -> Path:
|
||||
"""Get UE4 Editor executable path."""
|
||||
ue4_path = engine_path / "Engine/Binaries"
|
||||
def get_editor_executable_path(engine_path: Path, engine_version: str) -> Path:
|
||||
"""Get UE Editor executable path."""
|
||||
ue_path = engine_path / "Engine/Binaries"
|
||||
if platform.system().lower() == "windows":
|
||||
ue4_path /= "Win64/UE4Editor.exe"
|
||||
if engine_version.split(".")[0] == "4":
|
||||
ue_path /= "Win64/UE4Editor.exe"
|
||||
elif engine_version.split(".")[0] == "5":
|
||||
ue_path /= "Win64/UnrealEditor.exe"
|
||||
|
||||
elif platform.system().lower() == "linux":
|
||||
ue4_path /= "Linux/UE4Editor"
|
||||
ue_path /= "Linux/UE4Editor"
|
||||
|
||||
elif platform.system().lower() == "darwin":
|
||||
ue4_path /= "Mac/UE4Editor"
|
||||
ue_path /= "Mac/UE4Editor"
|
||||
|
||||
return ue4_path
|
||||
return ue_path
|
||||
|
||||
|
||||
def _win_get_engine_versions():
|
||||
|
|
@ -208,22 +211,26 @@ def create_unreal_project(project_name: str,
|
|||
# created in different UE4 version. When user convert such project
|
||||
# to his UE4 version, Engine ID is replaced in uproject file. If some
|
||||
# other user tries to open it, it will present him with similar error.
|
||||
ue4_modules = Path()
|
||||
ue_modules = Path()
|
||||
if platform.system().lower() == "windows":
|
||||
ue4_modules = Path(os.path.join(engine_path, "Engine", "Binaries",
|
||||
"Win64", "UE4Editor.modules"))
|
||||
ue_modules_path = engine_path / "Engine/Binaries/Win64"
|
||||
if ue_version.split(".")[0] == "4":
|
||||
ue_modules_path /= "UE4Editor.modules"
|
||||
elif ue_version.split(".")[0] == "5":
|
||||
ue_modules_path /= "UnrealEditor.modules"
|
||||
ue_modules = Path(ue_modules_path)
|
||||
|
||||
if platform.system().lower() == "linux":
|
||||
ue4_modules = Path(os.path.join(engine_path, "Engine", "Binaries",
|
||||
ue_modules = Path(os.path.join(engine_path, "Engine", "Binaries",
|
||||
"Linux", "UE4Editor.modules"))
|
||||
|
||||
if platform.system().lower() == "darwin":
|
||||
ue4_modules = Path(os.path.join(engine_path, "Engine", "Binaries",
|
||||
ue_modules = Path(os.path.join(engine_path, "Engine", "Binaries",
|
||||
"Mac", "UE4Editor.modules"))
|
||||
|
||||
if ue4_modules.exists():
|
||||
if ue_modules.exists():
|
||||
print("--- Loading Engine ID from modules file ...")
|
||||
with open(ue4_modules, "r") as mp:
|
||||
with open(ue_modules, "r") as mp:
|
||||
loaded_modules = json.load(mp)
|
||||
|
||||
if loaded_modules.get("BuildId"):
|
||||
|
|
@ -280,7 +287,7 @@ def create_unreal_project(project_name: str,
|
|||
python_path = None
|
||||
if platform.system().lower() == "windows":
|
||||
python_path = engine_path / ("Engine/Binaries/ThirdParty/"
|
||||
"Python3/Win64/pythonw.exe")
|
||||
"Python3/Win64/python.exe")
|
||||
|
||||
if platform.system().lower() == "linux":
|
||||
python_path = engine_path / ("Engine/Binaries/ThirdParty/"
|
||||
|
|
@ -294,14 +301,15 @@ def create_unreal_project(project_name: str,
|
|||
raise NotImplementedError("Unsupported platform")
|
||||
if not python_path.exists():
|
||||
raise RuntimeError(f"Unreal Python not found at {python_path}")
|
||||
subprocess.run(
|
||||
subprocess.check_call(
|
||||
[python_path.as_posix(), "-m", "pip", "install", "pyside2"])
|
||||
|
||||
if dev_mode or preset["dev_mode"]:
|
||||
_prepare_cpp_project(project_file, engine_path)
|
||||
_prepare_cpp_project(project_file, engine_path, ue_version)
|
||||
|
||||
|
||||
def _prepare_cpp_project(project_file: Path, engine_path: Path) -> None:
|
||||
def _prepare_cpp_project(
|
||||
project_file: Path, engine_path: Path, ue_version: str) -> None:
|
||||
"""Prepare CPP Unreal Project.
|
||||
|
||||
This function will add source files needed for project to be
|
||||
|
|
@ -420,8 +428,12 @@ class {1}_API A{0}GameModeBase : public AGameModeBase
|
|||
with open(sources_dir / f"{project_name}GameModeBase.h", mode="w") as f:
|
||||
f.write(game_mode_h)
|
||||
|
||||
u_build_tool = Path(
|
||||
engine_path / "Engine/Binaries/DotNET/UnrealBuildTool.exe")
|
||||
u_build_tool_path = engine_path / "Engine/Binaries/DotNET"
|
||||
if ue_version.split(".")[0] == "4":
|
||||
u_build_tool_path /= "UnrealBuildTool.exe"
|
||||
elif ue_version.split(".")[0] == "5":
|
||||
u_build_tool_path /= "UnrealBuildTool/UnrealBuildTool.exe"
|
||||
u_build_tool = Path(u_build_tool_path)
|
||||
u_header_tool = None
|
||||
|
||||
arch = "Win64"
|
||||
|
|
|
|||
|
|
@ -22,17 +22,24 @@ class CreateRender(Creator):
|
|||
|
||||
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]
|
||||
|
||||
# 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/{self.data['asset']}"],
|
||||
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/{self.data['asset']}"],
|
||||
package_paths=[f"/Game/OpenPype/{asset_name}"],
|
||||
recursive_paths=False)
|
||||
levels = ar.get_assets(filter)
|
||||
ml = levels[0].get_editor_property('object_path')
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ from openpype.pipeline import (
|
|||
)
|
||||
from openpype.hosts.unreal.api import plugin
|
||||
from openpype.hosts.unreal.api import pipeline as unreal_pipeline
|
||||
from openpype.api import get_asset
|
||||
|
||||
|
||||
class AnimationFBXLoader(plugin.Loader):
|
||||
|
|
@ -77,13 +78,15 @@ class AnimationFBXLoader(plugin.Loader):
|
|||
task.options.anim_sequence_import_data.set_editor_property(
|
||||
'import_meshes_in_bone_hierarchy', False)
|
||||
task.options.anim_sequence_import_data.set_editor_property(
|
||||
'use_default_sample_rate', True)
|
||||
'use_default_sample_rate', False)
|
||||
task.options.anim_sequence_import_data.set_editor_property(
|
||||
'custom_sample_rate', get_asset()["data"].get("fps"))
|
||||
task.options.anim_sequence_import_data.set_editor_property(
|
||||
'import_custom_attribute', True)
|
||||
task.options.anim_sequence_import_data.set_editor_property(
|
||||
'import_bone_tracks', True)
|
||||
task.options.anim_sequence_import_data.set_editor_property(
|
||||
'remove_redundant_keys', True)
|
||||
'remove_redundant_keys', False)
|
||||
task.options.anim_sequence_import_data.set_editor_property(
|
||||
'convert_scene', True)
|
||||
|
||||
|
|
@ -139,22 +142,18 @@ class AnimationFBXLoader(plugin.Loader):
|
|||
root = "/Game/OpenPype"
|
||||
asset = context.get('asset').get('name')
|
||||
suffix = "_CON"
|
||||
if asset:
|
||||
asset_name = "{}_{}".format(asset, name)
|
||||
else:
|
||||
asset_name = "{}".format(name)
|
||||
|
||||
asset_name = f"{asset}_{name}" if asset else f"{name}"
|
||||
tools = unreal.AssetToolsHelpers().get_asset_tools()
|
||||
asset_dir, container_name = tools.create_unique_asset_name(
|
||||
f"{root}/Animations/{asset}/{name}", suffix="")
|
||||
|
||||
ar = unreal.AssetRegistryHelpers.get_asset_registry()
|
||||
|
||||
filter = unreal.ARFilter(
|
||||
_filter = unreal.ARFilter(
|
||||
class_names=["World"],
|
||||
package_paths=[f"{root}/{hierarchy[0]}"],
|
||||
recursive_paths=False)
|
||||
levels = ar.get_assets(filter)
|
||||
levels = ar.get_assets(_filter)
|
||||
master_level = levels[0].get_editor_property('object_path')
|
||||
|
||||
hierarchy_dir = root
|
||||
|
|
@ -162,11 +161,11 @@ class AnimationFBXLoader(plugin.Loader):
|
|||
hierarchy_dir = f"{hierarchy_dir}/{h}"
|
||||
hierarchy_dir = f"{hierarchy_dir}/{asset}"
|
||||
|
||||
filter = unreal.ARFilter(
|
||||
_filter = unreal.ARFilter(
|
||||
class_names=["World"],
|
||||
package_paths=[f"{hierarchy_dir}/"],
|
||||
recursive_paths=True)
|
||||
levels = ar.get_assets(filter)
|
||||
levels = ar.get_assets(_filter)
|
||||
level = levels[0].get_editor_property('object_path')
|
||||
|
||||
unreal.EditorLevelLibrary.save_all_dirty_levels()
|
||||
|
|
@ -233,8 +232,7 @@ class AnimationFBXLoader(plugin.Loader):
|
|||
"parent": context["representation"]["parent"],
|
||||
"family": context["representation"]["context"]["family"]
|
||||
}
|
||||
unreal_pipeline.imprint(
|
||||
"{}/{}".format(asset_dir, container_name), data)
|
||||
unreal_pipeline.imprint(f"{asset_dir}/{container_name}", data)
|
||||
|
||||
imported_content = EditorAssetLibrary.list_assets(
|
||||
asset_dir, recursive=True, include_folder=False)
|
||||
|
|
@ -279,13 +277,15 @@ class AnimationFBXLoader(plugin.Loader):
|
|||
task.options.anim_sequence_import_data.set_editor_property(
|
||||
'import_meshes_in_bone_hierarchy', False)
|
||||
task.options.anim_sequence_import_data.set_editor_property(
|
||||
'use_default_sample_rate', True)
|
||||
'use_default_sample_rate', False)
|
||||
task.options.anim_sequence_import_data.set_editor_property(
|
||||
'custom_sample_rate', get_asset()["data"].get("fps"))
|
||||
task.options.anim_sequence_import_data.set_editor_property(
|
||||
'import_custom_attribute', True)
|
||||
task.options.anim_sequence_import_data.set_editor_property(
|
||||
'import_bone_tracks', True)
|
||||
task.options.anim_sequence_import_data.set_editor_property(
|
||||
'remove_redundant_keys', True)
|
||||
'remove_redundant_keys', False)
|
||||
task.options.anim_sequence_import_data.set_editor_property(
|
||||
'convert_scene', True)
|
||||
|
||||
|
|
@ -296,8 +296,7 @@ class AnimationFBXLoader(plugin.Loader):
|
|||
|
||||
# do import fbx and replace existing data
|
||||
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
|
||||
container_path = "{}/{}".format(container["namespace"],
|
||||
container["objectName"])
|
||||
container_path = f'{container["namespace"]}/{container["objectName"]}'
|
||||
# update metadata
|
||||
unreal_pipeline.imprint(
|
||||
container_path,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from pathlib import Path
|
|||
import unreal
|
||||
from unreal import EditorAssetLibrary
|
||||
from unreal import EditorLevelLibrary
|
||||
from unreal import EditorLevelUtils
|
||||
|
||||
from openpype.pipeline import (
|
||||
AVALON_CONTAINER_ID,
|
||||
|
|
@ -57,6 +58,33 @@ class CameraLoader(plugin.Loader):
|
|||
min_frame_j,
|
||||
max_frame_j + 1)
|
||||
|
||||
def _import_camera(
|
||||
self, world, sequence, bindings, import_fbx_settings, import_filename
|
||||
):
|
||||
ue_version = unreal.SystemLibrary.get_engine_version().split('.')
|
||||
ue_major = int(ue_version[0])
|
||||
ue_minor = int(ue_version[1])
|
||||
|
||||
if ue_major == 4 and ue_minor <= 26:
|
||||
unreal.SequencerTools.import_fbx(
|
||||
world,
|
||||
sequence,
|
||||
bindings,
|
||||
import_fbx_settings,
|
||||
import_filename
|
||||
)
|
||||
elif (ue_major == 4 and ue_minor >= 27) or ue_major == 5:
|
||||
unreal.SequencerTools.import_level_sequence_fbx(
|
||||
world,
|
||||
sequence,
|
||||
bindings,
|
||||
import_fbx_settings,
|
||||
import_filename
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
f"Unreal version {ue_major} not supported")
|
||||
|
||||
def load(self, context, name, namespace, data):
|
||||
"""
|
||||
Load and containerise representation into Content Browser.
|
||||
|
|
@ -84,10 +112,10 @@ class CameraLoader(plugin.Loader):
|
|||
hierarchy = context.get('asset').get('data').get('parents')
|
||||
root = "/Game/OpenPype"
|
||||
hierarchy_dir = root
|
||||
hierarchy_list = []
|
||||
hierarchy_dir_list = []
|
||||
for h in hierarchy:
|
||||
hierarchy_dir = f"{hierarchy_dir}/{h}"
|
||||
hierarchy_list.append(hierarchy_dir)
|
||||
hierarchy_dir_list.append(hierarchy_dir)
|
||||
asset = context.get('asset').get('name')
|
||||
suffix = "_CON"
|
||||
if asset:
|
||||
|
|
@ -121,27 +149,40 @@ class CameraLoader(plugin.Loader):
|
|||
asset_dir, container_name = tools.create_unique_asset_name(
|
||||
f"{hierarchy_dir}/{asset}/{name}_{unique_number:02d}", suffix="")
|
||||
|
||||
asset_path = Path(asset_dir)
|
||||
asset_path_parent = str(asset_path.parent.as_posix())
|
||||
|
||||
container_name += suffix
|
||||
|
||||
current_level = EditorLevelLibrary.get_editor_world().get_full_name()
|
||||
EditorAssetLibrary.make_directory(asset_dir)
|
||||
|
||||
# Create map for the shot, and create hierarchy of map. If the maps
|
||||
# already exist, we will use them.
|
||||
h_dir = hierarchy_dir_list[0]
|
||||
h_asset = hierarchy[0]
|
||||
master_level = f"{h_dir}/{h_asset}_map.{h_asset}_map"
|
||||
if not EditorAssetLibrary.does_asset_exist(master_level):
|
||||
EditorLevelLibrary.new_level(f"{h_dir}/{h_asset}_map")
|
||||
|
||||
level = f"{asset_path_parent}/{asset}_map.{asset}_map"
|
||||
if not EditorAssetLibrary.does_asset_exist(level):
|
||||
EditorLevelLibrary.new_level(f"{asset_path_parent}/{asset}_map")
|
||||
|
||||
EditorLevelLibrary.load_level(master_level)
|
||||
EditorLevelUtils.add_level_to_world(
|
||||
EditorLevelLibrary.get_editor_world(),
|
||||
level,
|
||||
unreal.LevelStreamingDynamic
|
||||
)
|
||||
EditorLevelLibrary.save_all_dirty_levels()
|
||||
|
||||
ar = unreal.AssetRegistryHelpers.get_asset_registry()
|
||||
filter = unreal.ARFilter(
|
||||
class_names=["World"],
|
||||
package_paths=[f"{hierarchy_dir}/{asset}/"],
|
||||
recursive_paths=True)
|
||||
maps = ar.get_assets(filter)
|
||||
|
||||
# There should be only one map in the list
|
||||
EditorLevelLibrary.load_level(maps[0].get_full_name())
|
||||
EditorLevelLibrary.load_level(level)
|
||||
|
||||
# Get all the sequences in the hierarchy. It will create them, if
|
||||
# they don't exist.
|
||||
sequences = []
|
||||
frame_ranges = []
|
||||
i = 0
|
||||
for h in hierarchy_list:
|
||||
for h in hierarchy_dir_list:
|
||||
root_content = EditorAssetLibrary.list_assets(
|
||||
h, recursive=False, include_folder=False)
|
||||
|
||||
|
|
@ -228,7 +269,7 @@ class CameraLoader(plugin.Loader):
|
|||
settings.set_editor_property('reduce_keys', False)
|
||||
|
||||
if cam_seq:
|
||||
unreal.SequencerTools.import_fbx(
|
||||
self._import_camera(
|
||||
EditorLevelLibrary.get_editor_world(),
|
||||
cam_seq,
|
||||
cam_seq.get_bindings(),
|
||||
|
|
@ -256,7 +297,7 @@ class CameraLoader(plugin.Loader):
|
|||
"{}/{}".format(asset_dir, container_name), data)
|
||||
|
||||
EditorLevelLibrary.save_all_dirty_levels()
|
||||
EditorLevelLibrary.load_level(current_level)
|
||||
EditorLevelLibrary.load_level(master_level)
|
||||
|
||||
asset_content = EditorAssetLibrary.list_assets(
|
||||
asset_dir, recursive=True, include_folder=True
|
||||
|
|
@ -388,7 +429,7 @@ class CameraLoader(plugin.Loader):
|
|||
|
||||
sub_scene.set_sequence(new_sequence)
|
||||
|
||||
unreal.SequencerTools.import_fbx(
|
||||
self._import_camera(
|
||||
EditorLevelLibrary.get_editor_world(),
|
||||
new_sequence,
|
||||
new_sequence.get_bindings(),
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ from openpype.pipeline import (
|
|||
AVALON_CONTAINER_ID,
|
||||
legacy_io,
|
||||
)
|
||||
from openpype.api import get_asset
|
||||
from openpype.hosts.unreal.api import plugin
|
||||
from openpype.hosts.unreal.api import pipeline as unreal_pipeline
|
||||
|
||||
|
|
@ -87,7 +88,8 @@ class LayoutLoader(plugin.Loader):
|
|||
|
||||
return None
|
||||
|
||||
def _get_data(self, asset_name):
|
||||
@staticmethod
|
||||
def _get_data(asset_name):
|
||||
asset_doc = legacy_io.find_one({
|
||||
"type": "asset",
|
||||
"name": asset_name
|
||||
|
|
@ -95,8 +97,9 @@ class LayoutLoader(plugin.Loader):
|
|||
|
||||
return asset_doc.get("data")
|
||||
|
||||
@staticmethod
|
||||
def _set_sequence_hierarchy(
|
||||
self, seq_i, seq_j, max_frame_i, min_frame_j, max_frame_j, map_paths
|
||||
seq_i, seq_j, max_frame_i, min_frame_j, max_frame_j, map_paths
|
||||
):
|
||||
# Get existing sequencer tracks or create them if they don't exist
|
||||
tracks = seq_i.get_master_tracks()
|
||||
|
|
@ -165,8 +168,9 @@ class LayoutLoader(plugin.Loader):
|
|||
hid_section.set_row_index(index)
|
||||
hid_section.set_level_names(maps)
|
||||
|
||||
@staticmethod
|
||||
def _process_family(
|
||||
self, assets, class_name, transform, sequence, inst_name=None
|
||||
assets, class_name, transform, sequence, inst_name=None
|
||||
):
|
||||
ar = unreal.AssetRegistryHelpers.get_asset_registry()
|
||||
|
||||
|
|
@ -262,13 +266,15 @@ class LayoutLoader(plugin.Loader):
|
|||
task.options.anim_sequence_import_data.set_editor_property(
|
||||
'import_meshes_in_bone_hierarchy', False)
|
||||
task.options.anim_sequence_import_data.set_editor_property(
|
||||
'use_default_sample_rate', True)
|
||||
'use_default_sample_rate', False)
|
||||
task.options.anim_sequence_import_data.set_editor_property(
|
||||
'custom_sample_rate', get_asset()["data"].get("fps"))
|
||||
task.options.anim_sequence_import_data.set_editor_property(
|
||||
'import_custom_attribute', True)
|
||||
task.options.anim_sequence_import_data.set_editor_property(
|
||||
'import_bone_tracks', True)
|
||||
task.options.anim_sequence_import_data.set_editor_property(
|
||||
'remove_redundant_keys', True)
|
||||
'remove_redundant_keys', False)
|
||||
task.options.anim_sequence_import_data.set_editor_property(
|
||||
'convert_scene', True)
|
||||
|
||||
|
|
@ -311,11 +317,8 @@ class LayoutLoader(plugin.Loader):
|
|||
for binding in bindings:
|
||||
tracks = binding.get_tracks()
|
||||
track = None
|
||||
if not tracks:
|
||||
track = binding.add_track(
|
||||
unreal.MovieSceneSkeletalAnimationTrack)
|
||||
else:
|
||||
track = tracks[0]
|
||||
track = tracks[0] if tracks else binding.add_track(
|
||||
unreal.MovieSceneSkeletalAnimationTrack)
|
||||
|
||||
sections = track.get_sections()
|
||||
section = None
|
||||
|
|
@ -335,11 +338,11 @@ class LayoutLoader(plugin.Loader):
|
|||
curr_anim.get_path_name()).parent
|
||||
).replace('\\', '/')
|
||||
|
||||
filter = unreal.ARFilter(
|
||||
_filter = unreal.ARFilter(
|
||||
class_names=["AssetContainer"],
|
||||
package_paths=[anim_path],
|
||||
recursive_paths=False)
|
||||
containers = ar.get_assets(filter)
|
||||
containers = ar.get_assets(_filter)
|
||||
|
||||
if len(containers) > 0:
|
||||
return
|
||||
|
|
@ -350,6 +353,7 @@ class LayoutLoader(plugin.Loader):
|
|||
sec_params = section.get_editor_property('params')
|
||||
sec_params.set_editor_property('animation', animation)
|
||||
|
||||
@staticmethod
|
||||
def _generate_sequence(self, h, h_dir):
|
||||
tools = unreal.AssetToolsHelpers().get_asset_tools()
|
||||
|
||||
|
|
@ -583,10 +587,7 @@ class LayoutLoader(plugin.Loader):
|
|||
hierarchy_dir_list.append(hierarchy_dir)
|
||||
asset = context.get('asset').get('name')
|
||||
suffix = "_CON"
|
||||
if asset:
|
||||
asset_name = "{}_{}".format(asset, name)
|
||||
else:
|
||||
asset_name = "{}".format(name)
|
||||
asset_name = f"{asset}_{name}" if asset else name
|
||||
|
||||
tools = unreal.AssetToolsHelpers().get_asset_tools()
|
||||
asset_dir, container_name = tools.create_unique_asset_name(
|
||||
|
|
@ -800,7 +801,7 @@ class LayoutLoader(plugin.Loader):
|
|||
lc for lc in layout_containers
|
||||
if asset in lc.get('loaded_assets')]
|
||||
|
||||
if len(layouts) == 0:
|
||||
if not layouts:
|
||||
EditorAssetLibrary.delete_directory(str(Path(asset).parent))
|
||||
|
||||
# Remove the Level Sequence from the parent.
|
||||
|
|
@ -810,17 +811,17 @@ class LayoutLoader(plugin.Loader):
|
|||
namespace = container.get('namespace').replace(f"{root}/", "")
|
||||
ms_asset = namespace.split('/')[0]
|
||||
ar = unreal.AssetRegistryHelpers.get_asset_registry()
|
||||
filter = unreal.ARFilter(
|
||||
_filter = unreal.ARFilter(
|
||||
class_names=["LevelSequence"],
|
||||
package_paths=[f"{root}/{ms_asset}"],
|
||||
recursive_paths=False)
|
||||
sequences = ar.get_assets(filter)
|
||||
sequences = ar.get_assets(_filter)
|
||||
master_sequence = sequences[0].get_asset()
|
||||
filter = unreal.ARFilter(
|
||||
_filter = unreal.ARFilter(
|
||||
class_names=["World"],
|
||||
package_paths=[f"{root}/{ms_asset}"],
|
||||
recursive_paths=False)
|
||||
levels = ar.get_assets(filter)
|
||||
levels = ar.get_assets(_filter)
|
||||
master_level = levels[0].get_editor_property('object_path')
|
||||
|
||||
sequences = [master_sequence]
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ def make_sequence_collection(path, otio_range, metadata):
|
|||
first, last = otio_range_to_frame_range(otio_range)
|
||||
collection = clique.Collection(
|
||||
head=head, tail=tail, padding=metadata["padding"])
|
||||
collection.indexes.update(list(range(first, last)))
|
||||
collection.indexes.update([i for i in range(first, last)])
|
||||
return dir_path, collection
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -143,14 +143,17 @@ def create_chunks(iterable, chunk_size=None):
|
|||
list<list>: Chunked items.
|
||||
"""
|
||||
chunks = []
|
||||
if not iterable:
|
||||
return chunks
|
||||
|
||||
tupled_iterable = tuple(iterable)
|
||||
if not tupled_iterable:
|
||||
return chunks
|
||||
iterable_size = len(tupled_iterable)
|
||||
if chunk_size is None:
|
||||
chunk_size = 200
|
||||
|
||||
if chunk_size < 1:
|
||||
chunk_size = 1
|
||||
|
||||
for idx in range(0, iterable_size, chunk_size):
|
||||
chunks.append(tupled_iterable[idx:idx + chunk_size])
|
||||
return chunks
|
||||
|
|
|
|||
|
|
@ -92,14 +92,18 @@ def check_credentials(username, api_key, ftrack_server=None):
|
|||
if not ftrack_server or not username or not api_key:
|
||||
return False
|
||||
|
||||
user_exists = False
|
||||
try:
|
||||
session = ftrack_api.Session(
|
||||
server_url=ftrack_server,
|
||||
api_key=api_key,
|
||||
api_user=username
|
||||
)
|
||||
# Validated that the username actually exists
|
||||
user = session.query("User where username is \"{}\"".format(username))
|
||||
user_exists = user is not None
|
||||
session.close()
|
||||
|
||||
except Exception:
|
||||
return False
|
||||
return True
|
||||
pass
|
||||
return user_exists
|
||||
|
|
|
|||
|
|
@ -165,6 +165,9 @@
|
|||
"CollectMayaRender": {
|
||||
"sync_workfile_version": false
|
||||
},
|
||||
"CollectFbxCamera": {
|
||||
"enabled": false
|
||||
},
|
||||
"ValidateInstanceInContext": {
|
||||
"enabled": true,
|
||||
"optional": true,
|
||||
|
|
|
|||
|
|
@ -15,6 +15,18 @@
|
|||
"destination-path": []
|
||||
}
|
||||
},
|
||||
"scriptsmenu": {
|
||||
"name": "OpenPype Tools",
|
||||
"definition": [
|
||||
{
|
||||
"type": "action",
|
||||
"sourcetype": "python",
|
||||
"title": "OpenPype Docs",
|
||||
"command": "import webbrowser;webbrowser.open(url='https://openpype.io/docs/artist_hosts_nuke_tut')",
|
||||
"tooltip": "Open the OpenPype Nuke user doc page"
|
||||
}
|
||||
]
|
||||
},
|
||||
"create": {
|
||||
"CreateWriteRender": {
|
||||
"fpath_template": "{work}/renders/nuke/{subset}/{subset}.{frame}.{ext}",
|
||||
|
|
|
|||
|
|
@ -1243,9 +1243,19 @@
|
|||
"host_name": "unreal",
|
||||
"environment": {},
|
||||
"variants": {
|
||||
"4-26": {
|
||||
"4-27": {
|
||||
"use_python_2": false,
|
||||
"environment": {}
|
||||
},
|
||||
"5-0": {
|
||||
"use_python_2": false,
|
||||
"environment": {
|
||||
"UE_PYTHONPATH": "{PYTHONPATH}"
|
||||
}
|
||||
},
|
||||
"__dynamic_keys_labels__": {
|
||||
"4-27": "4.27",
|
||||
"5-0": "5.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@
|
|||
},
|
||||
{
|
||||
"type": "schema",
|
||||
"name": "schema_maya_scriptsmenu"
|
||||
"name": "schema_scriptsmenu"
|
||||
},
|
||||
{
|
||||
"type": "schema",
|
||||
|
|
|
|||
|
|
@ -79,6 +79,10 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "schema",
|
||||
"name": "schema_scriptsmenu"
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
|
|
|
|||
|
|
@ -21,6 +21,20 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"key": "CollectFbxCamera",
|
||||
"label": "Collect Camera for FBX export",
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "enabled",
|
||||
"label": "Enabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "splitter"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -264,8 +264,6 @@ class HierarchyModel(QtCore.QAbstractItemModel):
|
|||
if not project_doc:
|
||||
return
|
||||
|
||||
self.blockSignals(True)
|
||||
|
||||
# Create project item
|
||||
project_item = ProjectItem(project_doc)
|
||||
self.add_item(project_item)
|
||||
|
|
@ -379,8 +377,6 @@ class HierarchyModel(QtCore.QAbstractItemModel):
|
|||
|
||||
self.add_items(task_items, asset_item)
|
||||
|
||||
self.blockSignals(False)
|
||||
|
||||
# Emit that project was successfully changed
|
||||
self.project_changed.emit()
|
||||
|
||||
|
|
@ -1476,12 +1472,7 @@ class HierarchyModel(QtCore.QAbstractItemModel):
|
|||
mimedata.setData("application/copy_task", encoded_data)
|
||||
return mimedata
|
||||
|
||||
def paste_mime_data(self, index, mime_data):
|
||||
if not index.isValid():
|
||||
return
|
||||
|
||||
item_id = index.data(IDENTIFIER_ROLE)
|
||||
item = self._items_by_id[item_id]
|
||||
def _paste_mime_data(self, item, mime_data):
|
||||
if not isinstance(item, (AssetItem, TaskItem)):
|
||||
return
|
||||
|
||||
|
|
@ -1515,6 +1506,25 @@ class HierarchyModel(QtCore.QAbstractItemModel):
|
|||
task_item = TaskItem(task_data, True)
|
||||
self.add_item(task_item, parent)
|
||||
|
||||
def paste(self, indexes, mime_data):
|
||||
|
||||
# Get the selected Assets uniquely
|
||||
items = set()
|
||||
for index in indexes:
|
||||
if not index.isValid():
|
||||
return
|
||||
item_id = index.data(IDENTIFIER_ROLE)
|
||||
item = self._items_by_id[item_id]
|
||||
|
||||
# Do not copy into the Task Item so get parent Asset instead
|
||||
if isinstance(item, TaskItem):
|
||||
item = item.parent()
|
||||
|
||||
items.add(item)
|
||||
|
||||
for item in items:
|
||||
self._paste_mime_data(item, mime_data)
|
||||
|
||||
|
||||
class BaseItem:
|
||||
"""Base item for HierarchyModel.
|
||||
|
|
|
|||
|
|
@ -139,6 +139,7 @@ class HierarchyView(QtWidgets.QTreeView):
|
|||
self.setAlternatingRowColors(True)
|
||||
self.setSelectionMode(HierarchyView.ExtendedSelection)
|
||||
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||
self.setEditTriggers(HierarchyView.AllEditTriggers)
|
||||
|
||||
column_delegates = {}
|
||||
column_key_to_index = {}
|
||||
|
|
@ -301,16 +302,6 @@ class HierarchyView(QtWidgets.QTreeView):
|
|||
def rowsInserted(self, parent_index, start, end):
|
||||
super(HierarchyView, self).rowsInserted(parent_index, start, end)
|
||||
|
||||
for row in range(start, end + 1):
|
||||
for key, column in self._column_key_to_index.items():
|
||||
if key not in self.persistent_columns:
|
||||
continue
|
||||
col_index = self._source_model.index(row, column, parent_index)
|
||||
if bool(
|
||||
self._source_model.flags(col_index)
|
||||
& QtCore.Qt.ItemIsEditable
|
||||
):
|
||||
self.openPersistentEditor(col_index)
|
||||
|
||||
# Expand parent on insert
|
||||
if not self.isExpanded(parent_index):
|
||||
|
|
@ -365,20 +356,24 @@ class HierarchyView(QtWidgets.QTreeView):
|
|||
event.accept()
|
||||
|
||||
def _copy_items(self, indexes=None):
|
||||
clipboard = QtWidgets.QApplication.clipboard()
|
||||
try:
|
||||
if indexes is None:
|
||||
indexes = self.selectedIndexes()
|
||||
mime_data = self._source_model.copy_mime_data(indexes)
|
||||
|
||||
QtWidgets.QApplication.clipboard().setMimeData(mime_data)
|
||||
clipboard.setMimeData(mime_data)
|
||||
self._show_message("Tasks copied")
|
||||
except ValueError as exc:
|
||||
# Change clipboard to contain empty data
|
||||
empty_mime_data = QtCore.QMimeData()
|
||||
clipboard.setMimeData(empty_mime_data)
|
||||
self._show_message(str(exc))
|
||||
|
||||
def _paste_items(self):
|
||||
index = self.currentIndex()
|
||||
mime_data = QtWidgets.QApplication.clipboard().mimeData()
|
||||
self._source_model.paste_mime_data(index, mime_data)
|
||||
rows = self.selectionModel().selectedRows()
|
||||
self._source_model.paste(rows, mime_data)
|
||||
|
||||
def _delete_items(self, indexes=None):
|
||||
if indexes is None:
|
||||
|
|
|
|||
|
|
@ -184,14 +184,14 @@ class ProjectManagerWindow(QtWidgets.QWidget):
|
|||
self.resize(1200, 600)
|
||||
self.setStyleSheet(load_stylesheet())
|
||||
|
||||
def _set_project(self, project_name=None):
|
||||
def _set_project(self, project_name=None, force=False):
|
||||
self._create_folders_btn.setEnabled(project_name is not None)
|
||||
self._remove_projects_btn.setEnabled(project_name is not None)
|
||||
self._add_asset_btn.setEnabled(project_name is not None)
|
||||
self._add_task_btn.setEnabled(project_name is not None)
|
||||
self._save_btn.setEnabled(project_name is not None)
|
||||
self._project_proxy_model.set_filter_default(project_name is not None)
|
||||
self.hierarchy_view.set_project(project_name, True)
|
||||
self.hierarchy_view.set_project(project_name, force)
|
||||
|
||||
def _current_project(self):
|
||||
row = self._project_combobox.currentIndex()
|
||||
|
|
@ -229,11 +229,11 @@ class ProjectManagerWindow(QtWidgets.QWidget):
|
|||
self._project_combobox.setCurrentIndex(row)
|
||||
|
||||
selected_project = self._current_project()
|
||||
self._set_project(selected_project)
|
||||
self._set_project(selected_project, True)
|
||||
|
||||
def _on_project_change(self):
|
||||
selected_project = self._current_project()
|
||||
self._set_project(selected_project)
|
||||
self._set_project(selected_project, False)
|
||||
|
||||
def _on_project_refresh(self):
|
||||
self.refresh_projects()
|
||||
|
|
|
|||
|
|
@ -494,8 +494,6 @@ class AssetModel(QtGui.QStandardItemModel):
|
|||
# Remove cache of removed items
|
||||
for asset_id in removed_asset_ids:
|
||||
self._items_by_asset_id.pop(asset_id)
|
||||
if asset_id in self._items_with_color_by_id:
|
||||
self._items_with_color_by_id.pop(asset_id)
|
||||
|
||||
# Refresh data
|
||||
# - all items refresh all data except id
|
||||
|
|
|
|||
|
|
@ -119,7 +119,8 @@ module.{module_name}()"""
|
|||
"""
|
||||
|
||||
# get the current application and its linked keyboard modifiers
|
||||
modifiers = QtWidgets.QApplication.keyboardModifiers()
|
||||
app = QtWidgets.QApplication.instance()
|
||||
modifiers = app.keyboardModifiers()
|
||||
|
||||
# If the menu has a callback registered for the current modifier
|
||||
# we run the callback instead of the action itself.
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ def _nuke_main_window():
|
|||
if (obj.inherits('QMainWindow') and
|
||||
obj.metaObject().className() == 'Foundry::UI::DockMainWindow'):
|
||||
return obj
|
||||
raise RuntimeError('Could not find Nuke MainWindow instance')
|
||||
raise RuntimeError('Could not find Nuke MainWindow instance')
|
||||
|
||||
|
||||
def _nuke_main_menubar():
|
||||
|
|
@ -22,9 +22,6 @@ def _nuke_main_menubar():
|
|||
|
||||
|
||||
def main(title="Scripts"):
|
||||
# Register control + shift callback to add to shelf (Nuke behavior)
|
||||
# modifiers = QtCore.Qt.ControlModifier | QtCore.Qt.ShiftModifier
|
||||
# menu.register_callback(modifiers, to_shelf)
|
||||
nuke_main_bar = _nuke_main_menubar()
|
||||
for nuke_bar in nuke_main_bar.children():
|
||||
if isinstance(nuke_bar, scriptsmenu.ScriptsMenu):
|
||||
|
|
@ -33,4 +30,4 @@ def main(title="Scripts"):
|
|||
return menu
|
||||
|
||||
menu = scriptsmenu.ScriptsMenu(title=title, parent=nuke_main_bar)
|
||||
return menu
|
||||
return menu
|
||||
|
|
|
|||
|
|
@ -264,8 +264,7 @@ class ScriptsMenu(QtWidgets.QMenu):
|
|||
action.setVisible(True)
|
||||
else:
|
||||
for action in self._script_actions:
|
||||
if not action.has_tag(search.lower()):
|
||||
action.setVisible(False)
|
||||
action.setVisible(action.has_tag(search.lower()))
|
||||
|
||||
# Set visibility for all submenus
|
||||
for action in self.actions():
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
VERSION_MAJOR = 1
|
||||
VERSION_MINOR = 5
|
||||
VERSION_PATCH = 1
|
||||
VERSION_PATCH = 2
|
||||
|
||||
|
||||
version = '{}.{}.{}'.format(VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Package declaring Pype version."""
|
||||
__version__ = "3.10.0-nightly.3"
|
||||
__version__ = "3.10.1-nightly.1"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "OpenPype"
|
||||
version = "3.10.0-nightly.3" # OpenPype
|
||||
version = "3.10.1-nightly.1" # OpenPype
|
||||
description = "Open VFX and Animation pipeline with support."
|
||||
authors = ["OpenPype Team <info@openpype.io>"]
|
||||
license = "MIT License"
|
||||
|
|
|
|||
14
website/docs/admin_hosts_nuke.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
id: admin_hosts_nuke
|
||||
title: Nuke
|
||||
sidebar_label: Nuke
|
||||
---
|
||||
|
||||
## Custom Menu
|
||||
You can add your custom tools menu into Nuke by extending definitions in **Nuke -> Scripts Menu Definition**.
|
||||

|
||||
|
||||
:::note Work in progress
|
||||
This is still work in progress. Menu definition will be handled more friendly with widgets and not
|
||||
raw json.
|
||||
:::
|
||||
BIN
website/docs/assets/nuke-admin_scriptsmenu.png
Normal file
|
After Width: | Height: | Size: 21 KiB |