feat(fusion): adding scripts (with bug which needs to be fixed)

This commit is contained in:
Jakub Jezek 2020-08-19 18:44:26 +02:00
parent 2701e152de
commit 152a00f945
No known key found for this signature in database
GPG key ID: C4B96E101D2A47F3
15 changed files with 547 additions and 74 deletions

View file

@ -0,0 +1,47 @@
import sys
import os
from .pipeline import (
install,
uninstall,
publish,
launch_workfiles_app
)
from .utils import (
setup
)
from .lib import (
get_additional_data,
update_frame_range
)
from .menu import launch_pype_menu
host_dir = os.path.dirname(__file__)
script_dir = os.path.join(host_dir, "scripts")
sys.path.append(script_dir)
__all__ = [
# pipeline
"install",
"uninstall",
"publish",
"launch_workfiles_app",
# utils
"setup",
"get_resolve_module",
# lib
"get_additional_data",
"update_frame_range",
# menu
"launch_pype_menu",
# scripts
"set_rendermode"
]

View file

@ -15,6 +15,8 @@ from avalon.tools import (
libraryloader
)
import set_rendermode
def load_stylesheet():
path = os.path.join(os.path.dirname(__file__), "menu_style.qss")
@ -65,7 +67,7 @@ class PypeMenu(QtWidgets.QWidget):
load_btn = QtWidgets.QPushButton("Load", self)
inventory_btn = QtWidgets.QPushButton("Inventory", self)
libload_btn = QtWidgets.QPushButton("Library", self)
rename_btn = QtWidgets.QPushButton("Rename", self)
rendermode_btn = QtWidgets.QPushButton("Set render mode", self)
set_colorspace_btn = QtWidgets.QPushButton(
"Set colorspace from presets", self
)
@ -88,7 +90,7 @@ class PypeMenu(QtWidgets.QWidget):
layout.addWidget(Spacer(15, self))
layout.addWidget(rename_btn)
layout.addWidget(rendermode_btn)
layout.addWidget(Spacer(15, self))
@ -103,7 +105,7 @@ class PypeMenu(QtWidgets.QWidget):
load_btn.clicked.connect(self.on_load_clicked)
inventory_btn.clicked.connect(self.on_inventory_clicked)
libload_btn.clicked.connect(self.on_libload_clicked)
rename_btn.clicked.connect(self.on_rename_clicked)
rendermode_btn.clicked.connect(self.on_rendernode_clicked)
set_colorspace_btn.clicked.connect(self.on_set_colorspace_clicked)
reset_resolution_btn.clicked.connect(self.on_reset_resolution_clicked)
@ -131,8 +133,9 @@ class PypeMenu(QtWidgets.QWidget):
print("Clicked Library")
libraryloader.show()
def on_rename_clicked(self):
print("Clicked Rename")
def on_rendernode_clicked(self):
print("Clicked Set Render Mode")
set_rendermode.main()
def on_set_colorspace_clicked(self):
print("Clicked Set Colorspace")

View file

@ -0,0 +1,29 @@
QWidget {
background-color: #282828;
border-radius: 3;
}
QPushButton {
border: 1px solid #090909;
background-color: #201f1f;
color: #ffffff;
padding: 5;
}
QPushButton:focus {
background-color: "#171717";
color: #d0d0d0;
}
QPushButton:hover {
background-color: "#171717";
color: #e64b3d;
}
#PypeMenu {
border: 1px solid #fef9ef;
}
#Spacer {
background-color: #282828;
}

View file

