From cc602f1da0829aaa7226a1d1dc9c36111464fc7c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sat, 26 Mar 2022 14:37:01 +0100 Subject: [PATCH 01/25] added implementation of overlay messages --- openpype/tools/utils/overlay_messages.py | 315 +++++++++++++++++++++++ 1 file changed, 315 insertions(+) create mode 100644 openpype/tools/utils/overlay_messages.py diff --git a/openpype/tools/utils/overlay_messages.py b/openpype/tools/utils/overlay_messages.py new file mode 100644 index 0000000000..ade037817a --- /dev/null +++ b/openpype/tools/utils/overlay_messages.py @@ -0,0 +1,315 @@ +import uuid + +from Qt import QtWidgets, QtCore, QtGui + +from .lib import set_style_property + + +class CloseButton(QtWidgets.QFrame): + """Close button drawed manually.""" + + clicked = QtCore.Signal() + + def __init__(self, parent): + super(CloseButton, self).__init__(parent) + self._mouse_pressed = False + policy = QtWidgets.QSizePolicy( + QtWidgets.QSizePolicy.Fixed, + QtWidgets.QSizePolicy.Fixed + ) + self.setSizePolicy(policy) + + def sizeHint(self): + size = self.fontMetrics().height() + return QtCore.QSize(size, size) + + def mousePressEvent(self, event): + if event.button() == QtCore.Qt.LeftButton: + self._mouse_pressed = True + super(CloseButton, self).mousePressEvent(event) + + def mouseReleaseEvent(self, event): + if self._mouse_pressed: + self._mouse_pressed = False + if self.rect().contains(event.pos()): + self.clicked.emit() + + super(CloseButton, self).mouseReleaseEvent(event) + + def paintEvent(self, event): + rect = self.rect() + painter = QtGui.QPainter(self) + painter.setClipRect(event.rect()) + pen = QtGui.QPen() + pen.setWidth(2) + pen.setColor(QtGui.QColor(255, 255, 255)) + pen.setStyle(QtCore.Qt.SolidLine) + pen.setCapStyle(QtCore.Qt.RoundCap) + painter.setPen(pen) + offset = int(rect.height() / 4) + top = rect.top() + offset + left = rect.left() + offset + right = rect.right() - offset + bottom = rect.bottom() - offset + painter.drawLine( + left, top, + right, bottom + ) + painter.drawLine( + left, bottom, + right, top + ) + + +class MessageWidget(QtWidgets.QFrame): + """Message widget showed as overlay. + + Message is hidden after timeout but can be overriden by mouse hover. + Mouse hover can add additional 2 seconds of widget's visibility. + + Args: + message_id (str): Unique identifier of message widget for + 'MessageOverlayObject'. + message (str): Text shown in message. + parent (QWidget): Parent widget where message is visible. + timeout (int): Timeout of message's visibility (default 5000). + message_type (str): Property which can be used in styles for specific + kid of message. + """ + + close_requested = QtCore.Signal(str) + _default_timeout = 5000 + + def __init__( + self, message_id, message, parent, timeout=None, message_type=None + ): + super(MessageWidget, self).__init__(parent) + self.setObjectName("OverlayMessageWidget") + + if message_type: + set_style_property(self, "type", message_type) + + if not timeout: + timeout = self._default_timeout + timeout_timer = QtCore.QTimer() + timeout_timer.setInterval(timeout) + timeout_timer.setSingleShot(True) + + hover_timer = QtCore.QTimer() + hover_timer.setInterval(2000) + hover_timer.setSingleShot(True) + + label_widget = QtWidgets.QLabel(message, self) + label_widget.setAlignment(QtCore.Qt.AlignCenter) + label_widget.setWordWrap(True) + close_btn = CloseButton(self) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(5, 5, 0, 5) + layout.addWidget(label_widget, 1) + layout.addWidget(close_btn, 0) + + close_btn.clicked.connect(self._on_close_clicked) + timeout_timer.timeout.connect(self._on_timer_timeout) + hover_timer.timeout.connect(self._on_hover_timeout) + + self._label_widget = label_widget + self._message_id = message_id + self._timeout_timer = timeout_timer + self._hover_timer = hover_timer + + def size_hint_without_word_wrap(self): + """Size hint in cases that word wrap of label is disabled.""" + self._label_widget.setWordWrap(False) + size_hint = self.sizeHint() + self._label_widget.setWordWrap(True) + return size_hint + + def showEvent(self, event): + """Start timeout on show.""" + super(MessageWidget, self).showEvent(event) + self._timeout_timer.start() + + def _on_timer_timeout(self): + """On message timeout.""" + # Skip closing if hover timer is active + if not self._hover_timer.isActive(): + self._close_message() + + def _on_hover_timeout(self): + """Hover timer timed out.""" + # Check if is still under widget + if self.underMouse(): + self._hover_timer.start() + else: + self._close_message() + + def _on_close_clicked(self): + self._close_message() + + def _close_message(self): + """Emmit close request to 'MessageOverlayObject'.""" + self.close_requested.emit(self._message_id) + + def enterEvent(self, event): + """Start hover timer on hover.""" + super(MessageWidget, self).enterEvent(event) + self._hover_timer.start() + + def leaveEvent(self, event): + """Start hover timer on hover leave.""" + super(MessageWidget, self).leaveEvent(event) + self._hover_timer.start() + + +class MessageOverlayObject(QtCore.QObject): + """Object that can be used to add overlay messages. + + Args: + widget (QWidget): + """ + + def __init__(self, widget): + super(MessageOverlayObject, self).__init__() + + widget.installEventFilter(self) + + # Timer which triggers recalculation of message positions + recalculate_timer = QtCore.QTimer() + recalculate_timer.setInterval(10) + + recalculate_timer.timeout.connect(self._recalculate_positions) + + self._widget = widget + self._recalculate_timer = recalculate_timer + + self._messages_order = [] + self._closing_messages = set() + self._messages = {} + self._spacing = 5 + self._move_size = 4 + self._move_size_remove = 8 + + def add_message(self, message, timeout=None, message_type=None): + """Add single message into overlay. + + Args: + message (str): Message that will be shown. + timeout (int): Message timeout. + message_type (str): Message type can be used as property in + stylesheets. + """ + # Skip empty messages + if not message: + return + + # Create unique id of message + label_id = str(uuid.uuid4()) + # Create message widget + widget = MessageWidget( + label_id, message, self._widget, timeout, message_type + ) + widget.close_requested.connect(self._on_message_close_request) + widget.show() + + # Move widget outside of window + pos = widget.pos() + pos.setY(pos.y() - widget.height()) + widget.move(pos) + # Store message + self._messages[label_id] = widget + self._messages_order.append(label_id) + # Trigger recalculation timer + self._recalculate_timer.start() + + def _on_message_close_request(self, label_id): + """Message widget requested removement.""" + + widget = self._messages.get(label_id) + if widget is not None: + # Add message to closing messages and start recalculation + self._closing_messages.add(label_id) + self._recalculate_timer.start() + + def _recalculate_positions(self): + """Recalculate positions of widgets.""" + + # Skip if there are no messages to process + if not self._messages_order: + self._recalculate_timer.stop() + return + + # All message widgets are in expected positions + all_at_place = True + # Starting y position + pos_y = self._spacing + # Current widget width + widget_width = self._widget.width() + max_width = widget_width - (2 * self._spacing) + widget_half_width = widget_width / 2 + + # Store message ids that should be removed + message_ids_to_remove = set() + for message_id in reversed(self._messages_order): + widget = self._messages[message_id] + pos = widget.pos() + # Messages to remove are moved upwards + if message_id in self._closing_messages: + bottom = pos.y() + widget.height() + # Add message to remove if is not visible + if bottom < 0 or self._move_size_remove < 1: + message_ids_to_remove.add(message_id) + continue + + # Calculate new y position of message + dst_pos_y = pos.y() - self._move_size_remove + + else: + # Calculate y position of message + # - use y position of previous message widget and add + # move size if is not in final destination yet + if widget.underMouse(): + dst_pos_y = pos.y() + elif pos.y() == pos_y or self._move_size < 1: + dst_pos_y = pos_y + elif pos.y() < pos_y: + dst_pos_y = min(pos_y, pos.y() + self._move_size) + else: + dst_pos_y = max(pos_y, pos.y() - self._move_size) + + # Store if widget is in place where should be + if all_at_place and dst_pos_y != pos_y: + all_at_place = False + + # Calculate ideal width and height of message widget + height = widget.heightForWidth(max_width) + w_size_hint = widget.size_hint_without_word_wrap() + widget.resize(min(max_width, w_size_hint.width()), height) + + # Center message widget + size = widget.size() + pos_x = widget_half_width - (size.width() / 2) + # Move widget to destination position + widget.move(pos_x, dst_pos_y) + + # Add message widget height and spacing for next message widget + pos_y += size.height() + self._spacing + + # Remove widgets to remove + for message_id in message_ids_to_remove: + self._messages_order.remove(message_id) + self._closing_messages.remove(message_id) + widget = self._messages.pop(message_id) + widget.hide() + widget.deleteLater() + + # Stop recalculation timer if all widgets are where should be + if all_at_place: + self._recalculate_timer.stop() + + def eventFilter(self, source, event): + # Trigger recalculation of timer on resize of widget + if source is self._widget and event.type() == QtCore.QEvent.Resize: + self._recalculate_timer.start() + + return super(MessageOverlayObject, self).eventFilter(source, event) From 8bc010a4f409a66f5536fc8bdc39dd4094dee05d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sat, 26 Mar 2022 14:54:08 +0100 Subject: [PATCH 02/25] define default styles for overlay messages --- openpype/style/data.json | 6 +++++- openpype/style/style.css | 20 +++++++++++++++++++ openpype/tools/utils/overlay_messages.py | 25 ++++++++++++++---------- 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/openpype/style/data.json b/openpype/style/data.json index a76a77015b..15d9472e3e 100644 --- a/openpype/style/data.json +++ b/openpype/style/data.json @@ -61,7 +61,11 @@ "icon-entity-default": "#bfccd6", "icon-entity-disabled": "#808080", "font-entity-deprecated": "#666666", - + "overlay-messages": { + "close-btn": "#D3D8DE", + "bg-success": "#458056", + "bg-success-hover": "#55a066" + }, "tab-widget": { "bg": "#21252B", "bg-selected": "#434a56", diff --git a/openpype/style/style.css b/openpype/style/style.css index df83600973..4d83e39780 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -687,6 +687,26 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background: none; } +/* Messages overlay */ +#OverlayMessageWidget { + border-radius: 0.2em; + background: {color:bg-buttons}; +} + +#OverlayMessageWidget:hover { + background: {color:bg-button-hover}; +} +#OverlayMessageWidget[type="success"] { + background: {color:overlay-messages:bg-success}; +} +#OverlayMessageWidget[type="success"]:hover { + background: {color:overlay-messages:bg-success-hover}; +} + +#OverlayMessageWidget QWidget { + background: transparent; +} + /* Password dialog*/ #PasswordBtn { border: none; diff --git a/openpype/tools/utils/overlay_messages.py b/openpype/tools/utils/overlay_messages.py index ade037817a..93082b9fb7 100644 --- a/openpype/tools/utils/overlay_messages.py +++ b/openpype/tools/utils/overlay_messages.py @@ -2,6 +2,8 @@ import uuid from Qt import QtWidgets, QtCore, QtGui +from openpype.style import get_objected_colors + from .lib import set_style_property @@ -12,6 +14,9 @@ class CloseButton(QtWidgets.QFrame): def __init__(self, parent): super(CloseButton, self).__init__(parent) + colors = get_objected_colors() + close_btn_color = colors["overlay-messages"]["close-btn"] + self._color = close_btn_color.get_qcolor() self._mouse_pressed = False policy = QtWidgets.QSizePolicy( QtWidgets.QSizePolicy.Fixed, @@ -42,7 +47,7 @@ class CloseButton(QtWidgets.QFrame): painter.setClipRect(event.rect()) pen = QtGui.QPen() pen.setWidth(2) - pen.setColor(QtGui.QColor(255, 255, 255)) + pen.setColor(self._color) pen.setStyle(QtCore.Qt.SolidLine) pen.setCapStyle(QtCore.Qt.RoundCap) painter.setPen(pen) @@ -61,7 +66,7 @@ class CloseButton(QtWidgets.QFrame): ) -class MessageWidget(QtWidgets.QFrame): +class OverlayMessageWidget(QtWidgets.QFrame): """Message widget showed as overlay. Message is hidden after timeout but can be overriden by mouse hover. @@ -81,9 +86,9 @@ class MessageWidget(QtWidgets.QFrame): _default_timeout = 5000 def __init__( - self, message_id, message, parent, timeout=None, message_type=None + self, message_id, message, parent, message_type=None, timeout=None ): - super(MessageWidget, self).__init__(parent) + super(OverlayMessageWidget, self).__init__(parent) self.setObjectName("OverlayMessageWidget") if message_type: @@ -127,7 +132,7 @@ class MessageWidget(QtWidgets.QFrame): def showEvent(self, event): """Start timeout on show.""" - super(MessageWidget, self).showEvent(event) + super(OverlayMessageWidget, self).showEvent(event) self._timeout_timer.start() def _on_timer_timeout(self): @@ -153,12 +158,12 @@ class MessageWidget(QtWidgets.QFrame): def enterEvent(self, event): """Start hover timer on hover.""" - super(MessageWidget, self).enterEvent(event) + super(OverlayMessageWidget, self).enterEvent(event) self._hover_timer.start() def leaveEvent(self, event): """Start hover timer on hover leave.""" - super(MessageWidget, self).leaveEvent(event) + super(OverlayMessageWidget, self).leaveEvent(event) self._hover_timer.start() @@ -190,7 +195,7 @@ class MessageOverlayObject(QtCore.QObject): self._move_size = 4 self._move_size_remove = 8 - def add_message(self, message, timeout=None, message_type=None): + def add_message(self, message, message_type=None, timeout=None): """Add single message into overlay. Args: @@ -206,8 +211,8 @@ class MessageOverlayObject(QtCore.QObject): # Create unique id of message label_id = str(uuid.uuid4()) # Create message widget - widget = MessageWidget( - label_id, message, self._widget, timeout, message_type + widget = OverlayMessageWidget( + label_id, message, self._widget, message_type, timeout ) widget.close_requested.connect(self._on_message_close_request) widget.show() From bd61eb99d4b88d640785ea77c10b4a1a5657b279 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Fri, 1 Apr 2022 17:56:28 +0200 Subject: [PATCH 03/25] fix support for renderman in maya --- openpype/hosts/maya/api/lib_renderproducts.py | 8 ++--- .../maya/plugins/create/create_render.py | 8 +++-- .../publish/validate_rendersettings.py | 3 +- .../plugins/publish/submit_maya_deadline.py | 18 +++++++++++ .../defaults/system_settings/tools.json | 31 ++++++++++++++++++- 5 files changed, 60 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index 0c34998874..8b282094db 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -1069,7 +1069,7 @@ class RenderProductsRenderman(ARenderProducts): default_ext = "exr" displays = cmds.listConnections("rmanGlobals.displays") for aov in displays: - enabled = self._get_attr(aov, "enabled") + enabled = self._get_attr(aov, "enable") if not enabled: continue @@ -1085,7 +1085,7 @@ class RenderProductsRenderman(ARenderProducts): return products - def get_files(self, product, camera): + def get_files(self, product): """Get expected files. In renderman we hack it with prepending path. This path would @@ -1094,13 +1094,13 @@ class RenderProductsRenderman(ARenderProducts): to mess around with this settings anyway and it is enforced in render settings validator. """ - files = super(RenderProductsRenderman, self).get_files(product, camera) + files = super(RenderProductsRenderman, self).get_files(product) layer_data = self.layer_data new_files = [] for file in files: new_file = "{}/{}/{}".format( - layer_data["sceneName"], layer_data["layerName"], file + layer_data.sceneName, layer_data.layerName, file ) new_files.append(new_file) diff --git a/openpype/hosts/maya/plugins/create/create_render.py b/openpype/hosts/maya/plugins/create/create_render.py index 9002ae3876..4d3e6dc9f5 100644 --- a/openpype/hosts/maya/plugins/create/create_render.py +++ b/openpype/hosts/maya/plugins/create/create_render.py @@ -76,7 +76,7 @@ class CreateRender(plugin.Creator): 'mentalray': 'defaultRenderGlobals.imageFilePrefix', 'vray': 'vraySettings.fileNamePrefix', 'arnold': 'defaultRenderGlobals.imageFilePrefix', - 'renderman': 'defaultRenderGlobals.imageFilePrefix', + 'renderman': 'rmanGlobals.imageFileFormat', 'redshift': 'defaultRenderGlobals.imageFilePrefix' } @@ -84,7 +84,7 @@ class CreateRender(plugin.Creator): 'mentalray': 'maya///{aov_separator}', # noqa 'vray': 'maya///', 'arnold': 'maya///{aov_separator}', # noqa - 'renderman': 'maya///{aov_separator}', + 'renderman': '_..', # this needs `imageOutputDir` set separately 'redshift': 'maya///' # noqa } @@ -463,6 +463,10 @@ class CreateRender(plugin.Creator): self._set_global_output_settings() + if renderer == "renderman": + cmds.setAttr("rmanGlobals.imageOutputDir", + "/maya//", type="string") + def _set_vray_settings(self, asset): # type: (dict) -> None """Sets important settings for Vray.""" diff --git a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py index e24e88cab7..966ebac95a 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py @@ -121,7 +121,8 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): cls.log.error("Animation needs to be enabled. Use the same " "frame for start and end to render single frame") - if not prefix.lower().startswith("maya/"): + if not prefix.lower().startswith("maya/") and \ + renderer != "renderman": invalid = True cls.log.error("Wrong image prefix [ {} ] - " "doesn't start with: 'maya/'".format(prefix)) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 15a6f8d828..498397b81b 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -215,6 +215,24 @@ def get_renderer_variables(renderlayer, root): filename_0 = os.path.normpath(os.path.join(root, filename_0)) elif renderer == "renderman": prefix_attr = "rmanGlobals.imageFileFormat" + # NOTE: This is guessing extensions from renderman display types. + # Some of them are just framebuffers, d_texture format can be + # set in display setting. We set those now to None, but it + # should be handled more gracefully. + display_types = { + "d_deepexr": "exr", + "d_it": None, + "d_null": None, + "d_openexr": "exr", + "d_png": "png", + "d_pointcloud": "ptc", + "d_targa": "tga", + "d_texture": None, + "d_tiff": "tif" + } + extension = display_types.get( + cmds.listConnections("rmanDefaultDisplay.displayType")[0] + ) elif renderer == "redshift": # mapping redshift extension dropdown values to strings ext_mapping = ["iff", "exr", "tif", "png", "tga", "jpg"] diff --git a/openpype/settings/defaults/system_settings/tools.json b/openpype/settings/defaults/system_settings/tools.json index 9e08465195..49c00bec7d 100644 --- a/openpype/settings/defaults/system_settings/tools.json +++ b/openpype/settings/defaults/system_settings/tools.json @@ -52,10 +52,39 @@ "environment": {}, "variants": {} }, + "renderman": { + "environment": {}, + "variants": { + "24-3-maya": { + "host_names": [ + "maya" + ], + "app_variants": [ + "maya/2022" + ], + "environment": { + "RFMTREE": { + "windows": "C:\\Program Files\\Pixar\\RenderManForMaya-24.3", + "darwin": "/Applications/Pixar/RenderManForMaya-24.3", + "linux": "/opt/pixar/RenderManForMaya-24.3" + }, + "RMANTREE": { + "windows": "C:\\Program Files\\Pixar\\RenderManProServer-24.3", + "darwin": "/Applications/Pixar/RenderManProServer-24.3", + "linux": "/opt/pixar/RenderManProServer-24.3" + } + } + }, + "__dynamic_keys_labels__": { + "24-3-maya": "24.3 RFM" + } + } + }, "__dynamic_keys_labels__": { "mtoa": "Autodesk Arnold", "vray": "Chaos Group Vray", - "yeti": "Pergrine Labs Yeti" + "yeti": "Pergrine Labs Yeti", + "renderman": "Pixar Renderman" } } } \ No newline at end of file From 7df6c29b4e08f78ad25ac57a65427540c54b5106 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Mon, 11 Apr 2022 11:28:24 +0200 Subject: [PATCH 04/25] fixing unrelated typo Co-authored-by: Roy Nieterau --- openpype/settings/defaults/system_settings/tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/defaults/system_settings/tools.json b/openpype/settings/defaults/system_settings/tools.json index 49c00bec7d..243cde40cc 100644 --- a/openpype/settings/defaults/system_settings/tools.json +++ b/openpype/settings/defaults/system_settings/tools.json @@ -83,7 +83,7 @@ "__dynamic_keys_labels__": { "mtoa": "Autodesk Arnold", "vray": "Chaos Group Vray", - "yeti": "Pergrine Labs Yeti", + "yeti": "Peregrine Labs Yeti", "renderman": "Pixar Renderman" } } From b90f54943b527fd98b808a3b4ca8be405a2ff367 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 12 Apr 2022 13:11:43 +0200 Subject: [PATCH 05/25] =?UTF-8?q?fixes=20=F0=9F=90=A9=20and=20optimize=20r?= =?UTF-8?q?enderman=20prefix=20condition?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- openpype/hosts/maya/plugins/create/create_render.py | 4 +++- .../maya/plugins/publish/validate_rendersettings.py | 13 ++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_render.py b/openpype/hosts/maya/plugins/create/create_render.py index 4d3e6dc9f5..13bfe1bf37 100644 --- a/openpype/hosts/maya/plugins/create/create_render.py +++ b/openpype/hosts/maya/plugins/create/create_render.py @@ -84,7 +84,9 @@ class CreateRender(plugin.Creator): 'mentalray': 'maya///{aov_separator}', # noqa 'vray': 'maya///', 'arnold': 'maya///{aov_separator}', # noqa - 'renderman': '_..', # this needs `imageOutputDir` set separately + # this needs `imageOutputDir` + # (/renders/maya/) set separately + 'renderman': '_..', 'redshift': 'maya///' # noqa } diff --git a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py index 966ebac95a..92aa3af05a 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py @@ -116,16 +116,23 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): prefix = prefix.replace( "{aov_separator}", instance.data.get("aovSeparator", "_")) + + required_prefix = "maya/" + + if renderer == "renderman": + # renderman has prefix set differently + required_prefix = "/renders/{}".format(required_prefix) + if not anim_override: invalid = True cls.log.error("Animation needs to be enabled. Use the same " "frame for start and end to render single frame") - if not prefix.lower().startswith("maya/") and \ - renderer != "renderman": + if not prefix.lower().startswith(required_prefix): invalid = True cls.log.error("Wrong image prefix [ {} ] - " - "doesn't start with: 'maya/'".format(prefix)) + "doesn't start with: '{}'".format( + prefix, required_prefix)) if not re.search(cls.R_LAYER_TOKEN, prefix): invalid = True From b895efac5ba8d9430a54b282aebd8552c3171114 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 12 Apr 2022 13:19:09 +0200 Subject: [PATCH 06/25] fix ident --- .../hosts/maya/plugins/publish/validate_rendersettings.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py index 92aa3af05a..28fe2d317c 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py @@ -130,9 +130,10 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): if not prefix.lower().startswith(required_prefix): invalid = True - cls.log.error("Wrong image prefix [ {} ] - " - "doesn't start with: '{}'".format( - prefix, required_prefix)) + cls.log.error( + "Wrong image prefix [ {} ] - doesn't start with: '{}'".format( + prefix, required_prefix) + ) if not re.search(cls.R_LAYER_TOKEN, prefix): invalid = True From cf37cd3e8c25b23691555ba34143da7e35efc47a Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 21 Apr 2022 17:43:16 +0200 Subject: [PATCH 07/25] fix deadline renderman version handling --- .../plugins/publish/submit_maya_deadline.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 498397b81b..14e458a401 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -837,6 +837,23 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): "AssetDependency0": data["filepath"], } + renderer = self._instance.data["renderer"] + + # This hack is here because of how Deadline handles Renderman version. + # it considers everything with `renderman` set as version older than + # Renderman 22, and so if we are using renderman > 21 we need to set + # renderer string on the job to `renderman22`. We will have to change + # this when Deadline releases new version handling this. + if self._instance.data["renderer"] == "renderman": + try: + from rfm2.config import cfg # noqa + except ImportError: + raise Exception("Cannot determine renderman version") + + rman_version = cfg().build_info.version() # type: str + if int(rman_version.split(".")[0]) > 22: + renderer = "renderman22" + plugin_info = { "SceneFile": data["filepath"], # Output directory and filename @@ -850,7 +867,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): "RenderLayer": data["renderlayer"], # Determine which renderer to use from the file itself - "Renderer": self._instance.data["renderer"], + "Renderer": renderer, # Resolve relative references "ProjectPath": data["workspace"], From 7415c857905a8eddb71eff26e2e1c1456330b113 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 22 Apr 2022 10:27:58 +0200 Subject: [PATCH 08/25] use operational patter to recognize op atom mxf format --- openpype/lib/transcoding.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index fcec5d4216..f20bef3854 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -727,9 +727,9 @@ def get_ffmpeg_format_args(ffprobe_data, source_ffmpeg_cmd=None): def _ffmpeg_mxf_format_args(ffprobe_data, source_ffmpeg_cmd): input_format = ffprobe_data["format"] format_tags = input_format.get("tags") or {} - product_name = format_tags.get("product_name") or "" + operational_pattern_ul = format_tags.get("operational_pattern_ul") or "" output = [] - if "opatom" in product_name.lower(): + if operational_pattern_ul == "060e2b34.04010102.0d010201.10030000": output.extend(["-f", "mxf_opatom"]) return output From 917013e353ce535d062b060a35d9ee6339fba2db Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 22 Apr 2022 12:33:55 +0200 Subject: [PATCH 09/25] Fix any render cameras check --- openpype/hosts/maya/plugins/publish/collect_render.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_render.py b/openpype/hosts/maya/plugins/publish/collect_render.py index 14b9157005..dfab8252d0 100644 --- a/openpype/hosts/maya/plugins/publish/collect_render.py +++ b/openpype/hosts/maya/plugins/publish/collect_render.py @@ -194,13 +194,11 @@ class CollectMayaRender(pyblish.api.ContextPlugin): assert render_products, "no render products generated" exp_files = [] multipart = False - render_cameras = [] for product in render_products: if product.multipart: multipart = True product_name = product.productName if product.camera and layer_render_products.has_camera_token(): - render_cameras.append(product.camera) product_name = "{}{}".format( product.camera, "_" + product_name if product_name else "") @@ -210,7 +208,8 @@ class CollectMayaRender(pyblish.api.ContextPlugin): product) }) - assert render_cameras, "No render cameras found." + has_cameras = any(product.camera for product in render_products) + assert has_cameras, "No render cameras found." self.log.info("multipart: {}".format( multipart)) From e311a48ef47f0ba8d80c59e30c1fb3dcf3f1c93a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 22 Apr 2022 18:23:03 +0200 Subject: [PATCH 10/25] skip containers with not found versions --- .../plugins/publish/collect_scene_loaded_versions.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/collect_scene_loaded_versions.py b/openpype/plugins/publish/collect_scene_loaded_versions.py index e54592abb8..4c54a7d46c 100644 --- a/openpype/plugins/publish/collect_scene_loaded_versions.py +++ b/openpype/plugins/publish/collect_scene_loaded_versions.py @@ -44,12 +44,20 @@ class CollectSceneLoadedVersions(pyblish.api.ContextPlugin): } for con in _containers: + repre_id = con["representation"] + version_id = version_by_repr.get(repre_id) + if version_id is None: + self.log.warning(( + "Skipping container, did not find version document. {}" + ).format(str(con))) + continue + # NOTE: # may have more then one representation that are same version version = { "subsetName": con["name"], - "representation": ObjectId(con["representation"]), - "version": version_by_repr[con["representation"]], # _id + "representation": ObjectId(repre_id), + "version": version_id, } loaded_versions.append(version) From 7c460886442aa0ec5097f7f93c097b2987386882 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 22 Apr 2022 18:27:30 +0200 Subject: [PATCH 11/25] better log message --- openpype/plugins/publish/collect_scene_loaded_versions.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/collect_scene_loaded_versions.py b/openpype/plugins/publish/collect_scene_loaded_versions.py index 4c54a7d46c..7b44aa7963 100644 --- a/openpype/plugins/publish/collect_scene_loaded_versions.py +++ b/openpype/plugins/publish/collect_scene_loaded_versions.py @@ -43,12 +43,15 @@ class CollectSceneLoadedVersions(pyblish.api.ContextPlugin): io.find({"_id": {"$in": _repr_ids}}, projection={"parent": 1}) } + # QUESTION should we add same representation id when loaded multiple + # times? for con in _containers: repre_id = con["representation"] version_id = version_by_repr.get(repre_id) if version_id is None: self.log.warning(( - "Skipping container, did not find version document. {}" + "Skipping container," + " did not find representation document. {}" ).format(str(con))) continue From a5826ae33667c67d424376a45edb59ca80c31c6f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 22 Apr 2022 18:27:39 +0200 Subject: [PATCH 12/25] reorganized code a little bit --- openpype/plugins/publish/collect_scene_loaded_versions.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/collect_scene_loaded_versions.py b/openpype/plugins/publish/collect_scene_loaded_versions.py index 7b44aa7963..ffdd532df2 100644 --- a/openpype/plugins/publish/collect_scene_loaded_versions.py +++ b/openpype/plugins/publish/collect_scene_loaded_versions.py @@ -38,9 +38,13 @@ class CollectSceneLoadedVersions(pyblish.api.ContextPlugin): loaded_versions = [] _containers = list(host.ls()) _repr_ids = [ObjectId(c["representation"]) for c in _containers] + repre_docs = io.find( + {"_id": {"$in": _repr_ids}}, + projection={"_id": 1, "parent": 1} + ) version_by_repr = { - str(doc["_id"]): doc["parent"] for doc in - io.find({"_id": {"$in": _repr_ids}}, projection={"parent": 1}) + str(doc["_id"]): doc["parent"] + for doc in repre_docs } # QUESTION should we add same representation id when loaded multiple From c4e826e77f97191b267421ccf23f9b2401ddc35b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 25 Apr 2022 17:29:32 +0200 Subject: [PATCH 13/25] added ability to create copy of TemplateResult --- openpype/lib/path_templates.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/openpype/lib/path_templates.py b/openpype/lib/path_templates.py index 14e5fe59f8..5c40aa4549 100644 --- a/openpype/lib/path_templates.py +++ b/openpype/lib/path_templates.py @@ -365,6 +365,7 @@ class TemplateResult(str): when value of key in data is dictionary but template expect string of number. """ + used_values = None solved = None template = None @@ -383,6 +384,12 @@ class TemplateResult(str): new_obj.invalid_types = invalid_types return new_obj + def __copy__(self, *args, **kwargs): + return self.copy() + + def __deepcopy__(self, *args, **kwargs): + return self.copy() + def validate(self): if not self.solved: raise TemplateUnsolved( @@ -391,6 +398,17 @@ class TemplateResult(str): self.invalid_types ) + def copy(self): + cls = self.__class__ + return cls( + str(self), + self.template, + self.solved, + self.used_values, + self.missing_keys, + self.invalid_types + ) + class TemplatesResultDict(dict): """Holds and wrap TemplateResults for easy bug report.""" From c68b1e42c5b6991db3d633b8462323b333384e4f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 26 Apr 2022 11:08:23 +0200 Subject: [PATCH 14/25] added MessageOverlayObject to utils init --- openpype/tools/utils/__init__.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/tools/utils/__init__.py b/openpype/tools/utils/__init__.py index ea1133c442..0f367510bd 100644 --- a/openpype/tools/utils/__init__.py +++ b/openpype/tools/utils/__init__.py @@ -22,6 +22,10 @@ from .lib import ( from .models import ( RecursiveSortFilterProxyModel, ) +from .overlay_messages import ( + MessageOverlayObject, +) + __all__ = ( "PlaceholderLineEdit", @@ -45,4 +49,6 @@ __all__ = ( "get_asset_icon", "RecursiveSortFilterProxyModel", + + "MessageOverlayObject", ) From 24728400eac2a954f8f82e70db8fc6bdd5491c38 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 26 Apr 2022 11:14:57 +0200 Subject: [PATCH 15/25] MessageOverlayObject can have it's own default timeout --- openpype/tools/utils/overlay_messages.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/tools/utils/overlay_messages.py b/openpype/tools/utils/overlay_messages.py index 93082b9fb7..62de2cf272 100644 --- a/openpype/tools/utils/overlay_messages.py +++ b/openpype/tools/utils/overlay_messages.py @@ -174,7 +174,7 @@ class MessageOverlayObject(QtCore.QObject): widget (QWidget): """ - def __init__(self, widget): + def __init__(self, widget, default_timeout=None): super(MessageOverlayObject, self).__init__() widget.installEventFilter(self) @@ -194,6 +194,7 @@ class MessageOverlayObject(QtCore.QObject): self._spacing = 5 self._move_size = 4 self._move_size_remove = 8 + self._default_timeout = default_timeout def add_message(self, message, message_type=None, timeout=None): """Add single message into overlay. @@ -208,6 +209,9 @@ class MessageOverlayObject(QtCore.QObject): if not message: return + if timeout is None: + timeout = self._default_timeout + # Create unique id of message label_id = str(uuid.uuid4()) # Create message widget From d5e7353b665cfd8ab12b243dfebbcd75d6b38cd4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 26 Apr 2022 11:20:56 +0200 Subject: [PATCH 16/25] changed success to default --- openpype/style/style.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/style/style.css b/openpype/style/style.css index bae648b860..f2b0cdd6ac 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -696,10 +696,10 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { #OverlayMessageWidget:hover { background: {color:bg-button-hover}; } -#OverlayMessageWidget[type="success"] { +#OverlayMessageWidget { background: {color:overlay-messages:bg-success}; } -#OverlayMessageWidget[type="success"]:hover { +#OverlayMessageWidget:hover { background: {color:overlay-messages:bg-success-hover}; } From 0ebe84adf4e0181a0c842aa105d6de5c80b1d243 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 26 Apr 2022 11:21:35 +0200 Subject: [PATCH 17/25] use overlay messages in local settings --- openpype/tools/settings/local_settings/window.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/tools/settings/local_settings/window.py b/openpype/tools/settings/local_settings/window.py index 4db0e01476..6a2db3fff5 100644 --- a/openpype/tools/settings/local_settings/window.py +++ b/openpype/tools/settings/local_settings/window.py @@ -8,6 +8,7 @@ from openpype.settings.lib import ( save_local_settings ) from openpype.tools.settings import CHILD_OFFSET +from openpype.tools.utils import MessageOverlayObject from openpype.api import ( Logger, SystemSettings, @@ -221,6 +222,8 @@ class LocalSettingsWindow(QtWidgets.QWidget): self.setWindowTitle("OpenPype Local settings") + overlay_object = MessageOverlayObject(self) + stylesheet = style.load_stylesheet() self.setStyleSheet(stylesheet) self.setWindowIcon(QtGui.QIcon(style.app_icon_path())) @@ -247,6 +250,7 @@ class LocalSettingsWindow(QtWidgets.QWidget): save_btn.clicked.connect(self._on_save_clicked) reset_btn.clicked.connect(self._on_reset_clicked) + self._overlay_object = overlay_object # Do not create local settings widget in init phase as it's using # settings objects that must be OK to be able create this widget # - we want to show dialog if anything goes wrong @@ -312,8 +316,10 @@ class LocalSettingsWindow(QtWidgets.QWidget): def _on_reset_clicked(self): self.reset() + self._overlay_object.add_message("Refreshed...") def _on_save_clicked(self): value = self._settings_widget.settings_value() save_local_settings(value) + self._overlay_object.add_message("Saved...", message_type="success") self.reset() From 1bfe4232855a79720d08dcb8a7500c06057507d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 26 Apr 2022 12:11:36 +0200 Subject: [PATCH 18/25] fix proper output directory --- openpype/hosts/maya/api/lib_renderproducts.py | 45 +++++++++++++------ .../maya/plugins/create/create_render.py | 2 +- .../publish/validate_rendersettings.py | 30 +++++-------- .../plugins/publish/submit_maya_deadline.py | 24 +++++++++- 4 files changed, 64 insertions(+), 37 deletions(-) diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index 8b282094db..5956cc482c 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -79,6 +79,7 @@ IMAGE_PREFIXES = { "redshift": "defaultRenderGlobals.imageFilePrefix", } +RENDERMAN_IMAGE_DIR = "maya//" @attr.s class LayerMetadata(object): @@ -1054,6 +1055,8 @@ class RenderProductsRenderman(ARenderProducts): :func:`ARenderProducts.get_render_products()` """ + from rfm2.api.displays import get_displays # noqa + cameras = [ self.sanitize_camera_name(c) for c in self.get_renderable_cameras() @@ -1066,20 +1069,38 @@ class RenderProductsRenderman(ARenderProducts): ] products = [] - default_ext = "exr" - displays = cmds.listConnections("rmanGlobals.displays") - for aov in displays: - enabled = self._get_attr(aov, "enable") + # NOTE: This is guessing extensions from renderman display types. + # Some of them are just framebuffers, d_texture format can be + # set in display setting. We set those now to None, but it + # should be handled more gracefully. + display_types = { + "d_deepexr": "exr", + "d_it": None, + "d_null": None, + "d_openexr": "exr", + "d_png": "png", + "d_pointcloud": "ptc", + "d_targa": "tga", + "d_texture": None, + "d_tiff": "tif" + } + + displays = get_displays()["displays"] + for name, display in displays.items(): + enabled = display["params"]["enable"]["value"] if not enabled: continue - aov_name = str(aov) + aov_name = name if aov_name == "rmanDefaultDisplay": aov_name = "beauty" + extensions = display_types.get( + display["driverNode"]["type"], "exr") + for camera in cameras: product = RenderProduct(productName=aov_name, - ext=default_ext, + ext=extensions, camera=camera) products.append(product) @@ -1088,20 +1109,16 @@ class RenderProductsRenderman(ARenderProducts): def get_files(self, product): """Get expected files. - In renderman we hack it with prepending path. This path would - normally be translated from `rmanGlobals.imageOutputDir`. We skip - this and hardcode prepend path we expect. There is no place for user - to mess around with this settings anyway and it is enforced in - render settings validator. """ files = super(RenderProductsRenderman, self).get_files(product) layer_data = self.layer_data new_files = [] + + resolved_image_dir = re.sub("", layer_data.sceneName, RENDERMAN_IMAGE_DIR, flags=re.IGNORECASE) # noqa: E501 + resolved_image_dir = re.sub("", layer_data.layerName, resolved_image_dir, flags=re.IGNORECASE) # noqa: E501 for file in files: - new_file = "{}/{}/{}".format( - layer_data.sceneName, layer_data.layerName, file - ) + new_file = "{}/{}".format(resolved_image_dir, file) new_files.append(new_file) return new_files diff --git a/openpype/hosts/maya/plugins/create/create_render.py b/openpype/hosts/maya/plugins/create/create_render.py index 13bfe1bf37..f2cf73557e 100644 --- a/openpype/hosts/maya/plugins/create/create_render.py +++ b/openpype/hosts/maya/plugins/create/create_render.py @@ -467,7 +467,7 @@ class CreateRender(plugin.Creator): if renderer == "renderman": cmds.setAttr("rmanGlobals.imageOutputDir", - "/maya//", type="string") + "maya//", type="string") def _set_vray_settings(self, asset): # type: (dict) -> None diff --git a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py index 28fe2d317c..a513c8ebc1 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py @@ -69,14 +69,7 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): redshift_AOV_prefix = "/{aov_separator}" # noqa: E501 - # WARNING: There is bug? in renderman, translating token - # to something left behind mayas default image prefix. So instead - # `SceneName_v01` it translates to: - # `SceneName_v01//` that means - # for example: - # `SceneName_v01/Main/Main_`. Possible solution is to define - # custom token like to point to determined scene name. - RendermanDirPrefix = "/renders/maya//" + renderman_dir_prefix = "maya//" R_AOV_TOKEN = re.compile( r'%a||', re.IGNORECASE) @@ -119,21 +112,18 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): required_prefix = "maya/" - if renderer == "renderman": - # renderman has prefix set differently - required_prefix = "/renders/{}".format(required_prefix) - if not anim_override: invalid = True cls.log.error("Animation needs to be enabled. Use the same " "frame for start and end to render single frame") - if not prefix.lower().startswith(required_prefix): - invalid = True - cls.log.error( - "Wrong image prefix [ {} ] - doesn't start with: '{}'".format( - prefix, required_prefix) - ) + if renderer != "renderman": + if not prefix.lower().startswith(required_prefix): + invalid = True + cls.log.error( + "Wrong image prefix [ {} ] - doesn't start with: '{}'".format( + prefix, required_prefix) + ) if not re.search(cls.R_LAYER_TOKEN, prefix): invalid = True @@ -207,7 +197,7 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): invalid = True cls.log.error("Wrong image prefix [ {} ]".format(file_prefix)) - if dir_prefix.lower() != cls.RendermanDirPrefix.lower(): + if dir_prefix.lower() != cls.renderman_dir_prefix.lower(): invalid = True cls.log.error("Wrong directory prefix [ {} ]".format( dir_prefix)) @@ -313,7 +303,7 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): default_prefix, type="string") cmds.setAttr("rmanGlobals.imageOutputDir", - cls.RendermanDirPrefix, + cls.renderman_dir_prefix, type="string") if renderer == "vray": diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 14e458a401..3f036dbca7 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -187,6 +187,10 @@ def get_renderer_variables(renderlayer, root): filename_0 = re.sub('_', '_beauty', filename_0, flags=re.IGNORECASE) prefix_attr = "defaultRenderGlobals.imageFilePrefix" + + scene = cmds.file(query=True, sceneName=True) + scene, _ = os.path.splitext(os.path.basename(scene)) + if renderer == "vray": renderlayer = renderlayer.split("_")[-1] # Maya's renderSettings function does not return V-Ray file extension @@ -206,8 +210,7 @@ def get_renderer_variables(renderlayer, root): filename_prefix = cmds.getAttr(prefix_attr) # we need to determine path for vray as maya `renderSettings` query # does not work for vray. - scene = cmds.file(query=True, sceneName=True) - scene, _ = os.path.splitext(os.path.basename(scene)) + filename_0 = re.sub('', scene, filename_prefix, flags=re.IGNORECASE) # noqa: E501 filename_0 = re.sub('', renderlayer, filename_0, flags=re.IGNORECASE) # noqa: E501 filename_0 = "{}.{}.{}".format( @@ -224,15 +227,30 @@ def get_renderer_variables(renderlayer, root): "d_it": None, "d_null": None, "d_openexr": "exr", + "d_openexr3": "exr", "d_png": "png", "d_pointcloud": "ptc", "d_targa": "tga", "d_texture": None, "d_tiff": "tif" } + extension = display_types.get( cmds.listConnections("rmanDefaultDisplay.displayType")[0] ) + + filename_prefix = "{}/{}".format( + cmds.getAttr("rmanGlobals.imageOutputDir"), + cmds.getAttr("rmanGlobals.imageFileFormat") + ) + + renderlayer = renderlayer.split("_")[-1] + + filename_0 = re.sub('', scene, filename_prefix, flags=re.IGNORECASE) # noqa: E501 + filename_0 = re.sub('', renderlayer, filename_0, flags=re.IGNORECASE) # noqa: E501 + filename_0 = re.sub('', "#" * int(padding), filename_0, flags=re.IGNORECASE) # noqa: E501 + filename_0 = re.sub('', extension, filename_0, flags=re.IGNORECASE) # noqa: E501 + filename_0 = os.path.normpath(os.path.join(root, filename_0)) elif renderer == "redshift": # mapping redshift extension dropdown values to strings ext_mapping = ["iff", "exr", "tif", "png", "tga", "jpg"] @@ -442,6 +460,8 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): output_filename_0 = filename_0 + dirname = os.path.dirname(output_filename_0) + # Create render folder ---------------------------------------------- try: # Ensure render folder exists From a2b2dfb1ef93b59c88097b97fcc314708ba529be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 26 Apr 2022 12:18:12 +0200 Subject: [PATCH 19/25] hound fixes --- .../plugins/publish/validate_rendersettings.py | 15 ++++++++------- .../plugins/publish/submit_maya_deadline.py | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py index a513c8ebc1..023e27de17 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py @@ -117,13 +117,14 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): cls.log.error("Animation needs to be enabled. Use the same " "frame for start and end to render single frame") - if renderer != "renderman": - if not prefix.lower().startswith(required_prefix): - invalid = True - cls.log.error( - "Wrong image prefix [ {} ] - doesn't start with: '{}'".format( - prefix, required_prefix) - ) + if renderer != "renderman" and not prefix.lower().startswith( + required_prefix): + invalid = True + cls.log.error( + ("Wrong image prefix [ {} ] " + " - doesn't start with: '{}'").format( + prefix, required_prefix) + ) if not re.search(cls.R_LAYER_TOKEN, prefix): invalid = True diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 3f036dbca7..347b6ab0fe 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -858,7 +858,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): } renderer = self._instance.data["renderer"] - + # This hack is here because of how Deadline handles Renderman version. # it considers everything with `renderman` set as version older than # Renderman 22, and so if we are using renderman > 21 we need to set From b0fcfc6feaa772df0a1f1e21b6dd641194169b34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 26 Apr 2022 15:24:08 +0200 Subject: [PATCH 20/25] handle default extension --- .../deadline/plugins/publish/submit_maya_deadline.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 347b6ab0fe..2fc495fa76 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -227,7 +227,6 @@ def get_renderer_variables(renderlayer, root): "d_it": None, "d_null": None, "d_openexr": "exr", - "d_openexr3": "exr", "d_png": "png", "d_pointcloud": "ptc", "d_targa": "tga", @@ -236,8 +235,9 @@ def get_renderer_variables(renderlayer, root): } extension = display_types.get( - cmds.listConnections("rmanDefaultDisplay.displayType")[0] - ) + cmds.listConnections("rmanDefaultDisplay.displayType")[0], + "exr" + ) or "exr" filename_prefix = "{}/{}".format( cmds.getAttr("rmanGlobals.imageOutputDir"), From 546b1d42015598a9b36dcfb07d942c12b391029b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 27 Apr 2022 19:07:23 +0200 Subject: [PATCH 21/25] removed unused method --- .../plugins/publish/integrate_ftrack_api.py | 42 ------------------- 1 file changed, 42 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_api.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_api.py index 650c59fae8..e60d00c7c3 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_api.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_api.py @@ -24,48 +24,6 @@ class IntegrateFtrackApi(pyblish.api.InstancePlugin): label = "Integrate Ftrack Api" families = ["ftrack"] - def query(self, entitytype, data): - """ Generate a query expression from data supplied. - - If a value is not a string, we'll add the id of the entity to the - query. - - Args: - entitytype (str): The type of entity to query. - data (dict): The data to identify the entity. - exclusions (list): All keys to exclude from the query. - - Returns: - str: String query to use with "session.query" - """ - queries = [] - if sys.version_info[0] < 3: - for key, value in data.iteritems(): - if not isinstance(value, (basestring, int)): - self.log.info("value: {}".format(value)) - if "id" in value.keys(): - queries.append( - "{0}.id is \"{1}\"".format(key, value["id"]) - ) - else: - queries.append("{0} is \"{1}\"".format(key, value)) - else: - for key, value in data.items(): - if not isinstance(value, (str, int)): - self.log.info("value: {}".format(value)) - if "id" in value.keys(): - queries.append( - "{0}.id is \"{1}\"".format(key, value["id"]) - ) - else: - queries.append("{0} is \"{1}\"".format(key, value)) - - query = ( - "select id from " + entitytype + " where " + " and ".join(queries) - ) - self.log.debug(query) - return query - def process(self, instance): session = instance.context.data["ftrackSession"] context = instance.context From 9c1fb9de477394b8b22c1910ab2a862a3005dd96 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 27 Apr 2022 19:08:22 +0200 Subject: [PATCH 22/25] added asset status name filtering for asset version --- .../publish/integrate_ftrack_instances.py | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py index 5ea0469bce..5eecf34c3d 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py @@ -3,6 +3,8 @@ import json import copy import pyblish.api +from openpype.lib.profiles_filtering import filter_profiles + class IntegrateFtrackInstance(pyblish.api.InstancePlugin): """Collect ftrack component data (not integrate yet). @@ -36,6 +38,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): "reference": "reference" } keep_first_subset_name_for_review = True + asset_versions_status_profiles = {} def process(self, instance): self.log.debug("instance {}".format(instance)) @@ -80,6 +83,8 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): if instance_fps is None: instance_fps = instance.context.data["fps"] + status_name = self._get_asset_version_status_name(instance) + # Base of component item data # - create a copy of this object when want to use it base_component_item = { @@ -91,7 +96,8 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): }, "assetversion_data": { "version": version_number, - "comment": instance.context.data.get("comment") or "" + "comment": instance.context.data.get("comment") or "", + "status_name": status_name }, "component_overwrite": False, # This can be change optionally @@ -317,3 +323,24 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): ) )) instance.data["ftrackComponentsList"] = component_list + + def _get_asset_version_status_name(self, instance): + if not self.asset_versions_status_profiles: + return None + + # Prepare filtering data for new asset version status + anatomy_data = instance.data["anatomyData"] + task_type = anatomy_data.get("task", {}).get("type") + filtering_criteria = { + "families": instance.data["family"], + "hosts": instance.context.data["hostName"], + "task_types": task_type + } + matching_profile = filter_profiles( + self.asset_versions_status_profiles, + filtering_criteria + ) + if not matching_profile: + return None + + return matching_profile["status"] or None From b0dd4d51530a5a345bad9fae2c3d9fac2ee6ef19 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 27 Apr 2022 19:10:53 +0200 Subject: [PATCH 23/25] implemented logic which change status of asset version --- .../plugins/publish/integrate_ftrack_api.py | 44 ++++++++++++++++--- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_api.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_api.py index e60d00c7c3..64af8cb208 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_api.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_api.py @@ -66,7 +66,19 @@ class IntegrateFtrackApi(pyblish.api.InstancePlugin): default_asset_name = parent_entity["name"] # Change status on task - self._set_task_status(instance, task_entity, session) + asset_version_status_ids_by_name = {} + project_entity = instance.context.data.get("ftrackProject") + if project_entity: + project_schema = project_entity["project_schema"] + asset_version_statuses = ( + project_schema.get_statuses("AssetVersion") + ) + asset_version_status_ids_by_name = { + status["name"].lower(): status["id"] + for status in asset_version_statuses + } + + self._set_task_status(instance, project_entity, task_entity, session) # Prepare AssetTypes asset_types_by_short = self._ensure_asset_types_exists( @@ -97,7 +109,11 @@ class IntegrateFtrackApi(pyblish.api.InstancePlugin): # Asset Version asset_version_data = data.get("assetversion_data") or {} asset_version_entity = self._ensure_asset_version_exists( - session, asset_version_data, asset_entity["id"], task_entity + session, + asset_version_data, + asset_entity["id"], + task_entity, + asset_version_status_ids_by_name ) # Component @@ -132,8 +148,7 @@ class IntegrateFtrackApi(pyblish.api.InstancePlugin): if asset_version not in instance.data[asset_versions_key]: instance.data[asset_versions_key].append(asset_version) - def _set_task_status(self, instance, task_entity, session): - project_entity = instance.context.data.get("ftrackProject") + def _set_task_status(self, instance, project_entity, task_entity, session): if not project_entity: self.log.info("Task status won't be set, project is not known.") return @@ -277,12 +292,19 @@ class IntegrateFtrackApi(pyblish.api.InstancePlugin): ).first() def _ensure_asset_version_exists( - self, session, asset_version_data, asset_id, task_entity + self, + session, + asset_version_data, + asset_id, + task_entity, + status_ids_by_name ): task_id = None if task_entity: task_id = task_entity["id"] + status_name = asset_version_data.pop("status_name", None) + # Try query asset version by criteria (asset id and version) version = asset_version_data.get("version") or 0 asset_version_entity = self._query_asset_version( @@ -324,6 +346,18 @@ class IntegrateFtrackApi(pyblish.api.InstancePlugin): session, version, asset_id ) + if status_name: + status_id = status_ids_by_name.get(status_name.lower()) + if not status_id: + self.log.info(( + "Ftrack status with name \"{}\"" + " for AssetVersion was not found." + ).format(status_name)) + + elif asset_version_entity["status_id"] != status_id: + asset_version_entity["status_id"] = status_id + session.commit() + # Set custom attributes if there were any set custom_attrs = asset_version_data.get("custom_attributes") or {} for attr_key, attr_value in custom_attrs.items(): From eea8f906a2a2b4031b672d021db1052ba0afdd0d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 27 Apr 2022 19:16:10 +0200 Subject: [PATCH 24/25] added settings schemas and defaults for new attribute --- .../defaults/project_settings/ftrack.json | 3 +- .../schema_project_ftrack.json | 37 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/ftrack.json b/openpype/settings/defaults/project_settings/ftrack.json index a846a596c2..f9d16d6476 100644 --- a/openpype/settings/defaults/project_settings/ftrack.json +++ b/openpype/settings/defaults/project_settings/ftrack.json @@ -418,7 +418,8 @@ "redshiftproxy": "cache", "usd": "usd" }, - "keep_first_subset_name_for_review": true + "keep_first_subset_name_for_review": true, + "asset_versions_status_profiles": [] } } } \ No newline at end of file diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json b/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json index 47effb3dbd..7db490b114 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json @@ -858,6 +858,43 @@ "key": "keep_first_subset_name_for_review", "label": "Make subset name as first asset name", "default": true + }, + { + "type": "list", + "collapsible": true, + "key": "asset_versions_status_profiles", + "label": "AssetVersion status on publish", + "use_label_wrap": true, + "object_type": { + "type": "dict", + "children": [ + { + "key": "hosts", + "label": "Host names", + "type": "hosts-enum", + "multiselection": true + }, + { + "key": "task_types", + "label": "Task types", + "type": "task-types-enum" + }, + { + "key": "family", + "label": "Family", + "type": "list", + "object_type": "text" + }, + { + "type": "separator" + }, + { + "key": "status", + "label": "Status name", + "type": "text" + } + ] + } } ] } From ca803331e595bb6bb8d2ff740d33fdfe25793aeb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 28 Apr 2022 14:00:02 +0200 Subject: [PATCH 25/25] make sure keys are as list instead of 'dict_keys' in py2 --- openpype/lib/avalon_context.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 3fcddef745..9d8a92cfe9 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -1532,13 +1532,13 @@ class BuildWorkfile: subsets = list(legacy_io.find({ "type": "subset", - "parent": {"$in": asset_entity_by_ids.keys()} + "parent": {"$in": list(asset_entity_by_ids.keys())} })) subset_entity_by_ids = {subset["_id"]: subset for subset in subsets} sorted_versions = list(legacy_io.find({ "type": "version", - "parent": {"$in": subset_entity_by_ids.keys()} + "parent": {"$in": list(subset_entity_by_ids.keys())} }).sort("name", -1)) subset_id_with_latest_version = [] @@ -1552,7 +1552,7 @@ class BuildWorkfile: repres = legacy_io.find({ "type": "representation", - "parent": {"$in": last_versions_by_id.keys()} + "parent": {"$in": list(last_versions_by_id.keys())} }) output = {}