This node is maintained by OpenPype Publisher.
-To remove it use Publisher gui.
- - """) - node.addKnob(info_knob) - def check_existing_subset(self, subset_name): """Make sure subset name is unique. @@ -153,8 +139,6 @@ class NukeCreator(NewCreator): created_node = nuke.createNode(node_type) created_node["name"].setValue(node_name) - self.add_info_knob(created_node) - for key, values in node_knobs.items(): if key in created_node.knobs(): created_node["key"].setValue(values) diff --git a/openpype/hosts/nuke/api/workfile_template_builder.py b/openpype/hosts/nuke/api/workfile_template_builder.py index 72d4ffb476..766fb0bc47 100644 --- a/openpype/hosts/nuke/api/workfile_template_builder.py +++ b/openpype/hosts/nuke/api/workfile_template_builder.py @@ -25,6 +25,7 @@ from .lib import ( select_nodes, duplicate_node, node_tempfile, + get_main_window ) PLACEHOLDER_SET = "PLACEHOLDERS_SET" @@ -963,8 +964,9 @@ def update_workfile_template(*args): def create_placeholder(*args): host = registered_host() builder = NukeTemplateBuilder(host) - window = WorkfileBuildPlaceholderDialog(host, builder) - window.exec_() + window = WorkfileBuildPlaceholderDialog(host, builder, + parent=get_main_window()) + window.show() def update_placeholder(*args): @@ -988,6 +990,7 @@ def update_placeholder(*args): raise ValueError("Too many selected nodes") placeholder_item = placeholder_items[0] - window = WorkfileBuildPlaceholderDialog(host, builder) + window = WorkfileBuildPlaceholderDialog(host, builder, + parent=get_main_window()) window.set_update_mode(placeholder_item) window.exec_() diff --git a/openpype/hosts/nuke/plugins/create/create_backdrop.py b/openpype/hosts/nuke/plugins/create/create_backdrop.py index ff415626be..52959bbef2 100644 --- a/openpype/hosts/nuke/plugins/create/create_backdrop.py +++ b/openpype/hosts/nuke/plugins/create/create_backdrop.py @@ -36,8 +36,6 @@ class CreateBackdrop(NukeCreator): created_node["note_font_size"].setValue(24) created_node["label"].setValue("[{}]".format(node_name)) - self.add_info_knob(created_node) - return created_node def create(self, subset_name, instance_data, pre_create_data): diff --git a/openpype/hosts/nuke/plugins/create/create_camera.py b/openpype/hosts/nuke/plugins/create/create_camera.py index 5553645af6..b84280b11b 100644 --- a/openpype/hosts/nuke/plugins/create/create_camera.py +++ b/openpype/hosts/nuke/plugins/create/create_camera.py @@ -39,8 +39,6 @@ class CreateCamera(NukeCreator): created_node["name"].setValue(node_name) - self.add_info_knob(created_node) - return created_node def create(self, subset_name, instance_data, pre_create_data): diff --git a/openpype/hosts/nuke/plugins/create/create_gizmo.py b/openpype/hosts/nuke/plugins/create/create_gizmo.py index e3ce70dd59..cbe2f635c9 100644 --- a/openpype/hosts/nuke/plugins/create/create_gizmo.py +++ b/openpype/hosts/nuke/plugins/create/create_gizmo.py @@ -40,8 +40,6 @@ class CreateGizmo(NukeCreator): created_node["name"].setValue(node_name) - self.add_info_knob(created_node) - return created_node def create(self, subset_name, instance_data, pre_create_data): diff --git a/openpype/hosts/nuke/plugins/create/create_model.py b/openpype/hosts/nuke/plugins/create/create_model.py index 08a53abca2..a94c9f0313 100644 --- a/openpype/hosts/nuke/plugins/create/create_model.py +++ b/openpype/hosts/nuke/plugins/create/create_model.py @@ -40,8 +40,6 @@ class CreateModel(NukeCreator): created_node["name"].setValue(node_name) - self.add_info_knob(created_node) - return created_node def create(self, subset_name, instance_data, pre_create_data): diff --git a/openpype/hosts/nuke/plugins/create/create_source.py b/openpype/hosts/nuke/plugins/create/create_source.py index 57504b5d53..8419c3ef33 100644 --- a/openpype/hosts/nuke/plugins/create/create_source.py +++ b/openpype/hosts/nuke/plugins/create/create_source.py @@ -32,7 +32,7 @@ class CreateSource(NukeCreator): read_node["tile_color"].setValue( int(self.node_color, 16)) read_node["name"].setValue(node_name) - self.add_info_knob(read_node) + return read_node def create(self, subset_name, instance_data, pre_create_data): diff --git a/openpype/hosts/nuke/plugins/create/create_write_image.py b/openpype/hosts/nuke/plugins/create/create_write_image.py index b74cea5dae..0c8adfb75c 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_image.py +++ b/openpype/hosts/nuke/plugins/create/create_write_image.py @@ -86,7 +86,6 @@ class CreateWriteImage(napi.NukeWriteCreator): "frame": nuke.frame() } ) - self.add_info_knob(created_node) self._add_frame_range_limit(created_node, instance_data) diff --git a/openpype/hosts/nuke/plugins/create/create_write_prerender.py b/openpype/hosts/nuke/plugins/create/create_write_prerender.py index 387768b1dd..f46dd2d6d5 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_prerender.py +++ b/openpype/hosts/nuke/plugins/create/create_write_prerender.py @@ -74,7 +74,6 @@ class CreateWritePrerender(napi.NukeWriteCreator): "height": height } ) - self.add_info_knob(created_node) self._add_frame_range_limit(created_node) diff --git a/openpype/hosts/nuke/plugins/create/create_write_render.py b/openpype/hosts/nuke/plugins/create/create_write_render.py index 09257f662e..c24405873a 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_render.py +++ b/openpype/hosts/nuke/plugins/create/create_write_render.py @@ -66,7 +66,6 @@ class CreateWriteRender(napi.NukeWriteCreator): "height": height } ) - self.add_info_knob(created_node) self.integrate_links(created_node, outputs=False) diff --git a/openpype/hosts/nuke/plugins/publish/extract_render_local.py b/openpype/hosts/nuke/plugins/publish/extract_render_local.py index e5feda4cd8..e2cf2addc5 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_render_local.py +++ b/openpype/hosts/nuke/plugins/publish/extract_render_local.py @@ -23,7 +23,7 @@ class NukeRenderLocal(publish.Extractor, order = pyblish.api.ExtractorOrder label = "Render Local" hosts = ["nuke"] - families = ["render.local", "prerender.local", "still.local"] + families = ["render.local", "prerender.local", "image.local"] def process(self, instance): child_nodes = ( @@ -136,9 +136,9 @@ class NukeRenderLocal(publish.Extractor, families.remove('prerender.local') families.insert(0, "prerender") instance.data["anatomyData"]["family"] = "prerender" - elif "still.local" in families: + elif "image.local" in families: instance.data['family'] = 'image' - families.remove('still.local') + families.remove('image.local') instance.data["anatomyData"]["family"] = "image" instance.data["families"] = families diff --git a/openpype/hosts/nuke/startup/custom_write_node.py b/openpype/hosts/nuke/startup/custom_write_node.py new file mode 100644 index 0000000000..ea53725834 --- /dev/null +++ b/openpype/hosts/nuke/startup/custom_write_node.py @@ -0,0 +1,151 @@ +""" OpenPype custom script for setting up write nodes for non-publish """ +import os +import nuke +import nukescripts +from openpype.pipeline import Anatomy +from openpype.hosts.nuke.api.lib import ( + set_node_knobs_from_settings, + get_nuke_imageio_settings +) + + +temp_rendering_path_template = ( + "{work}/renders/nuke/{subset}/{subset}.{frame}.{ext}") + +knobs_setting = { + "knobs": [ + { + "type": "text", + "name": "file_type", + "value": "exr" + }, + { + "type": "text", + "name": "datatype", + "value": "16 bit half" + }, + { + "type": "text", + "name": "compression", + "value": "Zip (1 scanline)" + }, + { + "type": "bool", + "name": "autocrop", + "value": True + }, + { + "type": "color_gui", + "name": "tile_color", + "value": [ + 186, + 35, + 35, + 255 + ] + }, + { + "type": "text", + "name": "channels", + "value": "rgb" + }, + { + "type": "bool", + "name": "create_directories", + "value": True + } + ] +} + + +class WriteNodeKnobSettingPanel(nukescripts.PythonPanel): + """ Write Node's Knobs Settings Panel """ + def __init__(self): + nukescripts.PythonPanel.__init__(self, "Set Knobs Value(Write Node)") + + preset_name, _ = self.get_node_knobs_setting() + # create knobs + + self.selected_preset_name = nuke.Enumeration_Knob( + 'preset_selector', 'presets', preset_name) + # add knobs to panel + self.addKnob(self.selected_preset_name) + + def process(self): + """ Process the panel values. """ + write_selected_nodes = [ + selected_nodes for selected_nodes in nuke.selectedNodes() + if selected_nodes.Class() == "Write"] + + selected_preset = self.selected_preset_name.value() + ext = None + knobs = knobs_setting["knobs"] + preset_name, node_knobs_presets = ( + self.get_node_knobs_setting(selected_preset) + ) + + if selected_preset and preset_name: + if not node_knobs_presets: + nuke.message( + "No knobs value found in subset group.." + "\nDefault setting will be used..") + else: + knobs = node_knobs_presets + + ext_knob_list = [knob for knob in knobs if knob["name"] == "file_type"] + if not ext_knob_list: + nuke.message( + "ERROR: No file type found in the subset's knobs." + "\nPlease add one to complete setting up the node") + return + else: + for knob in ext_knob_list: + ext = knob["value"] + + anatomy = Anatomy() + + frame_padding = int( + anatomy.templates["render"].get( + "frame_padding" + ) + ) + for write_node in write_selected_nodes: + # data for mapping the path + data = { + "work": os.getenv("AVALON_WORKDIR"), + "subset": write_node["name"].value(), + "frame": "#" * frame_padding, + "ext": ext + } + file_path = temp_rendering_path_template.format(**data) + file_path = file_path.replace("\\", "/") + write_node["file"].setValue(file_path) + set_node_knobs_from_settings(write_node, knobs) + + def get_node_knobs_setting(self, selected_preset=None): + preset_name = [] + knobs_nodes = [] + settings = [ + node_settings for node_settings + in get_nuke_imageio_settings()["nodes"]["overrideNodes"] + if node_settings["nukeNodeClass"] == "Write" + and node_settings["subsets"] + ] + if not settings: + return + + for i, _ in enumerate(settings): + if selected_preset in settings[i]["subsets"]: + knobs_nodes = settings[i]["knobs"] + + for setting in settings: + for subset in setting["subsets"]: + preset_name.append(subset) + + return preset_name, knobs_nodes + + +def main(): + p_ = WriteNodeKnobSettingPanel() + if p_.showModalDialog(): + print(p_.process()) diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_context.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_context.py index 96aaae23dc..8fa53f5f48 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/collect_context.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_context.py @@ -222,7 +222,6 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): "label": subset, "name": subset, "family": in_data["family"], - # "version": in_data.get("version", 1), "frameStart": in_data.get("representations", [None])[0].get( "frameStart", None ), @@ -232,6 +231,14 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): "families": instance_families } ) + # Fill version only if 'use_next_available_version' is disabled + # and version is filled in instance data + version = in_data.get("version") + use_next_available_version = in_data.get( + "use_next_available_version", True) + if not use_next_available_version and version is not None: + instance.data["version"] = version + self.log.info("collected instance: {}".format(pformat(instance.data))) self.log.info("parsing data: {}".format(pformat(in_data))) diff --git a/openpype/hosts/traypublisher/api/plugin.py b/openpype/hosts/traypublisher/api/plugin.py index 75930f0f31..36e041a32c 100644 --- a/openpype/hosts/traypublisher/api/plugin.py +++ b/openpype/hosts/traypublisher/api/plugin.py @@ -1,4 +1,14 @@ -from openpype.lib.attribute_definitions import FileDef +from openpype.client import ( + get_assets, + get_subsets, + get_last_versions, +) +from openpype.lib.attribute_definitions import ( + FileDef, + BoolDef, + NumberDef, + UISeparatorDef, +) from openpype.lib.transcoding import IMAGE_EXTENSIONS, VIDEO_EXTENSIONS from openpype.pipeline.create import ( Creator, @@ -94,6 +104,7 @@ class TrayPublishCreator(Creator): class SettingsCreator(TrayPublishCreator): create_allow_context_change = True create_allow_thumbnail = True + allow_version_control = False extensions = [] @@ -101,8 +112,18 @@ class SettingsCreator(TrayPublishCreator): # Pass precreate data to creator attributes thumbnail_path = pre_create_data.pop(PRE_CREATE_THUMBNAIL_KEY, None) + # Fill 'version_to_use' if version control is enabled + if self.allow_version_control: + asset_name = data["asset"] + subset_docs_by_asset_id = self._prepare_next_versions( + [asset_name], [subset_name]) + version = subset_docs_by_asset_id[asset_name].get(subset_name) + pre_create_data["version_to_use"] = version + data["_previous_last_version"] = version + data["creator_attributes"] = pre_create_data data["settings_creator"] = True + # Create new instance new_instance = CreatedInstance(self.family, subset_name, data, self) @@ -111,7 +132,158 @@ class SettingsCreator(TrayPublishCreator): if thumbnail_path: self.set_instance_thumbnail_path(new_instance.id, thumbnail_path) + def _prepare_next_versions(self, asset_names, subset_names): + """Prepare next versions for given asset and subset names. + + Todos: + Expect combination of subset names by asset name to avoid + unnecessary server calls for unused subsets. + + Args: + asset_names (Iterable[str]): Asset names. + subset_names (Iterable[str]): Subset names. + + Returns: + dict[str, dict[str, int]]: Last versions by asset + and subset names. + """ + + # Prepare all versions for all combinations to '1' + subset_docs_by_asset_id = { + asset_name: { + subset_name: 1 + for subset_name in subset_names + } + for asset_name in asset_names + } + if not asset_names or not subset_names: + return subset_docs_by_asset_id + + asset_docs = get_assets( + self.project_name, + asset_names=asset_names, + fields=["_id", "name"] + ) + asset_names_by_id = { + asset_doc["_id"]: asset_doc["name"] + for asset_doc in asset_docs + } + subset_docs = list(get_subsets( + self.project_name, + asset_ids=asset_names_by_id.keys(), + subset_names=subset_names, + fields=["_id", "name", "parent"] + )) + + subset_ids = {subset_doc["_id"] for subset_doc in subset_docs} + last_versions = get_last_versions( + self.project_name, + subset_ids, + fields=["name", "parent"]) + + for subset_doc in subset_docs: + asset_id = subset_doc["parent"] + asset_name = asset_names_by_id[asset_id] + subset_name = subset_doc["name"] + subset_id = subset_doc["_id"] + last_version = last_versions.get(subset_id) + version = 0 + if last_version is not None: + version = last_version["name"] + subset_docs_by_asset_id[asset_name][subset_name] += version + return subset_docs_by_asset_id + + def _fill_next_versions(self, instances_data): + """Fill next version for instances. + + Instances have also stored previous next version to be able to + recognize if user did enter different version. If version was + not changed by user, or user set it to '0' the next version will be + updated by current database state. + """ + + filtered_instance_data = [] + for instance in instances_data: + previous_last_version = instance.get("_previous_last_version") + creator_attributes = instance["creator_attributes"] + use_next_version = creator_attributes.get( + "use_next_version", True) + version = creator_attributes.get("version_to_use", 0) + if ( + use_next_version + or version == 0 + or version == previous_last_version + ): + filtered_instance_data.append(instance) + + asset_names = { + instance["asset"] + for instance in filtered_instance_data} + subset_names = { + instance["subset"] + for instance in filtered_instance_data} + subset_docs_by_asset_id = self._prepare_next_versions( + asset_names, subset_names + ) + for instance in filtered_instance_data: + asset_name = instance["asset"] + subset_name = instance["subset"] + version = subset_docs_by_asset_id[asset_name][subset_name] + instance["creator_attributes"]["version_to_use"] = version + instance["_previous_last_version"] = version + + def collect_instances(self): + """Collect instances from host. + + Overriden to be able to manage version control attributes. If version + control is disabled, the attributes will be removed from instances, + and next versions are filled if is version control enabled. + """ + + instances_by_identifier = cache_and_get_instances( + self, SHARED_DATA_KEY, list_instances + ) + instances = instances_by_identifier[self.identifier] + if not instances: + return + + if self.allow_version_control: + self._fill_next_versions(instances) + + for instance_data in instances: + # Make sure that there are not data related to version control + # if plugin does not support it + if not self.allow_version_control: + instance_data.pop("_previous_last_version", None) + creator_attributes = instance_data["creator_attributes"] + creator_attributes.pop("version_to_use", None) + creator_attributes.pop("use_next_version", None) + + instance = CreatedInstance.from_existing(instance_data, self) + self._add_instance_to_context(instance) + def get_instance_attr_defs(self): + defs = self.get_pre_create_attr_defs() + if self.allow_version_control: + defs += [ + UISeparatorDef(), + BoolDef( + "use_next_version", + default=True, + label="Use next version", + ), + NumberDef( + "version_to_use", + default=1, + minimum=0, + maximum=999, + label="Version to use", + ) + ] + return defs + + def get_pre_create_attr_defs(self): + # Use same attributes as for instance attributes return [ FileDef( "representation_files", @@ -132,10 +304,6 @@ class SettingsCreator(TrayPublishCreator): ) ] - def get_pre_create_attr_defs(self): - # Use same attributes as for instance attrobites - return self.get_instance_attr_defs() - @classmethod def from_settings(cls, item_data): identifier = item_data["identifier"] @@ -155,6 +323,8 @@ class SettingsCreator(TrayPublishCreator): "extensions": item_data["extensions"], "allow_sequences": item_data["allow_sequences"], "allow_multiple_items": item_data["allow_multiple_items"], - "default_variants": item_data["default_variants"] + "allow_version_control": item_data.get( + "allow_version_control", False), + "default_variants": item_data["default_variants"], } ) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index 0630dfb3da..8640500b18 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -487,7 +487,22 @@ or updating already created. Publishing will create OTIO file. ) # get video stream data - video_stream = media_data["streams"][0] + video_streams = [] + audio_streams = [] + for stream in media_data["streams"]: + codec_type = stream.get("codec_type") + if codec_type == "audio": + audio_streams.append(stream) + + elif codec_type == "video": + video_streams.append(stream) + + if not video_streams: + raise ValueError( + "Could not find video stream in source file." + ) + + video_stream = video_streams[0] return_data = { "video": True, "start_frame": 0, @@ -500,12 +515,7 @@ or updating already created. Publishing will create OTIO file. } # get audio streams data - audio_stream = [ - stream for stream in media_data["streams"] - if stream["codec_type"] == "audio" - ] - - if audio_stream: + if audio_streams: return_data["audio"] = True except Exception as exc: diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py index c081216481..3fa3c3b8c8 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py @@ -47,6 +47,8 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): "Created temp staging directory for instance {}. {}" ).format(instance_label, tmp_folder)) + self._fill_version(instance, instance_label) + # Store filepaths for validation of their existence source_filepaths = [] # Make sure there are no representations with same name @@ -93,6 +95,28 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): ) ) + def _fill_version(self, instance, instance_label): + """Fill instance version under which will be instance integrated. + + Instance must have set 'use_next_version' to 'False' + and 'version_to_use' to version to use. + + Args: + instance (pyblish.api.Instance): Instance to fill version for. + instance_label (str): Label of instance to fill version for. + """ + + creator_attributes = instance.data["creator_attributes"] + use_next_version = creator_attributes.get("use_next_version", True) + # If 'version_to_use' is '0' it means that next version should be used + version_to_use = creator_attributes.get("version_to_use", 0) + if use_next_version or not version_to_use: + return + instance.data["version"] = version_to_use + self.log.debug( + "Version for instance \"{}\" was set to \"{}\"".format( + instance_label, version_to_use)) + def _create_main_representations( self, instance, diff --git a/openpype/hosts/traypublisher/plugins/publish/help/validate_existing_version.xml b/openpype/hosts/traypublisher/plugins/publish/help/validate_existing_version.xml new file mode 100644 index 0000000000..8a3b8f4d7d --- /dev/null +++ b/openpype/hosts/traypublisher/plugins/publish/help/validate_existing_version.xml @@ -0,0 +1,16 @@ + +