mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge remote-tracking branch 'origin' into feature/2.0/documentation-support
This commit is contained in:
commit
6aa0fcfc4b
78 changed files with 7318 additions and 192 deletions
22
README.md
22
README.md
|
|
@ -1,27 +1,31 @@
|
|||
Pype
|
||||
====
|
||||
|
||||
The base studio *config* for [Avalon](https://getavalon.github.io/)
|
||||
The base studio _config_ for [Avalon](https://getavalon.github.io/)
|
||||
|
||||
Currently this config is dependent on our customised avalon instalation so it won't work with vanilla avalon core. We're working on open sourcing all of the necessary code though. You can still get inspiration or take our individual validators and scripts which should work just fine in other pipelines.
|
||||
|
||||
|
||||
_This configuration acts as a starting point for all pype club clients wth avalon deployment._
|
||||
|
||||
|
||||
|
||||
Code convention
|
||||
---------------
|
||||
|
||||
Below are some of the standard practices applied to this repositories.
|
||||
|
||||
- **Etiquette: PEP8**
|
||||
- All code is written in PEP8. It is recommended you use a linter as you work, flake8 and pylinter are both good options.
|
||||
|
||||
All code is written in PEP8. It is recommended you use a linter as you work, flake8 and pylinter are both good options.
|
||||
- **Etiquette: Napoleon docstrings**
|
||||
- Any docstrings are made in Google Napoleon format. See [Napoleon](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html) for details.
|
||||
|
||||
Any docstrings are made in Google Napoleon format. See [Napoleon](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html) for details.
|
||||
|
||||
- **Etiquette: Semantic Versioning**
|
||||
- This project follows [semantic versioning](http://semver.org).
|
||||
|
||||
This project follows [semantic versioning](http://semver.org).
|
||||
- **Etiquette: Underscore means private**
|
||||
- Anything prefixed with an underscore means that it is internal to wherever it is used. For example, a variable name is only ever used in the parent function or class. A module is not for use by the end-user. In contrast, anything without an underscore is public, but not necessarily part of the API. Members of the API resides in `api.py`.
|
||||
|
||||
Anything prefixed with an underscore means that it is internal to wherever it is used. For example, a variable name is only ever used in the parent function or class. A module is not for use by the end-user. In contrast, anything without an underscore is public, but not necessarily part of the API. Members of the API resides in `api.py`.
|
||||
|
||||
- **API: Idempotence**
|
||||
- A public function must be able to be called twice and produce the exact same result. This means no changing of state without restoring previous state when finishing. For example, if a function requires changing the current selection in Autodesk Maya, it must restore the previous selection prior to completing.
|
||||
|
||||
A public function must be able to be called twice and produce the exact same result. This means no changing of state without restoring previous state when finishing. For example, if a function requires changing the current selection in Autodesk Maya, it must restore the previous selection prior to completing.
|
||||
|
|
|
|||
|
|
@ -46,8 +46,6 @@ from .lib import (
|
|||
get_data_hierarchical_attr
|
||||
)
|
||||
|
||||
from .widgets.message_window import message
|
||||
|
||||
__all__ = [
|
||||
# plugin classes
|
||||
"Extractor",
|
||||
|
|
@ -89,7 +87,4 @@ __all__ = [
|
|||
"Colorspace",
|
||||
"Dataflow",
|
||||
|
||||
# QtWidgets
|
||||
"message"
|
||||
|
||||
]
|
||||
|
|
|
|||
|
|
@ -40,15 +40,7 @@ class AvalonApps:
|
|||
def show_launcher(self):
|
||||
# if app_launcher don't exist create it/otherwise only show main window
|
||||
if self.app_launcher is None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--demo", action="store_true")
|
||||
parser.add_argument(
|
||||
"--root", default=os.environ["AVALON_PROJECTS"]
|
||||
)
|
||||
kwargs = parser.parse_args()
|
||||
|
||||
root = kwargs.root
|
||||
root = os.path.realpath(root)
|
||||
root = os.path.realpath(os.environ["AVALON_PROJECTS"])
|
||||
io.install()
|
||||
APP_PATH = launcher_lib.resource("qml", "main.qml")
|
||||
self.app_launcher = launcher_widget.Launcher(root, APP_PATH)
|
||||
|
|
|
|||
|
|
@ -85,6 +85,12 @@ class DeleteAsset(BaseAction):
|
|||
'type': 'asset',
|
||||
'name': entity['name']
|
||||
})
|
||||
|
||||
if av_entity is None:
|
||||
return {
|
||||
'success': False,
|
||||
'message': 'Didn\'t found assets in avalon'
|
||||
}
|
||||
|
||||
asset_label = {
|
||||
'type': 'label',
|
||||
|
|
|
|||
|
|
@ -25,8 +25,6 @@ from pypeapp import Logger
|
|||
|
||||
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__)
|
||||
|
|
@ -38,9 +36,8 @@ 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")
|
||||
|
||||
self = sys.modules[__name__]
|
||||
self.nLogger = None
|
||||
|
||||
# registering pyblish gui regarding settings in presets
|
||||
if os.getenv("PYBLISH_GUI", None):
|
||||
pyblish.register_gui(os.getenv("PYBLISH_GUI", None))
|
||||
|
||||
|
|
@ -66,6 +63,7 @@ class NukeHandler(logging.Handler):
|
|||
"fatal",
|
||||
"error"
|
||||
]:
|
||||
msg = self.format(record)
|
||||
nuke.message(msg)
|
||||
|
||||
|
||||
|
|
@ -77,9 +75,6 @@ if nuke_handler.get_name() \
|
|||
logging.getLogger().addHandler(nuke_handler)
|
||||
logging.getLogger().setLevel(logging.INFO)
|
||||
|
||||
if not self.nLogger:
|
||||
self.nLogger = Logger
|
||||
|
||||
|
||||
def reload_config():
|
||||
"""Attempt to reload pipeline at run-time.
|
||||
|
|
@ -157,7 +152,7 @@ def uninstall():
|
|||
|
||||
def on_pyblish_instance_toggled(instance, old_value, new_value):
|
||||
"""Toggle node passthrough states on instance toggles."""
|
||||
self.log.info("instance toggle: {}, old_value: {}, new_value:{} ".format(
|
||||
log.info("instance toggle: {}, old_value: {}, new_value:{} ".format(
|
||||
instance, old_value, new_value))
|
||||
|
||||
from avalon.nuke import (
|
||||
|
|
|
|||
|
|
@ -29,6 +29,53 @@ def onScriptLoad():
|
|||
nuke.tcl('load movWriter')
|
||||
|
||||
|
||||
def checkInventoryVersions():
|
||||
"""
|
||||
Actiual version idetifier of Loaded containers
|
||||
|
||||
Any time this function is run it will check all nodes and filter only Loader nodes for its version. It will get all versions from database
|
||||
and check if the node is having actual version. If not then it will color it to red.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# get all Loader nodes by avalon attribute metadata
|
||||
for each in nuke.allNodes():
|
||||
if each.Class() == 'Read':
|
||||
container = avalon.nuke.parse_container(each)
|
||||
|
||||
if container:
|
||||
node = container["_tool"]
|
||||
avalon_knob_data = get_avalon_knob_data(node)
|
||||
|
||||
# get representation from io
|
||||
representation = io.find_one({
|
||||
"type": "representation",
|
||||
"_id": io.ObjectId(avalon_knob_data["representation"])
|
||||
})
|
||||
|
||||
# Get start frame from version data
|
||||
version = io.find_one({
|
||||
"type": "version",
|
||||
"_id": representation["parent"]
|
||||
})
|
||||
|
||||
# get all versions in list
|
||||
versions = io.find({
|
||||
"type": "version",
|
||||
"parent": version["parent"]
|
||||
}).distinct('name')
|
||||
|
||||
max_version = max(versions)
|
||||
|
||||
# check the available version and do match
|
||||
# change color of node if not max verion
|
||||
if version.get("name") not in [max_version]:
|
||||
node["tile_color"].setValue(int("0xd84f20ff", 16))
|
||||
else:
|
||||
node["tile_color"].setValue(int("0x4ecd25ff", 16))
|
||||
|
||||
|
||||
def writes_version_sync():
|
||||
try:
|
||||
rootVersion = pype.get_version_from_path(nuke.root().name())
|
||||
|
|
|
|||
117
pype/nukestudio/__init__.py
Normal file
117
pype/nukestudio/__init__.py
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
import os
|
||||
import sys
|
||||
from avalon import api as avalon
|
||||
from pyblish import api as pyblish
|
||||
|
||||
from .. import api
|
||||
|
||||
from .menu import install as menu_install
|
||||
|
||||
from .lib import (
|
||||
show,
|
||||
setup,
|
||||
add_to_filemenu
|
||||
)
|
||||
|
||||
|
||||
from pypeapp import Logger
|
||||
|
||||
|
||||
log = Logger().get_logger(__name__, "nukestudio")
|
||||
|
||||
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, "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")
|
||||
|
||||
|
||||
if os.getenv("PYBLISH_GUI", None):
|
||||
pyblish.register_gui(os.getenv("PYBLISH_GUI", None))
|
||||
|
||||
|
||||
def reload_config():
|
||||
"""Attempt to reload pipeline at run-time.
|
||||
|
||||
CAUTION: This is primarily for development and debugging purposes.
|
||||
|
||||
"""
|
||||
|
||||
import importlib
|
||||
|
||||
for module in (
|
||||
"pypeapp",
|
||||
"{}.api".format(AVALON_CONFIG),
|
||||
"{}.templates".format(AVALON_CONFIG),
|
||||
"{}.nukestudio.inventory".format(AVALON_CONFIG),
|
||||
"{}.nukestudio.lib".format(AVALON_CONFIG),
|
||||
"{}.nukestudio.menu".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 install(config):
|
||||
|
||||
# api.set_avalon_workdir()
|
||||
# reload_config()
|
||||
|
||||
# import sys
|
||||
# for path in sys.path:
|
||||
# if path.startswith("C:\\Users\\Public"):
|
||||
# sys.path.remove(path)
|
||||
|
||||
log.info("Registering NukeStudio plug-ins..")
|
||||
pyblish.register_host("nukestudio")
|
||||
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"
|
||||
]
|
||||
|
||||
avalon.data["familiesStateDefault"] = False
|
||||
avalon.data["familiesStateToggled"] = family_states
|
||||
|
||||
menu_install()
|
||||
|
||||
# load data from templates
|
||||
api.load_data_from_templates()
|
||||
|
||||
|
||||
def uninstall():
|
||||
log.info("Deregistering NukeStudio plug-ins..")
|
||||
pyblish.deregister_host("nukestudio")
|
||||
pyblish.deregister_plugin_path(PUBLISH_PATH)
|
||||
avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH)
|
||||
avalon.deregister_plugin_path(avalon.Creator, CREATE_PATH)
|
||||
|
||||
# reset data from templates
|
||||
api.reset_data_from_templates()
|
||||
|
||||
|
||||
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`
|
||||
"""
|
||||
return
|
||||
205
pype/nukestudio/lib.py
Normal file
205
pype/nukestudio/lib.py
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
# Standard library
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Pyblish libraries
|
||||
import pyblish.api
|
||||
|
||||
# Host libraries
|
||||
import hiero
|
||||
|
||||
from PySide2 import (QtWidgets, QtGui)
|
||||
|
||||
|
||||
cached_process = None
|
||||
|
||||
|
||||
self = sys.modules[__name__]
|
||||
self._has_been_setup = False
|
||||
self._has_menu = False
|
||||
self._registered_gui = None
|
||||
|
||||
|
||||
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):
|
||||
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_()
|
||||
92
pype/nukestudio/menu.py
Normal file
92
pype/nukestudio/menu.py
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
import os
|
||||
from avalon.api import Session
|
||||
from pprint import pprint
|
||||
|
||||
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 Exception:
|
||||
pass
|
||||
|
||||
if not check_made_menu:
|
||||
menu = M.addMenu(menu_name)
|
||||
else:
|
||||
menu = check_made_menu.menu()
|
||||
|
||||
actions = [{
|
||||
'action': QAction('Set Context', None),
|
||||
'function': contextmanager.show,
|
||||
'icon': QIcon('icons:Position.png')
|
||||
},
|
||||
{
|
||||
'action': QAction('Create...', None),
|
||||
'function': creator.show,
|
||||
'icon': QIcon('icons:ColorAdd.png')
|
||||
},
|
||||
{
|
||||
'action': QAction('Load...', None),
|
||||
'function': cbloader.show,
|
||||
'icon': QIcon('icons:CopyRectangle.png')
|
||||
},
|
||||
{
|
||||
'action': QAction('Publish...', None),
|
||||
'function': publish.show,
|
||||
'icon': QIcon('icons:Output.png')
|
||||
},
|
||||
{
|
||||
'action': QAction('Manage...', None),
|
||||
'function': cbsceneinventory.show,
|
||||
'icon': QIcon('icons:ModifyMetaData.png')
|
||||
},
|
||||
{
|
||||
'action': QAction('Library...', None),
|
||||
'function': libraryloader.show,
|
||||
'icon': QIcon('icons:ColorAdd.png')
|
||||
}]
|
||||
|
||||
|
||||
# Create menu items
|
||||
for a in actions:
|
||||
pprint(a)
|
||||
# create action
|
||||
for k in a.keys():
|
||||
if 'action' in k:
|
||||
action = a[k]
|
||||
elif 'function' in k:
|
||||
action.triggered.connect(a[k])
|
||||
elif 'icon' in k:
|
||||
action.setIcon(a[k])
|
||||
|
||||
# add action to menu
|
||||
menu.addAction(action)
|
||||
hiero.ui.registerAction(action)
|
||||
14
pype/plugins/global/publish/collect_presets.py
Normal file
14
pype/plugins/global/publish/collect_presets.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
from pyblish import api
|
||||
from pypeapp import config
|
||||
|
||||
|
||||
class CollectPresets(api.ContextPlugin):
|
||||
"""Collect Presets."""
|
||||
|
||||
order = api.CollectorOrder
|
||||
label = "Collect Presets"
|
||||
|
||||
def process(self, context):
|
||||
context.data["presets"] = config.get_presets()
|
||||
self.log.info(context.data["presets"])
|
||||
return
|
||||
24
pype/plugins/nuke/_publish_unused/test_instances.py
Normal file
24
pype/plugins/nuke/_publish_unused/test_instances.py
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import pyblish.api
|
||||
|
||||
|
||||
class IncrementTestPlugin(pyblish.api.ContextPlugin):
|
||||
"""Increment current script version."""
|
||||
|
||||
order = pyblish.api.CollectorOrder + 0.5
|
||||
label = "Test Plugin"
|
||||
hosts = ['nuke']
|
||||
|
||||
def process(self, context):
|
||||
instances = context[:]
|
||||
|
||||
prerender_check = list()
|
||||
families_check = list()
|
||||
for instance in instances:
|
||||
if ("prerender" in str(instance)):
|
||||
prerender_check.append(instance)
|
||||
if instance.data.get("families", None):
|
||||
families_check.append(True)
|
||||
|
||||
if len(prerender_check) != len(families_check):
|
||||
self.log.info(prerender_check)
|
||||
self.log.info(families_check)
|
||||
170
pype/plugins/nuke/load/load_script_precomp.py
Normal file
170
pype/plugins/nuke/load/load_script_precomp.py
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
from avalon import api, style, io
|
||||
from pype.nuke.lib import get_avalon_knob_data
|
||||
import nuke
|
||||
import os
|
||||
from pype.api import Logger
|
||||
log = Logger().get_logger(__name__, "nuke")
|
||||
|
||||
|
||||
|
||||
class LinkAsGroup(api.Loader):
|
||||
"""Copy the published file to be pasted at the desired location"""
|
||||
|
||||
representations = ["nk"]
|
||||
families = ["*"]
|
||||
|
||||
label = "Load Precomp"
|
||||
order = 10
|
||||
icon = "file"
|
||||
color = style.colors.dark
|
||||
|
||||
def load(self, context, name, namespace, data):
|
||||
|
||||
from avalon.nuke import containerise
|
||||
# for k, v in context.items():
|
||||
# log.info("key: `{}`, value: {}\n".format(k, v))
|
||||
version = context['version']
|
||||
version_data = version.get("data", {})
|
||||
|
||||
vname = version.get("name", None)
|
||||
first = version_data.get("startFrame", None)
|
||||
last = version_data.get("endFrame", None)
|
||||
|
||||
# Fallback to asset name when namespace is None
|
||||
if namespace is None:
|
||||
namespace = context['asset']['name']
|
||||
|
||||
file = self.fname.replace("\\", "/")
|
||||
self.log.info("file: {}\n".format(self.fname))
|
||||
|
||||
precomp_name = context["representation"]["context"]["subset"]
|
||||
|
||||
# Set global in point to start frame (if in version.data)
|
||||
start = context["version"]["data"].get("startFrame", None)
|
||||
|
||||
# add additional metadata from the version to imprint to Avalon knob
|
||||
add_keys = ["startFrame", "endFrame", "handles",
|
||||
"source", "author", "fps"]
|
||||
|
||||
data_imprint = {
|
||||
"start_frame": start,
|
||||
"fstart": first,
|
||||
"fend": last,
|
||||
"version": vname
|
||||
}
|
||||
for k in add_keys:
|
||||
data_imprint.update({k: context["version"]['data'][k]})
|
||||
data_imprint.update({"objectName": precomp_name})
|
||||
|
||||
# group context is set to precomp, so back up one level.
|
||||
nuke.endGroup()
|
||||
|
||||
# P = nuke.nodes.LiveGroup("file {}".format(file))
|
||||
P = nuke.createNode(
|
||||
"Precomp",
|
||||
"file {}".format(file))
|
||||
|
||||
# Set colorspace defined in version data
|
||||
colorspace = context["version"]["data"].get("colorspace", None)
|
||||
self.log.info("colorspace: {}\n".format(colorspace))
|
||||
|
||||
|
||||
# ['version', 'file', 'reading', 'output', 'useOutput']
|
||||
|
||||
P["name"].setValue("{}_{}".format(name, namespace))
|
||||
P["useOutput"].setValue(True)
|
||||
|
||||
with P:
|
||||
# iterate trough all nodes in group node and find pype writes
|
||||
writes = [n.name() for n in nuke.allNodes()
|
||||
if n.Class() == "Write"
|
||||
if get_avalon_knob_data(n)]
|
||||
|
||||
# create panel for selecting output
|
||||
panel_choices = " ".join(writes)
|
||||
panel_label = "Select write node for output"
|
||||
p = nuke.Panel("Select Write Node")
|
||||
p.addEnumerationPulldown(
|
||||
panel_label, panel_choices)
|
||||
p.show()
|
||||
P["output"].setValue(p.value(panel_label))
|
||||
|
||||
P["tile_color"].setValue(0xff0ff0ff)
|
||||
|
||||
return containerise(
|
||||
node=P,
|
||||
name=name,
|
||||
namespace=namespace,
|
||||
context=context,
|
||||
loader=self.__class__.__name__,
|
||||
data=data_imprint)
|
||||
|
||||
def switch(self, container, representation):
|
||||
self.update(container, representation)
|
||||
|
||||
def update(self, container, representation):
|
||||
"""Update the Loader's path
|
||||
|
||||
Nuke automatically tries to reset some variables when changing
|
||||
the loader's path to a new file. These automatic changes are to its
|
||||
inputs:
|
||||
|
||||
"""
|
||||
|
||||
from avalon.nuke import (
|
||||
update_container
|
||||
)
|
||||
|
||||
node = nuke.toNode(container['objectName'])
|
||||
|
||||
root = api.get_representation_path(representation).replace("\\","/")
|
||||
|
||||
# Get start frame from version data
|
||||
version = io.find_one({
|
||||
"type": "version",
|
||||
"_id": representation["parent"]
|
||||
})
|
||||
|
||||
# get all versions in list
|
||||
versions = io.find({
|
||||
"type": "version",
|
||||
"parent": version["parent"]
|
||||
}).distinct('name')
|
||||
|
||||
max_version = max(versions)
|
||||
|
||||
updated_dict = {}
|
||||
updated_dict.update({
|
||||
"representation": str(representation["_id"]),
|
||||
"endFrame": version["data"].get("endFrame"),
|
||||
"version": version.get("name"),
|
||||
"colorspace": version["data"].get("colorspace"),
|
||||
"source": version["data"].get("source"),
|
||||
"handles": version["data"].get("handles"),
|
||||
"fps": version["data"].get("fps"),
|
||||
"author": version["data"].get("author"),
|
||||
"outputDir": version["data"].get("outputDir"),
|
||||
})
|
||||
|
||||
# Update the imprinted representation
|
||||
update_container(
|
||||
node,
|
||||
updated_dict
|
||||
)
|
||||
|
||||
node["file"].setValue(root)
|
||||
|
||||
# change color of node
|
||||
if version.get("name") not in [max_version]:
|
||||
node["tile_color"].setValue(int("0xd84f20ff", 16))
|
||||
else:
|
||||
node["tile_color"].setValue(int("0xff0ff0ff", 16))
|
||||
|
||||
log.info("udated to version: {}".format(version.get("name")))
|
||||
|
||||
|
||||
def remove(self, container):
|
||||
from avalon.nuke import viewer_update_and_undo_stop
|
||||
node = nuke.toNode(container['objectName'])
|
||||
with viewer_update_and_undo_stop():
|
||||
nuke.delete(node)
|
||||
|
|
@ -128,11 +128,15 @@ class LoadSequence(api.Loader):
|
|||
|
||||
# add additional metadata from the version to imprint to Avalon knob
|
||||
add_keys = ["startFrame", "endFrame", "handles",
|
||||
"source", "colorspace", "author", "fps"]
|
||||
"source", "colorspace", "author", "fps", "version"]
|
||||
|
||||
data_imprint = {}
|
||||
for k in add_keys:
|
||||
data_imprint.update({k: context["version"]['data'][k]})
|
||||
if k is 'version':
|
||||
data_imprint.update({k: context["version"]['name']})
|
||||
else:
|
||||
data_imprint.update({k: context["version"]['data'][k]})
|
||||
|
||||
data_imprint.update({"objectName": read_name})
|
||||
|
||||
r["tile_color"].setValue(int("0x4ecd25ff", 16))
|
||||
|
|
|
|||
|
|
@ -17,6 +17,10 @@ class CollectNukeInstances(pyblish.api.ContextPlugin):
|
|||
def process(self, context):
|
||||
asset_data = io.find_one({"type": "asset",
|
||||
"name": api.Session["AVALON_ASSET"]})
|
||||
|
||||
# add handles into context
|
||||
context.data['handles'] = int(asset_data["data"].get("handles", 0))
|
||||
|
||||
self.log.debug("asset_data: {}".format(asset_data["data"]))
|
||||
instances = []
|
||||
# creating instances per write node
|
||||
|
|
@ -51,7 +55,6 @@ class CollectNukeInstances(pyblish.api.ContextPlugin):
|
|||
"family": avalon_knob_data["family"],
|
||||
"avalonKnob": avalon_knob_data,
|
||||
"publish": node.knob('publish').value(),
|
||||
"handles": int(asset_data["data"].get("handles", 0)),
|
||||
"step": 1,
|
||||
"fps": int(nuke.root()['fps'].value())
|
||||
|
||||
|
|
|
|||
|
|
@ -35,10 +35,12 @@ class CollectNukeWrites(pyblish.api.ContextPlugin):
|
|||
output_type = "mov"
|
||||
|
||||
# Get frame range
|
||||
handles = instance.context.data.get('handles', 0)
|
||||
first_frame = int(nuke.root()["first_frame"].getValue())
|
||||
last_frame = int(nuke.root()["last_frame"].getValue())
|
||||
|
||||
if node["use_limit"].getValue():
|
||||
handles = 0
|
||||
first_frame = int(node["first"].getValue())
|
||||
last_frame = int(node["last"].getValue())
|
||||
|
||||
|
|
@ -76,6 +78,7 @@ class CollectNukeWrites(pyblish.api.ContextPlugin):
|
|||
"outputDir": output_dir,
|
||||
"ext": ext,
|
||||
"label": label,
|
||||
"handles": handles,
|
||||
"startFrame": first_frame,
|
||||
"endFrame": last_frame,
|
||||
"outputType": output_type,
|
||||
|
|
|
|||
|
|
@ -16,7 +16,19 @@ class IncrementScriptVersion(pyblish.api.ContextPlugin):
|
|||
assert all(result["success"] for result in context.data["results"]), (
|
||||
"Atomicity not held, aborting.")
|
||||
|
||||
from pype.lib import version_up
|
||||
path = context.data["currentFile"]
|
||||
nuke.scriptSaveAs(version_up(path))
|
||||
self.log.info('Incrementing script version')
|
||||
instances = context[:]
|
||||
|
||||
prerender_check = list()
|
||||
families_check = list()
|
||||
for instance in instances:
|
||||
if ("prerender" in str(instance)) and instance.data.get("families", None):
|
||||
prerender_check.append(instance)
|
||||
if instance.data.get("families", None):
|
||||
families_check.append(True)
|
||||
|
||||
|
||||
if len(prerender_check) != len(families_check):
|
||||
from pype.lib import version_up
|
||||
path = context.data["currentFile"]
|
||||
nuke.scriptSaveAs(version_up(path))
|
||||
self.log.info('Incrementing script version')
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ class ValidateScript(pyblish.api.InstancePlugin):
|
|||
]
|
||||
|
||||
# Value of these attributes can be found on parents
|
||||
hierarchical_attributes = ["fps", "resolution_width", "resolution_height", "pixel_aspect"]
|
||||
hierarchical_attributes = ["fps", "resolution_width", "resolution_height", "pixel_aspect", "handles"]
|
||||
|
||||
missing_attributes = []
|
||||
asset_attributes = {}
|
||||
|
|
|
|||
191
pype/plugins/nukestudio/_unused/collect.py
Normal file
191
pype/plugins/nukestudio/_unused/collect.py
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
from pyblish import api
|
||||
|
||||
class CollectFramerate(api.ContextPlugin):
|
||||
"""Collect framerate from selected sequence."""
|
||||
|
||||
order = api.CollectorOrder
|
||||
label = "Collect Framerate"
|
||||
hosts = ["nukestudio"]
|
||||
|
||||
def process(self, context):
|
||||
for item in context.data.get("selection", []):
|
||||
context.data["framerate"] = item.sequence().framerate().toFloat()
|
||||
return
|
||||
|
||||
|
||||
class CollectTrackItems(api.ContextPlugin):
|
||||
"""Collect all tasks from submission."""
|
||||
|
||||
order = api.CollectorOrder
|
||||
label = "Collect Track Items"
|
||||
hosts = ["nukestudio"]
|
||||
|
||||
def process(self, context):
|
||||
import os
|
||||
|
||||
submission = context.data.get("submission", None)
|
||||
data = {}
|
||||
|
||||
# Set handles
|
||||
handles = 0
|
||||
if submission:
|
||||
for task in submission.getLeafTasks():
|
||||
|
||||
if task._cutHandles:
|
||||
handles = task._cutHandles
|
||||
self.log.info("__ handles: '{}'".format(handles))
|
||||
|
||||
# Skip audio track items
|
||||
media_type = "core.Hiero.Python.TrackItem.MediaType.kAudio"
|
||||
if str(task._item.mediaType()) == media_type:
|
||||
continue
|
||||
|
||||
item = task._item
|
||||
if item.name() not in data:
|
||||
data[item.name()] = {"item": item, "tasks": [task]}
|
||||
else:
|
||||
data[item.name()]["tasks"].append(task)
|
||||
|
||||
data[item.name()]["startFrame"] = task.outputRange()[0]
|
||||
data[item.name()]["endFrame"] = task.outputRange()[1]
|
||||
else:
|
||||
for item in context.data.get("selection", []):
|
||||
# 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:
|
||||
continue
|
||||
|
||||
data[item.name()] = {
|
||||
"item": item,
|
||||
"tasks": [],
|
||||
"startFrame": item.timelineIn(),
|
||||
"endFrame": item.timelineOut()
|
||||
}
|
||||
|
||||
for key, value in data.items():
|
||||
|
||||
context.create_instance(
|
||||
name=key,
|
||||
subset="trackItem",
|
||||
asset=value["item"].name(),
|
||||
item=value["item"],
|
||||
family="trackItem",
|
||||
tasks=value["tasks"],
|
||||
startFrame=value["startFrame"] + handles,
|
||||
endFrame=value["endFrame"] - handles,
|
||||
handles=handles
|
||||
)
|
||||
context.create_instance(
|
||||
name=key + "_review",
|
||||
subset="reviewItem",
|
||||
asset=value["item"].name(),
|
||||
item=value["item"],
|
||||
family="trackItem_review",
|
||||
families=["output"],
|
||||
handles=handles,
|
||||
output_path=os.path.abspath(
|
||||
os.path.join(
|
||||
context.data["activeProject"].path(),
|
||||
"..",
|
||||
"workspace",
|
||||
key + ".mov"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class CollectTasks(api.ContextPlugin):
|
||||
"""Collect all tasks from submission."""
|
||||
|
||||
order = api.CollectorOrder + 0.01
|
||||
label = "Collect Tasks"
|
||||
hosts = ["nukestudio"]
|
||||
|
||||
def process(self, context):
|
||||
import os
|
||||
import re
|
||||
|
||||
import hiero.exporters as he
|
||||
import clique
|
||||
|
||||
for parent in context:
|
||||
if "trackItem" != parent.data["family"]:
|
||||
continue
|
||||
|
||||
for task in parent.data["tasks"]:
|
||||
asset_type = None
|
||||
|
||||
hiero_cls = he.FnSymLinkExporter.SymLinkExporter
|
||||
if isinstance(task, hiero_cls):
|
||||
asset_type = "img"
|
||||
movie_formats = [".mov", ".R3D"]
|
||||
ext = os.path.splitext(task.resolvedExportPath())[1]
|
||||
if ext in movie_formats:
|
||||
asset_type = "mov"
|
||||
|
||||
hiero_cls = he.FnTranscodeExporter.TranscodeExporter
|
||||
if isinstance(task, hiero_cls):
|
||||
asset_type = "img"
|
||||
if task.resolvedExportPath().endswith(".mov"):
|
||||
asset_type = "mov"
|
||||
|
||||
hiero_cls = he.FnNukeShotExporter.NukeShotExporter
|
||||
if isinstance(task, hiero_cls):
|
||||
asset_type = "scene"
|
||||
|
||||
hiero_cls = he.FnAudioExportTask.AudioExportTask
|
||||
if isinstance(task, hiero_cls):
|
||||
asset_type = "audio"
|
||||
|
||||
# Skip all non supported export types
|
||||
if not asset_type:
|
||||
continue
|
||||
|
||||
resolved_path = task.resolvedExportPath()
|
||||
|
||||
# Formatting the basename to not include frame padding or
|
||||
# extension.
|
||||
name = os.path.splitext(os.path.basename(resolved_path))[0]
|
||||
name = name.replace(".", "")
|
||||
name = name.replace("#", "")
|
||||
name = re.sub(r"%.*d", "", name)
|
||||
instance = context.create_instance(name=name, parent=parent)
|
||||
|
||||
instance.data["task"] = task
|
||||
instance.data["item"] = parent.data["item"]
|
||||
|
||||
instance.data["family"] = "trackItem.task"
|
||||
instance.data["families"] = [asset_type, "local", "task"]
|
||||
|
||||
label = "{1}/{0} - {2} - local".format(
|
||||
name, parent, asset_type
|
||||
)
|
||||
instance.data["label"] = label
|
||||
|
||||
instance.data["handles"] = parent.data["handles"]
|
||||
|
||||
# Add collection or output
|
||||
if asset_type == "img":
|
||||
collection = None
|
||||
|
||||
if "#" in resolved_path:
|
||||
head = resolved_path.split("#")[0]
|
||||
padding = resolved_path.count("#")
|
||||
tail = resolved_path.split("#")[-1]
|
||||
|
||||
collection = clique.Collection(
|
||||
head=head, padding=padding, tail=tail
|
||||
)
|
||||
|
||||
if "%" in resolved_path:
|
||||
collection = clique.parse(
|
||||
resolved_path, pattern="{head}{padding}{tail}"
|
||||
)
|
||||
|
||||
instance.data["collection"] = collection
|
||||
else:
|
||||
instance.data["output_path"] = resolved_path
|
||||
14
pype/plugins/nukestudio/_unused/collect_submission.py
Normal file
14
pype/plugins/nukestudio/_unused/collect_submission.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
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"]))
|
||||
124
pype/plugins/nukestudio/_unused/extract_tasks.py
Normal file
124
pype/plugins/nukestudio/_unused/extract_tasks.py
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
from pyblish import api
|
||||
|
||||
|
||||
class ExtractTasks(api.InstancePlugin):
|
||||
"""Extract tasks."""
|
||||
|
||||
order = api.ExtractorOrder
|
||||
label = "Tasks"
|
||||
hosts = ["nukestudio"]
|
||||
families = ["trackItem.task"]
|
||||
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)
|
||||
27
pype/plugins/nukestudio/_unused/validate_resolved_paths.py
Normal file
27
pype/plugins/nukestudio/_unused/validate_resolved_paths.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
from pyblish import api
|
||||
|
||||
class ValidateResolvedPaths(api.ContextPlugin):
|
||||
"""Validate there are no overlapping resolved paths."""
|
||||
|
||||
order = api.ValidatorOrder
|
||||
label = "Resolved Paths"
|
||||
hosts = ["nukestudio"]
|
||||
|
||||
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
|
||||
57
pype/plugins/nukestudio/_unused/validate_task.py
Normal file
57
pype/plugins/nukestudio/_unused/validate_task.py
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
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 = ["nukestudio"]
|
||||
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 = ["nukestudio"]
|
||||
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
|
||||
13
pype/plugins/nukestudio/publish/collect_active_project.py
Normal file
13
pype/plugins/nukestudio/publish/collect_active_project.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import pyblish.api
|
||||
|
||||
|
||||
class CollectActiveProject(pyblish.api.ContextPlugin):
|
||||
"""Inject the active project into context"""
|
||||
|
||||
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"]))
|
||||
43
pype/plugins/nukestudio/publish/collect_clips.py
Normal file
43
pype/plugins/nukestudio/publish/collect_clips.py
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
from pyblish import api
|
||||
|
||||
|
||||
class CollectClips(api.ContextPlugin):
|
||||
"""Collect all Track items selection."""
|
||||
|
||||
order = api.CollectorOrder
|
||||
label = "Collect Clips"
|
||||
hosts = ["nukestudio"]
|
||||
|
||||
def process(self, context):
|
||||
data = {}
|
||||
for item in context.data.get("selection", []):
|
||||
self.log.info("__ item: {}".format(item))
|
||||
# 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:
|
||||
continue
|
||||
|
||||
data[item.name()] = {
|
||||
"item": item,
|
||||
"tasks": [],
|
||||
"startFrame": item.timelineIn(),
|
||||
"endFrame": item.timelineOut()
|
||||
}
|
||||
|
||||
for key, value in data.items():
|
||||
family = "clip"
|
||||
context.create_instance(
|
||||
name=key,
|
||||
subset="{0}{1}".format(family, 'Default'),
|
||||
asset=value["item"].name(),
|
||||
item=value["item"],
|
||||
family=family,
|
||||
tasks=value["tasks"],
|
||||
startFrame=value["startFrame"],
|
||||
endFrame=value["endFrame"],
|
||||
handles=0
|
||||
)
|
||||
27
pype/plugins/nukestudio/publish/collect_colorspace.py
Normal file
27
pype/plugins/nukestudio/publish/collect_colorspace.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
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"]))
|
||||
14
pype/plugins/nukestudio/publish/collect_current_file.py
Normal file
14
pype/plugins/nukestudio/publish/collect_current_file.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import pyblish.api
|
||||
|
||||
|
||||
class CollectCurrentFile(pyblish.api.ContextPlugin):
|
||||
"""Inject the current working file into context"""
|
||||
|
||||
order = pyblish.api.CollectorOrder
|
||||
|
||||
def process(self, context):
|
||||
"""Todo, inject the current working file"""
|
||||
|
||||
project = context.data('activeProject')
|
||||
context.set_data('currentFile', value=project.path())
|
||||
self.log.info("currentFile: {}".format(context.data["currentFile"]))
|
||||
13
pype/plugins/nukestudio/publish/collect_framerate.py
Normal file
13
pype/plugins/nukestudio/publish/collect_framerate.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
from pyblish import api
|
||||
|
||||
class CollectFramerate(api.ContextPlugin):
|
||||
"""Collect framerate from selected sequence."""
|
||||
|
||||
order = api.CollectorOrder
|
||||
label = "Collect Framerate"
|
||||
hosts = ["nukestudio"]
|
||||
|
||||
def process(self, context):
|
||||
for item in context.data.get("selection", []):
|
||||
context.data["framerate"] = item.sequence().framerate().toFloat()
|
||||
return
|
||||
72
pype/plugins/nukestudio/publish/collect_hierarchy_context.py
Normal file
72
pype/plugins/nukestudio/publish/collect_hierarchy_context.py
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import pyblish.api
|
||||
from avalon import api
|
||||
|
||||
|
||||
class CollectHierarchyContext(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 Context"
|
||||
order = pyblish.api.CollectorOrder + 0.1
|
||||
|
||||
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:
|
||||
new_dict[key] = ex_dict[key]
|
||||
return new_dict
|
||||
|
||||
def process(self, context):
|
||||
json_data = context.data.get("jsonData", None)
|
||||
temp_context = {}
|
||||
for instance in json_data['instances']:
|
||||
if instance['family'] in 'projectfile':
|
||||
continue
|
||||
|
||||
in_info = {}
|
||||
name = instance['name']
|
||||
# suppose that all instances are Shots
|
||||
in_info['entity_type'] = 'Shot'
|
||||
|
||||
instance_pyblish = [
|
||||
i for i in context.data["instances"] if i.data['asset'] in name][0]
|
||||
in_info['custom_attributes'] = {
|
||||
'fend': instance_pyblish.data['endFrame'],
|
||||
'fstart': instance_pyblish.data['startFrame'],
|
||||
'fps': instance_pyblish.data['fps']
|
||||
}
|
||||
|
||||
in_info['tasks'] = instance['tasks']
|
||||
|
||||
parents = instance.get('parents', [])
|
||||
|
||||
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)
|
||||
self.log.debug(temp_context)
|
||||
|
||||
# TODO: 100% sure way of get project! Will be Name or Code?
|
||||
project_name = api.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"]))
|
||||
13
pype/plugins/nukestudio/publish/collect_host.py
Normal file
13
pype/plugins/nukestudio/publish/collect_host.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
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()))
|
||||
12
pype/plugins/nukestudio/publish/collect_host_version.py
Normal file
12
pype/plugins/nukestudio/publish/collect_host_version.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import pyblish.api
|
||||
|
||||
|
||||
class CollectHostVersion(pyblish.api.ContextPlugin):
|
||||
"""Inject the hosts version into context"""
|
||||
|
||||
order = pyblish.api.CollectorOrder
|
||||
|
||||
def process(self, context):
|
||||
import nuke
|
||||
|
||||
context.set_data('hostVersion', value=nuke.NUKE_VERSION_STRING)
|
||||
30
pype/plugins/nukestudio/publish/collect_metadata.py
Normal file
30
pype/plugins/nukestudio/publish/collect_metadata.py
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
from pyblish import api
|
||||
|
||||
|
||||
class CollectClipMetadata(api.InstancePlugin):
|
||||
"""Collect Metadata from selected track items."""
|
||||
|
||||
order = api.CollectorOrder + 0.01
|
||||
label = "Collect Metadata"
|
||||
hosts = ["nukestudio"]
|
||||
|
||||
def process(self, instance):
|
||||
item = instance.data["item"]
|
||||
ti_metadata = self.metadata_to_string(dict(item.metadata()))
|
||||
ms_metadata = self.metadata_to_string(
|
||||
dict(item.source().mediaSource().metadata()))
|
||||
|
||||
instance.data["clipMetadata"] = ti_metadata
|
||||
instance.data["mediaSourceMetadata"] = ms_metadata
|
||||
|
||||
self.log.info(instance.data["clipMetadata"])
|
||||
self.log.info(instance.data["mediaSourceMetadata"])
|
||||
return
|
||||
|
||||
def metadata_to_string(self, metadata):
|
||||
data = dict()
|
||||
for k, v in metadata.items():
|
||||
if v not in ["-", ""]:
|
||||
data[str(k)] = v
|
||||
|
||||
return data
|
||||
15
pype/plugins/nukestudio/publish/collect_selection.py
Normal file
15
pype/plugins/nukestudio/publish/collect_selection.py
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
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 = getattr(hiero, "selection")
|
||||
|
||||
self.log.debug("selection: {}".format(selection))
|
||||
context.data["selection"] = hiero.selection
|
||||
45
pype/plugins/nukestudio/publish/collect_subsets.py
Normal file
45
pype/plugins/nukestudio/publish/collect_subsets.py
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
from pyblish import api
|
||||
|
||||
|
||||
class CollectClipSubsets(api.InstancePlugin):
|
||||
"""Collect Subsets from selected Clips, Tags, Preset."""
|
||||
|
||||
order = api.CollectorOrder + 0.01
|
||||
label = "Collect Subsets"
|
||||
hosts = ["nukestudio"]
|
||||
families = ['clip']
|
||||
|
||||
def process(self, instance):
|
||||
tags = instance.data.get('tags', None)
|
||||
presets = instance.context.data['presets'][
|
||||
instance.context.data['host']]
|
||||
if tags:
|
||||
self.log.info(tags)
|
||||
|
||||
if presets:
|
||||
self.log.info(presets)
|
||||
|
||||
# get presets and tags
|
||||
# iterate tags and get task family
|
||||
# iterate tags and get host family
|
||||
# iterate tags and get handles family
|
||||
|
||||
instance = instance.context.create_instance(instance_name)
|
||||
|
||||
instance.data.update({
|
||||
"subset": subset_name,
|
||||
"stagingDir": staging_dir,
|
||||
"task": task,
|
||||
"representation": ext[1:],
|
||||
"host": host,
|
||||
"asset": asset_name,
|
||||
"label": label,
|
||||
"name": name,
|
||||
# "hierarchy": hierarchy,
|
||||
# "parents": parents,
|
||||
"family": family,
|
||||
"families": [families, 'ftrack'],
|
||||
"publish": True,
|
||||
# "files": files_list
|
||||
})
|
||||
instances.append(instance)
|
||||
30
pype/plugins/nukestudio/publish/collect_tags.py
Normal file
30
pype/plugins/nukestudio/publish/collect_tags.py
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
from pyblish import api
|
||||
|
||||
|
||||
class CollectClipTags(api.InstancePlugin):
|
||||
"""Collect Tags from selected track items."""
|
||||
|
||||
order = api.CollectorOrder
|
||||
label = "Collect Tags"
|
||||
hosts = ["nukestudio"]
|
||||
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
|
||||
101
pype/plugins/nukestudio/publish/extract_review.py
Normal file
101
pype/plugins/nukestudio/publish/extract_review.py
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
from pyblish import api
|
||||
|
||||
|
||||
class ExtractReview(api.InstancePlugin):
|
||||
"""Extracts movie for review"""
|
||||
|
||||
order = api.ExtractorOrder
|
||||
label = "NukeStudio Review"
|
||||
optional = True
|
||||
hosts = ["nukestudio"]
|
||||
families = ["review"]
|
||||
|
||||
def process(self, instance):
|
||||
import os
|
||||
import time
|
||||
|
||||
import hiero.core
|
||||
from hiero.exporters.FnExportUtil import writeSequenceAudioWithHandles
|
||||
|
||||
nukeWriter = hiero.core.nuke.ScriptWriter()
|
||||
|
||||
item = instance.data["item"]
|
||||
|
||||
handles = instance.data["handles"]
|
||||
|
||||
sequence = item.parent().parent()
|
||||
|
||||
output_path = os.path.abspath(
|
||||
os.path.join(
|
||||
instance.context.data["currentFile"], "..", "workspace"
|
||||
)
|
||||
)
|
||||
|
||||
# Generate audio
|
||||
audio_file = os.path.join(
|
||||
output_path, "{0}.wav".format(instance.data["name"])
|
||||
)
|
||||
|
||||
writeSequenceAudioWithHandles(
|
||||
audio_file,
|
||||
sequence,
|
||||
item.timelineIn(),
|
||||
item.timelineOut(),
|
||||
handles,
|
||||
handles
|
||||
)
|
||||
|
||||
# Generate Nuke script
|
||||
root_node = hiero.core.nuke.RootNode(
|
||||
item.timelineIn() - handles,
|
||||
item.timelineOut() + handles,
|
||||
fps=sequence.framerate()
|
||||
)
|
||||
|
||||
root_node.addProjectSettings(instance.context.data["colorspace"])
|
||||
|
||||
nukeWriter.addNode(root_node)
|
||||
|
||||
item.addToNukeScript(
|
||||
script=nukeWriter,
|
||||
includeRetimes=True,
|
||||
retimeMethod="Frame",
|
||||
startHandle=handles,
|
||||
endHandle=handles
|
||||
)
|
||||
|
||||
movie_path = os.path.join(
|
||||
output_path, "{0}.mov".format(instance.data["name"])
|
||||
)
|
||||
write_node = hiero.core.nuke.WriteNode(movie_path.replace("\\", "/"))
|
||||
self.log.info("__ write_node: {0}".format(write_node))
|
||||
write_node.setKnob("file_type", "mov")
|
||||
write_node.setKnob("colorspace", instance.context.data["colorspace"]["lutSettingFloat"])
|
||||
write_node.setKnob("meta_codec", "ap4h")
|
||||
write_node.setKnob("mov64_codec", "ap4h")
|
||||
write_node.setKnob("mov64_bitrate", 400000)
|
||||
write_node.setKnob("mov64_bitrate_tolerance", 40000000)
|
||||
write_node.setKnob("mov64_quality_min", 2)
|
||||
write_node.setKnob("mov64_quality_max", 31)
|
||||
write_node.setKnob("mov64_gop_size", 12)
|
||||
write_node.setKnob("mov64_b_frames", 0)
|
||||
write_node.setKnob("raw", True )
|
||||
write_node.setKnob("mov64_audiofile", audio_file.replace("\\", "/"))
|
||||
write_node.setKnob("mov32_fps", sequence.framerate())
|
||||
nukeWriter.addNode(write_node)
|
||||
|
||||
nukescript_path = movie_path.replace(".mov", ".nk")
|
||||
nukeWriter.writeToDisk(nukescript_path)
|
||||
|
||||
process = hiero.core.nuke.executeNukeScript(
|
||||
nukescript_path,
|
||||
open(movie_path.replace(".mov", ".log"), "w")
|
||||
)
|
||||
|
||||
while process.poll() is None:
|
||||
time.sleep(0.5)
|
||||
|
||||
assert os.path.exists(movie_path), "Creating review failed."
|
||||
|
||||
instance.data["output_path"] = movie_path
|
||||
instance.data["review_family"] = "mov"
|
||||
132
pype/plugins/nukestudio/publish/integrate_assumed_destination.py
Normal file
132
pype/plugins/nukestudio/publish/integrate_assumed_destination.py
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
import pyblish.api
|
||||
import os
|
||||
|
||||
from avalon import io, api
|
||||
|
||||
|
||||
class IntegrateAssumedDestination(pyblish.api.InstancePlugin):
|
||||
"""Generate the assumed destination path where the file will be stored"""
|
||||
|
||||
label = "Integrate Assumed Destination"
|
||||
order = pyblish.api.IntegratorOrder - 0.05
|
||||
families = ["clip", "projectfile"]
|
||||
|
||||
def process(self, instance):
|
||||
|
||||
self.create_destination_template(instance)
|
||||
|
||||
template_data = instance.data["assumedTemplateData"]
|
||||
# template = instance.data["template"]
|
||||
|
||||
anatomy = instance.context.data['anatomy']
|
||||
# template = anatomy.publish.path
|
||||
anatomy_filled = anatomy.format(template_data)
|
||||
mock_template = anatomy_filled.publish.path
|
||||
|
||||
# For now assume resources end up in a "resources" folder in the
|
||||
# published folder
|
||||
mock_destination = os.path.join(os.path.dirname(mock_template),
|
||||
"resources")
|
||||
|
||||
# Clean the path
|
||||
mock_destination = os.path.abspath(os.path.normpath(mock_destination))
|
||||
|
||||
# Define resource destination and transfers
|
||||
resources = instance.data.get("resources", list())
|
||||
transfers = instance.data.get("transfers", list())
|
||||
for resource in resources:
|
||||
|
||||
# Add destination to the resource
|
||||
source_filename = os.path.basename(resource["source"])
|
||||
destination = os.path.join(mock_destination, source_filename)
|
||||
|
||||
# Force forward slashes to fix issue with software unable
|
||||
# to work correctly with backslashes in specific scenarios
|
||||
# (e.g. escape characters in PLN-151 V-Ray UDIM)
|
||||
destination = destination.replace("\\", "/")
|
||||
|
||||
resource['destination'] = destination
|
||||
|
||||
# Collect transfers for the individual files of the resource
|
||||
# e.g. all individual files of a cache or UDIM textures.
|
||||
files = resource['files']
|
||||
for fsrc in files:
|
||||
fname = os.path.basename(fsrc)
|
||||
fdest = os.path.join(mock_destination, fname)
|
||||
transfers.append([fsrc, fdest])
|
||||
|
||||
instance.data["resources"] = resources
|
||||
instance.data["transfers"] = transfers
|
||||
|
||||
def create_destination_template(self, instance):
|
||||
"""Create a filepath based on the current data available
|
||||
|
||||
Example template:
|
||||
{root}/{project}/{silo}/{asset}/publish/{subset}/v{version:0>3}/
|
||||
{subset}.{representation}
|
||||
Args:
|
||||
instance: the instance to publish
|
||||
|
||||
Returns:
|
||||
file path (str)
|
||||
"""
|
||||
|
||||
# get all the stuff from the database
|
||||
subset_name = instance.data["subset"]
|
||||
self.log.info(subset_name)
|
||||
asset_name = instance.data["asset"]
|
||||
project_name = api.Session["AVALON_PROJECT"]
|
||||
|
||||
project = io.find_one({"type": "project",
|
||||
"name": project_name},
|
||||
projection={"config": True, "data": True})
|
||||
|
||||
template = project["config"]["template"]["publish"]
|
||||
# anatomy = instance.context.data['anatomy']
|
||||
|
||||
asset = io.find_one({"type": "asset",
|
||||
"name": asset_name,
|
||||
"parent": project["_id"]})
|
||||
|
||||
assert asset, ("No asset found by the name '{}' "
|
||||
"in project '{}'".format(asset_name, project_name))
|
||||
silo = asset['silo']
|
||||
|
||||
subset = io.find_one({"type": "subset",
|
||||
"name": subset_name,
|
||||
"parent": asset["_id"]})
|
||||
|
||||
# assume there is no version yet, we start at `1`
|
||||
version = None
|
||||
version_number = 1
|
||||
if subset is not None:
|
||||
version = io.find_one({"type": "version",
|
||||
"parent": subset["_id"]},
|
||||
sort=[("name", -1)])
|
||||
|
||||
# if there is a subset there ought to be version
|
||||
if version is not None:
|
||||
version_number += version["name"]
|
||||
|
||||
if instance.data.get('version'):
|
||||
version_number = int(instance.data.get('version'))
|
||||
|
||||
hierarchy = asset['data']['parents']
|
||||
if hierarchy:
|
||||
# hierarchy = os.path.sep.join(hierarchy)
|
||||
hierarchy = os.path.join(*hierarchy)
|
||||
|
||||
template_data = {"root": api.Session["AVALON_PROJECTS"],
|
||||
"project": {"name": project_name,
|
||||
"code": project['data']['code']},
|
||||
"silo": silo,
|
||||
"family": instance.data['family'],
|
||||
"asset": asset_name,
|
||||
"subset": subset_name,
|
||||
"version": version_number,
|
||||
"hierarchy": hierarchy,
|
||||
"representation": "TEMP"}
|
||||
|
||||
instance.data["assumedTemplateData"] = template_data
|
||||
self.log.info(template_data)
|
||||
instance.data["template"] = template
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
import pyblish.api
|
||||
|
||||
|
||||
class IntegrateFtrackComponentOverwrite(pyblish.api.InstancePlugin):
|
||||
"""
|
||||
Set `component_overwrite` to True on all instances `ftrackComponentsList`
|
||||
"""
|
||||
|
||||
order = pyblish.api.IntegratorOrder + 0.49
|
||||
label = 'Overwrite ftrack created versions'
|
||||
families = ["clip"]
|
||||
optional = True
|
||||
active = False
|
||||
|
||||
def process(self, instance):
|
||||
component_list = instance.data['ftrackComponentsList']
|
||||
|
||||
for cl in component_list:
|
||||
cl['component_overwrite'] = True
|
||||
self.log.debug('Component {} overwriting'.format(
|
||||
cl['component_data']['name']))
|
||||
140
pype/plugins/nukestudio/publish/integrate_hierarchy_avalon.py
Normal file
140
pype/plugins/nukestudio/publish/integrate_hierarchy_avalon.py
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
import pyblish.api
|
||||
from avalon import io
|
||||
|
||||
|
||||
class IntegrateHierarchyToAvalon(pyblish.api.ContextPlugin):
|
||||
"""
|
||||
Create entities in ftrack based on collected data from premiere
|
||||
|
||||
"""
|
||||
|
||||
order = pyblish.api.IntegratorOrder - 0.1
|
||||
label = 'Integrate Hierarchy To Avalon'
|
||||
families = ['clip']
|
||||
|
||||
def process(self, context):
|
||||
if "hierarchyContext" not in context.data:
|
||||
return
|
||||
|
||||
self.db = io
|
||||
if not self.db.Session:
|
||||
self.db.install()
|
||||
|
||||
input_data = context.data["hierarchyContext"]
|
||||
self.import_to_avalon(input_data)
|
||||
|
||||
def import_to_avalon(self, input_data, parent=None):
|
||||
|
||||
for name in input_data:
|
||||
self.log.info('input_data[name]: {}'.format(input_data[name]))
|
||||
entity_data = input_data[name]
|
||||
entity_type = entity_data['entity_type']
|
||||
|
||||
data = {}
|
||||
# Process project
|
||||
if entity_type.lower() == 'project':
|
||||
entity = self.db.find_one({'type': 'project'})
|
||||
# TODO: should be in validator?
|
||||
assert (entity is not None), "Didn't find project in DB"
|
||||
|
||||
# get data from already existing project
|
||||
for key, value in entity.get('data', {}).items():
|
||||
data[key] = value
|
||||
|
||||
self.av_project = entity
|
||||
# Raise error if project or parent are not set
|
||||
elif self.av_project is None or parent is None:
|
||||
raise AssertionError(
|
||||
"Collected items are not in right order!"
|
||||
)
|
||||
# Else process assset
|
||||
else:
|
||||
entity = self.db.find_one({'type': 'asset', 'name': name})
|
||||
# Create entity if doesn't exist
|
||||
if entity is None:
|
||||
if self.av_project['_id'] == parent['_id']:
|
||||
silo = None
|
||||
elif parent['silo'] is None:
|
||||
silo = parent['name']
|
||||
else:
|
||||
silo = parent['silo']
|
||||
entity = self.create_avalon_asset(name, silo)
|
||||
self.log.info('entity: {}'.format(entity))
|
||||
self.log.info('data: {}'.format(entity.get('data', {})))
|
||||
self.log.info('____1____')
|
||||
data['entityType'] = entity_type
|
||||
# TASKS
|
||||
tasks = entity_data.get('tasks', [])
|
||||
if tasks is not None or len(tasks) > 0:
|
||||
data['tasks'] = tasks
|
||||
parents = []
|
||||
visualParent = None
|
||||
data = input_data[name]
|
||||
if self.av_project['_id'] != parent['_id']:
|
||||
visualParent = parent['_id']
|
||||
parents.extend(parent.get('data', {}).get('parents', []))
|
||||
parents.append(parent['name'])
|
||||
data['visualParent'] = visualParent
|
||||
data['parents'] = parents
|
||||
|
||||
self.db.update_many(
|
||||
{'_id': entity['_id']},
|
||||
{'$set': {
|
||||
'data': data,
|
||||
}})
|
||||
|
||||
entity = self.db.find_one({'type': 'asset', 'name': name})
|
||||
self.log.info('entity: {}'.format(entity))
|
||||
self.log.info('data: {}'.format(entity.get('data', {})))
|
||||
self.log.info('____2____')
|
||||
|
||||
# Else get data from already existing
|
||||
else:
|
||||
self.log.info('entity: {}'.format(entity))
|
||||
self.log.info('data: {}'.format(entity.get('data', {})))
|
||||
self.log.info('________')
|
||||
for key, value in entity.get('data', {}).items():
|
||||
data[key] = value
|
||||
|
||||
data['entityType'] = entity_type
|
||||
# TASKS
|
||||
tasks = entity_data.get('tasks', [])
|
||||
if tasks is not None or len(tasks) > 0:
|
||||
data['tasks'] = tasks
|
||||
parents = []
|
||||
visualParent = None
|
||||
# do not store project's id as visualParent (silo asset)
|
||||
|
||||
if self.av_project['_id'] != parent['_id']:
|
||||
visualParent = parent['_id']
|
||||
parents.extend(parent.get('data', {}).get('parents', []))
|
||||
parents.append(parent['name'])
|
||||
data['visualParent'] = visualParent
|
||||
data['parents'] = parents
|
||||
|
||||
# CUSTOM ATTRIBUTES
|
||||
for k, val in entity_data.get('custom_attributes', {}).items():
|
||||
data[k] = val
|
||||
|
||||
# Update entity data with input data
|
||||
self.db.update_many(
|
||||
{'_id': entity['_id']},
|
||||
{'$set': {
|
||||
'data': data,
|
||||
}})
|
||||
|
||||
if 'childs' in entity_data:
|
||||
self.import_to_avalon(entity_data['childs'], entity)
|
||||
|
||||
def create_avalon_asset(self, name, silo):
|
||||
item = {
|
||||
'schema': 'avalon-core:asset-2.0',
|
||||
'name': name,
|
||||
'silo': silo,
|
||||
'parent': self.av_project['_id'],
|
||||
'type': 'asset',
|
||||
'data': {}
|
||||
}
|
||||
entity_id = self.db.insert_one(item).inserted_id
|
||||
|
||||
return self.db.find_one({'_id': entity_id})
|
||||
155
pype/plugins/nukestudio/publish/integrate_hierarchy_ftrack.py
Normal file
155
pype/plugins/nukestudio/publish/integrate_hierarchy_ftrack.py
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
import pyblish.api
|
||||
|
||||
|
||||
class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin):
|
||||
"""
|
||||
Create entities in ftrack based on collected data from premiere
|
||||
Example of entry data:
|
||||
{
|
||||
"ProjectXS": {
|
||||
"entity_type": "Project",
|
||||
"custom_attributes": {
|
||||
"fps": 24,...
|
||||
},
|
||||
"tasks": [
|
||||
"Compositing",
|
||||
"Lighting",... *task must exist as task type in project schema*
|
||||
],
|
||||
"childs": {
|
||||
"sq01": {
|
||||
"entity_type": "Sequence",
|
||||
...
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
order = pyblish.api.IntegratorOrder
|
||||
label = 'Integrate Hierarchy To Ftrack'
|
||||
families = ["clip"]
|
||||
optional = False
|
||||
|
||||
def process(self, context):
|
||||
self.context = context
|
||||
if "hierarchyContext" not in context.data:
|
||||
return
|
||||
|
||||
self.ft_project = None
|
||||
self.session = context.data["ftrackSession"]
|
||||
|
||||
input_data = context.data["hierarchyContext"]
|
||||
|
||||
# adding ftrack types from presets
|
||||
ftrack_types = context.data['ftrackTypes']
|
||||
|
||||
self.import_to_ftrack(input_data, ftrack_types)
|
||||
|
||||
def import_to_ftrack(self, input_data, ftrack_types, parent=None):
|
||||
for entity_name in input_data:
|
||||
entity_data = input_data[entity_name]
|
||||
entity_type = entity_data['entity_type'].capitalize()
|
||||
|
||||
if entity_type.lower() == 'project':
|
||||
query = 'Project where full_name is "{}"'.format(entity_name)
|
||||
entity = self.session.query(query).one()
|
||||
self.ft_project = entity
|
||||
self.task_types = self.get_all_task_types(entity)
|
||||
|
||||
elif self.ft_project is None or parent is None:
|
||||
raise AssertionError(
|
||||
"Collected items are not in right order!"
|
||||
)
|
||||
|
||||
# try to find if entity already exists
|
||||
else:
|
||||
query = '{} where name is "{}" and parent_id is "{}"'.format(
|
||||
entity_type, entity_name, parent['id']
|
||||
)
|
||||
try:
|
||||
entity = self.session.query(query).one()
|
||||
except Exception:
|
||||
entity = None
|
||||
|
||||
# Create entity if not exists
|
||||
if entity is None:
|
||||
entity = self.create_entity(
|
||||
name=entity_name,
|
||||
type=entity_type,
|
||||
parent=parent
|
||||
)
|
||||
# self.log.info('entity: {}'.format(dict(entity)))
|
||||
# CUSTOM ATTRIBUTES
|
||||
custom_attributes = entity_data.get('custom_attributes', [])
|
||||
instances = [
|
||||
i for i in self.context.data["instances"] if i.data['asset'] in entity['name']]
|
||||
for key in custom_attributes:
|
||||
assert (key in entity['custom_attributes']), (
|
||||
'Missing custom attribute')
|
||||
|
||||
entity['custom_attributes'][key] = custom_attributes[key]
|
||||
for instance in instances:
|
||||
instance.data['ftrackShotId'] = entity['id']
|
||||
|
||||
self.session.commit()
|
||||
|
||||
# TASKS
|
||||
tasks = entity_data.get('tasks', [])
|
||||
existing_tasks = []
|
||||
tasks_to_create = []
|
||||
for child in entity['children']:
|
||||
if child.entity_type.lower() == 'task':
|
||||
existing_tasks.append(child['name'])
|
||||
# existing_tasks.append(child['type']['name'])
|
||||
|
||||
for task in tasks:
|
||||
if task in existing_tasks:
|
||||
print("Task {} already exists".format(task))
|
||||
continue
|
||||
tasks_to_create.append(task)
|
||||
|
||||
for task in tasks_to_create:
|
||||
self.create_task(
|
||||
name=task,
|
||||
task_type=ftrack_types[task],
|
||||
parent=entity
|
||||
)
|
||||
self.session.commit()
|
||||
|
||||
if 'childs' in entity_data:
|
||||
self.import_to_ftrack(
|
||||
entity_data['childs'], ftrack_types, entity)
|
||||
|
||||
def get_all_task_types(self, project):
|
||||
tasks = {}
|
||||
proj_template = project['project_schema']
|
||||
temp_task_types = proj_template['_task_type_schema']['types']
|
||||
|
||||
for type in temp_task_types:
|
||||
if type['name'] not in tasks:
|
||||
tasks[type['name']] = type
|
||||
|
||||
return tasks
|
||||
|
||||
def create_task(self, name, task_type, parent):
|
||||
task = self.session.create('Task', {
|
||||
'name': name,
|
||||
'parent': parent
|
||||
})
|
||||
# TODO not secured!!! - check if task_type exists
|
||||
self.log.info(task_type)
|
||||
self.log.info(self.task_types)
|
||||
task['type'] = self.task_types[task_type]
|
||||
|
||||
self.session.commit()
|
||||
|
||||
return task
|
||||
|
||||
def create_entity(self, name, type, parent):
|
||||
entity = self.session.create(type, {
|
||||
'name': name,
|
||||
'parent': parent
|
||||
})
|
||||
self.session.commit()
|
||||
|
||||
return entity
|
||||
42
pype/plugins/nukestudio/publish/validate_names.py
Normal file
42
pype/plugins/nukestudio/publish/validate_names.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
from pyblish import api
|
||||
|
||||
|
||||
class ValidateNames(api.InstancePlugin):
|
||||
"""Validate sequence, video track and track item names.
|
||||
|
||||
When creating output directories with the name of an item, ending with a
|
||||
whitespace will fail the extraction.
|
||||
Exact matching to optimize processing.
|
||||
"""
|
||||
|
||||
order = api.ValidatorOrder
|
||||
families = ["clip"]
|
||||
match = api.Exact
|
||||
label = "Names"
|
||||
hosts = ["nukestudio"]
|
||||
|
||||
def process(self, instance):
|
||||
|
||||
item = instance.data["item"]
|
||||
|
||||
msg = "Track item \"{0}\" ends with a whitespace."
|
||||
assert not item.name().endswith(" "), msg.format(item.name())
|
||||
|
||||
msg = "Video track \"{0}\" ends with a whitespace."
|
||||
msg = msg.format(item.parent().name())
|
||||
assert not item.parent().name().endswith(" "), msg
|
||||
|
||||
msg = "Sequence \"{0}\" ends with a whitespace."
|
||||
msg = msg.format(item.parent().parent().name())
|
||||
assert not item.parent().parent().name().endswith(" "), msg
|
||||
|
||||
|
||||
class ValidateNamesFtrack(ValidateNames):
|
||||
"""Validate sequence, video track and track item names.
|
||||
|
||||
Because we are matching the families exactly, we need this plugin to
|
||||
accommodate for the ftrack family addition.
|
||||
"""
|
||||
|
||||
order = api.ValidatorOrder
|
||||
families = ["clip", "ftrack"]
|
||||
52
pype/plugins/nukestudio/publish/validate_projectroot.py
Normal file
52
pype/plugins/nukestudio/publish/validate_projectroot.py
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
from pyblish import api
|
||||
|
||||
|
||||
class RepairProjectRoot(api.Action):
|
||||
|
||||
label = "Repair"
|
||||
icon = "wrench"
|
||||
on = "failed"
|
||||
|
||||
def process(self, context, plugin):
|
||||
import os
|
||||
|
||||
workspace = os.path.join(
|
||||
os.path.dirname(context.data["currentFile"]),
|
||||
"workspace"
|
||||
).replace("\\", "/")
|
||||
|
||||
if not os.path.exists(workspace):
|
||||
os.makedirs(workspace)
|
||||
|
||||
context.data["activeProject"].setProjectRoot(workspace)
|
||||
|
||||
# Need to manually fix the tasks "_projectRoot" attribute, because
|
||||
# setting the project root is not enough.
|
||||
submission = context.data.get("submission", None)
|
||||
if submission:
|
||||
for task in submission.getLeafTasks():
|
||||
task._projectRoot = workspace
|
||||
|
||||
|
||||
class ValidateProjectRoot(api.ContextPlugin):
|
||||
"""Validate the project root to the workspace directory."""
|
||||
|
||||
order = api.ValidatorOrder
|
||||
label = "Project Root"
|
||||
hosts = ["nukestudio"]
|
||||
actions = [RepairProjectRoot]
|
||||
|
||||
def process(self, context):
|
||||
import os
|
||||
|
||||
workspace = os.path.join(
|
||||
os.path.dirname(context.data["currentFile"]),
|
||||
"workspace"
|
||||
).replace("\\", "/")
|
||||
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
|
||||
46
pype/plugins/nukestudio/publish/validate_track_item.py
Normal file
46
pype/plugins/nukestudio/publish/validate_track_item.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
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 = ["nukestudio"]
|
||||
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()
|
||||
)
|
||||
21
pype/plugins/nukestudio/publish/validate_viewer_lut.py
Normal file
21
pype/plugins/nukestudio/publish/validate_viewer_lut.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
from pyblish import api
|
||||
|
||||
|
||||
class ValidateViewerLut(api.ContextPlugin):
|
||||
"""Validate viewer lut in NukeStudio is the same as in Nuke."""
|
||||
|
||||
order = api.ValidatorOrder
|
||||
label = "Viewer LUT"
|
||||
hosts = ["nukestudio"]
|
||||
optional = True
|
||||
|
||||
def process(self, context):
|
||||
import nuke
|
||||
import hiero
|
||||
|
||||
# nuke_lut = nuke.ViewerProcess.node()["current"].value()
|
||||
nukestudio_lut = context.data["activeProject"].lutSettingViewer()
|
||||
self.log.info("__ nukestudio_lut: {}".format(nukestudio_lut))
|
||||
|
||||
msg = "Viewer LUT can only be RGB"
|
||||
assert "RGB" in nukestudio_lut, msg
|
||||
|
|
@ -55,10 +55,10 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin):
|
|||
instance = context.create_instance(subset)
|
||||
|
||||
instance.data.update({
|
||||
"subset": family + subset,
|
||||
"subset": subset,
|
||||
"asset": asset_name,
|
||||
"label": family + subset,
|
||||
"name": family + subset,
|
||||
"label": subset,
|
||||
"name": subset,
|
||||
"family": family,
|
||||
"families": [family, 'ftrack'],
|
||||
})
|
||||
|
|
@ -74,10 +74,9 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin):
|
|||
collections, remainder = clique.assemble(component['files'])
|
||||
if collections:
|
||||
self.log.debug(collections)
|
||||
range = collections[0].format('{range}')
|
||||
instance.data['startFrame'] = range.split('-')[0]
|
||||
instance.data['endFrame'] = range.split('-')[1]
|
||||
|
||||
instance.data['startFrame'] = component['startFrame']
|
||||
instance.data['endFrame'] = component['endFrame']
|
||||
instance.data['frameRate'] = component['frameRate']
|
||||
|
||||
instance.data["files"].append(component)
|
||||
instance.data["representations"].append(component)
|
||||
|
|
|
|||
|
|
@ -57,19 +57,19 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin):
|
|||
"name": "thumbnail" # Default component name is "main".
|
||||
}
|
||||
elif comp['preview']:
|
||||
if not instance.data.get('startFrameReview'):
|
||||
instance.data['startFrameReview'] = instance.data['startFrame']
|
||||
if not instance.data.get('endFrameReview'):
|
||||
instance.data['endFrameReview'] = instance.data['endFrame']
|
||||
if not comp.get('startFrameReview'):
|
||||
comp['startFrameReview'] = comp['startFrame']
|
||||
if not comp.get('endFrameReview'):
|
||||
comp['endFrameReview'] = instance.data['endFrame']
|
||||
location = ft_session.query(
|
||||
'Location where name is "ftrack.server"').one()
|
||||
component_data = {
|
||||
# Default component name is "main".
|
||||
"name": "ftrackreview-mp4",
|
||||
"metadata": {'ftr_meta': json.dumps({
|
||||
'frameIn': int(instance.data['startFrameReview']),
|
||||
'frameOut': int(instance.data['endFrameReview']),
|
||||
'frameRate': 25.0})}
|
||||
'frameIn': int(comp['startFrameReview']),
|
||||
'frameOut': int(comp['endFrameReview']),
|
||||
'frameRate': float(comp['frameRate')]})}
|
||||
}
|
||||
else:
|
||||
component_data = {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ from pyblish import api as pyblish
|
|||
from pypeapp import Logger
|
||||
from .. import api
|
||||
|
||||
from ..widgets.message_window import message
|
||||
|
||||
import requests
|
||||
|
||||
log = Logger().get_logger(__name__, "premiere")
|
||||
|
|
@ -42,7 +44,7 @@ def request_aport(url_path, data={}):
|
|||
return req
|
||||
|
||||
except Exception as e:
|
||||
api.message(title="Premiere Aport Server",
|
||||
message(title="Premiere Aport Server",
|
||||
message="Before you can run Premiere, start Aport Server. \n Error: {}".format(
|
||||
e),
|
||||
level="critical")
|
||||
|
|
@ -99,7 +101,7 @@ def install():
|
|||
|
||||
# synchronize extensions
|
||||
extensions_sync()
|
||||
api.message(title="pyblish_paths", message=str(reg_paths), level="info")
|
||||
message(title="pyblish_paths", message=str(reg_paths), level="info")
|
||||
|
||||
|
||||
def uninstall():
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ class Window(QtWidgets.QDialog):
|
|||
initialized = False
|
||||
WIDTH = 1100
|
||||
HEIGHT = 500
|
||||
NOT_SELECTED = '< Nothing is selected >'
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(Window, self).__init__(parent=parent)
|
||||
|
|
@ -40,19 +39,9 @@ class Window(QtWidgets.QDialog):
|
|||
# Validators
|
||||
self.valid_parent = False
|
||||
|
||||
# statusbar - added under asset_widget
|
||||
label_message = QtWidgets.QLabel()
|
||||
label_message.setFixedHeight(20)
|
||||
|
||||
# assets widget
|
||||
widget_assets_wrap = QtWidgets.QWidget()
|
||||
widget_assets_wrap.setContentsMargins(0, 0, 0, 0)
|
||||
widget_assets = AssetWidget(self)
|
||||
|
||||
layout_assets = QtWidgets.QVBoxLayout(widget_assets_wrap)
|
||||
layout_assets.addWidget(widget_assets)
|
||||
layout_assets.addWidget(label_message)
|
||||
|
||||
# family widget
|
||||
widget_family = FamilyWidget(self)
|
||||
|
||||
|
|
@ -67,10 +56,10 @@ class Window(QtWidgets.QDialog):
|
|||
QtWidgets.QSizePolicy.Expanding
|
||||
)
|
||||
body.setOrientation(QtCore.Qt.Horizontal)
|
||||
body.addWidget(widget_assets_wrap)
|
||||
body.addWidget(widget_assets)
|
||||
body.addWidget(widget_family)
|
||||
body.addWidget(widget_components)
|
||||
body.setStretchFactor(body.indexOf(widget_assets_wrap), 2)
|
||||
body.setStretchFactor(body.indexOf(widget_assets), 2)
|
||||
body.setStretchFactor(body.indexOf(widget_family), 3)
|
||||
body.setStretchFactor(body.indexOf(widget_components), 5)
|
||||
|
||||
|
|
@ -82,13 +71,10 @@ class Window(QtWidgets.QDialog):
|
|||
# signals
|
||||
widget_assets.selection_changed.connect(self.on_asset_changed)
|
||||
|
||||
self.label_message = label_message
|
||||
self.widget_assets = widget_assets
|
||||
self.widget_family = widget_family
|
||||
self.widget_components = widget_components
|
||||
|
||||
self.echo("Connected to Database")
|
||||
|
||||
# on start
|
||||
self.on_start()
|
||||
|
||||
|
|
@ -131,22 +117,6 @@ class Window(QtWidgets.QDialog):
|
|||
parents.append(parent['name'])
|
||||
return parents
|
||||
|
||||
def echo(self, message):
|
||||
''' Shows message in label that disappear in 5s
|
||||
:param message: Message that will be displayed
|
||||
:type message: str
|
||||
'''
|
||||
self.label_message.setText(str(message))
|
||||
def clear_text():
|
||||
''' Helps prevent crash if this Window object
|
||||
is deleted before 5s passed
|
||||
'''
|
||||
try:
|
||||
self.label_message.set_text("")
|
||||
except:
|
||||
pass
|
||||
QtCore.QTimer.singleShot(5000, lambda: clear_text())
|
||||
|
||||
def on_asset_changed(self):
|
||||
'''Callback on asset selection changed
|
||||
|
||||
|
|
@ -160,7 +130,7 @@ class Window(QtWidgets.QDialog):
|
|||
self.widget_family.change_asset(asset['name'])
|
||||
else:
|
||||
self.valid_parent = False
|
||||
self.widget_family.change_asset(self.NOT_SELECTED)
|
||||
self.widget_family.change_asset(None)
|
||||
self.widget_family.on_data_changed()
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
|
|
|
|||
|
|
@ -20,15 +20,14 @@ from .model_tree_view_deselectable import DeselectableTreeView
|
|||
|
||||
from .widget_asset_view import AssetView
|
||||
from .widget_asset import AssetWidget
|
||||
|
||||
from .widget_family_desc import FamilyDescriptionWidget
|
||||
from .widget_family import FamilyWidget
|
||||
|
||||
from .widget_drop_empty import DropEmpty
|
||||
from .widget_component_item import ComponentItem
|
||||
from .widget_components_list import ComponentsList
|
||||
|
||||
from .widget_drop_frame import DropDataFrame
|
||||
|
||||
from .widget_components import ComponentsWidget
|
||||
|
||||
from.widget_shadow import ShadowWidget
|
||||
from .widget_shadow import ShadowWidget
|
||||
|
|
|
|||
|
|
@ -8,13 +8,13 @@ class TasksTemplateModel(TreeModel):
|
|||
|
||||
COLUMNS = ["Tasks"]
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, selectable=True):
|
||||
super(TasksTemplateModel, self).__init__()
|
||||
self.selectable = False
|
||||
self._icons = {
|
||||
"__default__": awesome.icon("fa.folder-o",
|
||||
color=style.colors.default)
|
||||
}
|
||||
self.selectable = selectable
|
||||
self.icon = awesome.icon(
|
||||
'fa.calendar-check-o',
|
||||
color=style.colors.default
|
||||
)
|
||||
|
||||
def set_tasks(self, tasks):
|
||||
"""Set assets to track by their database id
|
||||
|
|
@ -32,13 +32,11 @@ class TasksTemplateModel(TreeModel):
|
|||
|
||||
self.beginResetModel()
|
||||
|
||||
icon = self._icons["__default__"]
|
||||
for task in tasks:
|
||||
node = Node({
|
||||
"Tasks": task,
|
||||
"icon": icon
|
||||
"icon": self.icon
|
||||
})
|
||||
|
||||
self.add_child(node)
|
||||
|
||||
self.endResetModel()
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import contextlib
|
|||
from . import QtWidgets, QtCore
|
||||
from . import RecursiveSortFilterProxyModel, AssetModel, AssetView
|
||||
from . import awesome, style
|
||||
from . import TasksTemplateModel, DeselectableTreeView
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def preserve_expanded_rows(tree_view,
|
||||
|
|
@ -128,7 +130,7 @@ class AssetWidget(QtWidgets.QWidget):
|
|||
|
||||
self.parent_widget = parent
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.setSpacing(4)
|
||||
|
||||
|
|
@ -163,12 +165,31 @@ class AssetWidget(QtWidgets.QWidget):
|
|||
layout.addLayout(header)
|
||||
layout.addWidget(view)
|
||||
|
||||
# tasks
|
||||
task_view = DeselectableTreeView()
|
||||
task_view.setIndentation(0)
|
||||
task_view.setHeaderHidden(True)
|
||||
task_view.setVisible(False)
|
||||
|
||||
task_model = TasksTemplateModel()
|
||||
task_view.setModel(task_model)
|
||||
|
||||
main_layout = QtWidgets.QVBoxLayout(self)
|
||||
main_layout.setContentsMargins(0, 0, 0, 0)
|
||||
main_layout.setSpacing(4)
|
||||
main_layout.addLayout(layout, 80)
|
||||
main_layout.addWidget(task_view, 20)
|
||||
|
||||
# Signals/Slots
|
||||
selection = view.selectionModel()
|
||||
selection.selectionChanged.connect(self.selection_changed)
|
||||
selection.currentChanged.connect(self.current_changed)
|
||||
refresh.clicked.connect(self.refresh)
|
||||
|
||||
self.selection_changed.connect(self._refresh_tasks)
|
||||
|
||||
self.task_view = task_view
|
||||
self.task_model = task_model
|
||||
self.refreshButton = refresh
|
||||
self.model = model
|
||||
self.proxy = proxy
|
||||
|
|
@ -181,10 +202,17 @@ class AssetWidget(QtWidgets.QWidget):
|
|||
def collect_data(self):
|
||||
project = self.db.find_one({'type': 'project'})
|
||||
asset = self.db.find_one({'_id': self.get_active_asset()})
|
||||
|
||||
try:
|
||||
index = self.task_view.selectedIndexes()[0]
|
||||
task = self.task_model.itemData(index)[0]
|
||||
except Exception:
|
||||
task = None
|
||||
data = {
|
||||
'project': project['name'],
|
||||
'asset': asset['name'],
|
||||
'parents': self.get_parents(asset)
|
||||
'parents': self.get_parents(asset),
|
||||
'task': task
|
||||
}
|
||||
return data
|
||||
|
||||
|
|
@ -223,6 +251,18 @@ class AssetWidget(QtWidgets.QWidget):
|
|||
def refresh(self):
|
||||
self._refresh_model()
|
||||
|
||||
def _refresh_tasks(self):
|
||||
tasks = []
|
||||
selected = self.get_selected_assets()
|
||||
if len(selected) == 1:
|
||||
asset = self.db.find_one({
|
||||
"_id": selected[0], "type": "asset"
|
||||
})
|
||||
if asset:
|
||||
tasks = asset.get('data', {}).get('tasks', [])
|
||||
self.task_model.set_tasks(tasks)
|
||||
self.task_view.setVisible(len(tasks)>0)
|
||||
|
||||
def get_active_asset(self):
|
||||
"""Return the asset id the current asset."""
|
||||
current = self.view.currentIndex()
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ class ComponentItem(QtWidgets.QFrame):
|
|||
C_HOVER = '#ffffff'
|
||||
C_ACTIVE = '#4BB543'
|
||||
C_ACTIVE_HOVER = '#4BF543'
|
||||
|
||||
signal_remove = QtCore.Signal(object)
|
||||
signal_thumbnail = QtCore.Signal(object)
|
||||
signal_preview = QtCore.Signal(object)
|
||||
|
|
@ -283,12 +284,28 @@ class ComponentItem(QtWidgets.QFrame):
|
|||
self.preview.change_checked(hover)
|
||||
|
||||
def collect_data(self):
|
||||
in_files = self.in_data['files']
|
||||
staging_dir = os.path.dirname(in_files[0])
|
||||
|
||||
files = [os.path.basename(file) for file in in_files]
|
||||
if len(files) == 1:
|
||||
files = files[0]
|
||||
|
||||
data = {
|
||||
'ext': self.in_data['ext'],
|
||||
'label': self.name.text(),
|
||||
'representation': self.input_repre.text(),
|
||||
'files': self.in_data['files'],
|
||||
'name': self.input_repre.text(),
|
||||
'stagingDir': staging_dir,
|
||||
'files': files,
|
||||
'thumbnail': self.is_thumbnail(),
|
||||
'preview': self.is_preview()
|
||||
}
|
||||
|
||||
if ('startFrame' in self.in_data and 'endFrame' in self.in_data):
|
||||
data['startFrame'] = self.in_data['startFrame']
|
||||
data['endFrame'] = self.in_data['endFrame']
|
||||
|
||||
if 'frameRate' in self.in_data:
|
||||
data['frameRate'] = self.in_data['frameRate']
|
||||
|
||||
return data
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import os
|
||||
import re
|
||||
import json
|
||||
import clique
|
||||
import subprocess
|
||||
from pypeapp import config
|
||||
|
|
@ -49,7 +50,7 @@ class DropDataFrame(QtWidgets.QFrame):
|
|||
else:
|
||||
# If path is in clipboard as string
|
||||
try:
|
||||
path = ent.text()
|
||||
path = os.path.normpath(ent.text())
|
||||
if os.path.exists(path):
|
||||
paths.append(path)
|
||||
else:
|
||||
|
|
@ -170,6 +171,13 @@ class DropDataFrame(QtWidgets.QFrame):
|
|||
repr_name = file_ext.replace('.', '')
|
||||
range = collection.format('{ranges}')
|
||||
|
||||
# TODO: ranges must not be with missing frames!!!
|
||||
# - this is goal implementation:
|
||||
# startFrame, endFrame = range.split('-')
|
||||
rngs = range.split(',')
|
||||
startFrame = rngs[0].split('-')[0]
|
||||
endFrame = rngs[-1].split('-')[-1]
|
||||
|
||||
actions = []
|
||||
|
||||
data = {
|
||||
|
|
@ -177,39 +185,15 @@ class DropDataFrame(QtWidgets.QFrame):
|
|||
'name': file_base,
|
||||
'ext': file_ext,
|
||||
'file_info': range,
|
||||
'startFrame': startFrame,
|
||||
'endFrame': endFrame,
|
||||
'representation': repr_name,
|
||||
'folder_path': folder_path,
|
||||
'is_sequence': True,
|
||||
'actions': actions
|
||||
}
|
||||
self._process_data(data)
|
||||
|
||||
def _get_ranges(self, indexes):
|
||||
if len(indexes) == 1:
|
||||
return str(indexes[0])
|
||||
ranges = []
|
||||
first = None
|
||||
last = None
|
||||
for index in indexes:
|
||||
if first is None:
|
||||
first = index
|
||||
last = index
|
||||
elif (last+1) == index:
|
||||
last = index
|
||||
else:
|
||||
if first == last:
|
||||
range = str(first)
|
||||
else:
|
||||
range = '{}-{}'.format(first, last)
|
||||
ranges.append(range)
|
||||
first = index
|
||||
last = index
|
||||
if first == last:
|
||||
range = str(first)
|
||||
else:
|
||||
range = '{}-{}'.format(first, last)
|
||||
ranges.append(range)
|
||||
return ', '.join(ranges)
|
||||
self._process_data(data)
|
||||
|
||||
def _process_remainder(self, remainder):
|
||||
filename = os.path.basename(remainder)
|
||||
|
|
@ -232,39 +216,75 @@ class DropDataFrame(QtWidgets.QFrame):
|
|||
'is_sequence': False,
|
||||
'actions': actions
|
||||
}
|
||||
data['file_info'] = self.get_file_info(data)
|
||||
|
||||
self._process_data(data)
|
||||
|
||||
def get_file_info(self, data):
|
||||
output = None
|
||||
if data['ext'] == '.mov':
|
||||
try:
|
||||
# ffProbe must be in PATH
|
||||
filepath = data['files'][0]
|
||||
args = ['ffprobe', '-show_streams', filepath]
|
||||
p = subprocess.Popen(
|
||||
args,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
shell=True
|
||||
)
|
||||
datalines=[]
|
||||
for line in iter(p.stdout.readline, b''):
|
||||
line = line.decode("utf-8").replace('\r\n', '')
|
||||
datalines.append(line)
|
||||
def load_data_with_probe(self, filepath):
|
||||
args = [
|
||||
'ffprobe',
|
||||
'-v', 'quiet',
|
||||
'-print_format', 'json',
|
||||
'-show_format',
|
||||
'-show_streams', filepath
|
||||
]
|
||||
ffprobe_p = subprocess.Popen(
|
||||
args,
|
||||
stdout=subprocess.PIPE,
|
||||
shell=True
|
||||
)
|
||||
ffprobe_output = ffprobe_p.communicate()[0]
|
||||
if ffprobe_p.returncode != 0:
|
||||
raise RuntimeError(
|
||||
'Failed on ffprobe: check if ffprobe path is set in PATH env'
|
||||
)
|
||||
return json.loads(ffprobe_output)['streams'][0]
|
||||
|
||||
def get_file_data(self, data):
|
||||
filepath = data['files'][0]
|
||||
ext = data['ext']
|
||||
output = {}
|
||||
probe_data = self.load_data_with_probe(filepath)
|
||||
|
||||
if (
|
||||
ext in self.presets['extensions']['image_file'] or
|
||||
ext in self.presets['extensions']['video_file']
|
||||
):
|
||||
if 'frameRate' not in data:
|
||||
# default value
|
||||
frameRate = 25
|
||||
frameRate_string = probe_data.get('r_frame_rate')
|
||||
if frameRate_string:
|
||||
frameRate = int(frameRate_string.split('/')[0])
|
||||
|
||||
output['frameRate'] = frameRate
|
||||
|
||||
if 'startFrame' not in data or 'endFrame' not in data:
|
||||
startFrame = endFrame = 1
|
||||
endFrame_string = probe_data.get('nb_frames')
|
||||
|
||||
if endFrame_string:
|
||||
endFrame = int(endFrame_string)
|
||||
|
||||
output['startFrame'] = startFrame
|
||||
output['endFrame'] = endFrame
|
||||
|
||||
file_info = None
|
||||
if 'file_info' in data:
|
||||
file_info = data['file_info']
|
||||
elif ext in ['.mov']:
|
||||
file_info = probe_data.get('codec_name')
|
||||
|
||||
output['file_info'] = file_info
|
||||
|
||||
find_value = 'codec_name'
|
||||
for line in datalines:
|
||||
if line.startswith(find_value):
|
||||
output = line.replace(find_value + '=', '')
|
||||
break
|
||||
except Exception as e:
|
||||
pass
|
||||
return output
|
||||
|
||||
def _process_data(self, data):
|
||||
ext = data['ext']
|
||||
# load file data info
|
||||
file_data = self.get_file_data(data)
|
||||
for key, value in file_data.items():
|
||||
data[key] = value
|
||||
|
||||
icon = 'default'
|
||||
for ico, exts in self.presets['extensions'].items():
|
||||
if ext in exts:
|
||||
|
|
|
|||
|
|
@ -17,12 +17,14 @@ class FamilyWidget(QtWidgets.QWidget):
|
|||
data = dict()
|
||||
_jobs = dict()
|
||||
Separator = "---separator---"
|
||||
NOT_SELECTED = '< Nothing is selected >'
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
# Store internal states in here
|
||||
self.state = {"valid": False}
|
||||
self.parent_widget = parent
|
||||
self.asset_name = self.NOT_SELECTED
|
||||
|
||||
body = QtWidgets.QWidget()
|
||||
lists = QtWidgets.QWidget()
|
||||
|
|
@ -30,12 +32,10 @@ class FamilyWidget(QtWidgets.QWidget):
|
|||
container = QtWidgets.QWidget()
|
||||
|
||||
list_families = QtWidgets.QListWidget()
|
||||
input_asset = QtWidgets.QLineEdit()
|
||||
input_asset.setEnabled(False)
|
||||
input_asset.setStyleSheet("color: #BBBBBB;")
|
||||
|
||||
input_subset = QtWidgets.QLineEdit()
|
||||
input_result = QtWidgets.QLineEdit()
|
||||
input_result.setStyleSheet("color: gray;")
|
||||
input_result.setStyleSheet("color: #BBBBBB;")
|
||||
input_result.setEnabled(False)
|
||||
|
||||
# region Menu for default subset names
|
||||
|
|
@ -51,6 +51,20 @@ class FamilyWidget(QtWidgets.QWidget):
|
|||
name_layout.addWidget(btn_subset)
|
||||
name_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
# version
|
||||
version_spinbox = QtWidgets.QSpinBox()
|
||||
version_spinbox.setMinimum(1)
|
||||
version_spinbox.setMaximum(9999)
|
||||
version_spinbox.setEnabled(False)
|
||||
version_spinbox.setStyleSheet("color: #BBBBBB;")
|
||||
|
||||
version_checkbox = QtWidgets.QCheckBox("Next Available Version")
|
||||
version_checkbox.setCheckState(QtCore.Qt.CheckState(2))
|
||||
|
||||
version_layout = QtWidgets.QHBoxLayout()
|
||||
version_layout.addWidget(version_spinbox)
|
||||
version_layout.addWidget(version_checkbox)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(container)
|
||||
|
||||
header = FamilyDescriptionWidget(self)
|
||||
|
|
@ -58,11 +72,11 @@ class FamilyWidget(QtWidgets.QWidget):
|
|||
|
||||
layout.addWidget(QtWidgets.QLabel("Family"))
|
||||
layout.addWidget(list_families)
|
||||
layout.addWidget(QtWidgets.QLabel("Asset"))
|
||||
layout.addWidget(input_asset)
|
||||
layout.addWidget(QtWidgets.QLabel("Subset"))
|
||||
layout.addLayout(name_layout)
|
||||
layout.addWidget(input_result)
|
||||
layout.addWidget(QtWidgets.QLabel("Version"))
|
||||
layout.addLayout(version_layout)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
options = QtWidgets.QWidget()
|
||||
|
|
@ -75,6 +89,7 @@ class FamilyWidget(QtWidgets.QWidget):
|
|||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(body)
|
||||
|
||||
layout.addWidget(lists)
|
||||
layout.addWidget(options, 0, QtCore.Qt.AlignLeft)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
|
@ -83,9 +98,9 @@ class FamilyWidget(QtWidgets.QWidget):
|
|||
layout.addWidget(body)
|
||||
|
||||
input_subset.textChanged.connect(self.on_data_changed)
|
||||
input_asset.textChanged.connect(self.on_data_changed)
|
||||
list_families.currentItemChanged.connect(self.on_selection_changed)
|
||||
list_families.currentItemChanged.connect(header.set_item)
|
||||
version_checkbox.stateChanged.connect(self.on_version_refresh)
|
||||
|
||||
self.stateChanged.connect(self._on_state_changed)
|
||||
|
||||
|
|
@ -93,8 +108,9 @@ class FamilyWidget(QtWidgets.QWidget):
|
|||
self.menu_subset = menu_subset
|
||||
self.btn_subset = btn_subset
|
||||
self.list_families = list_families
|
||||
self.input_asset = input_asset
|
||||
self.input_result = input_result
|
||||
self.version_checkbox = version_checkbox
|
||||
self.version_spinbox = version_spinbox
|
||||
|
||||
self.refresh()
|
||||
|
||||
|
|
@ -103,7 +119,8 @@ class FamilyWidget(QtWidgets.QWidget):
|
|||
family = plugin.family.rsplit(".", 1)[-1]
|
||||
data = {
|
||||
'family': family,
|
||||
'subset': self.input_subset.text()
|
||||
'subset': self.input_result.text(),
|
||||
'version': self.version_spinbox.value()
|
||||
}
|
||||
return data
|
||||
|
||||
|
|
@ -112,7 +129,10 @@ class FamilyWidget(QtWidgets.QWidget):
|
|||
return self.parent_widget.db
|
||||
|
||||
def change_asset(self, name):
|
||||
self.input_asset.setText(name)
|
||||
if name is None:
|
||||
name = self.NOT_SELECTED
|
||||
self.asset_name = name
|
||||
self.on_data_changed()
|
||||
|
||||
def _on_state_changed(self, state):
|
||||
self.state['valid'] = state
|
||||
|
|
@ -153,22 +173,37 @@ class FamilyWidget(QtWidgets.QWidget):
|
|||
self.input_subset.setText(action.text())
|
||||
|
||||
def _on_data_changed(self):
|
||||
item = self.list_families.currentItem()
|
||||
asset_name = self.asset_name
|
||||
subset_name = self.input_subset.text()
|
||||
asset_name = self.input_asset.text()
|
||||
item = self.list_families.currentItem()
|
||||
|
||||
# Get the assets from the database which match with the name
|
||||
assets_db = self.db.find(filter={"type": "asset"}, projection={"name": 1})
|
||||
assets = [asset for asset in assets_db if asset_name in asset["name"]]
|
||||
if item is None:
|
||||
return
|
||||
if assets:
|
||||
# Get plugin and family
|
||||
plugin = item.data(PluginRole)
|
||||
if plugin is None:
|
||||
return
|
||||
family = plugin.family.rsplit(".", 1)[-1]
|
||||
|
||||
assets = None
|
||||
if asset_name != self.NOT_SELECTED:
|
||||
# Get the assets from the database which match with the name
|
||||
assets_db = self.db.find(
|
||||
filter={"type": "asset"},
|
||||
projection={"name": 1}
|
||||
)
|
||||
assets = [
|
||||
asset for asset in assets_db if asset_name in asset["name"]
|
||||
]
|
||||
|
||||
# Get plugin and family
|
||||
plugin = item.data(PluginRole)
|
||||
if plugin is None:
|
||||
return
|
||||
|
||||
family = plugin.family.rsplit(".", 1)[-1]
|
||||
|
||||
# Update the result
|
||||
if subset_name:
|
||||
subset_name = subset_name[0].upper() + subset_name[1:]
|
||||
self.input_result.setText("{}{}".format(family, subset_name))
|
||||
|
||||
if assets:
|
||||
# Get all subsets of the current asset
|
||||
asset_ids = [asset["_id"] for asset in assets]
|
||||
subsets = self.db.find(filter={"type": "subset",
|
||||
|
|
@ -191,28 +226,62 @@ class FamilyWidget(QtWidgets.QWidget):
|
|||
|
||||
self._build_menu(defaults)
|
||||
|
||||
# Update the result
|
||||
if subset_name:
|
||||
subset_name = subset_name[0].upper() + subset_name[1:]
|
||||
self.input_result.setText("{}{}".format(family, subset_name))
|
||||
|
||||
item.setData(ExistsRole, True)
|
||||
self.echo("Ready ..")
|
||||
else:
|
||||
self._build_menu([])
|
||||
item.setData(ExistsRole, False)
|
||||
if asset_name != self.parent_widget.NOT_SELECTED:
|
||||
self.echo("'%s' not found .." % asset_name)
|
||||
if asset_name != self.NOT_SELECTED:
|
||||
# TODO add logging into standalone_publish
|
||||
print("'%s' not found .." % asset_name)
|
||||
|
||||
self.on_version_refresh()
|
||||
|
||||
# Update the valid state
|
||||
valid = (
|
||||
asset_name != self.NOT_SELECTED and
|
||||
subset_name.strip() != "" and
|
||||
asset_name.strip() != "" and
|
||||
item.data(QtCore.Qt.ItemIsEnabled) and
|
||||
item.data(ExistsRole)
|
||||
)
|
||||
self.stateChanged.emit(valid)
|
||||
|
||||
def on_version_refresh(self):
|
||||
auto_version = self.version_checkbox.isChecked()
|
||||
self.version_spinbox.setEnabled(not auto_version)
|
||||
if not auto_version:
|
||||
return
|
||||
|
||||
asset_name = self.asset_name
|
||||
subset_name = self.input_result.text()
|
||||
version = 1
|
||||
|
||||
if (
|
||||
asset_name != self.NOT_SELECTED and
|
||||
subset_name.strip() != ''
|
||||
):
|
||||
asset = self.db.find_one({
|
||||
'type': 'asset',
|
||||
'name': asset_name
|
||||
})
|
||||
subset = self.db.find_one({
|
||||
'type': 'subset',
|
||||
'parent': asset['_id'],
|
||||
'name': subset_name
|
||||
})
|
||||
if subset:
|
||||
versions = self.db.find({
|
||||
'type': 'version',
|
||||
'parent': subset['_id']
|
||||
})
|
||||
if versions:
|
||||
versions = sorted(
|
||||
[v for v in versions],
|
||||
key=lambda ver: ver['name']
|
||||
)
|
||||
version = int(versions[-1]['name']) + 1
|
||||
|
||||
self.version_spinbox.setValue(version)
|
||||
|
||||
def on_data_changed(self, *args):
|
||||
|
||||
# Set invalid state until it's reconfirmed to be valid by the
|
||||
|
|
@ -270,10 +339,6 @@ class FamilyWidget(QtWidgets.QWidget):
|
|||
|
||||
self.list_families.setCurrentItem(self.list_families.item(0))
|
||||
|
||||
def echo(self, message):
|
||||
if hasattr(self.parent_widget, 'echo'):
|
||||
self.parent_widget.echo(message)
|
||||
|
||||
def schedule(self, func, time, channel="default"):
|
||||
try:
|
||||
self._jobs[channel].stop()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
|
||||
from pype.nuke.lib import writes_version_sync, onScriptLoad
|
||||
from pype.nuke.lib import (
|
||||
writes_version_sync,
|
||||
onScriptLoad,
|
||||
checkInventoryVersions
|
||||
)
|
||||
|
||||
import nuke
|
||||
from pypeapp import Logger
|
||||
|
||||
|
|
@ -8,5 +13,6 @@ log = Logger().get_logger(__name__, "nuke")
|
|||
|
||||
nuke.addOnScriptSave(writes_version_sync)
|
||||
nuke.addOnScriptSave(onScriptLoad)
|
||||
nuke.addOnScriptSave(checkInventoryVersions)
|
||||
|
||||
log.info('Automatic syncing of write file knob to script version')
|
||||
|
|
|
|||
1108
setup/nukestudio/hiero_plugin_path/HieroPlayer/PlayerPresets.hrox
Normal file
1108
setup/nukestudio/hiero_plugin_path/HieroPlayer/PlayerPresets.hrox
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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()
|
||||
19
setup/nukestudio/hiero_plugin_path/Python/Startup/Startup.py
Normal file
19
setup/nukestudio/hiero_plugin_path/Python/Startup/Startup.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import traceback
|
||||
|
||||
# activate nukestudio from pype
|
||||
import avalon.api
|
||||
import pype.nukestudio
|
||||
avalon.api.install(pype.nukestudio)
|
||||
|
||||
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()
|
||||
|
|
@ -0,0 +1,369 @@
|
|||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2018 Daniel Flehner Heen (Storm Studios)
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
|
||||
import os
|
||||
import re
|
||||
import hiero.core
|
||||
from hiero.core import util
|
||||
|
||||
import opentimelineio as otio
|
||||
|
||||
|
||||
marker_color_map = {
|
||||
"magenta": otio.schema.MarkerColor.MAGENTA,
|
||||
"red": otio.schema.MarkerColor.RED,
|
||||
"yellow": otio.schema.MarkerColor.YELLOW,
|
||||
"green": otio.schema.MarkerColor.GREEN,
|
||||
"cyan": otio.schema.MarkerColor.CYAN,
|
||||
"blue": otio.schema.MarkerColor.BLUE,
|
||||
}
|
||||
|
||||
|
||||
class OTIOExportTask(hiero.core.TaskBase):
|
||||
|
||||
def __init__(self, initDict):
|
||||
"""Initialize"""
|
||||
hiero.core.TaskBase.__init__(self, initDict)
|
||||
|
||||
def name(self):
|
||||
return str(type(self))
|
||||
|
||||
def get_rate(self, item):
|
||||
num, den = item.framerate().toRational()
|
||||
rate = float(num) / float(den)
|
||||
|
||||
if rate.is_integer():
|
||||
return rate
|
||||
|
||||
return round(rate, 2)
|
||||
|
||||
def get_clip_ranges(self, trackitem):
|
||||
# Is clip an audio file? Use sequence frame rate
|
||||
if not trackitem.source().mediaSource().hasVideo():
|
||||
rate_item = trackitem.sequence()
|
||||
|
||||
else:
|
||||
rate_item = trackitem.source()
|
||||
|
||||
source_rate = self.get_rate(rate_item)
|
||||
|
||||
# Reversed video/audio
|
||||
if trackitem.playbackSpeed() < 0:
|
||||
start = trackitem.sourceOut()
|
||||
|
||||
else:
|
||||
start = trackitem.sourceIn()
|
||||
|
||||
source_start_time = otio.opentime.RationalTime(
|
||||
start,
|
||||
source_rate
|
||||
)
|
||||
source_duration = otio.opentime.RationalTime(
|
||||
trackitem.duration(),
|
||||
source_rate
|
||||
)
|
||||
|
||||
source_range = otio.opentime.TimeRange(
|
||||
start_time=source_start_time,
|
||||
duration=source_duration
|
||||
)
|
||||
|
||||
available_range = None
|
||||
hiero_clip = trackitem.source()
|
||||
if not hiero_clip.mediaSource().isOffline():
|
||||
start_time = otio.opentime.RationalTime(
|
||||
hiero_clip.mediaSource().startTime(),
|
||||
source_rate
|
||||
)
|
||||
duration = otio.opentime.RationalTime(
|
||||
hiero_clip.mediaSource().duration(),
|
||||
source_rate
|
||||
)
|
||||
available_range = otio.opentime.TimeRange(
|
||||
start_time=start_time,
|
||||
duration=duration
|
||||
)
|
||||
|
||||
return source_range, available_range
|
||||
|
||||
def add_gap(self, trackitem, otio_track, prev_out):
|
||||
gap_length = trackitem.timelineIn() - prev_out
|
||||
if prev_out != 0:
|
||||
gap_length -= 1
|
||||
|
||||
rate = self.get_rate(trackitem.sequence())
|
||||
gap = otio.opentime.TimeRange(
|
||||
duration=otio.opentime.RationalTime(
|
||||
gap_length,
|
||||
rate
|
||||
)
|
||||
)
|
||||
otio_gap = otio.schema.Gap(source_range=gap)
|
||||
otio_track.append(otio_gap)
|
||||
|
||||
def get_marker_color(self, tag):
|
||||
icon = tag.icon()
|
||||
pat = 'icons:Tag(?P<color>\w+)\.\w+'
|
||||
|
||||
res = re.search(pat, icon)
|
||||
if res:
|
||||
color = res.groupdict().get('color')
|
||||
if color.lower() in marker_color_map:
|
||||
return marker_color_map[color.lower()]
|
||||
|
||||
return otio.schema.MarkerColor.RED
|
||||
|
||||
def add_markers(self, hiero_item, otio_item):
|
||||
for tag in hiero_item.tags():
|
||||
if not tag.visible():
|
||||
continue
|
||||
|
||||
if tag.name() == 'Copy':
|
||||
# Hiero adds this tag to a lot of clips
|
||||
continue
|
||||
|
||||
frame_rate = self.get_rate(hiero_item)
|
||||
|
||||
marked_range = otio.opentime.TimeRange(
|
||||
start_time=otio.opentime.RationalTime(
|
||||
tag.inTime(),
|
||||
frame_rate
|
||||
),
|
||||
duration=otio.opentime.RationalTime(
|
||||
int(tag.metadata().dict().get('tag.length', '0')),
|
||||
frame_rate
|
||||
)
|
||||
)
|
||||
|
||||
marker = otio.schema.Marker(
|
||||
name=tag.name(),
|
||||
color=self.get_marker_color(tag),
|
||||
marked_range=marked_range,
|
||||
metadata={
|
||||
'Hiero': tag.metadata().dict()
|
||||
}
|
||||
)
|
||||
|
||||
otio_item.markers.append(marker)
|
||||
|
||||
def add_clip(self, trackitem, otio_track, itemindex):
|
||||
hiero_clip = trackitem.source()
|
||||
|
||||
# Add Gap if needed
|
||||
prev_item = (
|
||||
itemindex and trackitem.parent().items()[itemindex - 1] or
|
||||
trackitem
|
||||
)
|
||||
|
||||
if prev_item == trackitem and trackitem.timelineIn() > 0:
|
||||
self.add_gap(trackitem, otio_track, 0)
|
||||
|
||||
elif (
|
||||
prev_item != trackitem and
|
||||
prev_item.timelineOut() != trackitem.timelineIn()
|
||||
):
|
||||
self.add_gap(trackitem, otio_track, prev_item.timelineOut())
|
||||
|
||||
# Create Clip
|
||||
source_range, available_range = self.get_clip_ranges(trackitem)
|
||||
|
||||
otio_clip = otio.schema.Clip()
|
||||
otio_clip.name = trackitem.name()
|
||||
otio_clip.source_range = source_range
|
||||
|
||||
# Add media reference
|
||||
media_reference = otio.schema.MissingReference()
|
||||
if not hiero_clip.mediaSource().isOffline():
|
||||
source = hiero_clip.mediaSource()
|
||||
media_reference = otio.schema.ExternalReference()
|
||||
media_reference.available_range = available_range
|
||||
|
||||
path, name = os.path.split(source.fileinfos()[0].filename())
|
||||
media_reference.target_url = os.path.join(path, name)
|
||||
media_reference.name = name
|
||||
|
||||
otio_clip.media_reference = media_reference
|
||||
|
||||
# Add Time Effects
|
||||
playbackspeed = trackitem.playbackSpeed()
|
||||
if playbackspeed != 1:
|
||||
if playbackspeed == 0:
|
||||
time_effect = otio.schema.FreezeFrame()
|
||||
|
||||
else:
|
||||
time_effect = otio.schema.LinearTimeWarp(
|
||||
time_scalar=playbackspeed
|
||||
)
|
||||
otio_clip.effects.append(time_effect)
|
||||
|
||||
# Add tags as markers
|
||||
if self._preset.properties()["includeTags"]:
|
||||
self.add_markers(trackitem.source(), otio_clip)
|
||||
|
||||
otio_track.append(otio_clip)
|
||||
|
||||
# Add Transition if needed
|
||||
if trackitem.inTransition() or trackitem.outTransition():
|
||||
self.add_transition(trackitem, otio_track)
|
||||
|
||||
def add_transition(self, trackitem, otio_track):
|
||||
transitions = []
|
||||
|
||||
if trackitem.inTransition():
|
||||
if trackitem.inTransition().alignment().name == 'kFadeIn':
|
||||
transitions.append(trackitem.inTransition())
|
||||
|
||||
if trackitem.outTransition():
|
||||
transitions.append(trackitem.outTransition())
|
||||
|
||||
for transition in transitions:
|
||||
alignment = transition.alignment().name
|
||||
|
||||
if alignment == 'kFadeIn':
|
||||
in_offset_frames = 0
|
||||
out_offset_frames = (
|
||||
transition.timelineOut() - transition.timelineIn()
|
||||
) + 1
|
||||
|
||||
elif alignment == 'kFadeOut':
|
||||
in_offset_frames = (
|
||||
trackitem.timelineOut() - transition.timelineIn()
|
||||
) + 1
|
||||
out_offset_frames = 0
|
||||
|
||||
elif alignment == 'kDissolve':
|
||||
in_offset_frames = (
|
||||
transition.inTrackItem().timelineOut() -
|
||||
transition.timelineIn()
|
||||
)
|
||||
out_offset_frames = (
|
||||
transition.timelineOut() -
|
||||
transition.outTrackItem().timelineIn()
|
||||
)
|
||||
|
||||
else:
|
||||
# kUnknown transition is ignored
|
||||
continue
|
||||
|
||||
rate = trackitem.source().framerate().toFloat()
|
||||
in_time = otio.opentime.RationalTime(in_offset_frames, rate)
|
||||
out_time = otio.opentime.RationalTime(out_offset_frames, rate)
|
||||
|
||||
otio_transition = otio.schema.Transition(
|
||||
name=alignment, # Consider placing Hiero name in metadata
|
||||
transition_type=otio.schema.TransitionTypes.SMPTE_Dissolve,
|
||||
in_offset=in_time,
|
||||
out_offset=out_time,
|
||||
metadata={}
|
||||
)
|
||||
|
||||
if alignment == 'kFadeIn':
|
||||
otio_track.insert(-2, otio_transition)
|
||||
|
||||
else:
|
||||
otio_track.append(otio_transition)
|
||||
|
||||
def add_tracks(self):
|
||||
for track in self._sequence.items():
|
||||
if isinstance(track, hiero.core.AudioTrack):
|
||||
kind = otio.schema.TrackKind.Audio
|
||||
|
||||
else:
|
||||
kind = otio.schema.TrackKind.Video
|
||||
|
||||
otio_track = otio.schema.Track(kind=kind)
|
||||
otio_track.name = track.name()
|
||||
|
||||
for itemindex, trackitem in enumerate(track):
|
||||
if isinstance(trackitem.source(), hiero.core.Clip):
|
||||
self.add_clip(trackitem, otio_track, itemindex)
|
||||
|
||||
self.otio_timeline.tracks.append(otio_track)
|
||||
|
||||
# Add tags as markers
|
||||
if self._preset.properties()["includeTags"]:
|
||||
self.add_markers(self._sequence, self.otio_timeline.tracks)
|
||||
|
||||
def create_OTIO(self):
|
||||
self.otio_timeline = otio.schema.Timeline()
|
||||
self.otio_timeline.name = self._sequence.name()
|
||||
|
||||
self.add_tracks()
|
||||
|
||||
def startTask(self):
|
||||
self.create_OTIO()
|
||||
|
||||
def taskStep(self):
|
||||
return False
|
||||
|
||||
def finishTask(self):
|
||||
try:
|
||||
exportPath = self.resolvedExportPath()
|
||||
|
||||
# Check file extension
|
||||
if not exportPath.lower().endswith(".otio"):
|
||||
exportPath += ".otio"
|
||||
|
||||
# check export root exists
|
||||
dirname = os.path.dirname(exportPath)
|
||||
util.filesystem.makeDirs(dirname)
|
||||
|
||||
# write otio file
|
||||
otio.adapters.write_to_file(self.otio_timeline, exportPath)
|
||||
|
||||
# Catch all exceptions and log error
|
||||
except Exception as e:
|
||||
self.setError("failed to write file {f}\n{e}".format(
|
||||
f=exportPath,
|
||||
e=e)
|
||||
)
|
||||
|
||||
hiero.core.TaskBase.finishTask(self)
|
||||
|
||||
def forcedAbort(self):
|
||||
pass
|
||||
|
||||
|
||||
class OTIOExportPreset(hiero.core.TaskPresetBase):
|
||||
def __init__(self, name, properties):
|
||||
"""Initialise presets to default values"""
|
||||
hiero.core.TaskPresetBase.__init__(self, OTIOExportTask, name)
|
||||
|
||||
self.properties()["includeTags"] = True
|
||||
self.properties().update(properties)
|
||||
|
||||
def supportedItems(self):
|
||||
return hiero.core.TaskPresetBase.kSequence
|
||||
|
||||
def addCustomResolveEntries(self, resolver):
|
||||
resolver.addResolver(
|
||||
"{ext}",
|
||||
"Extension of the file to be output",
|
||||
lambda keyword, task: "otio"
|
||||
)
|
||||
|
||||
def supportsAudio(self):
|
||||
return True
|
||||
|
||||
|
||||
hiero.core.taskRegistry.registerTask(OTIOExportPreset, OTIOExportTask)
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
import hiero.ui
|
||||
import OTIOExportTask
|
||||
|
||||
try:
|
||||
# Hiero >= 11.x
|
||||
from PySide2 import QtCore
|
||||
from PySide2.QtWidgets import QCheckBox
|
||||
from hiero.ui.FnTaskUIFormLayout import TaskUIFormLayout as FormLayout
|
||||
|
||||
except ImportError:
|
||||
# Hiero <= 10.x
|
||||
from PySide import QtCore # lint:ok
|
||||
from PySide.QtGui import QCheckBox, QFormLayout # lint:ok
|
||||
|
||||
FormLayout = QFormLayout # lint:ok
|
||||
|
||||
|
||||
class OTIOExportUI(hiero.ui.TaskUIBase):
|
||||
def __init__(self, preset):
|
||||
"""Initialize"""
|
||||
hiero.ui.TaskUIBase.__init__(
|
||||
self,
|
||||
OTIOExportTask.OTIOExportTask,
|
||||
preset,
|
||||
"OTIO Exporter"
|
||||
)
|
||||
|
||||
def includeMarkersCheckboxChanged(self, state):
|
||||
# Slot to handle change of checkbox state
|
||||
self._preset.properties()["includeTags"] = state == QtCore.Qt.Checked
|
||||
|
||||
def populateUI(self, widget, exportTemplate):
|
||||
layout = widget.layout()
|
||||
formLayout = FormLayout()
|
||||
|
||||
# Hiero ~= 10.0v4
|
||||
if layout is None:
|
||||
layout = formLayout
|
||||
widget.setLayout(layout)
|
||||
|
||||
else:
|
||||
layout.addLayout(formLayout)
|
||||
|
||||
# Checkboxes for whether the OTIO should contain markers or not
|
||||
self.includeMarkersCheckbox = QCheckBox()
|
||||
self.includeMarkersCheckbox.setToolTip(
|
||||
"Enable to include Tags as markers in the exported OTIO file."
|
||||
)
|
||||
self.includeMarkersCheckbox.setCheckState(QtCore.Qt.Unchecked)
|
||||
|
||||
if self._preset.properties()["includeTags"]:
|
||||
self.includeMarkersCheckbox.setCheckState(QtCore.Qt.Checked)
|
||||
|
||||
self.includeMarkersCheckbox.stateChanged.connect(
|
||||
self.includeMarkersCheckboxChanged
|
||||
)
|
||||
|
||||
# Add Checkbox to layout
|
||||
formLayout.addRow("Include Tags:", self.includeMarkersCheckbox)
|
||||
|
||||
|
||||
hiero.ui.taskUIRegistry.registerTaskUI(
|
||||
OTIOExportTask.OTIOExportPreset,
|
||||
OTIOExportUI
|
||||
)
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2018 Daniel Flehner Heen (Storm Studios)
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
from OTIOExportTask import OTIOExportTask
|
||||
from OTIOExportUI import OTIOExportUI
|
||||
|
||||
__all__ = [
|
||||
'OTIOExportTask',
|
||||
'OTIOExportUI'
|
||||
]
|
||||
|
|
@ -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)
|
||||
|
|
@ -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()
|
||||
|
|
@ -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 <HIERO_PLUGIN_PATH>/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()
|
||||
|
|
@ -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()
|
||||
142
setup/nukestudio/hiero_plugin_path/Python/StartupUI/Purge.py
Normal file
142
setup/nukestudio/hiero_plugin_path/Python/StartupUI/Purge.py
Normal file
|
|
@ -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()
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
# 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 File(s)...')
|
||||
# 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(s)...')
|
||||
a.setShortcut(QKeySequence('Shift+R'))
|
||||
#----------------------------------------------
|
||||
a = hiero.ui.findMenuAction('Import EDL/XML/AAF...')
|
||||
a.setShortcut(QKeySequence('Ctrl+Shift+O'))
|
||||
#----------------------------------------------
|
||||
a = hiero.ui.findMenuAction('Metadata View')
|
||||
a.setShortcut(QKeySequence('I'))
|
||||
#----------------------------------------------
|
||||
a = hiero.ui.findMenuAction('Edit Settings')
|
||||
a.setShortcut(QKeySequence('S'))
|
||||
#----------------------------------------------
|
||||
a = hiero.ui.findMenuAction('Monitor Output')
|
||||
a.setShortcut(QKeySequence('Ctrl+U'))
|
||||
#----------------------------------------------
|
||||
|
|
@ -0,0 +1,435 @@
|
|||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2018 Daniel Flehner Heen (Storm Studios)
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import hiero.core
|
||||
import hiero.ui
|
||||
|
||||
try:
|
||||
from urllib import unquote
|
||||
|
||||
except ImportError:
|
||||
from urllib.parse import unquote # lint:ok
|
||||
|
||||
import opentimelineio as otio
|
||||
|
||||
|
||||
def get_transition_type(otio_item, otio_track):
|
||||
_in, _out = otio_track.neighbors_of(otio_item)
|
||||
|
||||
if isinstance(_in, otio.schema.Gap):
|
||||
_in = None
|
||||
|
||||
if isinstance(_out, otio.schema.Gap):
|
||||
_out = None
|
||||
|
||||
if _in and _out:
|
||||
return 'dissolve'
|
||||
|
||||
elif _in and not _out:
|
||||
return 'fade_out'
|
||||
|
||||
elif not _in and _out:
|
||||
return 'fade_in'
|
||||
|
||||
else:
|
||||
return 'unknown'
|
||||
|
||||
|
||||
def find_trackitem(name, hiero_track):
|
||||
for item in hiero_track.items():
|
||||
if item.name() == name:
|
||||
return item
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_neighboring_trackitems(otio_item, otio_track, hiero_track):
|
||||
_in, _out = otio_track.neighbors_of(otio_item)
|
||||
trackitem_in = None
|
||||
trackitem_out = None
|
||||
|
||||
if _in:
|
||||
trackitem_in = find_trackitem(_in.name, hiero_track)
|
||||
|
||||
if _out:
|
||||
trackitem_out = find_trackitem(_out.name, hiero_track)
|
||||
|
||||
return trackitem_in, trackitem_out
|
||||
|
||||
|
||||
def apply_transition(otio_track, otio_item, track):
|
||||
# Figure out type of transition
|
||||
transition_type = get_transition_type(otio_item, otio_track)
|
||||
|
||||
# Figure out track kind for getattr below
|
||||
if isinstance(track, hiero.core.VideoTrack):
|
||||
kind = ''
|
||||
|
||||
else:
|
||||
kind = 'Audio'
|
||||
|
||||
try:
|
||||
# Gather TrackItems involved in trasition
|
||||
item_in, item_out = get_neighboring_trackitems(
|
||||
otio_item,
|
||||
otio_track,
|
||||
track
|
||||
)
|
||||
|
||||
# Create transition object
|
||||
if transition_type == 'dissolve':
|
||||
transition_func = getattr(
|
||||
hiero.core.Transition,
|
||||
'create{kind}DissolveTransition'.format(kind=kind)
|
||||
)
|
||||
|
||||
transition = transition_func(
|
||||
item_in,
|
||||
item_out,
|
||||
otio_item.in_offset.value,
|
||||
otio_item.out_offset.value
|
||||
)
|
||||
|
||||
elif transition_type == 'fade_in':
|
||||
transition_func = getattr(
|
||||
hiero.core.Transition,
|
||||
'create{kind}FadeInTransition'.format(kind=kind)
|
||||
)
|
||||
transition = transition_func(
|
||||
item_out,
|
||||
otio_item.out_offset.value
|
||||
)
|
||||
|
||||
elif transition_type == 'fade_out':
|
||||
transition_func = getattr(
|
||||
hiero.core.Transition,
|
||||
'create{kind}FadeOutTransition'.format(kind=kind)
|
||||
)
|
||||
transition = transition_func(
|
||||
item_in,
|
||||
otio_item.in_offset.value
|
||||
)
|
||||
|
||||
else:
|
||||
# Unknown transition
|
||||
return
|
||||
|
||||
# Apply transition to track
|
||||
track.addTransition(transition)
|
||||
|
||||
except Exception, e:
|
||||
sys.stderr.write(
|
||||
'Unable to apply transition "{t}": "{e}"\n'.format(
|
||||
t=otio_item,
|
||||
e=e
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def prep_url(url_in):
|
||||
url = unquote(url_in)
|
||||
|
||||
if url.startswith('file://localhost/'):
|
||||
return url.replace('file://localhost/', '')
|
||||
|
||||
url = '{url}'.format(
|
||||
sep=url.startswith(os.sep) and '' or os.sep,
|
||||
url=url.startswith(os.sep) and url[1:] or url
|
||||
)
|
||||
|
||||
return url
|
||||
|
||||
|
||||
def create_offline_mediasource(otio_clip, path=None):
|
||||
hiero_rate = hiero.core.TimeBase(
|
||||
otio_clip.source_range.start_time.rate
|
||||
)
|
||||
|
||||
if isinstance(otio_clip.media_reference, otio.schema.ExternalReference):
|
||||
source_range = otio_clip.available_range()
|
||||
|
||||
else:
|
||||
source_range = otio_clip.source_range
|
||||
|
||||
if path is None:
|
||||
path = otio_clip.name
|
||||
|
||||
media = hiero.core.MediaSource.createOfflineVideoMediaSource(
|
||||
prep_url(path),
|
||||
source_range.start_time.value,
|
||||
source_range.duration.value,
|
||||
hiero_rate,
|
||||
source_range.start_time.value
|
||||
)
|
||||
|
||||
return media
|
||||
|
||||
|
||||
def load_otio(otio_file):
|
||||
otio_timeline = otio.adapters.read_from_file(otio_file)
|
||||
build_sequence(otio_timeline)
|
||||
|
||||
|
||||
marker_color_map = {
|
||||
"PINK": "Magenta",
|
||||
"RED": "Red",
|
||||
"ORANGE": "Yellow",
|
||||
"YELLOW": "Yellow",
|
||||
"GREEN": "Green",
|
||||
"CYAN": "Cyan",
|
||||
"BLUE": "Blue",
|
||||
"PURPLE": "Magenta",
|
||||
"MAGENTA": "Magenta",
|
||||
"BLACK": "Blue",
|
||||
"WHITE": "Green"
|
||||
}
|
||||
|
||||
|
||||
def get_tag(tagname, tagsbin):
|
||||
for tag in tagsbin.items():
|
||||
if tag.name() == tagname:
|
||||
return tag
|
||||
|
||||
if isinstance(tag, hiero.core.Bin):
|
||||
tag = get_tag(tagname, tag)
|
||||
|
||||
if tag is not None:
|
||||
return tag
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def add_metadata(metadata, hiero_item):
|
||||
for key, value in metadata.items():
|
||||
if isinstance(value, dict):
|
||||
add_metadata(value, hiero_item)
|
||||
continue
|
||||
|
||||
if value is not None:
|
||||
if not key.startswith('tag.'):
|
||||
key = 'tag.' + key
|
||||
|
||||
hiero_item.metadata().setValue(key, str(value))
|
||||
|
||||
|
||||
def add_markers(otio_item, hiero_item, tagsbin):
|
||||
if isinstance(otio_item, (otio.schema.Stack, otio.schema.Clip)):
|
||||
markers = otio_item.markers
|
||||
|
||||
elif isinstance(otio_item, otio.schema.Timeline):
|
||||
markers = otio_item.tracks.markers
|
||||
|
||||
else:
|
||||
markers = []
|
||||
|
||||
for marker in markers:
|
||||
marker_color = marker.color
|
||||
|
||||
_tag = get_tag(marker.name, tagsbin)
|
||||
if _tag is None:
|
||||
_tag = get_tag(marker_color_map[marker_color], tagsbin)
|
||||
|
||||
if _tag is None:
|
||||
_tag = hiero.core.Tag(marker_color_map[marker.color])
|
||||
|
||||
start = marker.marked_range.start_time.value
|
||||
end = (
|
||||
marker.marked_range.start_time.value +
|
||||
marker.marked_range.duration.value
|
||||
)
|
||||
|
||||
tag = hiero_item.addTagToRange(_tag, start, end)
|
||||
tag.setName(marker.name or marker_color_map[marker_color])
|
||||
|
||||
# Add metadata
|
||||
add_metadata(marker.metadata, tag)
|
||||
|
||||
|
||||
def create_track(otio_track, tracknum, track_kind):
|
||||
# Add track kind when dealing with nested stacks
|
||||
if isinstance(otio_track, otio.schema.Stack):
|
||||
otio_track.kind = track_kind
|
||||
|
||||
# Create a Track
|
||||
if otio_track.kind == otio.schema.TrackKind.Video:
|
||||
track = hiero.core.VideoTrack(
|
||||
otio_track.name or 'Video{n}'.format(n=tracknum)
|
||||
)
|
||||
|
||||
else:
|
||||
track = hiero.core.AudioTrack(
|
||||
otio_track.name or 'Audio{n}'.format(n=tracknum)
|
||||
)
|
||||
|
||||
return track
|
||||
|
||||
|
||||
def create_clip(otio_clip, tagsbin):
|
||||
# Create MediaSource
|
||||
otio_media = otio_clip.media_reference
|
||||
if isinstance(otio_media, otio.schema.ExternalReference):
|
||||
url = prep_url(otio_media.target_url)
|
||||
media = hiero.core.MediaSource(url)
|
||||
if media.isOffline():
|
||||
media = create_offline_mediasource(otio_clip, url)
|
||||
|
||||
else:
|
||||
media = create_offline_mediasource(otio_clip)
|
||||
|
||||
# Create Clip
|
||||
clip = hiero.core.Clip(media)
|
||||
|
||||
# Add markers
|
||||
add_markers(otio_clip, clip, tagsbin)
|
||||
|
||||
return clip
|
||||
|
||||
|
||||
def create_trackitem(playhead, track, otio_clip, clip):
|
||||
source_range = otio_clip.source_range
|
||||
|
||||
trackitem = track.createTrackItem(otio_clip.name)
|
||||
trackitem.setPlaybackSpeed(source_range.start_time.rate)
|
||||
trackitem.setSource(clip)
|
||||
|
||||
# Check for speed effects and adjust playback speed accordingly
|
||||
for effect in otio_clip.effects:
|
||||
if isinstance(effect, otio.schema.LinearTimeWarp):
|
||||
trackitem.setPlaybackSpeed(
|
||||
trackitem.playbackSpeed() *
|
||||
effect.time_scalar
|
||||
)
|
||||
|
||||
# If reverse playback speed swap source in and out
|
||||
if trackitem.playbackSpeed() < 0:
|
||||
source_out = source_range.start_time.value
|
||||
source_in = (
|
||||
source_range.start_time.value +
|
||||
source_range.duration.value
|
||||
) - 1
|
||||
timeline_in = playhead + source_out
|
||||
timeline_out = (
|
||||
timeline_in +
|
||||
source_range.duration.value
|
||||
) - 1
|
||||
else:
|
||||
# Normal playback speed
|
||||
source_in = source_range.start_time.value
|
||||
source_out = (
|
||||
source_range.start_time.value +
|
||||
source_range.duration.value
|
||||
) - 1
|
||||
timeline_in = playhead
|
||||
timeline_out = (
|
||||
timeline_in +
|
||||
source_range.duration.value
|
||||
) - 1
|
||||
|
||||
# Set source and timeline in/out points
|
||||
trackitem.setSourceIn(source_in)
|
||||
trackitem.setSourceOut(source_out)
|
||||
trackitem.setTimelineIn(timeline_in)
|
||||
trackitem.setTimelineOut(timeline_out)
|
||||
|
||||
return trackitem
|
||||
|
||||
|
||||
def build_sequence(otio_timeline, project=None, track_kind=None):
|
||||
if project is None:
|
||||
# TODO: Find a proper way for active project
|
||||
project = hiero.core.projects(hiero.core.Project.kUserProjects)[-1]
|
||||
|
||||
# Create a Sequence
|
||||
sequence = hiero.core.Sequence(otio_timeline.name or 'OTIOSequence')
|
||||
|
||||
# Create a Bin to hold clips
|
||||
projectbin = project.clipsBin()
|
||||
projectbin.addItem(hiero.core.BinItem(sequence))
|
||||
sequencebin = hiero.core.Bin(sequence.name())
|
||||
projectbin.addItem(sequencebin)
|
||||
|
||||
# Get tagsBin
|
||||
tagsbin = hiero.core.project("Tag Presets").tagsBin()
|
||||
|
||||
# Add timeline markers
|
||||
add_markers(otio_timeline, sequence, tagsbin)
|
||||
|
||||
# TODO: Set sequence settings from otio timeline if available
|
||||
if isinstance(otio_timeline, otio.schema.Timeline):
|
||||
tracks = otio_timeline.tracks
|
||||
|
||||
else:
|
||||
# otio.schema.Stack
|
||||
tracks = otio_timeline
|
||||
|
||||
for tracknum, otio_track in enumerate(tracks):
|
||||
playhead = 0
|
||||
_transitions = []
|
||||
|
||||
# Add track to sequence
|
||||
track = create_track(otio_track, tracknum, track_kind)
|
||||
sequence.addTrack(track)
|
||||
|
||||
# iterate over items in track
|
||||
for itemnum, otio_clip in enumerate(otio_track):
|
||||
if isinstance(otio_clip, otio.schema.Stack):
|
||||
bar = hiero.ui.mainWindow().statusBar()
|
||||
bar.showMessage(
|
||||
'Nested sequences are created separately.',
|
||||
timeout=3000
|
||||
)
|
||||
build_sequence(otio_clip, project, otio_track.kind)
|
||||
|
||||
elif isinstance(otio_clip, otio.schema.Clip):
|
||||
# Create a Clip
|
||||
clip = create_clip(otio_clip, tagsbin)
|
||||
|
||||
# Add Clip to a Bin
|
||||
sequencebin.addItem(hiero.core.BinItem(clip))
|
||||
|
||||
# Create TrackItem
|
||||
trackitem = create_trackitem(
|
||||
playhead,
|
||||
track,
|
||||
otio_clip,
|
||||
clip
|
||||
)
|
||||
|
||||
# Add trackitem to track
|
||||
track.addTrackItem(trackitem)
|
||||
|
||||
# Update playhead
|
||||
playhead = trackitem.timelineOut() + 1
|
||||
|
||||
elif isinstance(otio_clip, otio.schema.Transition):
|
||||
# Store transitions for when all clips in the track are created
|
||||
_transitions.append((otio_track, otio_clip))
|
||||
|
||||
elif isinstance(otio_clip, otio.schema.Gap):
|
||||
# Hiero has no fillers, slugs or blanks at the moment
|
||||
playhead += otio_clip.source_range.duration.value
|
||||
|
||||
# Apply transitions we stored earlier now that all clips are present
|
||||
for otio_track, otio_item in _transitions:
|
||||
apply_transition(otio_track, otio_item, track)
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2018 Daniel Flehner Heen (Storm Studios)
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
import hiero.ui
|
||||
import hiero.core
|
||||
|
||||
from otioimporter.OTIOImport import load_otio
|
||||
|
||||
|
||||
def OTIO_menu_action(event):
|
||||
otio_action = hiero.ui.createMenuAction(
|
||||
'Import OTIO',
|
||||
open_otio_file,
|
||||
icon=None
|
||||
)
|
||||
hiero.ui.registerAction(otio_action)
|
||||
for action in event.menu.actions():
|
||||
if action.text() == 'Import':
|
||||
action.menu().addAction(otio_action)
|
||||
break
|
||||
|
||||
|
||||
def open_otio_file():
|
||||
files = hiero.ui.openFileBrowser(
|
||||
caption='Please select an OTIO file of choice',
|
||||
pattern='*.otio',
|
||||
requiredExtension='.otio'
|
||||
)
|
||||
for otio_file in files:
|
||||
load_otio(otio_file)
|
||||
|
||||
|
||||
# HieroPlayer is quite limited and can't create transitions etc.
|
||||
if not hiero.core.isHieroPlayer():
|
||||
hiero.core.events.registerInterest(
|
||||
"kShowContextMenu/kBin",
|
||||
OTIO_menu_action
|
||||
)
|
||||
|
|
@ -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()
|
||||
|
|
@ -0,0 +1,198 @@
|
|||
<root presetname="pipeline" tasktype="hiero.exporters.FnShotProcessor.ShotProcessor">
|
||||
<startFrameIndex valuetype="int">991</startFrameIndex>
|
||||
<exportRoot valuetype="str">//10.11.0.184/171001_ftrack/tgbvfx/editorial/nukestudio/workspace/</exportRoot>
|
||||
<versionIndex valuetype="int">1</versionIndex>
|
||||
<cutUseHandles valuetype="bool">True</cutUseHandles>
|
||||
<versionPadding valuetype="int">3</versionPadding>
|
||||
<exportTemplate valuetype="list">
|
||||
<SequenceItem valuetype="tuple">
|
||||
<SequenceItem valuetype="str">{shot}/editorial_raw.%04d.{fileext}</SequenceItem>
|
||||
<SequenceItem valuetype="hiero.exporters.FnSymLinkExporter.SymLinkPreset">
|
||||
<root presetname="hiero.exporters.FnSymLinkExporter.SymLinkExporter" tasktype="hiero.exporters.FnSymLinkExporter.SymLinkExporter">
|
||||
<colourspace valuetype="str">default</colourspace>
|
||||
<file_type valuetype="unicode">exr</file_type>
|
||||
<readAllLinesForExport valuetype="bool">False</readAllLinesForExport>
|
||||
<channels valuetype="str">all</channels>
|
||||
<includeAudio valuetype="bool">False</includeAudio>
|
||||
<burninDataEnabled valuetype="bool">False</burninDataEnabled>
|
||||
<useSingleSocket valuetype="bool">False</useSingleSocket>
|
||||
<additionalNodesEnabled valuetype="bool">False</additionalNodesEnabled>
|
||||
<deleteAudio valuetype="bool">True</deleteAudio>
|
||||
<additionalNodesData valuetype="list" />
|
||||
<dpx valuetype="dict">
|
||||
<datatype valuetype="str">8 bit</datatype>
|
||||
<transfer valuetype="str">(auto detect)</transfer>
|
||||
<bigEndian valuetype="bool">True</bigEndian>
|
||||
<fill valuetype="bool">False</fill>
|
||||
</dpx>
|
||||
<includeEffects valuetype="bool">False</includeEffects>
|
||||
<burninData valuetype="dict">
|
||||
<burnIn_bottomRight valuetype="NoneType">None</burnIn_bottomRight>
|
||||
<burnIn_topLeft valuetype="NoneType">None</burnIn_topLeft>
|
||||
<burnIn_topMiddle valuetype="NoneType">None</burnIn_topMiddle>
|
||||
<burnIn_padding valuetype="NoneType">None</burnIn_padding>
|
||||
<burnIn_topRight valuetype="NoneType">None</burnIn_topRight>
|
||||
<burnIn_textSize valuetype="NoneType">None</burnIn_textSize>
|
||||
<burnIn_bottomLeft valuetype="NoneType">None</burnIn_bottomLeft>
|
||||
<burnIn_bottomMiddle valuetype="NoneType">None</burnIn_bottomMiddle>
|
||||
<burnIn_font valuetype="NoneType">None</burnIn_font>
|
||||
</burninData>
|
||||
<exr valuetype="dict">
|
||||
<compression valuetype="str">Zip (16 scanline)</compression>
|
||||
<datatype valuetype="str">32 bit float</datatype>
|
||||
<noprefix valuetype="bool">False</noprefix>
|
||||
<write_full_layer_names valuetype="bool">False</write_full_layer_names>
|
||||
<standard_layer_name_format valuetype="bool">False</standard_layer_name_format>
|
||||
<interleave valuetype="str">channels, layers and views</interleave>
|
||||
<dw_compression_level valuetype="float">45.0</dw_compression_level>
|
||||
<truncateChannelNames valuetype="bool">False</truncateChannelNames>
|
||||
<metadata valuetype="str">all metadata</metadata>
|
||||
</exr>
|
||||
<writeNodeName valuetype="str">Write_{ext}</writeNodeName>
|
||||
<reformat valuetype="dict">
|
||||
<filter valuetype="str">Cubic</filter>
|
||||
<to_type valuetype="str">None</to_type>
|
||||
<scale valuetype="float">1.0</scale>
|
||||
<center valuetype="bool">True</center>
|
||||
<resize valuetype="str">width</resize>
|
||||
</reformat>
|
||||
<keepNukeScript valuetype="bool">False</keepNukeScript>
|
||||
<method valuetype="str">Blend</method>
|
||||
</root>
|
||||
</SequenceItem>
|
||||
</SequenceItem>
|
||||
<SequenceItem valuetype="tuple">
|
||||
<SequenceItem valuetype="str">{shot}/editorial.%04d.{ext}</SequenceItem>
|
||||
<SequenceItem valuetype="hiero.exporters.FnTranscodeExporter.TranscodePreset">
|
||||
<root presetname="hiero.exporters.FnTranscodeExporter.TranscodeExporter" tasktype="hiero.exporters.FnTranscodeExporter.TranscodeExporter">
|
||||
<colourspace valuetype="str">default</colourspace>
|
||||
<file_type valuetype="unicode">exr</file_type>
|
||||
<readAllLinesForExport valuetype="bool">False</readAllLinesForExport>
|
||||
<channels valuetype="str">all</channels>
|
||||
<includeAudio valuetype="bool">False</includeAudio>
|
||||
<burninDataEnabled valuetype="bool">False</burninDataEnabled>
|
||||
<useSingleSocket valuetype="bool">False</useSingleSocket>
|
||||
<additionalNodesEnabled valuetype="bool">False</additionalNodesEnabled>
|
||||
<deleteAudio valuetype="bool">True</deleteAudio>
|
||||
<additionalNodesData valuetype="list" />
|
||||
<dpx valuetype="dict">
|
||||
<datatype valuetype="str">8 bit</datatype>
|
||||
<transfer valuetype="str">(auto detect)</transfer>
|
||||
<bigEndian valuetype="bool">True</bigEndian>
|
||||
<fill valuetype="bool">False</fill>
|
||||
</dpx>
|
||||
<includeEffects valuetype="bool">True</includeEffects>
|
||||
<burninData valuetype="dict">
|
||||
<burnIn_bottomRight valuetype="NoneType">None</burnIn_bottomRight>
|
||||
<burnIn_topLeft valuetype="NoneType">None</burnIn_topLeft>
|
||||
<burnIn_topMiddle valuetype="NoneType">None</burnIn_topMiddle>
|
||||
<burnIn_padding valuetype="NoneType">None</burnIn_padding>
|
||||
<burnIn_topRight valuetype="NoneType">None</burnIn_topRight>
|
||||
<burnIn_textSize valuetype="NoneType">None</burnIn_textSize>
|
||||
<burnIn_bottomLeft valuetype="NoneType">None</burnIn_bottomLeft>
|
||||
<burnIn_bottomMiddle valuetype="NoneType">None</burnIn_bottomMiddle>
|
||||
<burnIn_font valuetype="NoneType">None</burnIn_font>
|
||||
</burninData>
|
||||
<exr valuetype="dict">
|
||||
<compression valuetype="str">Zip (16 scanline)</compression>
|
||||
<datatype valuetype="str">16 bit half</datatype>
|
||||
<noprefix valuetype="bool">False</noprefix>
|
||||
<write_full_layer_names valuetype="bool">False</write_full_layer_names>
|
||||
<standard_layer_name_format valuetype="bool">False</standard_layer_name_format>
|
||||
<interleave valuetype="str">channels, layers and views</interleave>
|
||||
<dw_compression_level valuetype="float">45.0</dw_compression_level>
|
||||
<truncateChannelNames valuetype="bool">False</truncateChannelNames>
|
||||
<metadata valuetype="str">all metadata</metadata>
|
||||
</exr>
|
||||
<writeNodeName valuetype="str">Write_{ext}</writeNodeName>
|
||||
<reformat valuetype="dict">
|
||||
<filter valuetype="str">Cubic</filter>
|
||||
<to_type valuetype="str">To Sequence Resolution</to_type>
|
||||
<scale valuetype="float">1.0</scale>
|
||||
<center valuetype="bool">True</center>
|
||||
<resize valuetype="str">width</resize>
|
||||
</reformat>
|
||||
<keepNukeScript valuetype="bool">False</keepNukeScript>
|
||||
<method valuetype="str">Blend</method>
|
||||
</root>
|
||||
</SequenceItem>
|
||||
</SequenceItem>
|
||||
<SequenceItem valuetype="tuple">
|
||||
<SequenceItem valuetype="str">{shot}/editorial.nk</SequenceItem>
|
||||
<SequenceItem valuetype="hiero.exporters.FnNukeShotExporter.NukeShotPreset">
|
||||
<root presetname="hiero.exporters.FnNukeShotExporter.NukeShotExporter" tasktype="hiero.exporters.FnNukeShotExporter.NukeShotExporter">
|
||||
<postProcessScript valuetype="bool">True</postProcessScript>
|
||||
<colourspace valuetype="str">default</colourspace>
|
||||
<file_type valuetype="unicode">mov</file_type>
|
||||
<annotationsPreCompPaths valuetype="list" />
|
||||
<channels valuetype="str">rgb</channels>
|
||||
<includeAudio valuetype="bool">False</includeAudio>
|
||||
<readPaths valuetype="list" />
|
||||
<connectTracks valuetype="bool">False</connectTracks>
|
||||
<useSingleSocket valuetype="bool">False</useSingleSocket>
|
||||
<collateSequence valuetype="bool">False</collateSequence>
|
||||
<additionalNodesData valuetype="list" />
|
||||
<collateShotNames valuetype="bool">True</collateShotNames>
|
||||
<includeEffects valuetype="bool">True</includeEffects>
|
||||
<writePaths valuetype="list">
|
||||
<SequenceItem valuetype="str">{shot}/editorial_raw.%04d.{fileext}</SequenceItem>
|
||||
</writePaths>
|
||||
<reformat valuetype="dict">
|
||||
<filter valuetype="str">Cubic</filter>
|
||||
<to_type valuetype="str">None</to_type>
|
||||
<scale valuetype="float">1.0</scale>
|
||||
<center valuetype="bool">True</center>
|
||||
<resize valuetype="str">width</resize>
|
||||
</reformat>
|
||||
<keepNukeScript valuetype="bool">False</keepNukeScript>
|
||||
<method valuetype="str">Blend</method>
|
||||
<includeAnnotations valuetype="bool">False</includeAnnotations>
|
||||
<enable valuetype="bool">True</enable>
|
||||
<showAnnotations valuetype="bool">True</showAnnotations>
|
||||
<mov valuetype="dict">
|
||||
<b_frames valuetype="int">0</b_frames>
|
||||
<bitrate_tolerance valuetype="int">40000000</bitrate_tolerance>
|
||||
<gop_size valuetype="int">12</gop_size>
|
||||
<quality_max valuetype="int">31</quality_max>
|
||||
<quality_min valuetype="int">2</quality_min>
|
||||
<codec valuetype="str">avc1	H.264</codec>
|
||||
<ycbcr_matrix_type valuetype="str">Auto</ycbcr_matrix_type>
|
||||
<encoder valuetype="str">mov32</encoder>
|
||||
<bitrate valuetype="int">20000</bitrate>
|
||||
</mov>
|
||||
<readAllLinesForExport valuetype="bool">False</readAllLinesForExport>
|
||||
<deleteAudio valuetype="bool">True</deleteAudio>
|
||||
<collateCustomStart valuetype="bool">True</collateCustomStart>
|
||||
<burninDataEnabled valuetype="bool">False</burninDataEnabled>
|
||||
<additionalNodesEnabled valuetype="bool">False</additionalNodesEnabled>
|
||||
<timelineWriteNode valuetype="str">{shot}/editorial_raw.%04d.{fileext}</timelineWriteNode>
|
||||
<burninData valuetype="dict">
|
||||
<burnIn_bottomRight valuetype="NoneType">None</burnIn_bottomRight>
|
||||
<burnIn_topLeft valuetype="NoneType">None</burnIn_topLeft>
|
||||
<burnIn_topMiddle valuetype="NoneType">None</burnIn_topMiddle>
|
||||
<burnIn_padding valuetype="NoneType">None</burnIn_padding>
|
||||
<burnIn_topRight valuetype="NoneType">None</burnIn_topRight>
|
||||
<burnIn_bottomMiddle valuetype="NoneType">None</burnIn_bottomMiddle>
|
||||
<burnIn_bottomLeft valuetype="NoneType">None</burnIn_bottomLeft>
|
||||
<burnIn_textSize valuetype="NoneType">None</burnIn_textSize>
|
||||
<burnIn_font valuetype="NoneType">None</burnIn_font>
|
||||
</burninData>
|
||||
<dpx valuetype="dict">
|
||||
<datatype valuetype="str">8 bit</datatype>
|
||||
<transfer valuetype="str">(auto detect)</transfer>
|
||||
<bigEndian valuetype="bool">True</bigEndian>
|
||||
<fill valuetype="bool">False</fill>
|
||||
</dpx>
|
||||
<writeNodeName valuetype="str">Write_{ext}</writeNodeName>
|
||||
<collateTracks valuetype="bool">False</collateTracks>
|
||||
</root>
|
||||
</SequenceItem>
|
||||
</SequenceItem>
|
||||
</exportTemplate>
|
||||
<excludeTags valuetype="list" />
|
||||
<includeTags valuetype="list" />
|
||||
<includeRetimes valuetype="bool">False</includeRetimes>
|
||||
<startFrameSource valuetype="str">Custom</startFrameSource>
|
||||
<cutLength valuetype="bool">True</cutLength>
|
||||
<cutHandles valuetype="int">10</cutHandles>
|
||||
</root>
|
||||
|
|
@ -0,0 +1,198 @@
|
|||
<root presetname="pipeline" tasktype="hiero.exporters.FnShotProcessor.ShotProcessor">
|
||||
<startFrameIndex valuetype="int">991</startFrameIndex>
|
||||
<exportRoot valuetype="str">//10.11.0.184/171001_ftrack/tgbvfx/editorial/nukestudio/workspace/</exportRoot>
|
||||
<versionIndex valuetype="int">1</versionIndex>
|
||||
<cutUseHandles valuetype="bool">True</cutUseHandles>
|
||||
<versionPadding valuetype="int">3</versionPadding>
|
||||
<exportTemplate valuetype="list">
|
||||
<SequenceItem valuetype="tuple">
|
||||
<SequenceItem valuetype="str">{shot}/editorial_raw.%04d.{fileext}</SequenceItem>
|
||||
<SequenceItem valuetype="hiero.exporters.FnSymLinkExporter.SymLinkPreset">
|
||||
<root presetname="hiero.exporters.FnSymLinkExporter.SymLinkExporter" tasktype="hiero.exporters.FnSymLinkExporter.SymLinkExporter">
|
||||
<colourspace valuetype="str">default</colourspace>
|
||||
<file_type valuetype="unicode">exr</file_type>
|
||||
<readAllLinesForExport valuetype="bool">False</readAllLinesForExport>
|
||||
<channels valuetype="str">all</channels>
|
||||
<includeAudio valuetype="bool">False</includeAudio>
|
||||
<burninDataEnabled valuetype="bool">False</burninDataEnabled>
|
||||
<useSingleSocket valuetype="bool">False</useSingleSocket>
|
||||
<additionalNodesEnabled valuetype="bool">False</additionalNodesEnabled>
|
||||
<deleteAudio valuetype="bool">True</deleteAudio>
|
||||
<additionalNodesData valuetype="list" />
|
||||
<dpx valuetype="dict">
|
||||
<datatype valuetype="str">8 bit</datatype>
|
||||
<transfer valuetype="str">(auto detect)</transfer>
|
||||
<bigEndian valuetype="bool">True</bigEndian>
|
||||
<fill valuetype="bool">False</fill>
|
||||
</dpx>
|
||||
<includeEffects valuetype="bool">False</includeEffects>
|
||||
<burninData valuetype="dict">
|
||||
<burnIn_bottomRight valuetype="NoneType">None</burnIn_bottomRight>
|
||||
<burnIn_topLeft valuetype="NoneType">None</burnIn_topLeft>
|
||||
<burnIn_topMiddle valuetype="NoneType">None</burnIn_topMiddle>
|
||||
<burnIn_padding valuetype="NoneType">None</burnIn_padding>
|
||||
<burnIn_topRight valuetype="NoneType">None</burnIn_topRight>
|
||||
<burnIn_textSize valuetype="NoneType">None</burnIn_textSize>
|
||||
<burnIn_bottomLeft valuetype="NoneType">None</burnIn_bottomLeft>
|
||||
<burnIn_bottomMiddle valuetype="NoneType">None</burnIn_bottomMiddle>
|
||||
<burnIn_font valuetype="NoneType">None</burnIn_font>
|
||||
</burninData>
|
||||
<exr valuetype="dict">
|
||||
<compression valuetype="str">Zip (16 scanline)</compression>
|
||||
<datatype valuetype="str">32 bit float</datatype>
|
||||
<noprefix valuetype="bool">False</noprefix>
|
||||
<write_full_layer_names valuetype="bool">False</write_full_layer_names>
|
||||
<standard_layer_name_format valuetype="bool">False</standard_layer_name_format>
|
||||
<interleave valuetype="str">channels, layers and views</interleave>
|
||||
<dw_compression_level valuetype="float">45.0</dw_compression_level>
|
||||
<truncateChannelNames valuetype="bool">False</truncateChannelNames>
|
||||
<metadata valuetype="str">all metadata</metadata>
|
||||
</exr>
|
||||
<writeNodeName valuetype="str">Write_{ext}</writeNodeName>
|
||||
<reformat valuetype="dict">
|
||||
<filter valuetype="str">Cubic</filter>
|
||||
<to_type valuetype="str">None</to_type>
|
||||
<scale valuetype="float">1.0</scale>
|
||||
<center valuetype="bool">True</center>
|
||||
<resize valuetype="str">width</resize>
|
||||
</reformat>
|
||||
<keepNukeScript valuetype="bool">False</keepNukeScript>
|
||||
<method valuetype="str">Blend</method>
|
||||
</root>
|
||||
</SequenceItem>
|
||||
</SequenceItem>
|
||||
<SequenceItem valuetype="tuple">
|
||||
<SequenceItem valuetype="str">{shot}/editorial.%04d.{ext}</SequenceItem>
|
||||
<SequenceItem valuetype="hiero.exporters.FnTranscodeExporter.TranscodePreset">
|
||||
<root presetname="hiero.exporters.FnTranscodeExporter.TranscodeExporter" tasktype="hiero.exporters.FnTranscodeExporter.TranscodeExporter">
|
||||
<colourspace valuetype="str">default</colourspace>
|
||||
<file_type valuetype="unicode">exr</file_type>
|
||||
<readAllLinesForExport valuetype="bool">False</readAllLinesForExport>
|
||||
<channels valuetype="str">all</channels>
|
||||
<includeAudio valuetype="bool">False</includeAudio>
|
||||
<burninDataEnabled valuetype="bool">False</burninDataEnabled>
|
||||
<useSingleSocket valuetype="bool">False</useSingleSocket>
|
||||
<additionalNodesEnabled valuetype="bool">False</additionalNodesEnabled>
|
||||
<deleteAudio valuetype="bool">True</deleteAudio>
|
||||
<additionalNodesData valuetype="list" />
|
||||
<dpx valuetype="dict">
|
||||
<datatype valuetype="str">8 bit</datatype>
|
||||
<transfer valuetype="str">(auto detect)</transfer>
|
||||
<bigEndian valuetype="bool">True</bigEndian>
|
||||
<fill valuetype="bool">False</fill>
|
||||
</dpx>
|
||||
<includeEffects valuetype="bool">True</includeEffects>
|
||||
<burninData valuetype="dict">
|
||||
<burnIn_bottomRight valuetype="NoneType">None</burnIn_bottomRight>
|
||||
<burnIn_topLeft valuetype="NoneType">None</burnIn_topLeft>
|
||||
<burnIn_topMiddle valuetype="NoneType">None</burnIn_topMiddle>
|
||||
<burnIn_padding valuetype="NoneType">None</burnIn_padding>
|
||||
<burnIn_topRight valuetype="NoneType">None</burnIn_topRight>
|
||||
<burnIn_textSize valuetype="NoneType">None</burnIn_textSize>
|
||||
<burnIn_bottomLeft valuetype="NoneType">None</burnIn_bottomLeft>
|
||||
<burnIn_bottomMiddle valuetype="NoneType">None</burnIn_bottomMiddle>
|
||||
<burnIn_font valuetype="NoneType">None</burnIn_font>
|
||||
</burninData>
|
||||
<exr valuetype="dict">
|
||||
<compression valuetype="str">Zip (16 scanline)</compression>
|
||||
<datatype valuetype="str">16 bit half</datatype>
|
||||
<noprefix valuetype="bool">False</noprefix>
|
||||
<write_full_layer_names valuetype="bool">False</write_full_layer_names>
|
||||
<standard_layer_name_format valuetype="bool">False</standard_layer_name_format>
|
||||
<interleave valuetype="str">channels, layers and views</interleave>
|
||||
<dw_compression_level valuetype="float">45.0</dw_compression_level>
|
||||
<truncateChannelNames valuetype="bool">False</truncateChannelNames>
|
||||
<metadata valuetype="str">all metadata</metadata>
|
||||
</exr>
|
||||
<writeNodeName valuetype="str">Write_{ext}</writeNodeName>
|
||||
<reformat valuetype="dict">
|
||||
<filter valuetype="str">Cubic</filter>
|
||||
<to_type valuetype="str">To Sequence Resolution</to_type>
|
||||
<scale valuetype="float">1.0</scale>
|
||||
<center valuetype="bool">True</center>
|
||||
<resize valuetype="str">width</resize>
|
||||
</reformat>
|
||||
<keepNukeScript valuetype="bool">False</keepNukeScript>
|
||||
<method valuetype="str">Blend</method>
|
||||
</root>
|
||||
</SequenceItem>
|
||||
</SequenceItem>
|
||||
<SequenceItem valuetype="tuple">
|
||||
<SequenceItem valuetype="str">{shot}/editorial.nk</SequenceItem>
|
||||
<SequenceItem valuetype="hiero.exporters.FnNukeShotExporter.NukeShotPreset">
|
||||
<root presetname="hiero.exporters.FnNukeShotExporter.NukeShotExporter" tasktype="hiero.exporters.FnNukeShotExporter.NukeShotExporter">
|
||||
<postProcessScript valuetype="bool">True</postProcessScript>
|
||||
<colourspace valuetype="str">default</colourspace>
|
||||
<file_type valuetype="unicode">mov</file_type>
|
||||
<annotationsPreCompPaths valuetype="list" />
|
||||
<channels valuetype="str">rgb</channels>
|
||||
<includeAudio valuetype="bool">False</includeAudio>
|
||||
<readPaths valuetype="list" />
|
||||
<connectTracks valuetype="bool">False</connectTracks>
|
||||
<useSingleSocket valuetype="bool">False</useSingleSocket>
|
||||
<collateSequence valuetype="bool">False</collateSequence>
|
||||
<additionalNodesData valuetype="list" />
|
||||
<collateShotNames valuetype="bool">True</collateShotNames>
|
||||
<includeEffects valuetype="bool">True</includeEffects>
|
||||
<writePaths valuetype="list">
|
||||
<SequenceItem valuetype="str">{shot}/editorial_raw.%04d.{fileext}</SequenceItem>
|
||||
</writePaths>
|
||||
<reformat valuetype="dict">
|
||||
<filter valuetype="str">Cubic</filter>
|
||||
<to_type valuetype="str">None</to_type>
|
||||
<scale valuetype="float">1.0</scale>
|
||||
<center valuetype="bool">True</center>
|
||||
<resize valuetype="str">width</resize>
|
||||
</reformat>
|
||||
<keepNukeScript valuetype="bool">False</keepNukeScript>
|
||||
<method valuetype="str">Blend</method>
|
||||
<includeAnnotations valuetype="bool">False</includeAnnotations>
|
||||
<enable valuetype="bool">True</enable>
|
||||
<showAnnotations valuetype="bool">True</showAnnotations>
|
||||
<mov valuetype="dict">
|
||||
<b_frames valuetype="int">0</b_frames>
|
||||
<bitrate_tolerance valuetype="int">40000000</bitrate_tolerance>
|
||||
<gop_size valuetype="int">12</gop_size>
|
||||
<quality_max valuetype="int">31</quality_max>
|
||||
<quality_min valuetype="int">2</quality_min>
|
||||
<codec valuetype="str">avc1	H.264</codec>
|
||||
<ycbcr_matrix_type valuetype="str">Auto</ycbcr_matrix_type>
|
||||
<encoder valuetype="str">mov32</encoder>
|
||||
<bitrate valuetype="int">20000</bitrate>
|
||||
</mov>
|
||||
<readAllLinesForExport valuetype="bool">False</readAllLinesForExport>
|
||||
<deleteAudio valuetype="bool">True</deleteAudio>
|
||||
<collateCustomStart valuetype="bool">True</collateCustomStart>
|
||||
<burninDataEnabled valuetype="bool">False</burninDataEnabled>
|
||||
<additionalNodesEnabled valuetype="bool">False</additionalNodesEnabled>
|
||||
<timelineWriteNode valuetype="str">{shot}/editorial_raw.%04d.{fileext}</timelineWriteNode>
|
||||
<burninData valuetype="dict">
|
||||
<burnIn_bottomRight valuetype="NoneType">None</burnIn_bottomRight>
|
||||
<burnIn_topLeft valuetype="NoneType">None</burnIn_topLeft>
|
||||
<burnIn_topMiddle valuetype="NoneType">None</burnIn_topMiddle>
|
||||
<burnIn_padding valuetype="NoneType">None</burnIn_padding>
|
||||
<burnIn_topRight valuetype="NoneType">None</burnIn_topRight>
|
||||
<burnIn_bottomMiddle valuetype="NoneType">None</burnIn_bottomMiddle>
|
||||
<burnIn_bottomLeft valuetype="NoneType">None</burnIn_bottomLeft>
|
||||
<burnIn_textSize valuetype="NoneType">None</burnIn_textSize>
|
||||
<burnIn_font valuetype="NoneType">None</burnIn_font>
|
||||
</burninData>
|
||||
<dpx valuetype="dict">
|
||||
<datatype valuetype="str">8 bit</datatype>
|
||||
<transfer valuetype="str">(auto detect)</transfer>
|
||||
<bigEndian valuetype="bool">True</bigEndian>
|
||||
<fill valuetype="bool">False</fill>
|
||||
</dpx>
|
||||
<writeNodeName valuetype="str">Write_{ext}</writeNodeName>
|
||||
<collateTracks valuetype="bool">False</collateTracks>
|
||||
</root>
|
||||
</SequenceItem>
|
||||
</SequenceItem>
|
||||
</exportTemplate>
|
||||
<excludeTags valuetype="list" />
|
||||
<includeTags valuetype="list" />
|
||||
<includeRetimes valuetype="bool">False</includeRetimes>
|
||||
<startFrameSource valuetype="str">Custom</startFrameSource>
|
||||
<cutLength valuetype="bool">True</cutLength>
|
||||
<cutHandles valuetype="int">10</cutHandles>
|
||||
</root>
|
||||
|
|
@ -0,0 +1,198 @@
|
|||
<root presetname="pipeline" tasktype="hiero.exporters.FnShotProcessor.ShotProcessor">
|
||||
<startFrameIndex valuetype="int">991</startFrameIndex>
|
||||
<exportRoot valuetype="str">//10.11.0.184/171001_ftrack/tgbvfx/editorial/nukestudio/workspace/</exportRoot>
|
||||
<versionIndex valuetype="int">1</versionIndex>
|
||||
<cutUseHandles valuetype="bool">True</cutUseHandles>
|
||||
<versionPadding valuetype="int">3</versionPadding>
|
||||
<exportTemplate valuetype="list">
|
||||
<SequenceItem valuetype="tuple">
|
||||
<SequenceItem valuetype="str">{shot}/editorial_raw.%04d.{fileext}</SequenceItem>
|
||||
<SequenceItem valuetype="hiero.exporters.FnSymLinkExporter.SymLinkPreset">
|
||||
<root presetname="hiero.exporters.FnSymLinkExporter.SymLinkExporter" tasktype="hiero.exporters.FnSymLinkExporter.SymLinkExporter">
|
||||
<colourspace valuetype="str">default</colourspace>
|
||||
<file_type valuetype="unicode">exr</file_type>
|
||||
<readAllLinesForExport valuetype="bool">False</readAllLinesForExport>
|
||||
<channels valuetype="str">all</channels>
|
||||
<includeAudio valuetype="bool">False</includeAudio>
|
||||
<burninDataEnabled valuetype="bool">False</burninDataEnabled>
|
||||
<useSingleSocket valuetype="bool">False</useSingleSocket>
|
||||
<additionalNodesEnabled valuetype="bool">False</additionalNodesEnabled>
|
||||
<deleteAudio valuetype="bool">True</deleteAudio>
|
||||
<additionalNodesData valuetype="list" />
|
||||
<dpx valuetype="dict">
|
||||
<datatype valuetype="str">8 bit</datatype>
|
||||
<transfer valuetype="str">(auto detect)</transfer>
|
||||
<bigEndian valuetype="bool">True</bigEndian>
|
||||
<fill valuetype="bool">False</fill>
|
||||
</dpx>
|
||||
<includeEffects valuetype="bool">False</includeEffects>
|
||||
<burninData valuetype="dict">
|
||||
<burnIn_bottomRight valuetype="NoneType">None</burnIn_bottomRight>
|
||||
<burnIn_topLeft valuetype="NoneType">None</burnIn_topLeft>
|
||||
<burnIn_topMiddle valuetype="NoneType">None</burnIn_topMiddle>
|
||||
<burnIn_padding valuetype="NoneType">None</burnIn_padding>
|
||||
<burnIn_topRight valuetype="NoneType">None</burnIn_topRight>
|
||||
<burnIn_textSize valuetype="NoneType">None</burnIn_textSize>
|
||||
<burnIn_bottomLeft valuetype="NoneType">None</burnIn_bottomLeft>
|
||||
<burnIn_bottomMiddle valuetype="NoneType">None</burnIn_bottomMiddle>
|
||||
<burnIn_font valuetype="NoneType">None</burnIn_font>
|
||||
</burninData>
|
||||
<exr valuetype="dict">
|
||||
<compression valuetype="str">Zip (16 scanline)</compression>
|
||||
<datatype valuetype="str">32 bit float</datatype>
|
||||
<noprefix valuetype="bool">False</noprefix>
|
||||
<write_full_layer_names valuetype="bool">False</write_full_layer_names>
|
||||
<standard_layer_name_format valuetype="bool">False</standard_layer_name_format>
|
||||
<interleave valuetype="str">channels, layers and views</interleave>
|
||||
<dw_compression_level valuetype="float">45.0</dw_compression_level>
|
||||
<truncateChannelNames valuetype="bool">False</truncateChannelNames>
|
||||
<metadata valuetype="str">all metadata</metadata>
|
||||
</exr>
|
||||
<writeNodeName valuetype="str">Write_{ext}</writeNodeName>
|
||||
<reformat valuetype="dict">
|
||||
<filter valuetype="str">Cubic</filter>
|
||||
<to_type valuetype="str">None</to_type>
|
||||
<scale valuetype="float">1.0</scale>
|
||||
<center valuetype="bool">True</center>
|
||||
<resize valuetype="str">width</resize>
|
||||
</reformat>
|
||||
<keepNukeScript valuetype="bool">False</keepNukeScript>
|
||||
<method valuetype="str">Blend</method>
|
||||
</root>
|
||||
</SequenceItem>
|
||||
</SequenceItem>
|
||||
<SequenceItem valuetype="tuple">
|
||||
<SequenceItem valuetype="str">{shot}/editorial.%04d.{ext}</SequenceItem>
|
||||
<SequenceItem valuetype="hiero.exporters.FnTranscodeExporter.TranscodePreset">
|
||||
<root presetname="hiero.exporters.FnTranscodeExporter.TranscodeExporter" tasktype="hiero.exporters.FnTranscodeExporter.TranscodeExporter">
|
||||
<colourspace valuetype="str">default</colourspace>
|
||||
<file_type valuetype="unicode">exr</file_type>
|
||||
<readAllLinesForExport valuetype="bool">False</readAllLinesForExport>
|
||||
<channels valuetype="str">all</channels>
|
||||
<includeAudio valuetype="bool">False</includeAudio>
|
||||
<burninDataEnabled valuetype="bool">False</burninDataEnabled>
|
||||
<useSingleSocket valuetype="bool">False</useSingleSocket>
|
||||
<additionalNodesEnabled valuetype="bool">False</additionalNodesEnabled>
|
||||
<deleteAudio valuetype="bool">True</deleteAudio>
|
||||
<additionalNodesData valuetype="list" />
|
||||
<dpx valuetype="dict">
|
||||
<datatype valuetype="str">8 bit</datatype>
|
||||
<transfer valuetype="str">(auto detect)</transfer>
|
||||
<bigEndian valuetype="bool">True</bigEndian>
|
||||
<fill valuetype="bool">False</fill>
|
||||
</dpx>
|
||||
<includeEffects valuetype="bool">True</includeEffects>
|
||||
<burninData valuetype="dict">
|
||||
<burnIn_bottomRight valuetype="NoneType">None</burnIn_bottomRight>
|
||||
<burnIn_topLeft valuetype="NoneType">None</burnIn_topLeft>
|
||||
<burnIn_topMiddle valuetype="NoneType">None</burnIn_topMiddle>
|
||||
<burnIn_padding valuetype="NoneType">None</burnIn_padding>
|
||||
<burnIn_topRight valuetype="NoneType">None</burnIn_topRight>
|
||||
<burnIn_textSize valuetype="NoneType">None</burnIn_textSize>
|
||||
<burnIn_bottomLeft valuetype="NoneType">None</burnIn_bottomLeft>
|
||||
<burnIn_bottomMiddle valuetype="NoneType">None</burnIn_bottomMiddle>
|
||||
<burnIn_font valuetype="NoneType">None</burnIn_font>
|
||||
</burninData>
|
||||
<exr valuetype="dict">
|
||||
<compression valuetype="str">Zip (16 scanline)</compression>
|
||||
<datatype valuetype="str">16 bit half</datatype>
|
||||
<noprefix valuetype="bool">False</noprefix>
|
||||
<write_full_layer_names valuetype="bool">False</write_full_layer_names>
|
||||
<standard_layer_name_format valuetype="bool">False</standard_layer_name_format>
|
||||
<interleave valuetype="str">channels, layers and views</interleave>
|
||||
<dw_compression_level valuetype="float">45.0</dw_compression_level>
|
||||
<truncateChannelNames valuetype="bool">False</truncateChannelNames>
|
||||
<metadata valuetype="str">all metadata</metadata>
|
||||
</exr>
|
||||
<writeNodeName valuetype="str">Write_{ext}</writeNodeName>
|
||||
<reformat valuetype="dict">
|
||||
<filter valuetype="str">Cubic</filter>
|
||||
<to_type valuetype="str">To Sequence Resolution</to_type>
|
||||
<scale valuetype="float">1.0</scale>
|
||||
<center valuetype="bool">True</center>
|
||||
<resize valuetype="str">width</resize>
|
||||
</reformat>
|
||||
<keepNukeScript valuetype="bool">False</keepNukeScript>
|
||||
<method valuetype="str">Blend</method>
|
||||
</root>
|
||||
</SequenceItem>
|
||||
</SequenceItem>
|
||||
<SequenceItem valuetype="tuple">
|
||||
<SequenceItem valuetype="str">{shot}/editorial.nk</SequenceItem>
|
||||
<SequenceItem valuetype="hiero.exporters.FnNukeShotExporter.NukeShotPreset">
|
||||
<root presetname="hiero.exporters.FnNukeShotExporter.NukeShotExporter" tasktype="hiero.exporters.FnNukeShotExporter.NukeShotExporter">
|
||||
<postProcessScript valuetype="bool">True</postProcessScript>
|
||||
<colourspace valuetype="str">default</colourspace>
|
||||
<file_type valuetype="unicode">mov</file_type>
|
||||
<annotationsPreCompPaths valuetype="list" />
|
||||
<channels valuetype="str">rgb</channels>
|
||||
<includeAudio valuetype="bool">False</includeAudio>
|
||||
<readPaths valuetype="list" />
|
||||
<connectTracks valuetype="bool">False</connectTracks>
|
||||
<useSingleSocket valuetype="bool">False</useSingleSocket>
|
||||
<collateSequence valuetype="bool">False</collateSequence>
|
||||
<additionalNodesData valuetype="list" />
|
||||
<collateShotNames valuetype="bool">True</collateShotNames>
|
||||
<includeEffects valuetype="bool">True</includeEffects>
|
||||
<writePaths valuetype="list">
|
||||
<SequenceItem valuetype="str">{shot}/editorial_raw.%04d.{fileext}</SequenceItem>
|
||||
</writePaths>
|
||||
<reformat valuetype="dict">
|
||||
<filter valuetype="str">Cubic</filter>
|
||||
<to_type valuetype="str">None</to_type>
|
||||
<scale valuetype="float">1.0</scale>
|
||||
<center valuetype="bool">True</center>
|
||||
<resize valuetype="str">width</resize>
|
||||
</reformat>
|
||||
<keepNukeScript valuetype="bool">False</keepNukeScript>
|
||||
<method valuetype="str">Blend</method>
|
||||
<includeAnnotations valuetype="bool">False</includeAnnotations>
|
||||
<enable valuetype="bool">True</enable>
|
||||
<showAnnotations valuetype="bool">True</showAnnotations>
|
||||
<mov valuetype="dict">
|
||||
<b_frames valuetype="int">0</b_frames>
|
||||
<bitrate_tolerance valuetype="int">40000000</bitrate_tolerance>
|
||||
<gop_size valuetype="int">12</gop_size>
|
||||
<quality_max valuetype="int">31</quality_max>
|
||||
<quality_min valuetype="int">2</quality_min>
|
||||
<codec valuetype="str">avc1	H.264</codec>
|
||||
<ycbcr_matrix_type valuetype="str">Auto</ycbcr_matrix_type>
|
||||
<encoder valuetype="str">mov32</encoder>
|
||||
<bitrate valuetype="int">20000</bitrate>
|
||||
</mov>
|
||||
<readAllLinesForExport valuetype="bool">False</readAllLinesForExport>
|
||||
<deleteAudio valuetype="bool">True</deleteAudio>
|
||||
<collateCustomStart valuetype="bool">True</collateCustomStart>
|
||||
<burninDataEnabled valuetype="bool">False</burninDataEnabled>
|
||||
<additionalNodesEnabled valuetype="bool">False</additionalNodesEnabled>
|
||||
<timelineWriteNode valuetype="str">{shot}/editorial_raw.%04d.{fileext}</timelineWriteNode>
|
||||
<burninData valuetype="dict">
|
||||
<burnIn_bottomRight valuetype="NoneType">None</burnIn_bottomRight>
|
||||
<burnIn_topLeft valuetype="NoneType">None</burnIn_topLeft>
|
||||
<burnIn_topMiddle valuetype="NoneType">None</burnIn_topMiddle>
|
||||
<burnIn_padding valuetype="NoneType">None</burnIn_padding>
|
||||
<burnIn_topRight valuetype="NoneType">None</burnIn_topRight>
|
||||
<burnIn_bottomMiddle valuetype="NoneType">None</burnIn_bottomMiddle>
|
||||
<burnIn_bottomLeft valuetype="NoneType">None</burnIn_bottomLeft>
|
||||
<burnIn_textSize valuetype="NoneType">None</burnIn_textSize>
|
||||
<burnIn_font valuetype="NoneType">None</burnIn_font>
|
||||
</burninData>
|
||||
<dpx valuetype="dict">
|
||||
<datatype valuetype="str">8 bit</datatype>
|
||||
<transfer valuetype="str">(auto detect)</transfer>
|
||||
<bigEndian valuetype="bool">True</bigEndian>
|
||||
<fill valuetype="bool">False</fill>
|
||||
</dpx>
|
||||
<writeNodeName valuetype="str">Write_{ext}</writeNodeName>
|
||||
<collateTracks valuetype="bool">False</collateTracks>
|
||||
</root>
|
||||
</SequenceItem>
|
||||
</SequenceItem>
|
||||
</exportTemplate>
|
||||
<excludeTags valuetype="list" />
|
||||
<includeTags valuetype="list" />
|
||||
<includeRetimes valuetype="bool">False</includeRetimes>
|
||||
<startFrameSource valuetype="str">Custom</startFrameSource>
|
||||
<cutLength valuetype="bool">True</cutLength>
|
||||
<cutHandles valuetype="int">10</cutHandles>
|
||||
</root>
|
||||
472
setup/nukestudio/hiero_plugin_path/Templates/SharedTags.hrox
Normal file
472
setup/nukestudio/hiero_plugin_path/Templates/SharedTags.hrox
Normal file
|
|
@ -0,0 +1,472 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE hieroXML>
|
||||
<hieroXML release="11.2v2" revision="4" name="NukeStudio" version="11">
|
||||
<Project useViewColors="0" customExportRootPath="" eightBitLut="sRGB" nukeUseOCIO="0" shotPresetName="Basic Nuke Shot With Annotations" timedisplayformat="0" exportRootPathMode="ProjectDirectory" redVideoDecodeMode="0" samplerate="48000/1" name="SharedTags" timelineReformatResizeType="Width" viewerLut="sRGB" framerate="24/1" logLut="Cineon" ocioConfigName="custom" timelineReformatCenter="1" floatLut="linear" posterCustomFrame="0" sixteenBitLut="sRGB" workingSpace="linear" editable="1" thumbnailLut="sRGB" timelineReformatType="To Format" ocioConfigCustom="0" starttimecode="86400" buildTrackName="VFX" guid="{ba9eb7de-26cc-7146-a12b-b59ab35d5469}" ocioconfigpath="" posterFrameSetting="First" project_directory="">
|
||||
<items>
|
||||
<RootBinProjectItem editable="1" guid="{a3ba3ce5-8d61-6240-a7b3-b99e4a8bbe9d}" name="Sequences">
|
||||
<BinViewType>2</BinViewType>
|
||||
<BinViewZoom>70</BinViewZoom>
|
||||
<BinViewSortColumnIndex>0</BinViewSortColumnIndex>
|
||||
<BinViewSortOrder>0</BinViewSortOrder>
|
||||
<AllowedItems>13</AllowedItems>
|
||||
</RootBinProjectItem>
|
||||
<RootBinProjectItem editable="1" guid="{74723746-d7a9-884f-9489-bf0cfe2137fa}" name="Tags">
|
||||
<items>
|
||||
<TagClassProjectItem editable="1" guid="{30184e7c-e9d0-ba40-8627-f3600b3c7d0b}" name="audio">
|
||||
<TagClass icon="Y:/bait-conda-git-deployment/repositories/bait-environment/pyblish-bumpybox/pyblish_bumpybox/environment_variables/hiero_plugin_path/StartupProjects/Hiero/volume.png" objName="tag" editable="1" guid="1978e3db-6ad6-af41-87e6-88a8c1a2ef6c" visible="1" name="audio">
|
||||
<sets>
|
||||
<Set title="" domainroot="tag">
|
||||
<values>
|
||||
<StringValue name="tag.label" default="1" value="audio"/>
|
||||
</values>
|
||||
</Set>
|
||||
</sets>
|
||||
</TagClass>
|
||||
</TagClassProjectItem>
|
||||
<BinProjectItem editable="1" guid="{c26fe4a3-ea9b-e446-92c3-4f2d1e5c5c47}" name="task">
|
||||
<items>
|
||||
<TagClassProjectItem editable="1" guid="{b5885be5-d09b-244b-b4a4-dec427a534a7}" name="Editing">
|
||||
<TagClass icon="icons:Tag.png" objName="tag" editable="1" guid="4b652e43-47ef-6c4e-871d-18cdc3299f0f" visible="1" name="Editing">
|
||||
<sets>
|
||||
<Set title="" domainroot="tag">
|
||||
<values>
|
||||
<StringValue name="tag.label" default="1" value="Editing"/>
|
||||
<StringValue name="tag.family" default="1" value="task"/>
|
||||
</values>
|
||||
</Set>
|
||||
</sets>
|
||||
</TagClass>
|
||||
</TagClassProjectItem>
|
||||
<TagClassProjectItem editable="1" guid="{2d8da183-0d62-344e-810f-133153ea9a42}" name="Blocking">
|
||||
<TagClass icon="icons:Tag.png" objName="tag" editable="1" guid="9d6fb373-166a-684c-a5d2-d9d6bd6ca0ca" visible="1" name="Blocking">
|
||||
<sets>
|
||||
<Set title="" domainroot="tag">
|
||||
<values>
|
||||
<StringValue name="tag.label" default="1" value="Blocking"/>
|
||||
<StringValue name="tag.family" default="1" value="task"/>
|
||||
</values>
|
||||
</Set>
|
||||
</sets>
|
||||
</TagClass>
|
||||
</TagClassProjectItem>
|
||||
<TagClassProjectItem editable="1" guid="{eb2f8f5f-4e80-2046-9aa5-cf49e3f44dc2}" name="Tracking">
|
||||
<TagClass icon="icons:Tag.png" objName="tag" editable="1" guid="aa977a57-886d-ed4b-b91a-aeb1e25ddabc" visible="1" name="Tracking">
|
||||
<sets>
|
||||
<Set title="" domainroot="tag">
|
||||
<values>
|
||||
<StringValue name="tag.label" default="1" value="Tracking"/>
|
||||
<StringValue name="tag.family" default="1" value="task"/>
|
||||
</values>
|
||||
</Set>
|
||||
</sets>
|
||||
</TagClass>
|
||||
</TagClassProjectItem>
|
||||
<TagClassProjectItem editable="1" guid="{bd84a0a6-454a-e346-8cfc-b32719aac901}" name="Animation">
|
||||
<TagClass icon="icons:Tag.png" objName="tag" editable="1" guid="4128ded5-cf1d-7249-9d2c-ddbe52d2e4e7" visible="1" name="Animation">
|
||||
<sets>
|
||||
<Set title="" domainroot="tag">
|
||||
<values>
|
||||
<StringValue name="tag.label" default="1" value="Animation"/>
|
||||
<StringValue name="tag.family" default="1" value="task"/>
|
||||
</values>
|
||||
</Set>
|
||||
</sets>
|
||||
</TagClass>
|
||||
</TagClassProjectItem>
|
||||
<TagClassProjectItem editable="1" guid="{b3236005-6f9b-ef4a-bb5d-0df15637e9a5}" name="Texturing">
|
||||
<TagClass icon="icons:Tag.png" objName="tag" editable="1" guid="3a00a983-f8a1-d44e-8446-48fd5ba2e7c4" visible="1" name="Texturing">
|
||||
<sets>
|
||||
<Set title="" domainroot="tag">
|
||||
<values>
|
||||
<StringValue name="tag.label" default="1" value="Texturing"/>
|
||||
<StringValue name="tag.family" default="1" value="task"/>
|
||||
</values>
|
||||
</Set>
|
||||
</sets>
|
||||
</TagClass>
|
||||
</TagClassProjectItem>
|
||||
<TagClassProjectItem editable="1" guid="{459777c3-87e1-b648-9f9e-48c63318e458}" name="Layout">
|
||||
<TagClass icon="icons:Tag.png" objName="tag" editable="1" guid="4289d2cd-5328-6b40-a897-f3ede4e816c7" visible="1" name="Layout">
|
||||
<sets>
|
||||
<Set title="" domainroot="tag">
|
||||
<values>
|
||||
<StringValue name="tag.label" default="1" value="Layout"/>
|
||||
<StringValue name="tag.family" default="1" value="task"/>
|
||||
</values>
|
||||
</Set>
|
||||
</sets>
|
||||
</TagClass>
|
||||
</TagClassProjectItem>
|
||||
<TagClassProjectItem editable="1" guid="{9151f675-6785-d74f-9fcb-5ba1d3975213}" name="Build">
|
||||
<TagClass icon="icons:Tag.png" objName="tag" editable="1" guid="ad57f46d-7f01-8b46-a42d-e4f166d6c5a2" visible="1" name="Build">
|
||||
<sets>
|
||||
<Set title="" domainroot="tag">
|
||||
<values>
|
||||
<StringValue name="tag.label" default="1" value="Build"/>
|
||||
<StringValue name="tag.family" default="1" value="task"/>
|
||||
</values>
|
||||
</Set>
|
||||
</sets>
|
||||
</TagClass>
|
||||
</TagClassProjectItem>
|
||||
<TagClassProjectItem editable="1" guid="{3f6a2f81-38ed-3a40-8dc1-8795d046ceb0}" name="Painting">
|
||||
<TagClass icon="icons:Tag.png" objName="tag" editable="1" guid="92531993-b3ef-f049-94da-0290fcb43877" visible="1" name="Painting">
|
||||
<sets>
|
||||
<Set title="" domainroot="tag">
|
||||
<values>
|
||||
<StringValue name="tag.label" default="1" value="Painting"/>
|
||||
<StringValue name="tag.family" default="1" value="task"/>
|
||||
</values>
|
||||
</Set>
|
||||
</sets>
|
||||
</TagClass>
|
||||
</TagClassProjectItem>
|
||||
<TagClassProjectItem editable="1" guid="{32c40ffe-b951-db45-a06f-96aa1ee886c9}" name="Rigging">
|
||||
<TagClass icon="icons:Tag.png" objName="tag" editable="1" guid="ef53b66c-6389-f341-a548-98ab0f5dfd36" visible="1" name="Rigging">
|
||||
<sets>
|
||||
<Set title="" domainroot="tag">
|
||||
<values>
|
||||
<StringValue name="tag.label" default="1" value="Rigging"/>
|
||||
<StringValue name="tag.family" default="1" value="task"/>
|
||||
</values>
|
||||
</Set>
|
||||
</sets>
|
||||
</TagClass>
|
||||
</TagClassProjectItem>
|
||||
<TagClassProjectItem editable="1" guid="{458175b6-56c8-6241-a3c5-3bb15c7b4321}" name="Match Move">
|
||||
<TagClass icon="icons:Tag.png" objName="tag" editable="1" guid="2da532f7-6102-e648-9cc9-f897adde24fa" visible="1" name="Match Move">
|
||||
<sets>
|
||||
<Set title="" domainroot="tag">
|
||||
<values>
|
||||
<StringValue name="tag.label" default="1" value="Match Move"/>
|
||||
<StringValue name="tag.family" default="1" value="task"/>
|
||||
</values>
|
||||
</Set>
|
||||
</sets>
|
||||
</TagClass>
|
||||
</TagClassProjectItem>
|
||||
<TagClassProjectItem editable="1" guid="{4f432ee8-08b9-1541-b724-16da8d326575}" name="Compositing">
|
||||
<TagClass icon="icons:Tag.png" objName="tag" editable="1" guid="6dfee79f-109e-6943-86ee-e4868eebf885" visible="1" name="Compositing">
|
||||
<sets>
|
||||
<Set title="" domainroot="tag">
|
||||
<values>
|
||||
<StringValue name="tag.label" default="1" value="Compositing"/>
|
||||
<StringValue name="tag.family" default="1" value="task"/>
|
||||
</values>
|
||||
</Set>
|
||||
</sets>
|
||||
</TagClass>
|
||||
</TagClassProjectItem>
|
||||
<TagClassProjectItem editable="1" guid="{3bc6599b-7095-f744-8f96-74a2fbbaba51}" name="Lighting">
|
||||
<TagClass icon="icons:Tag.png" objName="tag" editable="1" guid="2e295a1d-0730-4544-bc28-471e217295cf" visible="1" name="Lighting">
|
||||
<sets>
|
||||
<Set title="" domainroot="tag">
|
||||
<values>
|
||||
<StringValue name="tag.label" default="1" value="Lighting"/>
|
||||
<StringValue name="tag.family" default="1" value="task"/>
|
||||
</values>
|
||||
</Set>
|
||||
</sets>
|
||||
</TagClass>
|
||||
</TagClassProjectItem>
|
||||
<TagClassProjectItem editable="1" guid="{59cc88aa-30fb-d94b-9c6b-860585be1c5a}" name="Lookdev">
|
||||
<TagClass icon="icons:Tag.png" objName="tag" editable="1" guid="728df9b6-afc7-8d4f-b85e-0d944d484c73" visible="1" name="Lookdev">
|
||||
<sets>
|
||||
<Set title="" domainroot="tag">
|
||||
<values>
|
||||
<StringValue name="tag.label" default="1" value="Lookdev"/>
|
||||
<StringValue name="tag.family" default="1" value="task"/>
|
||||
</values>
|
||||
</Set>
|
||||
</sets>
|
||||
</TagClass>
|
||||
</TagClassProjectItem>
|
||||
<TagClassProjectItem editable="1" guid="{f3a97406-c1af-fe4a-a607-da3872d15f1b}" name="Modeling">
|
||||
<TagClass icon="icons:Tag.png" objName="tag" editable="1" guid="8440a540-145b-6b47-b935-18dcc0ca5766" visible="1" name="Modeling">
|
||||
<sets>
|
||||
<Set title="" domainroot="tag">
|
||||
<values>
|
||||
<StringValue name="tag.label" default="1" value="Modeling"/>
|
||||
<StringValue name="tag.family" default="1" value="task"/>
|
||||
</values>
|
||||
</Set>
|
||||
</sets>
|
||||
</TagClass>
|
||||
</TagClassProjectItem>
|
||||
</items>
|
||||
<BinViewType>2</BinViewType>
|
||||
<BinViewZoom>70</BinViewZoom>
|
||||
<BinViewSortColumnIndex>0</BinViewSortColumnIndex>
|
||||
<BinViewSortOrder>0</BinViewSortOrder>
|
||||
</BinProjectItem>
|
||||
<BinProjectItem editable="1" guid="{d9f52354-348b-b940-b9d8-fb902c94a9be}" name="handles">
|
||||
<items>
|
||||
<TagClassProjectItem editable="1" guid="{ecd238d1-6d38-a740-ae24-b2ba84d1360c}" name="25 frames">
|
||||
<TagClass icon="icons:TagClapperBoard.png" objName="tag" editable="1" guid="172ca2e6-9351-f346-968c-cbcd72adcd43" visible="1" name="25 frames">
|
||||
<sets>
|
||||
<Set title="" domainroot="tag">
|
||||
<values>
|
||||
<StringValue name="tag.label" default="1" value="25 frames"/>
|
||||
<StringValue name="tag.family" default="1" value="handles"/>
|
||||
<StringValue name="tag.value" default="1" value="25"/>
|
||||
</values>
|
||||
</Set>
|
||||
</sets>
|
||||
</TagClass>
|
||||
</TagClassProjectItem>
|
||||
<TagClassProjectItem editable="1" guid="{3c1da268-6255-5e43-8564-14f5c4483c7d}" name="20 frames">
|
||||
<TagClass icon="icons:TagClapperBoard.png" objName="tag" editable="1" guid="e2918655-a7f2-fc42-9cb7-f402c95ace69" visible="1" name="20 frames">
|
||||
<sets>
|
||||
<Set title="" domainroot="tag">
|
||||
<values>
|
||||
<StringValue name="tag.label" default="1" value="20 frames"/>
|
||||
<StringValue name="tag.family" default="1" value="handles"/>
|
||||
<StringValue name="tag.value" default="1" value="20"/>
|
||||
</values>
|
||||
</Set>
|
||||
</sets>
|
||||
</TagClass>
|
||||
</TagClassProjectItem>
|
||||
<TagClassProjectItem editable="1" guid="{5b502d49-cd75-3741-aa78-4f178049e9cd}" name="15 frames">
|
||||
<TagClass icon="icons:TagClapperBoard.png" objName="tag" editable="1" guid="36e70c47-29a8-9340-912c-1a1f70257618" visible="1" name="15 frames">
|
||||
<sets>
|
||||
<Set title="" domainroot="tag">
|
||||
<values>
|
||||
<StringValue name="tag.label" default="1" value="15 frames"/>
|
||||
<StringValue name="tag.family" default="1" value="handles"/>
|
||||
<StringValue name="tag.value" default="1" value="15"/>
|
||||
</values>
|
||||
</Set>
|
||||
</sets>
|
||||
</TagClass>
|
||||
</TagClassProjectItem>
|
||||
<TagClassProjectItem editable="1" guid="{eae75001-3b2f-1443-9687-dfbb6a381c55}" name="10 frames">
|
||||
<TagClass icon="icons:TagClapperBoard.png" objName="tag" editable="1" guid="a45bd02c-5f1e-c040-ad8c-0c6e124f55c0" visible="1" name="10 frames">
|
||||
<sets>
|
||||
<Set title="" domainroot="tag">
|
||||
<values>
|
||||
<StringValue name="tag.label" default="1" value="10 frames"/>
|
||||
<StringValue name="tag.family" default="1" value="handles"/>
|
||||
<StringValue name="tag.value" default="1" value="10"/>
|
||||
</values>
|
||||
</Set>
|
||||
</sets>
|
||||
</TagClass>
|
||||
</TagClassProjectItem>
|
||||
<TagClassProjectItem editable="1" guid="{39e0a5dc-9f30-8d45-b497-59af4a4e8f7d}" name="start: add 20 frames">
|
||||
<TagClass icon="icons:TagClapperBoard.png" objName="tag" editable="1" guid="8bfb4e13-42e7-4344-8b33-1df55ed5cc19" visible="1" name="start: add 20 frames">
|
||||
<sets>
|
||||
<Set title="" domainroot="tag">
|
||||
<values>
|
||||
<StringValue name="tag.label" default="1" value="start: add 20 frames"/>
|
||||
<StringValue name="tag.note" default="1" value="adding frames to start of selected clip"/>
|
||||
<StringValue name="tag.family" default="1" value="handles"/>
|
||||
<StringValue name="tag.value" default="1" value="20"/>
|
||||
<StringValue name="tag.args" default="1" value="{'op':'add','where':'start'}"/>
|
||||
</values>
|
||||
</Set>
|
||||
</sets>
|
||||
</TagClass>
|
||||
</TagClassProjectItem>
|
||||
<TagClassProjectItem editable="1" guid="{02801652-2e46-f04e-a9bb-0be47591d731}" name="start: add 15 frames">
|
||||
<TagClass icon="icons:TagClapperBoard.png" objName="tag" editable="1" guid="9d2f5078-5ded-ab47-9be7-8f30b9362b0c" visible="1" name="start: add 15 frames">
|
||||
<sets>
|
||||
<Set title="" domainroot="tag">
|
||||
<values>
|
||||
<StringValue name="tag.label" default="1" value="start: add 15 frames"/>
|
||||
<StringValue name="tag.note" default="1" value="adding frames to start of selected clip"/>
|
||||
<StringValue name="tag.family" default="1" value="handles"/>
|
||||
<StringValue name="tag.value" default="1" value="15"/>
|
||||
<StringValue name="tag.args" default="1" value="{'op':'add','where':'start'}"/>
|
||||
</values>
|
||||
</Set>
|
||||
</sets>
|
||||
</TagClass>
|
||||
</TagClassProjectItem>
|
||||
<TagClassProjectItem editable="1" guid="{b7415b6d-3b9c-924b-a6b9-0b4b20917c28}" name="start: add 10 frames">
|
||||
<TagClass icon="icons:TagClapperBoard.png" objName="tag" editable="1" guid="be4ae97e-d647-9145-bfd2-521a0ca48f80" visible="1" name="start: add 10 frames">
|
||||
<sets>
|
||||
<Set title="" domainroot="tag">
|
||||
<values>
|
||||
<StringValue name="tag.label" default="1" value="start: add 10 frames"/>
|
||||
<StringValue name="tag.note" default="1" value="adding frames to start of selected clip"/>
|
||||
<StringValue name="tag.family" default="1" value="handles"/>
|
||||
<StringValue name="tag.value" default="1" value="10"/>
|
||||
<StringValue name="tag.args" default="1" value="{'op':'add','where':'start'}"/>
|
||||
</values>
|
||||
</Set>
|
||||
</sets>
|
||||
</TagClass>
|
||||
</TagClassProjectItem>
|
||||
<TagClassProjectItem editable="1" guid="{4c146343-8f3d-6c43-9d49-6fc2fc8d860c}" name="end: add 15 frames">
|
||||
<TagClass icon="icons:TagClapperBoard.png" objName="tag" editable="1" guid="b07bda39-5339-b548-ae2f-54d791110cbe" visible="1" name="end: add 15 frames">
|
||||
<sets>
|
||||
<Set title="" domainroot="tag">
|
||||
<values>
|
||||
<StringValue name="tag.label" default="1" value="end: add 15 frames"/>
|
||||
<StringValue name="tag.note" default="1" value="adding frames to end of selected clip"/>
|
||||
<StringValue name="tag.family" default="1" value="handles"/>
|
||||
<StringValue name="tag.value" default="1" value="15"/>
|
||||
<StringValue name="tag.args" default="1" value="{'op':'add','where':'end'}"/>
|
||||
</values>
|
||||
</Set>
|
||||
</sets>
|
||||
</TagClass>
|
||||
</TagClassProjectItem>
|
||||
<TagClassProjectItem editable="1" guid="{1218eaee-7065-fa42-857d-3337a25d6a97}" name="end: add 10 frames">
|
||||
<TagClass icon="icons:TagClapperBoard.png" objName="tag" editable="1" guid="9c2bed9c-064a-3243-ad0f-8b7ac147a427" visible="1" name="end: add 10 frames">
|
||||
<sets>
|
||||
<Set title="" domainroot="tag">
|
||||
<values>
|
||||
<StringValue name="tag.label" default="1" value="end: add 10 frames"/>
|
||||
<StringValue name="tag.note" default="1" value="adding frames to end of selected clip"/>
|
||||
<StringValue name="tag.family" default="1" value="handles"/>
|
||||
<StringValue name="tag.value" default="1" value="10"/>
|
||||
<StringValue name="tag.args" default="1" value="{'op':'add','where':'end'}"/>
|
||||
</values>
|
||||
</Set>
|
||||
</sets>
|
||||
</TagClass>
|
||||
</TagClassProjectItem>
|
||||
<TagClassProjectItem editable="1" guid="{b9b0373d-1461-094c-a1e1-26408b68378f}" name="end: add 5 frames">
|
||||
<TagClass icon="icons:TagClapperBoard.png" objName="tag" editable="1" guid="2466e187-aa71-804e-9ffb-1c1c57d5673c" visible="1" name="end: add 5 frames">
|
||||
<sets>
|
||||
<Set title="" domainroot="tag">
|
||||
<values>
|
||||
<StringValue name="tag.label" default="1" value="end: add 5 frames"/>
|
||||
<StringValue name="tag.note" default="1" value="adding frames to end of selected clip"/>
|
||||
<StringValue name="tag.family" default="1" value="handles"/>
|
||||
<StringValue name="tag.value" default="1" value="5"/>
|
||||
<StringValue name="tag.args" default="1" value="{'op':'add','where':'end'}"/>
|
||||
</values>
|
||||
</Set>
|
||||
</sets>
|
||||
</TagClass>
|
||||
</TagClassProjectItem>
|
||||
<TagClassProjectItem editable="1" guid="{81446970-d7b7-a142-970e-d7d48073b74a}" name="end: add 20 frames">
|
||||
<TagClass icon="icons:TagClapperBoard.png" objName="tag" editable="1" guid="8f2d87d5-47c3-154e-8236-4f0fe3e24216" visible="1" name="end: add 20 frames">
|
||||
<sets>
|
||||
<Set title="" domainroot="tag">
|
||||
<values>
|
||||
<StringValue name="tag.label" default="1" value="end: add 20 frames"/>
|
||||
<StringValue name="tag.note" default="1" value="adding frames to end of selected clip"/>
|
||||
<StringValue name="tag.family" default="1" value="handles"/>
|
||||
<StringValue name="tag.value" default="1" value="20"/>
|
||||
<StringValue name="tag.args" default="1" value="{'op':'add','where':'end'}"/>
|
||||
</values>
|
||||
</Set>
|
||||
</sets>
|
||||
</TagClass>
|
||||
</TagClassProjectItem>
|
||||
<TagClassProjectItem editable="1" guid="{383f69df-2421-d848-a4a6-8ed17386cb67}" name="start: add 5 frames">
|
||||
<TagClass icon="icons:TagClapperBoard.png" objName="tag" editable="1" guid="aec1eac2-1257-e64d-9fed-78afbee0dd31" visible="1" name="start: add 5 frames">
|
||||
<sets>
|
||||
<Set title="" domainroot="tag">
|
||||
<values>
|
||||
<StringValue name="tag.label" default="1" value="start: add 5 frames"/>
|
||||
<StringValue name="tag.note" default="1" value="adding frames to start of selected clip"/>
|
||||
<StringValue name="tag.family" default="1" value="handles"/>
|
||||
<StringValue name="tag.value" default="1" value="5"/>
|
||||
<StringValue name="tag.args" default="1" value="{'op':'add','where':'start'}"/>
|
||||
</values>
|
||||
</Set>
|
||||
</sets>
|
||||
</TagClass>
|
||||
</TagClassProjectItem>
|
||||
<TagClassProjectItem editable="1" guid="{36e7472b-2ea6-8747-8181-f3bf46cfe04a}" name="5 frames">
|
||||
<TagClass icon="icons:TagClapperBoard.png" objName="tag" editable="1" guid="46e4f25b-b92f-414f-9c50-798f1905879f" visible="1" name="5 frames">
|
||||
<sets>
|
||||
<Set title="" domainroot="tag">
|
||||
<values>
|
||||
<StringValue name="tag.label" default="1" value="5 frames"/>
|
||||
<StringValue name="tag.family" default="1" value="handles"/>
|
||||
<StringValue name="tag.value" default="1" value="5"/>
|
||||
</values>
|
||||
</Set>
|
||||
</sets>
|
||||
</TagClass>
|
||||
</TagClassProjectItem>
|
||||
</items>
|
||||
<BinViewType>1</BinViewType>
|
||||
<BinViewZoom>70</BinViewZoom>
|
||||
<BinViewSortColumnIndex>0</BinViewSortColumnIndex>
|
||||
<BinViewSortOrder>0</BinViewSortOrder>
|
||||
</BinProjectItem>
|
||||
<BinProjectItem editable="1" guid="{2896f84a-7b81-0a45-af56-01df16a42c80}" name="software">
|
||||
<items>
|
||||
<TagClassProjectItem editable="1" guid="{e56c84f8-4958-9d4b-b712-c752daa20d87}" name="houdini">
|
||||
<TagClass icon="houdini.png" objName="tag" editable="1" guid="bfcba753-4337-6549-a6bb-db96e67261f0" visible="1" name="houdini">
|
||||
<sets>
|
||||
<Set title="" domainroot="tag">
|
||||
<values>
|
||||
<StringValue name="tag.label" default="1" value="houdini"/>
|
||||
<StringValue name="tag.family" default="1" value="host"/>
|
||||
</values>
|
||||
</Set>
|
||||
</sets>
|
||||
</TagClass>
|
||||
</TagClassProjectItem>
|
||||
<TagClassProjectItem editable="1" guid="{bcebc8dc-2725-2045-bfe3-678cd0cc14b2}" name="fusion">
|
||||
<TagClass icon="fusion.png" objName="tag" editable="1" guid="516c1100-11be-844d-842a-9e2f00a807d5" visible="1" name="fusion">
|
||||
<sets>
|
||||
<Set title="" domainroot="tag">
|
||||
<values>
|
||||
<StringValue name="tag.label" default="1" value="fusion"/>
|
||||
<StringValue name="tag.family" default="1" value="host"/>
|
||||
</values>
|
||||
</Set>
|
||||
</sets>
|
||||
</TagClass>
|
||||
</TagClassProjectItem>
|
||||
<TagClassProjectItem editable="1" guid="{1e8d1e85-7844-8c43-a0b1-be4fa10da068}" name="maya">
|
||||
<TagClass icon="maya.png" objName="tag" editable="1" guid="4bfb4372-c31d-6e4e-aedb-ff1611a487f8" visible="1" name="maya">
|
||||
<sets>
|
||||
<Set title="" domainroot="tag">
|
||||
<values>
|
||||
<StringValue name="tag.label" default="1" value="maya"/>
|
||||
<StringValue name="tag.family" default="1" value="host"/>
|
||||
</values>
|
||||
</Set>
|
||||
</sets>
|
||||
</TagClass>
|
||||
</TagClassProjectItem>
|
||||
<TagClassProjectItem editable="1" guid="{569a163e-3e00-3242-8be9-41aa4a9cbf5c}" name="nuke">
|
||||
<TagClass icon="nuke.png" objName="tag" editable="1" guid="1674dcb2-28b2-fe4d-8cc5-d979c3fa0ddd" visible="1" name="nuke">
|
||||
<sets>
|
||||
<Set title="" domainroot="tag">
|
||||
<values>
|
||||
<StringValue name="tag.label" default="1" value="nuke"/>
|
||||
<StringValue name="tag.family" default="1" value="host"/>
|
||||
</values>
|
||||
</Set>
|
||||
</sets>
|
||||
</TagClass>
|
||||
</TagClassProjectItem>
|
||||
</items>
|
||||
<BinViewType>2</BinViewType>
|
||||
<BinViewZoom>70</BinViewZoom>
|
||||
<BinViewSortColumnIndex>0</BinViewSortColumnIndex>
|
||||
<BinViewSortOrder>0</BinViewSortOrder>
|
||||
</BinProjectItem>
|
||||
</items>
|
||||
<BinViewType>2</BinViewType>
|
||||
<BinViewZoom>70</BinViewZoom>
|
||||
<BinViewSortColumnIndex>0</BinViewSortColumnIndex>
|
||||
<BinViewSortOrder>0</BinViewSortOrder>
|
||||
<AllowedItems>17</AllowedItems>
|
||||
</RootBinProjectItem>
|
||||
</items>
|
||||
<BinViewType>2</BinViewType>
|
||||
<BinViewZoom>70</BinViewZoom>
|
||||
<BinViewSortColumnIndex>0</BinViewSortColumnIndex>
|
||||
<BinViewSortOrder>0</BinViewSortOrder>
|
||||
<AllowedItems>2</AllowedItems>
|
||||
<RootBinProjectItem link="internal" objName="sequencesBin" guid="{a3ba3ce5-8d61-6240-a7b3-b99e4a8bbe9d}"/>
|
||||
<RootBinProjectItem link="internal" objName="tagsBin" guid="{74723746-d7a9-884f-9489-bf0cfe2137fa}"/>
|
||||
<MediaFormatValue objName="outputformat" name="" default="0" value="1,[ 0, 0, 1920, 1080],[ 0, 0, 1920, 1080],HD 1080"/>
|
||||
<Views>
|
||||
<View color="#ffffff" name="main"/>
|
||||
</Views>
|
||||
</Project>
|
||||
<UIState/>
|
||||
</hieroXML>
|
||||
BIN
setup/nukestudio/hiero_plugin_path/Templates/fusion.png
Normal file
BIN
setup/nukestudio/hiero_plugin_path/Templates/fusion.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 190 KiB |
BIN
setup/nukestudio/hiero_plugin_path/Templates/houdini.png
Normal file
BIN
setup/nukestudio/hiero_plugin_path/Templates/houdini.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
BIN
setup/nukestudio/hiero_plugin_path/Templates/maya.png
Normal file
BIN
setup/nukestudio/hiero_plugin_path/Templates/maya.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
BIN
setup/nukestudio/hiero_plugin_path/Templates/nuke.png
Normal file
BIN
setup/nukestudio/hiero_plugin_path/Templates/nuke.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
BIN
setup/nukestudio/hiero_plugin_path/Templates/volume.png
Normal file
BIN
setup/nukestudio/hiero_plugin_path/Templates/volume.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
Loading…
Add table
Add a link
Reference in a new issue