@ -2,20 +2,17 @@
Basic avalon integration
"""
import os
# import sys
from avalon.tools import workfiles
from avalon import api as avalon
from pyblish import api as pyblish
from pypeapp import Logger
from pype import PLUGINS_DIR
log = Logger().get_logger(__name__, "fusion")
# self = sys.modules[__name__]
AVALON_CONFIG = os.environ["AVALON_CONFIG"]
PARENT_DIR = os.path.dirname(__file__)
PACKAGE_DIR = os.path.dirname(PARENT_DIR)
PLUGINS_DIR = os.path.join(PACKAGE_DIR, "plugins")
LOAD_PATH = os.path.join(PLUGINS_DIR, "fusion", "load")
CREATE_PATH = os.path.join(PLUGINS_DIR, "fusion", "create")
@ -25,9 +22,6 @@ PUBLISH_PATH = os.path.join(
PLUGINS_DIR, "fusion", "publish"
).replace("\\", "/")
AVALON_CONTAINERS = ":AVALON_CONTAINERS"
# IS_HEADLESS = not hasattr(cmds, "about") or cmds.about(batch=True)
def install():
"""Install fusion-specific functionality of avalon-core.
@ -52,7 +46,7 @@ def install():
pyblish.register_host("fusion")
pyblish.register_plugin_path(PUBLISH_PATH)
log.info("Registering DaVinci Resovle plug-ins..")
log.info("Registering Fusion plug-ins..")
avalon.register_plugin_path(avalon.Loader, LOAD_PATH)
avalon.register_plugin_path(avalon.Creator, CREATE_PATH)
@ -74,7 +68,7 @@ def uninstall():
"""
pyblish.deregister_host("fusion")
pyblish.deregister_plugin_path(PUBLISH_PATH)
log.info("Deregistering DaVinci Resovle plug-ins..")
log.info("Deregistering Fusion plug-ins..")
avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH)
avalon.deregister_plugin_path(avalon.Creator, CREATE_PATH)
@ -109,57 +103,6 @@ def on_pyblish_instance_toggled(instance, new_value, old_value):
tool.SetAttrs({"TOOLB_PassThrough": passthrough})
def containerise(obj,
name,
namespace,
context,
loader=None,
data=None):
"""Bundle Fusion's object into an assembly and imprint it with metadata
Containerisation enables a tracking of version, author and origin
for loaded assets.
Arguments:
obj (obj): Resolve's object to imprint as container
name (str): Name of resulting assembly
namespace (str): Namespace under which to host container
context (dict): Asset information
loader (str, optional): Name of node used to produce this container.
Returns:
obj (obj): containerised object
"""
pass
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`
"""
pass
def parse_container(container):
"""Return the container node's full container data.
Args:
container (str): A container node name.
Returns:
dict: The container schema data for this container node.
"""
pass
def launch_workfiles_app(*args):
workdir = os.environ["AVALON_WORKDIR"]
workfiles.show(workdir)

View file

@ -0,0 +1,12 @@
from avalon.fusion import comp_lock_and_undo_chunk
def main():
"""Set all selected backgrounds to 32 bit"""
with comp_lock_and_undo_chunk(comp, 'Selected Backgrounds to 32bit'):
tools = comp.GetToolList(True, "Background").values()
for tool in tools:
tool.Depth = 5
main()

View file

@ -0,0 +1,12 @@
from avalon.fusion import comp_lock_and_undo_chunk
def main():
"""Set all backgrounds to 32 bit"""
with comp_lock_and_undo_chunk(comp, 'Backgrounds to 32bit'):
tools = comp.GetToolList(False, "Background").values()
for tool in tools:
tool.Depth = 5
main()

View file

@ -0,0 +1,12 @@
from avalon.fusion import comp_lock_and_undo_chunk
def main():
"""Set all selected loaders to 32 bit"""
with comp_lock_and_undo_chunk(comp, 'Selected Loaders to 32bit'):
tools = comp.GetToolList(True, "Loader").values()
for tool in tools:
tool.Depth = 5
main()

View file

@ -0,0 +1,12 @@
from avalon.fusion import comp_lock_and_undo_chunk
def main():
"""Set all loaders to 32 bit"""
with comp_lock_and_undo_chunk(comp, 'Loaders to 32bit'):
tools = comp.GetToolList(False, "Loader").values()
for tool in tools:
tool.Depth = 5
main()

View file

