From bde7d988a21fdf1df8ac95ff863089f25dc31612 Mon Sep 17 00:00:00 2001 From: jrsndlr Date: Mon, 7 Mar 2022 16:59:50 +0100 Subject: [PATCH 01/15] Fix family test in validate_write_legacy to work with stillImage --- openpype/hosts/nuke/plugins/publish/validate_write_legacy.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/validate_write_legacy.py b/openpype/hosts/nuke/plugins/publish/validate_write_legacy.py index a73bed8edd..91e7dacc6e 100644 --- a/openpype/hosts/nuke/plugins/publish/validate_write_legacy.py +++ b/openpype/hosts/nuke/plugins/publish/validate_write_legacy.py @@ -34,9 +34,8 @@ class ValidateWriteLegacy(pyblish.api.InstancePlugin): # test if render in family test knob # and only one item should be available assert len(family_test) == 1, msg + " > More avalon attributes" - assert "render" in node[family_test[0]].value(), msg + \ + assert "render" in node[family_test[0]].value() or "still" in node[family_test[0]].value(), msg + \ " > Not correct family" - # test if `file` knob in node, this way old # non-group-node write could be detected assert "file" not in node.knobs(), msg + \ @@ -74,6 +73,8 @@ class ValidateWriteLegacy(pyblish.api.InstancePlugin): Create_name = "CreateWriteRender" elif family == "prerender": Create_name = "CreateWritePrerender" + elif family == "still": + Create_name = "CreateWriteStill" # get appropriate plugin class creator_plugin = None From 9f39bba2d3bdc97f0005ed3793134f66ecad834b Mon Sep 17 00:00:00 2001 From: jrsndlr Date: Mon, 7 Mar 2022 17:06:23 +0100 Subject: [PATCH 02/15] fix hound --- openpype/hosts/nuke/plugins/publish/validate_write_legacy.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/plugins/publish/validate_write_legacy.py b/openpype/hosts/nuke/plugins/publish/validate_write_legacy.py index 91e7dacc6e..08f09f8097 100644 --- a/openpype/hosts/nuke/plugins/publish/validate_write_legacy.py +++ b/openpype/hosts/nuke/plugins/publish/validate_write_legacy.py @@ -34,7 +34,8 @@ class ValidateWriteLegacy(pyblish.api.InstancePlugin): # test if render in family test knob # and only one item should be available assert len(family_test) == 1, msg + " > More avalon attributes" - assert "render" in node[family_test[0]].value() or "still" in node[family_test[0]].value(), msg + \ + assert "render" in node[family_test[0]].value() \ + or "still" in node[family_test[0]].value(), msg + \ " > Not correct family" # test if `file` knob in node, this way old # non-group-node write could be detected From e1d07b2b13d7af7c604c5f5293aea2b8c7639c2e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 8 Mar 2022 17:11:13 +0100 Subject: [PATCH 03/15] nuke: adding input resolution of input video file --- .../plugins/publish/extract_review_slate.py | 43 +++++++++++++++---- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index 7002168cdb..948cee0639 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -14,7 +14,7 @@ class ExtractReviewSlate(openpype.api.Extractor): families = ["slate", "review"] match = pyblish.api.Subset - hosts = ["nuke", "maya", "shell"] + hosts = ["nuke", "shell"] optional = True def process(self, instance): @@ -59,13 +59,44 @@ class ExtractReviewSlate(openpype.api.Extractor): if "slate-frame" not in p_tags: continue + # get repre file + stagingdir = repre["stagingDir"] + input_file = "{0}".format(repre["files"]) + input_path = os.path.join( + os.path.normpath(stagingdir), repre["files"]) + self.log.debug("__ input_path: {}".format(input_path)) + + video_streams = openpype.lib.ffprobe_streams( + input_path, self.log + ) + + # Try to find first stream with defined 'width' and 'height' + # - this is to avoid order of streams where audio can be as first + # - there may be a better way (checking `codec_type`?) + input_width = None + input_height = None + for stream in video_streams: + if "width" in stream and "height" in stream: + input_width = int(stream["width"]) + input_height = int(stream["height"]) + break + + # Raise exception of any stream didn't define input resolution + if input_width is None: + raise AssertionError(( + "FFprobe couldn't read resolution from input file: \"{}\"" + ).format(input_path)) + # values are set in ExtractReview if use_legacy_code: to_width = inst_data["reviewToWidth"] to_height = inst_data["reviewToHeight"] else: - to_width = repre["resolutionWidth"] - to_height = repre["resolutionHeight"] + to_width = input_width + to_height = input_height + + self.log.debug("to_width: `{}`".format(to_width)) + self.log.debug("to_height: `{}`".format(to_height)) # defining image ratios resolution_ratio = ( @@ -94,15 +125,9 @@ class ExtractReviewSlate(openpype.api.Extractor): _remove_at_end = [] - stagingdir = repre["stagingDir"] - input_file = "{0}".format(repre["files"]) - ext = os.path.splitext(input_file)[1] output_file = input_file.replace(ext, "") + suffix + ext - input_path = os.path.join( - os.path.normpath(stagingdir), repre["files"]) - self.log.debug("__ input_path: {}".format(input_path)) _remove_at_end.append(input_path) output_path = os.path.join( From 09dbeffe7122d9ad3ee37aab4354637bbbb16e6c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 9 Mar 2022 16:42:49 +0100 Subject: [PATCH 04/15] global: slate could not be created when prores 4444 --- .../plugins/publish/extract_review_slate.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index 7002168cdb..f9ed3cfab6 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -347,8 +347,21 @@ class ExtractReviewSlate(openpype.api.Extractor): profile_name = no_audio_stream.get("profile") if profile_name: - profile_name = profile_name.replace(" ", "_").lower() - codec_args.append("-profile:v {}".format(profile_name)) + # Rest of arguments is prores_kw specific + if codec_name == "prores_ks": + codec_tag_to_profile_map = { + "apco": "proxy", + "apcs": "lt", + "apcn": "standard", + "apch": "hq", + "ap4h": "4444", + "ap4x": "4444xq" + } + codec_tag_str = no_audio_stream.get("codec_tag_string") + if codec_tag_str: + profile = codec_tag_to_profile_map.get(codec_tag_str) + if profile: + codec_args.extend(["-profile:v", profile]) pix_fmt = no_audio_stream.get("pix_fmt") if pix_fmt: From e61c8d992bc3a2308293b9077bc5ae85fa82ba05 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 10 Mar 2022 10:57:01 +0100 Subject: [PATCH 05/15] fix hardlink for windows --- openpype/lib/path_tools.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/lib/path_tools.py b/openpype/lib/path_tools.py index 3a9f835272..851bc872fb 100644 --- a/openpype/lib/path_tools.py +++ b/openpype/lib/path_tools.py @@ -43,6 +43,7 @@ def create_hard_link(src_path, dst_path): res = CreateHardLink(dst_path, src_path, None) if res == 0: raise ctypes.WinError() + return # Raises not implemented error if gets here raise NotImplementedError( "Implementation of hardlink for current environment is missing." From 58aaa25c5536cdc61d1852e132c3e88e313dc587 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 10 Mar 2022 11:56:06 +0100 Subject: [PATCH 06/15] added short description widget --- .../tools/publisher/widgets/create_dialog.py | 101 +++++++++++------- 1 file changed, 63 insertions(+), 38 deletions(-) diff --git a/openpype/tools/publisher/widgets/create_dialog.py b/openpype/tools/publisher/widgets/create_dialog.py index 6396b77901..83418b8bef 100644 --- a/openpype/tools/publisher/widgets/create_dialog.py +++ b/openpype/tools/publisher/widgets/create_dialog.py @@ -107,67 +107,100 @@ class CreatorDescriptionWidget(QtWidgets.QWidget): def __init__(self, parent=None): super(CreatorDescriptionWidget, self).__init__(parent=parent) - icon_widget = IconValuePixmapLabel(None, self) + # --- Short description widget --- + short_desc_widget = QtWidgets.QWidget(self) + + icon_widget = IconValuePixmapLabel(None, short_desc_widget) icon_widget.setObjectName("FamilyIconLabel") - family_label = QtWidgets.QLabel("family") + # --- Short description inputs --- + short_desc_input_widget = QtWidgets.QWidget(short_desc_widget) + + family_label = QtWidgets.QLabel(short_desc_input_widget) family_label.setAlignment( QtCore.Qt.AlignBottom | QtCore.Qt.AlignLeft ) - description_label = QtWidgets.QLabel("description") + description_label = QtWidgets.QLabel(short_desc_input_widget) description_label.setAlignment( QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft ) - detail_description_widget = QtWidgets.QTextEdit(self) + short_desc_input_layout = QtWidgets.QVBoxLayout( + short_desc_input_widget + ) + short_desc_input_layout.setSpacing(0) + short_desc_input_layout.addWidget(family_label) + short_desc_input_layout.addWidget(description_label) + # -------------------------------- + + short_desc_layout = QtWidgets.QHBoxLayout(short_desc_widget) + short_desc_layout.setContentsMargins(0, 0, 0, 0) + short_desc_layout.addWidget(icon_widget, 0) + short_desc_layout.addWidget(short_desc_input_widget, 1) + # -------------------------------- + + separator_widget = QtWidgets.QWidget(self) + separator_widget.setObjectName("Separator") + separator_widget.setMinimumHeight(2) + separator_widget.setMaximumHeight(2) + + # --- Bottom part ---------------- + bottom_widget = QtWidgets.QWidget(self) + + # Precreate attributes widget + pre_create_widget = PreCreateWidget(bottom_widget) + + # Detailed description of creator + detail_description_widget = QtWidgets.QTextEdit(bottom_widget) detail_description_widget.setObjectName("InfoText") detail_description_widget.setTextInteractionFlags( QtCore.Qt.TextBrowserInteraction ) + # TODO add HELP button + detail_description_widget.setVisible(False) - label_layout = QtWidgets.QVBoxLayout() - label_layout.setSpacing(0) - label_layout.addWidget(family_label) - label_layout.addWidget(description_label) - - top_layout = QtWidgets.QHBoxLayout() - top_layout.setContentsMargins(0, 0, 0, 0) - top_layout.addWidget(icon_widget, 0) - top_layout.addLayout(label_layout, 1) + bottom_layout = QtWidgets.QHBoxLayout(bottom_widget) + bottom_layout.setContentsMargins(0, 0, 0, 0) + bottom_layout.addWidget(pre_create_widget, 1) + bottom_layout.addWidget(detail_description_widget, 1) layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) - layout.addLayout(top_layout, 0) - layout.addWidget(detail_description_widget, 1) + layout.addWidget(short_desc_widget, 0) + layout.addWidget(separator_widget, 0) + layout.addWidget(bottom_widget, 1) - self.icon_widget = icon_widget - self.family_label = family_label - self.description_label = description_label - self.detail_description_widget = detail_description_widget + self._icon_widget = icon_widget + self._family_label = family_label + self._description_label = description_label + self._detail_description_widget = detail_description_widget + self._pre_create_widget = pre_create_widget def set_plugin(self, plugin=None): if not plugin: - self.icon_widget.set_icon_def(None) - self.family_label.setText("") - self.description_label.setText("") - self.detail_description_widget.setPlainText("") + self._icon_widget.set_icon_def(None) + self._family_label.setText("") + self._description_label.setText("") + self._detail_description_widget.setPlainText("") + self._pre_create_widget.set_plugin(plugin) return plugin_icon = plugin.get_icon() description = plugin.get_description() or "" detailed_description = plugin.get_detail_description() or "" - self.icon_widget.set_icon_def(plugin_icon) - self.family_label.setText("{}".format(plugin.family)) - self.family_label.setTextInteractionFlags(QtCore.Qt.NoTextInteraction) - self.description_label.setText(description) + self._icon_widget.set_icon_def(plugin_icon) + self._family_label.setText("{}".format(plugin.family)) + self._family_label.setTextInteractionFlags(QtCore.Qt.NoTextInteraction) + self._description_label.setText(description) if commonmark: html = commonmark.commonmark(detailed_description) - self.detail_description_widget.setHtml(html) + self._detail_description_widget.setHtml(html) else: - self.detail_description_widget.setMarkdown(detailed_description) + self._detail_description_widget.setMarkdown(detailed_description) + self._pre_create_widget.set_plugin(plugin) class CreateDialog(QtWidgets.QDialog): @@ -215,12 +248,7 @@ class CreateDialog(QtWidgets.QDialog): context_layout.addWidget(assets_widget, 2) context_layout.addWidget(tasks_widget, 1) - # Precreate attributes widgets - pre_create_widget = PreCreateWidget(self) - - # TODO add HELP button creator_description_widget = CreatorDescriptionWidget(self) - creator_description_widget.setVisible(False) creators_view = QtWidgets.QListView(self) creators_model = QtGui.QStandardItemModel() @@ -264,7 +292,7 @@ class CreateDialog(QtWidgets.QDialog): splitter_widget = QtWidgets.QSplitter(self) splitter_widget.addWidget(context_widget) splitter_widget.addWidget(mid_widget) - splitter_widget.addWidget(pre_create_widget) + splitter_widget.addWidget(creator_description_widget) splitter_widget.setStretchFactor(0, 1) splitter_widget.setStretchFactor(1, 1) splitter_widget.setStretchFactor(2, 1) @@ -295,8 +323,6 @@ class CreateDialog(QtWidgets.QDialog): self._splitter_widget = splitter_widget - self._pre_create_widget = pre_create_widget - self._context_widget = context_widget self._assets_widget = assets_widget self._tasks_widget = tasks_widget @@ -510,7 +536,6 @@ class CreateDialog(QtWidgets.QDialog): creator = self.controller.manual_creators.get(identifier) self.creator_description_widget.set_plugin(creator) - self._pre_create_widget.set_plugin(creator) self._selected_creator = creator From 2bce15f106611e9e3e6572b3d6c6b7cb77509f67 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 10 Mar 2022 11:57:21 +0100 Subject: [PATCH 07/15] changed Name to Variant --- openpype/tools/publisher/widgets/widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index 4e55e86491..5ced469b59 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -982,7 +982,7 @@ class GlobalAttrsWidget(QtWidgets.QWidget): btns_layout.addWidget(cancel_btn) main_layout = QtWidgets.QFormLayout(self) - main_layout.addRow("Name", variant_input) + main_layout.addRow("Variant", variant_input) main_layout.addRow("Asset", asset_value_widget) main_layout.addRow("Task", task_value_widget) main_layout.addRow("Family", family_value_widget) From 752af659bee065658e76d7a5ecb0996864de8069 Mon Sep 17 00:00:00 2001 From: jrsndlr Date: Thu, 10 Mar 2022 13:01:52 +0100 Subject: [PATCH 08/15] families is None for group/gizmo --- openpype/hosts/nuke/plugins/publish/precollect_instances.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/plugins/publish/precollect_instances.py b/openpype/hosts/nuke/plugins/publish/precollect_instances.py index 97ddef0a59..29c706f302 100644 --- a/openpype/hosts/nuke/plugins/publish/precollect_instances.py +++ b/openpype/hosts/nuke/plugins/publish/precollect_instances.py @@ -80,7 +80,7 @@ class PreCollectNukeInstances(pyblish.api.ContextPlugin): # Add all nodes in group instances. if node.Class() == "Group": # only alter families for render family - if "write" in families_ak.lower(): + if families_ak and "write" in families_ak.lower(): target = node["render"].value() if target == "Use existing frames": # Local rendering From 76b630aaac31e03ac867fa6ea4affa6a2c7b82b6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 10 Mar 2022 14:44:07 +0100 Subject: [PATCH 09/15] added description widget and detailed info widget --- openpype/style/style.css | 13 ++ .../tools/publisher/widgets/create_dialog.py | 208 +++++++++++++----- 2 files changed, 165 insertions(+), 56 deletions(-) diff --git a/openpype/style/style.css b/openpype/style/style.css index 5586cf766d..df83600973 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -836,6 +836,19 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { } /* New Create/Publish UI */ +#CreateDialogHelpButton { + background: rgba(255, 255, 255, 31); + border-top-right-radius: 0; + border-bottom-right-radius: 0; + font-size: 10pt; + font-weight: bold; + padding: 3px 3px 3px 3px; +} + +#CreateDialogHelpButton:hover { + background: rgba(255, 255, 255, 63); +} + #PublishLogConsole { font-family: "Noto Sans Mono"; } diff --git a/openpype/tools/publisher/widgets/create_dialog.py b/openpype/tools/publisher/widgets/create_dialog.py index 83418b8bef..27ce97955a 100644 --- a/openpype/tools/publisher/widgets/create_dialog.py +++ b/openpype/tools/publisher/widgets/create_dialog.py @@ -103,18 +103,16 @@ class CreateErrorMessageBox(ErrorMessageBox): # TODO add creator identifier/label to details -class CreatorDescriptionWidget(QtWidgets.QWidget): +class CreatorShortDescWidget(QtWidgets.QWidget): def __init__(self, parent=None): - super(CreatorDescriptionWidget, self).__init__(parent=parent) + super(CreatorShortDescWidget, self).__init__(parent=parent) # --- Short description widget --- - short_desc_widget = QtWidgets.QWidget(self) - - icon_widget = IconValuePixmapLabel(None, short_desc_widget) + icon_widget = IconValuePixmapLabel(None, self) icon_widget.setObjectName("FamilyIconLabel") # --- Short description inputs --- - short_desc_input_widget = QtWidgets.QWidget(short_desc_widget) + short_desc_input_widget = QtWidgets.QWidget(self) family_label = QtWidgets.QLabel(short_desc_input_widget) family_label.setAlignment( @@ -134,73 +132,69 @@ class CreatorDescriptionWidget(QtWidgets.QWidget): short_desc_input_layout.addWidget(description_label) # -------------------------------- - short_desc_layout = QtWidgets.QHBoxLayout(short_desc_widget) - short_desc_layout.setContentsMargins(0, 0, 0, 0) - short_desc_layout.addWidget(icon_widget, 0) - short_desc_layout.addWidget(short_desc_input_widget, 1) - # -------------------------------- - - separator_widget = QtWidgets.QWidget(self) - separator_widget.setObjectName("Separator") - separator_widget.setMinimumHeight(2) - separator_widget.setMaximumHeight(2) - - # --- Bottom part ---------------- - bottom_widget = QtWidgets.QWidget(self) - - # Precreate attributes widget - pre_create_widget = PreCreateWidget(bottom_widget) - - # Detailed description of creator - detail_description_widget = QtWidgets.QTextEdit(bottom_widget) - detail_description_widget.setObjectName("InfoText") - detail_description_widget.setTextInteractionFlags( - QtCore.Qt.TextBrowserInteraction - ) - # TODO add HELP button - detail_description_widget.setVisible(False) - - bottom_layout = QtWidgets.QHBoxLayout(bottom_widget) - bottom_layout.setContentsMargins(0, 0, 0, 0) - bottom_layout.addWidget(pre_create_widget, 1) - bottom_layout.addWidget(detail_description_widget, 1) - - layout = QtWidgets.QVBoxLayout(self) + layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) - layout.addWidget(short_desc_widget, 0) - layout.addWidget(separator_widget, 0) - layout.addWidget(bottom_widget, 1) + layout.addWidget(icon_widget, 0) + layout.addWidget(short_desc_input_widget, 1) + # -------------------------------- self._icon_widget = icon_widget self._family_label = family_label self._description_label = description_label - self._detail_description_widget = detail_description_widget - self._pre_create_widget = pre_create_widget def set_plugin(self, plugin=None): if not plugin: self._icon_widget.set_icon_def(None) self._family_label.setText("") self._description_label.setText("") - self._detail_description_widget.setPlainText("") - self._pre_create_widget.set_plugin(plugin) return plugin_icon = plugin.get_icon() description = plugin.get_description() or "" - detailed_description = plugin.get_detail_description() or "" self._icon_widget.set_icon_def(plugin_icon) self._family_label.setText("{}".format(plugin.family)) self._family_label.setTextInteractionFlags(QtCore.Qt.NoTextInteraction) self._description_label.setText(description) - if commonmark: - html = commonmark.commonmark(detailed_description) - self._detail_description_widget.setHtml(html) + +class HelpButton(QtWidgets.QPushButton): + resized = QtCore.Signal() + + def __init__(self, *args, **kwargs): + super(HelpButton, self).__init__(*args, **kwargs) + self.setObjectName("CreateDialogHelpButton") + + self._expanded = None + self.set_expanded() + + def set_expanded(self, expanded=None): + if self._expanded is expanded: + if expanded is not None: + return + expanded = False + self._expanded = expanded + if expanded: + text = "<" else: - self._detail_description_widget.setMarkdown(detailed_description) - self._pre_create_widget.set_plugin(plugin) + text = "?" + self.setText(text) + + self._update_size() + + def _update_size(self): + new_size = self.minimumSizeHint() + if self.size() != new_size: + self.resize(new_size) + self.resized.emit() + + def showEvent(self, event): + super(HelpButton, self).showEvent(event) + self._update_size() + + def resizeEvent(self, event): + super(HelpButton, self).resizeEvent(event) + self._update_size() class CreateDialog(QtWidgets.QDialog): @@ -248,8 +242,7 @@ class CreateDialog(QtWidgets.QDialog): context_layout.addWidget(assets_widget, 2) context_layout.addWidget(tasks_widget, 1) - creator_description_widget = CreatorDescriptionWidget(self) - + # --- Creators view --- creators_view = QtWidgets.QListView(self) creators_model = QtGui.QStandardItemModel() creators_view.setModel(creators_model) @@ -288,24 +281,65 @@ class CreateDialog(QtWidgets.QDialog): mid_layout.addWidget(creators_view, 1) mid_layout.addLayout(form_layout, 0) mid_layout.addWidget(create_btn, 0) + # ------------ + + # --- Creator short info and attr defs --- + creator_attrs_widget = QtWidgets.QWidget(self) + + creator_short_desc_widget = CreatorShortDescWidget( + creator_attrs_widget + ) + + separator_widget = QtWidgets.QWidget(self) + separator_widget.setObjectName("Separator") + separator_widget.setMinimumHeight(2) + separator_widget.setMaximumHeight(2) + + # Precreate attributes widget + pre_create_widget = PreCreateWidget(creator_attrs_widget) + + creator_attrs_layout = QtWidgets.QVBoxLayout(creator_attrs_widget) + creator_attrs_layout.setContentsMargins(0, 0, 0, 0) + creator_attrs_layout.addWidget(creator_short_desc_widget, 0) + creator_attrs_layout.addWidget(separator_widget, 0) + creator_attrs_layout.addWidget(pre_create_widget, 1) + # ------------------------------------- + + # --- Detailed information about creator --- + # Detailed description of creator + detail_description_widget = QtWidgets.QTextEdit(self) + detail_description_widget.setObjectName("InfoText") + detail_description_widget.setTextInteractionFlags( + QtCore.Qt.TextBrowserInteraction + ) + detail_description_widget.setVisible(False) + # ------------------------------------------- splitter_widget = QtWidgets.QSplitter(self) splitter_widget.addWidget(context_widget) splitter_widget.addWidget(mid_widget) - splitter_widget.addWidget(creator_description_widget) + splitter_widget.addWidget(creator_attrs_widget) + splitter_widget.addWidget(detail_description_widget) splitter_widget.setStretchFactor(0, 1) splitter_widget.setStretchFactor(1, 1) splitter_widget.setStretchFactor(2, 1) + splitter_widget.setStretchFactor(3, 1) layout = QtWidgets.QHBoxLayout(self) layout.addWidget(splitter_widget, 1) + # Floating help button + help_btn = HelpButton(self) + prereq_timer = QtCore.QTimer() prereq_timer.setInterval(50) prereq_timer.setSingleShot(True) prereq_timer.timeout.connect(self._on_prereq_timer) + help_btn.clicked.connect(self._on_help_btn) + help_btn.resized.connect(self._on_help_btn_resize) + create_btn.clicked.connect(self._on_create) variant_input.returnPressed.connect(self._on_create) variant_input.textChanged.connect(self._on_variant_change) @@ -326,7 +360,6 @@ class CreateDialog(QtWidgets.QDialog): self._context_widget = context_widget self._assets_widget = assets_widget self._tasks_widget = tasks_widget - self.creator_description_widget = creator_description_widget self.subset_name_input = subset_name_input @@ -339,6 +372,11 @@ class CreateDialog(QtWidgets.QDialog): self.creators_view = creators_view self.create_btn = create_btn + self._creator_short_desc_widget = creator_short_desc_widget + self._pre_create_widget = pre_create_widget + self._detail_description_widget = detail_description_widget + self._help_btn = help_btn + self._prereq_timer = prereq_timer self._first_show = True @@ -532,10 +570,62 @@ class CreateDialog(QtWidgets.QDialog): identifier = new_index.data(CREATOR_IDENTIFIER_ROLE) self._set_creator(identifier) + def _update_help_btn(self): + pos_x = self.width() - self._help_btn.width() + point = self._creator_short_desc_widget.rect().topRight() + mapped_point = self._creator_short_desc_widget.mapTo(self, point) + pos_y = mapped_point.y() + self._help_btn.move(max(0, pos_x), max(0, pos_y)) + + def _on_help_btn_resize(self): + self._update_help_btn() + + def _on_help_btn(self): + final_size = self.size() + cur_sizes = self._splitter_widget.sizes() + spacing = self._splitter_widget.handleWidth() + + sizes = [] + for idx, value in enumerate(cur_sizes): + if idx < 3: + sizes.append(value) + + now_visible = self._detail_description_widget.isVisible() + if now_visible: + width = final_size.width() - ( + spacing + self._detail_description_widget.width() + ) + + else: + last_size = self._detail_description_widget.sizeHint().width() + width = final_size.width() + spacing + last_size + sizes.append(last_size) + + final_size.setWidth(width) + + self._detail_description_widget.setVisible(not now_visible) + self._splitter_widget.setSizes(sizes) + self.resize(final_size) + + self._help_btn.set_expanded(not now_visible) + + def _set_creator_detailed_text(self, creator): + if not creator: + self._detail_description_widget.setPlainText("") + return + detailed_description = creator.get_detail_description() or "" + if commonmark: + html = commonmark.commonmark(detailed_description) + self._detail_description_widget.setHtml(html) + else: + self._detail_description_widget.setMarkdown(detailed_description) + def _set_creator(self, identifier): creator = self.controller.manual_creators.get(identifier) - self.creator_description_widget.set_plugin(creator) + self._creator_short_desc_widget.set_plugin(creator) + self._set_creator_detailed_text(creator) + self._pre_create_widget.set_plugin(creator) self._selected_creator = creator @@ -694,8 +784,14 @@ class CreateDialog(QtWidgets.QDialog): if self._last_pos is not None: self.move(self._last_pos) + self._update_help_btn() + self.refresh() + def resizeEvent(self, event): + super(CreateDialog, self).resizeEvent(event) + self._update_help_btn() + def _on_create(self): indexes = self.creators_view.selectedIndexes() if not indexes or len(indexes) > 1: From be19709107bc7c33781175c6948fa43b38891cd1 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 10 Mar 2022 15:56:02 +0100 Subject: [PATCH 10/15] nuke: fix slate check for frame length --- openpype/hosts/nuke/plugins/publish/extract_slate_frame.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py index 50e5f995f4..a91181c81b 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py +++ b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py @@ -48,8 +48,13 @@ class ExtractSlateFrame(openpype.api.Extractor): self.log.info( "StagingDir `{0}`...".format(instance.data["stagingDir"])) + frame_start = instance.data["frameStart"] + frame_end = instance.data["frameEnd"] + handle_start = instance.data["handleStart"] + handle_end = instance.data["handleEnd"] + frame_length = int( - instance.data["frameEnd"] - instance.data["frameStart"] + 1 + (frame_start - frame_end + 1) + (handle_start + handle_end) ) temporary_nodes = [] From 32923208687a221fb11f2f85cb43680292bc3981 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 10 Mar 2022 15:56:39 +0100 Subject: [PATCH 11/15] implemented get asset icon function --- openpype/tools/utils/__init__.py | 1 + openpype/tools/utils/assets_widget.py | 27 +++++------------ openpype/tools/utils/lib.py | 42 +++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 20 deletions(-) diff --git a/openpype/tools/utils/__init__.py b/openpype/tools/utils/__init__.py index c15e9f8139..6ab9e75b52 100644 --- a/openpype/tools/utils/__init__.py +++ b/openpype/tools/utils/__init__.py @@ -16,6 +16,7 @@ from .lib import ( set_style_property, DynamicQThread, qt_app_context, + get_asset_icon, ) from .models import ( diff --git a/openpype/tools/utils/assets_widget.py b/openpype/tools/utils/assets_widget.py index d410b0f1c3..4c77b81c0e 100644 --- a/openpype/tools/utils/assets_widget.py +++ b/openpype/tools/utils/assets_widget.py @@ -16,7 +16,10 @@ from .views import ( ) from .widgets import PlaceholderLineEdit from .models import RecursiveSortFilterProxyModel -from .lib import DynamicQThread +from .lib import ( + DynamicQThread, + get_asset_icon +) if Qt.__binding__ == "PySide": from PySide.QtGui import QStyleOptionViewItemV4 @@ -508,25 +511,9 @@ class AssetModel(QtGui.QStandardItemModel): item.setData(asset_label, QtCore.Qt.DisplayRole) item.setData(asset_label, ASSET_LABEL_ROLE) - icon_color = asset_data.get("color") or style.colors.default - icon_name = asset_data.get("icon") - if not icon_name: - # Use default icons if no custom one is specified. - # If it has children show a full folder, otherwise - # show an open folder - if item.rowCount() > 0: - icon_name = "folder" - else: - icon_name = "folder-o" - - try: - # font-awesome key - full_icon_name = "fa.{0}".format(icon_name) - icon = qtawesome.icon(full_icon_name, color=icon_color) - item.setData(icon, QtCore.Qt.DecorationRole) - - except Exception: - pass + has_children = item.rowCount() > 0 + icon = get_asset_icon(asset_data, has_children) + item.setData(icon, QtCore.Qt.DecorationRole) def _threaded_fetch(self): asset_docs = self._fetch_asset_docs() diff --git a/openpype/tools/utils/lib.py b/openpype/tools/utils/lib.py index 1cbc632804..d57b44728d 100644 --- a/openpype/tools/utils/lib.py +++ b/openpype/tools/utils/lib.py @@ -98,6 +98,48 @@ application = qt_app_context class SharedObjects: jobs = {} + icons = {} + + +def get_qta_icon_by_name_and_color(icon_name, icon_color): + if not icon_name or not icon_color: + return None + + full_icon_name = "{0}-{1}".format(icon_name, icon_color) + if full_icon_name in SharedObjects.icons: + return SharedObjects.icons[full_icon_name] + + variants = [icon_name] + qta_instance = qtawesome._instance() + for key in qta_instance.charmap.keys(): + variants.append("{0}.{1}".format(key, icon_name)) + + icon = None + for variant in variants: + try: + icon = qtawesome.icon(variant, color=icon_color) + break + except Exception: + pass + + SharedObjects.icons[full_icon_name] = icon + return icon + + +def get_asset_icon(asset_doc, has_children=False): + asset_data = asset_doc.get("data") or {} + icon_color = asset_data.get("color") or style.colors.default + icon_name = asset_data.get("icon") + if not icon_name: + # Use default icons if no custom one is specified. + # If it has children show a full folder, otherwise + # show an open folder + if has_children: + icon_name = "folder" + else: + icon_name = "folder-o" + + return get_qta_icon_by_name_and_color(icon_name, icon_color) def schedule(func, time, channel="default"): From ff440612a2bc00b84ebd9275192f12025732bb62 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 10 Mar 2022 15:56:50 +0100 Subject: [PATCH 12/15] added icons into publisher UI --- openpype/tools/publisher/widgets/assets_widget.py | 7 ++++++- openpype/tools/publisher/widgets/tasks_widget.py | 3 +++ openpype/tools/utils/lib.py | 10 ++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/openpype/tools/publisher/widgets/assets_widget.py b/openpype/tools/publisher/widgets/assets_widget.py index b8696a2665..984da59c77 100644 --- a/openpype/tools/publisher/widgets/assets_widget.py +++ b/openpype/tools/publisher/widgets/assets_widget.py @@ -3,7 +3,8 @@ import collections from Qt import QtWidgets, QtCore, QtGui from openpype.tools.utils import ( PlaceholderLineEdit, - RecursiveSortFilterProxyModel + RecursiveSortFilterProxyModel, + get_asset_icon, ) from openpype.tools.utils.assets_widget import ( SingleSelectAssetsWidget, @@ -102,11 +103,15 @@ class AssetsHierarchyModel(QtGui.QStandardItemModel): for name in sorted(children_by_name.keys()): child = children_by_name[name] child_id = child["_id"] + has_children = bool(assets_by_parent_id.get(child_id)) + icon = get_asset_icon(child, has_children) + item = QtGui.QStandardItem(name) item.setFlags( QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable ) + item.setData(icon, QtCore.Qt.DecorationRole) item.setData(child_id, ASSET_ID_ROLE) item.setData(name, ASSET_NAME_ROLE) diff --git a/openpype/tools/publisher/widgets/tasks_widget.py b/openpype/tools/publisher/widgets/tasks_widget.py index 2d1cc017af..8a913b7114 100644 --- a/openpype/tools/publisher/widgets/tasks_widget.py +++ b/openpype/tools/publisher/widgets/tasks_widget.py @@ -1,6 +1,7 @@ from Qt import QtCore, QtGui from openpype.tools.utils.tasks_widget import TasksWidget, TASK_NAME_ROLE +from openpype.tools.utils.lib import get_task_icon class TasksModel(QtGui.QStandardItemModel): @@ -118,6 +119,8 @@ class TasksModel(QtGui.QStandardItemModel): item = QtGui.QStandardItem(task_name) item.setData(task_name, TASK_NAME_ROLE) + if task_name: + item.setData(get_task_icon(), QtCore.Qt.DecorationRole) self._items_by_name[task_name] = item new_items.append(item) diff --git a/openpype/tools/utils/lib.py b/openpype/tools/utils/lib.py index d57b44728d..042ceaab88 100644 --- a/openpype/tools/utils/lib.py +++ b/openpype/tools/utils/lib.py @@ -142,6 +142,16 @@ def get_asset_icon(asset_doc, has_children=False): return get_qta_icon_by_name_and_color(icon_name, icon_color) +def get_task_icon(): + """Get icon for a task. + + TODO: Get task icon based on data in database. + + Icon should be defined by task type which is stored on project. + """ + return get_qta_icon_by_name_and_color("fa.male", style.colors.default) + + def schedule(func, time, channel="default"): """Run `func` at a later `time` in a dedicated `channel` From 87719ed878a55ec096a9cf57f0e3493577b3b3ce Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 10 Mar 2022 15:59:28 +0100 Subject: [PATCH 13/15] nuke: wrong expression --- openpype/hosts/nuke/plugins/publish/extract_slate_frame.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py index a91181c81b..e917a28046 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py +++ b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py @@ -54,7 +54,7 @@ class ExtractSlateFrame(openpype.api.Extractor): handle_end = instance.data["handleEnd"] frame_length = int( - (frame_start - frame_end + 1) + (handle_start + handle_end) + (frame_end - frame_start + 1) + (handle_start + handle_end) ) temporary_nodes = [] From 0b36fc2c65356b9b787cb79e58cf3822c4bd1e81 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 10 Mar 2022 16:08:12 +0100 Subject: [PATCH 14/15] fixing reformat in extract review when slate reformate --- openpype/plugins/publish/extract_review.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index bec1f75425..f9a02f58bb 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -1165,6 +1165,18 @@ class ExtractReview(pyblish.api.InstancePlugin): input_height = int(stream["height"]) break + # Get instance data + pixel_aspect = temp_data["pixel_aspect"] + + if reformat_in_baking: + self.log.debug(( + "Using resolution from input. It is already " + "reformated from upstream process" + )) + pixel_aspect = 1 + output_width = input_width + output_height = input_height + # Raise exception of any stream didn't define input resolution if input_width is None: raise AssertionError(( From 99d7912495b5a57eba48f5d614aac6cd08e7d1a3 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 10 Mar 2022 16:12:11 +0100 Subject: [PATCH 15/15] defining default none values for Output resolution --- openpype/plugins/publish/extract_review.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index f9a02f58bb..0b139a73e4 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -1159,6 +1159,8 @@ class ExtractReview(pyblish.api.InstancePlugin): # - there may be a better way (checking `codec_type`?) input_width = None input_height = None + output_width = None + output_height = None for stream in streams: if "width" in stream and "height" in stream: input_width = int(stream["width"]) @@ -1185,8 +1187,8 @@ class ExtractReview(pyblish.api.InstancePlugin): # NOTE Setting only one of `width` or `heigth` is not allowed # - settings value can't have None but has value of 0 - output_width = output_def.get("width") or None - output_height = output_def.get("height") or None + output_width = output_width or output_def.get("width") or None + output_height = output_height or output_def.get("height") or None # Overscal color overscan_color_value = "black"