Merge branch 'develop' of github.com:pypeclub/OpenPype into chore/add_validate_frame_range_to_traypublisher

This commit is contained in:
Petr Kalis 2022-07-18 10:57:46 +02:00
commit b32f153a9c
12 changed files with 199 additions and 66 deletions

View file

@ -1,8 +1,35 @@
# Changelog
## [3.12.2-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD)
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.12.1...HEAD)
**🚀 Enhancements**
- Ftrack: Automatic daily review session creation can define trigger hour [\#3516](https://github.com/pypeclub/OpenPype/pull/3516)
- Ftrack: add source into Note [\#3509](https://github.com/pypeclub/OpenPype/pull/3509)
- Ftrack: Trigger custom ftrack topic of project structure creation [\#3506](https://github.com/pypeclub/OpenPype/pull/3506)
- Settings UI: Add extract to file action on project view [\#3505](https://github.com/pypeclub/OpenPype/pull/3505)
- General: Event system [\#3499](https://github.com/pypeclub/OpenPype/pull/3499)
- NewPublisher: Keep plugins with mismatch target in report [\#3498](https://github.com/pypeclub/OpenPype/pull/3498)
- Nuke: load clip with options from settings [\#3497](https://github.com/pypeclub/OpenPype/pull/3497)
- Migrate basic families to the new Tray Publisher [\#3469](https://github.com/pypeclub/OpenPype/pull/3469)
**🐛 Bug fixes**
- General: Fix hash of centos oiio archive [\#3519](https://github.com/pypeclub/OpenPype/pull/3519)
- TrayPublisher: Simple creation enhancements and fixes [\#3513](https://github.com/pypeclub/OpenPype/pull/3513)
- NewPublisher: Publish attributes are properly collected [\#3510](https://github.com/pypeclub/OpenPype/pull/3510)
- TrayPublisher: Make sure host name is filled [\#3504](https://github.com/pypeclub/OpenPype/pull/3504)
- NewPublisher: Groups work and enum multivalue [\#3501](https://github.com/pypeclub/OpenPype/pull/3501)
**🔀 Refactored code**
- TimersManager: Use query functions [\#3495](https://github.com/pypeclub/OpenPype/pull/3495)
## [3.12.1](https://github.com/pypeclub/OpenPype/tree/3.12.1) (2022-07-13)
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.12.0...3.12.1)
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.12.1-nightly.6...3.12.1)
### 📖 Documentation
@ -45,8 +72,6 @@
- LogViewer: Escape html characters in log message [\#3443](https://github.com/pypeclub/OpenPype/pull/3443)
- Nuke: Slate frame is integrated [\#3427](https://github.com/pypeclub/OpenPype/pull/3427)
- Maya: Camera extra data - additional fix for \#3304 [\#3386](https://github.com/pypeclub/OpenPype/pull/3386)
- Maya: Handle excluding `model` family from frame range validator. [\#3370](https://github.com/pypeclub/OpenPype/pull/3370)
- Harmony: audio validator has wrong logic [\#3364](https://github.com/pypeclub/OpenPype/pull/3364)
**🔀 Refactored code**
@ -76,7 +101,6 @@
- Webserver: Added CORS middleware [\#3422](https://github.com/pypeclub/OpenPype/pull/3422)
- Attribute Defs UI: Files widget show what is allowed to drop in [\#3411](https://github.com/pypeclub/OpenPype/pull/3411)
- General: Add ability to change user value for templates [\#3366](https://github.com/pypeclub/OpenPype/pull/3366)
**🐛 Bug fixes**
@ -87,10 +111,7 @@
- Nuke: Collect representation files based on Write [\#3407](https://github.com/pypeclub/OpenPype/pull/3407)
- General: Filter representations before integration start [\#3398](https://github.com/pypeclub/OpenPype/pull/3398)
- Maya: look collector typo [\#3392](https://github.com/pypeclub/OpenPype/pull/3392)
- TVPaint: Make sure exit code is set to not None [\#3382](https://github.com/pypeclub/OpenPype/pull/3382)
- Maya: vray device aspect ratio fix [\#3381](https://github.com/pypeclub/OpenPype/pull/3381)
- Flame: bunch of publishing issues [\#3377](https://github.com/pypeclub/OpenPype/pull/3377)
- Harmony: added unc path to zifile command in Harmony [\#3372](https://github.com/pypeclub/OpenPype/pull/3372)
**🔀 Refactored code**
@ -101,30 +122,11 @@
- Hiero: Use client query functions [\#3393](https://github.com/pypeclub/OpenPype/pull/3393)
- Nuke: Use client query functions [\#3391](https://github.com/pypeclub/OpenPype/pull/3391)
- Maya: Use client query functions [\#3385](https://github.com/pypeclub/OpenPype/pull/3385)
- Harmony: Use client query functions [\#3378](https://github.com/pypeclub/OpenPype/pull/3378)
- Celaction: Use client query functions [\#3376](https://github.com/pypeclub/OpenPype/pull/3376)
- Photoshop: Use client query functions [\#3375](https://github.com/pypeclub/OpenPype/pull/3375)
- AfterEffects: Use client query functions [\#3374](https://github.com/pypeclub/OpenPype/pull/3374)
**Merged pull requests:**
- Sync Queue: Added far future value for null values for dates [\#3371](https://github.com/pypeclub/OpenPype/pull/3371)
- Maya - added support for single frame playblast review [\#3369](https://github.com/pypeclub/OpenPype/pull/3369)
## [3.11.1](https://github.com/pypeclub/OpenPype/tree/3.11.1) (2022-06-20)
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.11.1-nightly.1...3.11.1)
**🚀 Enhancements**
- Pyblish Pype: Hiding/Close issues [\#3367](https://github.com/pypeclub/OpenPype/pull/3367)
**🐛 Bug fixes**
- Nuke: bake streams with slate on farm [\#3368](https://github.com/pypeclub/OpenPype/pull/3368)
- Nuke: Fix missing variable in extract thumbnail [\#3363](https://github.com/pypeclub/OpenPype/pull/3363)
- Nuke: Fix precollect writes [\#3361](https://github.com/pypeclub/OpenPype/pull/3361)
## [3.11.0](https://github.com/pypeclub/OpenPype/tree/3.11.0) (2022-06-17)
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.11.0-nightly.4...3.11.0)

View file

@ -6,7 +6,10 @@ import collections
import ftrack_api
from openpype.lib import get_datetime_data
from openpype.api import get_project_settings
from openpype.settings.lib import (
get_project_settings,
get_default_project_settings
)
from openpype_modules.ftrack.lib import ServerAction
@ -79,6 +82,35 @@ class CreateDailyReviewSessionServerAction(ServerAction):
)
return True
def _calculate_next_cycle_delta(self):
studio_default_settings = get_default_project_settings()
action_settings = (
studio_default_settings
["ftrack"]
[self.settings_frack_subkey]
[self.settings_key]
)
cycle_hour_start = action_settings.get("cycle_hour_start")
if not cycle_hour_start:
h = m = s = 0
else:
h, m, s = cycle_hour_start
# Create threading timer which will trigger creation of report
# at the 00:00:01 of next day
# - callback will trigger another timer which will have 1 day offset
now = datetime.datetime.now()
# Create object of today morning
expected_next_trigger = datetime.datetime(
now.year, now.month, now.day, h, m, s
)
if expected_next_trigger > now:
seconds = (expected_next_trigger - now).total_seconds()
else:
expected_next_trigger += self._day_delta
seconds = (expected_next_trigger - now).total_seconds()
return seconds, expected_next_trigger
def register(self, *args, **kwargs):
"""Override register to be able trigger """
# Register server action as would be normally
@ -86,22 +118,14 @@ class CreateDailyReviewSessionServerAction(ServerAction):
*args, **kwargs
)
# Create threading timer which will trigger creation of report
# at the 00:00:01 of next day
# - callback will trigger another timer which will have 1 day offset
now = datetime.datetime.now()
# Create object of today morning
today_morning = datetime.datetime(
now.year, now.month, now.day, 0, 0, 1
)
# Add a day delta (to calculate next day date)
next_day_morning = today_morning + self._day_delta
# Calculate first delta in seconds for first threading timer
first_delta = (next_day_morning - now).total_seconds()
seconds_delta, cycle_time = self._calculate_next_cycle_delta()
# Store cycle time which will be used to create next timer
self._last_cyle_time = next_day_morning
self._last_cyle_time = cycle_time
# Create timer thread
self._cycle_timer = threading.Timer(first_delta, self._timer_callback)
self._cycle_timer = threading.Timer(
seconds_delta, self._timer_callback
)
self._cycle_timer.start()
self._check_review_session()
@ -111,13 +135,12 @@ class CreateDailyReviewSessionServerAction(ServerAction):
self._cycle_timer is not None
and self._last_cyle_time is not None
):
now = datetime.datetime.now()
while self._last_cyle_time < now:
self._last_cyle_time = self._last_cyle_time + self._day_delta
seconds_delta, cycle_time = self._calculate_next_cycle_delta()
self._last_cyle_time = cycle_time
delay = (self._last_cyle_time - now).total_seconds()
self._cycle_timer = threading.Timer(delay, self._timer_callback)
self._cycle_timer = threading.Timer(
seconds_delta, self._timer_callback
)
self._cycle_timer.start()
self._check_review_session()

View file

@ -22,7 +22,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
"imagesequence", "render", "render2d", "prerender",
"source", "plate", "take"
]
hosts = ["shell", "fusion", "resolve"]
hosts = ["shell", "fusion", "resolve", "traypublisher"]
enabled = False
# presetable attribute
@ -46,6 +46,10 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
self.log.info("Skipping - no review set on instance.")
return
if self._already_has_thumbnail(instance):
self.log.info("Thumbnail representation already present.")
return
filtered_repres = self._get_filtered_repres(instance)
for repre in filtered_repres:
repre_files = repre["files"]
@ -102,6 +106,14 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
# There is no need to create more then one thumbnail
break
def _already_has_thumbnail(self, instance):
for repre in instance.data.get("representations", []):
self.log.info("repre {}".format(repre))
if repre["name"] == "thumbnail":
return True
return False
def _get_filtered_repres(self, instance):
filtered_repres = []
src_repres = instance.data.get("representations") or []

View file

@ -124,6 +124,11 @@
"Project Manager"
],
"cycle_enabled": false,
"cycle_hour_start": [
0,
0,
0
],
"review_session_template": "{yy}{mm}{dd}"
}
},

View file

@ -410,7 +410,41 @@
{
"type": "boolean",
"key": "cycle_enabled",
"label": "Create daily review session"
"label": "Run automatically every day"
},
{
"type": "separator"
},
{
"type": "list-strict",
"key": "cycle_hour_start",
"label": "Create daily review session at",
"tooltip": "This may take affect on next day",
"object_types": [
{
"label": "H:",
"type": "number",
"minimum": 0,
"maximum": 23,
"decimal": 0
}, {
"label": "M:",
"type": "number",
"minimum": 0,
"maximum": 59,
"decimal": 0
}, {
"label": "S:",
"type": "number",
"minimum": 0,
"maximum": 59,
"decimal": 0
}
]
},
{
"type": "label",
"label": "This can't be overriden per project and any change will take effect on the next day or on restart of event server."
},
{
"type": "separator"

View file

@ -98,6 +98,7 @@ class GroupWidget(QtWidgets.QWidget):
instances(list<CreatedInstance>): List of instances in
CreateContext.
"""
# Store instances by id and by subset name
instances_by_id = {}
instances_by_subset_name = collections.defaultdict(list)
@ -142,6 +143,7 @@ class GroupWidget(QtWidgets.QWidget):
class CardWidget(BaseClickableFrame):
"""Clickable card used as bigger button."""
selected = QtCore.Signal(str, str)
# Group identifier of card
# - this must be set because if send when mouse is released with card id
@ -178,6 +180,7 @@ class ContextCardWidget(CardWidget):
Is not visually under group widget and is always at the top of card view.
"""
def __init__(self, parent):
super(ContextCardWidget, self).__init__(parent)
@ -204,13 +207,14 @@ class ContextCardWidget(CardWidget):
class InstanceCardWidget(CardWidget):
"""Card widget representing instance."""
active_changed = QtCore.Signal()
def __init__(self, instance, group_icon, parent):
super(InstanceCardWidget, self).__init__(parent)
self._id = instance.id
self._group_identifier = instance.creator_label
self._group_identifier = instance.group_label
self._group_icon = group_icon
self.instance = instance

View file

@ -1,4 +1,5 @@
from .widgets import (
CustomTextComboBox,
PlaceholderLineEdit,
BaseClickableFrame,
ClickableFrame,
@ -28,6 +29,7 @@ from .overlay_messages import (
__all__ = (
"CustomTextComboBox",
"PlaceholderLineEdit",
"BaseClickableFrame",
"ClickableFrame",

View file

@ -11,6 +11,28 @@ from openpype.style import (
log = logging.getLogger(__name__)
class CustomTextComboBox(QtWidgets.QComboBox):
"""Combobox which can have different text showed."""
def __init__(self, *args, **kwargs):
self._custom_text = None
super(CustomTextComboBox, self).__init__(*args, **kwargs)
def set_custom_text(self, text=None):
if self._custom_text != text:
self._custom_text = text
self.repaint()
def paintEvent(self, event):
painter = QtWidgets.QStylePainter(self)
option = QtWidgets.QStyleOptionComboBox()
self.initStyleOption(option)
if self._custom_text is not None:
option.currentText = self._custom_text
painter.drawComplexControl(QtWidgets.QStyle.CC_ComboBox, option)
painter.drawControl(QtWidgets.QStyle.CE_ComboBoxLabel, option)
class PlaceholderLineEdit(QtWidgets.QLineEdit):
"""Set placeholder color of QLineEdit in Qt 5.12 and higher."""
def __init__(self, *args, **kwargs):

View file

@ -403,7 +403,7 @@ def apply_view(panel, **options):
camera_options = options.get("camera_options", {})
_iteritems = getattr(camera_options, "iteritems", camera_options.items)
for key, value in _iteritems:
cmds.setAttr("{0}.{1}".format(camera, key), value)
_safe_setAttr("{0}.{1}".format(camera, key), value)
# Viewport options
viewport_options = options.get("viewport_options", {})
@ -417,7 +417,7 @@ def apply_view(panel, **options):
)
for key, value in _iteritems():
attr = "hardwareRenderingGlobals.{0}".format(key)
cmds.setAttr(attr, value)
_safe_setAttr(attr, value)
def parse_active_panel():
@ -551,10 +551,10 @@ def apply_scene(**options):
cmds.playbackOptions(maxTime=options["end_frame"])
if "width" in options:
cmds.setAttr("defaultResolution.width", options["width"])
_safe_setAttr("defaultResolution.width", options["width"])
if "height" in options:
cmds.setAttr("defaultResolution.height", options["height"])
_safe_setAttr("defaultResolution.height", options["height"])
if "compression" in options:
cmds.optionVar(
@ -665,7 +665,7 @@ def _applied_camera_options(options, panel):
_iteritems = getattr(options, "iteritems", options.items)
for opt, value in _iteritems():
cmds.setAttr(camera + "." + opt, value)
_safe_setAttr(camera + "." + opt, value)
try:
yield
@ -673,7 +673,7 @@ def _applied_camera_options(options, panel):
if old_options:
_iteritems = getattr(old_options, "iteritems", old_options.items)
for opt, value in _iteritems():
cmds.setAttr(camera + "." + opt, value)
_safe_setAttr(camera + "." + opt, value)
@contextlib.contextmanager
@ -760,7 +760,7 @@ def _applied_viewport2_options(options):
# Apply settings
_iteritems = getattr(options, "iteritems", options.items)
for opt, value in _iteritems():
cmds.setAttr("hardwareRenderingGlobals." + opt, value)
_safe_setAttr("hardwareRenderingGlobals." + opt, value)
try:
yield
@ -768,7 +768,7 @@ def _applied_viewport2_options(options):
# Restore previous settings
_iteritems = getattr(original, "iteritems", original.items)
for opt, value in _iteritems():
cmds.setAttr("hardwareRenderingGlobals." + opt, value)
_safe_setAttr("hardwareRenderingGlobals." + opt, value)
@contextlib.contextmanager
@ -802,14 +802,14 @@ def _maintain_camera(panel, camera):
else:
state = dict((camera, cmds.getAttr(camera + ".rnd"))
for camera in cmds.ls(type="camera"))
cmds.setAttr(camera + ".rnd", True)
_safe_setAttr(camera + ".rnd", True)
try:
yield
finally:
_iteritems = getattr(state, "iteritems", state.items)
for camera, renderable in _iteritems():
cmds.setAttr(camera + ".rnd", renderable)
_safe_setAttr(camera + ".rnd", renderable)
@contextlib.contextmanager
@ -846,6 +846,18 @@ def _in_standalone():
return not hasattr(cmds, "about") or cmds.about(batch=True)
def _safe_setAttr(*args, **kwargs):
"""Wrapper to handle failures when attribute is locked.
Temporary hotfix until better approach (store value, unlock, set new,
return old, lock again) is implemented.
"""
try:
cmds.setAttr(*args, **kwargs)
except RuntimeError:
print("Cannot setAttr {}!".format(args))
# --------------------------------
#
# Apply version specific settings

View file

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

View file

@ -15,6 +15,7 @@ from openpype.lib.attribute_definitions import (
UISeparatorDef,
UILabelDef
)
from openpype.tools.utils import CustomTextComboBox
from openpype.widgets.nice_checkbox import NiceCheckbox
from .files_widget import FilesWidget
@ -369,8 +370,12 @@ class BoolAttrWidget(_BaseAttrDefWidget):
class EnumAttrWidget(_BaseAttrDefWidget):
def __init__(self, *args, **kwargs):
self._multivalue = False
super(EnumAttrWidget, self).__init__(*args, **kwargs)
def _ui_init(self):
input_widget = QtWidgets.QComboBox(self)
input_widget = CustomTextComboBox(self)
combo_delegate = QtWidgets.QStyledItemDelegate(input_widget)
input_widget.setItemDelegate(combo_delegate)
@ -394,6 +399,9 @@ class EnumAttrWidget(_BaseAttrDefWidget):
def _on_value_change(self):
new_value = self.current_value()
if self._multivalue:
self._multivalue = False
self._input_widget.set_custom_text(None)
self.value_changed.emit(new_value, self.attr_def.id)
def current_value(self):
@ -401,14 +409,23 @@ class EnumAttrWidget(_BaseAttrDefWidget):
return self._input_widget.itemData(idx)
def set_value(self, value, multivalue=False):
if multivalue:
set_value = set(value)
if len(set_value) == 1:
multivalue = False
value = tuple(set_value)[0]
if not multivalue:
idx = self._input_widget.findData(value)
cur_idx = self._input_widget.currentIndex()
if idx != cur_idx and idx >= 0:
self._input_widget.setCurrentIndex(idx)
else:
self._input_widget.lineEdit().setText("Multiselection")
custom_text = None
if multivalue:
custom_text = "< Multiselection >"
self._input_widget.set_custom_text(custom_text)
self._multivalue = multivalue
class UnknownAttrWidget(_BaseAttrDefWidget):

View file

@ -1,6 +1,6 @@
[tool.poetry]
name = "OpenPype"
version = "3.12.1" # OpenPype
version = "3.12.2-nightly.1" # OpenPype
description = "Open VFX and Animation pipeline with support."
authors = ["OpenPype Team <info@openpype.io>"]
license = "MIT License"
@ -135,7 +135,7 @@ hash = "b9950f5d2fa3720b52b8be55bacf5f56d33f9e029d38ee86534995f3d8d253d2"
[openpype.thirdparty.oiio.linux]
url = "https://distribute.openpype.io/thirdparty/oiio_tools-2.2.20-linux-centos7.tgz"
hash = "be1abf8a50e9da5913298447421af0a17829d83ed6252ae1d40da7fa36a78787"
hash = "3894dec7e4e521463891a869586850e8605f5fd604858b674c87323bf33e273d"
[openpype.thirdparty.oiio.darwin]
url = "https://distribute.openpype.io/thirdparty/oiio-2.2.0-darwin.tgz"