Merge branch 'develop' into bugfix/unreal-on-linux

This commit is contained in:
Ondřej Samohel 2021-06-21 11:03:38 +02:00
commit afc48fe7a5
No known key found for this signature in database
GPG key ID: 02376E18990A97C6
15 changed files with 302 additions and 122 deletions

View file

@ -1,8 +1,59 @@
# Changelog
## [3.2.0-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD)
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/2.18.3...HEAD)
**🚀 Enhancements**
- Autoupdate launcher [\#1725](https://github.com/pypeclub/OpenPype/pull/1725)
- Subset template and TVPaint subset template docs [\#1717](https://github.com/pypeclub/OpenPype/pull/1717)
- Overscan color extract review [\#1701](https://github.com/pypeclub/OpenPype/pull/1701)
- Nuke: Prerender Frame Range by default [\#1699](https://github.com/pypeclub/OpenPype/pull/1699)
- Smoother edges of color triangle [\#1695](https://github.com/pypeclub/OpenPype/pull/1695)
**🐛 Bug fixes**
- TVPaint use layer name for default variant [\#1724](https://github.com/pypeclub/OpenPype/pull/1724)
- Default subset template for TVPaint review and workfile families [\#1716](https://github.com/pypeclub/OpenPype/pull/1716)
- Maya: Extract review hotfix [\#1714](https://github.com/pypeclub/OpenPype/pull/1714)
- Settings: Imageio improving granularity [\#1711](https://github.com/pypeclub/OpenPype/pull/1711)
## [2.18.3](https://github.com/pypeclub/OpenPype/tree/2.18.3) (2021-06-18)
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/2.18.2...2.18.3)
**🐛 Bug fixes**
- Remove publish highlight icon in AE [\#1664](https://github.com/pypeclub/OpenPype/pull/1664)
**Merged pull requests:**
- Sync main 2.x back to 2.x develop [\#1715](https://github.com/pypeclub/OpenPype/pull/1715)
- global: removing obsolete ftrack validator plugin [\#1710](https://github.com/pypeclub/OpenPype/pull/1710)
- \#683 - Validate frame range in Standalone Publisher [\#1680](https://github.com/pypeclub/OpenPype/pull/1680)
- Maya: Split model content validator [\#1654](https://github.com/pypeclub/OpenPype/pull/1654)
## [2.18.2](https://github.com/pypeclub/OpenPype/tree/2.18.2) (2021-06-16)
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.1.0...2.18.2)
**🚀 Enhancements**
- StandalonePublisher: adding exception for adding `delete` tag to repre [\#1650](https://github.com/pypeclub/OpenPype/pull/1650)
**🐛 Bug fixes**
- Maya: Extract review hotfix - 2.x backport [\#1713](https://github.com/pypeclub/OpenPype/pull/1713)
- StandalonePublisher: instance data attribute `keepSequence` [\#1668](https://github.com/pypeclub/OpenPype/pull/1668)
**Merged pull requests:**
- 1698 Nuke: Prerender Frame Range by default [\#1709](https://github.com/pypeclub/OpenPype/pull/1709)
## [3.1.0](https://github.com/pypeclub/OpenPype/tree/3.1.0) (2021-06-15)
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.0.0...3.1.0)
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.1.0-nightly.4...3.1.0)
**🚀 Enhancements**
@ -33,6 +84,7 @@
- Fix missing dbm python module [\#1652](https://github.com/pypeclub/OpenPype/pull/1652)
- Transparent branches in view on Mac [\#1648](https://github.com/pypeclub/OpenPype/pull/1648)
- Add asset on task item [\#1646](https://github.com/pypeclub/OpenPype/pull/1646)
- Project manager save and queue [\#1645](https://github.com/pypeclub/OpenPype/pull/1645)
- New project anatomy values [\#1644](https://github.com/pypeclub/OpenPype/pull/1644)
**Merged pull requests:**

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

@ -232,6 +232,17 @@
],
"tasks": [],
"template": "{family}{Task}_{Render_layer}_{Render_pass}"
},
{
"families": [
"review",
"workfile"
],
"hosts": [
"tvpaint"
],
"tasks": [],
"template": "{family}{Task}"
}
]
},

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

@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
"""Package declaring Pype version."""
__version__ = "3.1.0"
__version__ = "3.2.0-nightly.1"

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

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

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

After

Width:  |  Height:  |  Size: 17 KiB

View file

@ -172,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"
],
},
{