mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-25 05:14:40 +01:00
Merge branch 'master' of https://github.com/Colorbleed/colorbleed-config
This commit is contained in:
commit
428c96f21e
13 changed files with 703 additions and 34 deletions
40
colorbleed/fusion/lib.py
Normal file
40
colorbleed/fusion/lib.py
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import sys
|
||||
|
||||
import avalon.fusion
|
||||
|
||||
|
||||
self = sys.modules[__name__]
|
||||
self._project = None
|
||||
|
||||
|
||||
def update_frame_range(start, end, comp=None, set_render_range=True):
|
||||
"""Set Fusion comp's start and end frame range
|
||||
|
||||
Args:
|
||||
start (float, int): start frame
|
||||
end (float, int): end frame
|
||||
comp (object, Optional): comp object from fusion
|
||||
set_render_range (bool, Optional): When True this will also set the
|
||||
composition's render start and end frame.
|
||||
|
||||
Returns:
|
||||
None
|
||||
|
||||
"""
|
||||
|
||||
if not comp:
|
||||
comp = avalon.fusion.get_current_comp()
|
||||
|
||||
attrs = {
|
||||
"COMPN_GlobalStart": start,
|
||||
"COMPN_GlobalEnd": end
|
||||
}
|
||||
|
||||
if set_render_range:
|
||||
attrs.update({
|
||||
"COMPN_RenderStart": start,
|
||||
"COMPN_RenderEnd": end
|
||||
})
|
||||
|
||||
with avalon.fusion.comp_lock_and_undo_chunk(comp):
|
||||
comp.SetAttrs(attrs)
|
||||
|
|
@ -144,3 +144,73 @@ def version_up(filepath):
|
|||
|
||||
log.info("New version %s" % new_label)
|
||||
return new_filename
|
||||
|
||||
|
||||
def switch_item(container,
|
||||
asset_name=None,
|
||||
subset_name=None,
|
||||
representation_name=None):
|
||||
"""Switch container asset, subset or representation of a container by name.
|
||||
|
||||
It'll always switch to the latest version - of course a different
|
||||
approach could be implemented.
|
||||
|
||||
Args:
|
||||
container (dict): data of the item to switch with
|
||||
asset_name (str): name of the asset
|
||||
subset_name (str): name of the subset
|
||||
representation_name (str): name of the representation
|
||||
|
||||
Returns:
|
||||
dict
|
||||
|
||||
"""
|
||||
|
||||
if all(not x for x in [asset_name, subset_name, representation_name]):
|
||||
raise ValueError("Must have at least one change provided to switch.")
|
||||
|
||||
# Collect any of current asset, subset and representation if not provided
|
||||
# so we can use the original name from those.
|
||||
if any(not x for x in [asset_name, subset_name, representation_name]):
|
||||
_id = io.ObjectId(container["representation"])
|
||||
representation = io.find_one({"type": "representation", "_id": _id})
|
||||
version, subset, asset, project = io.parenthood(representation)
|
||||
|
||||
if asset_name is None:
|
||||
asset_name = asset["name"]
|
||||
|
||||
if subset_name is None:
|
||||
subset_name = subset["name"]
|
||||
|
||||
if representation_name is None:
|
||||
representation_name = representation["name"]
|
||||
|
||||
# Find the new one
|
||||
asset = io.find_one({"name": asset_name, "type": "asset"})
|
||||
assert asset, ("Could not find asset in the database with the name "
|
||||
"'%s'" % asset_name)
|
||||
|
||||
subset = io.find_one({"name": subset_name,
|
||||
"type": "subset",
|
||||
"parent": asset["_id"]})
|
||||
assert subset, ("Could not find subset in the database with the name "
|
||||
"'%s'" % subset_name)
|
||||
|
||||
version = io.find_one({"type": "version",
|
||||
"parent": subset["_id"]},
|
||||
sort=[('name', -1)])
|
||||
|
||||
assert version, "Could not find a version for {}.{}".format(
|
||||
asset_name, subset_name
|
||||
)
|
||||
|
||||
representation = io.find_one({"name": representation_name,
|
||||
"type": "representation",
|
||||
"parent": version["_id"]})
|
||||
|
||||
assert representation, ("Could not find representation in the database with "
|
||||
"the name '%s'" % representation_name)
|
||||
|
||||
avalon.api.switch(container, representation)
|
||||
|
||||
return representation
|
||||
|
|
|
|||
|
|
@ -5,37 +5,6 @@
|
|||
from avalon import api
|
||||
|
||||
|
||||
def _set_frame_range(start, end, set_render_range=True):
|
||||
"""Set Fusion comp's start and end frame range
|
||||
|
||||
Attrs:
|
||||
set_render_range (bool, Optional): When True this will also set the
|
||||
composition's render start and end frame.
|
||||
|
||||
Returns:
|
||||
None
|
||||
|
||||
"""
|
||||
|
||||
from avalon.fusion import get_current_comp, comp_lock_and_undo_chunk
|
||||
|
||||
comp = get_current_comp()
|
||||
|
||||
attrs = {
|
||||
"COMPN_GlobalStart": start,
|
||||
"COMPN_GlobalEnd": end
|
||||
}
|
||||
|
||||
if set_render_range:
|
||||
attrs.update({
|
||||
"COMPN_RenderStart": start,
|
||||
"COMPN_RenderEnd": end
|
||||
})
|
||||
|
||||
with comp_lock_and_undo_chunk(comp):
|
||||
comp.SetAttrs(attrs)
|
||||
|
||||
|
||||
class FusionSetFrameRangeLoader(api.Loader):
|
||||
"""Specific loader of Alembic for the avalon.animation family"""
|
||||
|
||||
|
|
@ -53,6 +22,8 @@ class FusionSetFrameRangeLoader(api.Loader):
|
|||
|
||||
def load(self, context, name, namespace, data):
|
||||
|
||||
from colorbleed.fusion import lib
|
||||
|
||||
version = context['version']
|
||||
version_data = version.get("data", {})
|
||||
|
||||
|
|
@ -64,7 +35,7 @@ class FusionSetFrameRangeLoader(api.Loader):
|
|||
"end frame data is missing..")
|
||||
return
|
||||
|
||||
_set_frame_range(start, end)
|
||||
lib.update_frame_range(start, end)
|
||||
|
||||
|
||||
class FusionSetFrameRangeWithHandlesLoader(api.Loader):
|
||||
|
|
@ -84,6 +55,8 @@ class FusionSetFrameRangeWithHandlesLoader(api.Loader):
|
|||
|
||||
def load(self, context, name, namespace, data):
|
||||
|
||||
from colorbleed.fusion import lib
|
||||
|
||||
version = context['version']
|
||||
version_data = version.get("data", {})
|
||||
|
||||
|
|
@ -100,4 +73,4 @@ class FusionSetFrameRangeWithHandlesLoader(api.Loader):
|
|||
start -= handles
|
||||
end += handles
|
||||
|
||||
_set_frame_range(start, end)
|
||||
lib.update_frame_range(start, end)
|
||||
|
|
|
|||
|
|
@ -28,3 +28,6 @@ class AbcLoader(colorbleed.maya.plugin.ReferenceLoader):
|
|||
self[:] = nodes
|
||||
|
||||
return nodes
|
||||
|
||||
def switch(self, container, representation):
|
||||
self.update(container, representation)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ class CameraLoader(colorbleed.maya.plugin.ReferenceLoader):
|
|||
def process_reference(self, context, name, namespace, data):
|
||||
|
||||
import maya.cmds as cmds
|
||||
# import pprint
|
||||
# Get family type from the context
|
||||
|
||||
cmds.loadPlugin("AbcImport.mll", quiet=True)
|
||||
|
|
@ -41,3 +40,6 @@ class CameraLoader(colorbleed.maya.plugin.ReferenceLoader):
|
|||
self[:] = nodes
|
||||
|
||||
return nodes
|
||||
|
||||
def switch(self, container, representation):
|
||||
self.update(container, representation)
|
||||
|
|
|
|||
|
|
@ -35,3 +35,6 @@ class LookLoader(colorbleed.maya.plugin.ReferenceLoader):
|
|||
returnNewNodes=True)
|
||||
|
||||
self[:] = nodes
|
||||
|
||||
def switch(self, container, representation):
|
||||
self.update(container, representation)
|
||||
|
|
|
|||
|
|
@ -28,3 +28,6 @@ class MayaAsciiLoader(colorbleed.maya.plugin.ReferenceLoader):
|
|||
self[:] = nodes
|
||||
|
||||
return nodes
|
||||
|
||||
def switch(self, container, representation):
|
||||
self.update(container, representation)
|
||||
|
|
|
|||
|
|
@ -30,6 +30,9 @@ class ModelLoader(colorbleed.maya.plugin.ReferenceLoader):
|
|||
|
||||
return nodes
|
||||
|
||||
def switch(self, container, representation):
|
||||
self.update(container, representation)
|
||||
|
||||
|
||||
class ImportModelLoader(api.Loader):
|
||||
"""An ImportModelLoader for Maya
|
||||
|
|
@ -200,6 +203,9 @@ class GpuCacheLoader(api.Loader):
|
|||
str(representation["_id"]),
|
||||
type="string")
|
||||
|
||||
def switch(self, container, representation):
|
||||
self.update(container, representation)
|
||||
|
||||
def remove(self, container):
|
||||
import maya.cmds as cmds
|
||||
members = cmds.sets(container['objectName'], query=True)
|
||||
|
|
|
|||
|
|
@ -65,3 +65,6 @@ class RigLoader(colorbleed.maya.plugin.ReferenceLoader):
|
|||
family="colorbleed.animation",
|
||||
options={"useSelection": True},
|
||||
data={"dependencies": dependency})
|
||||
|
||||
def switch(self, container, representation):
|
||||
self.update(container, representation)
|
||||
|
|
|
|||
|
|
@ -108,6 +108,9 @@ class YetiCacheLoader(api.Loader):
|
|||
str(representation["_id"]),
|
||||
type="string")
|
||||
|
||||
def switch(self, container, representation):
|
||||
self.update(container, representation)
|
||||
|
||||
# helper functions
|
||||
|
||||
def create_namespace(self, asset):
|
||||
|
|
|
|||
|
|
@ -0,0 +1,111 @@
|
|||
from maya import cmds
|
||||
|
||||
import pyblish.api
|
||||
import colorbleed.api
|
||||
|
||||
|
||||
def pairs(iterable):
|
||||
"""Iterate over iterable per group of two"""
|
||||
a = iter(iterable)
|
||||
for i, y in zip(a, a):
|
||||
yield i, y
|
||||
|
||||
|
||||
def get_invalid_sets(shape):
|
||||
"""Get sets that are considered related but do not contain the shape.
|
||||
|
||||
In some scenarios Maya keeps connections to multiple shaders
|
||||
even if just a single one is assigned on the full object.
|
||||
|
||||
These are related sets returned by `maya.cmds.listSets` that don't
|
||||
actually have the shape as member.
|
||||
|
||||
"""
|
||||
|
||||
invalid = []
|
||||
sets = cmds.listSets(object=shape, t=1, extendToShape=False)
|
||||
for s in sets:
|
||||
members = cmds.sets(s, query=True, nodesOnly=True)
|
||||
if not members:
|
||||
invalid.append(s)
|
||||
continue
|
||||
|
||||
members = set(cmds.ls(members, long=True))
|
||||
if shape not in members:
|
||||
invalid.append(s)
|
||||
|
||||
return invalid
|
||||
|
||||
|
||||
def disconnect(node_a, node_b):
|
||||
"""Remove all connections between node a and b."""
|
||||
|
||||
# Disconnect outputs
|
||||
outputs = cmds.listConnections(node_a,
|
||||
plugs=True,
|
||||
connections=True,
|
||||
source=False,
|
||||
destination=True)
|
||||
for output, destination in pairs(outputs):
|
||||
if destination.split(".", 1)[0] == node_b:
|
||||
cmds.disconnectAttr(output, destination)
|
||||
|
||||
# Disconnect inputs
|
||||
inputs = cmds.listConnections(node_a,
|
||||
plugs=True,
|
||||
connections=True,
|
||||
source=True,
|
||||
destination=False)
|
||||
for input, source in pairs(inputs):
|
||||
if source.split(".", 1)[0] == node_b:
|
||||
cmds.disconnectAttr(source, input)
|
||||
|
||||
|
||||
class ValidateMeshShaderConnections(pyblish.api.InstancePlugin):
|
||||
"""Ensure mesh shading engine connections are valid.
|
||||
|
||||
In some scenarios Maya keeps connections to multiple shaders even if just
|
||||
a single one is assigned on the shape.
|
||||
|
||||
These are related sets returned by `maya.cmds.listSets` that don't
|
||||
actually have the shape as member.
|
||||
|
||||
"""
|
||||
|
||||
order = colorbleed.api.ValidateMeshOrder
|
||||
hosts = ['maya']
|
||||
families = ['colorbleed.model']
|
||||
label = "Mesh Shader Connections"
|
||||
actions = [colorbleed.api.SelectInvalidAction,
|
||||
colorbleed.api.RepairAction]
|
||||
|
||||
def process(self, instance):
|
||||
"""Process all the nodes in the instance 'objectSet'"""
|
||||
|
||||
invalid = self.get_invalid(instance)
|
||||
|
||||
if invalid:
|
||||
raise RuntimeError("Shapes found with invalid shader "
|
||||
"connections: {0}".format(invalid))
|
||||
|
||||
@staticmethod
|
||||
def get_invalid(instance):
|
||||
|
||||
shapes = cmds.ls(instance[:], dag=1, leaf=1, shapes=1, long=True)
|
||||
shapes = cmds.ls(shapes, shapes=True, noIntermediate=True, long=True)
|
||||
|
||||
invalid = []
|
||||
for shape in shapes:
|
||||
if get_invalid_sets(shape):
|
||||
invalid.append(shape)
|
||||
|
||||
return invalid
|
||||
|
||||
@classmethod
|
||||
def repair(cls, instance):
|
||||
|
||||
shapes = cls.get_invalid(instance)
|
||||
for shape in shapes:
|
||||
invalid_sets = get_invalid_sets(shape)
|
||||
for set_node in invalid_sets:
|
||||
disconnect(shape, set_node)
|
||||
239
colorbleed/scripts/fusion_switch_shot.py
Normal file
239
colorbleed/scripts/fusion_switch_shot.py
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
import os
|
||||
import re
|
||||
import sys
|
||||
import logging
|
||||
|
||||
# Pipeline imports
|
||||
from avalon import api, io, pipeline
|
||||
import avalon.fusion
|
||||
|
||||
# Config imports
|
||||
import colorbleed.lib as colorbleed
|
||||
import colorbleed.fusion.lib as fusion_lib
|
||||
|
||||
log = logging.getLogger("Update Slap Comp")
|
||||
|
||||
self = sys.modules[__name__]
|
||||
self._project = None
|
||||
|
||||
|
||||
def _format_version_folder(folder):
|
||||
"""Format a version folder based on the filepath
|
||||
|
||||
Assumption here is made that, if the path does not exists the folder
|
||||
will be "v001"
|
||||
|
||||
Args:
|
||||
folder: file path to a folder
|
||||
|
||||
Returns:
|
||||
str: new version folder name
|
||||
"""
|
||||
|
||||
if not os.path.isdir(folder):
|
||||
return "v001"
|
||||
|
||||
re_version = re.compile("v\d+$")
|
||||
versions = [i for i in os.listdir(folder) if os.path.isdir(i)
|
||||
and re_version.match(i)]
|
||||
|
||||
new_version = int(max(versions)[1:]) + 1 # ensure the "v" is not included
|
||||
version_folder = "v{:03d}".format(new_version)
|
||||
|
||||
return version_folder
|
||||
|
||||
|
||||
def _get_work_folder(session):
|
||||
"""Convenience function to get the work folder path of the current asset"""
|
||||
|
||||
# Get new filename, create path based on asset and work template
|
||||
template_work = self._project["config"]["template"]["work"]
|
||||
work_path = pipeline._format_work_template(template_work, session)
|
||||
|
||||
return os.path.normpath(work_path)
|
||||
|
||||
|
||||
def _get_fusion_instance():
|
||||
fusion = getattr(sys.modules["__main__"], "fusion", None)
|
||||
if fusion is None:
|
||||
try:
|
||||
# Support for FuScript.exe, BlackmagicFusion module for py2 only
|
||||
import BlackmagicFusion as bmf
|
||||
fusion = bmf.scriptapp("Fusion")
|
||||
except ImportError:
|
||||
raise RuntimeError("Could not find a Fusion instance")
|
||||
return fusion
|
||||
|
||||
|
||||
def _format_filepath(session):
|
||||
|
||||
project = session["AVALON_PROJECT"]
|
||||
asset = session["AVALON_ASSET"]
|
||||
|
||||
# Save updated slap comp
|
||||
work_path = _get_work_folder(session)
|
||||
walk_to_dir = os.path.join(work_path, "scenes", "slapcomp")
|
||||
slapcomp_dir = os.path.abspath(walk_to_dir)
|
||||
|
||||
# Ensure destination exists
|
||||
if not os.path.isdir(slapcomp_dir):
|
||||
log.warning("Folder did not exist, creating folder structure")
|
||||
os.makedirs(slapcomp_dir)
|
||||
|
||||
# Compute output path
|
||||
new_filename = "{}_{}_slapcomp_v001.comp".format(project, asset)
|
||||
new_filepath = os.path.join(slapcomp_dir, new_filename)
|
||||
|
||||
# Create new unqiue filepath
|
||||
if os.path.exists(new_filepath):
|
||||
new_filepath = colorbleed.version_up(new_filepath)
|
||||
|
||||
return new_filepath
|
||||
|
||||
|
||||
def _update_savers(comp, session):
|
||||
"""Update all savers of the current comp to ensure the output is correct
|
||||
|
||||
Args:
|
||||
comp (object): current comp instance
|
||||
session (dict): the current Avalon session
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
|
||||
new_work = _get_work_folder(session)
|
||||
renders = os.path.join(new_work, "renders")
|
||||
version_folder = _format_version_folder(renders)
|
||||
renders_version = os.path.join(renders, version_folder)
|
||||
|
||||
comp.Print("New renders to: %s\n" % renders)
|
||||
|
||||
with avalon.fusion.comp_lock_and_undo_chunk(comp):
|
||||
savers = comp.GetToolList(False, "Saver").values()
|
||||
for saver in savers:
|
||||
filepath = saver.GetAttrs("TOOLST_Clip_Name")[1.0]
|
||||
filename = os.path.basename(filepath)
|
||||
new_path = os.path.join(renders_version, filename)
|
||||
saver["Clip"] = new_path
|
||||
|
||||
|
||||
def update_frame_range(comp, representations):
|
||||
"""Update the frame range of the comp and render length
|
||||
|
||||
The start and end frame are based on the lowest start frame and the highest
|
||||
end frame
|
||||
|
||||
Args:
|
||||
comp (object): current focused comp
|
||||
representations (list) collection of dicts
|
||||
|
||||
Returns:
|
||||
None
|
||||
|
||||
"""
|
||||
|
||||
version_ids = [r["parent"] for r in representations]
|
||||
versions = io.find({"type": "version", "_id": {"$in": version_ids}})
|
||||
versions = list(versions)
|
||||
|
||||
start = min(v["data"]["startFrame"] for v in versions)
|
||||
end = max(v["data"]["endFrame"] for v in versions)
|
||||
|
||||
fusion_lib.update_frame_range(start, end, comp=comp)
|
||||
|
||||
|
||||
def switch(filepath, asset_name, new=True):
|
||||
"""Switch the current containers of the file to the other asset (shot)
|
||||
|
||||
Args:
|
||||
filepath (str): file path of the comp file
|
||||
asset_name (str): name of the asset (shot)
|
||||
new (bool): Save updated comp under a different name
|
||||
|
||||
Returns:
|
||||
comp path (str): new filepath of the updated comp
|
||||
|
||||
"""
|
||||
|
||||
# Ensure filename is absolute
|
||||
if not os.path.abspath(filepath):
|
||||
filepath = os.path.abspath(filepath)
|
||||
|
||||
# Get current project
|
||||
self._project = io.find_one({"type": "project",
|
||||
"name": api.Session["AVALON_PROJECT"]})
|
||||
|
||||
# Assert asset name exists
|
||||
# It is better to do this here then to wait till switch_shot does it
|
||||
asset = io.find_one({"type": "asset", "name": asset_name})
|
||||
assert asset, "Could not find '%s' in the database" % asset_name
|
||||
|
||||
# Go to comp
|
||||
fusion = _get_fusion_instance()
|
||||
|
||||
current_comp = fusion.LoadComp(filepath)
|
||||
assert current_comp is not None, "Fusion could not load '%s'" % filepath
|
||||
|
||||
host = api.registered_host()
|
||||
containers = list(host.ls())
|
||||
assert containers, "Nothing to update"
|
||||
|
||||
representations = []
|
||||
for container in containers:
|
||||
try:
|
||||
representation = colorbleed.switch_item(container,
|
||||
asset_name=asset_name)
|
||||
representations.append(representation)
|
||||
current_comp.Print(str(representation["_id"]) + "\n")
|
||||
except Exception as e:
|
||||
current_comp.Print("Error in switching! %s\n" % e.message)
|
||||
|
||||
message = "Switched %i Loaders of the %i\n" % (len(representations),
|
||||
len(containers))
|
||||
current_comp.Print(message)
|
||||
|
||||
# Build the session to switch to
|
||||
switch_to_session = api.Session.copy()
|
||||
switch_to_session["AVALON_ASSET"] = asset['name']
|
||||
|
||||
if new:
|
||||
comp_path = _format_filepath(switch_to_session)
|
||||
|
||||
# Update savers output based on new session
|
||||
_update_savers(current_comp, switch_to_session)
|
||||
else:
|
||||
comp_path = colorbleed.version_up(filepath)
|
||||
|
||||
current_comp.Print(comp_path)
|
||||
|
||||
current_comp.Print("\nUpdating frame range")
|
||||
update_frame_range(current_comp, representations)
|
||||
|
||||
current_comp.Save(comp_path)
|
||||
|
||||
return comp_path
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="Switch to a shot within an"
|
||||
"existing comp file")
|
||||
|
||||
parser.add_argument("--file_path",
|
||||
type=str,
|
||||
default=True,
|
||||
help="File path of the comp to use")
|
||||
parser.add_argument("--asset_name",
|
||||
type=str,
|
||||
default=True,
|
||||
help="Name of the asset (shot) to switch")
|
||||
|
||||
args, unknown = parser.parse_args()
|
||||
|
||||
api.install(avalon.fusion)
|
||||
switch(args.file_path, args.asset_name)
|
||||
|
||||
sys.exit(0)
|
||||
213
setup/fusion/scripts/Comp/colorbleed/switch_ui.py
Normal file
213
setup/fusion/scripts/Comp/colorbleed/switch_ui.py
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
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)
|
||||
|
||||
update_label = QtWidgets.QLabel("Update version")
|
||||
update_check = QtWidgets.QCheckBox()
|
||||
update_check.setChecked(False)
|
||||
update_check.setToolTip("If checked it versions up the selected comp "
|
||||
"file with else it will create a new slapcomp "
|
||||
"file based on the selected comp file")
|
||||
|
||||
current_comp_check = QtWidgets.QCheckBox()
|
||||
current_comp_check.setChecked(True)
|
||||
current_comp_label = QtWidgets.QLabel("Use current comp")
|
||||
|
||||
options.addWidget(update_label)
|
||||
options.addWidget(update_check)
|
||||
|
||||
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._update = update_check
|
||||
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()
|
||||
new = not self._update.isChecked()
|
||||
|
||||
import colorbleed.scripts.fusion_switch_shot as switch_shot
|
||||
switch_shot.switch(file_name, asset, new)
|
||||
|
||||
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_())
|
||||
Loading…
Add table
Add a link
Reference in a new issue