@ -0,0 +1,43 @@
from avalon.fusion import comp_lock_and_undo_chunk
def is_connected(input):
"""Return whether an input has incoming connection"""
return input.GetAttrs()["INPB_Connected"]
def duplicate_with_input_connections():
"""Duplicate selected tools with incoming connections."""
original_tools = comp.GetToolList(True).values()
if not original_tools:
return # nothing selected
with comp_lock_and_undo_chunk(comp, "Duplicate With Input Connections"):
# Generate duplicates
comp.Copy()
comp.SetActiveTool()
comp.Paste()
duplicate_tools = comp.GetToolList(True).values()
# Copy connections
for original, new in zip(original_tools, duplicate_tools):
original_inputs = original.GetInputList().values()
new_inputs = new.GetInputList().values()
assert len(original_inputs) == len(new_inputs)
for original_input, new_input in zip(original_inputs, new_inputs):
if is_connected(original_input):
if is_connected(new_input):
# Already connected if it is between the copied tools
continue
new_input.ConnectTo(original_input.GetConnectedOutput())
assert is_connected(new_input), "Must be connected now"
duplicate_with_input_connections()

View file

@ -0,0 +1,125 @@
from avalon.vendor.Qt import QtCore, QtWidgets
from avalon.vendor import qtawesome
import avalon.fusion as avalon
from avalon import style
_help = {"renderlocal": "Render the comp on your own machine and publish "
"it from that the destination folder",
"deadline": "Submit a Fusion render job to Deadline to use all other "
"computers and add a publish job"}
class SetRenderMode(QtWidgets.QWidget):
def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent)
self._comp = avalon.get_current_comp()
self._comp_name = self._get_comp_name()
self.setWindowTitle("Set Render Mode")
self.setFixedSize(300, 175)
layout = QtWidgets.QVBoxLayout()
# region comp info
comp_info_layout = QtWidgets.QHBoxLayout()
update_btn = QtWidgets.QPushButton(qtawesome.icon("fa.refresh",
color="white"), "")
update_btn.setFixedWidth(25)
update_btn.setFixedHeight(25)
comp_information = QtWidgets.QLineEdit()
comp_information.setEnabled(False)
comp_info_layout.addWidget(comp_information)
comp_info_layout.addWidget(update_btn)
# endregion comp info
# region modes
mode_options = QtWidgets.QComboBox()
mode_options.addItems(_help.keys())
mode_information = QtWidgets.QTextEdit()
mode_information.setReadOnly(True)
# endregion modes
accept_btn = QtWidgets.QPushButton("Accept")
layout.addLayout(comp_info_layout)
layout.addWidget(mode_options)
layout.addWidget(mode_information)
layout.addWidget(accept_btn)
self.setLayout(layout)
self.comp_information = comp_information
self.update_btn = update_btn
self.mode_options = mode_options
self.mode_information = mode_information
self.accept_btn = accept_btn
self.connections()
self.update()
# Force updated render mode help text
self._update_rendermode_info()
def connections(self):
"""Build connections between code and buttons"""
self.update_btn.clicked.connect(self.update)
self.accept_btn.clicked.connect(self._set_comp_rendermode)
self.mode_options.currentIndexChanged.connect(
self._update_rendermode_info)
def update(self):
"""Update all information in the UI"""
self._comp = avalon.get_current_comp()
self._comp_name = self._get_comp_name()
self.comp_information.setText(self._comp_name)
# Update current comp settings
mode = self._get_comp_rendermode()
index = self.mode_options.findText(mode)
self.mode_options.setCurrentIndex(index)
def _update_rendermode_info(self):
rendermode = self.mode_options.currentText()
self.mode_information.setText(_help[rendermode])
def _get_comp_name(self):
return self._comp.GetAttrs("COMPS_Name")
def _get_comp_rendermode(self):
return self._comp.GetData("pype.rendermode") or "renderlocal"
def _set_comp_rendermode(self):
rendermode = self.mode_options.currentText()
self._comp.SetData("pype.rendermode", rendermode)
self._comp.Print("Updated render mode to '%s'\n" % rendermode)
def _validation(self):
ui_mode = self.mode_options.currentText()
comp_mode = self._get_comp_rendermode()
return comp_mode == ui_mode
def main():
import sys
app = QtWidgets.QApplication(sys.argv)
window = SetRenderMode()
window.setStyleSheet(style.load_stylesheet())
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

View file

