diff --git a/pype/nukestudio/__init__.py b/pype/nukestudio/__init__.py index a1ee6dedf7..f3ef69608f 100644 --- a/pype/nukestudio/__init__.py +++ b/pype/nukestudio/__init__.py @@ -19,27 +19,18 @@ import nuke from pypeapp import Logger -# #removing logger handler created in avalon_core -# for name, handler in [(handler.get_name(), handler) -# for handler in Logger.logging.root.handlers[:]]: -# if "pype" not in str(name).lower(): -# Logger.logging.root.removeHandler(handler) - - log = Logger().get_logger(__name__, "nuke") -# log = api.Logger.getLogger(__name__, "nuke") - AVALON_CONFIG = os.getenv("AVALON_CONFIG", "pype") PARENT_DIR = os.path.dirname(__file__) PACKAGE_DIR = os.path.dirname(PARENT_DIR) PLUGINS_DIR = os.path.join(PACKAGE_DIR, "plugins") -PUBLISH_PATH = os.path.join(PLUGINS_DIR, "nuke", "publish") -LOAD_PATH = os.path.join(PLUGINS_DIR, "nuke", "load") -CREATE_PATH = os.path.join(PLUGINS_DIR, "nuke", "create") -INVENTORY_PATH = os.path.join(PLUGINS_DIR, "nuke", "inventory") +PUBLISH_PATH = os.path.join(PLUGINS_DIR, "nukestudio", "publish") +LOAD_PATH = os.path.join(PLUGINS_DIR, "nukestudio", "load") +CREATE_PATH = os.path.join(PLUGINS_DIR, "nukestudio", "create") +INVENTORY_PATH = os.path.join(PLUGINS_DIR, "nukestudio", "inventory") self = sys.modules[__name__] self.nLogger = None @@ -48,42 +39,6 @@ if os.getenv("PYBLISH_GUI", None): pyblish.register_gui(os.getenv("PYBLISH_GUI", None)) -# class NukeHandler(Logger.logging.Handler): -# ''' -# Nuke Handler - emits logs into nuke's script editor. -# warning will emit nuke.warning() -# critical and fatal would popup msg dialog to alert of the error. -# ''' -# -# def __init__(self): -# api.Logger.logging.Handler.__init__(self) -# self.set_name("Pype_Nuke_Handler") -# -# def emit(self, record): -# # Formated message: -# msg = self.format(record) -# -# if record.levelname.lower() in [ -# # "warning", -# "critical", -# "fatal", -# "error" -# ]: -# nuke.message(msg) - -# -# '''Adding Nuke Logging Handler''' -# nuke_handler = NukeHandler() -# if nuke_handler.get_name() \ -# not in [handler.get_name() -# for handler in Logger.logging.root.handlers[:]]: -# api.Logger.logging.getLogger().addHandler(nuke_handler) -# api.Logger.logging.getLogger().setLevel(Logger.logging.INFO) -# -# if not self.nLogger: -# self.nLogger = Logger - - def reload_config(): """Attempt to reload pipeline at run-time. diff --git a/pype/nukestudio/menu.py b/pype/nukestudio/menu.py index 9180c924ba..b62a20559d 100644 --- a/pype/nukestudio/menu.py +++ b/pype/nukestudio/menu.py @@ -1,7 +1,83 @@ -import nuke from avalon.api import Session -from pype.nuke import lib +from pype.nukestudio import lib +import hiero.core + +try: + from PySide.QtGui import * +except Exception: + from PySide2.QtGui import * + from PySide2.QtWidgets import * + +from hiero.ui import findMenuAction + + +# def install(): + # here is the best place to add menu + from avalon.tools import ( + creator, + publish, + workfiles, + cbloader, + cbsceneinventory, + contextmanager, + libraryloader + ) + + menu_name = os.environ['PYPE_STUDIO_NAME'] + # Grab Hiero's MenuBar + M = hiero.ui.menuBar() + + # Add a Menu to the MenuBar + file_action = None + try: + check_made_menu = findMenuAction(menu_name) + except: + pass + + if not check_made_menu: + menu = M.addMenu(menu_name) + else: + menu = check_made_menu.menu() + + actions = [{ + 'action': QAction(QIcon('icons:Position.png'), 'Set Context', None), + 'function': contextmanager.show + }, + { + 'action': QAction(QIcon('icons:ColorAdd.png'), 'Create...', None), + 'function': creator.show + }, + { + 'action': QAction(QIcon('icons:CopyRectangle.png'), 'Load...', None), + 'function': cbloader.show + }, + { + 'action': QAction(QIcon('icons:Output.png'), 'Publish...', None), + 'function': publish.show + }, + { + 'action': QAction(QIcon('icons:ModifyMetaData.png'), 'Manage...', None), + 'function': cbsceneinventory.show + }, + { + 'action': QAction(QIcon('icons:ColorAdd.png'), 'Library...', None), + 'function': libraryloader.show + }] + + + # Create menu items + for a in actions: + # create action + for k in a.keys(): + if 'action' in k: + action = a[k] + elif 'function' in k: + action.triggered.connect(a[k]) + else: + pass + # add action to menu + menu.addAction(action) diff --git a/setup/nukestudio/hiero_plugin_path/HieroPlayer/PlayerPresets.hrox b/setup/nukestudio/hiero_plugin_path/HieroPlayer/PlayerPresets.hrox new file mode 100644 index 0000000000..ec50e123f0 --- /dev/null +++ b/setup/nukestudio/hiero_plugin_path/HieroPlayer/PlayerPresets.hrox @@ -0,0 +1,1108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + 70 + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + 70 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + 70 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + 70 + + + 2 + 70 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + 70 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + 70 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + 70 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + 70 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + 50 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + 70 + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + 70 + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + 70 + + + 2 + 70 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + 70 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + 70 + + + 2 + 70 + + + 2 + 70 + 17 + + + 126935040 + 70 + -1 + + + 2 + 70 + 2 + + + + + + diff --git a/setup/nukestudio/hiero_plugin_path/Python/Startup/SpreadsheetExport.py b/setup/nukestudio/hiero_plugin_path/Python/Startup/SpreadsheetExport.py new file mode 100644 index 0000000000..3adea8051c --- /dev/null +++ b/setup/nukestudio/hiero_plugin_path/Python/Startup/SpreadsheetExport.py @@ -0,0 +1,140 @@ +# This action adds itself to the Spreadsheet View context menu allowing the contents of the Spreadsheet be exported as a CSV file. +# Usage: Right-click in Spreadsheet > "Export as .CSV" +# Note: This only prints the text data that is visible in the active Spreadsheet View. +# If you've filtered text, only the visible text will be printed to the CSV file +# Usage: Copy to ~/.hiero/Python/StartupUI +import hiero.core.events +import hiero.ui +import os, csv +try: + from PySide.QtGui import * + from PySide.QtCore import * +except: + from PySide2.QtGui import * + from PySide2.QtWidgets import * + from PySide2.QtCore import * + + +### Magic Widget Finding Methods - This stuff crawls all the PySide widgets, looking for an answer +def findWidget(w): + global foundryWidgets + if 'Foundry' in w.metaObject().className(): + foundryWidgets += [w] + + for c in w.children(): + findWidget(c) + return foundryWidgets + + +def getFoundryWidgetsWithClassName(filter=None): + global foundryWidgets + foundryWidgets = [] + widgets = [] + app = QApplication.instance() + for w in app.topLevelWidgets(): + findWidget(w) + + filteredWidgets = foundryWidgets + if filter: + filteredWidgets = [] + for widget in foundryWidgets: + if filter in widget.metaObject().className(): + filteredWidgets += [widget] + return filteredWidgets + + +# When right click, get the Sequence Name +def activeSpreadsheetTreeView(): + """ + Does some PySide widget Magic to detect the Active Spreadsheet TreeView. + """ + spreadsheetViews = getFoundryWidgetsWithClassName( + filter='SpreadsheetTreeView') + for spreadSheet in spreadsheetViews: + if spreadSheet.hasFocus(): + activeSpreadSheet = spreadSheet + return activeSpreadSheet + return None + + +#### Adds "Export .CSV" action to the Spreadsheet Context menu #### +class SpreadsheetExportCSVAction(QAction): + def __init__(self): + QAction.__init__(self, "Export as .CSV", None) + self.triggered.connect(self.exportCSVFromActiveSpreadsheetView) + hiero.core.events.registerInterest("kShowContextMenu/kSpreadsheet", + self.eventHandler) + self.setIcon(QIcon("icons:FBGridView.png")) + + def eventHandler(self, event): + # Insert the action to the Export CSV menu + event.menu.addAction(self) + + #### The guts!.. Writes a CSV file from a Sequence Object #### + def exportCSVFromActiveSpreadsheetView(self): + + # Get the active QTreeView from the active Spreadsheet + spreadsheetTreeView = activeSpreadsheetTreeView() + + if not spreadsheetTreeView: + return 'Unable to detect the active TreeView.' + seq = hiero.ui.activeView().sequence() + if not seq: + print 'Unable to detect the active Sequence from the activeView.' + return + + # The data model of the QTreeView + model = spreadsheetTreeView.model() + + csvSavePath = os.path.join(QDir.homePath(), 'Desktop', + seq.name() + '.csv') + savePath, filter = QFileDialog.getSaveFileName( + None, + caption="Export Spreadsheet to .CSV as...", + dir=csvSavePath, + filter="*.csv") + print 'Saving To: ' + str(savePath) + + # Saving was cancelled... + if len(savePath) == 0: + return + + # Get the Visible Header Columns from the QTreeView + + #csvHeader = ['Event', 'Status', 'Shot Name', 'Reel', 'Track', 'Speed', 'Src In', 'Src Out','Src Duration', 'Dst In', 'Dst Out', 'Dst Duration', 'Clip', 'Clip Media'] + + # Get a CSV writer object + f = open(savePath, 'w') + csvWriter = csv.writer( + f, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL) + + # This is a list of the Column titles + csvHeader = [] + + for col in range(0, model.columnCount()): + if not spreadsheetTreeView.isColumnHidden(col): + csvHeader += [model.headerData(col, Qt.Horizontal)] + + # Write the Header row to the CSV file + csvWriter.writerow(csvHeader) + + # Go through each row/column and print + for row in range(model.rowCount()): + row_data = [] + for col in range(model.columnCount()): + if not spreadsheetTreeView.isColumnHidden(col): + row_data.append( + model.index(row, col, QModelIndex()).data( + Qt.DisplayRole)) + + # Write row to CSV file... + csvWriter.writerow(row_data) + + f.close() + # Conveniently show the CSV file in the native file browser... + QDesktopServices.openUrl( + QUrl('file:///%s' % (os.path.dirname(savePath)))) + + +# Add the action... +csvActions = SpreadsheetExportCSVAction() diff --git a/setup/nukestudio/hiero_plugin_path/Python/Startup/setFrameRate.py b/setup/nukestudio/hiero_plugin_path/Python/Startup/setFrameRate.py new file mode 100644 index 0000000000..ceb96a6fce --- /dev/null +++ b/setup/nukestudio/hiero_plugin_path/Python/Startup/setFrameRate.py @@ -0,0 +1,164 @@ +# setFrameRate - adds a Right-click menu to the Project Bin view, allowing multiple BinItems (Clips/Sequences) to have their frame rates set. +# Install in: ~/.hiero/Python/StartupUI +# Requires 1.5v1 or later + +import hiero.core +import hiero.ui +try: + from PySide.QtGui import * + from PySide.QtCore import * +except: + from PySide2.QtGui import * + from PySide2.QtCore import * + from PySide2.QtWidgets import * + +# Dialog for setting a Custom frame rate. +class SetFrameRateDialog(QDialog): + + def __init__(self,itemSelection=None,parent=None): + super(SetFrameRateDialog, self).__init__(parent) + self.setWindowTitle("Set Custom Frame Rate") + self.setSizePolicy( QSizePolicy.Expanding, QSizePolicy.Fixed ) + layout = QFormLayout() + self._itemSelection = itemSelection + + self._frameRateField = QLineEdit() + self._frameRateField.setToolTip('Enter custom frame rate here.') + self._frameRateField.setValidator(QDoubleValidator(1, 99, 3, self)) + self._frameRateField.textChanged.connect(self._textChanged) + layout.addRow("Enter fps: ",self._frameRateField) + + # Standard buttons for Add/Cancel + self._buttonbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + self._buttonbox.accepted.connect(self.accept) + self._buttonbox.rejected.connect(self.reject) + self._buttonbox.button(QDialogButtonBox.Ok).setEnabled(False) + layout.addRow("",self._buttonbox) + self.setLayout(layout) + + def _updateOkButtonState(self): + # Cancel is always an option but only enable Ok if there is some text. + currentFramerate = float(self.currentFramerateString()) + enableOk = False + enableOk = ((currentFramerate > 0.0) and (currentFramerate <= 250.0)) + print 'enabledOk',enableOk + self._buttonbox.button(QDialogButtonBox.Ok).setEnabled(enableOk) + + def _textChanged(self, newText): + self._updateOkButtonState() + + # Returns the current frame rate as a string + def currentFramerateString(self): + return str(self._frameRateField.text()) + + # Presents the Dialog and sets the Frame rate from a selection + def showDialogAndSetFrameRateFromSelection(self): + + if self._itemSelection is not None: + if self.exec_(): + # For the Undo loop... + + # Construct an TimeBase object for setting the Frame Rate (fps) + fps = hiero.core.TimeBase().fromString(self.currentFramerateString()) + + + # Set the frame rate for the selected BinItmes + for item in self._itemSelection: + item.setFramerate(fps) + return + +# This is just a convenience method for returning QActions with a title, triggered method and icon. +def makeAction(title, method, icon = None): + action = QAction(title,None) + action.setIcon(QIcon(icon)) + + # We do this magic, so that the title string from the action is used to set the frame rate! + def methodWrapper(): + method(title) + + action.triggered.connect( methodWrapper ) + return action + +# Menu which adds a Set Frame Rate Menu to Project Bin view +class SetFrameRateMenu: + + def __init__(self): + self._frameRateMenu = None + self._frameRatesDialog = None + + + # ant: Could use hiero.core.defaultFrameRates() here but messes up with string matching because we seem to mix decimal points + self.frameRates = ['8','12','12.50','15','23.98','24','25','29.97','30','48','50','59.94','60'] + hiero.core.events.registerInterest("kShowContextMenu/kBin", self.binViewEventHandler) + + self.menuActions = [] + + def createFrameRateMenus(self,selection): + selectedClipFPS = [str(bi.activeItem().framerate()) for bi in selection if (isinstance(bi,hiero.core.BinItem) and hasattr(bi,'activeItem'))] + selectedClipFPS = hiero.core.util.uniquify(selectedClipFPS) + sameFrameRate = len(selectedClipFPS)==1 + self.menuActions = [] + for fps in self.frameRates: + if fps in selectedClipFPS: + if sameFrameRate: + self.menuActions+=[makeAction(fps,self.setFrameRateFromMenuSelection, icon="icons:Ticked.png")] + else: + self.menuActions+=[makeAction(fps,self.setFrameRateFromMenuSelection, icon="icons:remove active.png")] + else: + self.menuActions+=[makeAction(fps,self.setFrameRateFromMenuSelection, icon=None)] + + # Now add Custom... menu + self.menuActions+=[makeAction('Custom...',self.setFrameRateFromMenuSelection, icon=None)] + + frameRateMenu = QMenu("Set Frame Rate") + for a in self.menuActions: + frameRateMenu.addAction(a) + + return frameRateMenu + + def setFrameRateFromMenuSelection(self, menuSelectionFPS): + + selectedBinItems = [bi.activeItem() for bi in self._selection if (isinstance(bi,hiero.core.BinItem) and hasattr(bi,'activeItem'))] + currentProject = selectedBinItems[0].project() + + with currentProject.beginUndo("Set Frame Rate"): + if menuSelectionFPS == 'Custom...': + self._frameRatesDialog = SetFrameRateDialog(itemSelection = selectedBinItems ) + self._frameRatesDialog.showDialogAndSetFrameRateFromSelection() + + else: + for b in selectedBinItems: + b.setFramerate(hiero.core.TimeBase().fromString(menuSelectionFPS)) + + return + + # This handles events from the Project Bin View + def binViewEventHandler(self,event): + if not hasattr(event.sender, 'selection'): + # Something has gone wrong, we should only be here if raised + # by the Bin view which gives a selection. + return + + # Reset the selection to None... + self._selection = None + s = event.sender.selection() + + # Return if there's no Selection. We won't add the Menu. + if s == None: + return + # Filter the selection to BinItems + self._selection = [item for item in s if isinstance(item, hiero.core.BinItem)] + if len(self._selection)==0: + return + # Creating the menu based on items selected, to highlight which frame rates are contained + + self._frameRateMenu = self.createFrameRateMenus(self._selection) + + # Insert the Set Frame Rate Button before the Set Media Colour Transform Action + for action in event.menu.actions(): + if str(action.text()) == "Set Media Colour Transform": + event.menu.insertMenu(action, self._frameRateMenu) + break + +# Instantiate the Menu to get it to register itself. +SetFrameRateMenu = SetFrameRateMenu() \ No newline at end of file diff --git a/setup/nukestudio/hiero_plugin_path/Python/Startup/version_everywhere.py b/setup/nukestudio/hiero_plugin_path/Python/Startup/version_everywhere.py new file mode 100644 index 0000000000..e85e02bfa5 --- /dev/null +++ b/setup/nukestudio/hiero_plugin_path/Python/Startup/version_everywhere.py @@ -0,0 +1,352 @@ +# version_up_everywhere.py +# Adds action to enable a Clip/Shot to be Min/Max/Next/Prev versioned in all shots used in a Project. +# +# Usage: +# 1) Copy file to /Python/Startup +# 2) Right-click on Clip(s) or Bins containing Clips in in the Bin View, or on Shots in the Timeline/Spreadsheet +# 3) Set Version for all Shots > OPTION to update the version in all shots where the Clip is used in the Project. + +import hiero.core +try: + from PySide.QtGui import * + from PySide.QtCore import * +except: + from PySide2.QtGui import * + from PySide2.QtWidgets import * + from PySide2.QtCore import * + + +def whereAmI(self, searchType='TrackItem'): + """returns a list of TrackItem or Sequnece objects in the Project which contain this Clip. + By default this will return a list of TrackItems where the Clip is used in its project. + You can also return a list of Sequences by specifying the searchType to be 'Sequence'. + Should consider putting this into hiero.core.Clip by default? + + Example usage: + + shotsForClip = clip.whereAmI('TrackItem') + sequencesForClip = clip.whereAmI('Sequence') + """ + proj = self.project() + + if ('TrackItem' not in searchType) and ('Sequence' not in searchType): + print "searchType argument must be 'TrackItem' or 'Sequence'" + return None + + # If user specifies a TrackItem, then it will return + searches = hiero.core.findItemsInProject(proj, searchType) + + if len(searches) == 0: + print 'Unable to find %s in any items of type: %s' % (str(self), + str(searchType)) + return None + + # Case 1: Looking for Shots (trackItems) + clipUsedIn = [] + if isinstance(searches[0], hiero.core.TrackItem): + for shot in searches: + # We have to wrap this in a try/except because it's possible through the Python API for a Shot to exist without a Clip in the Bin + try: + + # For versioning to work, we must look to the BinItem that a Clip is wrapped in. + if shot.source().binItem() == self.binItem(): + clipUsedIn.append(shot) + + # If we throw an exception here its because the Shot did not have a Source Clip in the Bin. + except RuntimeError: + hiero.core.log.info( + 'Unable to find Parent Clip BinItem for Shot: %s, Source:%s' + % (shot, shot.source())) + pass + + # Case 1: Looking for Shots (trackItems) + elif isinstance(searches[0], hiero.core.Sequence): + for seq in searches: + # Iterate tracks > shots... + tracks = seq.items() + for track in tracks: + shots = track.items() + for shot in shots: + if shot.source().binItem() == self.binItem(): + clipUsedIn.append(seq) + + return clipUsedIn + + +# Add whereAmI method to Clip object +hiero.core.Clip.whereAmI = whereAmI + + +#### MAIN VERSION EVERYWHERE GUBBINS ##### +class VersionAllMenu(object): + + # These are a set of action names we can use for operating on multiple Clip/TrackItems + eMaxVersion = "Max Version" + eMinVersion = "Min Version" + eNextVersion = "Next Version" + ePreviousVersion = "Previous Version" + + # This is the title used for the Version Menu title. It's long isn't it? + actionTitle = "Set Version for all Shots" + + def __init__(self): + self._versionEverywhereMenu = None + self._versionActions = [] + + hiero.core.events.registerInterest("kShowContextMenu/kBin", + self.binViewEventHandler) + hiero.core.events.registerInterest("kShowContextMenu/kTimeline", + self.binViewEventHandler) + hiero.core.events.registerInterest("kShowContextMenu/kSpreadsheet", + self.binViewEventHandler) + + def showVersionUpdateReportFromShotManifest(self, sequenceShotManifest): + """This just displays an info Message box, based on a Sequence[Shot] manifest dictionary""" + + # Now present an info dialog, explaining where shots were updated + updateReportString = "The following Versions were updated:\n" + for seq in sequenceShotManifest.keys(): + updateReportString += "%s:\n Shots:\n" % (seq.name()) + for shot in sequenceShotManifest[seq]: + updateReportString += ' %s\n (New Version: %s)\n' % ( + shot.name(), shot.currentVersion().name()) + updateReportString += '\n' + + infoBox = QMessageBox(hiero.ui.mainWindow()) + infoBox.setIcon(QMessageBox.Information) + + if len(sequenceShotManifest) <= 0: + infoBox.setText("No Shot Versions were updated") + infoBox.setInformativeText( + "Clip could not be found in any Shots in this Project") + else: + infoBox.setText( + "Versions were updated in %i Sequences of this Project." % + (len(sequenceShotManifest))) + infoBox.setInformativeText("Show Details for more info.") + infoBox.setDetailedText(updateReportString) + + infoBox.exec_() + + def makeVersionActionForSingleClip(self, version): + """This is used to populate the QAction list of Versions when a single Clip is selected in the BinView. + It also triggers the Version Update action based on the version passed to it. + (Not sure if this is good design practice, but it's compact!)""" + action = QAction(version.name(), None) + action.setData(lambda: version) + + def updateAllTrackItems(): + currentClip = version.item() + trackItems = currentClip.whereAmI() + if not trackItems: + return + + proj = currentClip.project() + + # A Sequence-Shot manifest dictionary + sequenceShotManifest = {} + + # Make this all undo-able in a single Group undo + with proj.beginUndo( + "Update All Versions for %s" % currentClip.name()): + for shot in trackItems: + seq = shot.parentSequence() + if seq not in sequenceShotManifest.keys(): + sequenceShotManifest[seq] = [shot] + else: + sequenceShotManifest[seq] += [shot] + shot.setCurrentVersion(version) + + # We also should update the current Version of the selected Clip for completeness... + currentClip.binItem().setActiveVersion(version) + + # Now disaplay a Dialog which informs the user of where and what was changed + self.showVersionUpdateReportFromShotManifest(sequenceShotManifest) + + action.triggered.connect(updateAllTrackItems) + return action + + # This is just a convenience method for returning QActions with a title, triggered method and icon. + def makeAction(self, title, method, icon=None): + action = QAction(title, None) + action.setIcon(QIcon(icon)) + + # We do this magic, so that the title string from the action is used to trigger the version change + def methodWrapper(): + method(title) + + action.triggered.connect(methodWrapper) + return action + + def clipSelectionFromView(self, view): + """Helper method to return a list of Clips in the Active View""" + selection = hiero.ui.activeView().selection() + + if len(selection) == 0: + return None + + if isinstance(view, hiero.ui.BinView): + # We could have a mixture of Bins and Clips selected, so sort of the Clips and Clips inside Bins + clipItems = [ + item.activeItem() for item in selection + if hasattr(item, "activeItem") + and isinstance(item.activeItem(), hiero.core.Clip) + ] + + # We'll also append Bins here, and see if can find Clips inside + bins = [ + item for item in selection if isinstance(item, hiero.core.Bin) + ] + + # We search inside of a Bin for a Clip which is not already in clipBinItems + if len(bins) > 0: + # Grab the Clips inside of a Bin and append them to a list + for bin in bins: + clips = hiero.core.findItemsInBin(bin, 'Clip') + for clip in clips: + if clip not in clipItems: + clipItems.append(clip) + + elif isinstance(view, + (hiero.ui.TimelineEditor, hiero.ui.SpreadsheetView)): + # Here, we have shots. To get to the Clip froma TrackItem, just call source() + clipItems = [ + item.source() for item in selection if hasattr(item, "source") + and isinstance(item, hiero.core.TrackItem) + ] + + return clipItems + + # This generates the Version Up Everywhere menu + def createVersionEveryWhereMenuForView(self, view): + + versionEverywhereMenu = QMenu(self.actionTitle) + self._versionActions = [] + # We look to the activeView for a selection of Clips + clips = self.clipSelectionFromView(view) + + # And bail if nothing is found + if len(clips) == 0: + return versionEverywhereMenu + + # Now, if we have just one Clip selected, we'll form a special menu, which lists all versions + if len(clips) == 1: + + # Get a reversed list of Versions, so that bigger ones appear at top + versions = list(reversed(clips[0].binItem().items())) + for version in versions: + self._versionActions += [ + self.makeVersionActionForSingleClip(version) + ] + + elif len(clips) > 1: + # We will add Max/Min/Prev/Next options, which can be called on a TrackItem, without the need for a Version object + self._versionActions += [ + self.makeAction( + self.eMaxVersion, + self.setTrackItemVersionForClipSelection, + icon=None) + ] + self._versionActions += [ + self.makeAction( + self.eMinVersion, + self.setTrackItemVersionForClipSelection, + icon=None) + ] + self._versionActions += [ + self.makeAction( + self.eNextVersion, + self.setTrackItemVersionForClipSelection, + icon=None) + ] + self._versionActions += [ + self.makeAction( + self.ePreviousVersion, + self.setTrackItemVersionForClipSelection, + icon=None) + ] + + for act in self._versionActions: + versionEverywhereMenu.addAction(act) + + return versionEverywhereMenu + + def setTrackItemVersionForClipSelection(self, versionOption): + + view = hiero.ui.activeView() + if not view: + return + + clipSelection = self.clipSelectionFromView(view) + + if len(clipSelection) == 0: + return + + proj = clipSelection[0].project() + + # Create a Sequence-Shot Manifest, to report to users where a Shot was updated + sequenceShotManifest = {} + + with proj.beginUndo("Update multiple Versions"): + for clip in clipSelection: + + # Look to see if it exists in a TrackItem somewhere... + shotUsage = clip.whereAmI('TrackItem') + + # Next, depending on the versionOption, make the appropriate update + # There's probably a more neat/compact way of doing this... + for shot in shotUsage: + + # This step is done for reporting reasons + seq = shot.parentSequence() + if seq not in sequenceShotManifest.keys(): + sequenceShotManifest[seq] = [shot] + else: + sequenceShotManifest[seq] += [shot] + + if versionOption == self.eMaxVersion: + shot.maxVersion() + elif versionOption == self.eMinVersion: + shot.minVersion() + elif versionOption == self.eNextVersion: + shot.nextVersion() + elif versionOption == self.ePreviousVersion: + shot.prevVersion() + + # Finally, for completeness, set the Max/Min version of the Clip too (if chosen) + # Note: It doesn't make sense to do Next/Prev on a Clip here because next/prev means different things for different Shots + if versionOption == self.eMaxVersion: + clip.binItem().maxVersion() + elif versionOption == self.eMinVersion: + clip.binItem().minVersion() + + # Now disaplay a Dialog which informs the user of where and what was changed + self.showVersionUpdateReportFromShotManifest(sequenceShotManifest) + + # This handles events from the Project Bin View + def binViewEventHandler(self, event): + + if not hasattr(event.sender, 'selection'): + # Something has gone wrong, we should only be here if raised + # by the Bin view which gives a selection. + return + selection = event.sender.selection() + + # Return if there's no Selection. We won't add the Localise Menu. + if selection == None: + return + + view = hiero.ui.activeView() + # Only add the Menu if Bins or Sequences are selected (this ensures menu isn't added in the Tags Pane) + if len(selection) > 0: + self._versionEverywhereMenu = self.createVersionEveryWhereMenuForView( + view) + hiero.ui.insertMenuAction( + self._versionEverywhereMenu.menuAction(), + event.menu, + after="foundry.menu.version") + return + + +# Instantiate the Menu to get it to register itself. +VersionAllMenu = VersionAllMenu() diff --git a/setup/nukestudio/hiero_plugin_path/Python/StartupUI/PimpMySpreadsheet.py b/setup/nukestudio/hiero_plugin_path/Python/StartupUI/PimpMySpreadsheet.py new file mode 100644 index 0000000000..3d40aa0293 --- /dev/null +++ b/setup/nukestudio/hiero_plugin_path/Python/StartupUI/PimpMySpreadsheet.py @@ -0,0 +1,844 @@ +# PimpMySpreadsheet 1.0, Antony Nasce, 23/05/13. +# Adds custom spreadsheet columns and right-click menu for setting the Shot Status, and Artist Shot Assignement. +# gStatusTags is a global dictionary of key(status)-value(icon) pairs, which can be overridden with custom icons if required +# Requires Hiero 1.7v2 or later. +# Install Instructions: Copy to ~/.hiero/Python/StartupUI + +import hiero.core +import hiero.ui + +try: + from PySide.QtGui import * + from PySide.QtCore import * +except: + from PySide2.QtGui import * + from PySide2.QtWidgets import * + from PySide2.QtCore import * + +# Set to True, if you wat 'Set Status' right-click menu, False if not +kAddStatusMenu = True + +# Set to True, if you wat 'Assign Artist' right-click menu, False if not +kAssignArtistMenu = True + +# Global list of Artist Name Dictionaries +# Note: Override this to add different names, icons, department, IDs. +gArtistList = [{ + 'artistName': 'John Smith', + 'artistIcon': 'icons:TagActor.png', + 'artistDepartment': '3D', + 'artistID': 0 +}, { + 'artistName': 'Savlvador Dali', + 'artistIcon': 'icons:TagActor.png', + 'artistDepartment': 'Roto', + 'artistID': 1 +}, { + 'artistName': 'Leonardo Da Vinci', + 'artistIcon': 'icons:TagActor.png', + 'artistDepartment': 'Paint', + 'artistID': 2 +}, { + 'artistName': 'Claude Monet', + 'artistIcon': 'icons:TagActor.png', + 'artistDepartment': 'Comp', + 'artistID': 3 +}, { + 'artistName': 'Pablo Picasso', + 'artistIcon': 'icons:TagActor.png', + 'artistDepartment': 'Animation', + 'artistID': 4 +}] + +# Global Dictionary of Status Tags. +# Note: This can be overwritten if you want to add a new status cellType or custom icon +# Override the gStatusTags dictionary by adding your own 'Status':'Icon.png' key-value pairs. +# Add new custom keys like so: gStatusTags['For Client'] = 'forClient.png' +gStatusTags = { + 'Approved': 'icons:status/TagApproved.png', + 'Unapproved': 'icons:status/TagUnapproved.png', + 'Ready To Start': 'icons:status/TagReadyToStart.png', + 'Blocked': 'icons:status/TagBlocked.png', + 'On Hold': 'icons:status/TagOnHold.png', + 'In Progress': 'icons:status/TagInProgress.png', + 'Awaiting Approval': 'icons:status/TagAwaitingApproval.png', + 'Omitted': 'icons:status/TagOmitted.png', + 'Final': 'icons:status/TagFinal.png' +} + + +# The Custom Spreadsheet Columns +class CustomSpreadsheetColumns(QObject): + """ + A class defining custom columns for Hiero's spreadsheet view. This has a similar, but + slightly simplified, interface to the QAbstractItemModel and QItemDelegate classes. + """ + global gStatusTags + global gArtistList + + # Ideally, we'd set this list on a Per Item basis, but this is expensive for a large mixed selection + standardColourSpaces = [ + 'linear', 'sRGB', 'rec709', 'Cineon', 'Gamma1.8', 'Gamma2.2', + 'Panalog', 'REDLog', 'ViperLog' + ] + arriColourSpaces = [ + 'Video - Rec709', 'LogC - Camera Native', 'Video - P3', 'ACES', + 'LogC - Film', 'LogC - Wide Gamut' + ] + r3dColourSpaces = [ + 'Linear', 'Rec709', 'REDspace', 'REDlog', 'PDlog685', 'PDlog985', + 'CustomPDlog', 'REDgamma', 'SRGB', 'REDlogFilm', 'REDgamma2', + 'REDgamma3' + ] + gColourSpaces = standardColourSpaces + arriColourSpaces + r3dColourSpaces + + currentView = hiero.ui.activeView() + + # This is the list of Columns available + gCustomColumnList = [ + { + 'name': 'Tags', + 'cellType': 'readonly' + }, + { + 'name': 'Colourspace', + 'cellType': 'dropdown' + }, + { + 'name': 'Notes', + 'cellType': 'readonly' + }, + { + 'name': 'FileType', + 'cellType': 'readonly' + }, + { + 'name': 'Shot Status', + 'cellType': 'dropdown' + }, + { + 'name': 'Thumbnail', + 'cellType': 'readonly' + }, + { + 'name': 'MediaType', + 'cellType': 'readonly' + }, + { + 'name': 'Width', + 'cellType': 'readonly' + }, + { + 'name': 'Height', + 'cellType': 'readonly' + }, + { + 'name': 'Pixel Aspect', + 'cellType': 'readonly' + }, + { + 'name': 'Artist', + 'cellType': 'dropdown' + }, + { + 'name': 'Department', + 'cellType': 'readonly' + }, + ] + + def numColumns(self): + """ + Return the number of custom columns in the spreadsheet view + """ + return len(self.gCustomColumnList) + + def columnName(self, column): + """ + Return the name of a custom column + """ + return self.gCustomColumnList[column]['name'] + + def getTagsString(self, item): + """ + Convenience method for returning all the Notes in a Tag as a string + """ + tagNames = [] + tags = item.tags() + for tag in tags: + tagNames += [tag.name()] + tagNameString = ','.join(tagNames) + return tagNameString + + def getNotes(self, item): + """ + Convenience method for returning all the Notes in a Tag as a string + """ + notes = '' + tags = item.tags() + for tag in tags: + note = tag.note() + if len(note) > 0: + notes += tag.note() + ', ' + return notes[:-2] + + def getData(self, row, column, item): + """ + Return the data in a cell + """ + currentColumn = self.gCustomColumnList[column] + if currentColumn['name'] == 'Tags': + return self.getTagsString(item) + + if currentColumn['name'] == 'Colourspace': + try: + colTransform = item.sourceMediaColourTransform() + except: + colTransform = '--' + return colTransform + + if currentColumn['name'] == 'Notes': + try: + note = self.getNotes(item) + except: + note = '' + return note + + if currentColumn['name'] == 'FileType': + fileType = '--' + M = item.source().mediaSource().metadata() + if M.hasKey('foundry.source.type'): + fileType = M.value('foundry.source.type') + elif M.hasKey('media.input.filereader'): + fileType = M.value('media.input.filereader') + return fileType + + if currentColumn['name'] == 'Shot Status': + status = item.status() + if not status: + status = "--" + return str(status) + + if currentColumn['name'] == 'MediaType': + M = item.mediaType() + return str(M).split('MediaType')[-1].replace('.k', '') + + if currentColumn['name'] == 'Thumbnail': + return str(item.eventNumber()) + + if currentColumn['name'] == 'Width': + return str(item.source().format().width()) + + if currentColumn['name'] == 'Height': + return str(item.source().format().height()) + + if currentColumn['name'] == 'Pixel Aspect': + return str(item.source().format().pixelAspect()) + + if currentColumn['name'] == 'Artist': + if item.artist(): + name = item.artist()['artistName'] + return name + else: + return '--' + + if currentColumn['name'] == 'Department': + if item.artist(): + dep = item.artist()['artistDepartment'] + return dep + else: + return '--' + + return "" + + def setData(self, row, column, item, data): + """ + Set the data in a cell - unused in this example + """ + + return None + + def getTooltip(self, row, column, item): + """ + Return the tooltip for a cell + """ + currentColumn = self.gCustomColumnList[column] + if currentColumn['name'] == 'Tags': + return str([item.name() for item in item.tags()]) + + if currentColumn['name'] == 'Notes': + return str(self.getNotes(item)) + return "" + + def getFont(self, row, column, item): + """ + Return the tooltip for a cell + """ + return None + + def getBackground(self, row, column, item): + """ + Return the background colour for a cell + """ + if not item.source().mediaSource().isMediaPresent(): + return QColor(80, 20, 20) + return None + + def getForeground(self, row, column, item): + """ + Return the text colour for a cell + """ + #if column == 1: + # return QColor(255, 64, 64) + return None + + def getIcon(self, row, column, item): + """ + Return the icon for a cell + """ + currentColumn = self.gCustomColumnList[column] + if currentColumn['name'] == 'Colourspace': + return QIcon("icons:LUT.png") + + if currentColumn['name'] == 'Shot Status': + status = item.status() + if status: + return QIcon(gStatusTags[status]) + + if currentColumn['name'] == 'MediaType': + mediaType = item.mediaType() + if mediaType == hiero.core.TrackItem.kVideo: + return QIcon("icons:VideoOnly.png") + elif mediaType == hiero.core.TrackItem.kAudio: + return QIcon("icons:AudioOnly.png") + + if currentColumn['name'] == 'Artist': + try: + return QIcon(item.artist()['artistIcon']) + except: + return None + return None + + def getSizeHint(self, row, column, item): + """ + Return the size hint for a cell + """ + currentColumnName = self.gCustomColumnList[column]['name'] + + if currentColumnName == 'Thumbnail': + return QSize(90, 50) + + return QSize(50, 50) + + def paintCell(self, row, column, item, painter, option): + """ + Paint a custom cell. Return True if the cell was painted, or False to continue + with the default cell painting. + """ + currentColumn = self.gCustomColumnList[column] + if currentColumn['name'] == 'Tags': + if option.state & QStyle.State_Selected: + painter.fillRect(option.rect, option.palette.highlight()) + iconSize = 20 + r = QRect(option.rect.x(), + option.rect.y() + (option.rect.height() - iconSize) / 2, + iconSize, iconSize) + tags = item.tags() + if len(tags) > 0: + painter.save() + painter.setClipRect(option.rect) + for tag in item.tags(): + M = tag.metadata() + if not (M.hasKey('tag.status') + or M.hasKey('tag.artistID')): + QIcon(tag.icon()).paint(painter, r, Qt.AlignLeft) + r.translate(r.width() + 2, 0) + painter.restore() + return True + + if currentColumn['name'] == 'Thumbnail': + imageView = None + pen = QPen() + r = QRect(option.rect.x() + 2, (option.rect.y() + + (option.rect.height() - 46) / 2), + 85, 46) + if not item.source().mediaSource().isMediaPresent(): + imageView = QImage("icons:Offline.png") + pen.setColor(QColor(Qt.red)) + + if item.mediaType() == hiero.core.TrackItem.MediaType.kAudio: + imageView = QImage("icons:AudioOnly.png") + #pen.setColor(QColor(Qt.green)) + painter.fillRect(r, QColor(45, 59, 45)) + + if option.state & QStyle.State_Selected: + painter.fillRect(option.rect, option.palette.highlight()) + + tags = item.tags() + painter.save() + painter.setClipRect(option.rect) + + if not imageView: + try: + imageView = item.thumbnail(item.sourceIn()) + pen.setColor(QColor(20, 20, 20)) + # If we're here, we probably have a TC error, no thumbnail, so get it from the source Clip... + except: + pen.setColor(QColor(Qt.red)) + + if not imageView: + try: + imageView = item.source().thumbnail() + pen.setColor(QColor(Qt.yellow)) + except: + imageView = QImage("icons:Offline.png") + pen.setColor(QColor(Qt.red)) + + QIcon(QPixmap.fromImage(imageView)).paint(painter, r, + Qt.AlignCenter) + painter.setPen(pen) + painter.drawRoundedRect(r, 1, 1) + painter.restore() + return True + + return False + + def createEditor(self, row, column, item, view): + """ + Create an editing widget for a custom cell + """ + self.currentView = view + + currentColumn = self.gCustomColumnList[column] + if currentColumn['cellType'] == 'readonly': + cle = QLabel() + cle.setEnabled(False) + cle.setVisible(False) + return cle + + if currentColumn['name'] == 'Colourspace': + cb = QComboBox() + for colourspace in self.gColourSpaces: + cb.addItem(colourspace) + cb.currentIndexChanged.connect(self.colourspaceChanged) + return cb + + if currentColumn['name'] == 'Shot Status': + cb = QComboBox() + cb.addItem('') + for key in gStatusTags.keys(): + cb.addItem(QIcon(gStatusTags[key]), key) + cb.addItem('--') + cb.currentIndexChanged.connect(self.statusChanged) + + return cb + + if currentColumn['name'] == 'Artist': + cb = QComboBox() + cb.addItem('') + for artist in gArtistList: + cb.addItem(artist['artistName']) + cb.addItem('--') + cb.currentIndexChanged.connect(self.artistNameChanged) + return cb + return None + + def setModelData(self, row, column, item, editor): + return False + + def dropMimeData(self, row, column, item, data, items): + """ + Handle a drag and drop operation - adds a Dragged Tag to the shot + """ + for thing in items: + if isinstance(thing, hiero.core.Tag): + item.addTag(thing) + return None + + def colourspaceChanged(self, index): + """ + This method is called when Colourspace widget changes index. + """ + index = self.sender().currentIndex() + colourspace = self.gColourSpaces[index] + selection = self.currentView.selection() + project = selection[0].project() + with project.beginUndo("Set Colourspace"): + items = [ + item for item in selection + if (item.mediaType() == hiero.core.TrackItem.MediaType.kVideo) + ] + for trackItem in items: + trackItem.setSourceMediaColourTransform(colourspace) + + def statusChanged(self, arg): + """ + This method is called when Shot Status widget changes index. + """ + view = hiero.ui.activeView() + selection = view.selection() + status = self.sender().currentText() + project = selection[0].project() + with project.beginUndo("Set Status"): + # A string of '--' characters denotes clear the status + if status != '--': + for trackItem in selection: + trackItem.setStatus(status) + else: + for trackItem in selection: + tTags = trackItem.tags() + for tag in tTags: + if tag.metadata().hasKey('tag.status'): + trackItem.removeTag(tag) + break + + def artistNameChanged(self, arg): + """ + This method is called when Artist widget changes index. + """ + view = hiero.ui.activeView() + selection = view.selection() + name = self.sender().currentText() + project = selection[0].project() + with project.beginUndo("Assign Artist"): + # A string of '--' denotes clear the assignee... + if name != '--': + for trackItem in selection: + trackItem.setArtistByName(name) + else: + for trackItem in selection: + tTags = trackItem.tags() + for tag in tTags: + if tag.metadata().hasKey('tag.artistID'): + trackItem.removeTag(tag) + break + + +def _getArtistFromID(self, artistID): + """ getArtistFromID -> returns an artist dictionary, by their given ID""" + global gArtistList + artist = [ + element for element in gArtistList + if element['artistID'] == int(artistID) + ] + if not artist: + return None + return artist[0] + + +def _getArtistFromName(self, artistName): + """ getArtistFromID -> returns an artist dictionary, by their given ID """ + global gArtistList + artist = [ + element for element in gArtistList + if element['artistName'] == artistName + ] + if not artist: + return None + return artist[0] + + +def _artist(self): + """_artist -> Returns the artist dictionary assigned to this shot""" + artist = None + tags = self.tags() + for tag in tags: + if tag.metadata().hasKey('tag.artistID'): + artistID = tag.metadata().value('tag.artistID') + artist = self.getArtistFromID(artistID) + return artist + + +def _updateArtistTag(self, artistDict): + # A shot will only have one artist assigned. Check if one exists and set accordingly + + artistTag = None + tags = self.tags() + for tag in tags: + if tag.metadata().hasKey('tag.artistID'): + artistTag = tag + break + + if not artistTag: + artistTag = hiero.core.Tag('Artist') + artistTag.setIcon(artistDict['artistIcon']) + artistTag.metadata().setValue('tag.artistID', + str(artistDict['artistID'])) + artistTag.metadata().setValue('tag.artistName', + str(artistDict['artistName'])) + artistTag.metadata().setValue('tag.artistDepartment', + str(artistDict['artistDepartment'])) + self.sequence().editFinished() + self.addTag(artistTag) + self.sequence().editFinished() + return + + artistTag.setIcon(artistDict['artistIcon']) + artistTag.metadata().setValue('tag.artistID', str(artistDict['artistID'])) + artistTag.metadata().setValue('tag.artistName', + str(artistDict['artistName'])) + artistTag.metadata().setValue('tag.artistDepartment', + str(artistDict['artistDepartment'])) + self.sequence().editFinished() + return + + +def _setArtistByName(self, artistName): + """ setArtistByName(artistName) -> sets the artist tag on a TrackItem by a given artistName string""" + global gArtistList + + artist = self.getArtistFromName(artistName) + if not artist: + print 'Artist name: %s was not found in the gArtistList.' % str( + artistName) + return + + # Do the update. + self.updateArtistTag(artist) + + +def _setArtistByID(self, artistID): + """ setArtistByID(artistID) -> sets the artist tag on a TrackItem by a given artistID integer""" + global gArtistList + + artist = self.getArtistFromID(artistID) + if not artist: + print 'Artist name: %s was not found in the gArtistList.' % str( + artistID) + return + + # Do the update. + self.updateArtistTag(artist) + + +# Inject status getter and setter methods into hiero.core.TrackItem +hiero.core.TrackItem.artist = _artist +hiero.core.TrackItem.setArtistByName = _setArtistByName +hiero.core.TrackItem.setArtistByID = _setArtistByID +hiero.core.TrackItem.getArtistFromName = _getArtistFromName +hiero.core.TrackItem.getArtistFromID = _getArtistFromID +hiero.core.TrackItem.updateArtistTag = _updateArtistTag + + +def _status(self): + """status -> Returns the Shot status. None if no Status is set.""" + + status = None + tags = self.tags() + for tag in tags: + if tag.metadata().hasKey('tag.status'): + status = tag.metadata().value('tag.status') + return status + + +def _setStatus(self, status): + """setShotStatus(status) -> Method to set the Status of a Shot. + Adds a special kind of status Tag to a TrackItem + Example: myTrackItem.setStatus('Final') + + @param status - a string, corresponding to the Status name + """ + global gStatusTags + + # Get a valid Tag object from the Global list of statuses + if not status in gStatusTags.keys(): + print 'Status requested was not a valid Status string.' + return + + # A shot should only have one status. Check if one exists and set accordingly + statusTag = None + tags = self.tags() + for tag in tags: + if tag.metadata().hasKey('tag.status'): + statusTag = tag + break + + if not statusTag: + statusTag = hiero.core.Tag('Status') + statusTag.setIcon(gStatusTags[status]) + statusTag.metadata().setValue('tag.status', status) + self.addTag(statusTag) + + statusTag.setIcon(gStatusTags[status]) + statusTag.metadata().setValue('tag.status', status) + + self.sequence().editFinished() + return + + +# Inject status getter and setter methods into hiero.core.TrackItem +hiero.core.TrackItem.setStatus = _setStatus +hiero.core.TrackItem.status = _status + + +# This is a convenience method for returning QActions with a triggered method based on the title string +def titleStringTriggeredAction(title, method, icon=None): + action = QAction(title, None) + action.setIcon(QIcon(icon)) + + # We do this magic, so that the title string from the action is used to set the status + def methodWrapper(): + method(title) + + action.triggered.connect(methodWrapper) + return action + + +# Menu which adds a Set Status Menu to Timeline and Spreadsheet Views +class SetStatusMenu(QMenu): + def __init__(self): + QMenu.__init__(self, "Set Status", None) + + global gStatusTags + self.statuses = gStatusTags + self._statusActions = self.createStatusMenuActions() + + # Add the Actions to the Menu. + for act in self.menuActions: + self.addAction(act) + + hiero.core.events.registerInterest("kShowContextMenu/kTimeline", + self.eventHandler) + hiero.core.events.registerInterest("kShowContextMenu/kSpreadsheet", + self.eventHandler) + + def createStatusMenuActions(self): + self.menuActions = [] + for status in self.statuses: + self.menuActions += [ + titleStringTriggeredAction( + status, + self.setStatusFromMenuSelection, + icon=gStatusTags[status]) + ] + + def setStatusFromMenuSelection(self, menuSelectionStatus): + selectedShots = [ + item for item in self._selection + if (isinstance(item, hiero.core.TrackItem)) + ] + selectedTracks = [ + item for item in self._selection + if (isinstance(item, (hiero.core.VideoTrack, + hiero.core.AudioTrack))) + ] + + # If we have a Track Header Selection, no shots could be selected, so create shotSelection list + if len(selectedTracks) >= 1: + for track in selectedTracks: + selectedShots += [ + item for item in track.items() + if (isinstance(item, hiero.core.TrackItem)) + ] + + # It's possible no shots exist on the Track, in which case nothing is required + if len(selectedShots) == 0: + return + + currentProject = selectedShots[0].project() + + with currentProject.beginUndo("Set Status"): + # Shots selected + for shot in selectedShots: + shot.setStatus(menuSelectionStatus) + + # This handles events from the Project Bin View + def eventHandler(self, event): + if not hasattr(event.sender, 'selection'): + # Something has gone wrong, we should only be here if raised + # by the Timeline/Spreadsheet view which gives a selection. + return + + # Set the current selection + self._selection = event.sender.selection() + + # Return if there's no Selection. We won't add the Menu. + if len(self._selection) == 0: + return + + event.menu.addMenu(self) + + +# Menu which adds a Set Status Menu to Timeline and Spreadsheet Views +class AssignArtistMenu(QMenu): + def __init__(self): + QMenu.__init__(self, "Assign Artist", None) + + global gArtistList + self.artists = gArtistList + self._artistsActions = self.createAssignArtistMenuActions() + + # Add the Actions to the Menu. + for act in self.menuActions: + self.addAction(act) + + hiero.core.events.registerInterest("kShowContextMenu/kTimeline", + self.eventHandler) + hiero.core.events.registerInterest("kShowContextMenu/kSpreadsheet", + self.eventHandler) + + def createAssignArtistMenuActions(self): + self.menuActions = [] + for artist in self.artists: + self.menuActions += [ + titleStringTriggeredAction( + artist['artistName'], + self.setArtistFromMenuSelection, + icon=artist['artistIcon']) + ] + + def setArtistFromMenuSelection(self, menuSelectionArtist): + selectedShots = [ + item for item in self._selection + if (isinstance(item, hiero.core.TrackItem)) + ] + selectedTracks = [ + item for item in self._selection + if (isinstance(item, (hiero.core.VideoTrack, + hiero.core.AudioTrack))) + ] + + # If we have a Track Header Selection, no shots could be selected, so create shotSelection list + if len(selectedTracks) >= 1: + for track in selectedTracks: + selectedShots += [ + item for item in track.items() + if (isinstance(item, hiero.core.TrackItem)) + ] + + # It's possible no shots exist on the Track, in which case nothing is required + if len(selectedShots) == 0: + return + + currentProject = selectedShots[0].project() + + with currentProject.beginUndo("Assign Artist"): + # Shots selected + for shot in selectedShots: + shot.setArtistByName(menuSelectionArtist) + + # This handles events from the Project Bin View + def eventHandler(self, event): + if not hasattr(event.sender, 'selection'): + # Something has gone wrong, we should only be here if raised + # by the Timeline/Spreadsheet view which gives a selection. + return + + # Set the current selection + self._selection = event.sender.selection() + + # Return if there's no Selection. We won't add the Menu. + if len(self._selection) == 0: + return + + event.menu.addMenu(self) + + +# Add the 'Set Status' context menu to Timeline and Spreadsheet +if kAddStatusMenu: + setStatusMenu = SetStatusMenu() + +if kAssignArtistMenu: + assignArtistMenu = AssignArtistMenu() + +# Register our custom columns +hiero.ui.customColumn = CustomSpreadsheetColumns() diff --git a/setup/nukestudio/hiero_plugin_path/Python/StartupUI/Purge.py b/setup/nukestudio/hiero_plugin_path/Python/StartupUI/Purge.py new file mode 100644 index 0000000000..4d2ab255ad --- /dev/null +++ b/setup/nukestudio/hiero_plugin_path/Python/StartupUI/Purge.py @@ -0,0 +1,142 @@ +# Purge Unused Clips - Removes any unused Clips from a Project +# Usage: Copy to ~/.hiero/Python/StartupUI +# Demonstrates the use of hiero.core.find_items module. +# Usage: Right-click on an item in the Bin View > "Purge Unused Clips" +# Result: Any Clips not used in a Sequence in the active project will be removed +# Requires Hiero 1.5v1 or later. +# Version 1.1 + +import hiero +import hiero.core.find_items +try: + from PySide.QtGui import * + from PySide.QtCore import * +except: + from PySide2.QtGui import * + from PySide2.QtWidgets import * + from PySide2.QtCore import * + + +class PurgeUnusedAction(QAction): + def __init__(self): + QAction.__init__(self, "Purge Unused Clips", None) + self.triggered.connect(self.PurgeUnused) + hiero.core.events.registerInterest("kShowContextMenu/kBin", + self.eventHandler) + self.setIcon(QIcon('icons:TagDelete.png')) + + # Method to return whether a Bin is empty... + def binIsEmpty(self, b): + numBinItems = 0 + bItems = b.items() + empty = False + + if len(bItems) == 0: + empty = True + return empty + else: + for b in bItems: + if isinstance(b, hiero.core.BinItem) or isinstance( + b, hiero.core.Bin): + numBinItems += 1 + if numBinItems == 0: + empty = True + + return empty + + def PurgeUnused(self): + + #Get selected items + item = self.selectedItem + proj = item.project() + + # Build a list of Projects + SEQS = hiero.core.findItems(proj, "Sequences") + + # Build a list of Clips + CLIPSTOREMOVE = hiero.core.findItems(proj, "Clips") + + if len(SEQS) == 0: + # Present Dialog Asking if User wants to remove Clips + msgBox = QMessageBox() + msgBox.setText("Purge Unused Clips") + msgBox.setInformativeText( + "You have no Sequences in this Project. Do you want to remove all Clips (%i) from Project: %s?" + % (len(CLIPSTOREMOVE), proj.name())) + msgBox.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) + msgBox.setDefaultButton(QMessageBox.Ok) + ret = msgBox.exec_() + if ret == QMessageBox.Cancel: + print 'Not purging anything.' + elif ret == QMessageBox.Ok: + with proj.beginUndo('Purge Unused Clips'): + BINS = [] + for clip in CLIPSTOREMOVE: + BI = clip.binItem() + B = BI.parentBin() + BINS += [B] + print 'Removing:', BI + try: + B.removeItem(BI) + except: + print 'Unable to remove: ' + BI + return + + # For each sequence, iterate through each track Item, see if the Clip is in the CLIPS list. + # Remaining items in CLIPS will be removed + + for seq in SEQS: + + #Loop through selected and make folders + for track in seq: + for trackitem in track: + + if trackitem.source() in CLIPSTOREMOVE: + CLIPSTOREMOVE.remove(trackitem.source()) + + # Present Dialog Asking if User wants to remove Clips + msgBox = QMessageBox() + msgBox.setText("Purge Unused Clips") + msgBox.setInformativeText("Remove %i unused Clips from Project %s?" % + (len(CLIPSTOREMOVE), proj.name())) + msgBox.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) + msgBox.setDefaultButton(QMessageBox.Ok) + ret = msgBox.exec_() + + if ret == QMessageBox.Cancel: + print 'Cancel' + return + elif ret == QMessageBox.Ok: + BINS = [] + with proj.beginUndo('Purge Unused Clips'): + # Delete the rest of the Clips + for clip in CLIPSTOREMOVE: + BI = clip.binItem() + B = BI.parentBin() + BINS += [B] + print 'Removing:', BI + try: + B.removeItem(BI) + except: + print 'Unable to remove: ' + BI + + def eventHandler(self, event): + if not hasattr(event.sender, 'selection'): + # Something has gone wrong, we shouldn't only be here if raised + # by the Bin view which will give a selection. + return + + self.selectedItem = None + s = event.sender.selection() + + if len(s) >= 1: + self.selectedItem = s[0] + title = "Purge Unused Clips" + self.setText(title) + event.menu.addAction(self) + + return + + +# Instantiate the action to get it to register itself. +PurgeUnusedAction = PurgeUnusedAction() diff --git a/setup/nukestudio/hiero_plugin_path/Python/StartupUI/nukeStyleKeyboardShortcuts.py b/setup/nukestudio/hiero_plugin_path/Python/StartupUI/nukeStyleKeyboardShortcuts.py new file mode 100644 index 0000000000..36a30e3a3c --- /dev/null +++ b/setup/nukestudio/hiero_plugin_path/Python/StartupUI/nukeStyleKeyboardShortcuts.py @@ -0,0 +1,36 @@ +# nukeStyleKeyboardShortcuts, v1, 30/07/2012, Ant Nasce. +# A few Nuke-Style File menu shortcuts for those whose muscle memory has set in... +# Usage: Copy this file to ~/.hiero/Python/StartupUI/ + +import hiero.ui +try: + from PySide.QtGui import * + from PySide.QtCore import * +except: + from PySide2.QtGui import * + from PySide2.QtWidgets import * + from PySide2.QtCore import * + +#---------------------------------------------- +a = hiero.ui.findMenuAction('Import Clips...') +# Note: You probably best to make this 'Ctrl+R' - currently conflicts with 'Red' in the Viewer! +a.setShortcut(QKeySequence('R')) +#---------------------------------------------- +a = hiero.ui.findMenuAction('Import Folder...') +a.setShortcut(QKeySequence('Shift+R')) +#---------------------------------------------- +a = hiero.ui.findMenuAction('Import EDL/XML...') +a.setShortcut(QKeySequence('Ctrl+Shift+O')) +#---------------------------------------------- +a = hiero.ui.findMenuAction('Show Metadata') +a.setShortcut(QKeySequence('I')) +#---------------------------------------------- +a = hiero.ui.findMenuAction('Edit Settings') +a.setShortcut(QKeySequence('S')) +#---------------------------------------------- +a = hiero.ui.findMenuAction('Monitor Controls') +a.setShortcut(QKeySequence('Ctrl+U')) +#---------------------------------------------- +a = hiero.ui.findMenuAction('New Viewer') +a.setShortcut(QKeySequence('Ctrl+I')) +#---------------------------------------------- diff --git a/setup/nukestudio/hiero_plugin_path/Python/StartupUI/setPosterFrame.py b/setup/nukestudio/hiero_plugin_path/Python/StartupUI/setPosterFrame.py new file mode 100644 index 0000000000..18398aa119 --- /dev/null +++ b/setup/nukestudio/hiero_plugin_path/Python/StartupUI/setPosterFrame.py @@ -0,0 +1,45 @@ +import hiero.core +import hiero.ui +try: + from PySide.QtGui import * + from PySide.QtCore import * +except: + from PySide2.QtGui import * + from PySide2.QtWidgets import * + from PySide2.QtCore import * + + +def setPosterFrame(posterFrame=.5): + ''' + Update the poster frame of the given clipItmes + posterFrame = .5 uses the centre frame, a value of 0 uses the first frame, a value of 1 uses the last frame + ''' + view = hiero.ui.activeView() + + selectedBinItems = view.selection() + selectedClipItems = [(item.activeItem() + if hasattr(item, 'activeItem') else item) + for item in selectedBinItems] + + for clip in selectedClipItems: + centreFrame = int(clip.duration() * posterFrame) + clip.setPosterFrame(centreFrame) + + +class SetPosterFrameAction(QAction): + def __init__(self): + QAction.__init__(self, "Set Poster Frame (centre)", None) + self._selection = None + + self.triggered.connect(lambda: setPosterFrame(.5)) + hiero.core.events.registerInterest("kShowContextMenu/kBin", + self.eventHandler) + + def eventHandler(self, event): + view = event.sender + # Add the Menu to the right-click menu + event.menu.addAction(self) + + +# The act of initialising the action adds it to the right-click menu... +SetPosterFrameAction() diff --git a/setup/nukestudio/hiero_plugin_path/Startup_old/pyblish_startup.py b/setup/nukestudio/hiero_plugin_path/Startup_old/pyblish_startup.py new file mode 100644 index 0000000000..4459be6713 --- /dev/null +++ b/setup/nukestudio/hiero_plugin_path/Startup_old/pyblish_startup.py @@ -0,0 +1,14 @@ +import traceback + +try: + __import__("pype.nukestudio") + __import__("pyblish") + +except ImportError as e: + print traceback.format_exc() + print("pyblish: Could not load integration: %s " % e) + +else: + # Setup integration + import pype.nukestudio.lib + pype.nukestudio.lib.setup() diff --git a/setup/nukestudio/hiero_plugin_path/Startup_old/selection_tracker.py b/setup/nukestudio/hiero_plugin_path/Startup_old/selection_tracker.py new file mode 100644 index 0000000000..b7e05fed7c --- /dev/null +++ b/setup/nukestudio/hiero_plugin_path/Startup_old/selection_tracker.py @@ -0,0 +1,9 @@ +"""Puts the selection project into 'hiero.selection'""" + +import hiero + + +def selectionChanged(event): + hiero.selection = event.sender.selection() + +hiero.core.events.registerInterest('kSelectionChanged', selectionChanged) diff --git a/setup/nukestudio/hiero_plugin_path/TaskPresets/10.5/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml b/setup/nukestudio/hiero_plugin_path/TaskPresets/10.5/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml new file mode 100644 index 0000000000..e24a4dbe4e --- /dev/null +++ b/setup/nukestudio/hiero_plugin_path/TaskPresets/10.5/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml @@ -0,0 +1,198 @@ + + 991 + //10.11.0.184/171001_ftrack/tgbvfx/editorial/nukestudio/workspace/ + 1 + True + 3 + + + {shot}/editorial_raw.%04d.{fileext} + + + default + exr + False + all + False + False + False + False + True + + + 8 bit + (auto detect) + True + False + + False + + None + None + None + None + None + None + None + None + None + + + Zip (16 scanline) + 32 bit float + False + False + False + channels, layers and views + 45.0 + False + all metadata + + Write_{ext} + + Cubic + None + 1.0 +
True
+ width +
+ False + Blend +
+
+
+ + {shot}/editorial.%04d.{ext} + + + default + exr + False + all + False + False + False + False + True + + + 8 bit + (auto detect) + True + False + + True + + None + None + None + None + None + None + None + None + None + + + Zip (16 scanline) + 16 bit half + False + False + False + channels, layers and views + 45.0 + False + all metadata + + Write_{ext} + + Cubic + To Sequence Resolution + 1.0 +
True
+ width +
+ False + Blend +
+
+
+ + {shot}/editorial.nk + + + True + default + mov + + rgb + False + + False + False + False + + True + True + + {shot}/editorial_raw.%04d.{fileext} + + + Cubic + None + 1.0 +
True
+ width +
+ False + Blend + False + True + True + + 0 + 40000000 + 12 + 31 + 2 + avc1 H.264 + Auto + mov32 + 20000 + + False + True + True + False + False + {shot}/editorial_raw.%04d.{fileext} + + None + None + None + None + None + None + None + None + None + + + 8 bit + (auto detect) + True + False + + Write_{ext} + False +
+
+
+
+ + + False + Custom + True + 10 +
diff --git a/setup/nukestudio/hiero_plugin_path/TaskPresets/11.1/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml b/setup/nukestudio/hiero_plugin_path/TaskPresets/11.1/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml new file mode 100644 index 0000000000..e24a4dbe4e --- /dev/null +++ b/setup/nukestudio/hiero_plugin_path/TaskPresets/11.1/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml @@ -0,0 +1,198 @@ + + 991 + //10.11.0.184/171001_ftrack/tgbvfx/editorial/nukestudio/workspace/ + 1 + True + 3 + + + {shot}/editorial_raw.%04d.{fileext} + + + default + exr + False + all + False + False + False + False + True + + + 8 bit + (auto detect) + True + False + + False + + None + None + None + None + None + None + None + None + None + + + Zip (16 scanline) + 32 bit float + False + False + False + channels, layers and views + 45.0 + False + all metadata + + Write_{ext} + + Cubic + None + 1.0 +
True
+ width +
+ False + Blend +
+
+
+ + {shot}/editorial.%04d.{ext} + + + default + exr + False + all + False + False + False + False + True + + + 8 bit + (auto detect) + True + False + + True + + None + None + None + None + None + None + None + None + None + + + Zip (16 scanline) + 16 bit half + False + False + False + channels, layers and views + 45.0 + False + all metadata + + Write_{ext} + + Cubic + To Sequence Resolution + 1.0 +
True
+ width +
+ False + Blend +
+
+
+ + {shot}/editorial.nk + + + True + default + mov + + rgb + False + + False + False + False + + True + True + + {shot}/editorial_raw.%04d.{fileext} + + + Cubic + None + 1.0 +
True
+ width +
+ False + Blend + False + True + True + + 0 + 40000000 + 12 + 31 + 2 + avc1 H.264 + Auto + mov32 + 20000 + + False + True + True + False + False + {shot}/editorial_raw.%04d.{fileext} + + None + None + None + None + None + None + None + None + None + + + 8 bit + (auto detect) + True + False + + Write_{ext} + False +
+
+
+
+ + + False + Custom + True + 10 +
diff --git a/setup/nukestudio/hiero_plugin_path/Templates/vfx_aces.hrox b/setup/nukestudio/hiero_plugin_path/Templates/vfx_aces.hrox new file mode 100644 index 0000000000..684cd0d1a2 --- /dev/null +++ b/setup/nukestudio/hiero_plugin_path/Templates/vfx_aces.hrox @@ -0,0 +1,38 @@ + + + + + + + + + 2 + 70 + + + 2 + 70 + + + 2 + 70 + 13 + + + 2 + 70 + 17 + + + 2 + 70 + 2 + + + + + + + + + diff --git a/setup/nukestudio/hiero_plugin_path/Templates/vfx_linear.hrox b/setup/nukestudio/hiero_plugin_path/Templates/vfx_linear.hrox new file mode 100644 index 0000000000..e915a24084 --- /dev/null +++ b/setup/nukestudio/hiero_plugin_path/Templates/vfx_linear.hrox @@ -0,0 +1,38 @@ + + + + + + + + + 2 + 70 + + + 2 + 70 + + + 2 + 70 + 13 + + + 2 + 70 + 17 + + + 2 + 70 + 2 + + + + + + + + + diff --git a/setup/nukestudio/hiero_plugin_path/Templates/vfx_rec709.hrox b/setup/nukestudio/hiero_plugin_path/Templates/vfx_rec709.hrox new file mode 100644 index 0000000000..42659cf81b --- /dev/null +++ b/setup/nukestudio/hiero_plugin_path/Templates/vfx_rec709.hrox @@ -0,0 +1,38 @@ + + + + + + + + + 2 + 70 + + + 2 + 70 + + + 2 + 70 + 13 + + + 2 + 70 + 17 + + + 2 + 70 + 2 + + + + + + + + +