From 2d7bc885ee7e8454edce483b00877bb23ae93245 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 18 Mar 2024 15:51:25 +0000 Subject: [PATCH 1/5] Initial working version --- openpype/hosts/nuke/api/pipeline.py | 5 + openpype/hosts/nuke/api/push_to_project.py | 128 ++++++++++++++++++ openpype/tools/push_to_project/app.py | 24 +++- .../tools/push_to_project/control_context.py | 18 ++- openpype/tools/push_to_project/window.py | 36 +++-- 5 files changed, 194 insertions(+), 17 deletions(-) create mode 100644 openpype/hosts/nuke/api/push_to_project.py diff --git a/openpype/hosts/nuke/api/pipeline.py b/openpype/hosts/nuke/api/pipeline.py index c2fc684c21b..2cff55030d1 100644 --- a/openpype/hosts/nuke/api/pipeline.py +++ b/openpype/hosts/nuke/api/pipeline.py @@ -64,6 +64,7 @@ current_file ) from .constants import ASSIST +from . import push_to_project log = Logger.get_logger(__name__) @@ -337,6 +338,10 @@ def _install_menu(): "Experimental tools...", lambda: host_tools.show_experimental_tools_dialog(parent=main_window) ) + menu.addCommand( + "Push to Project", + lambda: push_to_project.main() + ) menu.addSeparator() # add reload pipeline only in debug mode if bool(os.getenv("NUKE_DEBUG")): diff --git a/openpype/hosts/nuke/api/push_to_project.py b/openpype/hosts/nuke/api/push_to_project.py new file mode 100644 index 00000000000..86a3e29390c --- /dev/null +++ b/openpype/hosts/nuke/api/push_to_project.py @@ -0,0 +1,128 @@ +from collections import defaultdict +import shutil +import os +import json +import re + +from openpype.client import get_project, get_asset_by_id +from openpype.settings import get_system_settings, get_project_settings +from openpype.pipeline import Anatomy, registered_host +from openpype.pipeline.template_data import get_template_data +from openpype.pipeline.workfile import get_workdir_with_workdir_data +from openpype import PACKAGE_DIR +from openpype.lib import get_openpype_execute_args, run_subprocess + +import nuke + + +def bake_container(container): + """Bake containers to read nodes.""" + + node = container["node"] + + # Fetch knobs to remove in order. + knobs_to_remove = [] + remove = False + for count in range(0, node.numKnobs()): + knob = node.knob(count) + + # All knobs from "OpenPype" tab knob onwards. + if knob.name() == "OpenPype": + remove = True + + if remove: + knobs_to_remove.append(knob) + + # Dont remove knobs from "containerId" onwards. + if knob.name() == "containerId": + remove = False + + # Knobs needs to be remove in reverse order, because child knobs needs to + # be remove first. + for knob in reversed(knobs_to_remove): + node.removeKnob(knob) + + node["tile_color"].setValue(0) + + +def main(): + # Get project name, asset id and task name. + push_tool_script_path = os.path.join( + PACKAGE_DIR, + "tools", + "push_to_project", + "app.py" + ) + + args = get_openpype_execute_args( + "run", + push_tool_script_path, + "--library_filter", "False", + "--context_only", "True" + ) + output = run_subprocess(args) + dict_string = re.search(r'\{.*\}', output).group() + result = json.loads(dict_string) + + # Get workfile path to save to. + project_name = result["project_name"] + project_doc = get_project(project_name) + asset_doc = get_asset_by_id(project_name, result["asset_id"]) + task_name = result["task_name"] + host = registered_host() + system_settings = get_system_settings() + project_settings = get_project_settings(project_name) + anatomy = Anatomy(project_name) + + workdir_data = get_template_data( + project_doc, asset_doc, task_name, host.name, system_settings + ) + + workdir = get_workdir_with_workdir_data( + workdir_data, + project_name, + anatomy, + project_settings=project_settings + ) + + # Warn about saving current workfile. + # Save current workfile. + current_file = host.current_file() + host.save_file(current_file) + + for container in host.ls(): + bake_container(container) + + # Copy all read node files to "resources" folder next to workfile and + # change file path. + first_frame = int(nuke.root()["first_frame"].value()) + last_frame = int(nuke.root()["last_frame"].value()) + files_by_node_name = defaultdict(set) + nodes_by_name = {} + for count in range(first_frame, last_frame + 1): + nuke.frame(count) + for node in nuke.allNodes(filter="Read"): + files_by_node_name[node.name()].add( + nuke.filename(node, nuke.REPLACE) + ) + nodes_by_name[node.name()] = node + + resources_dir = os.path.join(workdir, "resources") + for name, files in files_by_node_name.items(): + dir = os.path.join(resources_dir, name) + if not os.path.exists(dir): + os.makedirs(dir) + + for f in files: + shutil.copy(f, os.path.join(dir, os.path.basename(f))) + + node = nodes_by_name[name] + path = node["file"].value().replace(os.path.dirname(f), dir) + node["file"].setValue(path.replace("\\", "/")) + + # Save current workfile to new context. + basename = os.path.basename(current_file) + host.save_file(os.path.join(workdir, basename)) + + # Open current contex workfile. + host.open_file(current_file) diff --git a/openpype/tools/push_to_project/app.py b/openpype/tools/push_to_project/app.py index b3ec33f353a..3bac2956360 100644 --- a/openpype/tools/push_to_project/app.py +++ b/openpype/tools/push_to_project/app.py @@ -1,3 +1,5 @@ +import json + import click from openpype.tools.utils import get_openpype_qt_app @@ -7,22 +9,38 @@ @click.command() @click.option("--project", help="Source project name") @click.option("--version", help="Source version id") -def main(project, version): +@click.option("--library_filter", help="Filter to library projects only.") +@click.option("--context_only", help="Return new context only.") +def main(project, version, library_filter, context_only): """Run PushToProject tool to integrate version in different project. Args: project (str): Source project name. version (str): Version id. + version (bool): Filter to library projects only. """ - app = get_openpype_qt_app() - window = PushToContextSelectWindow() + if library_filter is None: + library_filter = True + else: + library_filter = library_filter == "True" + + if context_only is None: + context_only = True + else: + context_only = context_only == "True" + + window = PushToContextSelectWindow( + library_filter=library_filter, context_only=context_only + ) window.show() window.controller.set_source(project, version) app.exec_() + print(json.dumps(window.context)) + if __name__ == "__main__": main() diff --git a/openpype/tools/push_to_project/control_context.py b/openpype/tools/push_to_project/control_context.py index e4058893d5f..d0468cc42ab 100644 --- a/openpype/tools/push_to_project/control_context.py +++ b/openpype/tools/push_to_project/control_context.py @@ -82,12 +82,13 @@ def from_asset_doc(cls, asset_doc, project_doc): class EntitiesModel: - def __init__(self, event_system): + def __init__(self, event_system, library_filter=True): self._event_system = event_system self._project_names = None self._project_docs_by_name = {} self._assets_by_project = {} self._tasks_by_asset_id = collections.defaultdict(dict) + self.library_filter = library_filter def has_cached_projects(self): return self._project_names is None @@ -135,7 +136,7 @@ def refresh_projects(self, force=False): project_docs_by_name = {} for project_doc in get_projects(): library_project = project_doc["data"].get("library_project") - if not library_project: + if not library_project and self.library_filter: continue project_name = project_doc["name"] project_names.append(project_name) @@ -366,7 +367,9 @@ def set_comment(self, comment): class PushToContextController: - def __init__(self, project_name=None, version_id=None): + def __init__( + self, project_name=None, version_id=None, library_filter=True + ): self._src_project_name = None self._src_version_id = None self._src_asset_doc = None @@ -374,7 +377,9 @@ def __init__(self, project_name=None, version_id=None): self._src_version_doc = None event_system = EventSystem() - entities_model = EntitiesModel(event_system) + entities_model = EntitiesModel( + event_system, library_filter=library_filter + ) selection_model = SelectionModel(event_system) user_values = UserPublishValues(event_system) @@ -629,13 +634,16 @@ def get_selected_asset_name(self): return asset_item.name return None - def submit(self, wait=True): + def submit(self, wait=True, context_only=False): if not self.submission_enabled: return if self._process_thread is not None: return + if context_only: + return + item = ProjectPushItem( self.src_project_name, self.src_version_id, diff --git a/openpype/tools/push_to_project/window.py b/openpype/tools/push_to_project/window.py index c0c47fd40ee..bcc5a8dcb95 100644 --- a/openpype/tools/push_to_project/window.py +++ b/openpype/tools/push_to_project/window.py @@ -368,11 +368,14 @@ def _refresh(self, new_project_name): class PushToContextSelectWindow(QtWidgets.QWidget): - def __init__(self, controller=None): + def __init__( + self, controller=None, library_filter=True, context_only=False + ): super(PushToContextSelectWindow, self).__init__() if controller is None: - controller = PushToContextController() + controller = PushToContextController(library_filter=library_filter) self._controller = controller + self.context_only = context_only self.setWindowTitle("Push to project (select context)") self.setWindowIcon(QtGui.QIcon(get_app_icon_path())) @@ -455,13 +458,13 @@ def __init__(self, controller=None): # --- Buttons widget --- btns_widget = QtWidgets.QWidget(self) cancel_btn = QtWidgets.QPushButton("Cancel", btns_widget) - publish_btn = QtWidgets.QPushButton("Publish", btns_widget) + push_btn = QtWidgets.QPushButton("Push", btns_widget) btns_layout = QtWidgets.QHBoxLayout(btns_widget) btns_layout.setContentsMargins(0, 0, 0, 0) btns_layout.addStretch(1) btns_layout.addWidget(cancel_btn, 0) - btns_layout.addWidget(publish_btn, 0) + btns_layout.addWidget(push_btn, 0) sep_1 = SeparatorWidget(parent=main_context_widget) sep_2 = SeparatorWidget(parent=main_context_widget) @@ -535,7 +538,7 @@ def __init__(self, controller=None): self._on_task_change ) task_model.items_changed.connect(self._on_task_model_change) - publish_btn.clicked.connect(self._on_select_click) + push_btn.clicked.connect(self._on_select_click) cancel_btn.clicked.connect(self._on_close_click) overlay_close_btn.clicked.connect(self._on_close_click) overlay_try_btn.clicked.connect(self._on_try_again_click) @@ -588,7 +591,7 @@ def __init__(self, controller=None): self._asset_name_input = asset_name_input self._comment_input = comment_input - self._publish_btn = publish_btn + self._push_btn = push_btn self._overlay_widget = overlay_widget self._overlay_close_btn = overlay_close_btn @@ -612,7 +615,7 @@ def __init__(self, controller=None): self._last_submit_message = None self._process_item = None - publish_btn.setEnabled(False) + push_btn.setEnabled(False) overlay_close_btn.setVisible(False) overlay_try_btn.setVisible(False) @@ -774,13 +777,28 @@ def _on_task_change(self): self._controller.selection_model.select_task(task_name) def _on_submission_change(self, event): - self._publish_btn.setEnabled(event["enabled"]) + self._push_btn.setEnabled(event["enabled"]) def _on_close_click(self): self.close() def _on_select_click(self): - self._process_item = self._controller.submit(wait=False) + result = self._controller.submit( + wait=True, context_only=self.context_only + ) + + if self.context_only: + self.context = { + "project_name": self._controller.selection_model.project_name, + "asset_id": self._controller.selection_model.asset_id, + "task_name": self._controller.selection_model.task_name, + "variant": self._controller.user_values.variant, + "comment": self._controller.user_values.comment, + "asset_name": self._controller.user_values.new_asset_name + } + self.close() + + self._process_item = result def _on_try_again_click(self): self._process_item = None From 1a6c6d8eb2c67e9726ed0c4fb81dda2a742ae2c9 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 21 Mar 2024 11:54:16 +0000 Subject: [PATCH 2/5] bake gizmos --- openpype/hosts/nuke/api/push_to_project.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/hosts/nuke/api/push_to_project.py b/openpype/hosts/nuke/api/push_to_project.py index 86a3e29390c..0609f3cdf0b 100644 --- a/openpype/hosts/nuke/api/push_to_project.py +++ b/openpype/hosts/nuke/api/push_to_project.py @@ -11,6 +11,7 @@ from openpype.pipeline.workfile import get_workdir_with_workdir_data from openpype import PACKAGE_DIR from openpype.lib import get_openpype_execute_args, run_subprocess +from .utils import bake_gizmos_recursively import nuke @@ -93,6 +94,9 @@ def main(): for container in host.ls(): bake_container(container) + # Bake gizmos. + bake_gizmos_recursively() + # Copy all read node files to "resources" folder next to workfile and # change file path. first_frame = int(nuke.root()["first_frame"].value()) From 7153e826acc6584922ed2e2142eba3978e372075 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Sun, 7 Apr 2024 20:49:45 +0100 Subject: [PATCH 3/5] Code cosmetics --- openpype/hosts/nuke/api/push_to_project.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/nuke/api/push_to_project.py b/openpype/hosts/nuke/api/push_to_project.py index 0609f3cdf0b..a0d54b91a55 100644 --- a/openpype/hosts/nuke/api/push_to_project.py +++ b/openpype/hosts/nuke/api/push_to_project.py @@ -86,7 +86,6 @@ def main(): project_settings=project_settings ) - # Warn about saving current workfile. # Save current workfile. current_file = host.current_file() host.save_file(current_file) From 143148fca0c94954115e1d08ea64bfa078dd6655 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 8 Apr 2024 13:51:09 +0100 Subject: [PATCH 4/5] Show app in main window. --- openpype/hosts/nuke/api/push_to_project.py | 27 ++++--------- openpype/tools/push_to_project/app.py | 44 +++++++++------------- openpype/tools/push_to_project/window.py | 3 +- 3 files changed, 28 insertions(+), 46 deletions(-) diff --git a/openpype/hosts/nuke/api/push_to_project.py b/openpype/hosts/nuke/api/push_to_project.py index a0d54b91a55..644a7f1973b 100644 --- a/openpype/hosts/nuke/api/push_to_project.py +++ b/openpype/hosts/nuke/api/push_to_project.py @@ -11,6 +11,8 @@ from openpype.pipeline.workfile import get_workdir_with_workdir_data from openpype import PACKAGE_DIR from openpype.lib import get_openpype_execute_args, run_subprocess +from openpype.tools.push_to_project.app import show + from .utils import bake_gizmos_recursively import nuke @@ -47,29 +49,16 @@ def bake_container(container): def main(): - # Get project name, asset id and task name. - push_tool_script_path = os.path.join( - PACKAGE_DIR, - "tools", - "push_to_project", - "app.py" - ) + context = show("", "", False, True) - args = get_openpype_execute_args( - "run", - push_tool_script_path, - "--library_filter", "False", - "--context_only", "True" - ) - output = run_subprocess(args) - dict_string = re.search(r'\{.*\}', output).group() - result = json.loads(dict_string) + if context is None: + return # Get workfile path to save to. - project_name = result["project_name"] + project_name = context["project_name"] project_doc = get_project(project_name) - asset_doc = get_asset_by_id(project_name, result["asset_id"]) - task_name = result["task_name"] + asset_doc = get_asset_by_id(project_name, context["asset_id"]) + task_name = context["task_name"] host = registered_host() system_settings = get_system_settings() project_settings = get_project_settings(project_name) diff --git a/openpype/tools/push_to_project/app.py b/openpype/tools/push_to_project/app.py index 3bac2956360..048cab5b1d1 100644 --- a/openpype/tools/push_to_project/app.py +++ b/openpype/tools/push_to_project/app.py @@ -1,17 +1,29 @@ -import json - import click from openpype.tools.utils import get_openpype_qt_app from openpype.tools.push_to_project.window import PushToContextSelectWindow +def show(project, version, library_filter, context_only): + window = PushToContextSelectWindow( + library_filter=library_filter, context_only=context_only + ) + window.show() + window.controller.set_source(project, version) + + if __name__ == "__main__": + app = get_openpype_qt_app() + app.exec_() + else: + window.exec_() + + return window.context + + @click.command() @click.option("--project", help="Source project name") @click.option("--version", help="Source version id") -@click.option("--library_filter", help="Filter to library projects only.") -@click.option("--context_only", help="Return new context only.") -def main(project, version, library_filter, context_only): +def main(project, version): """Run PushToProject tool to integrate version in different project. Args: @@ -19,27 +31,7 @@ def main(project, version, library_filter, context_only): version (str): Version id. version (bool): Filter to library projects only. """ - app = get_openpype_qt_app() - - if library_filter is None: - library_filter = True - else: - library_filter = library_filter == "True" - - if context_only is None: - context_only = True - else: - context_only = context_only == "True" - - window = PushToContextSelectWindow( - library_filter=library_filter, context_only=context_only - ) - window.show() - window.controller.set_source(project, version) - - app.exec_() - - print(json.dumps(window.context)) + show(project, version, True, False) if __name__ == "__main__": diff --git a/openpype/tools/push_to_project/window.py b/openpype/tools/push_to_project/window.py index bcc5a8dcb95..540614f10e1 100644 --- a/openpype/tools/push_to_project/window.py +++ b/openpype/tools/push_to_project/window.py @@ -367,7 +367,7 @@ def _refresh(self, new_project_name): self.items_changed.emit() -class PushToContextSelectWindow(QtWidgets.QWidget): +class PushToContextSelectWindow(QtWidgets.QDialog): def __init__( self, controller=None, library_filter=True, context_only=False ): @@ -376,6 +376,7 @@ def __init__( controller = PushToContextController(library_filter=library_filter) self._controller = controller self.context_only = context_only + self.context = None self.setWindowTitle("Push to project (select context)") self.setWindowIcon(QtGui.QIcon(get_app_icon_path())) From 0b36fa31910fc64f2dec474204c65ab8b5bf3f89 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 8 Apr 2024 13:51:52 +0100 Subject: [PATCH 5/5] Hound --- openpype/hosts/nuke/api/push_to_project.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/openpype/hosts/nuke/api/push_to_project.py b/openpype/hosts/nuke/api/push_to_project.py index 644a7f1973b..b386b2e1cdc 100644 --- a/openpype/hosts/nuke/api/push_to_project.py +++ b/openpype/hosts/nuke/api/push_to_project.py @@ -1,16 +1,12 @@ from collections import defaultdict import shutil import os -import json -import re from openpype.client import get_project, get_asset_by_id from openpype.settings import get_system_settings, get_project_settings from openpype.pipeline import Anatomy, registered_host from openpype.pipeline.template_data import get_template_data from openpype.pipeline.workfile import get_workdir_with_workdir_data -from openpype import PACKAGE_DIR -from openpype.lib import get_openpype_execute_args, run_subprocess from openpype.tools.push_to_project.app import show from .utils import bake_gizmos_recursively