diff --git a/pype/hosts/hiero/__init__.py b/pype/hosts/hiero/__init__.py index 9eb3cd551ab..e69de29bb2d 100644 --- a/pype/hosts/hiero/__init__.py +++ b/pype/hosts/hiero/__init__.py @@ -1,121 +0,0 @@ -import os -from pype.api import Logger -from avalon import api as avalon -from pyblish import api as pyblish -from pype import PLUGINS_DIR - -from .workio import ( - open_file, - save_file, - current_file, - has_unsaved_changes, - file_extensions, - work_root -) - -from .menu import ( - install as menu_install, - _update_menu_task_label -) - -from .events import register_hiero_events - -__all__ = [ - # Workfiles API - "open_file", - "save_file", - "current_file", - "has_unsaved_changes", - "file_extensions", - "work_root", -] - -# get logger -log = Logger().get_logger(__name__) - - -''' Creating all important host related variables ''' -AVALON_CONFIG = os.getenv("AVALON_CONFIG", "pype") - -# plugin root path -PUBLISH_PATH = os.path.join(PLUGINS_DIR, "hiero", "publish") -LOAD_PATH = os.path.join(PLUGINS_DIR, "hiero", "load") -CREATE_PATH = os.path.join(PLUGINS_DIR, "hiero", "create") -INVENTORY_PATH = os.path.join(PLUGINS_DIR, "hiero", "inventory") - -# registering particular pyblish gui but `lite` is recomended!! -if os.getenv("PYBLISH_GUI", None): - pyblish.register_gui(os.getenv("PYBLISH_GUI", None)) - - -def install(): - """ - Installing Hiero integration for avalon - - Args: - config (obj): avalon config module `pype` in our case, it is not - used but required by avalon.api.install() - - """ - - # adding all events - _register_events() - - log.info("Registering Hiero plug-ins..") - pyblish.register_host("hiero") - pyblish.register_plugin_path(PUBLISH_PATH) - avalon.register_plugin_path(avalon.Loader, LOAD_PATH) - avalon.register_plugin_path(avalon.Creator, CREATE_PATH) - avalon.register_plugin_path(avalon.InventoryAction, INVENTORY_PATH) - - # Disable all families except for the ones we explicitly want to see - family_states = [ - "write", - "review", - "plate" - ] - - avalon.data["familiesStateDefault"] = False - avalon.data["familiesStateToggled"] = family_states - - # install menu - menu_install() - - # register hiero events - register_hiero_events() - - -def uninstall(): - """ - Uninstalling Hiero integration for avalon - - """ - log.info("Deregistering Hiero plug-ins..") - pyblish.deregister_host("hiero") - pyblish.deregister_plugin_path(PUBLISH_PATH) - avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH) - avalon.deregister_plugin_path(avalon.Creator, CREATE_PATH) - - -def _register_events(): - """ - Adding all callbacks. - """ - - # if task changed then change notext of hiero - avalon.on("taskChanged", _update_menu_task_label) - log.info("Installed event callback for 'taskChanged'..") - - -def ls(): - """List available containers. - - This function is used by the Container Manager in Nuke. You'll - need to implement a for-loop that then *yields* one Container at - a time. - - See the `container.json` schema for details on how it should look, - and the Maya equivalent, which is in `avalon.maya.pipeline` - """ - # TODO: listing all availabe containers form sequence - return diff --git a/pype/hosts/hiero/api/__init__.py b/pype/hosts/hiero/api/__init__.py new file mode 100644 index 00000000000..fcb1d50ea81 --- /dev/null +++ b/pype/hosts/hiero/api/__init__.py @@ -0,0 +1,101 @@ +from .workio import ( + open_file, + save_file, + current_file, + has_unsaved_changes, + file_extensions, + work_root +) + +from .pipeline import ( + launch_workfiles_app, + ls, + install, + uninstall, + reload_config, + containerise, + publish, + maintained_selection, + parse_container, + update_container, + reset_selection +) + +from .lib import ( + get_track_items, + get_current_project, + get_current_sequence, + get_current_track, + get_track_item_pype_tag, + set_track_item_pype_tag, + get_track_item_pype_data, + set_publish_attribute, + get_publish_attribute, + imprint, + get_selected_track_items, + set_selected_track_items, + create_nuke_workfile_clips, + create_bin, + apply_colorspace_project, + apply_colorspace_clips, + is_overlapping, + get_sequence_pattern_and_padding +) + +from .plugin import ( + CreatorWidget, + Creator, + PublishClip, + SequenceLoader, + ClipLoader +) + +__all__ = [ + # avalon pipeline module + "launch_workfiles_app", + "ls", + "install", + "uninstall", + "reload_config", + "containerise", + "publish", + "maintained_selection", + "parse_container", + "update_container", + "reset_selection", + + # Workfiles API + "open_file", + "save_file", + "current_file", + "has_unsaved_changes", + "file_extensions", + "work_root", + + # Lib functions + "get_track_items", + "get_current_project", + "get_current_sequence", + "get_current_track", + "get_track_item_pype_tag", + "set_track_item_pype_tag", + "get_track_item_pype_data", + "set_publish_attribute", + "get_publish_attribute", + "imprint", + "get_selected_track_items", + "set_selected_track_items", + "create_nuke_workfile_clips", + "create_bin", + "is_overlapping", + "apply_colorspace_project", + "apply_colorspace_clips", + "get_sequence_pattern_and_padding", + + # plugins + "CreatorWidget", + "Creator", + "PublishClip", + "SequenceLoader", + "ClipLoader" +] diff --git a/pype/hosts/hiero/events.py b/pype/hosts/hiero/api/events.py similarity index 87% rename from pype/hosts/hiero/events.py rename to pype/hosts/hiero/api/events.py index 4f9851cbac8..918912ce2e1 100644 --- a/pype/hosts/hiero/events.py +++ b/pype/hosts/hiero/api/events.py @@ -1,8 +1,10 @@ import os import hiero.core.events +import avalon.api as avalon from pype.api import Logger from .lib import sync_avalon_data_to_workfile, launch_workfiles_app -from .tags import add_tags_from_presets +from .tags import add_tags_to_workfile +from .menu import update_menu_task_label log = Logger().get_logger(__name__) @@ -28,7 +30,7 @@ def afterNewProjectCreated(event): sync_avalon_data_to_workfile() # add tags from preset - add_tags_from_presets() + add_tags_to_workfile() # Workfiles. if int(os.environ.get("WORKFILES_STARTUP", "0")): @@ -48,7 +50,7 @@ def afterProjectLoad(event): sync_avalon_data_to_workfile() # add tags from preset - add_tags_from_presets() + add_tags_to_workfile() def beforeProjectClosed(event): @@ -77,7 +79,7 @@ def register_hiero_events(): "kAfterNewProjectCreated, kBeforeProjectLoad, kAfterProjectLoad, " "kBeforeProjectSave, kAfterProjectSave, kBeforeProjectClose, " "kAfterProjectClose, kShutdown, kStartup" - ) + ) # hiero.core.events.registerInterest( # "kBeforeNewProjectCreated", beforeNewProjectCreated) @@ -105,3 +107,13 @@ def register_hiero_events(): # workfiles hiero.core.events.registerEventType("kStartWorkfiles") hiero.core.events.registerInterest("kStartWorkfiles", launch_workfiles_app) + + +def register_events(): + """ + Adding all callbacks. + """ + + # if task changed then change notext of hiero + avalon.on("taskChanged", update_menu_task_label) + log.info("Installed event callback for 'taskChanged'..") diff --git a/pype/hosts/hiero/api/lib.py b/pype/hosts/hiero/api/lib.py new file mode 100644 index 00000000000..14760a31371 --- /dev/null +++ b/pype/hosts/hiero/api/lib.py @@ -0,0 +1,895 @@ +""" +Host specific functions where host api is connected +""" +import os +import re +import sys +import ast +import hiero +import avalon.api as avalon +import avalon.io +from avalon.vendor.Qt import QtWidgets +from pype.api import (Logger, Anatomy, config) +from . import tags +import shutil +from compiler.ast import flatten + +try: + from PySide.QtCore import QFile, QTextStream + from PySide.QtXml import QDomDocument +except ImportError: + from PySide2.QtCore import QFile, QTextStream + from PySide2.QtXml import QDomDocument + +# from opentimelineio import opentime +# from pprint import pformat + +log = Logger().get_logger(__name__) + +self = sys.modules[__name__] +self._has_been_setup = False +self._has_menu = False +self._registered_gui = None +self.pype_tag_name = "Pype Data" +self.default_sequence_name = "PypeSequence" +self.default_bin_name = "PypeBin" + +AVALON_CONFIG = os.getenv("AVALON_CONFIG", "pype") + + +def get_current_project(remove_untitled=False): + projects = flatten(hiero.core.projects()) + if not remove_untitled: + return next(iter(projects)) + + # if remove_untitled + for proj in projects: + if "Untitled" in proj.name(): + proj.close() + else: + return proj + + +def get_current_sequence(name=None, new=False): + """ + Get current sequence in context of active project. + + Args: + name (str)[optional]: name of sequence we want to return + new (bool)[optional]: if we want to create new one + + Returns: + hiero.core.Sequence: the sequence object + """ + sequence = None + project = get_current_project() + root_bin = project.clipsBin() + + if new: + # create new + name = name or self.default_sequence_name + sequence = hiero.core.Sequence(name) + root_bin.addItem(hiero.core.BinItem(sequence)) + elif name: + # look for sequence by name + sequences = project.sequences() + for _sequence in sequences: + if _sequence.name() == name: + sequence = _sequence + if not sequence: + # if nothing found create new with input name + sequence = get_current_sequence(name, True) + elif not name and not new: + # if name is none and new is False then return current open sequence + sequence = hiero.ui.activeSequence() + + return sequence + + +def get_current_track(sequence, name, audio=False): + """ + Get current track in context of active project. + + Creates new if none is found. + + Args: + sequence (hiero.core.Sequence): hiero sequene object + name (str): name of track we want to return + audio (bool)[optional]: switch to AudioTrack + + Returns: + hiero.core.Track: the track object + """ + tracks = sequence.videoTracks() + + if audio: + tracks = sequence.audioTracks() + + # get track by name + track = None + for _track in tracks: + if _track.name() in name: + track = _track + + if not track: + if not audio: + track = hiero.core.VideoTrack(name) + else: + track = hiero.core.AudioTrack(name) + sequence.addTrack(track) + + return track + + +def get_track_items( + selected=False, + sequence_name=None, + track_item_name=None, + track_name=None, + track_type=None, + check_enabled=True, + check_locked=True, + check_tagged=False): + """Get all available current timeline track items. + + Attribute: + selected (bool)[optional]: return only selected items on timeline + 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 + track_type (str)[optional]: return only items of given type + (`audio` or `video`) default is `video` + check_enabled (bool)[optional]: ignore disabled if True + check_locked (bool)[optional]: ignore locked if True + + Return: + list or hiero.core.TrackItem: list of track items or single track item + """ + return_list = list() + track_items = list() + + # get selected track items or all in active sequence + if selected: + 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) + else: + sequence = get_current_sequence(name=sequence_name) + # get all available tracks from sequence + tracks = list(sequence.audioTracks()) + list(sequence.videoTracks()) + # loop all tracks + for track in tracks: + if check_locked and track.isLocked(): + continue + 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(): + continue + + # check if track item is enabled + if check_enabled: + if not item.isEnabled(): + continue + if track_item_name: + if item.name() in track_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) + + # filter out only track items with defined track_type + for track_item in track_items: + if track_type and 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( + 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 return_list + + +def get_track_item_pype_tag(track_item): + """ + Get pype track item tag created by creator or loader plugin. + + Attributes: + trackItem (hiero.core.TrackItem): hiero object + + Returns: + hiero.core.Tag: hierarchy, orig clip attributes + """ + # get all tags from track item + _tags = track_item.tags() + if not _tags: + return None + for tag in _tags: + # return only correct tag defined by global name + if tag.name() in self.pype_tag_name: + return tag + + +def set_track_item_pype_tag(track_item, data=None): + """ + Set pype track item tag to input track_item. + + Attributes: + trackItem (hiero.core.TrackItem): hiero object + + Returns: + hiero.core.Tag + """ + data = data or dict() + + # basic Tag's attribute + tag_data = { + "editable": "0", + "note": "Pype data holder", + "icon": "pype_icon.png", + "metadata": {k: v for k, v in data.items()} + } + # get available pype tag if any + _tag = get_track_item_pype_tag(track_item) + + if _tag: + # it not tag then create one + tag = tags.update_tag(_tag, tag_data) + else: + # if pype tag available then update with input data + tag = tags.create_tag(self.pype_tag_name, tag_data) + # add it to the input track item + track_item.addTag(tag) + + return tag + + +def get_track_item_pype_data(track_item): + """ + Get track item's pype tag data. + + Attributes: + trackItem (hiero.core.TrackItem): hiero object + + Returns: + dict: data found on pype tag + """ + data = dict() + # get pype data tag from track item + tag = get_track_item_pype_tag(track_item) + + if not tag: + return None + + # get tag metadata attribut + tag_data = tag.metadata() + # convert tag metadata to normal keys names and values to correct types + for k, v in dict(tag_data).items(): + key = k.replace("tag.", "") + + try: + # capture exceptions which are related to strings only + value = ast.literal_eval(v) + except (ValueError, SyntaxError): + value = v + + data.update({key: value}) + + return data + + +def imprint(track_item, data=None): + """ + Adding `Avalon data` into a hiero track item tag. + + Also including publish attribute into tag. + + Arguments: + track_item (hiero.core.TrackItem): hiero track item object + data (dict): Any data which needst to be imprinted + + Examples: + data = { + 'asset': 'sq020sh0280', + 'family': 'render', + 'subset': 'subsetMain' + } + """ + data = data or {} + + tag = set_track_item_pype_tag(track_item, data) + + # add publish attribute + set_publish_attribute(tag, True) + + +def set_publish_attribute(tag, value): + """ Set Publish attribute in input Tag object + + Attribute: + tag (hiero.core.Tag): a tag object + value (bool): True or False + """ + tag_data = tag.metadata() + # set data to the publish attribute + tag_data.setValue("tag.publish", str(value)) + + +def get_publish_attribute(tag): + """ Get Publish attribute from input Tag object + + Attribute: + tag (hiero.core.Tag): a tag object + value (bool): True or False + """ + tag_data = tag.metadata() + # get data to the publish attribute + value = tag_data.value("tag.publish") + # return value converted to bool value. Atring is stored in tag. + return ast.literal_eval(value) + + +def sync_avalon_data_to_workfile(): + # import session to get project dir + project_name = avalon.Session["AVALON_PROJECT"] + + anatomy = Anatomy(project_name) + work_template = anatomy.templates["work"]["path"] + work_root = anatomy.root_value_for_template(work_template) + active_project_root = ( + os.path.join(work_root, project_name) + ).replace("\\", "/") + # getting project + project = get_current_project() + + if "Tag Presets" in project.name(): + return + + log.debug("Synchronizing Pype metadata to project: {}".format( + project.name())) + + # set project root with backward compatibility + try: + project.setProjectDirectory(active_project_root) + except Exception: + # old way of seting it + project.setProjectRoot(active_project_root) + + # get project data from avalon db + project_doc = avalon.io.find_one({"type": "project"}) + project_data = project_doc["data"] + + log.debug("project_data: {}".format(project_data)) + + # get format and fps property from avalon db on project + width = project_data["resolutionWidth"] + height = project_data["resolutionHeight"] + pixel_aspect = project_data["pixelAspect"] + fps = project_data['fps'] + format_name = project_data['code'] + + # create new format in hiero project + format = hiero.core.Format(width, height, pixel_aspect, format_name) + project.setOutputFormat(format) + + # set fps to hiero project + project.setFramerate(fps) + + # TODO: add auto colorspace set from project drop + log.info("Project property has been synchronised with Avalon db") + + +def launch_workfiles_app(event): + """ + Event for launching workfiles after hiero start + + Args: + event (obj): required but unused + """ + from . import launch_workfiles_app + launch_workfiles_app() + + +def setup(console=False, port=None, menu=True): + """Setup integration + + Registers Pyblish for Hiero plug-ins and appends an item to the File-menu + + Arguments: + console (bool): Display console with GUI + port (int, optional): Port from which to start looking for an + available port to connect with Pyblish QML, default + provided by Pyblish Integration. + menu (bool, optional): Display file menu in Hiero. + """ + + if self._has_been_setup: + teardown() + + add_submission() + + if menu: + add_to_filemenu() + self._has_menu = True + + self._has_been_setup = True + log.debug("pyblish: Loaded successfully.") + + +def teardown(): + """Remove integration""" + if not self._has_been_setup: + return + + if self._has_menu: + remove_from_filemenu() + self._has_menu = False + + self._has_been_setup = False + log.debug("pyblish: Integration torn down successfully") + + +def remove_from_filemenu(): + raise NotImplementedError("Implement me please.") + + +def add_to_filemenu(): + PublishAction() + + +class PyblishSubmission(hiero.exporters.FnSubmission.Submission): + + def __init__(self): + hiero.exporters.FnSubmission.Submission.__init__(self) + + def addToQueue(self): + from . import publish + # Add submission to Hiero module for retrieval in plugins. + hiero.submission = self + publish() + + +def add_submission(): + registry = hiero.core.taskRegistry + registry.addSubmission("Pyblish", PyblishSubmission) + + +class PublishAction(QtWidgets.QAction): + """ + Action with is showing as menu item + """ + + def __init__(self): + QtWidgets.QAction.__init__(self, "Publish", None) + self.triggered.connect(self.publish) + + for interest in ["kShowContextMenu/kTimeline", + "kShowContextMenukBin", + "kShowContextMenu/kSpreadsheet"]: + hiero.core.events.registerInterest(interest, self.eventHandler) + + self.setShortcut("Ctrl+Alt+P") + + def publish(self): + from . import publish + # Removing "submission" attribute from hiero module, to prevent tasks + # from getting picked up when not using the "Export" dialog. + if hasattr(hiero, "submission"): + del hiero.submission + publish() + + def eventHandler(self, event): + # Add the Menu to the right-click menu + event.menu.addAction(self) + + +# def CreateNukeWorkfile(nodes=None, +# nodes_effects=None, +# to_timeline=False, +# **kwargs): +# ''' Creating nuke workfile with particular version with given nodes +# Also it is creating timeline track items as precomps. +# +# Arguments: +# nodes(list of dict): each key in dict is knob order is important +# to_timeline(type): will build trackItem with metadata +# +# Returns: +# bool: True if done +# +# Raises: +# Exception: with traceback +# +# ''' +# import hiero.core +# from avalon.nuke import imprint +# from pype.hosts.nuke import ( +# lib as nklib +# ) +# +# # check if the file exists if does then Raise "File exists!" +# if os.path.exists(filepath): +# raise FileExistsError("File already exists: `{}`".format(filepath)) +# +# # if no representations matching then +# # Raise "no representations to be build" +# if len(representations) == 0: +# raise AttributeError("Missing list of `representations`") +# +# # check nodes input +# if len(nodes) == 0: +# log.warning("Missing list of `nodes`") +# +# # create temp nk file +# nuke_script = hiero.core.nuke.ScriptWriter() +# +# # create root node and save all metadata +# root_node = hiero.core.nuke.RootNode() +# +# anatomy = Anatomy(os.environ["AVALON_PROJECT"]) +# work_template = anatomy.templates["work"]["path"] +# root_path = anatomy.root_value_for_template(work_template) +# +# nuke_script.addNode(root_node) +# +# # here to call pype.hosts.nuke.lib.BuildWorkfile +# script_builder = nklib.BuildWorkfile( +# root_node=root_node, +# root_path=root_path, +# nodes=nuke_script.getNodes(), +# **kwargs +# ) + + +def create_nuke_workfile_clips(nuke_workfiles, seq=None): + ''' + nuke_workfiles is list of dictionaries like: + [{ + 'path': 'P:/Jakub_testy_pipeline/test_v01.nk', + 'name': 'test', + 'handleStart': 15, # added asymetrically to handles + 'handleEnd': 10, # added asymetrically to handles + "clipIn": 16, + "frameStart": 991, + "frameEnd": 1023, + 'task': 'Comp-tracking', + 'work_dir': 'VFX_PR', + 'shot': '00010' + }] + ''' + + proj = hiero.core.projects()[-1] + root = proj.clipsBin() + + if not seq: + seq = hiero.core.Sequence('NewSequences') + root.addItem(hiero.core.BinItem(seq)) + # todo will ned to define this better + # track = seq[1] # lazy example to get a destination# track + clips_lst = [] + for nk in nuke_workfiles: + task_path = '/'.join([nk['work_dir'], nk['shot'], nk['task']]) + bin = create_bin(task_path, proj) + + if nk['task'] not in seq.videoTracks(): + track = hiero.core.VideoTrack(nk['task']) + seq.addTrack(track) + else: + track = seq.tracks(nk['task']) + + # create clip media + media = hiero.core.MediaSource(nk['path']) + media_in = int(media.startTime() or 0) + media_duration = int(media.duration() or 0) + + handle_start = nk.get("handleStart") + handle_end = nk.get("handleEnd") + + if media_in: + source_in = media_in + handle_start + else: + source_in = nk["frameStart"] + handle_start + + if media_duration: + source_out = (media_in + media_duration - 1) - handle_end + else: + source_out = nk["frameEnd"] - handle_end + + source = hiero.core.Clip(media) + + name = os.path.basename(os.path.splitext(nk['path'])[0]) + split_name = split_by_client_version(name)[0] or name + + # add to bin as clip item + items_in_bin = [b.name() for b in bin.items()] + if split_name not in items_in_bin: + binItem = hiero.core.BinItem(source) + bin.addItem(binItem) + + new_source = [ + item for item in bin.items() if split_name in item.name() + ][0].items()[0].item() + + # add to track as clip item + trackItem = hiero.core.TrackItem( + split_name, hiero.core.TrackItem.kVideo) + trackItem.setSource(new_source) + trackItem.setSourceIn(source_in) + trackItem.setSourceOut(source_out) + trackItem.setTimelineIn(nk["clipIn"]) + trackItem.setTimelineOut(nk["clipIn"] + (source_out - source_in)) + track.addTrackItem(trackItem) + clips_lst.append(trackItem) + + return clips_lst + + +def create_bin(path=None, project=None): + ''' + Create bin in project. + If the path is "bin1/bin2/bin3" it will create whole depth + and return `bin3` + + ''' + # get the first loaded project + project = project or get_current_project() + + path = path or self.default_bin_name + + path = path.replace("\\", "/").split("/") + + root_bin = project.clipsBin() + + done_bin_lst = [] + for i, b in enumerate(path): + if i == 0 and len(path) > 1: + if b in [bin.name() for bin in root_bin.bins()]: + bin = [bin for bin in root_bin.bins() if b in bin.name()][0] + done_bin_lst.append(bin) + else: + create_bin = hiero.core.Bin(b) + root_bin.addItem(create_bin) + done_bin_lst.append(create_bin) + + elif i >= 1 and i < len(path) - 1: + if b in [bin.name() for bin in done_bin_lst[i - 1].bins()]: + bin = [ + bin for bin in done_bin_lst[i - 1].bins() + if b in bin.name() + ][0] + done_bin_lst.append(bin) + else: + create_bin = hiero.core.Bin(b) + done_bin_lst[i - 1].addItem(create_bin) + done_bin_lst.append(create_bin) + + elif i == len(path) - 1: + if b in [bin.name() for bin in done_bin_lst[i - 1].bins()]: + bin = [ + bin for bin in done_bin_lst[i - 1].bins() + if b in bin.name() + ][0] + done_bin_lst.append(bin) + else: + create_bin = hiero.core.Bin(b) + done_bin_lst[i - 1].addItem(create_bin) + done_bin_lst.append(create_bin) + + return done_bin_lst[-1] + + +def split_by_client_version(string): + regex = r"[/_.]v\d+" + try: + matches = re.findall(regex, string, re.IGNORECASE) + return string.split(matches[0]) + except Exception as error: + log.error(error) + return None + + +def get_selected_track_items(sequence=None): + _sequence = sequence or get_current_sequence() + + # Getting selection + timeline_editor = hiero.ui.getTimelineEditor(_sequence) + return timeline_editor.selection() + + +def set_selected_track_items(track_items_list, sequence=None): + _sequence = sequence or get_current_sequence() + + # Getting selection + timeline_editor = hiero.ui.getTimelineEditor(_sequence) + return timeline_editor.setSelection(track_items_list) + + +def _read_doc_from_path(path): + # reading QDomDocument from HROX path + hrox_file = QFile(path) + if not hrox_file.open(QFile.ReadOnly): + raise RuntimeError("Failed to open file for reading") + doc = QDomDocument() + doc.setContent(hrox_file) + hrox_file.close() + return doc + + +def _write_doc_to_path(doc, path): + # write QDomDocument to path as HROX + hrox_file = QFile(path) + if not hrox_file.open(QFile.WriteOnly): + raise RuntimeError("Failed to open file for writing") + stream = QTextStream(hrox_file) + doc.save(stream, 1) + hrox_file.close() + + +def _set_hrox_project_knobs(doc, **knobs): + # set attributes to Project Tag + proj_elem = doc.documentElement().firstChildElement("Project") + for k, v in knobs.items(): + proj_elem.setAttribute(k, v) + + +def apply_colorspace_project(): + # get path the the active projects + project = get_current_project(remove_untitled=True) + current_file = project.path() + + # close the active project + project.close() + + # get presets for hiero + presets = config.get_init_presets() + colorspace = presets["colorspace"] + hiero_project_clrs = colorspace.get("hiero", {}).get("project", {}) + + # save the workfile as subversion "comment:_colorspaceChange" + split_current_file = os.path.splitext(current_file) + copy_current_file = current_file + + if "_colorspaceChange" not in current_file: + copy_current_file = ( + split_current_file[0] + + "_colorspaceChange" + + split_current_file[1] + ) + + try: + # duplicate the file so the changes are applied only to the copy + shutil.copyfile(current_file, copy_current_file) + except shutil.Error: + # in case the file already exists and it want to copy to the + # same filewe need to do this trick + # TEMP file name change + copy_current_file_tmp = copy_current_file + "_tmp" + # create TEMP file + shutil.copyfile(current_file, copy_current_file_tmp) + # remove original file + os.remove(current_file) + # copy TEMP back to original name + shutil.copyfile(copy_current_file_tmp, copy_current_file) + # remove the TEMP file as we dont need it + os.remove(copy_current_file_tmp) + + # use the code from bellow for changing xml hrox Attributes + hiero_project_clrs.update({"name": os.path.basename(copy_current_file)}) + + # read HROX in as QDomSocument + doc = _read_doc_from_path(copy_current_file) + + # apply project colorspace properties + _set_hrox_project_knobs(doc, **hiero_project_clrs) + + # write QDomSocument back as HROX + _write_doc_to_path(doc, copy_current_file) + + # open the file as current project + hiero.core.openProject(copy_current_file) + + +def apply_colorspace_clips(): + project = get_current_project(remove_untitled=True) + clips = project.clips() + + # get presets for hiero + presets = config.get_init_presets() + colorspace = presets["colorspace"] + hiero_clips_clrs = colorspace.get("hiero", {}).get("clips", {}) + + for clip in clips: + clip_media_source_path = clip.mediaSource().firstpath() + clip_name = clip.name() + clip_colorspace = clip.sourceMediaColourTransform() + + if "default" in clip_colorspace: + continue + + # check if any colorspace presets for read is mathing + preset_clrsp = next((hiero_clips_clrs[k] + for k in hiero_clips_clrs + if bool(re.search(k, clip_media_source_path))), + None) + + if preset_clrsp: + log.debug("Changing clip.path: {}".format(clip_media_source_path)) + log.info("Changing clip `{}` colorspace {} to {}".format( + clip_name, clip_colorspace, preset_clrsp)) + # set the found preset to the clip + clip.setSourceMediaColourTransform(preset_clrsp) + + # save project after all is changed + project.save() + + +def is_overlapping(ti_test, ti_original, strict=False): + covering_exp = bool( + (ti_test.timelineIn() <= ti_original.timelineIn()) + and (ti_test.timelineOut() >= ti_original.timelineOut()) + ) + inside_exp = bool( + (ti_test.timelineIn() >= ti_original.timelineIn()) + and (ti_test.timelineOut() <= ti_original.timelineOut()) + ) + overlaying_right_exp = bool( + (ti_test.timelineIn() < ti_original.timelineOut()) + and (ti_test.timelineOut() >= ti_original.timelineOut()) + ) + overlaying_left_exp = bool( + (ti_test.timelineOut() > ti_original.timelineIn()) + and (ti_test.timelineIn() <= ti_original.timelineIn()) + ) + + if not strict: + return any(( + covering_exp, + inside_exp, + overlaying_right_exp, + overlaying_left_exp + )) + else: + return covering_exp + + +def get_sequence_pattern_and_padding(file): + """ Return sequence pattern and padding from file + + Attributes: + file (string): basename form path + + Example: + Can find file.0001.ext, file.%02d.ext, file.####.ext + + Return: + string: any matching sequence patern + int: padding of sequnce numbering + """ + 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: + return None, None diff --git a/pype/hosts/hiero/menu.py b/pype/hosts/hiero/api/menu.py similarity index 65% rename from pype/hosts/hiero/menu.py rename to pype/hosts/hiero/api/menu.py index 853ee8cf7e4..f32bea07e1b 100644 --- a/pype/hosts/hiero/menu.py +++ b/pype/hosts/hiero/api/menu.py @@ -5,12 +5,7 @@ from avalon.api import Session from hiero.ui import findMenuAction -from .tags import add_tags_from_presets - -from .lib import ( - reload_config, - set_workfiles -) +from . import tags log = Logger().get_logger(__name__) @@ -18,7 +13,7 @@ self._change_context_menu = None -def _update_menu_task_label(*args): +def update_menu_task_label(*args): """Update the task label in Avalon menu to current session""" object_name = self._change_context_menu @@ -36,14 +31,17 @@ def _update_menu_task_label(*args): menu.setTitle(label) -def install(): +def menu_install(): """ Installing menu into Hiero """ - + from . import ( + publish, launch_workfiles_app, reload_config, + apply_colorspace_project, apply_colorspace_clips + ) # here is the best place to add menu - from avalon.tools import publish, cbloader + from avalon.tools import cbloader, creator, sceneinventory from avalon.vendor.Qt import QtGui menu_name = os.environ['AVALON_LABEL'] @@ -72,36 +70,45 @@ def install(): workfiles_action = menu.addAction("Work Files...") workfiles_action.setIcon(QtGui.QIcon("icons:Position.png")) - workfiles_action.triggered.connect(set_workfiles) + workfiles_action.triggered.connect(launch_workfiles_app) default_tags_action = menu.addAction("Create Default Tags...") default_tags_action.setIcon(QtGui.QIcon("icons:Position.png")) - default_tags_action.triggered.connect(add_tags_from_presets) + default_tags_action.triggered.connect(tags.add_tags_to_workfile) menu.addSeparator() publish_action = menu.addAction("Publish...") publish_action.setIcon(QtGui.QIcon("icons:Output.png")) publish_action.triggered.connect( - lambda *args: publish.show(hiero.ui.mainWindow()) + lambda *args: publish(hiero.ui.mainWindow()) ) + creator_action = menu.addAction("Create...") + creator_action.setIcon(QtGui.QIcon("icons:CopyRectangle.png")) + creator_action.triggered.connect(creator.show) + loader_action = menu.addAction("Load...") loader_action.setIcon(QtGui.QIcon("icons:CopyRectangle.png")) loader_action.triggered.connect(cbloader.show) + + sceneinventory_action = menu.addAction("Manage...") + sceneinventory_action.setIcon(QtGui.QIcon("icons:CopyRectangle.png")) + sceneinventory_action.triggered.connect(sceneinventory.show) menu.addSeparator() reload_action = menu.addAction("Reload pipeline...") reload_action.setIcon(QtGui.QIcon("icons:ColorAdd.png")) reload_action.triggered.connect(reload_config) - # Is this required? - # hiero.ui.registerAction(context_label_action) - # hiero.ui.registerAction(workfiles_action) - # hiero.ui.registerAction(default_tags_action) - # hiero.ui.registerAction(publish_action) - # hiero.ui.registerAction(loader_action) - # hiero.ui.registerAction(reload_action) + menu.addSeparator() + apply_colorspace_p_action = menu.addAction("Apply Colorspace Project...") + apply_colorspace_p_action.setIcon(QtGui.QIcon("icons:ColorAdd.png")) + apply_colorspace_p_action.triggered.connect(apply_colorspace_project) + + apply_colorspace_c_action = menu.addAction("Apply Colorspace Clips...") + apply_colorspace_c_action.setIcon(QtGui.QIcon("icons:ColorAdd.png")) + apply_colorspace_c_action.triggered.connect(apply_colorspace_clips) self.context_label_action = context_label_action self.workfile_actions = workfiles_action diff --git a/pype/hosts/hiero/api/pipeline.py b/pype/hosts/hiero/api/pipeline.py new file mode 100644 index 00000000000..26777fa2528 --- /dev/null +++ b/pype/hosts/hiero/api/pipeline.py @@ -0,0 +1,300 @@ +""" +Basic avalon integration +""" +import os +import contextlib +from collections import OrderedDict +from avalon.tools import ( + workfiles, + publish as _publish +) +from avalon.pipeline import AVALON_CONTAINER_ID +from avalon import api as avalon +from avalon import schema +from pyblish import api as pyblish +from pype.api import Logger +from . import lib, menu, events + +log = Logger().get_logger(__name__) + +AVALON_CONFIG = os.getenv("AVALON_CONFIG", "pype") + +# plugin paths +API_DIR = os.path.dirname(os.path.abspath(__file__)) +HOST_DIR = os.path.dirname(API_DIR) +PLUGINS_DIR = os.path.join(HOST_DIR, "plugins") +PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish").replace("\\", "/") +LOAD_PATH = os.path.join(PLUGINS_DIR, "load").replace("\\", "/") +CREATE_PATH = os.path.join(PLUGINS_DIR, "create").replace("\\", "/") +INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory").replace("\\", "/") + +AVALON_CONTAINERS = ":AVALON_CONTAINERS" + + +def install(): + """ + Installing Hiero integration for avalon + + Args: + config (obj): avalon config module `pype` in our case, it is not + used but required by avalon.api.install() + + """ + + # adding all events + events.register_events() + + log.info("Registering Hiero plug-ins..") + pyblish.register_host("hiero") + pyblish.register_plugin_path(PUBLISH_PATH) + avalon.register_plugin_path(avalon.Loader, LOAD_PATH) + avalon.register_plugin_path(avalon.Creator, CREATE_PATH) + avalon.register_plugin_path(avalon.InventoryAction, INVENTORY_PATH) + + # register callback for switching publishable + pyblish.register_callback("instanceToggled", on_pyblish_instance_toggled) + + # Disable all families except for the ones we explicitly want to see + family_states = [ + "write", + "review", + "plate" + ] + + avalon.data["familiesStateDefault"] = False + avalon.data["familiesStateToggled"] = family_states + + # install menu + menu.menu_install() + + # register hiero events + events.register_hiero_events() + + +def uninstall(): + """ + Uninstalling Hiero integration for avalon + + """ + log.info("Deregistering Hiero plug-ins..") + pyblish.deregister_host("hiero") + pyblish.deregister_plugin_path(PUBLISH_PATH) + avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH) + avalon.deregister_plugin_path(avalon.Creator, CREATE_PATH) + + # register callback for switching publishable + pyblish.deregister_callback("instanceToggled", on_pyblish_instance_toggled) + + +def containerise(track_item, + name, + namespace, + context, + loader=None, + data=None): + """Bundle Hiero's object into an assembly and imprint it with metadata + + Containerisation enables a tracking of version, author and origin + for loaded assets. + + Arguments: + track_item (hiero.core.TrackItem): object to imprint as container + name (str): Name of resulting assembly + namespace (str): Namespace under which to host container + context (dict): Asset information + loader (str, optional): Name of node used to produce this container. + + Returns: + track_item (hiero.core.TrackItem): containerised object + + """ + + data_imprint = OrderedDict({ + "schema": "avalon-core:container-2.0", + "id": AVALON_CONTAINER_ID, + "name": str(name), + "namespace": str(namespace), + "loader": str(loader), + "representation": str(context["representation"]["_id"]), + }) + + if data: + for k, v in data.items(): + data_imprint.update({k: v}) + + log.debug("_ data_imprint: {}".format(data_imprint)) + lib.set_track_item_pype_tag(track_item, data_imprint) + + return track_item + + +def ls(): + """List available containers. + + This function is used by the Container Manager in Nuke. You'll + need to implement a for-loop that then *yields* one Container at + a time. + + See the `container.json` schema for details on how it should look, + and the Maya equivalent, which is in `avalon.maya.pipeline` + """ + + # get all track items from current timeline + all_track_items = lib.get_track_items() + + for track_item in all_track_items: + container = parse_container(track_item) + if container: + yield container + + +def parse_container(track_item, validate=True): + """Return container data from track_item's pype tag. + + Args: + track_item (hiero.core.TrackItem): A containerised track item. + validate (bool)[optional]: validating with avalon scheme + + Returns: + dict: The container schema data for input containerized track item. + + """ + # convert tag metadata to normal keys names + data = lib.get_track_item_pype_data(track_item) + + if validate and data and data.get("schema"): + schema.validate(data) + + if not isinstance(data, dict): + return + + # If not all required data return the empty container + required = ['schema', 'id', 'name', + 'namespace', 'loader', 'representation'] + + if not all(key in data for key in required): + return + + container = {key: data[key] for key in required} + + container["objectName"] = track_item.name() + + # Store reference to the node object + container["_track_item"] = track_item + + return container + + +def update_container(track_item, data=None): + """Update container data to input track_item's pype tag. + + Args: + track_item (hiero.core.TrackItem): A containerised track item. + data (dict)[optional]: dictionery with data to be updated + + Returns: + bool: True if container was updated correctly + + """ + data = data or dict() + + container = lib.get_track_item_pype_data(track_item) + + for _key, _value in container.items(): + try: + container[_key] = data[_key] + except KeyError: + pass + + log.info("Updating container: `{}`".format(track_item.name())) + return bool(lib.set_track_item_pype_tag(track_item, container)) + + +def launch_workfiles_app(*args): + ''' Wrapping function for workfiles launcher ''' + + workdir = os.environ["AVALON_WORKDIR"] + + # show workfile gui + workfiles.show(workdir) + + +def publish(parent): + """Shorthand to publish from within host""" + return _publish.show(parent) + + +@contextlib.contextmanager +def maintained_selection(): + """Maintain selection during context + + Example: + >>> with maintained_selection(): + ... for track_item in track_items: + ... < do some stuff > + """ + from .lib import ( + set_selected_track_items, + get_selected_track_items + ) + previous_selection = get_selected_track_items() + reset_selection() + try: + # do the operation + yield + finally: + reset_selection() + set_selected_track_items(previous_selection) + + +def reset_selection(): + """Deselect all selected nodes + """ + from .lib import set_selected_track_items + set_selected_track_items([]) + + +def reload_config(): + """Attempt to reload pipeline at run-time. + + CAUTION: This is primarily for development and debugging purposes. + + """ + import importlib + + for module in ( + "avalon", + "avalon.lib", + "avalon.pipeline", + "pyblish", + "pypeapp", + "{}.api".format(AVALON_CONFIG), + "{}.hosts.hiero.lib".format(AVALON_CONFIG), + "{}.hosts.hiero.menu".format(AVALON_CONFIG), + "{}.hosts.hiero.tags".format(AVALON_CONFIG) + ): + log.info("Reloading module: {}...".format(module)) + try: + module = importlib.import_module(module) + import imp + imp.reload(module) + except Exception as e: + log.warning("Cannot reload module: {}".format(e)) + importlib.reload(module) + + +def on_pyblish_instance_toggled(instance, old_value, new_value): + """Toggle node passthrough states on instance toggles.""" + + log.info("instance toggle: {}, old_value: {}, new_value:{} ".format( + instance, old_value, new_value)) + + from pype.hosts.hiero.api import ( + get_track_item_pype_tag, + set_publish_attribute + ) + + # Whether instances should be passthrough based on new value + track_item = instance.data["item"] + tag = get_track_item_pype_tag(track_item) + set_publish_attribute(tag, new_value) diff --git a/pype/hosts/hiero/api/plugin.py b/pype/hosts/hiero/api/plugin.py new file mode 100644 index 00000000000..db6720cbd69 --- /dev/null +++ b/pype/hosts/hiero/api/plugin.py @@ -0,0 +1,909 @@ +import re +import os +import hiero +from Qt import QtWidgets, QtCore +from avalon.vendor import qargparse +import avalon.api as avalon +import pype.api as pype +from . import lib + +log = pype.Logger().get_logger(__name__) + + +def load_stylesheet(): + path = os.path.join(os.path.dirname(__file__), "style.css") + if not os.path.exists(path): + log.warning("Unable to load stylesheet, file not found in resources") + return "" + + with open(path, "r") as file_stream: + stylesheet = file_stream.read() + return stylesheet + + +class CreatorWidget(QtWidgets.QDialog): + + # output items + items = dict() + + def __init__(self, name, info, ui_inputs, parent=None): + super(CreatorWidget, self).__init__(parent) + + self.setObjectName(name) + + self.setWindowFlags( + QtCore.Qt.Window + | QtCore.Qt.CustomizeWindowHint + | QtCore.Qt.WindowTitleHint + | QtCore.Qt.WindowCloseButtonHint + | QtCore.Qt.WindowStaysOnTopHint + ) + self.setWindowTitle(name or "Pype Creator Input") + self.resize(500, 700) + + # Where inputs and labels are set + self.content_widget = [QtWidgets.QWidget(self)] + top_layout = QtWidgets.QFormLayout(self.content_widget[0]) + top_layout.setObjectName("ContentLayout") + top_layout.addWidget(Spacer(5, self)) + + # first add widget tag line + top_layout.addWidget(QtWidgets.QLabel(info)) + + # main dynamic layout + self.scroll_area = QtWidgets.QScrollArea(self, widgetResizable=True) + self.scroll_area.setVerticalScrollBarPolicy( + QtCore.Qt.ScrollBarAsNeeded) + self.scroll_area.setVerticalScrollBarPolicy( + QtCore.Qt.ScrollBarAlwaysOn) + self.scroll_area.setHorizontalScrollBarPolicy( + QtCore.Qt.ScrollBarAlwaysOff) + self.scroll_area.setWidgetResizable(True) + + self.content_widget.append(self.scroll_area) + + scroll_widget = QtWidgets.QWidget(self) + in_scroll_area = QtWidgets.QVBoxLayout(scroll_widget) + self.content_layout = [in_scroll_area] + + # add preset data into input widget layout + self.items = self.populate_widgets(ui_inputs) + self.scroll_area.setWidget(scroll_widget) + + # Confirmation buttons + btns_widget = QtWidgets.QWidget(self) + btns_layout = QtWidgets.QHBoxLayout(btns_widget) + + cancel_btn = QtWidgets.QPushButton("Cancel") + btns_layout.addWidget(cancel_btn) + + ok_btn = QtWidgets.QPushButton("Ok") + btns_layout.addWidget(ok_btn) + + # Main layout of the dialog + main_layout = QtWidgets.QVBoxLayout(self) + main_layout.setContentsMargins(10, 10, 10, 10) + main_layout.setSpacing(0) + + # adding content widget + for w in self.content_widget: + main_layout.addWidget(w) + + main_layout.addWidget(btns_widget) + + ok_btn.clicked.connect(self._on_ok_clicked) + cancel_btn.clicked.connect(self._on_cancel_clicked) + + stylesheet = load_stylesheet() + self.setStyleSheet(stylesheet) + + def _on_ok_clicked(self): + self.result = self.value(self.items) + self.close() + + def _on_cancel_clicked(self): + self.result = None + self.close() + + def value(self, data, new_data=None): + new_data = new_data or dict() + for k, v in data.items(): + new_data[k] = { + "target": None, + "value": None + } + if v["type"] == "dict": + new_data[k]["target"] = v["target"] + new_data[k]["value"] = self.value(v["value"]) + if v["type"] == "section": + new_data.pop(k) + new_data = self.value(v["value"], new_data) + elif getattr(v["value"], "currentText", None): + new_data[k]["target"] = v["target"] + new_data[k]["value"] = v["value"].currentText() + elif getattr(v["value"], "isChecked", None): + new_data[k]["target"] = v["target"] + new_data[k]["value"] = v["value"].isChecked() + elif getattr(v["value"], "value", None): + new_data[k]["target"] = v["target"] + new_data[k]["value"] = v["value"].value() + elif getattr(v["value"], "text", None): + new_data[k]["target"] = v["target"] + new_data[k]["value"] = v["value"].text() + + return new_data + + def camel_case_split(self, text): + matches = re.finditer( + '.+?(?:(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|$)', text) + return " ".join([str(m.group(0)).capitalize() for m in matches]) + + def create_row(self, layout, type, text, **kwargs): + # get type attribute from qwidgets + attr = getattr(QtWidgets, type) + + # convert label text to normal capitalized text with spaces + label_text = self.camel_case_split(text) + + # assign the new text to lable widget + label = QtWidgets.QLabel(label_text) + label.setObjectName("LineLabel") + + # create attribute name text strip of spaces + attr_name = text.replace(" ", "") + + # create attribute and assign default values + setattr( + self, + attr_name, + attr(parent=self)) + + # assign the created attribute to variable + item = getattr(self, attr_name) + for func, val in kwargs.items(): + if getattr(item, func): + func_attr = getattr(item, func) + func_attr(val) + + # add to layout + layout.addRow(label, item) + + return item + + def populate_widgets(self, data, content_layout=None): + """ + Populate widget from input dict. + + Each plugin has its own set of widget rows defined in dictionary + each row values should have following keys: `type`, `target`, + `label`, `order`, `value` and optionally also `toolTip`. + + Args: + data (dict): widget rows or organized groups defined + by types `dict` or `section` + content_layout (QtWidgets.QFormLayout)[optional]: used when nesting + + Returns: + dict: redefined data dict updated with created widgets + + """ + + content_layout = content_layout or self.content_layout[-1] + # fix order of process by defined order value + ordered_keys = data.keys() + for k, v in data.items(): + try: + # try removing a key from index which should + # be filled with new + ordered_keys.pop(v["order"]) + except IndexError: + pass + # add key into correct order + ordered_keys.insert(v["order"], k) + + # process ordered + for k in ordered_keys: + v = data[k] + tool_tip = v.get("toolTip", "") + if v["type"] == "dict": + # adding spacer between sections + self.content_layout.append(QtWidgets.QWidget(self)) + content_layout.addWidget(self.content_layout[-1]) + self.content_layout[-1].setObjectName("sectionHeadline") + + headline = QtWidgets.QVBoxLayout(self.content_layout[-1]) + headline.addWidget(Spacer(20, self)) + headline.addWidget(QtWidgets.QLabel(v["label"])) + + # adding nested layout with label + self.content_layout.append(QtWidgets.QWidget(self)) + self.content_layout[-1].setObjectName("sectionContent") + + nested_content_layout = QtWidgets.QFormLayout( + self.content_layout[-1]) + nested_content_layout.setObjectName("NestedContentLayout") + content_layout.addWidget(self.content_layout[-1]) + + # add nested key as label + data[k]["value"] = self.populate_widgets( + v["value"], nested_content_layout) + + if v["type"] == "section": + # adding spacer between sections + self.content_layout.append(QtWidgets.QWidget(self)) + content_layout.addWidget(self.content_layout[-1]) + self.content_layout[-1].setObjectName("sectionHeadline") + + headline = QtWidgets.QVBoxLayout(self.content_layout[-1]) + headline.addWidget(Spacer(20, self)) + headline.addWidget(QtWidgets.QLabel(v["label"])) + + # adding nested layout with label + self.content_layout.append(QtWidgets.QWidget(self)) + self.content_layout[-1].setObjectName("sectionContent") + + nested_content_layout = QtWidgets.QFormLayout( + self.content_layout[-1]) + nested_content_layout.setObjectName("NestedContentLayout") + content_layout.addWidget(self.content_layout[-1]) + + # add nested key as label + data[k]["value"] = self.populate_widgets( + v["value"], nested_content_layout) + + elif v["type"] == "QLineEdit": + data[k]["value"] = self.create_row( + content_layout, "QLineEdit", v["label"], + setText=v["value"], setToolTip=tool_tip) + elif v["type"] == "QComboBox": + data[k]["value"] = self.create_row( + content_layout, "QComboBox", v["label"], + addItems=v["value"], setToolTip=tool_tip) + elif v["type"] == "QCheckBox": + data[k]["value"] = self.create_row( + content_layout, "QCheckBox", v["label"], + setChecked=v["value"], setToolTip=tool_tip) + elif v["type"] == "QSpinBox": + data[k]["value"] = self.create_row( + content_layout, "QSpinBox", v["label"], + setValue=v["value"], setMaximum=10000, setToolTip=tool_tip) + return data + + +class Spacer(QtWidgets.QWidget): + def __init__(self, height, *args, **kwargs): + super(self.__class__, self).__init__(*args, **kwargs) + + self.setFixedHeight(height) + + real_spacer = QtWidgets.QWidget(self) + real_spacer.setObjectName("Spacer") + real_spacer.setFixedHeight(height) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(real_spacer) + + self.setLayout(layout) + + +def get_reference_node_parents(ref): + """Return all parent reference nodes of reference node + + Args: + ref (str): reference node. + + Returns: + list: The upstream parent reference nodes. + + """ + parents = [] + return parents + + +class SequenceLoader(avalon.Loader): + """A basic SequenceLoader for Resolve + + This will implement the basic behavior for a loader to inherit from that + will containerize the reference and will implement the `remove` and + `update` logic. + + """ + + options = [ + qargparse.Boolean( + "handles", + label="Include handles", + default=0, + help="Load with handles or without?" + ), + qargparse.Choice( + "load_to", + label="Where to load clips", + items=[ + "Current timeline", + "New timeline" + ], + default="Current timeline", + help="Where do you want clips to be loaded?" + ), + qargparse.Choice( + "load_how", + label="How to load clips", + items=[ + "Original timing", + "Sequentially in order" + ], + default="Original timing", + help="Would you like to place it at orignal timing?" + ) + ] + + def load( + self, + context, + name=None, + namespace=None, + options=None + ): + pass + + def update(self, container, representation): + """Update an existing `container` + """ + pass + + def remove(self, container): + """Remove an existing `container` + """ + pass + + +class ClipLoader: + + active_bin = None + data = dict() + + def __init__(self, cls, context, **options): + """ Initialize object + + Arguments: + cls (avalon.api.Loader): plugin object + context (dict): loader plugin context + options (dict)[optional]: possible keys: + projectBinPath: "path/to/binItem" + + """ + self.__dict__.update(cls.__dict__) + self.context = context + self.active_project = lib.get_current_project() + + # try to get value from options or evaluate key value for `handles` + self.with_handles = options.get("handles") or bool( + options.get("handles") is True) + # try to get value from options or evaluate key value for `load_how` + self.sequencial_load = options.get("sequencially") or bool( + "Sequentially in order" in options.get("load_how", "")) + # try to get value from options or evaluate key value for `load_to` + self.new_sequence = options.get("newSequence") or bool( + "New timeline" in options.get("load_to", "")) + + assert self._populate_data(), str( + "Cannot Load selected data, look into database " + "or call your supervisor") + + # inject asset data to representation dict + self._get_asset_data() + log.debug("__init__ self.data: `{}`".format(self.data)) + + # add active components to class + if self.new_sequence: + if options.get("sequence"): + # if multiselection is set then use options sequence + self.active_sequence = options["sequence"] + else: + # create new sequence + self.active_sequence = lib.get_current_sequence(new=True) + self.active_sequence.setFramerate( + hiero.core.TimeBase.fromString( + str(self.data["assetData"]["fps"]))) + else: + self.active_sequence = lib.get_current_sequence() + + if options.get("track"): + # if multiselection is set then use options track + self.active_track = options["track"] + else: + self.active_track = lib.get_current_track( + self.active_sequence, self.data["track_name"]) + + def _populate_data(self): + """ Gets context and convert it to self.data + data structure: + { + "name": "assetName_subsetName_representationName" + "path": "path/to/file/created/by/get_repr..", + "binPath": "projectBinPath", + } + """ + # create name + repr = self.context["representation"] + repr_cntx = repr["context"] + asset = str(repr_cntx["asset"]) + subset = str(repr_cntx["subset"]) + representation = str(repr_cntx["representation"]) + self.data["clip_name"] = "_".join([asset, subset, representation]) + self.data["track_name"] = "_".join([subset, representation]) + self.data["versionData"] = self.context["version"]["data"] + # gets file path + file = self.fname + if not file: + repr_id = repr["_id"] + log.warning( + "Representation id `{}` is failing to load".format(repr_id)) + return None + self.data["path"] = file.replace("\\", "/") + + # convert to hashed path + if repr_cntx.get("frame"): + self._fix_path_hashes() + + # solve project bin structure path + hierarchy = str("/".join(( + "Loader", + repr_cntx["hierarchy"].replace("\\", "/"), + asset + ))) + + self.data["binPath"] = hierarchy + + return True + + def _fix_path_hashes(self): + """ Convert file path where it is needed padding with hashes + """ + file = self.data["path"] + if "#" not in file: + frame = self.context["representation"]["context"].get("frame") + padding = len(frame) + file = file.replace(frame, "#" * padding) + self.data["path"] = file + + def _get_asset_data(self): + """ Get all available asset data + + joint `data` key with asset.data dict into the representaion + + """ + asset_name = self.context["representation"]["context"]["asset"] + self.data["assetData"] = pype.get_asset(asset_name)["data"] + + def _make_track_item(self, source_bin_item, audio=False): + """ Create track item with """ + + clip = source_bin_item.activeItem() + + # add to track as clip item + if not audio: + track_item = hiero.core.TrackItem( + self.data["clip_name"], hiero.core.TrackItem.kVideo) + else: + track_item = hiero.core.TrackItem( + self.data["clip_name"], hiero.core.TrackItem.kAudio) + + 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.setTimelineOut(self.timeline_out) + track_item.setPlaybackSpeed(1) + self.active_track.addTrackItem(track_item) + + return track_item + + def load(self): + # create project bin for the media to be imported into + self.active_bin = lib.create_bin(self.data["binPath"]) + + # create mediaItem in active project bin + # create clip media + self.media = hiero.core.MediaSource(self.data["path"]) + self.media_duration = int(self.media.duration()) + + # get handles + 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"]) + if self.handle_end is None: + self.handle_end = int(self.data["assetData"]["handleEnd"]) + + if self.sequencial_load: + last_track_item = lib.get_track_items( + sequence_name=self.active_sequence.name(), + track_name=self.active_track.name()) + if len(last_track_item) == 0: + last_timeline_out = 0 + else: + last_track_item = last_track_item[-1] + last_timeline_out = int(last_track_item.timelineOut()) + 1 + self.timeline_in = last_timeline_out + self.timeline_out = last_timeline_out + int( + self.data["assetData"]["clipOut"] + - self.data["assetData"]["clipIn"]) + else: + self.timeline_in = int(self.data["assetData"]["clipIn"]) + self.timeline_out = int(self.data["assetData"]["clipOut"]) + + # 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((( + # put together duration of clip attributes + self.timeline_out - self.timeline_in + 1) \ + + self.handle_start \ + + self.handle_end + # and compare it with meda duration + ) > self.media_duration) + + print("__ slate_on: `{}`".format(slate_on)) + + # if slate is on then remove the slate frame from begining + if slate_on: + self.media_duration -= 1 + self.handle_start += 1 + + # create Clip from Media + clip = hiero.core.Clip(self.media) + clip.setName(self.data["clip_name"]) + + # add Clip to bin if not there yet + if self.data["clip_name"] not in [ + b.name() for b in self.active_bin.items()]: + bin_item = hiero.core.BinItem(clip) + self.active_bin.addItem(bin_item) + + # just make sure the clip is created + # 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(): + source_bin_item = item + if not source_bin_item: + log.warning("Problem with created Source clip: `{}`".format( + self.data["clip_name"])) + + # include handles + if self.with_handles: + self.timeline_in -= self.handle_start + self.timeline_out += self.handle_end + self.handle_start = 0 + self.handle_end = 0 + + # make track item from source in bin as item + track_item = self._make_track_item(source_bin_item) + + log.info("Loading clips: `{}`".format(self.data["clip_name"])) + return track_item + + +class Creator(avalon.Creator): + """Creator class wrapper + """ + clip_color = "Purple" + rename_index = None + + def __init__(self, *args, **kwargs): + import pype.hosts.hiero.api as phiero + super(Creator, self).__init__(*args, **kwargs) + self.presets = pype.get_current_project_settings()[ + "hiero"]["create"].get(self.__class__.__name__, {}) + + # adding basic current context resolve objects + self.project = phiero.get_current_project() + self.sequence = phiero.get_current_sequence() + + if (self.options or {}).get("useSelection"): + self.selected = phiero.get_track_items(selected=True) + else: + self.selected = phiero.get_track_items() + + self.widget = CreatorWidget + + +class PublishClip: + """ + Convert a track item to publishable instance + + Args: + track_item (hiero.core.TrackItem): hiero track item object + kwargs (optional): additional data needed for rename=True (presets) + + Returns: + hiero.core.TrackItem: hiero track item object with pype tag + """ + vertical_clip_match = dict() + tag_data = dict() + types = { + "shot": "shot", + "folder": "folder", + "episode": "episode", + "sequence": "sequence", + "track": "sequence", + } + + # parents search patern + parents_search_patern = r"\{([a-z]*?)\}" + + # default templates for non-ui use + rename_default = False + hierarchy_default = "{_folder_}/{_sequence_}/{_track_}" + clip_name_default = "shot_{_trackIndex_:0>3}_{_clipIndex_:0>4}" + subset_name_default = "" + review_track_default = "< none >" + subset_family_default = "plate" + count_from_default = 10 + count_steps_default = 10 + vertical_sync_default = False + driving_layer_default = "" + + def __init__(self, cls, track_item, **kwargs): + # populate input cls attribute onto self.[attr] + self.__dict__.update(cls.__dict__) + + # get main parent objects + self.track_item = track_item + sequence_name = lib.get_current_sequence().name() + self.sequence_name = str(sequence_name).replace(" ", "_") + + # track item (clip) main attributes + self.ti_name = track_item.name() + self.ti_index = int(track_item.eventNumber()) + + # get track name and index + track_name = track_item.parent().name() + self.track_name = str(track_name).replace(" ", "_") + self.track_index = int(track_item.parent().trackIndex()) + + # adding tag.family into tag + if kwargs.get("avalon"): + self.tag_data.update(kwargs["avalon"]) + + # adding ui inputs if any + self.ui_inputs = kwargs.get("ui_inputs", {}) + + # populate default data before we get other attributes + self._populate_track_item_default_data() + + # use all populated default data to create all important attributes + self._populate_attributes() + + # create parents with correct types + self._create_parents() + + def convert(self): + # solve track item data and add them to tag data + self._convert_to_tag_data() + + # if track name is in review track name and also if driving track name + # is not in review track name: skip tag creation + if (self.track_name in self.review_layer) and ( + self.driving_layer not in self.review_layer): + return + + # deal with clip name + new_name = self.tag_data.pop("newClipName") + + if self.rename: + # rename track item + self.track_item.setName(new_name) + self.tag_data["asset"] = new_name + else: + self.tag_data["asset"] = self.ti_name + + # create pype tag on track_item and add data + lib.imprint(self.track_item, self.tag_data) + + return self.track_item + + def _populate_track_item_default_data(self): + """ Populate default formating data from track item. """ + + self.track_item_default_data = { + "_folder_": "shots", + "_sequence_": self.sequence_name, + "_track_": self.track_name, + "_clip_": self.ti_name, + "_trackIndex_": self.track_index, + "_clipIndex_": self.ti_index + } + + def _populate_attributes(self): + """ Populate main object attributes. """ + # track item frame range and parent track name for vertical sync check + self.clip_in = int(self.track_item.timelineIn()) + self.clip_out = int(self.track_item.timelineOut()) + + # define ui inputs if non gui mode was used + self.shot_num = self.ti_index + log.debug( + "____ self.shot_num: {}".format(self.shot_num)) + + # ui_inputs data or default values if gui was not used + self.rename = self.ui_inputs.get( + "clipRename", {}).get("value") or self.rename_default + self.clip_name = self.ui_inputs.get( + "clipName", {}).get("value") or self.clip_name_default + self.hierarchy = self.ui_inputs.get( + "hierarchy", {}).get("value") or self.hierarchy_default + self.hierarchy_data = self.ui_inputs.get( + "hierarchyData", {}).get("value") or \ + self.track_item_default_data.copy() + self.count_from = self.ui_inputs.get( + "countFrom", {}).get("value") or self.count_from_default + self.count_steps = self.ui_inputs.get( + "countSteps", {}).get("value") or self.count_steps_default + self.subset_name = self.ui_inputs.get( + "subsetName", {}).get("value") or self.subset_name_default + self.subset_family = self.ui_inputs.get( + "subsetFamily", {}).get("value") or self.subset_family_default + self.vertical_sync = self.ui_inputs.get( + "vSyncOn", {}).get("value") or self.vertical_sync_default + self.driving_layer = self.ui_inputs.get( + "vSyncTrack", {}).get("value") or self.driving_layer_default + self.review_track = self.ui_inputs.get( + "reviewTrack", {}).get("value") or self.review_track_default + self.audio = self.ui_inputs.get( + "audio", {}).get("value") or False + + # build subset name from layer name + if self.subset_name == "": + self.subset_name = self.track_name + + # create subset for publishing + self.subset = self.subset_family + self.subset_name.capitalize() + + def _replace_hash_to_expression(self, name, text): + """ Replace hash with number in correct padding. """ + _spl = text.split("#") + _len = (len(_spl) - 1) + _repl = "{{{0}:0>{1}}}".format(name, _len) + new_text = text.replace(("#" * _len), _repl) + return new_text + + def _convert_to_tag_data(self): + """ Convert internal data to tag data. + + Populating the tag data into internal variable self.tag_data + """ + # define vertical sync attributes + master_layer = True + self.review_layer = "" + if self.vertical_sync: + # check if track name is not in driving layer + if self.track_name not in self.driving_layer: + # if it is not then define vertical sync as None + master_layer = False + + # increasing steps by index of rename iteration + self.count_steps *= self.rename_index + + hierarchy_formating_data = dict() + _data = self.track_item_default_data.copy() + if self.ui_inputs: + # adding tag metadata from ui + for _k, _v in self.ui_inputs.items(): + if _v["target"] == "tag": + self.tag_data[_k] = _v["value"] + + # driving layer is set as positive match + if master_layer or self.vertical_sync: + # mark review layer + if self.review_track and ( + self.review_track not in self.review_track_default): + # if review layer is defined and not the same as defalut + self.review_layer = self.review_track + # shot num calculate + if self.rename_index == 0: + self.shot_num = self.count_from + else: + self.shot_num = self.count_from + self.count_steps + + # clip name sequence number + _data.update({"shot": self.shot_num}) + + # solve # in test to pythonic expression + for _k, _v in self.hierarchy_data.items(): + if "#" not in _v["value"]: + continue + self.hierarchy_data[ + _k]["value"] = self._replace_hash_to_expression( + _k, _v["value"]) + + # fill up pythonic expresisons in hierarchy data + for k, _v in self.hierarchy_data.items(): + hierarchy_formating_data[k] = _v["value"].format(**_data) + else: + # if no gui mode then just pass default data + hierarchy_formating_data = self.hierarchy_data + + tag_hierarchy_data = self._solve_tag_hierarchy_data( + hierarchy_formating_data + ) + + tag_hierarchy_data.update({"masterLayer": True}) + if master_layer and self.vertical_sync: + self.vertical_clip_match.update({ + (self.clip_in, self.clip_out): tag_hierarchy_data + }) + + if not master_layer and self.vertical_sync: + # driving layer is set as negative match + for (_in, _out), master_data in self.vertical_clip_match.items(): + master_data.update({"masterLayer": False}) + if _in == self.clip_in and _out == self.clip_out: + data_subset = master_data["subset"] + # add track index in case duplicity of names in master data + if self.subset in data_subset: + master_data["subset"] = self.subset + str( + self.track_index) + # in case track name and subset name is the same then add + if self.subset_name == self.track_name: + master_data["subset"] = self.subset + # assing data to return hierarchy data to tag + tag_hierarchy_data = master_data + + # add data to return data dict + self.tag_data.update(tag_hierarchy_data) + + if master_layer and self.review_layer: + self.tag_data.update({"reviewTrack": self.review_layer}) + + def _solve_tag_hierarchy_data(self, hierarchy_formating_data): + """ Solve tag data from hierarchy data and templates. """ + # fill up clip name and hierarchy keys + hierarchy_filled = self.hierarchy.format(**hierarchy_formating_data) + clip_name_filled = self.clip_name.format(**hierarchy_formating_data) + + return { + "newClipName": clip_name_filled, + "hierarchy": hierarchy_filled, + "parents": self.parents, + "hierarchyData": hierarchy_formating_data, + "subset": self.subset, + "family": self.subset_family, + "families": [self.data["family"]] + } + + def _convert_to_entity(self, key): + """ Converting input key to key with type. """ + # convert to entity type + entity_type = self.types.get(key, None) + + assert entity_type, "Missing entity type for `{}`".format( + key + ) + + return { + "entity_type": entity_type, + "entity_name": self.hierarchy_data[key]["value"].format( + **self.track_item_default_data + ) + } + + def _create_parents(self): + """ Create parents and return it in list. """ + self.parents = list() + + patern = re.compile(self.parents_search_patern) + par_split = [patern.findall(t).pop() + for t in self.hierarchy.split("/")] + + for key in par_split: + parent = self._convert_to_entity(key) + self.parents.append(parent) diff --git a/pype/hosts/hiero/api/style.css b/pype/hosts/hiero/api/style.css new file mode 100644 index 00000000000..b64c391c6e9 --- /dev/null +++ b/pype/hosts/hiero/api/style.css @@ -0,0 +1,26 @@ +QWidget { + font-size: 13px; +} + +QSpinBox { + padding: 2; + max-width: 8em; +} + +QLineEdit { + padding: 2; + min-width: 15em; +} + +QVBoxLayout { + min-width: 15em; + background-color: #201f1f; +} + +QComboBox { + min-width: 8em; +} + +#sectionContent { + background-color: #2E2D2D; +} \ No newline at end of file diff --git a/pype/hosts/hiero/tags.py b/pype/hosts/hiero/api/tags.py similarity index 50% rename from pype/hosts/hiero/tags.py rename to pype/hosts/hiero/api/tags.py index af1a9b947a4..63c60a3039d 100644 --- a/pype/hosts/hiero/tags.py +++ b/pype/hosts/hiero/api/tags.py @@ -1,10 +1,7 @@ import re import os -import json import hiero -from pprint import pformat - from pype.api import Logger from avalon import io @@ -12,55 +9,99 @@ def tag_data(): - current_dir = os.path.dirname(__file__) - json_path = os.path.join(current_dir, "tags.json") - with open(json_path, "r") as json_stream: - data = json.load(json_stream) - return data + return { + "Retiming": { + "editable": "1", + "note": "Clip has retime or TimeWarp effects (or multiple effects stacked on the clip)", # noqa + "icon": "retiming.png", + "metadata": { + "family": "retiming", + "marginIn": 1, + "marginOut": 1 + } + }, + "[Lenses]": { + "Set lense here": { + "editable": "1", + "note": "Adjust parameters of your lense and then drop to clip. Remember! You can always overwrite on clip", # noqa + "icon": "lense.png", + "metadata": { + "focalLengthMm": 57 + + } + } + }, + "NukeScript": { + "editable": "1", + "note": "Collecting track items to Nuke scripts.", + "icon": "icons:TagNuke.png", + "metadata": { + "family": "nukescript", + "subset": "main" + } + }, + "Comment": { + "editable": "1", + "note": "Comment on a shot.", + "icon": "icons:TagComment.png", + "metadata": { + "family": "comment", + "subset": "main" + } + } + } -def create_tag(key, value): +def create_tag(key, data): """ Creating Tag object. Args: key (str): name of tag - value (dict): parameters of tag + data (dict): parameters of tag Returns: object: Tag object """ - tag = hiero.core.Tag(str(key)) + return update_tag(tag, data) - return update_tag(tag, value) - -def update_tag(tag, value): +def update_tag(tag, data): """ Fixing Tag object. Args: tag (obj): Tag object - value (dict): parameters of tag + data (dict): parameters of tag """ - - tag.setNote(value["note"]) - tag.setIcon(str(value["icon"]["path"])) + # set icon if any available in input data + if data.get("icon"): + tag.setIcon(str(data["icon"])) + # set note description of tag + tag.setNote(data["note"]) + # get metadata of tag mtd = tag.metadata() - pres_mtd = value.get("metadata", None) - if pres_mtd: - [mtd.setValue("tag.{}".format(str(k)), str(v)) - for k, v in pres_mtd.items()] - + # get metadata key from data + data_mtd = data.get("metadata", {}) + # set all data metadata to tag metadata + for k, v in data_mtd.items(): + mtd.setValue( + "tag.{}".format(str(k)), + str(v) + ) return tag -def add_tags_from_presets(): +def add_tags_to_workfile(): """ Will create default tags from presets. """ - project = hiero.core.projects()[-1] + from .lib import get_current_project + + # get project and root bin object + project = get_current_project() + root_bin = project.tagsBin() if "Tag Presets" in project.name(): return @@ -73,7 +114,7 @@ def add_tags_from_presets(): # Get project task types. tasks = io.find_one({"type": "project"})["config"]["tasks"] nks_pres_tags["[Tasks]"] = {} - log.debug("__ tasks: {}".format(pformat(tasks))) + log.debug("__ tasks: {}".format(tasks)) for task_type in tasks.keys(): nks_pres_tags["[Tasks]"][task_type.lower()] = { "editable": "1", @@ -104,24 +145,32 @@ def add_tags_from_presets(): } } - # get project and root bin object - project = hiero.core.projects()[-1] - root_bin = project.tagsBin() - + # loop trough tag data dict and create deep tag structure for _k, _val in nks_pres_tags.items(): + # check if key is not decorated with [] so it is defined as bin + bin_find = None pattern = re.compile(r"\[(.*)\]") - bin_find = pattern.findall(_k) + bin_finds = pattern.findall(_k) + # if there is available any then pop it to string + if bin_finds: + bin_find = bin_finds.pop() + + # if bin was found then create or update if bin_find: - # check what is in root bin + root_add = False + # first check if in root lever is not already created bins bins = [b for b in root_bin.items() - if b.name() in str(bin_find[0])] + if b.name() in str(bin_find)] + log.debug(">>> bins: {}".format(bins)) if bins: - bin = bins[0] + bin = bins.pop() else: - # create Bin object - bin = hiero.core.Bin(str(bin_find[0])) + root_add = True + # create Bin object for processing + bin = hiero.core.Bin(str(bin_find)) + # update or create tags in the bin for k, v in _val.items(): tags = [t for t in bin.items() if str(k) in t.name() @@ -133,13 +182,15 @@ def add_tags_from_presets(): # adding Tag to Bin bin.addItem(tag) else: - update_tag(tags[0], v) + update_tag(tags.pop(), v) - if not bins: + # finally add the Bin object to the root level Bin + if root_add: # adding Tag to Root Bin root_bin.addItem(bin) - else: + # for Tags to be created in root level Bin + # at first check if any of input data tag is not already created tags = None tags = [t for t in root_bin.items() if str(_k) in t.name()] @@ -151,16 +202,18 @@ def add_tags_from_presets(): # adding Tag to Root Bin root_bin.addItem(tag) else: - # check if Hierarchy in name - # update Tag if already exists + # update Tags if they already exists for _t in tags: + # skip bin objects if isinstance(_t, hiero.core.Bin): continue + + # check if Hierarchy in name and skip it + # because hierarchy could be edited if "hierarchy" in _t.name().lower(): continue # update only non hierarchy tags - # because hierarchy could be edited update_tag(_t, _val) log.info("Default Tags were set...") diff --git a/pype/hosts/hiero/workio.py b/pype/hosts/hiero/api/workio.py similarity index 100% rename from pype/hosts/hiero/workio.py rename to pype/hosts/hiero/api/workio.py diff --git a/pype/hosts/hiero/lib.py b/pype/hosts/hiero/lib.py deleted file mode 100644 index 182d7e53c09..00000000000 --- a/pype/hosts/hiero/lib.py +++ /dev/null @@ -1,817 +0,0 @@ -import os -import re -import sys -import hiero -import pyblish.api -import avalon.api as avalon -import avalon.io -from avalon.vendor.Qt import (QtWidgets, QtGui) -import pype.api as pype -from pype.api import Logger, Anatomy - -log = Logger().get_logger(__name__) - -cached_process = None - - -self = sys.modules[__name__] -self._has_been_setup = False -self._has_menu = False -self._registered_gui = None - -AVALON_CONFIG = os.getenv("AVALON_CONFIG", "pype") - - -def set_workfiles(): - ''' Wrapping function for workfiles launcher ''' - from avalon.tools import workfiles - - workdir = os.environ["AVALON_WORKDIR"] - - # show workfile gui - workfiles.show(workdir) - - -def sync_avalon_data_to_workfile(): - # import session to get project dir - project_name = avalon.Session["AVALON_PROJECT"] - - anatomy = Anatomy(project_name) - work_template = anatomy.templates["work"]["path"] - work_root = anatomy.root_value_for_template(work_template) - active_project_root = ( - os.path.join(work_root, project_name) - ).replace("\\", "/") - # getting project - project = hiero.core.projects()[-1] - - if "Tag Presets" in project.name(): - return - - log.debug("Synchronizing Pype metadata to project: {}".format( - project.name())) - - # set project root with backward compatibility - try: - project.setProjectDirectory(active_project_root) - except Exception: - # old way of seting it - project.setProjectRoot(active_project_root) - - # get project data from avalon db - project_doc = avalon.io.find_one({"type": "project"}) - project_data = project_doc["data"] - - log.debug("project_data: {}".format(project_data)) - - # get format and fps property from avalon db on project - width = project_data["resolutionWidth"] - height = project_data["resolutionHeight"] - pixel_aspect = project_data["pixelAspect"] - fps = project_data['fps'] - format_name = project_data['code'] - - # create new format in hiero project - format = hiero.core.Format(width, height, pixel_aspect, format_name) - project.setOutputFormat(format) - - # set fps to hiero project - project.setFramerate(fps) - - # TODO: add auto colorspace set from project drop - log.info("Project property has been synchronised with Avalon db") - - -def launch_workfiles_app(event): - """ - Event for launching workfiles after hiero start - - Args: - event (obj): required but unused - """ - set_workfiles() - - - -def reload_config(): - """Attempt to reload pipeline at run-time. - - CAUTION: This is primarily for development and debugging purposes. - - """ - - import importlib - - for module in ( - "avalon", - "avalon.lib", - "avalon.pipeline", - "pyblish", - "pyblish_lite", - "{}.api".format(AVALON_CONFIG), - "{}.templates".format(AVALON_CONFIG), - "{}.hosts.hiero.lib".format(AVALON_CONFIG), - "{}.hosts.hiero.menu".format(AVALON_CONFIG), - "{}.hosts.hiero.tags".format(AVALON_CONFIG) - ): - log.info("Reloading module: {}...".format(module)) - try: - module = importlib.import_module(module) - reload(module) - except Exception as e: - log.warning("Cannot reload module: {}".format(e)) - importlib.reload(module) - - -def setup(console=False, port=None, menu=True): - """Setup integration - - Registers Pyblish for Hiero plug-ins and appends an item to the File-menu - - Arguments: - console (bool): Display console with GUI - port (int, optional): Port from which to start looking for an - available port to connect with Pyblish QML, default - provided by Pyblish Integration. - menu (bool, optional): Display file menu in Hiero. - """ - - if self._has_been_setup: - teardown() - - add_submission() - - if menu: - add_to_filemenu() - self._has_menu = True - - self._has_been_setup = True - print("pyblish: Loaded successfully.") - - -def show(): - """Try showing the most desirable GUI - This function cycles through the currently registered - graphical user interfaces, if any, and presents it to - the user. - """ - - return (_discover_gui() or _show_no_gui)() - - -def _discover_gui(): - """Return the most desirable of the currently registered GUIs""" - - # Prefer last registered - guis = reversed(pyblish.api.registered_guis()) - - for gui in list(guis) + ["pyblish_lite"]: - try: - gui = __import__(gui).show - except (ImportError, AttributeError): - continue - else: - return gui - - -def teardown(): - """Remove integration""" - if not self._has_been_setup: - return - - if self._has_menu: - remove_from_filemenu() - self._has_menu = False - - self._has_been_setup = False - print("pyblish: Integration torn down successfully") - - -def remove_from_filemenu(): - raise NotImplementedError("Implement me please.") - - -def add_to_filemenu(): - PublishAction() - - -class PyblishSubmission(hiero.exporters.FnSubmission.Submission): - - def __init__(self): - hiero.exporters.FnSubmission.Submission.__init__(self) - - def addToQueue(self): - # Add submission to Hiero module for retrieval in plugins. - hiero.submission = self - show() - - -def add_submission(): - registry = hiero.core.taskRegistry - registry.addSubmission("Pyblish", PyblishSubmission) - - -class PublishAction(QtWidgets.QAction): - """ - Action with is showing as menu item - """ - - def __init__(self): - QtWidgets.QAction.__init__(self, "Publish", None) - self.triggered.connect(self.publish) - - for interest in ["kShowContextMenu/kTimeline", - "kShowContextMenukBin", - "kShowContextMenu/kSpreadsheet"]: - hiero.core.events.registerInterest(interest, self.eventHandler) - - self.setShortcut("Ctrl+Alt+P") - - def publish(self): - # Removing "submission" attribute from hiero module, to prevent tasks - # from getting picked up when not using the "Export" dialog. - if hasattr(hiero, "submission"): - del hiero.submission - show() - - def eventHandler(self, event): - # Add the Menu to the right-click menu - event.menu.addAction(self) - - -def _show_no_gui(): - """ - Popup with information about how to register a new GUI - In the event of no GUI being registered or available, - this information dialog will appear to guide the user - through how to get set up with one. - """ - - messagebox = QtWidgets.QMessageBox() - messagebox.setIcon(messagebox.Warning) - messagebox.setWindowIcon(QtGui.QIcon(os.path.join( - os.path.dirname(pyblish.__file__), - "icons", - "logo-32x32.svg")) - ) - - spacer = QtWidgets.QWidget() - spacer.setMinimumSize(400, 0) - spacer.setSizePolicy(QtWidgets.QSizePolicy.Minimum, - QtWidgets.QSizePolicy.Expanding) - - layout = messagebox.layout() - layout.addWidget(spacer, layout.rowCount(), 0, 1, layout.columnCount()) - - messagebox.setWindowTitle("Uh oh") - messagebox.setText("No registered GUI found.") - - if not pyblish.api.registered_guis(): - messagebox.setInformativeText( - "In order to show you a GUI, one must first be registered. " - "Press \"Show details...\" below for information on how to " - "do that.") - - messagebox.setDetailedText( - "Pyblish supports one or more graphical user interfaces " - "to be registered at once, the next acting as a fallback to " - "the previous." - "\n" - "\n" - "For example, to use Pyblish Lite, first install it:" - "\n" - "\n" - "$ pip install pyblish-lite" - "\n" - "\n" - "Then register it, like so:" - "\n" - "\n" - ">>> import pyblish.api\n" - ">>> pyblish.api.register_gui(\"pyblish_lite\")" - "\n" - "\n" - "The next time you try running this, Lite will appear." - "\n" - "See http://api.pyblish.com/register_gui.html for " - "more information.") - - else: - messagebox.setInformativeText( - "None of the registered graphical user interfaces " - "could be found." - "\n" - "\n" - "Press \"Show details\" for more information.") - - messagebox.setDetailedText( - "These interfaces are currently registered." - "\n" - "%s" % "\n".join(pyblish.api.registered_guis())) - - messagebox.setStandardButtons(messagebox.Ok) - messagebox.exec_() - - -def CreateNukeWorkfile(nodes=None, - nodes_effects=None, - to_timeline=False, - **kwargs): - ''' Creating nuke workfile with particular version with given nodes - Also it is creating timeline track items as precomps. - - Arguments: - nodes(list of dict): each key in dict is knob order is important - to_timeline(type): will build trackItem with metadata - - Returns: - bool: True if done - - Raises: - Exception: with traceback - - ''' - import hiero.core - from avalon.nuke import imprint - from pype.hosts.nuke import ( - lib as nklib - ) - - # check if the file exists if does then Raise "File exists!" - if os.path.exists(filepath): - raise FileExistsError("File already exists: `{}`".format(filepath)) - - # if no representations matching then - # Raise "no representations to be build" - if len(representations) == 0: - raise AttributeError("Missing list of `representations`") - - # check nodes input - if len(nodes) == 0: - log.warning("Missing list of `nodes`") - - # create temp nk file - nuke_script = hiero.core.nuke.ScriptWriter() - - # create root node and save all metadata - root_node = hiero.core.nuke.RootNode() - - anatomy = Anatomy(os.environ["AVALON_PROJECT"]) - work_template = anatomy.templates["work"]["path"] - root_path = anatomy.root_value_for_template(work_template) - - nuke_script.addNode(root_node) - - # here to call pype.hosts.nuke.lib.BuildWorkfile - script_builder = nklib.BuildWorkfile( - root_node=root_node, - root_path=root_path, - nodes=nuke_script.getNodes(), - **kwargs - ) - - -class ClipLoader: - - active_bin = None - - def __init__(self, plugin_cls, context, sequence=None, track=None, **kwargs): - """ Initialize object - - Arguments: - plugin_cls (api.Loader): plugin object - context (dict): loader plugin context - sequnce (hiero.core.Sequence): sequence object - track (hiero.core.Track): track object - kwargs (dict)[optional]: possible keys: - projectBinPath: "path/to/binItem" - hieroWorkfileName: "name_of_hiero_project_file_no_extension" - - """ - self.cls = plugin_cls - self.context = context - self.kwargs = kwargs - self.active_project = self._get_active_project() - self.project_bin = self.active_project.clipsBin() - - self.data = dict() - - assert self._set_data(), str("Cannot Load selected data, look into " - "database or call your supervisor") - - # inject asset data to representation dict - self._get_asset_data() - log.debug("__init__ self.data: `{}`".format(self.data)) - - # add active components to class - self.active_sequence = self._get_active_sequence(sequence) - self.active_track = self._get_active_track(track) - - def _set_data(self): - """ Gets context and convert it to self.data - data structure: - { - "name": "assetName_subsetName_representationName" - "path": "path/to/file/created/by/get_repr..", - "binPath": "projectBinPath", - } - """ - # create name - repr = self.context["representation"] - repr_cntx = repr["context"] - asset = str(repr_cntx["asset"]) - subset = str(repr_cntx["subset"]) - representation = str(repr_cntx["representation"]) - self.data["clip_name"] = "_".join([asset, subset, representation]) - self.data["track_name"] = "_".join([subset, representation]) - - # gets file path - file = self.cls.fname - if not file: - repr_id = repr["_id"] - log.warning( - "Representation id `{}` is failing to load".format(repr_id)) - return None - self.data["path"] = file.replace("\\", "/") - - # convert to hashed path - if repr_cntx.get("frame"): - self._fix_path_hashes() - - # solve project bin structure path - hierarchy = str("/".join(( - "Loader", - repr_cntx["hierarchy"].replace("\\", "/"), - asset - ))) - - self.data["binPath"] = self.kwargs.get( - "projectBinPath", - hierarchy - ) - - return True - - def _fix_path_hashes(self): - """ Convert file path where it is needed padding with hashes - """ - file = self.data["path"] - if "#" not in file: - frame = self.context["representation"]["context"].get("frame") - padding = len(frame) - file = file.replace(frame, "#"*padding) - self.data["path"] = file - - def _get_active_project(self): - """ Get hiero active project object - """ - fname = self.kwargs.get("hieroWorkfileName", "") - - return next((p for p in hiero.core.projects() - if fname in p.name()), - hiero.core.projects()[-1]) - - def _get_asset_data(self): - """ Get all available asset data - - joint `data` key with asset.data dict into the representaion - - """ - asset_name = self.context["representation"]["context"]["asset"] - self.data["assetData"] = pype.get_asset(asset_name)["data"] - - def _make_project_bin(self, hierarchy): - """ Creare bins by given hierarchy path - - It will also make sure no duplicit bins will be created - - Arguments: - hierarchy (str): path devided by slashes "bin0/bin1/bin2" - - Returns: - bin (hiero.core.BinItem): with the bin to be used for mediaItem - """ - if self.active_bin: - return self.active_bin - - assert hierarchy != "", "Please add hierarchy!" - log.debug("__ hierarchy1: `{}`".format(hierarchy)) - if '/' in hierarchy: - hierarchy = hierarchy.split('/') - else: - hierarchy = [hierarchy] - - parent_bin = None - for i, name in enumerate(hierarchy): - # if first index and list is more then one long - if i == 0: - bin = next((bin for bin in self.project_bin.bins() - if name in bin.name()), None) - if not bin: - bin = hiero.core.Bin(name) - self.project_bin.addItem(bin) - log.debug("__ bin.name: `{}`".format(bin.name())) - parent_bin = bin - - # if second to prelast - elif (i >= 1) and (i <= (len(hierarchy) - 1)): - bin = next((bin for bin in parent_bin.bins() - if name in bin.name()), None) - if not bin: - bin = hiero.core.Bin(name) - parent_bin.addItem(bin) - - parent_bin = bin - - return parent_bin - - def _make_track_item(self): - """ Create track item with """ - pass - - def _set_clip_color(self, last_version=True): - """ Sets color of clip on clip/track item - - Arguments: - last_version (bool): True = green | False = red - """ - pass - - def _set_container_tag(self, item, metadata): - """ Sets container tag to given clip/track item - - Arguments: - item (hiero.core.BinItem or hiero.core.TrackItem) - metadata (dict): data to be added to tag - """ - pass - - def _get_active_sequence(self, sequence): - if not sequence: - return hiero.ui.activeSequence() - else: - return sequence - - def _get_active_track(self, track): - if not track: - track_name = self.data["track_name"] - else: - track_name = track.name() - - track_pass = next( - (t for t in self.active_sequence.videoTracks() - if t.name() in track_name), None - ) - - if not track_pass: - track_pass = hiero.core.VideoTrack(track_name) - self.active_sequence.addTrack(track_pass) - - return track_pass - - def load(self): - log.debug("__ active_project: `{}`".format(self.active_project)) - log.debug("__ active_sequence: `{}`".format(self.active_sequence)) - - # create project bin for the media to be imported into - self.active_bin = self._make_project_bin(self.data["binPath"]) - log.debug("__ active_bin: `{}`".format(self.active_bin)) - - log.debug("__ version.data: `{}`".format( - self.context["version"]["data"])) - - # create mediaItem in active project bin - # create clip media - media = hiero.core.MediaSource(self.data["path"]) - media_duration = int(media.duration()) - - handle_start = int(self.data["assetData"]["handleStart"]) - handle_end = int(self.data["assetData"]["handleEnd"]) - - clip_in = int(self.data["assetData"]["clipIn"]) - clip_out = int(self.data["assetData"]["clipOut"]) - - log.debug("__ media_duration: `{}`".format(media_duration)) - log.debug("__ handle_start: `{}`".format(handle_start)) - log.debug("__ handle_end: `{}`".format(handle_end)) - log.debug("__ clip_in: `{}`".format(clip_in)) - log.debug("__ clip_out: `{}`".format(clip_out)) - - # check if slate is included - # either in version data families or by calculating frame diff - slate_on = next( - (f for f in self.context["version"]["data"]["families"] - if "slate" in f), - None) or bool((( - clip_out - clip_in + 1) + handle_start + handle_end - ) - media_duration) - - log.debug("__ slate_on: `{}`".format(slate_on)) - - # calculate slate differences - if slate_on: - media_duration -= 1 - handle_start += 1 - - fps = self.data["assetData"]["fps"] - - # create Clip from Media - _clip = hiero.core.Clip(media) - _clip.setName(self.data["clip_name"]) - - # add Clip to bin if not there yet - if self.data["clip_name"] not in [ - b.name() - for b in self.active_bin.items()]: - binItem = hiero.core.BinItem(_clip) - self.active_bin.addItem(binItem) - - _source = next((item for item in self.active_bin.items() - if self.data["clip_name"] in item.name()), None) - - if not _source: - log.warning("Problem with created Source clip: `{}`".format( - self.data["clip_name"])) - - version = next((s for s in _source.items()), None) - clip = version.item() - - # add to track as clip item - track_item = hiero.core.TrackItem( - self.data["clip_name"], hiero.core.TrackItem.kVideo) - - track_item.setSource(clip) - - track_item.setSourceIn(handle_start) - track_item.setTimelineIn(clip_in) - - track_item.setSourceOut(media_duration - handle_end) - track_item.setTimelineOut(clip_out) - track_item.setPlaybackSpeed(1) - self.active_track.addTrackItem(track_item) - - log.info("Loading clips: `{}`".format(self.data["clip_name"])) - - -def create_nk_workfile_clips(nk_workfiles, seq=None): - ''' - nk_workfile is list of dictionaries like: - [{ - 'path': 'P:/Jakub_testy_pipeline/test_v01.nk', - 'name': 'test', - 'handleStart': 15, # added asymetrically to handles - 'handleEnd': 10, # added asymetrically to handles - "clipIn": 16, - "frameStart": 991, - "frameEnd": 1023, - 'task': 'Comp-tracking', - 'work_dir': 'VFX_PR', - 'shot': '00010' - }] - ''' - - proj = hiero.core.projects()[-1] - root = proj.clipsBin() - - if not seq: - seq = hiero.core.Sequence('NewSequences') - root.addItem(hiero.core.BinItem(seq)) - # todo will ned to define this better - # track = seq[1] # lazy example to get a destination# track - clips_lst = [] - for nk in nk_workfiles: - task_path = '/'.join([nk['work_dir'], nk['shot'], nk['task']]) - bin = create_bin_in_project(task_path, proj) - - if nk['task'] not in seq.videoTracks(): - track = hiero.core.VideoTrack(nk['task']) - seq.addTrack(track) - else: - track = seq.tracks(nk['task']) - - # create clip media - media = hiero.core.MediaSource(nk['path']) - media_in = int(media.startTime() or 0) - media_duration = int(media.duration() or 0) - - handle_start = nk.get("handleStart") - handle_end = nk.get("handleEnd") - - if media_in: - source_in = media_in + handle_start - else: - source_in = nk["frameStart"] + handle_start - - if media_duration: - source_out = (media_in + media_duration - 1) - handle_end - else: - source_out = nk["frameEnd"] - handle_end - - source = hiero.core.Clip(media) - - name = os.path.basename(os.path.splitext(nk['path'])[0]) - split_name = split_by_client_version(name)[0] or name - - # add to bin as clip item - items_in_bin = [b.name() for b in bin.items()] - if split_name not in items_in_bin: - binItem = hiero.core.BinItem(source) - bin.addItem(binItem) - - new_source = [ - item for item in bin.items() if split_name in item.name() - ][0].items()[0].item() - - # add to track as clip item - trackItem = hiero.core.TrackItem( - split_name, hiero.core.TrackItem.kVideo) - trackItem.setSource(new_source) - trackItem.setSourceIn(source_in) - trackItem.setSourceOut(source_out) - trackItem.setTimelineIn(nk["clipIn"]) - trackItem.setTimelineOut(nk["clipIn"] + (source_out - source_in)) - track.addTrackItem(trackItem) - clips_lst.append(trackItem) - - return clips_lst - - -def create_bin_in_project(bin_name='', project=''): - ''' - create bin in project and - if the bin_name is "bin1/bin2/bin3" it will create whole depth - ''' - - if not project: - # get the first loaded project - project = hiero.core.projects()[-1] - if not bin_name: - return None - if '/' in bin_name: - bin_name = bin_name.split('/') - else: - bin_name = [bin_name] - - clipsBin = project.clipsBin() - - done_bin_lst = [] - for i, b in enumerate(bin_name): - if i == 0 and len(bin_name) > 1: - if b in [bin.name() for bin in clipsBin.bins()]: - bin = [bin for bin in clipsBin.bins() if b in bin.name()][0] - done_bin_lst.append(bin) - else: - create_bin = hiero.core.Bin(b) - clipsBin.addItem(create_bin) - done_bin_lst.append(create_bin) - - elif i >= 1 and i < len(bin_name) - 1: - if b in [bin.name() for bin in done_bin_lst[i - 1].bins()]: - bin = [ - bin for bin in done_bin_lst[i - 1].bins() - if b in bin.name() - ][0] - done_bin_lst.append(bin) - else: - create_bin = hiero.core.Bin(b) - done_bin_lst[i - 1].addItem(create_bin) - done_bin_lst.append(create_bin) - - elif i == len(bin_name) - 1: - if b in [bin.name() for bin in done_bin_lst[i - 1].bins()]: - bin = [ - bin for bin in done_bin_lst[i - 1].bins() - if b in bin.name() - ][0] - done_bin_lst.append(bin) - else: - create_bin = hiero.core.Bin(b) - done_bin_lst[i - 1].addItem(create_bin) - done_bin_lst.append(create_bin) - # print [bin.name() for bin in clipsBin.bins()] - return done_bin_lst[-1] - - -def split_by_client_version(string): - regex = r"[/_.]v\d+" - try: - matches = re.findall(regex, string, re.IGNORECASE) - return string.split(matches[0]) - except Exception as e: - print(e) - return None - - -# nk_workfiles = [{ -# 'path': 'C:/Users/hubert/_PYPE_testing/projects/D001_projectx/episodes/ep120/ep120sq01/120sh020/publish/plates/platesMain/v023/prjx_120sh020_platesMain_v023.nk', -# 'name': '120sh020_platesMain', -# 'handles': 10, -# 'handleStart': 10, -# 'handleEnd': 10, -# "clipIn": 16, -# "frameStart": 991, -# "frameEnd": 1023, -# 'task': 'platesMain', -# 'work_dir': 'shots', -# 'shot': '120sh020' -# }] diff --git a/pype/plugins/hiero/publish/collect_calculate_retime.py b/pype/hosts/hiero/plugins/_publish/collect_calculate_retime.py similarity index 100% rename from pype/plugins/hiero/publish/collect_calculate_retime.py rename to pype/hosts/hiero/plugins/_publish/collect_calculate_retime.py diff --git a/pype/plugins/hiero/publish/collect_framerate.py b/pype/hosts/hiero/plugins/_publish/collect_framerate.py similarity index 100% rename from pype/plugins/hiero/publish/collect_framerate.py rename to pype/hosts/hiero/plugins/_publish/collect_framerate.py diff --git a/pype/plugins/hiero/_unused/collect_metadata.py b/pype/hosts/hiero/plugins/_publish/collect_metadata.py similarity index 100% rename from pype/plugins/hiero/_unused/collect_metadata.py rename to pype/hosts/hiero/plugins/_publish/collect_metadata.py diff --git a/pype/plugins/hiero/publish/collect_tag_comments.py b/pype/hosts/hiero/plugins/_publish/collect_tag_comments.py similarity index 100% rename from pype/plugins/hiero/publish/collect_tag_comments.py rename to pype/hosts/hiero/plugins/_publish/collect_tag_comments.py diff --git a/pype/plugins/hiero/publish/collect_tag_retime.py b/pype/hosts/hiero/plugins/_publish/collect_tag_retime.py similarity index 100% rename from pype/plugins/hiero/publish/collect_tag_retime.py rename to pype/hosts/hiero/plugins/_publish/collect_tag_retime.py diff --git a/pype/plugins/hiero/_unused/collect_timecodes.py b/pype/hosts/hiero/plugins/_publish/collect_timecodes.py similarity index 100% rename from pype/plugins/hiero/_unused/collect_timecodes.py rename to pype/hosts/hiero/plugins/_publish/collect_timecodes.py diff --git a/pype/hosts/hiero/plugins/create/create_shot_clip.py b/pype/hosts/hiero/plugins/create/create_shot_clip.py new file mode 100644 index 00000000000..9f8ae1a300c --- /dev/null +++ b/pype/hosts/hiero/plugins/create/create_shot_clip.py @@ -0,0 +1,257 @@ +import pype.hosts.hiero.api as phiero +# from pype.hosts.hiero.api import plugin, lib +# reload(lib) +# reload(plugin) +# reload(phiero) + + +class CreateShotClip(phiero.Creator): + """Publishable clip""" + + label = "Create Publishable Clip" + family = "clip" + icon = "film" + defaults = ["Main"] + + gui_tracks = [track.name() + for track in phiero.get_current_sequence().videoTracks()] + gui_name = "Pype publish attributes creator" + gui_info = "Define sequential rename and fill hierarchy data." + gui_inputs = { + "renameHierarchy": { + "type": "section", + "label": "Shot Hierarchy And Rename Settings", + "target": "ui", + "order": 0, + "value": { + "hierarchy": { + "value": "{folder}/{sequence}", + "type": "QLineEdit", + "label": "Shot Parent Hierarchy", + "target": "tag", + "toolTip": "Parents folder for shot root folder, Template filled with `Hierarchy Data` section", # noqa + "order": 0}, + "clipRename": { + "value": False, + "type": "QCheckBox", + "label": "Rename clips", + "target": "ui", + "toolTip": "Renaming selected clips on fly", # noqa + "order": 1}, + "clipName": { + "value": "{sequence}{shot}", + "type": "QLineEdit", + "label": "Clip Name Template", + "target": "ui", + "toolTip": "template for creating shot namespaused for renaming (use rename: on)", # noqa + "order": 2}, + "countFrom": { + "value": 10, + "type": "QSpinBox", + "label": "Count sequence from", + "target": "ui", + "toolTip": "Set when the sequence number stafrom", # noqa + "order": 3}, + "countSteps": { + "value": 10, + "type": "QSpinBox", + "label": "Stepping number", + "target": "ui", + "toolTip": "What number is adding every new step", # noqa + "order": 4}, + } + }, + "hierarchyData": { + "type": "dict", + "label": "Shot Template Keywords", + "target": "tag", + "order": 1, + "value": { + "folder": { + "value": "shots", + "type": "QLineEdit", + "label": "{folder}", + "target": "tag", + "toolTip": "Name of folder used for root of generated shots.\nUsable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa + "order": 0}, + "episode": { + "value": "ep01", + "type": "QLineEdit", + "label": "{episode}", + "target": "tag", + "toolTip": "Name of episode.\nUsable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa + "order": 1}, + "sequence": { + "value": "sq01", + "type": "QLineEdit", + "label": "{sequence}", + "target": "tag", + "toolTip": "Name of sequence of shots.\nUsable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa + "order": 2}, + "track": { + "value": "{_track_}", + "type": "QLineEdit", + "label": "{track}", + "target": "tag", + "toolTip": "Name of sequence of shots.\nUsable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa + "order": 3}, + "shot": { + "value": "sh###", + "type": "QLineEdit", + "label": "{shot}", + "target": "tag", + "toolTip": "Name of shot. `#` is converted to paded number. \nAlso could be used with usable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa + "order": 4} + } + }, + "verticalSync": { + "type": "section", + "label": "Vertical Synchronization Of Attributes", + "target": "ui", + "order": 2, + "value": { + "vSyncOn": { + "value": True, + "type": "QCheckBox", + "label": "Enable Vertical Sync", + "target": "ui", + "toolTip": "Switch on if you want clips above each other to share its attributes", # noqa + "order": 0}, + "vSyncTrack": { + "value": gui_tracks, # noqa + "type": "QComboBox", + "label": "Master track", + "target": "ui", + "toolTip": "Select driving track name which should be mastering all others", # noqa + "order": 1} + } + }, + "publishSettings": { + "type": "section", + "label": "Publish Settings", + "target": "ui", + "order": 3, + "value": { + "subsetName": { + "value": ["", "main", "bg", "fg", "bg", + "animatic"], + "type": "QComboBox", + "label": "Subset Name", + "target": "ui", + "toolTip": "chose subset name patern, if is selected, name of track layer will be used", # noqa + "order": 0}, + "subsetFamily": { + "value": ["plate", "take"], + "type": "QComboBox", + "label": "Subset Family", + "target": "ui", "toolTip": "What use of this subset is for", # noqa + "order": 1}, + "reviewTrack": { + "value": ["< none >"] + gui_tracks, + "type": "QComboBox", + "label": "Use Review Track", + "target": "ui", + "toolTip": "Generate preview videos on fly, if `< none >` is defined nothing will be generated.", # noqa + "order": 2}, + "audio": { + "value": False, + "type": "QCheckBox", + "label": "Include audio", + "target": "tag", + "toolTip": "Process subsets with corresponding audio", # noqa + "order": 3}, + "sourceResolution": { + "value": False, + "type": "QCheckBox", + "label": "Source resolution", + "target": "tag", + "toolTip": "Is resloution taken from timeline or source?", # noqa + "order": 4}, + } + }, + "frameRangeAttr": { + "type": "section", + "label": "Shot Attributes", + "target": "ui", + "order": 4, + "value": { + "workfileFrameStart": { + "value": 1001, + "type": "QSpinBox", + "label": "Workfiles Start Frame", + "target": "tag", + "toolTip": "Set workfile starting frame number", # noqa + "order": 0 + }, + "handleStart": { + "value": 0, + "type": "QSpinBox", + "label": "Handle Start", + "target": "tag", + "toolTip": "Handle at start of clip", # noqa + "order": 1 + }, + "handleEnd": { + "value": 0, + "type": "QSpinBox", + "label": "Handle End", + "target": "tag", + "toolTip": "Handle at end of clip", # noqa + "order": 2 + } + } + } + } + + presets = None + + def process(self): + # get key pares from presets and match it on ui inputs + for k, v in self.gui_inputs.items(): + if v["type"] in ("dict", "section"): + # nested dictionary (only one level allowed + # for sections and dict) + for _k, _v in v["value"].items(): + if self.presets.get(_k): + self.gui_inputs[k][ + "value"][_k]["value"] = self.presets[_k] + if self.presets.get(k): + self.gui_inputs[k]["value"] = self.presets[k] + + # open widget for plugins inputs + widget = self.widget(self.gui_name, self.gui_info, self.gui_inputs) + widget.exec_() + + if len(self.selected) < 1: + return + + if not widget.result: + print("Operation aborted") + return + + self.rename_add = 0 + + # get ui output for track name for vertical sync + v_sync_track = widget.result["vSyncTrack"]["value"] + + # sort selected trackItems by + sorted_selected_track_items = list() + unsorted_selected_track_items = list() + for _ti in self.selected: + if _ti.parent().name() in v_sync_track: + sorted_selected_track_items.append(_ti) + else: + unsorted_selected_track_items.append(_ti) + + sorted_selected_track_items.extend(unsorted_selected_track_items) + + kwargs = { + "ui_inputs": widget.result, + "avalon": self.data + } + + for i, track_item in enumerate(sorted_selected_track_items): + self.rename_index = i + + # convert track item to timeline media pool item + phiero.PublishClip(self, track_item, **kwargs).convert() diff --git a/pype/hosts/hiero/plugins/load/load_clip.py b/pype/hosts/hiero/plugins/load/load_clip.py new file mode 100644 index 00000000000..d9f6bbf3c8c --- /dev/null +++ b/pype/hosts/hiero/plugins/load/load_clip.py @@ -0,0 +1,170 @@ +from avalon import io, api +import pype.hosts.hiero.api as phiero +# from pype.hosts.hiero.api import plugin, lib +# reload(lib) +# reload(plugin) +# reload(phiero) + + +class LoadClip(phiero.SequenceLoader): + """Load a subset to timeline as clip + + Place clip to timeline on its asset origin timings collected + during conforming to project + """ + + families = ["render2d", "source", "plate", "render", "review"] + representations = ["exr", "dpx", "jpg", "jpeg", "png", "h264"] + + label = "Load as clip" + order = -10 + icon = "code-fork" + color = "orange" + + # for loader multiselection + sequence = None + track = None + + # presets + clip_color_last = "green" + clip_color = "red" + + def load(self, context, name, namespace, options): + + # in case loader uses multiselection + if self.track and self.sequence: + options.update({ + "sequence": self.sequence, + "track": self.track + }) + + # load clip to timeline and get main variables + track_item = phiero.ClipLoader(self, context, **options).load() + namespace = namespace or track_item.name() + version = context['version'] + version_data = version.get("data", {}) + version_name = version.get("name", None) + colorspace = version_data.get("colorspace", None) + object_name = "{}_{}".format(name, namespace) + + # add additional metadata from the version to imprint Avalon knob + add_keys = [ + "frameStart", "frameEnd", "source", "author", + "fps", "handleStart", "handleEnd" + ] + + # move all version data keys to tag data + data_imprint = {} + for key in add_keys: + data_imprint.update({ + key: version_data.get(key, str(None)) + }) + + # add variables related to version context + data_imprint.update({ + "version": version_name, + "colorspace": colorspace, + "objectName": object_name + }) + + # update color of clip regarding the version order + self.set_item_color(track_item, version) + + # deal with multiselection + self.multiselection(track_item) + + self.log.info("Loader done: `{}`".format(name)) + + return phiero.containerise( + track_item, + name, namespace, context, + self.__class__.__name__, + data_imprint) + + def switch(self, container, representation): + self.update(container, representation) + + def update(self, container, representation): + """ Updating previously loaded clips + """ + + # load clip to timeline and get main variables + name = container['name'] + namespace = container['namespace'] + track_item = phiero.get_track_items( + track_item_name=namespace) + version = io.find_one({ + "type": "version", + "_id": representation["parent"] + }) + version_data = version.get("data", {}) + version_name = version.get("name", None) + colorspace = version_data.get("colorspace", None) + object_name = "{}_{}".format(name, namespace) + file = api.get_representation_path(representation).replace("\\", "/") + + # reconnect media to new path + track_item.source().reconnectMedia(file) + + # add additional metadata from the version to imprint Avalon knob + add_keys = [ + "frameStart", "frameEnd", "source", "author", + "fps", "handleStart", "handleEnd" + ] + + # move all version data keys to tag data + data_imprint = {} + for key in add_keys: + data_imprint.update({ + key: version_data.get(key, str(None)) + }) + + # add variables related to version context + data_imprint.update({ + "representation": str(representation["_id"]), + "version": version_name, + "colorspace": colorspace, + "objectName": object_name + }) + + # update color of clip regarding the version order + self.set_item_color(track_item, version) + + return phiero.update_container(track_item, data_imprint) + + def remove(self, container): + """ Removing previously loaded clips + """ + # load clip to timeline and get main variables + namespace = container['namespace'] + track_item = phiero.get_track_items( + track_item_name=namespace) + track = track_item.parent() + + # remove track item from track + track.removeItem(track_item) + + @classmethod + def multiselection(cls, track_item): + if not cls.track: + cls.track = track_item.parent() + cls.sequence = cls.track.parent() + + @classmethod + def set_item_color(cls, track_item, version): + + # define version name + version_name = version.get("name", None) + # get all versions in list + versions = io.find({ + "type": "version", + "parent": version["parent"] + }).distinct('name') + + max_version = max(versions) + + # set clip colour + if version_name == max_version: + track_item.source().binItem().setColor(cls.clip_color_last) + else: + track_item.source().binItem().setColor(cls.clip_color) diff --git a/pype/plugins/hiero/publish/collect_assetbuilds.py b/pype/hosts/hiero/plugins/publish/collect_assetbuilds.py similarity index 90% rename from pype/plugins/hiero/publish/collect_assetbuilds.py rename to pype/hosts/hiero/plugins/publish/collect_assetbuilds.py index f20acaf3da8..a90856c6fde 100644 --- a/pype/plugins/hiero/publish/collect_assetbuilds.py +++ b/pype/hosts/hiero/plugins/publish/collect_assetbuilds.py @@ -30,9 +30,12 @@ def process(self, context): # Exclude non-tagged instances. tagged = False asset_names = [] + for tag in instance.data["tags"]: - family = dict(tag["metadata"]).get("tag.family", "") - if family.lower() == "assetbuild": + t_metadata = dict(tag.metadata()) + t_family = t_metadata.get("tag.family", "") + + if t_family.lower() == "assetbuild": asset_names.append(tag["name"]) tagged = True diff --git a/pype/hosts/hiero/plugins/publish/collect_clip_resolution.py b/pype/hosts/hiero/plugins/publish/collect_clip_resolution.py new file mode 100644 index 00000000000..fea36d00fba --- /dev/null +++ b/pype/hosts/hiero/plugins/publish/collect_clip_resolution.py @@ -0,0 +1,38 @@ +import pyblish.api + + +class CollectClipResolution(pyblish.api.InstancePlugin): + """Collect clip geometry resolution""" + + order = pyblish.api.CollectorOrder - 0.1 + label = "Collect Clip Resoluton" + hosts = ["hiero"] + families = ["clip"] + + def process(self, instance): + sequence = instance.context.data['activeSequence'] + item = instance.data["item"] + source_resolution = instance.data.get("sourceResolution", None) + + resolution_width = int(sequence.format().width()) + resolution_height = int(sequence.format().height()) + pixel_aspect = sequence.format().pixelAspect() + + # source exception + if source_resolution: + resolution_width = int(item.source().mediaSource().width()) + resolution_height = int(item.source().mediaSource().height()) + pixel_aspect = item.source().mediaSource().pixelAspect() + + resolution_data = { + "resolutionWidth": resolution_width, + "resolutionHeight": resolution_height, + "pixelAspect": pixel_aspect + } + # add to instacne data + instance.data.update(resolution_data) + + self.log.info("Resolution of instance '{}' is: {}".format( + instance, + resolution_data + )) diff --git a/pype/hosts/hiero/plugins/publish/collect_frame_ranges.py b/pype/hosts/hiero/plugins/publish/collect_frame_ranges.py new file mode 100644 index 00000000000..39387578d20 --- /dev/null +++ b/pype/hosts/hiero/plugins/publish/collect_frame_ranges.py @@ -0,0 +1,70 @@ +import pyblish.api + + +class CollectFrameRanges(pyblish.api.InstancePlugin): + """ Collect all framranges. + """ + + order = pyblish.api.CollectorOrder + label = "Collect Frame Ranges" + hosts = ["hiero"] + families = ["clip", "effect"] + + def process(self, instance): + + data = dict() + track_item = instance.data["item"] + + # handles + handle_start = instance.data["handleStart"] + handle_end = instance.data["handleEnd"] + + # source frame ranges + source_in = int(track_item.sourceIn()) + source_out = int(track_item.sourceOut()) + source_in_h = int(source_in - handle_start) + source_out_h = int(source_out + handle_end) + + # timeline frame ranges + clip_in = int(track_item.timelineIn()) + clip_out = int(track_item.timelineOut()) + clip_in_h = clip_in - handle_start + clip_out_h = clip_out + handle_end + + # durations + clip_duration = (clip_out - clip_in) + 1 + clip_duration_h = clip_duration + (handle_start + handle_end) + + # set frame start with tag or take it from timeline `startingFrame` + frame_start = instance.data.get("workfileFrameStart") + + if not frame_start: + frame_start = clip_in + + frame_end = frame_start + (clip_out - clip_in) + + data.update({ + # media source frame range + "sourceIn": source_in, + "sourceOut": source_out, + "sourceInH": source_in_h, + "sourceOutH": source_out_h, + + # timeline frame range + "clipIn": clip_in, + "clipOut": clip_out, + "clipInH": clip_in_h, + "clipOutH": clip_out_h, + + # workfile frame range + "frameStart": frame_start, + "frameEnd": frame_end, + + "clipDuration": clip_duration, + "clipDurationH": clip_duration_h, + + "fps": instance.context.data["fps"] + }) + self.log.info("Frame range data for instance `{}` are: {}".format( + instance, data)) + instance.data.update(data) diff --git a/pype/hosts/hiero/plugins/publish/collect_hierarchy_context.py b/pype/hosts/hiero/plugins/publish/collect_hierarchy_context.py new file mode 100644 index 00000000000..ba3e388c533 --- /dev/null +++ b/pype/hosts/hiero/plugins/publish/collect_hierarchy_context.py @@ -0,0 +1,116 @@ +import pyblish.api +import avalon.api as avalon + + +class CollectHierarchy(pyblish.api.ContextPlugin): + """Collecting hierarchy from `parents`. + + present in `clip` family instances coming from the request json data file + + It will add `hierarchical_context` into each instance for integrate + plugins to be able to create needed parents for the context if they + don't exist yet + """ + + label = "Collect Hierarchy" + order = pyblish.api.CollectorOrder + families = ["clip"] + + def process(self, context): + temp_context = {} + project_name = avalon.Session["AVALON_PROJECT"] + final_context = {} + final_context[project_name] = {} + final_context[project_name]['entity_type'] = 'Project' + + for instance in context: + self.log.info("Processing instance: `{}` ...".format(instance)) + + # shot data dict + shot_data = {} + families = instance.data.get("families") + + # filter out all unepropriate instances + if not instance.data["publish"]: + continue + if not families: + continue + # exclude other families then self.families with intersection + if not set(self.families).intersection(families): + continue + + # exclude if not masterLayer True + if not instance.data.get("masterLayer"): + continue + + # update families to include `shot` for hierarchy integration + instance.data["families"] = families + ["shot"] + + # get asset build data if any available + shot_data["inputs"] = [ + x["_id"] for x in instance.data.get("assetbuilds", []) + ] + + # suppose that all instances are Shots + shot_data['entity_type'] = 'Shot' + shot_data['tasks'] = instance.data.get("tasks") or [] + shot_data["comments"] = instance.data.get("comments", []) + + shot_data['custom_attributes'] = { + "handleStart": instance.data["handleStart"], + "handleEnd": instance.data["handleEnd"], + "frameStart": instance.data["frameStart"], + "frameEnd": instance.data["frameEnd"], + "clipIn": instance.data["clipIn"], + "clipOut": instance.data["clipOut"], + 'fps': instance.context.data["fps"], + "resolutionWidth": instance.data["resolutionWidth"], + "resolutionHeight": instance.data["resolutionHeight"], + "pixelAspect": instance.data["pixelAspect"] + } + + actual = {instance.data["asset"]: shot_data} + + for parent in reversed(instance.data["parents"]): + next_dict = {} + parent_name = parent["entity_name"] + next_dict[parent_name] = {} + next_dict[parent_name]["entity_type"] = parent[ + "entity_type"].capitalize() + next_dict[parent_name]["childs"] = actual + actual = next_dict + + temp_context = self._update_dict(temp_context, actual) + + # skip if nothing for hierarchy available + if not temp_context: + return + + final_context[project_name]['childs'] = temp_context + + # adding hierarchy context to context + context.data["hierarchyContext"] = final_context + self.log.debug("context.data[hierarchyContext] is: {}".format( + context.data["hierarchyContext"])) + + def _update_dict(self, parent_dict, child_dict): + """ + Nesting each children into its parent. + + Args: + parent_dict (dict): parent dict wich should be nested with children + child_dict (dict): children dict which should be injested + """ + + for key in parent_dict: + if key in child_dict and isinstance(parent_dict[key], dict): + child_dict[key] = self._update_dict( + parent_dict[key], child_dict[key] + ) + else: + if parent_dict.get(key) and child_dict.get(key): + continue + else: + child_dict[key] = parent_dict[key] + + return child_dict diff --git a/pype/plugins/hiero/publish/collect_host_version.py b/pype/hosts/hiero/plugins/publish/collect_host_version.py similarity index 58% rename from pype/plugins/hiero/publish/collect_host_version.py rename to pype/hosts/hiero/plugins/publish/collect_host_version.py index 267d035f4dd..76e5bd11d51 100644 --- a/pype/plugins/hiero/publish/collect_host_version.py +++ b/pype/hosts/hiero/plugins/publish/collect_host_version.py @@ -4,9 +4,12 @@ class CollectHostVersion(pyblish.api.ContextPlugin): """Inject the hosts version into context""" - order = pyblish.api.CollectorOrder + label = "Collect Host and HostVersion" + order = pyblish.api.CollectorOrder - 0.5 def process(self, context): import nuke + import pyblish.api + context.set_data("host", pyblish.api.current_host()) context.set_data('hostVersion', value=nuke.NUKE_VERSION_STRING) diff --git a/pype/hosts/hiero/plugins/publish/collect_plates.py b/pype/hosts/hiero/plugins/publish/collect_plates.py new file mode 100644 index 00000000000..d4f98f509e8 --- /dev/null +++ b/pype/hosts/hiero/plugins/publish/collect_plates.py @@ -0,0 +1,169 @@ +from pyblish import api +import os +import re +import clique + + +class CollectPlates(api.InstancePlugin): + """Collect plate representations. + """ + + # Run just before CollectSubsets + order = api.CollectorOrder + 0.1020 + label = "Collect Plates" + hosts = ["hiero"] + families = ["plate"] + + def process(self, instance): + # add to representations + if not instance.data.get("representations"): + instance.data["representations"] = list() + + self.main_clip = instance.data["item"] + # get plate source attributes + source_media = instance.data["sourceMedia"] + source_path = instance.data["sourcePath"] + source_first = instance.data["sourceFirst"] + frame_start = instance.data["frameStart"] + frame_end = instance.data["frameEnd"] + handle_start = instance.data["handleStart"] + handle_end = instance.data["handleEnd"] + source_in = instance.data["sourceIn"] + source_out = instance.data["sourceOut"] + source_in_h = instance.data["sourceInH"] + source_out_h = instance.data["sourceOutH"] + + # define if review media is sequence + is_sequence = bool(not source_media.singleFile()) + self.log.debug("is_sequence: {}".format(is_sequence)) + + file_dir = os.path.dirname(source_path) + file = os.path.basename(source_path) + ext = os.path.splitext(file)[-1] + + # detect if sequence + if not is_sequence: + # is video file + files = file + else: + files = list() + spliter, padding = self.detect_sequence(file) + self.log.debug("_ spliter, padding: {}, {}".format( + spliter, padding)) + base_name = file.split(spliter)[0] + + # define collection and calculate frame range + collection = clique.Collection( + base_name, + ext, + padding, + set(range( + int(source_first + source_in_h), + int(source_first + source_out_h) + 1 + )) + ) + self.log.debug("_ collection: {}".format(collection)) + + real_files = os.listdir(file_dir) + self.log.debug("_ real_files: {}".format(real_files)) + + # collect frames to repre files list + self.handle_start_exclude = list() + self.handle_end_exclude = list() + for findex, item in enumerate(collection): + if item not in real_files: + self.log.debug("_ item: {}".format(item)) + test_index = findex + int(source_first + source_in_h) + test_start = int(source_first + source_in) + test_end = int(source_first + source_out) + if (test_index < test_start): + self.handle_start_exclude.append(test_index) + elif (test_index > test_end): + self.handle_end_exclude.append(test_index) + continue + files.append(item) + + # change label + instance.data["label"] = "{0} - ({1})".format( + instance.data["label"], ext + ) + + self.log.debug("Instance review: {}".format(instance.data["name"])) + + # adding representation for review mov + representation = { + "files": files, + "stagingDir": file_dir, + "frameStart": frame_start - handle_start, + "frameEnd": frame_end + handle_end, + "name": ext[1:], + "ext": ext[1:] + } + + instance.data["representations"].append(representation) + self.version_data(instance) + + self.log.debug( + "Added representations: {}".format( + instance.data["representations"])) + + self.log.debug( + "instance.data: {}".format(instance.data)) + + def version_data(self, instance): + transfer_data = [ + "handleStart", "handleEnd", "sourceIn", "sourceOut", + "frameStart", "frameEnd", "sourceInH", "sourceOutH", + "clipIn", "clipOut", "clipInH", "clipOutH", "asset", + "track" + ] + + version_data = dict() + # pass data to version + version_data.update({k: instance.data[k] for k in transfer_data}) + + if 'version' in instance.data: + version_data["version"] = instance.data["version"] + + handle_start = instance.data["handleStart"] + handle_end = instance.data["handleEnd"] + + if self.handle_start_exclude: + handle_start -= len(self.handle_start_exclude) + + if self.handle_end_exclude: + handle_end -= len(self.handle_end_exclude) + + # add to data of representation + version_data.update({ + "colorspace": self.main_clip.sourceMediaColourTransform(), + "families": instance.data["families"], + "subset": instance.data["subset"], + "fps": instance.data["fps"], + "handleStart": handle_start, + "handleEnd": handle_end + }) + instance.data["versionData"] = version_data + + def detect_sequence(self, file): + """ Get identificating pater for image sequence + + Can find file.0001.ext, file.%02d.ext, file.####.ext + + Return: + string: any matching sequence patern + int: padding of sequnce numbering + """ + 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: + return None, None diff --git a/pype/hosts/hiero/plugins/publish/collect_review.py b/pype/hosts/hiero/plugins/publish/collect_review.py new file mode 100644 index 00000000000..6f550223ce1 --- /dev/null +++ b/pype/hosts/hiero/plugins/publish/collect_review.py @@ -0,0 +1,261 @@ +from pyblish import api +import os +import clique +from pype.hosts.hiero.api import ( + is_overlapping, get_sequence_pattern_and_padding) + + +class CollectReview(api.InstancePlugin): + """Collect review representation. + """ + + # Run just before CollectSubsets + order = api.CollectorOrder + 0.1022 + label = "Collect Review" + hosts = ["hiero"] + families = ["review"] + + def get_review_item(self, instance): + """ + Get review clip track item from review track name + + Args: + instance (obj): publishing instance + + Returns: + hiero.core.TrackItem: corresponding track item + + Raises: + Exception: description + + """ + review_track = instance.data.get("review") + video_tracks = instance.context.data["videoTracks"] + for track in video_tracks: + if review_track not in track.name(): + continue + for item in track.items(): + self.log.debug(item) + if is_overlapping(item, self.main_clip): + self.log.debug("Winner is: {}".format(item)) + break + + # validate the clip is fully converted with review clip + assert is_overlapping( + item, self.main_clip, strict=True), ( + "Review clip not cowering fully " + "the clip `{}`").format(self.main_clip.name()) + + return item + + def process(self, instance): + tags = ["review", "ftrackreview"] + + # get reviewable item from `review` instance.data attribute + self.main_clip = instance.data.get("item") + self.rw_clip = self.get_review_item(instance) + + # let user know there is missing review clip and convert instance + # back as not reviewable + assert self.rw_clip, "Missing reviewable clip for '{}'".format( + self.main_clip.name() + ) + + # add to representations + if not instance.data.get("representations"): + instance.data["representations"] = list() + + # get review media main info + rw_source = self.rw_clip.source().mediaSource() + rw_source_duration = int(rw_source.duration()) + self.rw_source_path = rw_source.firstpath() + rw_source_file_info = rw_source.fileinfos().pop() + + # define if review media is sequence + is_sequence = bool(not rw_source.singleFile()) + self.log.debug("is_sequence: {}".format(is_sequence)) + + # get handles + handle_start = instance.data["handleStart"] + handle_end = instance.data["handleEnd"] + + # review timeline and source frame ranges + rw_clip_in = int(self.rw_clip.timelineIn()) + rw_clip_out = int(self.rw_clip.timelineOut()) + self.rw_clip_source_in = int(self.rw_clip.sourceIn()) + self.rw_clip_source_out = int(self.rw_clip.sourceOut()) + rw_source_first = int(rw_source_file_info.startFrame()) + + # calculate delivery source_in and source_out + # main_clip_timeline_in - review_item_timeline_in + 1 + main_clip_in = self.main_clip.timelineIn() + main_clip_out = self.main_clip.timelineOut() + + source_in_diff = main_clip_in - rw_clip_in + source_out_diff = main_clip_out - rw_clip_out + + if source_in_diff: + self.rw_clip_source_in += source_in_diff + if source_out_diff: + self.rw_clip_source_out += source_out_diff + + # review clip durations + rw_clip_duration = ( + self.rw_clip_source_out - self.rw_clip_source_in) + 1 + rw_clip_duration_h = rw_clip_duration + ( + handle_start + handle_end) + + # add created data to review item data + instance.data["reviewItemData"] = { + "mediaDuration": rw_source_duration + } + + file_dir = os.path.dirname(self.rw_source_path) + file = os.path.basename(self.rw_source_path) + ext = os.path.splitext(file)[-1] + + # detect if sequence + if not is_sequence: + # is video file + files = file + else: + files = list() + spliter, padding = get_sequence_pattern_and_padding(file) + self.log.debug("_ spliter, padding: {}, {}".format( + spliter, padding)) + base_name = file.split(spliter)[0] + + # define collection and calculate frame range + collection = clique.Collection(base_name, ext, padding, set(range( + int(rw_source_first + int( + self.rw_clip_source_in - handle_start)), + int(rw_source_first + int( + self.rw_clip_source_out + handle_end) + 1)))) + self.log.debug("_ collection: {}".format(collection)) + + real_files = os.listdir(file_dir) + self.log.debug("_ real_files: {}".format(real_files)) + + # collect frames to repre files list + for item in collection: + if item not in real_files: + self.log.debug("_ item: {}".format(item)) + continue + files.append(item) + + # add prep tag + tags.extend(["prep", "delete"]) + + # change label + instance.data["label"] = "{0} - ({1})".format( + instance.data["label"], ext + ) + + self.log.debug("Instance review: {}".format(instance.data["name"])) + + # adding representation for review mov + representation = { + "files": files, + "stagingDir": file_dir, + "frameStart": rw_source_first + self.rw_clip_source_in, + "frameEnd": rw_source_first + self.rw_clip_source_out, + "frameStartFtrack": int( + self.rw_clip_source_in - handle_start), + "frameEndFtrack": int(self.rw_clip_source_out + handle_end), + "step": 1, + "fps": instance.data["fps"], + "name": "review", + "tags": tags, + "ext": ext[1:] + } + + if rw_source_duration > rw_clip_duration_h: + self.log.debug("Media duration higher: {}".format( + (rw_source_duration - rw_clip_duration_h))) + representation.update({ + "frameStart": rw_source_first + int( + self.rw_clip_source_in - handle_start), + "frameEnd": rw_source_first + int( + self.rw_clip_source_out + handle_end), + "tags": ["_cut-bigger", "prep", "delete"] + }) + elif rw_source_duration < rw_clip_duration_h: + self.log.debug("Media duration higher: {}".format( + (rw_source_duration - rw_clip_duration_h))) + representation.update({ + "frameStart": rw_source_first + int( + self.rw_clip_source_in - handle_start), + "frameEnd": rw_source_first + int( + self.rw_clip_source_out + handle_end), + "tags": ["prep", "delete"] + }) + + instance.data["representations"].append(representation) + + self.create_thumbnail(instance) + + self.log.debug( + "Added representations: {}".format( + instance.data["representations"])) + + def create_thumbnail(self, instance): + source_file = os.path.basename(self.rw_source_path) + spliter, padding = get_sequence_pattern_and_padding(source_file) + + if spliter: + head, ext = source_file.split(spliter) + else: + head, ext = os.path.splitext(source_file) + + # staging dir creation + staging_dir = os.path.dirname( + self.rw_source_path) + + # get thumbnail frame from the middle + thumb_frame = int(self.rw_clip_source_in + ( + (self.rw_clip_source_out - self.rw_clip_source_in) / 2)) + + thumb_file = "{}thumbnail{}{}".format(head, thumb_frame, ".png") + thumb_path = os.path.join(staging_dir, thumb_file) + + thumbnail = self.rw_clip.thumbnail(thumb_frame).save( + thumb_path, + format='png' + ) + self.log.debug( + "__ thumbnail: `{}`, frame: `{}`".format(thumbnail, thumb_frame)) + + self.log.debug("__ thumbnail: {}".format(thumbnail)) + thumb_representation = { + 'files': thumb_file, + 'stagingDir': staging_dir, + 'name': "thumbnail", + 'thumbnail': True, + 'ext': "png" + } + instance.data["representations"].append( + thumb_representation) + + def version_data(self, instance): + transfer_data = [ + "handleStart", "handleEnd", "sourceIn", "sourceOut", + "frameStart", "frameEnd", "sourceInH", "sourceOutH", + "clipIn", "clipOut", "clipInH", "clipOutH", "asset", + "track" + ] + + version_data = dict() + # pass data to version + version_data.update({k: instance.data[k] for k in transfer_data}) + + if 'version' in instance.data: + version_data["version"] = instance.data["version"] + + # add to data of representation + version_data.update({ + "colorspace": self.rw_clip.sourceMediaColourTransform(), + "families": instance.data["families"], + "subset": instance.data["subset"], + "fps": instance.data["fps"] + }) + instance.data["versionData"] = version_data diff --git a/pype/plugins/hiero/publish/collect_tag_tasks.py b/pype/hosts/hiero/plugins/publish/collect_tag_tasks.py similarity index 88% rename from pype/plugins/hiero/publish/collect_tag_tasks.py rename to pype/hosts/hiero/plugins/publish/collect_tag_tasks.py index ae4578bbf70..70891d5b456 100644 --- a/pype/plugins/hiero/publish/collect_tag_tasks.py +++ b/pype/hosts/hiero/plugins/publish/collect_tag_tasks.py @@ -4,7 +4,7 @@ class CollectClipTagTasks(api.InstancePlugin): """Collect Tags from selected track items.""" - order = api.CollectorOrder + 0.012 + order = api.CollectorOrder label = "Collect Tag Tasks" hosts = ["hiero"] families = ['clip'] @@ -14,8 +14,8 @@ def process(self, instance): tags = instance.data["tags"] tasks = dict() - for t in tags: - t_metadata = dict(t["metadata"]) + for tag in tags: + t_metadata = dict(tag.metadata()) t_family = t_metadata.get("tag.family", "") # gets only task family tags and collect labels diff --git a/pype/plugins/hiero/publish/extract_audio.py b/pype/hosts/hiero/plugins/publish/extract_audio.py similarity index 55% rename from pype/plugins/hiero/publish/extract_audio.py rename to pype/hosts/hiero/plugins/publish/extract_audio.py index 34fdcdfe9d3..690e9c5b1e6 100644 --- a/pype/plugins/hiero/publish/extract_audio.py +++ b/pype/hosts/hiero/plugins/publish/extract_audio.py @@ -1,47 +1,44 @@ -from pyblish import api +import os +from hiero.exporters.FnExportUtil import writeSequenceAudioWithHandles +import pyblish import pype class ExtractAudioFile(pype.api.Extractor): - """Extracts audio subset file""" + """Extracts audio subset file from all active timeline audio tracks""" - order = api.ExtractorOrder + order = pyblish.api.ExtractorOrder label = "Extract Subset Audio" hosts = ["hiero"] - families = ["clip", "audio"] - match = api.Intersection + families = ["audio"] + match = pyblish.api.Intersection def process(self, instance): - import os - - from hiero.exporters.FnExportUtil import writeSequenceAudioWithHandles - - item = instance.data["item"] - context = instance.context - - self.log.debug("creating staging dir") - self.staging_dir(instance) - - staging_dir = instance.data["stagingDir"] + # get sequence + sequence = instance.context.data["activeSequence"] + subset = instance.data["subset"] + # get timeline in / out + clip_in = instance.data["clipIn"] + clip_out = instance.data["clipOut"] # get handles from context handle_start = instance.data["handleStart"] handle_end = instance.data["handleEnd"] - # get sequence from context - sequence = context.data["activeSequence"] + staging_dir = self.staging_dir(instance) + self.log.info("Created staging dir: {}...".format(staging_dir)) # path to wav file audio_file = os.path.join( - staging_dir, "{0}.wav".format(instance.data["subset"]) + staging_dir, "{}.wav".format(subset) ) # export audio to disk writeSequenceAudioWithHandles( audio_file, sequence, - item.timelineIn(), - item.timelineOut(), + clip_in, + clip_out, handle_start, handle_end ) diff --git a/pype/hosts/hiero/plugins/publish/extract_clip_effects.py b/pype/hosts/hiero/plugins/publish/extract_clip_effects.py new file mode 100644 index 00000000000..308b33ff3ac --- /dev/null +++ b/pype/hosts/hiero/plugins/publish/extract_clip_effects.py @@ -0,0 +1,100 @@ +# from pype import plugins +import os +import json +import pyblish.api +import pype + + +class ExtractClipEffects(pype.api.Extractor): + """Extract clip effects instances.""" + + order = pyblish.api.ExtractorOrder + label = "Export Clip Effects" + families = ["effect"] + + def process(self, instance): + item = instance.data["item"] + effects = instance.data.get("effects") + + # skip any without effects + if not effects: + return + + subset = instance.data.get("subset") + family = instance.data["family"] + + self.log.debug("creating staging dir") + staging_dir = self.staging_dir(instance) + + transfers = list() + if "transfers" not in instance.data: + instance.data["transfers"] = list() + + ext = "json" + file = subset + "." + ext + + # when instance is created during collection part + resources_dir = instance.data["resourcesDir"] + + # change paths in effects to files + for k, effect in effects.items(): + if "assignTo" in k: + continue + trn = self.copy_linked_files(effect, resources_dir) + if trn: + transfers.append((trn[0], trn[1])) + + instance.data["transfers"].extend(transfers) + self.log.debug("_ transfers: `{}`".format( + instance.data["transfers"])) + + # create representations + instance.data["representations"] = list() + + transfer_data = [ + "handleStart", "handleEnd", "sourceIn", "sourceOut", + "frameStart", "frameEnd", "sourceInH", "sourceOutH", + "clipIn", "clipOut", "clipInH", "clipOutH", "asset", "track", + "version" + ] + + # pass data to version + version_data = dict() + version_data.update({k: instance.data[k] for k in transfer_data}) + + # add to data of representation + version_data.update({ + "colorspace": item.sourceMediaColourTransform(), + "colorspaceScript": instance.context.data["colorspace"], + "families": [family, "plate"], + "subset": subset, + "fps": instance.context.data["fps"] + }) + instance.data["versionData"] = version_data + + representation = { + 'files': file, + 'stagingDir': staging_dir, + 'name': family + ext.title(), + 'ext': ext + } + instance.data["representations"].append(representation) + + self.log.debug("_ representations: `{}`".format( + instance.data["representations"])) + + self.log.debug("_ version_data: `{}`".format( + instance.data["versionData"])) + + with open(os.path.join(staging_dir, file), "w") as outfile: + outfile.write(json.dumps(effects, indent=4, sort_keys=True)) + + def copy_linked_files(self, effect, dst_dir): + for k, v in effect["node"].items(): + if k in "file" and v != '': + base_name = os.path.basename(v) + dst = os.path.join(dst_dir, base_name).replace("\\", "/") + + # add it to the json + effect["node"][k] = dst + return (v, dst) diff --git a/pype/plugins/hiero/publish/extract_review_cutup.py b/pype/hosts/hiero/plugins/publish/extract_review_preparation.py similarity index 92% rename from pype/plugins/hiero/publish/extract_review_cutup.py rename to pype/hosts/hiero/plugins/publish/extract_review_preparation.py index 32e43d502e6..bc4a895ba4f 100644 --- a/pype/plugins/hiero/publish/extract_review_cutup.py +++ b/pype/hosts/hiero/plugins/publish/extract_review_preparation.py @@ -8,12 +8,11 @@ from avalon.vendor import filelink -class ExtractReviewCutUp(pype.api.Extractor): +class ExtractReviewPreparation(pype.api.Extractor): """Cut up clips from long video file""" order = api.ExtractorOrder - # order = api.CollectorOrder + 0.1023 - label = "Extract Review CutUp" + label = "Extract Review Preparation" hosts = ["hiero"] families = ["review"] @@ -22,22 +21,18 @@ class ExtractReviewCutUp(pype.api.Extractor): def process(self, instance): inst_data = instance.data - asset = inst_data['asset'] - item = inst_data['item'] - event_number = int(item.eventNumber()) + asset = inst_data["asset"] + review_item_data = instance.data.get("reviewItemData") # get representation and loop them representations = inst_data["representations"] - # check if sequence - is_sequence = inst_data["isSequence"] - # get resolution default resolution_width = inst_data["resolutionWidth"] resolution_height = inst_data["resolutionHeight"] # frame range data - media_duration = inst_data["mediaDuration"] + media_duration = review_item_data["mediaDuration"] ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg") ffprobe_path = pype.lib.get_ffmpeg_tool_path("ffprobe") @@ -52,7 +47,7 @@ def process(self, instance): # check if supported tags are in representation for activation filter_tag = False - for tag in ["_cut-bigger", "_cut-smaller"]: + for tag in ["_cut-bigger", "prep"]: if tag in tags: filter_tag = True break @@ -70,7 +65,7 @@ def process(self, instance): full_output_dir = os.path.join( staging_dir, "cuts") - if is_sequence: + if isinstance(files, list): new_files = list() # frame range delivery included handles @@ -99,12 +94,7 @@ def process(self, instance): index = 0 for image in collection: dst_file_num = frame_start + index - dst_file_name = "".join([ - str(event_number), - head, - str(padding % dst_file_num), - tail - ]) + dst_file_name = head + str(padding % dst_file_num) + tail src = os.path.join(staging_dir, image) dst = os.path.join(full_output_dir, dst_file_name) self.log.info("Creating temp hardlinks: {}".format(dst)) @@ -142,7 +132,7 @@ def process(self, instance): ).format(**locals()) self.log.debug("ffprob_cmd: {}".format(ffprob_cmd)) - audio_check_output = pype.api.run_subprocess(ffprob_cmd) + audio_check_output = pype.api.subprocess(ffprob_cmd) self.log.debug( "audio_check_output: {}".format(audio_check_output)) @@ -177,7 +167,7 @@ def process(self, instance): # try to get video native resolution data try: - resolution_output = pype.api.run_subprocess(( + resolution_output = pype.api.subprocess(( "\"{ffprobe_path}\" -i \"{full_input_path}\"" " -v error " "-select_streams v:0 -show_entries " @@ -290,7 +280,8 @@ def process(self, instance): # run subprocess self.log.debug("Executing: {}".format(subprcs_cmd)) - pype.api.run_subprocess(subprcs_cmd, logger=self.log) + output = pype.api.subprocess(subprcs_cmd) + self.log.debug("Output: {}".format(output)) repre_new = { "files": new_files, @@ -302,7 +293,8 @@ def process(self, instance): "step": 1, "fps": fps, "name": "cut_up_preview", - "tags": ["review"] + self.tags_addition, + "tags": [ + "review", "ftrackreview", "delete"] + self.tags_addition, "ext": ext, "anatomy_template": "publish" } diff --git a/pype/hosts/hiero/plugins/publish/precollect_clip_effects.py b/pype/hosts/hiero/plugins/publish/precollect_clip_effects.py new file mode 100644 index 00000000000..f9bde24255f --- /dev/null +++ b/pype/hosts/hiero/plugins/publish/precollect_clip_effects.py @@ -0,0 +1,164 @@ +import re +import pyblish.api + + +class PreCollectClipEffects(pyblish.api.InstancePlugin): + """Collect soft effects instances.""" + + order = pyblish.api.CollectorOrder - 0.508 + label = "Pre-collect Clip Effects Instances" + families = ["clip"] + + def process(self, instance): + family = "effect" + effects = {} + review = instance.data.get("review") + review_track_index = instance.context.data.get("reviewTrackIndex") + item = instance.data["item"] + + # frame range + self.handle_start = instance.data["handleStart"] + self.handle_end = instance.data["handleEnd"] + self.clip_in = int(item.timelineIn()) + self.clip_out = int(item.timelineOut()) + self.clip_in_h = self.clip_in - self.handle_start + self.clip_out_h = self.clip_out + self.handle_end + + track = instance.data["trackItem"] + track_index = track.trackIndex() + tracks_effect_items = instance.context.data.get("tracksEffectItems") + clip_effect_items = instance.data.get("clipEffectItems") + + # add clips effects to track's: + if clip_effect_items: + tracks_effect_items[track_index] = clip_effect_items + + # process all effects and devide them to instance + for _track_index, sub_track_items in tracks_effect_items.items(): + # skip if track index is the same as review track index + if review and review_track_index == _track_index: + continue + for sitem in sub_track_items: + if not (track_index <= _track_index): + continue + + effect = self.add_effect(_track_index, sitem) + + if effect: + effects.update(effect) + + # skip any without effects + if not effects: + return + + subset = instance.data.get("subset") + effects.update({"assignTo": subset}) + + subset_split = re.findall(r'[A-Z][^A-Z]*', subset) + + if len(subset_split) > 0: + root_name = subset.replace(subset_split[0], "") + subset_split.insert(0, root_name.capitalize()) + + subset_split.insert(0, "effect") + + name = "".join(subset_split) + + # create new instance and inherit data + data = {} + for key, value in instance.data.items(): + if "clipEffectItems" in key: + continue + data[key] = value + + # change names + data["subset"] = name + data["family"] = family + data["families"] = [family] + data["name"] = data["subset"] + "_" + data["asset"] + data["label"] = "{} - {}".format( + data['asset'], data["subset"] + ) + data["effects"] = effects + + # create new instance + _instance = instance.context.create_instance(**data) + self.log.info("Created instance `{}`".format(_instance)) + self.log.debug("instance.data `{}`".format(_instance.data)) + + def test_overlap(self, effect_t_in, effect_t_out): + covering_exp = bool( + (effect_t_in <= self.clip_in) + and (effect_t_out >= self.clip_out) + ) + overlaying_right_exp = bool( + (effect_t_in < self.clip_out) + and (effect_t_out >= self.clip_out) + ) + overlaying_left_exp = bool( + (effect_t_out > self.clip_in) + and (effect_t_in <= self.clip_in) + ) + + return any(( + covering_exp, + overlaying_right_exp, + overlaying_left_exp + )) + + def add_effect(self, track_index, sitem): + track = sitem.parentTrack().name() + # node serialization + node = sitem.node() + node_serialized = self.node_serialisation(node) + node_name = sitem.name() + node_class = re.sub(r"\d+", "", node_name) + # collect timelineIn/Out + effect_t_in = int(sitem.timelineIn()) + effect_t_out = int(sitem.timelineOut()) + + if not self.test_overlap(effect_t_in, effect_t_out): + return + + self.log.debug("node_name: `{}`".format(node_name)) + + return {node_name: { + "class": node_class, + "timelineIn": effect_t_in, + "timelineOut": effect_t_out, + "subTrackIndex": sitem.subTrackIndex(), + "trackIndex": track_index, + "track": track, + "node": node_serialized + }} + + def node_serialisation(self, node): + node_serialized = {} + + # adding ignoring knob keys + _ignoring_keys = ['invert_mask', 'help', 'mask', + 'xpos', 'ypos', 'layer', 'process_mask', 'channel', + 'channels', 'maskChannelMask', 'maskChannelInput', + 'note_font', 'note_font_size', 'unpremult', + 'postage_stamp_frame', 'maskChannel', 'export_cc', + 'select_cccid', 'mix', 'version', 'matrix'] + + # loop trough all knobs and collect not ignored + # and any with any value + for knob in node.knobs().keys(): + # skip nodes in ignore keys + if knob in _ignoring_keys: + continue + + # get animation if node is animated + if node[knob].isAnimated(): + # grab animation including handles + knob_anim = [node[knob].getValueAt(i) + for i in range( + self.clip_in_h, self.clip_in_h + 1)] + + node_serialized[knob] = knob_anim + else: + node_serialized[knob] = node[knob].value() + + return node_serialized diff --git a/pype/hosts/hiero/plugins/publish/precollect_instances.py b/pype/hosts/hiero/plugins/publish/precollect_instances.py new file mode 100644 index 00000000000..2ca637cf571 --- /dev/null +++ b/pype/hosts/hiero/plugins/publish/precollect_instances.py @@ -0,0 +1,221 @@ +from compiler.ast import flatten +from pyblish import api +from pype.hosts.hiero import api as phiero +import hiero +# from pype.hosts.hiero.api import lib +# reload(lib) +# reload(phiero) + + +class PreCollectInstances(api.ContextPlugin): + """Collect all Track items selection.""" + + order = api.CollectorOrder - 0.509 + label = "Pre-collect Instances" + hosts = ["hiero"] + + def process(self, context): + track_items = phiero.get_track_items( + selected=True, check_tagged=True, check_enabled=True) + # only return enabled track items + if not track_items: + track_items = phiero.get_track_items( + check_enabled=True, check_tagged=True) + # get sequence and video tracks + sequence = context.data["activeSequence"] + tracks = sequence.videoTracks() + + # add collection to context + tracks_effect_items = self.collect_sub_track_items(tracks) + + context.data["tracksEffectItems"] = tracks_effect_items + + self.log.info( + "Processing enabled track items: {}".format(len(track_items))) + + for _ti in track_items: + data = dict() + clip = _ti.source() + + # get clips subtracks and anotations + annotations = self.clip_annotations(clip) + subtracks = self.clip_subtrack(_ti) + self.log.debug("Annotations: {}".format(annotations)) + self.log.debug(">> Subtracks: {}".format(subtracks)) + + # get pype tag data + tag_parsed_data = phiero.get_track_item_pype_data(_ti) + # self.log.debug(pformat(tag_parsed_data)) + + if not tag_parsed_data: + continue + + if tag_parsed_data.get("id") != "pyblish.avalon.instance": + continue + # add tag data to instance data + data.update({ + k: v for k, v in tag_parsed_data.items() + if k not in ("id", "applieswhole", "label") + }) + + asset = tag_parsed_data["asset"] + subset = tag_parsed_data["subset"] + review = tag_parsed_data.get("review") + audio = tag_parsed_data.get("audio") + + # remove audio attribute from data + data.pop("audio") + + # insert family into families + family = tag_parsed_data["family"] + families = [str(f) for f in tag_parsed_data["families"]] + families.insert(0, str(family)) + + track = _ti.parent() + media_source = _ti.source().mediaSource() + source_path = media_source.firstpath() + file_head = media_source.filenameHead() + file_info = media_source.fileinfos().pop() + source_first_frame = int(file_info.startFrame()) + + # apply only for feview and master track instance + if review: + families += ["review", "ftrack"] + + data.update({ + "name": "{} {} {}".format(asset, subset, families), + "asset": asset, + "item": _ti, + "families": families, + + # tags + "tags": _ti.tags(), + + # track item attributes + "track": track.name(), + "trackItem": track, + + # version data + "versionData": { + "colorspace": _ti.sourceMediaColourTransform() + }, + + # source attribute + "source": source_path, + "sourceMedia": media_source, + "sourcePath": source_path, + "sourceFileHead": file_head, + "sourceFirst": source_first_frame, + + # clip's effect + "clipEffectItems": subtracks + }) + + instance = context.create_instance(**data) + + self.log.info("Creating instance: {}".format(instance)) + + if audio: + a_data = dict() + + # add tag data to instance data + a_data.update({ + k: v for k, v in tag_parsed_data.items() + if k not in ("id", "applieswhole", "label") + }) + + # create main attributes + subset = "audioMain" + family = "audio" + families = ["clip", "ftrack"] + families.insert(0, str(family)) + + name = "{} {} {}".format(asset, subset, families) + + a_data.update({ + "name": name, + "subset": subset, + "asset": asset, + "family": family, + "families": families, + "item": _ti, + + # tags + "tags": _ti.tags(), + }) + + a_instance = context.create_instance(**a_data) + self.log.info("Creating audio instance: {}".format(a_instance)) + + @staticmethod + def clip_annotations(clip): + """ + Returns list of Clip's hiero.core.Annotation + """ + annotations = [] + subTrackItems = flatten(clip.subTrackItems()) + annotations += [item for item in subTrackItems if isinstance( + item, hiero.core.Annotation)] + return annotations + + @staticmethod + def clip_subtrack(clip): + """ + Returns list of Clip's hiero.core.SubTrackItem + """ + subtracks = [] + subTrackItems = flatten(clip.parent().subTrackItems()) + for item in subTrackItems: + # avoid all anotation + if isinstance(item, hiero.core.Annotation): + continue + # # avoid all not anaibled + if not item.isEnabled(): + continue + subtracks.append(item) + return subtracks + + @staticmethod + def collect_sub_track_items(tracks): + """ + Returns dictionary with track index as key and list of subtracks + """ + # collect all subtrack items + sub_track_items = dict() + for track in tracks: + items = track.items() + + # skip if no clips on track > need track with effect only + if items: + continue + + # skip all disabled tracks + if not track.isEnabled(): + continue + + track_index = track.trackIndex() + _sub_track_items = flatten(track.subTrackItems()) + + # continue only if any subtrack items are collected + if len(_sub_track_items) < 1: + continue + + enabled_sti = list() + # loop all found subtrack items and check if they are enabled + for _sti in _sub_track_items: + # checking if not enabled + if not _sti.isEnabled(): + continue + if isinstance(_sti, hiero.core.Annotation): + continue + # collect the subtrack item + enabled_sti.append(_sti) + + # continue only if any subtrack items are collected + if len(enabled_sti) < 1: + continue + + # add collection of subtrackitems to dict + sub_track_items[track_index] = enabled_sti + + return sub_track_items diff --git a/pype/hosts/hiero/plugins/publish/precollect_workfile.py b/pype/hosts/hiero/plugins/publish/precollect_workfile.py new file mode 100644 index 00000000000..1b223719436 --- /dev/null +++ b/pype/hosts/hiero/plugins/publish/precollect_workfile.py @@ -0,0 +1,74 @@ +import os +import pyblish.api +from pype.hosts.hiero import api as phiero +from avalon import api as avalon + + +class PreCollectWorkfile(pyblish.api.ContextPlugin): + """Inject the current working file into context""" + + label = "Pre-collect Workfile" + order = pyblish.api.CollectorOrder - 0.51 + + def process(self, context): + asset = avalon.Session["AVALON_ASSET"] + subset = "workfile" + + project = phiero.get_current_project() + active_sequence = phiero.get_current_sequence() + video_tracks = active_sequence.videoTracks() + audio_tracks = active_sequence.audioTracks() + current_file = project.path() + staging_dir = os.path.dirname(current_file) + base_name = os.path.basename(current_file) + + # get workfile's colorspace properties + _clrs = {} + _clrs["useOCIOEnvironmentOverride"] = project.useOCIOEnvironmentOverride() # noqa + _clrs["lutSetting16Bit"] = project.lutSetting16Bit() + _clrs["lutSetting8Bit"] = project.lutSetting8Bit() + _clrs["lutSettingFloat"] = project.lutSettingFloat() + _clrs["lutSettingLog"] = project.lutSettingLog() + _clrs["lutSettingViewer"] = project.lutSettingViewer() + _clrs["lutSettingWorkingSpace"] = project.lutSettingWorkingSpace() + _clrs["lutUseOCIOForExport"] = project.lutUseOCIOForExport() + _clrs["ocioConfigName"] = project.ocioConfigName() + _clrs["ocioConfigPath"] = project.ocioConfigPath() + + # set main project attributes to context + context.data["activeProject"] = project + context.data["activeSequence"] = active_sequence + context.data["videoTracks"] = video_tracks + context.data["audioTracks"] = audio_tracks + context.data["currentFile"] = current_file + context.data["colorspace"] = _clrs + + self.log.info("currentFile: {}".format(current_file)) + + # creating workfile representation + representation = { + 'name': 'hrox', + 'ext': 'hrox', + 'files': base_name, + "stagingDir": staging_dir, + } + + instance_data = { + "name": "{}_{}".format(asset, subset), + "asset": asset, + "subset": "{}{}".format(asset, subset.capitalize()), + "item": project, + "family": "workfile", + + # version data + "versionData": { + "colorspace": _clrs + }, + + # source attribute + "sourcePath": current_file, + "representations": [representation] + } + + instance = context.create_instance(**instance_data) + self.log.info("Creating instance: {}".format(instance)) diff --git a/pype/hosts/hiero/plugins/publish/validate_audio.py b/pype/hosts/hiero/plugins/publish/validate_audio.py new file mode 100644 index 00000000000..5decc86304b --- /dev/null +++ b/pype/hosts/hiero/plugins/publish/validate_audio.py @@ -0,0 +1,25 @@ +import pyblish +from pype.hosts.hiero.api import is_overlapping + + +class ValidateAudioFile(pyblish.api.InstancePlugin): + """Validate audio subset has avilable audio track clips""" + + order = pyblish.api.ValidatorOrder + label = "Validate Audio Tracks" + hosts = ["hiero"] + families = ["audio"] + + def process(self, instance): + clip = instance.data["item"] + audio_tracks = instance.context.data["audioTracks"] + audio_clip = None + + for a_track in audio_tracks: + for item in a_track.items(): + if is_overlapping(item, clip): + audio_clip = item + + assert audio_clip, "Missing relative audio clip for clip {}".format( + clip.name() + ) diff --git a/pype/plugins/hiero/publish/validate_hierarchy.py b/pype/hosts/hiero/plugins/publish/validate_hierarchy.py similarity index 100% rename from pype/plugins/hiero/publish/validate_hierarchy.py rename to pype/hosts/hiero/plugins/publish/validate_hierarchy.py diff --git a/pype/plugins/hiero/publish/validate_names.py b/pype/hosts/hiero/plugins/publish/validate_names.py similarity index 100% rename from pype/plugins/hiero/publish/validate_names.py rename to pype/hosts/hiero/plugins/publish/validate_names.py diff --git a/pype/plugins/hiero/publish/version_up_workfile.py b/pype/hosts/hiero/plugins/publish/version_up_workfile.py similarity index 100% rename from pype/plugins/hiero/publish/version_up_workfile.py rename to pype/hosts/hiero/plugins/publish/version_up_workfile.py diff --git a/pype/setup/hiero/hiero_plugin_path/HieroPlayer/PlayerPresets.hrox b/pype/hosts/hiero/startup/HieroPlayer/PlayerPresets.hrox similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/HieroPlayer/PlayerPresets.hrox rename to pype/hosts/hiero/startup/HieroPlayer/PlayerPresets.hrox diff --git a/pype/setup/hiero/hiero_plugin_path/Icons/1_add_handles_end.png b/pype/hosts/hiero/startup/Icons/1_add_handles_end.png similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/Icons/1_add_handles_end.png rename to pype/hosts/hiero/startup/Icons/1_add_handles_end.png diff --git a/pype/setup/hiero/hiero_plugin_path/Icons/2_add_handles.png b/pype/hosts/hiero/startup/Icons/2_add_handles.png similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/Icons/2_add_handles.png rename to pype/hosts/hiero/startup/Icons/2_add_handles.png diff --git a/pype/setup/hiero/hiero_plugin_path/Icons/3D.png b/pype/hosts/hiero/startup/Icons/3D.png similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/Icons/3D.png rename to pype/hosts/hiero/startup/Icons/3D.png diff --git a/pype/setup/hiero/hiero_plugin_path/Icons/3_add_handles_start.png b/pype/hosts/hiero/startup/Icons/3_add_handles_start.png similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/Icons/3_add_handles_start.png rename to pype/hosts/hiero/startup/Icons/3_add_handles_start.png diff --git a/pype/setup/hiero/hiero_plugin_path/Icons/4_2D.png b/pype/hosts/hiero/startup/Icons/4_2D.png similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/Icons/4_2D.png rename to pype/hosts/hiero/startup/Icons/4_2D.png diff --git a/pype/setup/hiero/hiero_plugin_path/Icons/edit.png b/pype/hosts/hiero/startup/Icons/edit.png similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/Icons/edit.png rename to pype/hosts/hiero/startup/Icons/edit.png diff --git a/pype/setup/hiero/hiero_plugin_path/Icons/fusion.png b/pype/hosts/hiero/startup/Icons/fusion.png similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/Icons/fusion.png rename to pype/hosts/hiero/startup/Icons/fusion.png diff --git a/pype/setup/hiero/hiero_plugin_path/Icons/hierarchy.png b/pype/hosts/hiero/startup/Icons/hierarchy.png similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/Icons/hierarchy.png rename to pype/hosts/hiero/startup/Icons/hierarchy.png diff --git a/pype/setup/hiero/hiero_plugin_path/Icons/houdini.png b/pype/hosts/hiero/startup/Icons/houdini.png similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/Icons/houdini.png rename to pype/hosts/hiero/startup/Icons/houdini.png diff --git a/pype/setup/hiero/hiero_plugin_path/Icons/layers.psd b/pype/hosts/hiero/startup/Icons/layers.psd similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/Icons/layers.psd rename to pype/hosts/hiero/startup/Icons/layers.psd diff --git a/pype/setup/hiero/hiero_plugin_path/Icons/lense.png b/pype/hosts/hiero/startup/Icons/lense.png similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/Icons/lense.png rename to pype/hosts/hiero/startup/Icons/lense.png diff --git a/pype/setup/hiero/hiero_plugin_path/Icons/lense1.png b/pype/hosts/hiero/startup/Icons/lense1.png similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/Icons/lense1.png rename to pype/hosts/hiero/startup/Icons/lense1.png diff --git a/pype/setup/hiero/hiero_plugin_path/Icons/maya.png b/pype/hosts/hiero/startup/Icons/maya.png similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/Icons/maya.png rename to pype/hosts/hiero/startup/Icons/maya.png diff --git a/pype/setup/hiero/hiero_plugin_path/Icons/nuke.png b/pype/hosts/hiero/startup/Icons/nuke.png similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/Icons/nuke.png rename to pype/hosts/hiero/startup/Icons/nuke.png diff --git a/pype/hosts/hiero/startup/Icons/pype_icon.png b/pype/hosts/hiero/startup/Icons/pype_icon.png new file mode 100644 index 00000000000..bfacf6eeedc Binary files /dev/null and b/pype/hosts/hiero/startup/Icons/pype_icon.png differ diff --git a/pype/setup/hiero/hiero_plugin_path/Icons/resolution.png b/pype/hosts/hiero/startup/Icons/resolution.png similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/Icons/resolution.png rename to pype/hosts/hiero/startup/Icons/resolution.png diff --git a/pype/setup/hiero/hiero_plugin_path/Icons/resolution.psd b/pype/hosts/hiero/startup/Icons/resolution.psd similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/Icons/resolution.psd rename to pype/hosts/hiero/startup/Icons/resolution.psd diff --git a/pype/setup/hiero/hiero_plugin_path/Icons/retiming.png b/pype/hosts/hiero/startup/Icons/retiming.png similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/Icons/retiming.png rename to pype/hosts/hiero/startup/Icons/retiming.png diff --git a/pype/setup/hiero/hiero_plugin_path/Icons/retiming.psd b/pype/hosts/hiero/startup/Icons/retiming.psd similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/Icons/retiming.psd rename to pype/hosts/hiero/startup/Icons/retiming.psd diff --git a/pype/setup/hiero/hiero_plugin_path/Icons/review.png b/pype/hosts/hiero/startup/Icons/review.png similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/Icons/review.png rename to pype/hosts/hiero/startup/Icons/review.png diff --git a/pype/setup/hiero/hiero_plugin_path/Icons/review.psd b/pype/hosts/hiero/startup/Icons/review.psd similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/Icons/review.psd rename to pype/hosts/hiero/startup/Icons/review.psd diff --git a/pype/setup/hiero/hiero_plugin_path/Icons/volume.png b/pype/hosts/hiero/startup/Icons/volume.png similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/Icons/volume.png rename to pype/hosts/hiero/startup/Icons/volume.png diff --git a/pype/setup/hiero/hiero_plugin_path/Icons/z_layer_bg.png b/pype/hosts/hiero/startup/Icons/z_layer_bg.png similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/Icons/z_layer_bg.png rename to pype/hosts/hiero/startup/Icons/z_layer_bg.png diff --git a/pype/setup/hiero/hiero_plugin_path/Icons/z_layer_fg.png b/pype/hosts/hiero/startup/Icons/z_layer_fg.png similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/Icons/z_layer_fg.png rename to pype/hosts/hiero/startup/Icons/z_layer_fg.png diff --git a/pype/setup/hiero/hiero_plugin_path/Icons/z_layer_main.png b/pype/hosts/hiero/startup/Icons/z_layer_main.png similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/Icons/z_layer_main.png rename to pype/hosts/hiero/startup/Icons/z_layer_main.png diff --git a/pype/setup/hiero/hiero_plugin_path/Python/Startup/SpreadsheetExport.py b/pype/hosts/hiero/startup/Python/Startup/SpreadsheetExport.py similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/Python/Startup/SpreadsheetExport.py rename to pype/hosts/hiero/startup/Python/Startup/SpreadsheetExport.py diff --git a/pype/setup/hiero/hiero_plugin_path/Python/Startup/Startup.py b/pype/hosts/hiero/startup/Python/Startup/Startup.py similarity index 59% rename from pype/setup/hiero/hiero_plugin_path/Python/Startup/Startup.py rename to pype/hosts/hiero/startup/Python/Startup/Startup.py index cdf85c524f9..94b2d53f9f6 100644 --- a/pype/setup/hiero/hiero_plugin_path/Python/Startup/Startup.py +++ b/pype/hosts/hiero/startup/Python/Startup/Startup.py @@ -2,11 +2,11 @@ # activate hiero from pype import avalon.api -import pype.hosts.hiero -avalon.api.install(pype.hosts.hiero) +import pype.hosts.hiero.api as phiero +avalon.api.install(phiero) try: - __import__("pype.hosts.hiero") + __import__("pype.hosts.hiero.api") __import__("pyblish") except ImportError as e: @@ -15,5 +15,5 @@ else: # Setup integration - import pype.hosts.hiero.lib - pype.hosts.hiero.lib.setup() + import pype.hosts.hiero.api as phiero + phiero.lib.setup() diff --git a/pype/setup/hiero/hiero_plugin_path/Python/Startup/otioexporter/OTIOExportTask.py b/pype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportTask.py similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/Python/Startup/otioexporter/OTIOExportTask.py rename to pype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportTask.py diff --git a/pype/setup/hiero/hiero_plugin_path/Python/Startup/otioexporter/OTIOExportUI.py b/pype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportUI.py similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/Python/Startup/otioexporter/OTIOExportUI.py rename to pype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportUI.py diff --git a/pype/setup/hiero/hiero_plugin_path/Python/Startup/otioexporter/__init__.py b/pype/hosts/hiero/startup/Python/Startup/otioexporter/__init__.py similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/Python/Startup/otioexporter/__init__.py rename to pype/hosts/hiero/startup/Python/Startup/otioexporter/__init__.py diff --git a/pype/setup/hiero/hiero_plugin_path/Python/Startup/project_helpers.py b/pype/hosts/hiero/startup/Python/Startup/project_helpers.py similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/Python/Startup/project_helpers.py rename to pype/hosts/hiero/startup/Python/Startup/project_helpers.py diff --git a/pype/setup/hiero/hiero_plugin_path/Python/Startup/selection_tracker.py b/pype/hosts/hiero/startup/Python/Startup/selection_tracker.py similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/Python/Startup/selection_tracker.py rename to pype/hosts/hiero/startup/Python/Startup/selection_tracker.py diff --git a/pype/setup/hiero/hiero_plugin_path/Python/Startup/setFrameRate.py b/pype/hosts/hiero/startup/Python/Startup/setFrameRate.py similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/Python/Startup/setFrameRate.py rename to pype/hosts/hiero/startup/Python/Startup/setFrameRate.py diff --git a/pype/setup/hiero/hiero_plugin_path/Python/Startup/version_everywhere.py b/pype/hosts/hiero/startup/Python/Startup/version_everywhere.py similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/Python/Startup/version_everywhere.py rename to pype/hosts/hiero/startup/Python/Startup/version_everywhere.py diff --git a/pype/setup/hiero/hiero_plugin_path/Python/StartupUI/PimpMySpreadsheet.py b/pype/hosts/hiero/startup/Python/StartupUI/PimpMySpreadsheet.py similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/Python/StartupUI/PimpMySpreadsheet.py rename to pype/hosts/hiero/startup/Python/StartupUI/PimpMySpreadsheet.py diff --git a/pype/setup/hiero/hiero_plugin_path/Python/StartupUI/Purge.py b/pype/hosts/hiero/startup/Python/StartupUI/Purge.py similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/Python/StartupUI/Purge.py rename to pype/hosts/hiero/startup/Python/StartupUI/Purge.py diff --git a/pype/setup/hiero/hiero_plugin_path/Python/StartupUI/nukeStyleKeyboardShortcuts.py b/pype/hosts/hiero/startup/Python/StartupUI/nukeStyleKeyboardShortcuts.py similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/Python/StartupUI/nukeStyleKeyboardShortcuts.py rename to pype/hosts/hiero/startup/Python/StartupUI/nukeStyleKeyboardShortcuts.py diff --git a/pype/setup/hiero/hiero_plugin_path/Python/StartupUI/otioimporter/OTIOImport.py b/pype/hosts/hiero/startup/Python/StartupUI/otioimporter/OTIOImport.py similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/Python/StartupUI/otioimporter/OTIOImport.py rename to pype/hosts/hiero/startup/Python/StartupUI/otioimporter/OTIOImport.py diff --git a/pype/setup/hiero/hiero_plugin_path/Python/StartupUI/otioimporter/__init__.py b/pype/hosts/hiero/startup/Python/StartupUI/otioimporter/__init__.py similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/Python/StartupUI/otioimporter/__init__.py rename to pype/hosts/hiero/startup/Python/StartupUI/otioimporter/__init__.py diff --git a/pype/setup/hiero/hiero_plugin_path/Python/StartupUI/setPosterFrame.py b/pype/hosts/hiero/startup/Python/StartupUI/setPosterFrame.py similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/Python/StartupUI/setPosterFrame.py rename to pype/hosts/hiero/startup/Python/StartupUI/setPosterFrame.py diff --git a/pype/setup/hiero/hiero_plugin_path/TaskPresets/10.5/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml b/pype/hosts/hiero/startup/TaskPresets/10.5/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/TaskPresets/10.5/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml rename to pype/hosts/hiero/startup/TaskPresets/10.5/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml diff --git a/pype/setup/hiero/hiero_plugin_path/TaskPresets/11.1/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml b/pype/hosts/hiero/startup/TaskPresets/11.1/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/TaskPresets/11.1/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml rename to pype/hosts/hiero/startup/TaskPresets/11.1/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml diff --git a/pype/setup/hiero/hiero_plugin_path/TaskPresets/11.2/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml b/pype/hosts/hiero/startup/TaskPresets/11.2/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml similarity index 100% rename from pype/setup/hiero/hiero_plugin_path/TaskPresets/11.2/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml rename to pype/hosts/hiero/startup/TaskPresets/11.2/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml diff --git a/pype/hosts/hiero/tags.json b/pype/hosts/hiero/tags.json deleted file mode 100644 index 9c95fd629c1..00000000000 --- a/pype/hosts/hiero/tags.json +++ /dev/null @@ -1,261 +0,0 @@ -{ - "Hierarchy": { - "editable": "1", - "note": "{folder}/{sequence}/{shot}", - "icon": { - "path": "hierarchy.png" - }, - "metadata": { - "folder": "FOLDER_NAME", - "shot": "{clip}", - "track": "{track}", - "sequence": "{sequence}", - "episode": "EPISODE_NAME", - "root": "{projectroot}" - } - }, - "Source Resolution": { - "editable": "1", - "note": "Use source resolution", - "icon": { - "path": "resolution.png" - }, - "metadata": { - "family": "resolution" - } - }, - "Retiming": { - "editable": "1", - "note": "Clip has retime or TimeWarp effects (or multiple effects stacked on the clip)", - "icon": { - "path": "retiming.png" - }, - "metadata": { - "family": "retiming", - "marginIn": 1, - "marginOut": 1 - } - }, - "Frame start": { - "editable": "1", - "note": "Starting frame for comps. \n\n> Use `value` and add either number or write `source` (if you want to preserve source frame numbering)", - "icon": { - "path": "icons:TagBackground.png" - }, - "metadata": { - "family": "frameStart", - "value": "1001" - } - }, - "[Lenses]": { - "Set lense here": { - "editable": "1", - "note": "Adjust parameters of your lense and then drop to clip. Remember! You can always overwrite on clip", - "icon": { - "path": "lense.png" - }, - "metadata": { - "focalLengthMm": 57 - } - } - }, - "[Subsets]": { - "Audio": { - "editable": "1", - "note": "Export with Audio", - "icon": { - "path": "volume.png" - }, - "metadata": { - "family": "audio", - "subset": "main" - } - }, - "plateFg": { - "editable": "1", - "note": "Add to publish to \"forground\" subset. Change metadata subset name if different order number", - "icon": { - "path": "z_layer_fg.png" - }, - "metadata": { - "family": "plate", - "subset": "Fg01" - } - }, - "plateBg": { - "editable": "1", - "note": "Add to publish to \"background\" subset. Change metadata subset name if different order number", - "icon": { - "path": "z_layer_bg.png" - }, - "metadata": { - "family": "plate", - "subset": "Bg01" - } - }, - "plateRef": { - "editable": "1", - "note": "Add to publish to \"reference\" subset.", - "icon": { - "path": "icons:Reference.png" - }, - "metadata": { - "family": "plate", - "subset": "Ref" - } - }, - "plateMain": { - "editable": "1", - "note": "Add to publish to \"main\" subset.", - "icon": { - "path": "z_layer_main.png" - }, - "metadata": { - "family": "plate", - "subset": "main" - } - }, - "plateProxy": { - "editable": "1", - "note": "Add to publish to \"proxy\" subset.", - "icon": { - "path": "z_layer_main.png" - }, - "metadata": { - "family": "plate", - "subset": "proxy" - } - }, - "review": { - "editable": "1", - "note": "Upload to Ftrack as review component.", - "icon": { - "path": "review.png" - }, - "metadata": { - "family": "review", - "track": "review" - } - } - }, - "[Handles]": { - "start: add 20 frames": { - "editable": "1", - "note": "Adding frames to start of selected clip", - "icon": { - "path": "3_add_handles_start.png" - }, - "metadata": { - "family": "handles", - "value": "20", - "args": "{'op':'add','where':'start'}" - } - }, - "start: add 10 frames": { - "editable": "1", - "note": "Adding frames to start of selected clip", - "icon": { - "path": "3_add_handles_start.png" - }, - "metadata": { - "family": "handles", - "value": "10", - "args": "{'op':'add','where':'start'}" - } - }, - "start: add 5 frames": { - "editable": "1", - "note": "Adding frames to start of selected clip", - "icon": { - "path": "3_add_handles_start.png" - }, - "metadata": { - "family": "handles", - "value": "5", - "args": "{'op':'add','where':'start'}" - } - }, - "start: add 0 frames": { - "editable": "1", - "note": "Adding frames to start of selected clip", - "icon": { - "path": "3_add_handles_start.png" - }, - "metadata": { - "family": "handles", - "value": "0", - "args": "{'op':'add','where':'start'}" - } - }, - "end: add 20 frames": { - "editable": "1", - "note": "Adding frames to end of selected clip", - "icon": { - "path": "1_add_handles_end.png" - }, - "metadata": { - "family": "handles", - "value": "20", - "args": "{'op':'add','where':'end'}" - } - }, - "end: add 10 frames": { - "editable": "1", - "note": "Adding frames to end of selected clip", - "icon": { - "path": "1_add_handles_end.png" - }, - "metadata": { - "family": "handles", - "value": "10", - "args": "{'op':'add','where':'end'}" - } - }, - "end: add 5 frames": { - "editable": "1", - "note": "Adding frames to end of selected clip", - "icon": { - "path": "1_add_handles_end.png" - }, - "metadata": { - "family": "handles", - "value": "5", - "args": "{'op':'add','where':'end'}" - } - }, - "end: add 0 frames": { - "editable": "1", - "note": "Adding frames to end of selected clip", - "icon": { - "path": "1_add_handles_end.png" - }, - "metadata": { - "family": "handles", - "value": "0", - "args": "{'op':'add','where':'end'}" - } - } - }, - "NukeScript": { - "editable": "1", - "note": "Collecting track items to Nuke scripts.", - "icon": { - "path": "icons:TagNuke.png" - }, - "metadata": { - "family": "nukescript", - "subset": "main" - } - }, - "Comment": { - "editable": "1", - "note": "Comment on a shot.", - "icon": { - "path": "icons:TagComment.png" - }, - "metadata": { - "family": "comment", - "subset": "main" - } - } -} diff --git a/pype/plugins/hiero/_unused/collect_submission.py b/pype/plugins/hiero/_unused/collect_submission.py deleted file mode 100644 index cd2b8555242..00000000000 --- a/pype/plugins/hiero/_unused/collect_submission.py +++ /dev/null @@ -1,14 +0,0 @@ -import pyblish.api - - -class CollectSubmission(pyblish.api.ContextPlugin): - """Collect submisson children.""" - - order = pyblish.api.CollectorOrder - 0.1 - - def process(self, context): - import hiero - - if hasattr(hiero, "submission"): - context.data["submission"] = hiero.submission - self.log.debug("__ submission: {}".format(context.data["submission"])) diff --git a/pype/plugins/hiero/_unused/collect_workfile_version.py b/pype/plugins/hiero/_unused/collect_workfile_version.py deleted file mode 100644 index 733fbfc44a9..00000000000 --- a/pype/plugins/hiero/_unused/collect_workfile_version.py +++ /dev/null @@ -1,15 +0,0 @@ -import pyblish.api -import pype.api as pype - -class CollectWorkfileVersion(pyblish.api.ContextPlugin): - """Inject the current working file version into context""" - - order = pyblish.api.CollectorOrder - 0.1 - label = "Collect workfile version" - - def process(self, context): - - project = context.data('activeProject') - path = project.path() - context.data["version"] = int(pype.get_version_from_path(path)) - self.log.info("version: {}".format(context.data["version"])) diff --git a/pype/plugins/hiero/_unused/extract_plates_waiting.py b/pype/plugins/hiero/_unused/extract_plates_waiting.py deleted file mode 100644 index f385f74a3e6..00000000000 --- a/pype/plugins/hiero/_unused/extract_plates_waiting.py +++ /dev/null @@ -1,30 +0,0 @@ -from pyblish import api -import os -import time - - -class ExtractPlateCheck(api.ContextPlugin): - """Collect all Track items selection.""" - - order = api.ExtractorOrder + 0.01 - label = "Plates Export Waiting" - hosts = ["hiero"] - families = ["encode"] - - def process(self, context): - - plate_path = context.data.get("platesCheck", None) - - self.log.info("Chacking plate: `{}`".format(plate_path)) - - if not plate_path: - return - - while not os.path.exists(plate_path): - self.log.info("Waiting for plates to be rendered") - time.sleep(5) - - if os.path.isfile(plate_path): - self.log.info("Plates were rendered: `{}`".format(plate_path)) - else: - raise ValueError("%s isn't a file!" % plate_path) diff --git a/pype/plugins/hiero/_unused/extract_tasks.py b/pype/plugins/hiero/_unused/extract_tasks.py deleted file mode 100644 index 4f75728468e..00000000000 --- a/pype/plugins/hiero/_unused/extract_tasks.py +++ /dev/null @@ -1,124 +0,0 @@ -from pyblish import api - - -class ExtractTasks(api.InstancePlugin): - """Extract tasks.""" - - order = api.ExtractorOrder - label = "Tasks" - hosts = ["hiero"] - families = ["clip"] - optional = True - - def filelink(self, src, dst): - import filecmp - import os - import shutil - - import filelink - - # Compare files to check whether they are the same. - if os.path.exists(dst) and filecmp.cmp(src, dst): - return - - # Remove existing destination file. - if os.path.exists(dst): - os.remove(dst) - - try: - filelink.create(src, dst, filelink.HARDLINK) - self.log.debug("Linking: \"{0}\" to \"{1}\"".format(src, dst)) - except WindowsError as e: - if e.winerror == 17: - self.log.warning( - "File linking failed due to: \"{0}\". " - "Resorting to copying instead.".format(e) - ) - shutil.copy(src, dst) - else: - raise e - - def process(self, instance): - import time - import os - - import hiero.core.nuke as nuke - import hiero.exporters as he - import clique - - task = instance.data["task"] - - hiero_cls = he.FnSymLinkExporter.SymLinkExporter - if isinstance(task, hiero_cls): - src = os.path.join( - task.filepath(), - task.fileName() - ) - # Filelink each image file - if "img" in instance.data["families"]: - collection = clique.parse(src + " []") - for f in os.listdir(os.path.dirname(src)): - f = os.path.join(os.path.dirname(src), f) - - frame_offset = task.outputRange()[0] - task.inputRange()[0] - input_range = ( - int(task.inputRange()[0]), int(task.inputRange()[1]) + 1 - ) - for index in range(*input_range): - dst = task.resolvedExportPath() % (index + frame_offset) - self.filelink(src % index, dst) - # Filelink movie file - if "mov" in instance.data["families"]: - dst = task.resolvedExportPath() - self.filelink(src, dst) - - hiero_cls = he.FnTranscodeExporter.TranscodeExporter - if isinstance(task, hiero_cls): - task.startTask() - while task.taskStep(): - time.sleep(1) - - script_path = task._scriptfile - log_path = script_path.replace(".nk", ".log") - log_file = open(log_path, "w") - process = nuke.executeNukeScript(script_path, log_file, True) - - self.poll(process) - - log_file.close() - - if not task._preset.properties()["keepNukeScript"]: - os.remove(script_path) - os.remove(log_path) - - hiero_cls = he.FnNukeShotExporter.NukeShotExporter - if isinstance(task, hiero_cls): - task.startTask() - while task.taskStep(): - time.sleep(1) - - hiero_cls = he.FnAudioExportTask.AudioExportTask - if isinstance(task, hiero_cls): - task.startTask() - while task.taskStep(): - time.sleep(1) - - # Fill collection with output - if "img" in instance.data["families"]: - collection = instance.data["collection"] - path = os.path.dirname(collection.format()) - for f in os.listdir(path): - file_path = os.path.join(path, f).replace("\\", "/") - if collection.match(file_path): - collection.add(file_path) - - def poll(self, process): - import time - - returnCode = process.poll() - - # if the return code hasn't been set, Nuke is still running - if returnCode is None: - time.sleep(1) - - self.poll(process) diff --git a/pype/plugins/hiero/_unused/subset-representations_logic.txt b/pype/plugins/hiero/_unused/subset-representations_logic.txt deleted file mode 100644 index dcb78152765..00000000000 --- a/pype/plugins/hiero/_unused/subset-representations_logic.txt +++ /dev/null @@ -1,26 +0,0 @@ -- tags get tasks - -- collect_subset(instance): - - gets presets for subset by tasks - - creates instances for comp .nk, plates (main instance converted to plates) - - add families: - - .nk compositing script [workfile, ftrack] - - plates [plates] - - audio [audio] - -- extract_submit_frameserver(instance) - - families [plates] - - adds .nk script created only for encoding (plates write) no color correction - - adds .nk script created only for encoding (mov write) - - add .nk script created only for encoding (jpg, thumbnail) - - _______ - - from hiero.ui.nuke_bridge import FnNsFrameServer - - FnNsFrameServer.renderFrames(nks, "1-10", "Write_exr", ["main"]) - - dict(script(str), framerange(str), writeNode(str), views(list)) - -# next step ######################################################## -- submit_exporting_task(instance) - - families [workfile] - - create compositing scripts - - create inventory containers for Reads - - create publishable write nodes diff --git a/pype/plugins/hiero/_unused/validate_projectroot.py b/pype/plugins/hiero/_unused/validate_projectroot.py deleted file mode 100644 index 51e5082250f..00000000000 --- a/pype/plugins/hiero/_unused/validate_projectroot.py +++ /dev/null @@ -1,40 +0,0 @@ -from pyblish import api - - -class RepairProjectRoot(api.Action): - - label = "Repair" - icon = "wrench" - on = "failed" - - def process(self, context, plugin): - import os - - project_root = os.path.join( - os.path.dirname(context.data["currentFile"]) - ) - - context.data["activeProject"].setProjectRoot(project_root) - - -class ValidateProjectRoot(api.ContextPlugin): - """Validate the project root to the workspace directory.""" - - order = api.ValidatorOrder - label = "Project Root" - hosts = ["hiero"] - actions = [RepairProjectRoot] - - def process(self, context): - import os - - workspace = os.path.join( - os.path.dirname(context.data["currentFile"]) - ) - project_root = context.data["activeProject"].projectRoot() - - failure_message = ( - 'The project root needs to be "{0}", its currently: "{1}"' - ).format(workspace, project_root) - - assert project_root == workspace, failure_message diff --git a/pype/plugins/hiero/_unused/validate_resolved_paths.py b/pype/plugins/hiero/_unused/validate_resolved_paths.py deleted file mode 100644 index 21883aa1d36..00000000000 --- a/pype/plugins/hiero/_unused/validate_resolved_paths.py +++ /dev/null @@ -1,27 +0,0 @@ -from pyblish import api - -class ValidateResolvedPaths(api.ContextPlugin): - """Validate there are no overlapping resolved paths.""" - - order = api.ValidatorOrder - label = "Resolved Paths" - hosts = ["hiero"] - - def process(self, context): - import os - import collections - - paths = [] - for instance in context: - if "trackItem.task" == instance.data["family"]: - paths.append( - os.path.abspath(instance.data["task"].resolvedExportPath()) - ) - - duplicates = [] - for item, count in collections.Counter(paths).items(): - if count > 1: - duplicates.append(item) - - msg = "Duplicate output paths found: {0}".format(duplicates) - assert not duplicates, msg diff --git a/pype/plugins/hiero/_unused/validate_task.py b/pype/plugins/hiero/_unused/validate_task.py deleted file mode 100644 index 3af94273bb0..00000000000 --- a/pype/plugins/hiero/_unused/validate_task.py +++ /dev/null @@ -1,57 +0,0 @@ -from pyblish import api - - -class ValidateOutputRange(api.InstancePlugin): - """Validate the output range of the task. - - This compares the output range and clip associated with the task, so see - whether there is a difference. This difference indicates that the user has - selected to export the clip length for the task which is very uncommon to - do. - """ - - order = api.ValidatorOrder - families = ["trackItem.task"] - label = "Output Range" - hosts = ["hiero"] - optional = True - - def process(self, instance): - - task = instance.data["task"] - item = instance.data["parent"] - - output_range = task.outputRange() - first_frame = int(item.data["item"].source().sourceIn()) - last_frame = int(item.data["item"].source().sourceOut()) - clip_duration = last_frame - first_frame + 1 - - difference = clip_duration - output_range[1] - failure_message = ( - 'Looks like you are rendering the clip length for the task ' - 'rather than the cut length. If this is intended, just uncheck ' - 'this validator after resetting, else adjust the export range in ' - 'the "Handles" section of the export dialog.' - ) - assert difference, failure_message - - -class ValidateImageSequence(api.InstancePlugin): - """Validate image sequence output path is setup correctly.""" - - order = api.ValidatorOrder - families = ["trackItem.task", "img"] - match = api.Subset - label = "Image Sequence" - hosts = ["hiero"] - optional = True - - def process(self, instance): - - resolved_path = instance.data["task"].resolvedExportPath() - - msg = ( - "Image sequence output is missing a padding. Please add \"####\" " - "or \"%04d\" to the output templates." - ) - assert "#" in resolved_path or "%" in resolved_path, msg diff --git a/pype/plugins/hiero/_unused/validate_track_item.py b/pype/plugins/hiero/_unused/validate_track_item.py deleted file mode 100644 index f29e1d5d755..00000000000 --- a/pype/plugins/hiero/_unused/validate_track_item.py +++ /dev/null @@ -1,46 +0,0 @@ -from pyblish import api - -class ValidateClip(api.InstancePlugin): - """Validate the track item to the sequence. - - Exact matching to optimize processing. - """ - - order = api.ValidatorOrder - families = ["clip"] - # match = api.Exact - label = "Validate Track Item" - hosts = ["hiero"] - optional = True - - def process(self, instance): - - item = instance.data["item"] - self.log.info("__ item: {}".format(item)) - media_source = item.source().mediaSource() - self.log.info("__ media_source: {}".format(media_source)) - - msg = ( - 'A setting does not match between track item "{0}" and sequence ' - '"{1}".'.format(item.name(), item.sequence().name()) + - '\n\nSetting: "{0}".''\n\nTrack item: "{1}".\n\nSequence: "{2}".' - ) - - # Validate format settings. - fmt = item.sequence().format() - assert fmt.width() == media_source.width(), msg.format( - "width", fmt.width(), media_source.width() - ) - assert fmt.height() == media_source.height(), msg.format( - "height", fmt.height(), media_source.height() - ) - assert fmt.pixelAspect() == media_source.pixelAspect(), msg.format( - "pixelAspect", fmt.pixelAspect(), media_source.pixelAspect() - ) - - # Validate framerate setting. - sequence = item.sequence() - source_framerate = media_source.metadata()["foundry.source.framerate"] - assert sequence.framerate() == source_framerate, msg.format( - "framerate", source_framerate, sequence.framerate() - ) diff --git a/pype/plugins/hiero/_unused/validate_viewer_lut.py b/pype/plugins/hiero/_unused/validate_viewer_lut.py deleted file mode 100644 index 4d307b5a71a..00000000000 --- a/pype/plugins/hiero/_unused/validate_viewer_lut.py +++ /dev/null @@ -1,21 +0,0 @@ -from pyblish import api - - -class ValidateViewerLut(api.ContextPlugin): - """Validate viewer lut in Hiero is the same as in Nuke.""" - - order = api.ValidatorOrder - label = "Viewer LUT" - hosts = ["hiero"] - optional = True - - def process(self, context): - import nuke - import hiero - - # nuke_lut = nuke.ViewerProcess.node()["current"].value() - hiero_lut = context.data["activeProject"].lutSettingViewer() - self.log.info("__ hiero_lut: {}".format(hiero_lut)) - - msg = "Viewer LUT can only be RGB" - assert "RGB" in hiero_lut, msg diff --git a/pype/plugins/hiero/load/load_sequences_to_timeline_asset_origin.py b/pype/plugins/hiero/load/load_sequences_to_timeline_asset_origin.py deleted file mode 100644 index 3c58a0a90d2..00000000000 --- a/pype/plugins/hiero/load/load_sequences_to_timeline_asset_origin.py +++ /dev/null @@ -1,49 +0,0 @@ -from avalon import api -import hiero -from pype.hosts.hiero import lib -reload(lib) - - -class LoadSequencesToTimelineAssetOrigin(api.Loader): - """Load image sequence into Hiero timeline - - Place clip to timeline on its asset origin timings collected - during conforming to project - """ - - families = ["render2d", "source", "plate", "render"] - representations = ["exr", "dpx", "jpg", "jpeg", "png"] - - label = "Load to timeline with shot origin timing" - order = -10 - icon = "code-fork" - color = "orange" - - def load(self, context, name, namespace, data): - - data.update({ - # "projectBinPath": "Loaded", - "hieroWorkfileName": hiero.ui.activeProject().name() - }) - - self.log.debug("_ context: `{}`".format(context)) - self.log.debug("_ representation._id: `{}`".format( - context["representation"]["_id"])) - - clip_loader = lib.ClipLoader(self, context, **data) - clip_loader.load() - - self.log.info("Loader done: `{}`".format(name)) - - def switch(self, container, representation): - self.update(container, representation) - - def update(self, container, representation): - """ Updating previously loaded clips - """ - pass - - def remove(self, container): - """ Removing previously loaded clips - """ - pass diff --git a/pype/plugins/hiero/publish/collect_active_project.py b/pype/plugins/hiero/publish/collect_active_project.py deleted file mode 100644 index 7fcd3c30a01..00000000000 --- a/pype/plugins/hiero/publish/collect_active_project.py +++ /dev/null @@ -1,14 +0,0 @@ -import pyblish.api - - -class CollectActiveProject(pyblish.api.ContextPlugin): - """Inject the active project into context""" - - label = "Collect Active Project" - order = pyblish.api.CollectorOrder - 0.2 - - def process(self, context): - import hiero - - context.data["activeProject"] = hiero.ui.activeSequence().project() - self.log.info("activeProject: {}".format(context.data["activeProject"])) diff --git a/pype/plugins/hiero/publish/collect_audio.py b/pype/plugins/hiero/publish/collect_audio.py deleted file mode 100644 index a63a18be05f..00000000000 --- a/pype/plugins/hiero/publish/collect_audio.py +++ /dev/null @@ -1,55 +0,0 @@ -from pyblish import api -import os - -class CollectAudio(api.InstancePlugin): - """Collect audio from tags. - - Tag is expected to have metadata: - { - "family": "audio", - "subset": "main" - } - """ - - # Run just before CollectSubsets - order = api.CollectorOrder + 0.1021 - label = "Collect Audio" - hosts = ["hiero"] - families = ["clip"] - - def process(self, instance): - # Exclude non-tagged instances. - tagged = False - for tag in instance.data["tags"]: - tag_data = dict(tag["metadata"]) - family = tag_data.get("tag.family", "") - if family.lower() == "audio": - subset = tag_data.get("tag.subset", "Main") - tagged = True - - if not tagged: - self.log.debug( - "Skipping \"{}\" because its not tagged with " - "\"audio\"".format(instance) - ) - return - - # Collect data. - data = {} - for key, value in instance.data.iteritems(): - data[key] = value - - data["family"] = "audio" - data["families"] = ["ftrack"] - - data["subset"] = "audio" + subset.title() - - data["source"] = data["sourcePath"] - - data["label"] = "{} - {} - ({})".format( - data['asset'], data["subset"], os.path.splitext(data["sourcePath"])[ - 1] - ) - - self.log.debug("Creating instance with data: {}".format(data)) - instance.context.create_instance(**data) diff --git a/pype/plugins/hiero/publish/collect_clip_resolution.py b/pype/plugins/hiero/publish/collect_clip_resolution.py deleted file mode 100644 index 8d441959ba8..00000000000 --- a/pype/plugins/hiero/publish/collect_clip_resolution.py +++ /dev/null @@ -1,21 +0,0 @@ -import pyblish.api - - -class CollectClipResolution(pyblish.api.InstancePlugin): - """Collect clip geometry resolution""" - - order = pyblish.api.CollectorOrder + 0.101 - label = "Collect Clip Resoluton" - hosts = ["hiero"] - - def process(self, instance): - sequence = instance.context.data['activeSequence'] - resolution_width = int(sequence.format().width()) - resolution_height = int(sequence.format().height()) - pixel_aspect = sequence.format().pixelAspect() - - instance.data.update({ - "resolutionWidth": resolution_width, - "resolutionHeight": resolution_height, - "pixelAspect": pixel_aspect - }) diff --git a/pype/plugins/hiero/publish/collect_clips.py b/pype/plugins/hiero/publish/collect_clips.py deleted file mode 100644 index 724e4730ed1..00000000000 --- a/pype/plugins/hiero/publish/collect_clips.py +++ /dev/null @@ -1,181 +0,0 @@ -import os - -from pyblish import api -import hiero -import nuke - -class CollectClips(api.ContextPlugin): - """Collect all Track items selection.""" - - order = api.CollectorOrder + 0.01 - label = "Collect Clips" - hosts = ["hiero"] - - def process(self, context): - # create asset_names conversion table - if not context.data.get("assetsShared"): - self.log.debug("Created `assetsShared` in context") - context.data["assetsShared"] = dict() - - projectdata = context.data["projectEntity"]["data"] - sequence = context.data.get("activeSequence") - selection = context.data.get("selection") - - track_effects = dict() - - # collect all trackItems as instances - for track_index, video_track in enumerate(sequence.videoTracks()): - items = video_track.items() - sub_items = video_track.subTrackItems() - - for item in items: - data = dict() - # compare with selection or if disabled - if item not in selection or not item.isEnabled(): - continue - - # Skip audio track items - # Try/Except is to handle items types, like EffectTrackItem - try: - media_type = "core.Hiero.Python.TrackItem.MediaType.kVideo" - if str(item.mediaType()) != media_type: - continue - except Exception: - continue - - asset = item.name() - track = item.parent() - source = item.source().mediaSource() - source_path = source.firstpath() - clip_in = int(item.timelineIn()) - clip_out = int(item.timelineOut()) - file_head = source.filenameHead() - file_info = next((f for f in source.fileinfos()), None) - source_first_frame = int(file_info.startFrame()) - is_sequence = False - - self.log.debug( - "__ assets_shared: {}".format( - context.data["assetsShared"])) - - # Check for clips with the same range - # this is for testing if any vertically neighbouring - # clips has been already processed - clip_matching_with_range = next( - (k for k, v in context.data["assetsShared"].items() - if (v.get("_clipIn", 0) == clip_in) - and (v.get("_clipOut", 0) == clip_out) - ), False) - - # check if clip name is the same in matched - # vertically neighbouring clip - # if it is then it is correct and resent variable to False - # not to be rised wrong name exception - if asset in str(clip_matching_with_range): - clip_matching_with_range = False - - # rise wrong name exception if found one - assert (not clip_matching_with_range), ( - "matching clip: {asset}" - " timeline range ({clip_in}:{clip_out})" - " conflicting with {clip_matching_with_range}" - " >> rename any of clips to be the same as the other <<" - ).format( - **locals()) - - if not source.singleFile(): - self.log.info("Single file") - is_sequence = True - source_path = file_info.filename() - - effects = [f for f in item.linkedItems() - if f.isEnabled() - if isinstance(f, hiero.core.EffectTrackItem)] - - # If source is *.nk its a comp effect and we need to fetch the - # write node output. This should be improved by parsing the script - # rather than opening it. - if source_path.endswith(".nk"): - nuke.scriptOpen(source_path) - # There should noly be one. - write_node = nuke.allNodes(filter="Write")[0] - path = nuke.filename(write_node) - - if "%" in path: - # Get start frame from Nuke script and use the item source - # in/out, because you can have multiple shots covered with - # one nuke script. - start_frame = int(nuke.root()["first_frame"].getValue()) - if write_node["use_limit"].getValue(): - start_frame = int(write_node["first"].getValue()) - - path = path % (start_frame + item.sourceIn()) - - source_path = path - self.log.debug( - "Fetched source path \"{}\" from \"{}\" in " - "\"{}\".".format( - source_path, write_node.name(), source.firstpath() - ) - ) - - data.update({ - "name": "{0}_{1}".format(track.name(), item.name()), - "item": item, - "source": source, - "timecodeStart": str(source.timecodeStart()), - "timelineTimecodeStart": str(sequence.timecodeStart()), - "sourcePath": source_path, - "sourceFileHead": file_head, - "isSequence": is_sequence, - "track": track.name(), - "trackIndex": track_index, - "sourceFirst": source_first_frame, - "effects": effects, - "sourceIn": int(item.sourceIn()), - "sourceOut": int(item.sourceOut()), - "mediaDuration": int(source.duration()), - "clipIn": clip_in, - "clipOut": clip_out, - "clipDuration": ( - int(item.timelineOut()) - int( - item.timelineIn())) + 1, - "asset": asset, - "family": "clip", - "families": [], - "handleStart": int(projectdata.get("handleStart", 0)), - "handleEnd": int(projectdata.get("handleEnd", 0)), - "fps": context.data["fps"] - }) - instance = context.create_instance(**data) - - self.log.info("Created instance: {}".format(instance)) - self.log.info("Created instance.data: {}".format(instance.data)) - self.log.debug(">> effects: {}".format(instance.data["effects"])) - - context.data["assetsShared"][asset] = { - "_clipIn": clip_in, - "_clipOut": clip_out - } - - # from now we are collecting only subtrackitems on - # track with no video items - if len(items) > 0: - continue - - # create list in track key - # get all subTrackItems and add it to context - track_effects[track_index] = list() - - # collect all subtrack items - for sitem in sub_items: - # unwrap from tuple >> it is always tuple with one item - sitem = sitem[0] - # checking if not enabled - if not sitem.isEnabled(): - continue - - track_effects[track_index].append(sitem) - - context.data["trackEffects"] = track_effects - self.log.debug(">> sub_track_items: `{}`".format(track_effects)) diff --git a/pype/plugins/hiero/publish/collect_colorspace.py b/pype/plugins/hiero/publish/collect_colorspace.py deleted file mode 100644 index 2b629ba1f7a..00000000000 --- a/pype/plugins/hiero/publish/collect_colorspace.py +++ /dev/null @@ -1,27 +0,0 @@ -import pyblish.api - - -class CollectProjectColorspace(pyblish.api.ContextPlugin): - """get active project color settings""" - - order = pyblish.api.CollectorOrder + 0.1 - label = "Project's color settings" - def process(self, context): - import hiero - - project = context.data["activeProject"] - colorspace = {} - colorspace["useOCIOEnvironmentOverride"] = project.useOCIOEnvironmentOverride() - colorspace["lutSetting16Bit"] = project.lutSetting16Bit() - colorspace["lutSetting8Bit"] = project.lutSetting8Bit() - colorspace["lutSettingFloat"] = project.lutSettingFloat() - colorspace["lutSettingLog"] = project.lutSettingLog() - colorspace["lutSettingViewer"] = project.lutSettingViewer() - colorspace["lutSettingWorkingSpace"] = project.lutSettingWorkingSpace() - colorspace["lutUseOCIOForExport"] = project.lutUseOCIOForExport() - colorspace["ocioConfigName"] = project.ocioConfigName() - colorspace["ocioConfigPath"] = project.ocioConfigPath() - - context.data["colorspace"] = colorspace - - self.log.info("context.data[colorspace]: {}".format(context.data["colorspace"])) diff --git a/pype/plugins/hiero/publish/collect_current_file.py b/pype/plugins/hiero/publish/collect_current_file.py deleted file mode 100644 index c0c217dd236..00000000000 --- a/pype/plugins/hiero/publish/collect_current_file.py +++ /dev/null @@ -1,13 +0,0 @@ -import pyblish.api - -class CollectCurrentFile(pyblish.api.ContextPlugin): - """Inject the current working file into context""" - - order = pyblish.api.CollectorOrder - 0.1 - - - def process(self, context): - - project = context.data('activeProject') - context.data["currentFile"] = path = project.path() - self.log.info("currentFile: {}".format(context.data["currentFile"])) diff --git a/pype/plugins/hiero/publish/collect_effects.py b/pype/plugins/hiero/publish/collect_effects.py deleted file mode 100644 index 55ff849c88f..00000000000 --- a/pype/plugins/hiero/publish/collect_effects.py +++ /dev/null @@ -1,106 +0,0 @@ -import pyblish.api -import re - - -class CollectVideoTracksLuts(pyblish.api.InstancePlugin): - """Collect video tracks effects into context.""" - - order = pyblish.api.CollectorOrder + 0.1015 - label = "Collect Soft Lut Effects" - families = ["clip"] - - def process(self, instance): - - self.log.debug( - "Finding soft effect for subset: `{}`".format( - instance.data.get("subset"))) - - # taking active sequence - subset = instance.data.get("subset") - - if not subset: - return - - track_effects = instance.context.data.get("trackEffects", {}) - track_index = instance.data["trackIndex"] - effects = instance.data["effects"] - - # creating context attribute - self.effects = {"assignTo": subset, "effects": dict()} - - for sitem in effects: - self.add_effect(instance, track_index, sitem) - - for t_index, sitems in track_effects.items(): - for sitem in sitems: - if not t_index > track_index: - continue - self.log.debug(">> sitem: `{}`".format(sitem)) - self.add_effect(instance, t_index, sitem) - - if self.effects["effects"]: - instance.data["effectTrackItems"] = self.effects - - if len(instance.data.get("effectTrackItems", {}).keys()) > 0: - instance.data["families"] += ["lut"] - self.log.debug( - "effects.keys: {}".format( - instance.data.get("effectTrackItems", {}).keys())) - self.log.debug( - "effects: {}".format( - instance.data.get("effectTrackItems", {}))) - - def add_effect(self, instance, track_index, item): - track = item.parentTrack().name() - # node serialization - node = item.node() - node_serialized = self.node_serialisation(instance, node) - - # collect timelineIn/Out - effect_t_in = int(item.timelineIn()) - effect_t_out = int(item.timelineOut()) - - node_name = item.name() - node_class = re.sub(r"\d+", "", node_name) - - self.effects["effects"].update({node_name: { - "class": node_class, - "timelineIn": effect_t_in, - "timelineOut": effect_t_out, - "subTrackIndex": item.subTrackIndex(), - "trackIndex": track_index, - "track": track, - "node": node_serialized - }}) - - def node_serialisation(self, instance, node): - node_serialized = {} - timeline_in_h = instance.data["clipInH"] - timeline_out_h = instance.data["clipOutH"] - - # adding ignoring knob keys - _ignoring_keys = ['invert_mask', 'help', 'mask', - 'xpos', 'ypos', 'layer', 'process_mask', 'channel', - 'channels', 'maskChannelMask', 'maskChannelInput', - 'note_font', 'note_font_size', 'unpremult', - 'postage_stamp_frame', 'maskChannel', 'export_cc', - 'select_cccid', 'mix', 'version', 'matrix'] - - # loop trough all knobs and collect not ignored - # and any with any value - for knob in node.knobs().keys(): - # skip nodes in ignore keys - if knob in _ignoring_keys: - continue - - # get animation if node is animated - if node[knob].isAnimated(): - # grab animation including handles - knob_anim = [node[knob].getValueAt(i) - for i in range(timeline_in_h, timeline_out_h + 1)] - - node_serialized[knob] = knob_anim - else: - node_serialized[knob] = node[knob].value() - - return node_serialized diff --git a/pype/plugins/hiero/publish/collect_frame_ranges.py b/pype/plugins/hiero/publish/collect_frame_ranges.py deleted file mode 100644 index 19a46d80b15..00000000000 --- a/pype/plugins/hiero/publish/collect_frame_ranges.py +++ /dev/null @@ -1,50 +0,0 @@ -import pyblish.api - - -class CollectClipFrameRanges(pyblish.api.InstancePlugin): - """Collect all frame range data: source(In,Out), timeline(In,Out), edit_(in, out), f(start, end)""" - - order = pyblish.api.CollectorOrder + 0.101 - label = "Collect Frame Ranges" - hosts = ["hiero"] - - def process(self, instance): - - data = dict() - - # Timeline data. - handle_start = instance.data["handleStart"] - handle_end = instance.data["handleEnd"] - - source_in_h = instance.data("sourceInH", - instance.data("sourceIn") - handle_start) - source_out_h = instance.data("sourceOutH", - instance.data("sourceOut") + handle_end) - - timeline_in = instance.data["clipIn"] - timeline_out = instance.data["clipOut"] - - timeline_in_h = timeline_in - handle_start - timeline_out_h = timeline_out + handle_end - - # set frame start with tag or take it from timeline - frame_start = instance.data.get("startingFrame") - - if not frame_start: - frame_start = timeline_in - - frame_end = frame_start + (timeline_out - timeline_in) - - data.update({ - "sourceInH": source_in_h, - "sourceOutH": source_out_h, - "frameStart": frame_start, - "frameEnd": frame_end, - "clipInH": timeline_in_h, - "clipOutH": timeline_out_h, - "clipDurationH": instance.data.get( - "clipDuration") + handle_start + handle_end - } - ) - self.log.debug("__ data: {}".format(data)) - instance.data.update(data) diff --git a/pype/plugins/hiero/publish/collect_handles.py b/pype/plugins/hiero/publish/collect_handles.py deleted file mode 100644 index 1f278a6b4cc..00000000000 --- a/pype/plugins/hiero/publish/collect_handles.py +++ /dev/null @@ -1,61 +0,0 @@ -from pyblish import api - - -class CollectClipHandles(api.ContextPlugin): - """Collect Handles from all instanes and add to assetShared.""" - - order = api.CollectorOrder + 0.0121 - label = "Collect Handles" - hosts = ["hiero"] - - def process(self, context): - assets_shared = context.data.get("assetsShared") - - # find all main types instances and add its handles to asset shared - instances = context[:] - filtered_instances = [] - for instance in instances: - self.log.debug("_ instance.name: `{}`".format(instance.data["name"])) - families = instance.data.get("families", []) - families += [instance.data["family"]] - if "clip" in families: - filtered_instances.append(instance) - else: - continue - - # get handles - handle_start = int(instance.data["handleStart"]) - handle_end = int(instance.data["handleEnd"]) - - if instance.data.get("main"): - name = instance.data["asset"] - if assets_shared.get(name): - asset_shared = assets_shared.get(name) - else: - asset_shared = assets_shared[name] - - self.log.debug("Adding to shared assets: `{}`".format( - instance.data["name"])) - asset_shared.update({ - "handleStart": handle_start, - "handleEnd": handle_end - }) - - for instance in filtered_instances: - if not instance.data.get("main") and not instance.data.get("handleTag"): - self.log.debug("Synchronize handles on: `{}`".format( - instance.data["name"])) - name = instance.data["asset"] - s_asset_data = assets_shared.get(name) - instance.data["handleStart"] = s_asset_data.get( - "handleStart", 0 - ) - instance.data["handleEnd"] = s_asset_data.get("handleEnd", 0) - - # debug printing - self.log.debug("_ s_asset_data: `{}`".format( - s_asset_data)) - self.log.debug("_ instance.data[handleStart]: `{}`".format( - instance.data["handleStart"])) - self.log.debug("_ instance.data[handleEnd]: `{}`".format( - instance.data["handleEnd"])) diff --git a/pype/plugins/hiero/publish/collect_hierarchy_context.py b/pype/plugins/hiero/publish/collect_hierarchy_context.py deleted file mode 100644 index 77b10dc9e41..00000000000 --- a/pype/plugins/hiero/publish/collect_hierarchy_context.py +++ /dev/null @@ -1,347 +0,0 @@ -import pyblish.api -import avalon.api as avalon -import re - - -class CollectHierarchyInstance(pyblish.api.ContextPlugin): - """Collecting hierarchy context from `parents` and `hierarchy` data - present in `clip` family instances coming from the request json data file - - It will add `hierarchical_context` into each instance for integrate - plugins to be able to create needed parents for the context if they - don't exist yet - """ - - label = "Collect Hierarchy Clip" - order = pyblish.api.CollectorOrder + 0.102 - families = ["clip"] - - def convert_to_entity(self, key, value): - # ftrack compatible entity types - types = {"shot": "Shot", - "folder": "Folder", - "episode": "Episode", - "sequence": "Sequence", - "track": "Sequence", - } - # convert to entity type - entity_type = types.get(key, None) - - # return if any - if entity_type: - return {"entityType": entity_type, "entityName": value} - - def process(self, context): - - for instance in context[:]: - assets_shared = context.data.get("assetsShared") - tags = instance.data.get("tags", None) - clip = instance.data["item"] - asset = instance.data["asset"] - sequence = context.data['activeSequence'] - resolution_width = instance.data["resolutionWidth"] - resolution_height = instance.data["resolutionHeight"] - pixel_aspect = instance.data["pixelAspect"] - clip_in = instance.data["clipIn"] - clip_out = instance.data["clipOut"] - fps = context.data["fps"] - - # build data for inner hiero project property - data = { - "sequence": ( - context.data['activeSequence'].name().replace(' ', '_') - ), - "track": clip.parent().name().replace(' ', '_'), - "clip": asset - } - self.log.debug("__ data: {}".format(data)) - - # checking if tags are available - self.log.debug("__ instance.data[name]: {}".format( - instance.data["name"])) - self.log.debug("__ tags: {}".format(tags)) - - if not tags: - continue - - # loop trough all tags - for t in tags: - t_metadata = dict(t["metadata"]) - t_type = t_metadata.get("tag.label", "") - t_note = t_metadata.get("tag.note", "") - - self.log.debug("__ t_type: {}".format(t_type)) - - # and finding only hierarchical tag - if "hierarchy" in t_type.lower(): - # Check for clips with the same range - # this is for testing if any vertically neighbouring - # clips has been already processed - match = next(( - k for k, v in assets_shared.items() - if (v["_clipIn"] == clip_in) - and (v["_clipOut"] == clip_out) - ), False) - - self.log.debug( - "__ assets_shared[match]: {}".format( - assets_shared[match])) - - # check if hierarchy key is present in matched - # vertically neighbouring clip - if not assets_shared[match].get("hierarchy"): - match = False - - # rise exception if multiple hierarchy tag found - assert not match, ( - "Two clips above each other with" - " hierarchy tag are not allowed" - " >> keep hierarchy tag only in one of them <<" - ) - - d_metadata = dict() - parents = list() - - # main template from Tag.note - template = t_note - - # if shot in template then remove it - if "shot" in template.lower(): - instance.data["asset"] = [ - t for t in template.split('/')][-1] - template = "/".join( - [t for t in template.split('/')][0:-1]) - - # take template from Tag.note and break it into parts - template_split = template.split("/") - patern = re.compile(r"\{([a-z]*?)\}") - par_split = [patern.findall(t) - for t in template.split("/")] - - # format all {} in two layers - for k, v in t_metadata.items(): - new_k = k.split(".")[1] - - # ignore all help strings - if 'help' in k: - continue - # self.log.info("__ new_k: `{}`".format(new_k)) - try: - # first try all data and context data to - # add to individual properties - new_v = str(v).format( - **dict(context.data, **data)) - d_metadata[new_k] = new_v - - # create parents - # find matching index of order - p_match_i = [i for i, p in enumerate(par_split) - if new_k in p] - - # if any is matching then convert to entity_types - if p_match_i: - parent = self.convert_to_entity( - new_k, template_split[p_match_i[0]]) - parents.insert(p_match_i[0], parent) - except Exception: - d_metadata[new_k] = v - - # create new shot asset name - instance.data["asset"] = instance.data["asset"].format( - **d_metadata) - self.log.debug( - "__ instance.data[asset]: " - "{}".format(instance.data["asset"]) - ) - - # lastly fill those individual properties itno - # format the string with collected data - parents = [{"entityName": p["entityName"].format( - **d_metadata), "entityType": p["entityType"]} - for p in parents] - self.log.debug("__ parents: {}".format(parents)) - - hierarchy = template.format( - **d_metadata) - self.log.debug("__ hierarchy: {}".format(hierarchy)) - - # check if hierarchy attribute is already created - # it should not be so return warning if it is - hd = instance.data.get("hierarchy") - assert not hd, ( - "Only one Hierarchy Tag is allowed. " - "Clip: `{}`".format(asset) - ) - - # add formated hierarchy path into instance data - instance.data["hierarchy"] = hierarchy - instance.data["parents"] = parents - - self.log.info( - "clip: {asset}[{clip_in}:{clip_out}]".format( - **locals())) - # adding to asset shared dict - self.log.debug( - "__ assets_shared: {}".format(assets_shared)) - if assets_shared.get(asset): - self.log.debug("Adding to shared assets: `{}`".format( - asset)) - asset_shared = assets_shared.get(asset) - else: - asset_shared = assets_shared[asset] - - asset_shared.update({ - "asset": asset, - "hierarchy": hierarchy, - "parents": parents, - "resolutionWidth": resolution_width, - "resolutionHeight": resolution_height, - "pixelAspect": pixel_aspect, - "fps": fps, - "tasks": instance.data["tasks"] - }) - - # adding frame start if any on instance - start_frame = instance.data.get("startingFrame") - if start_frame: - asset_shared.update({ - "startingFrame": start_frame - }) - self.log.debug( - "assets_shared: {assets_shared}".format(**locals())) - - -class CollectHierarchyContext(pyblish.api.ContextPlugin): - '''Collecting Hierarchy from instaces and building - context hierarchy tree - ''' - - label = "Collect Hierarchy Context" - order = pyblish.api.CollectorOrder + 0.103 - - def update_dict(self, ex_dict, new_dict): - for key in ex_dict: - if key in new_dict and isinstance(ex_dict[key], dict): - new_dict[key] = self.update_dict(ex_dict[key], new_dict[key]) - else: - if ex_dict.get(key) and new_dict.get(key): - continue - else: - new_dict[key] = ex_dict[key] - - return new_dict - - def process(self, context): - instances = context[:] - - # create hierarchyContext attr if context has none - - temp_context = {} - for instance in instances: - if 'projectfile' in instance.data.get('family', ''): - continue - - name = instance.data["asset"] - - # get handles - handle_start = int(instance.data["handleStart"]) - handle_end = int(instance.data["handleEnd"]) - - # inject assetsShared to other plates types - assets_shared = context.data.get("assetsShared") - - if assets_shared: - s_asset_data = assets_shared.get(name) - if s_asset_data: - self.log.debug("__ s_asset_data: {}".format(s_asset_data)) - name = instance.data["asset"] = s_asset_data["asset"] - instance.data["parents"] = s_asset_data["parents"] - instance.data["hierarchy"] = s_asset_data["hierarchy"] - instance.data["tasks"] = s_asset_data["tasks"] - instance.data["resolutionWidth"] = s_asset_data[ - "resolutionWidth"] - instance.data["resolutionHeight"] = s_asset_data[ - "resolutionHeight"] - instance.data["pixelAspect"] = s_asset_data["pixelAspect"] - instance.data["fps"] = s_asset_data["fps"] - - # adding frame start if any on instance - start_frame = s_asset_data.get("startingFrame") - if start_frame: - instance.data["frameStart"] = start_frame - instance.data["frameEnd"] = start_frame + ( - instance.data["clipOut"] - - instance.data["clipIn"]) - - self.log.debug( - "__ instance.data[parents]: {}".format( - instance.data["parents"] - ) - ) - self.log.debug( - "__ instance.data[hierarchy]: {}".format( - instance.data["hierarchy"] - ) - ) - self.log.debug( - "__ instance.data[name]: {}".format(instance.data["name"]) - ) - - in_info = {} - - in_info["inputs"] = [ - x["_id"] for x in instance.data.get("assetbuilds", []) - ] - - # suppose that all instances are Shots - in_info['entity_type'] = 'Shot' - - # get custom attributes of the shot - if instance.data.get("main"): - in_info['custom_attributes'] = { - "handleStart": handle_start, - "handleEnd": handle_end, - "frameStart": instance.data["frameStart"], - "frameEnd": instance.data["frameEnd"], - "clipIn": instance.data["clipIn"], - "clipOut": instance.data["clipOut"], - 'fps': instance.context.data["fps"] - } - - # adding SourceResolution if Tag was present - if instance.data.get("main"): - in_info['custom_attributes'].update({ - "resolutionWidth": instance.data["resolutionWidth"], - "resolutionHeight": instance.data["resolutionHeight"], - "pixelAspect": instance.data["pixelAspect"] - }) - - in_info['tasks'] = instance.data['tasks'] - in_info["comments"] = instance.data.get("comments", []) - - parents = instance.data.get('parents', []) - self.log.debug("__ in_info: {}".format(in_info)) - - actual = {name: in_info} - - for parent in reversed(parents): - next_dict = {} - parent_name = parent["entityName"] - next_dict[parent_name] = {} - next_dict[parent_name]["entity_type"] = parent["entityType"] - next_dict[parent_name]["childs"] = actual - actual = next_dict - - temp_context = self.update_dict(temp_context, actual) - - # TODO: 100% sure way of get project! Will be Name or Code? - project_name = avalon.Session["AVALON_PROJECT"] - final_context = {} - final_context[project_name] = {} - final_context[project_name]['entity_type'] = 'Project' - final_context[project_name]['childs'] = temp_context - - # adding hierarchy context to instance - context.data["hierarchyContext"] = final_context - self.log.debug("context.data[hierarchyContext] is: {}".format( - context.data["hierarchyContext"])) diff --git a/pype/plugins/hiero/publish/collect_host.py b/pype/plugins/hiero/publish/collect_host.py deleted file mode 100644 index caad4d344a9..00000000000 --- a/pype/plugins/hiero/publish/collect_host.py +++ /dev/null @@ -1,13 +0,0 @@ -import pyblish.api - - -class CollectHost(pyblish.api.ContextPlugin): - """Inject the host into context""" - - order = pyblish.api.CollectorOrder - - def process(self, context): - import pyblish.api - - context.set_data("host", pyblish.api.current_host()) - self.log.info("current host: {}".format(pyblish.api.current_host())) diff --git a/pype/plugins/hiero/publish/collect_instance_version.py b/pype/plugins/hiero/publish/collect_instance_version.py deleted file mode 100644 index b79ccbdf54a..00000000000 --- a/pype/plugins/hiero/publish/collect_instance_version.py +++ /dev/null @@ -1,18 +0,0 @@ -from pyblish import api - - -class CollectInstanceVersion(api.InstancePlugin): - """ Collecting versions of Hiero project into instances - - If activated then any subset version is created in - version of the actual project. - """ - - order = api.CollectorOrder + 0.011 - label = "Collect Instance Version" - - def process(self, instance): - version = instance.context.data.get("version", "001") - instance.data.update({ - "version": int(version) - }) diff --git a/pype/plugins/hiero/publish/collect_leader_clip.py b/pype/plugins/hiero/publish/collect_leader_clip.py deleted file mode 100644 index bba85cf411c..00000000000 --- a/pype/plugins/hiero/publish/collect_leader_clip.py +++ /dev/null @@ -1,24 +0,0 @@ -from pyblish import api - - -class CollectLeaderClip(api.InstancePlugin): - """Collect Leader clip from selected track items. Clip with hierarchy Tag is defining sharable data attributes between other clips with `subset` tags. So `handle_start/end`, `frame_start`, etc""" - - order = api.CollectorOrder + 0.0111 - label = "Collect Leader Clip" - hosts = ["hiero"] - families = ['clip'] - - def process(self, instance): - # gets tags - tags = instance.data["tags"] - - for t in tags: - t_metadata = dict(t["metadata"]) - t_type = t_metadata.get("tag.label", "") - self.log.info("`hierarhy`: `{}`".format(t_type)) - # gets only task family tags and collect labels - if "hierarchy" in t_type.lower(): - if not instance.data.get("main"): - instance.data["main"] = True - self.log.info("`Leader Clip` found in instance.name: `{}`".format(instance.data["name"])) diff --git a/pype/plugins/hiero/publish/collect_plates.py b/pype/plugins/hiero/publish/collect_plates.py deleted file mode 100644 index 7b671ef718b..00000000000 --- a/pype/plugins/hiero/publish/collect_plates.py +++ /dev/null @@ -1,264 +0,0 @@ -import os - -from pyblish import api - - -class CollectPlates(api.InstancePlugin): - """Collect plates from tags. - - Tag is expected to have metadata: - { - "family": "plate" - "subset": "main" - } - """ - - # Run just before CollectSubsets - order = api.CollectorOrder + 0.1021 - label = "Collect Plates" - hosts = ["hiero"] - families = ["clip"] - - def process(self, instance): - # Exclude non-tagged instances. - tagged = False - for tag in instance.data["tags"]: - tag_data = dict(tag["metadata"]) - family = tag_data.get("tag.family", "") - if family.lower() == "plate": - subset = tag_data.get("tag.subset", "Main") - tagged = True - break - - if not tagged: - self.log.debug( - "Skipping \"{}\" because its not tagged with " - "\"plate\"".format(instance) - ) - return - self.log.debug("__ subset: `{}`".format(instance.data["subset"])) - # if "audio" in instance.data["subset"]: - # return - - # Collect data. - data = {} - for key, value in instance.data.iteritems(): - data[key] = value - - self.log.debug("__ family: `{}`".format(family)) - self.log.debug("__ subset: `{}`".format(subset)) - - data["family"] = family.lower() - data["families"] = ["ftrack"] + instance.data["families"][1:] - data["source"] = data["sourcePath"] - data["subset"] = family + subset.title() - data["name"] = data["subset"] + "_" + data["asset"] - - data["label"] = "{} - {} - ({})".format( - data['asset'], data["subset"], os.path.splitext( - data["sourcePath"])[1]) - - if "review" in instance.data["families"]: - data["label"] += " - review" - - # adding SourceResolution if Tag was present - if instance.data.get("sourceResolution") and instance.data.get("main"): - item = instance.data["item"] - resolution_width = int(item.source().mediaSource().width()) - resolution_height = int(item.source().mediaSource().height()) - pixel_aspect = int(item.source().mediaSource().pixelAspect()) - - self.log.info("Source Width and Height are: `{0} x {1} : {2}`".format( - resolution_width, resolution_height, pixel_aspect)) - data.update({ - "resolutionWidth": resolution_width, - "resolutionHeight": resolution_height, - "pixelAspect": pixel_aspect - }) - - self.log.debug("Creating instance with name: {}".format(data["name"])) - instance.context.create_instance(**data) - - -class CollectPlatesData(api.InstancePlugin): - """Collect plates""" - - order = api.CollectorOrder + 0.48 - label = "Collect Plates Data" - hosts = ["hiero"] - families = ["plate"] - - def process(self, instance): - import os - if "review" in instance.data.get("track", ""): - self.log.debug( - "Skipping \"{}\" because its `review` track " - "\"plate\"".format(instance) - ) - return - - # add to representations - if not instance.data.get("representations"): - instance.data["representations"] = list() - - version_data = dict() - - name = instance.data["subset"] - source_path = instance.data["sourcePath"] - source_file = os.path.basename(source_path) - - # Filter out "clip" family. - families = instance.data["families"] + [instance.data["family"]] - families = list(set(families)) - if "clip" in families: - families.remove("clip") - family = families[-1] - - # staging dir creation - staging_dir = os.path.dirname( - source_path) - - item = instance.data["item"] - - transfer_data = [ - "handleStart", "handleEnd", "sourceIn", "sourceOut", "frameStart", - "frameEnd", "sourceInH", "sourceOutH", "clipIn", "clipOut", - "clipInH", "clipOutH", "asset", "track", "resolutionWidth", "resolutionHeight", "pixelAspect", "fps" - ] - - # pass data to version - version_data.update({k: instance.data[k] for k in transfer_data}) - - # add to data of representation - version_data.update({ - "colorspace": item.sourceMediaColourTransform(), - "colorspaceScript": instance.context.data["colorspace"], - "families": [f for f in families if 'ftrack' not in f], - "subset": name, - "fps": instance.context.data["fps"] - }) - - version = instance.data.get("version") - if version: - version_data.update({ - "version": version - }) - - source_first_frame = instance.data.get("sourceFirst") - source_file_head = instance.data.get("sourceFileHead") - self.log.debug("source_first_frame: `{}`".format(source_first_frame)) - - if instance.data.get("isSequence", False): - self.log.info("Is sequence of files") - file = os.path.basename(source_file) - ext = os.path.splitext(file)[-1][1:] - self.log.debug("source_file_head: `{}`".format(source_file_head)) - head = source_file_head[:-1] - start_frame = int(source_first_frame + instance.data["sourceInH"]) - duration = int( - instance.data["sourceOutH"] - instance.data["sourceInH"]) - end_frame = start_frame + duration - self.log.debug("start_frame: `{}`".format(start_frame)) - self.log.debug("end_frame: `{}`".format(end_frame)) - files = [file % i for i in range(start_frame, (end_frame + 1), 1)] - else: - self.log.info("Is single file") - ext = os.path.splitext(source_file)[-1][1:] - head = source_file_head - files = source_file - start_frame = instance.data["sourceInH"] - end_frame = instance.data["sourceOutH"] - - mov_file = head + ".mov" - mov_path = os.path.normpath(os.path.join(staging_dir, mov_file)) - if os.path.exists(mov_path): - # adding mov into the representations - self.log.debug("__ mov_path: {}".format(mov_path)) - instance.data["label"] += " - review" - - plates_mov_representation = { - 'files': mov_file, - 'stagingDir': staging_dir, - "frameStart": 0, - "frameEnd": instance.data["sourceOut"] - instance.data["sourceIn"] + 1, - 'step': 1, - 'fps': instance.context.data["fps"], - 'tags': ["review"], - 'name': "preview", - 'ext': "mov", - } - - if mov_file not in source_file: - instance.data["representations"].append( - plates_mov_representation) - - thumb_frame = instance.data["sourceInH"] + ( - (instance.data["sourceOutH"] - instance.data["sourceInH"]) / 2) - thumb_file = "{}_{}{}".format(head, thumb_frame, ".png") - thumb_path = os.path.join(staging_dir, thumb_file) - self.log.debug("__ thumb_path: `{}`, frame: `{}`".format( - thumb_path, thumb_frame)) - thumbnail = item.thumbnail(thumb_frame).save( - thumb_path, - format='png' - ) - self.log.debug("__ thumbnail: `{}`, frame: `{}`".format( - thumbnail, thumb_frame)) - - thumb_representation = { - 'files': thumb_file, - 'stagingDir': staging_dir, - 'name': "thumbnail", - 'thumbnail': True, - 'ext': "png" - } - instance.data["representations"].append( - thumb_representation) - - # adding representation for plates - frame_start = instance.data["frameStart"] - \ - instance.data["handleStart"] - frame_end = instance.data["frameEnd"] + instance.data["handleEnd"] - - # exception for retimes - if instance.data.get("retime"): - source_in_h = instance.data["sourceInH"] - source_in = instance.data["sourceIn"] - source_handle_start = source_in_h - source_in - frame_start = instance.data["frameStart"] + source_handle_start - duration = instance.data["sourceOutH"] - instance.data["sourceInH"] - frame_end = frame_start + duration - - plates_representation = { - 'files': files, - 'stagingDir': staging_dir, - 'name': ext, - 'ext': ext, - "frameEnd": frame_end, - "frameStart": "%0{}d".format( - len(str(frame_end))) % frame_start - } - instance.data["representations"].append(plates_representation) - - # deal with retimed clip - if instance.data.get("retime"): - version_data.update({ - "retime": True, - "speed": instance.data.get("speed", 1), - "timewarps": instance.data.get("timeWarpNodes", []), - "frameStart": frame_start, - "frameEnd": frame_end, - }) - - instance.data["versionData"] = version_data - - # testing families - family = instance.data["family"] - families = instance.data["families"] - - # test prints version_data - self.log.debug("__ version_data: {}".format(version_data)) - self.log.debug("__ representations: {}".format( - instance.data["representations"])) - self.log.debug("__ after family: {}".format(family)) - self.log.debug("__ after families: {}".format(families)) diff --git a/pype/plugins/hiero/publish/collect_remove_clip_instances.py b/pype/plugins/hiero/publish/collect_remove_clip_instances.py deleted file mode 100644 index c42bd1345b7..00000000000 --- a/pype/plugins/hiero/publish/collect_remove_clip_instances.py +++ /dev/null @@ -1,17 +0,0 @@ -from pyblish import api - -class CollectClipSubsets(api.InstancePlugin): - """Collect Subsets from selected Clips, Tags, Preset.""" - - order = api.CollectorOrder + 0.103 - label = "Collect Remove Clip Instaces" - hosts = ["hiero"] - families = ['clip'] - - def process(self, instance): - context = instance.context - - # removing original instance - self.log.info("Removing instance.name: `{}`".format(instance.data["name"])) - - context.remove(instance) diff --git a/pype/plugins/hiero/publish/collect_review.py b/pype/plugins/hiero/publish/collect_review.py deleted file mode 100644 index f1767b2a681..00000000000 --- a/pype/plugins/hiero/publish/collect_review.py +++ /dev/null @@ -1,259 +0,0 @@ -from pyblish import api -import os -import re -import clique - - -class CollectReview(api.InstancePlugin): - """Collect review from tags. - - Tag is expected to have metadata: - { - "family": "review" - "track": "trackName" - } - """ - - # Run just before CollectSubsets - order = api.CollectorOrder + 0.1022 - label = "Collect Review" - hosts = ["hiero"] - families = ["plate"] - - def process(self, instance): - is_sequence = instance.data["isSequence"] - - # Exclude non-tagged instances. - tagged = False - for tag in instance.data["tags"]: - family = dict(tag["metadata"]).get("tag.family", "") - if family.lower() == "review": - tagged = True - track = dict(tag["metadata"]).get("tag.track") - break - - if not tagged: - self.log.debug( - "Skipping \"{}\" because its not tagged with " - "\"review\"".format(instance) - ) - return - - if not track: - self.log.debug(( - "Skipping \"{}\" because tag is not having" - "`track` in metadata" - ).format(instance)) - return - - # add to representations - if not instance.data.get("representations"): - instance.data["representations"] = list() - - if track in instance.data["track"]: - self.log.debug("Review will work on `subset`: {}".format( - instance.data["subset"])) - - # change families - instance.data["family"] = "plate" - instance.data["families"] = ["review", "ftrack"] - - self.version_data(instance) - self.create_thumbnail(instance) - - rev_inst = instance - - else: - self.log.debug("Track item on plateMain") - rev_inst = None - for inst in instance.context[:]: - if inst.data["track"] != track: - continue - - if inst.data["item"].name() != instance.data["item"].name(): - continue - - rev_inst = inst - break - - if rev_inst is None: - raise RuntimeError(( - "TrackItem from track name `{}` has to" - "be also selected" - ).format(track)) - - instance.data["families"].append("review") - - file_path = rev_inst.data.get("sourcePath") - file_dir = os.path.dirname(file_path) - file = os.path.basename(file_path) - ext = os.path.splitext(file)[-1] - - # detect if sequence - if not is_sequence: - # is video file - files = file - else: - files = list() - source_first = instance.data["sourceFirst"] - self.log.debug("_ file: {}".format(file)) - spliter, padding = self.detect_sequence(file) - self.log.debug("_ spliter, padding: {}, {}".format( - spliter, padding)) - base_name = file.split(spliter)[0] - collection = clique.Collection(base_name, ext, padding, set(range( - int(source_first + rev_inst.data.get("sourceInH")), - int(source_first + rev_inst.data.get("sourceOutH") + 1)))) - self.log.debug("_ collection: {}".format(collection)) - real_files = os.listdir(file_dir) - for item in collection: - if item not in real_files: - continue - files.append(item) - - # change label - instance.data["label"] = "{0} - {1} - ({2})".format( - instance.data['asset'], instance.data["subset"], ext - ) - - self.log.debug("Instance review: {}".format(rev_inst.data["name"])) - - # adding representation for review mov - representation = { - "files": files, - "stagingDir": file_dir, - "frameStart": rev_inst.data.get("sourceIn"), - "frameEnd": rev_inst.data.get("sourceOut"), - "frameStartFtrack": rev_inst.data.get("sourceInH"), - "frameEndFtrack": rev_inst.data.get("sourceOutH"), - "step": 1, - "fps": rev_inst.data.get("fps"), - "name": "review", - "tags": ["review", "ftrackreview"], - "ext": ext[1:] - } - - media_duration = instance.data.get("mediaDuration") - clip_duration_h = instance.data.get("clipDurationH") - - if media_duration > clip_duration_h: - self.log.debug("Media duration higher: {}".format( - (media_duration - clip_duration_h))) - representation.update({ - "frameStart": instance.data.get("sourceInH"), - "frameEnd": instance.data.get("sourceOutH"), - "tags": ["_cut-bigger", "delete"] - }) - elif media_duration < clip_duration_h: - self.log.debug("Media duration higher: {}".format( - (media_duration - clip_duration_h))) - representation.update({ - "frameStart": instance.data.get("sourceInH"), - "frameEnd": instance.data.get("sourceOutH"), - "tags": ["_cut-smaller", "delete"] - }) - - instance.data["representations"].append(representation) - - self.log.debug("Added representation: {}".format(representation)) - - def create_thumbnail(self, instance): - item = instance.data["item"] - - source_path = instance.data["sourcePath"] - source_file = os.path.basename(source_path) - spliter, padding = self.detect_sequence(source_file) - - if spliter: - head, ext = source_file.split(spliter) - else: - head, ext = os.path.splitext(source_file) - - # staging dir creation - staging_dir = os.path.dirname( - source_path) - - media_duration = instance.data.get("mediaDuration") - clip_duration_h = instance.data.get("clipDurationH") - self.log.debug("__ media_duration: {}".format(media_duration)) - self.log.debug("__ clip_duration_h: {}".format(clip_duration_h)) - - thumb_frame = int(instance.data["sourceIn"] + ( - (instance.data["sourceOut"] - instance.data["sourceIn"]) / 2)) - - thumb_file = "{}thumbnail{}{}".format(head, thumb_frame, ".png") - thumb_path = os.path.join(staging_dir, thumb_file) - self.log.debug("__ thumb_path: {}".format(thumb_path)) - - self.log.debug("__ thumb_frame: {}".format(thumb_frame)) - self.log.debug( - "__ sourceIn: `{}`".format(instance.data["sourceIn"])) - - thumbnail = item.thumbnail(thumb_frame).save( - thumb_path, - format='png' - ) - self.log.debug( - "__ thumbnail: `{}`, frame: `{}`".format(thumbnail, thumb_frame)) - - self.log.debug("__ thumbnail: {}".format(thumbnail)) - thumb_representation = { - 'files': thumb_file, - 'stagingDir': staging_dir, - 'name': "thumbnail", - 'thumbnail': True, - 'ext': "png" - } - instance.data["representations"].append( - thumb_representation) - - def version_data(self, instance): - item = instance.data["item"] - - transfer_data = [ - "handleStart", "handleEnd", "sourceIn", "sourceOut", - "frameStart", "frameEnd", "sourceInH", "sourceOutH", - "clipIn", "clipOut", "clipInH", "clipOutH", "asset", - "track" - ] - - version_data = dict() - # pass data to version - version_data.update({k: instance.data[k] for k in transfer_data}) - - if 'version' in instance.data: - version_data["version"] = instance.data["version"] - - # add to data of representation - version_data.update({ - "colorspace": item.sourceMediaColourTransform(), - "families": instance.data["families"], - "subset": instance.data["subset"], - "fps": instance.context.data["fps"] - }) - instance.data["versionData"] = version_data - - instance.data["source"] = instance.data["sourcePath"] - - def detect_sequence(self, file): - """ Get identificating pater for image sequence - - Can find file.0001.ext, file.%02d.ext, file.####.ext - - Return: - string: any matching sequence patern - int: padding of sequnce numbering - """ - 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: - return None, None diff --git a/pype/plugins/hiero/publish/collect_selection.py b/pype/plugins/hiero/publish/collect_selection.py deleted file mode 100644 index 28a529d560a..00000000000 --- a/pype/plugins/hiero/publish/collect_selection.py +++ /dev/null @@ -1,17 +0,0 @@ -import pyblish.api - -import hiero - - -class CollectSelection(pyblish.api.ContextPlugin): - """Inject the selection in the context.""" - - order = pyblish.api.CollectorOrder - 0.1 - label = "Selection" - - def process(self, context): - selection = list(hiero.selection) - - self.log.debug("selection: {}".format(selection)) - - context.data["selection"] = selection diff --git a/pype/plugins/hiero/publish/collect_sequence.py b/pype/plugins/hiero/publish/collect_sequence.py deleted file mode 100644 index 4247c7c4cbe..00000000000 --- a/pype/plugins/hiero/publish/collect_sequence.py +++ /dev/null @@ -1,13 +0,0 @@ -from pyblish import api -import hiero - - -class CollectSequence(api.ContextPlugin): - """Collect all Track items selection.""" - - order = api.CollectorOrder - 0.01 - label = "Collect Sequence" - hosts = ["hiero"] - - def process(self, context): - context.data['activeSequence'] = hiero.ui.activeSequence() diff --git a/pype/plugins/hiero/publish/collect_shots.py b/pype/plugins/hiero/publish/collect_shots.py deleted file mode 100644 index ed658a19a9d..00000000000 --- a/pype/plugins/hiero/publish/collect_shots.py +++ /dev/null @@ -1,54 +0,0 @@ -from pyblish import api - - -class CollectShots(api.InstancePlugin): - """Collect Shot from Clip.""" - - # Run just before CollectClipSubsets - order = api.CollectorOrder + 0.1021 - label = "Collect Shots" - hosts = ["hiero"] - families = ["clip"] - - def process(self, instance): - self.log.debug( - "Skipping \"{}\" because its not tagged with " - "\"Hierarchy\"".format(instance)) - # Exclude non-tagged instances. - tagged = False - for tag in instance.data["tags"]: - if tag["name"].lower() == "hierarchy": - tagged = True - - if not tagged: - self.log.debug( - "Skipping \"{}\" because its not tagged with " - "\"Hierarchy\"".format(instance) - ) - return - - # Collect data. - data = {} - for key, value in instance.data.iteritems(): - data[key] = value - - data["family"] = "shot" - data["families"] = [] - - data["subset"] = data["family"] + "Main" - - data["name"] = data["subset"] + "_" + data["asset"] - - data["label"] = ( - "{} - {} - tasks:{} - assetbuilds:{} - comments:{}".format( - data["asset"], - data["subset"], - [task for task in data["tasks"]], - [x["name"] for x in data.get("assetbuilds", [])], - len(data.get("comments", [])) - ) - ) - - # Create instance. - self.log.debug("Creating instance with: {}".format(data["name"])) - instance.context.create_instance(**data) diff --git a/pype/plugins/hiero/publish/collect_tag_framestart.py b/pype/plugins/hiero/publish/collect_tag_framestart.py deleted file mode 100644 index 0d14271aa5a..00000000000 --- a/pype/plugins/hiero/publish/collect_tag_framestart.py +++ /dev/null @@ -1,43 +0,0 @@ -from pyblish import api -import os - -class CollectClipTagFrameStart(api.InstancePlugin): - """Collect FrameStart from Tags of selected track items.""" - - order = api.CollectorOrder + 0.013 - label = "Collect Frame Start" - hosts = ["hiero"] - families = ['clip'] - - def process(self, instance): - # gets tags - tags = instance.data["tags"] - - for t in tags: - t_metadata = dict(t["metadata"]) - t_family = t_metadata.get("tag.family", "") - - # gets only task family tags and collect labels - if "frameStart" in t_family: - t_value = t_metadata.get("tag.value", None) - - # backward compatibility - t_number = t_metadata.get("tag.number", None) - start_frame = t_number or t_value - - try: - start_frame = int(start_frame) - except ValueError: - if "source" in t_value: - source_first = instance.data["sourceFirst"] - if source_first == 0: - source_first = 1 - self.log.info("Start frame on `{0}`".format(source_first)) - source_in = instance.data["sourceIn"] - self.log.info("Start frame on `{0}`".format(source_in)) - start_frame = source_first + source_in - - instance.data["startingFrame"] = start_frame - self.log.info("Start frame on `{0}` set to `{1}`".format( - instance, start_frame - )) diff --git a/pype/plugins/hiero/publish/collect_tag_handles.py b/pype/plugins/hiero/publish/collect_tag_handles.py deleted file mode 100644 index 7e51efd8e1e..00000000000 --- a/pype/plugins/hiero/publish/collect_tag_handles.py +++ /dev/null @@ -1,64 +0,0 @@ -import json -from pyblish import api - - -class CollectClipTagHandles(api.ContextPlugin): - """Collect Handles from selected track items.""" - - order = api.CollectorOrder + 0.012 - label = "Collect Tag Handles" - hosts = ["hiero"] - families = ['clip'] - - def process(self, context): - assets_shared = context.data.get("assetsShared") - for instance in context[:]: - self.log.info("Instance.name: `{}`".format( - instance.data["name"])) - # gets tags - tags = instance.data["tags"] - assets_shared_a = assets_shared[instance.data["asset"]] - tag_occurance = 0 - for t in tags: - t_metadata = dict(t["metadata"]) - t_family = t_metadata.get("tag.family", "") - - # gets only task family tags and collect labels - if "handles" in t_family: - tag_occurance += 1 - - # restore handleStart/End to 0 at first occurance of Tag - if tag_occurance == 1: - instance.data["handleTag"] = True - instance.data["handleStart"] = 0 - instance.data["handleEnd"] = 0 - - # gets value of handles - t_value = int(t_metadata.get("tag.value", "")) - - # gets arguments if there are any - t_args = t_metadata.get("tag.args", "") - assert t_args, self.log.error( - "Tag with Handles is missing Args. " - "Use only handle start/end") - - t_args = json.loads(t_args.replace("'", "\"")) - # add in start - if 'start' in t_args['where']: - instance.data["handleStart"] += t_value - self.log.info("Collected Handle Start: `{}`".format( - instance.data["handleStart"])) - - # add in end - if 'end' in t_args['where']: - instance.data["handleEnd"] += t_value - self.log.info("Collected Handle End: `{}`".format( - instance.data["handleEnd"])) - - # adding handles to asset_shared on context - if instance.data.get("handleEnd"): - assets_shared_a[ - "handleEnd"] = instance.data["handleEnd"] - if instance.data.get("handleStart"): - assets_shared_a[ - "handleStart"] = instance.data["handleStart"] diff --git a/pype/plugins/hiero/publish/collect_tag_resolution.py b/pype/plugins/hiero/publish/collect_tag_resolution.py deleted file mode 100644 index ef46d9d5947..00000000000 --- a/pype/plugins/hiero/publish/collect_tag_resolution.py +++ /dev/null @@ -1,22 +0,0 @@ -from pyblish import api - - -class CollectClipTagResolution(api.InstancePlugin): - """Collect Source Resolution from Tags of selected track items.""" - - order = api.CollectorOrder + 0.013 - label = "Collect Source Resolution" - hosts = ["hiero"] - families = ['clip'] - - def process(self, instance): - # gets tags - tags = instance.data["tags"] - - for t in tags: - t_metadata = dict(t["metadata"]) - t_family = t_metadata.get("tag.family", "") - - # gets only task family tags and collect labels - if "resolution" in t_family: - instance.data["sourceResolution"] = True diff --git a/pype/plugins/hiero/publish/collect_tag_subsets.py b/pype/plugins/hiero/publish/collect_tag_subsets.py deleted file mode 100644 index d3d247727ba..00000000000 --- a/pype/plugins/hiero/publish/collect_tag_subsets.py +++ /dev/null @@ -1,28 +0,0 @@ -from pyblish import api - - -class CollectClipSubsetsTags(api.InstancePlugin): - """Collect Subsets from Tags of selected track items.""" - - order = api.CollectorOrder + 0.012 - label = "Collect Tags Subsets" - hosts = ["hiero"] - families = ['clip'] - - def process(self, instance): - # gets tags - tags = instance.data["tags"] - - for t in tags: - t_metadata = dict(t["metadata"]) - t_family = t_metadata.get("tag.family", None) - t_subset = t_metadata.get("tag.subset", None) - - # gets only task family tags and collect labels - if t_subset and t_family: - subset_name = "{0}{1}".format( - t_family, - t_subset.capitalize()) - instance.data['subset'] = subset_name - - self.log.info("`subset`: {0} found in `instance.name`: `{1}`".format(subset_name, instance.data["name"])) diff --git a/pype/plugins/hiero/publish/collect_tags.py b/pype/plugins/hiero/publish/collect_tags.py deleted file mode 100644 index fde6f7a26b8..00000000000 --- a/pype/plugins/hiero/publish/collect_tags.py +++ /dev/null @@ -1,30 +0,0 @@ -from pyblish import api - - -class CollectClipTags(api.InstancePlugin): - """Collect Tags from selected track items.""" - - order = api.CollectorOrder + 0.011 - label = "Collect Tags" - hosts = ["hiero"] - families = ['clip'] - - def process(self, instance): - tags = instance.data["item"].tags() - - tags_d = [] - if tags: - for t in tags: - tag_data = { - "name": t.name(), - "object": t, - "metadata": t.metadata(), - "inTime": t.inTime(), - "outTime": t.outTime(), - } - tags_d.append(tag_data) - - instance.data["tags"] = tags_d - - self.log.info(instance.data["tags"]) - return diff --git a/pype/plugins/hiero/publish/extract_effects.py b/pype/plugins/hiero/publish/extract_effects.py deleted file mode 100644 index 5c9ee97f2b5..00000000000 --- a/pype/plugins/hiero/publish/extract_effects.py +++ /dev/null @@ -1,260 +0,0 @@ -# from pype import plugins -import os -import json -import re -import copy -import pyblish.api -import tempfile -from avalon import io, api - - -class ExtractVideoTracksLuts(pyblish.api.InstancePlugin): - """Collect video tracks effects into context.""" - - order = pyblish.api.ExtractorOrder - label = "Export Soft Lut Effects" - families = ["lut"] - - def process(self, instance): - item = instance.data["item"] - effects = instance.data.get("effectTrackItems") - - instance.data["families"] = [f for f in instance.data.get( - "families", []) if f not in ["lut"]] - - self.log.debug( - "__ instance.data[families]: `{}`".format( - instance.data["families"])) - - # skip any without effects - if not effects: - return - - subset = instance.data.get("subset") - subset_split = re.findall(r'[A-Z][^A-Z]*', subset) - - if len(subset_split) > 0: - root_name = subset.replace(subset_split[0], "") - subset_split.insert(0, root_name.capitalize()) - - subset_split.insert(0, "lut") - - self.log.debug("creating staging dir") - # staging_dir = self.staging_dir(instance) - - # TODO: only provisory will be replace by function - staging_dir = instance.data.get('stagingDir', None) - - if not staging_dir: - staging_dir = os.path.normpath( - tempfile.mkdtemp(prefix="pyblish_tmp_") - ) - instance.data['stagingDir'] = staging_dir - - self.log.debug("creating staging dir: `{}`".format(staging_dir)) - - transfers = list() - if "transfers" not in instance.data: - instance.data["transfers"] = list() - - name = "".join(subset_split) - ext = "json" - file = name + "." + ext - - # create new instance and inherit data - data = {} - for key, value in instance.data.iteritems(): - data[key] = value - - # change names - data["subset"] = name - data["family"] = "lut" - data["families"] = [] - data["name"] = data["subset"] + "_" + data["asset"] - data["label"] = "{} - {} - ({})".format( - data['asset'], data["subset"], os.path.splitext(file)[1] - ) - data["source"] = data["sourcePath"] - - # WARNING instance should not be created in Extractor! - # create new instance - instance = instance.context.create_instance(**data) - # TODO replace line below with `instance.data["resourcesDir"]` - # when instance is created during collection part - dst_dir = self.resource_destination_dir(instance) - - # change paths in effects to files - for k, effect in effects["effects"].items(): - trn = self.copy_linked_files(effect, dst_dir) - if trn: - transfers.append((trn[0], trn[1])) - - instance.data["transfers"].extend(transfers) - self.log.debug("_ transfers: `{}`".format( - instance.data["transfers"])) - - # create representations - instance.data["representations"] = list() - - transfer_data = [ - "handleStart", "handleEnd", "sourceIn", "sourceOut", - "frameStart", "frameEnd", "sourceInH", "sourceOutH", - "clipIn", "clipOut", "clipInH", "clipOutH", "asset", "track", - "version" - ] - - # pass data to version - version_data = dict() - version_data.update({k: instance.data[k] for k in transfer_data}) - - # add to data of representation - version_data.update({ - "colorspace": item.sourceMediaColourTransform(), - "colorspaceScript": instance.context.data["colorspace"], - "families": ["plate", "lut"], - "subset": name, - "fps": instance.context.data["fps"] - }) - instance.data["versionData"] = version_data - - representation = { - 'files': file, - 'stagingDir': staging_dir, - 'name': "lut" + ext.title(), - 'ext': ext - } - instance.data["representations"].append(representation) - - self.log.debug("_ representations: `{}`".format( - instance.data["representations"])) - - self.log.debug("_ version_data: `{}`".format( - instance.data["versionData"])) - - with open(os.path.join(staging_dir, file), "w") as outfile: - outfile.write(json.dumps(effects, indent=4, sort_keys=True)) - - return - - def copy_linked_files(self, effect, dst_dir): - for k, v in effect["node"].items(): - if k in "file" and v != '': - base_name = os.path.basename(v) - dst = os.path.join(dst_dir, base_name).replace("\\", "/") - - # add it to the json - effect["node"][k] = dst - return (v, dst) - - def resource_destination_dir(self, instance): - # WARNING this is from `collect_instance_anatomy_data.py` - anatomy_data = copy.deepcopy(instance.context.data["anatomyData"]) - project_entity = instance.context.data["projectEntity"] - context_asset_entity = instance.context.data["assetEntity"] - - asset_name = instance.data["asset"] - if context_asset_entity["name"] == asset_name: - asset_entity = context_asset_entity - - else: - asset_entity = io.find_one({ - "type": "asset", - "name": asset_name, - "parent": project_entity["_id"] - }) - - subset_name = instance.data["subset"] - version_number = instance.data.get("version") - latest_version = None - - if asset_entity: - subset_entity = io.find_one({ - "type": "subset", - "name": subset_name, - "parent": asset_entity["_id"] - }) - - if subset_entity is None: - self.log.debug("Subset entity does not exist yet.") - else: - version_entity = io.find_one( - { - "type": "version", - "parent": subset_entity["_id"] - }, - sort=[("name", -1)] - ) - if version_entity: - latest_version = version_entity["name"] - - if version_number is None: - version_number = 1 - if latest_version is not None: - version_number += int(latest_version) - - anatomy_data.update({ - "asset": asset_name, - "family": instance.data["family"], - "subset": subset_name, - "version": version_number, - "hierarchy": instance.data["hierarchy"] - }) - - resolution_width = instance.data.get("resolutionWidth") - if resolution_width: - anatomy_data["resolution_width"] = resolution_width - - resolution_height = instance.data.get("resolutionHeight") - if resolution_height: - anatomy_data["resolution_height"] = resolution_height - - pixel_aspect = instance.data.get("pixelAspect") - if pixel_aspect: - anatomy_data["pixel_aspect"] = float("{:0.2f}".format(pixel_aspect)) - - fps = instance.data.get("fps") - if resolution_height: - anatomy_data["fps"] = float("{:0.2f}".format(fps)) - - instance.data["projectEntity"] = project_entity - instance.data["assetEntity"] = asset_entity - instance.data["anatomyData"] = anatomy_data - instance.data["latestVersion"] = latest_version - instance.data["version"] = version_number - - # WARNING this is from `collect_resources_path.py` - anatomy = instance.context.data["anatomy"] - - template_data = copy.deepcopy(instance.data["anatomyData"]) - - # This is for cases of Deprecated anatomy without `folder` - # TODO remove when all clients have solved this issue - template_data.update({ - "frame": "FRAME_TEMP", - "representation": "TEMP" - }) - - anatomy_filled = anatomy.format(template_data) - - if "folder" in anatomy.templates["publish"]: - publish_folder = anatomy_filled["publish"]["folder"] - else: - # solve deprecated situation when `folder` key is not underneath - # `publish` anatomy - project_name = api.Session["AVALON_PROJECT"] - self.log.warning(( - "Deprecation warning: Anatomy does not have set `folder`" - " key underneath `publish` (in global of for project `{}`)." - ).format(project_name)) - - file_path = anatomy_filled["publish"]["path"] - # Directory - publish_folder = os.path.dirname(file_path) - - publish_folder = os.path.normpath(publish_folder) - resources_folder = os.path.join(publish_folder, "resources") - - instance.data["publishDir"] = publish_folder - instance.data["resourcesDir"] = resources_folder - - return resources_folder diff --git a/pype/settings/defaults/project_settings/hiero.json b/pype/settings/defaults/project_settings/hiero.json index e4e65eedd38..b69bc664571 100644 --- a/pype/settings/defaults/project_settings/hiero.json +++ b/pype/settings/defaults/project_settings/hiero.json @@ -1,4 +1,22 @@ { + "create": { + "CreateShotClip": { + "hierarchy": "{folder}/{sequence}", + "clipRename": true, + "clipName": "{track}{sequence}{shot}", + "countFrom": 10, + "countSteps": 10, + "folder": "shots", + "episode": "ep01", + "sequence": "sq01", + "track": "{_track_}", + "shot": "sh###", + "vSyncOn": false, + "workfileFrameStart": 1001, + "handleStart": 10, + "handleEnd": 10 + } + }, "publish": { "CollectInstanceVersion": { "enabled": false diff --git a/pype/settings/defaults/system_settings/applications.json b/pype/settings/defaults/system_settings/applications.json index c4acd5f4fe7..01f1536620e 100644 --- a/pype/settings/defaults/system_settings/applications.json +++ b/pype/settings/defaults/system_settings/applications.json @@ -492,7 +492,7 @@ ] }, "HIERO_PLUGIN_PATH": [ - "{PYPE_ROOT}/setup/hiero/hiero_plugin_path" + "{PYPE_ROOT}/pype/hosts/hiero/startup" ], "PATH": { "windows": "C:/Program Files (x86)/QuickTime/QTSystem/;{PATH}" @@ -614,7 +614,7 @@ ] }, "HIERO_PLUGIN_PATH": [ - "{PYPE_ROOT}/setup/hiero/hiero_plugin_path" + "{PYPE_ROOT}/pype/hosts/hiero/startup" ], "PATH": { "windows": "C:/Program Files (x86)/QuickTime/QTSystem/;{PATH}" @@ -813,6 +813,8 @@ }, "PYTHONPATH": [ "{PYTHON36_RESOLVE}/Lib/site-packages", + "{VIRTUAL_ENV}/Lib/site-packages", + "{PYTHONPATH}", "{RESOLVE_SCRIPT_API}/Modules", "{PYTHONPATH}" ], @@ -1386,4 +1388,4 @@ } } } -} \ No newline at end of file +} diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_hiero.json b/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_hiero.json index e55e652cb6d..b6276ccf9ef 100644 --- a/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_hiero.json +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_hiero.json @@ -5,6 +5,121 @@ "label": "Hiero", "is_file": true, "children": [ + { + "type": "dict", + "collapsable": true, + "key": "create", + "label": "Create plugins", + "children": [ + { + "type": "dict", + "collapsable": true, + "key": "CreateShotClip", + "label": "Create Shot Clip", + "is_group": true, + "children": [ + { + "type": "collapsible-wrap", + "label": "Shot Hierarchy And Rename Settings", + "collapsable": false, + "children": [ + { + "type": "text", + "key": "hierarchy", + "label": "Shot parent hierarchy" + }, + { + "type": "boolean", + "key": "clipRename", + "label": "Rename clips" + }, + { + "type": "text", + "key": "clipName", + "label": "Clip name template" + }, + { + "type": "number", + "key": "countFrom", + "label": "Count sequence from" + }, + { + "type": "number", + "key": "countSteps", + "label": "Stepping number" + } + ] + }, + { + "type": "collapsible-wrap", + "label": "Shot Template Keywords", + "collapsable": false, + "children": [ + { + "type": "text", + "key": "folder", + "label": "{folder}" + }, + { + "type": "text", + "key": "episode", + "label": "{episode}" + }, + { + "type": "text", + "key": "sequence", + "label": "{sequence}" + }, + { + "type": "text", + "key": "track", + "label": "{track}" + }, + { + "type": "text", + "key": "shot", + "label": "{shot}" + } + ] + }, + { + "type": "collapsible-wrap", + "label": "Vertical Synchronization Of Attributes", + "collapsable": false, + "children": [ + { + "type": "boolean", + "key": "vSyncOn", + "label": "Enable Vertical Sync" + } + ] + }, + { + "type": "collapsible-wrap", + "label": "Shot Attributes", + "collapsable": false, + "children": [ + { + "type": "number", + "key": "workfileFrameStart", + "label": "Workfiles Start Frame" + }, + { + "type": "number", + "key": "handleStart", + "label": "Handle start (head)" + }, + { + "type": "number", + "key": "handleEnd", + "label": "Handle end (tail)" + } + ] + } + ] + } + ] + }, { "type": "dict", "collapsable": true, diff --git a/repos/avalon-core b/repos/avalon-core index 44272630062..93c4a89b3e9 160000 --- a/repos/avalon-core +++ b/repos/avalon-core @@ -1 +1 @@ -Subproject commit 44272630062ba5e440cdc6c2a5864328d736ae73 +Subproject commit 93c4a89b3e98ae88030e62bb9ad6c07220b23234