[Automated] Merged develop into main

This commit is contained in:
pypebot 2021-06-19 05:42:28 +02:00 committed by GitHub
commit 30d52c5af0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 457 additions and 197 deletions

View file

@ -72,7 +72,7 @@ class ExtractPlayblast(openpype.api.Extractor):
# Isolate view is requested by having objects in the set besides a
# camera.
if preset.pop("isolate_view", False) or instance.data.get("isolate"):
if preset.pop("isolate_view", False) and instance.data.get("isolate"):
preset["isolate"] = instance.data["setMembers"]
# Show/Hide image planes on request.

View file

@ -75,7 +75,7 @@ class ExtractThumbnail(openpype.api.Extractor):
# Isolate view is requested by having objects in the set besides a
# camera.
if preset.pop("isolate_view", False) or instance.data.get("isolate"):
if preset.pop("isolate_view", False) and instance.data.get("isolate"):
preset["isolate"] = instance.data["setMembers"]
with maintained_time():

View file

@ -286,7 +286,8 @@ def add_button_write_to_read(node):
node.addKnob(knob)
def create_write_node(name, data, input=None, prenodes=None, review=True):
def create_write_node(name, data, input=None, prenodes=None,
review=True, linked_knobs=None):
''' Creating write node which is group node
Arguments:
@ -465,12 +466,16 @@ def create_write_node(name, data, input=None, prenodes=None, review=True):
GN.addKnob(nuke.Text_Knob('', 'Rendering'))
# Add linked knobs.
linked_knob_names = [
"_grp-start_",
"use_limit", "first", "last",
"_grp-end_",
"Render"
]
linked_knob_names = []
# add input linked knobs and create group only if any input
if linked_knobs:
linked_knob_names.append("_grp-start_")
linked_knob_names.extend(linked_knobs)
linked_knob_names.append("_grp-end_")
linked_knob_names.append("Render")
for name in linked_knob_names:
if "_grp-start_" in name:
knob = nuke.Tab_Knob(
@ -481,13 +486,20 @@ def create_write_node(name, data, input=None, prenodes=None, review=True):
"rnd_attr_end", "Rendering attributes", nuke.TABENDGROUP)
GN.addKnob(knob)
else:
link = nuke.Link_Knob("")
link.makeLink(write_node.name(), name)
link.setName(name)
if "Render" in name:
link.setLabel("Render Local")
link.setFlag(0x1000)
GN.addKnob(link)
if "___" in name:
# add devider
GN.addKnob(nuke.Text_Knob(""))
else:
# add linked knob by name
link = nuke.Link_Knob("")
link.makeLink(write_node.name(), name)
link.setName(name)
# make render
if "Render" in name:
link.setLabel("Render Local")
link.setFlag(0x1000)
GN.addKnob(link)
# adding write to read button
add_button_write_to_read(GN)

View file

@ -103,7 +103,8 @@ class CreateWritePrerender(plugin.PypeCreator):
write_data,
input=selected_node,
prenodes=[],
review=False)
review=False,
linked_knobs=["channels", "___", "first", "last", "use_limit"])
# relinking to collected connections
for i, input in enumerate(inputs):
@ -122,19 +123,10 @@ class CreateWritePrerender(plugin.PypeCreator):
w_node = n
write_node.end()
# add inner write node Tab
write_node.addKnob(nuke.Tab_Knob("WriteLinkedKnobs"))
if self.presets.get("use_range_limit"):
w_node["use_limit"].setValue(True)
w_node["first"].setValue(nuke.root()["first_frame"].value())
w_node["last"].setValue(nuke.root()["last_frame"].value())
# linking knobs to group property panel
linking_knobs = ["channels", "___", "first", "last", "use_limit"]
for k in linking_knobs:
if "___" in k:
write_node.addKnob(nuke.Text_Knob(''))
else:
lnk = nuke.Link_Knob(k)
lnk.makeLink(w_node.name(), k)
lnk.setName(k.replace('_', ' ').capitalize())
lnk.clearFlag(nuke.STARTLINE)
write_node.addKnob(lnk)
return write_node

View file

@ -1,5 +1,6 @@
import os
import re
from pprint import pformat
import nuke
import pyblish.api
import openpype.api as pype
@ -17,6 +18,7 @@ class CollectNukeWrites(pyblish.api.InstancePlugin):
def process(self, instance):
_families_test = [instance.data["family"]] + instance.data["families"]
self.log.debug("_families_test: {}".format(_families_test))
node = None
for x in instance:
@ -133,22 +135,29 @@ class CollectNukeWrites(pyblish.api.InstancePlugin):
"outputDir": output_dir,
"ext": ext,
"label": label,
"handleStart": handle_start,
"handleEnd": handle_end,
"frameStart": first_frame + handle_start,
"frameEnd": last_frame - handle_end,
"frameStartHandle": first_frame,
"frameEndHandle": last_frame,
"outputType": output_type,
"colorspace": colorspace,
"deadlineChunkSize": deadlineChunkSize,
"deadlinePriority": deadlinePriority
})
if "prerender" in _families_test:
if self.is_prerender(_families_test):
instance.data.update({
"family": "prerender",
"families": []
"handleStart": 0,
"handleEnd": 0,
"frameStart": first_frame,
"frameEnd": last_frame,
"frameStartHandle": first_frame,
"frameEndHandle": last_frame,
})
else:
instance.data.update({
"handleStart": handle_start,
"handleEnd": handle_end,
"frameStart": first_frame + handle_start,
"frameEnd": last_frame - handle_end,
"frameStartHandle": first_frame,
"frameEndHandle": last_frame,
})
# * Add audio to instance if exists.
@ -170,4 +179,7 @@ class CollectNukeWrites(pyblish.api.InstancePlugin):
"filename": api.get_representation_path(repre_doc)
}]
self.log.debug("instance.data: {}".format(instance.data))
self.log.debug("instance.data: {}".format(pformat(instance.data)))
def is_prerender(self, families):
return next((f for f in families if "prerender" in f), None)

View file

@ -61,7 +61,6 @@ class ValidateRenderedFrames(pyblish.api.InstancePlugin):
hosts = ["nuke", "nukestudio"]
actions = [RepairCollectionActionToLocal, RepairCollectionActionToFarm]
def process(self, instance):
for repre in instance.data["representations"]:
@ -78,10 +77,10 @@ class ValidateRenderedFrames(pyblish.api.InstancePlugin):
collection = collections[0]
frame_length = int(
instance.data["frameEndHandle"]
- instance.data["frameStartHandle"] + 1
)
fstartH = instance.data["frameStartHandle"]
fendH = instance.data["frameEndHandle"]
frame_length = int(fendH - fstartH + 1)
if frame_length != 1:
if len(collections) != 1:
@ -95,7 +94,16 @@ class ValidateRenderedFrames(pyblish.api.InstancePlugin):
raise ValidationException(msg)
collected_frames_len = int(len(collection.indexes))
coll_start = min(collection.indexes)
coll_end = max(collection.indexes)
self.log.info("frame_length: {}".format(frame_length))
self.log.info("collected_frames_len: {}".format(
collected_frames_len))
self.log.info("fstartH-fendH: {}-{}".format(fstartH, fendH))
self.log.info(
"coll_start-coll_end: {}-{}".format(coll_start, coll_end))
self.log.info(
"len(collection.indexes): {}".format(collected_frames_len)
)
@ -103,8 +111,11 @@ class ValidateRenderedFrames(pyblish.api.InstancePlugin):
if ("slate" in instance.data["families"]) \
and (frame_length != collected_frames_len):
collected_frames_len -= 1
fstartH += 1
assert (collected_frames_len == frame_length), (
assert ((collected_frames_len >= frame_length)
and (coll_start <= fstartH)
and (coll_end >= fendH)), (
"{} missing frames. Use repair to render all frames"
).format(__name__)

View file

@ -58,18 +58,14 @@ class CreateRenderlayer(plugin.Creator):
# Get currently selected layers
layers_data = lib.layers_data()
group_ids = set()
for layer in layers_data:
if layer["selected"]:
group_ids.add(layer["group_id"])
selected_layers = [
layer
for layer in layers_data
if layer["selected"]
]
# Return layer name if only one is selected
if len(group_ids) == 1:
group_id = list(group_ids)[0]
groups_data = lib.groups_data()
for group in groups_data:
if group["group_id"] == group_id:
return group["name"]
if len(selected_layers) == 1:
return selected_layers[0]["name"]
# Use defaults
if cls.defaults:

View file

@ -986,9 +986,21 @@ class ExtractReview(pyblish.api.InstancePlugin):
output_width = output_def.get("width") or None
output_height = output_def.get("height") or None
# Overscal color
overscan_color_value = "black"
overscan_color = output_def.get("overscan_color")
if overscan_color:
bg_red, bg_green, bg_blue, _ = overscan_color
overscan_color_value = "#{0:0>2X}{1:0>2X}{2:0>2X}".format(
bg_red, bg_green, bg_blue
)
self.log.debug("Overscan color: `{}`".format(overscan_color_value))
# Convert overscan value video filters
overscan_crop = output_def.get("overscan_crop")
overscan = OverscanCrop(input_width, input_height, overscan_crop)
overscan = OverscanCrop(
input_width, input_height, overscan_crop, overscan_color_value
)
overscan_crop_filters = overscan.video_filters()
# Add overscan filters to filters if are any and modify input
# resolution by it's values
@ -1158,9 +1170,10 @@ class ExtractReview(pyblish.api.InstancePlugin):
"scale={}x{}:flags=lanczos".format(
width_scale, height_scale
),
"pad={}:{}:{}:{}:black".format(
"pad={}:{}:{}:{}:{}".format(
output_width, output_height,
width_half_pad, height_half_pad
width_half_pad, height_half_pad,
overscan_color_value
),
"setsar=1"
])
@ -1707,12 +1720,15 @@ class OverscanCrop:
item_regex = re.compile(r"([\+\-])?([0-9]+)(.+)?")
relative_source_regex = re.compile(r"%([\+\-])")
def __init__(self, input_width, input_height, string_value):
def __init__(
self, input_width, input_height, string_value, overscal_color=None
):
# Make sure that is not None
string_value = string_value or ""
self.input_width = input_width
self.input_height = input_height
self.overscal_color = overscal_color
width, height = self._convert_string_to_values(string_value)
self._width_value = width
@ -1767,16 +1783,22 @@ class OverscanCrop:
elif width >= self.input_width and height >= self.input_height:
output.append(
"pad={}:{}:(iw-ow)/2:(ih-oh)/2".format(width, height)
"pad={}:{}:(iw-ow)/2:(ih-oh)/2:{}".format(
width, height, self.overscal_color
)
)
elif width > self.input_width and height < self.input_height:
output.append("crop=iw:{}".format(height))
output.append("pad={}:ih:(iw-ow)/2:(ih-oh)/2".format(width))
output.append("pad={}:ih:(iw-ow)/2:(ih-oh)/2:{}".format(
width, self.overscal_color
))
elif width < self.input_width and height > self.input_height:
output.append("crop={}:ih".format(width))
output.append("pad=iw:{}:(iw-ow)/2:(ih-oh)/2".format(height))
output.append("pad=iw:{}:(iw-ow)/2:(ih-oh)/2:{}".format(
height, self.overscal_color
))
return output

View file

@ -78,6 +78,10 @@
{
"name": "colorspace",
"value": "linear"
},
{
"name": "create_directories",
"value": "True"
}
]
},
@ -114,6 +118,10 @@
{
"name": "colorspace",
"value": "linear"
},
{
"name": "create_directories",
"value": "True"
}
]
}

View file

@ -56,6 +56,12 @@
]
},
"overscan_crop": "",
"overscan_color": [
0,
0,
0,
255
],
"width": 0,
"height": 0,
"bg_color": [
@ -226,6 +232,17 @@
],
"tasks": [],
"template": "{family}{Task}_{Render_layer}_{Render_pass}"
},
{
"families": [
"review",
"workfile"
],
"hosts": [
"tvpaint"
],
"tasks": [],
"template": "{family}{Task}"
}
]
},

View file

@ -13,7 +13,8 @@
"fpath_template": "{work}/renders/nuke/{subset}/{subset}.{frame}.{ext}"
},
"CreateWritePrerender": {
"fpath_template": "{work}/prerenders/nuke/{subset}/{subset}.{frame}.{ext}"
"fpath_template": "{work}/prerenders/nuke/{subset}/{subset}.{frame}.{ext}",
"use_range_limit": true
}
},
"publish": {

View file

@ -12,6 +12,17 @@ class ColorEntity(InputEntity):
def _item_initalization(self):
self.valid_value_types = (list, )
self.value_on_not_set = [0, 0, 0, 255]
self.use_alpha = self.schema_data.get("use_alpha", True)
def set_override_state(self, *args, **kwargs):
super(ColorEntity, self).set_override_state(*args, **kwargs)
value = self._current_value
if (
not self.use_alpha
and isinstance(value, list)
and len(value) == 4
):
value[3] = 255
def convert_to_valid_type(self, value):
"""Conversion to valid type.
@ -51,4 +62,8 @@ class ColorEntity(InputEntity):
).format(value)
raise BaseInvalidValueType(reason, self.path)
new_value.append(item)
# Make sure
if not self.use_alpha:
new_value[3] = 255
return new_value

View file

@ -77,6 +77,11 @@
"type": "text",
"key": "fpath_template",
"label": "Path template"
},
{
"type": "boolean",
"key": "use_range_limit",
"label": "Use Frame range limit by default"
}
]
}

View file

@ -3,7 +3,6 @@
"key": "imageio",
"label": "Color Management and Output Formats",
"is_file": true,
"is_group": true,
"children": [
{
"key": "hiero",
@ -15,6 +14,7 @@
"type": "dict",
"label": "Workfile",
"collapsible": false,
"is_group": true,
"children": [
{
"type": "form",
@ -89,6 +89,7 @@
"type": "dict",
"label": "Colorspace on Inputs by regex detection",
"collapsible": true,
"is_group": true,
"children": [
{
"type": "list",
@ -123,6 +124,7 @@
"type": "dict",
"label": "Viewer",
"collapsible": false,
"is_group": true,
"children": [
{
"type": "text",
@ -136,6 +138,7 @@
"type": "dict",
"label": "Workfile",
"collapsible": false,
"is_group": true,
"children": [
{
"type": "form",
@ -233,6 +236,7 @@
"type": "dict",
"label": "Nodes",
"collapsible": true,
"is_group": true,
"children": [
{
"key": "requiredNodes",
@ -335,6 +339,7 @@
"type": "dict",
"label": "Colorspace on Inputs by regex detection",
"collapsible": true,
"is_group": true,
"children": [
{
"type": "list",

View file

@ -182,6 +182,16 @@
"key": "overscan_crop",
"label": "Overscan crop"
},
{
"type": "label",
"label": "Overscan color is used when input aspect ratio is not same as output aspect ratio."
},
{
"type": "color",
"label": "Overscan color",
"key": "overscan_color",
"use_alpha": false
},
{
"type": "label",
"label": "Width and Height must be both set to higher value than 0 else source resolution is used."

View file

@ -325,19 +325,59 @@ class ProjectModel(QtGui.QStandardItemModel):
self.hide_invisible = False
self.project_icon = qtawesome.icon("fa.map", color="white")
self._project_names = set()
def refresh(self):
self.clear()
self.beginResetModel()
project_names = set()
for project_doc in self.get_projects():
item = QtGui.QStandardItem(self.project_icon, project_doc["name"])
self.appendRow(item)
project_names.add(project_doc["name"])
self.endResetModel()
origin_project_names = set(self._project_names)
self._project_names = project_names
project_names_to_remove = origin_project_names - project_names
if project_names_to_remove:
row_counts = {}
continuous = None
for row in range(self.rowCount()):
index = self.index(row, 0)
index_name = index.data(QtCore.Qt.DisplayRole)
if index_name in project_names_to_remove:
if continuous is None:
continuous = row
row_counts[continuous] = 0
row_counts[continuous] += 1
else:
continuous = None
for row in reversed(sorted(row_counts.keys())):
count = row_counts[row]
self.removeRows(row, count)
continuous = None
row_counts = {}
for idx, project_name in enumerate(sorted(project_names)):
if project_name in origin_project_names:
continuous = None
continue
if continuous is None:
continuous = idx
row_counts[continuous] = []
row_counts[continuous].append(project_name)
for row in reversed(sorted(row_counts.keys())):
items = []
for project_name in row_counts[row]:
item = QtGui.QStandardItem(self.project_icon, project_name)
items.append(item)
self.invisibleRootItem().insertRows(row, items)
def get_projects(self):
project_docs = []
for project_doc in sorted(
self.dbcon.projects(), key=lambda x: x["name"]
):

View file

@ -22,6 +22,9 @@ from .constants import (
class ProjectBar(QtWidgets.QWidget):
project_changed = QtCore.Signal(int)
# Project list will be refreshed each 10000 msecs
refresh_interval = 10000
def __init__(self, dbcon, parent=None):
super(ProjectBar, self).__init__(parent)
@ -47,14 +50,16 @@ class ProjectBar(QtWidgets.QWidget):
QtWidgets.QSizePolicy.Maximum
)
refresh_timer = QtCore.QTimer()
refresh_timer.setInterval(self.refresh_interval)
self.model = model
self.project_delegate = project_delegate
self.project_combobox = project_combobox
# Initialize
self.refresh()
self.refresh_timer = refresh_timer
# Signals
refresh_timer.timeout.connect(self._on_refresh_timeout)
self.project_combobox.currentIndexChanged.connect(self.project_changed)
# Set current project by default if it's set.
@ -62,6 +67,20 @@ class ProjectBar(QtWidgets.QWidget):
if project_name:
self.set_project(project_name)
def showEvent(self, event):
if not self.refresh_timer.isActive():
self.refresh_timer.start()
super(ProjectBar, self).showEvent(event)
def _on_refresh_timeout(self):
if not self.isVisible():
# Stop timer if widget is not visible
self.refresh_timer.stop()
elif self.isActiveWindow():
# Refresh projects if window is active
self.model.refresh()
def get_current_project(self):
return self.project_combobox.currentText()
@ -69,27 +88,14 @@ class ProjectBar(QtWidgets.QWidget):
index = self.project_combobox.findText(project_name)
if index < 0:
# Try refresh combobox model
self.project_combobox.blockSignals(True)
self.model.refresh()
self.project_combobox.blockSignals(False)
self.refresh()
index = self.project_combobox.findText(project_name)
if index >= 0:
self.project_combobox.setCurrentIndex(index)
def refresh(self):
prev_project_name = self.get_current_project()
# Refresh without signals
self.project_combobox.blockSignals(True)
self.model.refresh()
self.set_project(prev_project_name)
self.project_combobox.blockSignals(False)
self.project_changed.emit(self.project_combobox.currentIndex())
class ActionBar(QtWidgets.QWidget):

View file

@ -91,6 +91,8 @@ class ProjectsPanel(QtWidgets.QWidget):
"""Projects Page"""
project_clicked = QtCore.Signal(str)
# Refresh projects each 10000 msecs
refresh_interval = 10000
def __init__(self, dbcon, parent=None):
super(ProjectsPanel, self).__init__(parent=parent)
@ -106,21 +108,40 @@ class ProjectsPanel(QtWidgets.QWidget):
flick.activateOn(view)
model = ProjectModel(self.dbcon)
model.hide_invisible = True
model.refresh()
view.setModel(model)
layout.addWidget(view)
refresh_timer = QtCore.QTimer()
refresh_timer.setInterval(self.refresh_interval)
refresh_timer.timeout.connect(self._on_refresh_timeout)
view.clicked.connect(self.on_clicked)
self.model = model
self.view = view
self.refresh_timer = refresh_timer
def on_clicked(self, index):
if index.isValid():
project_name = index.data(QtCore.Qt.DisplayRole)
self.project_clicked.emit(project_name)
def showEvent(self, event):
self.model.refresh()
if not self.refresh_timer.isActive():
self.refresh_timer.start()
super(ProjectsPanel, self).showEvent(event)
def _on_refresh_timeout(self):
if not self.isVisible():
# Stop timer if widget is not visible
self.refresh_timer.stop()
elif self.isActiveWindow():
# Refresh projects if window is active
self.model.refresh()
class AssetsPanel(QtWidgets.QWidget):
"""Assets page"""
@ -276,6 +297,8 @@ class AssetsPanel(QtWidgets.QWidget):
class LauncherWindow(QtWidgets.QDialog):
"""Launcher interface"""
# Refresh actions each 10000msecs
actions_refresh_timeout = 10000
def __init__(self, parent=None):
super(LauncherWindow, self).__init__(parent)
@ -344,6 +367,10 @@ class LauncherWindow(QtWidgets.QDialog):
layout.setSpacing(0)
layout.setContentsMargins(0, 0, 0, 0)
actions_refresh_timer = QtCore.QTimer()
actions_refresh_timer.setInterval(self.actions_refresh_timeout)
self.actions_refresh_timer = actions_refresh_timer
self.message_label = message_label
self.project_panel = project_panel
self.asset_panel = asset_panel
@ -353,6 +380,7 @@ class LauncherWindow(QtWidgets.QDialog):
self._page = 0
# signals
actions_refresh_timer.timeout.connect(self._on_action_timer)
actions_bar.action_clicked.connect(self.on_action_clicked)
action_history.trigger_history.connect(self.on_history_action)
project_panel.project_clicked.connect(self.on_project_clicked)
@ -367,9 +395,11 @@ class LauncherWindow(QtWidgets.QDialog):
self.resize(520, 740)
def showEvent(self, event):
super().showEvent(event)
# TODO implement refresh/reset which will trigger updating
self.discover_actions()
if not self.actions_refresh_timer.isActive():
self.actions_refresh_timer.start()
self.discover_actions()
super(LauncherWindow, self).showEvent(event)
def set_page(self, page):
current = self.page_slider.currentIndex()
@ -402,6 +432,15 @@ class LauncherWindow(QtWidgets.QDialog):
def filter_actions(self):
self.actions_bar.filter_actions()
def _on_action_timer(self):
if not self.isVisible():
# Stop timer if widget is not visible
self.actions_refresh_timer.stop()
elif self.isActiveWindow():
# Refresh projects if window is active
self.discover_actions()
def on_project_clicked(self, project_name):
self.dbcon.Session["AVALON_PROJECT"] = project_name
# Refresh projects
@ -412,7 +451,6 @@ class LauncherWindow(QtWidgets.QDialog):
def on_back_clicked(self):
self.dbcon.Session["AVALON_PROJECT"] = None
self.set_page(0)
self.project_panel.model.refresh() # Refresh projects
self.discover_actions()
def on_action_clicked(self, action):

View file

@ -25,7 +25,9 @@ class ColorWidget(InputWidget):
self._dialog.open()
return
dialog = ColorDialog(self.input_field.color(), self)
dialog = ColorDialog(
self.input_field.color(), self.entity.use_alpha, self
)
self._dialog = dialog
dialog.open()
@ -120,12 +122,12 @@ class ColorViewer(QtWidgets.QWidget):
class ColorDialog(QtWidgets.QDialog):
def __init__(self, color=None, parent=None):
def __init__(self, color=None, use_alpha=True, parent=None):
super(ColorDialog, self).__init__(parent)
self.setWindowTitle("Color picker dialog")
picker_widget = ColorPickerWidget(color, self)
picker_widget = ColorPickerWidget(color, use_alpha, self)
footer_widget = QtWidgets.QWidget(self)

View file

@ -4,35 +4,6 @@ from Qt import QtWidgets, QtCore, QtGui
from .color_view import draw_checkerboard_tile
slide_style = """
QSlider::groove:horizontal {
background: qlineargradient(
x1: 0, y1: 0, x2: 1, y2: 0, stop: 0 #000, stop: 1 #fff
);
height: 8px;
border-radius: 4px;
}
QSlider::handle:horizontal {
background: qlineargradient(
x1:0, y1:0, x2:1, y2:1, stop:0 #ddd, stop:1 #bbb
);
border: 1px solid #777;
width: 8px;
margin-top: -1px;
margin-bottom: -1px;
border-radius: 4px;
}
QSlider::handle:horizontal:hover {
background: qlineargradient(
x1:0, y1:0, x2:1, y2:1, stop:0 #eee, stop:1 #ddd
);
border: 1px solid #444;ff
border-radius: 4px;
}"""
class AlphaSlider(QtWidgets.QSlider):
def __init__(self, *args, **kwargs):
super(AlphaSlider, self).__init__(*args, **kwargs)
@ -80,7 +51,7 @@ class AlphaSlider(QtWidgets.QSlider):
painter.fillRect(event.rect(), QtCore.Qt.transparent)
painter.setRenderHint(QtGui.QPainter.SmoothPixmapTransform)
painter.setRenderHint(QtGui.QPainter.HighQualityAntialiasing)
rect = self.style().subControlRect(
QtWidgets.QStyle.CC_Slider,
opt,
@ -135,19 +106,8 @@ class AlphaSlider(QtWidgets.QSlider):
painter.save()
gradient = QtGui.QRadialGradient()
radius = handle_rect.height() / 2
center_x = handle_rect.width() / 2 + handle_rect.x()
center_y = handle_rect.height()
gradient.setCenter(center_x, center_y)
gradient.setCenterRadius(radius)
gradient.setFocalPoint(center_x, center_y)
gradient.setColorAt(0.9, QtGui.QColor(127, 127, 127))
gradient.setColorAt(1, QtCore.Qt.transparent)
painter.setPen(QtCore.Qt.NoPen)
painter.setBrush(gradient)
painter.setBrush(QtGui.QColor(127, 127, 127))
painter.drawEllipse(handle_rect)
painter.restore()

View file

@ -17,19 +17,12 @@ from .color_inputs import (
class ColorPickerWidget(QtWidgets.QWidget):
color_changed = QtCore.Signal(QtGui.QColor)
def __init__(self, color=None, parent=None):
def __init__(self, color=None, use_alpha=True, parent=None):
super(ColorPickerWidget, self).__init__(parent)
# Color triangle
color_triangle = QtColorTriangle(self)
alpha_slider_proxy = QtWidgets.QWidget(self)
alpha_slider = AlphaSlider(QtCore.Qt.Horizontal, alpha_slider_proxy)
alpha_slider_layout = QtWidgets.QHBoxLayout(alpha_slider_proxy)
alpha_slider_layout.setContentsMargins(5, 5, 5, 5)
alpha_slider_layout.addWidget(alpha_slider, 1)
# Eye picked widget
pick_widget = PickScreenColorWidget()
pick_widget.setMaximumHeight(50)
@ -47,8 +40,6 @@ class ColorPickerWidget(QtWidgets.QWidget):
color_view = ColorViewer(self)
color_view.setMaximumHeight(50)
alpha_inputs = AlphaInputs(self)
color_inputs_color = QtGui.QColor()
col_inputs_by_label = [
("HEX", HEXInputs(color_inputs_color, self)),
@ -58,6 +49,7 @@ class ColorPickerWidget(QtWidgets.QWidget):
]
layout = QtWidgets.QGridLayout(self)
empty_col = 1
label_col = empty_col + 1
input_col = label_col + 1
@ -65,6 +57,9 @@ class ColorPickerWidget(QtWidgets.QWidget):
empty_widget.setFixedWidth(10)
layout.addWidget(empty_widget, 0, empty_col)
layout.setColumnStretch(0, 1)
layout.setColumnStretch(input_col, 1)
row = 0
layout.addWidget(btn_pick_color, row, label_col)
layout.addWidget(color_view, row, input_col)
@ -84,20 +79,41 @@ class ColorPickerWidget(QtWidgets.QWidget):
layout.setRowStretch(row, 1)
row += 1
layout.addWidget(alpha_slider_proxy, row, 0)
alpha_label = None
alpha_slider_proxy = None
alpha_slider = None
alpha_inputs = None
if not use_alpha:
color.setAlpha(255)
else:
alpha_inputs = AlphaInputs(self)
alpha_label = QtWidgets.QLabel("Alpha", self)
alpha_slider_proxy = QtWidgets.QWidget(self)
alpha_slider = AlphaSlider(
QtCore.Qt.Horizontal, alpha_slider_proxy
)
alpha_slider_layout = QtWidgets.QHBoxLayout(alpha_slider_proxy)
alpha_slider_layout.setContentsMargins(5, 5, 5, 5)
alpha_slider_layout.addWidget(alpha_slider, 1)
layout.addWidget(alpha_slider_proxy, row, 0)
layout.addWidget(alpha_label, row, label_col)
layout.addWidget(alpha_inputs, row, input_col)
row += 1
layout.addWidget(QtWidgets.QLabel("Alpha", self), row, label_col)
layout.addWidget(alpha_inputs, row, input_col)
row += 1
layout.setRowStretch(row, 1)
color_view.set_color(color_triangle.cur_color)
color_triangle.color_changed.connect(self.triangle_color_changed)
alpha_slider.valueChanged.connect(self._on_alpha_slider_change)
pick_widget.color_selected.connect(self.on_color_change)
alpha_inputs.alpha_changed.connect(self._on_alpha_inputs_changed)
btn_pick_color.released.connect(self.pick_color)
if alpha_slider:
alpha_slider.valueChanged.connect(self._on_alpha_slider_change)
alpha_inputs.alpha_changed.connect(self._on_alpha_inputs_changed)
self.color_input_fields = color_input_fields
self.color_inputs_color = color_inputs_color
@ -131,7 +147,8 @@ class ColorPickerWidget(QtWidgets.QWidget):
return self.color_view.color()
def set_color(self, color):
self.alpha_inputs.set_alpha(color.alpha())
if self.alpha_inputs:
self.alpha_inputs.set_alpha(color.alpha())
self.on_color_change(color)
def pick_color(self):
@ -163,10 +180,10 @@ class ColorPickerWidget(QtWidgets.QWidget):
def alpha_changed(self, value):
self.color_view.set_alpha(value)
if self.alpha_slider.value() != value:
if self.alpha_slider and self.alpha_slider.value() != value:
self.alpha_slider.setValue(value)
if self.alpha_inputs.alpha_value != value:
if self.alpha_inputs and self.alpha_inputs.alpha_value != value:
self.alpha_inputs.set_alpha(value)
def _on_alpha_inputs_changed(self, value):

View file

@ -241,7 +241,11 @@ class QtColorTriangle(QtWidgets.QWidget):
# Blit the static generated background with the hue gradient onto
# the double buffer.
buf = QtGui.QImage(self.bg_image.copy())
buf = QtGui.QImage(
self.bg_image.width(),
self.bg_image.height(),
QtGui.QImage.Format_RGB32
)
# Draw the trigon
# Find the color with only the hue, and max value and saturation
@ -254,9 +258,21 @@ class QtColorTriangle(QtWidgets.QWidget):
)
# Slow step: convert the image to a pixmap
pix = QtGui.QPixmap.fromImage(buf)
pix = self.bg_image.copy()
pix_painter = QtGui.QPainter(pix)
pix_painter.setRenderHint(QtGui.QPainter.Antialiasing)
pix_painter.setRenderHint(QtGui.QPainter.HighQualityAntialiasing)
trigon_path = QtGui.QPainterPath()
trigon_path.moveTo(self.point_a)
trigon_path.lineTo(self.point_b)
trigon_path.lineTo(self.point_c)
trigon_path.closeSubpath()
pix_painter.setClipPath(trigon_path)
pix_painter.drawImage(0, 0, buf)
pix_painter.setClipping(False)
# Draw an outline of the triangle
pix_painter.setPen(self._triangle_outline_pen)
@ -724,27 +740,37 @@ class QtColorTriangle(QtWidgets.QWidget):
lx = leftX[y]
rx = rightX[y]
# if the xdist is 0, don't draw anything.
xdist = rx - lx
if xdist == 0.0:
continue
lxi = int(floor(lx))
rxi = int(floor(rx))
rc = rightColors[y]
lc = leftColors[y]
# if the xdist is 0, don't draw anything.
xdist = rx - lx
if xdist != 0.0:
r = lc.r
g = lc.g
b = lc.b
rdelta = (rc.r - r) / xdist
gdelta = (rc.g - g) / xdist
bdelta = (rc.b - b) / xdist
r = lc.r
g = lc.g
b = lc.b
rdelta = (rc.r - r) / xdist
gdelta = (rc.g - g) / xdist
bdelta = (rc.b - b) / xdist
# Inner loop 2. Draws the line from left to right.
for x in range(lxi, rxi + 1):
buf.setPixel(x, y, QtGui.qRgb(int(r), int(g), int(b)))
r += rdelta
g += gdelta
b += bdelta
# Draw 2 more pixels on left side for smoothing
for x in range(lxi - 2, lxi):
buf.setPixel(x, y, QtGui.qRgb(int(r), int(g), int(b)))
# Inner loop 2. Draws the line from left to right.
for x in range(lxi, rxi):
buf.setPixel(x, y, QtGui.qRgb(int(r), int(g), int(b)))
r += rdelta
g += gdelta
b += bdelta
# Draw 2 more pixels on right side for smoothing
for x in range(rxi, rxi + 3):
buf.setPixel(x, y, QtGui.qRgb(int(r), int(g), int(b)))
def _radius_at(self, pos, rect):
mousexdist = pos.x() - float(rect.center().x())

View file

@ -5,6 +5,8 @@ def draw_checkerboard_tile(piece_size=None, color_1=None, color_2=None):
if piece_size is None:
piece_size = 7
# Make sure piece size is not float
piece_size = int(piece_size)
if color_1 is None:
color_1 = QtGui.QColor(188, 188, 188)

View file

@ -0,0 +1,30 @@
---
id: admin_hosts_tvpaint
title: TVPaint
sidebar_label: TVPaint
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
## Subset name templates
Definition of possibile subset name templates in TVPaint integration.
### [Render Layer](artist_hosts_tvpaint#render-layer)
Render layer has additional keys for subset name template. It is possible to use **render_layer** and **render_pass**.
- Key **render_layer** is alias for variant (user's input).
- For key **render_pass** is used predefined value `"Beauty"` (ATM value can't be changed).
### [Render pass](artist_hosts_tvpaint#render-pass)
Render pass has additional keys for subset name template. It is possible to use **render_layer** and **render_pass**.
- Key **render_layer** is filled with value of **render_pass** from `renderLayer` group.
- Key **render_pass** is alias for variant (user's input).
:::important Render Layer/Pass templates
It is recommended to use same subset name template for both **renderLayer** and **renderPass** families.
- Example template: `"{family}{Task}_{Render_layer}_{Render_pass}"`
:::
### [Review](artist_hosts_tvpaint#review) and [Workfile](artist_hosts_tvpaint#workfile)
Families **review** and **workfile** are not manually created but are automatically generated during publishing. That's why it is recommended to not use **variant** key in their subset name template.

View file

@ -45,7 +45,7 @@ In TVPaint you can find the Tools in OpenPype menu extension. The OpenPype Tools
## Create
In TVPaint you can create and publish **[Reviews](#review)**, **[Render Passes](#render-pass)**, and **[Render Layers](#render-layer)**.
In TVPaint you can create and publish **[Reviews](#review)**, **[Workfile](#workfile)**, **[Render Passes](#render-pass)** and **[Render Layers](#render-layer)**.
You have the possibility to organize your layers by using `Color group`.
@ -67,26 +67,13 @@ OpenPype specifically never tries to guess what you want to publish from the sce
When you want to publish `review` or `render layer` or `render pass`, open the `Creator` through the Tools menu `Create` button.
### Review
### Review
`Review` renders the whole file as is and sends the resulting QuickTime to Ftrack.
- Is automatically created during publishing.
<div class="row markdown">
<div class="col col--6 markdown">
`Review` renders the whole file as is and sends the resulting QuickTime to Ftrack.
To create reviewable quicktime of your animation:
- select `Review` in the `Creator`
- press `Create`
- When you run [publish](#publish), file will be rendered and converted to quicktime.`
</div>
<div class="col col--6 markdown">
![createreview](assets/tvp_create_review.png)
</div>
</div>
### Workfile
`Workfile` stores the source workfile as is during publishing (e.g. for backup).
- Is automatically created during publishing.
### Render Layer

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View file

@ -112,6 +112,10 @@ Profile may generate multiple outputs from a single input. Each output must defi
| "-10% -200px" | 1800px 800px |
| "-10% -0px" | 1800px 1000px |
- **`Overscan color`**
- Color of empty area caused by different aspect ratio of input and output.
- By default is set to black color.
- **`Letter Box`**
- **Enabled** - Enable letter boxes
- **Ratio** - Ratio of letter boxes
@ -124,6 +128,14 @@ Profile may generate multiple outputs from a single input. Each output must defi
![global_extract_review_letter_box_settings](assets/global_extract_review_letter_box_settings.png)
![global_extract_review_letter_box](assets/global_extract_review_letter_box.png)
- **`Background color`**
- Background color can be used for inputs with possible transparency (e.g. png sequence).
- Input's without possible alpha channel are ignored all the time (e.g. mov).
- Background color slows down rendering process.
- set alpha to `0` to not use this option at all (in most of cases background stays black)
- other than `0` alpha will draw color as background
### IntegrateAssetNew
Saves information for all published subsets into DB, published assets are available for other hosts, tools and tasks after.
@ -160,6 +172,39 @@ Applicable context filters:
## Tools
Settings for OpenPype tools.
## Creator
Settings related to [Creator tool](artist_tools.md#details).
### Subset name profiles
![global_tools_creator_subset_template](assets/global_tools_creator_subset_template.png)
Subset name helps to identify published content. More specific name helps with organization and avoid mixing of published content. Subset name is defined using one of templates defined in **Subset name profiles settings**. The template is filled with context information at the time of creation.
Usage of template is defined by profile filtering using creator's family, host and task name. Profile without filters is used as default template and it is recommend to set default template. If default template is not available `"{family}{Task}"` is used.
**Formatting keys**
All templates can contain text and formatting keys **family**, **task** and **variant** e.g. `"MyStudio_{family}_{task}"` (example - not recommended in production).
|Key|Description|
|---|---|
|family|Creators family|
|task|Task under which is creation triggered|
|variant|User input in creator tool|
**Formatting keys have 3 variants with different letter capitalization.**
|Task|Key variant|Description|Result|
|---|---|---|---|
|`bgAnim`|`{task}`|Keep original value as is.|`bgAnim`|
|`bgAnim`|`{Task}`|Capitalize first letter of value.|`BgAnim`|
|`bgAnim`|`{TASK}`|Each letter which be capitalized.|`BGANIM`|
Template may look like `"{family}{Task}{Variant}"`.
Some creators may have other keys as their context may require more information or more specific values. Make sure you've read documentation of host you're using.
## Workfiles
All settings related to Workfile tool.

View file

@ -90,7 +90,8 @@ module.exports = {
"admin_hosts_maya",
"admin_hosts_resolve",
"admin_hosts_harmony",
"admin_hosts_aftereffects"
"admin_hosts_aftereffects",
"admin_hosts_tvpaint"
],
},
{