Merge branch 'pypeclub:develop' into develop
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ from .lib import (
|
|||
get_track_items,
|
||||
get_current_project,
|
||||
get_current_sequence,
|
||||
get_timeline_selection,
|
||||
get_current_track,
|
||||
get_track_item_pype_tag,
|
||||
set_track_item_pype_tag,
|
||||
|
|
@ -80,6 +81,7 @@ __all__ = [
|
|||
"get_track_items",
|
||||
"get_current_project",
|
||||
"get_current_sequence",
|
||||
"get_timeline_selection",
|
||||
"get_current_track",
|
||||
"get_track_item_pype_tag",
|
||||
"set_track_item_pype_tag",
|
||||
|
|
|
|||
|
|
@ -109,8 +109,9 @@ def register_hiero_events():
|
|||
# hiero.core.events.registerInterest("kShutdown", shutDown)
|
||||
# hiero.core.events.registerInterest("kStartup", startupCompleted)
|
||||
|
||||
hiero.core.events.registerInterest(
|
||||
("kSelectionChanged", "kTimeline"), selection_changed_timeline)
|
||||
# INFO: was disabled because it was slowing down timeline operations
|
||||
# hiero.core.events.registerInterest(
|
||||
# ("kSelectionChanged", "kTimeline"), selection_changed_timeline)
|
||||
|
||||
# workfiles
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
"""
|
||||
Host specific functions where host api is connected
|
||||
"""
|
||||
|
||||
from copy import deepcopy
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
|
@ -89,13 +91,19 @@ def get_current_sequence(name=None, new=False):
|
|||
if not sequence:
|
||||
# if nothing found create new with input name
|
||||
sequence = get_current_sequence(name, True)
|
||||
elif not name and not new:
|
||||
else:
|
||||
# if name is none and new is False then return current open sequence
|
||||
sequence = hiero.ui.activeSequence()
|
||||
|
||||
return sequence
|
||||
|
||||
|
||||
def get_timeline_selection():
|
||||
active_sequence = hiero.ui.activeSequence()
|
||||
timeline_editor = hiero.ui.getTimelineEditor(active_sequence)
|
||||
return list(timeline_editor.selection())
|
||||
|
||||
|
||||
def get_current_track(sequence, name, audio=False):
|
||||
"""
|
||||
Get current track in context of active project.
|
||||
|
|
@ -118,7 +126,7 @@ def get_current_track(sequence, name, audio=False):
|
|||
# get track by name
|
||||
track = None
|
||||
for _track in tracks:
|
||||
if _track.name() in name:
|
||||
if _track.name() == name:
|
||||
track = _track
|
||||
|
||||
if not track:
|
||||
|
|
@ -126,13 +134,14 @@ def get_current_track(sequence, name, audio=False):
|
|||
track = hiero.core.VideoTrack(name)
|
||||
else:
|
||||
track = hiero.core.AudioTrack(name)
|
||||
|
||||
sequence.addTrack(track)
|
||||
|
||||
return track
|
||||
|
||||
|
||||
def get_track_items(
|
||||
selected=False,
|
||||
selection=False,
|
||||
sequence_name=None,
|
||||
track_item_name=None,
|
||||
track_name=None,
|
||||
|
|
@ -143,7 +152,7 @@ def get_track_items(
|
|||
"""Get all available current timeline track items.
|
||||
|
||||
Attribute:
|
||||
selected (bool)[optional]: return only selected items on timeline
|
||||
selection (list)[optional]: list of selected track items
|
||||
sequence_name (str)[optional]: return only clips from input sequence
|
||||
track_item_name (str)[optional]: return only item with input name
|
||||
track_name (str)[optional]: return only items from track name
|
||||
|
|
@ -155,32 +164,34 @@ def get_track_items(
|
|||
Return:
|
||||
list or hiero.core.TrackItem: list of track items or single track item
|
||||
"""
|
||||
return_list = list()
|
||||
track_items = list()
|
||||
track_type = track_type or "video"
|
||||
selection = selection or []
|
||||
return_list = []
|
||||
|
||||
# get selected track items or all in active sequence
|
||||
if selected:
|
||||
if selection:
|
||||
try:
|
||||
selected_items = list(hiero.selection)
|
||||
for item in selected_items:
|
||||
if track_name and track_name in item.parent().name():
|
||||
# filter only items fitting input track name
|
||||
track_items.append(item)
|
||||
elif not track_name:
|
||||
# or add all if no track_name was defined
|
||||
track_items.append(item)
|
||||
for track_item in selection:
|
||||
log.info("___ track_item: {}".format(track_item))
|
||||
# make sure only trackitems are selected
|
||||
if not isinstance(track_item, hiero.core.TrackItem):
|
||||
continue
|
||||
|
||||
if _validate_all_atrributes(
|
||||
track_item,
|
||||
track_item_name,
|
||||
track_name,
|
||||
track_type,
|
||||
check_enabled,
|
||||
check_tagged
|
||||
):
|
||||
log.info("___ valid trackitem: {}".format(track_item))
|
||||
return_list.append(track_item)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
# check if any collected track items are
|
||||
# `core.Hiero.Python.TrackItem` instance
|
||||
if track_items:
|
||||
any_track_item = track_items[0]
|
||||
if not isinstance(any_track_item, hiero.core.TrackItem):
|
||||
selected_items = []
|
||||
|
||||
# collect all available active sequence track items
|
||||
if not track_items:
|
||||
if not return_list:
|
||||
sequence = get_current_sequence(name=sequence_name)
|
||||
# get all available tracks from sequence
|
||||
tracks = list(sequence.audioTracks()) + list(sequence.videoTracks())
|
||||
|
|
@ -191,42 +202,76 @@ def get_track_items(
|
|||
if check_enabled and not track.isEnabled():
|
||||
continue
|
||||
# and all items in track
|
||||
for item in track.items():
|
||||
if check_tagged and not item.tags():
|
||||
for track_item in track.items():
|
||||
# make sure no subtrackitem is also track items
|
||||
if not isinstance(track_item, hiero.core.TrackItem):
|
||||
continue
|
||||
|
||||
# check if track item is enabled
|
||||
if check_enabled:
|
||||
if not item.isEnabled():
|
||||
continue
|
||||
if track_item_name:
|
||||
if track_item_name in item.name():
|
||||
return item
|
||||
# make sure only track items with correct track names are added
|
||||
if track_name and track_name in track.name():
|
||||
# filter out only defined track_name items
|
||||
track_items.append(item)
|
||||
elif not track_name:
|
||||
# or add all if no track_name is defined
|
||||
track_items.append(item)
|
||||
if _validate_all_atrributes(
|
||||
track_item,
|
||||
track_item_name,
|
||||
track_name,
|
||||
track_type,
|
||||
check_enabled,
|
||||
check_tagged
|
||||
):
|
||||
return_list.append(track_item)
|
||||
|
||||
# filter out only track items with defined track_type
|
||||
for track_item in track_items:
|
||||
if track_type and track_type == "video" and isinstance(
|
||||
return return_list
|
||||
|
||||
|
||||
def _validate_all_atrributes(
|
||||
track_item,
|
||||
track_item_name,
|
||||
track_name,
|
||||
track_type,
|
||||
check_enabled,
|
||||
check_tagged
|
||||
):
|
||||
def _validate_correct_name_track_item():
|
||||
if track_item_name and track_item_name in track_item.name():
|
||||
return True
|
||||
elif not track_item_name:
|
||||
return True
|
||||
|
||||
def _validate_tagged_track_item():
|
||||
if check_tagged and track_item.tags():
|
||||
return True
|
||||
elif not check_tagged:
|
||||
return True
|
||||
|
||||
def _validate_enabled_track_item():
|
||||
if check_enabled and track_item.isEnabled():
|
||||
return True
|
||||
elif not check_enabled:
|
||||
return True
|
||||
|
||||
def _validate_parent_track_item():
|
||||
if track_name and track_name in track_item.parent().name():
|
||||
# filter only items fitting input track name
|
||||
return True
|
||||
elif not track_name:
|
||||
# or add all if no track_name was defined
|
||||
return True
|
||||
|
||||
def _validate_type_track_item():
|
||||
if track_type == "video" and isinstance(
|
||||
track_item.parent(), hiero.core.VideoTrack):
|
||||
# only video track items are allowed
|
||||
return_list.append(track_item)
|
||||
elif track_type and track_type == "audio" and isinstance(
|
||||
return True
|
||||
elif track_type == "audio" and isinstance(
|
||||
track_item.parent(), hiero.core.AudioTrack):
|
||||
# only audio track items are allowed
|
||||
return_list.append(track_item)
|
||||
elif not track_type:
|
||||
# add all if no track_type is defined
|
||||
return_list.append(track_item)
|
||||
return True
|
||||
|
||||
# return output list but make sure all items are TrackItems
|
||||
return [_i for _i in return_list
|
||||
if type(_i) == hiero.core.TrackItem]
|
||||
# check if track item is enabled
|
||||
return all([
|
||||
_validate_enabled_track_item(),
|
||||
_validate_type_track_item(),
|
||||
_validate_tagged_track_item(),
|
||||
_validate_parent_track_item(),
|
||||
_validate_correct_name_track_item()
|
||||
])
|
||||
|
||||
|
||||
def get_track_item_pype_tag(track_item):
|
||||
|
|
@ -245,7 +290,7 @@ def get_track_item_pype_tag(track_item):
|
|||
return None
|
||||
for tag in _tags:
|
||||
# return only correct tag defined by global name
|
||||
if tag.name() in self.pype_tag_name:
|
||||
if tag.name() == self.pype_tag_name:
|
||||
return tag
|
||||
|
||||
|
||||
|
|
@ -266,7 +311,7 @@ def set_track_item_pype_tag(track_item, data=None):
|
|||
"editable": "0",
|
||||
"note": "OpenPype data container",
|
||||
"icon": "openpype_icon.png",
|
||||
"metadata": {k: v for k, v in data.items()}
|
||||
"metadata": dict(data.items())
|
||||
}
|
||||
# get available pype tag if any
|
||||
_tag = get_track_item_pype_tag(track_item)
|
||||
|
|
@ -301,9 +346,9 @@ def get_track_item_pype_data(track_item):
|
|||
return None
|
||||
|
||||
# get tag metadata attribute
|
||||
tag_data = tag.metadata()
|
||||
tag_data = deepcopy(dict(tag.metadata()))
|
||||
# convert tag metadata to normal keys names and values to correct types
|
||||
for k, v in dict(tag_data).items():
|
||||
for k, v in tag_data.items():
|
||||
key = k.replace("tag.", "")
|
||||
|
||||
try:
|
||||
|
|
@ -324,7 +369,7 @@ def get_track_item_pype_data(track_item):
|
|||
log.warning(msg)
|
||||
value = v
|
||||
|
||||
data.update({key: value})
|
||||
data[key] = value
|
||||
|
||||
return data
|
||||
|
||||
|
|
@ -497,7 +542,7 @@ class PyblishSubmission(hiero.exporters.FnSubmission.Submission):
|
|||
from . import publish
|
||||
# Add submission to Hiero module for retrieval in plugins.
|
||||
hiero.submission = self
|
||||
publish()
|
||||
publish(hiero.ui.mainWindow())
|
||||
|
||||
|
||||
def add_submission():
|
||||
|
|
@ -527,7 +572,7 @@ class PublishAction(QtWidgets.QAction):
|
|||
# from getting picked up when not using the "Export" dialog.
|
||||
if hasattr(hiero, "submission"):
|
||||
del hiero.submission
|
||||
publish()
|
||||
publish(hiero.ui.mainWindow())
|
||||
|
||||
def eventHandler(self, event):
|
||||
# Add the Menu to the right-click menu
|
||||
|
|
@ -893,32 +938,32 @@ def apply_colorspace_clips():
|
|||
|
||||
|
||||
def is_overlapping(ti_test, ti_original, strict=False):
|
||||
covering_exp = bool(
|
||||
covering_exp = (
|
||||
(ti_test.timelineIn() <= ti_original.timelineIn())
|
||||
and (ti_test.timelineOut() >= ti_original.timelineOut())
|
||||
)
|
||||
inside_exp = bool(
|
||||
inside_exp = (
|
||||
(ti_test.timelineIn() >= ti_original.timelineIn())
|
||||
and (ti_test.timelineOut() <= ti_original.timelineOut())
|
||||
)
|
||||
overlaying_right_exp = bool(
|
||||
overlaying_right_exp = (
|
||||
(ti_test.timelineIn() < ti_original.timelineOut())
|
||||
and (ti_test.timelineOut() >= ti_original.timelineOut())
|
||||
)
|
||||
overlaying_left_exp = bool(
|
||||
overlaying_left_exp = (
|
||||
(ti_test.timelineOut() > ti_original.timelineIn())
|
||||
and (ti_test.timelineIn() <= ti_original.timelineIn())
|
||||
)
|
||||
|
||||
if not strict:
|
||||
if strict:
|
||||
return covering_exp
|
||||
else:
|
||||
return any((
|
||||
covering_exp,
|
||||
inside_exp,
|
||||
overlaying_right_exp,
|
||||
overlaying_left_exp
|
||||
))
|
||||
else:
|
||||
return covering_exp
|
||||
|
||||
|
||||
def get_sequence_pattern_and_padding(file):
|
||||
|
|
@ -936,17 +981,13 @@ def get_sequence_pattern_and_padding(file):
|
|||
"""
|
||||
foundall = re.findall(
|
||||
r"(#+)|(%\d+d)|(?<=[^a-zA-Z0-9])(\d+)(?=\.\w+$)", file)
|
||||
if foundall:
|
||||
found = sorted(list(set(foundall[0])))[-1]
|
||||
|
||||
if "%" in found:
|
||||
padding = int(re.findall(r"\d+", found)[-1])
|
||||
else:
|
||||
padding = len(found)
|
||||
|
||||
return found, padding
|
||||
else:
|
||||
if not foundall:
|
||||
return None, None
|
||||
found = sorted(list(set(foundall[0])))[-1]
|
||||
|
||||
padding = int(
|
||||
re.findall(r"\d+", found)[-1]) if "%" in found else len(found)
|
||||
return found, padding
|
||||
|
||||
|
||||
def sync_clip_name_to_data_asset(track_items_list):
|
||||
|
|
@ -982,7 +1023,7 @@ def sync_clip_name_to_data_asset(track_items_list):
|
|||
print("asset was changed in clip: {}".format(ti_name))
|
||||
|
||||
|
||||
def check_inventory_versions():
|
||||
def check_inventory_versions(track_items=None):
|
||||
"""
|
||||
Actual version color idetifier of Loaded containers
|
||||
|
||||
|
|
@ -993,14 +1034,14 @@ def check_inventory_versions():
|
|||
"""
|
||||
from . import parse_container
|
||||
|
||||
track_item = track_items or get_track_items()
|
||||
# presets
|
||||
clip_color_last = "green"
|
||||
clip_color = "red"
|
||||
|
||||
# get all track items from current timeline
|
||||
for track_item in get_track_items():
|
||||
for track_item in track_item:
|
||||
container = parse_container(track_item)
|
||||
|
||||
if container:
|
||||
# get representation from io
|
||||
representation = legacy_io.find_one({
|
||||
|
|
@ -1038,29 +1079,31 @@ def selection_changed_timeline(event):
|
|||
timeline_editor = event.sender
|
||||
selection = timeline_editor.selection()
|
||||
|
||||
selection = [ti for ti in selection
|
||||
if isinstance(ti, hiero.core.TrackItem)]
|
||||
track_items = get_track_items(
|
||||
selection=selection,
|
||||
track_type="video",
|
||||
check_enabled=True,
|
||||
check_locked=True,
|
||||
check_tagged=True
|
||||
)
|
||||
|
||||
# run checking function
|
||||
sync_clip_name_to_data_asset(selection)
|
||||
|
||||
# also mark old versions of loaded containers
|
||||
check_inventory_versions()
|
||||
sync_clip_name_to_data_asset(track_items)
|
||||
|
||||
|
||||
def before_project_save(event):
|
||||
track_items = get_track_items(
|
||||
selected=False,
|
||||
track_type="video",
|
||||
check_enabled=True,
|
||||
check_locked=True,
|
||||
check_tagged=True)
|
||||
check_tagged=True
|
||||
)
|
||||
|
||||
# run checking function
|
||||
sync_clip_name_to_data_asset(track_items)
|
||||
|
||||
# also mark old versions of loaded containers
|
||||
check_inventory_versions()
|
||||
check_inventory_versions(track_items)
|
||||
|
||||
|
||||
def get_main_window():
|
||||
|
|
|
|||
|
|
@ -143,6 +143,11 @@ def parse_container(track_item, validate=True):
|
|||
"""
|
||||
# convert tag metadata to normal keys names
|
||||
data = lib.get_track_item_pype_data(track_item)
|
||||
if (
|
||||
not data
|
||||
or data.get("id") != "pyblish.avalon.container"
|
||||
):
|
||||
return
|
||||
|
||||
if validate and data and data.get("schema"):
|
||||
schema.validate(data)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import os
|
||||
from pprint import pformat
|
||||
import re
|
||||
from copy import deepcopy
|
||||
|
||||
|
|
@ -400,7 +401,8 @@ class ClipLoader:
|
|||
|
||||
# inject asset data to representation dict
|
||||
self._get_asset_data()
|
||||
log.debug("__init__ self.data: `{}`".format(self.data))
|
||||
log.info("__init__ self.data: `{}`".format(pformat(self.data)))
|
||||
log.info("__init__ options: `{}`".format(pformat(options)))
|
||||
|
||||
# add active components to class
|
||||
if self.new_sequence:
|
||||
|
|
@ -482,7 +484,9 @@ class ClipLoader:
|
|||
|
||||
"""
|
||||
asset_name = self.context["representation"]["context"]["asset"]
|
||||
self.data["assetData"] = openpype.get_asset(asset_name)["data"]
|
||||
asset_doc = openpype.get_asset(asset_name)
|
||||
log.debug("__ asset_doc: {}".format(pformat(asset_doc)))
|
||||
self.data["assetData"] = asset_doc["data"]
|
||||
|
||||
def _make_track_item(self, source_bin_item, audio=False):
|
||||
""" Create track item with """
|
||||
|
|
@ -500,7 +504,7 @@ class ClipLoader:
|
|||
track_item.setSource(clip)
|
||||
track_item.setSourceIn(self.handle_start)
|
||||
track_item.setTimelineIn(self.timeline_in)
|
||||
track_item.setSourceOut(self.media_duration - self.handle_end)
|
||||
track_item.setSourceOut((self.media_duration) - self.handle_end)
|
||||
track_item.setTimelineOut(self.timeline_out)
|
||||
track_item.setPlaybackSpeed(1)
|
||||
self.active_track.addTrackItem(track_item)
|
||||
|
|
@ -520,14 +524,18 @@ class ClipLoader:
|
|||
self.handle_start = self.data["versionData"].get("handleStart")
|
||||
self.handle_end = self.data["versionData"].get("handleEnd")
|
||||
if self.handle_start is None:
|
||||
self.handle_start = int(self.data["assetData"]["handleStart"])
|
||||
self.handle_start = self.data["assetData"]["handleStart"]
|
||||
if self.handle_end is None:
|
||||
self.handle_end = int(self.data["assetData"]["handleEnd"])
|
||||
self.handle_end = self.data["assetData"]["handleEnd"]
|
||||
|
||||
self.handle_start = int(self.handle_start)
|
||||
self.handle_end = int(self.handle_end)
|
||||
|
||||
if self.sequencial_load:
|
||||
last_track_item = lib.get_track_items(
|
||||
sequence_name=self.active_sequence.name(),
|
||||
track_name=self.active_track.name())
|
||||
track_name=self.active_track.name()
|
||||
)
|
||||
if len(last_track_item) == 0:
|
||||
last_timeline_out = 0
|
||||
else:
|
||||
|
|
@ -541,17 +549,12 @@ class ClipLoader:
|
|||
self.timeline_in = int(self.data["assetData"]["clipIn"])
|
||||
self.timeline_out = int(self.data["assetData"]["clipOut"])
|
||||
|
||||
log.debug("__ self.timeline_in: {}".format(self.timeline_in))
|
||||
log.debug("__ self.timeline_out: {}".format(self.timeline_out))
|
||||
|
||||
# check if slate is included
|
||||
# either in version data families or by calculating frame diff
|
||||
slate_on = next(
|
||||
# check iterate if slate is in families
|
||||
(f for f in self.context["version"]["data"]["families"]
|
||||
if "slate" in f),
|
||||
# if nothing was found then use default None
|
||||
# so other bool could be used
|
||||
None) or bool(int(
|
||||
(self.timeline_out - self.timeline_in + 1)
|
||||
+ self.handle_start + self.handle_end) < self.media_duration)
|
||||
slate_on = "slate" in self.context["version"]["data"]["families"]
|
||||
log.debug("__ slate_on: {}".format(slate_on))
|
||||
|
||||
# if slate is on then remove the slate frame from beginning
|
||||
if slate_on:
|
||||
|
|
@ -572,7 +575,7 @@ class ClipLoader:
|
|||
# there were some cases were hiero was not creating it
|
||||
source_bin_item = None
|
||||
for item in self.active_bin.items():
|
||||
if self.data["clip_name"] in item.name():
|
||||
if self.data["clip_name"] == item.name():
|
||||
source_bin_item = item
|
||||
if not source_bin_item:
|
||||
log.warning("Problem with created Source clip: `{}`".format(
|
||||
|
|
@ -599,8 +602,8 @@ class Creator(LegacyCreator):
|
|||
rename_index = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
import openpype.hosts.hiero.api as phiero
|
||||
super(Creator, self).__init__(*args, **kwargs)
|
||||
import openpype.hosts.hiero.api as phiero
|
||||
self.presets = openpype.get_current_project_settings()[
|
||||
"hiero"]["create"].get(self.__class__.__name__, {})
|
||||
|
||||
|
|
@ -609,7 +612,10 @@ class Creator(LegacyCreator):
|
|||
self.sequence = phiero.get_current_sequence()
|
||||
|
||||
if (self.options or {}).get("useSelection"):
|
||||
self.selected = phiero.get_track_items(selected=True)
|
||||
timeline_selection = phiero.get_timeline_selection()
|
||||
self.selected = phiero.get_track_items(
|
||||
selection=timeline_selection
|
||||
)
|
||||
else:
|
||||
self.selected = phiero.get_track_items()
|
||||
|
||||
|
|
@ -716,6 +722,10 @@ class PublishClip:
|
|||
else:
|
||||
self.tag_data.update({"reviewTrack": None})
|
||||
|
||||
log.debug("___ self.tag_data: {}".format(
|
||||
pformat(self.tag_data)
|
||||
))
|
||||
|
||||
# create pype tag on track_item and add data
|
||||
lib.imprint(self.track_item, self.tag_data)
|
||||
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ def update_tag(tag, data):
|
|||
|
||||
# due to hiero bug we have to make sure keys which are not existent in
|
||||
# data are cleared of value by `None`
|
||||
for _mk in mtd.keys():
|
||||
for _mk in mtd.dict().keys():
|
||||
if _mk.replace("tag.", "") not in data_mtd.keys():
|
||||
mtd.setValue(_mk, str(None))
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,6 @@ from openpype.pipeline import (
|
|||
get_representation_path,
|
||||
)
|
||||
import openpype.hosts.hiero.api as phiero
|
||||
# from openpype.hosts.hiero.api import plugin, lib
|
||||
# reload(lib)
|
||||
# reload(plugin)
|
||||
# reload(phiero)
|
||||
|
||||
|
||||
class LoadClip(phiero.SequenceLoader):
|
||||
|
|
@ -106,7 +102,7 @@ class LoadClip(phiero.SequenceLoader):
|
|||
name = container['name']
|
||||
namespace = container['namespace']
|
||||
track_item = phiero.get_track_items(
|
||||
track_item_name=namespace)
|
||||
track_item_name=namespace).pop()
|
||||
version = legacy_io.find_one({
|
||||
"type": "version",
|
||||
"_id": representation["parent"]
|
||||
|
|
@ -157,7 +153,7 @@ class LoadClip(phiero.SequenceLoader):
|
|||
# load clip to timeline and get main variables
|
||||
namespace = container['namespace']
|
||||
track_item = phiero.get_track_items(
|
||||
track_item_name=namespace)
|
||||
track_item_name=namespace).pop()
|
||||
track = track_item.parent()
|
||||
|
||||
# remove track item from track
|
||||
|
|
|
|||
|
|
@ -19,9 +19,12 @@ class PrecollectInstances(pyblish.api.ContextPlugin):
|
|||
|
||||
def process(self, context):
|
||||
self.otio_timeline = context.data["otioTimeline"]
|
||||
|
||||
timeline_selection = phiero.get_timeline_selection()
|
||||
selected_timeline_items = phiero.get_track_items(
|
||||
selected=True, check_tagged=True, check_enabled=True)
|
||||
selection=timeline_selection,
|
||||
check_tagged=True,
|
||||
check_enabled=True
|
||||
)
|
||||
|
||||
# only return enabled track items
|
||||
if not selected_timeline_items:
|
||||
|
|
@ -292,10 +295,12 @@ class PrecollectInstances(pyblish.api.ContextPlugin):
|
|||
for otio_clip in self.otio_timeline.each_clip():
|
||||
track_name = otio_clip.parent().name
|
||||
parent_range = otio_clip.range_in_parent()
|
||||
if ti_track_name not in track_name:
|
||||
if ti_track_name != track_name:
|
||||
continue
|
||||
if otio_clip.name not in track_item.name():
|
||||
if otio_clip.name != track_item.name():
|
||||
continue
|
||||
self.log.debug("__ parent_range: {}".format(parent_range))
|
||||
self.log.debug("__ timeline_range: {}".format(timeline_range))
|
||||
if openpype.lib.is_overlapping_otio_ranges(
|
||||
parent_range, timeline_range, strict=True):
|
||||
|
||||
|
|
@ -312,7 +317,7 @@ class PrecollectInstances(pyblish.api.ContextPlugin):
|
|||
speed = track_item.playbackSpeed()
|
||||
timeline = phiero.get_current_sequence()
|
||||
frame_start = int(track_item.timelineIn())
|
||||
frame_duration = int(track_item.sourceDuration() / speed)
|
||||
frame_duration = int((track_item.duration() - 1) / speed)
|
||||
fps = timeline.framerate().toFloat()
|
||||
|
||||
return hiero_export.create_otio_time_range(
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ class PrecollectWorkfile(pyblish.api.ContextPlugin):
|
|||
"""Inject the current working file into context"""
|
||||
|
||||
label = "Precollect Workfile"
|
||||
order = pyblish.api.CollectorOrder - 0.5
|
||||
order = pyblish.api.CollectorOrder - 0.491
|
||||
|
||||
def process(self, context):
|
||||
|
||||
|
|
@ -84,6 +84,7 @@ class PrecollectWorkfile(pyblish.api.ContextPlugin):
|
|||
"colorspace": self.get_colorspace(project),
|
||||
"fps": fps
|
||||
}
|
||||
self.log.debug("__ context_data: {}".format(pformat(context_data)))
|
||||
context.data.update(context_data)
|
||||
|
||||
self.log.info("Creating instance: {}".format(instance))
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"""A module containing generic loader actions that will display in the Loader.
|
||||
|
||||
"""
|
||||
|
||||
import qargparse
|
||||
from openpype.pipeline import load
|
||||
from openpype.hosts.maya.api.lib import (
|
||||
maintained_selection,
|
||||
|
|
@ -98,6 +98,15 @@ class ImportMayaLoader(load.LoaderPlugin):
|
|||
icon = "arrow-circle-down"
|
||||
color = "#775555"
|
||||
|
||||
options = [
|
||||
qargparse.Boolean(
|
||||
"clean_import",
|
||||
label="Clean import",
|
||||
default=False,
|
||||
help="Should all occurences of cbId be purged?"
|
||||
)
|
||||
]
|
||||
|
||||
def load(self, context, name=None, namespace=None, data=None):
|
||||
import maya.cmds as cmds
|
||||
|
||||
|
|
@ -114,13 +123,22 @@ class ImportMayaLoader(load.LoaderPlugin):
|
|||
)
|
||||
|
||||
with maintained_selection():
|
||||
cmds.file(self.fname,
|
||||
i=True,
|
||||
preserveReferences=True,
|
||||
namespace=namespace,
|
||||
returnNewNodes=True,
|
||||
groupReference=True,
|
||||
groupName="{}:{}".format(namespace, name))
|
||||
nodes = cmds.file(self.fname,
|
||||
i=True,
|
||||
preserveReferences=True,
|
||||
namespace=namespace,
|
||||
returnNewNodes=True,
|
||||
groupReference=True,
|
||||
groupName="{}:{}".format(namespace, name))
|
||||
|
||||
if data.get("clean_import", False):
|
||||
remove_attributes = ["cbId"]
|
||||
for node in nodes:
|
||||
for attr in remove_attributes:
|
||||
if cmds.attributeQuery(attr, node=node, exists=True):
|
||||
full_attr = "{}.{}".format(node, attr)
|
||||
print("Removing {}".format(full_attr))
|
||||
cmds.deleteAttr(full_attr)
|
||||
|
||||
# We do not containerize imported content, it remains unmanaged
|
||||
return
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
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):
|
||||
# type: (dict, str) -> list
|
||||
if callable(dictionary[attr]):
|
||||
val = dictionary[attr]()
|
||||
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,19 @@ 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))
|
||||
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 +228,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 +286,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 +374,10 @@ class CollectLook(pyblish.api.InstancePlugin):
|
|||
"volumeShader",
|
||||
"displacementShader",
|
||||
"aiSurfaceShader",
|
||||
"aiVolumeShader"]
|
||||
"aiVolumeShader",
|
||||
"rman__surface",
|
||||
"rman__displacement"
|
||||
]
|
||||
if look_sets:
|
||||
materials = []
|
||||
|
||||
|
|
@ -374,9 +425,10 @@ 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
|
||||
|
|
@ -510,27 +562,24 @@ 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)))
|
||||
|
||||
attribute = FILE_NODES.get(cmds.nodeType(node))
|
||||
source = cmds.getAttr("{}.{}".format(
|
||||
node,
|
||||
attribute
|
||||
))
|
||||
computed_attribute = "{}.{}".format(node, attribute)
|
||||
if attribute == "fileTextureName":
|
||||
computed_attribute = node + ".computedFileTextureNamePattern"
|
||||
|
||||
source = cmds.getAttr(attribute)
|
||||
self.log.info(" - file source: {}".format(source))
|
||||
color_space_attr = "{}.colorSpace".format(node)
|
||||
try:
|
||||
|
|
@ -567,11 +616,15 @@ class CollectLook(pyblish.api.InstancePlugin):
|
|||
self.log.info(" - color space: {}".format(color_space))
|
||||
|
||||
# Define the resource
|
||||
return {"node": node,
|
||||
"attribute": attribute,
|
||||
"source": source, # required for resources
|
||||
"files": files,
|
||||
"color_space": color_space} # required for resources
|
||||
return {
|
||||
"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
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -39,6 +39,9 @@ class CollectBatchData(pyblish.api.ContextPlugin):
|
|||
def process(self, context):
|
||||
self.log.info("CollectBatchData")
|
||||
batch_dir = os.environ.get("OPENPYPE_PUBLISH_DATA")
|
||||
if os.environ.get("IS_TEST"):
|
||||
self.log.debug("Automatic testing, no batch data, skipping")
|
||||
return
|
||||
|
||||
assert batch_dir, (
|
||||
"Missing `OPENPYPE_PUBLISH_DATA`")
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import openpype.api
|
|||
from openpype.lib import (
|
||||
get_ffmpeg_tool_path,
|
||||
get_ffprobe_streams,
|
||||
path_to_subprocess_arg,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -37,82 +38,69 @@ class ExtractThumbnailSP(pyblish.api.InstancePlugin):
|
|||
if not thumbnail_repre:
|
||||
return
|
||||
|
||||
thumbnail_repre.pop("thumbnail")
|
||||
files = thumbnail_repre.get("files")
|
||||
if not files:
|
||||
return
|
||||
|
||||
if isinstance(files, list):
|
||||
files_len = len(files)
|
||||
file = str(files[0])
|
||||
first_filename = str(files[0])
|
||||
else:
|
||||
files_len = 1
|
||||
file = files
|
||||
first_filename = files
|
||||
|
||||
staging_dir = None
|
||||
is_jpeg = False
|
||||
if file.endswith(".jpeg") or file.endswith(".jpg"):
|
||||
is_jpeg = True
|
||||
|
||||
if is_jpeg and files_len == 1:
|
||||
# skip if already is single jpeg file
|
||||
return
|
||||
# Convert to jpeg if not yet
|
||||
full_input_path = os.path.join(
|
||||
thumbnail_repre["stagingDir"], first_filename
|
||||
)
|
||||
self.log.info("input {}".format(full_input_path))
|
||||
with tempfile.NamedTemporaryFile(suffix=".jpg") as tmp:
|
||||
full_thumbnail_path = tmp.name
|
||||
|
||||
elif is_jpeg:
|
||||
# use first frame as thumbnail if is sequence of jpegs
|
||||
full_thumbnail_path = os.path.join(
|
||||
thumbnail_repre["stagingDir"], file
|
||||
)
|
||||
self.log.info(
|
||||
"For thumbnail is used file: {}".format(full_thumbnail_path)
|
||||
)
|
||||
self.log.info("output {}".format(full_thumbnail_path))
|
||||
|
||||
else:
|
||||
# Convert to jpeg if not yet
|
||||
full_input_path = os.path.join(thumbnail_repre["stagingDir"], file)
|
||||
self.log.info("input {}".format(full_input_path))
|
||||
instance.context.data["cleanupFullPaths"].append(full_thumbnail_path)
|
||||
|
||||
full_thumbnail_path = tempfile.mkstemp(suffix=".jpg")[1]
|
||||
self.log.info("output {}".format(full_thumbnail_path))
|
||||
ffmpeg_path = get_ffmpeg_tool_path("ffmpeg")
|
||||
|
||||
ffmpeg_path = get_ffmpeg_tool_path("ffmpeg")
|
||||
ffmpeg_args = self.ffmpeg_args or {}
|
||||
|
||||
ffmpeg_args = self.ffmpeg_args or {}
|
||||
jpeg_items = [
|
||||
path_to_subprocess_arg(ffmpeg_path),
|
||||
# override file if already exists
|
||||
"-y"
|
||||
]
|
||||
|
||||
jpeg_items = [
|
||||
"\"{}\"".format(ffmpeg_path),
|
||||
# override file if already exists
|
||||
"-y"
|
||||
]
|
||||
|
||||
# add input filters from peresets
|
||||
jpeg_items.extend(ffmpeg_args.get("input") or [])
|
||||
# input file
|
||||
jpeg_items.append("-i \"{}\"".format(full_input_path))
|
||||
# add input filters from peresets
|
||||
jpeg_items.extend(ffmpeg_args.get("input") or [])
|
||||
# input file
|
||||
jpeg_items.extend([
|
||||
"-i", path_to_subprocess_arg(full_input_path),
|
||||
# extract only single file
|
||||
jpeg_items.append("-frames:v 1")
|
||||
"-frames:v", "1",
|
||||
# Add black background for transparent images
|
||||
jpeg_items.append((
|
||||
"-filter_complex"
|
||||
" \"color=black,format=rgb24[c]"
|
||||
"-filter_complex", (
|
||||
"\"color=black,format=rgb24[c]"
|
||||
";[c][0]scale2ref[c][i]"
|
||||
";[c][i]overlay=format=auto:shortest=1,setsar=1\""
|
||||
))
|
||||
),
|
||||
])
|
||||
|
||||
jpeg_items.extend(ffmpeg_args.get("output") or [])
|
||||
jpeg_items.extend(ffmpeg_args.get("output") or [])
|
||||
|
||||
# output file
|
||||
jpeg_items.append("\"{}\"".format(full_thumbnail_path))
|
||||
# output file
|
||||
jpeg_items.append(path_to_subprocess_arg(full_thumbnail_path))
|
||||
|
||||
subprocess_jpeg = " ".join(jpeg_items)
|
||||
subprocess_jpeg = " ".join(jpeg_items)
|
||||
|
||||
# run subprocess
|
||||
self.log.debug("Executing: {}".format(subprocess_jpeg))
|
||||
openpype.api.run_subprocess(
|
||||
subprocess_jpeg, shell=True, logger=self.log
|
||||
)
|
||||
# run subprocess
|
||||
self.log.debug("Executing: {}".format(subprocess_jpeg))
|
||||
openpype.api.run_subprocess(
|
||||
subprocess_jpeg, shell=True, logger=self.log
|
||||
)
|
||||
|
||||
# remove thumbnail key from origin repre
|
||||
thumbnail_repre.pop("thumbnail")
|
||||
streams = get_ffprobe_streams(full_thumbnail_path)
|
||||
width = height = None
|
||||
for stream in streams:
|
||||
|
|
@ -121,8 +109,7 @@ class ExtractThumbnailSP(pyblish.api.InstancePlugin):
|
|||
height = stream["height"]
|
||||
break
|
||||
|
||||
filename = os.path.basename(full_thumbnail_path)
|
||||
staging_dir = staging_dir or os.path.dirname(full_thumbnail_path)
|
||||
staging_dir, filename = os.path.split(full_thumbnail_path)
|
||||
|
||||
# create new thumbnail representation
|
||||
representation = {
|
||||
|
|
@ -130,15 +117,11 @@ class ExtractThumbnailSP(pyblish.api.InstancePlugin):
|
|||
'ext': 'jpg',
|
||||
'files': filename,
|
||||
"stagingDir": staging_dir,
|
||||
"tags": ["thumbnail"],
|
||||
"tags": ["thumbnail", "delete"],
|
||||
}
|
||||
if width and height:
|
||||
representation["width"] = width
|
||||
representation["height"] = height
|
||||
|
||||
# # add Delete tag when temp file was rendered
|
||||
if not is_jpeg:
|
||||
representation["tags"].append("delete")
|
||||
|
||||
self.log.info(f"New representation {representation}")
|
||||
instance.data["representations"].append(representation)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ from .pipeline import (
|
|||
|
||||
class TrayPublishCreator(Creator):
|
||||
create_allow_context_change = True
|
||||
host_name = "traypublisher"
|
||||
|
||||
def collect_instances(self):
|
||||
for instance_data in list_instances():
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ def otio_range_to_frame_range(otio_range):
|
|||
start = _ot.to_frames(
|
||||
otio_range.start_time, otio_range.start_time.rate)
|
||||
end = start + _ot.to_frames(
|
||||
otio_range.duration, otio_range.duration.rate) - 1
|
||||
otio_range.duration, otio_range.duration.rate)
|
||||
return start, end
|
||||
|
||||
|
||||
|
|
@ -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([i for i in range(first, (last + 1))])
|
||||
collection.indexes.update([i for i in range(first, last)])
|
||||
return dir_path, collection
|
||||
|
||||
|
||||
|
|
@ -218,6 +218,7 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end):
|
|||
"name": name
|
||||
}
|
||||
tw_node.update(metadata)
|
||||
tw_node["lookup"] = list(lookup)
|
||||
|
||||
# get first and last frame offsets
|
||||
offset_in += lookup[0]
|
||||
|
|
|
|||
|
|
@ -702,6 +702,32 @@ class ModulesManager:
|
|||
).format(expected_keys, " | ".join(msg_items)))
|
||||
return output
|
||||
|
||||
def collect_creator_plugin_paths(self, host_name):
|
||||
"""Helper to collect creator plugin paths from modules.
|
||||
|
||||
Args:
|
||||
host_name (str): For which host are creators meants.
|
||||
|
||||
Returns:
|
||||
list: List of creator plugin paths.
|
||||
"""
|
||||
# Output structure
|
||||
from openpype_interfaces import IPluginPaths
|
||||
|
||||
output = []
|
||||
for module in self.get_enabled_modules():
|
||||
# Skip module that do not inherit from `IPluginPaths`
|
||||
if not isinstance(module, IPluginPaths):
|
||||
continue
|
||||
|
||||
paths = module.get_creator_plugin_paths(host_name)
|
||||
if paths:
|
||||
# Convert to list if value is not list
|
||||
if not isinstance(paths, (list, tuple, set)):
|
||||
paths = [paths]
|
||||
output.extend(paths)
|
||||
return output
|
||||
|
||||
def collect_launch_hook_paths(self):
|
||||
"""Helper to collect hooks from modules inherited ILaunchHookPaths.
|
||||
|
||||
|
|
|
|||
|
|
@ -466,7 +466,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
|
|||
if instance_data.get("multipartExr"):
|
||||
preview = True
|
||||
|
||||
new_instance = copy(instance_data)
|
||||
new_instance = deepcopy(instance_data)
|
||||
new_instance["subset"] = subset_name
|
||||
new_instance["subsetGroup"] = group_name
|
||||
if preview:
|
||||
|
|
@ -883,8 +883,10 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
|
|||
new_i = copy(i)
|
||||
new_i["version"] = at.get("version")
|
||||
new_i["subset"] = at.get("subset")
|
||||
new_i["family"] = at.get("family")
|
||||
new_i["append"] = True
|
||||
new_i["families"].append(at.get("family"))
|
||||
# don't set subsetGroup if we are attaching
|
||||
new_i.pop("subsetGroup")
|
||||
new_instances.append(new_i)
|
||||
self.log.info(" - {} / v{}".format(
|
||||
at.get("subset"), at.get("version")))
|
||||
|
|
|
|||
|
|
@ -356,7 +356,7 @@ class PushHierValuesToNonHier(ServerAction):
|
|||
values_per_entity_id[entity_id][key] = None
|
||||
|
||||
values = query_custom_attributes(
|
||||
session, all_ids_with_parents, hier_attr_ids, True
|
||||
session, hier_attr_ids, all_ids_with_parents, True
|
||||
)
|
||||
for item in values:
|
||||
entity_id = item["entity_id"]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -176,7 +176,10 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin):
|
|||
# Add item to component list
|
||||
component_list.append(thumbnail_item)
|
||||
|
||||
if first_thumbnail_component is not None:
|
||||
if (
|
||||
not review_representations
|
||||
and first_thumbnail_component is not None
|
||||
):
|
||||
width = first_thumbnail_component_repre.get("width")
|
||||
height = first_thumbnail_component_repre.get("height")
|
||||
if not width or not height:
|
||||
|
|
|
|||
|
|
@ -14,11 +14,38 @@ class IPluginPaths(OpenPypeInterface):
|
|||
"publish": ["path/to/publish_plugins"]
|
||||
}
|
||||
"""
|
||||
# TODO validation of an output
|
||||
|
||||
@abstractmethod
|
||||
def get_plugin_paths(self):
|
||||
pass
|
||||
|
||||
def get_creator_plugin_paths(self, host_name):
|
||||
"""Retreive creator plugin paths.
|
||||
|
||||
Give addons ability to add creator plugin paths based on host name.
|
||||
|
||||
NOTES:
|
||||
- Default implementation uses 'get_plugin_paths' and always return
|
||||
all creator plugins.
|
||||
- Host name may help to organize plugins by host, but each creator
|
||||
alsomay have host filtering.
|
||||
|
||||
Args:
|
||||
host_name (str): For which host are the plugins meant.
|
||||
"""
|
||||
|
||||
paths = self.get_plugin_paths()
|
||||
if not paths or "create" not in paths:
|
||||
return []
|
||||
|
||||
create_paths = paths["create"]
|
||||
if not create_paths:
|
||||
return []
|
||||
|
||||
if not isinstance(create_paths, (list, tuple, set)):
|
||||
create_paths = [create_paths]
|
||||
return create_paths
|
||||
|
||||
|
||||
class ILaunchHookPaths(OpenPypeInterface):
|
||||
"""Module has launch hook paths to return.
|
||||
|
|
|
|||
|
|
@ -921,12 +921,18 @@ class SyncServerModule(OpenPypeModule, ITrayModule):
|
|||
if self.enabled:
|
||||
for project in self.connection.projects(projection={"name": 1}):
|
||||
project_name = project["name"]
|
||||
project_settings = self.get_sync_project_setting(project_name)
|
||||
if project_settings and project_settings.get("enabled"):
|
||||
if self.is_project_enabled(project_name):
|
||||
enabled_projects.append(project_name)
|
||||
|
||||
return enabled_projects
|
||||
|
||||
def is_project_enabled(self, project_name):
|
||||
if self.enabled:
|
||||
project_settings = self.get_sync_project_setting(project_name)
|
||||
if project_settings and project_settings.get("enabled"):
|
||||
return True
|
||||
return False
|
||||
|
||||
def handle_alternate_site(self, collection, representation, processed_site,
|
||||
file_id, synced_file_id):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import pyblish.api
|
|||
from pyblish.lib import MessageHandler
|
||||
|
||||
import openpype
|
||||
from openpype.modules import load_modules
|
||||
from openpype.modules import load_modules, ModulesManager
|
||||
from openpype.settings import get_project_settings
|
||||
from openpype.lib import (
|
||||
Anatomy,
|
||||
|
|
@ -107,7 +107,7 @@ def install_host(host):
|
|||
install_openpype_plugins()
|
||||
|
||||
|
||||
def install_openpype_plugins(project_name=None):
|
||||
def install_openpype_plugins(project_name=None, host_name=None):
|
||||
# Make sure modules are loaded
|
||||
load_modules()
|
||||
|
||||
|
|
@ -116,6 +116,18 @@ def install_openpype_plugins(project_name=None):
|
|||
pyblish.api.register_discovery_filter(filter_pyblish_plugins)
|
||||
register_loader_plugin_path(LOAD_PATH)
|
||||
|
||||
modules_manager = ModulesManager()
|
||||
publish_plugin_dirs = modules_manager.collect_plugin_paths()["publish"]
|
||||
for path in publish_plugin_dirs:
|
||||
pyblish.api.register_plugin_path(path)
|
||||
|
||||
if host_name is None:
|
||||
host_name = os.environ.get("AVALON_APP")
|
||||
|
||||
creator_paths = modules_manager.collect_creator_plugin_paths(host_name)
|
||||
for creator_path in creator_paths:
|
||||
register_creator_plugin_path(creator_path)
|
||||
|
||||
if project_name is None:
|
||||
project_name = os.environ.get("AVALON_PROJECT")
|
||||
|
||||
|
|
|
|||
|
|
@ -749,6 +749,10 @@ class CreateContext:
|
|||
"""Is host valid for creation."""
|
||||
return self._host_is_valid
|
||||
|
||||
@property
|
||||
def host_name(self):
|
||||
return os.environ["AVALON_APP"]
|
||||
|
||||
@property
|
||||
def log(self):
|
||||
"""Dynamic access to logger."""
|
||||
|
|
@ -861,6 +865,17 @@ class CreateContext:
|
|||
"Using first and skipping following"
|
||||
))
|
||||
continue
|
||||
|
||||
# Filter by host name
|
||||
if (
|
||||
creator_class.host_name
|
||||
and creator_class.host_name != self.host_name
|
||||
):
|
||||
self.log.info((
|
||||
"Creator's host name is not supported for current host {}"
|
||||
).format(creator_class.host_name, self.host_name))
|
||||
continue
|
||||
|
||||
creator = creator_class(
|
||||
self,
|
||||
system_settings,
|
||||
|
|
|
|||
|
|
@ -63,6 +63,12 @@ class BaseCreator:
|
|||
# `openpype.pipeline.attribute_definitions`
|
||||
instance_attr_defs = []
|
||||
|
||||
# Filtering by host name - can be used to be filtered by host name
|
||||
# - used on all hosts when set to 'None' for Backwards compatibility
|
||||
# - was added afterwards
|
||||
# QUESTION make this required?
|
||||
host_name = None
|
||||
|
||||
def __init__(
|
||||
self, create_context, system_settings, project_settings, headless=False
|
||||
):
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ class CollectHierarchy(pyblish.api.ContextPlugin):
|
|||
"frameEnd": instance.data["frameEnd"],
|
||||
"clipIn": instance.data["clipIn"],
|
||||
"clipOut": instance.data["clipOut"],
|
||||
'fps': instance.context.data["fps"],
|
||||
"fps": instance.data["fps"],
|
||||
"resolutionWidth": instance.data["resolutionWidth"],
|
||||
"resolutionHeight": instance.data["resolutionHeight"],
|
||||
"pixelAspect": instance.data["pixelAspect"]
|
||||
|
|
|
|||