Merge remote-tracking branch 'origin/develop' into feature/PYPE-316-muster-render-support

This commit is contained in:
antirotor 2019-05-22 21:17:02 +02:00
commit aca96d0d97
17 changed files with 501 additions and 168 deletions

View file

@ -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"
]

View file

@ -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',

View file

@ -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 (

View file

@ -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())

View 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)

View file

@ -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))

View file

@ -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)

View file

@ -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 = {

View file

@ -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():

View file

@ -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):

View file

@ -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

View file

@ -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()

View file

@ -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()

View file

@ -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

View file

@ -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:

View file

@ -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()

View file

@ -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')