@ -0,0 +1,201 @@
import os
import glob
import logging
import avalon.io as io
import avalon.api as api
import avalon.pipeline as pipeline
import avalon.fusion
import avalon.style as style
from avalon.vendor.Qt import QtWidgets, QtCore
from avalon.vendor import qtawesome as qta
log = logging.getLogger("Fusion Switch Shot")
class App(QtWidgets.QWidget):
def __init__(self, parent=None):
################################################
# |---------------------| |------------------| #
# |Comp | |Asset | #
# |[..][ v]| |[ v]| #
# |---------------------| |------------------| #
# | Update existing comp [ ] | #
# |------------------------------------------| #
# | Switch | #
# |------------------------------------------| #
################################################
QtWidgets.QWidget.__init__(self, parent)
layout = QtWidgets.QVBoxLayout()
# Comp related input
comp_hlayout = QtWidgets.QHBoxLayout()
comp_label = QtWidgets.QLabel("Comp file")
comp_label.setFixedWidth(50)
comp_box = QtWidgets.QComboBox()
button_icon = qta.icon("fa.folder", color="white")
open_from_dir = QtWidgets.QPushButton()
open_from_dir.setIcon(button_icon)
comp_box.setFixedHeight(25)
open_from_dir.setFixedWidth(25)
open_from_dir.setFixedHeight(25)
comp_hlayout.addWidget(comp_label)
comp_hlayout.addWidget(comp_box)
comp_hlayout.addWidget(open_from_dir)
# Asset related input
asset_hlayout = QtWidgets.QHBoxLayout()
asset_label = QtWidgets.QLabel("Shot")
asset_label.setFixedWidth(50)
asset_box = QtWidgets.QComboBox()
asset_box.setLineEdit(QtWidgets.QLineEdit())
asset_box.setFixedHeight(25)
refresh_icon = qta.icon("fa.refresh", color="white")
refresh_btn = QtWidgets.QPushButton()
refresh_btn.setIcon(refresh_icon)
asset_box.setFixedHeight(25)
refresh_btn.setFixedWidth(25)
refresh_btn.setFixedHeight(25)
asset_hlayout.addWidget(asset_label)
asset_hlayout.addWidget(asset_box)
asset_hlayout.addWidget(refresh_btn)
# Options
options = QtWidgets.QHBoxLayout()
options.setAlignment(QtCore.Qt.AlignLeft)
current_comp_check = QtWidgets.QCheckBox()
current_comp_check.setChecked(True)
current_comp_label = QtWidgets.QLabel("Use current comp")
options.addWidget(current_comp_label)
options.addWidget(current_comp_check)
accept_btn = QtWidgets.QPushButton("Switch")
layout.addLayout(options)
layout.addLayout(comp_hlayout)
layout.addLayout(asset_hlayout)
layout.addWidget(accept_btn)
self._open_from_dir = open_from_dir
self._comps = comp_box
self._assets = asset_box
self._use_current = current_comp_check
self._accept_btn = accept_btn
self._refresh_btn = refresh_btn
self.setWindowTitle("Fusion Switch Shot")
self.setLayout(layout)
self.resize(260, 140)
self.setMinimumWidth(260)
self.setFixedHeight(140)
self.connections()
# Update ui to correct state
self._on_use_current_comp()
self._refresh()
def connections(self):
self._use_current.clicked.connect(self._on_use_current_comp)
self._open_from_dir.clicked.connect(self._on_open_from_dir)
self._refresh_btn.clicked.connect(self._refresh)
self._accept_btn.clicked.connect(self._on_switch)
def _on_use_current_comp(self):
state = self._use_current.isChecked()
self._open_from_dir.setEnabled(not state)
self._comps.setEnabled(not state)
def _on_open_from_dir(self):
start_dir = self._get_context_directory()
comp_file, _ = QtWidgets.QFileDialog.getOpenFileName(
self, "Choose comp", start_dir)
if not comp_file:
return
# Create completer
self.populate_comp_box([comp_file])
self._refresh()
def _refresh(self):
# Clear any existing items
self._assets.clear()
asset_names = [a["name"] for a in self.collect_assets()]
completer = QtWidgets.QCompleter(asset_names)
self._assets.setCompleter(completer)
self._assets.addItems(asset_names)
def _on_switch(self):
if not self._use_current.isChecked():
file_name = self._comps.itemData(self._comps.currentIndex())
else:
comp = avalon.fusion.get_current_comp()
file_name = comp.GetAttrs("COMPS_FileName")
asset = self._assets.currentText()
import colorbleed.scripts.fusion_switch_shot as switch_shot
switch_shot.switch(asset_name=asset, filepath=file_name, new=True)
def _get_context_directory(self):
project = io.find_one({"type": "project",
"name": api.Session["AVALON_PROJECT"]},
projection={"config": True})
template = project["config"]["template"]["work"]
dir = pipeline._format_work_template(template, api.Session)
return dir
def collect_slap_comps(self, directory):
items = glob.glob("{}/*.comp".format(directory))
return items
def collect_assets(self):
return list(io.find({"type": "asset", "silo": "film"}))
def populate_comp_box(self, files):
"""Ensure we display the filename only but the path is stored as well
Args:
files (list): list of full file path [path/to/item/item.ext,]
Returns:
None
"""
for f in files:
filename = os.path.basename(f)
self._comps.addItem(filename, userData=f)
if __name__ == '__main__':
import sys
api.install(avalon.fusion)
app = QtWidgets.QApplication(sys.argv)
window = App()
window.setStyleSheet(style.load_stylesheet())
window.show()
sys.exit(app.exec_())

