diff --git a/pype/lib.py b/pype/lib.py index afcfa98307..4017686c6d 100644 --- a/pype/lib.py +++ b/pype/lib.py @@ -864,10 +864,10 @@ class BuildWorkfile: current_task_name = io.Session["AVALON_TASK"] # Load workfile presets for task - build_presets = self.get_build_presets(current_task_name) + self.build_presets = self.get_build_presets(current_task_name) # Skip if there are any presets for task - if not build_presets: + if not self.build_presets: log.warning( "Current task `{}` does not have any loading preset.".format( current_task_name @@ -876,9 +876,9 @@ class BuildWorkfile: return # Get presets for loading current asset - current_context_profiles = build_presets.get("current_context") + current_context_profiles = self.build_presets.get("current_context") # Get presets for loading linked assets - link_context_profiles = build_presets.get("linked_assets") + link_context_profiles = self.build_presets.get("linked_assets") # Skip if both are missing if not current_context_profiles and not link_context_profiles: log.warning("Current task `{}` has empty loading preset.".format( @@ -1231,7 +1231,36 @@ class BuildWorkfile: :rtype: list """ loaded_containers = [] - for subset_id, repres in repres_by_subset_id.items(): + + # Get subset id order from build presets. + build_presets = self.build_presets.get("current_context", []) + build_presets += self.build_presets.get("linked_assets", []) + subset_ids_ordered = [] + for preset in build_presets: + for preset_family in preset["families"]: + for id, subset in subsets_by_id.items(): + if preset_family not in subset["data"].get("families", []): + continue + + subset_ids_ordered.append(id) + + # Order representations from subsets. + print("repres_by_subset_id", repres_by_subset_id) + representations_ordered = [] + representations = [] + for id in subset_ids_ordered: + for subset_id, repres in repres_by_subset_id.items(): + if repres in representations: + continue + + if id == subset_id: + representations_ordered.append((subset_id, repres)) + representations.append(repres) + + print("representations", representations) + + # Load ordered reprensentations. + for subset_id, repres in representations_ordered: subset_name = subsets_by_id[subset_id]["name"] profile = profiles_per_subset_id[subset_id] diff --git a/pype/plugins/global/publish/extract_burnin.py b/pype/plugins/global/publish/extract_burnin.py index bf29e12eab..c856bf602f 100644 --- a/pype/plugins/global/publish/extract_burnin.py +++ b/pype/plugins/global/publish/extract_burnin.py @@ -157,6 +157,11 @@ class ExtractBurnin(pype.api.Extractor): filled_anatomy = anatomy.format_all(burnin_data) burnin_data["anatomy"] = filled_anatomy.get_solved() + # Add source camera name to burnin data + camera_name = repre.get("camera_name") + if camera_name: + burnin_data["camera_name"] = camera_name + first_output = True files_to_delete = [] diff --git a/pype/plugins/maya/load/load_image_plane.py b/pype/plugins/maya/load/load_image_plane.py index e17382f7ed..1bb29e90c4 100644 --- a/pype/plugins/maya/load/load_image_plane.py +++ b/pype/plugins/maya/load/load_image_plane.py @@ -4,14 +4,70 @@ import maya.cmds as cmds from avalon import api, io from avalon.maya.pipeline import containerise from avalon.maya import lib -from Qt import QtWidgets +from Qt import QtWidgets, QtCore + + +class CameraWindow(QtWidgets.QDialog): + + def __init__(self, cameras): + super(CameraWindow, self).__init__() + self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint) + + self.camera = None + + self.widgets = { + "label": QtWidgets.QLabel("Select camera for image plane."), + "list": QtWidgets.QListWidget(), + "warning": QtWidgets.QLabel("No cameras selected!"), + "buttons": QtWidgets.QWidget(), + "okButton": QtWidgets.QPushButton("Ok"), + "cancelButton": QtWidgets.QPushButton("Cancel") + } + + # Build warning. + self.widgets["warning"].setVisible(False) + self.widgets["warning"].setStyleSheet("color: red") + + # Build list. + for camera in cameras: + self.widgets["list"].addItem(camera) + + # Build buttons. + layout = QtWidgets.QHBoxLayout(self.widgets["buttons"]) + layout.addWidget(self.widgets["okButton"]) + layout.addWidget(self.widgets["cancelButton"]) + + # Build layout. + layout = QtWidgets.QVBoxLayout(self) + layout.addWidget(self.widgets["label"]) + layout.addWidget(self.widgets["list"]) + layout.addWidget(self.widgets["buttons"]) + layout.addWidget(self.widgets["warning"]) + + self.widgets["okButton"].pressed.connect(self.on_ok_pressed) + self.widgets["cancelButton"].pressed.connect(self.on_cancel_pressed) + self.widgets["list"].itemPressed.connect(self.on_list_itemPressed) + + def on_list_itemPressed(self, item): + self.camera = item.text() + + def on_ok_pressed(self): + if self.camera is None: + self.widgets["warning"].setVisible(True) + return + + self.close() + + def on_cancel_pressed(self): + self.camera = None + self.close() class ImagePlaneLoader(api.Loader): """Specific loader of plate for image planes on selected camera.""" families = ["plate", "render"] - label = "Create imagePlane on selected camera." + label = "Load imagePlane." representations = ["mov", "exr", "preview", "png"] icon = "image" color = "orange" @@ -26,43 +82,24 @@ class ImagePlaneLoader(api.Loader): suffix="_", ) - # Getting camera from selection. - selection = pc.ls(selection=True) - + # Get camera from user selection. camera = None + default_cameras = [ + "frontShape", "perspShape", "sideShape", "topShape" + ] + cameras = [ + x for x in pc.ls(type="camera") if x.name() not in default_cameras + ] + camera_names = {x.getParent().name(): x for x in cameras} + camera_names["Create new camera."] = "create_camera" + window = CameraWindow(camera_names.keys()) + window.exec_() + camera = camera_names[window.camera] - if len(selection) > 1: - QtWidgets.QMessageBox.critical( - None, - "Error!", - "Multiple nodes selected. Please select only one.", - QtWidgets.QMessageBox.Ok - ) - return + if camera == "create_camera": + camera = pc.createNode("camera") - if len(selection) < 1: - result = QtWidgets.QMessageBox.critical( - None, - "Error!", - "No camera selected. Do you want to create a camera?", - QtWidgets.QMessageBox.Ok, - QtWidgets.QMessageBox.Cancel - ) - if result == QtWidgets.QMessageBox.Ok: - camera = pc.createNode("camera") - else: - return - else: - relatives = pc.listRelatives(selection[0], shapes=True) - if pc.ls(relatives, type="camera"): - camera = selection[0] - else: - QtWidgets.QMessageBox.critical( - None, - "Error!", - "Selected node is not a camera.", - QtWidgets.QMessageBox.Ok - ) + if camera is None: return try: @@ -100,10 +137,16 @@ class ImagePlaneLoader(api.Loader): # Ensure OpenEXRLoader plugin is loaded. pc.loadPlugin("OpenEXRLoader.mll", quiet=True) + message = ( + "Hold image sequence on first frame?" + "\n{} files available.".format( + len(context["representation"]["files"]) + ) + ) reply = QtWidgets.QMessageBox.information( None, "Frame Hold.", - "Hold image sequence on first frame?", + message, QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.Cancel ) diff --git a/pype/plugins/maya/publish/extract_playblast.py b/pype/plugins/maya/publish/extract_playblast.py index 8f8b7fcb36..647d1f4503 100644 --- a/pype/plugins/maya/publish/extract_playblast.py +++ b/pype/plugins/maya/publish/extract_playblast.py @@ -110,6 +110,9 @@ class ExtractPlayblast(pype.api.Extractor): if not instance.data.get("keepImages"): tags.append("delete") + # Add camera node name to representation data + camera_node_name = pm.ls(camera)[0].getTransform().getName() + representation = { 'name': 'png', 'ext': 'png', @@ -119,7 +122,8 @@ class ExtractPlayblast(pype.api.Extractor): "frameEnd": end, 'fps': fps, 'preview': True, - 'tags': tags + 'tags': tags, + 'camera_name': camera_node_name } instance.data["representations"].append(representation) diff --git a/pype/plugins/tvpaint/load/load_image.py b/pype/plugins/tvpaint/load/load_image.py index 72bd23a720..5ab67e3df4 100644 --- a/pype/plugins/tvpaint/load/load_image.py +++ b/pype/plugins/tvpaint/load/load_image.py @@ -1,5 +1,6 @@ from avalon import api -from avalon.tvpaint import lib +from avalon.vendor import qargparse +from avalon.tvpaint import CommunicatorWrapper class ImportImage(api.Loader): @@ -16,11 +17,54 @@ class ImportImage(api.Loader): import_script = ( "filepath = \"{}\"\n" "layer_name = \"{}\"\n" - "tv_loadsequence filepath \"preload\" PARSE layer_id\n" + "tv_loadsequence filepath {}PARSE layer_id\n" "tv_layerrename layer_id layer_name" ) + defaults = { + "stretch": True, + "timestretch": True, + "preload": True + } + + options = [ + qargparse.Boolean( + "stretch", + label="Stretch to project size", + default=True, + help="Stretch loaded image/s to project resolution?" + ), + qargparse.Boolean( + "timestretch", + label="Stretch to timeline length", + default=True, + help="Clip loaded image/s to timeline length?" + ), + qargparse.Boolean( + "preload", + label="Preload loaded image/s", + default=True, + help="Preload image/s?" + ) + ] + def load(self, context, name, namespace, options): + stretch = options.get("stretch", self.defaults["stretch"]) + timestretch = options.get("timestretch", self.defaults["timestretch"]) + preload = options.get("preload", self.defaults["preload"]) + + load_options = [] + if stretch: + load_options.append("\"STRETCH\"") + if timestretch: + load_options.append("\"TIMESTRETCH\"") + if preload: + load_options.append("\"PRELOAD\"") + + load_options_str = "" + for load_option in load_options: + load_options_str += (load_option + " ") + # Prepare layer name asset_name = context["asset"]["name"] version_name = context["version"]["name"] @@ -33,6 +77,7 @@ class ImportImage(api.Loader): # - filename mus not contain backwards slashes george_script = self.import_script.format( self.fname.replace("\\", "/"), - layer_name + layer_name, + load_options_str ) return lib.execute_george_through_file(george_script)