View file

@ -0,0 +1,32 @@
"""Forces Fusion to 'retrigger' the Loader to update.
Warning:
This might change settings like 'Reverse', 'Loop', trims and other
settings of the Loader. So use this at your own risk.
"""
from avalon.fusion import comp_lock_and_undo_chunk
with comp_lock_and_undo_chunk(comp, "Reload clip time ranges"):
tools = comp.GetToolList(True, "Loader").values()
for tool in tools:
# Get tool attributes
tool_a = tool.GetAttrs()
clipTable = tool_a['TOOLST_Clip_Name']
altclipTable = tool_a['TOOLST_AltClip_Name']
startTime = tool_a['TOOLNT_Clip_Start']
old_global_in = tool.GlobalIn[comp.CurrentTime]
# Reapply
for index, _ in clipTable.items():
time = startTime[index]
tool.Clip[time] = tool.Clip[time]
for index, _ in altclipTable.items():
time = startTime[index]
tool.ProxyFilename[time] = tool.ProxyFilename[time]
tool.GlobalIn[comp.CurrentTime] = old_global_in

View file

@ -1,6 +1,5 @@
import os
import sys
import avalon.api as avalon
import pype
from pypeapp import Logger
@ -9,16 +8,17 @@ log = Logger().get_logger(__name__)
def main(env):
from pype.hosts import fusion
from pype.hosts.fusion import menu
import avalon.fusion
# Registers pype's Global pyblish plugins
pype.install()
# activate resolve from pype
avalon.install(bmdvr)
avalon.api.install(avalon.fusion)
log.info(f"Avalon registred hosts: {avalon.registered_host()}")
log.info(f"Avalon registred hosts: {avalon.api.registered_host()}")
bmdvr.launch_pype_menu()
menu.launch_pype_menu()
if __name__ == "__main__":

View file

@ -58,7 +58,10 @@ def _sync_utility_scripts(env=None):
src = os.path.join(d, s)
dst = os.path.join(us_dir, s)
log.info(f"Copying `{src}` to `{dst}`...")
shutil.copy2(src, dst)
if not os.path.isdir(src):
shutil.copy2(src, dst)
else:
shutil.copytree(src, dst)
def setup(env=None):

View file

@ -23,7 +23,7 @@ class CreateTiffSaver(avalon.api.Creator):
workdir = os.path.normpath(os.environ["AVALON_WORKDIR"])
filename = "{}..tiff".format(self.name)
filepath = os.path.join(workdir, "render", "preview", filename)
filepath = os.path.join(workdir, "render", filename)
with fusion.comp_lock_and_undo_chunk(comp):
args = (-32768, -32768) # Magical position numbers
@ -43,4 +43,3 @@ class CreateTiffSaver(avalon.api.Creator):
# Set file format attributes
saver[file_format]["Depth"] = 1 # int8 | int16 | float32 | other
saver[file_format]["SaveAlpha"] = 0