From f814db2ce0c4432023b18903c7819729997abfb5 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 6 Jul 2023 10:45:00 +0100 Subject: [PATCH 001/327] Use colorspace data when creating thumbnail. --- openpype/lib/transcoding.py | 13 +++-- openpype/plugins/publish/extract_thumbnail.py | 51 ++++++++++++------- 2 files changed, 42 insertions(+), 22 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index de6495900e..771f670f89 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1056,7 +1056,8 @@ def convert_colorspace( view=None, display=None, additional_command_args=None, - logger=None + logger=None, + input_args=None ): """Convert source file from one color space to another. @@ -1084,13 +1085,17 @@ def convert_colorspace( if logger is None: logger = logging.getLogger(__name__) - oiio_cmd = [ - get_oiio_tools_path(), + oiio_cmd = [get_oiio_tools_path()] + + if input_args: + oiio_cmd.extend(input_args) + + oiio_cmd.extend([ input_path, # Don't add any additional attributes "--nosoftwareattrib", "--colorconfig", config_path - ] + ]) if all([target_colorspace, view, display]): raise ValueError("Colorspace and both screen and display" diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index b98ab64f56..1d86741470 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -10,6 +10,7 @@ from openpype.lib import ( run_subprocess, path_to_subprocess_arg, ) +from openpype.lib.transcoding import convert_colorspace class ExtractThumbnail(pyblish.api.InstancePlugin): @@ -98,8 +99,18 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): self.log.debug("Trying to convert with OIIO") # If the input can read by OIIO then use OIIO method for # conversion otherwise use ffmpeg + colorspace_data = repre["colorspaceData"] + source_colorspace = colorspace_data["colorspace"] + config_path = colorspace_data.get("config", {}).get("path") + display = colorspace_data["display"] + view = colorspace_data["view"] thumbnail_created = self.create_thumbnail_oiio( - full_input_path, full_output_path + full_input_path, + full_output_path, + config_path, + source_colorspace, + display, + view ) # Try to use FFMPEG if OIIO is not supported or for cases when @@ -172,24 +183,28 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): filtered_repres.append(repre) return filtered_repres - def create_thumbnail_oiio(self, src_path, dst_path): + def create_thumbnail_oiio( + self, + src_path, + dst_path, + config_path, + source_colorspace, + display, + view + ): self.log.info("Extracting thumbnail {}".format(dst_path)) - oiio_tool_path = get_oiio_tools_path() - oiio_cmd = [ - oiio_tool_path, - "-a", src_path, - "-o", dst_path - ] - self.log.debug("running: {}".format(" ".join(oiio_cmd))) - try: - run_subprocess(oiio_cmd, logger=self.log) - return True - except Exception: - self.log.warning( - "Failed to create thumbnail using oiiotool", - exc_info=True - ) - return False + + convert_colorspace( + src_path, + dst_path, + config_path, + source_colorspace, + view=view, + display=display, + input_args=["-i:ch=R,G,B"] + ) + + return dst_path def create_thumbnail_ffmpeg(self, src_path, dst_path): self.log.info("outputting {}".format(dst_path)) From 3841d298b2d1d3837a5a5e569cc120b3f87b9655 Mon Sep 17 00:00:00 2001 From: Fabia Serra Arrizabalaga Date: Mon, 7 Aug 2023 18:22:49 +0200 Subject: [PATCH 002/327] First draft at adding support in Houdini renders to split render in export + render tasks --- .../plugins/create/create_arnold_rop.py | 14 ++ .../plugins/create/create_mantra_rop.py | 11 + .../houdini/plugins/create/create_vray_rop.py | 15 ++ .../plugins/publish/collect_arnold_rop.py | 15 ++ .../plugins/publish/collect_mantra_rop.py | 15 ++ .../plugins/publish/collect_vray_rop.py | 16 ++ .../deadline/abstract_submit_deadline.py | 22 +- .../publish/submit_houdini_render_deadline.py | 205 ++++++++++++++++-- 8 files changed, 293 insertions(+), 20 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_arnold_rop.py b/openpype/hosts/houdini/plugins/create/create_arnold_rop.py index ca516619f6..d19795d3a9 100644 --- a/openpype/hosts/houdini/plugins/create/create_arnold_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_arnold_rop.py @@ -14,6 +14,9 @@ class CreateArnoldRop(plugin.HoudiniCreator): # Default extension ext = "exr" + # Default to split export and render jobs + export_job = True + def create(self, subset_name, instance_data, pre_create_data): import hou @@ -49,6 +52,14 @@ class CreateArnoldRop(plugin.HoudiniCreator): "ar_exr_half_precision": 1 # half precision } + if pre_create_data.get("export_job"): + ass_filepath = "{export_dir}{subset_name}/{subset_name}.$F4.ass".format( + export_dir=hou.text.expandString("$HIP/pyblish/ass/"), + subset_name=subset_name, + ) + parms["ar_ass_export_enable"] = 1 + parms["ar_ass_file"] = ass_filepath + instance_node.setParms(parms) # Lock any parameters in this list @@ -67,6 +78,9 @@ class CreateArnoldRop(plugin.HoudiniCreator): BoolDef("farm", label="Submitting to Farm", default=True), + BoolDef("export_job", + label="Split export and render jobs", + default=self.export_job), EnumDef("image_format", image_format_enum, default=self.ext, diff --git a/openpype/hosts/houdini/plugins/create/create_mantra_rop.py b/openpype/hosts/houdini/plugins/create/create_mantra_rop.py index 5c29adb33f..41311a1e30 100644 --- a/openpype/hosts/houdini/plugins/create/create_mantra_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_mantra_rop.py @@ -13,6 +13,9 @@ class CreateMantraROP(plugin.HoudiniCreator): icon = "magic" defaults = ["master"] + # Default to split export and render jobs + export_job = True + def create(self, subset_name, instance_data, pre_create_data): import hou # noqa @@ -45,6 +48,14 @@ class CreateMantraROP(plugin.HoudiniCreator): "vm_picture": filepath, } + if pre_create_data.get("export_job"): + ifd_filepath = "{export_dir}{subset_name}/{subset_name}.$F4.ifd".format( + export_dir=hou.text.expandString("$HIP/pyblish/ifd/"), + subset_name=subset_name, + ) + parms["soho_outputmode"] = 1 + parms["soho_diskfile"] = ifd_filepath + if self.selected_nodes: # If camera found in selection # we will use as render camera diff --git a/openpype/hosts/houdini/plugins/create/create_vray_rop.py b/openpype/hosts/houdini/plugins/create/create_vray_rop.py index 58748d4c34..29fab0c60c 100644 --- a/openpype/hosts/houdini/plugins/create/create_vray_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_vray_rop.py @@ -18,6 +18,9 @@ class CreateVrayROP(plugin.HoudiniCreator): ext = "exr" + # Default to split export and render jobs + export_job = True + def create(self, subset_name, instance_data, pre_create_data): instance_data.pop("active", None) @@ -54,6 +57,15 @@ class CreateVrayROP(plugin.HoudiniCreator): "SettingsEXR_bits_per_channel": "16" # half precision } + if pre_create_data.get("export_job"): + scene_filepath = "{export_dir}{subset_name}/{subset_name}.$F4.vrscene".format( + export_dir=hou.text.expandString("$HIP/pyblish/vrscene/"), + subset_name=subset_name, + ) + # TODO: don't have VRay to check the names of these + parms["render_export_mode"] = 1 + parms["render_export_filepath"] = scene_filepath + if self.selected_nodes: # set up the render camera from the selected node camera = None @@ -142,6 +154,9 @@ class CreateVrayROP(plugin.HoudiniCreator): BoolDef("farm", label="Submitting to Farm", default=True), + BoolDef("export_job", + label="Split export and render jobs", + default=self.export_job), EnumDef("image_format", image_format_enum, default=self.ext, diff --git a/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py b/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py index 43b8428c60..063dd728e1 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py @@ -38,6 +38,21 @@ class CollectArnoldROPRenderProducts(pyblish.api.InstancePlugin): default_prefix = evalParmNoFrame(rop, "ar_picture") render_products = [] + # Store whether we are splitting the render job in an export + render + export_job = bool(rop.parm("ar_ass_export_enable").eval()) + instance.data["exportJob"] = export_job + export_prefix = None + export_products = [] + if export_job: + export_prefix = evalParmNoFrame(rop, "ar_ass_file", pad_character="0") + beauty_export_product = self.get_render_product_name( + prefix=export_prefix, + suffix=None) + export_products.append(beauty_export_product) + self.log.debug("Found export product: {}".format(beauty_export_product)) + instance.data["ifdFile"] = beauty_export_product + instance.data["exportFiles"] = list(export_products) + # Default beauty AOV beauty_product = self.get_render_product_name(prefix=default_prefix, suffix=None) diff --git a/openpype/hosts/houdini/plugins/publish/collect_mantra_rop.py b/openpype/hosts/houdini/plugins/publish/collect_mantra_rop.py index c4460f5350..b235e2c110 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_mantra_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_mantra_rop.py @@ -42,6 +42,21 @@ class CollectMantraROPRenderProducts(pyblish.api.InstancePlugin): default_prefix = evalParmNoFrame(rop, "vm_picture") render_products = [] + # Store whether we are splitting the render job in an export + render + export_job = bool(rop.parm("soho_outputmode").eval()) + instance.data["exportJob"] = export_job + export_prefix = None + export_products = [] + if export_job: + export_prefix = evalParmNoFrame(rop, "soho_diskfile", pad_character="0") + beauty_export_product = self.get_render_product_name( + prefix=export_prefix, + suffix=None) + export_products.append(beauty_export_product) + self.log.debug("Found export product: {}".format(beauty_export_product)) + instance.data["ifdFile"] = beauty_export_product + instance.data["exportFiles"] = list(export_products) + # Default beauty AOV beauty_product = self.get_render_product_name( prefix=default_prefix, suffix=None diff --git a/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py b/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py index d4fe37f993..89a7c8bf72 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py @@ -43,6 +43,22 @@ class CollectVrayROPRenderProducts(pyblish.api.InstancePlugin): render_products = [] # TODO: add render elements if render element + # Store whether we are splitting the render job in an export + render + # TODO: check names of VRay parms + export_job = bool(rop.parm("render_export_mode").eval()) + instance.data["exportJob"] = export_job + export_prefix = None + export_products = [] + if export_job: + export_prefix = evalParmNoFrame(rop, "render_export_filepath", pad_character="0") + beauty_export_product = self.get_render_product_name( + prefix=export_prefix, + suffix=None) + export_products.append(beauty_export_product) + self.log.debug("Found export product: {}".format(beauty_export_product)) + instance.data["ifdFile"] = beauty_export_product + instance.data["exportFiles"] = list(export_products) + beauty_product = self.get_beauty_render_product(default_prefix) render_products.append(beauty_product) files_by_aov = { diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index 3fa427204b..56d8afbe3e 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -446,11 +446,29 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin, self.scene_path = file_path self.log.info("Using {} for render/export.".format(file_path)) - self.job_info = self.get_job_info() + # Check whether we are splitting render job in export + render + # and if so, create a separate task for the render + # TODO: Find a cleaner way so `get_job_info` can take extra args depending + # on host + export_job = instance.data["exportJob"] + self.job_info = self.get_job_info(split_render_job=export_job, export_job=True) self.plugin_info = self.get_plugin_info() self.aux_files = self.get_aux_files() - self.process_submission() + job_id = self.process_submission() + if export_job: + self.log.info("Splitting export and render in two jobs") + self.log.info("Export job id: %s", job_id) + render_job_info = self.get_job_info( + split_render_job=True, dependency_job_ids=[job_id] + ) + render_plugin_info = self.get_plugin_info(split_render_job=True) + payload = self.assemble_payload( + job_info=render_job_info, + plugin_info=render_plugin_info + ) + render_job_id = self.submit(payload) + self.log.info("Render job id: %s", render_job_id) def process_submission(self): """Process data for submission. diff --git a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py index af341ca8e8..82f6876970 100644 --- a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py @@ -6,12 +6,15 @@ import getpass from datetime import datetime import pyblish.api -from openpype.pipeline import legacy_io +from openpype.pipeline import legacy_io, OpenPypePyblishPluginMixin from openpype.tests.lib import is_in_tests from openpype_modules.deadline import abstract_submit_deadline from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo -from openpype.lib import is_running_from_build - +from openpype.lib import ( + is_running_from_build, + BoolDef, + NumberDef +) @attr.s class DeadlinePluginInfo(): @@ -20,8 +23,27 @@ class DeadlinePluginInfo(): Version = attr.ib(default=None) IgnoreInputs = attr.ib(default=True) +@attr.s +class ArnoldRenderDeadlinePluginInfo(): + InputFile = attr.ib(default=None) + Verbose = attr.ib(default=4) -class HoudiniSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): + +@attr.s +class MantraRenderDeadlinePluginInfo(): + SceneFile = attr.ib(default=None) + Version = attr.ib(default=None) + + +@attr.s +class VrayRenderPluginInfo(): + InputFilename = attr.ib(default=None) + + +class HoudiniSubmitDeadline( + abstract_submit_deadline.AbstractSubmitDeadline, + OpenPypePyblishPluginMixin +): """Submit Solaris USD Render ROPs to Deadline. Renders are submitted to a Deadline Web Service as @@ -46,21 +68,117 @@ class HoudiniSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): targets = ["local"] use_published = True - def get_job_info(self): - job_info = DeadlineJobInfo(Plugin="Houdini") + # presets + priority = 50 + chunk_size = 1 + export_priority = 50 + export_chunk_size = 10 + group = "" + export_group = "" + department = "" + limit_groups = {} + env_allowed_keys = [] + env_search_replace_values = {} + + @classmethod + def apply_settings(cls, project_settings, system_settings): + settings = project_settings["deadline"]["publish"]["HoudiniSubmitDeadline"] # noqa + + # Take some defaults from settings + cls.use_published = settings.get( + "use_published", cls.use_published + ) + cls.priority = settings.get( + "priority", cls.priority + ) + cls.export_priority = settings.get( + "export_priority", cls.export_priority + ) + cls.chunk_size = settings.get("chunk_size", cls.chunk_size) + cls.export_chunk_size = settings.get( + "export_chunk_size", cls.export_chunk_size + ) + cls.group = settings.get("group", cls.group) + cls.export_group = settings.get("export_group", cls.export_group) + cls.department = settings.get("department", cls.department) + cls.env_allowed_keys = settings.get("env_allowed_keys", cls.env_allowed_keys) + cls.env_search_replace_values = settings.get( + "env_search_replace_values", cls.env_allowed_keys + ) + + @classmethod + def get_attribute_defs(cls): + return [ + NumberDef( + "priority", + label="Priority", + default=cls.priority, + decimals=0 + ), + NumberDef( + "chunk", + label="Frames Per Task", + default=cls.chunk_size, + decimals=0, + minimum=1, + maximum=1000 + ), + NumberDef( + "export_priority", + label="Export Priority", + default=cls.priority, + decimals=0 + ), + NumberDef( + "export_chunk", + label="Export Frames Per Task", + default=cls.export_chunk_size, + decimals=0, + minimum=1, + maximum=1000 + ), + BoolDef( + "suspend_publish", + default=False, + label="Suspend publish" + ) + ] + + def get_job_info(self, split_render_job=False, export_job=False, dependency_job_ids=None): instance = self._instance context = instance.context + attribute_values = self.get_attr_values_from_data(instance.data) + + if split_render_job and not export_job: + # Convert from family to Deadline plugin name + # i.e., arnold_rop -> Arnold + plugin = instance.data.get("family").replace("_rop", "").capitalize() + else: + plugin = "Houdini" + + job_info = DeadlineJobInfo(Plugin=plugin) + filepath = context.data["currentFile"] filename = os.path.basename(filepath) - job_info.Name = "{} - {}".format(filename, instance.name) job_info.BatchName = filename - job_info.Plugin = "Houdini" + job_info.UserName = context.data.get( "deadlineUser", getpass.getuser()) + if split_render_job and export_job: + job_info.Priority = attribute_values.get( + "export_priority", self.export_priority + ) + else: + job_info.Priority = attribute_values.get( + "priority", self.priority + ) + + job_info.Department = self.department + if is_in_tests(): job_info.BatchName += datetime.now().strftime("%d%m%Y%H%M%S") @@ -72,9 +190,23 @@ class HoudiniSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): ) job_info.Frames = frames + # Make sure we make job frame dependent so render tasks pick up a soon + # as export tasks are done + if split_render_job and not export_job: + job_info.IsFrameDependent = True + job_info.Pool = instance.data.get("primaryPool") job_info.SecondaryPool = instance.data.get("secondaryPool") - job_info.ChunkSize = instance.data.get("chunkSize", 10) + job_info.Group = self.group + if split_render_job and export_job: + job_info.ChunkSize = attribute_values.get( + "export_chunk", self.export_chunk_size + ) + else: + job_info.ChunkSize = attribute_values.get( + "chunk", self.chunk_size + ) + job_info.Comment = context.data.get("comment") keys = [ @@ -98,8 +230,19 @@ class HoudiniSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): if self._instance.context.data.get("deadlinePassMongoUrl"): keys.append("OPENPYPE_MONGO") + # add allowed keys from preset if any + if self.env_allowed_keys: + keys += self.env_allowed_keys + environment = dict({key: os.environ[key] for key in keys if key in os.environ}, **legacy_io.Session) + + # finally search replace in values of any key + if self.env_search_replace_values: + for key, value in environment.items(): + for _k, _v in self.env_search_replace_values.items(): + environment[key] = value.replace(_k, _v) + for key in keys: value = environment.get(key) if value: @@ -114,23 +257,49 @@ class HoudiniSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): job_info.OutputDirectory += dirname.replace("\\", "/") job_info.OutputFilename += fname + # Add dependencies if given + if dependency_job_ids: + job_info.JobDependencies = ",".join(dependency_job_ids) + return job_info - def get_plugin_info(self): + def get_plugin_info(self, split_render_job=False): instance = self._instance context = instance.context - # Output driver to render - driver = hou.node(instance.data["instance_node"]) hou_major_minor = hou.applicationVersionString().rsplit(".", 1)[0] - plugin_info = DeadlinePluginInfo( - SceneFile=context.data["currentFile"], - OutputDriver=driver.path(), - Version=hou_major_minor, - IgnoreInputs=True - ) + # Output driver to render + if split_render_job: + family = instance.data.get("family") + if family == "arnold_rop": + plugin_info = ArnoldRenderDeadlinePluginInfo( + InputFile=instance.data["ifdFile"] + ) + elif family == "mantra_rop": + plugin_info = MantraRenderDeadlinePluginInfo( + SceneFile=instance.data["ifdFile"], + Version=hou_major_minor, + ) + elif family == "vray_rop": + plugin_info = VrayRenderPluginInfo( + InputFilename=instance.data["ifdFile"], + ) + else: + self.log.error( + "Family %s not supported yet to split export and render job", + family + ) + return + else: + driver = hou.node(instance.data["instance_node"]) + plugin_info = DeadlinePluginInfo( + SceneFile=context.data["currentFile"], + OutputDriver=driver.path(), + Version=hou_major_minor, + IgnoreInputs=True + ) return attr.asdict(plugin_info) From fa261c6a3269815575824dfd035e8424195ecbec Mon Sep 17 00:00:00 2001 From: Fabia Serra Arrizabalaga Date: Mon, 7 Aug 2023 18:23:36 +0200 Subject: [PATCH 003/327] Add way to query export parm from Houdini and fix output parm --- openpype/hosts/houdini/api/lib.py | 121 ++++++++++++++++++++++++++---- 1 file changed, 106 insertions(+), 15 deletions(-) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index b03f8c8fc1..b1673b746d 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -104,8 +104,8 @@ def get_id_required_nodes(): return list(nodes) -def get_output_parameter(node): - """Return the render output parameter name of the given node +def get_export_parameter(node): + """Return the export output parameter of the given node Example: root = hou.node("/obj") @@ -120,21 +120,112 @@ def get_output_parameter(node): hou.Parm """ + node_type = node.type().description() - node_type = node.type().name() - if node_type == "geometry": - return node.parm("sopoutput") - elif node_type == "alembic": - return node.parm("filename") - elif node_type == "comp": - return node.parm("copoutput") - elif node_type == "opengl": - return node.parm("picture") - elif node_type == "arnold": - if node.evalParm("ar_ass_export_enable"): - return node.parm("ar_ass_file") - elif node_type == "Redshift_Proxy_Output": + # Ensures the proper Take is selected for each ROP to retrieve the correct ifd + try: + rop_take = hou.takes.findTake(node.parm("take").eval()) + if rop_take is not None: + hou.takes.setCurrentTake(rop_take) + except AttributeError: + # hou object doesn't always have the 'takes' attribute + pass + + if node_type == "Mantra" and node.parm("soho_outputmode").eval(): + return node.parm("soho_diskfile") + elif node_type == "Alfred": + return node.parm("alf_diskfile") + elif (node_type == "RenderMan" or node_type == "RenderMan RIS"): + pre_ris22 = node.parm("rib_outputmode") and node.parm("rib_outputmode").eval() + ris22 = node.parm("diskfile") and node.parm("diskfile").eval() + if pre_ris22 or ris22: + return node.parm("soho_diskfile") + elif node_type == "Redshift" and node.parm("RS_archive_enable").eval(): return node.parm("RS_archive_file") + elif node_type == "Wedge" and node.parm("driver").eval(): + return get_export_parameter(node.node(node.parm("driver").eval())) + elif node_type == "Arnold": + return node.parm("ar_ass_file") + elif node_type == "Alembic" and node.parm("use_sop_path").eval(): + return node.parm("sop_path") + elif node_type == "Shotgun Mantra" and node.parm("soho_outputmode").eval(): + return node.parm("sgtk_soho_diskfile") + elif node_type == "Shotgun Alembic" and node.parm("use_sop_path").eval(): + return node.parm("sop_path") + elif node.type().nameWithCategory() == "Driver/vray_renderer": + return node.parm("render_export_filepath") + + raise TypeError("Node type '%s' not supported" % node_type) + + +def get_output_parameter(node): + """Return the render output parameter of the given node + + Example: + root = hou.node("/obj") + my_alembic_node = root.createNode("alembic") + get_output_parameter(my_alembic_node) + # Result: "output" + + Args: + node(hou.Node): node instance + + Returns: + hou.Parm + + """ + node_type = node.type().description() + category = node.type().category().name() + + # Figure out which type of node is being rendered + if node_type == "Geometry" or node_type == "Filmbox FBX" or (node_type == "ROP Output Driver" and category == "Sop"): + return node.parm("sopoutput") + elif node_type == "Composite": + return node.parm("copoutput") + elif node_type == "Channel": + return node.parm("chopoutput") + elif node_type == "Dynamics" or (node_type == "ROP Output Driver" and category == "Dop"): + return node.parm("dopoutput") + elif node_type == "Alfred": + return node.parm("alf_diskfile") + elif node_type == "RenderMan" or node_type == "RenderMan RIS": + return node.parm("ri_display") + elif node_type == "Redshift": + return node.parm("RS_returnmePrefix") + elif node_type == "Mantra": + return node.parm("vm_picture") + elif node_type == "Wedge": + driver_node = node.node(node.parm("driver").eval()) + if driver_node: + return get_output_parameter(driver_node) + elif node_type == "Arnold": + return node.parm("ar_picture") + elif node_type == "HQueue Simulation": + inner_node = node.node(node.parm("hq_driver").eval()) + if inner_node: + return get_output_parameter(inner_node) + elif node_type == "ROP Alembic Output": + return node.parm("filename") + elif node_type == "Redshift": + return node.parm("RS_returnmePrefix") + elif node_type == "Alembic": + return node.parm("filename") + elif node_type == "Shotgun Mantra": + return node.parm("sgtk_vm_picture") + elif node_type == "Shotgun Alembic": + return node.parm("filename") + elif node_type == "Bake Texture": + return node.parm("vm_uvoutputpicture1") + elif node_type == "OpenGL": + return node.parm("picture") + elif node_type == "Octane": + return node.parm("HO_img_fileName") + elif node_type == "Fetch": + inner_node = node.node(node.parm("source").eval()) + if inner_node: + return get_output_parameter(inner_node) + elif node.type().nameWithCategory() == "Driver/vray_renderer": + return node.parm("SettingsOutput_img_file_path") raise TypeError("Node type '%s' not supported" % node_type) From 2b2ad8f1ed8ff4922fa9216983958e79ea93e26a Mon Sep 17 00:00:00 2001 From: Fabia Serra Arrizabalaga Date: Mon, 7 Aug 2023 18:30:12 +0200 Subject: [PATCH 004/327] Add missing pre create attr for splitting render job to Mantra ROP --- openpype/hosts/houdini/plugins/create/create_mantra_rop.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hosts/houdini/plugins/create/create_mantra_rop.py b/openpype/hosts/houdini/plugins/create/create_mantra_rop.py index 41311a1e30..0601124ac0 100644 --- a/openpype/hosts/houdini/plugins/create/create_mantra_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_mantra_rop.py @@ -90,6 +90,9 @@ class CreateMantraROP(plugin.HoudiniCreator): BoolDef("farm", label="Submitting to Farm", default=True), + BoolDef("export_job", + label="Split export and render jobs", + default=self.export_job), EnumDef("image_format", image_format_enum, default="exr", From aab913acbc7f33e0c79d2a966a52b22fd99c54fc Mon Sep 17 00:00:00 2001 From: Fabia Serra Arrizabalaga Date: Mon, 7 Aug 2023 18:32:09 +0200 Subject: [PATCH 005/327] Add Houdini deadline submission settings --- .../defaults/project_settings/deadline.json | 15 +++ .../schema_project_deadline.json | 91 +++++++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json index 1b8c8397d7..d088ef5747 100644 --- a/openpype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -19,6 +19,21 @@ "deadline" ] }, + "HoudiniSubmitDeadline": { + "enabled": true, + "active": true, + "use_published": true, + "priority": 50, + "render_chunk_size": 1, + "group": "none", + "export_priority": 50, + "export_chunk_size": 10, + "export_group": "none", + "department": "", + "env_allowed_keys": [], + "env_search_replace_values": {}, + "limit_groups": {} + }, "MayaSubmitDeadline": { "enabled": true, "optional": false, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json index 6d59b5a92b..dfbcd8ef27 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json @@ -89,6 +89,97 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "key": "HoudiniSubmitDeadline", + "label": "Houdini Submit to Deadline", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "active", + "label": "Active" + }, + { + "type": "splitter" + }, + { + "type": "boolean", + "key": "use_published", + "label": "Use Published scene" + }, + { + "type": "number", + "key": "priority", + "label": "Priority" + }, + { + "type": "number", + "key": "render_chunk_size", + "label": "Render Chunk Size" + }, + { + "type": "text", + "key": "group", + "label": "Group" + }, + { + "type": "splitter" + }, + { + "type": "number", + "key": "export_priority", + "label": "Export Priority" + }, + { + "type": "number", + "key": "export_chunk_size", + "label": "Export Chunk Size" + }, + { + "type": "text", + "key": "export_group", + "label": "Export Group" + }, + { + "type": "splitter" + }, + { + "type": "text", + "key": "department", + "label": "Department" + }, + { + "type": "list", + "key": "env_allowed_keys", + "object_type": "text", + "label": "Allowed environment keys" + }, + { + "type": "dict-modifiable", + "key": "env_search_replace_values", + "label": "Search & replace in environment values", + "object_type": { + "type": "text" + } + }, + { + "type": "dict-modifiable", + "key": "limit_groups", + "label": "Limit Groups", + "object_type": { + "type": "list", + "object_type": "text" + } + } + ] + }, { "type": "dict", "collapsible": true, From 33dff766fe6777ff5e801beebe2b366ed5122c20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A0=20Serra=20Arrizabalaga?= Date: Mon, 7 Aug 2023 18:51:53 +0200 Subject: [PATCH 006/327] Shush Hound --- openpype/hosts/houdini/api/lib.py | 12 ++++++++---- .../houdini/plugins/create/create_arnold_rop.py | 9 +++++---- .../houdini/plugins/create/create_mantra_rop.py | 9 +++++---- .../hosts/houdini/plugins/create/create_vray_rop.py | 9 +++++---- .../houdini/plugins/publish/collect_arnold_rop.py | 10 +++++++--- .../houdini/plugins/publish/collect_mantra_rop.py | 10 +++++++--- .../houdini/plugins/publish/collect_vray_rop.py | 8 ++++++-- .../publish/submit_houdini_render_deadline.py | 11 ++++++++--- 8 files changed, 51 insertions(+), 27 deletions(-) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index b1673b746d..94c4529ae2 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -122,7 +122,8 @@ def get_export_parameter(node): """ node_type = node.type().description() - # Ensures the proper Take is selected for each ROP to retrieve the correct ifd + # Ensures the proper Take is selected for each ROP to retrieve the correct + # ifd try: rop_take = hou.takes.findTake(node.parm("take").eval()) if rop_take is not None: @@ -136,7 +137,8 @@ def get_export_parameter(node): elif node_type == "Alfred": return node.parm("alf_diskfile") elif (node_type == "RenderMan" or node_type == "RenderMan RIS"): - pre_ris22 = node.parm("rib_outputmode") and node.parm("rib_outputmode").eval() + pre_ris22 = node.parm("rib_outputmode") and \ + node.parm("rib_outputmode").eval() ris22 = node.parm("diskfile") and node.parm("diskfile").eval() if pre_ris22 or ris22: return node.parm("soho_diskfile") @@ -178,13 +180,15 @@ def get_output_parameter(node): category = node.type().category().name() # Figure out which type of node is being rendered - if node_type == "Geometry" or node_type == "Filmbox FBX" or (node_type == "ROP Output Driver" and category == "Sop"): + if node_type == "Geometry" or node_type == "Filmbox FBX" or \ + (node_type == "ROP Output Driver" and category == "Sop"): return node.parm("sopoutput") elif node_type == "Composite": return node.parm("copoutput") elif node_type == "Channel": return node.parm("chopoutput") - elif node_type == "Dynamics" or (node_type == "ROP Output Driver" and category == "Dop"): + elif node_type == "Dynamics" or \ + (node_type == "ROP Output Driver" and category == "Dop"): return node.parm("dopoutput") elif node_type == "Alfred": return node.parm("alf_diskfile") diff --git a/openpype/hosts/houdini/plugins/create/create_arnold_rop.py b/openpype/hosts/houdini/plugins/create/create_arnold_rop.py index d19795d3a9..b2f04b64ec 100644 --- a/openpype/hosts/houdini/plugins/create/create_arnold_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_arnold_rop.py @@ -53,10 +53,11 @@ class CreateArnoldRop(plugin.HoudiniCreator): } if pre_create_data.get("export_job"): - ass_filepath = "{export_dir}{subset_name}/{subset_name}.$F4.ass".format( - export_dir=hou.text.expandString("$HIP/pyblish/ass/"), - subset_name=subset_name, - ) + ass_filepath = \ + "{export_dir}{subset_name}/{subset_name}.$F4.ass".format( + export_dir=hou.text.expandString("$HIP/pyblish/ass/"), + subset_name=subset_name, + ) parms["ar_ass_export_enable"] = 1 parms["ar_ass_file"] = ass_filepath diff --git a/openpype/hosts/houdini/plugins/create/create_mantra_rop.py b/openpype/hosts/houdini/plugins/create/create_mantra_rop.py index 0601124ac0..62074fe432 100644 --- a/openpype/hosts/houdini/plugins/create/create_mantra_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_mantra_rop.py @@ -49,10 +49,11 @@ class CreateMantraROP(plugin.HoudiniCreator): } if pre_create_data.get("export_job"): - ifd_filepath = "{export_dir}{subset_name}/{subset_name}.$F4.ifd".format( - export_dir=hou.text.expandString("$HIP/pyblish/ifd/"), - subset_name=subset_name, - ) + ifd_filepath = \ + "{export_dir}{subset_name}/{subset_name}.$F4.ifd".format( + export_dir=hou.text.expandString("$HIP/pyblish/ifd/"), + subset_name=subset_name, + ) parms["soho_outputmode"] = 1 parms["soho_diskfile"] = ifd_filepath diff --git a/openpype/hosts/houdini/plugins/create/create_vray_rop.py b/openpype/hosts/houdini/plugins/create/create_vray_rop.py index 29fab0c60c..de3cfc3858 100644 --- a/openpype/hosts/houdini/plugins/create/create_vray_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_vray_rop.py @@ -58,10 +58,11 @@ class CreateVrayROP(plugin.HoudiniCreator): } if pre_create_data.get("export_job"): - scene_filepath = "{export_dir}{subset_name}/{subset_name}.$F4.vrscene".format( - export_dir=hou.text.expandString("$HIP/pyblish/vrscene/"), - subset_name=subset_name, - ) + scene_filepath = \ + "{export_dir}{subset_name}/{subset_name}.$F4.vrscene".format( + export_dir=hou.text.expandString("$HIP/pyblish/vrscene/"), + subset_name=subset_name, + ) # TODO: don't have VRay to check the names of these parms["render_export_mode"] = 1 parms["render_export_filepath"] = scene_filepath diff --git a/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py b/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py index 063dd728e1..4af1d80a2d 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py @@ -38,18 +38,22 @@ class CollectArnoldROPRenderProducts(pyblish.api.InstancePlugin): default_prefix = evalParmNoFrame(rop, "ar_picture") render_products = [] - # Store whether we are splitting the render job in an export + render + # Store whether we are splitting the render job (export + render) export_job = bool(rop.parm("ar_ass_export_enable").eval()) instance.data["exportJob"] = export_job export_prefix = None export_products = [] if export_job: - export_prefix = evalParmNoFrame(rop, "ar_ass_file", pad_character="0") + export_prefix = evalParmNoFrame( + rop, "ar_ass_file", pad_character="0" + ) beauty_export_product = self.get_render_product_name( prefix=export_prefix, suffix=None) export_products.append(beauty_export_product) - self.log.debug("Found export product: {}".format(beauty_export_product)) + self.log.debug( + "Found export product: {}".format(beauty_export_product) + ) instance.data["ifdFile"] = beauty_export_product instance.data["exportFiles"] = list(export_products) diff --git a/openpype/hosts/houdini/plugins/publish/collect_mantra_rop.py b/openpype/hosts/houdini/plugins/publish/collect_mantra_rop.py index b235e2c110..7385a87679 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_mantra_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_mantra_rop.py @@ -42,18 +42,22 @@ class CollectMantraROPRenderProducts(pyblish.api.InstancePlugin): default_prefix = evalParmNoFrame(rop, "vm_picture") render_products = [] - # Store whether we are splitting the render job in an export + render + # Store whether we are splitting the render job (export + render) export_job = bool(rop.parm("soho_outputmode").eval()) instance.data["exportJob"] = export_job export_prefix = None export_products = [] if export_job: - export_prefix = evalParmNoFrame(rop, "soho_diskfile", pad_character="0") + export_prefix = evalParmNoFrame( + rop, "soho_diskfile", pad_character="0" + ) beauty_export_product = self.get_render_product_name( prefix=export_prefix, suffix=None) export_products.append(beauty_export_product) - self.log.debug("Found export product: {}".format(beauty_export_product)) + self.log.debug( + "Found export product: {}".format(beauty_export_product) + ) instance.data["ifdFile"] = beauty_export_product instance.data["exportFiles"] = list(export_products) diff --git a/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py b/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py index 89a7c8bf72..4259f19eb2 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py @@ -50,12 +50,16 @@ class CollectVrayROPRenderProducts(pyblish.api.InstancePlugin): export_prefix = None export_products = [] if export_job: - export_prefix = evalParmNoFrame(rop, "render_export_filepath", pad_character="0") + export_prefix = evalParmNoFrame( + rop, "render_export_filepath", pad_character="0" + ) beauty_export_product = self.get_render_product_name( prefix=export_prefix, suffix=None) export_products.append(beauty_export_product) - self.log.debug("Found export product: {}".format(beauty_export_product)) + self.log.debug( + "Found export product: {}".format(beauty_export_product) + ) instance.data["ifdFile"] = beauty_export_product instance.data["exportFiles"] = list(export_products) diff --git a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py index 82f6876970..d7d5ed906b 100644 --- a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py @@ -144,7 +144,12 @@ class HoudiniSubmitDeadline( ) ] - def get_job_info(self, split_render_job=False, export_job=False, dependency_job_ids=None): + def get_job_info( + self, + split_render_job=False, + export_job=False, + dependency_job_ids=None + ): instance = self._instance context = instance.context @@ -154,7 +159,7 @@ class HoudiniSubmitDeadline( if split_render_job and not export_job: # Convert from family to Deadline plugin name # i.e., arnold_rop -> Arnold - plugin = instance.data.get("family").replace("_rop", "").capitalize() + plugin = instance.data["family"].replace("_rop", "").capitalize() else: plugin = "Houdini" @@ -288,7 +293,7 @@ class HoudiniSubmitDeadline( ) else: self.log.error( - "Family %s not supported yet to split export and render job", + "Family '%s' not supported yet to split render job", family ) return From 2f62dd74ab9aed69f40d21b0ed3cc163c15d7bfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A0=20Serra=20Arrizabalaga?= Date: Mon, 7 Aug 2023 18:59:16 +0200 Subject: [PATCH 007/327] Shush Hound --- .../modules/deadline/abstract_submit_deadline.py | 8 +++++--- .../publish/submit_houdini_render_deadline.py | 15 +++++++++------ 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index 56d8afbe3e..ae251f39b9 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -448,10 +448,12 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin, # Check whether we are splitting render job in export + render # and if so, create a separate task for the render - # TODO: Find a cleaner way so `get_job_info` can take extra args depending - # on host + # TODO: Find a cleaner way so `get_job_info` can take extra args + # depending on host export_job = instance.data["exportJob"] - self.job_info = self.get_job_info(split_render_job=export_job, export_job=True) + self.job_info = self.get_job_info( + split_render_job=export_job, export_job=True + ) self.plugin_info = self.get_plugin_info() self.aux_files = self.get_aux_files() diff --git a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py index d7d5ed906b..e5cd6467f7 100644 --- a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py @@ -23,6 +23,7 @@ class DeadlinePluginInfo(): Version = attr.ib(default=None) IgnoreInputs = attr.ib(default=True) + @attr.s class ArnoldRenderDeadlinePluginInfo(): InputFile = attr.ib(default=None) @@ -101,7 +102,9 @@ class HoudiniSubmitDeadline( cls.group = settings.get("group", cls.group) cls.export_group = settings.get("export_group", cls.export_group) cls.department = settings.get("department", cls.department) - cls.env_allowed_keys = settings.get("env_allowed_keys", cls.env_allowed_keys) + cls.env_allowed_keys = settings.get( + "env_allowed_keys", cls.env_allowed_keys + ) cls.env_search_replace_values = settings.get( "env_search_replace_values", cls.env_allowed_keys ) @@ -145,11 +148,11 @@ class HoudiniSubmitDeadline( ] def get_job_info( - self, - split_render_job=False, - export_job=False, - dependency_job_ids=None - ): + self, + split_render_job=False, + export_job=False, + dependency_job_ids=None + ): instance = self._instance context = instance.context From a0987388c9a3202e34a76f5057528f5f9f7d63ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A0=20Serra=20Arrizabalaga?= Date: Mon, 7 Aug 2023 22:18:13 +0200 Subject: [PATCH 008/327] Revert "Add Houdini deadline submission settings" This reverts commit aab913acbc7f33e0c79d2a966a52b22fd99c54fc. --- .../defaults/project_settings/deadline.json | 15 --- .../schema_project_deadline.json | 91 ------------------- 2 files changed, 106 deletions(-) diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json index d088ef5747..1b8c8397d7 100644 --- a/openpype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -19,21 +19,6 @@ "deadline" ] }, - "HoudiniSubmitDeadline": { - "enabled": true, - "active": true, - "use_published": true, - "priority": 50, - "render_chunk_size": 1, - "group": "none", - "export_priority": 50, - "export_chunk_size": 10, - "export_group": "none", - "department": "", - "env_allowed_keys": [], - "env_search_replace_values": {}, - "limit_groups": {} - }, "MayaSubmitDeadline": { "enabled": true, "optional": false, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json index dfbcd8ef27..6d59b5a92b 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json @@ -89,97 +89,6 @@ } ] }, - { - "type": "dict", - "collapsible": true, - "key": "HoudiniSubmitDeadline", - "label": "Houdini Submit to Deadline", - "checkbox_key": "enabled", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "boolean", - "key": "active", - "label": "Active" - }, - { - "type": "splitter" - }, - { - "type": "boolean", - "key": "use_published", - "label": "Use Published scene" - }, - { - "type": "number", - "key": "priority", - "label": "Priority" - }, - { - "type": "number", - "key": "render_chunk_size", - "label": "Render Chunk Size" - }, - { - "type": "text", - "key": "group", - "label": "Group" - }, - { - "type": "splitter" - }, - { - "type": "number", - "key": "export_priority", - "label": "Export Priority" - }, - { - "type": "number", - "key": "export_chunk_size", - "label": "Export Chunk Size" - }, - { - "type": "text", - "key": "export_group", - "label": "Export Group" - }, - { - "type": "splitter" - }, - { - "type": "text", - "key": "department", - "label": "Department" - }, - { - "type": "list", - "key": "env_allowed_keys", - "object_type": "text", - "label": "Allowed environment keys" - }, - { - "type": "dict-modifiable", - "key": "env_search_replace_values", - "label": "Search & replace in environment values", - "object_type": { - "type": "text" - } - }, - { - "type": "dict-modifiable", - "key": "limit_groups", - "label": "Limit Groups", - "object_type": { - "type": "list", - "object_type": "text" - } - } - ] - }, { "type": "dict", "collapsible": true, From d309df3b49b2d20cdac28a4150f1546f83ee5a8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A0=20Serra=20Arrizabalaga?= Date: Mon, 7 Aug 2023 22:22:31 +0200 Subject: [PATCH 009/327] Remove Houdini Deadline settings as it's not relevant to feature --- .../publish/submit_houdini_render_deadline.py | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py index e5cd6467f7..fdfc66b2c0 100644 --- a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py @@ -81,34 +81,6 @@ class HoudiniSubmitDeadline( env_allowed_keys = [] env_search_replace_values = {} - @classmethod - def apply_settings(cls, project_settings, system_settings): - settings = project_settings["deadline"]["publish"]["HoudiniSubmitDeadline"] # noqa - - # Take some defaults from settings - cls.use_published = settings.get( - "use_published", cls.use_published - ) - cls.priority = settings.get( - "priority", cls.priority - ) - cls.export_priority = settings.get( - "export_priority", cls.export_priority - ) - cls.chunk_size = settings.get("chunk_size", cls.chunk_size) - cls.export_chunk_size = settings.get( - "export_chunk_size", cls.export_chunk_size - ) - cls.group = settings.get("group", cls.group) - cls.export_group = settings.get("export_group", cls.export_group) - cls.department = settings.get("department", cls.department) - cls.env_allowed_keys = settings.get( - "env_allowed_keys", cls.env_allowed_keys - ) - cls.env_search_replace_values = settings.get( - "env_search_replace_values", cls.env_allowed_keys - ) - @classmethod def get_attribute_defs(cls): return [ From 20583173c663e84b84e0512692cf1495f51fb1ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A0=20Serra=20Arrizabalaga?= Date: Mon, 7 Aug 2023 22:26:02 +0200 Subject: [PATCH 010/327] Remove some other noise from PR feature --- .../publish/submit_houdini_render_deadline.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py index fdfc66b2c0..d6c658944a 100644 --- a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py @@ -76,10 +76,6 @@ class HoudiniSubmitDeadline( export_chunk_size = 10 group = "" export_group = "" - department = "" - limit_groups = {} - env_allowed_keys = [] - env_search_replace_values = {} @classmethod def get_attribute_defs(cls): @@ -157,8 +153,6 @@ class HoudiniSubmitDeadline( "priority", self.priority ) - job_info.Department = self.department - if is_in_tests(): job_info.BatchName += datetime.now().strftime("%d%m%Y%H%M%S") @@ -210,18 +204,9 @@ class HoudiniSubmitDeadline( if self._instance.context.data.get("deadlinePassMongoUrl"): keys.append("OPENPYPE_MONGO") - # add allowed keys from preset if any - if self.env_allowed_keys: - keys += self.env_allowed_keys - environment = dict({key: os.environ[key] for key in keys if key in os.environ}, **legacy_io.Session) - # finally search replace in values of any key - if self.env_search_replace_values: - for key, value in environment.items(): - for _k, _v in self.env_search_replace_values.items(): - environment[key] = value.replace(_k, _v) for key in keys: value = environment.get(key) From f58c4de0d4a284867eb0fb43b3d235abff9f26ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A0=20Serra=20Arrizabalaga?= Date: Mon, 7 Aug 2023 22:44:45 +0200 Subject: [PATCH 011/327] Move some of the logic to Houdini submitter so abstract functions are a bit more generic --- .../deadline/abstract_submit_deadline.py | 19 +++++------- .../publish/submit_houdini_render_deadline.py | 30 +++++++++++-------- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index ae251f39b9..dddb4400c1 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -446,25 +446,20 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin, self.scene_path = file_path self.log.info("Using {} for render/export.".format(file_path)) - # Check whether we are splitting render job in export + render - # and if so, create a separate task for the render - # TODO: Find a cleaner way so `get_job_info` can take extra args - # depending on host - export_job = instance.data["exportJob"] - self.job_info = self.get_job_info( - split_render_job=export_job, export_job=True - ) + self.job_info = self.get_job_info() self.plugin_info = self.get_plugin_info() self.aux_files = self.get_aux_files() job_id = self.process_submission() + self.log.info("Submitted job to Deadline: {}.".format(job_id)) + + # TODO: Find a way that's more generic and not render type specific + export_job = instance.data["exportJob"] if export_job: self.log.info("Splitting export and render in two jobs") self.log.info("Export job id: %s", job_id) - render_job_info = self.get_job_info( - split_render_job=True, dependency_job_ids=[job_id] - ) - render_plugin_info = self.get_plugin_info(split_render_job=True) + render_job_info = self.get_job_info(dependency_job_ids=[job_id]) + render_plugin_info = self.get_plugin_info(job_type="render") payload = self.assemble_payload( job_info=render_job_info, plugin_info=render_plugin_info diff --git a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py index d6c658944a..83f6e81485 100644 --- a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py @@ -115,19 +115,24 @@ class HoudiniSubmitDeadline( ) ] - def get_job_info( - self, - split_render_job=False, - export_job=False, - dependency_job_ids=None - ): + def get_job_info(self, dependency_job_ids=None): instance = self._instance context = instance.context attribute_values = self.get_attr_values_from_data(instance.data) - if split_render_job and not export_job: + # Whether Deadline render submission is being split in two + # (extract + render) + split_render_job = instance.data["exportJob"] + + # If there's some dependency job ids we can assume this is a render job + # and not an export job + is_export_job = True + if dependency_job_ids: + is_export_job = False + + if split_render_job and not is_export_job: # Convert from family to Deadline plugin name # i.e., arnold_rop -> Arnold plugin = instance.data["family"].replace("_rop", "").capitalize() @@ -144,7 +149,7 @@ class HoudiniSubmitDeadline( job_info.UserName = context.data.get( "deadlineUser", getpass.getuser()) - if split_render_job and export_job: + if split_render_job and is_export_job: job_info.Priority = attribute_values.get( "export_priority", self.export_priority ) @@ -166,13 +171,13 @@ class HoudiniSubmitDeadline( # Make sure we make job frame dependent so render tasks pick up a soon # as export tasks are done - if split_render_job and not export_job: + if split_render_job and not is_export_job: job_info.IsFrameDependent = True job_info.Pool = instance.data.get("primaryPool") job_info.SecondaryPool = instance.data.get("secondaryPool") job_info.Group = self.group - if split_render_job and export_job: + if split_render_job and is_export_job: job_info.ChunkSize = attribute_values.get( "export_chunk", self.export_chunk_size ) @@ -207,7 +212,6 @@ class HoudiniSubmitDeadline( environment = dict({key: os.environ[key] for key in keys if key in os.environ}, **legacy_io.Session) - for key in keys: value = environment.get(key) if value: @@ -228,7 +232,7 @@ class HoudiniSubmitDeadline( return job_info - def get_plugin_info(self, split_render_job=False): + def get_plugin_info(self, job_type=None): instance = self._instance context = instance.context @@ -236,7 +240,7 @@ class HoudiniSubmitDeadline( hou_major_minor = hou.applicationVersionString().rsplit(".", 1)[0] # Output driver to render - if split_render_job: + if job_type == "render": family = instance.data.get("family") if family == "arnold_rop": plugin_info = ArnoldRenderDeadlinePluginInfo( From 12b2a74ea963a72737f9409f5bfd04acabb53f79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A0=20Serra=20Arrizabalaga?= Date: Mon, 7 Aug 2023 22:52:01 +0200 Subject: [PATCH 012/327] Fix docstring --- .../deadline/plugins/publish/submit_houdini_render_deadline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py index 83f6e81485..8434ddacd3 100644 --- a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py @@ -45,7 +45,7 @@ class HoudiniSubmitDeadline( abstract_submit_deadline.AbstractSubmitDeadline, OpenPypePyblishPluginMixin ): - """Submit Solaris USD Render ROPs to Deadline. + """Submit Render ROPs to Deadline. Renders are submitted to a Deadline Web Service as supplied via the environment variable AVALON_DEADLINE. From ad11a4d3d3c7ec8be0c605cf4da18deea3ab102e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A0=20Serra=20Arrizabalaga?= Date: Tue, 8 Aug 2023 16:08:45 +0200 Subject: [PATCH 013/327] Fix VRay submission parms --- openpype/hosts/houdini/plugins/create/create_vray_rop.py | 3 +-- openpype/hosts/houdini/plugins/publish/collect_vray_rop.py | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_vray_rop.py b/openpype/hosts/houdini/plugins/create/create_vray_rop.py index de3cfc3858..222088523a 100644 --- a/openpype/hosts/houdini/plugins/create/create_vray_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_vray_rop.py @@ -63,8 +63,7 @@ class CreateVrayROP(plugin.HoudiniCreator): export_dir=hou.text.expandString("$HIP/pyblish/vrscene/"), subset_name=subset_name, ) - # TODO: don't have VRay to check the names of these - parms["render_export_mode"] = 1 + parms["render_export_mode"] = "1" parms["render_export_filepath"] = scene_filepath if self.selected_nodes: diff --git a/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py b/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py index 4259f19eb2..d7d53ff566 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py @@ -44,7 +44,6 @@ class CollectVrayROPRenderProducts(pyblish.api.InstancePlugin): # TODO: add render elements if render element # Store whether we are splitting the render job in an export + render - # TODO: check names of VRay parms export_job = bool(rop.parm("render_export_mode").eval()) instance.data["exportJob"] = export_job export_prefix = None @@ -63,7 +62,7 @@ class CollectVrayROPRenderProducts(pyblish.api.InstancePlugin): instance.data["ifdFile"] = beauty_export_product instance.data["exportFiles"] = list(export_products) - beauty_product = self.get_beauty_render_product(default_prefix) + beauty_product = self.get_render_product_name(default_prefix) render_products.append(beauty_product) files_by_aov = { "RGB Color": self.generate_expected_files(instance, @@ -97,7 +96,7 @@ class CollectVrayROPRenderProducts(pyblish.api.InstancePlugin): instance.data["colorspaceDisplay"] = colorspace_data["display"] instance.data["colorspaceView"] = colorspace_data["view"] - def get_beauty_render_product(self, prefix, suffix=""): + def get_render_product_name(self, prefix, suffix=""): """Return the beauty output filename if render element enabled """ aov_parm = ".{}".format(suffix) From cb7274bf1a79d5c5d315b5ae08e7f6042c598539 Mon Sep 17 00:00:00 2001 From: Fabia Serra Arrizabalaga Date: Wed, 9 Aug 2023 15:19:24 +0200 Subject: [PATCH 014/327] Fix render_export_mode for VRay so it only exports --- openpype/hosts/houdini/plugins/create/create_vray_rop.py | 4 +++- openpype/hosts/houdini/plugins/publish/collect_vray_rop.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_vray_rop.py b/openpype/hosts/houdini/plugins/create/create_vray_rop.py index 222088523a..cd5ef2013d 100644 --- a/openpype/hosts/houdini/plugins/create/create_vray_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_vray_rop.py @@ -63,7 +63,9 @@ class CreateVrayROP(plugin.HoudiniCreator): export_dir=hou.text.expandString("$HIP/pyblish/vrscene/"), subset_name=subset_name, ) - parms["render_export_mode"] = "1" + # Setting render_export_mode to "2" because that's for + # "Export only" ("1" is for "Export & Render") + parms["render_export_mode"] = "2" parms["render_export_filepath"] = scene_filepath if self.selected_nodes: diff --git a/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py b/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py index d7d53ff566..184c644fd6 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py @@ -44,7 +44,7 @@ class CollectVrayROPRenderProducts(pyblish.api.InstancePlugin): # TODO: add render elements if render element # Store whether we are splitting the render job in an export + render - export_job = bool(rop.parm("render_export_mode").eval()) + export_job = rop.parm("render_export_mode").eval() == "2" instance.data["exportJob"] = export_job export_prefix = None export_products = [] From bfe8f88b241e4a71f3a9aabbfda2d87087f26e38 Mon Sep 17 00:00:00 2001 From: Fabia Serra Arrizabalaga Date: Thu, 10 Aug 2023 13:43:27 +0200 Subject: [PATCH 015/327] Add SeparateFilesPerFrame attribute to Vray plugin --- .../deadline/plugins/publish/submit_houdini_render_deadline.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py index 8434ddacd3..cc982cd2eb 100644 --- a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py @@ -39,6 +39,7 @@ class MantraRenderDeadlinePluginInfo(): @attr.s class VrayRenderPluginInfo(): InputFilename = attr.ib(default=None) + SeparateFilesPerFrame = attr.ib(default=True) class HoudiniSubmitDeadline( From 5e5db076d1795256ecf7b1cebe064651452cefaa Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 21 Sep 2023 11:42:42 +0800 Subject: [PATCH 016/327] remove unrelated codes to this pull request --- openpype/hosts/houdini/api/lib.py | 50 +- .../plugins/create/create_arnold_ass.py | 16 + .../houdini/plugins/create/create_bgeo.py | 16 +- .../plugins/create/create_mantra_ifd.py | 56 ++ .../plugins/create/create_pointcache.py | 17 + .../plugins/create/create_redshift_proxy.py | 16 + .../plugins/create/create_vbd_cache.py | 22 +- .../hosts/houdini/plugins/load/load_camera.py | 81 ++- .../plugins/publish/collect_cache_farm.py | 75 +++ .../plugins/publish/collect_chunk_size.py | 39 ++ .../houdini/plugins/publish/collect_frames.py | 3 +- .../plugins/publish/extract_alembic.py | 4 + .../houdini/plugins/publish/extract_ass.py | 5 +- .../houdini/plugins/publish/extract_bgeo.py | 4 +- .../plugins/publish/extract_mantra_ifd.py | 51 ++ .../plugins/publish/extract_redshift_proxy.py | 5 +- .../plugins/publish/extract_vdb_cache.py | 4 +- .../plugins/publish/increment_current_file.py | 3 +- .../publish/submit_houdini_cache_deadline.py | 183 +++++++ .../publish/submit_publish_cache_job.py | 501 ++++++++++++++++++ .../publish/validate_deadline_pools.py | 3 +- openpype/pipeline/farm/pyblish_functions.py | 232 ++++++++ .../defaults/project_settings/deadline.json | 8 + .../defaults/project_settings/houdini.json | 5 + .../schemas/schema_houdini_publish.json | 25 + .../server/settings/publish_plugins.py | 22 + server_addon/deadline/server/version.py | 2 +- .../server/settings/publish_plugins.py | 15 + website/docs/artist_hosts_houdini.md | 24 + .../assets/houdini_farm_cache_creator.png | Bin 0 -> 95572 bytes .../docs/assets/houdini_farm_cache_loader.png | Bin 0 -> 134816 bytes .../docs/assets/houdini_frame_per_task.png | Bin 0 -> 59257 bytes 32 files changed, 1380 insertions(+), 107 deletions(-) create mode 100644 openpype/hosts/houdini/plugins/create/create_mantra_ifd.py create mode 100644 openpype/hosts/houdini/plugins/publish/collect_cache_farm.py create mode 100644 openpype/hosts/houdini/plugins/publish/collect_chunk_size.py create mode 100644 openpype/hosts/houdini/plugins/publish/extract_mantra_ifd.py create mode 100644 openpype/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py create mode 100644 openpype/modules/deadline/plugins/publish/submit_publish_cache_job.py create mode 100644 website/docs/assets/houdini_farm_cache_creator.png create mode 100644 website/docs/assets/houdini_farm_cache_loader.png create mode 100644 website/docs/assets/houdini_frame_per_task.png diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index eff98c05f1..856e7abbd3 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -138,6 +138,9 @@ def get_output_parameter(node): return node.parm("ar_ass_file") elif node_type == "Redshift_Proxy_Output": return node.parm("RS_archive_file") + elif node_type == "ifd": + if node.evalParm("soho_outputmode"): + return node.parm("soho_diskfile") raise TypeError("Node type '%s' not supported" % node_type) @@ -649,50 +652,3 @@ def get_color_management_preferences(): "display": hou.Color.ocio_defaultDisplay(), "view": hou.Color.ocio_defaultView() } - - -def get_resolution_from_doc(doc): - """Get resolution from the given asset document. """ - - if not doc or "data" not in doc: - print("Entered document is not valid. \"{}\"".format(str(doc))) - return None - - resolution_width = doc["data"].get("resolutionWidth") - resolution_height = doc["data"].get("resolutionHeight") - - # Make sure both width and height are set - if resolution_width is None or resolution_height is None: - print("No resolution information found for \"{}\"".format(doc["name"])) - return None - - return int(resolution_width), int(resolution_height) - - -def set_camera_resolution(camera, asset_doc=None): - """Apply resolution to camera from asset document of the publish""" - - if not asset_doc: - asset_doc = get_current_project_asset() - - resolution = get_resolution_from_doc(asset_doc) - - if resolution: - print("Setting camera resolution: {} -> {}x{}".format( - camera.name(), resolution[0], resolution[1] - )) - camera.parm("resx").set(resolution[0]) - camera.parm("resy").set(resolution[1]) - - -def get_camera_from_container(container): - """Get camera from container node. """ - - cameras = container.recursiveGlob( - "*", - filter=hou.nodeTypeFilter.ObjCamera, - include_subnets=False - ) - - assert len(cameras) == 1, "Camera instance must have only one camera" - return cameras[0] diff --git a/openpype/hosts/houdini/plugins/create/create_arnold_ass.py b/openpype/hosts/houdini/plugins/create/create_arnold_ass.py index 12d08f7d83..437a14c723 100644 --- a/openpype/hosts/houdini/plugins/create/create_arnold_ass.py +++ b/openpype/hosts/houdini/plugins/create/create_arnold_ass.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- """Creator plugin for creating Arnold ASS files.""" from openpype.hosts.houdini.api import plugin +from openpype.lib import BoolDef class CreateArnoldAss(plugin.HoudiniCreator): @@ -21,6 +22,9 @@ class CreateArnoldAss(plugin.HoudiniCreator): instance_data.pop("active", None) instance_data.update({"node_type": "arnold"}) + creator_attributes = instance_data.setdefault( + "creator_attributes", dict()) + creator_attributes["farm"] = pre_create_data["farm"] instance = super(CreateArnoldAss, self).create( subset_name, @@ -52,3 +56,15 @@ class CreateArnoldAss(plugin.HoudiniCreator): # Lock any parameters in this list to_lock = ["ar_ass_export_enable", "family", "id"] self.lock_parameters(instance_node, to_lock) + + def get_instance_attr_defs(self): + return [ + BoolDef("farm", + label="Submitting to Farm", + default=False) + ] + + def get_pre_create_attr_defs(self): + attrs = super().get_pre_create_attr_defs() + # Use same attributes as for instance attributes + return attrs + self.get_instance_attr_defs() diff --git a/openpype/hosts/houdini/plugins/create/create_bgeo.py b/openpype/hosts/houdini/plugins/create/create_bgeo.py index a3f31e7e94..6d903fb02d 100644 --- a/openpype/hosts/houdini/plugins/create/create_bgeo.py +++ b/openpype/hosts/houdini/plugins/create/create_bgeo.py @@ -2,7 +2,7 @@ """Creator plugin for creating pointcache bgeo files.""" from openpype.hosts.houdini.api import plugin from openpype.pipeline import CreatedInstance, CreatorError -from openpype.lib import EnumDef +from openpype.lib import EnumDef, BoolDef class CreateBGEO(plugin.HoudiniCreator): @@ -18,6 +18,9 @@ class CreateBGEO(plugin.HoudiniCreator): instance_data.pop("active", None) instance_data.update({"node_type": "geometry"}) + creator_attributes = instance_data.setdefault( + "creator_attributes", dict()) + creator_attributes["farm"] = pre_create_data["farm"] instance = super(CreateBGEO, self).create( subset_name, @@ -58,6 +61,13 @@ class CreateBGEO(plugin.HoudiniCreator): instance_node.setParms(parms) + def get_instance_attr_defs(self): + return [ + BoolDef("farm", + label="Submitting to Farm", + default=False) + ] + def get_pre_create_attr_defs(self): attrs = super().get_pre_create_attr_defs() bgeo_enum = [ @@ -88,5 +98,5 @@ class CreateBGEO(plugin.HoudiniCreator): ] return attrs + [ - EnumDef("bgeo_type", bgeo_enum, label="BGEO Options"), - ] + EnumDef("bgeo_type", bgeo_enum, label="BGEO Options") + ] + self.get_instance_attr_defs() diff --git a/openpype/hosts/houdini/plugins/create/create_mantra_ifd.py b/openpype/hosts/houdini/plugins/create/create_mantra_ifd.py new file mode 100644 index 0000000000..7ea7d1042f --- /dev/null +++ b/openpype/hosts/houdini/plugins/create/create_mantra_ifd.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +"""Creator plugin for creating pointcache alembics.""" +from openpype.hosts.houdini.api import plugin +from openpype.pipeline import CreatedInstance +from openpype.lib import BoolDef + + +class CreateMantraIFD(plugin.HoudiniCreator): + """Mantra .ifd Archive""" + identifier = "io.openpype.creators.houdini.mantraifd" + label = "Mantra IFD" + family = "mantraifd" + icon = "gears" + + def create(self, subset_name, instance_data, pre_create_data): + import hou + instance_data.pop("active", None) + instance_data.update({"node_type": "ifd"}) + creator_attributes = instance_data.setdefault( + "creator_attributes", dict()) + creator_attributes["farm"] = pre_create_data["farm"] + instance = super(CreateMantraIFD, self).create( + subset_name, + instance_data, + pre_create_data) # type: CreatedInstance + + instance_node = hou.node(instance.get("instance_node")) + + filepath = "{}{}".format( + hou.text.expandString("$HIP/pyblish/"), + "{}.$F4.ifd".format(subset_name)) + parms = { + # Render frame range + "trange": 1, + # Arnold ROP settings + "soho_diskfile": filepath, + "soho_outputmode": 1 + } + + instance_node.setParms(parms) + + # Lock any parameters in this list + to_lock = ["soho_outputmode", "family", "id"] + self.lock_parameters(instance_node, to_lock) + + def get_instance_attr_defs(self): + return [ + BoolDef("farm", + label="Submitting to Farm", + default=False) + ] + + def get_pre_create_attr_defs(self): + attrs = super().get_pre_create_attr_defs() + # Use same attributes as for instance attributes + return attrs + self.get_instance_attr_defs() diff --git a/openpype/hosts/houdini/plugins/create/create_pointcache.py b/openpype/hosts/houdini/plugins/create/create_pointcache.py index 7eaf2aff2b..2d2f89cc48 100644 --- a/openpype/hosts/houdini/plugins/create/create_pointcache.py +++ b/openpype/hosts/houdini/plugins/create/create_pointcache.py @@ -1,10 +1,12 @@ # -*- coding: utf-8 -*- """Creator plugin for creating pointcache alembics.""" from openpype.hosts.houdini.api import plugin +from openpype.lib import BoolDef import hou + class CreatePointCache(plugin.HoudiniCreator): """Alembic ROP to pointcache""" identifier = "io.openpype.creators.houdini.pointcache" @@ -15,6 +17,9 @@ class CreatePointCache(plugin.HoudiniCreator): def create(self, subset_name, instance_data, pre_create_data): instance_data.pop("active", None) instance_data.update({"node_type": "alembic"}) + creator_attributes = instance_data.setdefault( + "creator_attributes", dict()) + creator_attributes["farm"] = pre_create_data["farm"] instance = super(CreatePointCache, self).create( subset_name, @@ -105,3 +110,15 @@ class CreatePointCache(plugin.HoudiniCreator): else: return min(outputs, key=lambda node: node.evalParm('outputidx')) + + def get_instance_attr_defs(self): + return [ + BoolDef("farm", + label="Submitting to Farm", + default=False) + ] + + def get_pre_create_attr_defs(self): + attrs = super().get_pre_create_attr_defs() + # Use same attributes as for instance attributes + return attrs + self.get_instance_attr_defs() diff --git a/openpype/hosts/houdini/plugins/create/create_redshift_proxy.py b/openpype/hosts/houdini/plugins/create/create_redshift_proxy.py index b814dd9d57..de1f1719fb 100644 --- a/openpype/hosts/houdini/plugins/create/create_redshift_proxy.py +++ b/openpype/hosts/houdini/plugins/create/create_redshift_proxy.py @@ -2,6 +2,7 @@ """Creator plugin for creating Redshift proxies.""" from openpype.hosts.houdini.api import plugin from openpype.pipeline import CreatedInstance +from openpype.lib import BoolDef class CreateRedshiftProxy(plugin.HoudiniCreator): @@ -24,6 +25,9 @@ class CreateRedshiftProxy(plugin.HoudiniCreator): # TODO: Somehow enforce so that it only shows the original limited # attributes of the Redshift_Proxy_Output node type instance_data.update({"node_type": "Redshift_Proxy_Output"}) + creator_attributes = instance_data.setdefault( + "creator_attributes", dict()) + creator_attributes["farm"] = pre_create_data["farm"] instance = super(CreateRedshiftProxy, self).create( subset_name, @@ -44,3 +48,15 @@ class CreateRedshiftProxy(plugin.HoudiniCreator): # Lock some Avalon attributes to_lock = ["family", "id", "prim_to_detail_pattern"] self.lock_parameters(instance_node, to_lock) + + def get_instance_attr_defs(self): + return [ + BoolDef("farm", + label="Submitting to Farm", + default=False) + ] + + def get_pre_create_attr_defs(self): + attrs = super().get_pre_create_attr_defs() + # Use same attributes as for instance attributes + return attrs + self.get_instance_attr_defs() diff --git a/openpype/hosts/houdini/plugins/create/create_vbd_cache.py b/openpype/hosts/houdini/plugins/create/create_vbd_cache.py index 9c96e48e3a..8589600fc6 100644 --- a/openpype/hosts/houdini/plugins/create/create_vbd_cache.py +++ b/openpype/hosts/houdini/plugins/create/create_vbd_cache.py @@ -2,6 +2,7 @@ """Creator plugin for creating VDB Caches.""" from openpype.hosts.houdini.api import plugin from openpype.pipeline import CreatedInstance +from openpype.lib import BoolDef import hou @@ -19,15 +20,20 @@ class CreateVDBCache(plugin.HoudiniCreator): instance_data.pop("active", None) instance_data.update({"node_type": "geometry"}) - + creator_attributes = instance_data.setdefault( + "creator_attributes", dict()) + creator_attributes["farm"] = pre_create_data["farm"] instance = super(CreateVDBCache, self).create( subset_name, instance_data, pre_create_data) # type: CreatedInstance instance_node = hou.node(instance.get("instance_node")) + file_path = "{}{}".format( + hou.text.expandString("$HIP/pyblish/"), + "{}.$F4.vdb".format(subset_name)) parms = { - "sopoutput": "$HIP/pyblish/{}.$F4.vdb".format(subset_name), + "sopoutput": file_path, "initsim": True, "trange": 1 } @@ -102,3 +108,15 @@ class CreateVDBCache(plugin.HoudiniCreator): else: return min(outputs, key=lambda node: node.evalParm('outputidx')) + + def get_instance_attr_defs(self): + return [ + BoolDef("farm", + label="Submitting to Farm", + default=False) + ] + + def get_pre_create_attr_defs(self): + attrs = super().get_pre_create_attr_defs() + # Use same attributes as for instance attributes + return attrs + self.get_instance_attr_defs() diff --git a/openpype/hosts/houdini/plugins/load/load_camera.py b/openpype/hosts/houdini/plugins/load/load_camera.py index e16146a267..7b4a04809e 100644 --- a/openpype/hosts/houdini/plugins/load/load_camera.py +++ b/openpype/hosts/houdini/plugins/load/load_camera.py @@ -4,13 +4,6 @@ from openpype.pipeline import ( ) from openpype.hosts.houdini.api import pipeline -from openpype.hosts.houdini.api.lib import ( - set_camera_resolution, - get_camera_from_container -) - -import hou - ARCHIVE_EXPRESSION = ('__import__("_alembic_hom_extensions")' '.alembicGetCameraDict') @@ -32,15 +25,7 @@ def transfer_non_default_values(src, dest, ignore=None): channel expression and ignore certain Parm types. """ - - ignore_types = { - hou.parmTemplateType.Toggle, - hou.parmTemplateType.Menu, - hou.parmTemplateType.Button, - hou.parmTemplateType.FolderSet, - hou.parmTemplateType.Separator, - hou.parmTemplateType.Label, - } + import hou src.updateParmStates() @@ -77,6 +62,14 @@ def transfer_non_default_values(src, dest, ignore=None): continue # Ignore folders, separators, etc. + ignore_types = { + hou.parmTemplateType.Toggle, + hou.parmTemplateType.Menu, + hou.parmTemplateType.Button, + hou.parmTemplateType.FolderSet, + hou.parmTemplateType.Separator, + hou.parmTemplateType.Label, + } if parm.parmTemplate().type() in ignore_types: continue @@ -97,8 +90,13 @@ class CameraLoader(load.LoaderPlugin): def load(self, context, name=None, namespace=None, data=None): + import os + import hou + # Format file name, Houdini only wants forward slashes - file_path = self.filepath_from_context(context).replace("\\", "/") + file_path = self.filepath_from_context(context) + file_path = os.path.normpath(file_path) + file_path = file_path.replace("\\", "/") # Get the root node obj = hou.node("/obj") @@ -108,21 +106,19 @@ class CameraLoader(load.LoaderPlugin): node_name = "{}_{}".format(namespace, name) if namespace else name # Create a archive node - node = self.create_and_connect(obj, "alembicarchive", node_name) + container = self.create_and_connect(obj, "alembicarchive", node_name) # TODO: add FPS of project / asset - node.setParms({"fileName": file_path, "channelRef": True}) + container.setParms({"fileName": file_path, + "channelRef": True}) # Apply some magic - node.parm("buildHierarchy").pressButton() - node.moveToGoodPosition() + container.parm("buildHierarchy").pressButton() + container.moveToGoodPosition() # Create an alembic xform node - nodes = [node] + nodes = [container] - camera = get_camera_from_container(node) - self._match_maya_render_mask(camera) - set_camera_resolution(camera, asset_doc=context["asset"]) self[:] = nodes return pipeline.containerise(node_name, @@ -147,14 +143,14 @@ class CameraLoader(load.LoaderPlugin): # Store the cam temporarily next to the Alembic Archive # so that we can preserve parm values the user set on it # after build hierarchy was triggered. - old_camera = get_camera_from_container(node) + old_camera = self._get_camera(node) temp_camera = old_camera.copyTo(node.parent()) # Rebuild node.parm("buildHierarchy").pressButton() # Apply values to the new camera - new_camera = get_camera_from_container(node) + new_camera = self._get_camera(node) transfer_non_default_values(temp_camera, new_camera, # The hidden uniform scale attribute @@ -162,9 +158,6 @@ class CameraLoader(load.LoaderPlugin): # "icon_scale" just skip that completely ignore={"scale"}) - self._match_maya_render_mask(new_camera) - set_camera_resolution(new_camera) - temp_camera.destroy() def remove(self, container): @@ -172,6 +165,15 @@ class CameraLoader(load.LoaderPlugin): node = container["node"] node.destroy() + def _get_camera(self, node): + import hou + cameras = node.recursiveGlob("*", + filter=hou.nodeTypeFilter.ObjCamera, + include_subnets=False) + + assert len(cameras) == 1, "Camera instance must have only one camera" + return cameras[0] + def create_and_connect(self, node, node_type, name=None): """Create a node within a node which and connect it to the input @@ -192,20 +194,5 @@ class CameraLoader(load.LoaderPlugin): new_node.moveToGoodPosition() return new_node - def _match_maya_render_mask(self, camera): - """Workaround to match Maya render mask in Houdini""" - - # print("Setting match maya render mask ") - parm = camera.parm("aperture") - expression = parm.expression() - expression = expression.replace("return ", "aperture = ") - expression += """ -# Match maya render mask (logic from Houdini's own FBX importer) -node = hou.pwd() -resx = node.evalParm('resx') -resy = node.evalParm('resy') -aspect = node.evalParm('aspect') -aperture *= min(1, (resx / resy * aspect) / 1.5) -return aperture -""" - parm.setExpression(expression, language=hou.exprLanguage.Python) + def switch(self, container, representation): + self.update(container, representation) diff --git a/openpype/hosts/houdini/plugins/publish/collect_cache_farm.py b/openpype/hosts/houdini/plugins/publish/collect_cache_farm.py new file mode 100644 index 0000000000..36ade32a35 --- /dev/null +++ b/openpype/hosts/houdini/plugins/publish/collect_cache_farm.py @@ -0,0 +1,75 @@ +import os +import pyblish.api +import hou +from openpype.hosts.houdini.api import lib + + +class CollectDataforCache(pyblish.api.InstancePlugin): + """Collect data for caching to Deadline.""" + + order = pyblish.api.CollectorOrder + 0.04 + families = ["ass", "pointcache", + "mantraifd", "redshiftproxy", + "vdbcache"] + hosts = ["houdini"] + targets = ["local", "remote"] + label = "Collect Data for Cache" + + def process(self, instance): + creator_attribute = instance.data["creator_attributes"] + farm_enabled = creator_attribute["farm"] + instance.data["farm"] = farm_enabled + if not farm_enabled: + self.log.debug("Caching on farm is disabled. " + "Skipping farm collecting.") + return + # Why do we need this particular collector to collect the expected + # output files from a ROP node. Don't we have a dedicated collector + # for that yet? + # Collect expected files + ropnode = hou.node(instance.data["instance_node"]) + output_parm = lib.get_output_parameter(ropnode) + expected_filepath = output_parm.eval() + instance.data.setdefault("files", list()) + instance.data.setdefault("expectedFiles", list()) + if instance.data.get("frames"): + files = self.get_files(instance, expected_filepath) + # list of files + instance.data["files"].extend(files) + else: + # single file + instance.data["files"].append(output_parm.eval()) + cache_files = {"_": instance.data["files"]} + # Convert instance family to pointcache if it is bgeo or abc + # because ??? + for family in instance.data["families"]: + if family == "bgeo" or "abc": + instance.data["family"] = "pointcache" + break + instance.data.update({ + "plugin": "Houdini", + "publish": True + }) + instance.data["families"].append("publish.hou") + instance.data["expectedFiles"].append(cache_files) + + self.log.debug("{}".format(instance.data)) + + def get_files(self, instance, output_parm): + """Get the files with the frame range data + + Args: + instance (_type_): instance + output_parm (_type_): path of output parameter + + Returns: + files: a list of files + """ + directory = os.path.dirname(output_parm) + + files = [ + os.path.join(directory, frame).replace("\\", "/") + for frame in instance.data["frames"] + ] + + return files diff --git a/openpype/hosts/houdini/plugins/publish/collect_chunk_size.py b/openpype/hosts/houdini/plugins/publish/collect_chunk_size.py new file mode 100644 index 0000000000..1c867e930a --- /dev/null +++ b/openpype/hosts/houdini/plugins/publish/collect_chunk_size.py @@ -0,0 +1,39 @@ +import pyblish.api +from openpype.lib import NumberDef +from openpype.pipeline import OpenPypePyblishPluginMixin + + +class CollectChunkSize(pyblish.api.InstancePlugin, + OpenPypePyblishPluginMixin): + """Collect chunk size for cache submission to Deadline.""" + + order = pyblish.api.CollectorOrder + 0.05 + families = ["ass", "pointcache", + "vdbcache", "mantraifd", + "redshiftproxy"] + hosts = ["houdini"] + targets = ["local", "remote"] + label = "Collect Chunk Size" + chunkSize = 999999 + + def process(self, instance): + # need to get the chunk size info from the setting + attr_values = self.get_attr_values_from_data(instance.data) + instance.data["chunkSize"] = attr_values.get("chunkSize") + + @classmethod + def apply_settings(cls, project_settings): + project_setting = project_settings["houdini"]["publish"]["CollectChunkSize"] # noqa + cls.chunkSize = project_setting["chunk_size"] + + @classmethod + def get_attribute_defs(cls): + return [ + NumberDef("chunkSize", + minimum=1, + maximum=999999, + decimals=0, + default=cls.chunkSize, + label="Frame Per Task") + + ] diff --git a/openpype/hosts/houdini/plugins/publish/collect_frames.py b/openpype/hosts/houdini/plugins/publish/collect_frames.py index 01df809d4c..cb4ff1a45b 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_frames.py +++ b/openpype/hosts/houdini/plugins/publish/collect_frames.py @@ -14,7 +14,8 @@ class CollectFrames(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.01 label = "Collect Frames" families = ["vdbcache", "imagesequence", "ass", - "redshiftproxy", "review", "bgeo"] + "mantraifd", "redshiftproxy", "review", + "bgeo"] def process(self, instance): diff --git a/openpype/hosts/houdini/plugins/publish/extract_alembic.py b/openpype/hosts/houdini/plugins/publish/extract_alembic.py index bdd19b23d4..df2fdda241 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_alembic.py +++ b/openpype/hosts/houdini/plugins/publish/extract_alembic.py @@ -14,8 +14,12 @@ class ExtractAlembic(publish.Extractor): label = "Extract Alembic" hosts = ["houdini"] families = ["abc", "camera"] + targets = ["local", "remote"] def process(self, instance): + if instance.data.get("farm"): + self.log.debug("Should be processed on farm, skipping.") + return ropnode = hou.node(instance.data["instance_node"]) diff --git a/openpype/hosts/houdini/plugins/publish/extract_ass.py b/openpype/hosts/houdini/plugins/publish/extract_ass.py index 0d246625ba..cf04a3994e 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_ass.py +++ b/openpype/hosts/houdini/plugins/publish/extract_ass.py @@ -14,9 +14,12 @@ class ExtractAss(publish.Extractor): label = "Extract Ass" families = ["ass"] hosts = ["houdini"] + targets = ["local", "remote"] def process(self, instance): - + if instance.data.get("farm"): + self.log.debug("Should be processed on farm, skipping.") + return ropnode = hou.node(instance.data["instance_node"]) # Get the filename from the filename parameter diff --git a/openpype/hosts/houdini/plugins/publish/extract_bgeo.py b/openpype/hosts/houdini/plugins/publish/extract_bgeo.py index c9625ec880..fc5cf0bb65 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_bgeo.py +++ b/openpype/hosts/houdini/plugins/publish/extract_bgeo.py @@ -17,7 +17,9 @@ class ExtractBGEO(publish.Extractor): families = ["bgeo"] def process(self, instance): - + if instance.data.get("farm"): + self.log.debug("Should be processed on farm, skipping.") + return ropnode = hou.node(instance.data["instance_node"]) # Get the filename from the filename parameter diff --git a/openpype/hosts/houdini/plugins/publish/extract_mantra_ifd.py b/openpype/hosts/houdini/plugins/publish/extract_mantra_ifd.py new file mode 100644 index 0000000000..894260d1bf --- /dev/null +++ b/openpype/hosts/houdini/plugins/publish/extract_mantra_ifd.py @@ -0,0 +1,51 @@ +import os + +import pyblish.api + +from openpype.pipeline import publish + +import hou + + +class ExtractMantraIFD(publish.Extractor): + + order = pyblish.api.ExtractorOrder + label = "Extract Mantra ifd" + hosts = ["houdini"] + families = ["mantraifd"] + targets = ["local", "remote"] + + def process(self, instance): + if instance.data.get("farm"): + self.log.debug("Should be processed on farm, skipping.") + return + + ropnode = hou.node(instance.data.get("instance_node")) + output = ropnode.evalParm("soho_diskfile") + staging_dir = os.path.dirname(output) + instance.data["stagingDir"] = staging_dir + + files = instance.data["frames"] + missing_frames = [ + frame + for frame in instance.data["frames"] + if not os.path.exists( + os.path.normpath(os.path.join(staging_dir, frame))) + ] + if missing_frames: + raise RuntimeError("Failed to complete Mantra ifd extraction. " + "Missing output files: {}".format( + missing_frames)) + + if "representations" not in instance.data: + instance.data["representations"] = [] + + representation = { + 'name': 'ifd', + 'ext': 'ifd', + 'files': files, + "stagingDir": staging_dir, + "frameStart": instance.data["frameStart"], + "frameEnd": instance.data["frameEnd"], + } + instance.data["representations"].append(representation) diff --git a/openpype/hosts/houdini/plugins/publish/extract_redshift_proxy.py b/openpype/hosts/houdini/plugins/publish/extract_redshift_proxy.py index 1d99ac665c..0ca0a01c5c 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_redshift_proxy.py +++ b/openpype/hosts/houdini/plugins/publish/extract_redshift_proxy.py @@ -14,9 +14,12 @@ class ExtractRedshiftProxy(publish.Extractor): label = "Extract Redshift Proxy" families = ["redshiftproxy"] hosts = ["houdini"] + targets = ["local", "remote"] def process(self, instance): - + if instance.data.get("farm"): + self.log.debug("Should be processed on farm, skipping.") + return ropnode = hou.node(instance.data.get("instance_node")) # Get the filename from the filename parameter diff --git a/openpype/hosts/houdini/plugins/publish/extract_vdb_cache.py b/openpype/hosts/houdini/plugins/publish/extract_vdb_cache.py index 4bca758f08..7d8634b83c 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_vdb_cache.py +++ b/openpype/hosts/houdini/plugins/publish/extract_vdb_cache.py @@ -16,7 +16,9 @@ class ExtractVDBCache(publish.Extractor): hosts = ["houdini"] def process(self, instance): - + if instance.data.get("farm"): + self.log.debug("Should be processed on farm, skipping.") + return ropnode = hou.node(instance.data["instance_node"]) # Get the filename from the filename parameter diff --git a/openpype/hosts/houdini/plugins/publish/increment_current_file.py b/openpype/hosts/houdini/plugins/publish/increment_current_file.py index 3569de7693..4788cca3cf 100644 --- a/openpype/hosts/houdini/plugins/publish/increment_current_file.py +++ b/openpype/hosts/houdini/plugins/publish/increment_current_file.py @@ -22,7 +22,8 @@ class IncrementCurrentFile(pyblish.api.ContextPlugin): "arnold_rop", "mantra_rop", "karma_rop", - "usdrender"] + "usdrender", + "publish.hou"] optional = True def process(self, context): diff --git a/openpype/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py b/openpype/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py new file mode 100644 index 0000000000..b1717d09ee --- /dev/null +++ b/openpype/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py @@ -0,0 +1,183 @@ +import os +import getpass +from datetime import datetime + +import hou + +import attr +import pyblish.api +from openpype.lib import ( + TextDef, + NumberDef, +) +from openpype.pipeline import ( + legacy_io, + OpenPypePyblishPluginMixin +) +from openpype.tests.lib import is_in_tests +from openpype.lib import is_running_from_build +from openpype_modules.deadline import abstract_submit_deadline +from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo + + +@attr.s +class HoudiniPluginInfo(object): + Build = attr.ib(default=None) + IgnoreInputs = attr.ib(default=True) + ScriptJob = attr.ib(default=True) + SceneFile = attr.ib(default=None) # Input + SaveFile = attr.ib(default=True) + ScriptFilename = attr.ib(default=None) + OutputDriver = attr.ib(default=None) + Version = attr.ib(default=None) # Mandatory for Deadline + ProjectPath = attr.ib(default=None) + + +class HoudiniCacheSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, # noqa + OpenPypePyblishPluginMixin): + """Submit Houdini scene to perform a local publish in Deadline. + + Publishing in Deadline can be helpful for scenes that publish very slow. + This way it can process in the background on another machine without the + Artist having to wait for the publish to finish on their local machine. + + Submission is done through the Deadline Web Service as + supplied via the environment variable AVALON_DEADLINE. + + """ + + label = "Submit Scene to Deadline" + order = pyblish.api.IntegratorOrder + hosts = ["houdini"] + families = ["publish.hou"] + targets = ["local"] + + priority = 50 + jobInfo = {} + pluginInfo = {} + group = None + + def get_job_info(self): + job_info = DeadlineJobInfo(Plugin="Houdini") + + job_info.update(self.jobInfo) + instance = self._instance + context = instance.context + assert all( + result["success"] for result in context.data["results"] + ), "Errors found, aborting integration.." + + # Deadline connection + AVALON_DEADLINE = legacy_io.Session.get( + "AVALON_DEADLINE", "http://localhost:8082" + ) + assert AVALON_DEADLINE, "Requires AVALON_DEADLINE" + + project_name = instance.context.data["projectName"] + filepath = context.data["currentFile"] + scenename = os.path.basename(filepath) + job_name = "{scene} - {instance} [PUBLISH]".format( + scene=scenename, instance=instance.name) + batch_name = "{code} - {scene}".format(code=project_name, + scene=scenename) + if is_in_tests(): + batch_name += datetime.now().strftime("%d%m%Y%H%M%S") + + job_info.Name = job_name + job_info.BatchName = batch_name + job_info.Plugin = instance.data["plugin"] + job_info.UserName = context.data.get("deadlineUser", getpass.getuser()) + rop_node = self.get_rop_node(instance) + if rop_node.type().name() != "alembic": + frames = "{start}-{end}x{step}".format( + start=int(instance.data["frameStart"]), + end=int(instance.data["frameEnd"]), + step=int(instance.data["byFrameStep"]), + ) + + job_info.Frames = frames + + job_info.Pool = instance.data.get("primaryPool") + job_info.SecondaryPool = instance.data.get("secondaryPool") + + attr_values = self.get_attr_values_from_data(instance.data) + + job_info.ChunkSize = instance.data["chunkSize"] + job_info.Comment = context.data.get("comment") + job_info.Priority = attr_values.get("priority", self.priority) + job_info.Group = attr_values.get("group", self.group) + + keys = [ + "FTRACK_API_USER", + "FTRACK_API_KEY", + "FTRACK_SERVER" + ] + + # Add OpenPype version if we are running from build. + if is_running_from_build(): + keys.append("OPENPYPE_VERSION") + # Add mongo url if it's enabled + if self._instance.context.data.get("deadlinePassMongoUrl"): + keys.append("OPENPYPE_MONGO") + + environment = dict({key: os.environ[key] for key in keys + if key in os.environ}, **legacy_io.Session) + + for key in keys: + value = environment.get(key) + if not value: + continue + job_info.EnvironmentKeyValue[key] = value + # to recognize render jobs + job_info.add_render_job_env_var() + + return job_info + + def get_plugin_info(self): + instance = self._instance + version = hou.applicationVersionString() + version = ".".join(version.split(".")[:2]) + rop = self.get_rop_node(instance) + plugin_info = HoudiniPluginInfo( + Build=None, + IgnoreInputs=True, + ScriptJob=True, + SceneFile=self.scene_path, + SaveFile=True, + OutputDriver=rop.path(), + Version=version, + ProjectPath=os.path.dirname(self.scene_path) + ) + + plugin_payload = attr.asdict(plugin_info) + + return plugin_payload + + def process(self, instance): + super(HoudiniCacheSubmitDeadline, self).process(instance) + output_dir = os.path.dirname(instance.data["files"][0]) + instance.data["outputDir"] = output_dir + instance.data["toBeRenderedOn"] = "deadline" + + def get_rop_node(self, instance): + rop = instance.data.get("instance_node") + rop_node = hou.node(rop) + + return rop_node + + @classmethod + def get_attribute_defs(cls): + defs = super(HoudiniCacheSubmitDeadline, cls).get_attribute_defs() + defs.extend([ + NumberDef("priority", + minimum=1, + maximum=250, + decimals=0, + default=cls.priority, + label="Priority"), + TextDef("group", + default=cls.group, + label="Group Name"), + ]) + + return defs diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_cache_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_cache_job.py new file mode 100644 index 0000000000..5651ff4c83 --- /dev/null +++ b/openpype/modules/deadline/plugins/publish/submit_publish_cache_job.py @@ -0,0 +1,501 @@ +# -*- coding: utf-8 -*- +"""Submit publishing job to farm.""" +import os +import json +import re +from copy import deepcopy +import requests + +import pyblish.api + +from openpype import AYON_SERVER_ENABLED +from openpype.client import ( + get_last_version_by_subset_name, +) +from openpype.pipeline import publish, legacy_io +from openpype.lib import EnumDef, is_running_from_build +from openpype.tests.lib import is_in_tests +from openpype.pipeline.version_start import get_versioning_start + +from openpype.pipeline.farm.pyblish_functions import ( + create_skeleton_instance_cache, + create_instances_for_cache, + attach_instances_to_subset, + prepare_cache_representations, + create_metadata_path +) + + +class ProcessSubmittedCacheJobOnFarm(pyblish.api.InstancePlugin, + publish.OpenPypePyblishPluginMixin, + publish.ColormanagedPyblishPluginMixin): + """Process Cache Job submitted on farm + This is replicated version of submit publish job + specifically for cache(s). + + These jobs are dependent on a deadline job + submission prior to this plug-in. + + - In case of Deadline, it creates dependent job on farm publishing + rendered image sequence. + + Options in instance.data: + - deadlineSubmissionJob (dict, Required): The returned .json + data from the job submission to deadline. + + - outputDir (str, Required): The output directory where the metadata + file should be generated. It's assumed that this will also be + final folder containing the output files. + + - ext (str, Optional): The extension (including `.`) that is required + in the output filename to be picked up for image sequence + publishing. + + - expectedFiles (list or dict): explained below + + """ + + label = "Submit cache jobs to Deadline" + order = pyblish.api.IntegratorOrder + 0.2 + icon = "tractor" + + targets = ["local"] + + hosts = ["houdini"] + + families = ["publish.hou"] + + environ_job_filter = [ + "OPENPYPE_METADATA_FILE" + ] + + environ_keys = [ + "AVALON_APP_NAME", + "OPENPYPE_USERNAME", + "OPENPYPE_SG_USER", + ] + + # custom deadline attributes + deadline_department = "" + deadline_pool = "" + deadline_pool_secondary = "" + deadline_group = "" + deadline_chunk_size = 1 + deadline_priority = None + + # regex for finding frame number in string + R_FRAME_NUMBER = re.compile(r'.+\.(?P[0-9]+)\..+') + + plugin_pype_version = "3.0" + + # script path for publish_filesequence.py + publishing_script = None + + def _submit_deadline_post_job(self, instance, job): + """Submit publish job to Deadline. + + Deadline specific code separated from :meth:`process` for sake of + more universal code. Muster post job is sent directly by Muster + submitter, so this type of code isn't necessary for it. + + Returns: + (str): deadline_publish_job_id + """ + data = instance.data.copy() + subset = data["subset"] + job_name = "Publish - {subset}".format(subset=subset) + + anatomy = instance.context.data['anatomy'] + + # instance.data.get("subset") != instances[0]["subset"] + # 'Main' vs 'renderMain' + override_version = None + instance_version = instance.data.get("version") # take this if exists + if instance_version != 1: + override_version = instance_version + + output_dir = self._get_publish_folder( + anatomy, + deepcopy(instance.data["anatomyData"]), + instance.data.get("asset"), + instance.data["subset"], + instance.context, + instance.data["family"], + override_version + ) + + # Transfer the environment from the original job to this dependent + # job so they use the same environment + metadata_path, rootless_metadata_path = \ + create_metadata_path(instance, anatomy) + + environment = { + "AVALON_PROJECT": instance.context.data["projectName"], + "AVALON_ASSET": instance.context.data["asset"], + "AVALON_TASK": instance.context.data["task"], + "OPENPYPE_USERNAME": instance.context.data["user"], + "OPENPYPE_LOG_NO_COLORS": "1", + "IS_TEST": str(int(is_in_tests())) + } + + if AYON_SERVER_ENABLED: + environment["AYON_PUBLISH_JOB"] = "1" + environment["AYON_RENDER_JOB"] = "0" + environment["AYON_REMOTE_PUBLISH"] = "0" + environment["AYON_BUNDLE_NAME"] = os.environ["AYON_BUNDLE_NAME"] + deadline_plugin = "Ayon" + else: + environment["OPENPYPE_PUBLISH_JOB"] = "1" + environment["OPENPYPE_RENDER_JOB"] = "0" + environment["OPENPYPE_REMOTE_PUBLISH"] = "0" + deadline_plugin = "OpenPype" + # Add OpenPype version if we are running from build. + if is_running_from_build(): + self.environ_keys.append("OPENPYPE_VERSION") + + # add environments from self.environ_keys + for env_key in self.environ_keys: + if os.getenv(env_key): + environment[env_key] = os.environ[env_key] + + # pass environment keys from self.environ_job_filter + job_environ = job["Props"].get("Env", {}) + for env_j_key in self.environ_job_filter: + if job_environ.get(env_j_key): + environment[env_j_key] = job_environ[env_j_key] + + # Add mongo url if it's enabled + if instance.context.data.get("deadlinePassMongoUrl"): + mongo_url = os.environ.get("OPENPYPE_MONGO") + if mongo_url: + environment["OPENPYPE_MONGO"] = mongo_url + + priority = self.deadline_priority or instance.data.get("priority", 50) + + instance_settings = self.get_attr_values_from_data(instance.data) + initial_status = instance_settings.get("publishJobState", "Active") + # TODO: Remove this backwards compatibility of `suspend_publish` + if instance.data.get("suspend_publish"): + initial_status = "Suspended" + + args = [ + "--headless", + 'publish', + '"{}"'.format(rootless_metadata_path), + "--targets", "deadline", + "--targets", "farm" + ] + + if is_in_tests(): + args.append("--automatic-tests") + + # Generate the payload for Deadline submission + secondary_pool = ( + self.deadline_pool_secondary or instance.data.get("secondaryPool") + ) + payload = { + "JobInfo": { + "Plugin": deadline_plugin, + "BatchName": job["Props"]["Batch"], + "Name": job_name, + "UserName": job["Props"]["User"], + "Comment": instance.context.data.get("comment", ""), + + "Department": self.deadline_department, + "ChunkSize": self.deadline_chunk_size, + "Priority": priority, + "InitialStatus": initial_status, + + "Group": self.deadline_group, + "Pool": self.deadline_pool or instance.data.get("primaryPool"), + "SecondaryPool": secondary_pool, + # ensure the outputdirectory with correct slashes + "OutputDirectory0": output_dir.replace("\\", "/") + }, + "PluginInfo": { + "Version": self.plugin_pype_version, + "Arguments": " ".join(args), + "SingleFrameOnly": "True", + }, + # Mandatory for Deadline, may be empty + "AuxFiles": [], + } + + if job.get("_id"): + payload["JobInfo"]["JobDependency0"] = job["_id"] + + for index, (key_, value_) in enumerate(environment.items()): + payload["JobInfo"].update( + { + "EnvironmentKeyValue%d" + % index: "{key}={value}".format( + key=key_, value=value_ + ) + } + ) + # remove secondary pool + payload["JobInfo"].pop("SecondaryPool", None) + + self.log.debug("Submitting Deadline publish job ...") + + url = "{}/api/jobs".format(self.deadline_url) + response = requests.post(url, json=payload, timeout=10) + if not response.ok: + raise Exception(response.text) + + deadline_publish_job_id = response.json()["_id"] + + return deadline_publish_job_id + + def process(self, instance): + # type: (pyblish.api.Instance) -> None + """Process plugin. + + Detect type of render farm submission and create and post dependent + job in case of Deadline. It creates json file with metadata needed for + publishing in directory of render. + + Args: + instance (pyblish.api.Instance): Instance data. + + """ + if not instance.data.get("farm"): + self.log.debug("Skipping local instance.") + return + + anatomy = instance.context.data["anatomy"] + + instance_skeleton_data = create_skeleton_instance_cache(instance) + """ + if content of `expectedFiles` list are dictionaries, we will handle + it as list of AOVs, creating instance for every one of them. + + Example: + -------- + + expectedFiles = [ + { + "beauty": [ + "foo_v01.0001.exr", + "foo_v01.0002.exr" + ], + + "Z": [ + "boo_v01.0001.exr", + "boo_v01.0002.exr" + ] + } + ] + + This will create instances for `beauty` and `Z` subset + adding those files to their respective representations. + + If we have only list of files, we collect all file sequences. + More then one doesn't probably make sense, but we'll handle it + like creating one instance with multiple representations. + + Example: + -------- + + expectedFiles = [ + "foo_v01.0001.exr", + "foo_v01.0002.exr", + "xxx_v01.0001.exr", + "xxx_v01.0002.exr" + ] + + This will result in one instance with two representations: + `foo` and `xxx` + """ + + if isinstance(instance.data.get("expectedFiles")[0], dict): + instances = create_instances_for_cache( + instance, instance_skeleton_data) + else: + representations = prepare_cache_representations( + instance_skeleton_data, + instance.data.get("expectedFiles"), + anatomy + ) + + if "representations" not in instance_skeleton_data.keys(): + instance_skeleton_data["representations"] = [] + + # add representation + instance_skeleton_data["representations"] += representations + instances = [instance_skeleton_data] + + # attach instances to subset + if instance.data.get("attachTo"): + instances = attach_instances_to_subset( + instance.data.get("attachTo"), instances + ) + + r''' SUBMiT PUBLiSH JOB 2 D34DLiN3 + ____ + ' ' .---. .---. .--. .---. .--..--..--..--. .---. + | | --= \ | . \/ _|/ \| . \ || || \ |/ _| + | JOB | --= / | | || __| .. | | | |;_ || \ || __| + | | |____./ \.__|._||_.|___./|_____|||__|\__|\.___| + ._____. + + ''' + + render_job = None + submission_type = "" + if instance.data.get("toBeRenderedOn") == "deadline": + render_job = instance.data.pop("deadlineSubmissionJob", None) + submission_type = "deadline" + + if not render_job: + import getpass + + render_job = {} + self.log.debug("Faking job data ...") + render_job["Props"] = {} + # Render job doesn't exist because we do not have prior submission. + # We still use data from it so lets fake it. + # + # Batch name reflect original scene name + + if instance.data.get("assemblySubmissionJobs"): + render_job["Props"]["Batch"] = instance.data.get( + "jobBatchName") + else: + batch = os.path.splitext(os.path.basename( + instance.context.data.get("currentFile")))[0] + render_job["Props"]["Batch"] = batch + # User is deadline user + render_job["Props"]["User"] = instance.context.data.get( + "deadlineUser", getpass.getuser()) + + deadline_publish_job_id = None + if submission_type == "deadline": + # get default deadline webservice url from deadline module + self.deadline_url = instance.context.data["defaultDeadline"] + # if custom one is set in instance, use that + if instance.data.get("deadlineUrl"): + self.deadline_url = instance.data.get("deadlineUrl") + assert self.deadline_url, "Requires Deadline Webservice URL" + + deadline_publish_job_id = \ + self._submit_deadline_post_job(instance, render_job) + + # Inject deadline url to instances. + for inst in instances: + inst["deadlineUrl"] = self.deadline_url + + # publish job file + publish_job = { + "asset": instance_skeleton_data["asset"], + "frameStart": instance_skeleton_data["frameStart"], + "frameEnd": instance_skeleton_data["frameEnd"], + "fps": instance_skeleton_data["fps"], + "source": instance_skeleton_data["source"], + "user": instance.context.data["user"], + "version": instance.context.data["version"], # workfile version + "intent": instance.context.data.get("intent"), + "comment": instance.context.data.get("comment"), + "job": render_job or None, + "session": legacy_io.Session.copy(), + "instances": instances + } + + if deadline_publish_job_id: + publish_job["deadline_publish_job_id"] = deadline_publish_job_id + + metadata_path, rootless_metadata_path = \ + create_metadata_path(instance, anatomy) + + with open(metadata_path, "w") as f: + json.dump(publish_job, f, indent=4, sort_keys=True) + + def _get_publish_folder(self, anatomy, template_data, + asset, subset, context, + family, version=None): + """ + Extracted logic to pre-calculate real publish folder, which is + calculated in IntegrateNew inside of Deadline process. + This should match logic in: + 'collect_anatomy_instance_data' - to + get correct anatomy, family, version for subset and + 'collect_resources_path' + get publish_path + + Args: + anatomy (openpype.pipeline.anatomy.Anatomy): + template_data (dict): pre-calculated collected data for process + asset (string): asset name + subset (string): subset name (actually group name of subset) + family (string): for current deadline process it's always 'render' + TODO - for generic use family needs to be dynamically + calculated like IntegrateNew does + version (int): override version from instance if exists + + Returns: + (string): publish folder where rendered and published files will + be stored + based on 'publish' template + """ + + project_name = context.data["projectName"] + if not version: + version = get_last_version_by_subset_name( + project_name, + subset, + asset_name=asset + ) + if version: + version = int(version["name"]) + 1 + else: + version = get_versioning_start( + project_name, + template_data["app"], + task_name=template_data["task"]["name"], + task_type=template_data["task"]["type"], + family="render", + subset=subset, + project_settings=context.data["project_settings"] + ) + + host_name = context.data["hostName"] + task_info = template_data.get("task") or {} + + template_name = publish.get_publish_template_name( + project_name, + host_name, + family, + task_info.get("name"), + task_info.get("type"), + ) + + template_data["subset"] = subset + template_data["family"] = family + template_data["version"] = version + + render_templates = anatomy.templates_obj[template_name] + if "folder" in render_templates: + publish_folder = render_templates["folder"].format_strict( + template_data + ) + else: + # solve deprecated situation when `folder` key is not underneath + # `publish` anatomy + self.log.warning(( + "Deprecation warning: Anatomy does not have set `folder`" + " key underneath `publish` (in global of for project `{}`)." + ).format(project_name)) + + file_path = render_templates["path"].format_strict(template_data) + publish_folder = os.path.dirname(file_path) + + return publish_folder + + @classmethod + def get_attribute_defs(cls): + return [ + EnumDef("publishJobState", + label="Publish Job State", + items=["Active", "Suspended"], + default="Active") + ] diff --git a/openpype/modules/deadline/plugins/publish/validate_deadline_pools.py b/openpype/modules/deadline/plugins/publish/validate_deadline_pools.py index 949caff7d8..90b8241803 100644 --- a/openpype/modules/deadline/plugins/publish/validate_deadline_pools.py +++ b/openpype/modules/deadline/plugins/publish/validate_deadline_pools.py @@ -22,7 +22,8 @@ class ValidateDeadlinePools(OptionalPyblishPluginMixin, "render.frames_farm", "renderFarm", "renderlayer", - "maxrender"] + "maxrender", + "publish.hou"] optional = True # cache diff --git a/openpype/pipeline/farm/pyblish_functions.py b/openpype/pipeline/farm/pyblish_functions.py index fe3ab97de8..33ec770be0 100644 --- a/openpype/pipeline/farm/pyblish_functions.py +++ b/openpype/pipeline/farm/pyblish_functions.py @@ -744,6 +744,238 @@ def get_resources(project_name, version, extension=None): return resources +def create_skeleton_instance_cache(instance): + # type: (pyblish.api.Instance, list, dict) -> dict + """Create skeleton instance from original instance data. + + This will create dictionary containing skeleton + - common - data used for publishing rendered instances. + This skeleton instance is then extended with additional data + and serialized to be processed by farm job. + + Args: + instance (pyblish.api.Instance): Original instance to + be used as a source of data. + + Returns: + dict: Dictionary with skeleton instance data. + + """ + # list of family names to transfer to new family if present + + context = instance.context + data = instance.data.copy() + anatomy = instance.context.data["anatomy"] # type: Anatomy + + # get time related data from instance (or context) + time_data = get_time_data_from_instance_or_context(instance) + + if data.get("extendFrames", False): + time_data.start, time_data.end = extend_frames( + data["asset"], + data["subset"], + time_data.start, + time_data.end, + ) + + source = data.get("source") or context.data.get("currentFile") + success, rootless_path = ( + anatomy.find_root_template_from_path(source) + ) + if success: + source = rootless_path + else: + # `rootless_path` is not set to `source` if none of roots match + log = Logger.get_logger("farm_publishing") + log.warning(("Could not find root path for remapping \"{}\". " + "This may cause issues.").format(source)) + + family = instance.data["family"] + # Make sure "render" is in the families to go through + # validating expected and rendered files + # during publishing job. + families = ["render", family] + + instance_skeleton_data = { + "family": family, + "subset": data["subset"], + "families": families, + "asset": data["asset"], + "frameStart": time_data.start, + "frameEnd": time_data.end, + "handleStart": time_data.handle_start, + "handleEnd": time_data.handle_end, + "frameStartHandle": time_data.start - time_data.handle_start, + "frameEndHandle": time_data.end + time_data.handle_end, + "comment": data.get("comment"), + "fps": time_data.fps, + "source": source, + "extendFrames": data.get("extendFrames"), + "overrideExistingFrame": data.get("overrideExistingFrame"), + "jobBatchName": data.get("jobBatchName", ""), + # map inputVersions `ObjectId` -> `str` so json supports it + "inputVersions": list(map(str, data.get("inputVersions", []))), + } + + # skip locking version if we are creating v01 + instance_version = data.get("version") # take this if exists + if instance_version != 1: + instance_skeleton_data["version"] = instance_version + + representations = get_transferable_representations(instance) + instance_skeleton_data["representations"] = representations + + persistent = instance.data.get("stagingDir_persistent") is True + instance_skeleton_data["stagingDir_persistent"] = persistent + + return instance_skeleton_data + + +def prepare_cache_representations(skeleton_data, exp_files, anatomy): + """Create representations for file sequences. + + This will return representations of expected files if they are not + in hierarchy of aovs. There should be only one sequence of files for + most cases, but if not - we create representation from each of them. + + Arguments: + skeleton_data (dict): instance data for which we are + setting representations + exp_files (list): list of expected files + anatomy (Anatomy) + Returns: + list of representations + + """ + representations = [] + collections, remainders = clique.assemble(exp_files) + + log = Logger.get_logger("farm_publishing") + + # create representation for every collected sequence + for collection in collections: + ext = collection.tail.lstrip(".") + + staging = os.path.dirname(list(collection)[0]) + success, rootless_staging_dir = ( + anatomy.find_root_template_from_path(staging) + ) + if success: + staging = rootless_staging_dir + else: + log.warning(( + "Could not find root path for remapping \"{}\"." + " This may cause issues on farm." + ).format(staging)) + + frame_start = int(skeleton_data.get("frameStartHandle")) + rep = { + "name": ext, + "ext": ext, + "files": [os.path.basename(f) for f in list(collection)], + "frameStart": frame_start, + "frameEnd": int(skeleton_data.get("frameEndHandle")), + # If expectedFile are absolute, we need only filenames + "stagingDir": staging, + "fps": skeleton_data.get("fps") + } + + representations.append(rep) + + return representations + + +def create_instances_for_cache(instance, skeleton): + """Create instance for cache. + + This will create new instance for every AOV it can detect in expected + files list. + + Args: + instance (pyblish.api.Instance): Original instance. + skeleton (dict): Skeleton data for instance (those needed) later + by collector. + + + Returns: + list of instances + + Throws: + ValueError: + + """ + anatomy = instance.context.data["anatomy"] + subset = skeleton["subset"] + family = skeleton["family"] + exp_files = instance.data["expectedFiles"] + log = Logger.get_logger("farm_publishing") + + instances = [] + # go through AOVs in expected files + for _, files in exp_files[0].items(): + cols, rem = clique.assemble(files) + # we shouldn't have any reminders. And if we do, it should + # be just one item for single frame renders. + if not cols and rem: + if len(rem) != 1: + raise ValueError("Found multiple non related files " + "to render, don't know what to do " + "with them.") + col = rem[0] + ext = os.path.splitext(col)[1].lstrip(".") + else: + # but we really expect only one collection. + # Nothing else make sense. + if len(cols) != 1: + raise ValueError("Only one image sequence type is expected.") # noqa: E501 + ext = cols[0].tail.lstrip(".") + col = list(cols[0]) + + if isinstance(col, (list, tuple)): + staging = os.path.dirname(col[0]) + else: + staging = os.path.dirname(col) + + try: + staging = remap_source(staging, anatomy) + except ValueError as e: + log.warning(e) + + new_instance = deepcopy(skeleton) + + new_instance["subset"] = subset + log.info("Creating data for: {}".format(subset)) + new_instance["family"] = family + new_instance["families"] = skeleton["families"] + # create representation + if isinstance(col, (list, tuple)): + files = [os.path.basename(f) for f in col] + else: + files = os.path.basename(col) + + rep = { + "name": ext, + "ext": ext, + "files": files, + "frameStart": int(skeleton["frameStartHandle"]), + "frameEnd": int(skeleton["frameEndHandle"]), + # If expectedFile are absolute, we need only filenames + "stagingDir": staging, + "fps": new_instance.get("fps"), + "tags": [], + } + + new_instance["representations"] = [rep] + + # if extending frames from existing version, copy files from there + # into our destination directory + if new_instance.get("extendFrames", False): + copy_extend_frames(new_instance, rep) + instances.append(new_instance) + log.debug("instances:{}".format(instances)) + return instances + + def copy_extend_frames(instance, representation): """Copy existing frames from latest version. diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json index 1b8c8397d7..3becb4e32d 100644 --- a/openpype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -99,6 +99,14 @@ "deadline_chunk_size": 10, "deadline_job_delay": "00:00:00:00" }, + "ProcessSubmittedCacheJobOnFarm": { + "enabled": true, + "deadline_department": "", + "deadline_pool": "", + "deadline_group": "", + "deadline_chunk_size": 1, + "deadline_priority": 50 + }, "ProcessSubmittedJobOnFarm": { "enabled": true, "deadline_department": "", diff --git a/openpype/settings/defaults/project_settings/houdini.json b/openpype/settings/defaults/project_settings/houdini.json index 93d5c50d5e..c0052185e8 100644 --- a/openpype/settings/defaults/project_settings/houdini.json +++ b/openpype/settings/defaults/project_settings/houdini.json @@ -81,6 +81,11 @@ } }, "publish": { + "CollectChunkSize": { + "enabled": true, + "optional": true, + "chunk_size": 999999 + }, "ValidateWorkfilePaths": { "enabled": true, "optional": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_publish.json index b57089007e..e2f1be8e3e 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_publish.json @@ -4,6 +4,31 @@ "key": "publish", "label": "Publish plugins", "children": [ + { + "type": "dict", + "collapsible": true, + "checkbox_key": "enabled", + "key": "CollectChunkSize", + "label": "Collect Chunk Size", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, + { + "type": "number", + "key": "chunk_size", + "label": "Frames Per Task" + } + ] + }, { "type": "dict", "collapsible": true, diff --git a/server_addon/deadline/server/settings/publish_plugins.py b/server_addon/deadline/server/settings/publish_plugins.py index 8d1b667345..2a60ab4aa8 100644 --- a/server_addon/deadline/server/settings/publish_plugins.py +++ b/server_addon/deadline/server/settings/publish_plugins.py @@ -217,6 +217,17 @@ class AOVFilterSubmodel(BaseSettingsModel): ) +class ProcessCacheJobFarmModel(BaseSettingsModel): + """Process submitted job on farm.""" + + enabled: bool = Field(title="Enabled") + deadline_department: str = Field(title="Department") + deadline_pool: str = Field(title="Pool") + deadline_group: str = Field(title="Group") + deadline_chunk_size: int = Field(title="Chunk Size") + deadline_priority: int = Field(title="Priority") + + class ProcessSubmittedJobOnFarmModel(BaseSettingsModel): """Process submitted job on farm.""" @@ -278,6 +289,9 @@ class PublishPluginsModel(BaseSettingsModel): default_factory=CelactionSubmitDeadlineModel, title="Celaction Submit Deadline" ) + ProcessSubmittedCacheJobOnFarm: ProcessCacheJobFarmModel = Field( + default_factory=ProcessCacheJobFarmModel, + title="Process submitted cache Job on farm.") ProcessSubmittedJobOnFarm: ProcessSubmittedJobOnFarmModel = Field( default_factory=ProcessSubmittedJobOnFarmModel, title="Process submitted job on farm.") @@ -384,6 +398,14 @@ DEFAULT_DEADLINE_PLUGINS_SETTINGS = { "deadline_chunk_size": 10, "deadline_job_delay": "00:00:00:00" }, + "ProcessSubmittedCacheJobOnFarm": { + "enabled": True, + "deadline_department": "", + "deadline_pool": "", + "deadline_group": "", + "deadline_chunk_size": 1, + "deadline_priority": 50 + }, "ProcessSubmittedJobOnFarm": { "enabled": True, "deadline_department": "", diff --git a/server_addon/deadline/server/version.py b/server_addon/deadline/server/version.py index 485f44ac21..b3f4756216 100644 --- a/server_addon/deadline/server/version.py +++ b/server_addon/deadline/server/version.py @@ -1 +1 @@ -__version__ = "0.1.1" +__version__ = "0.1.2" diff --git a/server_addon/houdini/server/settings/publish_plugins.py b/server_addon/houdini/server/settings/publish_plugins.py index 4534d8d0d9..e3092c103e 100644 --- a/server_addon/houdini/server/settings/publish_plugins.py +++ b/server_addon/houdini/server/settings/publish_plugins.py @@ -107,6 +107,12 @@ DEFAULT_HOUDINI_CREATE_SETTINGS = { # Publish Plugins +class CollectChunkSizeModel(BaseSettingsModel): + enabled: bool = Field(title="Enabled") + optional: bool = Field(title="Optional") + chunk_size: int = Field(title="Frame Per Task") + + class ValidateWorkfilePathsModel(BaseSettingsModel): enabled: bool = Field(title="Enabled") optional: bool = Field(title="Optional") @@ -127,6 +133,10 @@ class BasicValidateModel(BaseSettingsModel): class PublishPluginsModel(BaseSettingsModel): + CollectChunkSize: CollectChunkSizeModel = Field( + default_factory=CollectChunkSizeModel, + title="Collect Chunk Size" + ) ValidateWorkfilePaths: ValidateWorkfilePathsModel = Field( default_factory=ValidateWorkfilePathsModel, title="Validate workfile paths settings.") @@ -139,6 +149,11 @@ class PublishPluginsModel(BaseSettingsModel): DEFAULT_HOUDINI_PUBLISH_SETTINGS = { + "CollectChunkSize": { + "enabled": True, + "optional": True, + "chunk_size": 999999 + }, "ValidateWorkfilePaths": { "enabled": True, "optional": True, diff --git a/website/docs/artist_hosts_houdini.md b/website/docs/artist_hosts_houdini.md index 940d5ac351..c4e8293c36 100644 --- a/website/docs/artist_hosts_houdini.md +++ b/website/docs/artist_hosts_houdini.md @@ -83,6 +83,30 @@ select your render camera. All the render outputs are stored in the pyblish/render directory within your project path.\ For Karma-specific render, it also outputs the USD render as default. +## Publishing cache to Deadline +Artist can publish cache to deadline which increases productivity as artist can use local machine +could be used for other tasks. +Caching on the farm is supported for: + +**Arnold ASS (.ass)** +**Pointcache (.bgeo and .abc)** +**VDB (.vdb)** +**Redshift Proxy (.rs)** + +To submit your cache to deadline, you need to create the instance(s) with clicking +**Submitting to Farm** and you can also enable **Use selection** to +select the object for caching in farm. +![Houdini Farm Cache Creator](assets/houdini_farm_cache_creator.png) + +When you go to Publish Tab and click the instance(s), you can set up your preferred +**Frame per task**. +![Houdini Farm Per Task](assets/houdini_frame_per_task.png) + +Once you hit **Publish**, the cache would be submitted and rendered in deadline. +When the render is finished, all the caches would be located in your publish folder. +You can see them in the Loader. +![Houdini Farm Per Task](assets/houdini_farm_cache_loader.png) + ## USD (experimental support) ### Publishing USD You can publish your Solaris Stage as USD file. diff --git a/website/docs/assets/houdini_farm_cache_creator.png b/website/docs/assets/houdini_farm_cache_creator.png new file mode 100644 index 0000000000000000000000000000000000000000..bf0af8033614d37d8e988bf1b060ce17bc07b085 GIT binary patch literal 95572 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 Z5bNbauk6me<0ZB`p9u~>B|3TG`d_-PdpH09 literal 0 HcmV?d00001 diff --git a/website/docs/assets/houdini_farm_cache_loader.png b/website/docs/assets/houdini_farm_cache_loader.png new file mode 100644 index 0000000000000000000000000000000000000000..27b0122fab3e2f1b0509148fd1c7c00246c63264 GIT binary patch literal 134816 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

tWIr~7{r5kDaf^&o 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 literal 0 HcmV?d00001 diff --git a/website/docs/assets/houdini_frame_per_task.png b/website/docs/assets/houdini_frame_per_task.png new file mode 100644 index 0000000000000000000000000000000000000000..a7d8b33114e640499c892657fcd9ec9f3a79ef3c GIT binary patch literal 59257 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 Kjy4>*aO-bXs4lty literal 0 HcmV?d00001 From 82b75d53b319c2b7ddbc3da7b3773154a255a88a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 21 Sep 2023 15:03:58 +0800 Subject: [PATCH 017/327] update the latest lib --- openpype/hosts/houdini/api/lib.py | 48 ++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index 856e7abbd3..1ab38ae2c1 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -141,7 +141,6 @@ def get_output_parameter(node): elif node_type == "ifd": if node.evalParm("soho_outputmode"): return node.parm("soho_diskfile") - raise TypeError("Node type '%s' not supported" % node_type) @@ -652,3 +651,50 @@ def get_color_management_preferences(): "display": hou.Color.ocio_defaultDisplay(), "view": hou.Color.ocio_defaultView() } + + +def get_resolution_from_doc(doc): + """Get resolution from the given asset document. """ + + if not doc or "data" not in doc: + print("Entered document is not valid. \"{}\"".format(str(doc))) + return None + + resolution_width = doc["data"].get("resolutionWidth") + resolution_height = doc["data"].get("resolutionHeight") + + # Make sure both width and height are set + if resolution_width is None or resolution_height is None: + print("No resolution information found for \"{}\"".format(doc["name"])) + return None + + return int(resolution_width), int(resolution_height) + + +def set_camera_resolution(camera, asset_doc=None): + """Apply resolution to camera from asset document of the publish""" + + if not asset_doc: + asset_doc = get_current_project_asset() + + resolution = get_resolution_from_doc(asset_doc) + + if resolution: + print("Setting camera resolution: {} -> {}x{}".format( + camera.name(), resolution[0], resolution[1] + )) + camera.parm("resx").set(resolution[0]) + camera.parm("resy").set(resolution[1]) + + +def get_camera_from_container(container): + """Get camera from container node. """ + + cameras = container.recursiveGlob( + "*", + filter=hou.nodeTypeFilter.ObjCamera, + include_subnets=False + ) + + assert len(cameras) == 1, "Camera instance must have only one camera" + return cameras[0] From 6ab3f2d0a9fc3599461b7ea3ad052956b2bcad7a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 21 Sep 2023 15:05:44 +0800 Subject: [PATCH 018/327] update to exlcude unncessary codes --- .../hosts/houdini/plugins/load/load_camera.py | 81 +++++++++++-------- 1 file changed, 47 insertions(+), 34 deletions(-) diff --git a/openpype/hosts/houdini/plugins/load/load_camera.py b/openpype/hosts/houdini/plugins/load/load_camera.py index 7b4a04809e..e16146a267 100644 --- a/openpype/hosts/houdini/plugins/load/load_camera.py +++ b/openpype/hosts/houdini/plugins/load/load_camera.py @@ -4,6 +4,13 @@ from openpype.pipeline import ( ) from openpype.hosts.houdini.api import pipeline +from openpype.hosts.houdini.api.lib import ( + set_camera_resolution, + get_camera_from_container +) + +import hou + ARCHIVE_EXPRESSION = ('__import__("_alembic_hom_extensions")' '.alembicGetCameraDict') @@ -25,7 +32,15 @@ def transfer_non_default_values(src, dest, ignore=None): channel expression and ignore certain Parm types. """ - import hou + + ignore_types = { + hou.parmTemplateType.Toggle, + hou.parmTemplateType.Menu, + hou.parmTemplateType.Button, + hou.parmTemplateType.FolderSet, + hou.parmTemplateType.Separator, + hou.parmTemplateType.Label, + } src.updateParmStates() @@ -62,14 +77,6 @@ def transfer_non_default_values(src, dest, ignore=None): continue # Ignore folders, separators, etc. - ignore_types = { - hou.parmTemplateType.Toggle, - hou.parmTemplateType.Menu, - hou.parmTemplateType.Button, - hou.parmTemplateType.FolderSet, - hou.parmTemplateType.Separator, - hou.parmTemplateType.Label, - } if parm.parmTemplate().type() in ignore_types: continue @@ -90,13 +97,8 @@ class CameraLoader(load.LoaderPlugin): def load(self, context, name=None, namespace=None, data=None): - import os - import hou - # Format file name, Houdini only wants forward slashes - file_path = self.filepath_from_context(context) - file_path = os.path.normpath(file_path) - file_path = file_path.replace("\\", "/") + file_path = self.filepath_from_context(context).replace("\\", "/") # Get the root node obj = hou.node("/obj") @@ -106,19 +108,21 @@ class CameraLoader(load.LoaderPlugin): node_name = "{}_{}".format(namespace, name) if namespace else name # Create a archive node - container = self.create_and_connect(obj, "alembicarchive", node_name) + node = self.create_and_connect(obj, "alembicarchive", node_name) # TODO: add FPS of project / asset - container.setParms({"fileName": file_path, - "channelRef": True}) + node.setParms({"fileName": file_path, "channelRef": True}) # Apply some magic - container.parm("buildHierarchy").pressButton() - container.moveToGoodPosition() + node.parm("buildHierarchy").pressButton() + node.moveToGoodPosition() # Create an alembic xform node - nodes = [container] + nodes = [node] + camera = get_camera_from_container(node) + self._match_maya_render_mask(camera) + set_camera_resolution(camera, asset_doc=context["asset"]) self[:] = nodes return pipeline.containerise(node_name, @@ -143,14 +147,14 @@ class CameraLoader(load.LoaderPlugin): # Store the cam temporarily next to the Alembic Archive # so that we can preserve parm values the user set on it # after build hierarchy was triggered. - old_camera = self._get_camera(node) + old_camera = get_camera_from_container(node) temp_camera = old_camera.copyTo(node.parent()) # Rebuild node.parm("buildHierarchy").pressButton() # Apply values to the new camera - new_camera = self._get_camera(node) + new_camera = get_camera_from_container(node) transfer_non_default_values(temp_camera, new_camera, # The hidden uniform scale attribute @@ -158,6 +162,9 @@ class CameraLoader(load.LoaderPlugin): # "icon_scale" just skip that completely ignore={"scale"}) + self._match_maya_render_mask(new_camera) + set_camera_resolution(new_camera) + temp_camera.destroy() def remove(self, container): @@ -165,15 +172,6 @@ class CameraLoader(load.LoaderPlugin): node = container["node"] node.destroy() - def _get_camera(self, node): - import hou - cameras = node.recursiveGlob("*", - filter=hou.nodeTypeFilter.ObjCamera, - include_subnets=False) - - assert len(cameras) == 1, "Camera instance must have only one camera" - return cameras[0] - def create_and_connect(self, node, node_type, name=None): """Create a node within a node which and connect it to the input @@ -194,5 +192,20 @@ class CameraLoader(load.LoaderPlugin): new_node.moveToGoodPosition() return new_node - def switch(self, container, representation): - self.update(container, representation) + def _match_maya_render_mask(self, camera): + """Workaround to match Maya render mask in Houdini""" + + # print("Setting match maya render mask ") + parm = camera.parm("aperture") + expression = parm.expression() + expression = expression.replace("return ", "aperture = ") + expression += """ +# Match maya render mask (logic from Houdini's own FBX importer) +node = hou.pwd() +resx = node.evalParm('resx') +resy = node.evalParm('resy') +aspect = node.evalParm('aspect') +aperture *= min(1, (resx / resy * aspect) / 1.5) +return aperture +""" + parm.setExpression(expression, language=hou.exprLanguage.Python) From 7a765bd52836f50f90088d7738990130da957bcb Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 21 Sep 2023 20:09:59 +0800 Subject: [PATCH 019/327] mustafa's comment on deadline plugin and fix the bug of not getting right env variable --- .../publish/submit_houdini_cache_deadline.py | 2 - .../publish/submit_publish_cache_job.py | 2 +- .../schema_project_deadline.json | 40 +++++++++++++++++++ 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py b/openpype/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py index b1717d09ee..985f29a814 100644 --- a/openpype/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py @@ -128,8 +128,6 @@ class HoudiniCacheSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline if not value: continue job_info.EnvironmentKeyValue[key] = value - # to recognize render jobs - job_info.add_render_job_env_var() return job_info diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_cache_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_cache_job.py index 5651ff4c83..f8e4613b1c 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_cache_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_cache_job.py @@ -239,7 +239,7 @@ class ProcessSubmittedCacheJobOnFarm(pyblish.api.InstancePlugin, self.log.debug("Submitting Deadline publish job ...") url = "{}/api/jobs".format(self.deadline_url) - response = requests.post(url, json=payload, timeout=10) + response = requests.post(url, json=payload, timeout=10, verify=False) if not response.ok: raise Exception(response.text) diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json index 6d59b5a92b..4e582209bb 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json @@ -531,6 +531,46 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "key": "ProcessSubmittedCacheJobOnFarm", + "label": "ProcessSubmittedCacheJobOnFarm", + "checkbox_key": "enabled", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "text", + "key": "deadline_department", + "label": "Deadline department" + }, + { + "type": "text", + "key": "deadline_pool", + "label": "Deadline Pool" + }, + { + "type": "text", + "key": "deadline_group", + "label": "Deadline Group" + }, + { + "type": "number", + "key": "deadline_chunk_size", + "label": "Deadline Chunk Size" + }, + { + "type": "number", + "key": "deadline_priority", + "label": "Deadline Priotity" + } + ] + }, { "type": "dict", "collapsible": true, From 98cc7357d30bc1966c8c48d7604dcac3d7ee0c70 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 21 Sep 2023 22:44:38 +0800 Subject: [PATCH 020/327] make sure all the necessary environment variables needed to be there --- .../plugins/publish/submit_houdini_cache_deadline.py | 12 +++++++++--- .../plugins/publish/submit_publish_cache_job.py | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py b/openpype/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py index 985f29a814..f3b4d37a0f 100644 --- a/openpype/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py @@ -108,9 +108,13 @@ class HoudiniCacheSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline job_info.Group = attr_values.get("group", self.group) keys = [ - "FTRACK_API_USER", - "FTRACK_API_KEY", - "FTRACK_SERVER" + "OPENPYPE_SG_USER", + "AVALON_PROJECT", + "AVALON_ASSET", + "AVALON_TASK", + "AVALON_APP_NAME", + "OPENPYPE_DEV", + "OPENPYPE_LOG_NO_COLORS", ] # Add OpenPype version if we are running from build. @@ -128,6 +132,8 @@ class HoudiniCacheSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline if not value: continue job_info.EnvironmentKeyValue[key] = value + # to recognize render jobs + job_info.add_render_job_env_var() return job_info diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_cache_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_cache_job.py index f8e4613b1c..5651ff4c83 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_cache_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_cache_job.py @@ -239,7 +239,7 @@ class ProcessSubmittedCacheJobOnFarm(pyblish.api.InstancePlugin, self.log.debug("Submitting Deadline publish job ...") url = "{}/api/jobs".format(self.deadline_url) - response = requests.post(url, json=payload, timeout=10, verify=False) + response = requests.post(url, json=payload, timeout=10) if not response.ok: raise Exception(response.text) From 559e49abccd8bbed9920a069b945ac1d8004c95a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 21 Sep 2023 23:41:54 +0800 Subject: [PATCH 021/327] add ftrack and kitsu environments --- .../plugins/publish/submit_houdini_cache_deadline.py | 3 +++ .../deadline/plugins/publish/submit_publish_cache_job.py | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/openpype/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py b/openpype/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py index f3b4d37a0f..2b6231e916 100644 --- a/openpype/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py @@ -108,6 +108,9 @@ class HoudiniCacheSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline job_info.Group = attr_values.get("group", self.group) keys = [ + "FTRACK_API_KEY", + "FTRACK_API_USER", + "FTRACK_SERVER", "OPENPYPE_SG_USER", "AVALON_PROJECT", "AVALON_ASSET", diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_cache_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_cache_job.py index 5651ff4c83..b2ebebc303 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_cache_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_cache_job.py @@ -70,9 +70,14 @@ class ProcessSubmittedCacheJobOnFarm(pyblish.api.InstancePlugin, ] environ_keys = [ + "FTRACK_API_USER", + "FTRACK_API_KEY", + "FTRACK_SERVER", "AVALON_APP_NAME", "OPENPYPE_USERNAME", "OPENPYPE_SG_USER", + "KITSU_LOGIN", + "KITSU_PWD" ] # custom deadline attributes From 8267736bed9968a314658b45de2cbc904c0f2547 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 2 Oct 2023 16:24:23 +0200 Subject: [PATCH 022/327] Fix Fusion 18.6+ support: Avoid issues with Fusion's `BlackmagicFusion.PyRemoteObject` instances being unhashable. ```python Traceback (most recent call last): File "E:\openpype\OpenPype\.venv\lib\site-packages\pyblish\plugin.py", line 527, in __explicit_process runner(*args) File "E:\openpype\OpenPype\openpype\hosts\fusion\plugins\publish\extract_render_local.py", line 61, in process result = self.render(instance) File "E:\openpype\OpenPype\openpype\hosts\fusion\plugins\publish\extract_render_local.py", line 118, in render with enabled_savers(current_comp, savers_to_render): File "C:\Users\User\AppData\Local\Programs\Python\Python39\lib\contextlib.py", line 119, in __enter__ return next(self.gen) File "E:\openpype\OpenPype\openpype\hosts\fusion\plugins\publish\extract_render_local.py", line 33, in enabled_savers original_states[saver] = original_state TypeError: unhashable type: 'BlackmagicFusion.PyRemoteObject' ``` --- .../hosts/fusion/plugins/publish/extract_render_local.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/fusion/plugins/publish/extract_render_local.py b/openpype/hosts/fusion/plugins/publish/extract_render_local.py index 25c101cf00..e7bf010a9d 100644 --- a/openpype/hosts/fusion/plugins/publish/extract_render_local.py +++ b/openpype/hosts/fusion/plugins/publish/extract_render_local.py @@ -25,12 +25,13 @@ def enabled_savers(comp, savers): """ passthrough_key = "TOOLB_PassThrough" original_states = {} + savers_by_name = {saver.Name: saver for saver in savers} enabled_save_names = {saver.Name for saver in savers} try: all_savers = comp.GetToolList(False, "Saver").values() for saver in all_savers: original_state = saver.GetAttrs()[passthrough_key] - original_states[saver] = original_state + original_states[saver.Name] = original_state # The passthrough state we want to set (passthrough != enabled) state = saver.Name not in enabled_save_names @@ -38,7 +39,8 @@ def enabled_savers(comp, savers): saver.SetAttrs({passthrough_key: state}) yield finally: - for saver, original_state in original_states.items(): + for saver_name, original_state in original_states.items(): + saver = savers_by_name.get(saver_name) saver.SetAttrs({"TOOLB_PassThrough": original_state}) From 74070e0f25d9b53ec16512057e8e08ad228040ae Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 2 Oct 2023 16:28:46 +0200 Subject: [PATCH 023/327] Be more explicit that key should exist --- openpype/hosts/fusion/plugins/publish/extract_render_local.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/fusion/plugins/publish/extract_render_local.py b/openpype/hosts/fusion/plugins/publish/extract_render_local.py index e7bf010a9d..7cc03410c6 100644 --- a/openpype/hosts/fusion/plugins/publish/extract_render_local.py +++ b/openpype/hosts/fusion/plugins/publish/extract_render_local.py @@ -40,7 +40,7 @@ def enabled_savers(comp, savers): yield finally: for saver_name, original_state in original_states.items(): - saver = savers_by_name.get(saver_name) + saver = savers_by_name[saver_name] saver.SetAttrs({"TOOLB_PassThrough": original_state}) From 33ebd706a9232a3d8b0c34f79eb46474fde2f640 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 2 Oct 2023 16:31:25 +0200 Subject: [PATCH 024/327] Correctly produce the `savers_by_name` dict to include all savers --- .../fusion/plugins/publish/extract_render_local.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/fusion/plugins/publish/extract_render_local.py b/openpype/hosts/fusion/plugins/publish/extract_render_local.py index 7cc03410c6..08d608139d 100644 --- a/openpype/hosts/fusion/plugins/publish/extract_render_local.py +++ b/openpype/hosts/fusion/plugins/publish/extract_render_local.py @@ -25,16 +25,18 @@ def enabled_savers(comp, savers): """ passthrough_key = "TOOLB_PassThrough" original_states = {} - savers_by_name = {saver.Name: saver for saver in savers} - enabled_save_names = {saver.Name for saver in savers} + enabled_saver_names = {saver.Name for saver in savers} + + all_savers = comp.GetToolList(False, "Saver").values() + savers_by_name = {saver.Name: saver for saver in all_savers} + try: - all_savers = comp.GetToolList(False, "Saver").values() for saver in all_savers: original_state = saver.GetAttrs()[passthrough_key] original_states[saver.Name] = original_state # The passthrough state we want to set (passthrough != enabled) - state = saver.Name not in enabled_save_names + state = saver.Name not in enabled_saver_names if state != original_state: saver.SetAttrs({passthrough_key: state}) yield From 1f1676f5bb98aea465371641693b63ff2326abbd Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 13 Oct 2023 14:36:36 +0200 Subject: [PATCH 025/327] Refactor to use variable `node_value` instead of `value` - `value` only exists as the last variable value in the `for value in values` loop and might not be declared if `values` is an empty iterable. --- openpype/hosts/nuke/plugins/publish/validate_write_nodes.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py b/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py index 9aae53e59d..b882e240e6 100644 --- a/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py +++ b/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py @@ -111,7 +111,6 @@ class ValidateNukeWriteNode( for value in values: if type(node_value) in (int, float): try: - if isinstance(value, list): value = color_gui_to_int(value) else: @@ -130,7 +129,7 @@ class ValidateNukeWriteNode( and key != "file" and key != "tile_color" ): - check.append([key, value, write_node[key].value()]) + check.append([key, node_value, write_node[key].value()]) if check: self._make_error(check) From 41136557250175dcb829e62b12420b9316f7e2ae Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 17 Oct 2023 17:19:58 +0200 Subject: [PATCH 026/327] updating pr changes to latest changes --- openpype/lib/transcoding.py | 17 +++- openpype/plugins/publish/extract_thumbnail.py | 77 ++++++++++--------- 2 files changed, 54 insertions(+), 40 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 1b75b96525..7cd3671dd1 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1149,9 +1149,9 @@ def convert_colorspace( target_colorspace=None, view=None, display=None, + additional_input_args=None, additional_command_args=None, logger=None, - input_args=None ): """Convert source file from one color space to another. @@ -1170,6 +1170,8 @@ def convert_colorspace( view (str): name for viewer space (ocio valid) both 'view' and 'display' must be filled (if 'target_colorspace') display (str): name for display-referred reference space (ocio valid) + both 'view' and 'display' must be filled (if 'target_colorspace') + additional_input_args (list): input arguments for oiiotool additional_command_args (list): arguments for oiiotool (like binary depth for .dpx) logger (logging.Logger): Logger used for logging. @@ -1179,12 +1181,21 @@ def convert_colorspace( if logger is None: logger = logging.getLogger(__name__) - oiio_cmd = get_oiio_tool_args( - "oiiotool", + # prepare main oiio command args + args = [ input_path, # Don't add any additional attributes "--nosoftwareattrib", "--colorconfig", config_path + ] + + # prepand any additional args if available + if additional_input_args: + args = additional_input_args + args + + oiio_cmd = get_oiio_tool_args( + "oiiotool", + *args ) if all([target_colorspace, view, display]): diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index ff416ecb23..5b75a374ba 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -5,7 +5,6 @@ import tempfile import pyblish.api from openpype.lib import ( get_ffmpeg_tool_args, - get_oiio_tool_args, is_oiio_supported, run_subprocess, @@ -26,7 +25,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): hosts = ["shell", "fusion", "resolve", "traypublisher", "substancepainter"] enabled = False - # presetable attribute + # presentable attribute ffmpeg_args = None def process(self, instance): @@ -95,27 +94,26 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): filename = os.path.splitext(input_file)[0] jpeg_file = filename + "_thumb.jpg" full_output_path = os.path.join(dst_staging, jpeg_file) + colorspace_data = repre.get("colorspaceData") - if oiio_supported: - self.log.debug("Trying to convert with OIIO") + # only use OIIO if it is supported and representation has + # colorspace data + if oiio_supported and colorspace_data: + self.log.debug( + "Trying to convert with OIIO " + "with colorspace data: {}".format(colorspace_data) + ) # If the input can read by OIIO then use OIIO method for # conversion otherwise use ffmpeg - colorspace_data = repre["colorspaceData"] - source_colorspace = colorspace_data["colorspace"] - config_path = colorspace_data.get("config", {}).get("path") - display = colorspace_data["display"] - view = colorspace_data["view"] thumbnail_created = self.create_thumbnail_oiio( full_input_path, full_output_path, - config_path, - source_colorspace, - display, - view + colorspace_data ) # Try to use FFMPEG if OIIO is not supported or for cases when - # oiiotool isn't available + # oiiotool isn't available or representation is not having + # colorspace data if not thumbnail_created: if oiio_supported: self.log.debug( @@ -149,7 +147,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): break if not thumbnail_created: - self.log.warning("Thumbanil has not been created.") + self.log.warning("Thumbnail has not been created.") def _is_review_instance(self, instance): # TODO: We should probably handle "not creating" of thumbnail @@ -188,21 +186,36 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): self, src_path, dst_path, - config_path, - source_colorspace, - display, - view + colorspace_data, ): + """Create thumbnail using OIIO tool oiiotool + + Args: + src_path (str): path to source file + dst_path (str): path to destination file + colorspace_data (dict): colorspace data from representation + keys: + colorspace (str) + config (dict) + display (Optional[str]) + view (Optional[str]) + + Returns: + str: path to created thumbnail + """ self.log.info("Extracting thumbnail {}".format(dst_path)) - oiio_cmd = get_oiio_tool_args( - "oiiotool", - "-a", src_path, - "-o", dst_path - ) - self.log.debug("running: {}".format(" ".join(oiio_cmd))) + try: - run_subprocess(oiio_cmd, logger=self.log) - return True + convert_colorspace( + src_path, + dst_path, + colorspace_data["config"]["path"], + colorspace_data["colorspace"], + display=colorspace_data.get("display"), + view=colorspace_data.get("view"), + additional_input_args=["-i:ch=R,G,B"], + logger=self.log, + ) except Exception: self.log.warning( "Failed to create thumbnail using oiiotool", @@ -210,16 +223,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): ) return False - convert_colorspace( - src_path, - dst_path, - config_path, - source_colorspace, - view=view, - display=display, - input_args=["-i:ch=R,G,B"] - ) - return dst_path def create_thumbnail_ffmpeg(self, src_path, dst_path): From fc900780cd97fc15e4c91d24340dba02dd015aed Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 17 Oct 2023 17:33:38 +0200 Subject: [PATCH 027/327] typo --- openpype/lib/transcoding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 7cd3671dd1..07acc309d2 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1189,7 +1189,7 @@ def convert_colorspace( "--colorconfig", config_path ] - # prepand any additional args if available + # prepend any additional args if available if additional_input_args: args = additional_input_args + args From a526260b461a85e951a1fc87697a56df70afa7d3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 24 Oct 2023 17:57:09 +0200 Subject: [PATCH 028/327] 'get_assets' function can find folders by path and by name --- openpype/client/server/entities.py | 57 ++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 7 deletions(-) diff --git a/openpype/client/server/entities.py b/openpype/client/server/entities.py index 16223d3d91..c1e27eabb9 100644 --- a/openpype/client/server/entities.py +++ b/openpype/client/server/entities.py @@ -183,6 +183,20 @@ def get_asset_by_name(project_name, asset_name, fields=None): return None + +def _folders_query(project_name, con, fields, **kwargs): + if fields is None or "tasks" in fields: + folders = get_folders_with_tasks( + con, project_name, fields=fields, **kwargs + ) + + else: + folders = con.get_folders(project_name, fields=fields, **kwargs) + + for folder in folders: + yield folder + + def get_assets( project_name, asset_ids=None, @@ -199,22 +213,51 @@ def get_assets( active = None con = get_server_api_connection() + fields = folder_fields_v3_to_v4(fields, con) kwargs = dict( folder_ids=asset_ids, - folder_names=asset_names, parent_ids=parent_ids, active=active, - fields=fields ) + if not asset_names: + for folder in _folders_query(project_name, con, fields, **kwargs): + yield convert_v4_folder_to_v3(folder, project_name) + return - if fields is None or "tasks" in fields: - folders = get_folders_with_tasks(con, project_name, **kwargs) + new_asset_names = set() + folder_paths = set() + if asset_names: + for name in asset_names: + if "/" in name: + folder_paths.add(name) + else: + new_asset_names.add(name) - else: - folders = con.get_folders(project_name, **kwargs) + if folder_paths: + for folder in _folders_query( + project_name, con, fields, folder_paths=folder_paths, **kwargs + ): + yield convert_v4_folder_to_v3(folder, project_name) - for folder in folders: + if not new_asset_names: + return + + folders_by_name = collections.defaultdict(list) + for folder in _folders_query( + project_name, con, fields, folder_names=new_asset_names, **kwargs + ): + folders_by_name[folder["name"]].append(folder) + + for name, folders in folders_by_name.items(): + folder = next( + ( + folder + for folder in folders + if folder["path"] == name + ), + folders[0] + ) yield convert_v4_folder_to_v3(folder, project_name) From 331c4833ade7acf5ab0db7952e9821b817354d2d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 24 Oct 2023 17:58:34 +0200 Subject: [PATCH 029/327] added helper function 'get_asset_name_identifier' to receive folder path in AYON mode and asset name in openpype --- openpype/client/__init__.py | 4 ++++ openpype/client/entities.py | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py index 7831afd8ad..a313d6b3cc 100644 --- a/openpype/client/__init__.py +++ b/openpype/client/__init__.py @@ -43,6 +43,8 @@ from .entities import ( get_thumbnail_id_from_source, get_workfile_info, + + get_asset_name_identifier, ) from .entity_links import ( @@ -105,4 +107,6 @@ __all__ = ( "get_linked_representation_id", "create_project", + + "get_asset_name_identifier", ) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 5d9654c611..d085f90028 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -4,3 +4,22 @@ if not AYON_SERVER_ENABLED: from .mongo.entities import * else: from .server.entities import * + + +def get_asset_name_identifier(asset_doc): + """Get asset name identifier by asset document. + + This function is added because of AYON implementation where name + identifier is not just a name but full path. + + Asset document must have "name" key, and "data.parents" when in AYON mode. + + Args: + asset_doc (dict[str, Any]): Asset document. + """ + + if not AYON_SERVER_ENABLED: + return asset_doc["name"] + parents = list(asset_doc["data"]["parents"]) + parents.append(asset_doc["name"]) + return "/".join(parents) From 4c6ec4b9bcb245892421b14bee26d7564f33f971 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 24 Oct 2023 18:00:58 +0200 Subject: [PATCH 030/327] instances in AYON mode have 'folderPath' instead of 'asset' --- openpype/pipeline/create/context.py | 37 ++++++++++++++++--- .../publish/collect_from_create_context.py | 3 ++ 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 25f03ddd3b..333ab25f54 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -11,7 +11,12 @@ from contextlib import contextmanager import pyblish.logic import pyblish.api -from openpype.client import get_assets, get_asset_by_name +from openpype import AYON_SERVER_ENABLED +from openpype.client import ( + get_assets, + get_asset_by_name, + get_asset_name_identifier, +) from openpype.settings import ( get_system_settings, get_project_settings @@ -922,9 +927,19 @@ class CreatedInstance: self._orig_data = copy.deepcopy(data) # Pop family and subset to prevent unexpected changes + # TODO change to 'productType' and 'productName' in AYON data.pop("family", None) data.pop("subset", None) + if AYON_SERVER_ENABLED: + asset_name = data.pop("asset", None) + if "folderPath" not in data: + data["folderPath"] = asset_name + + elif "folderPath" in data: + asset_name = data.pop("folderPath").split("/")[-1] + if "asset" not in data: + data["asset"] = asset_name # QUESTION Does it make sense to have data stored as ordered dict? self._data = collections.OrderedDict() @@ -1268,6 +1283,8 @@ class CreatedInstance: def has_set_asset(self): """Asset name is set in data.""" + if AYON_SERVER_ENABLED: + return "folderPath" in self._data return "asset" in self._data @property @@ -2229,7 +2246,10 @@ class CreateContext: task_names_by_asset_name = {} for instance in instances: task_name = instance.get("task") - asset_name = instance.get("asset") + if AYON_SERVER_ENABLED: + asset_name = instance.get("folderPath") + else: + asset_name = instance.get("asset") if asset_name: task_names_by_asset_name[asset_name] = set() if task_name: @@ -2240,15 +2260,18 @@ class CreateContext: for asset_name in task_names_by_asset_name.keys() if asset_name is not None ] + fields = {"name", "data.tasks"} + if AYON_SERVER_ENABLED: + fields |= {"data.parents"} asset_docs = list(get_assets( self.project_name, asset_names=asset_names, - fields=["name", "data.tasks"] + fields=fields )) task_names_by_asset_name = {} for asset_doc in asset_docs: - asset_name = asset_doc["name"] + asset_name = get_asset_name_identifier(asset_doc) tasks = asset_doc.get("data", {}).get("tasks") or {} task_names_by_asset_name[asset_name] = set(tasks.keys()) @@ -2256,7 +2279,11 @@ class CreateContext: if not instance.has_valid_asset or not instance.has_valid_task: continue - asset_name = instance["asset"] + if AYON_SERVER_ENABLED: + asset_name = instance["folderPath"] + else: + asset_name = instance["asset"] + if asset_name not in task_names_by_asset_name: instance.set_asset_invalid(True) continue diff --git a/openpype/plugins/publish/collect_from_create_context.py b/openpype/plugins/publish/collect_from_create_context.py index 8806a13ca0..84f6141069 100644 --- a/openpype/plugins/publish/collect_from_create_context.py +++ b/openpype/plugins/publish/collect_from_create_context.py @@ -4,6 +4,7 @@ import os import pyblish.api +from openpype import AYON_SERVER_ENABLED from openpype.host import IPublishHost from openpype.pipeline import legacy_io, registered_host from openpype.pipeline.create import CreateContext @@ -38,6 +39,8 @@ class CollectFromCreateContext(pyblish.api.ContextPlugin): for created_instance in create_context.instances: instance_data = created_instance.data_to_store() + if AYON_SERVER_ENABLED: + instance_data["asset"] = instance_data.pop("folderPath") if instance_data["active"]: thumbnail_path = thumbnail_paths_by_instance_id.get( created_instance.id From 0ddd95eacf0fda0b058fccc3388d0816aa9d4145 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 24 Oct 2023 18:02:41 +0200 Subject: [PATCH 031/327] use 'get_asset_name_identifier' in pipeline logic --- openpype/pipeline/context_tools.py | 5 ++--- openpype/pipeline/create/utils.py | 11 ++++++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/openpype/pipeline/context_tools.py b/openpype/pipeline/context_tools.py index 13630ae7ca..e20099759a 100644 --- a/openpype/pipeline/context_tools.py +++ b/openpype/pipeline/context_tools.py @@ -17,6 +17,7 @@ from openpype.client import ( get_asset_by_id, get_asset_by_name, version_is_latest, + get_asset_name_identifier, ) from openpype.lib.events import emit_event from openpype.modules import load_modules, ModulesManager @@ -568,14 +569,12 @@ def compute_session_changes( Dict[str, str]: Changes in the Session dictionary. """ - changes = {} - # Get asset document and asset if not asset_doc: task_name = None asset_name = None else: - asset_name = asset_doc["name"] + asset_name = get_asset_name_identifier(asset_doc) # Detect any changes compared session mapping = { diff --git a/openpype/pipeline/create/utils.py b/openpype/pipeline/create/utils.py index 2ef1f02bd6..ce4af8f474 100644 --- a/openpype/pipeline/create/utils.py +++ b/openpype/pipeline/create/utils.py @@ -1,6 +1,11 @@ import collections -from openpype.client import get_assets, get_subsets, get_last_versions +from openpype.client import ( + get_assets, + get_subsets, + get_last_versions, + get_asset_name_identifier, +) def get_last_versions_for_instances( @@ -52,10 +57,10 @@ def get_last_versions_for_instances( asset_docs = get_assets( project_name, asset_names=subset_names_by_asset_name.keys(), - fields=["name", "_id"] + fields=["name", "_id", "data.parents"] ) asset_names_by_id = { - asset_doc["_id"]: asset_doc["name"] + asset_doc["_id"]: get_asset_name_identifier(asset_doc) for asset_doc in asset_docs } if not asset_names_by_id: From 922c481d95b6e377a8d7433819360c45179fcffa Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 24 Oct 2023 18:03:02 +0200 Subject: [PATCH 032/327] use 'get_asset_name_identifier' for 'AVALON_ASSET' on app start --- openpype/lib/applications.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/lib/applications.py b/openpype/lib/applications.py index ff5e27c122..4d75a01e1d 100644 --- a/openpype/lib/applications.py +++ b/openpype/lib/applications.py @@ -12,6 +12,7 @@ from abc import ABCMeta, abstractmethod import six from openpype import AYON_SERVER_ENABLED, PACKAGE_DIR +from openpype.client import get_asset_name_identifier from openpype.settings import ( get_system_settings, get_project_settings, @@ -1728,7 +1729,9 @@ def prepare_context_environments(data, env_group=None, modules_manager=None): "AVALON_APP_NAME": app.full_name } if asset_doc: - context_env["AVALON_ASSET"] = asset_doc["name"] + asset_name = get_asset_name_identifier(asset_doc) + context_env["AVALON_ASSET"] = asset_name + if task_name: context_env["AVALON_TASK"] = task_name From f38c3f395e68df0a33a3b1ba135dbbcf0cf7f899 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 24 Oct 2023 18:03:20 +0200 Subject: [PATCH 033/327] use 'get_asset_name_identifier' in global plugins --- .../publish/collect_anatomy_instance_data.py | 20 +++++++++++-------- openpype/plugins/publish/collect_audio.py | 10 ++++++---- .../publish/extract_hierarchy_to_ayon.py | 4 ++-- .../publish/validate_editorial_asset_name.py | 9 ++++++--- 4 files changed, 26 insertions(+), 17 deletions(-) diff --git a/openpype/plugins/publish/collect_anatomy_instance_data.py b/openpype/plugins/publish/collect_anatomy_instance_data.py index b4f4d6a16a..cc6da9b2c3 100644 --- a/openpype/plugins/publish/collect_anatomy_instance_data.py +++ b/openpype/plugins/publish/collect_anatomy_instance_data.py @@ -30,7 +30,8 @@ import pyblish.api from openpype.client import ( get_assets, get_subsets, - get_last_versions + get_last_versions, + get_asset_name_identifier, ) from openpype.pipeline.version_start import get_versioning_start @@ -60,6 +61,9 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin): self.log.debug("Querying asset documents for instances.") context_asset_doc = context.data.get("assetEntity") + context_asset_name = None + if context_asset_doc: + context_asset_name = get_asset_name_identifier(context_asset_doc) instances_with_missing_asset_doc = collections.defaultdict(list) for instance in context: @@ -68,15 +72,15 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin): # There is possibility that assetEntity on instance is already set # which can happen in standalone publisher - if ( - instance_asset_doc - and instance_asset_doc["name"] == _asset_name - ): - continue + if instance_asset_doc: + instance_asset_name = get_asset_name_identifier( + instance_asset_doc) + if instance_asset_name == _asset_name: + continue # Check if asset name is the same as what is in context # - they may be different, e.g. in NukeStudio - if context_asset_doc and context_asset_doc["name"] == _asset_name: + if context_asset_name and context_asset_name == _asset_name: instance.data["assetEntity"] = context_asset_doc else: @@ -93,7 +97,7 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin): asset_docs = get_assets(project_name, asset_names=asset_names) asset_docs_by_name = { - asset_doc["name"]: asset_doc + get_asset_name_identifier(asset_doc): asset_doc for asset_doc in asset_docs } diff --git a/openpype/plugins/publish/collect_audio.py b/openpype/plugins/publish/collect_audio.py index 6aaadfc568..734a625852 100644 --- a/openpype/plugins/publish/collect_audio.py +++ b/openpype/plugins/publish/collect_audio.py @@ -6,6 +6,7 @@ from openpype.client import ( get_subsets, get_last_versions, get_representations, + get_asset_name_identifier, ) from openpype.pipeline.load import get_representation_path_with_anatomy @@ -121,12 +122,13 @@ class CollectAudio(pyblish.api.ContextPlugin): asset_docs = get_assets( project_name, asset_names=asset_names, - fields=["_id", "name"] + fields=["_id", "name", "data.parents"] ) - asset_id_by_name = {} - for asset_doc in asset_docs: - asset_id_by_name[asset_doc["name"]] = asset_doc["_id"] + asset_id_by_name = { + get_asset_name_identifier(asset_doc): asset_doc["_id"] + for asset_doc in asset_docs + } asset_ids = set(asset_id_by_name.values()) # Query subsets with name define by 'audio_subset_name' attr diff --git a/openpype/plugins/publish/extract_hierarchy_to_ayon.py b/openpype/plugins/publish/extract_hierarchy_to_ayon.py index 0d9131718b..fe8cb40ad2 100644 --- a/openpype/plugins/publish/extract_hierarchy_to_ayon.py +++ b/openpype/plugins/publish/extract_hierarchy_to_ayon.py @@ -8,7 +8,7 @@ from ayon_api import slugify_string from ayon_api.entity_hub import EntityHub from openpype import AYON_SERVER_ENABLED -from openpype.client import get_assets +from openpype.client import get_assets, get_asset_name_identifier from openpype.pipeline.template_data import ( get_asset_template_data, get_task_template_data, @@ -58,7 +58,7 @@ class ExtractHierarchyToAYON(pyblish.api.ContextPlugin): project_name, asset_names=instances_by_asset_name.keys() ) asset_docs_by_name = { - asset_doc["name"]: asset_doc + get_asset_name_identifier(asset_doc): asset_doc for asset_doc in asset_docs } for asset_name, instances in instances_by_asset_name.items(): diff --git a/openpype/plugins/publish/validate_editorial_asset_name.py b/openpype/plugins/publish/validate_editorial_asset_name.py index fca0d8e7f5..b5afc49f2e 100644 --- a/openpype/plugins/publish/validate_editorial_asset_name.py +++ b/openpype/plugins/publish/validate_editorial_asset_name.py @@ -2,7 +2,7 @@ from pprint import pformat import pyblish.api -from openpype.client import get_assets +from openpype.client import get_assets, get_asset_name_identifier class ValidateEditorialAssetName(pyblish.api.ContextPlugin): @@ -34,8 +34,11 @@ class ValidateEditorialAssetName(pyblish.api.ContextPlugin): self.log.debug("__ db_assets: {}".format(db_assets)) asset_db_docs = { - str(e["name"]): [str(p) for p in e["data"]["parents"]] - for e in db_assets} + get_asset_name_identifier(asset_doc): list( + asset_doc["data"]["parents"] + ) + for asset_doc in db_assets + } self.log.debug("__ project_entities: {}".format( pformat(asset_db_docs))) From c505513b056e092ac8dd8c3652776a238046980c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 24 Oct 2023 18:04:31 +0200 Subject: [PATCH 034/327] use folder path in ayon tools to define current context --- openpype/tools/ayon_launcher/models/actions.py | 6 +++--- openpype/tools/ayon_loader/control.py | 2 +- openpype/tools/ayon_sceneinventory/control.py | 11 ++--------- openpype/tools/ayon_workfiles/control.py | 2 +- 4 files changed, 7 insertions(+), 14 deletions(-) diff --git a/openpype/tools/ayon_launcher/models/actions.py b/openpype/tools/ayon_launcher/models/actions.py index 93ec115734..d7c4219dc2 100644 --- a/openpype/tools/ayon_launcher/models/actions.py +++ b/openpype/tools/ayon_launcher/models/actions.py @@ -402,12 +402,12 @@ class ActionsModel: ) def _prepare_session(self, project_name, folder_id, task_id): - folder_name = None + folder_path = None if folder_id: folder = self._controller.get_folder_entity( project_name, folder_id) if folder: - folder_name = folder["name"] + folder_path = folder["path"] task_name = None if task_id: @@ -417,7 +417,7 @@ class ActionsModel: return { "AVALON_PROJECT": project_name, - "AVALON_ASSET": folder_name, + "AVALON_ASSET": folder_path, "AVALON_TASK": task_name, } diff --git a/openpype/tools/ayon_loader/control.py b/openpype/tools/ayon_loader/control.py index 2b779f5c2e..d2fae35f32 100644 --- a/openpype/tools/ayon_loader/control.py +++ b/openpype/tools/ayon_loader/control.py @@ -289,7 +289,7 @@ class LoaderController(BackendLoaderController, FrontendLoaderController): project_name = context.get("project_name") asset_name = context.get("asset_name") if project_name and asset_name: - folder = ayon_api.get_folder_by_name( + folder = ayon_api.get_folder_by_path( project_name, asset_name, fields=["id"] ) if folder: diff --git a/openpype/tools/ayon_sceneinventory/control.py b/openpype/tools/ayon_sceneinventory/control.py index e98b0e307b..6111d7e43b 100644 --- a/openpype/tools/ayon_sceneinventory/control.py +++ b/openpype/tools/ayon_sceneinventory/control.py @@ -70,19 +70,12 @@ class SceneInventoryController: context = self.get_current_context() project_name = context["project_name"] - folder_path = context.get("folder_path") folder_name = context.get("asset_name") folder_id = None - if folder_path: - folder = ayon_api.get_folder_by_path(project_name, folder_path) + if folder_name: + folder = ayon_api.get_folder_by_path(project_name, folder_name) if folder: folder_id = folder["id"] - elif folder_name: - for folder in ayon_api.get_folders( - project_name, folder_names=[folder_name] - ): - folder_id = folder["id"] - break self._current_folder_id = folder_id self._current_folder_set = True diff --git a/openpype/tools/ayon_workfiles/control.py b/openpype/tools/ayon_workfiles/control.py index 3784959caf..d86b04badb 100644 --- a/openpype/tools/ayon_workfiles/control.py +++ b/openpype/tools/ayon_workfiles/control.py @@ -427,7 +427,7 @@ class BaseWorkfileController( task_name = context["task_name"] folder_id = None if folder_name: - folder = ayon_api.get_folder_by_name(project_name, folder_name) + folder = ayon_api.get_folder_by_path(project_name, folder_name) if folder: folder_id = folder["id"] From 1ac66764d49af599915a592de0917a4b67977358 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 24 Oct 2023 18:05:16 +0200 Subject: [PATCH 035/327] use folder path in publisher --- openpype/tools/publisher/control.py | 6 ++- .../tools/publisher/widgets/assets_widget.py | 42 ++++++++++++++++--- openpype/tools/publisher/widgets/widgets.py | 17 ++++++-- openpype/tools/utils/assets_widget.py | 1 + 4 files changed, 56 insertions(+), 10 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index a6264303d5..ad87bdf607 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -12,10 +12,12 @@ from abc import ABCMeta, abstractmethod import six import pyblish.api +from openpype import AYON_SERVER_ENABLED from openpype.client import ( get_assets, get_asset_by_id, get_subsets, + get_asset_name_identifier, ) from openpype.lib.events import EventSystem from openpype.lib.attribute_definitions import ( @@ -73,6 +75,8 @@ class AssetDocsCache: "data.visualParent": True, "data.tasks": True } + if AYON_SERVER_ENABLED: + projection["data.parents"] = True def __init__(self, controller): self._controller = controller @@ -105,7 +109,7 @@ class AssetDocsCache: elif "tasks" not in asset_doc["data"]: asset_doc["data"]["tasks"] = {} - asset_name = asset_doc["name"] + asset_name = get_asset_name_identifier(asset_doc) asset_tasks = asset_doc["data"]["tasks"] task_names_by_asset_name[asset_name] = list(asset_tasks.keys()) asset_docs_by_name[asset_name] = asset_doc diff --git a/openpype/tools/publisher/widgets/assets_widget.py b/openpype/tools/publisher/widgets/assets_widget.py index c536f93c9b..5f74b79c99 100644 --- a/openpype/tools/publisher/widgets/assets_widget.py +++ b/openpype/tools/publisher/widgets/assets_widget.py @@ -11,7 +11,8 @@ from openpype.tools.utils import ( from openpype.tools.utils.assets_widget import ( SingleSelectAssetsWidget, ASSET_ID_ROLE, - ASSET_NAME_ROLE + ASSET_NAME_ROLE, + ASSET_PATH_ROLE, ) @@ -31,6 +32,15 @@ class CreateWidgetAssetsWidget(SingleSelectAssetsWidget): self._last_filter_height = None + def get_selected_asset_name(self): + if AYON_SERVER_ENABLED: + selection_model = self._view.selectionModel() + indexes = selection_model.selectedRows() + for index in indexes: + return index.data(ASSET_PATH_ROLE) + return None + return super(CreateWidgetAssetsWidget, self).get_selected_asset_name() + def _check_header_height(self): """Catch header height changes. @@ -100,21 +110,24 @@ class AssetsHierarchyModel(QtGui.QStandardItemModel): self._controller = controller self._items_by_name = {} + self._items_by_path = {} self._items_by_asset_id = {} def reset(self): self.clear() self._items_by_name = {} + self._items_by_path = {} self._items_by_asset_id = {} assets_by_parent_id = self._controller.get_asset_hierarchy() items_by_name = {} + items_by_path = {} items_by_asset_id = {} _queue = collections.deque() - _queue.append((self.invisibleRootItem(), None)) + _queue.append((self.invisibleRootItem(), None, None)) while _queue: - parent_item, parent_id = _queue.popleft() + parent_item, parent_id, parent_path = _queue.popleft() children = assets_by_parent_id.get(parent_id) if not children: continue @@ -127,6 +140,9 @@ class AssetsHierarchyModel(QtGui.QStandardItemModel): for name in sorted(children_by_name.keys()): child = children_by_name[name] child_id = child["_id"] + child_path = name + if parent_path: + child_path = "{}/{}".format(parent_path, child_path) has_children = bool(assets_by_parent_id.get(child_id)) icon = get_asset_icon(child, has_children) @@ -138,15 +154,18 @@ class AssetsHierarchyModel(QtGui.QStandardItemModel): item.setData(icon, QtCore.Qt.DecorationRole) item.setData(child_id, ASSET_ID_ROLE) item.setData(name, ASSET_NAME_ROLE) + item.setData(child_path, ASSET_PATH_ROLE) items_by_name[name] = item + items_by_path[child_path] = item items_by_asset_id[child_id] = item items.append(item) - _queue.append((item, child_id)) + _queue.append((item, child_id, child_path)) parent_item.appendRows(items) self._items_by_name = items_by_name + self._items_by_path = items_by_path self._items_by_asset_id = items_by_asset_id def get_index_by_asset_id(self, asset_id): @@ -156,12 +175,20 @@ class AssetsHierarchyModel(QtGui.QStandardItemModel): return QtCore.QModelIndex() def get_index_by_asset_name(self, asset_name): - item = self._items_by_name.get(asset_name) + item = None + if AYON_SERVER_ENABLED: + item = self._items_by_path.get(asset_name) + + if item is None: + item = self._items_by_name.get(asset_name) + if item is None: return QtCore.QModelIndex() return item.index() def name_is_valid(self, item_name): + if AYON_SERVER_ENABLED and item_name in self._items_by_path: + return True return item_name in self._items_by_name @@ -296,7 +323,10 @@ class AssetsDialog(QtWidgets.QDialog): index = self._asset_view.currentIndex() asset_name = None if index.isValid(): - asset_name = index.data(ASSET_NAME_ROLE) + if AYON_SERVER_ENABLED: + asset_name = index.data(ASSET_PATH_ROLE) + else: + asset_name = index.data(ASSET_NAME_ROLE) self._selected_asset = asset_name self.done(1) diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index 1bbe73381f..cc0f7a9f97 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -538,6 +538,7 @@ class AssetsField(BaseClickableFrame): Does not change selected items (assets). """ self._name_input.setText(text) + self._name_input.end(False) def set_selected_items(self, asset_names=None): """Set asset names for selection of instances. @@ -1162,7 +1163,10 @@ class GlobalAttrsWidget(QtWidgets.QWidget): invalid_tasks = False for instance in self._current_instances: new_variant_value = instance.get("variant") - new_asset_name = instance.get("asset") + if AYON_SERVER_ENABLED: + new_asset_name = instance.get("folderPath") + else: + new_asset_name = instance.get("asset") new_task_name = instance.get("task") if variant_value is not None: new_variant_value = variant_value @@ -1193,7 +1197,11 @@ class GlobalAttrsWidget(QtWidgets.QWidget): instance["variant"] = variant_value if asset_name is not None: - instance["asset"] = asset_name + if AYON_SERVER_ENABLED: + instance["folderPath"] = asset_name + else: + instance["asset"] = asset_name + instance.set_asset_invalid(False) if task_name is not None: @@ -1282,7 +1290,10 @@ class GlobalAttrsWidget(QtWidgets.QWidget): variants.add(instance.get("variant") or self.unknown_value) families.add(instance.get("family") or self.unknown_value) - asset_name = instance.get("asset") or self.unknown_value + if AYON_SERVER_ENABLED: + asset_name = instance.get("folderPath") or self.unknown_value + else: + asset_name = instance.get("asset") or self.unknown_value task_name = instance.get("task") or "" asset_names.add(asset_name) asset_task_combinations.append((asset_name, task_name)) diff --git a/openpype/tools/utils/assets_widget.py b/openpype/tools/utils/assets_widget.py index a45d762c73..b83f4dfcaf 100644 --- a/openpype/tools/utils/assets_widget.py +++ b/openpype/tools/utils/assets_widget.py @@ -36,6 +36,7 @@ ASSET_ID_ROLE = QtCore.Qt.UserRole + 1 ASSET_NAME_ROLE = QtCore.Qt.UserRole + 2 ASSET_LABEL_ROLE = QtCore.Qt.UserRole + 3 ASSET_UNDERLINE_COLORS_ROLE = QtCore.Qt.UserRole + 4 +ASSET_PATH_ROLE = QtCore.Qt.UserRole + 5 class AssetsView(TreeViewSpinner, DeselectableTreeView): From 69792a5c64446bf4c5b17333f2da14a2b9368e3a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 24 Oct 2023 18:07:06 +0200 Subject: [PATCH 036/327] use 'get_asset_name_identifier' in aftereffecs --- .../plugins/create/workfile_creator.py | 22 ++++++++++++++++--- .../plugins/publish/collect_workfile.py | 6 ++++- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/aftereffects/plugins/create/workfile_creator.py b/openpype/hosts/aftereffects/plugins/create/workfile_creator.py index 2e7b9d4a7e..5dc3d6592d 100644 --- a/openpype/hosts/aftereffects/plugins/create/workfile_creator.py +++ b/openpype/hosts/aftereffects/plugins/create/workfile_creator.py @@ -1,3 +1,4 @@ +from openpype import AYON_SERVER_ENABLED import openpype.hosts.aftereffects.api as api from openpype.client import get_asset_by_name from openpype.pipeline import ( @@ -43,6 +44,14 @@ class AEWorkfileCreator(AutoCreator): task_name = context.get_current_task_name() host_name = context.host_name + existing_asset_name = None + if existing_instance is not None: + if AYON_SERVER_ENABLED: + existing_asset_name = existing_instance.get("folderPath") + + if existing_asset_name is None: + existing_asset_name = existing_instance["asset"] + if existing_instance is None: asset_doc = get_asset_by_name(project_name, asset_name) subset_name = self.get_subset_name( @@ -50,10 +59,13 @@ class AEWorkfileCreator(AutoCreator): project_name, host_name ) data = { - "asset": asset_name, "task": task_name, "variant": self.default_variant } + if AYON_SERVER_ENABLED: + data["folderPath"] = asset_name + else: + data["asset"] = asset_name data.update(self.get_dynamic_data( self.default_variant, task_name, asset_doc, project_name, host_name, None @@ -68,7 +80,7 @@ class AEWorkfileCreator(AutoCreator): new_instance.data_to_store()) elif ( - existing_instance["asset"] != asset_name + existing_asset_name != asset_name or existing_instance["task"] != task_name ): asset_doc = get_asset_by_name(project_name, asset_name) @@ -76,6 +88,10 @@ class AEWorkfileCreator(AutoCreator): self.default_variant, task_name, asset_doc, project_name, host_name ) - existing_instance["asset"] = asset_name + if AYON_SERVER_ENABLED: + existing_instance["folderPath"] = asset_name + else: + existing_instance["asset"] = asset_name + existing_instance["task"] = task_name existing_instance["subset"] = subset_name diff --git a/openpype/hosts/aftereffects/plugins/publish/collect_workfile.py b/openpype/hosts/aftereffects/plugins/publish/collect_workfile.py index dc557f67fc..58d2757840 100644 --- a/openpype/hosts/aftereffects/plugins/publish/collect_workfile.py +++ b/openpype/hosts/aftereffects/plugins/publish/collect_workfile.py @@ -1,6 +1,8 @@ import os import pyblish.api + +from openpype.client import get_asset_name_identifier from openpype.pipeline.create import get_subset_name @@ -48,9 +50,11 @@ class CollectWorkfile(pyblish.api.ContextPlugin): asset_entity = context.data["assetEntity"] project_entity = context.data["projectEntity"] + asset_name = get_asset_name_identifier(asset_entity) + instance_data = { "active": True, - "asset": asset_entity["name"], + "asset": asset_name, "task": task, "frameStart": context.data['frameStart'], "frameEnd": context.data['frameEnd'], From 18fcfa4a4133ac71873ed5ac67e498891b514f96 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 24 Oct 2023 18:07:17 +0200 Subject: [PATCH 037/327] use 'get_asset_name_identifier' in fusion --- .../fusion/plugins/create/create_workfile.py | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/fusion/plugins/create/create_workfile.py b/openpype/hosts/fusion/plugins/create/create_workfile.py index 8acaaa172f..8063e56413 100644 --- a/openpype/hosts/fusion/plugins/create/create_workfile.py +++ b/openpype/hosts/fusion/plugins/create/create_workfile.py @@ -1,6 +1,7 @@ from openpype.hosts.fusion.api import ( get_current_comp ) +from openpype import AYON_SERVER_ENABLED from openpype.client import get_asset_by_name from openpype.pipeline import ( AutoCreator, @@ -68,6 +69,15 @@ class FusionWorkfileCreator(AutoCreator): task_name = self.create_context.get_current_task_name() host_name = self.create_context.host_name + existing_instance_asset = None + if existing_instance is not None: + if AYON_SERVER_ENABLED: + existing_instance_asset = existing_instance.data.get( + "folderPath") + + if not existing_instance_asset: + existing_instance_asset = existing_instance.data.get("asset") + if existing_instance is None: asset_doc = get_asset_by_name(project_name, asset_name) subset_name = self.get_subset_name( @@ -75,10 +85,13 @@ class FusionWorkfileCreator(AutoCreator): project_name, host_name ) data = { - "asset": asset_name, "task": task_name, "variant": self.default_variant } + if AYON_SERVER_ENABLED: + data["folderPath"] = asset_name + else: + data["asset"] = asset_name data.update(self.get_dynamic_data( self.default_variant, task_name, asset_doc, project_name, host_name, None @@ -91,7 +104,7 @@ class FusionWorkfileCreator(AutoCreator): self._add_instance_to_context(new_instance) elif ( - existing_instance["asset"] != asset_name + existing_instance_asset != asset_name or existing_instance["task"] != task_name ): asset_doc = get_asset_by_name(project_name, asset_name) @@ -99,6 +112,9 @@ class FusionWorkfileCreator(AutoCreator): self.default_variant, task_name, asset_doc, project_name, host_name ) - existing_instance["asset"] = asset_name + if AYON_SERVER_ENABLED: + existing_instance["folderPath"] = asset_name + else: + existing_instance["asset"] = asset_name existing_instance["task"] = task_name existing_instance["subset"] = subset_name From 279ab08dfacf6f0916d0162f35c11e72567565e1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 24 Oct 2023 18:07:27 +0200 Subject: [PATCH 038/327] use 'get_asset_name_identifier' in celaction --- .../plugins/publish/collect_celaction_instances.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/celaction/plugins/publish/collect_celaction_instances.py b/openpype/hosts/celaction/plugins/publish/collect_celaction_instances.py index c815c1edd4..875f15fcc5 100644 --- a/openpype/hosts/celaction/plugins/publish/collect_celaction_instances.py +++ b/openpype/hosts/celaction/plugins/publish/collect_celaction_instances.py @@ -1,6 +1,8 @@ import os import pyblish.api +from openpype.client import get_asset_name_identifier + class CollectCelactionInstances(pyblish.api.ContextPlugin): """ Adds the celaction render instances """ @@ -17,8 +19,10 @@ class CollectCelactionInstances(pyblish.api.ContextPlugin): asset_entity = context.data["assetEntity"] project_entity = context.data["projectEntity"] + asset_name = get_asset_name_identifier(asset_entity) + shared_instance_data = { - "asset": asset_entity["name"], + "asset": asset_name, "frameStart": asset_entity["data"]["frameStart"], "frameEnd": asset_entity["data"]["frameEnd"], "handleStart": asset_entity["data"]["handleStart"], From a5056ea3fbca99c74f63ccafe409bbc650ec612b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 24 Oct 2023 18:09:43 +0200 Subject: [PATCH 039/327] modified maya to follow new usage of 'asset' value --- openpype/hosts/maya/api/plugin.py | 11 ++++++---- .../maya/plugins/create/create_review.py | 7 +++++- .../maya/plugins/create/create_workfile.py | 22 +++++++++++++++---- .../validate_unreal_staticmesh_naming.py | 3 ++- 4 files changed, 33 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/maya/api/plugin.py b/openpype/hosts/maya/api/plugin.py index 3b54954c8a..6c8b5b6b78 100644 --- a/openpype/hosts/maya/api/plugin.py +++ b/openpype/hosts/maya/api/plugin.py @@ -7,6 +7,7 @@ import six from maya import cmds from maya.app.renderSetup.model import renderSetup +from openpype import AYON_SERVER_ENABLED from openpype.lib import BoolDef, Logger from openpype.settings import get_project_settings from openpype.pipeline import ( @@ -463,14 +464,16 @@ class RenderlayerCreator(NewCreator, MayaCreatorBase): # this instance will not have the `instance_node` data yet # until it's been saved/persisted at least once. project_name = self.create_context.get_current_project_name() - + asset_name = self.create_context.get_current_asset_name() instance_data = { - "asset": self.create_context.get_current_asset_name(), "task": self.create_context.get_current_task_name(), "variant": layer.name(), } - asset_doc = get_asset_by_name(project_name, - instance_data["asset"]) + if AYON_SERVER_ENABLED: + instance_data["folderPath"] = asset_name + else: + instance_data["asset"] = asset_name + asset_doc = get_asset_by_name(project_name, asset_name) subset_name = self.get_subset_name( layer.name(), instance_data["task"], diff --git a/openpype/hosts/maya/plugins/create/create_review.py b/openpype/hosts/maya/plugins/create/create_review.py index f60e2406bc..18d661b186 100644 --- a/openpype/hosts/maya/plugins/create/create_review.py +++ b/openpype/hosts/maya/plugins/create/create_review.py @@ -2,6 +2,7 @@ import json from maya import cmds +from openpype import AYON_SERVER_ENABLED from openpype.hosts.maya.api import ( lib, plugin @@ -43,7 +44,11 @@ class CreateReview(plugin.MayaCreator): members = cmds.ls(selection=True) project_name = self.project_name - asset_doc = get_asset_by_name(project_name, instance_data["asset"]) + if AYON_SERVER_ENABLED: + asset_name = instance_data["folderPath"] + else: + asset_name = instance_data["asset"] + asset_doc = get_asset_by_name(project_name, asset_name) task_name = instance_data["task"] preset = lib.get_capture_preset( task_name, diff --git a/openpype/hosts/maya/plugins/create/create_workfile.py b/openpype/hosts/maya/plugins/create/create_workfile.py index d84753cd7f..74629776af 100644 --- a/openpype/hosts/maya/plugins/create/create_workfile.py +++ b/openpype/hosts/maya/plugins/create/create_workfile.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- """Creator plugin for creating workfiles.""" +from openpype import AYON_SERVER_ENABLED from openpype.pipeline import CreatedInstance, AutoCreator -from openpype.client import get_asset_by_name +from openpype.client import get_asset_by_name, get_asset_name_identifier from openpype.hosts.maya.api import plugin from maya import cmds @@ -29,16 +30,27 @@ class CreateWorkfile(plugin.MayaCreatorBase, AutoCreator): task_name = self.create_context.get_current_task_name() host_name = self.create_context.host_name + current_instance_asset = None + if current_instance is not None: + if AYON_SERVER_ENABLED: + current_instance_asset = current_instance.get("folderPath") + if not current_instance_asset: + current_instance_asset = current_instance.get("asset") + if current_instance is None: asset_doc = get_asset_by_name(project_name, asset_name) subset_name = self.get_subset_name( variant, task_name, asset_doc, project_name, host_name ) data = { - "asset": asset_name, "task": task_name, "variant": variant } + if AYON_SERVER_ENABLED: + data["folderPath"] = asset_name + else: + data["asset"] = asset_name + data.update( self.get_dynamic_data( variant, task_name, asset_doc, @@ -50,14 +62,16 @@ class CreateWorkfile(plugin.MayaCreatorBase, AutoCreator): ) self._add_instance_to_context(current_instance) elif ( - current_instance["asset"] != asset_name - or current_instance["task"] != task_name + current_instance_asset != asset_name + or current_instance["task"] != task_name ): # Update instance context if is not the same asset_doc = get_asset_by_name(project_name, asset_name) subset_name = self.get_subset_name( variant, task_name, asset_doc, project_name, host_name ) + asset_name = get_asset_name_identifier(asset_doc) + current_instance["asset"] = asset_name current_instance["task"] = task_name current_instance["subset"] = subset_name diff --git a/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py b/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py index 58fa9d02bd..42d3dc3ac8 100644 --- a/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py +++ b/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py @@ -102,7 +102,8 @@ class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin, cl_r = re.compile(regex_collision) - mesh_name = "{}{}".format(instance.data["asset"], + asset_name = instance.data["assetEntity"]["name"] + mesh_name = "{}{}".format(asset_name, instance.data.get("variant", [])) for obj in collision_set: From 82b1e0b205428dd0496be71bb269bd7d179b67c1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 24 Oct 2023 18:12:08 +0200 Subject: [PATCH 040/327] modified houdini to follow new asset naming --- .../houdini/plugins/create/create_workfile.py | 23 +++++++++--- .../plugins/publish/collect_usd_bootstrap.py | 35 ++++++++++++------- .../plugins/publish/validate_subset_name.py | 10 +++--- 3 files changed, 47 insertions(+), 21 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_workfile.py b/openpype/hosts/houdini/plugins/create/create_workfile.py index cc45a6c2a8..04a844bdf5 100644 --- a/openpype/hosts/houdini/plugins/create/create_workfile.py +++ b/openpype/hosts/houdini/plugins/create/create_workfile.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- """Creator plugin for creating workfiles.""" +from openpype import AYON_SERVER_ENABLED from openpype.hosts.houdini.api import plugin from openpype.hosts.houdini.api.lib import read, imprint from openpype.hosts.houdini.api.pipeline import CONTEXT_CONTAINER @@ -30,16 +31,27 @@ class CreateWorkfile(plugin.HoudiniCreatorBase, AutoCreator): task_name = self.create_context.get_current_task_name() host_name = self.host_name + current_instance_asset = None + if current_instance is not None: + if AYON_SERVER_ENABLED: + current_instance_asset = current_instance.get("folderPath") + if not current_instance_asset: + current_instance_asset = current_instance.get("asset") + if current_instance is None: asset_doc = get_asset_by_name(project_name, asset_name) subset_name = self.get_subset_name( variant, task_name, asset_doc, project_name, host_name ) data = { - "asset": asset_name, "task": task_name, "variant": variant } + if AYON_SERVER_ENABLED: + data["folderpath"] = asset_name + else: + data["asset"] = asset_name + data.update( self.get_dynamic_data( variant, task_name, asset_doc, @@ -51,15 +63,18 @@ class CreateWorkfile(plugin.HoudiniCreatorBase, AutoCreator): ) self._add_instance_to_context(current_instance) elif ( - current_instance["asset"] != asset_name - or current_instance["task"] != task_name + current_instance_asset != asset_name + or current_instance["task"] != task_name ): # Update instance context if is not the same asset_doc = get_asset_by_name(project_name, asset_name) subset_name = self.get_subset_name( variant, task_name, asset_doc, project_name, host_name ) - current_instance["asset"] = asset_name + if AYON_SERVER_ENABLED: + current_instance["folderPath"] = asset_name + else: + current_instance["asset"] = asset_name current_instance["task"] = task_name current_instance["subset"] = subset_name diff --git a/openpype/hosts/houdini/plugins/publish/collect_usd_bootstrap.py b/openpype/hosts/houdini/plugins/publish/collect_usd_bootstrap.py index 14a8e3c056..462cf99b9c 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_usd_bootstrap.py +++ b/openpype/hosts/houdini/plugins/publish/collect_usd_bootstrap.py @@ -1,6 +1,10 @@ import pyblish.api -from openpype.client import get_subset_by_name, get_asset_by_name +from openpype.client import ( + get_subset_by_name, + get_asset_by_name, + get_asset_name_identifier, +) import openpype.lib.usdlib as usdlib @@ -51,8 +55,9 @@ class CollectUsdBootstrap(pyblish.api.InstancePlugin): self.log.debug("Add bootstrap for: %s" % bootstrap) project_name = instance.context.data["projectName"] - asset = get_asset_by_name(project_name, instance.data["asset"]) - assert asset, "Asset must exist: %s" % asset + asset_name = instance.data["asset"] + asset_doc = get_asset_by_name(project_name, asset_name) + assert asset_doc, "Asset must exist: %s" % asset_name # Check which are not about to be created and don't exist yet required = {"shot": ["usdShot"], "asset": ["usdAsset"]}.get(bootstrap) @@ -67,19 +72,21 @@ class CollectUsdBootstrap(pyblish.api.InstancePlugin): required += list(layers) self.log.debug("Checking required bootstrap: %s" % required) - for subset in required: - if self._subset_exists(project_name, instance, subset, asset): + for subset_name in required: + if self._subset_exists( + project_name, instance, subset_name, asset_doc + ): continue self.log.debug( "Creating {0} USD bootstrap: {1} {2}".format( - bootstrap, asset["name"], subset + bootstrap, asset_name, subset_name ) ) - new = instance.context.create_instance(subset) - new.data["subset"] = subset - new.data["label"] = "{0} ({1})".format(subset, asset["name"]) + new = instance.context.create_instance(subset_name) + new.data["subset"] = subset_name + new.data["label"] = "{0} ({1})".format(subset_name, asset_name) new.data["family"] = "usd.bootstrap" new.data["comment"] = "Automated bootstrap USD file." new.data["publishFamilies"] = ["usd"] @@ -91,21 +98,23 @@ class CollectUsdBootstrap(pyblish.api.InstancePlugin): for key in ["asset"]: new.data[key] = instance.data[key] - def _subset_exists(self, project_name, instance, subset, asset): + def _subset_exists(self, project_name, instance, subset_name, asset_doc): """Return whether subset exists in current context or in database.""" # Allow it to be created during this publish session context = instance.context + + asset_doc_name = get_asset_name_identifier(asset_doc) for inst in context: if ( - inst.data["subset"] == subset - and inst.data["asset"] == asset["name"] + inst.data["subset"] == subset_name + and inst.data["asset"] == asset_doc_name ): return True # Or, if they already exist in the database we can # skip them too. if get_subset_by_name( - project_name, subset, asset["_id"], fields=["_id"] + project_name, subset_name, asset_doc["_id"], fields=["_id"] ): return True return False diff --git a/openpype/hosts/houdini/plugins/publish/validate_subset_name.py b/openpype/hosts/houdini/plugins/publish/validate_subset_name.py index bb3648f361..7bed74ebb1 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_subset_name.py +++ b/openpype/hosts/houdini/plugins/publish/validate_subset_name.py @@ -54,12 +54,13 @@ class ValidateSubsetName(pyblish.api.InstancePlugin, rop_node = hou.node(instance.data["instance_node"]) # Check subset name + asset_doc = instance.data["assetEntity"] subset_name = get_subset_name( family=instance.data["family"], variant=instance.data["variant"], task_name=instance.data["task"], - asset_doc=instance.data["assetEntity"], - dynamic_data={"asset": instance.data["asset"]} + asset_doc=asset_doc, + dynamic_data={"asset": asset_doc["name"]} ) if instance.data.get("subset") != subset_name: @@ -76,12 +77,13 @@ class ValidateSubsetName(pyblish.api.InstancePlugin, rop_node = hou.node(instance.data["instance_node"]) # Check subset name + asset_doc = instance.data["assetEntity"] subset_name = get_subset_name( family=instance.data["family"], variant=instance.data["variant"], task_name=instance.data["task"], - asset_doc=instance.data["assetEntity"], - dynamic_data={"asset": instance.data["asset"]} + asset_doc=asset_doc, + dynamic_data={"asset": asset_doc["name"]} ) instance.data["subset"] = subset_name From 23c41fe12aedeedd567cd9d290e243a5871c07bf Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 24 Oct 2023 18:13:00 +0200 Subject: [PATCH 041/327] modified photoshop to follow new 'asset' name usage --- openpype/hosts/photoshop/lib.py | 22 ++++++++++++++++--- .../plugins/create/create_flatten_image.py | 22 +++++++++++++++---- .../plugins/publish/collect_auto_image.py | 3 ++- .../plugins/publish/collect_auto_review.py | 4 +++- .../plugins/publish/collect_auto_workfile.py | 3 ++- 5 files changed, 44 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/photoshop/lib.py b/openpype/hosts/photoshop/lib.py index 9f603a70d2..654528410a 100644 --- a/openpype/hosts/photoshop/lib.py +++ b/openpype/hosts/photoshop/lib.py @@ -1,5 +1,6 @@ import re +from openpype import AYON_SERVER_ENABLED import openpype.hosts.photoshop.api as api from openpype.client import get_asset_by_name from openpype.lib import prepare_template_data @@ -43,6 +44,15 @@ class PSAutoCreator(AutoCreator): asset_name = context.get_current_asset_name() task_name = context.get_current_task_name() host_name = context.host_name + + existing_instance_asset = None + if existing_instance is not None: + if AYON_SERVER_ENABLED: + existing_instance_asset = existing_instance.get("folderPath") + + if not existing_instance_asset: + existing_instance_asset = existing_instance.get("asset") + if existing_instance is None: asset_doc = get_asset_by_name(project_name, asset_name) subset_name = self.get_subset_name( @@ -50,10 +60,13 @@ class PSAutoCreator(AutoCreator): project_name, host_name ) data = { - "asset": asset_name, "task": task_name, "variant": self.default_variant } + if AYON_SERVER_ENABLED: + data["folderPath"] = asset_name + else: + data["asset"] = asset_name data.update(self.get_dynamic_data( self.default_variant, task_name, asset_doc, project_name, host_name, None @@ -70,7 +83,7 @@ class PSAutoCreator(AutoCreator): new_instance.data_to_store()) elif ( - existing_instance["asset"] != asset_name + existing_instance_asset != asset_name or existing_instance["task"] != task_name ): asset_doc = get_asset_by_name(project_name, asset_name) @@ -78,7 +91,10 @@ class PSAutoCreator(AutoCreator): self.default_variant, task_name, asset_doc, project_name, host_name ) - existing_instance["asset"] = asset_name + if AYON_SERVER_ENABLED: + existing_instance["folderPath"] = asset_name + else: + existing_instance["asset"] = asset_name existing_instance["task"] = task_name existing_instance["subset"] = subset_name diff --git a/openpype/hosts/photoshop/plugins/create/create_flatten_image.py b/openpype/hosts/photoshop/plugins/create/create_flatten_image.py index afde77fdb4..942f8f4989 100644 --- a/openpype/hosts/photoshop/plugins/create/create_flatten_image.py +++ b/openpype/hosts/photoshop/plugins/create/create_flatten_image.py @@ -1,5 +1,6 @@ from openpype.pipeline import CreatedInstance +from openpype import AYON_SERVER_ENABLED from openpype.lib import BoolDef import openpype.hosts.photoshop.api as api from openpype.hosts.photoshop.lib import PSAutoCreator, clean_subset_name @@ -37,6 +38,14 @@ class AutoImageCreator(PSAutoCreator): host_name = context.host_name asset_doc = get_asset_by_name(project_name, asset_name) + existing_instance_asset = None + if existing_instance is not None: + if AYON_SERVER_ENABLED: + existing_instance_asset = existing_instance.get("folderPath") + + if not existing_instance_asset: + existing_instance_asset = existing_instance.get("asset") + if existing_instance is None: subset_name = self.get_subset_name( self.default_variant, task_name, asset_doc, @@ -44,9 +53,12 @@ class AutoImageCreator(PSAutoCreator): ) data = { - "asset": asset_name, "task": task_name, } + if AYON_SERVER_ENABLED: + data["folderPath"] = asset_name + else: + data["asset"] = asset_name if not self.active_on_create: data["active"] = False @@ -62,15 +74,17 @@ class AutoImageCreator(PSAutoCreator): new_instance.data_to_store()) elif ( # existing instance from different context - existing_instance["asset"] != asset_name + existing_instance_asset != asset_name or existing_instance["task"] != task_name ): subset_name = self.get_subset_name( self.default_variant, task_name, asset_doc, project_name, host_name ) - - existing_instance["asset"] = asset_name + if AYON_SERVER_ENABLED: + existing_instance["folderPath"] = asset_name + else: + existing_instance["asset"] = asset_name existing_instance["task"] = task_name existing_instance["subset"] = subset_name diff --git a/openpype/hosts/photoshop/plugins/publish/collect_auto_image.py b/openpype/hosts/photoshop/plugins/publish/collect_auto_image.py index 77f1a3e91f..038ae9ff6c 100644 --- a/openpype/hosts/photoshop/plugins/publish/collect_auto_image.py +++ b/openpype/hosts/photoshop/plugins/publish/collect_auto_image.py @@ -1,5 +1,6 @@ import pyblish.api +from openpype.client import get_asset_name_identifier from openpype.hosts.photoshop import api as photoshop from openpype.pipeline.create import get_subset_name @@ -27,7 +28,7 @@ class CollectAutoImage(pyblish.api.ContextPlugin): task_name = context.data["anatomyData"]["task"]["name"] host_name = context.data["hostName"] asset_doc = context.data["assetEntity"] - asset_name = asset_doc["name"] + asset_name = get_asset_name_identifier(asset_doc) auto_creator = proj_settings.get( "photoshop", {}).get( diff --git a/openpype/hosts/photoshop/plugins/publish/collect_auto_review.py b/openpype/hosts/photoshop/plugins/publish/collect_auto_review.py index 82ba0ac09c..37e9e8bae8 100644 --- a/openpype/hosts/photoshop/plugins/publish/collect_auto_review.py +++ b/openpype/hosts/photoshop/plugins/publish/collect_auto_review.py @@ -7,6 +7,7 @@ Provides: """ import pyblish.api +from openpype.client import get_asset_name_identifier from openpype.hosts.photoshop import api as photoshop from openpype.pipeline.create import get_subset_name @@ -65,7 +66,8 @@ class CollectAutoReview(pyblish.api.ContextPlugin): task_name = context.data["anatomyData"]["task"]["name"] host_name = context.data["hostName"] asset_doc = context.data["assetEntity"] - asset_name = asset_doc["name"] + + asset_name = get_asset_name_identifier(asset_doc) subset_name = get_subset_name( family, diff --git a/openpype/hosts/photoshop/plugins/publish/collect_auto_workfile.py b/openpype/hosts/photoshop/plugins/publish/collect_auto_workfile.py index 01dc50af40..be5a641d51 100644 --- a/openpype/hosts/photoshop/plugins/publish/collect_auto_workfile.py +++ b/openpype/hosts/photoshop/plugins/publish/collect_auto_workfile.py @@ -1,6 +1,7 @@ import os import pyblish.api +from openpype.client import get_asset_name_identifier from openpype.hosts.photoshop import api as photoshop from openpype.pipeline.create import get_subset_name @@ -69,8 +70,8 @@ class CollectAutoWorkfile(pyblish.api.ContextPlugin): task_name = context.data["anatomyData"]["task"]["name"] host_name = context.data["hostName"] asset_doc = context.data["assetEntity"] - asset_name = asset_doc["name"] + asset_name = get_asset_name_identifier(asset_doc) subset_name = get_subset_name( family, variant, From 7c7f9f175c041292e8a11306763a0863cc6bae95 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 24 Oct 2023 18:13:23 +0200 Subject: [PATCH 042/327] modified substance to follow new 'asset' usage --- .../plugins/create/create_workfile.py | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/substancepainter/plugins/create/create_workfile.py b/openpype/hosts/substancepainter/plugins/create/create_workfile.py index d7f31f9dcf..8aa696f11d 100644 --- a/openpype/hosts/substancepainter/plugins/create/create_workfile.py +++ b/openpype/hosts/substancepainter/plugins/create/create_workfile.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- """Creator plugin for creating workfiles.""" +from openpype import AYON_SERVER_ENABLED from openpype.pipeline import CreatedInstance, AutoCreator from openpype.client import get_asset_by_name @@ -41,6 +42,13 @@ class CreateWorkfile(AutoCreator): if instance.creator_identifier == self.identifier ), None) + current_instance_asset = None + if current_instance is not None: + if AYON_SERVER_ENABLED: + current_instance_asset = current_instance.get("folderPath") + if not current_instance_asset: + current_instance_asset = current_instance.get("asset") + if current_instance is None: self.log.info("Auto-creating workfile instance...") asset_doc = get_asset_by_name(project_name, asset_name) @@ -48,22 +56,28 @@ class CreateWorkfile(AutoCreator): variant, task_name, asset_doc, project_name, host_name ) data = { - "asset": asset_name, "task": task_name, "variant": variant } + if AYON_SERVER_ENABLED: + data["folderPath"] = asset_name + else: + data["asset"] = asset_name current_instance = self.create_instance_in_context(subset_name, data) elif ( - current_instance["asset"] != asset_name - or current_instance["task"] != task_name + current_instance_asset != asset_name + or current_instance["task"] != task_name ): # Update instance context if is not the same asset_doc = get_asset_by_name(project_name, asset_name) subset_name = self.get_subset_name( variant, task_name, asset_doc, project_name, host_name ) - current_instance["asset"] = asset_name + if AYON_SERVER_ENABLED: + current_instance["folderPath"] = asset_name + else: + current_instance["asset"] = asset_name current_instance["task"] = task_name current_instance["subset"] = subset_name From 7d366371a5eec266083721b1ac651813103978fb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 24 Oct 2023 18:17:35 +0200 Subject: [PATCH 043/327] modified most of code in traypublisher to follow new asset usage --- openpype/hosts/traypublisher/api/plugin.py | 11 ++++++++--- .../plugins/create/create_colorspace_look.py | 7 ++++++- .../traypublisher/plugins/create/create_editorial.py | 6 +++++- .../plugins/create/create_movie_batch.py | 9 ++++++++- 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/traypublisher/api/plugin.py b/openpype/hosts/traypublisher/api/plugin.py index 36e041a32c..14c66fa08f 100644 --- a/openpype/hosts/traypublisher/api/plugin.py +++ b/openpype/hosts/traypublisher/api/plugin.py @@ -1,7 +1,9 @@ +from openpype import AYON_SERVER_ENABLED from openpype.client import ( get_assets, get_subsets, get_last_versions, + get_asset_name_identifier, ) from openpype.lib.attribute_definitions import ( FileDef, @@ -114,7 +116,10 @@ class SettingsCreator(TrayPublishCreator): # Fill 'version_to_use' if version control is enabled if self.allow_version_control: - asset_name = data["asset"] + if AYON_SERVER_ENABLED: + asset_name = data["folderPath"] + else: + 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) @@ -162,10 +167,10 @@ class SettingsCreator(TrayPublishCreator): asset_docs = get_assets( self.project_name, asset_names=asset_names, - fields=["_id", "name"] + fields=["_id", "name", "data.parents"] ) asset_names_by_id = { - asset_doc["_id"]: asset_doc["name"] + asset_doc["_id"]: get_asset_name_identifier(asset_doc) for asset_doc in asset_docs } subset_docs = list(get_subsets( diff --git a/openpype/hosts/traypublisher/plugins/create/create_colorspace_look.py b/openpype/hosts/traypublisher/plugins/create/create_colorspace_look.py index 5628d0973f..ac4c72a0ce 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_colorspace_look.py +++ b/openpype/hosts/traypublisher/plugins/create/create_colorspace_look.py @@ -6,6 +6,7 @@ production type `ociolook`. All files are published as representation. """ from pathlib import Path +from openpype import AYON_SERVER_ENABLED from openpype.client import get_asset_by_name from openpype.lib.attribute_definitions import ( FileDef, EnumDef, TextDef, UISeparatorDef @@ -54,8 +55,12 @@ This creator publishes color space look file (LUT). # this should never happen raise CreatorError("Missing files from representation") + if AYON_SERVER_ENABLED: + asset_name = instance_data["folderPath"] + else: + asset_name = instance_data["asset"] asset_doc = get_asset_by_name( - self.project_name, instance_data["asset"]) + self.project_name, asset_name) subset_name = self.get_subset_name( variant=instance_data["variant"], diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index 8640500b18..23cf066362 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -1,6 +1,7 @@ import os from copy import deepcopy import opentimelineio as otio +from openpype import AYON_SERVER_ENABLED from openpype.client import ( get_asset_by_name, get_project @@ -215,7 +216,10 @@ or updating already created. Publishing will create OTIO file. ] } # Create otio editorial instance - asset_name = instance_data["asset"] + if AYON_SERVER_ENABLED: + asset_name = instance_data["folderPath"] + else: + asset_name = instance_data["asset"] asset_doc = get_asset_by_name(self.project_name, asset_name) if pre_create_data["fps"] == "from_selection": diff --git a/openpype/hosts/traypublisher/plugins/create/create_movie_batch.py b/openpype/hosts/traypublisher/plugins/create/create_movie_batch.py index 3454b6e135..8fa65c7fff 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_movie_batch.py +++ b/openpype/hosts/traypublisher/plugins/create/create_movie_batch.py @@ -2,6 +2,8 @@ import copy import os import re +from openpype import AYON_SERVER_ENABLED +from openpype.client import get_asset_name_identifier from openpype.lib import ( FileDef, BoolDef, @@ -64,8 +66,13 @@ class BatchMovieCreator(TrayPublishCreator): subset_name, task_name = self._get_subset_and_task( asset_doc, data["variant"], self.project_name) + asset_name = get_asset_name_identifier(asset_doc) + instance_data["task"] = task_name - instance_data["asset"] = asset_doc["name"] + if AYON_SERVER_ENABLED: + instance_data["folderPath"] = asset_name + else: + instance_data["asset"] = asset_name # Create new instance new_instance = CreatedInstance(self.family, subset_name, From 8360c321bdf3713df75a7b0225d4cb59ac22b02f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 24 Oct 2023 18:39:04 +0200 Subject: [PATCH 044/327] modified tvpaint to use new 'asset' handling --- .../tvpaint/plugins/create/create_render.py | 54 +++++++++++++++---- .../tvpaint/plugins/create/create_review.py | 21 ++++++-- .../tvpaint/plugins/create/create_workfile.py | 20 +++++-- .../plugins/publish/validate_asset_name.py | 12 ++++- 4 files changed, 89 insertions(+), 18 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render.py b/openpype/hosts/tvpaint/plugins/create/create_render.py index b7a7c208d9..667103432e 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render.py @@ -37,7 +37,8 @@ Todos: import collections from typing import Any, Optional, Union -from openpype.client import get_asset_by_name +from openpype import AYON_SERVER_ENABLED +from openpype.client import get_asset_by_name, get_asset_name_identifier from openpype.lib import ( prepare_template_data, AbstractAttrDef, @@ -784,18 +785,25 @@ class TVPaintAutoDetectRenderCreator(TVPaintCreator): project_name, host_name=self.create_context.host_name, ) + asset_name = get_asset_name_identifier(asset_doc) if existing_instance is not None: - existing_instance["asset"] = asset_doc["name"] + if AYON_SERVER_ENABLED: + existing_instance["folderPath"] = asset_name + else: + existing_instance["asset"] = asset_name existing_instance["task"] = task_name existing_instance["subset"] = subset_name return existing_instance instance_data: dict[str, str] = { - "asset": asset_doc["name"], "task": task_name, "family": creator.family, "variant": variant } + if AYON_SERVER_ENABLED: + instance_data["folderPath"] = asset_name + else: + instance_data["asset"] = asset_name pre_create_data: dict[str, str] = { "group_id": group_id, "mark_for_review": mark_for_review @@ -820,6 +828,8 @@ class TVPaintAutoDetectRenderCreator(TVPaintCreator): for layer_name in render_pass["layer_names"]: render_pass_by_layer_name[layer_name] = render_pass + asset_name = get_asset_name_identifier(asset_doc) + for layer in layers: layer_name = layer["name"] variant = layer_name @@ -838,17 +848,25 @@ class TVPaintAutoDetectRenderCreator(TVPaintCreator): ) if render_pass is not None: - render_pass["asset"] = asset_doc["name"] + if AYON_SERVER_ENABLED: + render_pass["folderPath"] = asset_name + else: + render_pass["asset"] = asset_name + render_pass["task"] = task_name render_pass["subset"] = subset_name continue instance_data: dict[str, str] = { - "asset": asset_doc["name"], "task": task_name, "family": creator.family, "variant": variant } + if AYON_SERVER_ENABLED: + instance_data["folderPath"] = asset_name + else: + instance_data["asset"] = asset_name + pre_create_data: dict[str, Any] = { "render_layer_instance_id": render_layer_instance.id, "layer_names": [layer_name], @@ -882,9 +900,13 @@ class TVPaintAutoDetectRenderCreator(TVPaintCreator): def create(self, subset_name, instance_data, pre_create_data): project_name: str = self.create_context.get_current_project_name() - asset_name: str = instance_data["asset"] + if AYON_SERVER_ENABLED: + asset_name: str = instance_data["folderPath"] + else: + asset_name: str = instance_data["asset"] task_name: str = instance_data["task"] - asset_doc: dict[str, Any] = get_asset_by_name(project_name, asset_name) + asset_doc: dict[str, Any] = get_asset_by_name( + project_name, asset_name) render_layers_by_group_id: dict[int, CreatedInstance] = {} render_passes_by_render_layer_id: dict[int, list[CreatedInstance]] = ( @@ -1061,7 +1083,6 @@ class TVPaintSceneRenderCreator(TVPaintAutoCreator): host_name ) data = { - "asset": asset_name, "task": task_name, "variant": self.default_variant, "creator_attributes": { @@ -1073,6 +1094,10 @@ class TVPaintSceneRenderCreator(TVPaintAutoCreator): self.default_pass_name ) } + if AYON_SERVER_ENABLED: + data["folderPath"] = asset_name + else: + data["asset"] = asset_name if not self.active_on_create: data["active"] = False @@ -1101,8 +1126,14 @@ class TVPaintSceneRenderCreator(TVPaintAutoCreator): asset_name = create_context.get_current_asset_name() task_name = create_context.get_current_task_name() + existing_name = None + if AYON_SERVER_ENABLED: + existing_name = existing_instance.get("folderPath") + if existing_name is None: + existing_name = existing_instance["asset"] + if ( - existing_instance["asset"] != asset_name + existing_name != asset_name or existing_instance["task"] != task_name ): asset_doc = get_asset_by_name(project_name, asset_name) @@ -1114,7 +1145,10 @@ class TVPaintSceneRenderCreator(TVPaintAutoCreator): host_name, existing_instance ) - existing_instance["asset"] = asset_name + if AYON_SERVER_ENABLED: + existing_instance["folderPath"] = asset_name + else: + existing_instance["asset"] = asset_name existing_instance["task"] = task_name existing_instance["subset"] = subset_name diff --git a/openpype/hosts/tvpaint/plugins/create/create_review.py b/openpype/hosts/tvpaint/plugins/create/create_review.py index 7bb7510a8e..265cef00ef 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_review.py +++ b/openpype/hosts/tvpaint/plugins/create/create_review.py @@ -1,3 +1,4 @@ +from openpype import AYON_SERVER_ENABLED from openpype.client import get_asset_by_name from openpype.pipeline import CreatedInstance from openpype.hosts.tvpaint.api.plugin import TVPaintAutoCreator @@ -33,6 +34,13 @@ class TVPaintReviewCreator(TVPaintAutoCreator): asset_name = create_context.get_current_asset_name() task_name = create_context.get_current_task_name() + existing_asset_name = None + if existing_instance is not None: + if AYON_SERVER_ENABLED: + existing_asset_name = existing_instance.get("folderPath") + if existing_asset_name is None: + existing_asset_name = existing_instance.get("asset") + if existing_instance is None: asset_doc = get_asset_by_name(project_name, asset_name) subset_name = self.get_subset_name( @@ -43,10 +51,14 @@ class TVPaintReviewCreator(TVPaintAutoCreator): host_name ) data = { - "asset": asset_name, "task": task_name, "variant": self.default_variant } + if AYON_SERVER_ENABLED: + data["folderPath"] = asset_name + else: + data["asset"] = asset_name + if not self.active_on_create: data["active"] = False @@ -59,7 +71,7 @@ class TVPaintReviewCreator(TVPaintAutoCreator): self._add_instance_to_context(new_instance) elif ( - existing_instance["asset"] != asset_name + existing_asset_name != asset_name or existing_instance["task"] != task_name ): asset_doc = get_asset_by_name(project_name, asset_name) @@ -71,6 +83,9 @@ class TVPaintReviewCreator(TVPaintAutoCreator): host_name, existing_instance ) - existing_instance["asset"] = asset_name + if AYON_SERVER_ENABLED: + existing_instance["folderPath"] = asset_name + else: + existing_instance["asset"] = asset_name existing_instance["task"] = task_name existing_instance["subset"] = subset_name diff --git a/openpype/hosts/tvpaint/plugins/create/create_workfile.py b/openpype/hosts/tvpaint/plugins/create/create_workfile.py index c3982c0eca..eec0f8483f 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_workfile.py +++ b/openpype/hosts/tvpaint/plugins/create/create_workfile.py @@ -1,3 +1,4 @@ +from openpype import AYON_SERVER_ENABLED from openpype.client import get_asset_by_name from openpype.pipeline import CreatedInstance from openpype.hosts.tvpaint.api.plugin import TVPaintAutoCreator @@ -29,6 +30,13 @@ class TVPaintWorkfileCreator(TVPaintAutoCreator): asset_name = create_context.get_current_asset_name() task_name = create_context.get_current_task_name() + existing_asset_name = None + if existing_instance is not None: + if AYON_SERVER_ENABLED: + existing_asset_name = existing_instance.get("folderPath") + if existing_asset_name is None: + existing_asset_name = existing_instance.get("asset") + if existing_instance is None: asset_doc = get_asset_by_name(project_name, asset_name) subset_name = self.get_subset_name( @@ -39,10 +47,13 @@ class TVPaintWorkfileCreator(TVPaintAutoCreator): host_name ) data = { - "asset": asset_name, "task": task_name, "variant": self.default_variant } + if AYON_SERVER_ENABLED: + data["folderPath"] = asset_name + else: + data["asset"] = asset_name new_instance = CreatedInstance( self.family, subset_name, data, self @@ -53,7 +64,7 @@ class TVPaintWorkfileCreator(TVPaintAutoCreator): self._add_instance_to_context(new_instance) elif ( - existing_instance["asset"] != asset_name + existing_asset_name != asset_name or existing_instance["task"] != task_name ): asset_doc = get_asset_by_name(project_name, asset_name) @@ -65,6 +76,9 @@ class TVPaintWorkfileCreator(TVPaintAutoCreator): host_name, existing_instance ) - existing_instance["asset"] = asset_name + if AYON_SERVER_ENABLED: + existing_instance["folderPath"] = asset_name + else: + existing_instance["asset"] = asset_name existing_instance["task"] = task_name existing_instance["subset"] = subset_name diff --git a/openpype/hosts/tvpaint/plugins/publish/validate_asset_name.py b/openpype/hosts/tvpaint/plugins/publish/validate_asset_name.py index 9347960d3f..dc29e6c278 100644 --- a/openpype/hosts/tvpaint/plugins/publish/validate_asset_name.py +++ b/openpype/hosts/tvpaint/plugins/publish/validate_asset_name.py @@ -1,4 +1,5 @@ import pyblish.api +from openpype import AYON_SERVER_ENABLED from openpype.pipeline import ( PublishXmlValidationError, OptionalPyblishPluginMixin, @@ -24,12 +25,19 @@ class FixAssetNames(pyblish.api.Action): old_instance_items = list_instances() new_instance_items = [] for instance_item in old_instance_items: - instance_asset_name = instance_item.get("asset") + if AYON_SERVER_ENABLED: + instance_asset_name = instance_item.get("folderPath") + else: + instance_asset_name = instance_item.get("asset") + if ( instance_asset_name and instance_asset_name != context_asset_name ): - instance_item["asset"] = context_asset_name + if AYON_SERVER_ENABLED: + instance_item["folderPath"] = context_asset_name + else: + instance_item["asset"] = context_asset_name new_instance_items.append(instance_item) write_instances(new_instance_items) From 5ada46f2a05dc71b621a7cffe077cb9dcff0e997 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 24 Oct 2023 18:41:37 +0200 Subject: [PATCH 045/327] modified some parts of hiero to follow new 'asset' handling --- openpype/hosts/hiero/api/plugin.py | 6 ++---- .../publish/collect_frame_tag_instances.py | 7 ++++++- .../plugins/publish/precollect_instances.py | 1 - .../plugins/publish/precollect_workfile.py | 17 ++++++++++------- .../collect_assetbuilds.py | 18 ++++++++++-------- 5 files changed, 28 insertions(+), 21 deletions(-) diff --git a/openpype/hosts/hiero/api/plugin.py b/openpype/hosts/hiero/api/plugin.py index 52f96261b2..0e0632e032 100644 --- a/openpype/hosts/hiero/api/plugin.py +++ b/openpype/hosts/hiero/api/plugin.py @@ -11,7 +11,6 @@ import qargparse from openpype.settings import get_current_project_settings from openpype.lib import Logger from openpype.pipeline import LoaderPlugin, LegacyCreator -from openpype.pipeline.context_tools import get_current_project_asset from openpype.pipeline.load import get_representation_path_from_context from . import lib @@ -494,9 +493,8 @@ class ClipLoader: joint `data` key with asset.data dict into the representation """ - asset_name = self.context["representation"]["context"]["asset"] - asset_doc = get_current_project_asset(asset_name) - log.debug("__ asset_doc: {}".format(pformat(asset_doc))) + + asset_doc = self.context["asset"] self.data["assetData"] = asset_doc["data"] def _make_track_item(self, source_bin_item, audio=False): diff --git a/openpype/hosts/hiero/plugins/publish/collect_frame_tag_instances.py b/openpype/hosts/hiero/plugins/publish/collect_frame_tag_instances.py index 982a34efd6..79bf67b336 100644 --- a/openpype/hosts/hiero/plugins/publish/collect_frame_tag_instances.py +++ b/openpype/hosts/hiero/plugins/publish/collect_frame_tag_instances.py @@ -5,6 +5,8 @@ import json import pyblish.api +from openpype.client import get_asset_name_identifier + class CollectFrameTagInstances(pyblish.api.ContextPlugin): """Collect frames from tags. @@ -99,6 +101,9 @@ class CollectFrameTagInstances(pyblish.api.ContextPlugin): # first collect all available subset tag frames subset_data = {} + context_asset_doc = context.data["assetEntity"] + context_asset_name = get_asset_name_identifier(context_asset_doc) + for tag_data in sequence_tags: frame = int(tag_data["start"]) @@ -115,7 +120,7 @@ class CollectFrameTagInstances(pyblish.api.ContextPlugin): subset_data[subset] = { "frames": [frame], "format": tag_data["format"], - "asset": context.data["assetEntity"]["name"] + "asset": context_asset_name } return subset_data diff --git a/openpype/hosts/hiero/plugins/publish/precollect_instances.py b/openpype/hosts/hiero/plugins/publish/precollect_instances.py index 3f9da2cf60..65b8fed49c 100644 --- a/openpype/hosts/hiero/plugins/publish/precollect_instances.py +++ b/openpype/hosts/hiero/plugins/publish/precollect_instances.py @@ -178,7 +178,6 @@ class PrecollectInstances(pyblish.api.ContextPlugin): def create_shot_instance(self, context, **data): master_layer = data.get("heroTrack") hierarchy_data = data.get("hierarchyData") - asset = data.get("asset") item = data.get("item") clip_name = item.name() diff --git a/openpype/hosts/hiero/plugins/publish/precollect_workfile.py b/openpype/hosts/hiero/plugins/publish/precollect_workfile.py index 5a66581531..1d6bdc0257 100644 --- a/openpype/hosts/hiero/plugins/publish/precollect_workfile.py +++ b/openpype/hosts/hiero/plugins/publish/precollect_workfile.py @@ -7,6 +7,7 @@ from qtpy.QtGui import QPixmap import hiero.ui +from openpype import AYON_SERVER_ENABLED from openpype.hosts.hiero.api.otio import hiero_export @@ -17,9 +18,10 @@ class PrecollectWorkfile(pyblish.api.ContextPlugin): order = pyblish.api.CollectorOrder - 0.491 def process(self, context): + asset_name = context.data["asset"] + if AYON_SERVER_ENABLED: + asset_name = asset_name.split("/")[-1] - asset = context.data["asset"] - subset = "workfile" active_timeline = hiero.ui.activeSequence() project = active_timeline.project() fps = active_timeline.framerate().toFloat() @@ -59,13 +61,14 @@ class PrecollectWorkfile(pyblish.api.ContextPlugin): 'files': base_name, "stagingDir": staging_dir, } - + family = "workfile" instance_data = { - "name": "{}_{}".format(asset, subset), - "asset": asset, - "subset": "{}{}".format(asset, subset.capitalize()), + "name": "{}_{}".format(asset_name, family), + "asset": context.data["asset"], + # TODO use 'get_subset_name' + "subset": "{}{}".format(asset_name, family.capitalize()), "item": project, - "family": "workfile", + "family": family, "families": [], "representations": [workfile_representation, thumb_representation] } diff --git a/openpype/hosts/hiero/plugins/publish_old_workflow/collect_assetbuilds.py b/openpype/hosts/hiero/plugins/publish_old_workflow/collect_assetbuilds.py index 767f7c30f7..37370497a5 100644 --- a/openpype/hosts/hiero/plugins/publish_old_workflow/collect_assetbuilds.py +++ b/openpype/hosts/hiero/plugins/publish_old_workflow/collect_assetbuilds.py @@ -1,5 +1,6 @@ from pyblish import api -from openpype.client import get_assets + +from openpype.client import get_assets, get_asset_name_identifier class CollectAssetBuilds(api.ContextPlugin): @@ -19,10 +20,13 @@ class CollectAssetBuilds(api.ContextPlugin): def process(self, context): project_name = context.data["projectName"] asset_builds = {} - for asset in get_assets(project_name): - if asset["data"]["entityType"] == "AssetBuild": - self.log.debug("Found \"{}\" in database.".format(asset)) - asset_builds[asset["name"]] = asset + for asset_doc in get_assets(project_name): + if asset_doc["data"].get("entityType") != "AssetBuild": + continue + + asset_name = get_asset_name_identifier(asset_doc) + self.log.debug("Found \"{}\" in database.".format(asset_doc)) + asset_builds[asset_name] = asset_doc for instance in context: if instance.data["family"] != "clip": @@ -50,9 +54,7 @@ class CollectAssetBuilds(api.ContextPlugin): # Collect asset builds. data = {"assetbuilds": []} for name in asset_names: - data["assetbuilds"].append( - asset_builds[name] - ) + data["assetbuilds"].append(asset_builds[name]) self.log.debug( "Found asset builds: {}".format(data["assetbuilds"]) ) From 51bebd0f1e24264e72358d58ef55f77062aaba3b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 24 Oct 2023 18:42:14 +0200 Subject: [PATCH 046/327] modified part of flame to follow new 'asset' naming --- .../hosts/flame/plugins/publish/collect_timeline_otio.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/flame/plugins/publish/collect_timeline_otio.py b/openpype/hosts/flame/plugins/publish/collect_timeline_otio.py index f8cfa9e963..20ac048986 100644 --- a/openpype/hosts/flame/plugins/publish/collect_timeline_otio.py +++ b/openpype/hosts/flame/plugins/publish/collect_timeline_otio.py @@ -1,5 +1,6 @@ import pyblish.api +from openpype.client import get_asset_name_identifier import openpype.hosts.flame.api as opfapi from openpype.hosts.flame.otio import flame_export from openpype.pipeline.create import get_subset_name @@ -33,13 +34,15 @@ class CollecTimelineOTIO(pyblish.api.ContextPlugin): project_settings=context.data["project_settings"] ) + asset_name = get_asset_name_identifier(asset_doc) + # adding otio timeline to context with opfapi.maintained_segment_selection(sequence) as selected_seg: otio_timeline = flame_export.create_otio_timeline(sequence) instance_data = { "name": subset_name, - "asset": asset_doc["name"], + "asset": asset_name, "subset": subset_name, "family": "workfile", "families": [] From 9d617db64cac3e06ea373e05511bce4fc0728c19 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 24 Oct 2023 18:46:35 +0200 Subject: [PATCH 047/327] use only folder name to create instance name --- .../hosts/resolve/plugins/publish/precollect_workfile.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/resolve/plugins/publish/precollect_workfile.py b/openpype/hosts/resolve/plugins/publish/precollect_workfile.py index a2f3eaed7a..28b2350f01 100644 --- a/openpype/hosts/resolve/plugins/publish/precollect_workfile.py +++ b/openpype/hosts/resolve/plugins/publish/precollect_workfile.py @@ -15,6 +15,8 @@ class PrecollectWorkfile(pyblish.api.ContextPlugin): def process(self, context): asset = get_current_asset_name() + # AYON compatibility split name and use last piece + _asset_name = asset.split("/")[-1] subset = "workfile" project = rapi.get_current_project() fps = project.GetSetting("timelineFrameRate") @@ -24,9 +26,9 @@ class PrecollectWorkfile(pyblish.api.ContextPlugin): otio_timeline = davinci_export.create_otio_timeline(project) instance_data = { - "name": "{}_{}".format(asset, subset), + "name": "{}_{}".format(_asset_name, subset), "asset": asset, - "subset": "{}{}".format(asset, subset.capitalize()), + "subset": "{}{}".format(_asset_name, subset.capitalize()), "item": project, "family": "workfile", "families": [] From e34c0b9ecd1505657969676bb0d9a062498c6c52 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 24 Oct 2023 18:50:54 +0200 Subject: [PATCH 048/327] avoid unnecessary call to database --- openpype/hosts/resolve/api/plugin.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/resolve/api/plugin.py b/openpype/hosts/resolve/api/plugin.py index 8381f81acb..a3d533d3d7 100644 --- a/openpype/hosts/resolve/api/plugin.py +++ b/openpype/hosts/resolve/api/plugin.py @@ -1,10 +1,11 @@ import re import uuid +import copy + import qargparse from qtpy import QtWidgets, QtCore from openpype.settings import get_current_project_settings -from openpype.pipeline.context_tools import get_current_project_asset from openpype.pipeline import ( LegacyCreator, LoaderPlugin, @@ -379,8 +380,8 @@ class ClipLoader: joint `data` key with asset.data dict into the representation """ - asset_name = self.context["representation"]["context"]["asset"] - self.data["assetData"] = get_current_project_asset(asset_name)["data"] + + self.data["assetData"] = copy.deepcopy(self.context["asset"]["data"]) def load(self, files): """Load clip into timeline From 053cc44891343624d73de6a03ed3c560103ff118 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 25 Oct 2023 11:40:26 +0200 Subject: [PATCH 049/327] removed unnecessary line --- openpype/client/server/entities.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/client/server/entities.py b/openpype/client/server/entities.py index c1e27eabb9..c735c558d5 100644 --- a/openpype/client/server/entities.py +++ b/openpype/client/server/entities.py @@ -183,7 +183,6 @@ def get_asset_by_name(project_name, asset_name, fields=None): return None - def _folders_query(project_name, con, fields, **kwargs): if fields is None or "tasks" in fields: folders = get_folders_with_tasks( From 7942e33a117e487d5d86c15e8d919fc5dc947ff6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 25 Oct 2023 12:01:58 +0200 Subject: [PATCH 050/327] be expicit about source of asset and folder path --- .../hosts/fusion/plugins/create/create_workfile.py | 14 ++++++-------- .../houdini/plugins/create/create_workfile.py | 12 ++++++------ .../hosts/maya/plugins/create/create_workfile.py | 12 ++++++------ .../plugins/create/create_flatten_image.py | 13 ++++++------- .../plugins/create/create_workfile.py | 12 ++++++------ 5 files changed, 30 insertions(+), 33 deletions(-) diff --git a/openpype/hosts/fusion/plugins/create/create_workfile.py b/openpype/hosts/fusion/plugins/create/create_workfile.py index 8063e56413..4092086ea4 100644 --- a/openpype/hosts/fusion/plugins/create/create_workfile.py +++ b/openpype/hosts/fusion/plugins/create/create_workfile.py @@ -69,14 +69,12 @@ class FusionWorkfileCreator(AutoCreator): task_name = self.create_context.get_current_task_name() host_name = self.create_context.host_name - existing_instance_asset = None - if existing_instance is not None: - if AYON_SERVER_ENABLED: - existing_instance_asset = existing_instance.data.get( - "folderPath") - - if not existing_instance_asset: - existing_instance_asset = existing_instance.data.get("asset") + if existing_instance is None: + existing_instance_asset = None + elif AYON_SERVER_ENABLED: + existing_instance_asset = existing_instance["folderPath"] + else: + existing_instance_asset = existing_instance["asset"] if existing_instance is None: asset_doc = get_asset_by_name(project_name, asset_name) diff --git a/openpype/hosts/houdini/plugins/create/create_workfile.py b/openpype/hosts/houdini/plugins/create/create_workfile.py index 04a844bdf5..f8ee68ebc9 100644 --- a/openpype/hosts/houdini/plugins/create/create_workfile.py +++ b/openpype/hosts/houdini/plugins/create/create_workfile.py @@ -31,12 +31,12 @@ class CreateWorkfile(plugin.HoudiniCreatorBase, AutoCreator): task_name = self.create_context.get_current_task_name() host_name = self.host_name - current_instance_asset = None - if current_instance is not None: - if AYON_SERVER_ENABLED: - current_instance_asset = current_instance.get("folderPath") - if not current_instance_asset: - current_instance_asset = current_instance.get("asset") + if current_instance is None: + current_instance_asset = None + elif AYON_SERVER_ENABLED: + current_instance_asset = current_instance["folderPath"] + else: + current_instance_asset = current_instance["asset"] if current_instance is None: asset_doc = get_asset_by_name(project_name, asset_name) diff --git a/openpype/hosts/maya/plugins/create/create_workfile.py b/openpype/hosts/maya/plugins/create/create_workfile.py index 74629776af..7282fc6b8b 100644 --- a/openpype/hosts/maya/plugins/create/create_workfile.py +++ b/openpype/hosts/maya/plugins/create/create_workfile.py @@ -30,12 +30,12 @@ class CreateWorkfile(plugin.MayaCreatorBase, AutoCreator): task_name = self.create_context.get_current_task_name() host_name = self.create_context.host_name - current_instance_asset = None - if current_instance is not None: - if AYON_SERVER_ENABLED: - current_instance_asset = current_instance.get("folderPath") - if not current_instance_asset: - current_instance_asset = current_instance.get("asset") + if current_instance is None: + current_instance_asset = None + elif AYON_SERVER_ENABLED: + current_instance_asset = current_instance["folderPath"] + else: + current_instance_asset = current_instance["asset"] if current_instance is None: asset_doc = get_asset_by_name(project_name, asset_name) diff --git a/openpype/hosts/photoshop/plugins/create/create_flatten_image.py b/openpype/hosts/photoshop/plugins/create/create_flatten_image.py index 942f8f4989..24be9df0e0 100644 --- a/openpype/hosts/photoshop/plugins/create/create_flatten_image.py +++ b/openpype/hosts/photoshop/plugins/create/create_flatten_image.py @@ -38,13 +38,12 @@ class AutoImageCreator(PSAutoCreator): host_name = context.host_name asset_doc = get_asset_by_name(project_name, asset_name) - existing_instance_asset = None - if existing_instance is not None: - if AYON_SERVER_ENABLED: - existing_instance_asset = existing_instance.get("folderPath") - - if not existing_instance_asset: - existing_instance_asset = existing_instance.get("asset") + if existing_instance is None: + existing_instance_asset = None + elif AYON_SERVER_ENABLED: + existing_instance_asset = existing_instance["folderPath"] + else: + existing_instance_asset = existing_instance["asset"] if existing_instance is None: subset_name = self.get_subset_name( diff --git a/openpype/hosts/substancepainter/plugins/create/create_workfile.py b/openpype/hosts/substancepainter/plugins/create/create_workfile.py index 8aa696f11d..c73277e405 100644 --- a/openpype/hosts/substancepainter/plugins/create/create_workfile.py +++ b/openpype/hosts/substancepainter/plugins/create/create_workfile.py @@ -42,12 +42,12 @@ class CreateWorkfile(AutoCreator): if instance.creator_identifier == self.identifier ), None) - current_instance_asset = None - if current_instance is not None: - if AYON_SERVER_ENABLED: - current_instance_asset = current_instance.get("folderPath") - if not current_instance_asset: - current_instance_asset = current_instance.get("asset") + if current_instance is None: + current_instance_asset = None + elif AYON_SERVER_ENABLED: + current_instance_asset = current_instance["folderPath"] + else: + current_instance_asset = current_instance["asset"] if current_instance is None: self.log.info("Auto-creating workfile instance...") From c3ae2a3a09bdd72824f950682faf418931651ff7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 25 Oct 2023 15:20:28 +0200 Subject: [PATCH 051/327] be expicit about source of asset and folder path in photoshop --- openpype/hosts/photoshop/lib.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/photoshop/lib.py b/openpype/hosts/photoshop/lib.py index 654528410a..5c8dff947d 100644 --- a/openpype/hosts/photoshop/lib.py +++ b/openpype/hosts/photoshop/lib.py @@ -45,13 +45,12 @@ class PSAutoCreator(AutoCreator): task_name = context.get_current_task_name() host_name = context.host_name - existing_instance_asset = None - if existing_instance is not None: - if AYON_SERVER_ENABLED: - existing_instance_asset = existing_instance.get("folderPath") - - if not existing_instance_asset: - existing_instance_asset = existing_instance.get("asset") + if existing_instance is None: + existing_instance_asset = None + elif AYON_SERVER_ENABLED: + existing_instance_asset = existing_instance["folderPath"] + else: + existing_instance_asset = existing_instance["asset"] if existing_instance is None: asset_doc = get_asset_by_name(project_name, asset_name) From b6694606876eba68f3e0cb24928f93f7093e1c70 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 26 Oct 2023 18:06:47 +0200 Subject: [PATCH 052/327] Removed unnecessary condition. --- openpype/client/server/entities.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/openpype/client/server/entities.py b/openpype/client/server/entities.py index c735c558d5..fcb5ec2383 100644 --- a/openpype/client/server/entities.py +++ b/openpype/client/server/entities.py @@ -226,12 +226,11 @@ def get_assets( new_asset_names = set() folder_paths = set() - if asset_names: - for name in asset_names: - if "/" in name: - folder_paths.add(name) - else: - new_asset_names.add(name) + for name in asset_names: + if "/" in name: + folder_paths.add(name) + else: + new_asset_names.add(name) if folder_paths: for folder in _folders_query( From 130693b798be8c53e18ec02bcacde7b8b6f45fdd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 26 Oct 2023 18:24:42 +0200 Subject: [PATCH 053/327] use 'AYON_SERVER_ENABLED' in resolve plugin --- .../plugins/publish/precollect_workfile.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/resolve/plugins/publish/precollect_workfile.py b/openpype/hosts/resolve/plugins/publish/precollect_workfile.py index 28b2350f01..39c28e29f5 100644 --- a/openpype/hosts/resolve/plugins/publish/precollect_workfile.py +++ b/openpype/hosts/resolve/plugins/publish/precollect_workfile.py @@ -1,6 +1,7 @@ import pyblish.api from pprint import pformat +from openpype import AYON_SERVER_ENABLED from openpype.pipeline import get_current_asset_name from openpype.hosts.resolve import api as rapi from openpype.hosts.resolve.otio import davinci_export @@ -13,10 +14,13 @@ class PrecollectWorkfile(pyblish.api.ContextPlugin): order = pyblish.api.CollectorOrder - 0.5 def process(self, context): + current_asset = get_current_asset_name() + if AYON_SERVER_ENABLED: + # AYON compatibility split name and use last piece + asset_name = current_asset.split("/")[-1] + else: + asset_name = current_asset - asset = get_current_asset_name() - # AYON compatibility split name and use last piece - _asset_name = asset.split("/")[-1] subset = "workfile" project = rapi.get_current_project() fps = project.GetSetting("timelineFrameRate") @@ -26,9 +30,9 @@ class PrecollectWorkfile(pyblish.api.ContextPlugin): otio_timeline = davinci_export.create_otio_timeline(project) instance_data = { - "name": "{}_{}".format(_asset_name, subset), - "asset": asset, - "subset": "{}{}".format(_asset_name, subset.capitalize()), + "name": "{}_{}".format(asset_name, subset), + "asset": current_asset, + "subset": "{}{}".format(asset_name, subset.capitalize()), "item": project, "family": "workfile", "families": [] From e920c014307e0fcb543f129b6e72b4ba49433cfb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 26 Oct 2023 18:41:47 +0200 Subject: [PATCH 054/327] unify conditions --- .../hosts/tvpaint/plugins/create/create_review.py | 12 ++++++------ .../hosts/tvpaint/plugins/create/create_workfile.py | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_review.py b/openpype/hosts/tvpaint/plugins/create/create_review.py index 265cef00ef..5caf20f27d 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_review.py +++ b/openpype/hosts/tvpaint/plugins/create/create_review.py @@ -34,12 +34,12 @@ class TVPaintReviewCreator(TVPaintAutoCreator): asset_name = create_context.get_current_asset_name() task_name = create_context.get_current_task_name() - existing_asset_name = None - if existing_instance is not None: - if AYON_SERVER_ENABLED: - existing_asset_name = existing_instance.get("folderPath") - if existing_asset_name is None: - existing_asset_name = existing_instance.get("asset") + if existing_instance is None: + existing_asset_name = None + elif AYON_SERVER_ENABLED: + existing_asset_name = existing_instance["folderPath"] + else: + existing_asset_name = existing_instance["asset"] if existing_instance is None: asset_doc = get_asset_by_name(project_name, asset_name) diff --git a/openpype/hosts/tvpaint/plugins/create/create_workfile.py b/openpype/hosts/tvpaint/plugins/create/create_workfile.py index eec0f8483f..4ce5d7fc96 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_workfile.py +++ b/openpype/hosts/tvpaint/plugins/create/create_workfile.py @@ -30,12 +30,12 @@ class TVPaintWorkfileCreator(TVPaintAutoCreator): asset_name = create_context.get_current_asset_name() task_name = create_context.get_current_task_name() - existing_asset_name = None - if existing_instance is not None: - if AYON_SERVER_ENABLED: - existing_asset_name = existing_instance.get("folderPath") - if existing_asset_name is None: - existing_asset_name = existing_instance.get("asset") + if existing_instance is None: + existing_asset_name = None + elif AYON_SERVER_ENABLED: + existing_asset_name = existing_instance["folderPath"] + else: + existing_asset_name = existing_instance["asset"] if existing_instance is None: asset_doc = get_asset_by_name(project_name, asset_name) From 0b0cfb4116ed6067b0416bb34ecd8a3dd7e19805 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 27 Oct 2023 14:24:37 +0200 Subject: [PATCH 055/327] add slash at the beginning of path --- openpype/client/entities.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index d085f90028..cbaa943743 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -22,4 +22,4 @@ def get_asset_name_identifier(asset_doc): return asset_doc["name"] parents = list(asset_doc["data"]["parents"]) parents.append(asset_doc["name"]) - return "/".join(parents) + return "/" + "/".join(parents) From dd72d45ce7881b46c90c6972f27cc101b02f7696 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 27 Oct 2023 14:36:22 +0200 Subject: [PATCH 056/327] fix path in assets widget --- openpype/tools/publisher/widgets/assets_widget.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/tools/publisher/widgets/assets_widget.py b/openpype/tools/publisher/widgets/assets_widget.py index 5f74b79c99..32be514dd7 100644 --- a/openpype/tools/publisher/widgets/assets_widget.py +++ b/openpype/tools/publisher/widgets/assets_widget.py @@ -140,9 +140,11 @@ class AssetsHierarchyModel(QtGui.QStandardItemModel): for name in sorted(children_by_name.keys()): child = children_by_name[name] child_id = child["_id"] - child_path = name if parent_path: - child_path = "{}/{}".format(parent_path, child_path) + child_path = "{}/{}".format(parent_path, name) + else: + child_path = "/{}".format(name) + has_children = bool(assets_by_parent_id.get(child_id)) icon = get_asset_icon(child, has_children) From 3f8b250510e1cb0e6f8edae6afde87e5d2718c23 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 27 Oct 2023 17:05:00 +0200 Subject: [PATCH 057/327] removed unncessary path filtering --- openpype/client/server/entities.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/openpype/client/server/entities.py b/openpype/client/server/entities.py index fcb5ec2383..9e86dfdd63 100644 --- a/openpype/client/server/entities.py +++ b/openpype/client/server/entities.py @@ -241,21 +241,9 @@ def get_assets( if not new_asset_names: return - folders_by_name = collections.defaultdict(list) for folder in _folders_query( project_name, con, fields, folder_names=new_asset_names, **kwargs ): - folders_by_name[folder["name"]].append(folder) - - for name, folders in folders_by_name.items(): - folder = next( - ( - folder - for folder in folders - if folder["path"] == name - ), - folders[0] - ) yield convert_v4_folder_to_v3(folder, project_name) From 4bde7b7fd94ab4ca183f90d83ef347e738d8b843 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 27 Oct 2023 18:01:04 +0200 Subject: [PATCH 058/327] hiero: adding folderPath to creator - some minor typos fixes - modules sorting --- openpype/hosts/hiero/api/plugin.py | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/hiero/api/plugin.py b/openpype/hosts/hiero/api/plugin.py index 0e0632e032..dc90012b0f 100644 --- a/openpype/hosts/hiero/api/plugin.py +++ b/openpype/hosts/hiero/api/plugin.py @@ -31,7 +31,7 @@ def load_stylesheet(): class CreatorWidget(QtWidgets.QDialog): # output items - items = dict() + items = {} def __init__(self, name, info, ui_inputs, parent=None): super(CreatorWidget, self).__init__(parent) @@ -642,8 +642,8 @@ class PublishClip: Returns: hiero.core.TrackItem: hiero track item object with pype tag """ - vertical_clip_match = dict() - tag_data = dict() + vertical_clip_match = {} + tag_data = {} types = { "shot": "shot", "folder": "folder", @@ -705,9 +705,10 @@ class PublishClip: self._create_parents() def convert(self): - # solve track item data and add them to tag data - self._convert_to_tag_data() + tag_hierarchy_data = self._convert_to_tag_data() + + self.tag_data.update(tag_hierarchy_data) # if track name is in review track name and also if driving track name # is not in review track name: skip tag creation @@ -721,16 +722,28 @@ class PublishClip: if self.rename: # rename track item self.track_item.setName(new_name) - self.tag_data["asset"] = new_name + self.tag_data["asset_name"] = new_name else: - self.tag_data["asset"] = self.ti_name + self.tag_data["asset_name"] = self.ti_name self.tag_data["hierarchyData"]["shot"] = self.ti_name + # AYON unique identifier + folder_path = "/{}/{}".format( + tag_hierarchy_data["hierarchy"], + self.tag_data["asset_name"] + ) + self.tag_data["folderPath"] = folder_path + + # TODO: remove debug print + log.debug("___ folder_path: {}".format( + folder_path)) + if self.tag_data["heroTrack"] and self.review_layer: self.tag_data.update({"reviewTrack": self.review_layer}) else: self.tag_data.update({"reviewTrack": None}) + # TODO: remove debug print log.debug("___ self.tag_data: {}".format( pformat(self.tag_data) )) @@ -889,7 +902,7 @@ class PublishClip: tag_hierarchy_data = hero_data # add data to return data dict - self.tag_data.update(tag_hierarchy_data) + return tag_hierarchy_data def _solve_tag_hierarchy_data(self, hierarchy_formatting_data): """ Solve tag data from hierarchy data and templates. """ From 4e9173fa71b9e6b4c19994356ce704a7d3bd29f5 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 27 Oct 2023 18:02:11 +0200 Subject: [PATCH 059/327] Hiero: adding asset_name and processing folderPath - refactor labels --- .../plugins/publish/precollect_instances.py | 60 +++++++++++++------ .../plugins/publish/precollect_workfile.py | 15 +++-- 2 files changed, 52 insertions(+), 23 deletions(-) diff --git a/openpype/hosts/hiero/plugins/publish/precollect_instances.py b/openpype/hosts/hiero/plugins/publish/precollect_instances.py index 65b8fed49c..1acbbb3d88 100644 --- a/openpype/hosts/hiero/plugins/publish/precollect_instances.py +++ b/openpype/hosts/hiero/plugins/publish/precollect_instances.py @@ -1,9 +1,12 @@ import pyblish + +from openpype import AYON_SERVER_ENABLED from openpype.pipeline.editorial import is_overlapping_otio_ranges + from openpype.hosts.hiero import api as phiero from openpype.hosts.hiero.api.otio import hiero_export -import hiero +import hiero # # developer reload modules from pprint import pformat @@ -80,25 +83,24 @@ class PrecollectInstances(pyblish.api.ContextPlugin): if k not in ("id", "applieswhole", "label") }) - asset = tag_data["asset"] + asset, asset_name = self._get_asset_data(tag_data) + subset = tag_data["subset"] # insert family into families - family = tag_data["family"] families = [str(f) for f in tag_data["families"]] - families.insert(0, str(family)) # form label - label = asset - if asset != clip_name: + label = "{} -".format(asset) + if asset_name != clip_name: label += " ({})".format(clip_name) label += " {}".format(subset) - label += " {}".format("[" + ", ".join(families) + "]") data.update({ "name": "{}_{}".format(asset, subset), "label": label, "asset": asset, + "asset_name": asset_name, "item": track_item, "families": families, "publish": tag_data["publish"], @@ -176,6 +178,7 @@ class PrecollectInstances(pyblish.api.ContextPlugin): }) def create_shot_instance(self, context, **data): + subset = "shotMain" master_layer = data.get("heroTrack") hierarchy_data = data.get("hierarchyData") item = data.get("item") @@ -188,23 +191,21 @@ class PrecollectInstances(pyblish.api.ContextPlugin): return asset = data["asset"] - subset = "shotMain" + asset_name = data["asset_name"] # insert family into families family = "shot" # form label - label = asset - if asset != clip_name: + label = "{} -".format(asset) + if asset_name != clip_name: label += " ({}) ".format(clip_name) label += " {}".format(subset) - label += " [{}]".format(family) data.update({ "name": "{}_{}".format(asset, subset), "label": label, "subset": subset, - "asset": asset, "family": family, "families": [] }) @@ -214,7 +215,34 @@ class PrecollectInstances(pyblish.api.ContextPlugin): self.log.debug( "_ instance.data: {}".format(pformat(instance.data))) + def _get_asset_data(self, data): + folder_path = ( + data.pop("folderPath") if data.get("folderPath") else None) + + if data.get("asset_name"): + asset_name = data["asset_name"] + else: + asset_name = data["asset"] + + # backward compatibility for clip tags + # which are missing folderPath key + # TODO remove this in future versions + if not folder_path: + hierarchy_path = data["hierarchy"] + folder_path = "/{}/{}".format( + hierarchy_path, + asset_name + ) + + if AYON_SERVER_ENABLED: + asset = folder_path + else: + asset = asset_name + + return asset, asset_name + def create_audio_instance(self, context, **data): + subset = "audioMain" master_layer = data.get("heroTrack") if not master_layer: @@ -229,23 +257,21 @@ class PrecollectInstances(pyblish.api.ContextPlugin): return asset = data["asset"] - subset = "audioMain" + asset_name = data["asset_name"] # insert family into families family = "audio" # form label - label = asset - if asset != clip_name: + label = "{} -".format(asset) + if asset_name != clip_name: label += " ({}) ".format(clip_name) label += " {}".format(subset) - label += " [{}]".format(family) data.update({ "name": "{}_{}".format(asset, subset), "label": label, "subset": subset, - "asset": asset, "family": family, "families": ["clip"] }) diff --git a/openpype/hosts/hiero/plugins/publish/precollect_workfile.py b/openpype/hosts/hiero/plugins/publish/precollect_workfile.py index 1d6bdc0257..8abb0885c6 100644 --- a/openpype/hosts/hiero/plugins/publish/precollect_workfile.py +++ b/openpype/hosts/hiero/plugins/publish/precollect_workfile.py @@ -18,7 +18,8 @@ class PrecollectWorkfile(pyblish.api.ContextPlugin): order = pyblish.api.CollectorOrder - 0.491 def process(self, context): - asset_name = context.data["asset"] + asset = context.data["asset"] + asset_name = asset if AYON_SERVER_ENABLED: asset_name = asset_name.split("/")[-1] @@ -29,7 +30,7 @@ class PrecollectWorkfile(pyblish.api.ContextPlugin): # adding otio timeline to context otio_timeline = hiero_export.create_otio_timeline() - # get workfile thumnail paths + # get workfile thumbnail paths tmp_staging = tempfile.mkdtemp(prefix="pyblish_tmp_") thumbnail_name = "workfile_thumbnail.png" thumbnail_path = os.path.join(tmp_staging, thumbnail_name) @@ -51,8 +52,8 @@ class PrecollectWorkfile(pyblish.api.ContextPlugin): } # get workfile paths - curent_file = project.path() - staging_dir, base_name = os.path.split(curent_file) + current_file = project.path() + staging_dir, base_name = os.path.split(current_file) # creating workfile representation workfile_representation = { @@ -63,10 +64,12 @@ class PrecollectWorkfile(pyblish.api.ContextPlugin): } family = "workfile" instance_data = { + "label": "{} - {}Main".format( + asset, family), "name": "{}_{}".format(asset_name, family), "asset": context.data["asset"], # TODO use 'get_subset_name' - "subset": "{}{}".format(asset_name, family.capitalize()), + "subset": "{}{}Main".format(asset_name, family.capitalize()), "item": project, "family": family, "families": [], @@ -81,7 +84,7 @@ class PrecollectWorkfile(pyblish.api.ContextPlugin): "activeProject": project, "activeTimeline": active_timeline, "otioTimeline": otio_timeline, - "currentFile": curent_file, + "currentFile": current_file, "colorspace": self.get_colorspace(project), "fps": fps } From efef9e2fd35abc0e8fe7989776bbaeeef87b8336 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 27 Oct 2023 18:06:54 +0200 Subject: [PATCH 060/327] small fix in timers manager --- openpype/modules/timers_manager/timers_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/timers_manager/timers_manager.py b/openpype/modules/timers_manager/timers_manager.py index 43286f7da4..674d834a1d 100644 --- a/openpype/modules/timers_manager/timers_manager.py +++ b/openpype/modules/timers_manager/timers_manager.py @@ -247,7 +247,7 @@ class TimersManager( return { "project_name": project_name, "asset_id": str(asset_doc["_id"]), - "asset_name": asset_doc["name"], + "asset_name": asset_name, "task_name": task_name, "task_type": task_type, "hierarchy": hierarchy_items From d9a35e7804c5b747d2316d8b6a91b8ee0866809e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 27 Oct 2023 22:51:08 +0200 Subject: [PATCH 061/327] fixing extract hierarchy to ayon --- .../publish/extract_hierarchy_to_ayon.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/openpype/plugins/publish/extract_hierarchy_to_ayon.py b/openpype/plugins/publish/extract_hierarchy_to_ayon.py index fe8cb40ad2..ef69369d67 100644 --- a/openpype/plugins/publish/extract_hierarchy_to_ayon.py +++ b/openpype/plugins/publish/extract_hierarchy_to_ayon.py @@ -191,15 +191,15 @@ class ExtractHierarchyToAYON(pyblish.api.ContextPlugin): """ # filter only the active publishing instances - active_folder_names = set() + active_folder_paths = set() for instance in context: if instance.data.get("publish") is not False: - active_folder_names.add(instance.data.get("asset")) + active_folder_paths.add(instance.data.get("asset")) - active_folder_names.discard(None) + active_folder_paths.discard(None) - self.log.debug("Active folder names: {}".format(active_folder_names)) - if not active_folder_names: + self.log.debug("Active folder paths: {}".format(active_folder_paths)) + if not active_folder_paths: return None project_item = None @@ -230,12 +230,13 @@ class ExtractHierarchyToAYON(pyblish.api.ContextPlugin): if not children_context: continue - for asset_name, asset_info in children_context.items(): + for asset, asset_info in children_context.items(): if ( - asset_name not in active_folder_names + asset not in active_folder_paths and not asset_info.get("childs") ): continue + asset_name = asset.split("/")[-1] item_id = uuid.uuid4().hex new_item = copy.deepcopy(asset_info) new_item["name"] = asset_name @@ -252,7 +253,7 @@ class ExtractHierarchyToAYON(pyblish.api.ContextPlugin): items_by_id[item_id] = new_item parent_id_by_item_id[item_id] = parent_id - if asset_name in active_folder_names: + if asset in active_folder_paths: valid_ids.add(item_id) hierarchy_queue.append((item_id, new_children_context)) From dcf5855a477f35fd9fc160c8830974ec7cd2b8ed Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 27 Oct 2023 22:51:24 +0200 Subject: [PATCH 062/327] hound --- openpype/hosts/hiero/api/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/hiero/api/plugin.py b/openpype/hosts/hiero/api/plugin.py index dc90012b0f..f72d27fed5 100644 --- a/openpype/hosts/hiero/api/plugin.py +++ b/openpype/hosts/hiero/api/plugin.py @@ -902,7 +902,7 @@ class PublishClip: tag_hierarchy_data = hero_data # add data to return data dict - return tag_hierarchy_data + return tag_hierarchy_data def _solve_tag_hierarchy_data(self, hierarchy_formatting_data): """ Solve tag data from hierarchy data and templates. """ From 0550668d3d3356a91f6b9c0fc47d6ebc27000ce0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Mon, 30 Oct 2023 10:24:01 +0100 Subject: [PATCH 063/327] Update openpype/hosts/hiero/api/plugin.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/hiero/api/plugin.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/openpype/hosts/hiero/api/plugin.py b/openpype/hosts/hiero/api/plugin.py index f72d27fed5..b0c73e41fb 100644 --- a/openpype/hosts/hiero/api/plugin.py +++ b/openpype/hosts/hiero/api/plugin.py @@ -733,11 +733,6 @@ class PublishClip: self.tag_data["asset_name"] ) self.tag_data["folderPath"] = folder_path - - # TODO: remove debug print - log.debug("___ folder_path: {}".format( - folder_path)) - if self.tag_data["heroTrack"] and self.review_layer: self.tag_data.update({"reviewTrack": self.review_layer}) else: From 05ab5e2e0fe53f4d585db7d4050ab6f6051b4f7b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 30 Oct 2023 12:14:55 +0100 Subject: [PATCH 064/327] use 'asset' from context instead of from anatomy data --- .../plugins/publish/create_publish_royalrender_job.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/royalrender/plugins/publish/create_publish_royalrender_job.py b/openpype/modules/royalrender/plugins/publish/create_publish_royalrender_job.py index 3eb49a39ee..e13bf97e54 100644 --- a/openpype/modules/royalrender/plugins/publish/create_publish_royalrender_job.py +++ b/openpype/modules/royalrender/plugins/publish/create_publish_royalrender_job.py @@ -189,7 +189,7 @@ class CreatePublishRoyalRenderJob(pyblish.api.InstancePlugin, environment = RREnvList({ "AVALON_PROJECT": anatomy_data["project"]["name"], - "AVALON_ASSET": anatomy_data["asset"], + "AVALON_ASSET": instance.context.data["asset"], "AVALON_TASK": anatomy_data["task"]["name"], "OPENPYPE_USERNAME": anatomy_data["user"] }) From f27a25d2440671b19293bfba0c84180657178753 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 30 Oct 2023 12:15:27 +0100 Subject: [PATCH 065/327] add 'folder' key to template data in harmony collectors --- .../plugins/publish/collect_harmony_scenes.py | 3 +++ .../plugins/publish/collect_harmony_zips.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_harmony_scenes.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_harmony_scenes.py index 48c36aa067..c435ca2096 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/collect_harmony_scenes.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_harmony_scenes.py @@ -60,6 +60,9 @@ class CollectHarmonyScenes(pyblish.api.InstancePlugin): # updating hierarchy data anatomy_data_new.update({ "asset": asset_data["name"], + "folder": { + "name": asset_data["name"], + }, "task": { "name": task, "type": task_type, diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_harmony_zips.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_harmony_zips.py index 40a969f8df..d90215e767 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/collect_harmony_zips.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_harmony_zips.py @@ -56,6 +56,9 @@ class CollectHarmonyZips(pyblish.api.InstancePlugin): anatomy_data_new.update( { "asset": asset_data["name"], + "folder": { + "name": asset_data["name"], + }, "task": { "name": task, "type": task_type, From 4c55f515dd8513382fc8b9b7ac7710716708f6c6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 30 Oct 2023 12:16:07 +0100 Subject: [PATCH 066/327] do not use 'instance.data["asset"]' to prepare template data --- .../publish/collect_anatomy_instance_data.py | 30 ++++++++----------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/openpype/plugins/publish/collect_anatomy_instance_data.py b/openpype/plugins/publish/collect_anatomy_instance_data.py index cc6da9b2c3..1b4b44e40e 100644 --- a/openpype/plugins/publish/collect_anatomy_instance_data.py +++ b/openpype/plugins/publish/collect_anatomy_instance_data.py @@ -187,35 +187,29 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin): self.log.debug("Storing anatomy data to instance data.") project_doc = context.data["projectEntity"] - context_asset_doc = context.data.get("assetEntity") - project_task_types = project_doc["config"]["tasks"] for instance in context: + asset_doc = instance.data.get("assetEntity") anatomy_updates = { - "asset": instance.data["asset"], - "folder": { - "name": instance.data["asset"], - }, "family": instance.data["family"], "subset": instance.data["subset"], } - - # Hierarchy - asset_doc = instance.data.get("assetEntity") - if ( - asset_doc - and ( - not context_asset_doc - or asset_doc["_id"] != context_asset_doc["_id"] - ) - ): + if asset_doc: parents = asset_doc["data"].get("parents") or list() parent_name = project_doc["name"] if parents: parent_name = parents[-1] - anatomy_updates["hierarchy"] = "/".join(parents) - anatomy_updates["parent"] = parent_name + + hierarchy = "/".join(parents) + anatomy_updates.update({ + "asset": asset_doc["name"], + "hierarchy": hierarchy, + "parent": parent_name, + "folder": { + "name": asset_doc["name"], + }, + }) # Task task_type = None From 319a236bb2bfaf59dcaa2568685fb30bc1a04e8c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 30 Oct 2023 16:07:14 +0100 Subject: [PATCH 067/327] do not strip asset name --- openpype/tools/creator/window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/creator/window.py b/openpype/tools/creator/window.py index 47f27a262a..117519e1d7 100644 --- a/openpype/tools/creator/window.py +++ b/openpype/tools/creator/window.py @@ -214,7 +214,7 @@ class CreatorWindow(QtWidgets.QDialog): asset_name = self._asset_name_input.text() # Early exit if no asset name - if not asset_name.strip(): + if not asset_name: self._build_menu() self.echo("Asset name is required ..") self._set_valid_state(False) From 027cced5f58bb6af9b476f96744bf0909c5b6ebc Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 30 Oct 2023 16:40:46 +0100 Subject: [PATCH 068/327] show full folder path in look assigner --- openpype/hosts/maya/tools/mayalookassigner/commands.py | 8 +++++--- openpype/hosts/maya/tools/mayalookassigner/widgets.py | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/tools/mayalookassigner/commands.py b/openpype/hosts/maya/tools/mayalookassigner/commands.py index 5cc4f84931..86df502ecd 100644 --- a/openpype/hosts/maya/tools/mayalookassigner/commands.py +++ b/openpype/hosts/maya/tools/mayalookassigner/commands.py @@ -4,7 +4,7 @@ from collections import defaultdict import maya.cmds as cmds -from openpype.client import get_assets +from openpype.client import get_assets, get_asset_name_identifier from openpype.pipeline import ( remove_container, registered_host, @@ -128,7 +128,8 @@ def create_items_from_nodes(nodes): project_name = get_current_project_name() asset_ids = set(id_hashes.keys()) - asset_docs = get_assets(project_name, asset_ids, fields=["name"]) + fields = {"_id", "name", "data.parents"} + asset_docs = get_assets(project_name, asset_ids, fields=fields) asset_docs_by_id = { str(asset_doc["_id"]): asset_doc for asset_doc in asset_docs @@ -156,8 +157,9 @@ def create_items_from_nodes(nodes): namespace = get_namespace_from_node(node) namespaces.add(namespace) + label = get_asset_name_identifier(asset_doc) asset_view_items.append({ - "label": asset_doc["name"], + "label": label, "asset": asset_doc, "looks": looks, "namespaces": namespaces diff --git a/openpype/hosts/maya/tools/mayalookassigner/widgets.py b/openpype/hosts/maya/tools/mayalookassigner/widgets.py index 82c37e2104..ef29a4c726 100644 --- a/openpype/hosts/maya/tools/mayalookassigner/widgets.py +++ b/openpype/hosts/maya/tools/mayalookassigner/widgets.py @@ -3,6 +3,7 @@ from collections import defaultdict from qtpy import QtWidgets, QtCore +from openpype.client import get_asset_name_identifier from openpype.tools.utils.models import TreeModel from openpype.tools.utils.lib import ( preserve_expanded_rows, @@ -126,7 +127,7 @@ class AssetOutliner(QtWidgets.QWidget): asset_namespaces = defaultdict(set) for item in items: asset_id = str(item["asset"]["_id"]) - asset_name = item["asset"]["name"] + asset_name = get_asset_name_identifier(item["asset"]) asset_namespaces[asset_name].add(item.get("namespace")) if asset_name in assets: From 246c408ce7036de973b1dc6ba1d918f5670d495c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 30 Oct 2023 22:21:46 +0100 Subject: [PATCH 069/327] Maya: Remove RenderSetup layer observers that are not needed since new publisher --- openpype/hosts/maya/api/lib.py | 153 ---------------------------- openpype/hosts/maya/api/pipeline.py | 10 -- 2 files changed, 163 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 7c49c837e9..6d785234c5 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -3069,159 +3069,6 @@ def _get_render_instances(): return instances -renderItemObserverList = [] - - -class RenderSetupListObserver: - """Observer to catch changes in render setup layers.""" - - def listItemAdded(self, item): - print("--- adding ...") - self._add_render_layer(item) - - def listItemRemoved(self, item): - print("--- removing ...") - self._remove_render_layer(item.name()) - - def _add_render_layer(self, item): - render_sets = _get_render_instances() - layer_name = item.name() - - for render_set in render_sets: - members = cmds.sets(render_set, query=True) or [] - - namespace_name = "_{}".format(render_set) - if not cmds.namespace(exists=namespace_name): - index = 1 - namespace_name = "_{}".format(render_set) - try: - cmds.namespace(rm=namespace_name) - except RuntimeError: - # namespace is not empty, so we leave it untouched - pass - orignal_namespace_name = namespace_name - while(cmds.namespace(exists=namespace_name)): - namespace_name = "{}{}".format( - orignal_namespace_name, index) - index += 1 - - namespace = cmds.namespace(add=namespace_name) - - if members: - # if set already have namespaced members, use the same - # namespace as others. - namespace = members[0].rpartition(":")[0] - else: - namespace = namespace_name - - render_layer_set_name = "{}:{}".format(namespace, layer_name) - if render_layer_set_name in members: - continue - print(" - creating set for {}".format(layer_name)) - maya_set = cmds.sets(n=render_layer_set_name, empty=True) - cmds.sets(maya_set, forceElement=render_set) - rio = RenderSetupItemObserver(item) - print("- adding observer for {}".format(item.name())) - item.addItemObserver(rio.itemChanged) - renderItemObserverList.append(rio) - - def _remove_render_layer(self, layer_name): - render_sets = _get_render_instances() - - for render_set in render_sets: - members = cmds.sets(render_set, query=True) - if not members: - continue - - # all sets under set should have the same namespace - namespace = members[0].rpartition(":")[0] - render_layer_set_name = "{}:{}".format(namespace, layer_name) - - if render_layer_set_name in members: - print(" - removing set for {}".format(layer_name)) - cmds.delete(render_layer_set_name) - - -class RenderSetupItemObserver: - """Handle changes in render setup items.""" - - def __init__(self, item): - self.item = item - self.original_name = item.name() - - def itemChanged(self, *args, **kwargs): - """Item changed callback.""" - if self.item.name() == self.original_name: - return - - render_sets = _get_render_instances() - - for render_set in render_sets: - members = cmds.sets(render_set, query=True) - if not members: - continue - - # all sets under set should have the same namespace - namespace = members[0].rpartition(":")[0] - render_layer_set_name = "{}:{}".format( - namespace, self.original_name) - - if render_layer_set_name in members: - print(" <> renaming {} to {}".format(self.original_name, - self.item.name())) - cmds.rename(render_layer_set_name, - "{}:{}".format( - namespace, self.item.name())) - self.original_name = self.item.name() - - -renderListObserver = RenderSetupListObserver() - - -def add_render_layer_change_observer(): - import maya.app.renderSetup.model.renderSetup as renderSetup - - rs = renderSetup.instance() - render_sets = _get_render_instances() - - layers = rs.getRenderLayers() - for render_set in render_sets: - members = cmds.sets(render_set, query=True) - if not members: - continue - # all sets under set should have the same namespace - namespace = members[0].rpartition(":")[0] - for layer in layers: - render_layer_set_name = "{}:{}".format(namespace, layer.name()) - if render_layer_set_name not in members: - continue - rio = RenderSetupItemObserver(layer) - print("- adding observer for {}".format(layer.name())) - layer.addItemObserver(rio.itemChanged) - renderItemObserverList.append(rio) - - -def add_render_layer_observer(): - import maya.app.renderSetup.model.renderSetup as renderSetup - - print("> adding renderSetup observer ...") - rs = renderSetup.instance() - rs.addListObserver(renderListObserver) - pass - - -def remove_render_layer_observer(): - import maya.app.renderSetup.model.renderSetup as renderSetup - - print("< removing renderSetup observer ...") - rs = renderSetup.instance() - try: - rs.removeListObserver(renderListObserver) - except ValueError: - # no observer set yet - pass - - def update_content_on_context_change(): """ This will update scene content to match new asset on context change diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index 6b791c9665..1ecfdfaa40 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -580,20 +580,11 @@ def on_save(): lib.set_id(node, new_id, overwrite=False) -def _update_render_layer_observers(): - # Helper to trigger update for all renderlayer observer logic - lib.remove_render_layer_observer() - lib.add_render_layer_observer() - lib.add_render_layer_change_observer() - - def on_open(): """On scene open let's assume the containers have changed.""" from openpype.widgets import popup - utils.executeDeferred(_update_render_layer_observers) - # Validate FPS after update_task_from_path to # ensure it is using correct FPS for the asset lib.validate_fps() @@ -630,7 +621,6 @@ def on_new(): with lib.suspended_refresh(): lib.set_context_settings() - utils.executeDeferred(_update_render_layer_observers) _remove_workfile_lock() From 90c8922a8a53f0e2265ca8037d1593d9da6559d3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 30 Oct 2023 22:54:54 +0100 Subject: [PATCH 070/327] Remove remaining unused function --- openpype/hosts/maya/api/lib.py | 37 ---------------------------------- 1 file changed, 37 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 6d785234c5..2d394893cd 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -115,8 +115,6 @@ _alembic_options = { INT_FPS = {15, 24, 25, 30, 48, 50, 60, 44100, 48000} FLOAT_FPS = {23.98, 23.976, 29.97, 47.952, 59.94} -RENDERLIKE_INSTANCE_FAMILIES = ["rendering", "vrayscene"] - DISPLAY_LIGHTS_ENUM = [ {"label": "Use Project Settings", "value": "project_settings"}, @@ -3034,41 +3032,6 @@ class shelf(): cmds.shelfLayout(self.name, p="ShelfLayout") -def _get_render_instances(): - """Return all 'render-like' instances. - - This returns list of instance sets that needs to receive information - about render layer changes. - - Returns: - list: list of instances - - """ - objectset = cmds.ls("*.id", long=True, exactType="objectSet", - recursive=True, objectsOnly=True) - - instances = [] - for objset in objectset: - if not cmds.attributeQuery("id", node=objset, exists=True): - continue - - id_attr = "{}.id".format(objset) - if cmds.getAttr(id_attr) != "pyblish.avalon.instance": - continue - - has_family = cmds.attributeQuery("family", - node=objset, - exists=True) - if not has_family: - continue - - if cmds.getAttr( - "{}.family".format(objset)) in RENDERLIKE_INSTANCE_FAMILIES: - instances.append(objset) - - return instances - - def update_content_on_context_change(): """ This will update scene content to match new asset on context change From ae536f2409cf9b622b617150dbd9778b6c180e91 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 2 Nov 2023 16:15:03 +0100 Subject: [PATCH 071/327] traypublisher: folder path implementation --- openpype/hosts/traypublisher/api/editorial.py | 6 +++++- .../hosts/traypublisher/plugins/create/create_editorial.py | 4 +++- .../traypublisher/plugins/publish/collect_shot_instances.py | 1 - 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/traypublisher/api/editorial.py b/openpype/hosts/traypublisher/api/editorial.py index e8f76bd314..2f5e709ffc 100644 --- a/openpype/hosts/traypublisher/api/editorial.py +++ b/openpype/hosts/traypublisher/api/editorial.py @@ -319,8 +319,12 @@ class ShotMetadataSolver: tasks = self._generate_tasks_from_settings( project_doc) + # generate hierarchy path from parents + hierarchy_path = self._create_hierarchy_path(parents) + return shot_name, { - "hierarchy": self._create_hierarchy_path(parents), + "hierarchy": hierarchy_path, + "folderPath": f"{hierarchy_path}/{shot_name}", "parents": parents, "tasks": tasks } diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index 23cf066362..5dc3893697 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -217,9 +217,10 @@ or updating already created. Publishing will create OTIO file. } # Create otio editorial instance if AYON_SERVER_ENABLED: - asset_name = instance_data["folderPath"] + asset_name = instance_data.pop("folderPath") else: asset_name = instance_data["asset"] + asset_doc = get_asset_by_name(self.project_name, asset_name) if pre_create_data["fps"] == "from_selection": @@ -682,6 +683,7 @@ or updating already created. Publishing will create OTIO file. # create creator attributes creator_attributes = { "asset_name": shot_name, + "Folder path": shot_metadata["folderPath"], "Parent hierarchy path": shot_metadata["hierarchy"], "workfile_start_frame": workfile_start_frame, "fps": fps, diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py index 78c1f14e4e..b08397caf7 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py @@ -120,7 +120,6 @@ class CollectShotInstance(pyblish.api.InstancePlugin): frame_dur = frame_end - frame_start return { - "asset": _cr_attrs["asset_name"], "fps": float(_cr_attrs["fps"]), "handleStart": _cr_attrs["handle_start"], "handleEnd": _cr_attrs["handle_end"], From d94c19d0ba44a5d48b8964fe3fa6d98a29b3f0da Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 3 Nov 2023 00:59:42 +0100 Subject: [PATCH 072/327] Remove non-working LOPs USD output processors - These do not show up in recent Houdini versions - The recent output processor API is very different and processors should be registered differently - These are non-functional in current OpenPype code --- openpype/hosts/houdini/api/pipeline.py | 4 - .../vendor/husdoutputprocessors/__init__.py | 1 - .../avalon_uri_processor.py | 152 ------------------ .../stagingdir_processor.py | 90 ----------- 4 files changed, 247 deletions(-) delete mode 100644 openpype/hosts/houdini/vendor/husdoutputprocessors/__init__.py delete mode 100644 openpype/hosts/houdini/vendor/husdoutputprocessors/avalon_uri_processor.py delete mode 100644 openpype/hosts/houdini/vendor/husdoutputprocessors/stagingdir_processor.py diff --git a/openpype/hosts/houdini/api/pipeline.py b/openpype/hosts/houdini/api/pipeline.py index f8db45c56b..095004ea6f 100644 --- a/openpype/hosts/houdini/api/pipeline.py +++ b/openpype/hosts/houdini/api/pipeline.py @@ -71,10 +71,6 @@ class HoudiniHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): ) self._has_been_setup = True - # add houdini vendor packages - hou_pythonpath = os.path.join(HOUDINI_HOST_DIR, "vendor") - - sys.path.append(hou_pythonpath) # Set asset settings for the empty scene directly after launch of # Houdini so it initializes into the correct scene FPS, diff --git a/openpype/hosts/houdini/vendor/husdoutputprocessors/__init__.py b/openpype/hosts/houdini/vendor/husdoutputprocessors/__init__.py deleted file mode 100644 index 69e3be50da..0000000000 --- a/openpype/hosts/houdini/vendor/husdoutputprocessors/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__path__ = __import__('pkgutil').extend_path(__path__, __name__) diff --git a/openpype/hosts/houdini/vendor/husdoutputprocessors/avalon_uri_processor.py b/openpype/hosts/houdini/vendor/husdoutputprocessors/avalon_uri_processor.py deleted file mode 100644 index 310d057a11..0000000000 --- a/openpype/hosts/houdini/vendor/husdoutputprocessors/avalon_uri_processor.py +++ /dev/null @@ -1,152 +0,0 @@ -import os -import hou -import husdoutputprocessors.base as base - -import colorbleed.usdlib as usdlib - -from openpype.client import get_asset_by_name -from openpype.pipeline import Anatomy, get_current_project_name - - -class AvalonURIOutputProcessor(base.OutputProcessorBase): - """Process Avalon URIs into their full path equivalents. - - """ - - _parameters = None - _param_prefix = 'avalonurioutputprocessor_' - _parms = { - "use_publish_paths": _param_prefix + "use_publish_paths" - } - - def __init__(self): - """ There is only one object of each output processor class that is - ever created in a Houdini session. Therefore be very careful - about what data gets put in this object. - """ - self._use_publish_paths = False - self._cache = dict() - - def displayName(self): - return 'Avalon URI Output Processor' - - def parameters(self): - - if not self._parameters: - parameters = hou.ParmTemplateGroup() - use_publish_path = hou.ToggleParmTemplate( - name=self._parms["use_publish_paths"], - label='Resolve Reference paths to publish paths', - default_value=False, - help=("When enabled any paths for Layers, References or " - "Payloads are resolved to published master versions.\n" - "This is usually only used by the publishing pipeline, " - "but can be used for testing too.")) - parameters.append(use_publish_path) - self._parameters = parameters.asDialogScript() - - return self._parameters - - def beginSave(self, config_node, t): - parm = self._parms["use_publish_paths"] - self._use_publish_paths = config_node.parm(parm).evalAtTime(t) - self._cache.clear() - - def endSave(self): - self._use_publish_paths = None - self._cache.clear() - - def processAsset(self, - asset_path, - asset_path_for_save, - referencing_layer_path, - asset_is_layer, - for_save): - """ - Args: - asset_path (str): The incoming file path you want to alter or not. - asset_path_for_save (bool): Whether the current path is a - referenced path in the USD file. When True, return the path - you want inside USD file. - referencing_layer_path (str): ??? - asset_is_layer (bool): Whether this asset is a USD layer file. - If this is False, the asset is something else (for example, - a texture or volume file). - for_save (bool): Whether the asset path is for a file to be saved - out. If so, then return actual written filepath. - - Returns: - The refactored asset path. - - """ - - # Retrieve from cache if this query occurred before (optimization) - cache_key = (asset_path, asset_path_for_save, asset_is_layer, for_save) - if cache_key in self._cache: - return self._cache[cache_key] - - relative_template = "{asset}_{subset}.{ext}" - uri_data = usdlib.parse_avalon_uri(asset_path) - if uri_data: - - if for_save: - # Set save output path to a relative path so other - # processors can potentially manage it easily? - path = relative_template.format(**uri_data) - - print("Avalon URI Resolver: %s -> %s" % (asset_path, path)) - self._cache[cache_key] = path - return path - - if self._use_publish_paths: - # Resolve to an Avalon published asset for embedded paths - path = self._get_usd_master_path(**uri_data) - else: - path = relative_template.format(**uri_data) - - print("Avalon URI Resolver: %s -> %s" % (asset_path, path)) - self._cache[cache_key] = path - return path - - self._cache[cache_key] = asset_path - return asset_path - - def _get_usd_master_path(self, - asset, - subset, - ext): - """Get the filepath for a .usd file of a subset. - - This will return the path to an unversioned master file generated by - `usd_master_file.py`. - - """ - - PROJECT = get_current_project_name() - anatomy = Anatomy(PROJECT) - asset_doc = get_asset_by_name(PROJECT, asset) - if not asset_doc: - raise RuntimeError("Invalid asset name: '%s'" % asset) - - template_obj = anatomy.templates_obj["publish"]["path"] - path = template_obj.format_strict({ - "project": PROJECT, - "asset": asset_doc["name"], - "subset": subset, - "representation": ext, - "version": 0 # stub version zero - }) - - # Remove the version folder - subset_folder = os.path.dirname(os.path.dirname(path)) - master_folder = os.path.join(subset_folder, "master") - fname = "{0}.{1}".format(subset, ext) - - return os.path.join(master_folder, fname).replace("\\", "/") - - -output_processor = AvalonURIOutputProcessor() - - -def usdOutputProcessor(): - return output_processor diff --git a/openpype/hosts/houdini/vendor/husdoutputprocessors/stagingdir_processor.py b/openpype/hosts/houdini/vendor/husdoutputprocessors/stagingdir_processor.py deleted file mode 100644 index d8e36d5aa8..0000000000 --- a/openpype/hosts/houdini/vendor/husdoutputprocessors/stagingdir_processor.py +++ /dev/null @@ -1,90 +0,0 @@ -import hou -import husdoutputprocessors.base as base -import os - - -class StagingDirOutputProcessor(base.OutputProcessorBase): - """Output all USD Rop file nodes into the Staging Directory - - Ignore any folders and paths set in the Configured Layers - and USD Rop node, just take the filename and save into a - single directory. - - """ - theParameters = None - parameter_prefix = "stagingdiroutputprocessor_" - stagingdir_parm_name = parameter_prefix + "stagingDir" - - def __init__(self): - self.staging_dir = None - - def displayName(self): - return 'StagingDir Output Processor' - - def parameters(self): - if not self.theParameters: - parameters = hou.ParmTemplateGroup() - rootdirparm = hou.StringParmTemplate( - self.stagingdir_parm_name, - 'Staging Directory', 1, - string_type=hou.stringParmType.FileReference, - file_type=hou.fileType.Directory - ) - parameters.append(rootdirparm) - self.theParameters = parameters.asDialogScript() - return self.theParameters - - def beginSave(self, config_node, t): - - # Use the Root Directory parameter if it is set. - root_dir_parm = config_node.parm(self.stagingdir_parm_name) - if root_dir_parm: - self.staging_dir = root_dir_parm.evalAtTime(t) - - if not self.staging_dir: - out_file_parm = config_node.parm('lopoutput') - if out_file_parm: - self.staging_dir = out_file_parm.evalAtTime(t) - if self.staging_dir: - (self.staging_dir, filename) = os.path.split(self.staging_dir) - - def endSave(self): - self.staging_dir = None - - def processAsset(self, asset_path, - asset_path_for_save, - referencing_layer_path, - asset_is_layer, - for_save): - """ - Args: - asset_path (str): The incoming file path you want to alter or not. - asset_path_for_save (bool): Whether the current path is a - referenced path in the USD file. When True, return the path - you want inside USD file. - referencing_layer_path (str): ??? - asset_is_layer (bool): Whether this asset is a USD layer file. - If this is False, the asset is something else (for example, - a texture or volume file). - for_save (bool): Whether the asset path is for a file to be saved - out. If so, then return actual written filepath. - - Returns: - The refactored asset path. - - """ - - # Treat save paths as being relative to the output path. - if for_save and self.staging_dir: - # Whenever we're processing a Save Path make sure to - # resolve it to the Staging Directory - filename = os.path.basename(asset_path) - return os.path.join(self.staging_dir, filename) - - return asset_path - - -output_processor = StagingDirOutputProcessor() -def usdOutputProcessor(): - return output_processor - From 382ad931c670adbdd52fdf8996bb1d4115d37744 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 3 Nov 2023 14:13:08 +0100 Subject: [PATCH 073/327] resolve: frame duration and handles operation --- openpype/hosts/resolve/api/plugin.py | 53 ++++++++++++++++------------ 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/openpype/hosts/resolve/api/plugin.py b/openpype/hosts/resolve/api/plugin.py index 5c4a92df89..0f6f4d14bd 100644 --- a/openpype/hosts/resolve/api/plugin.py +++ b/openpype/hosts/resolve/api/plugin.py @@ -405,26 +405,41 @@ class ClipLoader: self.active_bin ) _clip_property = media_pool_item.GetClipProperty + source_in = int(_clip_property("Start")) + source_out = int(_clip_property("End")) + source_duration = int(_clip_property("Frames")) - # get handles + # get version data frame data from db + frame_start = self.data["versionData"].get("frameStart") + frame_end = self.data["versionData"].get("frameEnd") handle_start = self.data["versionData"].get("handleStart") handle_end = self.data["versionData"].get("handleEnd") - if handle_start is None: - handle_start = int(self.data["assetData"]["handleStart"]) - if handle_end is None: - handle_end = int(self.data["assetData"]["handleEnd"]) - # check frame duration from versionData or assetData - frame_start = self.data["versionData"].get("frameStart") - if frame_start is None: - frame_start = self.data["assetData"]["frameStart"] + # check if source duration is shorter than db frame duration + source_with_handles = True + # make sure all frame data is available + if ( + frame_start is None or + frame_end is None or + handle_start is None or + handle_end is None + ): + # if not then rather assume that source has no handles + source_with_handles = False + else: + # calculate db frame duration + db_frame_duration = ( + # include handles + int(handle_start) + int(handle_end) + + # include frame duration + (int(frame_end) - int(frame_start) + 1) + ) - # check frame duration from versionData or assetData - frame_end = self.data["versionData"].get("frameEnd") - if frame_end is None: - frame_end = self.data["assetData"]["frameEnd"] - - db_frame_duration = int(frame_end) - int(frame_start) + 1 + # compare source duration with db frame duration + # and assume that source has no handles if source duration + # is shorter than db frame duration + if source_duration < db_frame_duration: + source_with_handles = False # get timeline in timeline_start = self.active_timeline.GetStartFrame() @@ -436,14 +451,6 @@ class ClipLoader: timeline_in = int( timeline_start + self.data["assetData"]["clipIn"]) - source_in = int(_clip_property("Start")) - source_out = int(_clip_property("End")) - source_duration = int(_clip_property("Frames")) - - # check if source duration is shorter than db frame duration - source_with_handles = True - if source_duration < db_frame_duration: - source_with_handles = False # only exclude handles if source has no handles or # if user wants to load without handles From 1132d1c9f3eb7d512b548988e717b16563dde119 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 3 Nov 2023 15:43:33 +0100 Subject: [PATCH 074/327] avoid double slashes in context title path --- openpype/host/host.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/host/host.py b/openpype/host/host.py index 630fb873a8..afe06d1f55 100644 --- a/openpype/host/host.py +++ b/openpype/host/host.py @@ -170,7 +170,7 @@ class HostBase(object): if project_name: items.append(project_name) if asset_name: - items.append(asset_name) + items.append(asset_name.lstrip("/")) if task_name: items.append(task_name) if items: From e5e278201b359772e51ae7dafae188753de06a3e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 3 Nov 2023 16:18:04 +0100 Subject: [PATCH 075/327] better code explanation --- openpype/hosts/resolve/api/plugin.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/resolve/api/plugin.py b/openpype/hosts/resolve/api/plugin.py index 0f6f4d14bd..09c5a69d73 100644 --- a/openpype/hosts/resolve/api/plugin.py +++ b/openpype/hosts/resolve/api/plugin.py @@ -415,6 +415,19 @@ class ClipLoader: handle_start = self.data["versionData"].get("handleStart") handle_end = self.data["versionData"].get("handleEnd") + """ + There are cases where representation could be published without + handles if the "Extract review output tags" is set to "no_handles". + This would result in a shorter source duration compared to the + db frame-range. In such cases, we need to assume that the source + has no handles. + + To address this, we should compare the duration of the source + frame with the db frame-range. The duration of the db frame-range + should be calculated from the version data. If, for any reason, + the frame data is missing in the version data, we should again + assume that the source has no handles. + """ # check if source duration is shorter than db frame duration source_with_handles = True # make sure all frame data is available @@ -771,7 +784,7 @@ class PublishClip: # increasing steps by index of rename iteration self.count_steps *= self.rename_index - hierarchy_formatting_data = dict() + hierarchy_formatting_data = {} _data = self.timeline_item_default_data.copy() if self.ui_inputs: # adding tag metadata from ui @@ -867,7 +880,7 @@ class PublishClip: def _convert_to_entity(self, key): """ Converting input key to key with type. """ # convert to entity type - entity_type = self.types.get(key, None) + entity_type = self.types.get(key) assert entity_type, "Missing entity type for `{}`".format( key From 49154c0750ab45ffeb7bd413ab99b79a6f6aadef Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 3 Nov 2023 16:45:12 +0100 Subject: [PATCH 076/327] fix create multishot layout --- .../maya/plugins/create/create_multishot_layout.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_multishot_layout.py b/openpype/hosts/maya/plugins/create/create_multishot_layout.py index 0b027c02ea..7cd3fdbd17 100644 --- a/openpype/hosts/maya/plugins/create/create_multishot_layout.py +++ b/openpype/hosts/maya/plugins/create/create_multishot_layout.py @@ -45,10 +45,14 @@ class CreateMultishotLayout(plugin.MayaCreator): above is done. """ - current_folder = get_folder_by_name( - project_name=get_current_project_name(), - folder_name=get_current_asset_name(), - ) + project_name = get_current_project_name() + folder_path = get_current_asset_name() + if "/" in folder_path: + current_folder = get_folder_by_path(project_name, folder_path) + else: + current_folder = get_folder_by_name( + project_name, folder_name=folder_path + ) current_path_parts = current_folder["path"].split("/") From 1d34d5b1ae48ef69e12d30585b03a9352203f498 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 6 Nov 2023 18:04:08 +0100 Subject: [PATCH 077/327] use "asset" value instead of name from assetEntity --- .../hosts/maya/plugins/publish/validate_instance_in_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_instance_in_context.py b/openpype/hosts/maya/plugins/publish/validate_instance_in_context.py index 4ded57137c..edfb002278 100644 --- a/openpype/hosts/maya/plugins/publish/validate_instance_in_context.py +++ b/openpype/hosts/maya/plugins/publish/validate_instance_in_context.py @@ -74,4 +74,4 @@ class ValidateInstanceInContext(pyblish.api.InstancePlugin, @staticmethod def get_context_asset(instance): - return instance.context.data["assetEntity"]["name"] + return instance.context.data["asset"] From f2e14e7f4aa4c9d1557e3fdacc05cc564c21d7bb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 6 Nov 2023 18:05:37 +0100 Subject: [PATCH 078/327] use asset and project from instance and context data --- openpype/hosts/maya/plugins/publish/collect_review.py | 8 ++++---- .../hosts/maya/plugins/publish/validate_model_name.py | 6 ++++-- .../hosts/maya/plugins/publish/validate_shader_name.py | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_review.py b/openpype/hosts/maya/plugins/publish/collect_review.py index 586939a3b8..0930da8f27 100644 --- a/openpype/hosts/maya/plugins/publish/collect_review.py +++ b/openpype/hosts/maya/plugins/publish/collect_review.py @@ -3,7 +3,7 @@ from maya import cmds, mel import pyblish.api from openpype.client import get_subset_by_name -from openpype.pipeline import legacy_io, KnownPublishError +from openpype.pipeline import KnownPublishError from openpype.hosts.maya.api import lib @@ -116,10 +116,10 @@ class CollectReview(pyblish.api.InstancePlugin): instance.data['remove'] = True else: - task = legacy_io.Session["AVALON_TASK"] - legacy_subset_name = task + 'Review' + project_name = instance.context.data["projectName"] asset_doc = instance.context.data['assetEntity'] - project_name = legacy_io.active_project() + task = instance.context.data["task"] + legacy_subset_name = task + 'Review' subset_doc = get_subset_by_name( project_name, legacy_subset_name, diff --git a/openpype/hosts/maya/plugins/publish/validate_model_name.py b/openpype/hosts/maya/plugins/publish/validate_model_name.py index f4c1aa39c7..11f59bb439 100644 --- a/openpype/hosts/maya/plugins/publish/validate_model_name.py +++ b/openpype/hosts/maya/plugins/publish/validate_model_name.py @@ -67,13 +67,15 @@ class ValidateModelName(pyblish.api.InstancePlugin, regex = cls.top_level_regex r = re.compile(regex) m = r.match(top_group) + project_name = instance.context.data["projectName"] + current_asset_name = instance.context.data["asset"] if m is None: cls.log.error("invalid name on: {}".format(top_group)) cls.log.error("name doesn't match regex {}".format(regex)) invalid.append(top_group) else: if "asset" in r.groupindex: - if m.group("asset") != legacy_io.Session["AVALON_ASSET"]: + if m.group("asset") != current_asset_name: cls.log.error("Invalid asset name in top level group.") return top_group if "subset" in r.groupindex: @@ -81,7 +83,7 @@ class ValidateModelName(pyblish.api.InstancePlugin, cls.log.error("Invalid subset name in top level group.") return top_group if "project" in r.groupindex: - if m.group("project") != legacy_io.Session["AVALON_PROJECT"]: + if m.group("project") != project_name: cls.log.error("Invalid project name in top level group.") return top_group diff --git a/openpype/hosts/maya/plugins/publish/validate_shader_name.py b/openpype/hosts/maya/plugins/publish/validate_shader_name.py index 36bb2c1fee..d6486dea7f 100644 --- a/openpype/hosts/maya/plugins/publish/validate_shader_name.py +++ b/openpype/hosts/maya/plugins/publish/validate_shader_name.py @@ -51,7 +51,7 @@ class ValidateShaderName(pyblish.api.InstancePlugin, descendants = cmds.ls(descendants, noIntermediate=True, long=True) shapes = cmds.ls(descendants, type=["nurbsSurface", "mesh"], long=True) - asset_name = instance.data.get("asset", None) + asset_name = instance.data.get("asset") # Check the number of connected shadingEngines per shape regex_compile = re.compile(cls.regex) From 7f5986a683299a10d65789cc9ac86362d97262d4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Nov 2023 11:43:08 +0100 Subject: [PATCH 079/327] fix maya workfile creator --- openpype/hosts/maya/plugins/create/create_workfile.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/create/create_workfile.py b/openpype/hosts/maya/plugins/create/create_workfile.py index 7282fc6b8b..198f9c4a36 100644 --- a/openpype/hosts/maya/plugins/create/create_workfile.py +++ b/openpype/hosts/maya/plugins/create/create_workfile.py @@ -72,7 +72,10 @@ class CreateWorkfile(plugin.MayaCreatorBase, AutoCreator): ) asset_name = get_asset_name_identifier(asset_doc) - current_instance["asset"] = asset_name + if AYON_SERVER_ENABLED: + current_instance["folderPath"] = asset_name + else: + current_instance["asset"] = asset_name current_instance["task"] = task_name current_instance["subset"] = subset_name From 1e819a26838570aaf93cf89e23adf5f673413a4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Tue, 7 Nov 2023 13:28:26 +0100 Subject: [PATCH 080/327] Update openpype/hosts/traypublisher/api/editorial.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/traypublisher/api/editorial.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/traypublisher/api/editorial.py b/openpype/hosts/traypublisher/api/editorial.py index 2f5e709ffc..ddb369468f 100644 --- a/openpype/hosts/traypublisher/api/editorial.py +++ b/openpype/hosts/traypublisher/api/editorial.py @@ -321,7 +321,8 @@ class ShotMetadataSolver: # generate hierarchy path from parents hierarchy_path = self._create_hierarchy_path(parents) - + if hierarchy_path: + hierarchy_path = f"/{hierarchy_path}" return shot_name, { "hierarchy": hierarchy_path, "folderPath": f"{hierarchy_path}/{shot_name}", From 46761a028f6a6c91815f6b8c7705abf706f48380 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 7 Nov 2023 13:31:04 +0100 Subject: [PATCH 081/327] traypublisher: editorial better handling hierarchy for folder path --- openpype/hosts/traypublisher/api/editorial.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/traypublisher/api/editorial.py b/openpype/hosts/traypublisher/api/editorial.py index ddb369468f..1a83fcecbd 100644 --- a/openpype/hosts/traypublisher/api/editorial.py +++ b/openpype/hosts/traypublisher/api/editorial.py @@ -322,10 +322,13 @@ class ShotMetadataSolver: # generate hierarchy path from parents hierarchy_path = self._create_hierarchy_path(parents) if hierarchy_path: - hierarchy_path = f"/{hierarchy_path}" + folder_path = f"/{hierarchy_path}/{shot_name}" + else: + folder_path = f"/{shot_name}" + return shot_name, { "hierarchy": hierarchy_path, - "folderPath": f"{hierarchy_path}/{shot_name}", + "folderPath": folder_path, "parents": parents, "tasks": tasks } From 62af7138e11cc11509b91ec2f1ee7cc1995fb10b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 7 Nov 2023 13:38:43 +0100 Subject: [PATCH 082/327] traypublisher: editorial ayon attribute only https://github.com/ynput/OpenPype/pull/5873#discussion_r1384655823 --- .../hosts/traypublisher/plugins/create/create_editorial.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index 5dc3893697..59f24a2a2b 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -683,13 +683,15 @@ or updating already created. Publishing will create OTIO file. # create creator attributes creator_attributes = { "asset_name": shot_name, - "Folder path": shot_metadata["folderPath"], "Parent hierarchy path": shot_metadata["hierarchy"], "workfile_start_frame": workfile_start_frame, "fps": fps, "handle_start": int(handle_start), "handle_end": int(handle_end) } + if AYON_SERVER_ENABLED: + creator_attributes["folderPath"] = shot_metadata["folderPath"] + creator_attributes.update(timing_data) # create shared new instance data From 7aa5b09ef26dd2da96cccb0eb28d79a6e3d20844 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 7 Nov 2023 14:56:46 +0100 Subject: [PATCH 083/327] improving code --- openpype/hosts/traypublisher/api/editorial.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/traypublisher/api/editorial.py b/openpype/hosts/traypublisher/api/editorial.py index 1a83fcecbd..613f1de768 100644 --- a/openpype/hosts/traypublisher/api/editorial.py +++ b/openpype/hosts/traypublisher/api/editorial.py @@ -53,11 +53,11 @@ class ShotMetadataSolver: try: # format to new shot name return shot_rename_template.format(**data) - except KeyError as _E: + except KeyError as _error: raise CreatorError(( "Make sure all keys in settings are correct:: \n\n" f"From template string {shot_rename_template} > " - f"`{_E}` has no equivalent in \n" + f"`{_error}` has no equivalent in \n" f"{list(data.keys())} input formatting keys!" )) @@ -100,7 +100,7 @@ class ShotMetadataSolver: "at your project settings..." )) - # QUESTION:how to refactory `match[-1]` to some better way? + # QUESTION:how to refactor `match[-1]` to some better way? output_data[token_key] = match[-1] return output_data @@ -130,10 +130,10 @@ class ShotMetadataSolver: parent_token["name"]: parent_token["value"].format(**data) for parent_token in hierarchy_parents } - except KeyError as _E: + except KeyError as _error: raise CreatorError(( "Make sure all keys in settings are correct : \n" - f"`{_E}` has no equivalent in \n{list(data.keys())}" + f"`{_error}` has no equivalent in \n{list(data.keys())}" )) _parent_tokens_type = { @@ -147,10 +147,10 @@ class ShotMetadataSolver: try: parent_name = _parent.format( **_parent_tokens_formatting_data) - except KeyError as _E: + except KeyError as _error: raise CreatorError(( "Make sure all keys in settings are correct : \n\n" - f"`{_E}` from template string " + f"`{_error}` from template string " f"{shot_hierarchy['parents_path']}, " f" has no equivalent in \n" f"{list(_parent_tokens_formatting_data.keys())} parents" From 638c1c65e78cdf4fc9327820a265878f0a5e13a6 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 7 Nov 2023 14:57:44 +0100 Subject: [PATCH 084/327] ayon distributed folder path from create to publish --- .../plugins/create/create_editorial.py | 69 +++++++++++-------- .../plugins/publish/collect_shot_instances.py | 8 ++- 2 files changed, 49 insertions(+), 28 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index 59f24a2a2b..8c5083bcb2 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -102,14 +102,23 @@ class EditorialShotInstanceCreator(EditorialClipInstanceCreatorBase): label = "Editorial Shot" def get_instance_attr_defs(self): - attr_defs = [ - TextDef( - "asset_name", - label="Asset name", + instance_attributes = [] + if AYON_SERVER_ENABLED: + instance_attributes.append( + TextDef( + "folderPath", + label="Folder path" + ) ) - ] - attr_defs.extend(CLIP_ATTR_DEFS) - return attr_defs + else: + instance_attributes.append( + TextDef( + "shotName", + label="Shot name" + ) + ) + instance_attributes.extend(CLIP_ATTR_DEFS) + return instance_attributes class EditorialPlateInstanceCreator(EditorialClipInstanceCreatorBase): @@ -216,10 +225,7 @@ or updating already created. Publishing will create OTIO file. ] } # Create otio editorial instance - if AYON_SERVER_ENABLED: - asset_name = instance_data.pop("folderPath") - else: - asset_name = instance_data["asset"] + asset_name = instance_data["asset"] asset_doc = get_asset_by_name(self.project_name, asset_name) @@ -671,7 +677,10 @@ or updating already created. Publishing will create OTIO file. } ) - self._validate_name_uniqueness(shot_name) + # It should be validated only in openpype since we are supporting + # publishing to AYON with folder path and uniqueness is not an issue + if not AYON_SERVER_ENABLED: + self._validate_name_uniqueness(shot_name) timing_data = self._get_timing_data( otio_clip, @@ -682,36 +691,42 @@ or updating already created. Publishing will create OTIO file. # create creator attributes creator_attributes = { - "asset_name": shot_name, - "Parent hierarchy path": shot_metadata["hierarchy"], + "workfile_start_frame": workfile_start_frame, "fps": fps, "handle_start": int(handle_start), "handle_end": int(handle_end) } - if AYON_SERVER_ENABLED: - creator_attributes["folderPath"] = shot_metadata["folderPath"] - + # add timing data creator_attributes.update(timing_data) - # create shared new instance data + # create base instance data base_instance_data = { "shotName": shot_name, "variant": variant_name, - - # HACK: just for temporal bug workaround - # TODO: should loockup shot name for update - "asset": parent_asset_name, "task": "", - "newAssetPublishing": True, - - # parent time properties "trackStartFrame": track_start_frame, "timelineOffset": timeline_offset, - # creator_attributes - "creator_attributes": creator_attributes } + # update base instance data with context data + # and also update creator attributes with context data + if AYON_SERVER_ENABLED: + # TODO: this is here just to be able to publish + # to AYON with folder path + creator_attributes["folderPath"] = shot_metadata.pop("folderPath") + base_instance_data["folderPath"] = parent_asset_name + else: + creator_attributes.update({ + "shotName": shot_name, + "Parent hierarchy path": shot_metadata["hierarchy"] + }) + + base_instance_data["asset"] = parent_asset_name + + + # add creator attributes to shared instance data + base_instance_data["creator_attributes"] = creator_attributes # add hierarchy shot metadata base_instance_data.update(shot_metadata) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py index b08397caf7..65dd35782f 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py @@ -2,6 +2,8 @@ from pprint import pformat import pyblish.api import opentimelineio as otio +from openpype import AYON_SERVER_ENABLED + class CollectShotInstance(pyblish.api.InstancePlugin): """ Collect shot instances @@ -119,7 +121,7 @@ class CollectShotInstance(pyblish.api.InstancePlugin): frame_end = _cr_attrs["frameEnd"] frame_dur = frame_end - frame_start - return { + data = { "fps": float(_cr_attrs["fps"]), "handleStart": _cr_attrs["handle_start"], "handleEnd": _cr_attrs["handle_end"], @@ -132,6 +134,10 @@ class CollectShotInstance(pyblish.api.InstancePlugin): "sourceOut": _cr_attrs["sourceOut"], "workfileFrameStart": workfile_start_frame } + if AYON_SERVER_ENABLED: + data["asset"] = _cr_attrs["asset"] + + return data def _solve_hierarchy_context(self, instance): """ Adding hierarchy data to context shared data. From 44578b2121f1eee9fa6daca3e72dac047f41ebf2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 7 Nov 2023 16:15:16 +0100 Subject: [PATCH 085/327] traypublisher: editorial collector asset to instance with ayon exception for folderPath --- .../traypublisher/plugins/publish/collect_shot_instances.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py index 65dd35782f..0b7e022658 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py @@ -135,7 +135,9 @@ class CollectShotInstance(pyblish.api.InstancePlugin): "workfileFrameStart": workfile_start_frame } if AYON_SERVER_ENABLED: - data["asset"] = _cr_attrs["asset"] + data["asset"] = _cr_attrs["folderPath"] + else: + data["asset"] = _cr_attrs["shotName"] return data From f3370c0229da5ff9c323a7277f8711122b25a4b7 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Tue, 7 Nov 2023 15:21:10 +0000 Subject: [PATCH 086/327] Changed how extractors name the output files --- openpype/hosts/blender/plugins/publish/extract_abc.py | 9 ++++++--- .../blender/plugins/publish/extract_abc_animation.py | 10 +++++++--- .../hosts/blender/plugins/publish/extract_blend.py | 9 ++++++--- .../plugins/publish/extract_blend_animation.py | 9 ++++++--- .../blender/plugins/publish/extract_camera_abc.py | 9 ++++++--- .../blender/plugins/publish/extract_camera_fbx.py | 9 ++++++--- openpype/hosts/blender/plugins/publish/extract_fbx.py | 9 ++++++--- .../blender/plugins/publish/extract_fbx_animation.py | 11 +++++++---- .../hosts/blender/plugins/publish/extract_layout.py | 10 +++++++--- .../blender/plugins/publish/extract_playblast.py | 5 ++++- .../blender/plugins/publish/extract_thumbnail.py | 5 ++++- 11 files changed, 65 insertions(+), 30 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/extract_abc.py b/openpype/hosts/blender/plugins/publish/extract_abc.py index b17d7cc6e4..59035d8f61 100644 --- a/openpype/hosts/blender/plugins/publish/extract_abc.py +++ b/openpype/hosts/blender/plugins/publish/extract_abc.py @@ -17,7 +17,10 @@ class ExtractABC(publish.Extractor): def process(self, instance): # Define extract output file path stagingdir = self.staging_dir(instance) - filename = f"{instance.name}.abc" + asset_name = instance.data["assetEntity"]["name"] + subset = instance.data["subset"] + instance_name = f"{asset_name}_{subset}" + filename = f"{instance_name}.abc" filepath = os.path.join(stagingdir, filename) # Perform extraction @@ -59,8 +62,8 @@ class ExtractABC(publish.Extractor): } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s", - instance.name, representation) + self.log.info( + f"Extracted instance '{instance_name}' to: {representation}") class ExtractModelABC(ExtractABC): diff --git a/openpype/hosts/blender/plugins/publish/extract_abc_animation.py b/openpype/hosts/blender/plugins/publish/extract_abc_animation.py index 6866b05fea..0ac6f12de5 100644 --- a/openpype/hosts/blender/plugins/publish/extract_abc_animation.py +++ b/openpype/hosts/blender/plugins/publish/extract_abc_animation.py @@ -17,7 +17,11 @@ class ExtractAnimationABC(publish.Extractor): def process(self, instance): # Define extract output file path stagingdir = self.staging_dir(instance) - filename = f"{instance.name}.abc" + asset_name = instance.data["assetEntity"]["name"] + subset = instance.data["subset"] + instance_name = f"{asset_name}_{subset}" + filename = f"{instance_name}.abc" + filepath = os.path.join(stagingdir, filename) # Perform extraction @@ -66,5 +70,5 @@ class ExtractAnimationABC(publish.Extractor): } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s", - instance.name, representation) + self.log.info( + f"Extracted instance '{instance_name}' to: {representation}") diff --git a/openpype/hosts/blender/plugins/publish/extract_blend.py b/openpype/hosts/blender/plugins/publish/extract_blend.py index c8eeef7fd7..0a9fb74f7b 100644 --- a/openpype/hosts/blender/plugins/publish/extract_blend.py +++ b/openpype/hosts/blender/plugins/publish/extract_blend.py @@ -17,7 +17,10 @@ class ExtractBlend(publish.Extractor): # Define extract output file path stagingdir = self.staging_dir(instance) - filename = f"{instance.name}.blend" + asset_name = instance.data["assetEntity"]["name"] + subset = instance.data["subset"] + instance_name = f"{asset_name}_{subset}" + filename = f"{instance_name}.blend" filepath = os.path.join(stagingdir, filename) # Perform extraction @@ -52,5 +55,5 @@ class ExtractBlend(publish.Extractor): } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s", - instance.name, representation) + self.log.info( + f"Extracted instance '{instance_name}' to: {representation}") diff --git a/openpype/hosts/blender/plugins/publish/extract_blend_animation.py b/openpype/hosts/blender/plugins/publish/extract_blend_animation.py index 661cecce81..3d36ee7ec3 100644 --- a/openpype/hosts/blender/plugins/publish/extract_blend_animation.py +++ b/openpype/hosts/blender/plugins/publish/extract_blend_animation.py @@ -17,7 +17,10 @@ class ExtractBlendAnimation(publish.Extractor): # Define extract output file path stagingdir = self.staging_dir(instance) - filename = f"{instance.name}.blend" + asset_name = instance.data["assetEntity"]["name"] + subset = instance.data["subset"] + instance_name = f"{asset_name}_{subset}" + filename = f"{instance_name}.blend" filepath = os.path.join(stagingdir, filename) # Perform extraction @@ -50,5 +53,5 @@ class ExtractBlendAnimation(publish.Extractor): } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s", - instance.name, representation) + self.log.info( + f"Extracted instance '{instance_name}' to: {representation}") diff --git a/openpype/hosts/blender/plugins/publish/extract_camera_abc.py b/openpype/hosts/blender/plugins/publish/extract_camera_abc.py index 5916564ac0..b6b38b41ff 100644 --- a/openpype/hosts/blender/plugins/publish/extract_camera_abc.py +++ b/openpype/hosts/blender/plugins/publish/extract_camera_abc.py @@ -18,7 +18,10 @@ class ExtractCameraABC(publish.Extractor): def process(self, instance): # Define extract output file path stagingdir = self.staging_dir(instance) - filename = f"{instance.name}.abc" + asset_name = instance.data["assetEntity"]["name"] + subset = instance.data["subset"] + instance_name = f"{asset_name}_{subset}" + filename = f"{instance_name}.abc" filepath = os.path.join(stagingdir, filename) # Perform extraction @@ -64,5 +67,5 @@ class ExtractCameraABC(publish.Extractor): } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s", - instance.name, representation) + self.log.info( + f"Extracted instance '{instance_name}' to: {representation}") diff --git a/openpype/hosts/blender/plugins/publish/extract_camera_fbx.py b/openpype/hosts/blender/plugins/publish/extract_camera_fbx.py index a541f5b375..be9f178d1b 100644 --- a/openpype/hosts/blender/plugins/publish/extract_camera_fbx.py +++ b/openpype/hosts/blender/plugins/publish/extract_camera_fbx.py @@ -17,7 +17,10 @@ class ExtractCamera(publish.Extractor): def process(self, instance): # Define extract output file path stagingdir = self.staging_dir(instance) - filename = f"{instance.name}.fbx" + asset_name = instance.data["assetEntity"]["name"] + subset = instance.data["subset"] + instance_name = f"{asset_name}_{subset}" + filename = f"{instance_name}.fbx" filepath = os.path.join(stagingdir, filename) # Perform extraction @@ -73,5 +76,5 @@ class ExtractCamera(publish.Extractor): } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s", - instance.name, representation) + self.log.info( + f"Extracted instance '{instance_name}' to: {representation}") diff --git a/openpype/hosts/blender/plugins/publish/extract_fbx.py b/openpype/hosts/blender/plugins/publish/extract_fbx.py index f2ce117dcd..c21dc35ff6 100644 --- a/openpype/hosts/blender/plugins/publish/extract_fbx.py +++ b/openpype/hosts/blender/plugins/publish/extract_fbx.py @@ -18,7 +18,10 @@ class ExtractFBX(publish.Extractor): def process(self, instance): # Define extract output file path stagingdir = self.staging_dir(instance) - filename = f"{instance.name}.fbx" + asset_name = instance.data["assetEntity"]["name"] + subset = instance.data["subset"] + instance_name = f"{asset_name}_{subset}" + filename = f"{instance_name}.fbx" filepath = os.path.join(stagingdir, filename) # Perform extraction @@ -84,5 +87,5 @@ class ExtractFBX(publish.Extractor): } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s", - instance.name, representation) + self.log.info( + f"Extracted instance '{instance_name}' to: {representation}") diff --git a/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py b/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py index 5fe5931e65..ed4e7ecc6a 100644 --- a/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py @@ -86,7 +86,10 @@ class ExtractAnimationFBX(publish.Extractor): asset_group.select_set(True) armature.select_set(True) - fbx_filename = f"{instance.name}_{armature.name}.fbx" + asset_name = instance.data["assetEntity"]["name"] + subset = instance.data["subset"] + instance_name = f"{asset_name}_{subset}" + fbx_filename = f"{instance_name}_{armature.name}.fbx" filepath = os.path.join(stagingdir, fbx_filename) override = plugin.create_blender_context( @@ -119,7 +122,7 @@ class ExtractAnimationFBX(publish.Extractor): pair[1].user_clear() bpy.data.actions.remove(pair[1]) - json_filename = f"{instance.name}.json" + json_filename = f"{instance_name}.json" json_path = os.path.join(stagingdir, json_filename) json_dict = { @@ -158,5 +161,5 @@ class ExtractAnimationFBX(publish.Extractor): instance.data["representations"].append(fbx_representation) instance.data["representations"].append(json_representation) - self.log.info("Extracted instance '{}' to: {}".format( - instance.name, fbx_representation)) + self.log.info( + f"Extracted instance '{instance_name}' to: {fbx_representation}") diff --git a/openpype/hosts/blender/plugins/publish/extract_layout.py b/openpype/hosts/blender/plugins/publish/extract_layout.py index 05f86b8370..8e820ee84e 100644 --- a/openpype/hosts/blender/plugins/publish/extract_layout.py +++ b/openpype/hosts/blender/plugins/publish/extract_layout.py @@ -212,7 +212,11 @@ class ExtractLayout(publish.Extractor): json_data.append(json_element) - json_filename = "{}.json".format(instance.name) + asset_name = instance.data["assetEntity"]["name"] + subset = instance.data["subset"] + instance_name = f"{asset_name}_{subset}" + json_filename = f"{instance_name}.json" + json_path = os.path.join(stagingdir, json_filename) with open(json_path, "w+") as file: @@ -245,5 +249,5 @@ class ExtractLayout(publish.Extractor): } instance.data["representations"].append(fbx_representation) - self.log.info("Extracted instance '%s' to: %s", - instance.name, json_representation) + self.log.info( + f"Extracted instance '{instance_name}' to: {json_representation}") diff --git a/openpype/hosts/blender/plugins/publish/extract_playblast.py b/openpype/hosts/blender/plugins/publish/extract_playblast.py index b0099cce85..805aacc5f4 100644 --- a/openpype/hosts/blender/plugins/publish/extract_playblast.py +++ b/openpype/hosts/blender/plugins/publish/extract_playblast.py @@ -50,7 +50,10 @@ class ExtractPlayblast(publish.Extractor): # get output path stagingdir = self.staging_dir(instance) - filename = instance.name + asset_name = instance.data["assetEntity"]["name"] + subset = instance.data["subset"] + filename = f"{asset_name}_{subset}" + path = os.path.join(stagingdir, filename) self.log.debug(f"Outputting images to {path}") diff --git a/openpype/hosts/blender/plugins/publish/extract_thumbnail.py b/openpype/hosts/blender/plugins/publish/extract_thumbnail.py index 52e5d98fc4..e8a9c68dd1 100644 --- a/openpype/hosts/blender/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/blender/plugins/publish/extract_thumbnail.py @@ -27,7 +27,10 @@ class ExtractThumbnail(publish.Extractor): self.log.debug("Extracting capture..") stagingdir = self.staging_dir(instance) - filename = instance.name + asset_name = instance.data["assetEntity"]["name"] + subset = instance.data["subset"] + filename = f"{asset_name}_{subset}" + path = os.path.join(stagingdir, filename) self.log.debug(f"Outputting images to {path}") From e32263916c4856d3d65c4b1c31aeeeaf3047f018 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 7 Nov 2023 17:26:29 +0100 Subject: [PATCH 087/327] traypublisher: label of instances with folder path --- .../traypublisher/plugins/create/create_editorial.py | 12 ++++++++---- .../plugins/publish/collect_shot_instances.py | 4 ++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index 8c5083bcb2..128010cef9 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -606,19 +606,23 @@ or updating already created. Publishing will create OTIO file. Returns: str: label string """ - shot_name = instance_data["shotName"] + if AYON_SERVER_ENABLED: + asset_name = instance_data["creator_attributes"]["folderPath"] + else: + asset_name = instance_data["creator_attributes"]["shotName"] + variant_name = instance_data["variant"] family = preset["family"] - # get variant name from preset or from inharitance + # get variant name from preset or from inheritance _variant_name = preset.get("variant") or variant_name # subset name subset_name = "{}{}".format( family, _variant_name.capitalize() ) - label = "{}_{}".format( - shot_name, + label = "{} {}".format( + asset_name, subset_name ) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py index 0b7e022658..e00ac64244 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py @@ -155,7 +155,7 @@ class CollectShotInstance(pyblish.api.InstancePlugin): else {} ) - name = instance.data["asset"] + asset_name = instance.data["asset"] # get handles handle_start = int(instance.data["handleStart"]) @@ -177,7 +177,7 @@ class CollectShotInstance(pyblish.api.InstancePlugin): parents = instance.data.get('parents', []) - actual = {name: in_info} + actual = {asset_name: in_info} for parent in reversed(parents): parent_name = parent["entity_name"] From 1207ef3bbbcfe1b5db9bf0b5107d6e67eb5f6c53 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 8 Nov 2023 12:00:59 +0100 Subject: [PATCH 088/327] autofix folder path on older instances --- openpype/pipeline/create/context.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 333ab25f54..e4dcedda2c 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -2255,11 +2255,11 @@ class CreateContext: if task_name: task_names_by_asset_name[asset_name].add(task_name) - asset_names = [ + asset_names = { asset_name for asset_name in task_names_by_asset_name.keys() if asset_name is not None - ] + } fields = {"name", "data.tasks"} if AYON_SERVER_ENABLED: fields |= {"data.parents"} @@ -2270,10 +2270,12 @@ class CreateContext: )) task_names_by_asset_name = {} + asset_docs_by_name = collections.defaultdict(list) for asset_doc in asset_docs: asset_name = get_asset_name_identifier(asset_doc) tasks = asset_doc.get("data", {}).get("tasks") or {} task_names_by_asset_name[asset_name] = set(tasks.keys()) + asset_docs_by_name[asset_doc["name"]].append(asset_doc) for instance in instances: if not instance.has_valid_asset or not instance.has_valid_task: @@ -2281,6 +2283,11 @@ class CreateContext: if AYON_SERVER_ENABLED: asset_name = instance["folderPath"] + if "/" not in asset_name: + asset_docs = asset_docs_by_name.get(asset_name) + if len(asset_docs) == 1: + asset_name = get_asset_name_identifier(asset_docs[0]) + instance["folderPath"] = asset_name else: asset_name = instance["asset"] From d3804564f26b3ba1a3d304195ec42b408c2b3dff Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 8 Nov 2023 12:01:20 +0100 Subject: [PATCH 089/327] do not yield same asset multiple times --- openpype/client/server/entities.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/client/server/entities.py b/openpype/client/server/entities.py index 9e86dfdd63..becf4abda3 100644 --- a/openpype/client/server/entities.py +++ b/openpype/client/server/entities.py @@ -232,10 +232,12 @@ def get_assets( else: new_asset_names.add(name) + yielded_ids = set() if folder_paths: for folder in _folders_query( project_name, con, fields, folder_paths=folder_paths, **kwargs ): + yielded_ids.add(folder["id"]) yield convert_v4_folder_to_v3(folder, project_name) if not new_asset_names: @@ -244,7 +246,9 @@ def get_assets( for folder in _folders_query( project_name, con, fields, folder_names=new_asset_names, **kwargs ): - yield convert_v4_folder_to_v3(folder, project_name) + if folder["id"] not in yielded_ids: + yielded_ids.add(folder["id"]) + yield convert_v4_folder_to_v3(folder, project_name) def get_archived_assets( From 8f1648f87a917538b8ab139f0c1af5286aa19988 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 8 Nov 2023 14:04:56 +0100 Subject: [PATCH 090/327] duplicated session 3 > session 4 --- openpype/pipeline/schema/session-4.0.json | 81 +++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 openpype/pipeline/schema/session-4.0.json diff --git a/openpype/pipeline/schema/session-4.0.json b/openpype/pipeline/schema/session-4.0.json new file mode 100644 index 0000000000..9f785939e4 --- /dev/null +++ b/openpype/pipeline/schema/session-4.0.json @@ -0,0 +1,81 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + + "title": "openpype:session-3.0", + "description": "The Avalon environment", + + "type": "object", + + "additionalProperties": true, + + "required": [ + "AVALON_PROJECT", + "AVALON_ASSET" + ], + + "properties": { + "AVALON_PROJECTS": { + "description": "Absolute path to root of project directories", + "type": "string", + "example": "/nas/projects" + }, + "AVALON_PROJECT": { + "description": "Name of project", + "type": "string", + "pattern": "^\\w*$", + "example": "Hulk" + }, + "AVALON_ASSET": { + "description": "Name of asset", + "type": "string", + "pattern": "^\\w*$", + "example": "Bruce" + }, + "AVALON_TASK": { + "description": "Name of task", + "type": "string", + "pattern": "^\\w*$", + "example": "modeling" + }, + "AVALON_APP": { + "description": "Name of host", + "type": "string", + "pattern": "^\\w*$", + "example": "maya2016" + }, + "AVALON_DB": { + "description": "Name of database", + "type": "string", + "pattern": "^\\w*$", + "example": "avalon", + "default": "avalon" + }, + "AVALON_LABEL": { + "description": "Nice name of Avalon, used in e.g. graphical user interfaces", + "type": "string", + "example": "Mindbender", + "default": "Avalon" + }, + "AVALON_TIMEOUT": { + "description": "Wherever there is a need for a timeout, this is the default value.", + "type": "string", + "pattern": "^[0-9]*$", + "default": "1000", + "example": "1000" + }, + "AVALON_INSTANCE_ID": { + "description": "Unique identifier for instances in a working file", + "type": "string", + "pattern": "^[\\w.]*$", + "default": "avalon.instance", + "example": "avalon.instance" + }, + "AVALON_CONTAINER_ID": { + "description": "Unique identifier for a loaded representation in a working file", + "type": "string", + "pattern": "^[\\w.]*$", + "default": "avalon.container", + "example": "avalon.container" + } + } +} From e85504b80c5665d6ddf73fa3b98855db7504b18c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 8 Nov 2023 14:08:03 +0100 Subject: [PATCH 091/327] use new schema in 'legacy_io' --- openpype/pipeline/legacy_io.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/legacy_io.py b/openpype/pipeline/legacy_io.py index 60fa035c22..864102dff9 100644 --- a/openpype/pipeline/legacy_io.py +++ b/openpype/pipeline/legacy_io.py @@ -30,7 +30,7 @@ def install(): session = session_data_from_environment(context_keys=True) - session["schema"] = "openpype:session-3.0" + session["schema"] = "openpype:session-4.0" try: schema.validate(session) except schema.ValidationError as e: From 2bc41f53bfa696ff6fe04bd1e840fd85f5fa48ed Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 8 Nov 2023 14:06:17 +0100 Subject: [PATCH 092/327] chnaged title of schema --- openpype/pipeline/schema/session-4.0.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/schema/session-4.0.json b/openpype/pipeline/schema/session-4.0.json index 9f785939e4..dc4791994e 100644 --- a/openpype/pipeline/schema/session-4.0.json +++ b/openpype/pipeline/schema/session-4.0.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-04/schema#", - "title": "openpype:session-3.0", + "title": "openpype:session-4.0", "description": "The Avalon environment", "type": "object", From d3fc80f9055b4c6b5d2e4d95ef6a489db465169c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 8 Nov 2023 14:06:35 +0100 Subject: [PATCH 093/327] allow forward slash in AVALON_ASSET --- openpype/pipeline/schema/session-4.0.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/schema/session-4.0.json b/openpype/pipeline/schema/session-4.0.json index dc4791994e..9610d8ec64 100644 --- a/openpype/pipeline/schema/session-4.0.json +++ b/openpype/pipeline/schema/session-4.0.json @@ -28,7 +28,7 @@ "AVALON_ASSET": { "description": "Name of asset", "type": "string", - "pattern": "^\\w*$", + "pattern": "^[\\/\\w]*$", "example": "Bruce" }, "AVALON_TASK": { From 3cade9a288db81c4eb6b8684641eafb947b5a183 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 8 Nov 2023 14:06:50 +0100 Subject: [PATCH 094/327] removed unused keys --- openpype/pipeline/schema/session-4.0.json | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/openpype/pipeline/schema/session-4.0.json b/openpype/pipeline/schema/session-4.0.json index 9610d8ec64..54a6323e8f 100644 --- a/openpype/pipeline/schema/session-4.0.json +++ b/openpype/pipeline/schema/session-4.0.json @@ -14,11 +14,6 @@ ], "properties": { - "AVALON_PROJECTS": { - "description": "Absolute path to root of project directories", - "type": "string", - "example": "/nas/projects" - }, "AVALON_PROJECT": { "description": "Name of project", "type": "string", @@ -62,20 +57,6 @@ "pattern": "^[0-9]*$", "default": "1000", "example": "1000" - }, - "AVALON_INSTANCE_ID": { - "description": "Unique identifier for instances in a working file", - "type": "string", - "pattern": "^[\\w.]*$", - "default": "avalon.instance", - "example": "avalon.instance" - }, - "AVALON_CONTAINER_ID": { - "description": "Unique identifier for a loaded representation in a working file", - "type": "string", - "pattern": "^[\\w.]*$", - "default": "avalon.container", - "example": "avalon.container" } } } From e5d3a1aeb0d9d9320eb1b201f73aab3fa383cc65 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 8 Nov 2023 14:07:06 +0100 Subject: [PATCH 095/327] 'AVALON_ASSET' is not required --- openpype/pipeline/schema/session-4.0.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/pipeline/schema/session-4.0.json b/openpype/pipeline/schema/session-4.0.json index 54a6323e8f..088156af85 100644 --- a/openpype/pipeline/schema/session-4.0.json +++ b/openpype/pipeline/schema/session-4.0.json @@ -9,8 +9,7 @@ "additionalProperties": true, "required": [ - "AVALON_PROJECT", - "AVALON_ASSET" + "AVALON_PROJECT" ], "properties": { From 389b568f6b8f356a9be4ab89496b37fa26706337 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 8 Nov 2023 14:10:06 +0100 Subject: [PATCH 096/327] removed AVALON_PROJECTS --- openpype/pipeline/context_tools.py | 6 +----- openpype/pipeline/mongodb.py | 2 -- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/openpype/pipeline/context_tools.py b/openpype/pipeline/context_tools.py index 38c80c87bb..fe46bd1558 100644 --- a/openpype/pipeline/context_tools.py +++ b/openpype/pipeline/context_tools.py @@ -88,11 +88,7 @@ def registered_root(): root = _registered_root["_"] if root: return root - - root = legacy_io.Session.get("AVALON_PROJECTS") - if root: - return os.path.normpath(root) - return "" + return {} def install_host(host): diff --git a/openpype/pipeline/mongodb.py b/openpype/pipeline/mongodb.py index 41a44c7373..c948983c3d 100644 --- a/openpype/pipeline/mongodb.py +++ b/openpype/pipeline/mongodb.py @@ -62,8 +62,6 @@ def auto_reconnect(func): SESSION_CONTEXT_KEYS = ( - # Root directory of projects on disk - "AVALON_PROJECTS", # Name of current Project "AVALON_PROJECT", # Name of current Asset From 81d054799c5761180baca44dba95a4f00a0f32de Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 8 Nov 2023 15:26:21 +0100 Subject: [PATCH 097/327] Apply suggestions from code review Co-authored-by: Roy Nieterau --- openpype/pipeline/schema/session-4.0.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/schema/session-4.0.json b/openpype/pipeline/schema/session-4.0.json index 088156af85..0dab48aa46 100644 --- a/openpype/pipeline/schema/session-4.0.json +++ b/openpype/pipeline/schema/session-4.0.json @@ -35,7 +35,7 @@ "description": "Name of host", "type": "string", "pattern": "^\\w*$", - "example": "maya2016" + "example": "maya" }, "AVALON_DB": { "description": "Name of database", @@ -47,7 +47,7 @@ "AVALON_LABEL": { "description": "Nice name of Avalon, used in e.g. graphical user interfaces", "type": "string", - "example": "Mindbender", + "example": "MyLabel", "default": "Avalon" }, "AVALON_TIMEOUT": { From 1676f37d3aa98633ff9ac8531285b850d7c5ba28 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 8 Nov 2023 16:32:33 +0100 Subject: [PATCH 098/327] change default value of registered root --- openpype/pipeline/context_tools.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/openpype/pipeline/context_tools.py b/openpype/pipeline/context_tools.py index fe46bd1558..33eb335ab9 100644 --- a/openpype/pipeline/context_tools.py +++ b/openpype/pipeline/context_tools.py @@ -43,7 +43,7 @@ from . import ( _is_installed = False _process_id = None -_registered_root = {"_": ""} +_registered_root = {"_": {}} _registered_host = {"_": None} # Keep modules manager (and it's modules) in memory # - that gives option to register modules' callbacks @@ -85,10 +85,7 @@ def register_root(path): def registered_root(): """Return currently registered root""" - root = _registered_root["_"] - if root: - return root - return {} + return _registered_root["_"] def install_host(host): From 06856644d160155dee3b378ad2326cd44babf5b9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 8 Nov 2023 17:07:48 +0100 Subject: [PATCH 099/327] modify 'registered_root' function docstring --- openpype/pipeline/context_tools.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/context_tools.py b/openpype/pipeline/context_tools.py index 33eb335ab9..71f41fd234 100644 --- a/openpype/pipeline/context_tools.py +++ b/openpype/pipeline/context_tools.py @@ -84,7 +84,21 @@ def register_root(path): def registered_root(): - """Return currently registered root""" + """Return registered roots from current project anatomy. + + Consider this does return roots only for current project and current + platforms, only if host was installer using 'install_host'. + + Deprecated: + Please use project 'Anatomy' to get roots. This function is still used + at current core functions of load logic, but that will change + in future and this function will be removed eventually. Using this + function at new places can cause problems in the future. + + Returns: + dict[str, str]: Root paths. + """ + return _registered_root["_"] From 2c0d1b5c2bd3efb9e0740b0d5324a588533459e1 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 9 Nov 2023 13:56:08 +0100 Subject: [PATCH 100/327] improving the logic for handles operation --- openpype/hosts/resolve/api/plugin.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/resolve/api/plugin.py b/openpype/hosts/resolve/api/plugin.py index 09c5a69d73..886615c7aa 100644 --- a/openpype/hosts/resolve/api/plugin.py +++ b/openpype/hosts/resolve/api/plugin.py @@ -410,10 +410,16 @@ class ClipLoader: source_duration = int(_clip_property("Frames")) # get version data frame data from db - frame_start = self.data["versionData"].get("frameStart") - frame_end = self.data["versionData"].get("frameEnd") - handle_start = self.data["versionData"].get("handleStart") - handle_end = self.data["versionData"].get("handleEnd") + version_data = self.data["versionData"] + frame_start = version_data.get("frameStart") + frame_end = version_data.get("frameEnd") + + if self.with_handles: + handle_start = version_data.get("handleStart") or 0 + handle_end = version_data.get("handleEnd") or 0 + else: + handle_start = 0 + handle_end = 0 """ There are cases where representation could be published without @@ -434,8 +440,8 @@ class ClipLoader: if ( frame_start is None or frame_end is None or - handle_start is None or - handle_end is None + handle_start is 0 or + handle_end is 0 ): # if not then rather assume that source has no handles source_with_handles = False @@ -464,12 +470,11 @@ class ClipLoader: timeline_in = int( timeline_start + self.data["assetData"]["clipIn"]) - # only exclude handles if source has no handles or # if user wants to load without handles if ( - not self.with_handles - or not source_with_handles + not self.with_handles # set by user + or not source_with_handles # result of source duration check ): source_in += handle_start source_out -= handle_end From 6fa92ebc14204c3c723a0b2a1d42ba492d10ff30 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 9 Nov 2023 14:40:33 +0100 Subject: [PATCH 101/327] resolve: clip data should be integer rather then timecode --- openpype/hosts/resolve/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/resolve/api/lib.py b/openpype/hosts/resolve/api/lib.py index aef9caca78..3866477c77 100644 --- a/openpype/hosts/resolve/api/lib.py +++ b/openpype/hosts/resolve/api/lib.py @@ -298,7 +298,7 @@ def create_timeline_item( if source_end: clip_data["endFrame"] = source_end if timecode_in: - clip_data["recordFrame"] = timecode_in + clip_data["recordFrame"] = timeline_in # add to timeline media_pool.AppendToTimeline([clip_data]) From 85bad0ae3d6f43732b9087583af2e09de55fed40 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 9 Nov 2023 14:42:30 +0100 Subject: [PATCH 102/327] hound --- openpype/hosts/resolve/api/plugin.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/resolve/api/plugin.py b/openpype/hosts/resolve/api/plugin.py index 886615c7aa..7cf5913b67 100644 --- a/openpype/hosts/resolve/api/plugin.py +++ b/openpype/hosts/resolve/api/plugin.py @@ -440,8 +440,8 @@ class ClipLoader: if ( frame_start is None or frame_end is None or - handle_start is 0 or - handle_end is 0 + handle_start == 0 or + handle_end == 0 ): # if not then rather assume that source has no handles source_with_handles = False @@ -473,8 +473,8 @@ class ClipLoader: # only exclude handles if source has no handles or # if user wants to load without handles if ( - not self.with_handles # set by user - or not source_with_handles # result of source duration check + not self.with_handles # set by user + or not source_with_handles # result of source duration check ): source_in += handle_start source_out -= handle_end From 0b8dde268708594e23d1f305a7bfbd3da99a55d9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 9 Nov 2023 15:47:14 +0100 Subject: [PATCH 103/327] resolve: creating clips with folder path also converting dict() to {} --- openpype/hosts/resolve/api/plugin.py | 29 +++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/resolve/api/plugin.py b/openpype/hosts/resolve/api/plugin.py index 49c3b484d2..197f288150 100644 --- a/openpype/hosts/resolve/api/plugin.py +++ b/openpype/hosts/resolve/api/plugin.py @@ -19,7 +19,7 @@ from .menu import load_stylesheet class CreatorWidget(QtWidgets.QDialog): # output items - items = dict() + items = {} def __init__(self, name, info, ui_inputs, parent=None): super(CreatorWidget, self).__init__(parent) @@ -101,7 +101,7 @@ class CreatorWidget(QtWidgets.QDialog): self.close() def value(self, data, new_data=None): - new_data = new_data or dict() + new_data = new_data or {} for k, v in data.items(): new_data[k] = { "target": None, @@ -290,7 +290,7 @@ class Spacer(QtWidgets.QWidget): class ClipLoader: active_bin = None - data = dict() + data = {} def __init__(self, loader_obj, context, **options): """ Initialize object @@ -588,8 +588,8 @@ class PublishClip: Returns: hiero.core.TrackItem: hiero track item object with openpype tag """ - vertical_clip_match = dict() - tag_data = dict() + vertical_clip_match = {} + tag_data = {} types = { "shot": "shot", "folder": "folder", @@ -665,15 +665,23 @@ class PublishClip: new_name = self.tag_data.pop("newClipName") if self.rename: - self.tag_data["asset"] = new_name + self.tag_data["asset_name"] = new_name else: - self.tag_data["asset"] = self.ti_name + self.tag_data["asset_name"] = self.ti_name + # AYON unique identifier + folder_path = "/{}/{}".format( + self.tag_data["hierarchy"], + self.tag_data["asset_name"] + ) + self.tag_data["folder_path"] = folder_path + + # create new name for track item if not lib.pype_marker_workflow: # create compound clip workflow lib.create_compound_clip( self.timeline_item_data, - self.tag_data["asset"], + self.tag_data["asset_name"], self.mp_folder ) @@ -765,7 +773,7 @@ class PublishClip: # increasing steps by index of rename iteration self.count_steps *= self.rename_index - hierarchy_formatting_data = dict() + hierarchy_formatting_data = {} _data = self.timeline_item_default_data.copy() if self.ui_inputs: # adding tag metadata from ui @@ -854,8 +862,7 @@ class PublishClip: "parents": self.parents, "hierarchyData": hierarchy_formatting_data, "subset": self.subset, - "family": self.subset_family, - "families": ["clip"] + "family": self.subset_family } def _convert_to_entity(self, key): From d07898a3286b139c0d48bc14fb16b1417adcd8e5 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 9 Nov 2023 15:47:50 +0100 Subject: [PATCH 104/327] resolve: collect instances with folder path --- .../plugins/publish/precollect_instances.py | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/resolve/plugins/publish/precollect_instances.py b/openpype/hosts/resolve/plugins/publish/precollect_instances.py index 8ec169ad65..58c1c85276 100644 --- a/openpype/hosts/resolve/plugins/publish/precollect_instances.py +++ b/openpype/hosts/resolve/plugins/publish/precollect_instances.py @@ -9,6 +9,7 @@ from openpype.hosts.resolve.api.lib import ( get_publish_attribute, get_otio_clip_instance_data, ) +from openpype import AYON_SERVER_ENABLED class PrecollectInstances(pyblish.api.ContextPlugin): @@ -29,7 +30,7 @@ class PrecollectInstances(pyblish.api.ContextPlugin): for timeline_item_data in selected_timeline_items: - data = dict() + data = {} timeline_item = timeline_item_data["clip"]["item"] # get pype tag data @@ -60,24 +61,25 @@ class PrecollectInstances(pyblish.api.ContextPlugin): if k not in ("id", "applieswhole", "label") }) - asset = tag_data["asset"] + if AYON_SERVER_ENABLED: + asset = tag_data["folder_path"] + else: + asset = tag_data["asset_name"] + subset = tag_data["subset"] - # insert family into families - family = tag_data["family"] - families = [str(f) for f in tag_data["families"]] - families.insert(0, str(family)) - data.update({ - "name": "{} {} {}".format(asset, subset, families), + "name": "{}_{}".format(asset, subset), + "label": "{} {}".format(asset, subset), "asset": asset, "item": timeline_item, - "families": families, "publish": get_publish_attribute(timeline_item), "fps": context.data["fps"], "handleStart": handle_start, "handleEnd": handle_end, - "newAssetPublishing": True + "newAssetPublishing": True, + "families": ["clip"], + "isEditorial": True }) # otio clip data @@ -135,7 +137,8 @@ class PrecollectInstances(pyblish.api.ContextPlugin): family = "shot" data.update({ - "name": "{} {} {}".format(asset, subset, family), + "name": "{}_{}".format(asset, subset), + "label": "{} {}".format(asset, subset), "subset": subset, "asset": asset, "family": family, From 42c61fa348ca6036cef594f0f6fe6932b3c41b5a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Nov 2023 16:22:17 +0100 Subject: [PATCH 105/327] do not check for "/" if asset name is empty --- openpype/pipeline/create/context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index e4dcedda2c..f32477dfb7 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -2283,7 +2283,7 @@ class CreateContext: if AYON_SERVER_ENABLED: asset_name = instance["folderPath"] - if "/" not in asset_name: + if asset_name and "/" not in asset_name: asset_docs = asset_docs_by_name.get(asset_name) if len(asset_docs) == 1: asset_name = get_asset_name_identifier(asset_docs[0]) From 573da36d4b12d58ab84110bbc46105be3f4e9e90 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 9 Nov 2023 16:32:20 +0100 Subject: [PATCH 106/327] workfile instance with support of folder path --- .../plugins/publish/extract_workfile.py | 1 + .../plugins/publish/precollect_workfile.py | 18 +++++++++--------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/resolve/plugins/publish/extract_workfile.py b/openpype/hosts/resolve/plugins/publish/extract_workfile.py index 535f879b58..db63487405 100644 --- a/openpype/hosts/resolve/plugins/publish/extract_workfile.py +++ b/openpype/hosts/resolve/plugins/publish/extract_workfile.py @@ -26,6 +26,7 @@ class ExtractWorkfile(publish.Extractor): resolve_workfile_ext = ".drp" drp_file_name = name + resolve_workfile_ext + drp_file_path = os.path.normpath( os.path.join(staging_dir, drp_file_name)) diff --git a/openpype/hosts/resolve/plugins/publish/precollect_workfile.py b/openpype/hosts/resolve/plugins/publish/precollect_workfile.py index 39c28e29f5..ccc5fd86ff 100644 --- a/openpype/hosts/resolve/plugins/publish/precollect_workfile.py +++ b/openpype/hosts/resolve/plugins/publish/precollect_workfile.py @@ -3,6 +3,7 @@ from pprint import pformat from openpype import AYON_SERVER_ENABLED from openpype.pipeline import get_current_asset_name + from openpype.hosts.resolve import api as rapi from openpype.hosts.resolve.otio import davinci_export @@ -14,14 +15,12 @@ class PrecollectWorkfile(pyblish.api.ContextPlugin): order = pyblish.api.CollectorOrder - 0.5 def process(self, context): - current_asset = get_current_asset_name() - if AYON_SERVER_ENABLED: - # AYON compatibility split name and use last piece - asset_name = current_asset.split("/")[-1] - else: - asset_name = current_asset + current_asset_name = asset_name = get_current_asset_name() - subset = "workfile" + if AYON_SERVER_ENABLED: + asset_name = current_asset_name.split("/")[-1] + + subset = "workfileMain" project = rapi.get_current_project() fps = project.GetSetting("timelineFrameRate") video_tracks = rapi.get_video_track_names() @@ -31,8 +30,9 @@ class PrecollectWorkfile(pyblish.api.ContextPlugin): instance_data = { "name": "{}_{}".format(asset_name, subset), - "asset": current_asset, - "subset": "{}{}".format(asset_name, subset.capitalize()), + "label": "{} {}".format(current_asset_name, subset), + "asset": current_asset_name, + "subset": subset, "item": project, "family": "workfile", "families": [] From eaf3e1d3f8f05fc04f89329aaa9354a9553797d5 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 9 Nov 2023 17:07:45 +0100 Subject: [PATCH 107/327] :bug: fix broken imports maya specific imports were moved to specific methods but not in all cases by #5775. This is just quickly restoring functionality without questioning that decision. --- openpype/hosts/maya/api/lib_rendersettings.py | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/api/lib_rendersettings.py b/openpype/hosts/maya/api/lib_rendersettings.py index 20264c2cdf..74ec3deea4 100644 --- a/openpype/hosts/maya/api/lib_rendersettings.py +++ b/openpype/hosts/maya/api/lib_rendersettings.py @@ -62,8 +62,8 @@ class RenderSettings(object): def set_default_renderer_settings(self, renderer=None): """Set basic settings based on renderer.""" # Not all hosts can import this module. - from maya import cmds - import maya.mel as mel + from maya import cmds # noqa: F401 + import maya.mel as mel # noqa: F401 if not renderer: renderer = cmds.getAttr( @@ -118,6 +118,10 @@ class RenderSettings(object): """Sets settings for Arnold.""" from mtoa.core import createOptions # noqa from mtoa.aovs import AOVInterface # noqa + # Not all hosts can import this module. + from maya import cmds # noqa: F401 + import maya.mel as mel # noqa: F401 + createOptions() render_settings = self._project_settings["maya"]["RenderSettings"] arnold_render_presets = render_settings["arnold_renderer"] # noqa @@ -164,6 +168,10 @@ class RenderSettings(object): def _set_redshift_settings(self, width, height): """Sets settings for Redshift.""" + # Not all hosts can import this module. + from maya import cmds # noqa: F401 + import maya.mel as mel # noqa: F401 + render_settings = self._project_settings["maya"]["RenderSettings"] redshift_render_presets = render_settings["redshift_renderer"] @@ -216,6 +224,10 @@ class RenderSettings(object): def _set_renderman_settings(self, width, height, aov_separator): """Sets settings for Renderman""" + # Not all hosts can import this module. + from maya import cmds # noqa: F401 + import maya.mel as mel # noqa: F401 + rman_render_presets = ( self._project_settings ["maya"] @@ -277,6 +289,11 @@ class RenderSettings(object): def _set_vray_settings(self, aov_separator, width, height): # type: (str, int, int) -> None """Sets important settings for Vray.""" + # Not all hosts can import this module. + from maya import cmds # noqa: F401 + import maya.mel as mel # noqa: F401 + + settings = cmds.ls(type="VRaySettingsNode") node = settings[0] if settings else cmds.createNode("VRaySettingsNode") render_settings = self._project_settings["maya"]["RenderSettings"] @@ -349,6 +366,10 @@ class RenderSettings(object): @staticmethod def _set_global_output_settings(): + # Not all hosts can import this module. + from maya import cmds # noqa: F401 + import maya.mel as mel # noqa: F401 + # enable animation cmds.setAttr("defaultRenderGlobals.outFormatControl", 0) cmds.setAttr("defaultRenderGlobals.animation", 1) @@ -356,6 +377,10 @@ class RenderSettings(object): cmds.setAttr("defaultRenderGlobals.extensionPadding", 4) def _additional_attribs_setter(self, additional_attribs): + # Not all hosts can import this module. + from maya import cmds # noqa: F401 + import maya.mel as mel # noqa: F401 + for item in additional_attribs: attribute, value = item attribute = str(attribute) # ensure str conversion from settings From b497876c51466103a60691f5106d2663063be3c8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Nov 2023 14:33:03 +0100 Subject: [PATCH 108/327] Fix houdini workfile creator --- openpype/hosts/houdini/plugins/create/create_workfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/create/create_workfile.py b/openpype/hosts/houdini/plugins/create/create_workfile.py index f8ee68ebc9..850f5c994e 100644 --- a/openpype/hosts/houdini/plugins/create/create_workfile.py +++ b/openpype/hosts/houdini/plugins/create/create_workfile.py @@ -48,7 +48,7 @@ class CreateWorkfile(plugin.HoudiniCreatorBase, AutoCreator): "variant": variant } if AYON_SERVER_ENABLED: - data["folderpath"] = asset_name + data["folderPath"] = asset_name else: data["asset"] = asset_name From 8c9463aa2a3a91ced4cdb4792e03f06f7a0119b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Wed, 15 Nov 2023 10:25:11 +0100 Subject: [PATCH 109/327] Update openpype/hosts/traypublisher/plugins/create/create_editorial.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/traypublisher/plugins/create/create_editorial.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index 128010cef9..03b36dd232 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -727,8 +727,6 @@ or updating already created. Publishing will create OTIO file. }) base_instance_data["asset"] = parent_asset_name - - # add creator attributes to shared instance data base_instance_data["creator_attributes"] = creator_attributes # add hierarchy shot metadata From 59c8e05ffbb5ebdf9c74892b08fc3ba27639cc29 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Nov 2023 11:39:16 +0100 Subject: [PATCH 110/327] use 'folderPath' in create widget instead of asset --- openpype/tools/publisher/widgets/create_widget.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openpype/tools/publisher/widgets/create_widget.py b/openpype/tools/publisher/widgets/create_widget.py index 64fed1d70c..73dcae51a5 100644 --- a/openpype/tools/publisher/widgets/create_widget.py +++ b/openpype/tools/publisher/widgets/create_widget.py @@ -816,8 +816,13 @@ class CreateWidget(QtWidgets.QWidget): # Where to define these data? # - what data show be stored? + if AYON_SERVER_ENABLED: + asset_key = "folderPath" + else: + asset_key = "asset" + instance_data = { - "asset": asset_name, + asset_key: asset_name, "task": task_name, "variant": variant, "family": family From c326a8bfd4e9032162bde742d13da628a6f70d1c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 15 Nov 2023 11:44:28 +0100 Subject: [PATCH 111/327] in ayon folderPath should be on creator instance data --- .../hosts/traypublisher/plugins/create/create_editorial.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index c3453f1eae..198e05d593 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -224,8 +224,10 @@ or updating already created. Publishing will create OTIO file. i["family"] for i in self._creator_settings["family_presets"] ] } - # Create otio editorial instance - asset_name = instance_data["asset"] + if AYON_SERVER_ENABLED: + asset_name = instance_data["folderPath"] + else: + asset_name = instance_data["asset"] asset_doc = get_asset_by_name(self.project_name, asset_name) From 89dcb9452b9537701168a6a4a457e025fbd2ef63 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Nov 2023 12:04:59 +0100 Subject: [PATCH 112/327] fix '_check_existing' method in create hda --- openpype/hosts/houdini/api/plugin.py | 14 +++++++++++--- .../hosts/houdini/plugins/create/create_hda.py | 9 +++++---- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/houdini/api/plugin.py b/openpype/hosts/houdini/api/plugin.py index 72565f7211..e162d0e461 100644 --- a/openpype/hosts/houdini/api/plugin.py +++ b/openpype/hosts/houdini/api/plugin.py @@ -6,6 +6,8 @@ from abc import ( ) import six import hou + +from openpype import AYON_SERVER_ENABLED from openpype.pipeline import ( CreatorError, LegacyCreator, @@ -142,12 +144,13 @@ class HoudiniCreatorBase(object): @staticmethod def create_instance_node( - node_name, parent, - node_type="geometry"): + asset_name, node_name, parent, node_type="geometry" + ): # type: (str, str, str) -> hou.Node """Create node representing instance. Arguments: + asset_name (str): Asset name. node_name (str): Name of the new node. parent (str): Name of the parent node. node_type (str, optional): Type of the node. @@ -182,8 +185,13 @@ class HoudiniCreator(NewCreator, HoudiniCreatorBase): if node_type is None: node_type = "geometry" + if AYON_SERVER_ENABLED: + asset_name = instance_data["folderPath"] + else: + asset_name = instance_data["asset"] + instance_node = self.create_instance_node( - subset_name, "/out", node_type) + asset_name, subset_name, "/out", node_type) self.customize_node_look(instance_node) diff --git a/openpype/hosts/houdini/plugins/create/create_hda.py b/openpype/hosts/houdini/plugins/create/create_hda.py index ac075d2072..f670b55eb6 100644 --- a/openpype/hosts/houdini/plugins/create/create_hda.py +++ b/openpype/hosts/houdini/plugins/create/create_hda.py @@ -17,13 +17,13 @@ class CreateHDA(plugin.HoudiniCreator): icon = "gears" maintain_selection = False - def _check_existing(self, subset_name): + def _check_existing(self, asset_name, subset_name): # type: (str) -> bool """Check if existing subset name versions already exists.""" # Get all subsets of the current asset project_name = self.project_name asset_doc = get_asset_by_name( - project_name, self.data["asset"], fields=["_id"] + project_name, asset_name, fields=["_id"] ) subset_docs = get_subsets( project_name, asset_ids=[asset_doc["_id"]], fields=["name"] @@ -35,7 +35,8 @@ class CreateHDA(plugin.HoudiniCreator): return subset_name.lower() in existing_subset_names_low def create_instance_node( - self, node_name, parent, node_type="geometry"): + self, asset_name, node_name, parent, node_type="geometry" + ): parent_node = hou.node("/obj") if self.selected_nodes: @@ -61,7 +62,7 @@ class CreateHDA(plugin.HoudiniCreator): hda_file_name="$HIP/{}.hda".format(node_name) ) hda_node.layoutChildren() - elif self._check_existing(node_name): + elif self._check_existing(asset_name, node_name): raise plugin.OpenPypeCreatorError( ("subset {} is already published with different HDA" "definition.").format(node_name)) From da8285d2cea1413ab45415dbe3a3c6b3a2aafd6b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Nov 2023 12:25:06 +0100 Subject: [PATCH 113/327] fix create multishot layout --- openpype/hosts/maya/plugins/create/create_multishot_layout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/create/create_multishot_layout.py b/openpype/hosts/maya/plugins/create/create_multishot_layout.py index 7cd3fdbd17..8f5c423202 100644 --- a/openpype/hosts/maya/plugins/create/create_multishot_layout.py +++ b/openpype/hosts/maya/plugins/create/create_multishot_layout.py @@ -158,7 +158,7 @@ class CreateMultishotLayout(plugin.MayaCreator): # Create layout instance by the layout creator instance_data = { - "asset": shot["name"], + "folderPath": shot["path"], "variant": layout_creator.get_default_variant() } if layout_task: From 43a50ecb2afc164d3243c90a91ca46bbf8c8025e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Nov 2023 12:28:19 +0100 Subject: [PATCH 114/327] fix folder path fix in validate instance in context --- .../maya/plugins/publish/validate_instance_in_context.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_instance_in_context.py b/openpype/hosts/maya/plugins/publish/validate_instance_in_context.py index edfb002278..4222e63898 100644 --- a/openpype/hosts/maya/plugins/publish/validate_instance_in_context.py +++ b/openpype/hosts/maya/plugins/publish/validate_instance_in_context.py @@ -3,6 +3,7 @@ from __future__ import absolute_import import pyblish.api +from openpype import AYON_SERVER_ENABLED import openpype.hosts.maya.api.action from openpype.pipeline.publish import ( RepairAction, @@ -66,8 +67,12 @@ class ValidateInstanceInContext(pyblish.api.InstancePlugin, def repair(cls, instance): context_asset = cls.get_context_asset(instance) instance_node = instance.data["instance_node"] + if AYON_SERVER_ENABLED: + asset_name_attr = "folderPath" + else: + asset_name_attr = "asset" cmds.setAttr( - "{}.asset".format(instance_node), + "{}.{}".format(instance_node, asset_name_attr), context_asset, type="string" ) From 6fece8f6eacdc31a57bfaebe6b8075baa038ffea Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Nov 2023 18:29:59 +0100 Subject: [PATCH 115/327] fix workfile create plugin in blender --- .../blender/plugins/create/create_workfile.py | 34 ++++++++++++++----- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/blender/plugins/create/create_workfile.py b/openpype/hosts/blender/plugins/create/create_workfile.py index 9245434766..ceec3e0552 100644 --- a/openpype/hosts/blender/plugins/create/create_workfile.py +++ b/openpype/hosts/blender/plugins/create/create_workfile.py @@ -1,5 +1,6 @@ import bpy +from openpype import AYON_SERVER_ENABLED from openpype.pipeline import CreatedInstance, AutoCreator from openpype.client import get_asset_by_name from openpype.hosts.blender.api.plugin import BaseCreator @@ -24,7 +25,7 @@ class CreateWorkfile(BaseCreator, AutoCreator): def create(self): """Create workfile instances.""" - current_instance = next( + existing_instance = next( ( instance for instance in self.create_context.instances if instance.creator_identifier == self.identifier @@ -37,16 +38,27 @@ class CreateWorkfile(BaseCreator, AutoCreator): task_name = self.create_context.get_current_task_name() host_name = self.create_context.host_name - if not current_instance: + existing_asset_name = None + if existing_instance is not None: + if AYON_SERVER_ENABLED: + existing_asset_name = existing_instance.get("folderPath") + + if existing_asset_name is None: + existing_asset_name = existing_instance["asset"] + + if not existing_instance: asset_doc = get_asset_by_name(project_name, asset_name) subset_name = self.get_subset_name( task_name, task_name, asset_doc, project_name, host_name ) data = { - "asset": asset_name, "task": task_name, "variant": task_name, } + if AYON_SERVER_ENABLED: + data["folderPath"] = asset_name + else: + data["asset"] = asset_name data.update( self.get_dynamic_data( task_name, @@ -54,7 +66,7 @@ class CreateWorkfile(BaseCreator, AutoCreator): asset_doc, project_name, host_name, - current_instance, + existing_instance, ) ) self.log.info("Auto-creating workfile instance...") @@ -65,17 +77,21 @@ class CreateWorkfile(BaseCreator, AutoCreator): current_instance.transient_data["instance_node"] = instance_node self._add_instance_to_context(current_instance) elif ( - current_instance["asset"] != asset_name - or current_instance["task"] != task_name + existing_asset_name != asset_name + or existing_instance["task"] != task_name ): # Update instance context if it's different asset_doc = get_asset_by_name(project_name, asset_name) subset_name = self.get_subset_name( task_name, task_name, asset_doc, project_name, host_name ) - current_instance["asset"] = asset_name - current_instance["task"] = task_name - current_instance["subset"] = subset_name + if AYON_SERVER_ENABLED: + existing_instance["folderPath"] = asset_name + else: + existing_instance["asset"] = asset_name + + existing_instance["task"] = task_name + existing_instance["subset"] = subset_name def collect_instances(self): From 4cd7d72dce91c88cf7e1aa36f44e439606eb227b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Nov 2023 11:12:00 +0100 Subject: [PATCH 116/327] use folder path in AYON mode --- openpype/hosts/nuke/api/lib.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 8b1ba0ab0d..51096b5dcc 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -13,6 +13,7 @@ from collections import OrderedDict import nuke from qtpy import QtCore, QtWidgets +from openpype import AYON_SERVER_ENABLED from openpype.client import ( get_project, get_asset_by_name, @@ -1107,7 +1108,9 @@ def format_anatomy(data): Return: path (str) ''' - anatomy = Anatomy() + + project_name = get_current_project_name() + anatomy = Anatomy(project_name) log.debug("__ anatomy.templates: {}".format(anatomy.templates)) padding = None @@ -1125,8 +1128,10 @@ def format_anatomy(data): file = script_name() data["version"] = get_version_from_path(file) - project_name = anatomy.project_name - asset_name = data["asset"] + if AYON_SERVER_ENABLED: + asset_name = data["folderPath"] + else: + asset_name = data["asset"] task_name = data["task"] host_name = get_current_host_name() context_data = get_template_data_with_names( From e0790a1cb1d5660ea887b387d44e422575111c7a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Nov 2023 11:26:54 +0100 Subject: [PATCH 117/327] fix create logic in blender --- openpype/hosts/blender/api/plugin.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 8d33187da3..568d8f6695 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -6,11 +6,11 @@ from typing import Dict, List, Optional import bpy +from openpype import AYON_SERVER_ENABLED from openpype.pipeline import ( Creator, CreatedInstance, LoaderPlugin, - get_current_task_name, ) from openpype.lib import BoolDef @@ -225,7 +225,12 @@ class BaseCreator(Creator): bpy.context.scene.collection.children.link(instances) # Create asset group - name = prepare_scene_name(instance_data["asset"], subset_name) + if AYON_SERVER_ENABLED: + asset_name = instance_data["folderPath"] + else: + asset_name = instance_data["asset"] + + name = prepare_scene_name(asset_name, subset_name) if self.create_as_asset_group: # Create instance as empty instance_node = bpy.data.objects.new(name=name, object_data=None) @@ -281,7 +286,14 @@ class BaseCreator(Creator): Args: update_list(List[UpdateData]): Changed instances - and their changes, as a list of tuples.""" + and their changes, as a list of tuples. + """ + + if AYON_SERVER_ENABLED: + asset_name_key = "folderPath" + else: + asset_name_key = "asset" + for created_instance, changes in update_list: data = created_instance.data_to_store() node = created_instance.transient_data["instance_node"] @@ -295,11 +307,12 @@ class BaseCreator(Creator): # Rename the instance node in the scene if subset or asset changed if ( - "subset" in changes.changed_keys - or "asset" in changes.changed_keys + "subset" in changes.changed_keys + or asset_name_key in changes.changed_keys ): + asset_name = data[asset_name_key] name = prepare_scene_name( - asset=data["asset"], subset=data["subset"] + asset=asset_name, subset=data["subset"] ) node.name = name From d9c8950f346886d25d09ff5e8fa6b4d4a9a49fde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 16 Nov 2023 16:26:28 +0100 Subject: [PATCH 118/327] :bug: fix asset path use in maya skeletal mesh --- .../maya/plugins/create/create_unreal_skeletalmesh.py | 2 +- .../plugins/publish/extract_unreal_skeletalmesh_abc.py | 10 ---------- .../plugins/publish/extract_unreal_skeletalmesh_fbx.py | 4 ++++ 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_unreal_skeletalmesh.py b/openpype/hosts/maya/plugins/create/create_unreal_skeletalmesh.py index 3c9a79156a..b4151bac99 100644 --- a/openpype/hosts/maya/plugins/create/create_unreal_skeletalmesh.py +++ b/openpype/hosts/maya/plugins/create/create_unreal_skeletalmesh.py @@ -51,7 +51,7 @@ class CreateUnrealSkeletalMesh(plugin.MayaCreator): # We reorganize the geometry that was originally added into the # set into either 'joints_SET' or 'geometry_SET' based on the # joint_hints from project settings - members = cmds.sets(instance_node, query=True) + members = cmds.sets(instance_node, query=True) or [] cmds.sets(clear=instance_node) geometry_set = cmds.sets(name="geometry_SET", empty=True) diff --git a/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh_abc.py b/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh_abc.py index 9c2f55a1ef..780ed2377c 100644 --- a/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh_abc.py +++ b/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh_abc.py @@ -13,16 +13,6 @@ from openpype.hosts.maya.api.lib import ( ) -@contextmanager -def renamed(original_name, renamed_name): - # type: (str, str) -> None - try: - cmds.rename(original_name, renamed_name) - yield - finally: - cmds.rename(renamed_name, original_name) - - class ExtractUnrealSkeletalMeshAbc(publish.Extractor): """Extract Unreal Skeletal Mesh as FBX from Maya. """ diff --git a/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh_fbx.py b/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh_fbx.py index 96175a07d7..4b36134694 100644 --- a/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh_fbx.py +++ b/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh_fbx.py @@ -62,6 +62,10 @@ class ExtractUnrealSkeletalMeshFbx(publish.Extractor): original_parent = to_extract[0].split("|")[1] parent_node = instance.data.get("asset") + # this needs to be done for AYON + # WARNING: since AYON supports duplicity of asset names, + # this needs to be refactored throughout the pipeline. + parent_node = parent_node.split("/")[-1] renamed_to_extract = [] for node in to_extract: From c0926104d59104cbc79d19296da70844ccc3fa21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 16 Nov 2023 18:29:31 +0100 Subject: [PATCH 119/327] :bug: handle empty materials list --- openpype/hosts/maya/plugins/publish/collect_look.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_look.py b/openpype/hosts/maya/plugins/publish/collect_look.py index db042963c6..16ae20e0ae 100644 --- a/openpype/hosts/maya/plugins/publish/collect_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_look.py @@ -367,7 +367,11 @@ class CollectLook(pyblish.api.InstancePlugin): self.log.debug("Found the following sets:\n{}".format(look_sets)) # Get the entire node chain of the look sets # history = cmds.listHistory(look_sets, allConnections=True) - history = cmds.listHistory(materials, allConnections=True) + # if materials list is empty, listHistory() will crash with + # RuntimeError + history = [] + if materials: + history = cmds.listHistory(materials, allConnections=True) # Since we retrieved history only of the connected materials # connected to the look sets above we now add direct history From f4d6199b8db5eed96770996c7aca111319628033 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 17 Nov 2023 12:33:10 +0100 Subject: [PATCH 120/327] fix 'create' from 'CreateContext' --- openpype/pipeline/create/context.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index f32477dfb7..683699a0d1 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -2020,8 +2020,14 @@ class CreateContext: project_name, self.host_name ) + asset_name = get_asset_name_identifier(asset_doc) + if AYON_SERVER_ENABLED: + asset_name_key = "folderPath" + else: + asset_name_key = "asset" + instance_data = { - "asset": asset_doc["name"], + asset_name_key: asset_name, "task": task_name, "family": creator.family, "variant": variant From b0ab09201a61167c8193cf36270cfd622f3e1868 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 20 Nov 2023 16:48:03 +0800 Subject: [PATCH 121/327] add optional validator to check verbosity level in Arnold and plugin info for arnold verbose --- .../validate_arnold_verbosity_level.py | 42 +++++++++++++++++++ .../plugins/publish/submit_maya_deadline.py | 12 +++++- openpype/pipeline/publish/lib.py | 2 +- 3 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 openpype/hosts/maya/plugins/publish/validate_arnold_verbosity_level.py diff --git a/openpype/hosts/maya/plugins/publish/validate_arnold_verbosity_level.py b/openpype/hosts/maya/plugins/publish/validate_arnold_verbosity_level.py new file mode 100644 index 0000000000..b4a08e6bb4 --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/validate_arnold_verbosity_level.py @@ -0,0 +1,42 @@ +import pyblish.api +from openpype.pipeline import ( + PublishValidationError, + OptionalPyblishPluginMixin +) +from maya import cmds +from openpype.pipeline.publish import RepairAction + + +class ValidateArnoldVerbosityLevel(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): + """Validate Arnold Verbosity Level For Deadline Submission""" + + order = pyblish.api.ValidatorOrder + families = ["renderlayer"] + hosts = ["maya"] + label = "Validate Arnold Verbosity Level" + actions = [RepairAction] + optional = True + + def process(self, instance): + if not self.is_active(instance.data): + return + if instance.data["renderer"] != "arnold": + self.log.debug("The renderer for deadline submission is not Arnold." + " Skipping Validate Arnold Verbosity Level.") + return + current_verbosity_level = cmds.getAttr( + "defaultArnoldRenderOptions.log_verbosity") + + if not current_verbosity_level >= 3: + report = ( + "Arnold verbosity level has invalid value(s).\n\n" + "It must be always greater than 3.\n\n" + "You can use repair action to set the correct value\n" + ) + raise PublishValidationError( + report, title="Invalid Value(s) for Arnold Verbosity Level") + + @classmethod + def repair(cls, instance): + return cmds.setAttr("defaultArnoldRenderOptions.log_verbosity", 3) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 7d532923ff..cdaf329ef2 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -97,6 +97,7 @@ class VRayPluginInfo(object): @attr.s class ArnoldPluginInfo(object): ArnoldFile = attr.ib(default=None) + ArnoldVerbose = attr.ib(default=2) class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, @@ -281,6 +282,10 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, plugin_payload = attr.asdict(plugin_info) + if instance.data["renderer"] == "arnold": + plugin_payload["ArnoldVerbose"] = cmds.getAttr( + "defaultArnoldRenderOptions.log_verbosity") + # Patching with pluginInfo from settings for key, value in self.pluginInfo.items(): plugin_payload[key] = value @@ -648,7 +653,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, return job_info, attr.asdict(plugin_info) def _get_arnold_render_payload(self, data): - + from maya import cmds # Job Info job_info = copy.deepcopy(self.job_info) job_info.Name = self._job_info_label("Render") @@ -658,9 +663,12 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, # Plugin Info ass_file, _ = os.path.splitext(data["output_filename_0"]) ass_filepath = ass_file + ".ass" + current_verbosity_level = cmds.getAttr( + "defaultArnoldRenderOptions.log_verbosity") plugin_info = ArnoldPluginInfo( - ArnoldFile=ass_filepath + ArnoldFile=ass_filepath, + ArnoldVerbose=current_verbosity_level ) return job_info, attr.asdict(plugin_info) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index 4ea2f932f1..87ca3323cb 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -74,7 +74,7 @@ def get_template_name_profiles( project_settings ["global"] ["publish"] - ["IntegrateAssetNew"] + ["IntegrateHeroVersion"] ["template_name_profiles"] ) if legacy_profiles: From be7c5a99dc783be712ae69affc400d630275434b Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 20 Nov 2023 16:56:28 +0800 Subject: [PATCH 122/327] restore unnecessary code change --- openpype/pipeline/publish/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index 87ca3323cb..4ea2f932f1 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -74,7 +74,7 @@ def get_template_name_profiles( project_settings ["global"] ["publish"] - ["IntegrateHeroVersion"] + ["IntegrateAssetNew"] ["template_name_profiles"] ) if legacy_profiles: From 1ad7c1bca8b1dc6c3fed56a55febba7fd004fa12 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 20 Nov 2023 16:58:13 +0800 Subject: [PATCH 123/327] hound --- .../maya/plugins/publish/validate_arnold_verbosity_level.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_arnold_verbosity_level.py b/openpype/hosts/maya/plugins/publish/validate_arnold_verbosity_level.py index b4a08e6bb4..67e48cd63b 100644 --- a/openpype/hosts/maya/plugins/publish/validate_arnold_verbosity_level.py +++ b/openpype/hosts/maya/plugins/publish/validate_arnold_verbosity_level.py @@ -22,8 +22,9 @@ class ValidateArnoldVerbosityLevel(pyblish.api.InstancePlugin, if not self.is_active(instance.data): return if instance.data["renderer"] != "arnold": - self.log.debug("The renderer for deadline submission is not Arnold." - " Skipping Validate Arnold Verbosity Level.") + self.log.debug( + "The renderer for deadline submission is not Arnold.\n\n" + " Skipping Validate Arnold Verbosity Level.") return current_verbosity_level = cmds.getAttr( "defaultArnoldRenderOptions.log_verbosity") From 252d375bd6f6cbd72a6895b17feac7b72a0c9da1 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 20 Nov 2023 11:29:33 +0100 Subject: [PATCH 124/327] Add python3.10 libs for Houdini 20 startup --- .../hosts/houdini/startup/python3.10libs/pythonrc.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 openpype/hosts/houdini/startup/python3.10libs/pythonrc.py diff --git a/openpype/hosts/houdini/startup/python3.10libs/pythonrc.py b/openpype/hosts/houdini/startup/python3.10libs/pythonrc.py new file mode 100644 index 0000000000..683ea6721c --- /dev/null +++ b/openpype/hosts/houdini/startup/python3.10libs/pythonrc.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +"""OpenPype startup script.""" +from openpype.pipeline import install_host +from openpype.hosts.houdini.api import HoudiniHost + + +def main(): + print("Installing OpenPype ...") + install_host(HoudiniHost()) + + +main() From 8551110c4e0a5c9a8ec5cca480d0ffdf4ebdf22e Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 20 Nov 2023 14:44:42 +0200 Subject: [PATCH 125/327] add missing families --- .../plugins/publish/validate_houdini_license_category.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/publish/validate_houdini_license_category.py b/openpype/hosts/houdini/plugins/publish/validate_houdini_license_category.py index 5076acda60..108a700bbe 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_houdini_license_category.py +++ b/openpype/hosts/houdini/plugins/publish/validate_houdini_license_category.py @@ -20,7 +20,7 @@ class ValidateHoudiniNotApprenticeLicense(pyblish.api.InstancePlugin): """ order = pyblish.api.ValidatorOrder - families = ["usd", "abc"] + families = ["usd", "abc", "fbx", "camera"] hosts = ["houdini"] label = "Houdini Apprentice License" From 898f728a065b1b3ea19bfba5f65a3f6251172565 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 20 Nov 2023 18:28:11 +0100 Subject: [PATCH 126/327] simplify expression --- openpype/hosts/hiero/plugins/publish/precollect_instances.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/hiero/plugins/publish/precollect_instances.py b/openpype/hosts/hiero/plugins/publish/precollect_instances.py index 1acbbb3d88..590d7b7050 100644 --- a/openpype/hosts/hiero/plugins/publish/precollect_instances.py +++ b/openpype/hosts/hiero/plugins/publish/precollect_instances.py @@ -216,8 +216,7 @@ class PrecollectInstances(pyblish.api.ContextPlugin): "_ instance.data: {}".format(pformat(instance.data))) def _get_asset_data(self, data): - folder_path = ( - data.pop("folderPath") if data.get("folderPath") else None) + folder_path = data.pop("folderPath", None) if data.get("asset_name"): asset_name = data["asset_name"] From 477bc613e210af70c4ed4a1d06fc4654d954de18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Mon, 20 Nov 2023 18:28:18 +0100 Subject: [PATCH 127/327] Hiero | Global: editorial with folder path (#5829) * hiero: adding folderPath to creator - some minor typos fixes - modules sorting * Hiero: adding asset_name and processing folderPath - refactor labels * fixing extract hierarchy to ayon * hound * Update openpype/hosts/hiero/api/plugin.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> * simplify expression --------- Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/hiero/api/plugin.py | 24 +++++--- .../plugins/publish/precollect_instances.py | 59 +++++++++++++------ .../plugins/publish/precollect_workfile.py | 15 +++-- .../publish/extract_hierarchy_to_ayon.py | 17 +++--- 4 files changed, 76 insertions(+), 39 deletions(-) diff --git a/openpype/hosts/hiero/api/plugin.py b/openpype/hosts/hiero/api/plugin.py index 0e0632e032..b0c73e41fb 100644 --- a/openpype/hosts/hiero/api/plugin.py +++ b/openpype/hosts/hiero/api/plugin.py @@ -31,7 +31,7 @@ def load_stylesheet(): class CreatorWidget(QtWidgets.QDialog): # output items - items = dict() + items = {} def __init__(self, name, info, ui_inputs, parent=None): super(CreatorWidget, self).__init__(parent) @@ -642,8 +642,8 @@ class PublishClip: Returns: hiero.core.TrackItem: hiero track item object with pype tag """ - vertical_clip_match = dict() - tag_data = dict() + vertical_clip_match = {} + tag_data = {} types = { "shot": "shot", "folder": "folder", @@ -705,9 +705,10 @@ class PublishClip: self._create_parents() def convert(self): - # solve track item data and add them to tag data - self._convert_to_tag_data() + tag_hierarchy_data = self._convert_to_tag_data() + + self.tag_data.update(tag_hierarchy_data) # if track name is in review track name and also if driving track name # is not in review track name: skip tag creation @@ -721,16 +722,23 @@ class PublishClip: if self.rename: # rename track item self.track_item.setName(new_name) - self.tag_data["asset"] = new_name + self.tag_data["asset_name"] = new_name else: - self.tag_data["asset"] = self.ti_name + self.tag_data["asset_name"] = self.ti_name self.tag_data["hierarchyData"]["shot"] = self.ti_name + # AYON unique identifier + folder_path = "/{}/{}".format( + tag_hierarchy_data["hierarchy"], + self.tag_data["asset_name"] + ) + self.tag_data["folderPath"] = folder_path if self.tag_data["heroTrack"] and self.review_layer: self.tag_data.update({"reviewTrack": self.review_layer}) else: self.tag_data.update({"reviewTrack": None}) + # TODO: remove debug print log.debug("___ self.tag_data: {}".format( pformat(self.tag_data) )) @@ -889,7 +897,7 @@ class PublishClip: tag_hierarchy_data = hero_data # add data to return data dict - self.tag_data.update(tag_hierarchy_data) + return tag_hierarchy_data def _solve_tag_hierarchy_data(self, hierarchy_formatting_data): """ Solve tag data from hierarchy data and templates. """ diff --git a/openpype/hosts/hiero/plugins/publish/precollect_instances.py b/openpype/hosts/hiero/plugins/publish/precollect_instances.py index 65b8fed49c..590d7b7050 100644 --- a/openpype/hosts/hiero/plugins/publish/precollect_instances.py +++ b/openpype/hosts/hiero/plugins/publish/precollect_instances.py @@ -1,9 +1,12 @@ import pyblish + +from openpype import AYON_SERVER_ENABLED from openpype.pipeline.editorial import is_overlapping_otio_ranges + from openpype.hosts.hiero import api as phiero from openpype.hosts.hiero.api.otio import hiero_export -import hiero +import hiero # # developer reload modules from pprint import pformat @@ -80,25 +83,24 @@ class PrecollectInstances(pyblish.api.ContextPlugin): if k not in ("id", "applieswhole", "label") }) - asset = tag_data["asset"] + asset, asset_name = self._get_asset_data(tag_data) + subset = tag_data["subset"] # insert family into families - family = tag_data["family"] families = [str(f) for f in tag_data["families"]] - families.insert(0, str(family)) # form label - label = asset - if asset != clip_name: + label = "{} -".format(asset) + if asset_name != clip_name: label += " ({})".format(clip_name) label += " {}".format(subset) - label += " {}".format("[" + ", ".join(families) + "]") data.update({ "name": "{}_{}".format(asset, subset), "label": label, "asset": asset, + "asset_name": asset_name, "item": track_item, "families": families, "publish": tag_data["publish"], @@ -176,6 +178,7 @@ class PrecollectInstances(pyblish.api.ContextPlugin): }) def create_shot_instance(self, context, **data): + subset = "shotMain" master_layer = data.get("heroTrack") hierarchy_data = data.get("hierarchyData") item = data.get("item") @@ -188,23 +191,21 @@ class PrecollectInstances(pyblish.api.ContextPlugin): return asset = data["asset"] - subset = "shotMain" + asset_name = data["asset_name"] # insert family into families family = "shot" # form label - label = asset - if asset != clip_name: + label = "{} -".format(asset) + if asset_name != clip_name: label += " ({}) ".format(clip_name) label += " {}".format(subset) - label += " [{}]".format(family) data.update({ "name": "{}_{}".format(asset, subset), "label": label, "subset": subset, - "asset": asset, "family": family, "families": [] }) @@ -214,7 +215,33 @@ class PrecollectInstances(pyblish.api.ContextPlugin): self.log.debug( "_ instance.data: {}".format(pformat(instance.data))) + def _get_asset_data(self, data): + folder_path = data.pop("folderPath", None) + + if data.get("asset_name"): + asset_name = data["asset_name"] + else: + asset_name = data["asset"] + + # backward compatibility for clip tags + # which are missing folderPath key + # TODO remove this in future versions + if not folder_path: + hierarchy_path = data["hierarchy"] + folder_path = "/{}/{}".format( + hierarchy_path, + asset_name + ) + + if AYON_SERVER_ENABLED: + asset = folder_path + else: + asset = asset_name + + return asset, asset_name + def create_audio_instance(self, context, **data): + subset = "audioMain" master_layer = data.get("heroTrack") if not master_layer: @@ -229,23 +256,21 @@ class PrecollectInstances(pyblish.api.ContextPlugin): return asset = data["asset"] - subset = "audioMain" + asset_name = data["asset_name"] # insert family into families family = "audio" # form label - label = asset - if asset != clip_name: + label = "{} -".format(asset) + if asset_name != clip_name: label += " ({}) ".format(clip_name) label += " {}".format(subset) - label += " [{}]".format(family) data.update({ "name": "{}_{}".format(asset, subset), "label": label, "subset": subset, - "asset": asset, "family": family, "families": ["clip"] }) diff --git a/openpype/hosts/hiero/plugins/publish/precollect_workfile.py b/openpype/hosts/hiero/plugins/publish/precollect_workfile.py index 1d6bdc0257..8abb0885c6 100644 --- a/openpype/hosts/hiero/plugins/publish/precollect_workfile.py +++ b/openpype/hosts/hiero/plugins/publish/precollect_workfile.py @@ -18,7 +18,8 @@ class PrecollectWorkfile(pyblish.api.ContextPlugin): order = pyblish.api.CollectorOrder - 0.491 def process(self, context): - asset_name = context.data["asset"] + asset = context.data["asset"] + asset_name = asset if AYON_SERVER_ENABLED: asset_name = asset_name.split("/")[-1] @@ -29,7 +30,7 @@ class PrecollectWorkfile(pyblish.api.ContextPlugin): # adding otio timeline to context otio_timeline = hiero_export.create_otio_timeline() - # get workfile thumnail paths + # get workfile thumbnail paths tmp_staging = tempfile.mkdtemp(prefix="pyblish_tmp_") thumbnail_name = "workfile_thumbnail.png" thumbnail_path = os.path.join(tmp_staging, thumbnail_name) @@ -51,8 +52,8 @@ class PrecollectWorkfile(pyblish.api.ContextPlugin): } # get workfile paths - curent_file = project.path() - staging_dir, base_name = os.path.split(curent_file) + current_file = project.path() + staging_dir, base_name = os.path.split(current_file) # creating workfile representation workfile_representation = { @@ -63,10 +64,12 @@ class PrecollectWorkfile(pyblish.api.ContextPlugin): } family = "workfile" instance_data = { + "label": "{} - {}Main".format( + asset, family), "name": "{}_{}".format(asset_name, family), "asset": context.data["asset"], # TODO use 'get_subset_name' - "subset": "{}{}".format(asset_name, family.capitalize()), + "subset": "{}{}Main".format(asset_name, family.capitalize()), "item": project, "family": family, "families": [], @@ -81,7 +84,7 @@ class PrecollectWorkfile(pyblish.api.ContextPlugin): "activeProject": project, "activeTimeline": active_timeline, "otioTimeline": otio_timeline, - "currentFile": curent_file, + "currentFile": current_file, "colorspace": self.get_colorspace(project), "fps": fps } diff --git a/openpype/plugins/publish/extract_hierarchy_to_ayon.py b/openpype/plugins/publish/extract_hierarchy_to_ayon.py index fe8cb40ad2..ef69369d67 100644 --- a/openpype/plugins/publish/extract_hierarchy_to_ayon.py +++ b/openpype/plugins/publish/extract_hierarchy_to_ayon.py @@ -191,15 +191,15 @@ class ExtractHierarchyToAYON(pyblish.api.ContextPlugin): """ # filter only the active publishing instances - active_folder_names = set() + active_folder_paths = set() for instance in context: if instance.data.get("publish") is not False: - active_folder_names.add(instance.data.get("asset")) + active_folder_paths.add(instance.data.get("asset")) - active_folder_names.discard(None) + active_folder_paths.discard(None) - self.log.debug("Active folder names: {}".format(active_folder_names)) - if not active_folder_names: + self.log.debug("Active folder paths: {}".format(active_folder_paths)) + if not active_folder_paths: return None project_item = None @@ -230,12 +230,13 @@ class ExtractHierarchyToAYON(pyblish.api.ContextPlugin): if not children_context: continue - for asset_name, asset_info in children_context.items(): + for asset, asset_info in children_context.items(): if ( - asset_name not in active_folder_names + asset not in active_folder_paths and not asset_info.get("childs") ): continue + asset_name = asset.split("/")[-1] item_id = uuid.uuid4().hex new_item = copy.deepcopy(asset_info) new_item["name"] = asset_name @@ -252,7 +253,7 @@ class ExtractHierarchyToAYON(pyblish.api.ContextPlugin): items_by_id[item_id] = new_item parent_id_by_item_id[item_id] = parent_id - if asset_name in active_folder_names: + if asset in active_folder_paths: valid_ids.add(item_id) hierarchy_queue.append((item_id, new_children_context)) From ba697c85cc2c9e22bb5be6ac772a257fe174a4e9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 20 Nov 2023 18:45:24 +0100 Subject: [PATCH 128/327] added function to get AYON original settings values --- openpype/settings/ayon_settings.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/openpype/settings/ayon_settings.py b/openpype/settings/ayon_settings.py index 5171517232..6676e71a8e 100644 --- a/openpype/settings/ayon_settings.py +++ b/openpype/settings/ayon_settings.py @@ -1591,3 +1591,18 @@ def get_ayon_system_settings(default_values): return convert_system_settings( ayon_settings, default_values, addon_versions ) + + +def get_ayon_settings(project_name=None): + """AYON studio settings. + + Raw AYON settings values. + + Args: + project_name (Optional[str]): Project name. + + Returns: + dict[str, Any]: AYON settings. + """ + + return _AyonSettingsCache.get_value_by_project(project_name) From cdf49c4d40de868732802f6157553eac1b6b0c28 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 20 Nov 2023 18:48:28 +0100 Subject: [PATCH 129/327] Added 'AYONAddon' class which is initialized with AYON settings --- openpype/modules/__init__.py | 2 + openpype/modules/base.py | 193 +++++++++++++++++++++++------------ 2 files changed, 131 insertions(+), 64 deletions(-) diff --git a/openpype/modules/__init__.py b/openpype/modules/__init__.py index 1f345feea9..3097805353 100644 --- a/openpype/modules/__init__.py +++ b/openpype/modules/__init__.py @@ -10,6 +10,7 @@ from .interfaces import ( ) from .base import ( + AYONAddon, OpenPypeModule, OpenPypeAddOn, @@ -35,6 +36,7 @@ __all__ = ( "ISettingsChangeListener", "IHostAddon", + "AYONAddon", "OpenPypeModule", "OpenPypeAddOn", diff --git a/openpype/modules/base.py b/openpype/modules/base.py index 4636906cec..9d5a2608a5 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -"""Base class for Pype Modules.""" +"""Base class for AYON addons.""" import copy import os import sys @@ -11,6 +11,7 @@ import platform import threading import collections import traceback + from uuid import uuid4 from abc import ABCMeta, abstractmethod @@ -29,9 +30,12 @@ from openpype.settings import ( from openpype.settings.lib import ( get_studio_system_settings_overrides, - load_json_file + load_json_file, +) +from openpype.settings.ayon_settings import ( + is_dev_mode_enabled, + get_ayon_settings, ) -from openpype.settings.ayon_settings import is_dev_mode_enabled from openpype.lib import ( Logger, @@ -47,11 +51,11 @@ from .interfaces import ( ITrayService ) -# Files that will be always ignored on modules import +# Files that will be always ignored on addons import IGNORED_FILENAMES = ( "__pycache__", ) -# Files ignored on modules import from "./openpype/modules" +# Files ignored on addons import from "./openpype/modules" IGNORED_DEFAULT_FILENAMES = ( "__init__.py", "base.py", @@ -59,8 +63,8 @@ IGNORED_DEFAULT_FILENAMES = ( "example_addons", "default_modules", ) -# Modules that won't be loaded in AYON mode from "./openpype/modules" -# - the same modules are ignored in "./server_addon/create_ayon_addons.py" +# Addons that won't be loaded in AYON mode from "./openpype/modules" +# - the same addons are ignored in "./server_addon/create_ayon_addons.py" IGNORED_FILENAMES_IN_AYON = { "ftrack", "shotgrid", @@ -466,7 +470,7 @@ def _load_ayon_addons(openpype_modules, modules_key, log): attr = getattr(mod, attr_name) if ( inspect.isclass(attr) - and issubclass(attr, OpenPypeModule) + and issubclass(attr, AYONAddon) ): imported_modules.append(mod) break @@ -633,26 +637,22 @@ def _load_modules(): @six.add_metaclass(ABCMeta) -class OpenPypeModule: - """Base class of pype module. +class AYONAddon(object): + """Base class of AYON addon. Attributes: - id (UUID): Module's id. - enabled (bool): Is module enabled. - name (str): Module name. - manager (ModulesManager): Manager that created the module. + id (UUID): Addon object id. + enabled (bool): Is addon enabled. + name (str): Addon name. + + Args: + manager (ModulesManager): Manager object who discovered addon. + settings (dict[str, Any]): AYON settings. """ - # Disable by default - enabled = False + enabled = True _id = None - @property - @abstractmethod - def name(self): - """Module's name.""" - pass - def __init__(self, manager, settings): self.manager = manager @@ -662,22 +662,45 @@ class OpenPypeModule: @property def id(self): + """Random id of addon object. + + Returns: + str: Object id. + """ + if self._id is None: self._id = uuid4() return self._id + @property @abstractmethod - def initialize(self, module_settings): - """Initialization of module attributes. + def name(self): + """Addon name. - It is not recommended to override __init__ that's why specific method - was implemented. + Returns: + str: Addon name. """ pass - def connect_with_modules(self, enabled_modules): - """Connect with other enabled modules.""" + def initialize(self, settings): + """Initialization of module attributes. + + It is not recommended to override __init__ that's why specific method + was implemented. + + Args: + settings (dict[str, Any]): Settings. + """ + + pass + + def connect_with_modules(self, enabled_addons): + """Connect with other enabled addons. + + Args: + enabled_addons (list[AYONAddon]): Addons that are enabled. + """ pass @@ -685,6 +708,9 @@ class OpenPypeModule: """Get global environments values of module. Environment variables that can be get only from system settings. + + Returns: + dict[str, str]: Environment variables. """ return {} @@ -697,7 +723,7 @@ class OpenPypeModule: Args: application (Application): Application that is launched. - env (dict): Current environment variables. + env (dict[str, str]): Current environment variables. """ pass @@ -713,7 +739,8 @@ class OpenPypeModule: to receive from 'host' object. Args: - host (ModuleType): Access to installed/registered host object. + host (Union[ModuleType, HostBase]): Access to installed/registered + host object. host_name (str): Name of host. project_name (str): Project name which is main part of host context. @@ -727,47 +754,64 @@ class OpenPypeModule: The best practise is to create click group for whole module which is used to separate commands. - class MyPlugin(OpenPypeModule): - ... - def cli(self, module_click_group): - module_click_group.add_command(cli_main) + Example: + class MyPlugin(AYONAddon): + ... + def cli(self, module_click_group): + module_click_group.add_command(cli_main) - @click.group(, help="") - def cli_main(): - pass + @click.group(, help="") + def cli_main(): + pass - @cli_main.command() - def mycommand(): - print("my_command") + @cli_main.command() + def mycommand(): + print("my_command") + + Args: + module_click_group (click.Group): Group to which can be added + commands. """ pass +class OpenPypeModule(AYONAddon): + """Base class of OpenPype module. + + Instead of 'AYONAddon' are passed in module settings. + + Args: + manager (ModulesManager): Manager object who discovered addon. + settings (dict[str, Any]): OpenPype settings. + """ + + # Disable by default + enabled = False + + class OpenPypeAddOn(OpenPypeModule): # Enable Addon by default enabled = True - def initialize(self, module_settings): - """Initialization is not be required for most of addons.""" - pass - class ModulesManager: """Manager of Pype modules helps to load and prepare them to work. Args: - modules_settings(dict): To be able create module manager with specified - data. For settings changes callbacks and testing purposes. + system_settings (Optional[dict[str, Any]]): OpenPype system settings. + ayon_settings (Optional[dict[str, Any]]): AYON studio settings. """ + # Helper attributes for report _report_total_key = "Total" - def __init__(self, _system_settings=None): + def __init__(self, system_settings=None, ayon_settings=None): self.log = logging.getLogger(self.__class__.__name__) - self._system_settings = _system_settings + self._system_settings = system_settings + self._ayon_settings = ayon_settings self.modules = [] self.modules_by_id = {} @@ -789,8 +833,9 @@ class ModulesManager: default (Any): Default output if module is not available. Returns: - Union[OpenPypeModule, None]: Module found by name or None. + Union[AYONAddon, None]: Module found by name or None. """ + return self.modules_by_name.get(module_name, default) def get_enabled_module(self, module_name, default=None): @@ -804,7 +849,7 @@ class ModulesManager: not enabled. Returns: - Union[OpenPypeModule, None]: Enabled module found by name or None. + Union[AYONAddon, None]: Enabled module found by name or None. """ module = self.get(module_name) @@ -821,9 +866,14 @@ class ModulesManager: self.log.debug("*** Pype modules initialization.") # Prepare settings for modules - system_settings = getattr(self, "_system_settings", None) + system_settings = self._system_settings if system_settings is None: system_settings = get_system_settings() + + ayon_settings = self._ayon_settings + if AYON_SERVER_ENABLED and ayon_settings is None: + ayon_settings = get_ayon_settings() + modules_settings = system_settings["modules"] report = {} @@ -836,12 +886,13 @@ class ModulesManager: for name in dir(module): modules_item = getattr(module, name, None) # Filter globals that are not classes which inherit from - # OpenPypeModule + # AYONAddon if ( not inspect.isclass(modules_item) + or modules_item is AYONAddon or modules_item is OpenPypeModule or modules_item is OpenPypeAddOn - or not issubclass(modules_item, OpenPypeModule) + or not issubclass(modules_item, AYONAddon) ): continue @@ -866,10 +917,14 @@ class ModulesManager: module_classes.append(modules_item) for modules_item in module_classes: + is_openpype_module = issubclass(modules_item, OpenPypeModule) + settings = ( + system_settings if is_openpype_module else ayon_settings + ) + name = modules_item.__name__ try: - name = modules_item.__name__ # Try initialize module - module = modules_item(self, modules_settings) + module = modules_item(self, settings) # Store initialized object self.modules.append(module) self.modules_by_id[module.id] = module @@ -924,8 +979,9 @@ class ModulesManager: """Enabled modules initialized by the manager. Returns: - list: Initialized and enabled modules. + list[AYONAddon]: Initialized and enabled modules. """ + return [ module for module in self.modules @@ -1108,7 +1164,7 @@ class ModulesManager: host_name (str): Host name for which is found host module. Returns: - OpenPypeModule: Found host module by name. + AYONAddon: Found host module by name. None: There was not found module inheriting IHostAddon which has host name set to passed 'host_name'. """ @@ -1129,12 +1185,11 @@ class ModulesManager: inheriting 'IHostAddon'. """ - host_names = { + return { module.host_name for module in self.get_enabled_modules() if isinstance(module, IHostAddon) } - return host_names def print_report(self): """Print out report of time spent on modules initialization parts. @@ -1290,6 +1345,10 @@ class TrayModulesManager(ModulesManager): callback can be defined with `doubleclick_callback` attribute. Missing feature how to define default callback. + + Args: + addon (AYONAddon): Addon object. + callback (FunctionType): Function callback. """ callback_name = "_".join([module.name, callback.__name__]) if callback_name not in self.doubleclick_callbacks: @@ -1310,11 +1369,17 @@ class TrayModulesManager(ModulesManager): self.tray_menu(tray_menu) def get_enabled_tray_modules(self): - output = [] - for module in self.modules: - if module.enabled and isinstance(module, ITrayModule): - output.append(module) - return output + """Enabled tray modules. + + Returns: + list[AYONAddon]: Enabled addons that inherit from tray interface. + """ + + return [ + module + for module in self.modules + if module.enabled and isinstance(module, ITrayModule) + ] def restart_tray(self): if self.tray_manager: From db5d4bd254914b83adce418a741bfa2dfc3d0f80 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 20 Nov 2023 21:38:08 +0100 Subject: [PATCH 130/327] unreal hash --- openpype/hosts/unreal/integration | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/unreal/integration b/openpype/hosts/unreal/integration index ff15c70077..63266607ce 160000 --- a/openpype/hosts/unreal/integration +++ b/openpype/hosts/unreal/integration @@ -1 +1 @@ -Subproject commit ff15c700771e719cc5f3d561ac5d6f7590623986 +Subproject commit 63266607ceb972a61484f046634ddfc9eb0b5757 From 97c10d2555171b457b854d5d11c0e914d18183d4 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 20 Nov 2023 21:45:46 +0100 Subject: [PATCH 131/327] adding oiio defaults from settings --- openpype/plugins/publish/extract_thumbnail.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 5b75a374ba..22c579ab86 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -26,6 +26,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): enabled = False # presentable attribute + oiiotool_defaults = None ffmpeg_args = None def process(self, instance): @@ -205,14 +206,26 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): """ self.log.info("Extracting thumbnail {}".format(dst_path)) + oiio_default_type = None + oiio_default_display = None + oiio_default_view = None + oiio_default_colorspace = None + if self.oiiotool_defaults: + oiio_default_type = self.oiiotool_defaults["type"] + if "colorspace" in oiio_default_type: + oiio_default_colorspace = self.oiiotool_defaults["colorspace"] + else: + oiio_default_display = self.oiiotool_defaults["display"] + oiio_default_view = self.oiiotool_defaults["view"] try: convert_colorspace( src_path, dst_path, colorspace_data["config"]["path"], colorspace_data["colorspace"], - display=colorspace_data.get("display"), - view=colorspace_data.get("view"), + display=colorspace_data.get("display") or oiio_default_display, + view=colorspace_data.get("view") or oiio_default_view, + target_colorspace=oiio_default_colorspace, additional_input_args=["-i:ch=R,G,B"], logger=self.log, ) From 282c75631a63cc47c5622ca9ca01eb3ef1cc7753 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 20 Nov 2023 21:58:20 +0100 Subject: [PATCH 132/327] adding settings for oiio defaults --- .../defaults/project_settings/global.json | 6 ++++ .../schemas/schema_global_publish.json | 32 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 9ccf5cae05..959faf14fa 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -70,6 +70,12 @@ }, "ExtractThumbnail": { "enabled": true, + "oiiotool_defaults": { + "type": "colorspace", + "colorspace": "color_picking", + "view": "sRGB", + "display": "default" + }, "ffmpeg_args": { "input": [ "-apply_trc gamma22" diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index c7e91fd22d..a850cb68ed 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -202,6 +202,38 @@ "key": "enabled", "label": "Enabled" }, + { + "type": "dict", + "collapsible": true, + "key": "oiiotool_defaults", + "label": "OIIOtool defaults", + "children": [ + { + "type": "enum", + "key": "type", + "label": "Target type", + "enum_items": [ + { "colorspace": "Colorspace" }, + { "display_and_view": "Display & View" } + ] + }, + { + "type": "text", + "key": "colorspace", + "label": "Colorspace" + }, + { + "type": "text", + "key": "view", + "label": "View" + }, + { + "type": "text", + "key": "display", + "label": "Display" + } + ] + }, { "type": "dict", "key": "ffmpeg_args", From cd4f603b4e61a7a9f54a1e5420430d14e3307443 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 21 Nov 2023 13:46:13 +0800 Subject: [PATCH 133/327] supports the settings of additional plugin info and job info --- .../deadline/plugins/publish/submit_maya_deadline.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index cdaf329ef2..86de5c620e 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -133,6 +133,8 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, cls.group = settings.get("group", cls.group) cls.strict_error_checking = settings.get("strict_error_checking", cls.strict_error_checking) + cls.jobInfo = settings.get("jobInfo", cls.jobInfo) + cls.pluginInfo = settings.get("pluginInfo", cls.pluginInfo) def get_job_info(self): job_info = DeadlineJobInfo(Plugin="MayaBatch") @@ -282,10 +284,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, plugin_payload = attr.asdict(plugin_info) - if instance.data["renderer"] == "arnold": - plugin_payload["ArnoldVerbose"] = cmds.getAttr( - "defaultArnoldRenderOptions.log_verbosity") - # Patching with pluginInfo from settings for key, value in self.pluginInfo.items(): plugin_payload[key] = value From 460433cd136f50421fce685272d7e711970c227c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 21 Nov 2023 13:47:24 +0800 Subject: [PATCH 134/327] remove the validator for arnold verbosity level --- .../validate_arnold_verbosity_level.py | 43 ------------------- 1 file changed, 43 deletions(-) delete mode 100644 openpype/hosts/maya/plugins/publish/validate_arnold_verbosity_level.py diff --git a/openpype/hosts/maya/plugins/publish/validate_arnold_verbosity_level.py b/openpype/hosts/maya/plugins/publish/validate_arnold_verbosity_level.py deleted file mode 100644 index 67e48cd63b..0000000000 --- a/openpype/hosts/maya/plugins/publish/validate_arnold_verbosity_level.py +++ /dev/null @@ -1,43 +0,0 @@ -import pyblish.api -from openpype.pipeline import ( - PublishValidationError, - OptionalPyblishPluginMixin -) -from maya import cmds -from openpype.pipeline.publish import RepairAction - - -class ValidateArnoldVerbosityLevel(pyblish.api.InstancePlugin, - OptionalPyblishPluginMixin): - """Validate Arnold Verbosity Level For Deadline Submission""" - - order = pyblish.api.ValidatorOrder - families = ["renderlayer"] - hosts = ["maya"] - label = "Validate Arnold Verbosity Level" - actions = [RepairAction] - optional = True - - def process(self, instance): - if not self.is_active(instance.data): - return - if instance.data["renderer"] != "arnold": - self.log.debug( - "The renderer for deadline submission is not Arnold.\n\n" - " Skipping Validate Arnold Verbosity Level.") - return - current_verbosity_level = cmds.getAttr( - "defaultArnoldRenderOptions.log_verbosity") - - if not current_verbosity_level >= 3: - report = ( - "Arnold verbosity level has invalid value(s).\n\n" - "It must be always greater than 3.\n\n" - "You can use repair action to set the correct value\n" - ) - raise PublishValidationError( - report, title="Invalid Value(s) for Arnold Verbosity Level") - - @classmethod - def repair(cls, instance): - return cmds.setAttr("defaultArnoldRenderOptions.log_verbosity", 3) From a31214d2c4f68ae5820139aa9857e711a8383577 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 21 Nov 2023 11:05:55 +0100 Subject: [PATCH 135/327] use 'newAssetPublishing' instead of 'isEditorial' --- .../hosts/resolve/plugins/publish/precollect_instances.py | 1 - .../hosts/traypublisher/plugins/create/create_editorial.py | 1 - .../plugins/publish/collect_sequence_frame_data.py | 6 +++--- .../traypublisher/plugins/publish/validate_frame_ranges.py | 6 +++--- openpype/plugins/publish/collect_resources_path.py | 6 +++--- 5 files changed, 9 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/resolve/plugins/publish/precollect_instances.py b/openpype/hosts/resolve/plugins/publish/precollect_instances.py index 58c1c85276..bca6734848 100644 --- a/openpype/hosts/resolve/plugins/publish/precollect_instances.py +++ b/openpype/hosts/resolve/plugins/publish/precollect_instances.py @@ -79,7 +79,6 @@ class PrecollectInstances(pyblish.api.ContextPlugin): "handleEnd": handle_end, "newAssetPublishing": True, "families": ["clip"], - "isEditorial": True }) # otio clip data diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index 198e05d593..26cce35d55 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -714,7 +714,6 @@ or updating already created. Publishing will create OTIO file. "newAssetPublishing": True, "trackStartFrame": track_start_frame, "timelineOffset": timeline_offset, - "isEditorial": True, # creator_attributes "creator_attributes": creator_attributes diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_sequence_frame_data.py b/openpype/hosts/traypublisher/plugins/publish/collect_sequence_frame_data.py index 92cedf6b5b..5e60a94927 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_sequence_frame_data.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_sequence_frame_data.py @@ -28,9 +28,9 @@ class CollectSequenceFrameData( return # editorial would fail since they might not be in database yet - is_editorial = instance.data.get("isEditorial") - if is_editorial: - self.log.debug("Instance is Editorial. Skipping.") + new_asset_publishing = instance.data.get("newAssetPublishing") + if new_asset_publishing: + self.log.debug("Instance is creating new asset. Skipping.") return frame_data = self.get_frame_data_from_repre_sequence(instance) diff --git a/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py b/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py index 4977a13374..95894848a4 100644 --- a/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py +++ b/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py @@ -31,9 +31,9 @@ class ValidateFrameRange(OptionalPyblishPluginMixin, return # editorial would fail since they might not be in database yet - is_editorial = instance.data.get("isEditorial") - if is_editorial: - self.log.debug("Instance is Editorial. Skipping.") + new_asset_publishing = instance.data.get("newAssetPublishing") + if new_asset_publishing: + self.log.debug("Instance is creating new asset. Skipping.") return if (self.skip_timelines_check and diff --git a/openpype/plugins/publish/collect_resources_path.py b/openpype/plugins/publish/collect_resources_path.py index 14c13310df..a7f12bdfdb 100644 --- a/openpype/plugins/publish/collect_resources_path.py +++ b/openpype/plugins/publish/collect_resources_path.py @@ -69,9 +69,9 @@ class CollectResourcesPath(pyblish.api.InstancePlugin): def process(self, instance): # editorial would fail since they might not be in database yet - is_editorial = instance.data.get("isEditorial") - if is_editorial: - self.log.debug("Instance is Editorial. Skipping.") + new_asset_publishing = instance.data.get("newAssetPublishing") + if new_asset_publishing: + self.log.debug("Instance is creating new asset. Skipping.") return anatomy = instance.context.data["anatomy"] From f78f9c98f347031a7a6c52059fef94a9ada5848a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 21 Nov 2023 11:07:10 +0100 Subject: [PATCH 136/327] added harmony and flame to skipped addons --- openpype/modules/base.py | 18 ++++++++++++++--- server_addon/create_ayon_addons.py | 32 +++++++++++++++++++----------- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/openpype/modules/base.py b/openpype/modules/base.py index 4636906cec..eba7d54db0 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -68,6 +68,10 @@ IGNORED_FILENAMES_IN_AYON = { "slack", "kitsu", } +IGNORED_HOSTS_IN_AYON = { + "flame", + "harmony", +} # Inherit from `object` for Python 2 hosts @@ -536,6 +540,11 @@ def _load_modules(): addons_dir = os.path.join(os.path.dirname(current_dir), "addons") module_dirs.append(addons_dir) + ignored_host_names = set(IGNORED_HOSTS_IN_AYON) + ignored_current_dir_filenames = set(IGNORED_DEFAULT_FILENAMES) + if AYON_SERVER_ENABLED: + ignored_current_dir_filenames |= IGNORED_FILENAMES_IN_AYON + processed_paths = set() for dirpath in frozenset(module_dirs): # Skip already processed paths @@ -551,9 +560,6 @@ def _load_modules(): is_in_current_dir = dirpath == current_dir is_in_host_dir = dirpath == hosts_dir - ignored_current_dir_filenames = set(IGNORED_DEFAULT_FILENAMES) - if AYON_SERVER_ENABLED: - ignored_current_dir_filenames |= IGNORED_FILENAMES_IN_AYON for filename in os.listdir(dirpath): # Ignore filenames @@ -566,6 +572,12 @@ def _load_modules(): ): continue + if ( + is_in_host_dir + and filename in ignored_host_names + ): + continue + fullpath = os.path.join(dirpath, filename) basename, ext = os.path.splitext(filename) diff --git a/server_addon/create_ayon_addons.py b/server_addon/create_ayon_addons.py index fe8d278321..fc7a673dcc 100644 --- a/server_addon/create_ayon_addons.py +++ b/server_addon/create_ayon_addons.py @@ -33,6 +33,20 @@ IGNORE_FILE_PATTERNS: List[Pattern] = [ } ] +IGNORED_HOSTS = [ + "flame", + "harmony", +] + +IGNORED_MODULES = [ + "ftrack", + "shotgrid", + "sync_server", + "example_addons", + "slack", + "kitsu", +] + class ZipFileLongPaths(zipfile.ZipFile): """Allows longer paths in zip files. @@ -202,16 +216,6 @@ def create_openpype_package( str(pyproject_path), (private_dir / pyproject_path.name) ) - - ignored_hosts = [] - ignored_modules = [ - "ftrack", - "shotgrid", - "sync_server", - "example_addons", - "slack", - "kitsu", - ] # Subdirs that won't be added to output zip file ignored_subpaths = [ ["addons"], @@ -219,11 +223,11 @@ def create_openpype_package( ] ignored_subpaths.extend( ["hosts", host_name] - for host_name in ignored_hosts + for host_name in IGNORED_HOSTS ) ignored_subpaths.extend( ["modules", module_name] - for module_name in ignored_modules + for module_name in IGNORED_MODULES ) # Zip client @@ -297,6 +301,7 @@ def main( # Make sure output dir is created output_dir.mkdir(parents=True, exist_ok=True) + ignored_addons = set(IGNORED_HOSTS) | set(IGNORED_MODULES) for addon_dir in current_dir.iterdir(): if not addon_dir.is_dir(): continue @@ -304,6 +309,9 @@ def main( if addons and addon_dir.name not in addons: continue + if addon_dir.name in ignored_addons: + continue + server_dir = addon_dir / "server" if not server_dir.exists(): continue From 1c6db9d8ae25a61f43c1c89756c93b153f180cee Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Nov 2023 11:45:04 +0100 Subject: [PATCH 137/327] Ftrack: rewriting component creation to support multiple thumbnails --- .../publish/integrate_ftrack_instances.py | 405 ++++++++++++------ 1 file changed, 265 insertions(+), 140 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py index 75f43cb22f..334e70ce0c 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py @@ -1,6 +1,7 @@ import os import json import copy + import pyblish.api from openpype.pipeline.publish import get_publish_repre_path @@ -61,6 +62,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): additional_metadata_keys = [] def process(self, instance): + # QUESTION: should this be operating even for `farm` target? self.log.debug("instance {}".format(instance)) instance_repres = instance.data.get("representations") @@ -143,70 +145,87 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): unmanaged_location_name = "ftrack.unmanaged" ftrack_server_location_name = "ftrack.server" + # check if any outputName keys are in review_representations + # also check if any outputName keys are in thumbnail_representations + synced_multiple_output_names = [] + for review_repre in review_representations: + review_output_name = review_repre.get("outputName") + if not review_output_name: + continue + for thumb_repre in thumbnail_representations: + thumb_output_name = thumb_repre.get("outputName") + if not thumb_output_name: + continue + if ( + thumb_output_name == review_output_name + # output name can be added also as tags during intermediate + # files creation + or thumb_output_name in review_repre.get("tags", []) + ): + synced_multiple_output_names.append( + thumb_repre["outputName"]) + self.log.debug("Multiple output names: {}".format( + synced_multiple_output_names + )) + multiple_synced_thumbnails = len(thumbnail_representations) > 1 + # Components data component_list = [] - # Components that will be duplicated to unmanaged location - src_components_to_add = [] + thumbnail_data_items = [] # Create thumbnail components - # TODO what if there is multiple thumbnails? - first_thumbnail_component = None - first_thumbnail_component_repre = None - - if not review_representations or has_movie_review: - for repre in thumbnail_representations: - repre_path = get_publish_repre_path(instance, repre, False) - if not repre_path: - self.log.warning( - "Published path is not set and source was removed." - ) - continue - - # Create copy of base comp item and append it - thumbnail_item = copy.deepcopy(base_component_item) - thumbnail_item["component_path"] = repre_path - thumbnail_item["component_data"] = { - "name": "thumbnail" - } - thumbnail_item["thumbnail"] = True - - # Create copy of item before setting location - if "delete" not in repre.get("tags", []): - src_components_to_add.append(copy.deepcopy(thumbnail_item)) - # Create copy of first thumbnail - if first_thumbnail_component is None: - first_thumbnail_component_repre = repre - first_thumbnail_component = thumbnail_item - # Set location - thumbnail_item["component_location_name"] = ( - ftrack_server_location_name + for repre in thumbnail_representations: + repre_path = get_publish_repre_path(instance, repre, False) + if not repre_path: + self.log.warning( + "Published path is not set and source was removed." ) + continue - # Add item to component list - component_list.append(thumbnail_item) + # Create copy of base comp item and append it + thumbnail_item = copy.deepcopy(base_component_item) + thumbnail_item.update({ + "component_path": repre_path, + "component_data": { + "name": ( + "thumbnail" if review_representations + else "ftrackreview-image" + ), + "metadata": self._prepare_image_component_metadata( + repre, + repre_path + ) + }, + "thumbnail": True, + "component_location_name": ftrack_server_location_name + }) - if first_thumbnail_component is not None: - metadata = self._prepare_image_component_metadata( - first_thumbnail_component_repre, - first_thumbnail_component["component_path"] - ) + # add thumbnail to items data for future synchronization + current_item = { + "sync_key": repre.get("outputName"), + "representation": repre, + "item": thumbnail_item + } + # Create copy of item before setting location + if "delete" not in repre.get("tags", []): + src_comp = self._create_src_components( + instance, + repre, + copy.deepcopy(thumbnail_item), + unmanaged_location_name + ) + component_list.append(src_comp) - if metadata: - component_data = first_thumbnail_component["component_data"] - component_data["metadata"] = metadata + current_item["src_component"] = src_comp - if review_representations: - component_data["name"] = "thumbnail" - else: - component_data["name"] = "ftrackreview-image" + # Add item to component list + component_list.append(thumbnail_item) + thumbnail_data_items.append(current_item) # Create review components # Change asset name of each new component for review - is_first_review_repre = True - not_first_components = [] - extended_asset_name = "" multiple_reviewable = len(review_representations) > 1 - for repre in review_representations: + for index, repre in enumerate(review_representations): if not self._is_repre_video(repre) and has_movie_review: self.log.debug("Movie repre has priority " "from {}".format(repre)) @@ -222,45 +241,38 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): # Create copy of base comp item and append it review_item = copy.deepcopy(base_component_item) - # get asset name and define extended name variant - asset_name = review_item["asset_data"]["name"] - extended_asset_name = "_".join( - (asset_name, repre["name"]) + # get first or synchronize thumbnail item + sync_thumbnail_item = self._get_matching_thumbnail_item( + repre, + thumbnail_data_items, + multiple_synced_thumbnails ) - # reset extended if no need for extended asset name - if ( - self.keep_first_subset_name_for_review - and is_first_review_repre - ): - extended_asset_name = "" - else: - # only rename if multiple reviewable - if multiple_reviewable: - review_item["asset_data"]["name"] = extended_asset_name - else: - extended_asset_name = "" - - # rename all already created components - # only if first repre and extended name available - if is_first_review_repre and extended_asset_name: - # and rename all already created components - for _ci in component_list: - _ci["asset_data"]["name"] = extended_asset_name - - # and rename all already created src components - for _sci in src_components_to_add: - _sci["asset_data"]["name"] = extended_asset_name - - # rename also first thumbnail component if any - if first_thumbnail_component is not None: - first_thumbnail_component[ - "asset_data"]["name"] = extended_asset_name - - # Change location - review_item["component_path"] = repre_path - # Change component data + """ + Renaming asset name only to those components which are explicitly + allowed in settings. Usually clients wanted to keep first component + as untouched product name with version and any other assetVersion + to be named with extended form. The renaming will only happen if + there is more than one reviewable component and extended name is + not empty. + """ + extended_asset_name = self._make_extended_component_name( + base_component_item, repre, index) + if multiple_reviewable and extended_asset_name: + review_item["asset_data"]["name"] = extended_asset_name + # rename also thumbnail + if sync_thumbnail_item: + sync_thumbnail_item["item"]["asset_data"]["name"] = ( + extended_asset_name + ) + # rename also src_thumbnail + if sync_thumbnail_item.get("src_component"): + thumb_src_component = sync_thumbnail_item["src_component"] + thumb_src_component["asset_data"]["name"] = ( + extended_asset_name + ) + # add metadata to review component if self._is_repre_video(repre): component_name = "ftrackreview-mp4" metadata = self._prepare_video_component_metadata( @@ -273,28 +285,49 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): ) review_item["thumbnail"] = True - review_item["component_data"] = { - # Default component name is "main". - "name": component_name, - "metadata": metadata - } - - if is_first_review_repre: - is_first_review_repre = False - else: - # later detection for thumbnail duplication - not_first_components.append(review_item) + review_item.update({ + "component_path": repre_path, + "component_data": { + "name": component_name, + "metadata": metadata + }, + "component_location_name": ftrack_server_location_name + }) # Create copy of item before setting location if "delete" not in repre.get("tags", []): - src_components_to_add.append(copy.deepcopy(review_item)) + src_comp = self._create_src_components( + instance, + repre, + copy.deepcopy(review_item), + unmanaged_location_name + ) + component_list.append(src_comp) + + if index > 0: + asset_name = review_item["asset_data"]["name"] + # perhaps it might happen that no thumbnail for + # current iteration is found in thumbnails list + # QUESTION: should this be consider as bug in code? + # NOTE: this was inherited from original code, but perhaps it + # should not be included + if ( + sync_thumbnail_item + and sync_thumbnail_item["item"]["asset_data"]["name"] != asset_name # noqa + ): + new_thumbnail_component = copy.deepcopy( + sync_thumbnail_item["item"] + ) + new_thumbnail_component["asset_data"]["name"] = asset_name + new_thumbnail_component["component_location_name"] = ( + ftrack_server_location_name + ) + component_list.append(new_thumbnail_component) - # Set location - review_item["component_location_name"] = ( - ftrack_server_location_name - ) # Add item to component list component_list.append(review_item) + + if self.upload_reviewable_with_origin_name: origin_name_component = copy.deepcopy(review_item) filename = os.path.basename(repre_path) @@ -303,34 +336,6 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): ) component_list.append(origin_name_component) - # Duplicate thumbnail component for all not first reviews - if first_thumbnail_component is not None: - for component_item in not_first_components: - asset_name = component_item["asset_data"]["name"] - new_thumbnail_component = copy.deepcopy( - first_thumbnail_component - ) - new_thumbnail_component["asset_data"]["name"] = asset_name - new_thumbnail_component["component_location_name"] = ( - ftrack_server_location_name - ) - component_list.append(new_thumbnail_component) - - # Add source components for review and thubmnail components - for copy_src_item in src_components_to_add: - # Make sure thumbnail is disabled - copy_src_item["thumbnail"] = False - # Set location - copy_src_item["component_location_name"] = unmanaged_location_name - # Modify name of component to have suffix "_src" - component_data = copy_src_item["component_data"] - component_name = component_data["name"] - component_data["name"] = component_name + "_src" - component_data["metadata"] = self._prepare_component_metadata( - instance, repre, copy_src_item["component_path"], False - ) - component_list.append(copy_src_item) - # Add others representations as component for repre in other_representations: published_path = get_publish_repre_path(instance, repre, True) @@ -346,15 +351,17 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): ): other_item["asset_data"]["name"] = extended_asset_name - component_data = { - "name": repre["name"], - "metadata": self._prepare_component_metadata( - instance, repre, published_path, False - ) - } - other_item["component_data"] = component_data - other_item["component_location_name"] = unmanaged_location_name - other_item["component_path"] = published_path + other_item.update({ + "component_path": published_path, + "component_data": { + "name": repre["name"], + "metadata": self._prepare_component_metadata( + instance, repre, published_path, False + ) + }, + "component_location_name": unmanaged_location_name, + }) + component_list.append(other_item) def json_obj_parser(obj): @@ -370,6 +377,124 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): )) instance.data["ftrackComponentsList"] = component_list + def _get_matching_thumbnail_item( + self, + review_representation, + thumbnail_data_items, + are_multiple_synced_thumbnails + ): + """Return matching thumbnail item from list of thumbnail items. + + If a thumbnail item already exists, this should return it. + The benefit is that if an `outputName` key is found in + representation and is also used as a `sync_key` in a thumbnail + data item, it can sync with that item. + + Args: + review_representation (dict): Review representation + thumbnail_data_items (list): List of thumbnail data items + are_multiple_synced_thumbnails (bool): If there are multiple synced + thumbnails + + Returns: + dict: Thumbnail data item or empty dict + """ + output_name = review_representation.get("outputName") + tags = review_representation.get("tags", []) + matching_thumbnail_item = {} + for thumb_item in thumbnail_data_items: + if ( + are_multiple_synced_thumbnails + and ( + thumb_item["sync_key"] == output_name + # intermediate files can have preset name in tags + # this is usually aligned with `outputName` distributed + # during thumbnail creation in `need_thumbnail` tagging + # workflow + or thumb_item["sync_key"] in tags + ) + ): + # return only synchronized thumbnail if multiple + matching_thumbnail_item = thumb_item + break + elif not are_multiple_synced_thumbnails: + # return any first found thumbnail since we need thumbnail + # but dont care which one + matching_thumbnail_item = thumb_item + break + + if not matching_thumbnail_item: + # WARNING: this can only happen if multiple thumbnails + # workflow is broken, since it found multiple matching outputName + # in representation but they do not align with any thumbnail item + self.log.warning( + "No matching thumbnail item found for output name " + "'{}'".format(output_name) + ) + if not thumbnail_data_items: + self.log.warning( + "No thumbnail data items found" + ) + return {} + # as fallback return first thumbnail item + return thumbnail_data_items[0] + + + return matching_thumbnail_item + + def _make_extended_component_name(self, component_item, repre, iteration_index): + """ Returns the extended component name + + Name is based on the asset name and representation name. + + Args: + component_item (dict): The component item dictionary. + repre (dict): The representation dictionary. + iteration_index (int): The index of the iteration. + + Returns: + str: The extended component name. + + """ + # reset extended if no need for extended asset name + if self.keep_first_subset_name_for_review and iteration_index == 0: + return + + # get asset name and define extended name variant + asset_name = component_item["asset_data"]["name"] + return "_".join( + (asset_name, repre["name"]) + ) + + def _create_src_components( + self, instance, repre, component_item, location): + """Create src component for thumbnail. + + This will replicate the input component and change its name to + have suffix "_src". + + Args: + instance (pyblish.api.Instance): Instance + repre (dict): Representation + component_item (dict): Component item + location (str): Location name + + Returns: + dict: Component item + """ + # Make sure thumbnail is disabled + component_item["thumbnail"] = False + # Set location + component_item["component_location_name"] = location + # Modify name of component to have suffix "_src" + component_data = component_item["component_data"] + component_name = component_data["name"] + component_data["name"] = component_name + "_src" + component_data["metadata"] = self._prepare_component_metadata( + instance, repre, component_item["component_path"], False + ) + return component_item + def _collect_additional_metadata(self, streams): pass From 3f850f1b0ae5b91db9e2f2dee7f5f8183af6906d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 21 Nov 2023 12:02:01 +0100 Subject: [PATCH 138/327] fix module settings usage --- openpype/modules/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/base.py b/openpype/modules/base.py index 9d5a2608a5..40a9db45d5 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -919,7 +919,7 @@ class ModulesManager: for modules_item in module_classes: is_openpype_module = issubclass(modules_item, OpenPypeModule) settings = ( - system_settings if is_openpype_module else ayon_settings + modules_settings if is_openpype_module else ayon_settings ) name = modules_item.__name__ try: From 8dddc31706bd2560366d3fe26cfe31dd998a93a1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 21 Nov 2023 12:06:38 +0100 Subject: [PATCH 139/327] change debug log message --- openpype/modules/base.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openpype/modules/base.py b/openpype/modules/base.py index 40a9db45d5..8ed6b4b9c1 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -806,6 +806,8 @@ class ModulesManager: # Helper attributes for report _report_total_key = "Total" + _system_settings = None + _ayon_settings = None def __init__(self, system_settings=None, ayon_settings=None): self.log = logging.getLogger(self.__class__.__name__) @@ -864,7 +866,11 @@ class ModulesManager: import openpype_modules - self.log.debug("*** Pype modules initialization.") + self.log.debug("*** {} initialization.".format( + "AYON addons" + if AYON_SERVER_ENABLED + else "OpenPype modules" + )) # Prepare settings for modules system_settings = self._system_settings if system_settings is None: From 4ba778e73e6123b5fa6ce43fed2110729740f813 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Nov 2023 13:49:57 +0100 Subject: [PATCH 140/327] fixing duplication of single thumbnail for multiple reviewables --- .../publish/integrate_ftrack_instances.py | 46 +++++++------------ 1 file changed, 16 insertions(+), 30 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py index 334e70ce0c..dcc9d6569a 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py @@ -1,7 +1,6 @@ import os import json import copy - import pyblish.api from openpype.pipeline.publish import get_publish_repre_path @@ -219,7 +218,6 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): current_item["src_component"] = src_comp # Add item to component list - component_list.append(thumbnail_item) thumbnail_data_items.append(current_item) # Create review components @@ -242,11 +240,16 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): review_item = copy.deepcopy(base_component_item) # get first or synchronize thumbnail item - sync_thumbnail_item = self._get_matching_thumbnail_item( + sync_thumbnail_item = None + sync_thumbnail_item_src = None + sync_thumbnail_data = self._get_matching_thumbnail_item( repre, thumbnail_data_items, multiple_synced_thumbnails ) + if sync_thumbnail_data: + sync_thumbnail_item = sync_thumbnail_data.get("item") + sync_thumbnail_item_src = sync_thumbnail_data.get("src_component") """ Renaming asset name only to those components which are explicitly @@ -258,20 +261,26 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): """ extended_asset_name = self._make_extended_component_name( base_component_item, repre, index) + if multiple_reviewable and extended_asset_name: review_item["asset_data"]["name"] = extended_asset_name # rename also thumbnail if sync_thumbnail_item: - sync_thumbnail_item["item"]["asset_data"]["name"] = ( + sync_thumbnail_item["asset_data"]["name"] = ( extended_asset_name ) # rename also src_thumbnail - if sync_thumbnail_item.get("src_component"): - thumb_src_component = sync_thumbnail_item["src_component"] - thumb_src_component["asset_data"]["name"] = ( + if sync_thumbnail_item_src: + sync_thumbnail_item_src["asset_data"]["name"] = ( extended_asset_name ) + # adding thumbnail component to component list + if sync_thumbnail_item: + component_list.append(copy.deepcopy(sync_thumbnail_item)) + if sync_thumbnail_item_src: + component_list.append(copy.deepcopy(sync_thumbnail_item_src)) + # add metadata to review component if self._is_repre_video(repre): component_name = "ftrackreview-mp4" @@ -304,26 +313,6 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): ) component_list.append(src_comp) - if index > 0: - asset_name = review_item["asset_data"]["name"] - # perhaps it might happen that no thumbnail for - # current iteration is found in thumbnails list - # QUESTION: should this be consider as bug in code? - # NOTE: this was inherited from original code, but perhaps it - # should not be included - if ( - sync_thumbnail_item - and sync_thumbnail_item["item"]["asset_data"]["name"] != asset_name # noqa - ): - new_thumbnail_component = copy.deepcopy( - sync_thumbnail_item["item"] - ) - new_thumbnail_component["asset_data"]["name"] = asset_name - new_thumbnail_component["component_location_name"] = ( - ftrack_server_location_name - ) - component_list.append(new_thumbnail_component) - # Add item to component list component_list.append(review_item) @@ -597,9 +586,6 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): stream_width = tmp_width stream_height = tmp_height - self.log.debug("FPS from stream is {} and duration is {}".format( - input_framerate, stream_duration - )) frame_out = float(stream_duration) * stream_fps break From 73600fb0a8dbf60175f3d9984e89be95743e5859 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Nov 2023 13:57:13 +0100 Subject: [PATCH 141/327] hound --- .../ftrack/plugins/publish/integrate_ftrack_instances.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py index dcc9d6569a..66d5b73dc6 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py @@ -428,10 +428,10 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): # as fallback return first thumbnail item return thumbnail_data_items[0] - return matching_thumbnail_item - def _make_extended_component_name(self, component_item, repre, iteration_index): + def _make_extended_component_name( + self, component_item, repre, iteration_index): """ Returns the extended component name Name is based on the asset name and representation name. @@ -445,7 +445,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): str: The extended component name. """ - # reset extended if no need for extended asset name + # reset extended if no need for extended asset name if self.keep_first_subset_name_for_review and iteration_index == 0: return From e6c050403871e45ccc7c0423ac107e2b8d9a8b02 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Nov 2023 13:59:33 +0100 Subject: [PATCH 142/327] unused variable comment https://github.com/ynput/OpenPype/pull/5939#discussion_r1400418886 --- .../ftrack/plugins/publish/integrate_ftrack_instances.py | 2 +- 1 file changed, 1 insertion(+), 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 66d5b73dc6..ffa2672576 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py @@ -166,7 +166,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): self.log.debug("Multiple output names: {}".format( synced_multiple_output_names )) - multiple_synced_thumbnails = len(thumbnail_representations) > 1 + multiple_synced_thumbnails = len(synced_multiple_output_names) > 1 # Components data component_list = [] From 166bfb2f31981647a18c4f381a082b54eead9f6d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Nov 2023 14:06:33 +0100 Subject: [PATCH 143/327] improving code readability --- .../publish/integrate_ftrack_instances.py | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py index ffa2672576..edad0b0132 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py @@ -174,10 +174,17 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): # Create thumbnail components for repre in thumbnail_representations: - repre_path = get_publish_repre_path(instance, repre, False) + # get repre path from representation + # and return published_path if available + # the path is validated and if it does not exists it returns None + repre_path = get_publish_repre_path( + instance, + repre, + only_published=False + ) if not repre_path: self.log.warning( - "Published path is not set and source was removed." + "Published path is not set or source was removed." ) continue @@ -199,15 +206,15 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): "component_location_name": ftrack_server_location_name }) - # add thumbnail to items data for future synchronization - current_item = { + # add thumbnail data to items for future synchronization + current_item_data = { "sync_key": repre.get("outputName"), "representation": repre, "item": thumbnail_item } # Create copy of item before setting location if "delete" not in repre.get("tags", []): - src_comp = self._create_src_components( + src_comp = self._create_src_component( instance, repre, copy.deepcopy(thumbnail_item), @@ -215,10 +222,10 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): ) component_list.append(src_comp) - current_item["src_component"] = src_comp + current_item_data["src_component"] = src_comp # Add item to component list - thumbnail_data_items.append(current_item) + thumbnail_data_items.append(current_item_data) # Create review components # Change asset name of each new component for review @@ -249,7 +256,8 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): ) if sync_thumbnail_data: sync_thumbnail_item = sync_thumbnail_data.get("item") - sync_thumbnail_item_src = sync_thumbnail_data.get("src_component") + sync_thumbnail_item_src = sync_thumbnail_data.get( + "src_component") """ Renaming asset name only to those components which are explicitly @@ -305,7 +313,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): # Create copy of item before setting location if "delete" not in repre.get("tags", []): - src_comp = self._create_src_components( + src_comp = self._create_src_component( instance, repre, copy.deepcopy(review_item), @@ -455,7 +463,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): (asset_name, repre["name"]) ) - def _create_src_components( + def _create_src_component( self, instance, repre, component_item, location): """Create src component for thumbnail. From d42a67e814fc7503afe70887f54b6b7176daedf8 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Nov 2023 15:14:18 +0100 Subject: [PATCH 144/327] nuke: adding `need_thumnail` to intermediate representations --- openpype/hosts/nuke/api/plugin.py | 35 ++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py index 301b9533a9..15d7bfc4b9 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -21,6 +21,11 @@ from openpype.pipeline import ( CreatedInstance, get_current_task_name ) +from openpype.pipeline.colorspace import ( + get_display_view_colorspace_name, + get_colorspace_settings_from_publish_context, + set_colorspace_data_to_representation +) from openpype.lib.transcoding import ( VIDEO_EXTENSIONS ) @@ -612,7 +617,7 @@ class ExporterReview(object): def get_representation_data( self, tags=None, range=False, - custom_tags=None + custom_tags=None, colorspace=None ): """ Add representation data to self.data @@ -652,6 +657,14 @@ class ExporterReview(object): if self.publish_on_farm: repre["tags"].append("publish_on_farm") + # add colorspace data to representation + if colorspace: + set_colorspace_data_to_representation( + repre, + self.instance.context.data, + colorspace=colorspace, + log=self.log + ) self.data["representations"].append(repre) def get_imageio_baking_profile(self): @@ -866,6 +879,13 @@ class ExporterReviewMov(ExporterReview): return path def generate_mov(self, farm=False, **kwargs): + # colorspace data + colorspace = None + # get colorspace settings + # get colorspace data from context + config_data, _ = get_colorspace_settings_from_publish_context( + self.instance.context.data) + add_tags = [] self.publish_on_farm = farm read_raw = kwargs["read_raw"] @@ -951,6 +971,14 @@ class ExporterReviewMov(ExporterReview): # assign viewer dag_node["view"].setValue(viewer) + if config_data: + # convert display and view to colorspace + colorspace = get_display_view_colorspace_name( + config_path=config_data["path"], + display=display, + view=viewer + ) + self._connect_to_above_nodes(dag_node, subset, "OCIODisplay... `{}`") # Write node write_node = nuke.createNode("Write") @@ -996,9 +1024,10 @@ class ExporterReviewMov(ExporterReview): # ---------- generate representation data self.get_representation_data( - tags=["review", "delete"] + add_tags, + tags=["review", "need_thumbnail", "delete"] + add_tags, custom_tags=add_custom_tags, - range=True + range=True, + colorspace=colorspace ) self.log.debug("Representation... `{}`".format(self.data)) From 5432c2fa699ed0c1ebae49c64e8b4e08ca4dfadd Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Nov 2023 15:25:22 +0100 Subject: [PATCH 145/327] Global: adding `need_thumbnail` tag worklfow extract review and extract review slate --- openpype/plugins/publish/extract_review.py | 29 +++++++++++++++---- .../plugins/publish/extract_review_slate.py | 10 +++++-- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index cd0f78530a..8448c45e70 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -89,8 +89,18 @@ class ExtractReview(pyblish.api.InstancePlugin): # Make sure cleanup happens and pop representations with "delete" tag. for repre in tuple(instance.data["representations"]): tags = repre.get("tags") or [] - if "delete" in tags and "thumbnail" not in tags: - instance.data["representations"].remove(repre) + # Representation is not marked to be deleted + if "delete" not in tags: + continue + + # The representation can be used as thumbnail source + if "thumbnail" in tags or "need_thumbnail" in tags: + continue + + self.log.debug( + "Removing representation: {}".format(repre) + ) + instance.data["representations"].remove(repre) def _get_outputs_for_instance(self, instance): host_name = instance.context.data["hostName"] @@ -321,19 +331,26 @@ class ExtractReview(pyblish.api.InstancePlugin): # Create copy of representation new_repre = copy.deepcopy(repre) + new_tags = new_repre.get("tags") or [] # Make sure new representation has origin staging dir # - this is because source representation may change # it's staging dir because of ffmpeg conversion new_repre["stagingDir"] = src_repre_staging_dir # Remove "delete" tag from new repre if there is - if "delete" in new_repre["tags"]: - new_repre["tags"].remove("delete") + if "delete" in new_tags: + new_tags.remove("delete") + + if "need_thumbnail" in new_tags: + new_tags.remove("need_thumbnail") # Add additional tags from output definition to representation for tag in output_def["tags"]: - if tag not in new_repre["tags"]: - new_repre["tags"].append(tag) + if tag not in new_tags: + new_tags.append(tag) + + # Return tags to new representation + new_repre["tags"] = new_tags # Add burnin link from output definition to representation for burnin in output_def["burnins"]: diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index d89fbb90c4..4e3406d3f9 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -376,9 +376,13 @@ class ExtractReviewSlate(publish.Extractor): # Remove any representations tagged for deletion. for repre in inst_data.get("representations", []): - if "delete" in repre.get("tags", []): - self.log.debug("Removing representation: {}".format(repre)) - inst_data["representations"].remove(repre) + tags = repre.get("tags", []) + if "delete" not in tags: + continue + if "need_thumbnail" in tags: + continue + self.log.debug("Removing representation: {}".format(repre)) + inst_data["representations"].remove(repre) self.log.debug(inst_data["representations"]) From 7aacc4f0ec37ca3f2b673a470216376a4360d01a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Nov 2023 15:40:14 +0100 Subject: [PATCH 146/327] global: extract review with `need_thumbnail` tag explicit source --- openpype/plugins/publish/extract_thumbnail.py | 86 +++++++++++++++++-- 1 file changed, 78 insertions(+), 8 deletions(-) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 0ddbb3f40b..839a0f70f2 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -29,6 +29,26 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): ffmpeg_args = None def process(self, instance): + # run main process + self._main_process(instance) + + # Make sure cleanup happens to representations which are having both + # tags `delete` and `need_thumbnail` + for repre in tuple(instance.data["representations"]): + tags = repre.get("tags") or [] + # skip representations which are going to be published on farm + if "publish_on_farm" in tags: + continue + if ( + "delete" in tags + and "need_thumbnail" in tags + ): + self.log.debug( + "Removing representation: {}".format(repre) + ) + instance.data["representations"].remove(repre) + + def _main_process(self, instance): subset_name = instance.data["subset"] instance_repres = instance.data.get("representations") if not instance_repres: @@ -61,7 +81,13 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): self.log.debug("Skipping crypto passes.") return - filtered_repres = self._get_filtered_repres(instance) + # first check for any explicitly marked representations for thumbnail + explicit_repres = self._get_explicit_repres_for_thumbnail(instance) + if explicit_repres: + filtered_repres = explicit_repres + else: + filtered_repres = self._get_filtered_repres(instance) + if not filtered_repres: self.log.info( "Instance doesn't have representations that can be used " @@ -120,8 +146,21 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): if not thumbnail_created: continue + if len(explicit_repres) > 1: + repre_name = "thumbnail_{}".format(repre["outputName"]) + else: + repre_name = "thumbnail" + + # add thumbnail path to instance data for integrator + instance_thumb_path = instance.data.get("thumbnailPath") + if ( + not instance_thumb_path + or not os.path.isfile(instance_thumb_path) + ): + instance.data["thumbnailPath"] = full_output_path + new_repre = { - "name": "thumbnail", + "name": repre_name, "ext": "jpg", "files": jpeg_file, "stagingDir": dst_staging, @@ -130,15 +169,23 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): } # adding representation - self.log.debug( - "Adding thumbnail representation: {}".format(new_repre) - ) instance.data["representations"].append(new_repre) - # There is no need to create more then one thumbnail - break + + if explicit_repres: + # this key will then align assetVersion ftrack thumbnail sync + new_repre["outputName"] = repre["outputName"] + self.log.debug( + "Adding explicit thumbnail representation: {}".format( + new_repre)) + else: + self.log.debug( + "Adding thumbnail representation: {}".format(new_repre) + ) + # There is no need to create more then one thumbnail + break if not thumbnail_created: - self.log.warning("Thumbanil has not been created.") + self.log.warning("Thumbnail has not been created.") def _is_review_instance(self, instance): # TODO: We should probably handle "not creating" of thumbnail @@ -154,6 +201,29 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): return True return False + def _get_explicit_repres_for_thumbnail(self, instance): + src_repres = instance.data.get("representations") or [] + # This is mainly for Nuke where we have multiple representations for + # one instance. We want to use only one representation for thumbnail + # first check if any of the representations have + # `need-thumbnail` in tags and add them to filtered_repres + need_thumb_repres = [ + repre for repre in src_repres + if "need_thumbnail" in repre.get("tags", []) + if "publish_on_farm" not in repre.get("tags", []) + ] + if not need_thumb_repres: + return [] + + self.log.info( + "Instance has representation with tag `need_thumbnail`. " + "Using only this representations for thumbnail creation. " + ) + self.log.debug( + "Representations: {}".format(pformat(need_thumb_repres)) + ) + return need_thumb_repres + def _get_filtered_repres(self, instance): filtered_repres = [] src_repres = instance.data.get("representations") or [] From f4354080188c6949463a3c76181e88efc3e6cbef Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Nov 2023 15:41:21 +0100 Subject: [PATCH 147/327] typo --- openpype/plugins/publish/extract_thumbnail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 839a0f70f2..4ace429cf4 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -206,7 +206,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # This is mainly for Nuke where we have multiple representations for # one instance. We want to use only one representation for thumbnail # first check if any of the representations have - # `need-thumbnail` in tags and add them to filtered_repres + # `need_thumbnail` in tags and add them to filtered_repres need_thumb_repres = [ repre for repre in src_repres if "need_thumbnail" in repre.get("tags", []) From d3de3fc295ce0cc155be13a16f33ae28ccdf545a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Nov 2023 17:04:35 +0100 Subject: [PATCH 148/327] removing input arguments --- openpype/lib/transcoding.py | 112 ++++++++---------- openpype/plugins/publish/extract_thumbnail.py | 1 - 2 files changed, 51 insertions(+), 62 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 07acc309d2..20394c8a5e 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -592,29 +592,7 @@ def convert_for_ffmpeg( oiio_cmd.extend(["--compression", compression]) # Collect channels to export - channel_names = input_info["channelnames"] - review_channels = get_convert_rgb_channels(channel_names) - if review_channels is None: - raise ValueError( - "Couldn't find channels that can be used for conversion." - ) - - red, green, blue, alpha = review_channels - input_channels = [red, green, blue] - channels_arg = "R={},G={},B={}".format(red, green, blue) - if alpha is not None: - channels_arg += ",A={}".format(alpha) - input_channels.append(alpha) - input_channels_str = ",".join(input_channels) - - subimages = input_info.get("subimages") - input_arg = "-i" - if subimages is None or subimages == 1: - # Tell oiiotool which channels should be loaded - # - other channels are not loaded to memory so helps to avoid memory - # leak issues - # - this option is crashing if used on multipart/subimages exrs - input_arg += ":ch={}".format(input_channels_str) + input_arg, channels_arg = get_oiio_input_and_channel_args(input_info) oiio_cmd.extend([ input_arg, first_input_path, @@ -677,6 +655,37 @@ def convert_for_ffmpeg( run_subprocess(oiio_cmd, logger=logger) +def get_oiio_input_and_channel_args(oiio_input_info): + channel_names = oiio_input_info["channelnames"] + review_channels = get_convert_rgb_channels(channel_names) + + if review_channels is None: + raise ValueError( + "Couldn't find channels that can be used for conversion." + ) + + red, green, blue, alpha = review_channels + input_channels = [red, green, blue] + + # TODO find subimage inder where rgba is available for multipart exrs + channels_arg = "R={},G={},B={}".format(red, green, blue) + if alpha is not None: + channels_arg += ",A={}".format(alpha) + input_channels.append(alpha) + + input_channels_str = ",".join(input_channels) + + subimages = oiio_input_info.get("subimages") + input_arg = "-i" + if subimages is None or subimages == 1: + # Tell oiiotool which channels should be loaded + # - other channels are not loaded to memory so helps to avoid memory + # leak issues + # - this option is crashing if used on multipart exrs + input_arg += ":ch={}".format(input_channels_str) + + return input_arg, channels_arg + def convert_input_paths_for_ffmpeg( input_paths, output_dir, @@ -709,6 +718,7 @@ def convert_input_paths_for_ffmpeg( first_input_path = input_paths[0] ext = os.path.splitext(first_input_path)[1].lower() + if ext != ".exr": raise ValueError(( "Function 'convert_for_ffmpeg' currently support only" @@ -724,30 +734,7 @@ def convert_input_paths_for_ffmpeg( compression = "none" # Collect channels to export - channel_names = input_info["channelnames"] - review_channels = get_convert_rgb_channels(channel_names) - if review_channels is None: - raise ValueError( - "Couldn't find channels that can be used for conversion." - ) - - red, green, blue, alpha = review_channels - input_channels = [red, green, blue] - # TODO find subimage inder where rgba is available for multipart exrs - channels_arg = "R={},G={},B={}".format(red, green, blue) - if alpha is not None: - channels_arg += ",A={}".format(alpha) - input_channels.append(alpha) - input_channels_str = ",".join(input_channels) - - subimages = input_info.get("subimages") - input_arg = "-i" - if subimages is None or subimages == 1: - # Tell oiiotool which channels should be loaded - # - other channels are not loaded to memory so helps to avoid memory - # leak issues - # - this option is crashing if used on multipart exrs - input_arg += ":ch={}".format(input_channels_str) + input_arg, channels_arg = get_oiio_input_and_channel_args(input_info) for input_path in input_paths: # Prepare subprocess arguments @@ -1149,7 +1136,6 @@ def convert_colorspace( target_colorspace=None, view=None, display=None, - additional_input_args=None, additional_command_args=None, logger=None, ): @@ -1171,7 +1157,6 @@ def convert_colorspace( both 'view' and 'display' must be filled (if 'target_colorspace') display (str): name for display-referred reference space (ocio valid) both 'view' and 'display' must be filled (if 'target_colorspace') - additional_input_args (list): input arguments for oiiotool additional_command_args (list): arguments for oiiotool (like binary depth for .dpx) logger (logging.Logger): Logger used for logging. @@ -1181,23 +1166,28 @@ def convert_colorspace( if logger is None: logger = logging.getLogger(__name__) - # prepare main oiio command args - args = [ - input_path, + input_info = get_oiio_info_for_input(input_path, logger=logger) + + # Collect channels to export + input_arg, channels_arg = get_oiio_input_and_channel_args(input_info) + + # Prepare subprocess arguments + oiio_cmd = get_oiio_tool_args( + "oiiotool", # Don't add any additional attributes "--nosoftwareattrib", "--colorconfig", config_path - ] - - # prepend any additional args if available - if additional_input_args: - args = additional_input_args + args - - oiio_cmd = get_oiio_tool_args( - "oiiotool", - *args ) + oiio_cmd.extend([ + input_arg, input_path, + # Tell oiiotool which channels should be put to top stack + # (and output) + "--ch", channels_arg, + # Use first subimage + "--subimage", "0" + ]) + if all([target_colorspace, view, display]): raise ValueError("Colorspace and both screen and display" " cannot be set together." diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 22c579ab86..2215c94f42 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -226,7 +226,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): display=colorspace_data.get("display") or oiio_default_display, view=colorspace_data.get("view") or oiio_default_view, target_colorspace=oiio_default_colorspace, - additional_input_args=["-i:ch=R,G,B"], logger=self.log, ) except Exception: From 50c37b7e110cc0c9e5c6d272dbc7f1d7f19ea87f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Nov 2023 17:05:06 +0100 Subject: [PATCH 149/327] typos and code style --- openpype/lib/transcoding.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 20394c8a5e..334ea25ea4 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -536,7 +536,7 @@ def convert_for_ffmpeg( input_frame_end=None, logger=None ): - """Contert source file to format supported in ffmpeg. + """Convert source file to format supported in ffmpeg. Currently can convert only exrs. @@ -613,7 +613,7 @@ def convert_for_ffmpeg( continue # Remove attributes that have string value longer than allowed length - # for ffmpeg or when contain unallowed symbols + # for ffmpeg or when contain prohibited symbols erase_reason = "Missing reason" erase_attribute = False if len(attr_value) > MAX_FFMPEG_STRING_LEN: @@ -656,6 +656,15 @@ def convert_for_ffmpeg( def get_oiio_input_and_channel_args(oiio_input_info): + """Get input and channel arguments for oiiotool. + + Args: + oiio_input_info (dict): Information about input from oiio tool. + Should be output of function `get_oiio_info_for_input`. + + Returns: + tuple[str, str]: Tuple of input and channel arguments. + """ channel_names = oiio_input_info["channelnames"] review_channels = get_convert_rgb_channels(channel_names) @@ -667,7 +676,7 @@ def get_oiio_input_and_channel_args(oiio_input_info): red, green, blue, alpha = review_channels input_channels = [red, green, blue] - # TODO find subimage inder where rgba is available for multipart exrs + # TODO find subimage where rgba is available for multipart exrs channels_arg = "R={},G={},B={}".format(red, green, blue) if alpha is not None: channels_arg += ",A={}".format(alpha) @@ -686,6 +695,7 @@ def get_oiio_input_and_channel_args(oiio_input_info): return input_arg, channels_arg + def convert_input_paths_for_ffmpeg( input_paths, output_dir, @@ -704,7 +714,7 @@ def convert_input_paths_for_ffmpeg( Args: input_paths (str): Paths that should be converted. It is expected that - contains single file or image sequence of samy type. + contains single file or image sequence of same type. output_dir (str): Path to directory where output will be rendered. Must not be same as input's directory. logger (logging.Logger): Logger used for logging. @@ -761,7 +771,7 @@ def convert_input_paths_for_ffmpeg( continue # Remove attributes that have string value longer than allowed - # length for ffmpeg or when containing unallowed symbols + # length for ffmpeg or when containing prohibited symbols erase_reason = "Missing reason" erase_attribute = False if len(attr_value) > MAX_FFMPEG_STRING_LEN: @@ -1008,9 +1018,7 @@ def _ffmpeg_h264_codec_args(stream_data, source_ffmpeg_cmd): if pix_fmt: output.extend(["-pix_fmt", pix_fmt]) - output.extend(["-intra"]) - output.extend(["-g", "1"]) - + output.extend(["-intra", "-g", "1"]) return output From 8914ea0da998074d7cf1709af486e67c2ff8f90a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Nov 2023 17:09:23 +0100 Subject: [PATCH 150/327] return True if successful oiio conversion --- openpype/plugins/publish/extract_thumbnail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 2215c94f42..a97ffdf569 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -235,7 +235,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): ) return False - return dst_path + return True def create_thumbnail_ffmpeg(self, src_path, dst_path): self.log.debug("Extracting thumbnail with FFMPEG: {}".format(dst_path)) From 09ad8cb4f1d9f6e8f7c72f99633e97f0bfa4fd4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 21 Nov 2023 17:12:19 +0100 Subject: [PATCH 151/327] :art: launcher action this needs to be moved to subprocess to show UI properly --- .../actions/generate_asset_usage_report.py | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 openpype/plugins/actions/generate_asset_usage_report.py diff --git a/openpype/plugins/actions/generate_asset_usage_report.py b/openpype/plugins/actions/generate_asset_usage_report.py new file mode 100644 index 0000000000..60630c5a43 --- /dev/null +++ b/openpype/plugins/actions/generate_asset_usage_report.py @@ -0,0 +1,157 @@ +""" +TODO: we need to move it to subprocess to show UI +""" +import csv +import os +import tempfile +import time + +from qtpy import QtWidgets +from qtpy.QtCore import Qt +from qtpy.QtGui import QClipboard + +from pymongo.collection import Collection + +from openpype.client import OpenPypeMongoConnection +from openpype.pipeline import LauncherAction + + +class ReportWindow(QtWidgets.QWidget): + def __init__(self): + super().__init__() + + self.text_area = QtWidgets.QTextEdit(self) + self.copy_button = QtWidgets.QPushButton('Copy to Clipboard', self) + self.save_button = QtWidgets.QPushButton('Save to CSV File', self) + + self.copy_button.clicked.connect(self.copy_to_clipboard) + self.save_button.clicked.connect(self.save_to_file) + + layout = QtWidgets.QVBoxLayout(self) + layout.addWidget(self.text_area) + layout.addWidget(self.copy_button) + layout.addWidget(self.save_button) + + def copy_to_clipboard(self): + clipboard = QtWidgets.QApplication.clipboard() + clipboard.setText(self.text_area.toPlainText(), QClipboard.Clipboard) + + def save_to_file(self): + file_name, _ = QtWidgets.QFileDialog.getSaveFileName(self, 'Save File') + if file_name: + with open(file_name, 'w') as file: + file.write(self.text_area.toPlainText()) + + def set_content(self, content): + self.text_area.setText(content) + + +class OpenTaskPath(LauncherAction): + name = "get_asset_usage_report" + label = "Asset Usage Report" + icon = "list" + order = 500 + + def is_compatible(self, session): + """Return whether the action is compatible with the session""" + return bool(session.get("AVALON_ASSET")) + + def _get_subset(self, version_id, project: Collection): + pipeline = [ + { + "$match": { + "_id": version_id + }, + }, { + "$lookup": { + "from": project.name, + "localField": "parent", + "foreignField": "_id", + "as": "parents" + } + } + ] + + result = project.aggregate(pipeline) + doc = next(result) + # print(doc) + return { + "name": f'{"/".join(doc["parents"][0]["data"]["parents"])}/{doc["parents"][0]["name"]}/{doc["name"]}', + "family": doc["data"].get("family") or doc["data"].get("families")[0] + } + + def process(self, session, **kwargs): + start = time.perf_counter() + project = session["AVALON_PROJECT"] + + pipeline = [ + { + "$match": { + "data.inputLinks": { + "$exists": True, + "$ne": [] + }, + "data.families": {"$in": ["workfile"]} + } + }, { + "$lookup": { + "from": "OP01_CG_demo", + "localField": "data.inputLinks.id", + "foreignField": "_id", + "as": "linked_docs" + } + } + ] + + client = OpenPypeMongoConnection.get_mongo_client() + db = client["avalon"] + + result = db[project].aggregate(pipeline) + + asset_map = [] + for doc in result: + source = { + "source": self._get_subset(doc["parent"], db[project]), + } + source["source"].update({"version": doc["name"]}) + refs = [ + { + "subset": self._get_subset(linked["parent"], db[project]), + "version": linked.get("name") + } + for linked in doc["linked_docs"] + ] + source["refs"] = refs + asset_map.append(source) + + # for ref in asset_map: + # print(ref) + + grouped = {} + + for asset in asset_map: + for ref in asset["refs"]: + key = f'{ref["subset"]["name"]} (v{ref["version"]})' + if key in grouped: + grouped[key].append(asset["source"]) + else: + grouped[key] = [asset["source"]] + + temp = tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".csv") + try: + with open(temp.name, "w", newline="") as csvfile: + writer = csv.writer(csvfile, delimiter=";") + writer.writerow(["Subset", "Used in", "Version"]) + for key, value in grouped.items(): + writer.writerow([key, "", ""]) + for source in value: + writer.writerow(["", source["name"], source["version"]]) + finally: + temp.close() + + end = time.perf_counter() + app = QtWidgets.QApplication.instance() + window = ReportWindow() + # window.set_content(open(temp.name).read()) + window.show() + print(f"Finished in {end - start:0.4f} seconds", 2) From 9d2c600c8be3dcb75408665249ed7a4f3c130f53 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Nov 2023 17:23:14 +0100 Subject: [PATCH 152/327] nuke: typo and wrong log in slate extractor --- openpype/hosts/nuke/plugins/publish/extract_slate_frame.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py index 7befb7b7f3..5816434f2b 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py +++ b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py @@ -276,7 +276,7 @@ class ExtractSlateFrame(publish.Extractor): if not matching_repre: self.log.info( - "Matching reresentation was not found." + "Matching representation was not found." " Representation files were not filled with slate." ) return @@ -294,7 +294,7 @@ class ExtractSlateFrame(publish.Extractor): self.log.debug( "__ matching_repre: {}".format(pformat(matching_repre))) - self.log.warning("Added slate frame to representation files") + self.log.info("Added slate frame to representation files") def add_comment_slate_node(self, instance, node): From 51992419e731e2a0bf51607ab640ea5158d61be5 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Nov 2023 17:25:20 +0100 Subject: [PATCH 153/327] nuke: removing thumbnail extractor --- .../nuke/plugins/publish/extract_thumbnail.py | 216 ------------------ .../defaults/project_settings/nuke.json | 62 ----- .../schemas/schema_nuke_publish.json | 75 ------ 3 files changed, 353 deletions(-) delete mode 100644 openpype/hosts/nuke/plugins/publish/extract_thumbnail.py diff --git a/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py b/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py deleted file mode 100644 index de7567c1b1..0000000000 --- a/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py +++ /dev/null @@ -1,216 +0,0 @@ -import sys -import os -import nuke -import pyblish.api - -from openpype.pipeline import publish -from openpype.hosts.nuke import api as napi -from openpype.hosts.nuke.api.lib import set_node_knobs_from_settings - - -# Python 2/3 compatibility -if sys.version_info[0] >= 3: - unicode = str - - -class ExtractThumbnail(publish.Extractor): - """Extracts movie and thumbnail with baked in luts - - must be run after extract_render_local.py - - """ - - order = pyblish.api.ExtractorOrder + 0.011 - label = "Extract Thumbnail" - - families = ["review"] - hosts = ["nuke"] - - # settings - use_rendered = False - bake_viewer_process = True - bake_viewer_input_process = True - nodes = {} - reposition_nodes = None - - def process(self, instance): - if instance.data.get("farm"): - return - - with napi.maintained_selection(): - self.log.debug("instance: {}".format(instance)) - self.log.debug("instance.data[families]: {}".format( - instance.data["families"])) - - if instance.data.get("bakePresets"): - for o_name, o_data in instance.data["bakePresets"].items(): - self.render_thumbnail(instance, o_name, **o_data) - else: - viewer_process_switches = { - "bake_viewer_process": True, - "bake_viewer_input_process": True - } - self.render_thumbnail( - instance, None, **viewer_process_switches) - - def render_thumbnail(self, instance, output_name=None, **kwargs): - first_frame = instance.data["frameStartHandle"] - last_frame = instance.data["frameEndHandle"] - colorspace = instance.data["colorspace"] - - # find frame range and define middle thumb frame - mid_frame = int((last_frame - first_frame) / 2) - - # solve output name if any is set - output_name = output_name or "" - - bake_viewer_process = kwargs["bake_viewer_process"] - bake_viewer_input_process_node = kwargs[ - "bake_viewer_input_process"] - - node = instance.data["transientData"]["node"] # group node - self.log.debug("Creating staging dir...") - - if "representations" not in instance.data: - instance.data["representations"] = [] - - staging_dir = os.path.normpath( - os.path.dirname(instance.data['path'])) - - instance.data["stagingDir"] = staging_dir - - self.log.debug( - "StagingDir `{0}`...".format(instance.data["stagingDir"])) - - temporary_nodes = [] - - # try to connect already rendered images - previous_node = node - collection = instance.data.get("collection", None) - self.log.debug("__ collection: `{}`".format(collection)) - - if collection: - # get path - fhead = collection.format("{head}") - - thumb_fname = list(collection)[mid_frame] - else: - fname = thumb_fname = os.path.basename( - instance.data.get("path", None)) - fhead = os.path.splitext(fname)[0] + "." - - self.log.debug("__ fhead: `{}`".format(fhead)) - - if "#" in fhead: - fhead = fhead.replace("#", "")[:-1] - - path_render = os.path.join( - staging_dir, thumb_fname).replace("\\", "/") - self.log.debug("__ path_render: `{}`".format(path_render)) - - if self.use_rendered and os.path.isfile(path_render): - # check if file exist otherwise connect to write node - rnode = nuke.createNode("Read") - rnode["file"].setValue(path_render) - rnode["colorspace"].setValue(colorspace) - - # turn it raw if none of baking is ON - if all([ - not self.bake_viewer_input_process, - not self.bake_viewer_process - ]): - rnode["raw"].setValue(True) - - temporary_nodes.append(rnode) - previous_node = rnode - - if self.reposition_nodes is None: - # [deprecated] create reformat node old way - reformat_node = nuke.createNode("Reformat") - ref_node = self.nodes.get("Reformat", None) - if ref_node: - for k, v in ref_node: - self.log.debug("k, v: {0}:{1}".format(k, v)) - if isinstance(v, unicode): - v = str(v) - reformat_node[k].setValue(v) - - reformat_node.setInput(0, previous_node) - previous_node = reformat_node - temporary_nodes.append(reformat_node) - else: - # create reformat node new way - for repo_node in self.reposition_nodes: - node_class = repo_node["node_class"] - knobs = repo_node["knobs"] - node = nuke.createNode(node_class) - set_node_knobs_from_settings(node, knobs) - - # connect in order - node.setInput(0, previous_node) - previous_node = node - temporary_nodes.append(node) - - # only create colorspace baking if toggled on - if bake_viewer_process: - if bake_viewer_input_process_node: - # get input process and connect it to baking - ipn = napi.get_view_process_node() - if ipn is not None: - ipn.setInput(0, previous_node) - previous_node = ipn - temporary_nodes.append(ipn) - - dag_node = nuke.createNode("OCIODisplay") - dag_node.setInput(0, previous_node) - previous_node = dag_node - temporary_nodes.append(dag_node) - - thumb_name = "thumbnail" - # only add output name and - # if there are more than one bake preset - if ( - output_name - and len(instance.data.get("bakePresets", {}).keys()) > 1 - ): - thumb_name = "{}_{}".format(output_name, thumb_name) - - # create write node - write_node = nuke.createNode("Write") - file = fhead[:-1] + thumb_name + ".jpg" - thumb_path = os.path.join(staging_dir, file).replace("\\", "/") - - # add thumbnail to cleanup - instance.context.data["cleanupFullPaths"].append(thumb_path) - - # make sure only one thumbnail path is set - # and it is existing file - instance_thumb_path = instance.data.get("thumbnailPath") - if not instance_thumb_path or not os.path.isfile(instance_thumb_path): - instance.data["thumbnailPath"] = thumb_path - - write_node["file"].setValue(thumb_path) - write_node["file_type"].setValue("jpg") - write_node["raw"].setValue(1) - write_node.setInput(0, previous_node) - temporary_nodes.append(write_node) - - repre = { - 'name': thumb_name, - 'ext': "jpg", - "outputName": thumb_name, - 'files': file, - "stagingDir": staging_dir, - "tags": ["thumbnail", "publish_on_farm", "delete"] - } - instance.data["representations"].append(repre) - - # Render frames - nuke.execute(write_node.name(), mid_frame, mid_frame) - - self.log.debug( - "representations: {}".format(instance.data["representations"])) - - # Clean up - for node in temporary_nodes: - nuke.delete(node) diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index 20df0ad5c2..17932c793d 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -379,68 +379,6 @@ "optional": true, "active": true }, - "ExtractThumbnail": { - "enabled": true, - "use_rendered": true, - "bake_viewer_process": true, - "bake_viewer_input_process": true, - "nodes": { - "Reformat": [ - [ - "type", - "to format" - ], - [ - "format", - "HD_1080" - ], - [ - "filter", - "Lanczos6" - ], - [ - "black_outside", - true - ], - [ - "pbb", - false - ] - ] - }, - "reposition_nodes": [ - { - "node_class": "Reformat", - "knobs": [ - { - "type": "text", - "name": "type", - "value": "to format" - }, - { - "type": "text", - "name": "format", - "value": "HD_1080" - }, - { - "type": "text", - "name": "filter", - "value": "Lanczos6" - }, - { - "type": "bool", - "name": "black_outside", - "value": true - }, - { - "type": "bool", - "name": "pbb", - "value": false - } - ] - } - ] - }, "ExtractReviewData": { "enabled": false }, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json index e0cd086119..09b67e7d1a 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json @@ -125,81 +125,6 @@ "type": "label", "label": "Extractors" }, - { - "type": "dict", - "collapsible": true, - "checkbox_key": "enabled", - "key": "ExtractThumbnail", - "label": "ExtractThumbnail", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "boolean", - "key": "use_rendered", - "label": "Use rendered images" - }, - { - "type": "boolean", - "key": "bake_viewer_process", - "label": "Bake viewer process" - }, - { - "type": "boolean", - "key": "bake_viewer_input_process", - "label": "Bake viewer input process" - }, - { - "type": "collapsible-wrap", - "label": "Nodes", - "collapsible": true, - "children": [ - { - "type": "label", - "label": "Nodes attribute will be deprecated in future releases. Use reposition_nodes instead." - }, - { - "type": "raw-json", - "key": "nodes", - "label": "Nodes [depricated]" - }, - { - "type": "label", - "label": "Reposition knobs supported only. You can add multiple reformat nodes
and set their knobs. Order of reformat nodes is important. First reformat node
will be applied first and last reformat node will be applied last." - }, - { - "key": "reposition_nodes", - "type": "list", - "label": "Reposition nodes", - "object_type": { - "type": "dict", - "children": [ - { - "key": "node_class", - "label": "Node class", - "type": "text" - }, - { - "type": "schema_template", - "name": "template_nuke_knob_inputs", - "template_data": [ - { - "label": "Node knobs", - "key": "knobs" - } - ] - } - ] - } - } - ] - } - ] - }, { "type": "dict", "collapsible": true, From 0f6cecb29a74fecb4d8ff22504de619466ceeb0d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Nov 2023 17:26:58 +0100 Subject: [PATCH 154/327] nuke: deadline not adding explicit farm representation to expected files --- .../plugins/publish/submit_nuke_deadline.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py index fb3ab2710d..182afe546d 100644 --- a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -470,6 +470,22 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin, dirname = os.path.dirname(path) file = os.path.basename(path) + # since some files might be already tagged as publish_on_farm + # we need to avoid adding them to expected files since those would be + # duplicated into metadata.json file + representations = instance.data.get("representations", []) + if representations: + # check if file is not in representations with publish_on_farm tag + for repre in representations: + # is file in representations files? + if file not in repre.get("files", []): + continue + # is publish_on_farm tag set to False? + if "publish_on_farm" in repre.get("tags", []): + self.log.debug( + "Skipping expected file: {}".format(path)) + return + if "#" in file: pparts = file.split("#") padding = "%0{}d".format(len(pparts) - 1) From e0b4cef87e9f44edec0e5faf689ba5af0f3a9ffe Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Nov 2023 17:28:25 +0100 Subject: [PATCH 155/327] nuke: deadline removing redundant code --- .../modules/deadline/plugins/publish/submit_nuke_deadline.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py index 182afe546d..7b5a8c9b2d 100644 --- a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -370,10 +370,6 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin, environment = dict({key: os.environ[key] for key in keys if key in os.environ}, **legacy_io.Session) - for _path in os.environ: - if _path.lower().startswith('openpype_'): - environment[_path] = os.environ[_path] - # to recognize render jobs if AYON_SERVER_ENABLED: environment["AYON_BUNDLE_NAME"] = os.environ["AYON_BUNDLE_NAME"] From e0776c8548990691edeb6775cc2c8103dfef9ac0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Nov 2023 17:32:37 +0100 Subject: [PATCH 156/327] farm: remove `publish_on_farm` from representations to metadata.json --- openpype/pipeline/farm/pyblish_functions.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/pipeline/farm/pyblish_functions.py b/openpype/pipeline/farm/pyblish_functions.py index 7ef3439dbd..380ada234e 100644 --- a/openpype/pipeline/farm/pyblish_functions.py +++ b/openpype/pipeline/farm/pyblish_functions.py @@ -145,6 +145,9 @@ def get_transferable_representations(instance): trans_rep = representation.copy() + # remove publish_on_farm from representations tags + trans_rep["tags"].remove("publish_on_farm") + staging_dir = trans_rep.get("stagingDir") if staging_dir: From 2f78943791a928ef9571a1148de5d744d928dcf0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Nov 2023 17:33:55 +0100 Subject: [PATCH 157/327] improving code coment --- openpype/plugins/publish/extract_burnin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_burnin.py b/openpype/plugins/publish/extract_burnin.py index 9a978ed286..56d45e477b 100644 --- a/openpype/plugins/publish/extract_burnin.py +++ b/openpype/plugins/publish/extract_burnin.py @@ -89,8 +89,8 @@ class ExtractBurnin(publish.Extractor): self.main_process(instance) - # Remove any representations tagged for deletion. - # QUESTION Is possible to have representation with "delete" tag? + # Remove only representation tagged with both + # tags `delete` and `burnin` for repre in tuple(instance.data["representations"]): if all(x in repre.get("tags", []) for x in ['delete', 'burnin']): self.log.debug("Removing representation: {}".format(repre)) From 619b4ccafc565fc9da0f7741fc768778604084c0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Nov 2023 17:38:43 +0100 Subject: [PATCH 158/327] ayon-nuke: removing extract thumbnail from settings --- .../nuke/server/settings/publish_plugins.py | 104 ------------------ server_addon/nuke/server/version.py | 2 +- 2 files changed, 1 insertion(+), 105 deletions(-) diff --git a/server_addon/nuke/server/settings/publish_plugins.py b/server_addon/nuke/server/settings/publish_plugins.py index 81663fa5aa..d76e95a638 100644 --- a/server_addon/nuke/server/settings/publish_plugins.py +++ b/server_addon/nuke/server/settings/publish_plugins.py @@ -51,17 +51,6 @@ class NodeModel(BaseSettingsModel): return value -class ThumbnailRepositionNodeModel(BaseSettingsModel): - node_class: str = Field(title="Node class") - knobs: list[KnobModel] = Field(title="Knobs", default_factory=list) - - @validator("knobs") - def ensure_unique_names(cls, value): - """Ensure name fields within the lists have unique names.""" - ensure_unique_names(value) - return value - - class CollectInstanceDataModel(BaseSettingsModel): sync_workfile_version_on_product_types: list[str] = Field( default_factory=list, @@ -89,22 +78,6 @@ class ValidateKnobsModel(BaseSettingsModel): return validate_json_dict(value) -class ExtractThumbnailModel(BaseSettingsModel): - enabled: bool = Field(title="Enabled") - use_rendered: bool = Field(title="Use rendered images") - bake_viewer_process: bool = Field(title="Bake view process") - bake_viewer_input_process: bool = Field(title="Bake viewer input process") - - nodes: list[NodeModel] = Field( - default_factory=list, - title="Nodes (deprecated)" - ) - reposition_nodes: list[ThumbnailRepositionNodeModel] = Field( - title="Reposition nodes", - default_factory=list - ) - - class ExtractReviewDataModel(BaseSettingsModel): enabled: bool = Field(title="Enabled") @@ -267,11 +240,6 @@ class PublishPuginsModel(BaseSettingsModel): title="Validate workfile attributes", default_factory=OptionalPluginModel ) - ExtractThumbnail: ExtractThumbnailModel = Field( - title="Extract Thumbnail", - default_factory=ExtractThumbnailModel, - section="Extractors" - ) ExtractReviewData: ExtractReviewDataModel = Field( title="Extract Review Data", default_factory=ExtractReviewDataModel @@ -350,78 +318,6 @@ DEFAULT_PUBLISH_PLUGIN_SETTINGS = { "optional": True, "active": True }, - "ExtractThumbnail": { - "enabled": True, - "use_rendered": True, - "bake_viewer_process": True, - "bake_viewer_input_process": True, - "nodes": [ - { - "name": "Reformat01", - "nodeclass": "Reformat", - "dependency": "", - "knobs": [ - { - "type": "text", - "name": "type", - "text": "to format" - }, - { - "type": "text", - "name": "format", - "text": "HD_1080" - }, - { - "type": "text", - "name": "filter", - "text": "Lanczos6" - }, - { - "type": "boolean", - "name": "black_outside", - "boolean": True - }, - { - "type": "boolean", - "name": "pbb", - "boolean": False - } - ] - } - ], - "reposition_nodes": [ - { - "node_class": "Reformat", - "knobs": [ - { - "type": "text", - "name": "type", - "text": "to format" - }, - { - "type": "text", - "name": "format", - "text": "HD_1080" - }, - { - "type": "text", - "name": "filter", - "text": "Lanczos6" - }, - { - "type": "boolean", - "name": "black_outside", - "boolean": True - }, - { - "type": "boolean", - "name": "pbb", - "boolean": False - } - ] - } - ] - }, "ExtractReviewData": { "enabled": False }, diff --git a/server_addon/nuke/server/version.py b/server_addon/nuke/server/version.py index 1276d0254f..0a8da88258 100644 --- a/server_addon/nuke/server/version.py +++ b/server_addon/nuke/server/version.py @@ -1 +1 @@ -__version__ = "0.1.5" +__version__ = "0.1.6" From 93b5a3941d23cf9f8a0567a9f0808fc7f0fdba5b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Nov 2023 21:43:16 +0100 Subject: [PATCH 159/327] adding nuke host into extract thumbnail plugin --- openpype/plugins/publish/extract_thumbnail.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 4ace429cf4..1c970affcd 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -22,10 +22,17 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): "imagesequence", "render", "render2d", "prerender", "source", "clip", "take", "online", "image" ] - hosts = ["shell", "fusion", "resolve", "traypublisher", "substancepainter"] + hosts = [ + "shell", + "fusion", + "resolve", + "traypublisher", + "substancepainter", + "nuke", + ] enabled = False - # presetable attribute + # presentable attribute ffmpeg_args = None def process(self, instance): @@ -220,7 +227,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): "Using only this representations for thumbnail creation. " ) self.log.debug( - "Representations: {}".format(pformat(need_thumb_repres)) + "Representations: {}".format(need_thumb_repres) ) return need_thumb_repres From 178f044869b4527e417129a7c8200b27a1c4f912 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Nov 2023 21:49:17 +0100 Subject: [PATCH 160/327] ftrack: ignore representations with `publish_on_farm` tag --- .../ftrack/plugins/publish/integrate_ftrack_instances.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py index 75f43cb22f..fc97b2c516 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py @@ -126,6 +126,8 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): other_representations = [] has_movie_review = False for repre in instance_repres: + if "publish_on_farm" in repre.get("tags", []): + continue self.log.debug("Representation {}".format(repre)) repre_tags = repre.get("tags") or [] if repre.get("thumbnail") or "thumbnail" in repre_tags: From b8838d70cf55b0888dbda0adf63a5dbf52d1ae9f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Nov 2023 21:55:48 +0100 Subject: [PATCH 161/327] ignoring `publish_on_farm` representations it was creating thumbnails in Ftrack even the version had to be created in future --- openpype/plugins/publish/extract_thumbnail.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 1c970affcd..a46c3acf12 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -234,9 +234,16 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): def _get_filtered_repres(self, instance): filtered_repres = [] src_repres = instance.data.get("representations") or [] + for repre in src_repres: self.log.debug(repre) tags = repre.get("tags") or [] + + if "publish_on_farm" in tags: + # only process representations with are going + # to be published locally + continue + valid = "review" in tags or "thumb-nuke" in tags if not valid: continue From ec47c7466fc4e133c0c776aea66b046576eef69f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Nov 2023 22:12:41 +0100 Subject: [PATCH 162/327] enhancing code --- .../plugins/publish/integrate_ftrack_instances.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py index fc97b2c516..5f70f5340b 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py @@ -126,19 +126,25 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): other_representations = [] has_movie_review = False for repre in instance_repres: - if "publish_on_farm" in repre.get("tags", []): - continue - self.log.debug("Representation {}".format(repre)) repre_tags = repre.get("tags") or [] + # exclude representations with are going to be published on farm + if "publish_on_farm" in repre_tags: + continue + + self.log.debug("Representation {}".format(repre)) + + # include only thumbnail representations if repre.get("thumbnail") or "thumbnail" in repre_tags: thumbnail_representations.append(repre) + # include only review representations elif "ftrackreview" in repre_tags: review_representations.append(repre) if self._is_repre_video(repre): has_movie_review = True else: + # include all other representations other_representations.append(repre) # Prepare ftrack locations From 9b1d24acb87428aa8c74e6aaf5cdae6b7cfb3551 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Nov 2023 22:18:43 +0100 Subject: [PATCH 163/327] improving code in nuke deadline submitter --- .../plugins/publish/submit_nuke_deadline.py | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py index 7b5a8c9b2d..8cb4e9eea4 100644 --- a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -470,17 +470,16 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin, # we need to avoid adding them to expected files since those would be # duplicated into metadata.json file representations = instance.data.get("representations", []) - if representations: - # check if file is not in representations with publish_on_farm tag - for repre in representations: - # is file in representations files? - if file not in repre.get("files", []): - continue - # is publish_on_farm tag set to False? - if "publish_on_farm" in repre.get("tags", []): - self.log.debug( - "Skipping expected file: {}".format(path)) - return + # check if file is not in representations with publish_on_farm tag + for repre in representations: + # Skip if 'publish_on_farm' not available + if "publish_on_farm" not in repre.get("tags", []): + continue + # is file in representations files? + if file in repre.get("files", []): + self.log.debug( + "Skipping expected file: {}".format(path)) + return if "#" in file: pparts = file.split("#") From a4225a2a35734df3f246361f3171b8675be0d5f9 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Wed, 22 Nov 2023 03:25:56 +0000 Subject: [PATCH 164/327] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index 9d8724f926..89067af269 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.17.7-nightly.1" +__version__ = "3.17.7-nightly.2" From d47128cb1f510845b9706c5ba59fcc56a1f0edd7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 22 Nov 2023 03:26:34 +0000 Subject: [PATCH 165/327] chore(): update bug report / version --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index f484016bfe..e2afcdaac7 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,7 @@ body: label: Version description: What version are you running? Look to OpenPype Tray options: + - 3.17.7-nightly.2 - 3.17.7-nightly.1 - 3.17.6 - 3.17.6-nightly.3 @@ -134,7 +135,6 @@ body: - 3.15.2-nightly.5 - 3.15.2-nightly.4 - 3.15.2-nightly.3 - - 3.15.2-nightly.2 validations: required: true - type: dropdown From 835f50e57cef2917330362f96b28b32dfb0a012d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 22 Nov 2023 12:13:36 +0100 Subject: [PATCH 166/327] global: adding settings for target size and frame picking --- openpype/plugins/publish/extract_thumbnail.py | 122 ++++++++++++++++-- 1 file changed, 111 insertions(+), 11 deletions(-) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 0ddbb3f40b..9f764c435c 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -1,3 +1,4 @@ +import copy import os import subprocess import tempfile @@ -5,13 +6,14 @@ import tempfile import pyblish.api from openpype.lib import ( get_ffmpeg_tool_args, + get_ffprobe_data, get_oiio_tool_args, is_oiio_supported, run_subprocess, path_to_subprocess_arg, ) - +from openpype.lib.transcoding import VIDEO_EXTENSIONS class ExtractThumbnail(pyblish.api.InstancePlugin): """Create jpg thumbnail from sequence using ffmpeg""" @@ -25,7 +27,13 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): hosts = ["shell", "fusion", "resolve", "traypublisher", "substancepainter"] enabled = False - # presetable attribute + publishing_thumbnail = False + target_size = { + "type": "resize", + "width": 1920, + "height": 1080 + } + duration_split = 0.5 ffmpeg_args = None def process(self, instance): @@ -82,15 +90,39 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): oiio_supported = is_oiio_supported() for repre in filtered_repres: repre_files = repre["files"] + src_staging = os.path.normpath(repre["stagingDir"]) if not isinstance(repre_files, (list, tuple)): - input_file = repre_files + # convert any video file to frame so oiio doesn't need to + # read video file (it is slow) and also we are having control + # over which frame is used for thumbnail + # this will also work with ffmpeg fallback conversion in case + # oiio is not supported + repre_extension = os.path.splitext(repre_files)[1] + if repre_extension in VIDEO_EXTENSIONS: + video_file_path = os.path.join( + src_staging, repre_files + ) + file_path = self._create_frame_from_video( + video_file_path, + dst_staging + ) + if file_path: + src_staging, input_file = os.path.split(file_path) + else: + # if it is not video file then just use first file + input_file = repre_files else: - file_index = int(float(len(repre_files)) * 0.5) + repre_files_thumb = copy(repre_files) + # exclude first frame if slate in representation tags + if "slate-frame" in repre.get("tags", []): + repre_files_thumb = repre_files_thumb[1:] + file_index = int( + float(len(repre_files_thumb)) * self.duration_split) input_file = repre_files[file_index] - src_staging = os.path.normpath(repre["stagingDir"]) full_input_path = os.path.join(src_staging, input_file) self.log.debug("input {}".format(full_input_path)) + filename = os.path.splitext(input_file)[0] jpeg_file = filename + "_thumb.jpg" full_output_path = os.path.join(dst_staging, jpeg_file) @@ -99,7 +131,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): self.log.debug("Trying to convert with OIIO") # If the input can read by OIIO then use OIIO method for # conversion otherwise use ffmpeg - thumbnail_created = self.create_thumbnail_oiio( + thumbnail_created = self._create_thumbnail_oiio( full_input_path, full_output_path ) @@ -112,7 +144,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): " can't be read by OIIO." ) - thumbnail_created = self.create_thumbnail_ffmpeg( + thumbnail_created = self._create_thumbnail_ffmpeg( full_input_path, full_output_path ) @@ -120,13 +152,19 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): if not thumbnail_created: continue + new_repre_tags = ["thumbnail"] + # for workflows which needs to have thumbnails published as + # separate representations `delete` tag should not be added + if not self.publishing_thumbnail: + new_repre_tags.append("delete") + new_repre = { "name": "thumbnail", "ext": "jpg", "files": jpeg_file, "stagingDir": dst_staging, "thumbnail": True, - "tags": ["thumbnail"] + "tags": new_repre_tags } # adding representation @@ -173,7 +211,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): filtered_repres.append(repre) return filtered_repres - def create_thumbnail_oiio(self, src_path, dst_path): + def _create_thumbnail_oiio(self, src_path, dst_path): self.log.debug("Extracting thumbnail with OIIO: {}".format(dst_path)) oiio_cmd = get_oiio_tool_args( "oiiotool", @@ -191,9 +229,9 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): ) return False - def create_thumbnail_ffmpeg(self, src_path, dst_path): + def _create_thumbnail_ffmpeg(self, src_path, dst_path): self.log.debug("Extracting thumbnail with FFMPEG: {}".format(dst_path)) - + resolution_arg = self._get_resolution_arg("ffmpeg") ffmpeg_path_args = get_ffmpeg_tool_args("ffmpeg") ffmpeg_args = self.ffmpeg_args or {} @@ -215,6 +253,10 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): jpeg_items.extend(ffmpeg_args.get("output") or []) # we just want one frame from movie files jpeg_items.extend(["-vframes", "1"]) + + if resolution_arg: + jpeg_items.extend(resolution_arg) + # output file jpeg_items.append(path_to_subprocess_arg(dst_path)) subprocess_command = " ".join(jpeg_items) @@ -229,3 +271,61 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): exc_info=True ) return False + + def _create_frame_from_video(self, video_file_path, output_dir): + """Convert video file to one frame image via ffmpeg""" + # create output file path + base_name = os.path.basename(video_file_path) + filename = os.path.splitext(base_name)[0] + output_thumb_file_path = os.path.join(output_dir, "{}.png".format(filename)) + + # Set video input attributes + max_int = str(2147483647) + video_data = get_ffprobe_data(video_file_path, logger=self.log) + duration = float(video_data["format"]["duration"]) + + resolution_arg = self._get_resolution_arg("ffmpeg") + cmd_args = [ + "-y", + "-ss", str(duration * self.duration_split), + "-i", video_file_path, + "-analyzeduration", max_int, + "-probesize", max_int, + "-vframes", "1" + ] + if resolution_arg: + cmd_args.extend(resolution_arg) + + # add output file path + cmd_args.append(output_thumb_file_path) + + # create ffmpeg command + cmd = get_ffmpeg_tool_args( + "ffmpeg", + *cmd_args + ) + try: + # run subprocess + self.log.debug("Executing: {}".format(" ".join(cmd))) + run_subprocess(cmd, logger=self.log) + self.log.debug("Thumbnail created: {}".format(output_thumb_file_path)) + return output_thumb_file_path + except RuntimeError as error: + self.log.warning( + "Failed intermediate thumb source using ffmpeg: {}".format( + error) + ) + return None + + def _get_resolution_arg(self, application): + # get settings + if self.target_size.get("type") == "source": + return + + width = self.target_size["width"] + height = self.target_size["height"] + # form arg string per application + if application == "ffmpeg": + return ["-vf", "scale={0}:{1}".format(width, height)] + elif application == "oiiotool": + return ["-resize", "{0}x{1}".format(width, height)] From 24abe0e0f309bc062fb4bbc19e69d832fa5d3ecc Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 22 Nov 2023 12:16:02 +0100 Subject: [PATCH 167/327] comments from https://github.com/ynput/OpenPype/pull/5936 --- openpype/plugins/publish/extract_thumbnail.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 9f764c435c..f09dc92184 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -27,7 +27,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): hosts = ["shell", "fusion", "resolve", "traypublisher", "substancepainter"] enabled = False - publishing_thumbnail = False + integrate_thumbnail = False target_size = { "type": "resize", "width": 1920, @@ -155,7 +155,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): new_repre_tags = ["thumbnail"] # for workflows which needs to have thumbnails published as # separate representations `delete` tag should not be added - if not self.publishing_thumbnail: + if not self.integrate_thumbnail: new_repre_tags.append("delete") new_repre = { @@ -320,7 +320,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): def _get_resolution_arg(self, application): # get settings if self.target_size.get("type") == "source": - return + return [] width = self.target_size["width"] height = self.target_size["height"] @@ -329,3 +329,5 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): return ["-vf", "scale={0}:{1}".format(width, height)] elif application == "oiiotool": return ["-resize", "{0}x{1}".format(width, height)] + + return [] From a9ecc8f4b30f0471221d5bd07f3891759ac2682e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 22 Nov 2023 12:24:33 +0100 Subject: [PATCH 168/327] settings for size target and frame picking --- .../defaults/project_settings/global.json | 7 +++ .../schemas/schema_global_publish.json | 61 +++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 9ccf5cae05..5633e81526 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -70,6 +70,13 @@ }, "ExtractThumbnail": { "enabled": true, + "integrate_thumbnail": false, + "duration_split": 0.5, + "target_size": { + "type": "resize", + "width": 1920, + "height": 1080 + }, "ffmpeg_args": { "input": [ "-apply_trc gamma22" diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index c7e91fd22d..f5a9975d8c 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -202,6 +202,67 @@ "key": "enabled", "label": "Enabled" }, + { + "type": "boolean", + "key": "integrate_thumbnail", + "label": "Integrate Thumbnail as representation" + }, + { + "type": "dict-conditional", + "use_label_wrap": false, + "collapsible": false, + "key": "target_size", + "label": "Target size", + "enum_key": "type", + "enum_label": "Type", + "enum_children": [ + { + "key": "source", + "label": "Image source", + "children": [ + { + "type": "label", + "label": "Image size will be inherited from source image." + } + ] + }, + { + "key": "resize", + "label": "Resize", + "children": [ + { + "type": "label", + "label": "Image will be resized to specified size." + }, + { + "type": "number", + "key": "width", + "label": "Width", + "decimal": 0, + "minimum": 0, + "maximum": 99999 + }, + { + "type": "number", + "key": "height", + "label": "Height", + "decimal": 0, + "minimum": 0, + "maximum": 99999 + } + ] + } + ] + }, + { + "key": "duration_split", + "label": "Duration split ratio", + "type": "number", + "decimal": 1, + "default": 0.5, + "minimum": 0, + "maximum": 1 + }, { "type": "dict", "key": "ffmpeg_args", From 45c42f5a789963b12514a611d282bf04678222a2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 22 Nov 2023 12:25:01 +0100 Subject: [PATCH 169/327] typo --- .../schemas/projects_schema/schemas/schema_global_publish.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index f5a9975d8c..b00301d8a2 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -205,7 +205,7 @@ { "type": "boolean", "key": "integrate_thumbnail", - "label": "Integrate Thumbnail as representation" + "label": "Integrate thumbnail as representation" }, { "type": "dict-conditional", From 03198fafcf2ae34f7fb1203638e64f69ce7cce82 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 22 Nov 2023 14:30:19 +0100 Subject: [PATCH 170/327] adding back asset_doc variable --- .../hosts/traypublisher/plugins/publish/validate_frame_ranges.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py b/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py index 95894848a4..56389a927f 100644 --- a/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py +++ b/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py @@ -41,6 +41,7 @@ class ValidateFrameRange(OptionalPyblishPluginMixin, for pattern in self.skip_timelines_check)): self.log.info("Skipping for {} task".format(instance.data["task"])) + asset_doc = instance.data["assetEntity"] asset_data = asset_doc["data"] frame_start = asset_data["frameStart"] frame_end = asset_data["frameEnd"] From 2aea1cd8fc9a5c43e937ab70255454a1cc31a3bd Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 22 Nov 2023 14:52:11 +0100 Subject: [PATCH 171/327] avoid situation where missing `outputName` this might happen if only one Intermediate reviewable file stream is used. --- openpype/plugins/publish/extract_thumbnail.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index a46c3acf12..2948f80ce5 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -180,7 +180,8 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): if explicit_repres: # this key will then align assetVersion ftrack thumbnail sync - new_repre["outputName"] = repre["outputName"] + new_repre["outputName"] = ( + repre.get("outputName") or repre["name"]) self.log.debug( "Adding explicit thumbnail representation: {}".format( new_repre)) From 85c73fa52c478afe441052bf997b4b2752f7b137 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 22 Nov 2023 22:52:00 +0800 Subject: [PATCH 172/327] remove duplicated variable of MTOA verbosity level in submit maya deadline --- .../deadline/plugins/publish/submit_maya_deadline.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 86de5c620e..26a605a744 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -97,7 +97,6 @@ class VRayPluginInfo(object): @attr.s class ArnoldPluginInfo(object): ArnoldFile = attr.ib(default=None) - ArnoldVerbose = attr.ib(default=2) class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, @@ -661,12 +660,9 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, # Plugin Info ass_file, _ = os.path.splitext(data["output_filename_0"]) ass_filepath = ass_file + ".ass" - current_verbosity_level = cmds.getAttr( - "defaultArnoldRenderOptions.log_verbosity") plugin_info = ArnoldPluginInfo( - ArnoldFile=ass_filepath, - ArnoldVerbose=current_verbosity_level + ArnoldFile=ass_filepath ) return job_info, attr.asdict(plugin_info) From 61fe4825e61d707e914387c0c54e1d5a9e667ce2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 22 Nov 2023 16:25:33 +0100 Subject: [PATCH 173/327] :art: initial work --- openpype/modules/asset_reporter/__init__.py | 8 + openpype/modules/asset_reporter/module.py | 30 ++ openpype/modules/asset_reporter/window.py | 378 ++++++++++++++++++ .../defaults/system_settings/modules.json | 3 + .../schemas/system_schema/schema_modules.json | 14 + 5 files changed, 433 insertions(+) create mode 100644 openpype/modules/asset_reporter/__init__.py create mode 100644 openpype/modules/asset_reporter/module.py create mode 100644 openpype/modules/asset_reporter/window.py diff --git a/openpype/modules/asset_reporter/__init__.py b/openpype/modules/asset_reporter/__init__.py new file mode 100644 index 0000000000..6267b4824b --- /dev/null +++ b/openpype/modules/asset_reporter/__init__.py @@ -0,0 +1,8 @@ +from .module import ( + AssetReporterAction +) + + +__all__ = ( + "AssetReporterAction", +) diff --git a/openpype/modules/asset_reporter/module.py b/openpype/modules/asset_reporter/module.py new file mode 100644 index 0000000000..34b84795d5 --- /dev/null +++ b/openpype/modules/asset_reporter/module.py @@ -0,0 +1,30 @@ +import os.path + +from openpype import AYON_SERVER_ENABLED +from openpype.modules import OpenPypeModule, ITrayAction +from openpype.lib import run_detached_process, get_openpype_execute_args + + +class AssetReporterAction(OpenPypeModule, ITrayAction): + label = "Asset Usage Report" + name = "asset_reporter" + admin_action = True + + def initialize(self, modules_settings): + self.enabled = not AYON_SERVER_ENABLED + + def tray_init(self): + ... + + def tray_exit(self): + ... + + def on_action_trigger(self): + args = get_openpype_execute_args() + args += ["run", + os.path.join( + os.path.dirname(__file__), + "window.py")] + + print(" ".join(args)) + run_detached_process(args) diff --git a/openpype/modules/asset_reporter/window.py b/openpype/modules/asset_reporter/window.py new file mode 100644 index 0000000000..6cb2f0e0e0 --- /dev/null +++ b/openpype/modules/asset_reporter/window.py @@ -0,0 +1,378 @@ +import appdirs +import qtawesome +import csv +import tempfile +from qtpy import QtCore, QtWidgets +from qtpy.QtGui import QClipboard, QColor +import time + +from openpype import ( + resources, + style +) +from openpype.client import OpenPypeMongoConnection +from pymongo.collection import Collection +from openpype.lib import JSONSettingRegistry +from openpype.tools.utils import PlaceholderLineEdit, get_openpype_qt_app +from openpype.tools.utils.constants import PROJECT_NAME_ROLE +from openpype.tools.utils.models import ProjectModel, ProjectSortFilterProxy + + +class AssetReporterRegistry(JSONSettingRegistry): + """Class handling OpenPype general settings registry. + + Attributes: + vendor (str): Name used for path construction. + product (str): Additional name used for path construction. + + """ + + def __init__(self): + self.vendor = "ynput" + self.product = "openpype" + name = "asset_usage_reporter" + path = appdirs.user_data_dir(self.product, self.vendor) + super(AssetReporterRegistry, self).__init__(name, path) + + +class OverlayWidget(QtWidgets.QFrame): + project_selected = QtCore.Signal(str) + + def __init__(self, publisher_window): + super(OverlayWidget, self).__init__(publisher_window) + self.setObjectName("OverlayFrame") + + middle_frame = QtWidgets.QFrame(self) + middle_frame.setObjectName("ChooseProjectFrame") + + content_widget = QtWidgets.QWidget(middle_frame) + + header_label = QtWidgets.QLabel("Choose project", content_widget) + header_label.setObjectName("ChooseProjectLabel") + # Create project models and view + projects_model = ProjectModel() + projects_proxy = ProjectSortFilterProxy() + projects_proxy.setSourceModel(projects_model) + projects_proxy.setFilterKeyColumn(0) + + projects_view = QtWidgets.QListView(content_widget) + projects_view.setObjectName("ChooseProjectView") + projects_view.setModel(projects_proxy) + projects_view.setEditTriggers( + QtWidgets.QAbstractItemView.NoEditTriggers + ) + + confirm_btn = QtWidgets.QPushButton("Confirm", content_widget) + cancel_btn = QtWidgets.QPushButton("Cancel", content_widget) + cancel_btn.setVisible(False) + btns_layout = QtWidgets.QHBoxLayout() + btns_layout.addStretch(1) + btns_layout.addWidget(cancel_btn, 0) + btns_layout.addWidget(confirm_btn, 0) + + txt_filter = PlaceholderLineEdit(content_widget) + txt_filter.setPlaceholderText("Quick filter projects..") + txt_filter.setClearButtonEnabled(True) + txt_filter.addAction(qtawesome.icon("fa.filter", color="gray"), + QtWidgets.QLineEdit.LeadingPosition) + + content_layout = QtWidgets.QVBoxLayout(content_widget) + content_layout.setContentsMargins(0, 0, 0, 0) + content_layout.setSpacing(20) + content_layout.addWidget(header_label, 0) + content_layout.addWidget(txt_filter, 0) + content_layout.addWidget(projects_view, 1) + content_layout.addLayout(btns_layout, 0) + + middle_layout = QtWidgets.QHBoxLayout(middle_frame) + middle_layout.setContentsMargins(30, 30, 10, 10) + middle_layout.addWidget(content_widget) + + main_layout = QtWidgets.QHBoxLayout(self) + main_layout.setContentsMargins(10, 10, 10, 10) + main_layout.addStretch(1) + main_layout.addWidget(middle_frame, 2) + main_layout.addStretch(1) + + projects_view.doubleClicked.connect(self._on_double_click) + confirm_btn.clicked.connect(self._on_confirm_click) + cancel_btn.clicked.connect(self._on_cancel_click) + txt_filter.textChanged.connect(self._on_text_changed) + + self._projects_view = projects_view + self._projects_model = projects_model + self._projects_proxy = projects_proxy + self._cancel_btn = cancel_btn + self._confirm_btn = confirm_btn + self._txt_filter = txt_filter + + self._publisher_window = publisher_window + self._project_name = None + + def showEvent(self, event): + self._projects_model.refresh() + # Sort projects after refresh + self._projects_proxy.sort(0) + + setting_registry = AssetReporterRegistry() + try: + project_name = setting_registry.get_item("project_name") + except ValueError: + project_name = None + + if project_name: + index = None + src_index = self._projects_model.find_project(project_name) + if src_index is not None: + index = self._projects_proxy.mapFromSource(src_index) + + if index is not None: + selection_model = self._projects_view.selectionModel() + selection_model.select( + index, + QtCore.QItemSelectionModel.SelectCurrent + ) + self._projects_view.setCurrentIndex(index) + + self._cancel_btn.setVisible(self._project_name is not None) + super(OverlayWidget, self).showEvent(event) + + def _on_double_click(self): + self.set_selected_project() + + def _on_confirm_click(self): + self.set_selected_project() + + def _on_cancel_click(self): + self._set_project(self._project_name) + + def _on_text_changed(self): + self._projects_proxy.setFilterRegularExpression( + self._txt_filter.text()) + + def set_selected_project(self): + index = self._projects_view.currentIndex() + + if project_name := index.data(PROJECT_NAME_ROLE): + self._set_project(project_name) + + def _set_project(self, project_name): + self._project_name = project_name + self.setVisible(False) + self.project_selected.emit(project_name) + + setting_registry = AssetReporterRegistry() + setting_registry.set_item("project_name", project_name) + + +class AssetReporterWindow(QtWidgets.QDialog): + default_width = 1300 + default_height = 800 + footer_border = 8 + _content = None + + def __init__(self, parent=None, controller=None, reset_on_show=None): + super(AssetReporterWindow, self).__init__(parent) + + self._result = {} + self.setObjectName("AssetReporterWindow") + + self.setWindowTitle("Asset Usage Reporter") + + if parent is None: + on_top_flag = QtCore.Qt.WindowStaysOnTopHint + else: + on_top_flag = QtCore.Qt.Dialog + + self.setWindowFlags( + QtCore.Qt.WindowTitleHint + | QtCore.Qt.WindowMaximizeButtonHint + | QtCore.Qt.WindowMinimizeButtonHint + | QtCore.Qt.WindowCloseButtonHint + | on_top_flag + ) + self.table = QtWidgets.QTableWidget(self) + self.table.setColumnCount(3) + self.table.setColumnWidth(0, 400) + self.table.setColumnWidth(1, 300) + self.table.setHorizontalHeaderLabels(["Subset", "Used in", "Version"]) + + # self.text_area = QtWidgets.QTextEdit(self) + self.copy_button = QtWidgets.QPushButton('Copy to Clipboard', self) + self.save_button = QtWidgets.QPushButton('Save to CSV File', self) + + self.copy_button.clicked.connect(self.copy_to_clipboard) + self.save_button.clicked.connect(self.save_to_file) + + layout = QtWidgets.QVBoxLayout(self) + layout.addWidget(self.table) + # layout.addWidget(self.text_area) + layout.addWidget(self.copy_button) + layout.addWidget(self.save_button) + + self.resize(self.default_width, self.default_height) + self.setStyleSheet(style.load_stylesheet()) + + overlay_widget = OverlayWidget(self) + overlay_widget.project_selected.connect(self._on_project_select) + self._overlay_widget = overlay_widget + + def _on_project_select(self, project_name): + self._project_name = project_name + self.process() + if not self._result: + self.set_content("no result generated") + return + + rows = sum(len(value) for key, value in self._result.items()) + self.table.setRowCount(rows) + + row = 0 + content = [] + for key, value in self._result.items(): + item = QtWidgets.QTableWidgetItem(key) + item.setBackground(QColor(32, 32, 32)) + self.table.setItem(row, 0, item) + for source in value: + self.table.setItem(row, 1, QtWidgets.QTableWidgetItem(source["name"])) + self.table.setItem(row, 2, QtWidgets.QTableWidgetItem(str(source["version"]))) + print(f' - {source["version"]}') + row += 1 + + content.append(f"{key}") + content.extend( + f"\t{source['name']} (v{source['version']})" for source in value + ) + self.set_content("\n".join(content)) + + def copy_to_clipboard(self): + clipboard = QtWidgets.QApplication.clipboard() + clipboard.setText(self._content, QClipboard.Clipboard) + + def save_to_file(self): + file_name, _ = QtWidgets.QFileDialog.getSaveFileName(self, 'Save File') + if file_name: + self._write_csv(file_name) + + def set_content(self, content): + self._content = content + + def get_content(self): + return self._content + + def _resize_overlay(self): + self._overlay_widget.resize( + self.width(), + self.height() + ) + + def resizeEvent(self, event): + super(AssetReporterWindow, self).resizeEvent(event) + self._resize_overlay() + + def _get_subset(self, version_id, project: Collection): + pipeline = [ + { + "$match": { + "_id": version_id + }, + }, { + "$lookup": { + "from": project.name, + "localField": "parent", + "foreignField": "_id", + "as": "parents" + } + } + ] + + result = project.aggregate(pipeline) + doc = next(result) + # print(doc) + return { + "name": f'{"/".join(doc["parents"][0]["data"]["parents"])}/{doc["parents"][0]["name"]}/{doc["name"]}', + "family": doc["data"].get("family") or doc["data"].get("families")[0] + } + + def process(self): + start = time.perf_counter() + project = self._project_name + + pipeline = [ + { + "$match": { + "data.inputLinks": { + "$exists": True, + "$ne": [] + }, + "data.families": {"$in": ["workfile"]} + } + }, { + "$lookup": { + "from": project, + "localField": "data.inputLinks.id", + "foreignField": "_id", + "as": "linked_docs" + } + } + ] + + client = OpenPypeMongoConnection.get_mongo_client() + db = client["avalon"] + + result = db[project].aggregate(pipeline) + + asset_map = [] + for doc in result: + source = { + "source": self._get_subset(doc["parent"], db[project]), + } + source["source"].update({"version": doc["name"]}) + refs = [ + { + "subset": self._get_subset(linked["parent"], db[project]), + "version": linked.get("name") + } + for linked in doc["linked_docs"] + ] + source["refs"] = refs + asset_map.append(source) + + # for ref in asset_map: + # print(ref) + + grouped = {} + + for asset in asset_map: + for ref in asset["refs"]: + key = f'{ref["subset"]["name"]} (v{ref["version"]})' + if key in grouped: + grouped[key].append(asset["source"]) + else: + grouped[key] = [asset["source"]] + self._result = grouped + + end = time.perf_counter() + + print(f"Finished in {end - start:0.4f} seconds", 2) + + def _write_csv(self, file_name): + with open(file_name, "w", newline="") as csvfile: + writer = csv.writer(csvfile, delimiter=";") + writer.writerow(["Subset", "Used in", "Version"]) + for key, value in self._result.items(): + writer.writerow([key, "", ""]) + for source in value: + writer.writerow(["", source["name"], source["version"]]) + + + +def main(): + app_instance = get_openpype_qt_app() + window = AssetReporterWindow() + window.show() + app_instance.exec_() + + +if __name__ == "__main__": + main() diff --git a/openpype/settings/defaults/system_settings/modules.json b/openpype/settings/defaults/system_settings/modules.json index f524f01d45..bb943524f1 100644 --- a/openpype/settings/defaults/system_settings/modules.json +++ b/openpype/settings/defaults/system_settings/modules.json @@ -210,5 +210,8 @@ "darwin": "", "linux": "" } + }, + "asset_reporter": { + "enabled": false } } diff --git a/openpype/settings/entities/schemas/system_schema/schema_modules.json b/openpype/settings/entities/schemas/system_schema/schema_modules.json index 952b38040c..2258dd42e3 100644 --- a/openpype/settings/entities/schemas/system_schema/schema_modules.json +++ b/openpype/settings/entities/schemas/system_schema/schema_modules.json @@ -355,6 +355,20 @@ { "type": "dynamic_schema", "name": "system_settings/modules" + }, + { + "type": "dict", + "key": "asset_reporter", + "label": "Asset Usage Report", + "collapsible": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + } + ] } ] } From 5125db72b4ad2ece64e20bef0406fb9596df29d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 22 Nov 2023 16:38:09 +0100 Subject: [PATCH 174/327] :memo: add docstrings and some polishing --- openpype/modules/asset_reporter/module.py | 11 ++- openpype/modules/asset_reporter/window.py | 71 +++++++++++++------ .../schemas/system_schema/schema_modules.json | 2 +- 3 files changed, 55 insertions(+), 29 deletions(-) diff --git a/openpype/modules/asset_reporter/module.py b/openpype/modules/asset_reporter/module.py index 34b84795d5..8c754cc3c0 100644 --- a/openpype/modules/asset_reporter/module.py +++ b/openpype/modules/asset_reporter/module.py @@ -6,19 +6,16 @@ from openpype.lib import run_detached_process, get_openpype_execute_args class AssetReporterAction(OpenPypeModule, ITrayAction): + label = "Asset Usage Report" name = "asset_reporter" - admin_action = True + + def tray_init(self): + pass def initialize(self, modules_settings): self.enabled = not AYON_SERVER_ENABLED - def tray_init(self): - ... - - def tray_exit(self): - ... - def on_action_trigger(self): args = get_openpype_execute_args() args += ["run", diff --git a/openpype/modules/asset_reporter/window.py b/openpype/modules/asset_reporter/window.py index 6cb2f0e0e0..9dbcc3ea74 100644 --- a/openpype/modules/asset_reporter/window.py +++ b/openpype/modules/asset_reporter/window.py @@ -1,17 +1,22 @@ -import appdirs -import qtawesome +"""Tool for generating asset usage report. + +This tool is used to generate asset usage report for a project. +It is using links between published version to find out where +the asset is used. + +""" + import csv -import tempfile -from qtpy import QtCore, QtWidgets -from qtpy.QtGui import QClipboard, QColor import time -from openpype import ( - resources, - style -) -from openpype.client import OpenPypeMongoConnection +import appdirs +import qtawesome from pymongo.collection import Collection +from qtpy import QtCore, QtWidgets +from qtpy.QtGui import QClipboard, QColor + +from openpype import style +from openpype.client import OpenPypeMongoConnection from openpype.lib import JSONSettingRegistry from openpype.tools.utils import PlaceholderLineEdit, get_openpype_qt_app from openpype.tools.utils.constants import PROJECT_NAME_ROLE @@ -21,6 +26,8 @@ from openpype.tools.utils.models import ProjectModel, ProjectSortFilterProxy class AssetReporterRegistry(JSONSettingRegistry): """Class handling OpenPype general settings registry. + This is used to store last selected project. + Attributes: vendor (str): Name used for path construction. product (str): Additional name used for path construction. @@ -36,6 +43,10 @@ class AssetReporterRegistry(JSONSettingRegistry): class OverlayWidget(QtWidgets.QFrame): + """Overlay widget for choosing project. + + This code is taken from the Tray Publisher tool. + """ project_selected = QtCore.Signal(str) def __init__(self, publisher_window): @@ -116,7 +127,7 @@ class OverlayWidget(QtWidgets.QFrame): setting_registry = AssetReporterRegistry() try: - project_name = setting_registry.get_item("project_name") + project_name = str(setting_registry.get_item("project_name")) except ValueError: project_name = None @@ -166,9 +177,8 @@ class OverlayWidget(QtWidgets.QFrame): class AssetReporterWindow(QtWidgets.QDialog): - default_width = 1300 + default_width = 1000 default_height = 800 - footer_border = 8 _content = None def __init__(self, parent=None, controller=None, reset_on_show=None): @@ -217,7 +227,13 @@ class AssetReporterWindow(QtWidgets.QDialog): overlay_widget.project_selected.connect(self._on_project_select) self._overlay_widget = overlay_widget - def _on_project_select(self, project_name): + def _on_project_select(self, project_name: str): + """Generate table when project is selected. + + This will generate the table and fill it with data. + Source data are held in memory in `_result` attribute that + is used to transform them into clipboard or csv file. + """ self._project_name = project_name self.process() if not self._result: @@ -231,14 +247,15 @@ class AssetReporterWindow(QtWidgets.QDialog): content = [] for key, value in self._result.items(): item = QtWidgets.QTableWidgetItem(key) - item.setBackground(QColor(32, 32, 32)) + # this doesn't work as it is probably overriden by stylesheet? + # item.setBackground(QColor(32, 32, 32)) self.table.setItem(row, 0, item) for source in value: self.table.setItem(row, 1, QtWidgets.QTableWidgetItem(source["name"])) self.table.setItem(row, 2, QtWidgets.QTableWidgetItem(str(source["version"]))) - print(f' - {source["version"]}') row += 1 + # generate clipboard content content.append(f"{key}") content.extend( f"\t{source['name']} (v{source['version']})" for source in value @@ -295,9 +312,20 @@ class AssetReporterWindow(QtWidgets.QDialog): } def process(self): + """Generate asset usage report data. + + This is the main method of the tool. It is using MongoDB + aggregation pipeline to find all published versions that + are used as input for other published versions. Then it + generates a map of assets and their usage. + + """ start = time.perf_counter() project = self._project_name + # get all versions of published workfiles that has non-empty + # inputLinks and connect it with their respective documents + # using ID. pipeline = [ { "$match": { @@ -323,6 +351,9 @@ class AssetReporterWindow(QtWidgets.QDialog): result = db[project].aggregate(pipeline) asset_map = [] + # this is creating the map - for every workfile and its linked + # documents, create a dictionary with "source" and "refs" keys + # and resolve the subset name and version from the document for doc in result: source = { "source": self._get_subset(doc["parent"], db[project]), @@ -338,11 +369,9 @@ class AssetReporterWindow(QtWidgets.QDialog): source["refs"] = refs asset_map.append(source) - # for ref in asset_map: - # print(ref) - grouped = {} + # this will group the assets by subset name and version for asset in asset_map: for ref in asset["refs"]: key = f'{ref["subset"]["name"]} (v{ref["version"]})' @@ -356,7 +385,8 @@ class AssetReporterWindow(QtWidgets.QDialog): print(f"Finished in {end - start:0.4f} seconds", 2) - def _write_csv(self, file_name): + def _write_csv(self, file_name: str) -> None: + """Write CSV file with results.""" with open(file_name, "w", newline="") as csvfile: writer = csv.writer(csvfile, delimiter=";") writer.writerow(["Subset", "Used in", "Version"]) @@ -366,7 +396,6 @@ class AssetReporterWindow(QtWidgets.QDialog): writer.writerow(["", source["name"], source["version"]]) - def main(): app_instance = get_openpype_qt_app() window = AssetReporterWindow() diff --git a/openpype/settings/entities/schemas/system_schema/schema_modules.json b/openpype/settings/entities/schemas/system_schema/schema_modules.json index 2258dd42e3..5b189eae88 100644 --- a/openpype/settings/entities/schemas/system_schema/schema_modules.json +++ b/openpype/settings/entities/schemas/system_schema/schema_modules.json @@ -359,7 +359,7 @@ { "type": "dict", "key": "asset_reporter", - "label": "Asset Usage Report", + "label": "Asset Usage Reporter", "collapsible": true, "checkbox_key": "enabled", "children": [ From 455482041080894b1cc78731292f63a7139effdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 22 Nov 2023 16:42:11 +0100 Subject: [PATCH 175/327] :bug: invalid header order --- openpype/modules/asset_reporter/window.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/modules/asset_reporter/window.py b/openpype/modules/asset_reporter/window.py index 9dbcc3ea74..bdcf0a9adf 100644 --- a/openpype/modules/asset_reporter/window.py +++ b/openpype/modules/asset_reporter/window.py @@ -205,7 +205,7 @@ class AssetReporterWindow(QtWidgets.QDialog): self.table.setColumnCount(3) self.table.setColumnWidth(0, 400) self.table.setColumnWidth(1, 300) - self.table.setHorizontalHeaderLabels(["Subset", "Used in", "Version"]) + self.table.setHorizontalHeaderLabels(["Used in", "Product", "Version"]) # self.text_area = QtWidgets.QTextEdit(self) self.copy_button = QtWidgets.QPushButton('Copy to Clipboard', self) @@ -389,7 +389,7 @@ class AssetReporterWindow(QtWidgets.QDialog): """Write CSV file with results.""" with open(file_name, "w", newline="") as csvfile: writer = csv.writer(csvfile, delimiter=";") - writer.writerow(["Subset", "Used in", "Version"]) + writer.writerow(["Used in", "Product", "Version"]) for key, value in self._result.items(): writer.writerow([key, "", ""]) for source in value: From 5ec3f92d7c79165ef324a5028a1125ffd39465cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 22 Nov 2023 17:02:30 +0100 Subject: [PATCH 176/327] :bug: remove unused plugin and hound fixes --- openpype/modules/asset_reporter/window.py | 15 +- .../actions/generate_asset_usage_report.py | 157 ------------------ 2 files changed, 9 insertions(+), 163 deletions(-) delete mode 100644 openpype/plugins/actions/generate_asset_usage_report.py diff --git a/openpype/modules/asset_reporter/window.py b/openpype/modules/asset_reporter/window.py index bdcf0a9adf..e34037965c 100644 --- a/openpype/modules/asset_reporter/window.py +++ b/openpype/modules/asset_reporter/window.py @@ -251,14 +251,17 @@ class AssetReporterWindow(QtWidgets.QDialog): # item.setBackground(QColor(32, 32, 32)) self.table.setItem(row, 0, item) for source in value: - self.table.setItem(row, 1, QtWidgets.QTableWidgetItem(source["name"])) - self.table.setItem(row, 2, QtWidgets.QTableWidgetItem(str(source["version"]))) + self.table.setItem( + row, 1, QtWidgets.QTableWidgetItem(source["name"])) + self.table.setItem( + row, 2, QtWidgets.QTableWidgetItem( + str(source["version"]))) row += 1 # generate clipboard content - content.append(f"{key}") + content.append(key) content.extend( - f"\t{source['name']} (v{source['version']})" for source in value + f"\t{source['name']} (v{source['version']})" for source in value # noqa: E501 ) self.set_content("\n".join(content)) @@ -307,8 +310,8 @@ class AssetReporterWindow(QtWidgets.QDialog): doc = next(result) # print(doc) return { - "name": f'{"/".join(doc["parents"][0]["data"]["parents"])}/{doc["parents"][0]["name"]}/{doc["name"]}', - "family": doc["data"].get("family") or doc["data"].get("families")[0] + "name": f'{"/".join(doc["parents"][0]["data"]["parents"])}/{doc["parents"][0]["name"]}/{doc["name"]}', # noqa: E501 + "family": doc["data"].get("family") or doc["data"].get("families")[0] # noqa: E501 } def process(self): diff --git a/openpype/plugins/actions/generate_asset_usage_report.py b/openpype/plugins/actions/generate_asset_usage_report.py deleted file mode 100644 index 60630c5a43..0000000000 --- a/openpype/plugins/actions/generate_asset_usage_report.py +++ /dev/null @@ -1,157 +0,0 @@ -""" -TODO: we need to move it to subprocess to show UI -""" -import csv -import os -import tempfile -import time - -from qtpy import QtWidgets -from qtpy.QtCore import Qt -from qtpy.QtGui import QClipboard - -from pymongo.collection import Collection - -from openpype.client import OpenPypeMongoConnection -from openpype.pipeline import LauncherAction - - -class ReportWindow(QtWidgets.QWidget): - def __init__(self): - super().__init__() - - self.text_area = QtWidgets.QTextEdit(self) - self.copy_button = QtWidgets.QPushButton('Copy to Clipboard', self) - self.save_button = QtWidgets.QPushButton('Save to CSV File', self) - - self.copy_button.clicked.connect(self.copy_to_clipboard) - self.save_button.clicked.connect(self.save_to_file) - - layout = QtWidgets.QVBoxLayout(self) - layout.addWidget(self.text_area) - layout.addWidget(self.copy_button) - layout.addWidget(self.save_button) - - def copy_to_clipboard(self): - clipboard = QtWidgets.QApplication.clipboard() - clipboard.setText(self.text_area.toPlainText(), QClipboard.Clipboard) - - def save_to_file(self): - file_name, _ = QtWidgets.QFileDialog.getSaveFileName(self, 'Save File') - if file_name: - with open(file_name, 'w') as file: - file.write(self.text_area.toPlainText()) - - def set_content(self, content): - self.text_area.setText(content) - - -class OpenTaskPath(LauncherAction): - name = "get_asset_usage_report" - label = "Asset Usage Report" - icon = "list" - order = 500 - - def is_compatible(self, session): - """Return whether the action is compatible with the session""" - return bool(session.get("AVALON_ASSET")) - - def _get_subset(self, version_id, project: Collection): - pipeline = [ - { - "$match": { - "_id": version_id - }, - }, { - "$lookup": { - "from": project.name, - "localField": "parent", - "foreignField": "_id", - "as": "parents" - } - } - ] - - result = project.aggregate(pipeline) - doc = next(result) - # print(doc) - return { - "name": f'{"/".join(doc["parents"][0]["data"]["parents"])}/{doc["parents"][0]["name"]}/{doc["name"]}', - "family": doc["data"].get("family") or doc["data"].get("families")[0] - } - - def process(self, session, **kwargs): - start = time.perf_counter() - project = session["AVALON_PROJECT"] - - pipeline = [ - { - "$match": { - "data.inputLinks": { - "$exists": True, - "$ne": [] - }, - "data.families": {"$in": ["workfile"]} - } - }, { - "$lookup": { - "from": "OP01_CG_demo", - "localField": "data.inputLinks.id", - "foreignField": "_id", - "as": "linked_docs" - } - } - ] - - client = OpenPypeMongoConnection.get_mongo_client() - db = client["avalon"] - - result = db[project].aggregate(pipeline) - - asset_map = [] - for doc in result: - source = { - "source": self._get_subset(doc["parent"], db[project]), - } - source["source"].update({"version": doc["name"]}) - refs = [ - { - "subset": self._get_subset(linked["parent"], db[project]), - "version": linked.get("name") - } - for linked in doc["linked_docs"] - ] - source["refs"] = refs - asset_map.append(source) - - # for ref in asset_map: - # print(ref) - - grouped = {} - - for asset in asset_map: - for ref in asset["refs"]: - key = f'{ref["subset"]["name"]} (v{ref["version"]})' - if key in grouped: - grouped[key].append(asset["source"]) - else: - grouped[key] = [asset["source"]] - - temp = tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".csv") - try: - with open(temp.name, "w", newline="") as csvfile: - writer = csv.writer(csvfile, delimiter=";") - writer.writerow(["Subset", "Used in", "Version"]) - for key, value in grouped.items(): - writer.writerow([key, "", ""]) - for source in value: - writer.writerow(["", source["name"], source["version"]]) - finally: - temp.close() - - end = time.perf_counter() - app = QtWidgets.QApplication.instance() - window = ReportWindow() - # window.set_content(open(temp.name).read()) - window.show() - print(f"Finished in {end - start:0.4f} seconds", 2) From e51ddf8682d3c85baaca89788ccddf796212eba1 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 22 Nov 2023 17:10:13 +0100 Subject: [PATCH 177/327] fixing situation where display and view are in representation --- openpype/plugins/publish/extract_thumbnail.py | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index a97ffdf569..c2d472b20a 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -206,25 +206,40 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): """ self.log.info("Extracting thumbnail {}".format(dst_path)) + repre_display = colorspace_data.get("display") + repre_view = colorspace_data.get("view") oiio_default_type = None oiio_default_display = None oiio_default_view = None oiio_default_colorspace = None - if self.oiiotool_defaults: + # first look into representation colorspaceData, perhaps it has + # display and view + if not all([repre_display, repre_view]): + self.log.info( + "Using Display & View from " + "representation: '{} ({})'".format( + repre_view, + repre_display + ) + ) + # if representation doesn't have display and view then use + # oiiotool_defaults + elif self.oiiotool_defaults: oiio_default_type = self.oiiotool_defaults["type"] if "colorspace" in oiio_default_type: oiio_default_colorspace = self.oiiotool_defaults["colorspace"] else: oiio_default_display = self.oiiotool_defaults["display"] oiio_default_view = self.oiiotool_defaults["view"] + try: convert_colorspace( src_path, dst_path, colorspace_data["config"]["path"], colorspace_data["colorspace"], - display=colorspace_data.get("display") or oiio_default_display, - view=colorspace_data.get("view") or oiio_default_view, + display=repre_display or oiio_default_display, + view=repre_view or oiio_default_view, target_colorspace=oiio_default_colorspace, logger=self.log, ) From 8050d23529d8eded73f21b16c87df7480a359d7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 22 Nov 2023 18:10:56 +0100 Subject: [PATCH 178/327] Revert ":bug: invalid header order" This reverts commit 455482041080894b1cc78731292f63a7139effdb. --- openpype/modules/asset_reporter/window.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/modules/asset_reporter/window.py b/openpype/modules/asset_reporter/window.py index e34037965c..3dfc585528 100644 --- a/openpype/modules/asset_reporter/window.py +++ b/openpype/modules/asset_reporter/window.py @@ -205,7 +205,7 @@ class AssetReporterWindow(QtWidgets.QDialog): self.table.setColumnCount(3) self.table.setColumnWidth(0, 400) self.table.setColumnWidth(1, 300) - self.table.setHorizontalHeaderLabels(["Used in", "Product", "Version"]) + self.table.setHorizontalHeaderLabels(["Subset", "Used in", "Version"]) # self.text_area = QtWidgets.QTextEdit(self) self.copy_button = QtWidgets.QPushButton('Copy to Clipboard', self) @@ -392,7 +392,7 @@ class AssetReporterWindow(QtWidgets.QDialog): """Write CSV file with results.""" with open(file_name, "w", newline="") as csvfile: writer = csv.writer(csvfile, delimiter=";") - writer.writerow(["Used in", "Product", "Version"]) + writer.writerow(["Subset", "Used in", "Version"]) for key, value in self._result.items(): writer.writerow([key, "", ""]) for source in value: From 3833819557a768200fe8ffb6620261a0b9ad648b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 23 Nov 2023 10:10:08 +0100 Subject: [PATCH 179/327] inverting bugged condition --- openpype/plugins/publish/extract_thumbnail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index c2d472b20a..3b829f19bd 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -214,7 +214,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): oiio_default_colorspace = None # first look into representation colorspaceData, perhaps it has # display and view - if not all([repre_display, repre_view]): + if all([repre_display, repre_view]): self.log.info( "Using Display & View from " "representation: '{} ({})'".format( From 00c1288c9d24d7a14e0b6b01da0867465ceae852 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 23 Nov 2023 11:25:45 +0100 Subject: [PATCH 180/327] :bug: handle hero versions --- openpype/modules/asset_reporter/window.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/openpype/modules/asset_reporter/window.py b/openpype/modules/asset_reporter/window.py index 3dfc585528..9b19d6295c 100644 --- a/openpype/modules/asset_reporter/window.py +++ b/openpype/modules/asset_reporter/window.py @@ -362,13 +362,20 @@ class AssetReporterWindow(QtWidgets.QDialog): "source": self._get_subset(doc["parent"], db[project]), } source["source"].update({"version": doc["name"]}) - refs = [ - { - "subset": self._get_subset(linked["parent"], db[project]), - "version": linked.get("name") - } - for linked in doc["linked_docs"] - ] + refs = [] + version = '' + for linked in doc["linked_docs"]: + try: + version = f'v{linked["name"]}' + except KeyError: + if linked["type"] == "hero_version": + version = "hero" + finally: + refs.append({ + "subset": self._get_subset(linked["parent"], db[project]), + "version": version + }) + source["refs"] = refs asset_map.append(source) @@ -377,7 +384,7 @@ class AssetReporterWindow(QtWidgets.QDialog): # this will group the assets by subset name and version for asset in asset_map: for ref in asset["refs"]: - key = f'{ref["subset"]["name"]} (v{ref["version"]})' + key = f'{ref["subset"]["name"]} ({ref["version"]})' if key in grouped: grouped[key].append(asset["source"]) else: From 5e5ed5b5177bada4484da014830c765f8be6587b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 23 Nov 2023 11:26:46 +0100 Subject: [PATCH 181/327] :dog: hound fix --- openpype/modules/asset_reporter/window.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/modules/asset_reporter/window.py b/openpype/modules/asset_reporter/window.py index 9b19d6295c..ed3bc298e1 100644 --- a/openpype/modules/asset_reporter/window.py +++ b/openpype/modules/asset_reporter/window.py @@ -372,7 +372,8 @@ class AssetReporterWindow(QtWidgets.QDialog): version = "hero" finally: refs.append({ - "subset": self._get_subset(linked["parent"], db[project]), + "subset": self._get_subset( + linked["parent"], db[project]), "version": version }) From cb938ad58cc56186b2cfa62144821c142bf3bc5f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 23 Nov 2023 12:02:41 +0100 Subject: [PATCH 182/327] remove usage of kwarg to create ModulesManager --- openpype/settings/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/lib.py b/openpype/settings/lib.py index ce62dde43f..d62e50d3c7 100644 --- a/openpype/settings/lib.py +++ b/openpype/settings/lib.py @@ -172,7 +172,7 @@ def save_studio_settings(data): clear_metadata_from_settings(new_data) changes = calculate_changes(old_data, new_data) - modules_manager = ModulesManager(_system_settings=new_data) + modules_manager = ModulesManager(new_data) warnings = [] for module in modules_manager.get_enabled_modules(): From 44b4d04bbeb12029dd55ebac4c3b7c7e74a73dfb Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 23 Nov 2023 16:24:41 +0000 Subject: [PATCH 183/327] Added attributes to publisher and fixed chunk size --- .../publish/submit_blender_deadline.py | 47 ++++++++++++++++--- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_blender_deadline.py b/openpype/modules/deadline/plugins/publish/submit_blender_deadline.py index 094f2b1821..e1bf5c074a 100644 --- a/openpype/modules/deadline/plugins/publish/submit_blender_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_blender_deadline.py @@ -6,8 +6,14 @@ import getpass import attr from datetime import datetime -from openpype.lib import is_running_from_build +from openpype.lib import ( + is_running_from_build, + BoolDef, + NumberDef, + TextDef, +) from openpype.pipeline import legacy_io +from openpype.pipeline.publish import OpenPypePyblishPluginMixin from openpype.pipeline.farm.tools import iter_expected_files from openpype.tests.lib import is_in_tests @@ -22,7 +28,8 @@ class BlenderPluginInfo(): SaveFile = attr.ib(default=True) -class BlenderSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): +class BlenderSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, + OpenPypePyblishPluginMixin): label = "Submit Render to Deadline" hosts = ["blender"] families = ["render.farm"] @@ -67,8 +74,6 @@ class BlenderSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): job_info.Pool = instance.data.get("primaryPool") job_info.SecondaryPool = instance.data.get("secondaryPool") - job_info.Comment = context.data.get("comment") - job_info.Priority = instance.data.get("priority", self.priority) if self.group != "none" and self.group: job_info.Group = self.group @@ -83,8 +88,9 @@ class BlenderSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): machine_list_key = "Blacklist" render_globals[machine_list_key] = machine_list - job_info.Priority = attr_values.get("priority") - job_info.ChunkSize = attr_values.get("chunkSize") + job_info.Comment = context.data.get("comment") + job_info.ChunkSize = attr_values.get("chunkSize", self.chunk_size) + job_info.Priority = attr_values.get("priority", self.priority) # Add options from RenderGlobals render_globals = instance.data.get("renderGlobals", {}) @@ -180,3 +186,32 @@ class BlenderSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): the metadata and the rendered files are in the same location. """ return super().from_published_scene(False) + + @classmethod + def get_attribute_defs(cls): + defs = super(BlenderSubmitDeadline, cls).get_attribute_defs() + defs.extend([ + BoolDef("use_published", + default=cls.use_published, + label="Use Published Scene"), + + NumberDef("priority", + minimum=1, + maximum=250, + decimals=0, + default=cls.priority, + label="Priority"), + + NumberDef("chunkSize", + minimum=1, + maximum=50, + decimals=0, + default=cls.chunk_size, + label="Frame Per Task"), + + TextDef("group", + default=cls.group, + label="Group Name"), + ]) + + return defs From ee7bf575953fcfc29bb8bc1f56d2df418918830f Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 23 Nov 2023 16:25:22 +0000 Subject: [PATCH 184/327] Changed family for render --- openpype/hosts/blender/plugins/create/create_render.py | 2 +- openpype/hosts/blender/plugins/publish/collect_render.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/blender/plugins/create/create_render.py b/openpype/hosts/blender/plugins/create/create_render.py index 7fb3e5eb00..a643ccdaa3 100644 --- a/openpype/hosts/blender/plugins/create/create_render.py +++ b/openpype/hosts/blender/plugins/create/create_render.py @@ -10,7 +10,7 @@ class CreateRenderlayer(plugin.BaseCreator): identifier = "io.openpype.creators.blender.render" label = "Render" - family = "render" + family = "render.farm" icon = "eye" def create( diff --git a/openpype/hosts/blender/plugins/publish/collect_render.py b/openpype/hosts/blender/plugins/publish/collect_render.py index 00faf85aed..d1b1da5f4e 100644 --- a/openpype/hosts/blender/plugins/publish/collect_render.py +++ b/openpype/hosts/blender/plugins/publish/collect_render.py @@ -15,7 +15,7 @@ class CollectBlenderRender(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.01 hosts = ["blender"] - families = ["render"] + families = ["render.farm"] label = "Collect Render Layers" sync_workfile_version = False @@ -101,7 +101,6 @@ class CollectBlenderRender(pyblish.api.InstancePlugin): expected_files = expected_beauty | expected_aovs instance.data.update({ - "family": "render.farm", "frameStart": frame_start, "frameEnd": frame_end, "frameStartHandle": frame_handle_start, From f40125d7b2a49cf35bc3e165a8c53102eca799eb Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 23 Nov 2023 16:25:57 +0000 Subject: [PATCH 185/327] Fixed problem when preparing rendering --- openpype/hosts/blender/api/render_lib.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/blender/api/render_lib.py b/openpype/hosts/blender/api/render_lib.py index d564b5ebcb..646f87f9d7 100644 --- a/openpype/hosts/blender/api/render_lib.py +++ b/openpype/hosts/blender/api/render_lib.py @@ -1,4 +1,4 @@ -import os +from pathlib import Path import bpy @@ -59,7 +59,7 @@ def get_render_product(output_path, name, aov_sep): instance (pyblish.api.Instance): The instance to publish. ext (str): The image format to render. """ - filepath = os.path.join(output_path, name) + filepath = output_path / name.relative_to(name.anchor) render_product = f"{filepath}{aov_sep}beauty.####" render_product = render_product.replace("\\", "/") @@ -180,7 +180,7 @@ def set_node_tree(output_path, name, aov_sep, ext, multilayer): return [] output.file_slots.clear() - output.base_path = output_path + output.base_path = str(output_path) aov_file_products = [] @@ -191,8 +191,9 @@ def set_node_tree(output_path, name, aov_sep, ext, multilayer): output.file_slots.new(filepath) - aov_file_products.append( - (render_pass.name, os.path.join(output_path, filepath))) + filename = output_path / filepath.relative_to(filepath.anchor) + + aov_file_products.append((render_pass.name, filename)) node_input = output.inputs[-1] @@ -212,14 +213,13 @@ def imprint_render_settings(node, data): def prepare_rendering(asset_group): - name = asset_group.name + name = Path(asset_group.name) - filepath = bpy.data.filepath + filepath = Path(bpy.data.filepath) assert filepath, "Workfile not saved. Please save the file first." - file_path = os.path.dirname(filepath) - file_name = os.path.basename(filepath) - file_name, _ = os.path.splitext(file_name) + file_path = filepath.parent + file_name = Path(filepath.name).stem project = get_current_project_name() settings = get_project_settings(project) @@ -232,11 +232,11 @@ def prepare_rendering(asset_group): set_render_format(ext, multilayer) aov_list, custom_passes = set_render_passes(settings) - output_path = os.path.join(file_path, render_folder, file_name) + output_path = Path.joinpath(file_path, render_folder, file_name) render_product = get_render_product(output_path, name, aov_sep) aov_file_product = set_node_tree( - output_path, name, aov_sep, ext, multilayer) + output_path, str(name), aov_sep, ext, multilayer) bpy.context.scene.render.filepath = render_product From 1a027a412e5f141ad45dc418d7ddb3cd299873ca Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 24 Nov 2023 12:57:49 +0800 Subject: [PATCH 186/327] hound --- openpype/hosts/houdini/plugins/create/create_bgeo.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/create/create_bgeo.py b/openpype/hosts/houdini/plugins/create/create_bgeo.py index 8dfb2ebb46..4140919202 100644 --- a/openpype/hosts/houdini/plugins/create/create_bgeo.py +++ b/openpype/hosts/houdini/plugins/create/create_bgeo.py @@ -2,7 +2,6 @@ """Creator plugin for creating pointcache bgeo files.""" from openpype.hosts.houdini.api import plugin from openpype.pipeline import CreatedInstance, CreatorError -from openpype.lib import EnumDef import hou from openpype.lib import EnumDef, BoolDef From 452048d0eef4bb8824ce8325426dc0a3989fd304 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Fri, 24 Nov 2023 10:30:40 +0000 Subject: [PATCH 187/327] Testing: Fix is_test_failed (#5951) * Fix is_test_failed * Fix hound? --- tests/conftest.py | 8 ++++++++ tests/lib/testing_classes.py | 6 +----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index a862030fff..028c19bc5f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -85,3 +85,11 @@ def pytest_runtest_makereport(item, call): # be "setup", "call", "teardown" setattr(item, "rep_" + rep.when, rep) + + # In the event of module scoped fixtures, also mark failure in module. + module = item + while module is not None and not isinstance(module, pytest.Module): + module = module.parent + if module is not None: + if rep.when == 'call' and (rep.failed or rep.skipped): + module.module_test_failure = True diff --git a/tests/lib/testing_classes.py b/tests/lib/testing_classes.py index 7dae9a762f..7700381aa6 100644 --- a/tests/lib/testing_classes.py +++ b/tests/lib/testing_classes.py @@ -218,11 +218,7 @@ class ModuleUnitTest(BaseTest): yield mongo_client[self.TEST_OPENPYPE_NAME]["settings"] def is_test_failed(self, request): - # if request.node doesn't have rep_call, something failed - try: - return request.node.rep_call.failed - except AttributeError: - return True + return getattr(request.node, "module_test_failure", False) class PublishTest(ModuleUnitTest): From 714f66f7643d840f9dab44d49874cd7379cd44ce Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 24 Nov 2023 10:44:10 +0000 Subject: [PATCH 188/327] Fixes and suggestions applied from comments --- openpype/hosts/blender/api/render_lib.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/blender/api/render_lib.py b/openpype/hosts/blender/api/render_lib.py index 646f87f9d7..22c51d664a 100644 --- a/openpype/hosts/blender/api/render_lib.py +++ b/openpype/hosts/blender/api/render_lib.py @@ -191,6 +191,7 @@ def set_node_tree(output_path, name, aov_sep, ext, multilayer): output.file_slots.new(filepath) + filepath = Path(filepath) filename = output_path / filepath.relative_to(filepath.anchor) aov_file_products.append((render_pass.name, filename)) @@ -213,12 +214,12 @@ def imprint_render_settings(node, data): def prepare_rendering(asset_group): - name = Path(asset_group.name) + name = asset_group.name filepath = Path(bpy.data.filepath) assert filepath, "Workfile not saved. Please save the file first." - file_path = filepath.parent + dirpath = filepath.parent file_name = Path(filepath.name).stem project = get_current_project_name() @@ -232,11 +233,11 @@ def prepare_rendering(asset_group): set_render_format(ext, multilayer) aov_list, custom_passes = set_render_passes(settings) - output_path = Path.joinpath(file_path, render_folder, file_name) + output_path = Path.joinpath(dirpath, render_folder, file_name) - render_product = get_render_product(output_path, name, aov_sep) + render_product = get_render_product(output_path, Path(name), aov_sep) aov_file_product = set_node_tree( - output_path, str(name), aov_sep, ext, multilayer) + output_path, name, aov_sep, ext, multilayer) bpy.context.scene.render.filepath = render_product From cc82e645c6b2210dccbc63fc63a6e6a56244bf3d Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 24 Nov 2023 10:54:30 +0000 Subject: [PATCH 189/327] Fixed compatibility with old instances --- openpype/hosts/blender/plugins/publish/collect_render.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/blender/plugins/publish/collect_render.py b/openpype/hosts/blender/plugins/publish/collect_render.py index d1b1da5f4e..4f23f30cc0 100644 --- a/openpype/hosts/blender/plugins/publish/collect_render.py +++ b/openpype/hosts/blender/plugins/publish/collect_render.py @@ -15,7 +15,7 @@ class CollectBlenderRender(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.01 hosts = ["blender"] - families = ["render.farm"] + families = ["render", "render.farm"] label = "Collect Render Layers" sync_workfile_version = False @@ -101,6 +101,7 @@ class CollectBlenderRender(pyblish.api.InstancePlugin): expected_files = expected_beauty | expected_aovs instance.data.update({ + "family": "render.farm", "frameStart": frame_start, "frameEnd": frame_end, "frameStartHandle": frame_handle_start, From 75519b0a959f468d714f53bdcd7db2a57f75bfc1 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 24 Nov 2023 12:19:59 +0000 Subject: [PATCH 190/327] Fix increment workfile for render.farm family --- .../hosts/blender/plugins/publish/increment_workfile_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/blender/plugins/publish/increment_workfile_version.py b/openpype/hosts/blender/plugins/publish/increment_workfile_version.py index 7e33fd53fa..9f8d20aedc 100644 --- a/openpype/hosts/blender/plugins/publish/increment_workfile_version.py +++ b/openpype/hosts/blender/plugins/publish/increment_workfile_version.py @@ -14,7 +14,7 @@ class IncrementWorkfileVersion( optional = True hosts = ["blender"] families = ["animation", "model", "rig", "action", "layout", "blendScene", - "pointcache", "render"] + "pointcache", "render.farm"] def process(self, context): if not self.is_active(context.data): From 5162d8e4074ff46143558984f98283f5208720c4 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 24 Nov 2023 12:20:38 +0000 Subject: [PATCH 191/327] Added Delay option --- .../deadline/plugins/publish/submit_blender_deadline.py | 6 ++++++ openpype/settings/defaults/project_settings/deadline.json | 3 ++- .../schemas/projects_schema/schema_project_deadline.json | 5 +++++ server_addon/deadline/server/settings/publish_plugins.py | 4 +++- server_addon/deadline/server/version.py | 2 +- 5 files changed, 17 insertions(+), 3 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_blender_deadline.py b/openpype/modules/deadline/plugins/publish/submit_blender_deadline.py index e1bf5c074a..4c04b4a9c4 100644 --- a/openpype/modules/deadline/plugins/publish/submit_blender_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_blender_deadline.py @@ -40,6 +40,7 @@ class BlenderSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, jobInfo = {} pluginInfo = {} group = None + job_delay = "00:00:00:00" def get_job_info(self): job_info = DeadlineJobInfo(Plugin="Blender") @@ -91,6 +92,7 @@ class BlenderSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, job_info.Comment = context.data.get("comment") job_info.ChunkSize = attr_values.get("chunkSize", self.chunk_size) job_info.Priority = attr_values.get("priority", self.priority) + job_info.JobDelay = attr_values.get("job_delay", self.job_delay) # Add options from RenderGlobals render_globals = instance.data.get("renderGlobals", {}) @@ -212,6 +214,10 @@ class BlenderSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, TextDef("group", default=cls.group, label="Group Name"), + + TextDef("job_delay", + default=cls.job_delay, + label="Job Delay"), ]) return defs diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json index 2c5e0dc65d..50dd5367da 100644 --- a/openpype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -107,7 +107,8 @@ "use_published": true, "priority": 50, "chunk_size": 10, - "group": "none" + "group": "none", + "job_delay": "00:00:00:00" }, "ProcessSubmittedJobOnFarm": { "enabled": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json index 64db852c89..a3408e9871 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json @@ -581,6 +581,11 @@ "type": "text", "key": "group", "label": "Group Name" + }, + { + "type": "text", + "key": "job_delay", + "label": "Delay job (timecode dd:hh:mm:ss)" } ] }, diff --git a/server_addon/deadline/server/settings/publish_plugins.py b/server_addon/deadline/server/settings/publish_plugins.py index 54b7ff57c1..1d8b8c4eb2 100644 --- a/server_addon/deadline/server/settings/publish_plugins.py +++ b/server_addon/deadline/server/settings/publish_plugins.py @@ -237,6 +237,7 @@ class BlenderSubmitDeadlineModel(BaseSettingsModel): priority: int = Field(title="Priority") chunk_size: int = Field(title="Frame per Task") group: str = Field("", title="Group Name") + job_delay: str = Field("", title="Delay job (timecode dd:hh:mm:ss)") class AOVFilterSubmodel(BaseSettingsModel): @@ -424,7 +425,8 @@ DEFAULT_DEADLINE_PLUGINS_SETTINGS = { "use_published": True, "priority": 50, "chunk_size": 10, - "group": "none" + "group": "none", + "job_delay": "00:00:00:00" }, "ProcessSubmittedJobOnFarm": { "enabled": True, diff --git a/server_addon/deadline/server/version.py b/server_addon/deadline/server/version.py index ae7362549b..bbab0242f6 100644 --- a/server_addon/deadline/server/version.py +++ b/server_addon/deadline/server/version.py @@ -1 +1 @@ -__version__ = "0.1.3" +__version__ = "0.1.4" From 09b3646becdb77f2682a2aa8f148e3816bd58c95 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 24 Nov 2023 14:37:21 +0100 Subject: [PATCH 192/327] rescaling wip --- openpype/lib/__init__.py | 2 + openpype/lib/transcoding.py | 202 ++++++++++++++++++ openpype/plugins/publish/extract_thumbnail.py | 29 ++- 3 files changed, 224 insertions(+), 9 deletions(-) diff --git a/openpype/lib/__init__.py b/openpype/lib/__init__.py index f1eb564e5e..b3b12ac250 100644 --- a/openpype/lib/__init__.py +++ b/openpype/lib/__init__.py @@ -111,6 +111,7 @@ from .transcoding import ( get_ffmpeg_format_args, convert_ffprobe_fps_value, convert_ffprobe_fps_to_float, + get_rescaled_command_arguments, ) from .local_settings import ( @@ -232,6 +233,7 @@ __all__ = [ "get_ffmpeg_format_args", "convert_ffprobe_fps_value", "convert_ffprobe_fps_to_float", + "get_rescaled_command_arguments", "IniSettingRegistry", "JSONSettingRegistry", diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 97c8dd41ab..d52b4a8133 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1226,3 +1226,205 @@ def split_cmd_args(in_args): continue splitted_args.extend(arg.split(" ")) return splitted_args + + +def get_rescaled_command_arguments( + app, + input_path, + input_width, + input_height, + input_par, + target_width, + target_height, + target_par, + bg_color=None, + log=None +): + """Get command arguments for rescaling input to target size. + + Args: + app (str): Application for which command should be created. + Currently supported are "ffmpeg" and "oiiotool". + input_path (str): Path to input file. + input_width (int): Width of input. + input_height (int): Height of input. + input_par (float): Pixel aspect ratio of input. + target_width (int): Width of target. + target_height (int): Height of target. + target_par (float): Pixel aspect ratio of target. + bg_color (list[float]): List of float values for background color. + Should be in range 0.0 - 1.0. + log (logging.Logger): Logger used for logging. + + Returns: + list[str]: List of command arguments. + """ + command_args = [] + # recalculating input and target width + input_width = int(input_width * input_par) + target_width = int(target_width * target_par) + + # calculate aspect ratios + target_aspect = float(target_width) / target_height + input_aspect = float(input_width) / input_height + + # calculate scale size + scale_size = float(input_width) / target_width + if input_aspect < target_aspect: + scale_size = float(input_height) / target_height + + # calculate rescaled width and height + rescaled_width = int(input_width / scale_size) + rescaled_height = int(input_height / scale_size) + + # calculate width and height shift + rescaled_width_shift = int((target_width - rescaled_width) / 2) + rescaled_height_shift = int((target_height - rescaled_height) / 2) + + if app == "ffmpeg": + # create scale command + scale = "scale={0}:{1}".format(input_width, input_height) + pad = "pad={0}:{1}:({2}-iw)/2:({3}-ih)/2".format( + target_width, + target_height, + target_width, + target_height + ) + if input_width > target_width or input_height > target_height: + scale = "scale={0}:{1}".format(rescaled_width, rescaled_height) + pad = "pad={0}:{1}:{2}:{3}".format( + target_width, + target_height, + rescaled_width_shift, + rescaled_height_shift + ) + + if bg_color: + color = convert_color_float_to_hex(bg_color) + pad += ":{0}".format(color) + command_args.extend(["-vf", "{0},{1}".format(scale, pad)]) + + elif app == "oiiotool": + input_info = get_oiio_info_for_input(input_path, logger=log) + # Collect channels to export + _, channels_arg = get_oiio_input_and_channel_args( + input_info, alpha_default=1.0) + + command_args.extend([ + # Tell oiiotool which channels should be put to top stack + # (and output) + "--ch", channels_arg, + # Use first subimage + "--subimage", "0" + ]) + + if input_par != 1.0: + command_args.extend(["--pixelaspect", "1"]) + + width_shift = int((target_width - input_width) / 2) + height_shift = int((target_height - input_height) / 2) + + # default resample is not scaling source image + resample = [ + "--resize", + "{0}x{1}".format(input_width, input_height), + "--origin", + "+{0}+{1}".format(width_shift, height_shift), + ] + # scaled source image to target size + if input_width > target_width or input_height > target_height: + # form resample command + resample = [ + "--resize:filter=lanczos3", + "{0}x{1}".format(rescaled_width, rescaled_height), + "--origin", + "+{0}+{1}".format(rescaled_width_shift, rescaled_height_shift), + ] + command_args.extend(resample) + + fullsize = [ + "--fullsize", + "{0}x{1}".format(target_width, target_height) + ] + if bg_color: + color_str = ",".join([str(c) for c in bg_color]) + + fullsize.extend([ + "--pattern", + "constant:color={0}".format(color_str), + "{0}x{1}".format(target_width, target_height), + "4", # 4 channels + "--over" + ]) + command_args.extend(fullsize) + + else: + raise ValueError("app should be either \"ffmpeg\" or \"oiiotool\"") + + return command_args + + +def convert_color_float_to_hex(color_value): + """Get color mapping for ffmpeg. + Args: + color_value (list[float]): List of float values + Returns: + str: String with color values in hex format. + """ + red, green, blue, alpha = color_value + + # clamp values to max 1.0 and convert 255 range + red = int(min(red, 1.0) * 255) + green = int(min(green, 1.0) * 255) + blue = int(min(blue, 1.0) * 255) + alpha = min(alpha, 1.0) + + print("red: {0}, green: {1}, blue: {2}, alpha: {3}".format( + red, green, blue, alpha) + ) + # convert to 0-255 range + return "{0:0>2X}{1:0>2X}{2:0>2X}@{3}".format( + red, green, blue, alpha + ) + + +def get_oiio_input_and_channel_args(oiio_input_info, alpha_default=None): + """Get input and channel arguments for oiiotool. + Args: + oiio_input_info (dict): Information about input from oiio tool. + Should be output of function `get_oiio_info_for_input`. + alpha_default (float, optional): Default value for alpha channel. + Returns: + tuple[str, str]: Tuple of input and channel arguments. + """ + channel_names = oiio_input_info["channelnames"] + review_channels = get_convert_rgb_channels(channel_names) + + if review_channels is None: + raise ValueError( + "Couldn't find channels that can be used for conversion." + ) + + red, green, blue, alpha = review_channels + input_channels = [red, green, blue] + + channels_arg = "R={0},G={1},B={2}".format(red, green, blue) + if alpha is not None: + channels_arg += ",A={}".format(alpha) + input_channels.append(alpha) + elif alpha_default: + channels_arg += ",A={}".format(float(alpha_default)) + input_channels.append("A") + + input_channels_str = ",".join(input_channels) + + subimages = oiio_input_info.get("subimages") + input_arg = "-i" + if subimages is None or subimages == 1: + # Tell oiiotool which channels should be loaded + # - other channels are not loaded to memory so helps to avoid memory + # leak issues + # - this option is crashing if used on multipart exrs + input_arg += ":ch={}".format(input_channels_str) + + return input_arg, channels_arg diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index f09dc92184..66473a76aa 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -9,12 +9,15 @@ from openpype.lib import ( get_ffprobe_data, get_oiio_tool_args, is_oiio_supported, + get_rescaled_command_arguments, - run_subprocess, path_to_subprocess_arg, + run_subprocess, ) + from openpype.lib.transcoding import VIDEO_EXTENSIONS + class ExtractThumbnail(pyblish.api.InstancePlugin): """Create jpg thumbnail from sequence using ffmpeg""" @@ -322,12 +325,20 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): if self.target_size.get("type") == "source": return [] - width = self.target_size["width"] - height = self.target_size["height"] - # form arg string per application - if application == "ffmpeg": - return ["-vf", "scale={0}:{1}".format(width, height)] - elif application == "oiiotool": - return ["-resize", "{0}x{1}".format(width, height)] + target_width = self.target_size["width"] + target_height = self.target_size["height"] + target_par = self.target_size.get("par", 1.0) - return [] + # form arg string per application + return get_rescaled_command_arguments( + application, + str(input_path), + input_width, + input_height, + input_par, + target_width, + target_height, + target_par, + bg_color, + log=self.log + ) From 862cb05d2f5d358e7c62dde8637553191fbfe431 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 24 Nov 2023 14:47:16 +0000 Subject: [PATCH 193/327] Get comment from instance --- .../modules/deadline/plugins/publish/submit_blender_deadline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_blender_deadline.py b/openpype/modules/deadline/plugins/publish/submit_blender_deadline.py index 4c04b4a9c4..c5217b45f1 100644 --- a/openpype/modules/deadline/plugins/publish/submit_blender_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_blender_deadline.py @@ -75,6 +75,7 @@ class BlenderSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, job_info.Pool = instance.data.get("primaryPool") job_info.SecondaryPool = instance.data.get("secondaryPool") + job_info.Comment = instance.data.get("comment") if self.group != "none" and self.group: job_info.Group = self.group @@ -89,7 +90,6 @@ class BlenderSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, machine_list_key = "Blacklist" render_globals[machine_list_key] = machine_list - job_info.Comment = context.data.get("comment") job_info.ChunkSize = attr_values.get("chunkSize", self.chunk_size) job_info.Priority = attr_values.get("priority", self.priority) job_info.JobDelay = attr_values.get("job_delay", self.job_delay) From c3c9a93f50e4583e69bb3a0bd1c8948ebbacb8fd Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 24 Nov 2023 14:54:37 +0000 Subject: [PATCH 194/327] Make clearer some path joins --- openpype/hosts/blender/api/render_lib.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/blender/api/render_lib.py b/openpype/hosts/blender/api/render_lib.py index 22c51d664a..1ab3b260bb 100644 --- a/openpype/hosts/blender/api/render_lib.py +++ b/openpype/hosts/blender/api/render_lib.py @@ -59,7 +59,7 @@ def get_render_product(output_path, name, aov_sep): instance (pyblish.api.Instance): The instance to publish. ext (str): The image format to render. """ - filepath = output_path / name.relative_to(name.anchor) + filepath = output_path / name.lstrip("/") render_product = f"{filepath}{aov_sep}beauty.####" render_product = render_product.replace("\\", "/") @@ -191,8 +191,7 @@ def set_node_tree(output_path, name, aov_sep, ext, multilayer): output.file_slots.new(filepath) - filepath = Path(filepath) - filename = output_path / filepath.relative_to(filepath.anchor) + filename = output_path / filepath.lstrip("/") aov_file_products.append((render_pass.name, filename)) @@ -235,7 +234,7 @@ def prepare_rendering(asset_group): output_path = Path.joinpath(dirpath, render_folder, file_name) - render_product = get_render_product(output_path, Path(name), aov_sep) + render_product = get_render_product(output_path, name, aov_sep) aov_file_product = set_node_tree( output_path, name, aov_sep, ext, multilayer) From a35f9d935ea2de29ae8853006bb569097a3efc6b Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 24 Nov 2023 15:37:27 +0000 Subject: [PATCH 195/327] Reverted family to render and fixed problems with family --- .../hosts/blender/plugins/create/create_render.py | 2 +- .../hosts/blender/plugins/publish/collect_render.py | 12 ++++-------- .../plugins/publish/validate_deadline_publish.py | 2 +- .../plugins/publish/submit_blender_deadline.py | 2 +- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/blender/plugins/create/create_render.py b/openpype/hosts/blender/plugins/create/create_render.py index a643ccdaa3..7fb3e5eb00 100644 --- a/openpype/hosts/blender/plugins/create/create_render.py +++ b/openpype/hosts/blender/plugins/create/create_render.py @@ -10,7 +10,7 @@ class CreateRenderlayer(plugin.BaseCreator): identifier = "io.openpype.creators.blender.render" label = "Render" - family = "render.farm" + family = "render" icon = "eye" def create( diff --git a/openpype/hosts/blender/plugins/publish/collect_render.py b/openpype/hosts/blender/plugins/publish/collect_render.py index 4f23f30cc0..da02f99052 100644 --- a/openpype/hosts/blender/plugins/publish/collect_render.py +++ b/openpype/hosts/blender/plugins/publish/collect_render.py @@ -11,12 +11,12 @@ import pyblish.api class CollectBlenderRender(pyblish.api.InstancePlugin): - """Gather all publishable render layers from renderSetup.""" + """Gather all publishable render instances.""" order = pyblish.api.CollectorOrder + 0.01 hosts = ["blender"] - families = ["render", "render.farm"] - label = "Collect Render Layers" + families = ["render"] + label = "Collect Render" sync_workfile_version = False @staticmethod @@ -78,8 +78,6 @@ class CollectBlenderRender(pyblish.api.InstancePlugin): assert render_data, "No render data found." - self.log.debug(f"render_data: {dict(render_data)}") - render_product = render_data.get("render_product") aov_file_product = render_data.get("aov_file_product") ext = render_data.get("image_format") @@ -101,7 +99,7 @@ class CollectBlenderRender(pyblish.api.InstancePlugin): expected_files = expected_beauty | expected_aovs instance.data.update({ - "family": "render.farm", + "families": ["render", "render.farm"], "frameStart": frame_start, "frameEnd": frame_end, "frameStartHandle": frame_handle_start, @@ -120,5 +118,3 @@ class CollectBlenderRender(pyblish.api.InstancePlugin): "colorspaceView": "ACES 1.0 SDR-video", "renderProducts": colorspace.ARenderProduct(), }) - - self.log.debug(f"data: {instance.data}") diff --git a/openpype/hosts/blender/plugins/publish/validate_deadline_publish.py b/openpype/hosts/blender/plugins/publish/validate_deadline_publish.py index d8826adc9c..bb243f08cc 100644 --- a/openpype/hosts/blender/plugins/publish/validate_deadline_publish.py +++ b/openpype/hosts/blender/plugins/publish/validate_deadline_publish.py @@ -19,7 +19,7 @@ class ValidateDeadlinePublish(pyblish.api.InstancePlugin, """ order = ValidateContentsOrder - families = ["render.farm"] + families = ["render"] hosts = ["blender"] label = "Validate Render Output for Deadline" optional = True diff --git a/openpype/modules/deadline/plugins/publish/submit_blender_deadline.py b/openpype/modules/deadline/plugins/publish/submit_blender_deadline.py index c5217b45f1..52a307646e 100644 --- a/openpype/modules/deadline/plugins/publish/submit_blender_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_blender_deadline.py @@ -32,7 +32,7 @@ class BlenderSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, OpenPypePyblishPluginMixin): label = "Submit Render to Deadline" hosts = ["blender"] - families = ["render.farm"] + families = ["render"] use_published = True priority = 50 From d1cf2e895fb23b247bb836a9762220af8e43bdcb Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 24 Nov 2023 16:20:33 +0000 Subject: [PATCH 196/327] Fix problem with imprinting of data when saving render settings --- openpype/hosts/blender/api/render_lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/blender/api/render_lib.py b/openpype/hosts/blender/api/render_lib.py index 1ab3b260bb..b437078ad8 100644 --- a/openpype/hosts/blender/api/render_lib.py +++ b/openpype/hosts/blender/api/render_lib.py @@ -191,7 +191,7 @@ def set_node_tree(output_path, name, aov_sep, ext, multilayer): output.file_slots.new(filepath) - filename = output_path / filepath.lstrip("/") + filename = str(output_path / filepath.lstrip("/")) aov_file_products.append((render_pass.name, filename)) From 319109be593940d282d9beb56a26b70e0cd76ac0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Fri, 24 Nov 2023 17:47:23 +0100 Subject: [PATCH 197/327] :bug: fix issue with render sets collection and inventory action --- .../plugins/inventory/import_modelrender.py | 11 ++---- .../maya/plugins/publish/collect_look.py | 39 +++++++++++++------ openpype/pipeline/actions.py | 7 ++++ 3 files changed, 37 insertions(+), 20 deletions(-) diff --git a/openpype/hosts/maya/plugins/inventory/import_modelrender.py b/openpype/hosts/maya/plugins/inventory/import_modelrender.py index 4db8c4f2f6..3b30695146 100644 --- a/openpype/hosts/maya/plugins/inventory/import_modelrender.py +++ b/openpype/hosts/maya/plugins/inventory/import_modelrender.py @@ -33,7 +33,7 @@ class ImportModelRender(InventoryAction): ) def process(self, containers): - from maya import cmds + from maya import cmds # noqa: F401 project_name = get_current_project_name() for container in containers: @@ -66,7 +66,7 @@ class ImportModelRender(InventoryAction): None """ - from maya import cmds + from maya import cmds # noqa: F401 project_name = get_current_project_name() repre_docs = get_representations( @@ -85,12 +85,7 @@ class ImportModelRender(InventoryAction): if scene_type_regex.fullmatch(repre_name): look_repres.append(repre_doc) - # QUESTION should we care if there is more then one look - # representation? (since it's based on regex match) - look_repre = None - if look_repres: - look_repre = look_repres[0] - + look_repre = look_repres[0] if look_repres else None # QUESTION shouldn't be json representation validated too? if not look_repre: print("No model render sets for this model version..") diff --git a/openpype/hosts/maya/plugins/publish/collect_look.py b/openpype/hosts/maya/plugins/publish/collect_look.py index 16ae20e0ae..a9e967a094 100644 --- a/openpype/hosts/maya/plugins/publish/collect_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_look.py @@ -69,9 +69,7 @@ def get_attributes(dictionary, attr, node=None): else: val = dictionary.get(attr, []) - if not isinstance(val, list): - return [val] - return val + return val if isinstance(val, list) else [val] def get_look_attrs(node): @@ -106,7 +104,7 @@ def get_look_attrs(node): def node_uses_image_sequence(node, node_path): - # type: (str) -> bool + # type: (str, str) -> bool """Return whether file node uses an image sequence or single image. Determine if a node uses an image sequence or just a single image, @@ -114,6 +112,7 @@ def node_uses_image_sequence(node, node_path): Args: node (str): Name of the Maya node + node_path (str): The file path of the node Returns: bool: True if node uses an image sequence @@ -247,7 +246,7 @@ def get_file_node_files(node): # For sequences get all files and filter to only existing files result = [] - for index, path in enumerate(paths): + for path in paths: if node_uses_image_sequence(node, path): glob_pattern = seq_to_glob(path) result.extend(glob.glob(glob_pattern)) @@ -358,6 +357,11 @@ class CollectLook(pyblish.api.InstancePlugin): for attr in shader_attrs: if cmds.attributeQuery(attr, node=look, exists=True): existing_attrs.append("{}.{}".format(look, attr)) + + print("-"*100) + print("existing_attrs: {}".format(existing_attrs)) + print("-"*100) + materials = cmds.listConnections(existing_attrs, source=True, destination=False) or [] @@ -377,13 +381,24 @@ class CollectLook(pyblish.api.InstancePlugin): # connected to the look sets above we now add direct history # for some of the look sets directly # handling render attribute sets - render_set_types = [ - "VRayDisplacement", - "VRayLightMesh", - "VRayObjectProperties", - "RedshiftObjectId", - "RedshiftMeshParameters", - ] + + # this needs to be done like this now, because Maya + # (at least 2024) crashes with Warning when render set type + # isn't available. cmds.ls() will return empty list + render_set_types = [] + if cmds.pluginInfo("vrayformaya", query=True, loaded=True): + render_set_types += [ + "VRayDisplacement", + "VRayLightMesh", + "VRayObjectProperties", + ] + + if cmds.pluginInfo("redshift4maya", query=True, loaded=True): + render_set_types += [ + "RedshiftObjectId", + "RedshiftMeshParameters", + ] + render_sets = cmds.ls(look_sets, type=render_set_types) if render_sets: history.extend( diff --git a/openpype/pipeline/actions.py b/openpype/pipeline/actions.py index feb1bd05d2..d89e2096ef 100644 --- a/openpype/pipeline/actions.py +++ b/openpype/pipeline/actions.py @@ -7,6 +7,8 @@ from openpype.pipeline.plugin_discover import ( deregister_plugin_path ) +from .load.utils import get_representation_path_from_context + class LauncherAction(object): """A custom action available""" @@ -100,6 +102,11 @@ class InventoryAction(object): """ return True + @classmethod + def filepath_from_context(cls, context): + return get_representation_path_from_context(context) + + # Launcher action def discover_launcher_actions(): From 6b4d0487acea25c67c1b4d1d64dfbd2e63fb7308 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Fri, 24 Nov 2023 17:48:22 +0100 Subject: [PATCH 198/327] :dog: remove debug prints --- openpype/hosts/maya/plugins/publish/collect_look.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_look.py b/openpype/hosts/maya/plugins/publish/collect_look.py index a9e967a094..11e87a8295 100644 --- a/openpype/hosts/maya/plugins/publish/collect_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_look.py @@ -358,10 +358,6 @@ class CollectLook(pyblish.api.InstancePlugin): if cmds.attributeQuery(attr, node=look, exists=True): existing_attrs.append("{}.{}".format(look, attr)) - print("-"*100) - print("existing_attrs: {}".format(existing_attrs)) - print("-"*100) - materials = cmds.listConnections(existing_attrs, source=True, destination=False) or [] From 069977b3274f2a2c3de2dd4315a0d4decb7423a4 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 24 Nov 2023 21:35:06 +0100 Subject: [PATCH 199/327] improving rescale functionality --- openpype/lib/transcoding.py | 86 +++++++++++-------- openpype/plugins/publish/extract_thumbnail.py | 23 +++-- .../defaults/project_settings/global.json | 6 ++ .../schemas/schema_global_publish.json | 5 ++ 4 files changed, 76 insertions(+), 44 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index d52b4a8133..ace95002d9 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1229,37 +1229,45 @@ def split_cmd_args(in_args): def get_rescaled_command_arguments( - app, + application, input_path, - input_width, - input_height, - input_par, target_width, target_height, - target_par, + target_par=None, bg_color=None, log=None ): """Get command arguments for rescaling input to target size. Args: - app (str): Application for which command should be created. + application (str): Application for which command should be created. Currently supported are "ffmpeg" and "oiiotool". input_path (str): Path to input file. - input_width (int): Width of input. - input_height (int): Height of input. - input_par (float): Pixel aspect ratio of input. target_width (int): Width of target. target_height (int): Height of target. - target_par (float): Pixel aspect ratio of target. - bg_color (list[float]): List of float values for background color. - Should be in range 0.0 - 1.0. - log (logging.Logger): Logger used for logging. + target_par (Optional[float]): Pixel aspect ratio of target. + bg_color (Optional[list[int]]): List of 8bit int values for + background color. Should be in range 0 - 255. + log (Optional[logging.Logger]): Logger used for logging. Returns: list[str]: List of command arguments. """ command_args = [] + target_par = target_par or 1.0 + input_par = 1.0 + + # ffmpeg command + input_file_metadata = get_ffprobe_data(input_path, logger=log) + input_width = int(input_file_metadata["streams"][0]["width"]) + input_height = int(input_file_metadata["streams"][0]["height"]) + stream_input_par = input_file_metadata["streams"][0].get( + "sample_aspect_ratio") + if stream_input_par: + input_par = ( + float(stream_input_par.split(":")[0]) + / float(stream_input_par.split(":")[1]) + ) # recalculating input and target width input_width = int(input_width * input_par) target_width = int(target_width * target_par) @@ -1281,7 +1289,7 @@ def get_rescaled_command_arguments( rescaled_width_shift = int((target_width - rescaled_width) / 2) rescaled_height_shift = int((target_height - rescaled_height) / 2) - if app == "ffmpeg": + if application == "ffmpeg": # create scale command scale = "scale={0}:{1}".format(input_width, input_height) pad = "pad={0}:{1}:({2}-iw)/2:({3}-ih)/2".format( @@ -1300,11 +1308,11 @@ def get_rescaled_command_arguments( ) if bg_color: - color = convert_color_float_to_hex(bg_color) + color = convert_color_values(application, bg_color) pad += ":{0}".format(color) command_args.extend(["-vf", "{0},{1}".format(scale, pad)]) - elif app == "oiiotool": + elif application == "oiiotool": input_info = get_oiio_info_for_input(input_path, logger=log) # Collect channels to export _, channels_arg = get_oiio_input_and_channel_args( @@ -1347,11 +1355,11 @@ def get_rescaled_command_arguments( "{0}x{1}".format(target_width, target_height) ] if bg_color: - color_str = ",".join([str(c) for c in bg_color]) + color = convert_color_values(application, bg_color) fullsize.extend([ "--pattern", - "constant:color={0}".format(color_str), + "constant:color={0}".format(color), "{0}x{1}".format(target_width, target_height), "4", # 4 channels "--over" @@ -1359,33 +1367,41 @@ def get_rescaled_command_arguments( command_args.extend(fullsize) else: - raise ValueError("app should be either \"ffmpeg\" or \"oiiotool\"") + raise ValueError( + "\"application\" input argument should " + "be either \"ffmpeg\" or \"oiiotool\"" + ) return command_args -def convert_color_float_to_hex(color_value): - """Get color mapping for ffmpeg. +def convert_color_values(application, color_value): + """Get color mapping for ffmpeg and oiiotool. Args: - color_value (list[float]): List of float values + application (str): Application for which command should be created. + color_value (list[int]): List of 8bit int values for RGBA. Returns: - str: String with color values in hex format. + str: ffmpeg returns hex string, oiiotool is string with floats. """ red, green, blue, alpha = color_value - # clamp values to max 1.0 and convert 255 range - red = int(min(red, 1.0) * 255) - green = int(min(green, 1.0) * 255) - blue = int(min(blue, 1.0) * 255) - alpha = min(alpha, 1.0) + if application == "ffmpeg": + return "{0:0>2X}{1:0>2X}{2:0>2X}@{3}".format( + red, green, blue, (alpha / 255.0) + ) + elif application == "oiiotool": + red = float(red / 255) + green = float(green / 255) + blue = float(blue / 255) + alpha = float(alpha / 255) - print("red: {0}, green: {1}, blue: {2}, alpha: {3}".format( - red, green, blue, alpha) - ) - # convert to 0-255 range - return "{0:0>2X}{1:0>2X}{2:0>2X}@{3}".format( - red, green, blue, alpha - ) + return "{0:.2f},{1:.2f},{2:.2f},{3:.2f}".format( + red, green, blue, alpha) + else: + raise ValueError( + "\"application\" input argument should " + "be either \"ffmpeg\" or \"oiiotool\"" + ) def get_oiio_input_and_channel_args(oiio_input_info, alpha_default=None): diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 66473a76aa..7265b9c164 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -36,6 +36,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): "width": 1920, "height": 1080 } + background_color = None duration_split = 0.5 ffmpeg_args = None @@ -287,7 +288,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): video_data = get_ffprobe_data(video_file_path, logger=self.log) duration = float(video_data["format"]["duration"]) - resolution_arg = self._get_resolution_arg("ffmpeg") cmd_args = [ "-y", "-ss", str(duration * self.duration_split), @@ -296,6 +296,12 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): "-probesize", max_int, "-vframes", "1" ] + + # get resolution arg + resolution_arg = self._get_resolution_arg( + "ffmpeg", + video_file_path, + ) if resolution_arg: cmd_args.extend(resolution_arg) @@ -320,25 +326,24 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): ) return None - def _get_resolution_arg(self, application): + def _get_resolution_arg( + self, + application, + input_path, + ): # get settings if self.target_size.get("type") == "source": return [] target_width = self.target_size["width"] target_height = self.target_size["height"] - target_par = self.target_size.get("par", 1.0) # form arg string per application return get_rescaled_command_arguments( application, - str(input_path), - input_width, - input_height, - input_par, + input_path, target_width, target_height, - target_par, - bg_color, + bg_color=self.background_color, log=self.log ) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 5633e81526..c585d1b00e 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -71,6 +71,12 @@ "ExtractThumbnail": { "enabled": true, "integrate_thumbnail": false, + "background_color": [ + 0, + 0, + 0, + 255 + ], "duration_split": 0.5, "target_size": { "type": "resize", diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index b00301d8a2..23739c7520 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -254,6 +254,11 @@ } ] }, + { + "type": "color", + "label": "Background color", + "key": "background_color" + }, { "key": "duration_split", "label": "Duration split ratio", From cd8451e811b4481c8f394c56dbe7ea7a1d945403 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 24 Nov 2023 22:04:11 +0100 Subject: [PATCH 200/327] adding reformating to oiiotool process - removing it from wrong ffmpeg process --- openpype/plugins/publish/extract_thumbnail.py | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 7265b9c164..0de0df9692 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -217,11 +217,17 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): def _create_thumbnail_oiio(self, src_path, dst_path): self.log.debug("Extracting thumbnail with OIIO: {}".format(dst_path)) + resolution_arg = self._get_resolution_arg("oiiotool", src_path) oiio_cmd = get_oiio_tool_args( - "oiiotool", - "-a", src_path, - "-o", dst_path + "oiiotool", path_to_subprocess_arg(src_path) ) + if resolution_arg: + oiio_cmd.extend(resolution_arg) + + oiio_cmd.extend([ + "-o", path_to_subprocess_arg(dst_path) + ]) + self.log.debug("running: {}".format(" ".join(oiio_cmd))) try: run_subprocess(oiio_cmd, logger=self.log) @@ -235,7 +241,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): def _create_thumbnail_ffmpeg(self, src_path, dst_path): self.log.debug("Extracting thumbnail with FFMPEG: {}".format(dst_path)) - resolution_arg = self._get_resolution_arg("ffmpeg") + resolution_arg = self._get_resolution_arg("ffmpeg", src_path) ffmpeg_path_args = get_ffmpeg_tool_args("ffmpeg") ffmpeg_args = self.ffmpeg_args or {} @@ -297,14 +303,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): "-vframes", "1" ] - # get resolution arg - resolution_arg = self._get_resolution_arg( - "ffmpeg", - video_file_path, - ) - if resolution_arg: - cmd_args.extend(resolution_arg) - # add output file path cmd_args.append(output_thumb_file_path) From 97533ea25a02992ec0e674e3792cf255372b65d1 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Sat, 25 Nov 2023 03:25:28 +0000 Subject: [PATCH 201/327] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index 89067af269..a54f88871a 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.17.7-nightly.2" +__version__ = "3.17.7-nightly.3" From ddefee8f0e05320ea9c3d8a5d63d196517db747b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 25 Nov 2023 03:26:09 +0000 Subject: [PATCH 202/327] chore(): update bug report / version --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index e2afcdaac7..f59eb1edb1 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,7 @@ body: label: Version description: What version are you running? Look to OpenPype Tray options: + - 3.17.7-nightly.3 - 3.17.7-nightly.2 - 3.17.7-nightly.1 - 3.17.6 @@ -134,7 +135,6 @@ body: - 3.15.2-nightly.6 - 3.15.2-nightly.5 - 3.15.2-nightly.4 - - 3.15.2-nightly.3 validations: required: true - type: dropdown From 12c8e9852c58011acf86e923276a0b946f14c9ea Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 27 Nov 2023 17:29:10 +0800 Subject: [PATCH 203/327] make sure basename of the filenames for 'files' in representation --- .../hosts/maya/plugins/publish/extract_redshift_proxy.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py b/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py index 3868270b79..d601267802 100644 --- a/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py +++ b/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py @@ -50,11 +50,12 @@ class ExtractRedshiftProxy(publish.Extractor): # Padding is taken from number of digits of the end_frame. # Not sure where Redshift is taking it. repr_files = [ - "{}.{}{}".format(root, str(frame).rjust(4, "0"), ext) # noqa: E501 + "{}.{}{}".format( + os.path.basename(root), str(frame).rjust(4, "0"), ext) # noqa: E501 for frame in range( int(start_frame), int(end_frame) + 1, - int(instance.data["step"]), + int(instance.data["step"]) )] # vertex_colors = instance.data.get("vertexColors", False) From 90a62c35dcde56f68a9149d6aae45fde11a998e3 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 27 Nov 2023 17:32:21 +0800 Subject: [PATCH 204/327] hound --- openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py b/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py index d601267802..67533cad35 100644 --- a/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py +++ b/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py @@ -50,8 +50,7 @@ class ExtractRedshiftProxy(publish.Extractor): # Padding is taken from number of digits of the end_frame. # Not sure where Redshift is taking it. repr_files = [ - "{}.{}{}".format( - os.path.basename(root), str(frame).rjust(4, "0"), ext) # noqa: E501 + "{}.{}{}".format(os.path.basename(root), str(frame).rjust(4, "0"), ext) # noqa: E501 for frame in range( int(start_frame), int(end_frame) + 1, From de572f59f9d67a9c1e4b7407776d088fb15b9a11 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 27 Nov 2023 17:33:28 +0800 Subject: [PATCH 205/327] hound --- openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py b/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py index 67533cad35..7fc8760a70 100644 --- a/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py +++ b/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py @@ -50,7 +50,7 @@ class ExtractRedshiftProxy(publish.Extractor): # Padding is taken from number of digits of the end_frame. # Not sure where Redshift is taking it. repr_files = [ - "{}.{}{}".format(os.path.basename(root), str(frame).rjust(4, "0"), ext) # noqa: E501 + "{}.{}{}".format(os.path.basename(root), str(frame).rjust(4, "0"), ext) # noqa: E501 for frame in range( int(start_frame), int(end_frame) + 1, From 1d885222870b8ebf1f7f92f3d162ab2d666b1315 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 27 Nov 2023 18:39:09 +0800 Subject: [PATCH 206/327] make sure default shader connection validator not checking when the maya version is 2024 --- .../publish/validate_look_default_shaders_connections.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/hosts/maya/plugins/publish/validate_look_default_shaders_connections.py b/openpype/hosts/maya/plugins/publish/validate_look_default_shaders_connections.py index 0109f6ebd5..464c3facc2 100644 --- a/openpype/hosts/maya/plugins/publish/validate_look_default_shaders_connections.py +++ b/openpype/hosts/maya/plugins/publish/validate_look_default_shaders_connections.py @@ -45,6 +45,10 @@ class ValidateLookDefaultShadersConnections(pyblish.api.InstancePlugin): # Process as usual invalid = list() + if int(cmds.about(version=True)) >= 2024: + self.log.debug("IntialShadingGroup no longer connected to the default shader" + " in Maya 2024. Skipping Look Default Shader Connections..") + return for plug, input_node in self.DEFAULTS: inputs = cmds.listConnections(plug, source=True, From d0a423423ca8e88a40a1d4b5dfcbbd206cbe9da5 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 27 Nov 2023 18:44:14 +0800 Subject: [PATCH 207/327] hound --- .../publish/validate_look_default_shaders_connections.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_look_default_shaders_connections.py b/openpype/hosts/maya/plugins/publish/validate_look_default_shaders_connections.py index 464c3facc2..69a2081689 100644 --- a/openpype/hosts/maya/plugins/publish/validate_look_default_shaders_connections.py +++ b/openpype/hosts/maya/plugins/publish/validate_look_default_shaders_connections.py @@ -46,8 +46,9 @@ class ValidateLookDefaultShadersConnections(pyblish.api.InstancePlugin): # Process as usual invalid = list() if int(cmds.about(version=True)) >= 2024: - self.log.debug("IntialShadingGroup no longer connected to the default shader" - " in Maya 2024. Skipping Look Default Shader Connections..") + self.log.debug("IntialShadingGroup no longer " + "connected to the default shader in Maya 2024" + "Skipping Look Default Shader Connections..") return for plug, input_node in self.DEFAULTS: inputs = cmds.listConnections(plug, From 618b4b0c6d7b4cd7af858558de0463134d39634c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Mon, 27 Nov 2023 12:22:11 +0100 Subject: [PATCH 208/327] :art: inject submitter environment to the royal render job --- openpype/modules/royalrender/lib.py | 88 +++++++++++++++++-- .../publish/create_maya_royalrender_job.py | 3 +- .../publish/create_publish_royalrender_job.py | 3 + openpype/modules/royalrender/rr_job.py | 4 +- 4 files changed, 86 insertions(+), 12 deletions(-) diff --git a/openpype/modules/royalrender/lib.py b/openpype/modules/royalrender/lib.py index 4708d25eed..9c4221d9cd 100644 --- a/openpype/modules/royalrender/lib.py +++ b/openpype/modules/royalrender/lib.py @@ -1,23 +1,28 @@ # -*- coding: utf-8 -*- """Submitting render job to RoyalRender.""" import os -import re +import json import platform +import re +import tempfile +import uuid from datetime import datetime import pyblish.api -from openpype.tests.lib import is_in_tests -from openpype.pipeline.publish.lib import get_published_workfile_instance -from openpype.pipeline.publish import KnownPublishError + +from openpype.lib import BoolDef, NumberDef, is_running_from_build +from openpype.lib.execute import run_openpype_process from openpype.modules.royalrender.api import Api as rrApi from openpype.modules.royalrender.rr_job import ( - RRJob, CustomAttribute, get_rr_platform) -from openpype.lib import ( - is_running_from_build, - BoolDef, - NumberDef, + CustomAttribute, + RRJob, + RREnvList, + get_rr_platform, ) from openpype.pipeline import OpenPypePyblishPluginMixin +from openpype.pipeline.publish import KnownPublishError +from openpype.pipeline.publish.lib import get_published_workfile_instance +from openpype.tests.lib import is_in_tests class BaseCreateRoyalRenderJob(pyblish.api.InstancePlugin, @@ -302,3 +307,68 @@ class BaseCreateRoyalRenderJob(pyblish.api.InstancePlugin, path = path.replace(first_frame, "#" * padding) return path + + def inject_environment(self, instance, job): + # type: (pyblish.api.Instance, RRJob) -> RRJob + """Inject environment variables for RR submission. + + This function mimics the behaviour of the Deadline + integration. It is just temporary solution until proper + runtime environment injection is implemented in RR. + + Args: + instance (pyblish.api.Instance): Publishing instance + job (RRJob): RRJob instance to be injected. + + Returns: + RRJob: Injected RRJob instance. + + Throws: + RuntimeError: If any of the required env vars is missing. + + """ + + temp_file_name = "{}_{}.json".format( + datetime.utcnow().strftime('%Y%m%d%H%M%S%f'), + str(uuid.uuid1()) + ) + + export_url = os.path.join(tempfile.gettempdir(), temp_file_name) + print(">>> Temporary path: {}".format(export_url)) + + args = [ + "--headless", + "extractenvironments", + export_url + ] + + anatomy_data = instance.context.data["anatomyData"] + + add_kwargs = { + "project": anatomy_data["project"]["name"], + "asset": instance.context.data["asset"], + "task": anatomy_data["task"]["name"], + "app": instance.context.data.get("appName"), + "envgroup": "farm" + } + + if os.getenv('IS_TEST'): + args.append("--automatic-tests") + + if not all(add_kwargs.values()): + raise RuntimeError(( + "Missing required env vars: AVALON_PROJECT, AVALON_ASSET," + " AVALON_TASK, AVALON_APP_NAME" + )) + + for key, value in add_kwargs.items(): + args.extend([f"--{key}", value]) + self.log.debug("Executing: {}".format(" ".join(args))) + run_openpype_process(*args, logger=self.log) + + self.log.debug("Loading file ...") + with open(export_url) as fp: + contents = json.load(fp) + + job.rrEnvList = RREnvList(contents).serialize() + return job diff --git a/openpype/modules/royalrender/plugins/publish/create_maya_royalrender_job.py b/openpype/modules/royalrender/plugins/publish/create_maya_royalrender_job.py index 22d910b7cd..775a2964fd 100644 --- a/openpype/modules/royalrender/plugins/publish/create_maya_royalrender_job.py +++ b/openpype/modules/royalrender/plugins/publish/create_maya_royalrender_job.py @@ -2,7 +2,7 @@ """Submitting render job to RoyalRender.""" import os -from maya.OpenMaya import MGlobal +from maya.OpenMaya import MGlobal # noqa: F401 from openpype.modules.royalrender import lib from openpype.pipeline.farm.tools import iter_expected_files @@ -38,5 +38,6 @@ class CreateMayaRoyalRenderJob(lib.BaseCreateRoyalRenderJob): job = self.get_job(instance, self.scene_path, first_file_path, layer_name) job = self.update_job_with_host_specific(instance, job) + job = self.inject_environment(instance, job) instance.data["rrJobs"].append(job) diff --git a/openpype/modules/royalrender/plugins/publish/create_publish_royalrender_job.py b/openpype/modules/royalrender/plugins/publish/create_publish_royalrender_job.py index e13bf97e54..9be5d300df 100644 --- a/openpype/modules/royalrender/plugins/publish/create_publish_royalrender_job.py +++ b/openpype/modules/royalrender/plugins/publish/create_publish_royalrender_job.py @@ -205,6 +205,9 @@ class CreatePublishRoyalRenderJob(pyblish.api.InstancePlugin, jobs_pre_ids = [] for job in instance.data["rrJobs"]: # type: RRJob if job.rrEnvList: + if len(job.rrEnvList) > 2000: + self.log.warning(("Job environment is too long " + f"{len(job.rrEnvList)} > 2000")) job_environ.update( dict(RREnvList.parse(job.rrEnvList)) ) diff --git a/openpype/modules/royalrender/rr_job.py b/openpype/modules/royalrender/rr_job.py index b85ac592f8..62a82d45e8 100644 --- a/openpype/modules/royalrender/rr_job.py +++ b/openpype/modules/royalrender/rr_job.py @@ -32,7 +32,7 @@ class RREnvList(dict): """Parse rrEnvList string and return it as RREnvList object.""" out = RREnvList() for var in data.split("~~~"): - k, v = var.split("=") + k, v = var.split("=", maxsplit=1) out[k] = v return out @@ -172,7 +172,7 @@ class RRJob(object): # Environment # only used in RR 8.3 and newer - rrEnvList = attr.ib(default=None) # type: str + rrEnvList = attr.ib(default=None, type=str) # type: str class SubmitterParameter: From fbc8021b45a3456fe37f402b7c9918eab02ced66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Mon, 27 Nov 2023 12:54:28 +0100 Subject: [PATCH 209/327] :bug: prevent passing all environments --- openpype/pype_commands.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/pype_commands.py b/openpype/pype_commands.py index b5828d3dfe..b6535e0835 100644 --- a/openpype/pype_commands.py +++ b/openpype/pype_commands.py @@ -186,6 +186,7 @@ class PypeCommands: app, env_group=env_group, launch_type=LaunchTypes.farm_render, + env={} ) else: env = os.environ.copy() From 425cbd2ac0524f45ef0a7c7196f8455d9d010d51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Mon, 27 Nov 2023 13:12:07 +0100 Subject: [PATCH 210/327] :dog: fix hound --- .../plugins/publish/create_publish_royalrender_job.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/modules/royalrender/plugins/publish/create_publish_royalrender_job.py b/openpype/modules/royalrender/plugins/publish/create_publish_royalrender_job.py index 9be5d300df..d4af1c2aee 100644 --- a/openpype/modules/royalrender/plugins/publish/create_publish_royalrender_job.py +++ b/openpype/modules/royalrender/plugins/publish/create_publish_royalrender_job.py @@ -206,8 +206,8 @@ class CreatePublishRoyalRenderJob(pyblish.api.InstancePlugin, for job in instance.data["rrJobs"]: # type: RRJob if job.rrEnvList: if len(job.rrEnvList) > 2000: - self.log.warning(("Job environment is too long " - f"{len(job.rrEnvList)} > 2000")) + self.log.warning(("Job environment is too long " + f"{len(job.rrEnvList)} > 2000")) job_environ.update( dict(RREnvList.parse(job.rrEnvList)) ) From 5175cdfe8366a4a48ca6e69ab5b388a8cd540490 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Mon, 27 Nov 2023 13:49:15 +0100 Subject: [PATCH 211/327] :sparkles: inject env to nuke jobs --- .../royalrender/plugins/publish/create_nuke_royalrender_job.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/modules/royalrender/plugins/publish/create_nuke_royalrender_job.py b/openpype/modules/royalrender/plugins/publish/create_nuke_royalrender_job.py index 71daa6edf8..4f589e56f8 100644 --- a/openpype/modules/royalrender/plugins/publish/create_nuke_royalrender_job.py +++ b/openpype/modules/royalrender/plugins/publish/create_nuke_royalrender_job.py @@ -25,6 +25,7 @@ class CreateNukeRoyalRenderJob(lib.BaseCreateRoyalRenderJob): jobs = self.create_jobs(instance) for job in jobs: job = self.update_job_with_host_specific(instance, job) + job = self.inject_environment(instance, job) instance.data["rrJobs"].append(job) From c7529c2b56967bc2c6dc92470224b4bee4952c52 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 27 Nov 2023 21:58:37 +0800 Subject: [PATCH 212/327] get the logic from the colorbleed's branch. thanks for @BigRoy contribution --- ...lidate_look_default_shaders_connections.py | 83 +++++++++++-------- 1 file changed, 47 insertions(+), 36 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_look_default_shaders_connections.py b/openpype/hosts/maya/plugins/publish/validate_look_default_shaders_connections.py index 69a2081689..c3edf5f1c3 100644 --- a/openpype/hosts/maya/plugins/publish/validate_look_default_shaders_connections.py +++ b/openpype/hosts/maya/plugins/publish/validate_look_default_shaders_connections.py @@ -3,65 +3,76 @@ from maya import cmds import pyblish.api from openpype.pipeline.publish import ( ValidateContentsOrder, + RepairContextAction, PublishValidationError ) -class ValidateLookDefaultShadersConnections(pyblish.api.InstancePlugin): +class ValidateLookDefaultShadersConnections(pyblish.api.ContextPlugin): """Validate default shaders in the scene have their default connections. - For example the lambert1 could potentially be disconnected from the - initialShadingGroup. As such it's not lambert1 that will be identified - as the default shader which can have unpredictable results. + For example the standardSurface1 or lambert1 (maya 2023 and before) could + potentially be disconnected from the initialShadingGroup. As such it's not + lambert1 that will be identified as the default shader which can have + unpredictable results. To fix the default connections need to be made again. See the logs for more details on which connections are missing. """ - order = ValidateContentsOrder + order = pyblish.api.ValidatorOrder - 0.4999 families = ['look'] hosts = ['maya'] label = 'Look Default Shader Connections' + actions = [RepairContextAction] # The default connections to check - DEFAULTS = [("initialShadingGroup.surfaceShader", "lambert1"), - ("initialParticleSE.surfaceShader", "lambert1"), - ("initialParticleSE.volumeShader", "particleCloud1") - ] + DEFAULTS = { + "initialShadingGroup.surfaceShader": ["standardSurface1.outColor", + "lambert1.outColor"], + "initialParticleSE.surfaceShader": ["standardSurface1.outColor", + "lambert1.outColor"], + "initialParticleSE.volumeShader": ["particleCloud1.outColor"] + } - def process(self, instance): + def process(self, context): - # Ensure check is run only once. We don't use ContextPlugin because - # of a bug where the ContextPlugin will always be visible. Even when - # the family is not present in an instance. - key = "__validate_look_default_shaders_connections_checked" - context = instance.context - is_run = context.data.get(key, False) - if is_run: - return - else: - context.data[key] = True + if self.get_invalid(): + raise PublishValidationError( + "Default shaders in your scene do not have their " + "default shader connections. Please repair them to continue." + ) + + @classmethod + def get_invalid(cls): # Process as usual invalid = list() - if int(cmds.about(version=True)) >= 2024: - self.log.debug("IntialShadingGroup no longer " - "connected to the default shader in Maya 2024" - "Skipping Look Default Shader Connections..") - return - for plug, input_node in self.DEFAULTS: + for plug, valid_inputs in cls.DEFAULTS.items(): inputs = cmds.listConnections(plug, source=True, - destination=False) or None - - if not inputs or inputs[0] != input_node: - self.log.error("{0} is not connected to {1}. " - "This can result in unexpected behavior. " - "Please reconnect to continue.".format( - plug, - input_node)) + destination=False, + plugs=True) or None + if not inputs or inputs[0] not in valid_inputs: + cls.log.error( + "{0} is not connected to {1}. This can result in " + "unexpected behavior. Please reconnect to continue." + "".format(plug, " or ".join(valid_inputs)) + ) invalid.append(plug) - if invalid: - raise PublishValidationError("Invalid connections.") + return invalid + + @classmethod + def repair(cls, context): + invalid = cls.get_invalid() + for plug in invalid: + valid_inputs = cls.DEFAULTS[plug] + for valid_input in valid_inputs: + if cmds.objExists(valid_input): + cls.log.info( + "Connecting {} -> {}".format(valid_input, plug) + ) + cmds.connectAttr(valid_input, plug, force=True) + break From 6b02f779b3cccd41410fc09eea6ace1b39edf64f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 27 Nov 2023 16:00:16 +0100 Subject: [PATCH 213/327] improving readability of code https://github.com/ynput/OpenPype/pull/5944#discussion_r1404643069 --- openpype/lib/transcoding.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index ace95002d9..bd3700e522 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1259,10 +1259,10 @@ def get_rescaled_command_arguments( # ffmpeg command input_file_metadata = get_ffprobe_data(input_path, logger=log) - input_width = int(input_file_metadata["streams"][0]["width"]) - input_height = int(input_file_metadata["streams"][0]["height"]) - stream_input_par = input_file_metadata["streams"][0].get( - "sample_aspect_ratio") + stream = input_file_metadata["streams"][0] + input_width = int(stream["width"]) + input_height = int(stream["height"]) + stream_input_par = stream[0].get("sample_aspect_ratio") if stream_input_par: input_par = ( float(stream_input_par.split(":")[0]) From 8cf109340b72a03bee36cf5d48dc2b0062997cab Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 27 Nov 2023 16:03:09 +0100 Subject: [PATCH 214/327] rising amount of colors so it matches input of 8bits https://github.com/ynput/OpenPype/pull/5944#discussion_r1404641936 --- openpype/lib/transcoding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index bd3700e522..acf110635f 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1395,7 +1395,7 @@ def convert_color_values(application, color_value): blue = float(blue / 255) alpha = float(alpha / 255) - return "{0:.2f},{1:.2f},{2:.2f},{3:.2f}".format( + return "{0:.3f},{1:.3f},{2:.3f},{3:.3f}".format( red, green, blue, alpha) else: raise ValueError( From ddfb95467e301d0d609c84244750348408027b0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Mon, 27 Nov 2023 16:39:34 +0100 Subject: [PATCH 215/327] Update openpype/plugins/publish/extract_thumbnail.py Co-authored-by: Roy Nieterau --- openpype/plugins/publish/extract_thumbnail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 2948f80ce5..202b8647c8 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -32,7 +32,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): ] enabled = False - # presentable attribute + # attribute presets from settings ffmpeg_args = None def process(self, instance): From 7146b7b7848c666a69efef2b0c4e164ae9cfde78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Mon, 27 Nov 2023 16:42:31 +0100 Subject: [PATCH 216/327] Update openpype/plugins/publish/extract_thumbnail.py --- openpype/plugins/publish/extract_thumbnail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 3b829f19bd..c6112b3cdf 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -25,7 +25,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): hosts = ["shell", "fusion", "resolve", "traypublisher", "substancepainter"] enabled = False - # presentable attribute + # attribute presets from settings oiiotool_defaults = None ffmpeg_args = None From 02342c864ddd62654699ae4351aeb9e76637bdef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Mon, 27 Nov 2023 17:19:09 +0100 Subject: [PATCH 217/327] :recycle: filter render set types --- .../maya/plugins/publish/collect_look.py | 53 +++++++++---------- openpype/pipeline/actions.py | 1 - 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_look.py b/openpype/hosts/maya/plugins/publish/collect_look.py index 11e87a8295..72682f7800 100644 --- a/openpype/hosts/maya/plugins/publish/collect_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_look.py @@ -45,11 +45,23 @@ FILE_NODES = { "PxrTexture": "filename" } +RENDER_SET_TYPES = [ + "VRayDisplacement", + "VRayLightMesh", + "VRayObjectProperties", + "RedshiftObjectId", + "RedshiftMeshParameters", +] + # Keep only node types that actually exist all_node_types = set(cmds.allNodeTypes()) for node_type in list(FILE_NODES.keys()): if node_type not in all_node_types: FILE_NODES.pop(node_type) + +for node_type in RENDER_SET_TYPES: + if node_type not in all_node_types: + RENDER_SET_TYPES.remove(node_type) del all_node_types # Cache pixar dependency node types so we can perform a type lookup against it @@ -369,43 +381,30 @@ class CollectLook(pyblish.api.InstancePlugin): # history = cmds.listHistory(look_sets, allConnections=True) # if materials list is empty, listHistory() will crash with # RuntimeError - history = [] + history = set() if materials: - history = cmds.listHistory(materials, allConnections=True) + history = set( + cmds.listHistory(materials, allConnections=True)) # Since we retrieved history only of the connected materials # connected to the look sets above we now add direct history # for some of the look sets directly # handling render attribute sets - # this needs to be done like this now, because Maya - # (at least 2024) crashes with Warning when render set type + # Maya (at least 2024) crashes with Warning when render set type # isn't available. cmds.ls() will return empty list - render_set_types = [] - if cmds.pluginInfo("vrayformaya", query=True, loaded=True): - render_set_types += [ - "VRayDisplacement", - "VRayLightMesh", - "VRayObjectProperties", - ] - - if cmds.pluginInfo("redshift4maya", query=True, loaded=True): - render_set_types += [ - "RedshiftObjectId", - "RedshiftMeshParameters", - ] - - render_sets = cmds.ls(look_sets, type=render_set_types) - if render_sets: - history.extend( - cmds.listHistory(render_sets, - future=False, - pruneDagObjects=True) - or [] - ) + if RENDER_SET_TYPES: + render_sets = cmds.ls(look_sets, type=RENDER_SET_TYPES) + if render_sets: + history.update( + cmds.listHistory(render_sets, + future=False, + pruneDagObjects=True) + or [] + ) # Ensure unique entries only - history = list(set(history)) + history = list(history) files = cmds.ls(history, # It's important only node types are passed that diff --git a/openpype/pipeline/actions.py b/openpype/pipeline/actions.py index d89e2096ef..68533f7485 100644 --- a/openpype/pipeline/actions.py +++ b/openpype/pipeline/actions.py @@ -107,7 +107,6 @@ class InventoryAction(object): return get_representation_path_from_context(context) - # Launcher action def discover_launcher_actions(): return discover(LauncherAction) From efff32392375e8712ecc7f561e5a59f8893680ef Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 28 Nov 2023 11:02:47 +0100 Subject: [PATCH 218/327] Harmony: Fix local rendering (#5953) * Fix local rendering It was throwing licence issue, but didn't produce anything. * Commenting out proto line This seems to be more stable in showing Openpype menu. For unknown reason menu creation started to fail to show up, this line caused crashes when this script was triggered manually inside of Harmony. --- openpype/hosts/harmony/api/TB_sceneOpened.js | 2 +- openpype/hosts/harmony/plugins/publish/extract_render.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/harmony/api/TB_sceneOpened.js b/openpype/hosts/harmony/api/TB_sceneOpened.js index a284a6ec5c..48daf094dd 100644 --- a/openpype/hosts/harmony/api/TB_sceneOpened.js +++ b/openpype/hosts/harmony/api/TB_sceneOpened.js @@ -13,7 +13,7 @@ var LD_OPENHARMONY_PATH = System.getenv('LIB_OPENHARMONY_PATH'); LD_OPENHARMONY_PATH = LD_OPENHARMONY_PATH + '/openHarmony.js'; LD_OPENHARMONY_PATH = LD_OPENHARMONY_PATH.replace(/\\/g, "/"); include(LD_OPENHARMONY_PATH); -this.__proto__['$'] = $; +//this.__proto__['$'] = $; function Client() { var self = this; diff --git a/openpype/hosts/harmony/plugins/publish/extract_render.py b/openpype/hosts/harmony/plugins/publish/extract_render.py index 5825d95a4a..96a375716b 100644 --- a/openpype/hosts/harmony/plugins/publish/extract_render.py +++ b/openpype/hosts/harmony/plugins/publish/extract_render.py @@ -59,8 +59,8 @@ class ExtractRender(pyblish.api.InstancePlugin): args = [application_path, "-batch", "-frames", str(frame_start), str(frame_end), - "-scene", scene_path] - self.log.info(f"running [ {application_path} {' '.join(args)}") + scene_path] + self.log.info(f"running: {' '.join(args)}") proc = subprocess.Popen( args, stdout=subprocess.PIPE, From 74566d8e07a6bfb89042f687b13151e7c47cc810 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 28 Nov 2023 21:32:37 +0800 Subject: [PATCH 219/327] add label to MayaUSDReferenceLoader --- openpype/hosts/maya/plugins/load/load_reference.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/maya/plugins/load/load_reference.py b/openpype/hosts/maya/plugins/load/load_reference.py index 0d7f08d3c3..a4ab6c79c1 100644 --- a/openpype/hosts/maya/plugins/load/load_reference.py +++ b/openpype/hosts/maya/plugins/load/load_reference.py @@ -265,6 +265,7 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): class MayaUSDReferenceLoader(ReferenceLoader): """Reference USD file to native Maya nodes using MayaUSDImport reference""" + label = "Reference Maya USD" families = ["usd"] representations = ["usd"] extensions = {"usd", "usda", "usdc"} From 12417e1ffa6edf8ca4b49f7b746eec165f58fc72 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 28 Nov 2023 22:55:44 +0800 Subject: [PATCH 220/327] make sure resolution with burnin are correct --- openpype/hosts/max/api/preview_animation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/max/api/preview_animation.py b/openpype/hosts/max/api/preview_animation.py index 6c7b8eaa80..74579b165f 100644 --- a/openpype/hosts/max/api/preview_animation.py +++ b/openpype/hosts/max/api/preview_animation.py @@ -198,8 +198,8 @@ def _render_preview_animation_max_pre_2024( res_width, res_height, filename=filepath ) dib = rt.gw.getViewportDib() - dib_width = rt.renderWidth - dib_height = rt.renderHeight + dib_width = float(dib.width) + dib_height = float(dib.height) # aspect ratio viewportRatio = dib_width / dib_height renderRatio = float(res_width / res_height) From 497e3ec9624e3d8090c9fa16caa49b1ab5a8c7a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Tue, 28 Nov 2023 21:32:42 +0100 Subject: [PATCH 221/327] Update openpype/hosts/resolve/api/plugin.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/resolve/api/plugin.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/resolve/api/plugin.py b/openpype/hosts/resolve/api/plugin.py index 9f1d8fc1c7..2f1f4d3545 100644 --- a/openpype/hosts/resolve/api/plugin.py +++ b/openpype/hosts/resolve/api/plugin.py @@ -439,10 +439,10 @@ class ClipLoader: source_with_handles = True # make sure all frame data is available if ( - frame_start is None or - frame_end is None or - handle_start == 0 or - handle_end == 0 + frame_start is None + or frame_end is None + or handle_start == 0 + or handle_end == 0 ): # if not then rather assume that source has no handles source_with_handles = False From 7c25e4a664fc062b581ee183329d2811f784ec7c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 28 Nov 2023 21:49:05 +0100 Subject: [PATCH 222/327] fixing the logic from @bigroy https://github.com/ynput/OpenPype/pull/5863/files#r1381847587 --- openpype/hosts/resolve/api/plugin.py | 83 ++++++++++------------------ 1 file changed, 28 insertions(+), 55 deletions(-) diff --git a/openpype/hosts/resolve/api/plugin.py b/openpype/hosts/resolve/api/plugin.py index 2f1f4d3545..a00933405f 100644 --- a/openpype/hosts/resolve/api/plugin.py +++ b/openpype/hosts/resolve/api/plugin.py @@ -410,56 +410,38 @@ class ClipLoader: source_out = int(_clip_property("End")) source_duration = int(_clip_property("Frames")) - # get version data frame data from db - version_data = self.data["versionData"] - frame_start = version_data.get("frameStart") - frame_end = version_data.get("frameEnd") - - if self.with_handles: - handle_start = version_data.get("handleStart") or 0 - handle_end = version_data.get("handleEnd") or 0 - else: + if not self.with_handles: + # Load file without the handles of the source media + # We remove the handles from the source in and source out + # so that the handles are excluded in the timeline handle_start = 0 handle_end = 0 - """ - There are cases where representation could be published without - handles if the "Extract review output tags" is set to "no_handles". - This would result in a shorter source duration compared to the - db frame-range. In such cases, we need to assume that the source - has no handles. + # get version data frame data from db + version_data = self.data["versionData"] + frame_start = version_data.get("frameStart") + frame_end = version_data.get("frameEnd") - To address this, we should compare the duration of the source - frame with the db frame-range. The duration of the db frame-range - should be calculated from the version data. If, for any reason, - the frame data is missing in the version data, we should again - assume that the source has no handles. - """ - # check if source duration is shorter than db frame duration - source_with_handles = True - # make sure all frame data is available - if ( - frame_start is None - or frame_end is None - or handle_start == 0 - or handle_end == 0 - ): - # if not then rather assume that source has no handles - source_with_handles = False - else: - # calculate db frame duration - db_frame_duration = ( - # include handles - int(handle_start) + int(handle_end) + - # include frame duration - (int(frame_end) - int(frame_start) + 1) - ) - - # compare source duration with db frame duration - # and assume that source has no handles if source duration - # is shorter than db frame duration - if source_duration < db_frame_duration: - source_with_handles = False + # The version data usually stored the frame range + handles of the + # media however certain representations may be shorter because they + # exclude those handles intentionally. Unfortunately the + # representation does not store that in the database currently; + # so we should compensate for those cases. If the media is shorter + # than the frame range specified in the database we assume it is + # without handles and thus we do not need to remove the handles + # from source and out + if frame_start is not None and frame_end is not None: + # Version has frame range data, so we can compare media length + handle_start = version_data.get("handleStart", 0) + handle_end = version_data.get("handleEnd", 0) + frame_start_handle = frame_start - handle_start + frame_end_handle = frame_start + handle_end + database_frame_duration = int( + frame_end_handle - frame_start_handle + 1 + ) + if source_duration >= database_frame_duration: + source_in += handle_start + source_out -= handle_end # get timeline in timeline_start = self.active_timeline.GetStartFrame() @@ -471,15 +453,6 @@ class ClipLoader: timeline_in = int( timeline_start + self.data["assetData"]["clipIn"]) - # only exclude handles if source has no handles or - # if user wants to load without handles - if ( - not self.with_handles # set by user - or not source_with_handles # result of source duration check - ): - source_in += handle_start - source_out -= handle_end - # make track item from source in bin as item timeline_item = lib.create_timeline_item( media_pool_item, From 6224d01059e549ec0b932546688a4508434a124a Mon Sep 17 00:00:00 2001 From: Ynbot Date: Wed, 29 Nov 2023 03:25:40 +0000 Subject: [PATCH 223/327] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index a54f88871a..c1c33c0f65 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.17.7-nightly.3" +__version__ = "3.17.7-nightly.4" From 90e75b4d7150ef176bc47b8f0e961731de1a820e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 29 Nov 2023 03:26:15 +0000 Subject: [PATCH 224/327] chore(): update bug report / version --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index f59eb1edb1..60ad923546 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,7 @@ body: label: Version description: What version are you running? Look to OpenPype Tray options: + - 3.17.7-nightly.4 - 3.17.7-nightly.3 - 3.17.7-nightly.2 - 3.17.7-nightly.1 @@ -134,7 +135,6 @@ body: - 3.15.2 - 3.15.2-nightly.6 - 3.15.2-nightly.5 - - 3.15.2-nightly.4 validations: required: true - type: dropdown From d4afd11241583c6e1d7c568c399a0b3ef3c9bd1f Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 29 Nov 2023 09:48:41 +0100 Subject: [PATCH 225/327] Added placeholder and tooltip for delay attribute --- .../deadline/plugins/publish/submit_blender_deadline.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_blender_deadline.py b/openpype/modules/deadline/plugins/publish/submit_blender_deadline.py index 52a307646e..b5c3944891 100644 --- a/openpype/modules/deadline/plugins/publish/submit_blender_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_blender_deadline.py @@ -217,7 +217,10 @@ class BlenderSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, TextDef("job_delay", default=cls.job_delay, - label="Job Delay"), + label="Job Delay", + placeholder="dd:hh:mm:ss", + tooltip="Delay the job by the specified amount of time. " + "Timecode: dd:hh:mm:ss."), ]) return defs From 1abbee13a00af40e288d4bb0d60e9a6420cd636d Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 29 Nov 2023 10:40:02 +0100 Subject: [PATCH 226/327] Fix missing ScheduledType --- .../modules/deadline/plugins/publish/submit_blender_deadline.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/modules/deadline/plugins/publish/submit_blender_deadline.py b/openpype/modules/deadline/plugins/publish/submit_blender_deadline.py index b5c3944891..8f9e9a7425 100644 --- a/openpype/modules/deadline/plugins/publish/submit_blender_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_blender_deadline.py @@ -92,6 +92,7 @@ class BlenderSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, job_info.ChunkSize = attr_values.get("chunkSize", self.chunk_size) job_info.Priority = attr_values.get("priority", self.priority) + job_info.ScheduledType = "Once" job_info.JobDelay = attr_values.get("job_delay", self.job_delay) # Add options from RenderGlobals From f899191fb1816f83db996c972fcd3ccaf6964b2d Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 29 Nov 2023 14:38:39 +0100 Subject: [PATCH 227/327] Use manual thumbnail if present when publishing --- openpype/plugins/publish/integrate_thumbnail_ayon.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/integrate_thumbnail_ayon.py b/openpype/plugins/publish/integrate_thumbnail_ayon.py index f9b48eebec..79fe341045 100644 --- a/openpype/plugins/publish/integrate_thumbnail_ayon.py +++ b/openpype/plugins/publish/integrate_thumbnail_ayon.py @@ -92,8 +92,10 @@ class IntegrateThumbnailsAYON(pyblish.api.ContextPlugin): continue # Find thumbnail path on instance - thumbnail_path = self._get_instance_thumbnail_path( - published_repres) + thumbnail_source = instance.data.get("thumbnailSource") + thumbnail_path = (thumbnail_source or + self._get_instance_thumbnail_path( + published_repres)) if thumbnail_path: self.log.debug(( "Found thumbnail path for instance \"{}\"." From 955a6e22538657395b076e3d660b3ae9312b5b99 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 29 Nov 2023 16:48:22 +0100 Subject: [PATCH 228/327] make sure single entity id changes thumbnail id only once in operations --- .../publish/integrate_thumbnail_ayon.py | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/openpype/plugins/publish/integrate_thumbnail_ayon.py b/openpype/plugins/publish/integrate_thumbnail_ayon.py index f9b48eebec..f5a2b3feaa 100644 --- a/openpype/plugins/publish/integrate_thumbnail_ayon.py +++ b/openpype/plugins/publish/integrate_thumbnail_ayon.py @@ -157,8 +157,8 @@ class IntegrateThumbnailsAYON(pyblish.api.ContextPlugin): ): from openpype.client.server.operations import create_thumbnail - op_session = OperationsSession() - + # Make sure each entity id has defined only one thumbnail id + thumbnail_info_by_entity_id = {} for instance_item in filtered_instance_items: instance, thumbnail_path, version_id = instance_item instance_label = self._get_instance_label(instance) @@ -172,12 +172,10 @@ class IntegrateThumbnailsAYON(pyblish.api.ContextPlugin): thumbnail_id = create_thumbnail(project_name, thumbnail_path) # Set thumbnail id for version - op_session.update_entity( - project_name, - version_doc["type"], - version_doc["_id"], - {"data.thumbnail_id": thumbnail_id} - ) + thumbnail_info_by_entity_id[version_id] = { + "thumbnail_id": thumbnail_id, + "entity_type": version_doc["type"], + } if version_doc["type"] == "hero_version": version_name = "Hero" else: @@ -187,16 +185,23 @@ class IntegrateThumbnailsAYON(pyblish.api.ContextPlugin): )) asset_entity = instance.data["assetEntity"] - op_session.update_entity( - project_name, - asset_entity["type"], - asset_entity["_id"], - {"data.thumbnail_id": thumbnail_id} - ) + thumbnail_info_by_entity_id[asset_entity["_id"]] = { + "thumbnail_id": thumbnail_id, + "entity_type": "asset", + } self.log.debug("Setting thumbnail for asset \"{}\" <{}>".format( asset_entity["name"], version_id )) + op_session = OperationsSession() + for entity_id, thumbnail_info in thumbnail_info_by_entity_id.items(): + thumbnail_id = thumbnail_info["thumbnail_id"] + op_session.update_entity( + project_name, + thumbnail_info["entity_type"], + entity_id, + {"data.thumbnail_id": thumbnail_id} + ) op_session.commit() def _get_instance_label(self, instance): From 4c932f3bdce4db4c32354cf7d4cf2310b1b392c8 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 30 Nov 2023 10:27:06 +0100 Subject: [PATCH 229/327] Skip extract thumbnail in Blender if thumbnail has been added manually --- openpype/hosts/blender/plugins/publish/extract_thumbnail.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/hosts/blender/plugins/publish/extract_thumbnail.py b/openpype/hosts/blender/plugins/publish/extract_thumbnail.py index e8a9c68dd1..e593e0de27 100644 --- a/openpype/hosts/blender/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/blender/plugins/publish/extract_thumbnail.py @@ -26,6 +26,10 @@ class ExtractThumbnail(publish.Extractor): def process(self, instance): self.log.debug("Extracting capture..") + if instance.data.get("thumbnailSource"): + self.log.debug("Thumbnail source found, skipping...") + return + stagingdir = self.staging_dir(instance) asset_name = instance.data["assetEntity"]["name"] subset = instance.data["subset"] From e6d3e888c68dfe13ef11a1ef48a3267716260749 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 30 Nov 2023 18:06:33 +0800 Subject: [PATCH 230/327] make sure the menu is named AYON when AYON launches --- openpype/hosts/max/api/menu.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/max/api/menu.py b/openpype/hosts/max/api/menu.py index 364f9cd5c5..7869433148 100644 --- a/openpype/hosts/max/api/menu.py +++ b/openpype/hosts/max/api/menu.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- -"""3dsmax menu definition of OpenPype.""" +"""3dsmax menu definition of OpenPype/AYON.""" +import os from qtpy import QtWidgets, QtCore from pymxs import runtime as rt @@ -8,7 +9,7 @@ from openpype.hosts.max.api import lib class OpenPypeMenu(object): - """Object representing OpenPype menu. + """Object representing OpenPype/AYON menu. This is using "hack" to inject itself before "Help" menu of 3dsmax. For some reason `postLoadingMenus` event doesn't fire, and main menu @@ -50,17 +51,17 @@ class OpenPypeMenu(object): return list(self.main_widget.findChildren(QtWidgets.QMenuBar))[0] def get_or_create_openpype_menu( - self, name: str = "&OpenPype", + self, name: str = "&Openpype", before: str = "&Help") -> QtWidgets.QAction: - """Create OpenPype menu. + """Create AYON menu. Args: - name (str, Optional): OpenPypep menu name. + name (str, Optional): Openpype/AYON menu name. before (str, Optional): Name of the 3dsmax main menu item to - add OpenPype menu before. + add Openpype/AYON menu before. Returns: - QtWidgets.QAction: OpenPype menu action. + QtWidgets.QAction: Openpype/AYON menu action. """ if self.menu is not None: @@ -77,15 +78,15 @@ class OpenPypeMenu(object): if before in item.title(): help_action = item.menuAction() - - op_menu = QtWidgets.QMenu("&OpenPype") + tab_menu_label = os.environ.get("AVALON_LABEL") or "AYON" + op_menu = QtWidgets.QMenu("&{}".format(tab_menu_label)) menu_bar.insertMenu(help_action, op_menu) self.menu = op_menu return op_menu def build_openpype_menu(self) -> QtWidgets.QAction: - """Build items in OpenPype menu.""" + """Build items in AYON menu.""" openpype_menu = self.get_or_create_openpype_menu() load_action = QtWidgets.QAction("Load...", openpype_menu) load_action.triggered.connect(self.load_callback) From fd82f858bdd16a76d3922f231eb47076c1e05602 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 30 Nov 2023 18:14:16 +0800 Subject: [PATCH 231/327] cosmetic fix in docstring --- openpype/hosts/max/api/menu.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/max/api/menu.py b/openpype/hosts/max/api/menu.py index 7869433148..869f74c97e 100644 --- a/openpype/hosts/max/api/menu.py +++ b/openpype/hosts/max/api/menu.py @@ -56,12 +56,12 @@ class OpenPypeMenu(object): """Create AYON menu. Args: - name (str, Optional): Openpype/AYON menu name. + name (str, Optional): AYON menu name. before (str, Optional): Name of the 3dsmax main menu item to - add Openpype/AYON menu before. + add AYON menu before. Returns: - QtWidgets.QAction: Openpype/AYON menu action. + QtWidgets.QAction: AYON menu action. """ if self.menu is not None: From 212f6d004f5aee245b95139264bfa4e1bf3d0596 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 30 Nov 2023 18:18:11 +0800 Subject: [PATCH 232/327] cosmetic fix in the docstring --- openpype/hosts/max/api/menu.py | 2 +- openpype/hosts/max/api/pipeline.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/max/api/menu.py b/openpype/hosts/max/api/menu.py index 869f74c97e..caaa3e3730 100644 --- a/openpype/hosts/max/api/menu.py +++ b/openpype/hosts/max/api/menu.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -"""3dsmax menu definition of OpenPype/AYON.""" +"""3dsmax menu definition of AYON.""" import os from qtpy import QtWidgets, QtCore from pymxs import runtime as rt diff --git a/openpype/hosts/max/api/pipeline.py b/openpype/hosts/max/api/pipeline.py index e46c4cabe7..d0ae854dc8 100644 --- a/openpype/hosts/max/api/pipeline.py +++ b/openpype/hosts/max/api/pipeline.py @@ -175,7 +175,7 @@ def containerise(name: str, nodes: list, context, def load_custom_attribute_data(): - """Re-loading the Openpype/AYON custom parameter built by the creator + """Re-loading the AYON custom parameter built by the creator Returns: attribute: re-loading the custom OP attributes set in Maxscript @@ -213,7 +213,7 @@ def import_custom_attribute_data(container: str, selections: list): def update_custom_attribute_data(container: str, selections: list): - """Updating the Openpype/AYON custom parameter built by the creator + """Updating the AYON custom parameter built by the creator Args: container (str): target container which adds custom attributes From 767e8d46ca1c94969edc91622d26ef13e37a5220 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 30 Nov 2023 12:02:12 +0100 Subject: [PATCH 233/327] workfile template builder should work in ayon mode now --- .../workfile/workfile_template_builder.py | 251 ++++++++++++++---- 1 file changed, 206 insertions(+), 45 deletions(-) diff --git a/openpype/pipeline/workfile/workfile_template_builder.py b/openpype/pipeline/workfile/workfile_template_builder.py index 99bdb12543..0c4caa04f6 100644 --- a/openpype/pipeline/workfile/workfile_template_builder.py +++ b/openpype/pipeline/workfile/workfile_template_builder.py @@ -19,6 +19,7 @@ from abc import ABCMeta, abstractmethod import six +from openpype import AYON_SERVER_ENABLED from openpype.client import ( get_asset_by_name, get_linked_assets, @@ -1272,31 +1273,54 @@ class PlaceholderLoadMixin(object): # Sort for readability families = list(sorted(families)) - return [ + if AYON_SERVER_ENABLED: + builder_type_enum_items = [ + {"label": "Current folder", "value": "context_folder"}, + # TODO implement linked folders + # {"label": "Linked folders", "value": "linked_folders"}, + {"label": "All folders", "value": "all_folders"}, + ] + build_type_label = "Folder Builder Type" + build_type_help = ( + "Folder Builder Type\n" + "\nBuilder type describe what template loader will look" + " for." + "\nCurrent Folder: Template loader will look for products" + " of current context folder (Folder /assets/bob will" + " find asset)" + "\nAll folders: All folders matching the regex will be" + " used." + ) + else: + builder_type_enum_items = [ + {"label": "Current asset", "value": "context_asset"}, + {"label": "Linked assets", "value": "linked_asset"}, + {"label": "All assets", "value": "all_assets"}, + ] + build_type_label = "Asset Builder Type" + build_type_help = ( + "Asset Builder Type\n" + "\nBuilder type describe what template loader will look" + " for." + "\ncontext_asset : Template loader will look for subsets" + " of current context asset (Asset bob will find asset)" + "\nlinked_asset : Template loader will look for assets" + " linked to current context asset." + "\nLinked asset are looked in database under" + " field \"inputLinks\"" + ) + + attr_defs = [ attribute_definitions.UISeparatorDef(), attribute_definitions.UILabelDef("Main attributes"), attribute_definitions.UISeparatorDef(), attribute_definitions.EnumDef( "builder_type", - label="Asset Builder Type", + label=build_type_label, default=options.get("builder_type"), - items=[ - {"label": "Current asset", "value": "context_asset"}, - {"label": "Linked assets", "value": "linked_asset"}, - {"label": "All assets", "value": "all_assets"}, - ], - tooltip=( - "Asset Builder Type\n" - "\nBuilder type describe what template loader will look" - " for." - "\ncontext_asset : Template loader will look for subsets" - " of current context asset (Asset bob will find asset)" - "\nlinked_asset : Template loader will look for assets" - " linked to current context asset." - "\nLinked asset are looked in database under" - " field \"inputLinks\"" - ) + items=builder_type_enum_items, + tooltip=build_type_help ), attribute_definitions.EnumDef( "family", @@ -1352,34 +1376,61 @@ class PlaceholderLoadMixin(object): attribute_definitions.UISeparatorDef(), attribute_definitions.UILabelDef("Optional attributes"), attribute_definitions.UISeparatorDef(), - attribute_definitions.TextDef( - "asset", - label="Asset filter", - default=options.get("asset"), - placeholder="regex filtering by asset name", - tooltip=( - "Filtering assets by matching field regex to asset's name" - ) - ), - attribute_definitions.TextDef( - "subset", - label="Subset filter", - default=options.get("subset"), - placeholder="regex filtering by subset name", - tooltip=( - "Filtering assets by matching field regex to subset's name" - ) - ), - attribute_definitions.TextDef( - "hierarchy", - label="Hierarchy filter", - default=options.get("hierarchy"), - placeholder="regex filtering by asset's hierarchy", - tooltip=( - "Filtering assets by matching field asset's hierarchy" - ) - ) ] + if AYON_SERVER_ENABLED: + attr_defs.extend([ + attribute_definitions.TextDef( + "folder_path", + label="Folder filter", + default=options.get("folder_path"), + placeholder="regex filtering by folder path", + tooltip=( + "Filtering assets by matching" + " field regex to folder path" + ) + ), + attribute_definitions.TextDef( + "product_name", + label="Product filter", + default=options.get("product_name"), + placeholder="regex filtering by product name", + tooltip=( + "Filtering assets by matching" + " field regex to product name" + ) + ), + ]) + else: + attr_defs.extend([ + attribute_definitions.TextDef( + "asset", + label="Asset filter", + default=options.get("asset"), + placeholder="regex filtering by asset name", + tooltip=( + "Filtering assets by matching field regex to asset's name" + ) + ), + attribute_definitions.TextDef( + "subset", + label="Subset filter", + default=options.get("subset"), + placeholder="regex filtering by subset name", + tooltip=( + "Filtering assets by matching field regex to subset's name" + ) + ), + attribute_definitions.TextDef( + "hierarchy", + label="Hierarchy filter", + default=options.get("hierarchy"), + placeholder="regex filtering by asset's hierarchy", + tooltip=( + "Filtering assets by matching field asset's hierarchy" + ) + ) + ]) + return attr_defs def parse_loader_args(self, loader_args): """Helper function to parse string of loader arugments. @@ -1409,6 +1460,109 @@ class PlaceholderLoadMixin(object): return {} + def _query_by_folder_regex(self, project_name, folder_regex): + """Query folders by folder path regex. + + WARNING: + This method will be removed once the same functionality is + available in ayon-python-api. + + Args: + project_name (str): Project name. + folder_regex (str): Regex for folder path. + + Returns: + list[str]: List of folder paths. + """ + + from ayon_api.graphql_queries import folders_graphql_query + from openpype.client import get_ayon_server_api_connection + + query = folders_graphql_query({"id"}) + + folders_field = None + for child in query._children: + if child.path != "project": + continue + + for project_child in child._children: + if project_child.path == "project/folders": + folders_field = project_child + break + if folders_field: + break + + if "folderPathRegex" not in query._variables: + folder_path_regex_var = query.add_variable( + "folderPathRegex", "String!" + ) + folders_field.set_filter("pathEx", folder_path_regex_var) + + query.set_variable_value("projectName", project_name) + query.set_variable_value("folderPathRegex", folder_regex) + + api = get_ayon_server_api_connection() + for parsed_data in query.continuous_query(api): + for folder in parsed_data["project"]["folders"]: + yield folder["id"] + + def _get_representations_ayon(self, placeholder): + # An OpenPype placeholder loaded in AYON + if "asset" in placeholder.data: + return [] + + representation_name = placeholder.data["representation"] + if not representation_name: + return [] + + project_name = self.builder.project_name + current_asset_doc = self.builder.current_asset_doc + + folder_path_regex = placeholder.data["folder_path"] + product_name_regex = re.compile(placeholder.data["product_name"]) + product_type = placeholder.data["family"] + + builder_type = placeholder.data["builder_type"] + folder_ids = [] + if builder_type == "context_folder": + folder_ids = [current_asset_doc["_id"]] + + elif builder_type == "all_folders": + folder_ids = list(self._query_by_folder_regex( + project_name, folder_path_regex + )) + + if not folder_ids: + return [] + + from ayon_api import get_products, get_last_versions + + products = list(get_products( + project_name, + folder_ids=folder_ids, + product_types=[product_type], + fields={"id", "name"} + )) + filtered_product_ids = set() + for product in products: + if product_name_regex.match(product["name"]): + filtered_product_ids.add(product["id"]) + + if not filtered_product_ids: + return [] + + version_ids = set( + get_last_versions( + project_name, filtered_product_ids, fields={"id"} + ).values() + ) + return list(get_representations( + project_name, + representation_names=[representation_name], + version_ids=version_ids + )) + + def _get_representations(self, placeholder): """Prepared query of representations based on load options. @@ -1428,6 +1582,13 @@ class PlaceholderLoadMixin(object): from placeholder data. """ + if AYON_SERVER_ENABLED: + return self._get_representations_ayon(placeholder) + + # An AYON placeholder loaded in OpenPype + if "folder_path" in placeholder.data: + return [] + project_name = self.builder.project_name current_asset_doc = self.builder.current_asset_doc linked_asset_docs = self.builder.linked_asset_docs From d7db8b0090f95620982f5171a05e5eb2a2aee7b7 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 30 Nov 2023 12:03:03 +0100 Subject: [PATCH 234/327] renaming menu to ayon --- openpype/hosts/resolve/api/menu.py | 7 ++++-- .../resolve/utility_scripts/AYON__Menu.py | 22 +++++++++++++++++++ openpype/hosts/resolve/utils.py | 9 ++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 openpype/hosts/resolve/utility_scripts/AYON__Menu.py diff --git a/openpype/hosts/resolve/api/menu.py b/openpype/hosts/resolve/api/menu.py index 9c6fe4957c..2210178a67 100644 --- a/openpype/hosts/resolve/api/menu.py +++ b/openpype/hosts/resolve/api/menu.py @@ -7,6 +7,9 @@ from openpype.tools.utils import host_tools from openpype.pipeline import registered_host +MENU_LABEL = os.environ["AVALON_LABEL"] + + def load_stylesheet(): path = os.path.join(os.path.dirname(__file__), "menu_style.qss") if not os.path.exists(path): @@ -39,7 +42,7 @@ class OpenPypeMenu(QtWidgets.QWidget): def __init__(self, *args, **kwargs): super(OpenPypeMenu, self).__init__(*args, **kwargs) - self.setObjectName("OpenPypeMenu") + self.setObjectName(f"{MENU_LABEL}Menu") self.setWindowFlags( QtCore.Qt.Window @@ -49,7 +52,7 @@ class OpenPypeMenu(QtWidgets.QWidget): | QtCore.Qt.WindowStaysOnTopHint ) - self.setWindowTitle("OpenPype") + self.setWindowTitle(f"{MENU_LABEL}") save_current_btn = QtWidgets.QPushButton("Save current file", self) workfiles_btn = QtWidgets.QPushButton("Workfiles ...", self) create_btn = QtWidgets.QPushButton("Create ...", self) diff --git a/openpype/hosts/resolve/utility_scripts/AYON__Menu.py b/openpype/hosts/resolve/utility_scripts/AYON__Menu.py new file mode 100644 index 0000000000..4f14927074 --- /dev/null +++ b/openpype/hosts/resolve/utility_scripts/AYON__Menu.py @@ -0,0 +1,22 @@ +import os +import sys + +from openpype.pipeline import install_host +from openpype.lib import Logger + +log = Logger.get_logger(__name__) + + +def main(env): + from openpype.hosts.resolve.api import ResolveHost, launch_pype_menu + + # activate resolve from openpype + host = ResolveHost() + install_host(host) + + launch_pype_menu() + + +if __name__ == "__main__": + result = main(os.environ) + sys.exit(not bool(result)) diff --git a/openpype/hosts/resolve/utils.py b/openpype/hosts/resolve/utils.py index 5e3003862f..9b91a14267 100644 --- a/openpype/hosts/resolve/utils.py +++ b/openpype/hosts/resolve/utils.py @@ -2,6 +2,7 @@ import os import shutil from openpype.lib import Logger, is_running_from_build +from openpype import AYON_SERVER_ENABLED RESOLVE_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -54,6 +55,14 @@ def setup(env): src = os.path.join(directory, script) dst = os.path.join(util_scripts_dir, script) + # TODO: remove this once we have a proper solution + if AYON_SERVER_ENABLED: + if "OpenPype__Menu.py" == script: + continue + else: + if "AYON__Menu.py" == script: + continue + # TODO: Make this a less hacky workaround if script == "openpype_startup.scriptlib": # Handle special case for scriptlib that needs to be a folder From 3dcbc2ff18eb72af1214ad1e93863839e86fb788 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 30 Nov 2023 12:09:18 +0100 Subject: [PATCH 235/327] formatting fix --- openpype/pipeline/workfile/workfile_template_builder.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/workfile/workfile_template_builder.py b/openpype/pipeline/workfile/workfile_template_builder.py index 0c4caa04f6..4f53f3993b 100644 --- a/openpype/pipeline/workfile/workfile_template_builder.py +++ b/openpype/pipeline/workfile/workfile_template_builder.py @@ -1408,7 +1408,8 @@ class PlaceholderLoadMixin(object): default=options.get("asset"), placeholder="regex filtering by asset name", tooltip=( - "Filtering assets by matching field regex to asset's name" + "Filtering assets by matching" + " field regex to asset's name" ) ), attribute_definitions.TextDef( @@ -1417,7 +1418,8 @@ class PlaceholderLoadMixin(object): default=options.get("subset"), placeholder="regex filtering by subset name", tooltip=( - "Filtering assets by matching field regex to subset's name" + "Filtering assets by matching" + " field regex to subset's name" ) ), attribute_definitions.TextDef( From 8c87b9963d162f5886ee4ccbae43700539f39e51 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 30 Nov 2023 14:22:32 +0100 Subject: [PATCH 236/327] script menu name change in server addon --- server_addon/hiero/server/settings/scriptsmenu.py | 8 ++++---- server_addon/hiero/server/version.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/server_addon/hiero/server/settings/scriptsmenu.py b/server_addon/hiero/server/settings/scriptsmenu.py index 51cb088298..ea898dd7ff 100644 --- a/server_addon/hiero/server/settings/scriptsmenu.py +++ b/server_addon/hiero/server/settings/scriptsmenu.py @@ -28,14 +28,14 @@ class ScriptsmenuSettings(BaseSettingsModel): DEFAULT_SCRIPTSMENU_SETTINGS = { - "name": "OpenPype Tools", + "name": "Custom Tools", "definition": [ { "type": "action", "sourcetype": "python", - "title": "OpenPype Docs", - "command": "import webbrowser;webbrowser.open(url='https://openpype.io/docs/artist_hosts_hiero')", - "tooltip": "Open the OpenPype Hiero user doc page" + "title": "Ayon Hiero Docs", + "command": "import webbrowser;webbrowser.open(url='https://ayon.ynput.io/docs/addon_hiero_artist')", # noqa + "tooltip": "Open the Ayon Hiero user doc page" } ] } diff --git a/server_addon/hiero/server/version.py b/server_addon/hiero/server/version.py index 3dc1f76bc6..485f44ac21 100644 --- a/server_addon/hiero/server/version.py +++ b/server_addon/hiero/server/version.py @@ -1 +1 @@ -__version__ = "0.1.0" +__version__ = "0.1.1" From 93eb7751e3673a58d69da5f2526cc58b17620261 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 30 Nov 2023 14:24:43 +0100 Subject: [PATCH 237/327] wrong menu order --- openpype/hosts/hiero/api/menu.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/hiero/api/menu.py b/openpype/hosts/hiero/api/menu.py index 9967e9c875..ca611570cc 100644 --- a/openpype/hosts/hiero/api/menu.py +++ b/openpype/hosts/hiero/api/menu.py @@ -95,18 +95,18 @@ def menu_install(): menu.addSeparator() - publish_action = menu.addAction("Publish...") - publish_action.setIcon(QtGui.QIcon("icons:Output.png")) - publish_action.triggered.connect( - lambda *args: publish(hiero.ui.mainWindow()) - ) - creator_action = menu.addAction("Create...") creator_action.setIcon(QtGui.QIcon("icons:CopyRectangle.png")) creator_action.triggered.connect( lambda: host_tools.show_creator(parent=main_window) ) + publish_action = menu.addAction("Publish...") + publish_action.setIcon(QtGui.QIcon("icons:Output.png")) + publish_action.triggered.connect( + lambda *args: publish(hiero.ui.mainWindow()) + ) + loader_action = menu.addAction("Load...") loader_action.setIcon(QtGui.QIcon("icons:CopyRectangle.png")) loader_action.triggered.connect( From 4c9adaf2e838c94aaadb3453fe3b5bf4f0acadac Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 30 Nov 2023 14:36:38 +0100 Subject: [PATCH 238/327] nuke: updating name for custom tools menu item --- server_addon/nuke/server/settings/scriptsmenu.py | 8 ++++---- server_addon/nuke/server/version.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/server_addon/nuke/server/settings/scriptsmenu.py b/server_addon/nuke/server/settings/scriptsmenu.py index 0b2d660da5..f7495dd2db 100644 --- a/server_addon/nuke/server/settings/scriptsmenu.py +++ b/server_addon/nuke/server/settings/scriptsmenu.py @@ -26,14 +26,14 @@ class ScriptsmenuSettings(BaseSettingsModel): DEFAULT_SCRIPTSMENU_SETTINGS = { - "name": "OpenPype Tools", + "name": "Custom Tools", "definition": [ { "type": "action", "sourcetype": "python", - "title": "OpenPype Docs", - "command": "import webbrowser;webbrowser.open(url='https://openpype.io/docs/artist_hosts_nuke_tut')", - "tooltip": "Open the OpenPype Nuke user doc page" + "title": "Ayon Nuke Docs", + "command": "import webbrowser;webbrowser.open(url='https://ayon.ynput.io/docs/addon_nuke_artist')", + "tooltip": "Open the Ayon Nuke user doc page" }, { "type": "action", diff --git a/server_addon/nuke/server/version.py b/server_addon/nuke/server/version.py index 1276d0254f..0a8da88258 100644 --- a/server_addon/nuke/server/version.py +++ b/server_addon/nuke/server/version.py @@ -1 +1 @@ -__version__ = "0.1.5" +__version__ = "0.1.6" From 8449f0468f02780e4b4d6c5b8e651d724d890dc9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 30 Nov 2023 14:39:26 +0100 Subject: [PATCH 239/327] hound --- server_addon/nuke/server/settings/scriptsmenu.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server_addon/nuke/server/settings/scriptsmenu.py b/server_addon/nuke/server/settings/scriptsmenu.py index f7495dd2db..3dd6765920 100644 --- a/server_addon/nuke/server/settings/scriptsmenu.py +++ b/server_addon/nuke/server/settings/scriptsmenu.py @@ -32,21 +32,21 @@ DEFAULT_SCRIPTSMENU_SETTINGS = { "type": "action", "sourcetype": "python", "title": "Ayon Nuke Docs", - "command": "import webbrowser;webbrowser.open(url='https://ayon.ynput.io/docs/addon_nuke_artist')", + "command": "import webbrowser;webbrowser.open(url='https://ayon.ynput.io/docs/addon_nuke_artist')", # noqa "tooltip": "Open the Ayon Nuke user doc page" }, { "type": "action", "sourcetype": "python", "title": "Set Frame Start (Read Node)", - "command": "from openpype.hosts.nuke.startup.frame_setting_for_read_nodes import main;main();", + "command": "from openpype.hosts.nuke.startup.frame_setting_for_read_nodes import main;main();", # noqa "tooltip": "Set frame start for read node(s)" }, { "type": "action", "sourcetype": "python", "title": "Set non publish output for Write Node", - "command": "from openpype.hosts.nuke.startup.custom_write_node import main;main();", + "command": "from openpype.hosts.nuke.startup.custom_write_node import main;main();", # noqa "tooltip": "Open the OpenPype Nuke user doc page" } ] From 7bd067d5a5097def4a234f9d230ab9189f40cfc9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 30 Nov 2023 15:29:47 +0100 Subject: [PATCH 240/327] fusion: adding ayon menu --- openpype/hosts/fusion/api/menu.py | 9 ++- .../hosts/fusion/deploy_ayon/Config/menu.fu | 60 +++++++++++++++++++ .../fusion/deploy_ayon/MenuScripts/README.md | 6 ++ .../MenuScripts/install_pyside2.py | 29 +++++++++ .../fusion/deploy_ayon/MenuScripts/menu.py | 48 +++++++++++++++ .../fusion/deploy_ayon/fusion_shared.prefs | 19 ++++++ .../fusion/hooks/pre_fusion_profile_hook.py | 8 ++- .../hosts/fusion/hooks/pre_fusion_setup.py | 9 ++- 8 files changed, 183 insertions(+), 5 deletions(-) create mode 100644 openpype/hosts/fusion/deploy_ayon/Config/menu.fu create mode 100644 openpype/hosts/fusion/deploy_ayon/MenuScripts/README.md create mode 100644 openpype/hosts/fusion/deploy_ayon/MenuScripts/install_pyside2.py create mode 100644 openpype/hosts/fusion/deploy_ayon/MenuScripts/menu.py create mode 100644 openpype/hosts/fusion/deploy_ayon/fusion_shared.prefs diff --git a/openpype/hosts/fusion/api/menu.py b/openpype/hosts/fusion/api/menu.py index 50250a6656..0b9ad1a43b 100644 --- a/openpype/hosts/fusion/api/menu.py +++ b/openpype/hosts/fusion/api/menu.py @@ -1,3 +1,4 @@ +import os import sys from qtpy import QtWidgets, QtCore, QtGui @@ -18,6 +19,10 @@ from openpype.resources import get_openpype_icon_filepath from .pipeline import FusionEventHandler from .pulse import FusionPulse + +MENU_LABEL = os.environ["AVALON_LABEL"] + + self = sys.modules[__name__] self.menu = None @@ -26,7 +31,7 @@ class OpenPypeMenu(QtWidgets.QWidget): def __init__(self, *args, **kwargs): super(OpenPypeMenu, self).__init__(*args, **kwargs) - self.setObjectName("OpenPypeMenu") + self.setObjectName(f"{MENU_LABEL}Menu") icon_path = get_openpype_icon_filepath() icon = QtGui.QIcon(icon_path) @@ -41,7 +46,7 @@ class OpenPypeMenu(QtWidgets.QWidget): | QtCore.Qt.WindowStaysOnTopHint ) self.render_mode_widget = None - self.setWindowTitle("OpenPype") + self.setWindowTitle(MENU_LABEL) asset_label = QtWidgets.QLabel("Context", self) asset_label.setStyleSheet( diff --git a/openpype/hosts/fusion/deploy_ayon/Config/menu.fu b/openpype/hosts/fusion/deploy_ayon/Config/menu.fu new file mode 100644 index 0000000000..2846497a9e --- /dev/null +++ b/openpype/hosts/fusion/deploy_ayon/Config/menu.fu @@ -0,0 +1,60 @@ +{ + Action + { + ID = "AYON_Menu", + Category = "AYON", + Name = "AYON Menu", + + Targets = + { + Composition = + { + Execute = _Lua [=[ + local scriptPath = app:MapPath("AYON:MenuScripts/menu.py") + if bmd.fileexists(scriptPath) == false then + print("[AYON Error] Can't run file: " .. scriptPath) + else + target:RunScript(scriptPath) + end + ]=], + }, + }, + }, + Action + { + ID = "AYON_Install_PySide2", + Category = "AYON", + Name = "Install PySide2", + + Targets = + { + Composition = + { + Execute = _Lua [=[ + local scriptPath = app:MapPath("AYON:MenuScripts/install_pyside2.py") + if bmd.fileexists(scriptPath) == false then + print("[AYON Error] Can't run file: " .. scriptPath) + else + target:RunScript(scriptPath) + end + ]=], + }, + }, + }, + Menus + { + Target = "ChildFrame", + + Before "Help" + { + Sub "AYON" + { + "AYON_Menu{}", + "_", + Sub "Admin" { + "AYON_Install_PySide2{}" + } + } + }, + }, +} diff --git a/openpype/hosts/fusion/deploy_ayon/MenuScripts/README.md b/openpype/hosts/fusion/deploy_ayon/MenuScripts/README.md new file mode 100644 index 0000000000..9076f240ad --- /dev/null +++ b/openpype/hosts/fusion/deploy_ayon/MenuScripts/README.md @@ -0,0 +1,6 @@ +### Ayon deploy MenuScripts + +Note that this `MenuScripts` is not an official Fusion folder. +Ayon only uses this folder in `{fusion}/deploy/` to trigger the Ayon menu actions. + +They are used in the actions defined in `.fu` files in `{fusion}/deploy_ayon/Config`. diff --git a/openpype/hosts/fusion/deploy_ayon/MenuScripts/install_pyside2.py b/openpype/hosts/fusion/deploy_ayon/MenuScripts/install_pyside2.py new file mode 100644 index 0000000000..e1240fd677 --- /dev/null +++ b/openpype/hosts/fusion/deploy_ayon/MenuScripts/install_pyside2.py @@ -0,0 +1,29 @@ +# This is just a quick hack for users running Py3 locally but having no +# Qt library installed +import os +import subprocess +import importlib + + +try: + from qtpy import API_NAME + + print(f"Qt binding: {API_NAME}") + mod = importlib.import_module(API_NAME) + print(f"Qt path: {mod.__file__}") + print("Qt library found, nothing to do..") + +except ImportError: + print("Assuming no Qt library is installed..") + print('Installing PySide2 for Python 3.6: ' + f'{os.environ["FUSION16_PYTHON36_HOME"]}') + + # Get full path to python executable + exe = "python.exe" if os.name == 'nt' else "python" + python = os.path.join(os.environ["FUSION16_PYTHON36_HOME"], exe) + assert os.path.exists(python), f"Python doesn't exist: {python}" + + # Do python -m pip install PySide2 + args = [python, "-m", "pip", "install", "PySide2"] + print(f"Args: {args}") + subprocess.Popen(args) diff --git a/openpype/hosts/fusion/deploy_ayon/MenuScripts/menu.py b/openpype/hosts/fusion/deploy_ayon/MenuScripts/menu.py new file mode 100644 index 0000000000..1c58ee50e4 --- /dev/null +++ b/openpype/hosts/fusion/deploy_ayon/MenuScripts/menu.py @@ -0,0 +1,48 @@ +import os +import sys + +if sys.version_info < (3, 7): + # hack to handle discrepancy between distributed libraries and Python 3.6 + # mostly because wrong version of urllib3 + # TODO remove when not necessary + from openpype import PACKAGE_DIR + FUSION_HOST_DIR = os.path.join(PACKAGE_DIR, "hosts", "fusion") + + vendor_path = os.path.join(FUSION_HOST_DIR, "vendor") + if vendor_path not in sys.path: + sys.path.insert(0, vendor_path) + + print(f"Added vendorized libraries from {vendor_path}") + +from openpype.lib import Logger +from openpype.pipeline import ( + install_host, + registered_host, +) + + +def main(env): + # This script working directory starts in Fusion application folder. + # However the contents of that folder can conflict with Qt library dlls + # so we make sure to move out of it to avoid DLL Load Failed errors. + os.chdir("..") + from openpype.hosts.fusion.api import FusionHost + from openpype.hosts.fusion.api import menu + + # activate resolve from pype + install_host(FusionHost()) + + log = Logger.get_logger(__name__) + log.info(f"Registered host: {registered_host()}") + + menu.launch_openpype_menu() + + # Initiate a QTimer to check if Fusion is still alive every X interval + # If Fusion is not found - kill itself + # todo(roy): Implement timer that ensures UI doesn't remain when e.g. + # Fusion closes down + + +if __name__ == "__main__": + result = main(os.environ) + sys.exit(not bool(result)) diff --git a/openpype/hosts/fusion/deploy_ayon/fusion_shared.prefs b/openpype/hosts/fusion/deploy_ayon/fusion_shared.prefs new file mode 100644 index 0000000000..b5e8e3d024 --- /dev/null +++ b/openpype/hosts/fusion/deploy_ayon/fusion_shared.prefs @@ -0,0 +1,19 @@ +{ +Locked = true, +Global = { + Paths = { + Map = { + ["AYON:"] = "$(AYON_FUSION)/deploy_ayon", + ["Config:"] = "UserPaths:Config;AYON:Config", + ["Scripts:"] = "UserPaths:Scripts;Reactor:System/Scripts", + }, + }, + Script = { + PythonVersion = 3, + Python3Forced = true + }, + UserInterface = { + Language = "en_US" + }, + }, +} diff --git a/openpype/hosts/fusion/hooks/pre_fusion_profile_hook.py b/openpype/hosts/fusion/hooks/pre_fusion_profile_hook.py index 66b0f803aa..9e61d11e6e 100644 --- a/openpype/hosts/fusion/hooks/pre_fusion_profile_hook.py +++ b/openpype/hosts/fusion/hooks/pre_fusion_profile_hook.py @@ -2,6 +2,7 @@ import os import shutil import platform from pathlib import Path +from openpype import AYON_SERVER_ENABLED from openpype.hosts.fusion import ( FUSION_HOST_DIR, FUSION_VERSIONS_DICT, @@ -161,6 +162,11 @@ class FusionCopyPrefsPrelaunch(PreLaunchHook): # profile directory variables to customize Fusion # to define where it can read custom scripts and tools from master_prefs_variable = f"FUSION{profile_version}_MasterPrefs" - master_prefs = Path(FUSION_HOST_DIR, "deploy", "fusion_shared.prefs") + + if AYON_SERVER_ENABLED: + master_prefs = Path(FUSION_HOST_DIR, "deploy_ayon", "fusion_shared.prefs") + else: + master_prefs = Path(FUSION_HOST_DIR, "deploy", "fusion_shared.prefs") + self.log.info(f"Setting {master_prefs_variable}: {master_prefs}") self.launch_context.env[master_prefs_variable] = str(master_prefs) diff --git a/openpype/hosts/fusion/hooks/pre_fusion_setup.py b/openpype/hosts/fusion/hooks/pre_fusion_setup.py index 576628e876..bd7f35f900 100644 --- a/openpype/hosts/fusion/hooks/pre_fusion_setup.py +++ b/openpype/hosts/fusion/hooks/pre_fusion_setup.py @@ -1,4 +1,5 @@ import os +from openpype import AYON_SERVER_ENABLED from openpype.lib.applications import ( PreLaunchHook, LaunchTypes, @@ -64,5 +65,9 @@ class FusionPrelaunch(PreLaunchHook): self.launch_context.env[py3_var] = py3_dir - self.log.info(f"Setting OPENPYPE_FUSION: {FUSION_HOST_DIR}") - self.launch_context.env["OPENPYPE_FUSION"] = FUSION_HOST_DIR + if AYON_SERVER_ENABLED: + self.log.info(f"Setting AYON_FUSION: {FUSION_HOST_DIR}") + self.launch_context.env["AYON_FUSION"] = FUSION_HOST_DIR + else: + self.log.info(f"Setting OPENPYPE_FUSION: {FUSION_HOST_DIR}") + self.launch_context.env["OPENPYPE_FUSION"] = FUSION_HOST_DIR From 9246dfeec10dd80ab0b608a48517446fb64ce220 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 30 Nov 2023 15:44:34 +0100 Subject: [PATCH 241/327] hound --- openpype/hosts/fusion/hooks/pre_fusion_profile_hook.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/fusion/hooks/pre_fusion_profile_hook.py b/openpype/hosts/fusion/hooks/pre_fusion_profile_hook.py index 9e61d11e6e..0b6626777e 100644 --- a/openpype/hosts/fusion/hooks/pre_fusion_profile_hook.py +++ b/openpype/hosts/fusion/hooks/pre_fusion_profile_hook.py @@ -164,9 +164,11 @@ class FusionCopyPrefsPrelaunch(PreLaunchHook): master_prefs_variable = f"FUSION{profile_version}_MasterPrefs" if AYON_SERVER_ENABLED: - master_prefs = Path(FUSION_HOST_DIR, "deploy_ayon", "fusion_shared.prefs") + master_prefs = Path( + FUSION_HOST_DIR, "deploy_ayon", "fusion_shared.prefs") else: - master_prefs = Path(FUSION_HOST_DIR, "deploy", "fusion_shared.prefs") + master_prefs = Path( + FUSION_HOST_DIR, "deploy", "fusion_shared.prefs") self.log.info(f"Setting {master_prefs_variable}: {master_prefs}") self.launch_context.env[master_prefs_variable] = str(master_prefs) From dc033d283ebb7164ca4024d00ab0b4a645ee81af Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 30 Nov 2023 16:21:11 +0100 Subject: [PATCH 242/327] Changed the labels for layout json extractor --- openpype/hosts/blender/plugins/publish/extract_layout.py | 2 +- server_addon/blender/server/settings/publish_plugins.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/extract_layout.py b/openpype/hosts/blender/plugins/publish/extract_layout.py index 41c6b0912c..3e8978c8d3 100644 --- a/openpype/hosts/blender/plugins/publish/extract_layout.py +++ b/openpype/hosts/blender/plugins/publish/extract_layout.py @@ -14,7 +14,7 @@ from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY class ExtractLayout(publish.Extractor, publish.OptionalPyblishPluginMixin): """Extract a layout.""" - label = "Extract Layout" + label = "Extract Layout (JSON)" hosts = ["blender"] families = ["layout"] optional = True diff --git a/server_addon/blender/server/settings/publish_plugins.py b/server_addon/blender/server/settings/publish_plugins.py index 1c4ad0c6fd..7a5bc236d4 100644 --- a/server_addon/blender/server/settings/publish_plugins.py +++ b/server_addon/blender/server/settings/publish_plugins.py @@ -128,7 +128,7 @@ class PublishPuginsModel(BaseSettingsModel): ) ExtractLayout: ValidatePluginModel = Field( default_factory=ValidatePluginModel, - title="Extract Layout" + title="Extract Layout (JSON)" ) ExtractThumbnail: ExtractPlayblastModel = Field( default_factory=ExtractPlayblastModel, From 6f293e231aafc157913cfa28de45f158d632cfe4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 30 Nov 2023 18:52:37 +0100 Subject: [PATCH 243/327] ignore additional filters if are not filled --- .../pipeline/workfile/workfile_template_builder.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/openpype/pipeline/workfile/workfile_template_builder.py b/openpype/pipeline/workfile/workfile_template_builder.py index 4f53f3993b..0dd8e21426 100644 --- a/openpype/pipeline/workfile/workfile_template_builder.py +++ b/openpype/pipeline/workfile/workfile_template_builder.py @@ -1501,7 +1501,8 @@ class PlaceholderLoadMixin(object): folders_field.set_filter("pathEx", folder_path_regex_var) query.set_variable_value("projectName", project_name) - query.set_variable_value("folderPathRegex", folder_regex) + if folder_regex: + query.set_variable_value("folderPathRegex", folder_regex) api = get_ayon_server_api_connection() for parsed_data in query.continuous_query(api): @@ -1521,7 +1522,10 @@ class PlaceholderLoadMixin(object): current_asset_doc = self.builder.current_asset_doc folder_path_regex = placeholder.data["folder_path"] - product_name_regex = re.compile(placeholder.data["product_name"]) + product_name_regex_value = placeholder.data["product_name"] + product_name_regex = None + if product_name_regex_value: + product_name_regex = re.compile(product_name_regex_value) product_type = placeholder.data["family"] builder_type = placeholder.data["builder_type"] @@ -1547,7 +1551,10 @@ class PlaceholderLoadMixin(object): )) filtered_product_ids = set() for product in products: - if product_name_regex.match(product["name"]): + if ( + product_name_regex is None + or product_name_regex.match(product["name"]) + ): filtered_product_ids.add(product["id"]) if not filtered_product_ids: From 2ea4bcc3afeccb8fba5f891550e82d43d79eb859 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 30 Nov 2023 19:08:35 +0100 Subject: [PATCH 244/327] fix versions loop --- openpype/pipeline/workfile/workfile_template_builder.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/workfile/workfile_template_builder.py b/openpype/pipeline/workfile/workfile_template_builder.py index 0dd8e21426..9dc833061a 100644 --- a/openpype/pipeline/workfile/workfile_template_builder.py +++ b/openpype/pipeline/workfile/workfile_template_builder.py @@ -1561,7 +1561,8 @@ class PlaceholderLoadMixin(object): return [] version_ids = set( - get_last_versions( + version["id"] + for version in get_last_versions( project_name, filtered_product_ids, fields={"id"} ).values() ) From 1f1a2cde001e562e4042fe881caf06949e3f6a79 Mon Sep 17 00:00:00 2001 From: Petr Dvorak Date: Fri, 1 Dec 2023 09:37:16 +0100 Subject: [PATCH 245/327] Changes in default settings --- .../defaults/project_anatomy/attributes.json | 12 +- .../defaults/project_anatomy/templates.json | 12 - .../defaults/project_settings/blender.json | 20 +- .../defaults/project_settings/maya.json | 18 +- .../defaults/project_settings/unreal.json | 4 +- .../system_settings/applications.json | 986 +++++++----------- .../defaults/system_settings/tools.json | 445 ++++++-- 7 files changed, 767 insertions(+), 730 deletions(-) diff --git a/openpype/settings/defaults/project_anatomy/attributes.json b/openpype/settings/defaults/project_anatomy/attributes.json index 0cc414fb69..f388a9336b 100644 --- a/openpype/settings/defaults/project_anatomy/attributes.json +++ b/openpype/settings/defaults/project_anatomy/attributes.json @@ -10,16 +10,8 @@ "resolutionHeight": 1080, "pixelAspect": 1.0, "applications": [ - "maya/2020", - "nuke/12-2", - "nukex/12-2", - "hiero/12-2", - "resolve/stable", - "houdini/18-5", - "blender/2-91", - "harmony/20", - "photoshop/2021", - "aftereffects/2021" + "maya/2024", + "nuke/14-0" ], "tools_env": [], "active": true diff --git a/openpype/settings/defaults/project_anatomy/templates.json b/openpype/settings/defaults/project_anatomy/templates.json index 5766a09100..6c3e038d27 100644 --- a/openpype/settings/defaults/project_anatomy/templates.json +++ b/openpype/settings/defaults/project_anatomy/templates.json @@ -38,16 +38,6 @@ "file": "{subset}_{@version}<_{output}><.{@frame}>.{ext}", "path": "{@folder}/{@file}" }, - "simpleUnrealTextureHero": { - "folder": "{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/hero", - "file": "{originalBasename}.{ext}", - "path": "{@folder}/{@file}" - }, - "simpleUnrealTexture": { - "folder": "{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{@version}", - "file": "{originalBasename}_{@version}.{ext}", - "path": "{@folder}/{@file}" - }, "online": { "folder": "{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/{@version}", "file": "{originalBasename}<.{@frame}><_{udim}>.{ext}", @@ -68,8 +58,6 @@ }, "__dynamic_keys_labels__": { "maya2unreal": "Maya to Unreal", - "simpleUnrealTextureHero": "Simple Unreal Texture - Hero", - "simpleUnrealTexture": "Simple Unreal Texture", "online": "online", "tycache": "tycache", "source": "source", diff --git a/openpype/settings/defaults/project_settings/blender.json b/openpype/settings/defaults/project_settings/blender.json index 385e97ef91..937d177d4a 100644 --- a/openpype/settings/defaults/project_settings/blender.json +++ b/openpype/settings/defaults/project_settings/blender.json @@ -2,7 +2,7 @@ "unit_scale_settings": { "enabled": true, "apply_on_opening": false, - "base_file_unit_scale": 0.01 + "base_file_unit_scale": 1.00 }, "set_resolution_startup": true, "set_frames_startup": true, @@ -31,7 +31,7 @@ }, "publish": { "ValidateCameraZeroKeyframe": { - "enabled": true, + "enabled": false, "optional": true, "active": true }, @@ -62,13 +62,13 @@ "active": true }, "ValidateTransformZero": { - "enabled": true, - "optional": false, + "enabled": false, + "optional": true, "active": true }, "ValidateNoColonsInName": { - "enabled": true, - "optional": false, + "enabled": false, + "optional": true, "active": true }, "ValidateInstanceEmpty": { @@ -90,9 +90,9 @@ ] }, "ExtractFBX": { - "enabled": true, + "enabled": false, "optional": true, - "active": false + "active": true }, "ExtractModelABC": { "enabled": true, @@ -105,9 +105,9 @@ "active": true }, "ExtractAnimationFBX": { - "enabled": true, + "enabled": false, "optional": true, - "active": false + "active": true }, "ExtractCamera": { "enabled": true, diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 7719a5e255..3ecefef4f9 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -1,5 +1,5 @@ { - "open_workfile_post_initialization": false, + "open_workfile_post_initialization": true, "explicit_plugins_loading": { "enabled": false, "plugins_to_load": [ @@ -458,7 +458,7 @@ "per_task_type": [] }, "scriptsmenu": { - "name": "OpenPype Tools", + "name": "Custom Tools", "definition": [ { "type": "action", @@ -708,7 +708,7 @@ "sync_workfile_version": false }, "CollectFbxAnimation": { - "enabled": true + "enabled": false }, "CollectFbxCamera": { "enabled": false @@ -785,7 +785,7 @@ ] }, "ValidatePluginPathAttributes": { - "enabled": true, + "enabled": false, "optional": false, "active": true, "attribute": { @@ -840,12 +840,12 @@ "active": true }, "ValidateGLSLMaterial": { - "enabled": true, + "enabled": false, "optional": false, "active": true }, "ValidateGLSLPlugin": { - "enabled": true, + "enabled": false, "optional": false, "active": true }, @@ -1096,7 +1096,7 @@ "active": true }, "ExtractProxyAlembic": { - "enabled": true, + "enabled": false, "families": [ "proxyAbc" ] @@ -1383,7 +1383,7 @@ "keep_image_planes": false }, "ExtractGLB": { - "enabled": true, + "enabled": false, "active": true, "ogsfx_path": "/maya2glTF/PBR/shaders/glTF_PBR.ogsfx" }, @@ -1581,7 +1581,7 @@ { "subset_name_filters": [], "families": [ - "sedress" + "setdress" ], "repre_names": [ "ma" diff --git a/openpype/settings/defaults/project_settings/unreal.json b/openpype/settings/defaults/project_settings/unreal.json index 20e55c74f0..dc7e4229aa 100644 --- a/openpype/settings/defaults/project_settings/unreal.json +++ b/openpype/settings/defaults/project_settings/unreal.json @@ -10,11 +10,11 @@ "rules": {} } }, - "level_sequences_for_layouts": false, + "level_sequences_for_layouts": true, "delete_unmatched_assets": false, "render_config_path": "", "preroll_frames": 0, - "render_format": "png", + "render_format": "exr", "project_setup": { "dev_mode": false } diff --git a/openpype/settings/defaults/system_settings/applications.json b/openpype/settings/defaults/system_settings/applications.json index a5283751e9..7dd767598e 100644 --- a/openpype/settings/defaults/system_settings/applications.json +++ b/openpype/settings/defaults/system_settings/applications.json @@ -18,7 +18,9 @@ "windows": [ "C:\\Program Files\\Autodesk\\Maya2024\\bin\\maya.exe" ], - "darwin": [], + "darwin": [ + "/Applications/Autodesk/maya2024/Maya.app" + ], "linux": [ "/usr/autodesk/maya2024/bin/maya" ] @@ -38,7 +40,9 @@ "windows": [ "C:\\Program Files\\Autodesk\\Maya2023\\bin\\maya.exe" ], - "darwin": [], + "darwin": [ + "/Applications/Autodesk/maya2023/Maya.app" + ], "linux": [ "/usr/autodesk/maya2023/bin/maya" ] @@ -58,7 +62,9 @@ "windows": [ "C:\\Program Files\\Autodesk\\Maya2022\\bin\\maya.exe" ], - "darwin": [], + "darwin": [ + "/Applications/Autodesk/maya2022/Maya.app" + ], "linux": [ "/usr/autodesk/maya2022/bin/maya" ] @@ -157,6 +163,24 @@ "environment": { "3DSMAX_VERSION": "2023" } + }, + "2024": { + "use_python_2": false, + "executables": { + "windows": [ + "C:\\Program Files\\Autodesk\\3ds Max 2024\\3dsmax.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": { + "3DSMAX_VERSION": "2024" + } } } }, @@ -238,15 +262,17 @@ ] }, "variants": { - "13-2": { + "15-0": { "use_python_2": false, "executables": { "windows": [ - "C:\\Program Files\\Nuke13.2v1\\Nuke13.2.exe" + "C:\\Program Files\\Nuke15.0v2\\Nuke15.0.exe" + ], + "darwin": [ + "/Applications/Nuke15.0v2/Nuke15.0v2.app" ], - "darwin": [], "linux": [ - "/usr/local/Nuke13.2v1/Nuke13.2" + "/usr/local/Nuke15.0v2/Nuke15.0" ] }, "arguments": { @@ -256,15 +282,37 @@ }, "environment": {} }, - "13-0": { + "14-0": { "use_python_2": false, "executables": { "windows": [ - "C:\\Program Files\\Nuke13.0v1\\Nuke13.0.exe" + "C:\\Program Files\\Nuke14.0v5\\Nuke14.0.exe" + ], + "darwin": [ + "/Applications/Nuke14.0v5/Nuke14.0v5.app" ], - "darwin": [], "linux": [ - "/usr/local/Nuke13.0v1/Nuke13.0" + "/usr/local/Nuke14.0v5/Nuke14.0" + ] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": {} + }, + "13-2": { + "use_python_2": false, + "executables": { + "windows": [ + "C:\\Program Files\\Nuke13.2v1\\Nuke13.2.exe" + ], + "darwin": [ + "/Applications/Nuke13.2v1/Nuke13.2v1.app" + ], + "linux": [ + "/usr/local/Nuke13.2v1/Nuke13.2" ] }, "arguments": { @@ -280,9 +328,11 @@ "windows": [ "C:\\Program Files\\Nuke12.2v3\\Nuke12.2.exe" ], - "darwin": [], + "darwin": [ + "/Applications/Nuke12.2v3/Nuke12.2v3.app" + ], "linux": [ - "/usr/local/Nuke12.2v3Nuke12.2" + "/usr/local/Nuke12.2v3/Nuke12.2" ] }, "arguments": { @@ -292,82 +342,11 @@ }, "environment": {} }, - "12-0": { - "use_python_2": true, - "executables": { - "windows": [ - "C:\\Program Files\\Nuke12.0v1\\Nuke12.0.exe" - ], - "darwin": [], - "linux": [ - "/usr/local/Nuke12.0v1/Nuke12.0" - ] - }, - "arguments": { - "windows": [], - "darwin": [], - "linux": [] - }, - "environment": {} - }, - "11-3": { - "use_python_2": true, - "executables": { - "windows": [ - "C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe" - ], - "darwin": [], - "linux": [ - "/usr/local/Nuke11.3v5/Nuke11.3" - ] - }, - "arguments": { - "windows": [], - "darwin": [], - "linux": [] - }, - "environment": {} - }, - "11-2": { - "use_python_2": true, - "executables": { - "windows": [ - "C:\\Program Files\\Nuke11.2v2\\Nuke11.2.exe" - ], - "darwin": [], - "linux": [] - }, - "arguments": { - "windows": [], - "darwin": [], - "linux": [] - }, - "environment": {} - }, - "11-0": { - "use_python_2": true, - "executables": { - "windows": [ - "C:\\Program Files\\Nuke11.0v4\\Nuke11.0.exe" - ], - "darwin": [], - "linux": [] - }, - "arguments": { - "windows": [], - "darwin": [], - "linux": [] - }, - "environment": {} - }, "__dynamic_keys_labels__": { + "15-0": "15.0", + "14-0": "14.0", "13-2": "13.2", - "13-0": "13.0", - "12-2": "12.2", - "12-0": "12.0", - "11-3": "11.3", - "11-2": "11.2", - "11-0": "11.0" + "12-2": "12.2" } } }, @@ -383,15 +362,17 @@ ] }, "variants": { - "13-2": { + "15-0": { "use_python_2": false, "executables": { "windows": [ - "C:\\Program Files\\Nuke13.2v1\\Nuke13.2.exe" + "C:\\Program Files\\Nuke15.0v2\\Nuke15.0.exe" + ], + "darwin": [ + "/Applications/Nuke15.0v2/Nuke15.0v2.app" ], - "darwin": [], "linux": [ - "/usr/local/Nuke13.2v1/Nuke13.2" + "/usr/local/Nuke15.0v2/Nuke15.0" ] }, "arguments": { @@ -407,15 +388,43 @@ }, "environment": {} }, - "13-0": { + "14-0": { "use_python_2": false, "executables": { "windows": [ - "C:\\Program Files\\Nuke13.0v1\\Nuke13.0.exe" + "C:\\Program Files\\Nuke14.0v5\\Nuke14.0.exe" + ], + "darwin": [ + "/Applications/Nuke14.0v5/Nuke14.0v5.app" ], - "darwin": [], "linux": [ - "/usr/local/Nuke13.0v1/Nuke13.0" + "/usr/local/Nuke14.0v5/Nuke14.0" + ] + }, + "arguments": { + "windows": [ + "--nukeassist" + ], + "darwin": [ + "--nukeassist" + ], + "linux": [ + "--nukeassist" + ] + }, + "environment": {} + }, + "13-2": { + "use_python_2": false, + "executables": { + "windows": [ + "C:\\Program Files\\Nuke13.2v1\\Nuke13.2.exe" + ], + "darwin": [ + "/Applications/Nuke13.2v1/Nuke13.2v1.app" + ], + "linux": [ + "/usr/local/Nuke13.2v1/Nuke13.2" ] }, "arguments": { @@ -437,81 +446,13 @@ "windows": [ "C:\\Program Files\\Nuke12.2v3\\Nuke12.2.exe" ], - "darwin": [], - "linux": [ - "/usr/local/Nuke12.2v3Nuke12.2" - ] - }, - "arguments": { - "windows": [ - "--nukeassist" - ], "darwin": [ - "--nukeassist" + "/Applications/Nuke12.2v3/Nuke12.2v3.app" ], "linux": [ - "--nukeassist" + "/usr/local/Nuke12.2v3/Nuke12.2" ] }, - "environment": {} - }, - "12-0": { - "use_python_2": true, - "executables": { - "windows": [ - "C:\\Program Files\\Nuke12.0v1\\Nuke12.0.exe" - ], - "darwin": [], - "linux": [ - "/usr/local/Nuke12.0v1/Nuke12.0" - ] - }, - "arguments": { - "windows": [ - "--nukeassist" - ], - "darwin": [ - "--nukeassist" - ], - "linux": [ - "--nukeassist" - ] - }, - "environment": {} - }, - "11-3": { - "use_python_2": true, - "executables": { - "windows": [ - "C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe" - ], - "darwin": [], - "linux": [ - "/usr/local/Nuke11.3v5/Nuke11.3" - ] - }, - "arguments": { - "windows": [ - "--nukeassist" - ], - "darwin": [ - "--nukeassist" - ], - "linux": [ - "--nukeassist" - ] - }, - "environment": {} - }, - "11-2": { - "use_python_2": true, - "executables": { - "windows": [ - "C:\\Program Files\\Nuke11.2v2\\Nuke11.2.exe" - ], - "darwin": [], - "linux": [] - }, "arguments": { "windows": [ "--nukeassist" @@ -526,12 +467,10 @@ "environment": {} }, "__dynamic_keys_labels__": { + "15-0": "15.0", + "14-0": "14.0", "13-2": "13.2", - "13-0": "13.0", - "12-2": "12.2", - "12-0": "12.0", - "11-3": "11.3", - "11-2": "11.2" + "12-2": "12.2" } } }, @@ -547,15 +486,17 @@ ] }, "variants": { - "13-2": { + "15-0": { "use_python_2": false, "executables": { "windows": [ - "C:\\Program Files\\Nuke13.2v1\\Nuke13.2.exe" + "C:\\Program Files\\Nuke15.0v2\\Nuke15.0.exe" + ], + "darwin": [ + "/Applications/Nuke15.0v2/Nuke15.0v2.app" ], - "darwin": [], "linux": [ - "/usr/local/Nuke13.2v1/Nuke13.2" + "/usr/local/Nuke15.0v2/Nuke15.0" ] }, "arguments": { @@ -571,15 +512,43 @@ }, "environment": {} }, - "13-0": { + "14-0": { "use_python_2": false, "executables": { "windows": [ - "C:\\Program Files\\Nuke13.0v1\\Nuke13.0.exe" + "C:\\Program Files\\Nuke14.0v5\\Nuke14.0.exe" + ], + "darwin": [ + "/Applications/Nuke14.0v5/Nuke14.0v5.app" ], - "darwin": [], "linux": [ - "/usr/local/Nuke13.0v1/Nuke13.0" + "/usr/local/Nuke14.0v5/Nuke14.0" + ] + }, + "arguments": { + "windows": [ + "--nukex" + ], + "darwin": [ + "--nukex" + ], + "linux": [ + "--nukex" + ] + }, + "environment": {} + }, + "13-2": { + "use_python_2": false, + "executables": { + "windows": [ + "C:\\Program Files\\Nuke13.2v1\\Nuke13.2.exe" + ], + "darwin": [ + "/Applications/Nuke13.2v1/Nuke13.2v1.app" + ], + "linux": [ + "/usr/local/Nuke13.2v1/Nuke13.2" ] }, "arguments": { @@ -601,81 +570,13 @@ "windows": [ "C:\\Program Files\\Nuke12.2v3\\Nuke12.2.exe" ], - "darwin": [], - "linux": [ - "/usr/local/Nuke12.2v3Nuke12.2" - ] - }, - "arguments": { - "windows": [ - "--nukex" - ], "darwin": [ - "--nukex" + "/Applications/Nuke12.2v3/Nuke12.2v3.app" ], "linux": [ - "--nukex" + "/usr/local/Nuke12.2v3/Nuke12.2" ] }, - "environment": {} - }, - "12-0": { - "use_python_2": true, - "executables": { - "windows": [ - "C:\\Program Files\\Nuke12.0v1\\Nuke12.0.exe" - ], - "darwin": [], - "linux": [ - "/usr/local/Nuke12.0v1/Nuke12.0" - ] - }, - "arguments": { - "windows": [ - "--nukex" - ], - "darwin": [ - "--nukex" - ], - "linux": [ - "--nukex" - ] - }, - "environment": {} - }, - "11-3": { - "use_python_2": true, - "executables": { - "windows": [ - "C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe" - ], - "darwin": [], - "linux": [ - "/usr/local/Nuke11.3v5/Nuke11.3" - ] - }, - "arguments": { - "windows": [ - "--nukex" - ], - "darwin": [ - "--nukex" - ], - "linux": [ - "--nukex" - ] - }, - "environment": {} - }, - "11-2": { - "use_python_2": true, - "executables": { - "windows": [ - "C:\\Program Files\\Nuke11.2v2\\Nuke11.2.exe" - ], - "darwin": [], - "linux": [] - }, "arguments": { "windows": [ "--nukex" @@ -690,12 +591,10 @@ "environment": {} }, "__dynamic_keys_labels__": { + "15-0": "15.0", + "14-0": "14.0", "13-2": "13.2", - "13-0": "13.0", - "12-2": "12.2", - "12-0": "12.0", - "11-3": "11.3", - "11-2": "11.2" + "12-2": "12.2" } } }, @@ -709,15 +608,17 @@ "TAG_ASSETBUILD_STARTUP": "0" }, "variants": { - "13-2": { + "15-0": { "use_python_2": false, "executables": { "windows": [ - "C:\\Program Files\\Nuke13.2v1\\Nuke13.2.exe" + "C:\\Program Files\\Nuke15.0v2\\Nuke15.0.exe" + ], + "darwin": [ + "/Applications/Nuke15.0v2/Nuke15.0v2.app" ], - "darwin": [], "linux": [ - "/usr/local/Nuke13.2v1/Nuke13.2" + "/usr/local/Nuke15.0v2/Nuke15.0" ] }, "arguments": { @@ -733,15 +634,43 @@ }, "environment": {} }, - "13-0": { + "14-0": { "use_python_2": false, "executables": { "windows": [ - "C:\\Program Files\\Nuke13.0v1\\Nuke13.0.exe" + "C:\\Program Files\\Nuke14.0v5\\Nuke14.0.exe" + ], + "darwin": [ + "/Applications/Nuke14.0v5/Nuke14.0v5.app" ], - "darwin": [], "linux": [ - "/usr/local/Nuke13.0v1/Nuke13.0" + "/usr/local/Nuke14.0v1/Nuke14.0" + ] + }, + "arguments": { + "windows": [ + "--studio" + ], + "darwin": [ + "--studio" + ], + "linux": [ + "--studio" + ] + }, + "environment": {} + }, + "13-2": { + "use_python_2": false, + "executables": { + "windows": [ + "C:\\Program Files\\Nuke13.2v1\\Nuke13.2.exe" + ], + "darwin": [ + "/Applications/Nuke13.2v1/Nuke13.2v1.app" + ], + "linux": [ + "/usr/local/Nuke13.2v1/Nuke13.2" ] }, "arguments": { @@ -763,79 +692,13 @@ "windows": [ "C:\\Program Files\\Nuke12.2v3\\Nuke12.2.exe" ], - "darwin": [], - "linux": [ - "/usr/local/Nuke12.2v3Nuke12.2" - ] - }, - "arguments": { - "windows": [ - "--studio" - ], "darwin": [ - "--studio" + "/Applications/Nuke12.2v3/Nuke12.2v3.app" ], "linux": [ - "--studio" + "/usr/local/Nuke12.2v3/Nuke12.2" ] }, - "environment": {} - }, - "12-0": { - "use_python_2": true, - "executables": { - "windows": [ - "C:\\Program Files\\Nuke12.0v1\\Nuke12.0.exe" - ], - "darwin": [], - "linux": [ - "/usr/local/Nuke12.0v1/Nuke12.0" - ] - }, - "arguments": { - "windows": [ - "--studio" - ], - "darwin": [ - "--studio" - ], - "linux": [ - "--studio" - ] - }, - "environment": {} - }, - "11-3": { - "use_python_2": true, - "executables": { - "windows": [ - "C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe" - ], - "darwin": [], - "linux": [ - "/usr/local/Nuke11.3v5/Nuke11.3" - ] - }, - "arguments": { - "windows": [ - "--studio" - ], - "darwin": [ - "--studio" - ], - "linux": [ - "--studio" - ] - }, - "environment": {} - }, - "11-2": { - "use_python_2": true, - "executables": { - "windows": [], - "darwin": [], - "linux": [] - }, "arguments": { "windows": [ "--studio" @@ -850,12 +713,10 @@ "environment": {} }, "__dynamic_keys_labels__": { + "15-0": "15.0", + "14-0": "14.0", "13-2": "13.2", - "13-0": "13.0", - "12-2": "12.2", - "12-0": "12.0", - "11-3": "11.3", - "11-2": "11.2" + "12-2": "12.2" } } }, @@ -869,15 +730,17 @@ "TAG_ASSETBUILD_STARTUP": "0" }, "variants": { - "13-2": { + "15-0": { "use_python_2": false, "executables": { "windows": [ - "C:\\Program Files\\Nuke13.2v1\\Nuke13.2.exe" + "C:\\Program Files\\Nuke15.0v2\\Nuke15.0.exe" + ], + "darwin": [ + "/Applications/Nuke15.0v2/Nuke15.0v2.app" ], - "darwin": [], "linux": [ - "/usr/local/Nuke13.2v1/Nuke13.2" + "/usr/local/Nuke15.0v2/Nuke15.0" ] }, "arguments": { @@ -893,15 +756,43 @@ }, "environment": {} }, - "13-0": { + "14-0": { "use_python_2": false, "executables": { "windows": [ - "C:\\Program Files\\Nuke13.0v1\\Nuke13.0.exe" + "C:\\Program Files\\Nuke14.0v5\\Nuke14.0.exe" + ], + "darwin": [ + "/Applications/Nuke14.0v5/Nuke14.0v5.app" ], - "darwin": [], "linux": [ - "/usr/local/Nuke13.0v1/Nuke13.0" + "/usr/local/Nuke14.0v5/Nuke14.0" + ] + }, + "arguments": { + "windows": [ + "--hiero" + ], + "darwin": [ + "--hiero" + ], + "linux": [ + "--hiero" + ] + }, + "environment": {} + }, + "13-2": { + "use_python_2": false, + "executables": { + "windows": [ + "C:\\Program Files\\Nuke13.2v1\\Nuke13.2.exe" + ], + "darwin": [ + "/Applications/Nuke13.2v1/Nuke13.2v1.app" + ], + "linux": [ + "/usr/local/Nuke13.2v1/Nuke13.2" ] }, "arguments": { @@ -923,81 +814,13 @@ "windows": [ "C:\\Program Files\\Nuke12.2v3\\Nuke12.2.exe" ], - "darwin": [], - "linux": [ - "/usr/local/Nuke12.2v3Nuke12.2" - ] - }, - "arguments": { - "windows": [ - "--hiero" - ], "darwin": [ - "--hiero" + "/Applications/Nuke12.2v3/Nuke12.2v3.app" ], "linux": [ - "--hiero" + "/usr/local/Nuke12.2v3/Nuke12.2" ] }, - "environment": {} - }, - "12-0": { - "use_python_2": true, - "executables": { - "windows": [ - "C:\\Program Files\\Nuke12.0v1\\Nuke12.0.exe" - ], - "darwin": [], - "linux": [ - "/usr/local/Nuke12.0v1/Nuke12.0" - ] - }, - "arguments": { - "windows": [ - "--hiero" - ], - "darwin": [ - "--hiero" - ], - "linux": [ - "--hiero" - ] - }, - "environment": {} - }, - "11-3": { - "use_python_2": true, - "executables": { - "windows": [ - "C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe" - ], - "darwin": [], - "linux": [ - "/usr/local/Nuke11.3v5/Nuke11.3" - ] - }, - "arguments": { - "windows": [ - "--hiero" - ], - "darwin": [ - "--hiero" - ], - "linux": [ - "--hiero" - ] - }, - "environment": {} - }, - "11-2": { - "use_python_2": true, - "executables": { - "windows": [ - "C:\\Program Files\\Nuke11.2v2\\Nuke11.2.exe" - ], - "darwin": [], - "linux": [] - }, "arguments": { "windows": [ "--hiero" @@ -1012,12 +835,10 @@ "environment": {} }, "__dynamic_keys_labels__": { + "15-0": "15.0", + "14-0": "14.0", "13-2": "13.2", - "13-0": "13.0", - "12-2": "12.2", - "12-0": "12.0", - "11-3": "11.3", - "11-2": "11.2" + "12-2": "12.2" } } }, @@ -1063,36 +884,6 @@ "linux": [] }, "environment": {} - }, - "16": { - "executables": { - "windows": [ - "C:\\Program Files\\Blackmagic Design\\Fusion 16\\Fusion.exe" - ], - "darwin": [], - "linux": [] - }, - "arguments": { - "windows": [], - "darwin": [], - "linux": [] - }, - "environment": {} - }, - "9": { - "executables": { - "windows": [ - "C:\\Program Files\\Blackmagic Design\\Fusion 9\\Fusion.exe" - ], - "darwin": [], - "linux": [] - }, - "arguments": { - "windows": [], - "darwin": [], - "linux": [] - }, - "environment": {} } } }, @@ -1137,54 +928,27 @@ "host_name": "houdini", "environment": {}, "variants": { - "18-5": { + "19-5-716": { "use_python_2": true, "executables": { "windows": [ - "C:\\Program Files\\Side Effects Software\\Houdini 18.5.499\\bin\\houdini.exe" + "c:\\Program Files\\Side Effects Software\\Houdini 19.5.716\\bin\\houdini.exe" ], - "darwin": [], - "linux": [] + "darwin": [ + "/Applications/Houdini/Houdini19.5.716/Houdini.app" + ], + "linux": [ + "/opt/hfs19.5.716/bin/houdini" + ] }, "arguments": { "windows": [], "darwin": [], "linux": [] }, - "environment": {} - }, - "18": { - "use_python_2": true, - "executables": { - "windows": [], - "darwin": [], - "linux": [] - }, - "arguments": { - "windows": [], - "darwin": [], - "linux": [] - }, - "environment": {} - }, - "17": { - "use_python_2": true, - "executables": { - "windows": [], - "darwin": [], - "linux": [] - }, - "arguments": { - "windows": [], - "darwin": [], - "linux": [] - }, - "environment": {} - }, - "__dynamic_keys_labels__": { - "18-5": "18.5", - "18": "18", - "17": "17" + "environment": { + "HOUDINI_VERSION": "19.5.716" + } } } }, @@ -1195,73 +959,23 @@ "host_name": "blender", "environment": {}, "variants": { - "2-83": { + "3-6-5": { "executables": { "windows": [ - "C:\\Program Files\\Blender Foundation\\Blender 2.83\\blender.exe" + "C:\\Program Files\\Blender Foundation\\Blender 3.6\\blender.exe" ], "darwin": [], "linux": [] }, "arguments": { - "windows": [ - "--python-use-system-env" - ], - "darwin": [ - "--python-use-system-env" - ], - "linux": [ - "--python-use-system-env" - ] - }, - "environment": {} - }, - "2-90": { - "executables": { - "windows": [ - "C:\\Program Files\\Blender Foundation\\Blender 2.90\\blender.exe" - ], + "windows": [], "darwin": [], "linux": [] }, - "arguments": { - "windows": [ - "--python-use-system-env" - ], - "darwin": [ - "--python-use-system-env" - ], - "linux": [ - "--python-use-system-env" - ] - }, - "environment": {} - }, - "2-91": { - "executables": { - "windows": [ - "C:\\Program Files\\Blender Foundation\\Blender 2.91\\blender.exe" - ], - "darwin": [], - "linux": [] - }, - "arguments": { - "windows": [ - "--python-use-system-env" - ], - "darwin": [ - "--python-use-system-env" - ], - "linux": [ - "--python-use-system-env" - ] - }, "environment": {} }, "__dynamic_keys_labels__": { - "2-83": "2.83", - "2-90": "2.90", - "2-91": "2.91" + "3-6-5": "3.6.5 LTS" } } }, @@ -1274,6 +988,23 @@ "AVALON_HARMONY_WORKFILES_ON_LAUNCH": "1" }, "variants": { + "22": { + "executables": { + "windows": [ + "c:\\Program Files (x86)\\Toon Boom Animation\\Toon Boom Harmony 22 Premium\\win64\\bin\\HarmonyPremium.exe" + ], + "darwin": [ + "/Applications/Toon Boom Harmony 22 Premium/Harmony Premium.app/Contents/MacOS/Harmony Premium" + ], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": {} + }, "21": { "executables": { "windows": [ @@ -1307,23 +1038,6 @@ "linux": [] }, "environment": {} - }, - "17": { - "executables": { - "windows": [ - "c:\\Program Files (x86)\\Toon Boom Animation\\Toon Boom Harmony 17 Premium\\win64\\bin\\HarmonyPremium.exe" - ], - "darwin": [ - "/Applications/Toon Boom Harmony 17 Premium/Harmony Premium.app/Contents/MacOS/Harmony Premium" - ], - "linux": [] - }, - "arguments": { - "windows": [], - "darwin": [], - "linux": [] - }, - "environment": {} } } }, @@ -1380,44 +1094,50 @@ "WORKFILES_SAVE_AS": "Yes" }, "variants": { - "2020": { - "executables": { - "windows": [ - "C:\\Program Files\\Adobe\\Adobe Photoshop 2020\\Photoshop.exe" - ], - "darwin": [], - "linux": [] - }, - "arguments": { - "windows": [], - "darwin": [], - "linux": [] - }, - "environment": {} - }, - "2021": { - "executables": { - "windows": [ - "C:\\Program Files\\Adobe\\Adobe Photoshop 2021\\Photoshop.exe" - ], - "darwin": [], - "linux": [] - }, - "arguments": { - "windows": [], - "darwin": [], - "linux": [] - }, - "environment": {} - }, "2022": { "executables": { "windows": [ "C:\\Program Files\\Adobe\\Adobe Photoshop 2022\\Photoshop.exe" ], + "darwin": [ + "/Applications/Adobe Photoshop 2022/Adobe Photoshop 2022" + ], + "linux": [] + }, + "arguments": { + "windows": [], "darwin": [], "linux": [] }, + "environment": {} + }, + "2023": { + "executables": { + "windows": [ + "C:\\Program Files\\Adobe\\Adobe Photoshop 2023\\Photoshop.exe" + ], + "darwin": [ + "/Applications/Adobe Photoshop 2023/Adobe Photoshop 2023" + ], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": {} + }, + "2024": { + "executables": { + "windows": [ + "C:\\Program Files\\Adobe\\Adobe Photoshop 2024\\Photoshop.exe" + ], + "darwin": [ + "/Applications/Adobe Photoshop 2024/Adobe Photoshop 2024" + ], + "linux": [] + }, "arguments": { "windows": [], "darwin": [], @@ -1437,44 +1157,54 @@ "WORKFILES_SAVE_AS": "Yes" }, "variants": { - "2020": { - "executables": { - "windows": [ - "C:\\Program Files\\Adobe\\Adobe After Effects 2020\\Support Files\\AfterFX.exe" - ], - "darwin": [], - "linux": [] - }, - "arguments": { - "windows": [], - "darwin": [], - "linux": [] - }, - "environment": {} - }, - "2021": { - "executables": { - "windows": [ - "C:\\Program Files\\Adobe\\Adobe After Effects 2021\\Support Files\\AfterFX.exe" - ], - "darwin": [], - "linux": [] - }, - "arguments": { - "windows": [], - "darwin": [], - "linux": [] - }, - "environment": {} - }, "2022": { "executables": { "windows": [ "C:\\Program Files\\Adobe\\Adobe After Effects 2022\\Support Files\\AfterFX.exe" ], + "darwin": [ + "/Applications/Adobe After Effects 2022/Adobe After Effects 2022" + ], + "linux": [] + }, + "arguments": { + "windows": [], "darwin": [], "linux": [] }, + "environment": { + "MULTIPROCESS": "No" + } + }, + "2023": { + "executables": { + "windows": [ + "C:\\Program Files\\Adobe\\Adobe After Effects 2023\\Support Files\\AfterFX.exe" + ], + "darwin": [ + "/Applications/Adobe After Effects 2023/Adobe After Effects 2023" + ], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": { + "MULTIPROCESS": "No" + } + }, + "2024": { + "executables": { + "windows": [ + "C:\\Program Files\\Adobe\\Adobe After Effects 2024\\Support Files\\AfterFX.exe" + ], + "darwin": [ + "/Applications/Adobe After Effects 2024/Adobe After Effects 2024" + ], + "linux": [] + }, "arguments": { "windows": [], "darwin": [], @@ -1522,7 +1252,7 @@ "host_name": "substancepainter", "environment": {}, "variants": { - "8-2-0": { + "stable": { "executables": { "windows": [ "C:\\Program Files\\Adobe\\Adobe Substance 3D Painter\\Adobe Substance 3D Painter.exe" @@ -1536,9 +1266,6 @@ "linux": [] }, "environment": {} - }, - "__dynamic_keys_labels__": { - "8-2-0": "8.2.0" } } }, @@ -1583,9 +1310,26 @@ }, "environment": {} }, + "5-2": { + "use_python_2": false, + "executables": { + "windows": [ + "C:\\Program Files\\Epic Games\\UE_5.2\\Engine\\Binaries\\Win64\\UnrealEditor.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": {} + }, "__dynamic_keys_labels__": { + "5-0": "Unreal 5.0", "5-1": "Unreal 5.1", - "5-0": "Unreal 5.0" + "5-2": "Unreal 5.2" } } }, diff --git a/openpype/settings/defaults/system_settings/tools.json b/openpype/settings/defaults/system_settings/tools.json index 921e13af3a..9e768b91e9 100644 --- a/openpype/settings/defaults/system_settings/tools.json +++ b/openpype/settings/defaults/system_settings/tools.json @@ -1,90 +1,403 @@ { "tool_groups": { + "htoa": { + "environment": { + "HOUDINI_PATH": [ + "{STUDIO_SW}/APP/HTOA/{HTOA_VERSION}/HOUDINI{HOUDINI_VERSION}/WINDOWS/htoa-6.1.3.3_rdb15014_houdini-{HTOA_VERSION}", + "{HOUDINI_PATH}" + ], + "PATH": { + "windows": [ + "{STUDIO_SW}/APP/HTOA/{HTOA_VERSION}/HOUDINI{HOUDINI_VERSION}/WINDOWS/htoa-6.1.3.3_rdb15014_houdini-{HTOA_VERSION}/scripts/bin", + "{PATH}" + ] + } + }, + "variants": { + "5-4-2-7": { + "host_names": [ + "houdini" + ], + "app_variants": [], + "environment": { + "HTOA_VERSION": "5.4.2.7" + } + } + } + }, "mtoa": { "environment": { - "MTOA": "{STUDIO_SOFTWARE}/arnold/mtoa_{MAYA_VERSION}_{MTOA_VERSION}", - "MAYA_RENDER_DESC_PATH": "{MTOA}", - "MAYA_MODULE_PATH": "{MTOA}", - "ARNOLD_PLUGIN_PATH": "{MTOA}/shaders", - "MTOA_EXTENSIONS_PATH": { - "darwin": "{MTOA}/extensions", - "linux": "{MTOA}/extensions", - "windows": "{MTOA}/extensions" - }, - "MTOA_EXTENSIONS": { - "darwin": "{MTOA}/extensions", - "linux": "{MTOA}/extensions", - "windows": "{MTOA}/extensions" + "MTOA": { + "darwin": "{STUDIO_SW}/APP/MTOA/{MTOA_VERSION}/MAYA{MAYA_VERSION}/MAC", + "linux": "{STUDIO_SW}/APP/MTOA/{MTOA_VERSION}/MAYA{MAYA_VERSION}/LINUX", + "windows": "{STUDIO_SW}/APP/MTOA/{MTOA_VERSION}/MAYA{MAYA_VERSION}/WINDOWS" }, + "MAYA_MODULE_PATH": [ + "{STUDIO_SW}/APP/MTOA", + "{MAYA_MODULE_PATH}" + ], "DYLD_LIBRARY_PATH": { "darwin": "{MTOA}/bin" }, "PATH": { - "windows": "{PATH};{MTOA}/bin" - } + "windows": [ + "{MTOA}/bin", + "{PATH}" + ] + }, + "XBMLANGPATH": [ + "{MTOA}/icons", + "{XBMLANGPATH}" + ], + "MAYA_RENDER_DESC_PATH": [ + "{MTOA}", + "{MAYA_RENDER_DESC_PATH}" + ], + "MTOA_STARTUP_LOG_VERBOSITY": "3" }, "variants": { - "3-2": { - "host_names": [], - "app_variants": [], - "environment": { - "MTOA_VERSION": "3.2" - } - }, - "3-1": { - "host_names": [], - "app_variants": [], - "environment": { - "MTOA_VERSION": "3.1" - } - }, - "__dynamic_keys_labels__": { - "3-2": "3.2", - "3-1": "3.1" - } - } - }, - "vray": { - "environment": {}, - "variants": {} - }, - "yeti": { - "environment": {}, - "variants": {} - }, - "renderman": { - "environment": {}, - "variants": { - "24-3-maya": { + "5-3-1-0": { "host_names": [ "maya" ], - "app_variants": [ - "maya/2022" - ], + "app_variants": [], "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" - } + "MTOA_VERSION": "5.3.1.0" } }, - "__dynamic_keys_labels__": { - "24-3-maya": "24.3 RFM" + "5-3-4-1": { + "host_names": [ + "maya" + ], + "app_variants": [], + "environment": { + "MTOA_VERSION": "5.3.1.0" + } + } + } + }, + "rendermanMaya": { + "environment": { + "RFMTREE": { + "darwin": "{STUDIO_SW}/APP/RENDERMAN/{RM_VERSION}/MAC/MAYA", + "linux": "{STUDIO_SW}/APP/RENDERMAN/{RM_VERSION}/LINUX/MAYA", + "windows": "{STUDIO_SW}/APP/RENDERMAN/{RM_VERSION}/WINDOWS/MAYA" + }, + "RMANTREE": { + "darwin": "{STUDIO_SW}/APP/RENDERMAN/{RM_VERSION}/MAC/RenderManProServer-{RM_VERSION}", + "linux": "{STUDIO_SW}/APP/RENDERMAN/{RM_VERSION}/LINUX/RenderManProServer-{RM_VERSION}", + "windows": "{STUDIO_SW}/APP/RENDERMAN/{RM_VERSION}/WINDOWS/RenderManProServer-{RM_VERSION}" + }, + "MAYA_MODULE_PATH": [ + "{STUDIO_SW}/APP/RENDERMAN", + "{MAYA_MODULE_PATH}" + ], + "PIXAR_LICENSE_FILE": "{STUDIO_SW}/APP/RENDERMAN/pixar.license", + "RFM_DO_NOT_CREATE_MODULE_FILE": "1" + }, + "variants": { + "24-3": { + "host_names": [ + "maya" + ], + "app_variants": [], + "environment": { + "RM_VERSION": "24.3" + } + } + } + }, + "yetiMaya": { + "environment": { + "YETI_HOME": { + "darwin": "{STUDIO_SW}/APP/YETI/{YETI_VERSION}/MAYA{MAYA_VERSION}/MAC", + "linux": "{STUDIO_SW}/APP/YETI/{YETI_VERSION}/MAYA{MAYA_VERSION}/LINUX", + "windows": "{STUDIO_SW}/APP/YETI/{YETI_VERSION}/MAYA{MAYA_VERSION}/WINDOWS" + }, + "YETI_TMP": { + "windows": "C:/temp", + "darwin": "/tmp", + "linux": "/tmp" + }, + "peregrinel_LICENSE": "", + "MAYA_MODULE_PATH": [ + "{STUDIO_SW}/APP/YETI", + "{MAYA_MODULE_PATH}" + ] + }, + "variants": { + "4-2-11": { + "host_names": [ + "maya" + ], + "app_variants": [], + "environment": { + "YETI_VERSION": "4.2.11" + } + } + } + }, + "redshiftMaya": { + "environment": { + "REDSHIFT_COREDATAPATH": { + "darwin": "{STUDIO_SW}/APP/REDSHIFT/{REDSHIFT_VERSION}/MAC", + "linux": "{STUDIO_SW}/APP/REDSHIFT/{REDSHIFT_VERSION}/LINUX", + "windows": "{STUDIO_SW}/APP/REDSHIFT/{REDSHIFT_VERSION}/WINDOWS" + }, + "REDSHIFT_ABORTONLICENSEFAIL": "0", + "MAYA_MODULE_PATH": [ + "{STUDIO_SW}/APP/REDSHIFT", + "{MAYA_MODULE_PATH}" + ], + "MAYA_PLUG_IN_PATH": { + "windows": [ + "{REDSHIFT_COREDATAPATH}/Plugins/Maya/{MAYA_VERSION}/nt-x86-64", + "{MAYA_PLUG_IN_PATH}" + ], + "linux": [ + "{REDSHIFT_COREDATAPATH}/redshift4maya/{MAYA_VERSION}", + "{MAYA_PLUG_IN_PATH}" + ], + "darwin": [ + "{REDSHIFT_COREDATAPATH}/redshift4maya/{MAYA_VERSION}", + "{MAYA_PLUG_IN_PATH}" + ] + }, + "MAYA_SCRIPT_PATH": { + "windows": [ + "{REDSHIFT_COREDATAPATH}/Plugins/Maya/Common/scripts", + "{MAYA_SCRIPT_PATH}" + ], + "linux": [ + "{REDSHIFT_COREDATAPATH}/redshift4maya/common/scripts", + "{MAYA_SCRIPT_PATH}" + ], + "darwin": [ + "{REDSHIFT_COREDATAPATH}/redshift4maya/common/scripts", + "{MAYA_SCRIPT_PATH}" + ] + }, + "REDSHIFT_PROCEDURALSPATH": { + "windows": [ + "{REDSHIFT_COREDATAPATH}/Procedurals", + "{REDSHIFT_PROCEDURALSPATH}" + ], + "linux": [ + "{REDSHIFT_COREDATAPATH}/procedurals", + "{REDSHIFT_PROCEDURALSPATH}" + ], + "darwin": [ + "{REDSHIFT_COREDATAPATH}/procedurals", + "{REDSHIFT_PROCEDURALSPATH}" + ] + }, + "REDSHIFT_MAYAEXTENSIONSPATH": { + "windows": [ + "{REDSHIFT_COREDATAPATH}/Plugins/Maya/{MAYA_VERSION}/nt-x86-64/extensions", + "{REDSHIFT_MAYAEXTENSIONSPATH}" + ], + "linux": [ + "{REDSHIFT_COREDATAPATH}/redshift4maya/{MAYA_VERSION}/extensions", + "{REDSHIFT_MAYAEXTENSIONSPATH}" + ], + "darwin": [ + "{REDSHIFT_COREDATAPATH}/redshift4maya/{MAYA_VERSION}/extensions", + "{REDSHIFT_MAYAEXTENSIONSPATH}" + ] + }, + "XBMLANGPATH": { + "windows": [ + "{REDSHIFT_COREDATAPATH}/Plugins/Maya/Common/icons", + "{XBMLANGPATH}" + ], + "linux": [ + "{REDSHIFT_COREDATAPATH}/redshift4maya/common/icons", + "{XBMLANGPATH}" + ], + "darwin": [ + "{REDSHIFT_COREDATAPATH}/redshift4maya/common/icons", + "{XBMLANGPATH}" + ] + }, + "MAYA_RENDER_DESC_PATH": { + "windows": [ + "{REDSHIFT_COREDATAPATH}/Plugins/Maya/Common/rendererDesc", + "{MAYA_RENDER_DESC_PATH}" + ], + "linux": [ + "{REDSHIFT_COREDATAPATH}/redshift4maya/common/rendererDesc", + "{MAYA_RENDER_DESC_PATH}" + ], + "darwin": [ + "{REDSHIFT_COREDATAPATH}/redshift4maya/common/rendererDesc", + "{MAYA_RENDER_DESC_PATH}" + ] + }, + "MAYA_CUSTOM_TEMPLATE_PATH": { + "windows": [ + "{REDSHIFT_COREDATAPATH}/Plugins/Maya/Common/scripts/NETemplates", + "{MAYA_CUSTOM_TEMPLATE_PATH}" + ], + "linux": [ + "{REDSHIFT_COREDATAPATH}/redshift4maya/common/scripts/NETemplates", + "{MAYA_CUSTOM_TEMPLATE_PATH}" + ], + "darwin": [ + "{REDSHIFT_COREDATAPATH}/redshift4maya/common/scripts/NETemplates", + "{MAYA_CUSTOM_TEMPLATE_PATH}" + ] + }, + "PATH": { + "windows": [ + "{REDSHIFT_COREDATAPATH}/bin", + "{PATH}" + ] + } + }, + "variants": { + "3-5-19": { + "host_names": [ + "maya" + ], + "app_variants": [], + "environment": { + "REDSHIFT_VERSION": "3.5.19" + } + } + } + }, + "redshift3dsmax": { + "environment": { + "REDSHIFT_COREDATAPATH": { + "darwin": "{STUDIO_SW}/APP/REDSHIFT/{REDSHIFT_VERSION}/MAC", + "linux": "{STUDIO_SW}/APP/REDSHIFT/{REDSHIFT_VERSION}/LINUX", + "windows": "{STUDIO_SW}/APP/REDSHIFT/{REDSHIFT_VERSION}/WINDOWS" + }, + "REDSHIFT_ABORTONLICENSEFAIL": "0", + "REDSHIFT_PROCEDURALSPATH": { + "windows": [ + "{REDSHIFT_COREDATAPATH}/Procedurals", + "{REDSHIFT_PROCEDURALSPATH}" + ], + "linux": [ + "{REDSHIFT_COREDATAPATH}/procedurals", + "{REDSHIFT_PROCEDURALSPATH}" + ], + "darwin": [ + "{REDSHIFT_COREDATAPATH}/procedurals", + "{REDSHIFT_PROCEDURALSPATH}" + ] + }, + "PATH": { + "windows": [ + "{REDSHIFT_COREDATAPATH}/bin", + "{PATH}" + ] + } + }, + "variants": { + "3-5-19": { + "host_names": [ + "max" + ], + "app_variants": [], + "environment": { + "REDSHIFT_VERSION": "3.5.19" + } + } + } + }, + "mGear": { + "environment": { + "MGEAR_ROOT": "{STUDIO_SW}/APP/MGEAR/{MGEAR_VERSION}/MAYA{MAYA_VERSION}/windows/x64", + "MAYA_MODULE_PATH": [ + "{STUDIO_SW}/APP/MGEAR/{MGEAR_VERSION}/release", + "{MAYA_MODULE_PATH}" + ] + }, + "variants": { + "4-0-7": { + "host_names": [ + "maya" + ], + "app_variants": [], + "environment": { + "MGEAR_VERSION": "4.0.7" + } + } + } + }, + "vrayMaya": { + "environment": { + "MAYA_MODULE_PATH": { + "windows": [ + "{STUDIO_SW}/APP/VRAY/{VRAY_VERSION}/MAYA{MAYA_VERSION}/WINDOWS/maya_root/modules", + "{MAYA_MODULE_PATH}" + ], + "linux": [ + "{STUDIO_SW}/APP/VRAY/{VRAY_VERSION}/MAYA{MAYA_VERSION}/LINUX/maya_root/modules", + "{MAYA_MODULE_PATH}" + ], + "darwin": [ + "{STUDIO_SW}/APP/VRAY/{VRAY_VERSION}/MAYA{MAYA_VERSION}/MAC/maya_root/modules", + "{MAYA_MODULE_PATH}" + ] + }, + "VRAY_AUTH_CLIENT_FILE_PATH": "{STUDIO_SW}/APP/VRAY" + }, + "variants": { + "6-10-01": { + "host_names": [ + "maya" + ], + "app_variants": [], + "environment": { + "VRAY_VERSION": "6.10.01" + } + } + } + }, + "vraynuke": { + "environment": { + "VRAY_FOR_NUKE_13_0_PLUGINS": { + "windows": "{STUDIO_SW}/APP/VRAYNUKE/{VRAYNUKE_VERSION}/NUKE{NUKE_VRAY_VERSION}/WINDOWS/nuke_vray/plugins/vray" + }, + "NUKE_PATH": { + "windows": [ + "{STUDIO_SW}/APP/VRAYNUKE/{VRAYNUKE_VERSION}/NUKE{NUKE_VRAY_VERSION}/WINDOWS/nuke_root", + "{NUKE_PATH}" + ] + }, + "PATH": { + "windows": [ + "{STUDIO_SW}/APP/VRAYNUKE/{VRAYNUKE_VERSION}/NUKE{NUKE_VRAY_VERSION}/WINDOWS/nuke_vray", + "{PATH}" + ] + }, + "VRAY_AUTH_CLIENT_FILE_PATH": "{STUDIO_SW}/APP/VRAY" + }, + "variants": { + "5-20-00": { + "host_names": [ + "nuke" + ], + "app_variants": [], + "environment": { + "VRAYNUKE_VERSION": "5.20.00" + } } } }, "__dynamic_keys_labels__": { - "mtoa": "Autodesk Arnold", - "vray": "Chaos Group Vray", - "yeti": "Peregrine Labs Yeti", - "renderman": "Pixar Renderman" + "htoa": "Arnold for Houdini (example)", + "mtoa": "Arnold for Maya (example)", + "rendermanMaya": "Renderman for Maya (example)", + "yetiMaya": "Yeti for Maya (example)", + "redshiftMaya": "Redshift for Maya (example)", + "redshift3dsmax": "Redshift for 3dsmax (example)", + "mGear": "mGear for Maya (example)", + "vrayMaya": "Vray for Maya (example)", + "vraynuke": "Vray for Nuke (example)" } } } From 804086f8694ef0b56c3da6471e4df6247c840e91 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Sat, 2 Dec 2023 03:25:30 +0000 Subject: [PATCH 246/327] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index c1c33c0f65..98efdaec5f 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.17.7-nightly.4" +__version__ = "3.17.7-nightly.5" From 9678fb35b875cbfd2b47e10ea546773510f620b6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 2 Dec 2023 03:26:07 +0000 Subject: [PATCH 247/327] chore(): update bug report / version --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 60ad923546..c65a04c774 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,7 @@ body: label: Version description: What version are you running? Look to OpenPype Tray options: + - 3.17.7-nightly.5 - 3.17.7-nightly.4 - 3.17.7-nightly.3 - 3.17.7-nightly.2 @@ -134,7 +135,6 @@ body: - 3.15.3-nightly.1 - 3.15.2 - 3.15.2-nightly.6 - - 3.15.2-nightly.5 validations: required: true - type: dropdown From c62edc1c040f83954b849e093a28399cfa092f5b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 4 Dec 2023 11:26:07 +0100 Subject: [PATCH 248/327] Updated label (#5980) --- server_addon/photoshop/server/settings/publish_plugins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/photoshop/server/settings/publish_plugins.py b/server_addon/photoshop/server/settings/publish_plugins.py index 6bc72b4072..2863979ca9 100644 --- a/server_addon/photoshop/server/settings/publish_plugins.py +++ b/server_addon/photoshop/server/settings/publish_plugins.py @@ -150,7 +150,7 @@ class PhotoshopPublishPlugins(BaseSettingsModel): ) CollectVersion: CollectVersionPlugin = Field( - title="Create Image", + title="Collect Version", default_factory=CollectVersionPlugin, ) From 523f0230334a8f17e592bc56188e3a5b00d2c7b3 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 4 Dec 2023 11:27:43 +0100 Subject: [PATCH 249/327] SiteSync: implemented in Ayon Loader (#5962) * Added new SiteSync model Used to get information from SiteSync module to enhance Loader UI. * Added new SiteSync method to controller Other models will be using these to get information pertaining SiteSync * Added missed commit * Implemented collection of SiteSync info * Added AvailabilityDelegate Shows how many representations are present locally and remotely in Loader summary page. * Added fields to store progress info * Fix HiddenAttr to carry value * Refactored to internal variable Changes made after discussion * Implemented ActionItems for upload/download/remove Replaced old Launcher approach, now it is not necessary after refactor of Ayon launcher. * Update openpype/tools/ayon_loader/abstract.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> * Hound * Refactor better retrieval of icon * Refactor better readability * Refactor renamed delegate * Refactor better retrieval of icons * Refactor better readability * Refactor removed unneeded explicit refresh * Hound * Hound * Hound * Fix used wrong type * Update openpype/tools/ayon_loader/ui/products_delegates.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> * Refactor renamed variable name * Refactor formatting * Added progress for representations * cache version availability * cache representations sync status * changed representations count logic and moved it to products model * site sync enabled is cached * active and remote site names are cached * small tweaks in site sync model * change methods called by controller * hide site sync columns if site sync not enabled * use string conversion before iteration * smal formatting changes * updated abstract class with abstract methods * renamed site sync model variable * fixed method name * fix used method name * rename '_sitesync_addon' to '_site_sync_addon' * fix remote site name cache * small formatting changes in delegate * modify site sync delegate to be more dynamic * fix delegate painting * do not handle repre progress in products model * Add comma back * simplify delegate code --------- Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Co-authored-by: Jakub Trllo --- openpype/tools/attribute_defs/widgets.py | 2 +- openpype/tools/ayon_loader/abstract.py | 97 +++- openpype/tools/ayon_loader/control.py | 61 ++- openpype/tools/ayon_loader/models/__init__.py | 2 + openpype/tools/ayon_loader/models/products.py | 36 ++ .../tools/ayon_loader/models/site_sync.py | 509 ++++++++++++++++++ .../ayon_loader/ui/products_delegates.py | 80 +++ .../tools/ayon_loader/ui/products_model.py | 51 +- .../tools/ayon_loader/ui/products_widget.py | 30 +- .../tools/ayon_loader/ui/repres_widget.py | 83 ++- 10 files changed, 931 insertions(+), 20 deletions(-) create mode 100644 openpype/tools/ayon_loader/models/site_sync.py diff --git a/openpype/tools/attribute_defs/widgets.py b/openpype/tools/attribute_defs/widgets.py index 8957f2b19d..7dea01e0a8 100644 --- a/openpype/tools/attribute_defs/widgets.py +++ b/openpype/tools/attribute_defs/widgets.py @@ -608,7 +608,7 @@ class UnknownAttrWidget(_BaseAttrDefWidget): class HiddenAttrWidget(_BaseAttrDefWidget): def _ui_init(self): self.setVisible(False) - self._value = None + self._value = self.attr_def.default self._multivalue = False def setVisible(self, visible): diff --git a/openpype/tools/ayon_loader/abstract.py b/openpype/tools/ayon_loader/abstract.py index 45042395d9..bf3e81d485 100644 --- a/openpype/tools/ayon_loader/abstract.py +++ b/openpype/tools/ayon_loader/abstract.py @@ -137,7 +137,7 @@ class VersionItem: handles, step, comment, - source + source, ): self.version_id = version_id self.product_id = product_id @@ -215,7 +215,7 @@ class RepreItem: representation_name, representation_icon, product_name, - folder_label, + folder_label ): self.representation_id = representation_id self.representation_name = representation_name @@ -590,6 +590,22 @@ class FrontendLoaderController(_BaseLoaderController): pass + @abstractmethod + def get_versions_representation_count( + self, project_name, version_ids, sender=None + ): + """ + Args: + project_name (str): Project name. + version_ids (Iterable[str]): Version ids. + sender (Optional[str]): Sender who requested the items. + + Returns: + dict[str, int]: Representation count by version id. + """ + + pass + @abstractmethod def get_thumbnail_path(self, project_name, thumbnail_id): """Get thumbnail path for thumbnail id. @@ -849,3 +865,80 @@ class FrontendLoaderController(_BaseLoaderController): """ pass + + # Site sync functions + @abstractmethod + def is_site_sync_enabled(self, project_name=None): + """Is site sync enabled. + + Site sync addon can be enabled but can be disabled per project. + + When asked for enabled state without project name, it should return + True if site sync addon is available and enabled. + + Args: + project_name (Optional[str]): Project name. + + Returns: + bool: True if site sync is enabled. + """ + + pass + + @abstractmethod + def get_active_site_icon_def(self, project_name): + """Active site icon definition. + + Args: + project_name (Union[str, None]): Project name. + + Returns: + Union[dict[str, Any], None]: Icon definition or None if site sync + is not enabled for the project. + """ + + pass + + @abstractmethod + def get_remote_site_icon_def(self, project_name): + """Remote site icon definition. + + Args: + project_name (Union[str, None]): Project name. + + Returns: + Union[dict[str, Any], None]: Icon definition or None if site sync + is not enabled for the project. + """ + + pass + + @abstractmethod + def get_version_sync_availability(self, project_name, version_ids): + """Version sync availability. + + Args: + project_name (str): Project name. + version_ids (Iterable[str]): Version ids. + + Returns: + dict[str, tuple[int, int]]: Sync availability by version id. + """ + + pass + + @abstractmethod + def get_representations_sync_status( + self, project_name, representation_ids + ): + """Representations sync status. + + Args: + project_name (str): Project name. + representation_ids (Iterable[str]): Representation ids. + + Returns: + dict[str, tuple[int, int]]: Sync status by representation id. + """ + + pass diff --git a/openpype/tools/ayon_loader/control.py b/openpype/tools/ayon_loader/control.py index 8ec0d96e2e..060cef6661 100644 --- a/openpype/tools/ayon_loader/control.py +++ b/openpype/tools/ayon_loader/control.py @@ -15,7 +15,12 @@ from openpype.tools.ayon_utils.models import ( ) from .abstract import BackendLoaderController, FrontendLoaderController -from .models import SelectionModel, ProductsModel, LoaderActionsModel +from .models import ( + SelectionModel, + ProductsModel, + LoaderActionsModel, + SiteSyncModel +) class ExpectedSelection: @@ -108,6 +113,7 @@ class LoaderController(BackendLoaderController, FrontendLoaderController): self._products_model = ProductsModel(self) self._loader_actions_model = LoaderActionsModel(self) self._thumbnails_model = ThumbnailsModel() + self._site_sync_model = SiteSyncModel(self) @property def log(self): @@ -143,6 +149,7 @@ class LoaderController(BackendLoaderController, FrontendLoaderController): self._loader_actions_model.reset() self._projects_model.reset() self._thumbnails_model.reset() + self._site_sync_model.reset() self._projects_model.refresh() @@ -195,13 +202,22 @@ class LoaderController(BackendLoaderController, FrontendLoaderController): project_name, version_ids, sender ) + def get_versions_representation_count( + self, project_name, version_ids, sender=None + ): + return self._products_model.get_versions_repre_count( + project_name, version_ids, sender + ) + def get_folder_thumbnail_ids(self, project_name, folder_ids): return self._thumbnails_model.get_folder_thumbnail_ids( - project_name, folder_ids) + project_name, folder_ids + ) def get_version_thumbnail_ids(self, project_name, version_ids): return self._thumbnails_model.get_version_thumbnail_ids( - project_name, version_ids) + project_name, version_ids + ) def get_thumbnail_path(self, project_name, thumbnail_id): return self._thumbnails_model.get_thumbnail_path( @@ -219,8 +235,16 @@ class LoaderController(BackendLoaderController, FrontendLoaderController): def get_representations_action_items( self, project_name, representation_ids): - return self._loader_actions_model.get_representations_action_items( + action_items = ( + self._loader_actions_model.get_representations_action_items( + project_name, representation_ids) + ) + + action_items.extend(self._site_sync_model.get_site_sync_action_items( project_name, representation_ids) + ) + + return action_items def trigger_action_item( self, @@ -230,6 +254,14 @@ class LoaderController(BackendLoaderController, FrontendLoaderController): version_ids, representation_ids ): + if self._site_sync_model.is_site_sync_action(identifier): + self._site_sync_model.trigger_action_item( + identifier, + project_name, + representation_ids + ) + return + self._loader_actions_model.trigger_action_item( identifier, options, @@ -336,6 +368,27 @@ class LoaderController(BackendLoaderController, FrontendLoaderController): self._loaded_products_cache.update_data(product_ids) return self._loaded_products_cache.get_data() + def is_site_sync_enabled(self, project_name=None): + return self._site_sync_model.is_site_sync_enabled(project_name) + + def get_active_site_icon_def(self, project_name): + return self._site_sync_model.get_active_site_icon_def(project_name) + + def get_remote_site_icon_def(self, project_name): + return self._site_sync_model.get_remote_site_icon_def(project_name) + + def get_version_sync_availability(self, project_name, version_ids): + return self._site_sync_model.get_version_sync_availability( + project_name, version_ids + ) + + def get_representations_sync_status( + self, project_name, representation_ids + ): + return self._site_sync_model.get_representations_sync_status( + project_name, representation_ids + ) + def is_loaded_products_supported(self): return self._host is not None diff --git a/openpype/tools/ayon_loader/models/__init__.py b/openpype/tools/ayon_loader/models/__init__.py index 6adfe71d86..8e640659a0 100644 --- a/openpype/tools/ayon_loader/models/__init__.py +++ b/openpype/tools/ayon_loader/models/__init__.py @@ -1,10 +1,12 @@ from .selection import SelectionModel from .products import ProductsModel from .actions import LoaderActionsModel +from .site_sync import SiteSyncModel __all__ = ( "SelectionModel", "ProductsModel", "LoaderActionsModel", + "SiteSyncModel", ) diff --git a/openpype/tools/ayon_loader/models/products.py b/openpype/tools/ayon_loader/models/products.py index 816dabaf90..daa36aefdc 100644 --- a/openpype/tools/ayon_loader/models/products.py +++ b/openpype/tools/ayon_loader/models/products.py @@ -317,6 +317,42 @@ class ProductsModel: return output + def get_versions_repre_count(self, project_name, version_ids, sender): + """Get representation count for passed version ids. + + Args: + project_name (str): Project name. + version_ids (Iterable[str]): Version ids. + sender (Union[str, None]): Who triggered the method. + + Returns: + dict[str, int]: Number of representations by version id. + """ + + output = {} + if not any((project_name, version_ids)): + return output + + invalid_version_ids = set() + project_cache = self._repre_items_cache[project_name] + for version_id in version_ids: + version_cache = project_cache[version_id] + if version_cache.is_valid: + output[version_id] = len(version_cache.get_data()) + else: + invalid_version_ids.add(version_id) + + if invalid_version_ids: + self.refresh_representation_items( + project_name, invalid_version_ids, sender + ) + + for version_id in invalid_version_ids: + version_cache = project_cache[version_id] + output[version_id] = len(version_cache.get_data()) + + return output + def change_products_group(self, project_name, product_ids, group_name): """Change group name for passed product ids. diff --git a/openpype/tools/ayon_loader/models/site_sync.py b/openpype/tools/ayon_loader/models/site_sync.py new file mode 100644 index 0000000000..90852b6954 --- /dev/null +++ b/openpype/tools/ayon_loader/models/site_sync.py @@ -0,0 +1,509 @@ +import collections + +from openpype.lib import Logger +from openpype.client.entities import get_representations +from openpype.client import get_linked_representation_id +from openpype.modules import ModulesManager +from openpype.tools.ayon_utils.models import NestedCacheItem +from openpype.tools.ayon_loader.abstract import ActionItem + +DOWNLOAD_IDENTIFIER = "sitesync.download" +UPLOAD_IDENTIFIER = "sitesync.upload" +REMOVE_IDENTIFIER = "sitesync.remove" + +log = Logger.get_logger(__name__) + + +def _default_version_availability(): + return 0, 0 + + +def _default_repre_status(): + return 0.0, 0.0 + + +class SiteSyncModel: + """Model handling site sync logic. + + Model cares about handling of site sync functionality. All public + functions should be possible to call even if site sync is not available. + """ + + lifetime = 60 # In seconds (minute by default) + status_lifetime = 20 + + def __init__(self, controller): + self._controller = controller + + self._site_icons = None + self._site_sync_enabled_cache = NestedCacheItem( + levels=1, lifetime=self.lifetime + ) + self._active_site_cache = NestedCacheItem( + levels=1, lifetime=self.lifetime + ) + self._remote_site_cache = NestedCacheItem( + levels=1, lifetime=self.lifetime + ) + self._version_availability_cache = NestedCacheItem( + levels=2, + default_factory=_default_version_availability, + lifetime=self.status_lifetime + ) + self._repre_status_cache = NestedCacheItem( + levels=2, + default_factory=_default_repre_status, + lifetime=self.status_lifetime + ) + + manager = ModulesManager() + self._site_sync_addon = manager.get("sync_server") + + def reset(self): + self._site_icons = None + self._site_sync_enabled_cache.reset() + self._active_site_cache.reset() + self._remote_site_cache.reset() + self._version_availability_cache.reset() + self._repre_status_cache.reset() + + def is_site_sync_enabled(self, project_name=None): + """Site sync is enabled for a project. + + Returns false if site sync addon is not available or enabled + or project has disabled it. + + Args: + project_name (Union[str, None]): Project name. If project name + is 'None', True is returned if site sync addon + is available and enabled. + + Returns: + bool: Site sync is enabled. + """ + + if not self._is_site_sync_addon_enabled(): + return False + cache = self._site_sync_enabled_cache[project_name] + if not cache.is_valid: + enabled = True + if project_name: + enabled = self._site_sync_addon.is_project_enabled( + project_name, single=True + ) + cache.update_data(enabled) + return cache.get_data() + + def get_active_site(self, project_name): + """Active site name for a project. + + Args: + project_name (str): Project name. + + Returns: + Union[str, None]: Remote site name. + """ + + cache = self._active_site_cache[project_name] + if not cache.is_valid: + site_name = None + if project_name and self._is_site_sync_addon_enabled(): + site_name = self._site_sync_addon.get_active_site(project_name) + cache.update_data(site_name) + return cache.get_data() + + def get_remote_site(self, project_name): + """Remote site name for a project. + + Args: + project_name (str): Project name. + + Returns: + Union[str, None]: Remote site name. + """ + + cache = self._remote_site_cache[project_name] + if not cache.is_valid: + site_name = None + if project_name and self._is_site_sync_addon_enabled(): + site_name = self._site_sync_addon.get_remote_site(project_name) + cache.update_data(site_name) + return cache.get_data() + + def get_active_site_icon_def(self, project_name): + """Active site icon definition. + + Args: + project_name (Union[str, None]): Name of project. + + Returns: + Union[dict[str, Any], None]: Site icon definition. + """ + + if not project_name: + return None + + active_site = self.get_active_site(project_name) + provider = self._get_provider_for_site(project_name, active_site) + return self._get_provider_icon(provider) + + def get_remote_site_icon_def(self, project_name): + """Remote site icon definition. + + Args: + project_name (Union[str, None]): Name of project. + + Returns: + Union[dict[str, Any], None]: Site icon definition. + """ + + if not project_name or not self.is_site_sync_enabled(project_name): + return None + remote_site = self.get_remote_site(project_name) + provider = self._get_provider_for_site(project_name, remote_site) + return self._get_provider_icon(provider) + + def get_version_sync_availability(self, project_name, version_ids): + """Returns how many representations are available on sites. + + Returned value `{version_id: (4, 6)}` denotes that locally are + available 4 and remotely 6 representation. + NOTE: Available means they were synced to site. + + Returns: + dict[str, tuple[int, int]] + """ + + if not self.is_site_sync_enabled(project_name): + return { + version_id: _default_version_availability() + for version_id in version_ids + } + + output = {} + project_cache = self._version_availability_cache[project_name] + invalid_ids = set() + for version_id in version_ids: + repre_cache = project_cache[version_id] + if repre_cache.is_valid: + output[version_id] = repre_cache.get_data() + else: + invalid_ids.add(version_id) + + if invalid_ids: + self._refresh_version_availability( + project_name, invalid_ids + ) + for version_id in invalid_ids: + version_cache = project_cache[version_id] + output[version_id] = version_cache.get_data() + return output + + def get_representations_sync_status( + self, project_name, representation_ids + ): + """ + + Args: + project_name (str): Project name. + representation_ids (Iterable[str]): Representation ids. + + Returns: + dict[str, tuple[float, float]] + """ + + if not self.is_site_sync_enabled(project_name): + return { + repre_id: _default_repre_status() + for repre_id in representation_ids + } + + output = {} + project_cache = self._repre_status_cache[project_name] + invalid_ids = set() + for repre_id in representation_ids: + repre_cache = project_cache[repre_id] + if repre_cache.is_valid: + output[repre_id] = repre_cache.get_data() + else: + invalid_ids.add(repre_id) + + if invalid_ids: + self._refresh_representations_sync_status( + project_name, invalid_ids + ) + for repre_id in invalid_ids: + repre_cache = project_cache[repre_id] + output[repre_id] = repre_cache.get_data() + return output + + def get_site_sync_action_items(self, project_name, representation_ids): + """ + + Args: + project_name (str): Project name. + representation_ids (Iterable[str]): Representation ids. + + Returns: + list[ActionItem]: Actions that can be shown in loader. + """ + + if not self.is_site_sync_enabled(project_name): + return [] + + repres_status = self.get_representations_sync_status( + project_name, representation_ids + ) + + repre_ids_per_identifier = collections.defaultdict(set) + for repre_id in representation_ids: + repre_status = repres_status[repre_id] + local_status, remote_status = repre_status + + if local_status: + repre_ids_per_identifier[UPLOAD_IDENTIFIER].add(repre_id) + repre_ids_per_identifier[REMOVE_IDENTIFIER].add(repre_id) + + if remote_status: + repre_ids_per_identifier[DOWNLOAD_IDENTIFIER].add(repre_id) + + action_items = [] + for identifier, repre_ids in repre_ids_per_identifier.items(): + if identifier == DOWNLOAD_IDENTIFIER: + action_items.append(self._create_download_action_item( + project_name, repre_ids + )) + elif identifier == UPLOAD_IDENTIFIER: + action_items.append(self._create_upload_action_item( + project_name, repre_ids + )) + elif identifier == REMOVE_IDENTIFIER: + action_items.append(self._create_delete_action_item( + project_name, repre_ids + )) + + return action_items + + def is_site_sync_action(self, identifier): + """Should be `identifier` handled by SiteSync. + + Args: + identifier (str): Action identifier. + + Returns: + bool: Should action be handled by SiteSync. + """ + + return identifier in { + UPLOAD_IDENTIFIER, + DOWNLOAD_IDENTIFIER, + REMOVE_IDENTIFIER, + } + + def trigger_action_item( + self, + identifier, + project_name, + representation_ids + ): + """Resets status for site_name or remove local files. + + Args: + identifier (str): Action identifier. + project_name (str): Project name. + representation_ids (Iterable[str]): Representation ids. + """ + + active_site = self.get_active_site(project_name) + remote_site = self.get_remote_site(project_name) + + repre_docs = list(get_representations( + project_name, representation_ids=representation_ids + )) + families_per_repre_id = { + item["_id"]: item["context"]["family"] + for item in repre_docs + } + + for repre_id in representation_ids: + family = families_per_repre_id[repre_id] + if identifier == DOWNLOAD_IDENTIFIER: + self._add_site( + project_name, repre_id, active_site, family + ) + + elif identifier == UPLOAD_IDENTIFIER: + self._add_site( + project_name, repre_id, remote_site, family + ) + + elif identifier == REMOVE_IDENTIFIER: + self._site_sync_addon.remove_site( + project_name, + repre_id, + active_site, + remove_local_files=True + ) + + def _is_site_sync_addon_enabled(self): + """ + Returns: + bool: Site sync addon is enabled. + """ + + if self._site_sync_addon is None: + return False + return self._site_sync_addon.enabled + + def _get_provider_for_site(self, project_name, site_name): + """Provider for a site. + + Args: + project_name (str): Project name. + site_name (str): Site name. + + Returns: + Union[str, None]: Provider name. + """ + + if not self._is_site_sync_addon_enabled(): + return None + return self._site_sync_addon.get_provider_for_site( + project_name, site_name + ) + + def _get_provider_icon(self, provider): + """site provider icons. + + Returns: + Union[dict[str, Any], None]: Icon of site provider. + """ + + if not provider: + return None + + if self._site_icons is None: + self._site_icons = self._site_sync_addon.get_site_icons() + return self._site_icons.get(provider) + + def _refresh_version_availability(self, project_name, version_ids): + if not project_name or not version_ids: + return + project_cache = self._version_availability_cache[project_name] + + avail_by_id = self._site_sync_addon.get_version_availability( + project_name, + version_ids, + self.get_active_site(project_name), + self.get_remote_site(project_name), + ) + for version_id in version_ids: + status = avail_by_id.get(version_id) + if status is None: + status = _default_version_availability() + project_cache[version_id].update_data(status) + + def _refresh_representations_sync_status( + self, project_name, representation_ids + ): + if not project_name or not representation_ids: + return + project_cache = self._repre_status_cache[project_name] + status_by_repre_id = ( + self._site_sync_addon.get_representations_sync_state( + project_name, + representation_ids, + self.get_active_site(project_name), + self.get_remote_site(project_name), + ) + ) + for repre_id in representation_ids: + status = status_by_repre_id.get(repre_id) + if status is None: + status = _default_repre_status() + project_cache[repre_id].update_data(status) + + def _create_download_action_item(self, project_name, representation_ids): + return self._create_action_item( + project_name, + representation_ids, + DOWNLOAD_IDENTIFIER, + "Download", + "Mark representation for download locally", + "fa.download" + ) + + def _create_upload_action_item(self, project_name, representation_ids): + return self._create_action_item( + project_name, + representation_ids, + UPLOAD_IDENTIFIER, + "Upload", + "Mark representation for upload remotely", + "fa.upload" + ) + + def _create_delete_action_item(self, project_name, representation_ids): + return self._create_action_item( + project_name, + representation_ids, + REMOVE_IDENTIFIER, + "Remove from local", + "Remove local synchronization", + "fa.trash" + ) + + def _create_action_item( + self, + project_name, + representation_ids, + identifier, + label, + tooltip, + icon_name + ): + return ActionItem( + identifier, + label, + icon={ + "type": "awesome-font", + "name": icon_name, + "color": "#999999" + }, + tooltip=tooltip, + options={}, + order=1, + project_name=project_name, + folder_ids=[], + product_ids=[], + version_ids=[], + representation_ids=representation_ids, + ) + + def _add_site(self, project_name, repre_id, site_name, family): + self._site_sync_addon.add_site( + project_name, repre_id, site_name, force=True + ) + + # TODO this should happen in site sync addon + if family != "workfile": + return + + links = get_linked_representation_id( + project_name, + repre_id=repre_id, + link_type="reference" + ) + for link_repre_id in links: + try: + print("Adding {} to linked representation: {}".format( + site_name, link_repre_id)) + self._site_sync_addon.add_site( + project_name, + link_repre_id, + site_name, + force=False + ) + except Exception: + # do not add/reset working site for references + log.debug("Site present", exc_info=True) diff --git a/openpype/tools/ayon_loader/ui/products_delegates.py b/openpype/tools/ayon_loader/ui/products_delegates.py index 6729468bfa..979fa57fd2 100644 --- a/openpype/tools/ayon_loader/ui/products_delegates.py +++ b/openpype/tools/ayon_loader/ui/products_delegates.py @@ -8,6 +8,11 @@ from .products_model import ( VERSION_NAME_EDIT_ROLE, VERSION_ID_ROLE, PRODUCT_IN_SCENE_ROLE, + ACTIVE_SITE_ICON_ROLE, + REMOTE_SITE_ICON_ROLE, + REPRESENTATIONS_COUNT_ROLE, + SYNC_ACTIVE_SITE_AVAILABILITY, + SYNC_REMOTE_SITE_AVAILABILITY, ) @@ -189,3 +194,78 @@ class LoadedInSceneDelegate(QtWidgets.QStyledItemDelegate): value = index.data(PRODUCT_IN_SCENE_ROLE) color = self._colors.get(value, self._default_color) option.palette.setBrush(QtGui.QPalette.Text, color) + + +class SiteSyncDelegate(QtWidgets.QStyledItemDelegate): + """Paints icons and downloaded representation ration for both sites.""" + + def paint(self, painter, option, index): + super(SiteSyncDelegate, self).paint(painter, option, index) + option = QtWidgets.QStyleOptionViewItem(option) + option.showDecorationSelected = True + + active_icon = index.data(ACTIVE_SITE_ICON_ROLE) + remote_icon = index.data(REMOTE_SITE_ICON_ROLE) + + availability_active = "{}/{}".format( + index.data(SYNC_ACTIVE_SITE_AVAILABILITY), + index.data(REPRESENTATIONS_COUNT_ROLE) + ) + availability_remote = "{}/{}".format( + index.data(SYNC_REMOTE_SITE_AVAILABILITY), + index.data(REPRESENTATIONS_COUNT_ROLE) + ) + + if availability_active is None or availability_remote is None: + return + + items_to_draw = [ + (value, icon) + for value, icon in ( + (availability_active, active_icon), + (availability_remote, remote_icon), + ) + if icon + ] + if not items_to_draw: + return + + icon_size = QtCore.QSize(24, 24) + padding = 10 + pos_x = option.rect.x() + + item_width = int(option.rect.width() / len(items_to_draw)) + if item_width < 1: + item_width = 0 + + for value, icon in items_to_draw: + item_rect = QtCore.QRect( + pos_x, + option.rect.y(), + item_width, + option.rect.height() + ) + # Prepare pos_x for next item + pos_x = item_rect.x() + item_rect.width() + + pixmap = icon.pixmap(icon.actualSize(icon_size)) + point = QtCore.QPoint( + item_rect.x() + padding, + item_rect.y() + ((item_rect.height() - pixmap.height()) * 0.5) + ) + painter.drawPixmap(point, pixmap) + + icon_offset = icon_size.width() + (padding * 2) + text_rect = QtCore.QRect(item_rect) + text_rect.setLeft(text_rect.left() + icon_offset) + if text_rect.width() < 1: + continue + + painter.drawText( + text_rect, + option.displayAlignment, + value + ) + + def displayText(self, value, locale): + pass diff --git a/openpype/tools/ayon_loader/ui/products_model.py b/openpype/tools/ayon_loader/ui/products_model.py index 741f15766b..84f5bc9a5f 100644 --- a/openpype/tools/ayon_loader/ui/products_model.py +++ b/openpype/tools/ayon_loader/ui/products_model.py @@ -29,6 +29,11 @@ VERSION_HANDLES_ROLE = QtCore.Qt.UserRole + 18 VERSION_STEP_ROLE = QtCore.Qt.UserRole + 19 VERSION_AVAILABLE_ROLE = QtCore.Qt.UserRole + 20 VERSION_THUMBNAIL_ID_ROLE = QtCore.Qt.UserRole + 21 +ACTIVE_SITE_ICON_ROLE = QtCore.Qt.UserRole + 22 +REMOTE_SITE_ICON_ROLE = QtCore.Qt.UserRole + 23 +REPRESENTATIONS_COUNT_ROLE = QtCore.Qt.UserRole + 24 +SYNC_ACTIVE_SITE_AVAILABILITY = QtCore.Qt.UserRole + 25 +SYNC_REMOTE_SITE_AVAILABILITY = QtCore.Qt.UserRole + 26 class ProductsModel(QtGui.QStandardItemModel): @@ -68,6 +73,7 @@ class ProductsModel(QtGui.QStandardItemModel): published_time_col = column_labels.index("Time") folders_label_col = column_labels.index("Folder") in_scene_col = column_labels.index("In scene") + site_sync_avail_col = column_labels.index("Availability") def __init__(self, controller): super(ProductsModel, self).__init__() @@ -303,7 +309,26 @@ class ProductsModel(QtGui.QStandardItemModel): model_item.setData( version_item.thumbnail_id, VERSION_THUMBNAIL_ID_ROLE) - def _get_product_model_item(self, product_item): + # TODO call site sync methods for all versions at once + project_name = self._last_project_name + version_id = version_item.version_id + repre_count = self._controller.get_versions_representation_count( + project_name, [version_id] + )[version_id] + active, remote = self._controller.get_version_sync_availability( + project_name, [version_id] + )[version_id] + + model_item.setData(repre_count, REPRESENTATIONS_COUNT_ROLE) + model_item.setData(active, SYNC_ACTIVE_SITE_AVAILABILITY) + model_item.setData(remote, SYNC_REMOTE_SITE_AVAILABILITY) + + def _get_product_model_item( + self, + product_item, + active_site_icon, + remote_site_icon + ): model_item = self._items_by_id.get(product_item.product_id) versions = list(product_item.version_items.values()) versions.sort() @@ -329,6 +354,9 @@ class ProductsModel(QtGui.QStandardItemModel): in_scene = 1 if product_item.product_in_scene else 0 model_item.setData(in_scene, PRODUCT_IN_SCENE_ROLE) + model_item.setData(active_site_icon, ACTIVE_SITE_ICON_ROLE) + model_item.setData(remote_site_icon, REMOTE_SITE_ICON_ROLE) + self._set_version_data_to_product_item(model_item, last_version) return model_item @@ -341,6 +369,15 @@ class ProductsModel(QtGui.QStandardItemModel): self._last_project_name = project_name self._last_folder_ids = folder_ids + active_site_icon_def = self._controller.get_active_site_icon_def( + project_name + ) + remote_site_icon_def = self._controller.get_remote_site_icon_def( + project_name + ) + active_site_icon = get_qt_icon(active_site_icon_def) + remote_site_icon = get_qt_icon(remote_site_icon_def) + product_items = self._controller.get_product_items( project_name, folder_ids, @@ -402,7 +439,11 @@ class ProductsModel(QtGui.QStandardItemModel): new_root_items.append(parent_item) for product_item in top_items: - item = self._get_product_model_item(product_item) + item = self._get_product_model_item( + product_item, + active_site_icon, + remote_site_icon, + ) new_items.append(item) for path_info in merged_product_items.values(): @@ -418,7 +459,11 @@ class ProductsModel(QtGui.QStandardItemModel): merged_product_types = set() new_merged_items = [] for product_item in product_items: - item = self._get_product_model_item(product_item) + item = self._get_product_model_item( + product_item, + active_site_icon, + remote_site_icon, + ) new_merged_items.append(item) merged_product_types.add(product_item.product_type) diff --git a/openpype/tools/ayon_loader/ui/products_widget.py b/openpype/tools/ayon_loader/ui/products_widget.py index 2d4959dc19..99faefe693 100644 --- a/openpype/tools/ayon_loader/ui/products_widget.py +++ b/openpype/tools/ayon_loader/ui/products_widget.py @@ -19,7 +19,11 @@ from .products_model import ( VERSION_ID_ROLE, VERSION_THUMBNAIL_ID_ROLE, ) -from .products_delegates import VersionDelegate, LoadedInSceneDelegate +from .products_delegates import ( + VersionDelegate, + LoadedInSceneDelegate, + SiteSyncDelegate +) from .actions_utils import show_actions_menu @@ -92,7 +96,7 @@ class ProductsWidget(QtWidgets.QWidget): 55, # Handles 10, # Step 25, # Loaded in scene - 65, # Site info (maybe?) + 65, # Site sync info ) def __init__(self, controller, parent): @@ -135,6 +139,10 @@ class ProductsWidget(QtWidgets.QWidget): products_view.setItemDelegateForColumn( products_model.in_scene_col, in_scene_delegate) + site_sync_delegate = SiteSyncDelegate() + products_view.setItemDelegateForColumn( + products_model.site_sync_avail_col, site_sync_delegate) + main_layout = QtWidgets.QHBoxLayout(self) main_layout.setContentsMargins(0, 0, 0, 0) main_layout.addWidget(products_view, 1) @@ -167,6 +175,8 @@ class ProductsWidget(QtWidgets.QWidget): self._version_delegate = version_delegate self._time_delegate = time_delegate + self._in_scene_delegate = in_scene_delegate + self._site_sync_delegate = site_sync_delegate self._selected_project_name = None self._selected_folder_ids = set() @@ -182,6 +192,9 @@ class ProductsWidget(QtWidgets.QWidget): products_model.in_scene_col, not controller.is_loaded_products_supported() ) + self._set_site_sync_visibility( + self._controller.is_site_sync_enabled() + ) def set_name_filter(self, name): """Set filter of product name. @@ -216,6 +229,12 @@ class ProductsWidget(QtWidgets.QWidget): def refresh(self): self._refresh_model() + def _set_site_sync_visibility(self, site_sync_enabled): + self._products_view.setColumnHidden( + self._products_model.site_sync_avail_col, + not site_sync_enabled + ) + def _fill_version_editor(self): model = self._products_proxy_model index_queue = collections.deque() @@ -375,7 +394,12 @@ class ProductsWidget(QtWidgets.QWidget): self._on_selection_change() def _on_folders_selection_change(self, event): - self._selected_project_name = event["project_name"] + project_name = event["project_name"] + site_sync_enabled = self._controller.is_site_sync_enabled( + project_name + ) + self._set_site_sync_visibility(site_sync_enabled) + self._selected_project_name = project_name self._selected_folder_ids = event["folder_ids"] self._refresh_model() self._update_folders_label_visible() diff --git a/openpype/tools/ayon_loader/ui/repres_widget.py b/openpype/tools/ayon_loader/ui/repres_widget.py index 7de582e629..efc1bb89a4 100644 --- a/openpype/tools/ayon_loader/ui/repres_widget.py +++ b/openpype/tools/ayon_loader/ui/repres_widget.py @@ -14,6 +14,10 @@ REPRESENTATION_ID_ROLE = QtCore.Qt.UserRole + 2 PRODUCT_NAME_ROLE = QtCore.Qt.UserRole + 3 FOLDER_LABEL_ROLE = QtCore.Qt.UserRole + 4 GROUP_TYPE_ROLE = QtCore.Qt.UserRole + 5 +ACTIVE_SITE_ICON_ROLE = QtCore.Qt.UserRole + 6 +REMOTE_SITE_ICON_ROLE = QtCore.Qt.UserRole + 7 +SYNC_ACTIVE_SITE_PROGRESS = QtCore.Qt.UserRole + 8 +SYNC_REMOTE_SITE_PROGRESS = QtCore.Qt.UserRole + 9 class RepresentationsModel(QtGui.QStandardItemModel): @@ -22,12 +26,14 @@ class RepresentationsModel(QtGui.QStandardItemModel): ("Name", 120), ("Product name", 125), ("Folder", 125), - # ("Active site", 85), - # ("Remote site", 85) + ("Active site", 85), + ("Remote site", 85) ] column_labels = [label for label, _ in colums_info] column_widths = [width for _, width in colums_info] folder_column = column_labels.index("Product name") + active_site_column = column_labels.index("Active site") + remote_site_column = column_labels.index("Remote site") def __init__(self, controller): super(RepresentationsModel, self).__init__() @@ -59,7 +65,7 @@ class RepresentationsModel(QtGui.QStandardItemModel): repre_items = self._controller.get_representation_items( self._selected_project_name, self._selected_version_ids ) - self._fill_items(repre_items) + self._fill_items(repre_items, self._selected_project_name) self.refreshed.emit() def data(self, index, role=None): @@ -69,13 +75,23 @@ class RepresentationsModel(QtGui.QStandardItemModel): col = index.column() if col != 0: if role == QtCore.Qt.DecorationRole: - return None + if col == 3: + role = ACTIVE_SITE_ICON_ROLE + elif col == 4: + role = REMOTE_SITE_ICON_ROLE + else: + return None if role == QtCore.Qt.DisplayRole: if col == 1: role = PRODUCT_NAME_ROLE elif col == 2: role = FOLDER_LABEL_ROLE + elif col == 3: + role = SYNC_ACTIVE_SITE_PROGRESS + elif col == 4: + role = SYNC_REMOTE_SITE_PROGRESS + index = self.index(index.row(), 0, index.parent()) return super(RepresentationsModel, self).data(index, role) @@ -89,7 +105,13 @@ class RepresentationsModel(QtGui.QStandardItemModel): root_item = self.invisibleRootItem() root_item.removeRows(0, root_item.rowCount()) - def _get_repre_item(self, repre_item): + def _get_repre_item( + self, + repre_item, + active_site_icon, + remote_site_icon, + repres_sync_status + ): repre_id = repre_item.representation_id repre_name = repre_item.representation_name repre_icon = repre_item.representation_icon @@ -102,6 +124,12 @@ class RepresentationsModel(QtGui.QStandardItemModel): item.setColumnCount(self.columnCount()) item.setEditable(False) + sync_status = repres_sync_status[repre_id] + active_progress, remote_progress = sync_status + + active_site_progress = "{}%".format(int(active_progress * 100)) + remote_site_progress = "{}%".format(int(remote_progress * 100)) + icon = get_qt_icon(repre_icon) item.setData(repre_name, QtCore.Qt.DisplayRole) item.setData(icon, QtCore.Qt.DecorationRole) @@ -109,6 +137,10 @@ class RepresentationsModel(QtGui.QStandardItemModel): item.setData(repre_id, REPRESENTATION_ID_ROLE) item.setData(repre_item.product_name, PRODUCT_NAME_ROLE) item.setData(repre_item.folder_label, FOLDER_LABEL_ROLE) + item.setData(active_site_icon, ACTIVE_SITE_ICON_ROLE) + item.setData(remote_site_icon, REMOTE_SITE_ICON_ROLE) + item.setData(active_site_progress, SYNC_ACTIVE_SITE_PROGRESS) + item.setData(remote_site_progress, SYNC_REMOTE_SITE_PROGRESS) return is_new_item, item def _get_group_icon(self): @@ -134,14 +166,29 @@ class RepresentationsModel(QtGui.QStandardItemModel): self._groups_items_by_name[repre_name] = item return True, item - def _fill_items(self, repre_items): + def _fill_items(self, repre_items, project_name): + active_site_icon_def = self._controller.get_active_site_icon_def( + project_name + ) + remote_site_icon_def = self._controller.get_remote_site_icon_def( + project_name + ) + active_site_icon = get_qt_icon(active_site_icon_def) + remote_site_icon = get_qt_icon(remote_site_icon_def) + items_to_remove = set(self._items_by_id.keys()) repre_items_by_name = collections.defaultdict(list) + repre_ids = set() for repre_item in repre_items: + repre_ids.add(repre_item.representation_id) items_to_remove.discard(repre_item.representation_id) repre_name = repre_item.representation_name repre_items_by_name[repre_name].append(repre_item) + repres_sync_status = self._controller.get_representations_sync_status( + project_name, repre_ids + ) + root_item = self.invisibleRootItem() for repre_id in items_to_remove: item = self._items_by_id.pop(repre_id) @@ -164,7 +211,12 @@ class RepresentationsModel(QtGui.QStandardItemModel): new_group_items = [] for repre_item in repre_name_items: - is_new_item, item = self._get_repre_item(repre_item) + is_new_item, item = self._get_repre_item( + repre_item, + active_site_icon, + remote_site_icon, + repres_sync_status + ) item_parent = item.parent() if item_parent is None: item_parent = root_item @@ -255,6 +307,9 @@ class RepresentationsWidget(QtWidgets.QWidget): self._repre_model = repre_model self._repre_proxy_model = repre_proxy_model + self._set_site_sync_visibility( + self._controller.is_site_sync_enabled() + ) self._set_multiple_folders_selected(False) def refresh(self): @@ -265,6 +320,20 @@ class RepresentationsWidget(QtWidgets.QWidget): def _on_project_change(self, event): self._selected_project_name = event["project_name"] + site_sync_enabled = self._controller.is_site_sync_enabled( + self._selected_project_name + ) + self._set_site_sync_visibility(site_sync_enabled) + + def _set_site_sync_visibility(self, site_sync_enabled): + self._repre_view.setColumnHidden( + self._repre_model.active_site_column, + not site_sync_enabled + ) + self._repre_view.setColumnHidden( + self._repre_model.remote_site_column, + not site_sync_enabled + ) def _set_multiple_folders_selected(self, selected_multiple_folders): if selected_multiple_folders == self._selected_multiple_folders: From 4076968581beb2144a3dbb33b632bd116df28513 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 4 Dec 2023 21:23:59 +0800 Subject: [PATCH 250/327] rename openpype tools as custom tools --- server_addon/maya/server/settings/scriptsmenu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/maya/server/settings/scriptsmenu.py b/server_addon/maya/server/settings/scriptsmenu.py index 82c1c2e53c..4ac2263f7a 100644 --- a/server_addon/maya/server/settings/scriptsmenu.py +++ b/server_addon/maya/server/settings/scriptsmenu.py @@ -26,7 +26,7 @@ class ScriptsmenuModel(BaseSettingsModel): DEFAULT_SCRIPTSMENU_SETTINGS = { - "name": "OpenPype Tools", + "name": "Custom Tools", "definition": [ { "type": "action", From 0ce9af3f839ea056663bda3bc366f292e92f6a55 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 4 Dec 2023 21:25:38 +0800 Subject: [PATCH 251/327] increment version --- server_addon/maya/server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/maya/server/version.py b/server_addon/maya/server/version.py index 805897cda3..b87834cc35 100644 --- a/server_addon/maya/server/version.py +++ b/server_addon/maya/server/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring addon version.""" -__version__ = "0.1.6" +__version__ = "0.1.7" From 87a00a5fe755d49216d9b0e2a7fc46d20be54227 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 4 Dec 2023 16:08:38 +0100 Subject: [PATCH 252/327] Updated name of Adobe extension to Ayon --- openpype/hosts/aftereffects/api/README.md | 20 +++++++++-------- openpype/hosts/aftereffects/api/extension.zxp | Bin 103005 -> 106275 bytes .../hosts/aftereffects/api/extension/.debug | 21 +++++++++--------- .../api/extension/CSXS/manifest.xml | 12 +++++----- .../api/extension/icons/ayon_logo.png | Bin 0 -> 3538 bytes openpype/hosts/aftereffects/api/panel.png | Bin 0 -> 16269 bytes .../hosts/aftereffects/api/panel_failure.PNG | Bin 13568 -> 0 bytes .../hosts/aftereffects/api/panel_failure.png | Bin 0 -> 13115 bytes openpype/hosts/photoshop/api/README.md | 10 ++++----- openpype/hosts/photoshop/api/extension.zxp | Bin 54056 -> 55653 bytes openpype/hosts/photoshop/api/extension/.debug | 4 ++-- .../photoshop/api/extension/CSXS/manifest.xml | 8 +++---- .../api/extension/icons/avalon-logo-48.png | Bin 1362 -> 0 bytes .../api/extension/icons/ayon_logo.png | Bin 0 -> 3538 bytes openpype/hosts/photoshop/api/panel.PNG | Bin 8756 -> 0 bytes .../api/panel.PNG => photoshop/api/panel.png} | Bin .../hosts/photoshop/api/panel_failure.PNG | Bin 13568 -> 0 bytes .../hosts/photoshop/api/panel_failure.png | Bin 0 -> 13115 bytes 18 files changed, 38 insertions(+), 37 deletions(-) create mode 100644 openpype/hosts/aftereffects/api/extension/icons/ayon_logo.png create mode 100644 openpype/hosts/aftereffects/api/panel.png delete mode 100644 openpype/hosts/aftereffects/api/panel_failure.PNG create mode 100644 openpype/hosts/aftereffects/api/panel_failure.png delete mode 100644 openpype/hosts/photoshop/api/extension/icons/avalon-logo-48.png create mode 100644 openpype/hosts/photoshop/api/extension/icons/ayon_logo.png delete mode 100644 openpype/hosts/photoshop/api/panel.PNG rename openpype/hosts/{aftereffects/api/panel.PNG => photoshop/api/panel.png} (100%) delete mode 100644 openpype/hosts/photoshop/api/panel_failure.PNG create mode 100644 openpype/hosts/photoshop/api/panel_failure.png diff --git a/openpype/hosts/aftereffects/api/README.md b/openpype/hosts/aftereffects/api/README.md index 790c9f859a..847b27ab53 100644 --- a/openpype/hosts/aftereffects/api/README.md +++ b/openpype/hosts/aftereffects/api/README.md @@ -1,6 +1,6 @@ # AfterEffects Integration -Requirements: This extension requires use of Javascript engine, which is +Requirements: This extension requires use of Javascript engine, which is available since CC 16.0. Please check your File>Project Settings>Expressions>Expressions Engine @@ -13,26 +13,28 @@ The After Effects integration requires two components to work; `extension` and ` To install the extension download [Extension Manager Command Line tool (ExManCmd)](https://github.com/Adobe-CEP/Getting-Started-guides/tree/master/Package%20Distribute%20Install#option-2---exmancmd). ``` -ExManCmd /install {path to avalon-core}\avalon\photoshop\extension.zxp +ExManCmd /install {path to addon}/api/extension.zxp ``` OR download [Anastasiy’s Extension Manager](https://install.anastasiy.com/) +`{pat to addon}` will be most likely in your AppData (on Windows, in your user data folder in Linux and MacOS.) + ### Server The easiest way to get the server and After Effects launch is with: ``` -python -c ^"import avalon.photoshop;avalon.aftereffects.launch(""c:\Program Files\Adobe\Adobe After Effects 2020\Support Files\AfterFX.exe"")^" +python -c ^"import openpype.hosts.photoshop;openpype.hosts..aftereffects.launch(""c:\Program Files\Adobe\Adobe After Effects 2020\Support Files\AfterFX.exe"")^" ``` `avalon.aftereffects.launch` launches the application and server, and also closes the server when After Effects exists. ## Usage -The After Effects extension can be found under `Window > Extensions > OpenPype`. Once launched you should be presented with a panel like this: +The After Effects extension can be found under `Window > Extensions > AYON`. Once launched you should be presented with a panel like this: -![Avalon Panel](panel.PNG "Avalon Panel") +![Ayon Panel](panel.png "Ayon Panel") ## Developing @@ -43,8 +45,8 @@ When developing the extension you can load it [unsigned](https://github.com/Adob When signing the extension you can use this [guide](https://github.com/Adobe-CEP/Getting-Started-guides/tree/master/Package%20Distribute%20Install#package-distribute-install-guide). ``` -ZXPSignCmd -selfSignedCert NA NA Avalon Avalon-After-Effects avalon extension.p12 -ZXPSignCmd -sign {path to avalon-core}\avalon\aftereffects\extension {path to avalon-core}\avalon\aftereffects\extension.zxp extension.p12 avalon +ZXPSignCmd -selfSignedCert NA NA Ayon Avalon-After-Effects Ayon extension.p12 +ZXPSignCmd -sign {path to addon}/api/extension {path to addon}/api/extension.zxp extension.p12 Ayon ``` ### Plugin Examples @@ -52,14 +54,14 @@ ZXPSignCmd -sign {path to avalon-core}\avalon\aftereffects\extension {path to av These plugins were made with the [polly config](https://github.com/mindbender-studio/config). To fully integrate and load, you will have to use this config and add `image` to the [integration plugin](https://github.com/mindbender-studio/config/blob/master/polly/plugins/publish/integrate_asset.py). Expected deployed extension location on default Windows: -`c:\Program Files (x86)\Common Files\Adobe\CEP\extensions\com.openpype.AE.panel` +`c:\Program Files (x86)\Common Files\Adobe\CEP\extensions\io.ynput.AE.panel` For easier debugging of Javascript: https://community.adobe.com/t5/download-install/adobe-extension-debuger-problem/td-p/10911704?page=1 Add (optional) --enable-blink-features=ShadowDOMV0,CustomElementsV0 when starting Chrome then localhost:8092 -Or use Visual Studio Code https://medium.com/adobetech/extendscript-debugger-for-visual-studio-code-public-release-a2ff6161fa01 +Or use Visual Studio Code https://medium.com/adobetech/extendscript-debugger-for-visual-studio-code-public-release-a2ff6161fa01 ## Resources - https://javascript-tools-guide.readthedocs.io/introduction/index.html - https://github.com/Adobe-CEP/Getting-Started-guides diff --git a/openpype/hosts/aftereffects/api/extension.zxp b/openpype/hosts/aftereffects/api/extension.zxp index 933dc7dc6cab34b6a1630b25d11a2167fa0aef7a..104a5c9e997577959bd8c134fc2ac9febbb8797a 100644 GIT binary patch delta 11531 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 diff --git a/openpype/hosts/aftereffects/api/extension/.debug b/openpype/hosts/aftereffects/api/extension/.debug index b06ec515dd..20a6713ab2 100644 --- a/openpype/hosts/aftereffects/api/extension/.debug +++ b/openpype/hosts/aftereffects/api/extension/.debug @@ -1,32 +1,31 @@ - + - + - + - + - + - + - + - + - + - + - \ No newline at end of file diff --git a/openpype/hosts/aftereffects/api/extension/CSXS/manifest.xml b/openpype/hosts/aftereffects/api/extension/CSXS/manifest.xml index 7329a9e723..cf6ba67f44 100644 --- a/openpype/hosts/aftereffects/api/extension/CSXS/manifest.xml +++ b/openpype/hosts/aftereffects/api/extension/CSXS/manifest.xml @@ -1,8 +1,8 @@ - + - + @@ -38,7 +38,7 @@ - + ./index.html @@ -49,7 +49,7 @@ Panel - OpenPype + AYON 200 @@ -66,7 +66,7 @@ - ./icons/iconNormal.png + ./icons/ayon_logo.png ./icons/iconRollover.png ./icons/iconDisabled.png ./icons/iconDarkNormal.png diff --git a/openpype/hosts/aftereffects/api/extension/icons/ayon_logo.png b/openpype/hosts/aftereffects/api/extension/icons/ayon_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..3a96f8e2b499baa337cdc5a4d3cdf547f9ded972 GIT binary patch literal 3538 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 E-zj>|X#fBK literal 0 HcmV?d00001 diff --git a/openpype/hosts/aftereffects/api/panel.png b/openpype/hosts/aftereffects/api/panel.png new file mode 100644 index 0000000000000000000000000000000000000000..d05ed354286dc0dd00b5d7a2df40ba1e3e1fa6cb GIT binary patch literal 16269 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 + AYON 300 From 1721563a5f0ef733731275a236cb5ac3fc34a9f6 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 4 Dec 2023 18:28:08 +0100 Subject: [PATCH 259/327] Photoshop: Fix removed unsupported Path (#5996) * Fix removed unsupported Path Path is not json serializable by default, it is not necessary, better model reused. * Fix wrong key Must be 'path', not 'template_path' as workfile builder expects the former one. --- .../server/settings/workfile_builder.py | 28 ++++++------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/server_addon/photoshop/server/settings/workfile_builder.py b/server_addon/photoshop/server/settings/workfile_builder.py index ec2ee136ad..68db05270d 100644 --- a/server_addon/photoshop/server/settings/workfile_builder.py +++ b/server_addon/photoshop/server/settings/workfile_builder.py @@ -1,31 +1,18 @@ from pydantic import Field -from pathlib import Path -from ayon_server.settings import BaseSettingsModel - - -class PathsTemplate(BaseSettingsModel): - windows: Path = Field( - '', - title="Windows" - ) - darwin: Path = Field( - '', - title="MacOS" - ) - linux: Path = Field( - '', - title="Linux" - ) +from ayon_server.settings import BaseSettingsModel, MultiplatformPathModel class CustomBuilderTemplate(BaseSettingsModel): + _layout = "expanded" task_types: list[str] = Field( default_factory=list, title="Task types", ) - template_path: PathsTemplate = Field( - default_factory=PathsTemplate + + path: MultiplatformPathModel = Field( + default_factory=MultiplatformPathModel, + title="Template path" ) @@ -37,5 +24,6 @@ class WorkfileBuilderPlugin(BaseSettingsModel): ) custom_templates: list[CustomBuilderTemplate] = Field( - default_factory=CustomBuilderTemplate + default_factory=CustomBuilderTemplate, + title="Template profiles" ) From 21c9709a8ebbfb0e34d3ab5dcdc971214cfe5c99 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 4 Dec 2023 21:19:23 +0100 Subject: [PATCH 260/327] `menu.py` to `launch_menu.py` and removing script folder ref --- .../hosts/fusion/deploy/MenuScripts/{menu.py => launch_menu.py} | 0 openpype/hosts/fusion/deploy/ayon/Config/menu.fu | 2 +- openpype/hosts/fusion/deploy/ayon/fusion_shared.prefs | 2 +- openpype/hosts/fusion/deploy/openpype/Config/menu.fu | 2 +- openpype/hosts/fusion/deploy/openpype/fusion_shared.prefs | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) rename openpype/hosts/fusion/deploy/MenuScripts/{menu.py => launch_menu.py} (100%) diff --git a/openpype/hosts/fusion/deploy/MenuScripts/menu.py b/openpype/hosts/fusion/deploy/MenuScripts/launch_menu.py similarity index 100% rename from openpype/hosts/fusion/deploy/MenuScripts/menu.py rename to openpype/hosts/fusion/deploy/MenuScripts/launch_menu.py diff --git a/openpype/hosts/fusion/deploy/ayon/Config/menu.fu b/openpype/hosts/fusion/deploy/ayon/Config/menu.fu index 79ef4595d9..c968a1bb3d 100644 --- a/openpype/hosts/fusion/deploy/ayon/Config/menu.fu +++ b/openpype/hosts/fusion/deploy/ayon/Config/menu.fu @@ -10,7 +10,7 @@ Composition = { Execute = _Lua [=[ - local scriptPath = app:MapPath("AYON:../MenuScripts/menu.py") + local scriptPath = app:MapPath("AYON:../MenuScripts/launch_menu.py") if bmd.fileexists(scriptPath) == false then print("[AYON Error] Can't run file: " .. scriptPath) else diff --git a/openpype/hosts/fusion/deploy/ayon/fusion_shared.prefs b/openpype/hosts/fusion/deploy/ayon/fusion_shared.prefs index 731f26682b..9c67af7db9 100644 --- a/openpype/hosts/fusion/deploy/ayon/fusion_shared.prefs +++ b/openpype/hosts/fusion/deploy/ayon/fusion_shared.prefs @@ -5,7 +5,7 @@ Global = { Map = { ["AYON:"] = "$(OPENPYPE_FUSION)/deploy/ayon", ["Config:"] = "UserPaths:Config;AYON:Config", - ["Scripts:"] = "UserPaths:Scripts;Reactor:System/Scripts;AYON:../Script", + ["Scripts:"] = "UserPaths:Scripts;Reactor:System/Scripts", }, }, Script = { diff --git a/openpype/hosts/fusion/deploy/openpype/Config/menu.fu b/openpype/hosts/fusion/deploy/openpype/Config/menu.fu index 715fa98aa3..85134d2c62 100644 --- a/openpype/hosts/fusion/deploy/openpype/Config/menu.fu +++ b/openpype/hosts/fusion/deploy/openpype/Config/menu.fu @@ -10,7 +10,7 @@ Composition = { Execute = _Lua [=[ - local scriptPath = app:MapPath("OpenPype:../MenuScripts/menu.py") + local scriptPath = app:MapPath("OpenPype:../MenuScripts/launch_menu.py") if bmd.fileexists(scriptPath) == false then print("[OpenPype Error] Can't run file: " .. scriptPath) else diff --git a/openpype/hosts/fusion/deploy/openpype/fusion_shared.prefs b/openpype/hosts/fusion/deploy/openpype/fusion_shared.prefs index 1425f8c317..0035a38990 100644 --- a/openpype/hosts/fusion/deploy/openpype/fusion_shared.prefs +++ b/openpype/hosts/fusion/deploy/openpype/fusion_shared.prefs @@ -5,7 +5,7 @@ Global = { Map = { ["OpenPype:"] = "$(OPENPYPE_FUSION)/deploy/openpype", ["Config:"] = "UserPaths:Config;OpenPype:Config", - ["Scripts:"] = "UserPaths:Scripts;Reactor:System/Scripts;OpenPype:../Script", + ["Scripts:"] = "UserPaths:Scripts;Reactor:System/Scripts", }, }, Script = { From fe132f1d6aad13e15ca848d8a0a396189eaaa3c8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 5 Dec 2023 11:27:04 +0100 Subject: [PATCH 261/327] AYON: Prepare functions for newer ayon-python-api (#5997) * use kwargs to define filters in ayon api * use new variable for version entities * fix kwarg for product_ids --- openpype/client/server/entities.py | 34 +++++++++++++++--------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/openpype/client/server/entities.py b/openpype/client/server/entities.py index 75e58703be..75b5dc2cdd 100644 --- a/openpype/client/server/entities.py +++ b/openpype/client/server/entities.py @@ -80,8 +80,8 @@ def _get_subsets( for subset in con.get_products( project_name, - subset_ids, - subset_names, + product_ids=subset_ids, + product_names=subset_names, folder_ids=folder_ids, names_by_folder_ids=names_by_folder_ids, active=active, @@ -113,23 +113,23 @@ def _get_versions( queried_versions = con.get_versions( project_name, - version_ids, - subset_ids, - versions, - hero, - standard, - latest, + version_ids=version_ids, + product_ids=subset_ids, + versions=versions, + hero=hero, + standard=standard, + latest=latest, active=active, fields=fields ) - versions = [] + version_entities = [] hero_versions = [] for version in queried_versions: if version["version"] < 0: hero_versions.append(version) else: - versions.append(convert_v4_version_to_v3(version)) + version_entities.append(convert_v4_version_to_v3(version)) if hero_versions: subset_ids = set() @@ -159,9 +159,9 @@ def _get_versions( break conv_hero = convert_v4_version_to_v3(hero_version) conv_hero["version_id"] = version_id - versions.append(conv_hero) + version_entities.append(conv_hero) - return versions + return version_entities def get_asset_by_id(project_name, asset_id, fields=None): @@ -539,11 +539,11 @@ def get_representations( representations = con.get_representations( project_name, - representation_ids, - representation_names, - version_ids, - names_by_version_ids, - active, + representation_ids=representation_ids, + representation_names=representation_names, + version_ids=version_ids, + names_by_version_ids=names_by_version_ids, + active=active, fields=fields ) for representation in representations: From 9ae773708ef1bce901d5b0b576d23039e9654e8d Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 5 Dec 2023 18:50:06 +0800 Subject: [PATCH 262/327] conversion of the ayon settings --- openpype/settings/ayon_settings.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/openpype/settings/ayon_settings.py b/openpype/settings/ayon_settings.py index 6676e71a8e..e626368ad1 100644 --- a/openpype/settings/ayon_settings.py +++ b/openpype/settings/ayon_settings.py @@ -572,6 +572,27 @@ def _convert_maya_project_settings(ayon_settings, output): for item in viewport_options["pluginObjects"] } + ayon_playblast_settings = ayon_publish["ExtractPlayblast"]["profiles"] + if ayon_playblast_settings: + for setting in ayon_playblast_settings: + capture_preset = setting["capture_preset"] + display_options = capture_preset["DisplayOptions"] + for key in ("background", "backgroundBottom", "backgroundTop"): + display_options[key] = _convert_color(display_options[key]) + + for src_key, dst_key in ( + ("DisplayOptions", "Display Options"), + ("ViewportOptions", "Viewport Options"), + ("CameraOptions", "Camera Options"), + ): + capture_preset[dst_key] = capture_preset.pop(src_key) + + viewport_options = capture_preset["Viewport Options"] + viewport_options["pluginObjects"] = { + item["name"]: item["value"] + for item in viewport_options["pluginObjects"] + } + # Extract Camera Alembic bake attributes try: bake_attributes = json.loads( From a557b8b5e3fcd8d078c24fe981a054af49e99207 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 5 Dec 2023 13:04:32 +0100 Subject: [PATCH 263/327] Wrap: new integration (#5823) * Added Wrap to applications * Added icon * Added wrap to template pre hooks Needed to copy template as new workfile. Needed to open Wrap with workfile. --- openpype/hooks/pre_add_last_workfile_arg.py | 1 + openpype/hooks/pre_copy_template_workfile.py | 3 +- openpype/resources/app_icons/wrap.png | Bin 0 -> 1044 bytes .../applications/server/applications.json | 26 ++++++++++++++++++ server_addon/applications/server/settings.py | 2 ++ 5 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 openpype/resources/app_icons/wrap.png diff --git a/openpype/hooks/pre_add_last_workfile_arg.py b/openpype/hooks/pre_add_last_workfile_arg.py index 1418bc210b..6e255ae82a 100644 --- a/openpype/hooks/pre_add_last_workfile_arg.py +++ b/openpype/hooks/pre_add_last_workfile_arg.py @@ -27,6 +27,7 @@ class AddLastWorkfileToLaunchArgs(PreLaunchHook): "tvpaint", "substancepainter", "aftereffects", + "wrap" } launch_types = {LaunchTypes.local} diff --git a/openpype/hooks/pre_copy_template_workfile.py b/openpype/hooks/pre_copy_template_workfile.py index 2203ff4396..4d91d83c95 100644 --- a/openpype/hooks/pre_copy_template_workfile.py +++ b/openpype/hooks/pre_copy_template_workfile.py @@ -19,7 +19,8 @@ class CopyTemplateWorkfile(PreLaunchHook): # Before `AddLastWorkfileToLaunchArgs` order = 0 - app_groups = {"blender", "photoshop", "tvpaint", "aftereffects"} + app_groups = {"blender", "photoshop", "tvpaint", "aftereffects", + "wrap"} launch_types = {LaunchTypes.local} def execute(self): diff --git a/openpype/resources/app_icons/wrap.png b/openpype/resources/app_icons/wrap.png new file mode 100644 index 0000000000000000000000000000000000000000..34ae1d68ed9de874a612d94bf69804de2af01eea GIT binary patch literal 1044 zcmV+v1nc{WP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D1F1R6 zy`}G^1FGW7AMy2h&h=gpfo?ynGnqH}=C=7h=lPuToO7Q00sf;$fc<_y>~?$Fq=9WV z8zf0eo8E)drgsaxP0|TvNO~duy?W4UPxaTvWYGVul=|}v1oolVG){6v2megSYBTaE^{s;uk`(2u2!cz`G=Ex-!dg}?pv5~rg8j~2 zIB9aj4`mFxCvdQ~9vL)#Q!o$y{`6T5%(~Ij2gzVYR(>ID*KgsTF9bR9MDyj?A{v{T zu|+bV`fv^UY2plJ@gz#D%A`_)t>qQ)$CtFk;rT`678YZBX*uqW-pBB?3~zJ+k=K4S z`5e7{12E+j;N;o!aCp38#ZD@B=jCfKXIfH&Sp`K!xZ@ZRd&r67l=yJnF&t_C-2rVf-%Qfb~m*3Rk!p&X`GnDk__!JJ()~wFW+7N8c%tC8h2jZ{6F;egEPS|?- zX|<2wpq0310|+cEYv6~$IIi9vK%dJkO3YxcPBB$!b#B&%U`eOL=ha_hF1Da04*c~5 zLzA>x0&~*Ady^RR1`wfYu+MJ~Rlzx>_zb^6<goJcipBq*5y@Zfb53O=fNGO~Ki@`S{6pu20wc8I?*2dC%BLzNxMJ5{42?bzhl`Vj=@fhBqUgHbgB>MS5mVRLEKa=&mZ<`6 zuOBtXTi(j2Dka#GlMnl?KDwt$m*NBD4bP+bbUO@Y<N literal 0 HcmV?d00001 diff --git a/server_addon/applications/server/applications.json b/server_addon/applications/server/applications.json index f846b04215..825f50276a 100644 --- a/server_addon/applications/server/applications.json +++ b/server_addon/applications/server/applications.json @@ -1158,6 +1158,32 @@ } ] }, + "wrap": { + "enabled": true, + "label": "Wrap", + "icon": "{}/app_icons/wrap.png", + "host_name": "wrap", + "environment": "{\n \n}", + "variants": [ + { + "name": "2023", + "use_python_2": false, + "executables": { + "windows": [ + "c:\\Program Files\\Faceform\\Wrap 2023.10.2\\Wrap.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{\n \n}" + } + ] + }, "additional_apps": [] } } diff --git a/server_addon/applications/server/settings.py b/server_addon/applications/server/settings.py index 981d56c30f..224f999564 100644 --- a/server_addon/applications/server/settings.py +++ b/server_addon/applications/server/settings.py @@ -168,6 +168,8 @@ class ApplicationsSettings(BaseSettingsModel): default_factory=AppGroupWithPython, title="Substance Painter") unreal: AppGroup = Field( default_factory=AppGroupWithPython, title="Unreal Editor") + wrap: AppGroup = Field( + default_factory=AppGroupWithPython, title="Wrap") additional_apps: list[AdditionalAppGroup] = Field( default_factory=list, title="Additional Applications") From ad3a74709df1f22eb8be34f1a41bf3a9cf41a115 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 5 Dec 2023 14:18:39 +0100 Subject: [PATCH 264/327] Update openpype/hosts/aftereffects/api/README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondřej Samohel <33513211+antirotor@users.noreply.github.com> --- openpype/hosts/aftereffects/api/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/aftereffects/api/README.md b/openpype/hosts/aftereffects/api/README.md index 847b27ab53..9c4bad3689 100644 --- a/openpype/hosts/aftereffects/api/README.md +++ b/openpype/hosts/aftereffects/api/README.md @@ -18,7 +18,7 @@ ExManCmd /install {path to addon}/api/extension.zxp OR download [Anastasiy’s Extension Manager](https://install.anastasiy.com/) -`{pat to addon}` will be most likely in your AppData (on Windows, in your user data folder in Linux and MacOS.) +`{path to addon}` will be most likely in your AppData (on Windows, in your user data folder in Linux and MacOS.) ### Server From 5e59ffce81584f31eb3a1d7101d49a9dd3db1d59 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Tue, 5 Dec 2023 13:28:33 +0000 Subject: [PATCH 265/327] hou module should be within class code. (#5954) --- .../plugins/publish/submit_houdini_cache_deadline.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py b/openpype/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py index 2b6231e916..ada69575a8 100644 --- a/openpype/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py @@ -2,8 +2,6 @@ import os import getpass from datetime import datetime -import hou - import attr import pyblish.api from openpype.lib import ( @@ -141,6 +139,9 @@ class HoudiniCacheSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline return job_info def get_plugin_info(self): + # Not all hosts can import this module. + import hou + instance = self._instance version = hou.applicationVersionString() version = ".".join(version.split(".")[:2]) @@ -167,6 +168,9 @@ class HoudiniCacheSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline instance.data["toBeRenderedOn"] = "deadline" def get_rop_node(self, instance): + # Not all hosts can import this module. + import hou + rop = instance.data.get("instance_node") rop_node = hou.node(rop) From 3bc341b8ba5d877db9e9cb8a3c9eb616d1f21636 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 5 Dec 2023 22:01:18 +0800 Subject: [PATCH 266/327] make sure the processEvents not rushing into indefinite loop --- openpype/hosts/substancepainter/api/lib.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/substancepainter/api/lib.py b/openpype/hosts/substancepainter/api/lib.py index 2cd08f862e..7055c1f8ba 100644 --- a/openpype/hosts/substancepainter/api/lib.py +++ b/openpype/hosts/substancepainter/api/lib.py @@ -588,6 +588,7 @@ def prompt_new_file_with_mesh(mesh_filepath): # the file while not file_dialog.selectedFiles(): app.processEvents(QtCore.QEventLoop.ExcludeUserInputEvents, 1000) + continue print(f"Selected: {file_dialog.selectedFiles()}") # Set it again now we know the path is refreshed - without this From 61c97ef7a24c9fe49b82ec7d9948286943b69000 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 5 Dec 2023 15:49:45 +0100 Subject: [PATCH 267/327] update ayon python api to '1.0.0-rc.1' (#6002) --- .../vendor/python/common/ayon_api/_api.py | 2 +- .../python/common/ayon_api/constants.py | 11 + .../python/common/ayon_api/entity_hub.py | 35 +- .../vendor/python/common/ayon_api/graphql.py | 9 + .../python/common/ayon_api/graphql_queries.py | 74 ++- .../python/common/ayon_api/server_api.py | 494 +++++++++++++++--- .../vendor/python/common/ayon_api/utils.py | 26 +- .../vendor/python/common/ayon_api/version.py | 2 +- 8 files changed, 557 insertions(+), 96 deletions(-) diff --git a/openpype/vendor/python/common/ayon_api/_api.py b/openpype/vendor/python/common/ayon_api/_api.py index 9f89d3d59e..9d4fc697ae 100644 --- a/openpype/vendor/python/common/ayon_api/_api.py +++ b/openpype/vendor/python/common/ayon_api/_api.py @@ -374,7 +374,7 @@ def get_default_settings_variant(): """ con = get_server_api_connection() - return con.get_client_version() + return con.get_default_settings_variant() def set_default_settings_variant(variant): diff --git a/openpype/vendor/python/common/ayon_api/constants.py b/openpype/vendor/python/common/ayon_api/constants.py index eaeb77b607..c31940d93a 100644 --- a/openpype/vendor/python/common/ayon_api/constants.py +++ b/openpype/vendor/python/common/ayon_api/constants.py @@ -3,6 +3,10 @@ SERVER_URL_ENV_KEY = "AYON_SERVER_URL" SERVER_API_ENV_KEY = "AYON_API_KEY" SERVER_TIMEOUT_ENV_KEY = "AYON_SERVER_TIMEOUT" SERVER_RETRIES_ENV_KEY = "AYON_SERVER_RETRIES" +# Default variant used for settings +DEFAULT_VARIANT_ENV_KEY = "AYON_DEFAULT_SETTINGS_VARIANT" +# Default site id used for connection +SITE_ID_ENV_KEY = "AYON_SITE_ID" # Backwards compatibility SERVER_TOKEN_ENV_KEY = SERVER_API_ENV_KEY @@ -40,6 +44,7 @@ DEFAULT_PROJECT_FIELDS = { "code", "config", "createdAt", + "data", } # --- Folders --- @@ -52,6 +57,7 @@ DEFAULT_FOLDER_FIELDS = { "parentId", "active", "thumbnailId", + "data", } # --- Tasks --- @@ -63,6 +69,7 @@ DEFAULT_TASK_FIELDS = { "folderId", "active", "assignees", + "data", } # --- Products --- @@ -72,6 +79,7 @@ DEFAULT_PRODUCT_FIELDS = { "folderId", "active", "productType", + "data", } # --- Versions --- @@ -86,6 +94,7 @@ DEFAULT_VERSION_FIELDS = { "thumbnailId", "createdAt", "updatedAt", + "data", } # --- Representations --- @@ -96,6 +105,7 @@ DEFAULT_REPRESENTATION_FIELDS = { "createdAt", "active", "versionId", + "data", } REPRESENTATION_FILES_FIELDS = { @@ -119,6 +129,7 @@ DEFAULT_WORKFILE_INFO_FIELDS = { "thumbnailId", "updatedAt", "updatedBy", + "data", } DEFAULT_EVENT_FIELDS = { diff --git a/openpype/vendor/python/common/ayon_api/entity_hub.py b/openpype/vendor/python/common/ayon_api/entity_hub.py index b9b017bac5..61d740fe57 100644 --- a/openpype/vendor/python/common/ayon_api/entity_hub.py +++ b/openpype/vendor/python/common/ayon_api/entity_hub.py @@ -36,11 +36,20 @@ class EntityHub(object): """ def __init__( - self, project_name, connection=None, allow_data_changes=False + self, project_name, connection=None, allow_data_changes=None ): if not connection: connection = get_server_api_connection() + major, minor, patch, _, _ = connection.server_version_tuple + path_start_with_slash = True + if (major, minor) < (0, 6): + path_start_with_slash = False + + if allow_data_changes is None: + allow_data_changes = connection.graphql_allows_data_in_query + self._connection = connection + self._path_start_with_slash = path_start_with_slash self._project_name = project_name self._entities_by_id = {} @@ -65,6 +74,18 @@ class EntityHub(object): return self._allow_data_changes + @property + def path_start_with_slash(self): + """Folder path should start with slash. + + This changed in 0.6.x server version. + + Returns: + bool: Path starts with slash. + """ + + return self._path_start_with_slash + @property def project_name(self): """Project name which is maintained by hub. @@ -2419,10 +2440,13 @@ class FolderEntity(BaseEntity): if self._path is None: parent = self.parent - path = self.name if parent.entity_type == "folder": parent_path = parent.path - path = "/".join([parent_path, path]) + path = "/".join([parent_path, self.name]) + elif self._entity_hub.path_start_with_slash: + path = "/{}".format(self.name) + else: + path = self.name self._path = path return self._path @@ -2525,7 +2549,10 @@ class FolderEntity(BaseEntity): if self.thumbnail_id is not UNKNOWN_VALUE: output["thumbnailId"] = self.thumbnail_id - if self._entity_hub.allow_data_changes: + if ( + self._entity_hub.allow_data_changes + and self._data is not UNKNOWN_VALUE + ): output["data"] = self._data return output diff --git a/openpype/vendor/python/common/ayon_api/graphql.py b/openpype/vendor/python/common/ayon_api/graphql.py index 854f207a00..2594b45966 100644 --- a/openpype/vendor/python/common/ayon_api/graphql.py +++ b/openpype/vendor/python/common/ayon_api/graphql.py @@ -202,6 +202,15 @@ class GraphQlQuery: self._variables[key]["value"] = value + def get_variable_keys(self): + """Get all variable keys. + + Returns: + set[str]: Variable keys. + """ + + return set(self._variables.keys()) + def get_variables_values(self): """Calculate variable values used that should be used in query. diff --git a/openpype/vendor/python/common/ayon_api/graphql_queries.py b/openpype/vendor/python/common/ayon_api/graphql_queries.py index cedb3ed2ac..f121a5649f 100644 --- a/openpype/vendor/python/common/ayon_api/graphql_queries.py +++ b/openpype/vendor/python/common/ayon_api/graphql_queries.py @@ -175,10 +175,15 @@ def folders_graphql_query(fields): folder_ids_var = query.add_variable("folderIds", "[String!]") parent_folder_ids_var = query.add_variable("parentFolderIds", "[String!]") folder_paths_var = query.add_variable("folderPaths", "[String!]") + folder_path_regex_var = query.add_variable("folderPathRegex", "String!") folder_names_var = query.add_variable("folderNames", "[String!]") folder_types_var = query.add_variable("folderTypes", "[String!]") - statuses_var = query.add_variable("folderStatuses", "[String!]") has_products_var = query.add_variable("folderHasProducts", "Boolean!") + has_tasks_var = query.add_variable("folderHasTasks", "Boolean!") + has_links_var = query.add_variable("folderHasLinks", "HasLinksFilter") + has_children_var = query.add_variable("folderHasChildren", "Boolean!") + statuses_var = query.add_variable("folderStatuses", "[String!]") + tags_var = query.add_variable("folderTags", "[String!]") project_field = query.add_field("project") project_field.set_filter("name", project_name_var) @@ -188,9 +193,14 @@ def folders_graphql_query(fields): folders_field.set_filter("parentIds", parent_folder_ids_var) folders_field.set_filter("names", folder_names_var) folders_field.set_filter("paths", folder_paths_var) + folders_field.set_filter("pathEx", folder_path_regex_var) folders_field.set_filter("folderTypes", folder_types_var) folders_field.set_filter("statuses", statuses_var) + folders_field.set_filter("tags", tags_var) folders_field.set_filter("hasProducts", has_products_var) + folders_field.set_filter("hasTasks", has_tasks_var) + folders_field.set_filter("hasLinks", has_links_var) + folders_field.set_filter("hasChildren", has_children_var) nested_fields = fields_to_dict(fields) add_links_fields(folders_field, nested_fields) @@ -218,6 +228,10 @@ def tasks_graphql_query(fields): task_names_var = query.add_variable("taskNames", "[String!]") task_types_var = query.add_variable("taskTypes", "[String!]") folder_ids_var = query.add_variable("folderIds", "[String!]") + assignees_any_var = query.add_variable("taskAssigneesAny", "[String!]") + assignees_all_var = query.add_variable("taskAssigneesAll", "[String!]") + statuses_var = query.add_variable("taskStatuses", "[String!]") + tags_var = query.add_variable("taskTags", "[String!]") project_field = query.add_field("project") project_field.set_filter("name", project_name_var) @@ -228,6 +242,10 @@ def tasks_graphql_query(fields): tasks_field.set_filter("names", task_names_var) tasks_field.set_filter("taskTypes", task_types_var) tasks_field.set_filter("folderIds", folder_ids_var) + tasks_field.set_filter("assigneesAny", assignees_any_var) + tasks_field.set_filter("assignees", assignees_all_var) + tasks_field.set_filter("statuses", statuses_var) + tasks_field.set_filter("tags", tags_var) nested_fields = fields_to_dict(fields) add_links_fields(tasks_field, nested_fields) @@ -256,7 +274,10 @@ def products_graphql_query(fields): product_names_var = query.add_variable("productNames", "[String!]") folder_ids_var = query.add_variable("folderIds", "[String!]") product_types_var = query.add_variable("productTypes", "[String!]") - statuses_var = query.add_variable("statuses", "[String!]") + product_name_regex_var = query.add_variable("productNameRegex", "String!") + product_path_regex_var = query.add_variable("productPathRegex", "String!") + statuses_var = query.add_variable("productStatuses.", "[String!]") + tags_var = query.add_variable("productTags.", "[String!]") project_field = query.add_field("project") project_field.set_filter("name", project_name_var) @@ -267,6 +288,9 @@ def products_graphql_query(fields): products_field.set_filter("folderIds", folder_ids_var) products_field.set_filter("productTypes", product_types_var) products_field.set_filter("statuses", statuses_var) + products_field.set_filter("tags", tags_var) + products_field.set_filter("nameEx", product_name_regex_var) + products_field.set_filter("pathEx", product_path_regex_var) nested_fields = fields_to_dict(set(fields)) add_links_fields(products_field, nested_fields) @@ -299,24 +323,28 @@ def versions_graphql_query(fields): hero_or_latest_only_var = query.add_variable( "heroOrLatestOnly", "Boolean" ) + statuses_var = query.add_variable("versionStatuses", "[String!]") + tags_var = query.add_variable("versionTags", "[String!]") project_field = query.add_field("project") project_field.set_filter("name", project_name_var) - products_field = project_field.add_field_with_edges("versions") - products_field.set_filter("ids", version_ids_var) - products_field.set_filter("productIds", product_ids_var) - products_field.set_filter("versions", versions_var) - products_field.set_filter("heroOnly", hero_only_var) - products_field.set_filter("latestOnly", latest_only_var) - products_field.set_filter("heroOrLatestOnly", hero_or_latest_only_var) + versions_field = project_field.add_field_with_edges("versions") + versions_field.set_filter("ids", version_ids_var) + versions_field.set_filter("productIds", product_ids_var) + versions_field.set_filter("versions", versions_var) + versions_field.set_filter("heroOnly", hero_only_var) + versions_field.set_filter("latestOnly", latest_only_var) + versions_field.set_filter("heroOrLatestOnly", hero_or_latest_only_var) + versions_field.set_filter("statuses", statuses_var) + versions_field.set_filter("tags", tags_var) nested_fields = fields_to_dict(set(fields)) - add_links_fields(products_field, nested_fields) + add_links_fields(versions_field, nested_fields) query_queue = collections.deque() for key, value in nested_fields.items(): - query_queue.append((key, value, products_field)) + query_queue.append((key, value, versions_field)) while query_queue: item = query_queue.popleft() @@ -337,6 +365,13 @@ def representations_graphql_query(fields): repre_ids_var = query.add_variable("representationIds", "[String!]") repre_names_var = query.add_variable("representationNames", "[String!]") version_ids_var = query.add_variable("versionIds", "[String!]") + has_links_var = query.add_variable("representationHasLinks", "HasLinksFilter") + statuses_var = query.add_variable( + "representationStatuses", "[String!]" + ) + tags_var = query.add_variable( + "representationTags", "[String!]" + ) project_field = query.add_field("project") project_field.set_filter("name", project_name_var) @@ -345,6 +380,9 @@ def representations_graphql_query(fields): repres_field.set_filter("ids", repre_ids_var) repres_field.set_filter("versionIds", version_ids_var) repres_field.set_filter("names", repre_names_var) + repres_field.set_filter("hasLinks", has_links_var) + repres_field.set_filter("statuses", statuses_var) + repres_field.set_filter("tags", tags_var) nested_fields = fields_to_dict(set(fields)) add_links_fields(repres_field, nested_fields) @@ -412,6 +450,10 @@ def workfiles_info_graphql_query(fields): workfiles_info_ids = query.add_variable("workfileIds", "[String!]") task_ids_var = query.add_variable("taskIds", "[String!]") paths_var = query.add_variable("paths", "[String!]") + path_regex_var = query.add_variable("workfilePathRegex", "String!") + has_links_var = query.add_variable("workfilehasLinks", "HasLinksFilter") + statuses_var = query.add_variable("workfileStatuses", "[String!]") + tags_var = query.add_variable("workfileTags", "[String!]") project_field = query.add_field("project") project_field.set_filter("name", project_name_var) @@ -420,6 +462,10 @@ def workfiles_info_graphql_query(fields): workfiles_field.set_filter("ids", workfiles_info_ids) workfiles_field.set_filter("taskIds", task_ids_var) workfiles_field.set_filter("paths", paths_var) + workfiles_field.set_filter("pathEx", path_regex_var) + workfiles_field.set_filter("hasLinks", has_links_var) + workfiles_field.set_filter("statuses", statuses_var) + workfiles_field.set_filter("tags", tags_var) nested_fields = fields_to_dict(set(fields)) add_links_fields(workfiles_field, nested_fields) @@ -447,6 +493,9 @@ def events_graphql_query(fields): states_var = query.add_variable("eventStates", "[String!]") users_var = query.add_variable("eventUsers", "[String!]") include_logs_var = query.add_variable("includeLogsFilter", "Boolean!") + has_children_var = query.add_variable("hasChildrenFilter", "Boolean!") + newer_than_var = query.add_variable("newerThanFilter", "String!") + older_than_var = query.add_variable("olderThanFilter", "String!") events_field = query.add_field_with_edges("events") events_field.set_filter("topics", topics_var) @@ -454,6 +503,9 @@ def events_graphql_query(fields): events_field.set_filter("states", states_var) events_field.set_filter("users", users_var) events_field.set_filter("includeLogs", include_logs_var) + events_field.set_filter("hasChildren", has_children_var) + events_field.set_filter("newerThan", newer_than_var) + events_field.set_filter("olderThan", older_than_var) nested_fields = fields_to_dict(set(fields)) diff --git a/openpype/vendor/python/common/ayon_api/server_api.py b/openpype/vendor/python/common/ayon_api/server_api.py index 3bac59c192..e4e7146279 100644 --- a/openpype/vendor/python/common/ayon_api/server_api.py +++ b/openpype/vendor/python/common/ayon_api/server_api.py @@ -9,6 +9,9 @@ import platform import copy import uuid from contextlib import contextmanager + +import six + try: from http import HTTPStatus except ImportError: @@ -27,7 +30,6 @@ except ImportError: from json import JSONDecodeError as RequestsJSONDecodeError from .constants import ( - SERVER_TIMEOUT_ENV_KEY, SERVER_RETRIES_ENV_KEY, DEFAULT_PRODUCT_TYPE_FIELDS, DEFAULT_PROJECT_FIELDS, @@ -76,8 +78,11 @@ from .utils import ( create_dependency_package_basename, ThumbnailContent, get_default_timeout, + get_default_settings_variant, + get_default_site_id, ) +_PLACEHOLDER = object() PatternType = type(re.compile("")) JSONDecodeError = getattr(json, "JSONDecodeError", ValueError) # This should be collected from server schema @@ -176,6 +181,11 @@ class RestApiResponse(object): return self.status def raise_for_status(self, message=None): + if self._response is None: + if self._data and self._data.get("detail"): + raise ServerError(self._data["detail"]) + raise ValueError("Response is not available.") + try: self._response.raise_for_status() except requests.exceptions.HTTPError as exc: @@ -353,12 +363,16 @@ class ServerAPI(object): max_retries (Optional[int]): Number of retries for requests. """ _default_max_retries = 3 + # 1 MB chunk by default + # TODO find out if these are reasonable default value + default_download_chunk_size = 1024 * 1024 + default_upload_chunk_size = 1024 * 1024 def __init__( self, base_url, token=None, - site_id=None, + site_id=_PLACEHOLDER, client_version=None, default_settings_variant=None, sender=None, @@ -377,11 +391,14 @@ class ServerAPI(object): self._graphql_url = "{}/graphql".format(base_url) self._log = None self._access_token = token + # Allow to have 'site_id' to 'None' + if site_id is _PLACEHOLDER: + site_id = get_default_site_id() self._site_id = site_id self._client_version = client_version self._default_settings_variant = ( default_settings_variant - or "production" + or get_default_settings_variant() ) self._sender = sender @@ -411,6 +428,8 @@ class ServerAPI(object): self._server_version = None self._server_version_tuple = None + self._graphql_allows_data_in_query = None + self._session = None self._base_functions_mapping = { @@ -936,6 +955,26 @@ class ServerAPI(object): server_version = property(get_server_version) server_version_tuple = property(get_server_version_tuple) + @property + def graphql_allows_data_in_query(self): + """GraphlQl query can support 'data' field. + + This applies only to project hierarchy entities 'project', 'folder', + 'task', 'product', 'version' and 'representation'. Others like 'user' + still require to use rest api to access 'data'. + + Returns: + bool: True if server supports 'data' field in GraphQl query. + """ + + if self._graphql_allows_data_in_query is None: + major, minor, patch, _, _ = self.server_version_tuple + graphql_allows_data_in_query = True + if (major, minor, patch) < (0, 5, 5): + graphql_allows_data_in_query = False + self._graphql_allows_data_in_query = graphql_allows_data_in_query + return self._graphql_allows_data_in_query + def _get_user_info(self): if self._access_token is None: return None @@ -1279,6 +1318,9 @@ class ServerAPI(object): states=None, users=None, include_logs=None, + has_children=None, + newer_than=None, + older_than=None, fields=None ): """Get events from server with filtering options. @@ -1294,6 +1336,12 @@ class ServerAPI(object): users (Optional[Iterable[str]]): Filtering by users who created/triggered an event. include_logs (Optional[bool]): Query also log events. + has_children (Optional[bool]): Event is with/without children + events. If 'None' then all events are returned, default. + newer_than (Optional[str]): Return only events newer than given + iso datetime string. + older_than (Optional[str]): Return only events older than given + iso datetime string. fields (Optional[Iterable[str]]): Fields that should be received for each event. @@ -1330,6 +1378,15 @@ class ServerAPI(object): include_logs = False filters["includeLogsFilter"] = include_logs + if has_children is not None: + filters["hasChildrenFilter"] = has_children + + if newer_than is not None: + filters["newerThanFilter"] = newer_than + + if older_than is not None: + filters["olderThanFilter"] = older_than + if not fields: fields = self.get_default_fields_for_type("event") @@ -1512,8 +1569,9 @@ class ServerAPI(object): "sourceTopic": source_topic, "targetTopic": target_topic, "sender": sender, - "maxRetries": max_retries, } + if max_retries is not None: + kwargs["maxRetries"] = max_retries if sequential is not None: kwargs["sequential"] = sequential if description is not None: @@ -1560,6 +1618,10 @@ class ServerAPI(object): download happens in thread and other thread want to catch changes over time. + Todos: + Use retries and timeout. + Return RestApiResponse. + Args: endpoint (str): Endpoint or URL to file that should be downloaded. filepath (str): Path where file will be downloaded. @@ -1570,8 +1632,7 @@ class ServerAPI(object): """ if not chunk_size: - # 1 MB chunk by default - chunk_size = 1024 * 1024 + chunk_size = self.default_download_chunk_size if endpoint.startswith(self._base_url): url = endpoint @@ -1598,33 +1659,93 @@ class ServerAPI(object): progress.set_transfer_done() return progress - def _upload_file(self, url, filepath, progress, request_type=None): + @staticmethod + def _upload_chunks_iter(file_stream, progress, chunk_size): + """Generator that yields chunks of file. + + Args: + file_stream (io.BinaryIO): Byte stream. + progress (TransferProgress): Object to track upload progress. + chunk_size (int): Size of chunks that are uploaded at once. + + Yields: + bytes: Chunk of file. + """ + + # Get size of file + file_stream.seek(0, io.SEEK_END) + size = file_stream.tell() + file_stream.seek(0) + # Set content size to progress object + progress.set_content_size(size) + + while True: + chunk = file_stream.read(chunk_size) + if not chunk: + break + progress.add_transferred_chunk(len(chunk)) + yield chunk + + def _upload_file( + self, + url, + filepath, + progress, + request_type=None, + chunk_size=None, + **kwargs + ): + """ + + Args: + url (str): Url where file will be uploaded. + filepath (str): Source filepath. + progress (TransferProgress): Object that gives ability to track + progress. + request_type (Optional[RequestType]): Type of request that will + be used. Default is PUT. + chunk_size (Optional[int]): Size of chunks that are uploaded + at once. + **kwargs (Any): Additional arguments that will be passed + to request function. + + Returns: + RestApiResponse: Server response. + """ + if request_type is None: request_type = RequestTypes.put - kwargs = {} + if self._session is None: - kwargs["headers"] = self.get_headers() + headers = kwargs.setdefault("headers", {}) + for key, value in self.get_headers().items(): + if key not in headers: + headers[key] = value post_func = self._base_functions_mapping[request_type] else: post_func = self._session_functions_mapping[request_type] + if not chunk_size: + chunk_size = self.default_upload_chunk_size + with open(filepath, "rb") as stream: - stream.seek(0, io.SEEK_END) - size = stream.tell() - stream.seek(0) - progress.set_content_size(size) - response = post_func(url, data=stream, **kwargs) + response = post_func( + url, + data=self._upload_chunks_iter(stream, progress, chunk_size), + **kwargs + ) + response.raise_for_status() - progress.set_transferred_size(size) return response def upload_file( - self, endpoint, filepath, progress=None, request_type=None + self, endpoint, filepath, progress=None, request_type=None, **kwargs ): """Upload file to server. Todos: - Uploading with more detailed progress. + Use retries and timeout. + Return RestApiResponse. Args: endpoint (str): Endpoint or url where file will be uploaded. @@ -1633,6 +1754,8 @@ class ServerAPI(object): to track upload progress. request_type (Optional[RequestType]): Type of request that will be used to upload file. + **kwargs (Any): Additional arguments that will be passed + to request function. Returns: requests.Response: Response object. @@ -1654,7 +1777,9 @@ class ServerAPI(object): progress.set_started() try: - return self._upload_file(url, filepath, progress, request_type) + return self._upload_file( + url, filepath, progress, request_type, **kwargs + ) except Exception as exc: progress.set_failed(str(exc)) @@ -1877,31 +2002,45 @@ class ServerAPI(object): return set(DEFAULT_EVENT_FIELDS) if entity_type == "project": - entity_type_defaults = DEFAULT_PROJECT_FIELDS + entity_type_defaults = set(DEFAULT_PROJECT_FIELDS) + if not self.graphql_allows_data_in_query: + entity_type_defaults.discard("data") elif entity_type == "folder": - entity_type_defaults = DEFAULT_FOLDER_FIELDS + entity_type_defaults = set(DEFAULT_FOLDER_FIELDS) + if not self.graphql_allows_data_in_query: + entity_type_defaults.discard("data") elif entity_type == "task": - entity_type_defaults = DEFAULT_TASK_FIELDS + entity_type_defaults = set(DEFAULT_TASK_FIELDS) + if not self.graphql_allows_data_in_query: + entity_type_defaults.discard("data") elif entity_type == "product": - entity_type_defaults = DEFAULT_PRODUCT_FIELDS + entity_type_defaults = set(DEFAULT_PRODUCT_FIELDS) + if not self.graphql_allows_data_in_query: + entity_type_defaults.discard("data") elif entity_type == "version": - entity_type_defaults = DEFAULT_VERSION_FIELDS + entity_type_defaults = set(DEFAULT_VERSION_FIELDS) + if not self.graphql_allows_data_in_query: + entity_type_defaults.discard("data") elif entity_type == "representation": entity_type_defaults = ( DEFAULT_REPRESENTATION_FIELDS | REPRESENTATION_FILES_FIELDS ) + if not self.graphql_allows_data_in_query: + entity_type_defaults.discard("data") elif entity_type == "productType": - entity_type_defaults = DEFAULT_PRODUCT_TYPE_FIELDS + entity_type_defaults = set(DEFAULT_PRODUCT_TYPE_FIELDS) elif entity_type == "workfile": - entity_type_defaults = DEFAULT_WORKFILE_INFO_FIELDS + entity_type_defaults = set(DEFAULT_WORKFILE_INFO_FIELDS) + if not self.graphql_allows_data_in_query: + entity_type_defaults.discard("data") elif entity_type == "user": entity_type_defaults = set(DEFAULT_USER_FIELDS) @@ -3549,8 +3688,14 @@ class ServerAPI(object): folder_names=None, folder_types=None, parent_ids=None, + folder_path_regex=None, + has_products=None, + has_tasks=None, + has_children=None, statuses=None, + tags=None, active=True, + has_links=None, fields=None, own_attributes=False ): @@ -3574,10 +3719,22 @@ class ServerAPI(object): for filtering. parent_ids (Optional[Iterable[str]]): Ids of folder parents. Use 'None' if folder is direct child of project. + folder_path_regex (Optional[str]): Folder path regex used + for filtering. + has_products (Optional[bool]): Filter folders with/without + products. Ignored when None, default behavior. + has_tasks (Optional[bool]): Filter folders with/without + tasks. Ignored when None, default behavior. + has_children (Optional[bool]): Filter folders with/without + children. Ignored when None, default behavior. statuses (Optional[Iterable[str]]): Folder statuses used for filtering. + tags (Optional[Iterable[str]]): Folder tags used + for filtering. active (Optional[bool]): Filter active/inactive folders. Both are returned if is set to None. + has_links (Optional[Literal[IN, OUT, ANY]]): Filter + representations with IN/OUT/ANY links. fields (Optional[Iterable[str]]): Fields to be queried for folder. All possible folder fields are returned if 'None' is passed. @@ -3624,6 +3781,12 @@ class ServerAPI(object): return filters["folderStatuses"] = list(statuses) + if tags is not None: + tags = set(tags) + if not tags: + return + filters["folderTags"] = list(tags) + if parent_ids is not None: parent_ids = set(parent_ids) if not parent_ids: @@ -3644,6 +3807,21 @@ class ServerAPI(object): filters["parentFolderIds"] = list(parent_ids) + if folder_path_regex is not None: + filters["folderPathRegex"] = folder_path_regex + + if has_products is not None: + filters["folderHasProducts"] = has_products + + if has_tasks is not None: + filters["folderHasTasks"] = has_tasks + + if has_links is not None: + filters["folderHasLinks"] = has_links.upper() + + if has_children is not None: + filters["folderHasChildren"] = has_children + if not fields: fields = self.get_default_fields_for_type("folder") else: @@ -3653,7 +3831,7 @@ class ServerAPI(object): fields |= self.get_attributes_fields_for_type("folder") use_rest = False - if "data" in fields: + if "data" in fields and not self.graphql_allows_data_in_query: use_rest = True fields = {"id"} @@ -3674,6 +3852,8 @@ class ServerAPI(object): if use_rest: folder = self.get_rest_folder(project_name, folder["id"]) + else: + self._convert_entity_data(folder) if own_attributes: fill_own_attribs(folder) @@ -3826,6 +4006,10 @@ class ServerAPI(object): task_names=None, task_types=None, folder_ids=None, + assignees=None, + assignees_all=None, + statuses=None, + tags=None, active=True, fields=None, own_attributes=False @@ -3839,6 +4023,16 @@ class ServerAPI(object): task_types (Iterable[str]): Task types used for filtering. folder_ids (Iterable[str]): Ids of task parents. Use 'None' if folder is direct child of project. + assignees (Optional[Iterable[str]]): Task assignees used for + filtering. All tasks with any of passed assignees are + returned. + assignees_all (Optional[Iterable[str]]): Task assignees used + for filtering. Task must have all of passed assignees to be + returned. + statuses (Optional[Iterable[str]]): Task statuses used for + filtering. + tags (Optional[Iterable[str]]): Task tags used for + filtering. active (Optional[bool]): Filter active/inactive tasks. Both are returned if is set to None. fields (Optional[Iterable[str]]): Fields to be queried for @@ -3882,6 +4076,30 @@ class ServerAPI(object): return filters["folderIds"] = list(folder_ids) + if assignees is not None: + assignees = set(assignees) + if not assignees: + return + filters["taskAssigneesAny"] = list(assignees) + + if assignees_all is not None: + assignees_all = set(assignees_all) + if not assignees_all: + return + filters["taskAssigneesAll"] = list(assignees_all) + + if statuses is not None: + statuses = set(statuses) + if not statuses: + return + filters["taskStatuses"] = list(statuses) + + if tags is not None: + tags = set(tags) + if not tags: + return + filters["taskTags"] = list(tags) + if not fields: fields = self.get_default_fields_for_type("task") else: @@ -3891,7 +4109,7 @@ class ServerAPI(object): fields |= self.get_attributes_fields_for_type("task") use_rest = False - if "data" in fields: + if "data" in fields and not self.graphql_allows_data_in_query: use_rest = True fields = {"id"} @@ -3912,6 +4130,8 @@ class ServerAPI(object): if use_rest: task = self.get_rest_task(project_name, task["id"]) + else: + self._convert_entity_data(task) if own_attributes: fill_own_attribs(task) @@ -3992,6 +4212,8 @@ class ServerAPI(object): if use_rest: product = self.get_rest_product(project_name, product["id"]) + else: + self._convert_entity_data(product) if own_attributes: fill_own_attribs(product) @@ -4005,8 +4227,11 @@ class ServerAPI(object): product_names=None, folder_ids=None, product_types=None, - statuses=None, + product_name_regex=None, + product_path_regex=None, names_by_folder_ids=None, + statuses=None, + tags=None, active=True, fields=None, own_attributes=False @@ -4026,10 +4251,15 @@ class ServerAPI(object): Use 'None' if folder is direct child of project. product_types (Optional[Iterable[str]]): Product types used for filtering. - statuses (Optional[Iterable[str]]): Product statuses used for - filtering. + product_name_regex (Optional[str]): Filter products by name regex. + product_path_regex (Optional[str]): Filter products by path regex. + Path starts with folder path and ends with product name. names_by_folder_ids (Optional[dict[str, Iterable[str]]]): Product name filtering by folder id. + statuses (Optional[Iterable[str]]): Product statuses used + for filtering. + tags (Optional[Iterable[str]]): Product tags used + for filtering. active (Optional[bool]): Filter active/inactive products. Both are returned if is set to None. fields (Optional[Iterable[str]]): Fields to be queried for @@ -4045,11 +4275,7 @@ class ServerAPI(object): if not project_name: return - if product_ids is not None: - product_ids = set(product_ids) - if not product_ids: - return - + # Prepare these filters before 'name_by_filter_ids' filter filter_product_names = None if product_names is not None: filter_product_names = set(product_names) @@ -4062,18 +4288,6 @@ class ServerAPI(object): if not filter_folder_ids: return - filter_product_types = None - if product_types is not None: - filter_product_types = set(product_types) - if not filter_product_types: - return - - filter_statuses = None - if statuses is not None: - filter_statuses = set(statuses) - if not filter_statuses: - return - # This will disable 'folder_ids' and 'product_names' filters # - maybe could be enhanced in future? if names_by_folder_ids is not None: @@ -4098,7 +4312,7 @@ class ServerAPI(object): fields = self.get_default_fields_for_type("product") use_rest = False - if "data" in fields: + if "data" in fields and not self.graphql_allows_data_in_query: use_rest = True fields = {"id"} @@ -4117,21 +4331,43 @@ class ServerAPI(object): filters = { "projectName": project_name } + if filter_folder_ids: filters["folderIds"] = list(filter_folder_ids) - if filter_product_types: - filters["productTypes"] = list(filter_product_types) - - if filter_statuses: - filters["statuses"] = list(filter_statuses) - - if product_ids: - filters["productIds"] = list(product_ids) - if filter_product_names: filters["productNames"] = list(filter_product_names) + if product_ids is not None: + product_ids = set(product_ids) + if not product_ids: + return + filters["productIds"] = list(product_ids) + + if product_types is not None: + product_types = set(product_types) + if not product_types: + return + filters["productTypes"] = list(product_types) + + if statuses is not None: + statuses = set(statuses) + if not statuses: + return + filters["productStatuses"] = list(statuses) + + if tags is not None: + tags = set(tags) + if not tags: + return + filters["productTags"] = list(tags) + + if product_name_regex: + filters["productNameRegex"] = product_name_regex + + if product_path_regex: + filters["productPathRegex"] = product_path_regex + query = products_graphql_query(fields) for attr, filter_value in filters.items(): query.set_variable_value(attr, filter_value) @@ -4323,6 +4559,8 @@ class ServerAPI(object): hero=True, standard=True, latest=None, + statuses=None, + tags=None, active=True, fields=None, own_attributes=False @@ -4342,6 +4580,10 @@ class ServerAPI(object): latest (Optional[bool]): Return only latest version of standard versions. This can be combined only with 'standard' attribute set to True. + statuses (Optional[Iterable[str]]): Representation statuses used + for filtering. + tags (Optional[Iterable[str]]): Representation tags used + for filtering. active (Optional[bool]): Receive active/inactive entities. Both are returned when 'None' is passed. fields (Optional[Iterable[str]]): Fields to be queried @@ -4366,7 +4608,7 @@ class ServerAPI(object): fields |= {"id", "version"} use_rest = False - if "data" in fields: + if "data" in fields and not self.graphql_allows_data_in_query: use_rest = True fields = {"id"} @@ -4398,6 +4640,18 @@ class ServerAPI(object): return filters["versions"] = list(versions) + if statuses is not None: + statuses = set(statuses) + if not statuses: + return + filters["versionStatuses"] = list(statuses) + + if tags is not None: + tags = set(tags) + if not tags: + return + filters["versionTags"] = list(tags) + if not hero and not standard: return @@ -4446,6 +4700,8 @@ class ServerAPI(object): version = self.get_rest_version( project_name, version["id"] ) + else: + self._convert_entity_data(version) if own_attributes: fill_own_attribs(version) @@ -4652,6 +4908,10 @@ class ServerAPI(object): dict[str, dict[str, Any]]: Last versions by product id. """ + if fields: + fields = set(fields) + fields.add("productId") + versions = self.get_versions( project_name, product_ids=product_ids, @@ -4661,7 +4921,7 @@ class ServerAPI(object): own_attributes=own_attributes ) return { - version["parent"]: version + version["productId"]: version for version in versions } @@ -4775,6 +5035,23 @@ class ServerAPI(object): ) return latest_version["id"] == version_id + def _representation_conversion(self, representation): + if "context" in representation: + orig_context = representation["context"] + context = {} + if orig_context and orig_context != "null": + context = json.loads(orig_context) + representation["context"] = context + + repre_files = representation.get("files") + if not repre_files: + return + + for repre_file in repre_files: + repre_file_size = repre_file.get("size") + if repre_file_size is not None: + repre_file["size"] = int(repre_file["size"]) + def get_representations( self, project_name, @@ -4782,7 +5059,10 @@ class ServerAPI(object): representation_names=None, version_ids=None, names_by_version_ids=None, + statuses=None, + tags=None, active=True, + has_links=None, fields=None, own_attributes=False ): @@ -4804,8 +5084,14 @@ class ServerAPI(object): names_by_version_ids (Optional[bool]): Find representations by names and version ids. This filter discard all other filters. + statuses (Optional[Iterable[str]]): Representation statuses used + for filtering. + tags (Optional[Iterable[str]]): Representation tags used + for filtering. active (Optional[bool]): Receive active/inactive entities. Both are returned when 'None' is passed. + has_links (Optional[Literal[IN, OUT, ANY]]): Filter + representations with IN/OUT/ANY links. fields (Optional[Iterable[str]]): Fields to be queried for representation. All possible fields are returned if 'None' is passed. @@ -4822,10 +5108,12 @@ class ServerAPI(object): fields = set(fields) if "attrib" in fields: fields.remove("attrib") - fields |= self.get_attributes_fields_for_type("representation") + fields |= self.get_attributes_fields_for_type( + "representation" + ) use_rest = False - if "data" in fields: + if "data" in fields and not self.graphql_allows_data_in_query: use_rest = True fields = {"id"} @@ -4874,6 +5162,21 @@ class ServerAPI(object): if representaion_names_filter: filters["representationNames"] = list(representaion_names_filter) + if statuses is not None: + statuses = set(statuses) + if not statuses: + return + filters["representationStatuses"] = list(statuses) + + if tags is not None: + tags = set(tags) + if not tags: + return + filters["representationTags"] = list(tags) + + if has_links is not None: + filters["representationHasLinks"] = has_links.upper() + query = representations_graphql_query(fields) for attr, filter_value in filters.items(): @@ -4888,13 +5191,10 @@ class ServerAPI(object): repre = self.get_rest_representation( project_name, repre["id"] ) + else: + self._convert_entity_data(repre) - if "context" in repre: - orig_context = repre["context"] - context = {} - if orig_context and orig_context != "null": - context = json.loads(orig_context) - repre["context"] = context + self._representation_conversion(repre) if own_attributes: fill_own_attribs(repre) @@ -5007,6 +5307,9 @@ class ServerAPI(object): version = repre.pop("version") product = version.pop("product") folder = product.pop("folder") + self._convert_entity_data(version) + self._convert_entity_data(product) + self._convert_entity_data(folder) output[repre_id] = RepresentationParents( version, product, folder, project ) @@ -5128,6 +5431,10 @@ class ServerAPI(object): workfile_ids=None, task_ids=None, paths=None, + path_regex=None, + statuses=None, + tags=None, + has_links=None, fields=None, own_attributes=False ): @@ -5138,6 +5445,13 @@ class ServerAPI(object): workfile_ids (Optional[Iterable[str]]): Workfile ids. task_ids (Optional[Iterable[str]]): Task ids. paths (Optional[Iterable[str]]): Rootless workfiles paths. + path_regex (Optional[str]): Regex filter for workfile path. + statuses (Optional[Iterable[str]]): Workfile info statuses used + for filtering. + tags (Optional[Iterable[str]]): Workfile info tags used + for filtering. + has_links (Optional[Literal[IN, OUT, ANY]]): Filter + representations with IN/OUT/ANY links. fields (Optional[Iterable[str]]): Fields to be queried for representation. All possible fields are returned if 'None' is passed. @@ -5161,12 +5475,30 @@ class ServerAPI(object): return filters["paths"] = list(paths) + if path_regex is not None: + filters["workfilePathRegex"] = path_regex + if workfile_ids is not None: workfile_ids = set(workfile_ids) if not workfile_ids: return filters["workfileIds"] = list(workfile_ids) + if statuses is not None: + statuses = set(statuses) + if not statuses: + return + filters["workfileStatuses"] = list(statuses) + + if tags is not None: + tags = set(tags) + if not tags: + return + filters["workfileTags"] = list(tags) + + if has_links is not None: + filters["workfilehasLinks"] = has_links.upper() + if not fields: fields = self.get_default_fields_for_type("workfile") @@ -5459,16 +5791,14 @@ class ServerAPI(object): return thumbnail_id mime_type = self._get_thumbnail_mime_type(src_filepath) - with open(src_filepath, "rb") as stream: - content = stream.read() - - response = self.raw_post( + response = self.upload_file( "projects/{}/thumbnails".format(project_name), + src_filepath, + request_type=RequestTypes.post, headers={"Content-Type": mime_type}, - data=content ) response.raise_for_status() - return response.data["id"] + return response.json()["id"] def update_thumbnail(self, project_name, thumbnail_id, src_filepath): """Change thumbnail content by id. @@ -5489,13 +5819,11 @@ class ServerAPI(object): raise ValueError("Entered filepath does not exist.") mime_type = self._get_thumbnail_mime_type(src_filepath) - with open(src_filepath, "rb") as stream: - content = stream.read() - - response = self.raw_put( + response = self.upload_file( "projects/{}/thumbnails/{}".format(project_name, thumbnail_id), + src_filepath, + request_type=RequestTypes.put, headers={"Content-Type": mime_type}, - data=content ) response.raise_for_status() @@ -6317,3 +6645,13 @@ class ServerAPI(object): op_result["detail"], )) return op_results + + def _convert_entity_data(self, entity): + if not entity: + return + entity_data = entity.get("data") + if ( + entity_data is not None + and isinstance(entity_data, six.string_types) + ): + entity["data"] = json.loads(entity_data) diff --git a/openpype/vendor/python/common/ayon_api/utils.py b/openpype/vendor/python/common/ayon_api/utils.py index 502d24f713..e9d7f53771 100644 --- a/openpype/vendor/python/common/ayon_api/utils.py +++ b/openpype/vendor/python/common/ayon_api/utils.py @@ -16,7 +16,11 @@ except ImportError: import requests import unidecode -from .constants import SERVER_TIMEOUT_ENV_KEY +from .constants import ( + SERVER_TIMEOUT_ENV_KEY, + DEFAULT_VARIANT_ENV_KEY, + SITE_ID_ENV_KEY, +) from .exceptions import UrlError REMOVED_VALUE = object() @@ -46,6 +50,26 @@ def get_default_timeout(): return 10.0 +def get_default_settings_variant(): + """Default settings variant. + + Returns: + str: Settings variant from environment variable or 'production'. + """ + + return os.environ.get(DEFAULT_VARIANT_ENV_KEY) or "production" + + +def get_default_site_id(): + """Site id used for server connection. + + Returns: + Union[str, None]: Site id from environment variable or None. + """ + + return os.environ.get(SITE_ID_ENV_KEY) + + class ThumbnailContent: """Wrapper for thumbnail content. diff --git a/openpype/vendor/python/common/ayon_api/version.py b/openpype/vendor/python/common/ayon_api/version.py index ac4f32997f..bc1107da1e 100644 --- a/openpype/vendor/python/common/ayon_api/version.py +++ b/openpype/vendor/python/common/ayon_api/version.py @@ -1,2 +1,2 @@ """Package declaring Python API for Ayon server.""" -__version__ = "0.5.1" +__version__ = "1.0.0-rc.1" From 5d9dd3ae9af14a117f14545979b62882d398473e Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 5 Dec 2023 15:50:19 +0100 Subject: [PATCH 268/327] AYON: Use AYON label in ayon mode (#5995) * use AYON instead of OpenPype in tool titles * add bundle name to info data * modified message to report ynput team * fix experimental tool titles --- .../python_console_interpreter/window/widgets.py | 5 ++++- openpype/tools/experimental_tools/dialog.py | 13 ++++++++----- openpype/tools/publisher/control.py | 2 +- openpype/tools/publisher/window.py | 5 ++++- openpype/tools/tray/pype_info_widget.py | 7 ++++++- 5 files changed, 23 insertions(+), 9 deletions(-) diff --git a/openpype/modules/python_console_interpreter/window/widgets.py b/openpype/modules/python_console_interpreter/window/widgets.py index b670352f44..49a5f62165 100644 --- a/openpype/modules/python_console_interpreter/window/widgets.py +++ b/openpype/modules/python_console_interpreter/window/widgets.py @@ -8,6 +8,7 @@ import appdirs from qtpy import QtCore, QtWidgets, QtGui from openpype import resources +from openpype import AYON_SERVER_ENABLED from openpype.style import load_stylesheet from openpype.lib import JSONSettingRegistry @@ -339,7 +340,9 @@ class PythonInterpreterWidget(QtWidgets.QWidget): def __init__(self, parent=None): super(PythonInterpreterWidget, self).__init__(parent) - self.setWindowTitle("OpenPype Console") + self.setWindowTitle("{} Console".format( + "AYON" if AYON_SERVER_ENABLED else "OpenPype" + )) self.setWindowIcon(QtGui.QIcon(resources.get_openpype_icon_filepath())) self.ansi_escape = re.compile( diff --git a/openpype/tools/experimental_tools/dialog.py b/openpype/tools/experimental_tools/dialog.py index 00b6ae07a4..3a25d72ff4 100644 --- a/openpype/tools/experimental_tools/dialog.py +++ b/openpype/tools/experimental_tools/dialog.py @@ -1,5 +1,6 @@ from qtpy import QtWidgets, QtCore, QtGui +from openpype import AYON_SERVER_ENABLED from openpype.style import ( load_stylesheet, app_icon_path @@ -26,7 +27,8 @@ class ExperimentalToolsDialog(QtWidgets.QDialog): def __init__(self, parent=None): super(ExperimentalToolsDialog, self).__init__(parent) - self.setWindowTitle("OpenPype Experimental tools") + app_label = "AYON" if AYON_SERVER_ENABLED else "OpenPype" + self.setWindowTitle("{} Experimental tools".format(app_label)) icon = QtGui.QIcon(app_icon_path()) self.setWindowIcon(icon) self.setStyleSheet(load_stylesheet()) @@ -68,8 +70,8 @@ class ExperimentalToolsDialog(QtWidgets.QDialog): tool_btns_label = QtWidgets.QLabel( ( "You can enable these features in" - "
OpenPype tray -> Settings -> Experimental tools" - ), + "
{} tray -> Settings -> Experimental tools" + ).format(app_label), tool_btns_widget ) tool_btns_label.setAlignment(QtCore.Qt.AlignCenter) @@ -113,6 +115,7 @@ class ExperimentalToolsDialog(QtWidgets.QDialog): self._window_is_active = False def refresh(self): + app_label = "AYON" if AYON_SERVER_ENABLED else "OpenPype" self._experimental_tools.refresh_availability() buttons_to_remove = set(self._buttons_by_tool_identifier.keys()) @@ -139,8 +142,8 @@ class ExperimentalToolsDialog(QtWidgets.QDialog): elif is_new or button.isEnabled(): button.setToolTip(( "You can enable this tool in local settings." - "\n\nOpenPype Tray > Settings > Experimental Tools" - )) + "\n\n{} Tray > Settings > Experimental Tools" + ).format(app_label)) if tool.enabled != button.isEnabled(): button.setEnabled(tool.enabled) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 9e00d21750..b02d83e4f6 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -2517,7 +2517,7 @@ class PublisherController(BasePublisherController): else: msg = ( "Something went wrong. Send report" - " to your supervisor or OpenPype." + " to your supervisor or Ynput team." ) self.publish_error_msg = msg self.publish_has_crashed = True diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index 2416763c27..b3138c3f45 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -9,6 +9,7 @@ from openpype import ( resources, style ) +from openpype import AYON_SERVER_ENABLED from openpype.tools.utils import ( ErrorMessageBox, PlaceholderLineEdit, @@ -53,7 +54,9 @@ class PublisherWindow(QtWidgets.QDialog): self.setObjectName("PublishWindow") - self.setWindowTitle("OpenPype publisher") + self.setWindowTitle("{} publisher".format( + "AYON" if AYON_SERVER_ENABLED else "OpenPype" + )) icon = QtGui.QIcon(resources.get_openpype_icon_filepath()) self.setWindowIcon(icon) diff --git a/openpype/tools/tray/pype_info_widget.py b/openpype/tools/tray/pype_info_widget.py index dc222b79b5..985a23f64c 100644 --- a/openpype/tools/tray/pype_info_widget.py +++ b/openpype/tools/tray/pype_info_widget.py @@ -219,7 +219,9 @@ class PypeInfoWidget(QtWidgets.QWidget): icon = QtGui.QIcon(resources.get_openpype_icon_filepath()) self.setWindowIcon(icon) - self.setWindowTitle("OpenPype info") + self.setWindowTitle( + "{} info".format("AYON" if AYON_SERVER_ENABLED else "OpenPype") + ) scroll_area = QtWidgets.QScrollArea(self) info_widget = PypeInfoSubWidget(scroll_area) @@ -441,16 +443,19 @@ class PypeInfoSubWidget(QtWidgets.QWidget): info_values = { "executable": executable_args[-1], "server_url": os.environ["AYON_SERVER_URL"], + "bundle_name": os.environ["AYON_BUNDLE_NAME"], "username": username } key_label_mapping = { "executable": "AYON Executable:", "server_url": "AYON Server:", + "bundle_name": "AYON Bundle:", "username": "AYON Username:" } # Prepare keys order keys_order = [ "server_url", + "bundle_name", "username", "executable", ] From c189cee75942bd257411bd5ffaced5ffc058b614 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 5 Dec 2023 17:43:23 +0100 Subject: [PATCH 269/327] trigger 'refresh_finished' signal out of 'run' method --- openpype/tools/ayon_utils/widgets/utils.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/tools/ayon_utils/widgets/utils.py b/openpype/tools/ayon_utils/widgets/utils.py index 2817b5efc0..e8bb1bf6c7 100644 --- a/openpype/tools/ayon_utils/widgets/utils.py +++ b/openpype/tools/ayon_utils/widgets/utils.py @@ -15,6 +15,7 @@ class RefreshThread(QtCore.QThread): self._callback = partial(func, *args, **kwargs) self._exception = None self._result = None + self.finished.connect(self._on_finish_callback) @property def id(self): @@ -29,11 +30,13 @@ class RefreshThread(QtCore.QThread): self._result = self._callback() except Exception as exc: self._exception = exc - self.refresh_finished.emit(self.id) def get_result(self): return self._result + def _on_finish_callback(self): + self.refresh_finished.emit(self.id) + class _IconsCache: """Cache for icons.""" From b77f6b73583f4a7b6899ef46a691818cebfbb3ae Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 5 Dec 2023 17:57:33 +0100 Subject: [PATCH 270/327] added small docstring --- openpype/tools/ayon_utils/widgets/utils.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/tools/ayon_utils/widgets/utils.py b/openpype/tools/ayon_utils/widgets/utils.py index e8bb1bf6c7..5165b3a262 100644 --- a/openpype/tools/ayon_utils/widgets/utils.py +++ b/openpype/tools/ayon_utils/widgets/utils.py @@ -35,6 +35,12 @@ class RefreshThread(QtCore.QThread): return self._result def _on_finish_callback(self): + """Trigger custom signal with thread id. + + Listening for 'finished' signal we make sure that execution of thread + finished and QThread object can be safely deleted. + """ + self.refresh_finished.emit(self.id) From d834c59f6c8d5beb73de406a4d81cc8045d5d152 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Wed, 6 Dec 2023 03:26:09 +0000 Subject: [PATCH 271/327] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index 98efdaec5f..48688d5651 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.17.7-nightly.5" +__version__ = "3.17.7-nightly.6" From e403f860f9a5298fea6e23f7de8213537f027889 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 6 Dec 2023 03:26:49 +0000 Subject: [PATCH 272/327] chore(): update bug report / version --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index c65a04c774..f827d275a6 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,7 @@ body: label: Version description: What version are you running? Look to OpenPype Tray options: + - 3.17.7-nightly.6 - 3.17.7-nightly.5 - 3.17.7-nightly.4 - 3.17.7-nightly.3 @@ -134,7 +135,6 @@ body: - 3.15.3-nightly.2 - 3.15.3-nightly.1 - 3.15.2 - - 3.15.2-nightly.6 validations: required: true - type: dropdown From d9d1242eafd4160a3e6b63b1c64f3ded9de9e49f Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 6 Dec 2023 18:32:27 +0800 Subject: [PATCH 273/327] make sure the args mapped to the get_imageio_file_rules_colorspace_from_filepath --- openpype/hosts/maya/plugins/load/load_image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/load/load_image.py b/openpype/hosts/maya/plugins/load/load_image.py index 3b1f5442ce..4a309549ee 100644 --- a/openpype/hosts/maya/plugins/load/load_image.py +++ b/openpype/hosts/maya/plugins/load/load_image.py @@ -286,7 +286,7 @@ class FileNodeLoader(load.LoaderPlugin): path = get_representation_path_from_context(context) colorspace = get_imageio_colorspace_from_filepath( - path=path, + filepath=path, host_name=host_name, project_name=project_name, config_data=config_data, From 610ed75aafb1101aadbb57538fd418bbb6bf9acf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 6 Dec 2023 12:02:50 +0100 Subject: [PATCH 274/327] :wastebasket: remove simple unreal texture publishing --- .../publish/collect_original_basename.py | 18 -------- .../validate_simple_unreal_texture_naming.py | 29 ------------- openpype/plugins/publish/integrate.py | 1 - .../defaults/project_anatomy/templates.json | 12 ------ .../defaults/project_settings/global.json | 42 ++----------------- .../project_settings/standalonepublisher.json | 8 ---- .../project_settings/traypublisher.json | 13 ------ 7 files changed, 3 insertions(+), 120 deletions(-) delete mode 100644 openpype/hosts/standalonepublisher/plugins/publish/collect_original_basename.py delete mode 100644 openpype/hosts/standalonepublisher/plugins/publish/validate_simple_unreal_texture_naming.py diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_original_basename.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_original_basename.py deleted file mode 100644 index b83a924d33..0000000000 --- a/openpype/hosts/standalonepublisher/plugins/publish/collect_original_basename.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- -"""Collect original base name for use in templates.""" -from pathlib import Path - -import pyblish.api - - -class CollectOriginalBasename(pyblish.api.InstancePlugin): - """Collect original file base name.""" - - order = pyblish.api.CollectorOrder + 0.498 - label = "Collect Base Name" - hosts = ["standalonepublisher"] - families = ["simpleUnrealTexture"] - - def process(self, instance): - file_name = Path(instance.data["representations"][0]["files"]) - instance.data["originalBasename"] = file_name.stem diff --git a/openpype/hosts/standalonepublisher/plugins/publish/validate_simple_unreal_texture_naming.py b/openpype/hosts/standalonepublisher/plugins/publish/validate_simple_unreal_texture_naming.py deleted file mode 100644 index c123bef4f8..0000000000 --- a/openpype/hosts/standalonepublisher/plugins/publish/validate_simple_unreal_texture_naming.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- -"""Validator for correct file naming.""" -import re -import pyblish.api - -from openpype.pipeline.publish import ( - ValidateContentsOrder, - PublishXmlValidationError, -) - - -class ValidateSimpleUnrealTextureNaming(pyblish.api.InstancePlugin): - label = "Validate Unreal Texture Names" - hosts = ["standalonepublisher"] - families = ["simpleUnrealTexture"] - order = ValidateContentsOrder - regex = "^T_{asset}.*" - - def process(self, instance): - file_name = instance.data.get("originalBasename") - self.log.info(file_name) - pattern = self.regex.format(asset=instance.data.get("asset")) - if not re.match(pattern, file_name): - msg = f"Invalid file name {file_name}" - raise PublishXmlValidationError( - self, msg, formatting_data={ - "invalid_file": file_name, - "asset": instance.data.get("asset") - }) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 5bb51a3049..581c0c012f 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -137,7 +137,6 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "mvUsd", "mvUsdComposition", "mvUsdOverride", - "simpleUnrealTexture", "online", "uasset", "blendScene", diff --git a/openpype/settings/defaults/project_anatomy/templates.json b/openpype/settings/defaults/project_anatomy/templates.json index 5766a09100..6c3e038d27 100644 --- a/openpype/settings/defaults/project_anatomy/templates.json +++ b/openpype/settings/defaults/project_anatomy/templates.json @@ -38,16 +38,6 @@ "file": "{subset}_{@version}<_{output}><.{@frame}>.{ext}", "path": "{@folder}/{@file}" }, - "simpleUnrealTextureHero": { - "folder": "{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/hero", - "file": "{originalBasename}.{ext}", - "path": "{@folder}/{@file}" - }, - "simpleUnrealTexture": { - "folder": "{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{@version}", - "file": "{originalBasename}_{@version}.{ext}", - "path": "{@folder}/{@file}" - }, "online": { "folder": "{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/{@version}", "file": "{originalBasename}<.{@frame}><_{udim}>.{ext}", @@ -68,8 +58,6 @@ }, "__dynamic_keys_labels__": { "maya2unreal": "Maya to Unreal", - "simpleUnrealTextureHero": "Simple Unreal Texture - Hero", - "simpleUnrealTexture": "Simple Unreal Texture", "online": "online", "tycache": "tycache", "source": "source", diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 959faf14fa..885e8638f9 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -322,22 +322,9 @@ "animation", "setdress", "layout", - "mayaScene", - "simpleUnrealTexture" + "mayaScene" ], - "template_name_profiles": [ - { - "families": [ - "simpleUnrealTexture" - ], - "hosts": [ - "standalonepublisher" - ], - "task_types": [], - "task_names": [], - "template_name": "simpleUnrealTextureHero" - } - ] + "template_name_profiles": [] }, "CleanUp": { "paterns": [], @@ -519,17 +506,6 @@ "task_names": [], "template_name": "render" }, - { - "families": [ - "simpleUnrealTexture" - ], - "hosts": [ - "standalonepublisher" - ], - "task_types": [], - "task_names": [], - "template_name": "simpleUnrealTexture" - }, { "families": [ "staticMesh", @@ -565,19 +541,7 @@ "template_name": "tycache" } ], - "hero_template_name_profiles": [ - { - "families": [ - "simpleUnrealTexture" - ], - "hosts": [ - "standalonepublisher" - ], - "task_types": [], - "task_names": [], - "template_name": "simpleUnrealTextureHero" - } - ], + "hero_template_name_profiles": [], "custom_staging_dir_profiles": [] } }, diff --git a/openpype/settings/defaults/project_settings/standalonepublisher.json b/openpype/settings/defaults/project_settings/standalonepublisher.json index d923b4db43..44982133eb 100644 --- a/openpype/settings/defaults/project_settings/standalonepublisher.json +++ b/openpype/settings/defaults/project_settings/standalonepublisher.json @@ -133,14 +133,6 @@ ], "help": "Texture files with UDIM together with worfile" }, - "create_simple_unreal_texture": { - "name": "simple_unreal_texture", - "label": "Simple Unreal Texture", - "family": "simpleUnrealTexture", - "icon": "Image", - "defaults": [], - "help": "Texture files with Unreal naming convention" - }, "create_vdb": { "name": "vdb", "label": "VDB Volumetric Data", diff --git a/openpype/settings/defaults/project_settings/traypublisher.json b/openpype/settings/defaults/project_settings/traypublisher.json index e13de11414..7d2f358cb2 100644 --- a/openpype/settings/defaults/project_settings/traypublisher.json +++ b/openpype/settings/defaults/project_settings/traypublisher.json @@ -244,19 +244,6 @@ ".hda" ] }, - { - "family": "simpleUnrealTexture", - "identifier": "", - "label": "Simple UE texture", - "icon": "fa.image", - "default_variants": [], - "description": "Simple Unreal Engine texture", - "detailed_description": "Texture files with Unreal Engine naming conventions", - "allow_sequences": false, - "allow_multiple_items": true, - "allow_version_control": false, - "extensions": [] - }, { "family": "audio", "identifier": "", From 719dee21d1e3b62c696ef028cc3b312ef64ad9aa Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 6 Dec 2023 19:20:35 +0800 Subject: [PATCH 275/327] use get_imageio_file_rules_colorspace_from_filepath instead of deprecated function --- openpype/hosts/maya/plugins/load/load_image.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_image.py b/openpype/hosts/maya/plugins/load/load_image.py index 4a309549ee..27c9ec7118 100644 --- a/openpype/hosts/maya/plugins/load/load_image.py +++ b/openpype/hosts/maya/plugins/load/load_image.py @@ -9,7 +9,7 @@ from openpype.pipeline import ( ) from openpype.pipeline.load.utils import get_representation_path_from_context from openpype.pipeline.colorspace import ( - get_imageio_colorspace_from_filepath, + get_imageio_file_rules_colorspace_from_filepath, get_imageio_config, get_imageio_file_rules ) @@ -285,10 +285,10 @@ class FileNodeLoader(load.LoaderPlugin): ) path = get_representation_path_from_context(context) - colorspace = get_imageio_colorspace_from_filepath( - filepath=path, - host_name=host_name, - project_name=project_name, + colorspace = get_imageio_file_rules_colorspace_from_filepath( + path, + host_name, + project_name, config_data=config_data, file_rules=file_rules, project_settings=project_settings From a2c37975aab733da1bd55ef39b4aa9af449b89a8 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 6 Dec 2023 12:24:27 +0100 Subject: [PATCH 276/327] feat: Add support for multiple reviewable components This commit adds support for creating multiple reviewable components in the `IntegrateFtrackInstance` plugin. It introduces a new variable `extended_asset_name` to change the asset name of each new component for review. This feature is particularly useful when there are more than one representation to be reviewed. --- .../modules/ftrack/plugins/publish/integrate_ftrack_instances.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py index edad0b0132..8422ddc9f8 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py @@ -230,6 +230,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): # Create review components # Change asset name of each new component for review multiple_reviewable = len(review_representations) > 1 + extended_asset_name = None for index, repre in enumerate(review_representations): if not self._is_repre_video(repre) and has_movie_review: self.log.debug("Movie repre has priority " From 0ed8a66fb8130ec2cef4fa47b1b573eaf1985399 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 6 Dec 2023 15:05:10 +0100 Subject: [PATCH 277/327] AYON Change of login should work Co-authored-by: Libor Batek <112623825+LiborBatek@users.noreply.github.com> --- openpype/tools/tray/pype_tray.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openpype/tools/tray/pype_tray.py b/openpype/tools/tray/pype_tray.py index a5876ca721..db391b469a 100644 --- a/openpype/tools/tray/pype_tray.py +++ b/openpype/tools/tray/pype_tray.py @@ -632,6 +632,14 @@ class TrayManager: self.exit() elif result.restart or result.token_changed: + # Remove environment variables from current connection + # - keep develop, staging, headless values + for key in { + "AYON_SERVER_URL", + "AYON_API_KEY", + "AYON_BUNDLE_NAME", + }: + os.environ.pop(key, None) self.restart() def _on_restart_action(self): From 54205db2225a55486f6348c9dec1b5bd69d02df9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 6 Dec 2023 15:24:19 +0100 Subject: [PATCH 278/327] Refactor transcoding.py to fix sample aspect ratio parsing The code change fixes an issue in the transcoding.py file where the sample aspect ratio was not being parsed correctly. The fix involves modifying the way the sample aspect ratio is accessed from the stream object. --- openpype/lib/transcoding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index acf110635f..1fa3447a1f 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1262,7 +1262,7 @@ def get_rescaled_command_arguments( stream = input_file_metadata["streams"][0] input_width = int(stream["width"]) input_height = int(stream["height"]) - stream_input_par = stream[0].get("sample_aspect_ratio") + stream_input_par = stream.get("sample_aspect_ratio") if stream_input_par: input_par = ( float(stream_input_par.split(":")[0]) From 7c104e1336984071cbd341e4e4938421c094b54e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 6 Dec 2023 22:28:35 +0800 Subject: [PATCH 279/327] make sure the select file dialog is closed after selecting the mesh file and make sure the new project dialog hit accepted after finishing the mesh selection and other settings --- openpype/hosts/substancepainter/api/lib.py | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/openpype/hosts/substancepainter/api/lib.py b/openpype/hosts/substancepainter/api/lib.py index 7055c1f8ba..71b7e3630a 100644 --- a/openpype/hosts/substancepainter/api/lib.py +++ b/openpype/hosts/substancepainter/api/lib.py @@ -583,21 +583,7 @@ def prompt_new_file_with_mesh(mesh_filepath): file_dialog.setDirectory(os.path.dirname(mesh_filepath)) url = QtCore.QUrl.fromLocalFile(os.path.basename(mesh_filepath)) file_dialog.selectUrl(url) - - # Give the explorer window time to refresh to the folder and select - # the file - while not file_dialog.selectedFiles(): - app.processEvents(QtCore.QEventLoop.ExcludeUserInputEvents, 1000) - continue - print(f"Selected: {file_dialog.selectedFiles()}") - - # Set it again now we know the path is refreshed - without this - # accepting the dialog will often not trigger the correct filepath - file_dialog.setDirectory(os.path.dirname(mesh_filepath)) - url = QtCore.QUrl.fromLocalFile(os.path.basename(mesh_filepath)) - file_dialog.selectUrl(url) - - file_dialog.done(file_dialog.Accepted) + file_dialog.close() app.processEvents(QtCore.QEventLoop.AllEvents) def _setup_prompt(): @@ -630,6 +616,8 @@ def prompt_new_file_with_mesh(mesh_filepath): if not mesh_filename_label.text(): dialog.close() raise RuntimeError(f"Failed to set mesh path: {mesh_filepath}") + else: + dialog.done(dialog.Accepted) new_action = _get_new_project_action() if not new_action: From 7a79330d5344d8cbc612909f74deb8451824087e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 6 Dec 2023 15:40:56 +0100 Subject: [PATCH 280/327] :bug: handle missing key --- openpype/modules/deadline/abstract_submit_deadline.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index 9b31b6402b..187feb9b1a 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -464,8 +464,7 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin, self.log.info("Submitted job to Deadline: {}.".format(job_id)) # TODO: Find a way that's more generic and not render type specific - export_job = instance.data["exportJob"] - if export_job: + if "exportJob" in instance.data: self.log.info("Splitting export and render in two jobs") self.log.info("Export job id: %s", job_id) render_job_info = self.get_job_info(dependency_job_ids=[job_id]) From 206e1d94eb3037c688bcfbabaa32c55e14e66d0c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 6 Dec 2023 16:04:47 +0100 Subject: [PATCH 281/327] removing duplicity of `get_oiio_input_and_channel_args` --- openpype/lib/transcoding.py | 41 ------------------------------------- 1 file changed, 41 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index d3edfdfc7c..6870eda59a 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -655,47 +655,6 @@ def convert_for_ffmpeg( run_subprocess(oiio_cmd, logger=logger) -def get_oiio_input_and_channel_args(oiio_input_info): - """Get input and channel arguments for oiiotool. - - Args: - oiio_input_info (dict): Information about input from oiio tool. - Should be output of function `get_oiio_info_for_input`. - - Returns: - tuple[str, str]: Tuple of input and channel arguments. - """ - channel_names = oiio_input_info["channelnames"] - review_channels = get_convert_rgb_channels(channel_names) - - if review_channels is None: - raise ValueError( - "Couldn't find channels that can be used for conversion." - ) - - red, green, blue, alpha = review_channels - input_channels = [red, green, blue] - - # TODO find subimage where rgba is available for multipart exrs - channels_arg = "R={},G={},B={}".format(red, green, blue) - if alpha is not None: - channels_arg += ",A={}".format(alpha) - input_channels.append(alpha) - - input_channels_str = ",".join(input_channels) - - subimages = oiio_input_info.get("subimages") - input_arg = "-i" - if subimages is None or subimages == 1: - # Tell oiiotool which channels should be loaded - # - other channels are not loaded to memory so helps to avoid memory - # leak issues - # - this option is crashing if used on multipart exrs - input_arg += ":ch={}".format(input_channels_str) - - return input_arg, channels_arg - - def convert_input_paths_for_ffmpeg( input_paths, output_dir, From 74a949685ed1ac1e4bd0f1ad8b16a427dee54b93 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 6 Dec 2023 16:37:27 +0100 Subject: [PATCH 282/327] new defaults for AYON --- .../applications/server/applications.json | 598 ++++++++++-------- server_addon/applications/server/settings.py | 2 +- server_addon/blender/server/settings/main.py | 2 +- .../server/settings/publish_plugins.py | 18 +- server_addon/maya/server/settings/main.py | 2 +- .../maya/server/settings/publishers.py | 12 +- .../settings/workfile_build_settings.py | 2 +- server_addon/unreal/server/settings.py | 4 +- 8 files changed, 340 insertions(+), 300 deletions(-) diff --git a/server_addon/applications/server/applications.json b/server_addon/applications/server/applications.json index 825f50276a..4a65d1cc1c 100644 --- a/server_addon/applications/server/applications.json +++ b/server_addon/applications/server/applications.json @@ -14,7 +14,7 @@ "windows": [ "C:\\Program Files\\Autodesk\\Maya2024\\bin\\maya.exe" ], - "darwin": [], + "darwin": ["/Applications/Autodesk/maya2024/Maya.app"], "linux": [ "/usr/autodesk/maya2024/bin/maya" ] @@ -34,7 +34,7 @@ "windows": [ "C:\\Program Files\\Autodesk\\Maya2023\\bin\\maya.exe" ], - "darwin": [], + "darwin": ["/Applications/Autodesk/maya2023/Maya.app"], "linux": [ "/usr/autodesk/maya2023/bin/maya" ] @@ -54,7 +54,7 @@ "windows": [ "C:\\Program Files\\Autodesk\\Maya2022\\bin\\maya.exe" ], - "darwin": [], + "darwin": ["/Applications/Autodesk/maya2022/Maya.app"], "linux": [ "/usr/autodesk/maya2022/bin/maya" ] @@ -125,6 +125,23 @@ "host_name": "max", "environment": "{\n \"ADSK_3DSMAX_STARTUPSCRIPTS_ADDON_DIR\": \"{OPENPYPE_ROOT}/openpype/hosts/max/startup\"\n}", "variants": [ + { + "name": "2024", + "use_python_2": false, + "executables": { + "windows": [ + "C:\\Program Files\\Autodesk\\3ds Max 2024\\3dsmax.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{\n \"3DSMAX_VERSION\": \"2024\"\n}" + }, { "name": "2023", "use_python_2": false, @@ -200,18 +217,40 @@ "host_name": "nuke", "environment": "{\n \"NUKE_PATH\": [\n \"{NUKE_PATH}\",\n \"{OPENPYPE_STUDIO_PLUGINS}/nuke\"\n ]\n}", "variants": [ + { + "name": "15-0", + "label": "15.0", + "executables": { + "windows": [ + "C:\\Program Files\\Nuke15.0v2\\Nuke15.0.exe" + ], + "darwin": [ + "/Applications/Nuke15.0v2/Nuke15.0v2.app" + ], + "linux": [ + "/usr/local/Nuke5.0v2/Nuke15.0" + ] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{}", + "use_python_2": false + }, { "name": "14-0", "label": "14.0", "executables": { "windows": [ - "C:\\Program Files\\Nuke14.0v4\\Nuke14.0.exe" + "C:\\Program Files\\Nuke14.0v5\\Nuke14.0.exe" ], "darwin": [ - "/Applications/Nuke14.0v4/Nuke14.0v4.app" + "/Applications/Nuke14.0v5/Nuke14.0v5.app" ], "linux": [ - "/usr/local/Nuke14.0v4/Nuke14.0" + "/usr/local/Nuke14.0v5/Nuke14.0" ] }, "arguments": { @@ -243,28 +282,6 @@ }, "environment": "{}", "use_python_2": false - }, - { - "name": "13-0", - "label": "13.0", - "use_python_2": false, - "executables": { - "windows": [ - "C:\\Program Files\\Nuke13.0v1\\Nuke13.0.exe" - ], - "darwin": [ - "/Applications/Nuke13.0v1/Nuke13.0v1.app" - ], - "linux": [ - "/usr/local/Nuke13.0v1/Nuke13.0" - ] - }, - "arguments": { - "windows": [], - "darwin": [], - "linux": [] - }, - "environment": "{}" } ] }, @@ -275,28 +292,46 @@ "host_name": "nuke", "environment": "{\n \"NUKE_PATH\": [\n \"{NUKE_PATH}\",\n \"{OPENPYPE_STUDIO_PLUGINS}/nuke\"\n ]\n}", "variants": [ + { + "name": "15-0", + "label": "15.0", + "executables": { + "windows": [ + "C:\\Program Files\\Nuke15.0v2\\Nuke15.0.exe" + ], + "darwin": [ + "/Applications/Nuke15.0v2/NukeAssist15.0v2.app" + ], + "linux": [ + "/usr/local/Nuke5.0v2/Nuke15.0" + ] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{}", + "use_python_2": false + }, { "name": "14-0", "label": "14.0", "executables": { "windows": [ - "C:\\Program Files\\Nuke14.0v4\\Nuke14.0.exe" + "C:\\Program Files\\Nuke14.0v5\\Nuke14.0.exe" ], "darwin": [ - "/Applications/Nuke14.0v4/NukeAssist14.0v4.app" + "/Applications/Nuke14.0v5/NukeAssist14.0v5.app" ], "linux": [ - "/usr/local/Nuke14.0v4/Nuke14.0" + "/usr/local/Nuke14.0v5/Nuke14.0" ] }, "arguments": { - "windows": [ - "--nukeassist" - ], + "windows": [], "darwin": [], - "linux": [ - "--nukeassist" - ] + "linux": [] }, "environment": "{}", "use_python_2": false @@ -316,44 +351,13 @@ ] }, "arguments": { - "windows": [ - "--nukeassist" - ], + "windows": [], "darwin": [], - "linux": [ - "--nukeassist" - ] + "linux": [] }, "environment": "{}", "use_python_2": false - }, - { - "name": "13-0", - "label": "13.0", - "use_python_2": false, - "executables": { - "windows": [ - "C:\\Program Files\\Nuke13.0v1\\Nuke13.0.exe" - ], - "darwin": [ - "/Applications/Nuke13.0v1/NukeAssist13.0v1.app" - ], - "linux": [ - "/usr/local/Nuke13.0v1/Nuke13.0" - ] - }, - "arguments": { - "windows": [ - "--nukeassist" - ], - "darwin": [], - "linux": [ - "--nukeassist" - ] - }, - "environment": "{}" } - ] }, "nukex": { "enabled": true, @@ -362,28 +366,46 @@ "host_name": "nuke", "environment": "{\n \"NUKE_PATH\": [\n \"{NUKE_PATH}\",\n \"{OPENPYPE_STUDIO_PLUGINS}/nuke\"\n ]\n}", "variants": [ + { + "name": "15-0", + "label": "15.0", + "executables": { + "windows": [ + "C:\\Program Files\\Nuke15.0v2\\Nuke15.0.exe" + ], + "darwin": [ + "/Applications/Nuke15.0v2/NukeX15.0v2.app" + ], + "linux": [ + "/usr/local/Nuke5.0v2/Nuke15.0" + ] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{}", + "use_python_2": false + }, { "name": "14-0", "label": "14.0", "executables": { "windows": [ - "C:\\Program Files\\Nuke14.0v4\\Nuke14.0.exe" + "C:\\Program Files\\Nuke14.0v5\\Nuke14.0.exe" ], "darwin": [ - "/Applications/Nuke14.0v4/NukeX14.0v4.app" + "/Applications/Nuke14.0v5/NukeX14.0v5.app" ], "linux": [ - "/usr/local/Nuke14.0v4/Nuke14.0" + "/usr/local/Nuke14.0v5/Nuke14.0" ] }, "arguments": { - "windows": [ - "--nukex" - ], + "windows": [], "darwin": [], - "linux": [ - "--nukex" - ] + "linux": [] }, "environment": "{}", "use_python_2": false @@ -403,42 +425,12 @@ ] }, "arguments": { - "windows": [ - "--nukex" - ], + "windows": [], "darwin": [], - "linux": [ - "--nukex" - ] + "linux": [] }, "environment": "{}", "use_python_2": false - }, - { - "name": "13-0", - "label": "13.0", - "use_python_2": false, - "executables": { - "windows": [ - "C:\\Program Files\\Nuke13.0v1\\Nuke13.0.exe" - ], - "darwin": [ - "/Applications/Nuke13.0v1/NukeX13.0v1.app" - ], - "linux": [ - "/usr/local/Nuke13.0v1/Nuke13.0" - ] - }, - "arguments": { - "windows": [ - "--nukex" - ], - "darwin": [], - "linux": [ - "--nukex" - ] - }, - "environment": "{}" } ] }, @@ -449,28 +441,46 @@ "host_name": "hiero", "environment": "{\n \"WORKFILES_STARTUP\": \"0\",\n \"TAG_ASSETBUILD_STARTUP\": \"0\"\n}", "variants": [ + { + "name": "15-0", + "label": "15.0", + "executables": { + "windows": [ + "C:\\Program Files\\Nuke15.0v2\\Nuke15.0.exe" + ], + "darwin": [ + "/Applications/Nuke15.0v2/NukeStudio15.0v2.app" + ], + "linux": [ + "/usr/local/Nuke5.0v2/Nuke15.0" + ] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{}", + "use_python_2": false + }, { "name": "14-0", "label": "14.0", "executables": { "windows": [ - "C:\\Program Files\\Nuke14.0v4\\Nuke14.0.exe" + "C:\\Program Files\\Nuke14.0v5\\Nuke14.0.exe" ], "darwin": [ - "/Applications/Nuke14.0v4/NukeStudio14.0v4.app" + "/Applications/Nuke14.0v5/NukeStudio14.0v5.app" ], "linux": [ - "/usr/local/Nuke14.0v4/Nuke14.0" + "/usr/local/Nuke14.0v5/Nuke14.0" ] }, "arguments": { - "windows": [ - "--studio" - ], + "windows": [], "darwin": [], - "linux": [ - "--studio" - ] + "linux": [] }, "environment": "{}", "use_python_2": false @@ -490,42 +500,12 @@ ] }, "arguments": { - "windows": [ - "--studio" - ], + "windows": [], "darwin": [], - "linux": [ - "--studio" - ] + "linux": [] }, "environment": "{}", "use_python_2": false - }, - { - "name": "13-0", - "label": "13.0", - "use_python_2": false, - "executables": { - "windows": [ - "C:\\Program Files\\Nuke13.0v1\\Nuke13.0.exe" - ], - "darwin": [ - "/Applications/Nuke13.0v1/NukeStudio13.0v1.app" - ], - "linux": [ - "/usr/local/Nuke13.0v1/Nuke13.0" - ] - }, - "arguments": { - "windows": [ - "--studio" - ], - "darwin": [], - "linux": [ - "--studio" - ] - }, - "environment": "{}" } ] }, @@ -536,28 +516,46 @@ "host_name": "hiero", "environment": "{\n \"WORKFILES_STARTUP\": \"0\",\n \"TAG_ASSETBUILD_STARTUP\": \"0\"\n}", "variants": [ + { + "name": "15-0", + "label": "15.0", + "executables": { + "windows": [ + "C:\\Program Files\\Nuke15.0v2\\Nuke15.0.exe" + ], + "darwin": [ + "/Applications/Nuke15.0v2/Hiero15.0v2.app" + ], + "linux": [ + "/usr/local/Nuke5.0v2/Nuke15.0" + ] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{}", + "use_python_2": false + }, { "name": "14-0", "label": "14.0", "executables": { "windows": [ - "C:\\Program Files\\Nuke14.0v4\\Nuke14.0.exe" + "C:\\Program Files\\Nuke14.0v5\\Nuke14.0.exe" ], "darwin": [ - "/Applications/Nuke14.0v4/Hiero14.0v4.app" + "/Applications/Nuke14.0v5/Hiero14.0v5.app" ], "linux": [ - "/usr/local/Nuke14.0v4/Nuke14.0" + "/usr/local/Nuke14.0v5/Nuke14.0" ] }, "arguments": { - "windows": [ - "--hiero" - ], + "windows": [], "darwin": [], - "linux": [ - "--hiero" - ] + "linux": [] }, "environment": "{}", "use_python_2": false @@ -577,42 +575,12 @@ ] }, "arguments": { - "windows": [ - "--hiero" - ], + "windows": [], "darwin": [], - "linux": [ - "--hiero" - ] + "linux": [] }, "environment": "{}", "use_python_2": false - }, - { - "name": "13-0", - "label": "13.0", - "use_python_2": false, - "executables": { - "windows": [ - "C:\\Program Files\\Nuke13.0v1\\Nuke13.0.exe" - ], - "darwin": [ - "/Applications/Nuke13.0v1/Hiero13.0v1.app" - ], - "linux": [ - "/usr/local/Nuke13.0v1/Nuke13.0" - ] - }, - "arguments": { - "windows": [ - "--hiero" - ], - "darwin": [], - "linux": [ - "--hiero" - ] - }, - "environment": "{}" } ] }, @@ -623,6 +591,23 @@ "host_name": "fusion", "environment": "{\n \"FUSION_PYTHON3_HOME\": {\n \"windows\": \"{LOCALAPPDATA}/Programs/Python/Python36\",\n \"darwin\": \"~/Library/Python/3.6/bin\",\n \"linux\": \"/opt/Python/3.6/bin\"\n }\n}", "variants": [ + { + "name": "18", + "label": "18", + "executables": { + "windows": [ + "C:\\Program Files\\Blackmagic Design\\Fusion 18\\Fusion.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{}" + }, { "name": "17", "label": "17", @@ -656,23 +641,6 @@ "linux": [] }, "environment": "{}" - }, - { - "name": "9", - "label": "9", - "executables": { - "windows": [ - "C:\\Program Files\\Blackmagic Design\\Fusion 9\\Fusion.exe" - ], - "darwin": [], - "linux": [] - }, - "arguments": { - "windows": [], - "darwin": [], - "linux": [] - }, - "environment": "{}" } ] }, @@ -710,11 +678,11 @@ "environment": "{}", "variants": [ { - "name": "18-5", - "label": "18.5", + "name": "19-5", + "label": "19.5", "executables": { "windows": [ - "C:\\Program Files\\Side Effects Software\\Houdini 18.5.499\\bin\\houdini.exe" + "C:\\Program Files\\Side Effects Software\\Houdini 19.5.805\\bin\\houdini.exe" ], "darwin": [], "linux": [] @@ -728,10 +696,12 @@ "use_python_2": true }, { - "name": "18", - "label": "18", + "name": "19-0", + "label": "19.0", "executables": { - "windows": [], + "windows": [ + "C:\\Program Files\\Side Effects Software\\Houdini 19.0.720\\bin\\houdini.exe" + ], "darwin": [], "linux": [] }, @@ -744,10 +714,12 @@ "use_python_2": true }, { - "name": "17", - "label": "17", + "name": "18-5", + "label": "18.5", "executables": { - "windows": [], + "windows": [ + "C:\\Program Files\\Side Effects Software\\Houdini 18.5.759\\bin\\houdini.exe" + ], "darwin": [], "linux": [] }, @@ -769,11 +741,11 @@ "environment": "{}", "variants": [ { - "name": "2-83", - "label": "2.83", + "name": "3-6-5", + "label": "3.6.5 LTS", "executables": { "windows": [ - "C:\\Program Files\\Blender Foundation\\Blender 2.83\\blender.exe" + "C:\\Program Files\\Blender Foundation\\Blender 3.6\\blender.exe" ], "darwin": [], "linux": [] @@ -846,6 +818,25 @@ "host_name": "harmony", "environment": "{\n \"AVALON_HARMONY_WORKFILES_ON_LAUNCH\": \"1\"\n}", "variants": [ + { + "name": "22", + "label": "22", + "executables": { + "windows": [ + "c:\\Program Files (x86)\\Toon Boom Animation\\Toon Boom Harmony 22 Premium\\win64\\bin\\HarmonyPremium.exe" + ], + "darwin": [ + "/Applications/Toon Boom Harmony 22 Premium/Harmony Premium.app/Contents/MacOS/Harmony Premium" + ], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{}" + }, { "name": "21", "label": "21", @@ -955,40 +946,6 @@ "host_name": "photoshop", "environment": "{\n \"AVALON_PHOTOSHOP_WORKFILES_ON_LAUNCH\": \"1\",\n \"WORKFILES_SAVE_AS\": \"Yes\"\n}", "variants": [ - { - "name": "2020", - "label": "2020", - "executables": { - "windows": [ - "C:\\Program Files\\Adobe\\Adobe Photoshop 2020\\Photoshop.exe" - ], - "darwin": [], - "linux": [] - }, - "arguments": { - "windows": [], - "darwin": [], - "linux": [] - }, - "environment": "{}" - }, - { - "name": "2021", - "label": "2021", - "executables": { - "windows": [ - "C:\\Program Files\\Adobe\\Adobe Photoshop 2021\\Photoshop.exe" - ], - "darwin": [], - "linux": [] - }, - "arguments": { - "windows": [], - "darwin": [], - "linux": [] - }, - "environment": "{}" - }, { "name": "2022", "label": "2022", @@ -1005,6 +962,40 @@ "linux": [] }, "environment": "{}" + }, + { + "name": "2023", + "label": "2023", + "executables": { + "windows": [ + "C:\\Program Files\\Adobe\\Adobe Photoshop 2023\\Photoshop.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{}" + }, + { + "name": "2024", + "label": "2024", + "executables": { + "windows": [ + "C:\\Program Files\\Adobe\\Adobe Photoshop 2024\\Photoshop.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{}" } ] }, @@ -1015,23 +1006,6 @@ "host_name": "aftereffects", "environment": "{\n \"AVALON_AFTEREFFECTS_WORKFILES_ON_LAUNCH\": \"1\",\n \"WORKFILES_SAVE_AS\": \"Yes\"\n}", "variants": [ - { - "name": "2020", - "label": "2020", - "executables": { - "windows": [ - "C:\\Program Files\\Adobe\\Adobe After Effects 2020\\Support Files\\AfterFX.exe" - ], - "darwin": [], - "linux": [] - }, - "arguments": { - "windows": [], - "darwin": [], - "linux": [] - }, - "environment": "{}" - }, { "name": "2021", "label": "2021", @@ -1065,6 +1039,40 @@ "linux": [] }, "environment": "{\n \"MULTIPROCESS\": \"No\"\n}" + }, + { + "name": "2023", + "label": "2023", + "executables": { + "windows": [ + "C:\\Program Files\\Adobe\\Adobe After Effects 2023\\Support Files\\AfterFX.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{\n \"MULTIPROCESS\": \"No\"\n}" + }, + { + "name": "2024", + "label": "2024", + "executables": { + "windows": [ + "C:\\Program Files\\Adobe\\Adobe After Effects 2024\\Support Files\\AfterFX.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{\n \"MULTIPROCESS\": \"No\"\n}" } ] }, @@ -1100,8 +1108,8 @@ "environment": "{}", "variants": [ { - "name": "8-2-0", - "label": "8.2", + "name": "stable", + "label": "Stable", "executables": { "windows": [ "C:\\Program Files\\Adobe\\Adobe Substance 3D Painter\\Adobe Substance 3D Painter.exe" @@ -1119,16 +1127,48 @@ ] }, "unreal": { - "enabled": true, + "enabled": false, "label": "Unreal Editor", "icon": "{}/app_icons/ue4.png", "host_name": "unreal", "environment": "{}", "variants": [ { - "name": "4-26", - "label": "4.26", - "executables": {}, + "name": "5-0", + "label": "5.0", + "executables": { + "windows": [ + "C:\\Program Files\\Epic Games\\UE_5.0\\Engine\\Binaries\\Win64\\UnrealEditor.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": {}, + "environment": "{}" + }, + { + "name": "5-1", + "label": "5.1", + "executables": { + "windows": [ + "C:\\Program Files\\Epic Games\\UE_5.1\\Engine\\Binaries\\Win64\\UnrealEditor.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": {}, + "environment": "{}" + }, + { + "name": "5-2", + "label": "5.2", + "executables": { + "windows": [ + "C:\\Program Files\\Epic Games\\UE_5.2\\Engine\\Binaries\\Win64\\UnrealEditor.exe" + ], + "darwin": [], + "linux": [] + }, "arguments": {}, "environment": "{}" } diff --git a/server_addon/applications/server/settings.py b/server_addon/applications/server/settings.py index 224f999564..70c8b57c6a 100644 --- a/server_addon/applications/server/settings.py +++ b/server_addon/applications/server/settings.py @@ -199,5 +199,5 @@ class ApplicationsAddonSettings(BaseSettingsModel): DEFAULT_VALUES = { - "only_available": False + "only_available": True } diff --git a/server_addon/blender/server/settings/main.py b/server_addon/blender/server/settings/main.py index 5eff276ef5..3d53162e45 100644 --- a/server_addon/blender/server/settings/main.py +++ b/server_addon/blender/server/settings/main.py @@ -57,7 +57,7 @@ DEFAULT_VALUES = { "unit_scale_settings": { "enabled": True, "apply_on_opening": False, - "base_file_unit_scale": 0.01 + "base_file_unit_scale": 1.00 }, "set_frames_startup": True, "set_resolution_startup": True, diff --git a/server_addon/blender/server/settings/publish_plugins.py b/server_addon/blender/server/settings/publish_plugins.py index 7a5bc236d4..9a1e0c681e 100644 --- a/server_addon/blender/server/settings/publish_plugins.py +++ b/server_addon/blender/server/settings/publish_plugins.py @@ -142,7 +142,7 @@ class PublishPuginsModel(BaseSettingsModel): DEFAULT_BLENDER_PUBLISH_SETTINGS = { "ValidateCameraZeroKeyframe": { - "enabled": True, + "enabled": False, "optional": True, "active": True }, @@ -173,13 +173,13 @@ DEFAULT_BLENDER_PUBLISH_SETTINGS = { "active": True }, "ValidateTransformZero": { - "enabled": True, - "optional": False, + "enabled": False, + "optional": True, "active": True }, "ValidateNoColonsInName": { - "enabled": True, - "optional": False, + "enabled": False, + "optional": True, "active": True }, "ValidateInstanceEmpty": { @@ -201,9 +201,9 @@ DEFAULT_BLENDER_PUBLISH_SETTINGS = { ] }, "ExtractFBX": { - "enabled": True, + "enabled": False, "optional": True, - "active": False + "active": True }, "ExtractModelABC": { "enabled": True, @@ -216,9 +216,9 @@ DEFAULT_BLENDER_PUBLISH_SETTINGS = { "active": True }, "ExtractAnimationFBX": { - "enabled": True, + "enabled": False, "optional": True, - "active": False + "active": True }, "ExtractCamera": { "enabled": True, diff --git a/server_addon/maya/server/settings/main.py b/server_addon/maya/server/settings/main.py index c8021614be..55a079066c 100644 --- a/server_addon/maya/server/settings/main.py +++ b/server_addon/maya/server/settings/main.py @@ -101,7 +101,7 @@ DEFAULT_MEL_WORKSPACE_SETTINGS = "\n".join(( )) DEFAULT_MAYA_SETTING = { - "open_workfile_post_initialization": False, + "open_workfile_post_initialization": True, "explicit_plugins_loading": DEFAULT_EXPLITCIT_PLUGINS_LOADING_SETTINGS, "imageio": DEFAULT_IMAGEIO_SETTINGS, "mel_workspace": DEFAULT_MEL_WORKSPACE_SETTINGS, diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index dd8d4a0a37..e823efe681 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -785,7 +785,7 @@ DEFAULT_PUBLISH_SETTINGS = { "sync_workfile_version": False }, "CollectFbxAnimation": { - "enabled": True + "enabled": False }, "CollectFbxCamera": { "enabled": False @@ -862,7 +862,7 @@ DEFAULT_PUBLISH_SETTINGS = { ] }, "ValidatePluginPathAttributes": { - "enabled": True, + "enabled": False, "optional": False, "active": True, "attribute": [ @@ -917,12 +917,12 @@ DEFAULT_PUBLISH_SETTINGS = { "active": True }, "ValidateGLSLMaterial": { - "enabled": True, + "enabled": False, "optional": False, "active": True }, "ValidateGLSLPlugin": { - "enabled": True, + "enabled": False, "optional": False, "active": True }, @@ -1154,7 +1154,7 @@ DEFAULT_PUBLISH_SETTINGS = { "active": True }, "ExtractProxyAlembic": { - "enabled": True, + "enabled": False, "families": [ "proxyAbc" ] @@ -1311,7 +1311,7 @@ DEFAULT_PUBLISH_SETTINGS = { "bake_attributes": "[]" }, "ExtractGLB": { - "enabled": True, + "enabled": False, "active": True, "ogsfx_path": "/maya2glTF/PBR/shaders/glTF_PBR.ogsfx" }, diff --git a/server_addon/maya/server/settings/workfile_build_settings.py b/server_addon/maya/server/settings/workfile_build_settings.py index dc56d1a320..2c7fea85c4 100644 --- a/server_addon/maya/server/settings/workfile_build_settings.py +++ b/server_addon/maya/server/settings/workfile_build_settings.py @@ -104,7 +104,7 @@ DEFAULT_WORKFILE_SETTING = { { "product_name_filters": [], "product_types": [ - "sedress" + "setdress" ], "repre_names": [ "ma" diff --git a/server_addon/unreal/server/settings.py b/server_addon/unreal/server/settings.py index 479e041e25..110ccc563a 100644 --- a/server_addon/unreal/server/settings.py +++ b/server_addon/unreal/server/settings.py @@ -53,11 +53,11 @@ class UnrealSettings(BaseSettingsModel): DEFAULT_VALUES = { - "level_sequences_for_layouts": False, + "level_sequences_for_layouts": True, "delete_unmatched_assets": False, "render_config_path": "", "preroll_frames": 0, - "render_format": "png", + "render_format": "exr", "project_setup": { "dev_mode": False } From 244db2f18d74cfefe730143550b5d97253a98ea0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 6 Dec 2023 16:37:28 +0100 Subject: [PATCH 283/327] :recycle: pass all environments --- openpype/pype_commands.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/pype_commands.py b/openpype/pype_commands.py index b6535e0835..960e9d410d 100644 --- a/openpype/pype_commands.py +++ b/openpype/pype_commands.py @@ -185,8 +185,7 @@ class PypeCommands: task, app, env_group=env_group, - launch_type=LaunchTypes.farm_render, - env={} + launch_type=LaunchTypes.farm_render ) else: env = os.environ.copy() From f9509f361317aa68e5d830e135ee0802b695e266 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 6 Dec 2023 16:40:24 +0100 Subject: [PATCH 284/327] Deadline: correct webservice couldn't be selected in Ayon (#6007) * Refactor necessary for Ayon changes in Setting model * Removed unnecessary configuration MongoDB is not a thing in Ayon. * Changed DL model to use dynamic enum Enum values are set in Studio Settings, in Project settings will be selected from. Used this way to be close to OP variant and support both until OP is deprecated. * Hound * Refactor with use of AYON_SERVER_ENABLED This will make it simpler to remove obsolete code in the future. --------- Co-authored-by: Libor Batek <112623825+LiborBatek@users.noreply.github.com> --- .../collect_default_deadline_server.py | 36 ++++++++++--------- server_addon/deadline/server/settings/main.py | 27 +++++++++++--- .../server/settings/publish_plugins.py | 15 -------- 3 files changed, 43 insertions(+), 35 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/collect_default_deadline_server.py b/openpype/modules/deadline/plugins/publish/collect_default_deadline_server.py index 58721efad3..cd4cde2519 100644 --- a/openpype/modules/deadline/plugins/publish/collect_default_deadline_server.py +++ b/openpype/modules/deadline/plugins/publish/collect_default_deadline_server.py @@ -2,6 +2,8 @@ """Collect default Deadline server.""" import pyblish.api +from openpype import AYON_SERVER_ENABLED + class CollectDefaultDeadlineServer(pyblish.api.ContextPlugin): """Collect default Deadline Webservice URL. @@ -30,24 +32,26 @@ class CollectDefaultDeadlineServer(pyblish.api.ContextPlugin): self.log.error("Cannot get OpenPype Deadline module.") raise AssertionError("OpenPype Deadline module not found.") - # get default deadline webservice url from deadline module - self.log.debug(deadline_module.deadline_urls) - context.data["defaultDeadline"] = deadline_module.deadline_urls["default"] # noqa: E501 + deadline_settings = context.data["project_settings"]["deadline"] + deadline_server_name = None + if AYON_SERVER_ENABLED: + deadline_server_name = deadline_settings["deadline_server"] + else: + deadline_servers = deadline_settings["deadline_servers"] + if deadline_servers: + deadline_server_name = deadline_servers[0] - context.data["deadlinePassMongoUrl"] = self.pass_mongo_url + context.data["deadlinePassMongoUrl"] = self.pass_mongo_url - deadline_servers = (context.data - ["project_settings"] - ["deadline"] - ["deadline_servers"]) - if deadline_servers: - deadline_server_name = deadline_servers[0] + deadline_webservice = None + if deadline_server_name: deadline_webservice = deadline_module.deadline_urls.get( deadline_server_name) - if deadline_webservice: - context.data["defaultDeadline"] = deadline_webservice - self.log.debug("Overriding from project settings with {}".format( # noqa: E501 - deadline_webservice)) - context.data["defaultDeadline"] = \ - context.data["defaultDeadline"].strip().rstrip("/") + default_deadline_webservice = deadline_module.deadline_urls["default"] + deadline_webservice = ( + deadline_webservice + or default_deadline_webservice + ) + + context.data["defaultDeadline"] = deadline_webservice.strip().rstrip("/") # noqa diff --git a/server_addon/deadline/server/settings/main.py b/server_addon/deadline/server/settings/main.py index f158b7464d..f766ef9db8 100644 --- a/server_addon/deadline/server/settings/main.py +++ b/server_addon/deadline/server/settings/main.py @@ -14,15 +14,35 @@ class ServerListSubmodel(BaseSettingsModel): value: str = Field(title="Value") +async def defined_deadline_ws_name_enum_resolver( + addon: "BaseServerAddon", + settings_variant: str = "production", + project_name: str | None = None, +) -> list[str]: + """Provides list of names of configured Deadline webservice urls.""" + if addon is None: + return [] + + settings = await addon.get_studio_settings(variant=settings_variant) + + ws_urls = [] + for deadline_url_item in settings.deadline_urls: + ws_urls.append(deadline_url_item.name) + + return ws_urls + + class DeadlineSettings(BaseSettingsModel): deadline_urls: list[ServerListSubmodel] = Field( default_factory=list, title="System Deadline Webservice URLs", scope=["studio"], ) - deadline_servers: list[str] = Field( - title="Project deadline servers", + deadline_server: str = Field( + title="Project deadline server", section="---", + scope=["project"], + enum_resolver=defined_deadline_ws_name_enum_resolver ) publish: PublishPluginsModel = Field( default_factory=PublishPluginsModel, @@ -42,7 +62,6 @@ DEFAULT_VALUES = { "value": "http://127.0.0.1:8082" } ], - # TODO: this needs to be dynamic from "deadline_urls" - "deadline_servers": [], + "deadline_server": "default", "publish": DEFAULT_DEADLINE_PLUGINS_SETTINGS } diff --git a/server_addon/deadline/server/settings/publish_plugins.py b/server_addon/deadline/server/settings/publish_plugins.py index 0781902fe5..a989f3ad9d 100644 --- a/server_addon/deadline/server/settings/publish_plugins.py +++ b/server_addon/deadline/server/settings/publish_plugins.py @@ -3,12 +3,6 @@ from pydantic import Field, validator from ayon_server.settings import BaseSettingsModel, ensure_unique_names -class CollectDefaultDeadlineServerModel(BaseSettingsModel): - """Settings for event handlers running in ftrack service.""" - - pass_mongo_url: bool = Field(title="Pass Mongo url to job") - - class CollectDeadlinePoolsModel(BaseSettingsModel): """Settings Deadline default pools.""" @@ -286,12 +280,6 @@ class ProcessSubmittedJobOnFarmModel(BaseSettingsModel): class PublishPluginsModel(BaseSettingsModel): - CollectDefaultDeadlineServer: CollectDefaultDeadlineServerModel = Field( - default_factory=CollectDefaultDeadlineServerModel, - title="Default Deadline Webservice") - CollectDefaultDeadlineServer: CollectDefaultDeadlineServerModel = Field( - default_factory=CollectDefaultDeadlineServerModel, - title="Default Deadline Webservice") CollectDeadlinePools: CollectDeadlinePoolsModel = Field( default_factory=CollectDeadlinePoolsModel, title="Default Pools") @@ -332,9 +320,6 @@ class PublishPluginsModel(BaseSettingsModel): DEFAULT_DEADLINE_PLUGINS_SETTINGS = { - "CollectDefaultDeadlineServer": { - "pass_mongo_url": True - }, "CollectDeadlinePools": { "primary_pool": "", "secondary_pool": "" From 15eabaf3ad871e134a6732a9460c1d04f1573f8c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 6 Dec 2023 16:41:14 +0100 Subject: [PATCH 285/327] Revert "Ayon: Updated name of Adobe extension to Ayon" (#6010) --- openpype/hosts/aftereffects/api/README.md | 20 ++++++++--------- openpype/hosts/aftereffects/api/extension.zxp | Bin 106275 -> 103005 bytes .../hosts/aftereffects/api/extension/.debug | 21 +++++++++--------- .../api/extension/CSXS/manifest.xml | 12 +++++----- .../api/extension/icons/ayon_logo.png | Bin 3538 -> 0 bytes .../panel.png => aftereffects/api/panel.PNG} | Bin openpype/hosts/aftereffects/api/panel.png | Bin 16269 -> 0 bytes .../hosts/aftereffects/api/panel_failure.PNG | Bin 0 -> 13568 bytes .../hosts/aftereffects/api/panel_failure.png | Bin 13115 -> 0 bytes openpype/hosts/photoshop/api/README.md | 10 ++++----- openpype/hosts/photoshop/api/extension.zxp | Bin 55656 -> 54056 bytes openpype/hosts/photoshop/api/extension/.debug | 4 ++-- .../photoshop/api/extension/CSXS/manifest.xml | 10 ++++----- .../api/extension/icons/avalon-logo-48.png | Bin 0 -> 1362 bytes .../api/extension/icons/ayon_logo.png | Bin 3538 -> 0 bytes openpype/hosts/photoshop/api/panel.PNG | Bin 0 -> 8756 bytes .../hosts/photoshop/api/panel_failure.PNG | Bin 0 -> 13568 bytes .../hosts/photoshop/api/panel_failure.png | Bin 13115 -> 0 bytes 18 files changed, 38 insertions(+), 39 deletions(-) delete mode 100644 openpype/hosts/aftereffects/api/extension/icons/ayon_logo.png rename openpype/hosts/{photoshop/api/panel.png => aftereffects/api/panel.PNG} (100%) delete mode 100644 openpype/hosts/aftereffects/api/panel.png create mode 100644 openpype/hosts/aftereffects/api/panel_failure.PNG delete mode 100644 openpype/hosts/aftereffects/api/panel_failure.png create mode 100644 openpype/hosts/photoshop/api/extension/icons/avalon-logo-48.png delete mode 100644 openpype/hosts/photoshop/api/extension/icons/ayon_logo.png create mode 100644 openpype/hosts/photoshop/api/panel.PNG create mode 100644 openpype/hosts/photoshop/api/panel_failure.PNG delete mode 100644 openpype/hosts/photoshop/api/panel_failure.png diff --git a/openpype/hosts/aftereffects/api/README.md b/openpype/hosts/aftereffects/api/README.md index 9c4bad3689..790c9f859a 100644 --- a/openpype/hosts/aftereffects/api/README.md +++ b/openpype/hosts/aftereffects/api/README.md @@ -1,6 +1,6 @@ # AfterEffects Integration -Requirements: This extension requires use of Javascript engine, which is +Requirements: This extension requires use of Javascript engine, which is available since CC 16.0. Please check your File>Project Settings>Expressions>Expressions Engine @@ -13,28 +13,26 @@ The After Effects integration requires two components to work; `extension` and ` To install the extension download [Extension Manager Command Line tool (ExManCmd)](https://github.com/Adobe-CEP/Getting-Started-guides/tree/master/Package%20Distribute%20Install#option-2---exmancmd). ``` -ExManCmd /install {path to addon}/api/extension.zxp +ExManCmd /install {path to avalon-core}\avalon\photoshop\extension.zxp ``` OR download [Anastasiy’s Extension Manager](https://install.anastasiy.com/) -`{path to addon}` will be most likely in your AppData (on Windows, in your user data folder in Linux and MacOS.) - ### Server The easiest way to get the server and After Effects launch is with: ``` -python -c ^"import openpype.hosts.photoshop;openpype.hosts..aftereffects.launch(""c:\Program Files\Adobe\Adobe After Effects 2020\Support Files\AfterFX.exe"")^" +python -c ^"import avalon.photoshop;avalon.aftereffects.launch(""c:\Program Files\Adobe\Adobe After Effects 2020\Support Files\AfterFX.exe"")^" ``` `avalon.aftereffects.launch` launches the application and server, and also closes the server when After Effects exists. ## Usage -The After Effects extension can be found under `Window > Extensions > AYON`. Once launched you should be presented with a panel like this: +The After Effects extension can be found under `Window > Extensions > OpenPype`. Once launched you should be presented with a panel like this: -![Ayon Panel](panel.png "Ayon Panel") +![Avalon Panel](panel.PNG "Avalon Panel") ## Developing @@ -45,8 +43,8 @@ When developing the extension you can load it [unsigned](https://github.com/Adob When signing the extension you can use this [guide](https://github.com/Adobe-CEP/Getting-Started-guides/tree/master/Package%20Distribute%20Install#package-distribute-install-guide). ``` -ZXPSignCmd -selfSignedCert NA NA Ayon Avalon-After-Effects Ayon extension.p12 -ZXPSignCmd -sign {path to addon}/api/extension {path to addon}/api/extension.zxp extension.p12 Ayon +ZXPSignCmd -selfSignedCert NA NA Avalon Avalon-After-Effects avalon extension.p12 +ZXPSignCmd -sign {path to avalon-core}\avalon\aftereffects\extension {path to avalon-core}\avalon\aftereffects\extension.zxp extension.p12 avalon ``` ### Plugin Examples @@ -54,14 +52,14 @@ ZXPSignCmd -sign {path to addon}/api/extension {path to addon}/api/extension.zxp These plugins were made with the [polly config](https://github.com/mindbender-studio/config). To fully integrate and load, you will have to use this config and add `image` to the [integration plugin](https://github.com/mindbender-studio/config/blob/master/polly/plugins/publish/integrate_asset.py). Expected deployed extension location on default Windows: -`c:\Program Files (x86)\Common Files\Adobe\CEP\extensions\io.ynput.AE.panel` +`c:\Program Files (x86)\Common Files\Adobe\CEP\extensions\com.openpype.AE.panel` For easier debugging of Javascript: https://community.adobe.com/t5/download-install/adobe-extension-debuger-problem/td-p/10911704?page=1 Add (optional) --enable-blink-features=ShadowDOMV0,CustomElementsV0 when starting Chrome then localhost:8092 -Or use Visual Studio Code https://medium.com/adobetech/extendscript-debugger-for-visual-studio-code-public-release-a2ff6161fa01 +Or use Visual Studio Code https://medium.com/adobetech/extendscript-debugger-for-visual-studio-code-public-release-a2ff6161fa01 ## Resources - https://javascript-tools-guide.readthedocs.io/introduction/index.html - https://github.com/Adobe-CEP/Getting-Started-guides diff --git a/openpype/hosts/aftereffects/api/extension.zxp b/openpype/hosts/aftereffects/api/extension.zxp index 104a5c9e997577959bd8c134fc2ac9febbb8797a..933dc7dc6cab34b6a1630b25d11a2167fa0aef7a 100644 GIT binary patch delta 8354 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 delta 11531 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 I*u? + - + - + - + - + - + - + - + - + - + + \ No newline at end of file diff --git a/openpype/hosts/aftereffects/api/extension/CSXS/manifest.xml b/openpype/hosts/aftereffects/api/extension/CSXS/manifest.xml index cf6ba67f44..7329a9e723 100644 --- a/openpype/hosts/aftereffects/api/extension/CSXS/manifest.xml +++ b/openpype/hosts/aftereffects/api/extension/CSXS/manifest.xml @@ -1,8 +1,8 @@ - + - + @@ -38,7 +38,7 @@ - + ./index.html @@ -49,7 +49,7 @@ Panel - AYON + OpenPype 200 @@ -66,7 +66,7 @@ - ./icons/ayon_logo.png + ./icons/iconNormal.png ./icons/iconRollover.png ./icons/iconDisabled.png ./icons/iconDarkNormal.png diff --git a/openpype/hosts/aftereffects/api/extension/icons/ayon_logo.png b/openpype/hosts/aftereffects/api/extension/icons/ayon_logo.png deleted file mode 100644 index 3a96f8e2b499baa337cdc5a4d3cdf547f9ded972..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3538 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 E-zj>|X#fBK diff --git a/openpype/hosts/photoshop/api/panel.png b/openpype/hosts/aftereffects/api/panel.PNG similarity index 100% rename from openpype/hosts/photoshop/api/panel.png rename to openpype/hosts/aftereffects/api/panel.PNG diff --git a/openpype/hosts/aftereffects/api/panel.png b/openpype/hosts/aftereffects/api/panel.png deleted file mode 100644 index d05ed354286dc0dd00b5d7a2df40ba1e3e1fa6cb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16269 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 + OpenPype 300 @@ -44,7 +44,7 @@ - ./icons/ayon_logo.png + ./icons/avalon-logo-48.png diff --git a/openpype/hosts/photoshop/api/extension/icons/avalon-logo-48.png b/openpype/hosts/photoshop/api/extension/icons/avalon-logo-48.png new file mode 100644 index 0000000000000000000000000000000000000000..33fe2a606bd1ac9d285eb0d6a90b9b14150ca3c4 GIT binary patch literal 1362 zcmV-Y1+DstP)5JpvKiH|A7SK4P~kT{6`M*9LrdQ!Ns9=$qj3(E_W@4gmP#=ht)#^0_psT^(5Px6qr0)mBB%5&-P}{Y*9ph@Pcn`!ete zwiE<#115v#ScdV2GBk!NTFTzWbF>Xip`p8%&KqcqI~Jb6tC``Vaf&07o~axnzSGF( z(ok|5&-4zgtV5rc$qke?7a8cU$D55m^%IcuOgXaxfTb~yegblyEaWJw%`Qe=-M%S@ zhOXSbt2KkcJv{&)s&PL6vC{g1Y-aKYBs(yc@x{whhk_0fK#=N=)Uup zs)>qe=dc=h3&3Gwr10?^8zc#g%1L4Xs{p!rj(uw=)9Szs&#`@sH{=+ zG+fz{pjE0VR%8l+hOX;W8`PbV32glOJ!~I2VXJkTz5Ufkuk(!F8z4>Ok_kkI+Kb}3)n06_ssJy4_*!y{BAe4)9jbBbSR!>UnLxyMT9bL9_?YdfL@K^^G6aZ)C$Qje z(NzKf2bZq2#ed1=gx1ZJQM{TNMk>CBw!wSvUjy@gS4qs1_a85GREVYsFz!+tU$`&M%7iR@HuBiw5bSa5S}|?)>G0PCUMb-Q{Pf zZt0{hEhroOCi1l=h%&q$mkBdG$MzLns~iea1>hEds{qcP5QbL){0`u*@Qfwke+13^ UGpuMiD*ylh07*qoM6N<$g1d2qT>t<8 literal 0 HcmV?d00001 diff --git a/openpype/hosts/photoshop/api/extension/icons/ayon_logo.png b/openpype/hosts/photoshop/api/extension/icons/ayon_logo.png deleted file mode 100644 index 3a96f8e2b499baa337cdc5a4d3cdf547f9ded972..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3538 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 E-zj>|X#fBK diff --git a/openpype/hosts/photoshop/api/panel.PNG b/openpype/hosts/photoshop/api/panel.PNG new file mode 100644 index 0000000000000000000000000000000000000000..be5db3b8df08aa426b92de61eacc9afd00e93d45 GIT binary patch literal 8756 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 literal 0 HcmV?d00001 diff --git a/openpype/hosts/photoshop/api/panel_failure.png b/openpype/hosts/photoshop/api/panel_failure.png deleted file mode 100644 index 6e52a77d22d54708b8bf0d1abac676ce723bd254..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13115 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 Date: Wed, 6 Dec 2023 23:46:21 +0800 Subject: [PATCH 286/327] update the repair function in validate_rendersettings --- .../hosts/maya/plugins/publish/validate_rendersettings.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py index 3409b4ec91..f2a98eab32 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py @@ -371,7 +371,6 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): continue for node in data["nodes"]: lib.set_attribute(data["attribute"], data["values"][0], node) - with lib.renderlayer(layer_node): # Repair animation must be enabled @@ -392,13 +391,11 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): if renderer != "renderman": prefix_attr = RenderSettings.get_image_prefix_attr(renderer) fname_prefix = default_prefix - cmds.setAttr("{}.{}".format(node, prefix_attr), - fname_prefix, type="string") + cmds.setAttr(prefix_attr, fname_prefix, type="string") # Repair padding padding_attr = RenderSettings.get_padding_attr(renderer) - cmds.setAttr("{}.{}".format(node, padding_attr), - cls.DEFAULT_PADDING) + cmds.setAttr(padding_attr, cls.DEFAULT_PADDING) else: # renderman handles stuff differently cmds.setAttr("rmanGlobals.imageFileFormat", From 79cd0e6991b80ee57f91489b28adb62b66b29352 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 6 Dec 2023 16:57:13 +0100 Subject: [PATCH 287/327] revert changes in OP --- .../defaults/project_anatomy/attributes.json | 12 +- .../defaults/project_anatomy/templates.json | 12 + .../defaults/project_settings/blender.json | 20 +- .../defaults/project_settings/maya.json | 18 +- .../defaults/project_settings/unreal.json | 4 +- .../system_settings/applications.json | 1196 ++++++++++------- .../defaults/system_settings/tools.json | 423 +----- 7 files changed, 824 insertions(+), 861 deletions(-) diff --git a/openpype/settings/defaults/project_anatomy/attributes.json b/openpype/settings/defaults/project_anatomy/attributes.json index f388a9336b..0cc414fb69 100644 --- a/openpype/settings/defaults/project_anatomy/attributes.json +++ b/openpype/settings/defaults/project_anatomy/attributes.json @@ -10,8 +10,16 @@ "resolutionHeight": 1080, "pixelAspect": 1.0, "applications": [ - "maya/2024", - "nuke/14-0" + "maya/2020", + "nuke/12-2", + "nukex/12-2", + "hiero/12-2", + "resolve/stable", + "houdini/18-5", + "blender/2-91", + "harmony/20", + "photoshop/2021", + "aftereffects/2021" ], "tools_env": [], "active": true diff --git a/openpype/settings/defaults/project_anatomy/templates.json b/openpype/settings/defaults/project_anatomy/templates.json index 6c3e038d27..5766a09100 100644 --- a/openpype/settings/defaults/project_anatomy/templates.json +++ b/openpype/settings/defaults/project_anatomy/templates.json @@ -38,6 +38,16 @@ "file": "{subset}_{@version}<_{output}><.{@frame}>.{ext}", "path": "{@folder}/{@file}" }, + "simpleUnrealTextureHero": { + "folder": "{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/hero", + "file": "{originalBasename}.{ext}", + "path": "{@folder}/{@file}" + }, + "simpleUnrealTexture": { + "folder": "{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{@version}", + "file": "{originalBasename}_{@version}.{ext}", + "path": "{@folder}/{@file}" + }, "online": { "folder": "{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/{@version}", "file": "{originalBasename}<.{@frame}><_{udim}>.{ext}", @@ -58,6 +68,8 @@ }, "__dynamic_keys_labels__": { "maya2unreal": "Maya to Unreal", + "simpleUnrealTextureHero": "Simple Unreal Texture - Hero", + "simpleUnrealTexture": "Simple Unreal Texture", "online": "online", "tycache": "tycache", "source": "source", diff --git a/openpype/settings/defaults/project_settings/blender.json b/openpype/settings/defaults/project_settings/blender.json index 937d177d4a..385e97ef91 100644 --- a/openpype/settings/defaults/project_settings/blender.json +++ b/openpype/settings/defaults/project_settings/blender.json @@ -2,7 +2,7 @@ "unit_scale_settings": { "enabled": true, "apply_on_opening": false, - "base_file_unit_scale": 1.00 + "base_file_unit_scale": 0.01 }, "set_resolution_startup": true, "set_frames_startup": true, @@ -31,7 +31,7 @@ }, "publish": { "ValidateCameraZeroKeyframe": { - "enabled": false, + "enabled": true, "optional": true, "active": true }, @@ -62,13 +62,13 @@ "active": true }, "ValidateTransformZero": { - "enabled": false, - "optional": true, + "enabled": true, + "optional": false, "active": true }, "ValidateNoColonsInName": { - "enabled": false, - "optional": true, + "enabled": true, + "optional": false, "active": true }, "ValidateInstanceEmpty": { @@ -90,9 +90,9 @@ ] }, "ExtractFBX": { - "enabled": false, + "enabled": true, "optional": true, - "active": true + "active": false }, "ExtractModelABC": { "enabled": true, @@ -105,9 +105,9 @@ "active": true }, "ExtractAnimationFBX": { - "enabled": false, + "enabled": true, "optional": true, - "active": true + "active": false }, "ExtractCamera": { "enabled": true, diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 3ecefef4f9..7719a5e255 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -1,5 +1,5 @@ { - "open_workfile_post_initialization": true, + "open_workfile_post_initialization": false, "explicit_plugins_loading": { "enabled": false, "plugins_to_load": [ @@ -458,7 +458,7 @@ "per_task_type": [] }, "scriptsmenu": { - "name": "Custom Tools", + "name": "OpenPype Tools", "definition": [ { "type": "action", @@ -708,7 +708,7 @@ "sync_workfile_version": false }, "CollectFbxAnimation": { - "enabled": false + "enabled": true }, "CollectFbxCamera": { "enabled": false @@ -785,7 +785,7 @@ ] }, "ValidatePluginPathAttributes": { - "enabled": false, + "enabled": true, "optional": false, "active": true, "attribute": { @@ -840,12 +840,12 @@ "active": true }, "ValidateGLSLMaterial": { - "enabled": false, + "enabled": true, "optional": false, "active": true }, "ValidateGLSLPlugin": { - "enabled": false, + "enabled": true, "optional": false, "active": true }, @@ -1096,7 +1096,7 @@ "active": true }, "ExtractProxyAlembic": { - "enabled": false, + "enabled": true, "families": [ "proxyAbc" ] @@ -1383,7 +1383,7 @@ "keep_image_planes": false }, "ExtractGLB": { - "enabled": false, + "enabled": true, "active": true, "ogsfx_path": "/maya2glTF/PBR/shaders/glTF_PBR.ogsfx" }, @@ -1581,7 +1581,7 @@ { "subset_name_filters": [], "families": [ - "setdress" + "sedress" ], "repre_names": [ "ma" diff --git a/openpype/settings/defaults/project_settings/unreal.json b/openpype/settings/defaults/project_settings/unreal.json index dc7e4229aa..20e55c74f0 100644 --- a/openpype/settings/defaults/project_settings/unreal.json +++ b/openpype/settings/defaults/project_settings/unreal.json @@ -10,11 +10,11 @@ "rules": {} } }, - "level_sequences_for_layouts": true, + "level_sequences_for_layouts": false, "delete_unmatched_assets": false, "render_config_path": "", "preroll_frames": 0, - "render_format": "exr", + "render_format": "png", "project_setup": { "dev_mode": false } diff --git a/openpype/settings/defaults/system_settings/applications.json b/openpype/settings/defaults/system_settings/applications.json index 7dd767598e..a5283751e9 100644 --- a/openpype/settings/defaults/system_settings/applications.json +++ b/openpype/settings/defaults/system_settings/applications.json @@ -18,9 +18,7 @@ "windows": [ "C:\\Program Files\\Autodesk\\Maya2024\\bin\\maya.exe" ], - "darwin": [ - "/Applications/Autodesk/maya2024/Maya.app" - ], + "darwin": [], "linux": [ "/usr/autodesk/maya2024/bin/maya" ] @@ -40,9 +38,7 @@ "windows": [ "C:\\Program Files\\Autodesk\\Maya2023\\bin\\maya.exe" ], - "darwin": [ - "/Applications/Autodesk/maya2023/Maya.app" - ], + "darwin": [], "linux": [ "/usr/autodesk/maya2023/bin/maya" ] @@ -62,9 +58,7 @@ "windows": [ "C:\\Program Files\\Autodesk\\Maya2022\\bin\\maya.exe" ], - "darwin": [ - "/Applications/Autodesk/maya2022/Maya.app" - ], + "darwin": [], "linux": [ "/usr/autodesk/maya2022/bin/maya" ] @@ -163,24 +157,6 @@ "environment": { "3DSMAX_VERSION": "2023" } - }, - "2024": { - "use_python_2": false, - "executables": { - "windows": [ - "C:\\Program Files\\Autodesk\\3ds Max 2024\\3dsmax.exe" - ], - "darwin": [], - "linux": [] - }, - "arguments": { - "windows": [], - "darwin": [], - "linux": [] - }, - "environment": { - "3DSMAX_VERSION": "2024" - } } } }, @@ -262,55 +238,13 @@ ] }, "variants": { - "15-0": { - "use_python_2": false, - "executables": { - "windows": [ - "C:\\Program Files\\Nuke15.0v2\\Nuke15.0.exe" - ], - "darwin": [ - "/Applications/Nuke15.0v2/Nuke15.0v2.app" - ], - "linux": [ - "/usr/local/Nuke15.0v2/Nuke15.0" - ] - }, - "arguments": { - "windows": [], - "darwin": [], - "linux": [] - }, - "environment": {} - }, - "14-0": { - "use_python_2": false, - "executables": { - "windows": [ - "C:\\Program Files\\Nuke14.0v5\\Nuke14.0.exe" - ], - "darwin": [ - "/Applications/Nuke14.0v5/Nuke14.0v5.app" - ], - "linux": [ - "/usr/local/Nuke14.0v5/Nuke14.0" - ] - }, - "arguments": { - "windows": [], - "darwin": [], - "linux": [] - }, - "environment": {} - }, "13-2": { "use_python_2": false, "executables": { "windows": [ "C:\\Program Files\\Nuke13.2v1\\Nuke13.2.exe" ], - "darwin": [ - "/Applications/Nuke13.2v1/Nuke13.2v1.app" - ], + "darwin": [], "linux": [ "/usr/local/Nuke13.2v1/Nuke13.2" ] @@ -322,17 +256,33 @@ }, "environment": {} }, + "13-0": { + "use_python_2": false, + "executables": { + "windows": [ + "C:\\Program Files\\Nuke13.0v1\\Nuke13.0.exe" + ], + "darwin": [], + "linux": [ + "/usr/local/Nuke13.0v1/Nuke13.0" + ] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": {} + }, "12-2": { "use_python_2": true, "executables": { "windows": [ "C:\\Program Files\\Nuke12.2v3\\Nuke12.2.exe" ], - "darwin": [ - "/Applications/Nuke12.2v3/Nuke12.2v3.app" - ], + "darwin": [], "linux": [ - "/usr/local/Nuke12.2v3/Nuke12.2" + "/usr/local/Nuke12.2v3Nuke12.2" ] }, "arguments": { @@ -342,11 +292,82 @@ }, "environment": {} }, + "12-0": { + "use_python_2": true, + "executables": { + "windows": [ + "C:\\Program Files\\Nuke12.0v1\\Nuke12.0.exe" + ], + "darwin": [], + "linux": [ + "/usr/local/Nuke12.0v1/Nuke12.0" + ] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": {} + }, + "11-3": { + "use_python_2": true, + "executables": { + "windows": [ + "C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe" + ], + "darwin": [], + "linux": [ + "/usr/local/Nuke11.3v5/Nuke11.3" + ] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": {} + }, + "11-2": { + "use_python_2": true, + "executables": { + "windows": [ + "C:\\Program Files\\Nuke11.2v2\\Nuke11.2.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": {} + }, + "11-0": { + "use_python_2": true, + "executables": { + "windows": [ + "C:\\Program Files\\Nuke11.0v4\\Nuke11.0.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": {} + }, "__dynamic_keys_labels__": { - "15-0": "15.0", - "14-0": "14.0", "13-2": "13.2", - "12-2": "12.2" + "13-0": "13.0", + "12-2": "12.2", + "12-0": "12.0", + "11-3": "11.3", + "11-2": "11.2", + "11-0": "11.0" } } }, @@ -362,67 +383,13 @@ ] }, "variants": { - "15-0": { - "use_python_2": false, - "executables": { - "windows": [ - "C:\\Program Files\\Nuke15.0v2\\Nuke15.0.exe" - ], - "darwin": [ - "/Applications/Nuke15.0v2/Nuke15.0v2.app" - ], - "linux": [ - "/usr/local/Nuke15.0v2/Nuke15.0" - ] - }, - "arguments": { - "windows": [ - "--nukeassist" - ], - "darwin": [ - "--nukeassist" - ], - "linux": [ - "--nukeassist" - ] - }, - "environment": {} - }, - "14-0": { - "use_python_2": false, - "executables": { - "windows": [ - "C:\\Program Files\\Nuke14.0v5\\Nuke14.0.exe" - ], - "darwin": [ - "/Applications/Nuke14.0v5/Nuke14.0v5.app" - ], - "linux": [ - "/usr/local/Nuke14.0v5/Nuke14.0" - ] - }, - "arguments": { - "windows": [ - "--nukeassist" - ], - "darwin": [ - "--nukeassist" - ], - "linux": [ - "--nukeassist" - ] - }, - "environment": {} - }, "13-2": { "use_python_2": false, "executables": { "windows": [ "C:\\Program Files\\Nuke13.2v1\\Nuke13.2.exe" ], - "darwin": [ - "/Applications/Nuke13.2v1/Nuke13.2v1.app" - ], + "darwin": [], "linux": [ "/usr/local/Nuke13.2v1/Nuke13.2" ] @@ -440,17 +407,39 @@ }, "environment": {} }, + "13-0": { + "use_python_2": false, + "executables": { + "windows": [ + "C:\\Program Files\\Nuke13.0v1\\Nuke13.0.exe" + ], + "darwin": [], + "linux": [ + "/usr/local/Nuke13.0v1/Nuke13.0" + ] + }, + "arguments": { + "windows": [ + "--nukeassist" + ], + "darwin": [ + "--nukeassist" + ], + "linux": [ + "--nukeassist" + ] + }, + "environment": {} + }, "12-2": { "use_python_2": true, "executables": { "windows": [ "C:\\Program Files\\Nuke12.2v3\\Nuke12.2.exe" ], - "darwin": [ - "/Applications/Nuke12.2v3/Nuke12.2v3.app" - ], + "darwin": [], "linux": [ - "/usr/local/Nuke12.2v3/Nuke12.2" + "/usr/local/Nuke12.2v3Nuke12.2" ] }, "arguments": { @@ -466,11 +455,83 @@ }, "environment": {} }, + "12-0": { + "use_python_2": true, + "executables": { + "windows": [ + "C:\\Program Files\\Nuke12.0v1\\Nuke12.0.exe" + ], + "darwin": [], + "linux": [ + "/usr/local/Nuke12.0v1/Nuke12.0" + ] + }, + "arguments": { + "windows": [ + "--nukeassist" + ], + "darwin": [ + "--nukeassist" + ], + "linux": [ + "--nukeassist" + ] + }, + "environment": {} + }, + "11-3": { + "use_python_2": true, + "executables": { + "windows": [ + "C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe" + ], + "darwin": [], + "linux": [ + "/usr/local/Nuke11.3v5/Nuke11.3" + ] + }, + "arguments": { + "windows": [ + "--nukeassist" + ], + "darwin": [ + "--nukeassist" + ], + "linux": [ + "--nukeassist" + ] + }, + "environment": {} + }, + "11-2": { + "use_python_2": true, + "executables": { + "windows": [ + "C:\\Program Files\\Nuke11.2v2\\Nuke11.2.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [ + "--nukeassist" + ], + "darwin": [ + "--nukeassist" + ], + "linux": [ + "--nukeassist" + ] + }, + "environment": {} + }, "__dynamic_keys_labels__": { - "15-0": "15.0", - "14-0": "14.0", "13-2": "13.2", - "12-2": "12.2" + "13-0": "13.0", + "12-2": "12.2", + "12-0": "12.0", + "11-3": "11.3", + "11-2": "11.2" } } }, @@ -486,67 +547,13 @@ ] }, "variants": { - "15-0": { - "use_python_2": false, - "executables": { - "windows": [ - "C:\\Program Files\\Nuke15.0v2\\Nuke15.0.exe" - ], - "darwin": [ - "/Applications/Nuke15.0v2/Nuke15.0v2.app" - ], - "linux": [ - "/usr/local/Nuke15.0v2/Nuke15.0" - ] - }, - "arguments": { - "windows": [ - "--nukex" - ], - "darwin": [ - "--nukex" - ], - "linux": [ - "--nukex" - ] - }, - "environment": {} - }, - "14-0": { - "use_python_2": false, - "executables": { - "windows": [ - "C:\\Program Files\\Nuke14.0v5\\Nuke14.0.exe" - ], - "darwin": [ - "/Applications/Nuke14.0v5/Nuke14.0v5.app" - ], - "linux": [ - "/usr/local/Nuke14.0v5/Nuke14.0" - ] - }, - "arguments": { - "windows": [ - "--nukex" - ], - "darwin": [ - "--nukex" - ], - "linux": [ - "--nukex" - ] - }, - "environment": {} - }, "13-2": { "use_python_2": false, "executables": { "windows": [ "C:\\Program Files\\Nuke13.2v1\\Nuke13.2.exe" ], - "darwin": [ - "/Applications/Nuke13.2v1/Nuke13.2v1.app" - ], + "darwin": [], "linux": [ "/usr/local/Nuke13.2v1/Nuke13.2" ] @@ -564,17 +571,39 @@ }, "environment": {} }, + "13-0": { + "use_python_2": false, + "executables": { + "windows": [ + "C:\\Program Files\\Nuke13.0v1\\Nuke13.0.exe" + ], + "darwin": [], + "linux": [ + "/usr/local/Nuke13.0v1/Nuke13.0" + ] + }, + "arguments": { + "windows": [ + "--nukex" + ], + "darwin": [ + "--nukex" + ], + "linux": [ + "--nukex" + ] + }, + "environment": {} + }, "12-2": { "use_python_2": true, "executables": { "windows": [ "C:\\Program Files\\Nuke12.2v3\\Nuke12.2.exe" ], - "darwin": [ - "/Applications/Nuke12.2v3/Nuke12.2v3.app" - ], + "darwin": [], "linux": [ - "/usr/local/Nuke12.2v3/Nuke12.2" + "/usr/local/Nuke12.2v3Nuke12.2" ] }, "arguments": { @@ -590,11 +619,83 @@ }, "environment": {} }, + "12-0": { + "use_python_2": true, + "executables": { + "windows": [ + "C:\\Program Files\\Nuke12.0v1\\Nuke12.0.exe" + ], + "darwin": [], + "linux": [ + "/usr/local/Nuke12.0v1/Nuke12.0" + ] + }, + "arguments": { + "windows": [ + "--nukex" + ], + "darwin": [ + "--nukex" + ], + "linux": [ + "--nukex" + ] + }, + "environment": {} + }, + "11-3": { + "use_python_2": true, + "executables": { + "windows": [ + "C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe" + ], + "darwin": [], + "linux": [ + "/usr/local/Nuke11.3v5/Nuke11.3" + ] + }, + "arguments": { + "windows": [ + "--nukex" + ], + "darwin": [ + "--nukex" + ], + "linux": [ + "--nukex" + ] + }, + "environment": {} + }, + "11-2": { + "use_python_2": true, + "executables": { + "windows": [ + "C:\\Program Files\\Nuke11.2v2\\Nuke11.2.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [ + "--nukex" + ], + "darwin": [ + "--nukex" + ], + "linux": [ + "--nukex" + ] + }, + "environment": {} + }, "__dynamic_keys_labels__": { - "15-0": "15.0", - "14-0": "14.0", "13-2": "13.2", - "12-2": "12.2" + "13-0": "13.0", + "12-2": "12.2", + "12-0": "12.0", + "11-3": "11.3", + "11-2": "11.2" } } }, @@ -608,67 +709,13 @@ "TAG_ASSETBUILD_STARTUP": "0" }, "variants": { - "15-0": { - "use_python_2": false, - "executables": { - "windows": [ - "C:\\Program Files\\Nuke15.0v2\\Nuke15.0.exe" - ], - "darwin": [ - "/Applications/Nuke15.0v2/Nuke15.0v2.app" - ], - "linux": [ - "/usr/local/Nuke15.0v2/Nuke15.0" - ] - }, - "arguments": { - "windows": [ - "--studio" - ], - "darwin": [ - "--studio" - ], - "linux": [ - "--studio" - ] - }, - "environment": {} - }, - "14-0": { - "use_python_2": false, - "executables": { - "windows": [ - "C:\\Program Files\\Nuke14.0v5\\Nuke14.0.exe" - ], - "darwin": [ - "/Applications/Nuke14.0v5/Nuke14.0v5.app" - ], - "linux": [ - "/usr/local/Nuke14.0v1/Nuke14.0" - ] - }, - "arguments": { - "windows": [ - "--studio" - ], - "darwin": [ - "--studio" - ], - "linux": [ - "--studio" - ] - }, - "environment": {} - }, "13-2": { "use_python_2": false, "executables": { "windows": [ "C:\\Program Files\\Nuke13.2v1\\Nuke13.2.exe" ], - "darwin": [ - "/Applications/Nuke13.2v1/Nuke13.2v1.app" - ], + "darwin": [], "linux": [ "/usr/local/Nuke13.2v1/Nuke13.2" ] @@ -686,17 +733,39 @@ }, "environment": {} }, + "13-0": { + "use_python_2": false, + "executables": { + "windows": [ + "C:\\Program Files\\Nuke13.0v1\\Nuke13.0.exe" + ], + "darwin": [], + "linux": [ + "/usr/local/Nuke13.0v1/Nuke13.0" + ] + }, + "arguments": { + "windows": [ + "--studio" + ], + "darwin": [ + "--studio" + ], + "linux": [ + "--studio" + ] + }, + "environment": {} + }, "12-2": { "use_python_2": true, "executables": { "windows": [ "C:\\Program Files\\Nuke12.2v3\\Nuke12.2.exe" ], - "darwin": [ - "/Applications/Nuke12.2v3/Nuke12.2v3.app" - ], + "darwin": [], "linux": [ - "/usr/local/Nuke12.2v3/Nuke12.2" + "/usr/local/Nuke12.2v3Nuke12.2" ] }, "arguments": { @@ -712,11 +781,81 @@ }, "environment": {} }, + "12-0": { + "use_python_2": true, + "executables": { + "windows": [ + "C:\\Program Files\\Nuke12.0v1\\Nuke12.0.exe" + ], + "darwin": [], + "linux": [ + "/usr/local/Nuke12.0v1/Nuke12.0" + ] + }, + "arguments": { + "windows": [ + "--studio" + ], + "darwin": [ + "--studio" + ], + "linux": [ + "--studio" + ] + }, + "environment": {} + }, + "11-3": { + "use_python_2": true, + "executables": { + "windows": [ + "C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe" + ], + "darwin": [], + "linux": [ + "/usr/local/Nuke11.3v5/Nuke11.3" + ] + }, + "arguments": { + "windows": [ + "--studio" + ], + "darwin": [ + "--studio" + ], + "linux": [ + "--studio" + ] + }, + "environment": {} + }, + "11-2": { + "use_python_2": true, + "executables": { + "windows": [], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [ + "--studio" + ], + "darwin": [ + "--studio" + ], + "linux": [ + "--studio" + ] + }, + "environment": {} + }, "__dynamic_keys_labels__": { - "15-0": "15.0", - "14-0": "14.0", "13-2": "13.2", - "12-2": "12.2" + "13-0": "13.0", + "12-2": "12.2", + "12-0": "12.0", + "11-3": "11.3", + "11-2": "11.2" } } }, @@ -730,67 +869,13 @@ "TAG_ASSETBUILD_STARTUP": "0" }, "variants": { - "15-0": { - "use_python_2": false, - "executables": { - "windows": [ - "C:\\Program Files\\Nuke15.0v2\\Nuke15.0.exe" - ], - "darwin": [ - "/Applications/Nuke15.0v2/Nuke15.0v2.app" - ], - "linux": [ - "/usr/local/Nuke15.0v2/Nuke15.0" - ] - }, - "arguments": { - "windows": [ - "--hiero" - ], - "darwin": [ - "--hiero" - ], - "linux": [ - "--hiero" - ] - }, - "environment": {} - }, - "14-0": { - "use_python_2": false, - "executables": { - "windows": [ - "C:\\Program Files\\Nuke14.0v5\\Nuke14.0.exe" - ], - "darwin": [ - "/Applications/Nuke14.0v5/Nuke14.0v5.app" - ], - "linux": [ - "/usr/local/Nuke14.0v5/Nuke14.0" - ] - }, - "arguments": { - "windows": [ - "--hiero" - ], - "darwin": [ - "--hiero" - ], - "linux": [ - "--hiero" - ] - }, - "environment": {} - }, "13-2": { "use_python_2": false, "executables": { "windows": [ "C:\\Program Files\\Nuke13.2v1\\Nuke13.2.exe" ], - "darwin": [ - "/Applications/Nuke13.2v1/Nuke13.2v1.app" - ], + "darwin": [], "linux": [ "/usr/local/Nuke13.2v1/Nuke13.2" ] @@ -808,17 +893,39 @@ }, "environment": {} }, + "13-0": { + "use_python_2": false, + "executables": { + "windows": [ + "C:\\Program Files\\Nuke13.0v1\\Nuke13.0.exe" + ], + "darwin": [], + "linux": [ + "/usr/local/Nuke13.0v1/Nuke13.0" + ] + }, + "arguments": { + "windows": [ + "--hiero" + ], + "darwin": [ + "--hiero" + ], + "linux": [ + "--hiero" + ] + }, + "environment": {} + }, "12-2": { "use_python_2": true, "executables": { "windows": [ "C:\\Program Files\\Nuke12.2v3\\Nuke12.2.exe" ], - "darwin": [ - "/Applications/Nuke12.2v3/Nuke12.2v3.app" - ], + "darwin": [], "linux": [ - "/usr/local/Nuke12.2v3/Nuke12.2" + "/usr/local/Nuke12.2v3Nuke12.2" ] }, "arguments": { @@ -834,11 +941,83 @@ }, "environment": {} }, + "12-0": { + "use_python_2": true, + "executables": { + "windows": [ + "C:\\Program Files\\Nuke12.0v1\\Nuke12.0.exe" + ], + "darwin": [], + "linux": [ + "/usr/local/Nuke12.0v1/Nuke12.0" + ] + }, + "arguments": { + "windows": [ + "--hiero" + ], + "darwin": [ + "--hiero" + ], + "linux": [ + "--hiero" + ] + }, + "environment": {} + }, + "11-3": { + "use_python_2": true, + "executables": { + "windows": [ + "C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe" + ], + "darwin": [], + "linux": [ + "/usr/local/Nuke11.3v5/Nuke11.3" + ] + }, + "arguments": { + "windows": [ + "--hiero" + ], + "darwin": [ + "--hiero" + ], + "linux": [ + "--hiero" + ] + }, + "environment": {} + }, + "11-2": { + "use_python_2": true, + "executables": { + "windows": [ + "C:\\Program Files\\Nuke11.2v2\\Nuke11.2.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [ + "--hiero" + ], + "darwin": [ + "--hiero" + ], + "linux": [ + "--hiero" + ] + }, + "environment": {} + }, "__dynamic_keys_labels__": { - "15-0": "15.0", - "14-0": "14.0", "13-2": "13.2", - "12-2": "12.2" + "13-0": "13.0", + "12-2": "12.2", + "12-0": "12.0", + "11-3": "11.3", + "11-2": "11.2" } } }, @@ -884,6 +1063,36 @@ "linux": [] }, "environment": {} + }, + "16": { + "executables": { + "windows": [ + "C:\\Program Files\\Blackmagic Design\\Fusion 16\\Fusion.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": {} + }, + "9": { + "executables": { + "windows": [ + "C:\\Program Files\\Blackmagic Design\\Fusion 9\\Fusion.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": {} } } }, @@ -928,41 +1137,11 @@ "host_name": "houdini", "environment": {}, "variants": { - "19-5-716": { + "18-5": { "use_python_2": true, "executables": { "windows": [ - "c:\\Program Files\\Side Effects Software\\Houdini 19.5.716\\bin\\houdini.exe" - ], - "darwin": [ - "/Applications/Houdini/Houdini19.5.716/Houdini.app" - ], - "linux": [ - "/opt/hfs19.5.716/bin/houdini" - ] - }, - "arguments": { - "windows": [], - "darwin": [], - "linux": [] - }, - "environment": { - "HOUDINI_VERSION": "19.5.716" - } - } - } - }, - "blender": { - "enabled": true, - "label": "Blender", - "icon": "{}/app_icons/blender.png", - "host_name": "blender", - "environment": {}, - "variants": { - "3-6-5": { - "executables": { - "windows": [ - "C:\\Program Files\\Blender Foundation\\Blender 3.6\\blender.exe" + "C:\\Program Files\\Side Effects Software\\Houdini 18.5.499\\bin\\houdini.exe" ], "darwin": [], "linux": [] @@ -974,8 +1153,115 @@ }, "environment": {} }, + "18": { + "use_python_2": true, + "executables": { + "windows": [], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": {} + }, + "17": { + "use_python_2": true, + "executables": { + "windows": [], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": {} + }, "__dynamic_keys_labels__": { - "3-6-5": "3.6.5 LTS" + "18-5": "18.5", + "18": "18", + "17": "17" + } + } + }, + "blender": { + "enabled": true, + "label": "Blender", + "icon": "{}/app_icons/blender.png", + "host_name": "blender", + "environment": {}, + "variants": { + "2-83": { + "executables": { + "windows": [ + "C:\\Program Files\\Blender Foundation\\Blender 2.83\\blender.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [ + "--python-use-system-env" + ], + "darwin": [ + "--python-use-system-env" + ], + "linux": [ + "--python-use-system-env" + ] + }, + "environment": {} + }, + "2-90": { + "executables": { + "windows": [ + "C:\\Program Files\\Blender Foundation\\Blender 2.90\\blender.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [ + "--python-use-system-env" + ], + "darwin": [ + "--python-use-system-env" + ], + "linux": [ + "--python-use-system-env" + ] + }, + "environment": {} + }, + "2-91": { + "executables": { + "windows": [ + "C:\\Program Files\\Blender Foundation\\Blender 2.91\\blender.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [ + "--python-use-system-env" + ], + "darwin": [ + "--python-use-system-env" + ], + "linux": [ + "--python-use-system-env" + ] + }, + "environment": {} + }, + "__dynamic_keys_labels__": { + "2-83": "2.83", + "2-90": "2.90", + "2-91": "2.91" } } }, @@ -988,23 +1274,6 @@ "AVALON_HARMONY_WORKFILES_ON_LAUNCH": "1" }, "variants": { - "22": { - "executables": { - "windows": [ - "c:\\Program Files (x86)\\Toon Boom Animation\\Toon Boom Harmony 22 Premium\\win64\\bin\\HarmonyPremium.exe" - ], - "darwin": [ - "/Applications/Toon Boom Harmony 22 Premium/Harmony Premium.app/Contents/MacOS/Harmony Premium" - ], - "linux": [] - }, - "arguments": { - "windows": [], - "darwin": [], - "linux": [] - }, - "environment": {} - }, "21": { "executables": { "windows": [ @@ -1038,6 +1307,23 @@ "linux": [] }, "environment": {} + }, + "17": { + "executables": { + "windows": [ + "c:\\Program Files (x86)\\Toon Boom Animation\\Toon Boom Harmony 17 Premium\\win64\\bin\\HarmonyPremium.exe" + ], + "darwin": [ + "/Applications/Toon Boom Harmony 17 Premium/Harmony Premium.app/Contents/MacOS/Harmony Premium" + ], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": {} } } }, @@ -1094,50 +1380,44 @@ "WORKFILES_SAVE_AS": "Yes" }, "variants": { + "2020": { + "executables": { + "windows": [ + "C:\\Program Files\\Adobe\\Adobe Photoshop 2020\\Photoshop.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": {} + }, + "2021": { + "executables": { + "windows": [ + "C:\\Program Files\\Adobe\\Adobe Photoshop 2021\\Photoshop.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": {} + }, "2022": { "executables": { "windows": [ "C:\\Program Files\\Adobe\\Adobe Photoshop 2022\\Photoshop.exe" ], - "darwin": [ - "/Applications/Adobe Photoshop 2022/Adobe Photoshop 2022" - ], - "linux": [] - }, - "arguments": { - "windows": [], "darwin": [], "linux": [] }, - "environment": {} - }, - "2023": { - "executables": { - "windows": [ - "C:\\Program Files\\Adobe\\Adobe Photoshop 2023\\Photoshop.exe" - ], - "darwin": [ - "/Applications/Adobe Photoshop 2023/Adobe Photoshop 2023" - ], - "linux": [] - }, - "arguments": { - "windows": [], - "darwin": [], - "linux": [] - }, - "environment": {} - }, - "2024": { - "executables": { - "windows": [ - "C:\\Program Files\\Adobe\\Adobe Photoshop 2024\\Photoshop.exe" - ], - "darwin": [ - "/Applications/Adobe Photoshop 2024/Adobe Photoshop 2024" - ], - "linux": [] - }, "arguments": { "windows": [], "darwin": [], @@ -1157,54 +1437,44 @@ "WORKFILES_SAVE_AS": "Yes" }, "variants": { + "2020": { + "executables": { + "windows": [ + "C:\\Program Files\\Adobe\\Adobe After Effects 2020\\Support Files\\AfterFX.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": {} + }, + "2021": { + "executables": { + "windows": [ + "C:\\Program Files\\Adobe\\Adobe After Effects 2021\\Support Files\\AfterFX.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": {} + }, "2022": { "executables": { "windows": [ "C:\\Program Files\\Adobe\\Adobe After Effects 2022\\Support Files\\AfterFX.exe" ], - "darwin": [ - "/Applications/Adobe After Effects 2022/Adobe After Effects 2022" - ], - "linux": [] - }, - "arguments": { - "windows": [], "darwin": [], "linux": [] }, - "environment": { - "MULTIPROCESS": "No" - } - }, - "2023": { - "executables": { - "windows": [ - "C:\\Program Files\\Adobe\\Adobe After Effects 2023\\Support Files\\AfterFX.exe" - ], - "darwin": [ - "/Applications/Adobe After Effects 2023/Adobe After Effects 2023" - ], - "linux": [] - }, - "arguments": { - "windows": [], - "darwin": [], - "linux": [] - }, - "environment": { - "MULTIPROCESS": "No" - } - }, - "2024": { - "executables": { - "windows": [ - "C:\\Program Files\\Adobe\\Adobe After Effects 2024\\Support Files\\AfterFX.exe" - ], - "darwin": [ - "/Applications/Adobe After Effects 2024/Adobe After Effects 2024" - ], - "linux": [] - }, "arguments": { "windows": [], "darwin": [], @@ -1252,7 +1522,7 @@ "host_name": "substancepainter", "environment": {}, "variants": { - "stable": { + "8-2-0": { "executables": { "windows": [ "C:\\Program Files\\Adobe\\Adobe Substance 3D Painter\\Adobe Substance 3D Painter.exe" @@ -1266,6 +1536,9 @@ "linux": [] }, "environment": {} + }, + "__dynamic_keys_labels__": { + "8-2-0": "8.2.0" } } }, @@ -1310,26 +1583,9 @@ }, "environment": {} }, - "5-2": { - "use_python_2": false, - "executables": { - "windows": [ - "C:\\Program Files\\Epic Games\\UE_5.2\\Engine\\Binaries\\Win64\\UnrealEditor.exe" - ], - "darwin": [], - "linux": [] - }, - "arguments": { - "windows": [], - "darwin": [], - "linux": [] - }, - "environment": {} - }, "__dynamic_keys_labels__": { - "5-0": "Unreal 5.0", "5-1": "Unreal 5.1", - "5-2": "Unreal 5.2" + "5-0": "Unreal 5.0" } } }, diff --git a/openpype/settings/defaults/system_settings/tools.json b/openpype/settings/defaults/system_settings/tools.json index 9e768b91e9..921e13af3a 100644 --- a/openpype/settings/defaults/system_settings/tools.json +++ b/openpype/settings/defaults/system_settings/tools.json @@ -1,403 +1,90 @@ { "tool_groups": { - "htoa": { - "environment": { - "HOUDINI_PATH": [ - "{STUDIO_SW}/APP/HTOA/{HTOA_VERSION}/HOUDINI{HOUDINI_VERSION}/WINDOWS/htoa-6.1.3.3_rdb15014_houdini-{HTOA_VERSION}", - "{HOUDINI_PATH}" - ], - "PATH": { - "windows": [ - "{STUDIO_SW}/APP/HTOA/{HTOA_VERSION}/HOUDINI{HOUDINI_VERSION}/WINDOWS/htoa-6.1.3.3_rdb15014_houdini-{HTOA_VERSION}/scripts/bin", - "{PATH}" - ] - } - }, - "variants": { - "5-4-2-7": { - "host_names": [ - "houdini" - ], - "app_variants": [], - "environment": { - "HTOA_VERSION": "5.4.2.7" - } - } - } - }, "mtoa": { "environment": { - "MTOA": { - "darwin": "{STUDIO_SW}/APP/MTOA/{MTOA_VERSION}/MAYA{MAYA_VERSION}/MAC", - "linux": "{STUDIO_SW}/APP/MTOA/{MTOA_VERSION}/MAYA{MAYA_VERSION}/LINUX", - "windows": "{STUDIO_SW}/APP/MTOA/{MTOA_VERSION}/MAYA{MAYA_VERSION}/WINDOWS" + "MTOA": "{STUDIO_SOFTWARE}/arnold/mtoa_{MAYA_VERSION}_{MTOA_VERSION}", + "MAYA_RENDER_DESC_PATH": "{MTOA}", + "MAYA_MODULE_PATH": "{MTOA}", + "ARNOLD_PLUGIN_PATH": "{MTOA}/shaders", + "MTOA_EXTENSIONS_PATH": { + "darwin": "{MTOA}/extensions", + "linux": "{MTOA}/extensions", + "windows": "{MTOA}/extensions" + }, + "MTOA_EXTENSIONS": { + "darwin": "{MTOA}/extensions", + "linux": "{MTOA}/extensions", + "windows": "{MTOA}/extensions" }, - "MAYA_MODULE_PATH": [ - "{STUDIO_SW}/APP/MTOA", - "{MAYA_MODULE_PATH}" - ], "DYLD_LIBRARY_PATH": { "darwin": "{MTOA}/bin" }, "PATH": { - "windows": [ - "{MTOA}/bin", - "{PATH}" - ] - }, - "XBMLANGPATH": [ - "{MTOA}/icons", - "{XBMLANGPATH}" - ], - "MAYA_RENDER_DESC_PATH": [ - "{MTOA}", - "{MAYA_RENDER_DESC_PATH}" - ], - "MTOA_STARTUP_LOG_VERBOSITY": "3" + "windows": "{PATH};{MTOA}/bin" + } }, "variants": { - "5-3-1-0": { - "host_names": [ - "maya" - ], + "3-2": { + "host_names": [], "app_variants": [], "environment": { - "MTOA_VERSION": "5.3.1.0" + "MTOA_VERSION": "3.2" } }, - "5-3-4-1": { - "host_names": [ - "maya" - ], + "3-1": { + "host_names": [], "app_variants": [], "environment": { - "MTOA_VERSION": "5.3.1.0" + "MTOA_VERSION": "3.1" } + }, + "__dynamic_keys_labels__": { + "3-2": "3.2", + "3-1": "3.1" } } }, - "rendermanMaya": { - "environment": { - "RFMTREE": { - "darwin": "{STUDIO_SW}/APP/RENDERMAN/{RM_VERSION}/MAC/MAYA", - "linux": "{STUDIO_SW}/APP/RENDERMAN/{RM_VERSION}/LINUX/MAYA", - "windows": "{STUDIO_SW}/APP/RENDERMAN/{RM_VERSION}/WINDOWS/MAYA" - }, - "RMANTREE": { - "darwin": "{STUDIO_SW}/APP/RENDERMAN/{RM_VERSION}/MAC/RenderManProServer-{RM_VERSION}", - "linux": "{STUDIO_SW}/APP/RENDERMAN/{RM_VERSION}/LINUX/RenderManProServer-{RM_VERSION}", - "windows": "{STUDIO_SW}/APP/RENDERMAN/{RM_VERSION}/WINDOWS/RenderManProServer-{RM_VERSION}" - }, - "MAYA_MODULE_PATH": [ - "{STUDIO_SW}/APP/RENDERMAN", - "{MAYA_MODULE_PATH}" - ], - "PIXAR_LICENSE_FILE": "{STUDIO_SW}/APP/RENDERMAN/pixar.license", - "RFM_DO_NOT_CREATE_MODULE_FILE": "1" - }, + "vray": { + "environment": {}, + "variants": {} + }, + "yeti": { + "environment": {}, + "variants": {} + }, + "renderman": { + "environment": {}, "variants": { - "24-3": { + "24-3-maya": { "host_names": [ "maya" ], - "app_variants": [], + "app_variants": [ + "maya/2022" + ], "environment": { - "RM_VERSION": "24.3" + "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" + } } - } - } - }, - "yetiMaya": { - "environment": { - "YETI_HOME": { - "darwin": "{STUDIO_SW}/APP/YETI/{YETI_VERSION}/MAYA{MAYA_VERSION}/MAC", - "linux": "{STUDIO_SW}/APP/YETI/{YETI_VERSION}/MAYA{MAYA_VERSION}/LINUX", - "windows": "{STUDIO_SW}/APP/YETI/{YETI_VERSION}/MAYA{MAYA_VERSION}/WINDOWS" }, - "YETI_TMP": { - "windows": "C:/temp", - "darwin": "/tmp", - "linux": "/tmp" - }, - "peregrinel_LICENSE": "", - "MAYA_MODULE_PATH": [ - "{STUDIO_SW}/APP/YETI", - "{MAYA_MODULE_PATH}" - ] - }, - "variants": { - "4-2-11": { - "host_names": [ - "maya" - ], - "app_variants": [], - "environment": { - "YETI_VERSION": "4.2.11" - } - } - } - }, - "redshiftMaya": { - "environment": { - "REDSHIFT_COREDATAPATH": { - "darwin": "{STUDIO_SW}/APP/REDSHIFT/{REDSHIFT_VERSION}/MAC", - "linux": "{STUDIO_SW}/APP/REDSHIFT/{REDSHIFT_VERSION}/LINUX", - "windows": "{STUDIO_SW}/APP/REDSHIFT/{REDSHIFT_VERSION}/WINDOWS" - }, - "REDSHIFT_ABORTONLICENSEFAIL": "0", - "MAYA_MODULE_PATH": [ - "{STUDIO_SW}/APP/REDSHIFT", - "{MAYA_MODULE_PATH}" - ], - "MAYA_PLUG_IN_PATH": { - "windows": [ - "{REDSHIFT_COREDATAPATH}/Plugins/Maya/{MAYA_VERSION}/nt-x86-64", - "{MAYA_PLUG_IN_PATH}" - ], - "linux": [ - "{REDSHIFT_COREDATAPATH}/redshift4maya/{MAYA_VERSION}", - "{MAYA_PLUG_IN_PATH}" - ], - "darwin": [ - "{REDSHIFT_COREDATAPATH}/redshift4maya/{MAYA_VERSION}", - "{MAYA_PLUG_IN_PATH}" - ] - }, - "MAYA_SCRIPT_PATH": { - "windows": [ - "{REDSHIFT_COREDATAPATH}/Plugins/Maya/Common/scripts", - "{MAYA_SCRIPT_PATH}" - ], - "linux": [ - "{REDSHIFT_COREDATAPATH}/redshift4maya/common/scripts", - "{MAYA_SCRIPT_PATH}" - ], - "darwin": [ - "{REDSHIFT_COREDATAPATH}/redshift4maya/common/scripts", - "{MAYA_SCRIPT_PATH}" - ] - }, - "REDSHIFT_PROCEDURALSPATH": { - "windows": [ - "{REDSHIFT_COREDATAPATH}/Procedurals", - "{REDSHIFT_PROCEDURALSPATH}" - ], - "linux": [ - "{REDSHIFT_COREDATAPATH}/procedurals", - "{REDSHIFT_PROCEDURALSPATH}" - ], - "darwin": [ - "{REDSHIFT_COREDATAPATH}/procedurals", - "{REDSHIFT_PROCEDURALSPATH}" - ] - }, - "REDSHIFT_MAYAEXTENSIONSPATH": { - "windows": [ - "{REDSHIFT_COREDATAPATH}/Plugins/Maya/{MAYA_VERSION}/nt-x86-64/extensions", - "{REDSHIFT_MAYAEXTENSIONSPATH}" - ], - "linux": [ - "{REDSHIFT_COREDATAPATH}/redshift4maya/{MAYA_VERSION}/extensions", - "{REDSHIFT_MAYAEXTENSIONSPATH}" - ], - "darwin": [ - "{REDSHIFT_COREDATAPATH}/redshift4maya/{MAYA_VERSION}/extensions", - "{REDSHIFT_MAYAEXTENSIONSPATH}" - ] - }, - "XBMLANGPATH": { - "windows": [ - "{REDSHIFT_COREDATAPATH}/Plugins/Maya/Common/icons", - "{XBMLANGPATH}" - ], - "linux": [ - "{REDSHIFT_COREDATAPATH}/redshift4maya/common/icons", - "{XBMLANGPATH}" - ], - "darwin": [ - "{REDSHIFT_COREDATAPATH}/redshift4maya/common/icons", - "{XBMLANGPATH}" - ] - }, - "MAYA_RENDER_DESC_PATH": { - "windows": [ - "{REDSHIFT_COREDATAPATH}/Plugins/Maya/Common/rendererDesc", - "{MAYA_RENDER_DESC_PATH}" - ], - "linux": [ - "{REDSHIFT_COREDATAPATH}/redshift4maya/common/rendererDesc", - "{MAYA_RENDER_DESC_PATH}" - ], - "darwin": [ - "{REDSHIFT_COREDATAPATH}/redshift4maya/common/rendererDesc", - "{MAYA_RENDER_DESC_PATH}" - ] - }, - "MAYA_CUSTOM_TEMPLATE_PATH": { - "windows": [ - "{REDSHIFT_COREDATAPATH}/Plugins/Maya/Common/scripts/NETemplates", - "{MAYA_CUSTOM_TEMPLATE_PATH}" - ], - "linux": [ - "{REDSHIFT_COREDATAPATH}/redshift4maya/common/scripts/NETemplates", - "{MAYA_CUSTOM_TEMPLATE_PATH}" - ], - "darwin": [ - "{REDSHIFT_COREDATAPATH}/redshift4maya/common/scripts/NETemplates", - "{MAYA_CUSTOM_TEMPLATE_PATH}" - ] - }, - "PATH": { - "windows": [ - "{REDSHIFT_COREDATAPATH}/bin", - "{PATH}" - ] - } - }, - "variants": { - "3-5-19": { - "host_names": [ - "maya" - ], - "app_variants": [], - "environment": { - "REDSHIFT_VERSION": "3.5.19" - } - } - } - }, - "redshift3dsmax": { - "environment": { - "REDSHIFT_COREDATAPATH": { - "darwin": "{STUDIO_SW}/APP/REDSHIFT/{REDSHIFT_VERSION}/MAC", - "linux": "{STUDIO_SW}/APP/REDSHIFT/{REDSHIFT_VERSION}/LINUX", - "windows": "{STUDIO_SW}/APP/REDSHIFT/{REDSHIFT_VERSION}/WINDOWS" - }, - "REDSHIFT_ABORTONLICENSEFAIL": "0", - "REDSHIFT_PROCEDURALSPATH": { - "windows": [ - "{REDSHIFT_COREDATAPATH}/Procedurals", - "{REDSHIFT_PROCEDURALSPATH}" - ], - "linux": [ - "{REDSHIFT_COREDATAPATH}/procedurals", - "{REDSHIFT_PROCEDURALSPATH}" - ], - "darwin": [ - "{REDSHIFT_COREDATAPATH}/procedurals", - "{REDSHIFT_PROCEDURALSPATH}" - ] - }, - "PATH": { - "windows": [ - "{REDSHIFT_COREDATAPATH}/bin", - "{PATH}" - ] - } - }, - "variants": { - "3-5-19": { - "host_names": [ - "max" - ], - "app_variants": [], - "environment": { - "REDSHIFT_VERSION": "3.5.19" - } - } - } - }, - "mGear": { - "environment": { - "MGEAR_ROOT": "{STUDIO_SW}/APP/MGEAR/{MGEAR_VERSION}/MAYA{MAYA_VERSION}/windows/x64", - "MAYA_MODULE_PATH": [ - "{STUDIO_SW}/APP/MGEAR/{MGEAR_VERSION}/release", - "{MAYA_MODULE_PATH}" - ] - }, - "variants": { - "4-0-7": { - "host_names": [ - "maya" - ], - "app_variants": [], - "environment": { - "MGEAR_VERSION": "4.0.7" - } - } - } - }, - "vrayMaya": { - "environment": { - "MAYA_MODULE_PATH": { - "windows": [ - "{STUDIO_SW}/APP/VRAY/{VRAY_VERSION}/MAYA{MAYA_VERSION}/WINDOWS/maya_root/modules", - "{MAYA_MODULE_PATH}" - ], - "linux": [ - "{STUDIO_SW}/APP/VRAY/{VRAY_VERSION}/MAYA{MAYA_VERSION}/LINUX/maya_root/modules", - "{MAYA_MODULE_PATH}" - ], - "darwin": [ - "{STUDIO_SW}/APP/VRAY/{VRAY_VERSION}/MAYA{MAYA_VERSION}/MAC/maya_root/modules", - "{MAYA_MODULE_PATH}" - ] - }, - "VRAY_AUTH_CLIENT_FILE_PATH": "{STUDIO_SW}/APP/VRAY" - }, - "variants": { - "6-10-01": { - "host_names": [ - "maya" - ], - "app_variants": [], - "environment": { - "VRAY_VERSION": "6.10.01" - } - } - } - }, - "vraynuke": { - "environment": { - "VRAY_FOR_NUKE_13_0_PLUGINS": { - "windows": "{STUDIO_SW}/APP/VRAYNUKE/{VRAYNUKE_VERSION}/NUKE{NUKE_VRAY_VERSION}/WINDOWS/nuke_vray/plugins/vray" - }, - "NUKE_PATH": { - "windows": [ - "{STUDIO_SW}/APP/VRAYNUKE/{VRAYNUKE_VERSION}/NUKE{NUKE_VRAY_VERSION}/WINDOWS/nuke_root", - "{NUKE_PATH}" - ] - }, - "PATH": { - "windows": [ - "{STUDIO_SW}/APP/VRAYNUKE/{VRAYNUKE_VERSION}/NUKE{NUKE_VRAY_VERSION}/WINDOWS/nuke_vray", - "{PATH}" - ] - }, - "VRAY_AUTH_CLIENT_FILE_PATH": "{STUDIO_SW}/APP/VRAY" - }, - "variants": { - "5-20-00": { - "host_names": [ - "nuke" - ], - "app_variants": [], - "environment": { - "VRAYNUKE_VERSION": "5.20.00" - } + "__dynamic_keys_labels__": { + "24-3-maya": "24.3 RFM" } } }, "__dynamic_keys_labels__": { - "htoa": "Arnold for Houdini (example)", - "mtoa": "Arnold for Maya (example)", - "rendermanMaya": "Renderman for Maya (example)", - "yetiMaya": "Yeti for Maya (example)", - "redshiftMaya": "Redshift for Maya (example)", - "redshift3dsmax": "Redshift for 3dsmax (example)", - "mGear": "mGear for Maya (example)", - "vrayMaya": "Vray for Maya (example)", - "vraynuke": "Vray for Nuke (example)" + "mtoa": "Autodesk Arnold", + "vray": "Chaos Group Vray", + "yeti": "Peregrine Labs Yeti", + "renderman": "Pixar Renderman" } } } From 26de73ca1a18f5f37a450aa9aa6b27f5ec5e93f0 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 6 Dec 2023 17:12:44 +0100 Subject: [PATCH 288/327] typo --- server_addon/applications/server/applications.json | 1 + 1 file changed, 1 insertion(+) diff --git a/server_addon/applications/server/applications.json b/server_addon/applications/server/applications.json index 4a65d1cc1c..35f1b4cfbb 100644 --- a/server_addon/applications/server/applications.json +++ b/server_addon/applications/server/applications.json @@ -358,6 +358,7 @@ "environment": "{}", "use_python_2": false } + ] }, "nukex": { "enabled": true, From 129b35e754c7be262d35ed691ba1ce6bd085091e Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Wed, 6 Dec 2023 16:36:14 +0000 Subject: [PATCH 289/327] Testing: dump_databases flag (#5955) * dump_databases flag * Remove wrongly placed code. * Turn flag into format and support json export. * Added new argument to readme --------- Co-authored-by: kalisp --- openpype/cli.py | 7 ++++-- openpype/pype_commands.py | 9 +++++++- tests/conftest.py | 10 +++++++++ tests/integration/README.md | 2 +- tests/lib/db_handler.py | 42 ++++++++++++++++++++++++++---------- tests/lib/testing_classes.py | 30 ++++++++++++++++++-------- 6 files changed, 76 insertions(+), 24 deletions(-) diff --git a/openpype/cli.py b/openpype/cli.py index f0fe550a1f..8caa139765 100644 --- a/openpype/cli.py +++ b/openpype/cli.py @@ -296,12 +296,15 @@ def run(script): @click.option("--mongo_url", help="MongoDB for testing.", default=None) +@click.option("--dump_databases", + help="Dump all databases to data folder.", + default=None) def runtests(folder, mark, pyargs, test_data_folder, persist, app_variant, - timeout, setup_only, mongo_url, app_group): + timeout, setup_only, mongo_url, app_group, dump_databases): """Run all automatic tests after proper initialization via start.py""" PypeCommands().run_tests(folder, mark, pyargs, test_data_folder, persist, app_variant, timeout, setup_only, - mongo_url, app_group) + mongo_url, app_group, dump_databases) @main.command(help="DEPRECATED - run sync server") diff --git a/openpype/pype_commands.py b/openpype/pype_commands.py index 960e9d410d..f744337c67 100644 --- a/openpype/pype_commands.py +++ b/openpype/pype_commands.py @@ -214,7 +214,7 @@ class PypeCommands: def run_tests(self, folder, mark, pyargs, test_data_folder, persist, app_variant, timeout, setup_only, - mongo_url, app_group): + mongo_url, app_group, dump_databases): """ Runs tests from 'folder' @@ -275,6 +275,13 @@ class PypeCommands: if mongo_url: args.extend(["--mongo_url", mongo_url]) + if dump_databases: + msg = "dump_databases format is not recognized: {}".format( + dump_databases + ) + assert dump_databases in ["bson", "json"], msg + args.extend(["--dump_databases", dump_databases]) + print("run_tests args: {}".format(args)) import pytest pytest.main(args) diff --git a/tests/conftest.py b/tests/conftest.py index 028c19bc5f..cb4806998a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -39,6 +39,11 @@ def pytest_addoption(parser): help="Provide url of the Mongo database." ) + parser.addoption( + "--dump_databases", action="store", default=None, + help="Dump databases to data folder." + ) + @pytest.fixture(scope="module") def test_data_folder(request): @@ -75,6 +80,11 @@ def mongo_url(request): return request.config.getoption("--mongo_url") +@pytest.fixture(scope="module") +def dump_databases(request): + return request.config.getoption("--dump_databases") + + @pytest.hookimpl(tryfirst=True, hookwrapper=True) def pytest_runtest_makereport(item, call): # execute all other hooks to obtain the report object diff --git a/tests/integration/README.md b/tests/integration/README.md index 7b9b7cd706..1e45b55ed5 100644 --- a/tests/integration/README.md +++ b/tests/integration/README.md @@ -29,7 +29,7 @@ Command line arguments - "--timeout" - "Provide specific timeout value for test case", - "--setup_only" - "Only create dbs, do not run tests", - "--mongo_url" - "MongoDB for testing.", - + - "--dump_databases" - ("json"|"bson") export database in expected format after successful test (to output folder in temp location - which is made persistent by this, must be cleared manually) Run Tray for test ----------------- In case of failed test you might want to run it manually and visually debug what happened. diff --git a/tests/lib/db_handler.py b/tests/lib/db_handler.py index 82e741cc3b..d9a52be5b4 100644 --- a/tests/lib/db_handler.py +++ b/tests/lib/db_handler.py @@ -2,7 +2,7 @@ Helper class for automatic testing, provides dump and restore via command line utilities. - Expect mongodump, mongoimport and mongorestore present at PATH + Expect mongodump, mongoexport, mongoimport and mongorestore present at PATH """ import os import pymongo @@ -148,7 +148,7 @@ class DBHandler: self.client.drop_database(db_name) def backup_to_dump(self, db_name, dump_dir, overwrite=False, - collection=None): + collection=None, format="bson"): """ Helper method for running mongodump for specific 'db_name' """ @@ -160,15 +160,24 @@ class DBHandler: raise RuntimeError("Backup already exists, " "run with overwrite=True") - query = self._dump_query(self.uri, dump_dir, - db_name=db_name, collection=collection) - print("Mongodump query:: {}".format(query)) - subprocess.run(query) + collections = [collection] + if format == "json" and collection is None: + collections = self.client[db_name].list_collection_names() + + for collection in collections: + query = self._dump_query(self.uri, dump_dir, + db_name=db_name, collection=collection, + format=format) + print("Mongodump query:: {}".format(query)) + process = subprocess.run(query) + assert process.returncode == 0, "Mongo dump failed." def _db_exists(self, db_name): return db_name in self.client.list_database_names() - def _dump_query(self, uri, output_path, db_name=None, collection=None): + def _dump_query( + self, uri, output_path, db_name=None, collection=None, format="bson" + ): """Prepares dump query based on 'db_name' or 'collection'.""" db_part = coll_part = "" if db_name: @@ -177,11 +186,22 @@ class DBHandler: if not db_name: raise ValueError("db_name must be present") coll_part = "--collection={}".format(collection) - query = "\"{}\" --uri=\"{}\" --out={} {} {}".format( - "mongodump", uri, output_path, db_part, coll_part - ) - return query + tool = "mongodump" + query = "{} --uri=\"{}\"" + + if format == "json": + assert collection, "Collection is needed for json export." + + query += " --jsonArray --pretty" + tool = "mongoexport" + output_path = os.path.join( + output_path, "{}.{}.json".format(db_name, collection) + ) + + query += " --out={} {} {}" + + return query.format(tool, uri, output_path, db_part, coll_part) def _restore_query(self, uri, dump_dir, db_name=None, db_name_out=None, diff --git a/tests/lib/testing_classes.py b/tests/lib/testing_classes.py index 7700381aa6..7a90f76662 100644 --- a/tests/lib/testing_classes.py +++ b/tests/lib/testing_classes.py @@ -70,7 +70,9 @@ class ModuleUnitTest(BaseTest): ) @pytest.fixture(scope="module") - def download_test_data(self, test_data_folder, persist, request): + def download_test_data( + self, test_data_folder, persist, request, dump_databases + ): test_data_folder = test_data_folder or self.TEST_DATA_FOLDER if test_data_folder: print("Using existing folder {}".format(test_data_folder)) @@ -100,13 +102,13 @@ class ModuleUnitTest(BaseTest): if ext and ext.lstrip('.') in handler_class.IMPLEMENTED_ZIP_FORMATS: # noqa: E501 handler_class.unzip(os.path.join(tmpdir, file_name)) - yield tmpdir + yield tmpdir - persist = (persist or self.PERSIST or - self.is_test_failed(request)) - if not persist: - print("Removing {}".format(tmpdir)) - shutil.rmtree(tmpdir) + persist = (persist or self.PERSIST or + self.is_test_failed(request) or dump_databases) + if not persist: + print("Removing {}".format(tmpdir)) + shutil.rmtree(tmpdir) @pytest.fixture(scope="module") def output_folder_url(self, download_test_data): @@ -163,7 +165,7 @@ class ModuleUnitTest(BaseTest): @pytest.fixture(scope="module") def db_setup(self, download_test_data, env_var, monkeypatch_session, - request, mongo_url): + request, mongo_url, dump_databases, persist): """Restore prepared MongoDB dumps into selected DB.""" backup_dir = os.path.join(download_test_data, "input", "dumps") uri = os.environ.get("OPENPYPE_MONGO") @@ -178,7 +180,17 @@ class ModuleUnitTest(BaseTest): yield db_handler - persist = self.PERSIST or self.is_test_failed(request) + if dump_databases: + print("Dumping databases to {}".format(download_test_data)) + output_dir = os.path.join(download_test_data, "output", "dumps") + db_handler.backup_to_dump( + self.TEST_DB_NAME, output_dir, format=dump_databases + ) + db_handler.backup_to_dump( + self.TEST_OPENPYPE_NAME, output_dir, format=dump_databases + ) + + persist = persist or self.PERSIST or self.is_test_failed(request) if not persist: db_handler.teardown(self.TEST_DB_NAME) db_handler.teardown(self.TEST_OPENPYPE_NAME) From f2fd1750b3bcfcdb332f5a9faf3f6b204fd69372 Mon Sep 17 00:00:00 2001 From: Mustafa Taher Date: Wed, 6 Dec 2023 18:43:57 +0200 Subject: [PATCH 290/327] Chore/houdini update startup log (#6003) * replace OpenPype with AYON * replace OpenPype with AYON * kuba's comments * Resolve Hound * remove redundant space --- openpype/hosts/houdini/startup/python2.7libs/pythonrc.py | 4 +++- openpype/hosts/houdini/startup/python3.10libs/pythonrc.py | 4 +++- openpype/hosts/houdini/startup/python3.7libs/pythonrc.py | 4 +++- openpype/hosts/houdini/startup/python3.9libs/pythonrc.py | 4 +++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/houdini/startup/python2.7libs/pythonrc.py b/openpype/hosts/houdini/startup/python2.7libs/pythonrc.py index 683ea6721c..0b92fc2706 100644 --- a/openpype/hosts/houdini/startup/python2.7libs/pythonrc.py +++ b/openpype/hosts/houdini/startup/python2.7libs/pythonrc.py @@ -2,10 +2,12 @@ """OpenPype startup script.""" from openpype.pipeline import install_host from openpype.hosts.houdini.api import HoudiniHost +from openpype import AYON_SERVER_ENABLED def main(): - print("Installing OpenPype ...") + print("Installing {} ...".format( + "AYON" if AYON_SERVER_ENABLED else "OpenPype")) install_host(HoudiniHost()) diff --git a/openpype/hosts/houdini/startup/python3.10libs/pythonrc.py b/openpype/hosts/houdini/startup/python3.10libs/pythonrc.py index 683ea6721c..0b92fc2706 100644 --- a/openpype/hosts/houdini/startup/python3.10libs/pythonrc.py +++ b/openpype/hosts/houdini/startup/python3.10libs/pythonrc.py @@ -2,10 +2,12 @@ """OpenPype startup script.""" from openpype.pipeline import install_host from openpype.hosts.houdini.api import HoudiniHost +from openpype import AYON_SERVER_ENABLED def main(): - print("Installing OpenPype ...") + print("Installing {} ...".format( + "AYON" if AYON_SERVER_ENABLED else "OpenPype")) install_host(HoudiniHost()) diff --git a/openpype/hosts/houdini/startup/python3.7libs/pythonrc.py b/openpype/hosts/houdini/startup/python3.7libs/pythonrc.py index 683ea6721c..0b92fc2706 100644 --- a/openpype/hosts/houdini/startup/python3.7libs/pythonrc.py +++ b/openpype/hosts/houdini/startup/python3.7libs/pythonrc.py @@ -2,10 +2,12 @@ """OpenPype startup script.""" from openpype.pipeline import install_host from openpype.hosts.houdini.api import HoudiniHost +from openpype import AYON_SERVER_ENABLED def main(): - print("Installing OpenPype ...") + print("Installing {} ...".format( + "AYON" if AYON_SERVER_ENABLED else "OpenPype")) install_host(HoudiniHost()) diff --git a/openpype/hosts/houdini/startup/python3.9libs/pythonrc.py b/openpype/hosts/houdini/startup/python3.9libs/pythonrc.py index 683ea6721c..0b92fc2706 100644 --- a/openpype/hosts/houdini/startup/python3.9libs/pythonrc.py +++ b/openpype/hosts/houdini/startup/python3.9libs/pythonrc.py @@ -2,10 +2,12 @@ """OpenPype startup script.""" from openpype.pipeline import install_host from openpype.hosts.houdini.api import HoudiniHost +from openpype import AYON_SERVER_ENABLED def main(): - print("Installing OpenPype ...") + print("Installing {} ...".format( + "AYON" if AYON_SERVER_ENABLED else "OpenPype")) install_host(HoudiniHost()) From 8bcd4b793aa53ef8a8655a7b895ab93af9384ef4 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 6 Dec 2023 17:51:03 +0100 Subject: [PATCH 291/327] add studio SW example --- server_addon/core/server/settings/main.py | 2 +- server_addon/maya/server/settings/main.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/server_addon/core/server/settings/main.py b/server_addon/core/server/settings/main.py index 433d0ef2f0..f9e572cbf9 100644 --- a/server_addon/core/server/settings/main.py +++ b/server_addon/core/server/settings/main.py @@ -198,7 +198,7 @@ DEFAULT_VALUES = { }, "studio_name": "", "studio_code": "", - "environments": "{}", + "environments": "{\n\"STUDIO_SW\": {\n \"darwin\": \"/mnt/REPO_SW\",\n \"linux\": \"/mnt/REPO_SW\",\n \"windows\": \"P:/REPO_SW\"\n }\n}", "tools": DEFAULT_TOOLS_VALUES, "version_start_category": { "profiles": [] diff --git a/server_addon/maya/server/settings/main.py b/server_addon/maya/server/settings/main.py index 55a079066c..62fd12ec8a 100644 --- a/server_addon/maya/server/settings/main.py +++ b/server_addon/maya/server/settings/main.py @@ -97,6 +97,7 @@ DEFAULT_MEL_WORKSPACE_SETTINGS = "\n".join(( 'workspace -fr "renderData" "renderData";', 'workspace -fr "sourceImages" "sourceimages";', 'workspace -fr "fileCache" "cache/nCache";', + 'workspace -fr "autoSave" "autosave"', '', )) From f6b43481b3a0da827037fb96e9a3c07bf89e941f Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 6 Dec 2023 17:58:44 +0100 Subject: [PATCH 292/327] do not expect workfileInstances constains only new type instance data (#6015) --- .../hosts/tvpaint/plugins/publish/collect_render_instances.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_render_instances.py b/openpype/hosts/tvpaint/plugins/publish/collect_render_instances.py index e89fbf7882..577e6e30e2 100644 --- a/openpype/hosts/tvpaint/plugins/publish/collect_render_instances.py +++ b/openpype/hosts/tvpaint/plugins/publish/collect_render_instances.py @@ -73,7 +73,7 @@ class CollectRenderInstances(pyblish.api.InstancePlugin): render_layer_id = creator_attributes["render_layer_instance_id"] for in_data in instance.context.data["workfileInstances"]: if ( - in_data["creator_identifier"] == "render.layer" + in_data.get("creator_identifier") == "render.layer" and in_data["instance_id"] == render_layer_id ): render_layer_data = in_data From 4229461755a79fd955153f42dd6ceb22123ef291 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 6 Dec 2023 21:05:32 +0100 Subject: [PATCH 293/327] hound --- openpype/lib/transcoding.py | 2 +- openpype/plugins/publish/extract_thumbnail.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 6870eda59a..37709f45e0 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1241,7 +1241,7 @@ def get_rescaled_command_arguments( input_par = ( float(stream_input_par.split(":")[0]) / float(stream_input_par.split(":")[1]) - ) + ) # recalculating input and target width input_width = int(input_width * input_par) target_width = int(target_width * target_par) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 40e08628c1..f10a29bf60 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -346,7 +346,8 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # create output file path base_name = os.path.basename(video_file_path) filename = os.path.splitext(base_name)[0] - output_thumb_file_path = os.path.join(output_dir, "{}.png".format(filename)) + output_thumb_file_path = os.path.join( + output_dir, "{}.png".format(filename)) # Set video input attributes max_int = str(2147483647) @@ -374,7 +375,8 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # run subprocess self.log.debug("Executing: {}".format(" ".join(cmd))) run_subprocess(cmd, logger=self.log) - self.log.debug("Thumbnail created: {}".format(output_thumb_file_path)) + self.log.debug( + "Thumbnail created: {}".format(output_thumb_file_path)) return output_thumb_file_path except RuntimeError as error: self.log.warning( From b97c943b004113eac47288e676883b38ff4bde24 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 6 Dec 2023 22:50:30 +0100 Subject: [PATCH 294/327] feat: Update Ayon settings and publish plugins - Updated Ayon settings to fix issues with the ExtractThumbnail plugin. - Fixed display and view at oiio defaults in the ExtractThumbnail plugin. - Fixed target size in the ExtractThumbnail plugin. - Fixed background color in the ExtractThumbnail plugin. Also, updated server version to 0.1.4. --- openpype/settings/ayon_settings.py | 18 +++ .../core/server/settings/publish_plugins.py | 107 +++++++++++++++++- server_addon/core/server/version.py | 2 +- 3 files changed, 125 insertions(+), 2 deletions(-) diff --git a/openpype/settings/ayon_settings.py b/openpype/settings/ayon_settings.py index e626368ad1..2f5920ae29 100644 --- a/openpype/settings/ayon_settings.py +++ b/openpype/settings/ayon_settings.py @@ -1261,6 +1261,24 @@ def _convert_global_project_settings(ayon_settings, output, default_settings): output_def["height"] = output_def.pop("output_height") profile["outputs"] = new_outputs + # ExtractThumbnail plugin + ayon_extract_thumbnail = ayon_publish["ExtractThumbnail"] + # fix display and view at oiio defaults + ayon_default_oiio = copy.deepcopy(ayon_extract_thumbnail["oiiotool_defaults"]) + display_and_view = ayon_default_oiio.pop("display_and_view") + ayon_default_oiio["display"] = display_and_view["display"] + ayon_default_oiio["view"] = display_and_view["view"] + ayon_extract_thumbnail["oiiotool_defaults"] = ayon_default_oiio + # fix target size + ayon_default_resize = copy.deepcopy(ayon_extract_thumbnail["target_size"]) + resize = ayon_default_resize.pop("resize") + ayon_default_resize["width"] = resize["width"] + ayon_default_resize["height"] = resize["height"] + ayon_extract_thumbnail["target_size"] = ayon_default_resize + # fix background color + ayon_extract_thumbnail["background_color"] = _convert_color( + ayon_extract_thumbnail["background_color"] + ) # ExtractOIIOTranscode plugin extract_oiio_transcode = ayon_publish["ExtractOIIOTranscode"] diff --git a/server_addon/core/server/settings/publish_plugins.py b/server_addon/core/server/settings/publish_plugins.py index 93d8db964d..744ef1279f 100644 --- a/server_addon/core/server/settings/publish_plugins.py +++ b/server_addon/core/server/settings/publish_plugins.py @@ -84,7 +84,6 @@ class ValidateIntentModel(BaseSettingsModel): class ExtractThumbnailFFmpegModel(BaseSettingsModel): - _layout = "expanded" input: list[str] = Field( default_factory=list, title="FFmpeg input arguments" @@ -95,9 +94,106 @@ class ExtractThumbnailFFmpegModel(BaseSettingsModel): ) +class ResizeItemModel(BaseSettingsModel): + _layout = "expanded" + width: int = Field( + 1920, + ge=0, + le=100000, + title="Width", + description="Width and Height must be both set to higher value than 0" + " else source resolution is used." + ) + height: int = Field( + 1080, + title="Height", + ge=0, + le=100000, + ) + +_resize_types_enum = [ + {"value": "source", "label": "Image source"}, + {"value": "resize", "label": "Resize"}, +] + + +class ResizeModel(BaseSettingsModel): + _layout = "expanded" + + type: str = Field( + title="Type", + description="Type of resizing", + enum_resolver=lambda: _resize_types_enum, + conditionalEnum=True, + default="source" + ) + + resize: ResizeItemModel = Field( + default_factory=ResizeItemModel, + title="Resize" + ) + +_thumbnail_oiio_transcoding_type = [ + {"value": "colorspace", "label": "Use Colorspace"}, + {"value": "display_and_view", "label": "Use Display&View"} + ] + +class DisplayAndViewModel(BaseSettingsModel): + _layout = "expanded" + display: str = Field( + "default", + title="Display" + ) + view: str = Field( + "sRGB", + title="View" + ) + +class ExtractThumbnailOIIODefaultsModel(BaseSettingsModel): + type: str = Field( + title="Type", + description="Transcoding type", + enum_resolver=lambda: _thumbnail_oiio_transcoding_type, + conditionalEnum=True, + default="colorspace" + ) + + colorspace: str = Field( + "", + title="Colorspace" + ) + display_and_view: DisplayAndViewModel = Field( + default_factory=DisplayAndViewModel, + title="Display&View" + ) + + + class ExtractThumbnailModel(BaseSettingsModel): _isGroup = True enabled: bool = Field(True) + integrate_thumbnail: bool = Field( + True, + title="Integrate Thumbnail Representation" + ) + target_size: ResizeModel = Field( + default_factory=ResizeModel, + title="Target size" + ) + background_color: ColorRGBA_uint8 = Field( + (0, 0, 0, 0.0), + title="Background color" + ) + duration_split: float = Field( + 0.5, + title="Duration split", + ge=0.0, + le=1.0 + ) + oiiotool_defaults: ExtractThumbnailOIIODefaultsModel = Field( + default_factory=ExtractThumbnailOIIODefaultsModel, + title="OIIOtool defaults" + ) ffmpeg_args: ExtractThumbnailFFmpegModel = Field( default_factory=ExtractThumbnailFFmpegModel ) @@ -741,6 +837,15 @@ DEFAULT_PUBLISH_VALUES = { }, "ExtractThumbnail": { "enabled": True, + "integrate_thumbnail": True, + "target_size": { + "type": "source" + }, + "duration_split": 0.5, + "oiiotool_defaults": { + "type": "colorspace", + "colorspace": "color_picking" + }, "ffmpeg_args": { "input": [ "-apply_trc gamma22" diff --git a/server_addon/core/server/version.py b/server_addon/core/server/version.py index ae7362549b..bbab0242f6 100644 --- a/server_addon/core/server/version.py +++ b/server_addon/core/server/version.py @@ -1 +1 @@ -__version__ = "0.1.3" +__version__ = "0.1.4" From f31cfe9cf56952aa5d3ac9456b940c3e331f0996 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 6 Dec 2023 22:51:28 +0100 Subject: [PATCH 295/327] Refactor code to use deep copy for `repre_files_thumb` variable --- openpype/plugins/publish/extract_thumbnail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index f10a29bf60..06e8fba99b 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -119,7 +119,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # if it is not video file then just use first file input_file = repre_files else: - repre_files_thumb = copy(repre_files) + repre_files_thumb = copy.deepcopy(repre_files) # exclude first frame if slate in representation tags if "slate-frame" in repre.get("tags", []): repre_files_thumb = repre_files_thumb[1:] From 50c0609a4556d8f4905d81f567108e926419ab42 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 6 Dec 2023 22:55:57 +0100 Subject: [PATCH 296/327] hound --- openpype/settings/ayon_settings.py | 4 +++- .../core/server/settings/publish_plugins.py | 15 +++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/openpype/settings/ayon_settings.py b/openpype/settings/ayon_settings.py index 2f5920ae29..3b308e510b 100644 --- a/openpype/settings/ayon_settings.py +++ b/openpype/settings/ayon_settings.py @@ -1261,10 +1261,12 @@ def _convert_global_project_settings(ayon_settings, output, default_settings): output_def["height"] = output_def.pop("output_height") profile["outputs"] = new_outputs + # ExtractThumbnail plugin ayon_extract_thumbnail = ayon_publish["ExtractThumbnail"] # fix display and view at oiio defaults - ayon_default_oiio = copy.deepcopy(ayon_extract_thumbnail["oiiotool_defaults"]) + ayon_default_oiio = copy.deepcopy( + ayon_extract_thumbnail["oiiotool_defaults"]) display_and_view = ayon_default_oiio.pop("display_and_view") ayon_default_oiio["display"] = display_and_view["display"] ayon_default_oiio["view"] = display_and_view["view"] diff --git a/server_addon/core/server/settings/publish_plugins.py b/server_addon/core/server/settings/publish_plugins.py index 744ef1279f..ef52416369 100644 --- a/server_addon/core/server/settings/publish_plugins.py +++ b/server_addon/core/server/settings/publish_plugins.py @@ -111,6 +111,7 @@ class ResizeItemModel(BaseSettingsModel): le=100000, ) + _resize_types_enum = [ {"value": "source", "label": "Image source"}, {"value": "resize", "label": "Resize"}, @@ -133,10 +134,12 @@ class ResizeModel(BaseSettingsModel): title="Resize" ) + _thumbnail_oiio_transcoding_type = [ - {"value": "colorspace", "label": "Use Colorspace"}, - {"value": "display_and_view", "label": "Use Display&View"} - ] + {"value": "colorspace", "label": "Use Colorspace"}, + {"value": "display_and_view", "label": "Use Display&View"} +] + class DisplayAndViewModel(BaseSettingsModel): _layout = "expanded" @@ -149,6 +152,7 @@ class DisplayAndViewModel(BaseSettingsModel): title="View" ) + class ExtractThumbnailOIIODefaultsModel(BaseSettingsModel): type: str = Field( title="Type", @@ -168,7 +172,6 @@ class ExtractThumbnailOIIODefaultsModel(BaseSettingsModel): ) - class ExtractThumbnailModel(BaseSettingsModel): _isGroup = True enabled: bool = Field(True) @@ -843,8 +846,8 @@ DEFAULT_PUBLISH_VALUES = { }, "duration_split": 0.5, "oiiotool_defaults": { - "type": "colorspace", - "colorspace": "color_picking" + "type": "colorspace", + "colorspace": "color_picking" }, "ffmpeg_args": { "input": [ From e88b692d2acd44b686bec7e6759892ca1404b94a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 7 Dec 2023 11:57:50 +0800 Subject: [PATCH 297/327] add the processEvents after the dialog has selected the file URL --- openpype/hosts/substancepainter/api/lib.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/substancepainter/api/lib.py b/openpype/hosts/substancepainter/api/lib.py index 71b7e3630a..bb4481bb1d 100644 --- a/openpype/hosts/substancepainter/api/lib.py +++ b/openpype/hosts/substancepainter/api/lib.py @@ -583,7 +583,9 @@ def prompt_new_file_with_mesh(mesh_filepath): file_dialog.setDirectory(os.path.dirname(mesh_filepath)) url = QtCore.QUrl.fromLocalFile(os.path.basename(mesh_filepath)) file_dialog.selectUrl(url) - file_dialog.close() + app.processEvents(QtCore.QEventLoop.ExcludeUserInputEvents, 3000) + + file_dialog.done(file_dialog.Accepted) app.processEvents(QtCore.QEventLoop.AllEvents) def _setup_prompt(): From fafb34ffc0ca7184db14d2376d00fc7c554ef0c7 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 7 Dec 2023 11:06:30 +0100 Subject: [PATCH 298/327] Update Ayon settings and Nuke server version - Updated Ayon settings to remove the 'ExtractThumbnail' schema in v3 - Updated Nuke server version from 0.1.6 to 0.1.7 --- openpype/settings/ayon_settings.py | 22 ---------------------- server_addon/nuke/server/version.py | 2 +- 2 files changed, 1 insertion(+), 23 deletions(-) diff --git a/openpype/settings/ayon_settings.py b/openpype/settings/ayon_settings.py index 5171517232..992e91dfe4 100644 --- a/openpype/settings/ayon_settings.py +++ b/openpype/settings/ayon_settings.py @@ -821,28 +821,6 @@ def _convert_nuke_project_settings(ayon_settings, output): collect_instance_data.pop( "sync_workfile_version_on_product_types")) - # TODO 'ExtractThumbnail' does not have ideal schema in v3 - ayon_extract_thumbnail = ayon_publish["ExtractThumbnail"] - new_thumbnail_nodes = {} - for item in ayon_extract_thumbnail["nodes"]: - name = item["nodeclass"] - value = [] - for knob in _convert_nuke_knobs(item["knobs"]): - knob_name = knob["name"] - # This may crash - if knob["type"] == "expression": - knob_value = knob["expression"] - else: - knob_value = knob["value"] - value.append([knob_name, knob_value]) - new_thumbnail_nodes[name] = value - - ayon_extract_thumbnail["nodes"] = new_thumbnail_nodes - - if "reposition_nodes" in ayon_extract_thumbnail: - for item in ayon_extract_thumbnail["reposition_nodes"]: - item["knobs"] = _convert_nuke_knobs(item["knobs"]) - # --- ImageIO --- # NOTE 'monitorOutLut' is maybe not yet in v3 (ut should be) _convert_host_imageio(ayon_nuke) diff --git a/server_addon/nuke/server/version.py b/server_addon/nuke/server/version.py index 0a8da88258..f1380eede2 100644 --- a/server_addon/nuke/server/version.py +++ b/server_addon/nuke/server/version.py @@ -1 +1 @@ -__version__ = "0.1.6" +__version__ = "0.1.7" From cdbf764a85178962771f888f4ae1a26e60ad8526 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 7 Dec 2023 11:13:22 +0100 Subject: [PATCH 299/327] improving comments --- openpype/plugins/publish/extract_thumbnail.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 202b8647c8..b05415a9e0 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -212,8 +212,8 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): def _get_explicit_repres_for_thumbnail(self, instance): src_repres = instance.data.get("representations") or [] # This is mainly for Nuke where we have multiple representations for - # one instance. We want to use only one representation for thumbnail - # first check if any of the representations have + # one instance and representations are tagged for thumbnail. + # First check if any of the representations have # `need_thumbnail` in tags and add them to filtered_repres need_thumb_repres = [ repre for repre in src_repres From 459000ec90fa3cdcd11608c4085a620844f5e0c9 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 7 Dec 2023 18:28:28 +0800 Subject: [PATCH 300/327] if the prompt dialog can't help to import mesh, use substance_painter.project.create instead to 'load' the mesh into the scene --- .../hosts/substancepainter/plugins/load/load_mesh.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/substancepainter/plugins/load/load_mesh.py b/openpype/hosts/substancepainter/plugins/load/load_mesh.py index 57db869a11..ca22163c5d 100644 --- a/openpype/hosts/substancepainter/plugins/load/load_mesh.py +++ b/openpype/hosts/substancepainter/plugins/load/load_mesh.py @@ -44,14 +44,20 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): # Get user inputs import_cameras = data.get("import_cameras", True) preserve_strokes = data.get("preserve_strokes", True) - + sp_settings = substance_painter.project.Settings( + import_cameras=import_cameras + ) if not substance_painter.project.is_open(): # Allow to 'initialize' a new project path = self.filepath_from_context(context) result = prompt_new_file_with_mesh(mesh_filepath=path) if not result: - self.log.info("User cancelled new project prompt.") - return + self.log.info("User cancelled new project prompt." + "Creating new project directly from" + " Substance Painter API Instead.") + settings = substance_painter.project.create( + mesh_file_path=path, settings=sp_settings + ) else: # Reload the mesh From aeaee28539a2903eb2fb061e14ab673204f2a11b Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 7 Dec 2023 18:51:07 +0800 Subject: [PATCH 301/327] edit the comment --- openpype/hosts/substancepainter/api/lib.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/substancepainter/api/lib.py b/openpype/hosts/substancepainter/api/lib.py index bb4481bb1d..35e5b63625 100644 --- a/openpype/hosts/substancepainter/api/lib.py +++ b/openpype/hosts/substancepainter/api/lib.py @@ -617,7 +617,9 @@ def prompt_new_file_with_mesh(mesh_filepath): mesh_filename_label = mesh_filename.findChild(QtWidgets.QLabel) if not mesh_filename_label.text(): dialog.close() - raise RuntimeError(f"Failed to set mesh path: {mesh_filepath}") + substance_painter.logging.warning( + f"Failed to set mesh path with the prompt dialog: {mesh_filepath}\n\n" + "Creating new project directly with the mesh path instead.") else: dialog.done(dialog.Accepted) From 34bb371b810ca0c12ff99729cfb9d045aaa1bdeb Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 7 Dec 2023 18:53:04 +0800 Subject: [PATCH 302/327] hound --- openpype/hosts/substancepainter/api/lib.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/substancepainter/api/lib.py b/openpype/hosts/substancepainter/api/lib.py index 35e5b63625..4d3a1d19bd 100644 --- a/openpype/hosts/substancepainter/api/lib.py +++ b/openpype/hosts/substancepainter/api/lib.py @@ -618,7 +618,8 @@ def prompt_new_file_with_mesh(mesh_filepath): if not mesh_filename_label.text(): dialog.close() substance_painter.logging.warning( - f"Failed to set mesh path with the prompt dialog: {mesh_filepath}\n\n" + "Failed to set mesh path with the prompt dialog:" + f"{mesh_filepath}\n\n" "Creating new project directly with the mesh path instead.") else: dialog.done(dialog.Accepted) From 99674b8f0e34f231a21b02594bdf5bc8cfed5c3b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 7 Dec 2023 11:57:44 +0100 Subject: [PATCH 303/327] Fix expected files path handling in NukeSubmitDeadline plugin The commit fixes a typo in the code where "expectied" was changed to "expected". It also improves the handling of expected file paths by correctly adding them to the instance data. Additionally, it adds support for hashed sequence expressions and shifts the start frame by 1 if a slate is present. --- .../plugins/publish/submit_nuke_deadline.py | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py index 8cb4e9eea4..d03416ca00 100644 --- a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -398,7 +398,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin, self.log.debug("Submitting..") self.log.debug(json.dumps(payload, indent=4, sort_keys=True)) - # adding expectied files to instance.data + # adding expected files to instance.data self.expected_files( instance, render_path, @@ -454,7 +454,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin, def expected_files( self, instance, - path, + filepath, start_frame, end_frame ): @@ -463,8 +463,8 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin, if not instance.data.get("expectedFiles"): instance.data["expectedFiles"] = [] - dirname = os.path.dirname(path) - file = os.path.basename(path) + dirname = os.path.dirname(filepath) + file = os.path.basename(filepath) # since some files might be already tagged as publish_on_farm # we need to avoid adding them to expected files since those would be @@ -475,24 +475,32 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin, # Skip if 'publish_on_farm' not available if "publish_on_farm" not in repre.get("tags", []): continue - # is file in representations files? + + # in case where single file (video, image) is already in + # representation file. Will be added to expected files via + # submit_publish_job.py if file in repre.get("files", []): self.log.debug( - "Skipping expected file: {}".format(path)) + "Skipping expected file: {}".format(filepath)) return + # in case path is hashed sequence expression + # (e.g. /path/to/file.####.png) if "#" in file: pparts = file.split("#") padding = "%0{}d".format(len(pparts) - 1) file = pparts[0] + padding + pparts[-1] + # in case input path was single file (video or image) if "%" not in file: - instance.data["expectedFiles"].append(path) + instance.data["expectedFiles"].append(filepath) return + # shift start frame by 1 if slate is present if instance.data.get("slate"): start_frame -= 1 + # add sequence files to expected files for i in range(start_frame, (end_frame + 1)): instance.data["expectedFiles"].append( os.path.join(dirname, (file % i)).replace("\\", "/")) From 5ea8beb8ae16d31acef863e3858f3ca6ed0ea035 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 7 Dec 2023 12:27:02 +0100 Subject: [PATCH 304/327] Bump version because of Settings changes for Deadline --- server_addon/deadline/server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/deadline/server/version.py b/server_addon/deadline/server/version.py index bbab0242f6..1276d0254f 100644 --- a/server_addon/deadline/server/version.py +++ b/server_addon/deadline/server/version.py @@ -1 +1 @@ -__version__ = "0.1.4" +__version__ = "0.1.5" From f0692c87777f9bd31f73a48d3731c54f0f46c1d0 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 7 Dec 2023 20:53:53 +0800 Subject: [PATCH 305/327] add comment --- openpype/hosts/substancepainter/api/lib.py | 2 ++ openpype/hosts/substancepainter/plugins/load/load_mesh.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/openpype/hosts/substancepainter/api/lib.py b/openpype/hosts/substancepainter/api/lib.py index 4d3a1d19bd..1cb480b552 100644 --- a/openpype/hosts/substancepainter/api/lib.py +++ b/openpype/hosts/substancepainter/api/lib.py @@ -583,6 +583,8 @@ def prompt_new_file_with_mesh(mesh_filepath): file_dialog.setDirectory(os.path.dirname(mesh_filepath)) url = QtCore.QUrl.fromLocalFile(os.path.basename(mesh_filepath)) file_dialog.selectUrl(url) + # TODO: find a way to improve the process event to + # load more complicated mesh app.processEvents(QtCore.QEventLoop.ExcludeUserInputEvents, 3000) file_dialog.done(file_dialog.Accepted) diff --git a/openpype/hosts/substancepainter/plugins/load/load_mesh.py b/openpype/hosts/substancepainter/plugins/load/load_mesh.py index ca22163c5d..6b4b79753b 100644 --- a/openpype/hosts/substancepainter/plugins/load/load_mesh.py +++ b/openpype/hosts/substancepainter/plugins/load/load_mesh.py @@ -50,6 +50,8 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): if not substance_painter.project.is_open(): # Allow to 'initialize' a new project path = self.filepath_from_context(context) + #TODO: improve the prompt dialog function to not + # only works for simple polygon scene result = prompt_new_file_with_mesh(mesh_filepath=path) if not result: self.log.info("User cancelled new project prompt." From 16c7d5565d072d84d9a1f5e717c54b832431b7fc Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 7 Dec 2023 20:54:35 +0800 Subject: [PATCH 306/327] hound --- openpype/hosts/substancepainter/plugins/load/load_mesh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/substancepainter/plugins/load/load_mesh.py b/openpype/hosts/substancepainter/plugins/load/load_mesh.py index 6b4b79753b..08c1d5c391 100644 --- a/openpype/hosts/substancepainter/plugins/load/load_mesh.py +++ b/openpype/hosts/substancepainter/plugins/load/load_mesh.py @@ -50,7 +50,7 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): if not substance_painter.project.is_open(): # Allow to 'initialize' a new project path = self.filepath_from_context(context) - #TODO: improve the prompt dialog function to not + # TODO: improve the prompt dialog function to not # only works for simple polygon scene result = prompt_new_file_with_mesh(mesh_filepath=path) if not result: From 7449f27855b5a56deea6e24a182324a1222bf6bf Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 7 Dec 2023 14:26:53 +0100 Subject: [PATCH 307/327] Update IntermediateOutputModel in publish_plugins.py - Set read_raw switch to False - Set viewer_process_override to an empty string - Set bake_viewer_process to True - Set bake_viewer_input_process to True - Set extension to `mov` by default --- .../nuke/server/settings/publish_plugins.py | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/server_addon/nuke/server/settings/publish_plugins.py b/server_addon/nuke/server/settings/publish_plugins.py index d76e95a638..84457d2484 100644 --- a/server_addon/nuke/server/settings/publish_plugins.py +++ b/server_addon/nuke/server/settings/publish_plugins.py @@ -126,16 +126,29 @@ class IntermediateOutputModel(BaseSettingsModel): name: str = Field(title="Output name") filter: BakingStreamFilterModel = Field( title="Filter", default_factory=BakingStreamFilterModel) - read_raw: bool = Field(title="Read raw switch") - viewer_process_override: str = Field(title="Viewer process override") - bake_viewer_process: bool = Field(title="Bake viewer process") + read_raw: bool = Field( + False, + title="Read raw switch" + ) + viewer_process_override: str = Field( + "", + title="Viewer process override" + ) + bake_viewer_process: bool = Field( + True, + title="Bake viewer process" + ) bake_viewer_input_process: bool = Field( + True, title="Bake viewer input process node (LUT)" ) reformat_nodes_config: ReformatNodesConfigModel = Field( default_factory=ReformatNodesConfigModel, title="Reformat Nodes") - extension: str = Field(title="File extension") + extension: str = Field( + "mov", + title="File extension" + ) add_custom_tags: list[str] = Field( title="Custom tags", default_factory=list) From 18a04c706ed1ac02e2e814884d11f029c6d24770 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 7 Dec 2023 14:54:49 +0100 Subject: [PATCH 308/327] OP-7535 - Fix renaming composition in AE (#6025) --- .../plugins/create/create_render.py | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/aftereffects/plugins/create/create_render.py b/openpype/hosts/aftereffects/plugins/create/create_render.py index fbe600ae68..fadfc0c206 100644 --- a/openpype/hosts/aftereffects/plugins/create/create_render.py +++ b/openpype/hosts/aftereffects/plugins/create/create_render.py @@ -56,16 +56,15 @@ class RenderCreator(Creator): use_composition_name = (pre_create_data.get("use_composition_name") or len(comps) > 1) for comp in comps: + composition_name = re.sub( + "[^{}]+".format(SUBSET_NAME_ALLOWED_SYMBOLS), + "", + comp.name + ) if use_composition_name: if "{composition}" not in subset_name_from_ui.lower(): subset_name_from_ui += "{Composition}" - composition_name = re.sub( - "[^{}]+".format(SUBSET_NAME_ALLOWED_SYMBOLS), - "", - comp.name - ) - dynamic_fill = prepare_template_data({"composition": composition_name}) subset_name = subset_name_from_ui.format(**dynamic_fill) @@ -81,6 +80,8 @@ class RenderCreator(Creator): inst.subset_name)) data["members"] = [comp.id] + data["orig_comp_name"] = composition_name + new_instance = CreatedInstance(self.family, subset_name, data, self) if "farm" in pre_create_data: @@ -88,7 +89,7 @@ class RenderCreator(Creator): new_instance.creator_attributes["farm"] = use_farm review = pre_create_data["mark_for_review"] - new_instance.creator_attributes["mark_for_review"] = review + new_instance. creator_attributes["mark_for_review"] = review api.get_stub().imprint(new_instance.id, new_instance.data_to_store()) @@ -150,16 +151,18 @@ class RenderCreator(Creator): subset_change.new_value) def remove_instances(self, instances): + """Removes metadata and renames to original comp name if available.""" for instance in instances: self._remove_instance_from_context(instance) self.host.remove_instance(instance) - subset = instance.data["subset"] comp_id = instance.data["members"][0] comp = api.get_stub().get_item(comp_id) + orig_comp_name = instance.data.get("orig_comp_name") if comp: - new_comp_name = comp.name.replace(subset, '') - if not new_comp_name: + if orig_comp_name: + new_comp_name = orig_comp_name + else: new_comp_name = "dummyCompName" api.get_stub().rename_item(comp_id, new_comp_name) From 30800c1c2ca5cd910772632a62ac269f0a9e2f40 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 7 Dec 2023 15:06:20 +0100 Subject: [PATCH 309/327] Refactor transcoding and thumbnail extraction code - Removed the unused `additional_input_args` parameter from the `convert_colorspace` function in `transcoding.py` - Renamed the `additional_input_args` parameter to `additional_command_args` in the `ExtractThumbnail` class in `extract_thumbnail.py` These changes improve code clarity and remove unnecessary parameters. --- openpype/lib/transcoding.py | 5 ----- openpype/plugins/publish/extract_thumbnail.py | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 37709f45e0..316dedbd3d 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1103,7 +1103,6 @@ def convert_colorspace( target_colorspace=None, view=None, display=None, - additional_input_args=None, additional_command_args=None, logger=None, ): @@ -1125,7 +1124,6 @@ def convert_colorspace( both 'view' and 'display' must be filled (if 'target_colorspace') display (str): name for display-referred reference space (ocio valid) both 'view' and 'display' must be filled (if 'target_colorspace') - additional_input_args (list): arguments for input file additional_command_args (list): arguments for oiiotool (like binary depth for .dpx) logger (logging.Logger): Logger used for logging. @@ -1140,9 +1138,6 @@ def convert_colorspace( # Collect channels to export input_arg, channels_arg = get_oiio_input_and_channel_args(input_info) - if additional_input_args: - input_arg = "{} {}".format(input_arg, " ".join(additional_input_args)) - # Prepare subprocess arguments oiio_cmd = get_oiio_tool_args( "oiiotool", diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index c782bda73e..206ad76707 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -371,7 +371,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): display=repre_display or oiio_default_display, view=repre_view or oiio_default_view, target_colorspace=oiio_default_colorspace, - additional_input_args=resolution_arg, + additional_command_args=resolution_arg, logger=self.log, ) except Exception: From 3ec7f9149f726854b74430083d3cbb3807f3d707 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 7 Dec 2023 15:28:07 +0100 Subject: [PATCH 310/327] Add thumbnail path to instance data and integrate thumbnails in Ayon plugin. - Added code to add the thumbnail path to the instance data. - Modified code to retrieve the thumbnail path from the instance data in Ayon plugin. - Updated code to handle cases where the thumbnail source is not available or specified. - Improved logic for finding the thumbnail representation in published representations. --- openpype/plugins/publish/extract_thumbnail.py | 5 +++++ .../plugins/publish/integrate_thumbnail_ayon.py | 13 ++++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 206ad76707..2b4a61845d 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -212,6 +212,11 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): not instance_thumb_path or not os.path.isfile(instance_thumb_path) ): + self.log.debug( + "Adding thumbnail path to instance data: {}".format( + full_output_path + ) + ) instance.data["thumbnailPath"] = full_output_path new_repre_tags = ["thumbnail"] diff --git a/openpype/plugins/publish/integrate_thumbnail_ayon.py b/openpype/plugins/publish/integrate_thumbnail_ayon.py index 2c8fb5b692..1947c9dd4c 100644 --- a/openpype/plugins/publish/integrate_thumbnail_ayon.py +++ b/openpype/plugins/publish/integrate_thumbnail_ayon.py @@ -72,7 +72,7 @@ class IntegrateThumbnailsAYON(pyblish.api.ContextPlugin): ) def _prepare_instances(self, context): - context_thumbnail_path = context.get("thumbnailPath") + context_thumbnail_path = context.data.get("thumbnailPath") valid_context_thumbnail = bool( context_thumbnail_path and os.path.exists(context_thumbnail_path) @@ -93,9 +93,12 @@ class IntegrateThumbnailsAYON(pyblish.api.ContextPlugin): # Find thumbnail path on instance thumbnail_source = instance.data.get("thumbnailSource") - thumbnail_path = (thumbnail_source or - self._get_instance_thumbnail_path( - published_repres)) + thumbnail_path = instance.data.get("thumbnailPath") + thumbnail_path = ( + thumbnail_source + or thumbnail_path + or self._get_instance_thumbnail_path(published_repres) + ) if thumbnail_path: self.log.debug(( "Found thumbnail path for instance \"{}\"." @@ -133,7 +136,7 @@ class IntegrateThumbnailsAYON(pyblish.api.ContextPlugin): thumb_repre_doc = None for repre_info in published_representations.values(): repre_doc = repre_info["representation"] - if repre_doc["name"].lower() == "thumbnail": + if "thumbnail" in repre_doc["name"].lower(): thumb_repre_doc = repre_doc break From b48253739af15cdb0a3e9610bacd9cacf16d871c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 7 Dec 2023 23:33:56 +0800 Subject: [PATCH 311/327] add the AYON/OP settings to enable extractor for model family in 3dsmax --- .../defaults/project_settings/max.json | 15 ++++ .../schemas/schema_max_publish.json | 75 +++++++++++++++++++ .../max/server/settings/publishers.py | 29 +++++++ server_addon/max/server/version.py | 2 +- 4 files changed, 120 insertions(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/max.json b/openpype/settings/defaults/project_settings/max.json index 97fcf69e31..359db19226 100644 --- a/openpype/settings/defaults/project_settings/max.json +++ b/openpype/settings/defaults/project_settings/max.json @@ -56,6 +56,21 @@ "enabled": false, "optional": true, "family_plugins_mapping": [] + }, + "ExtractModelObj": { + "enabled": false, + "optional": true, + "active": true + }, + "ExtractModelFbx": { + "enabled": false, + "optional": true, + "active": true + }, + "ExtractModelUSD": { + "enabled": false, + "optional": true, + "active": true } } } diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json index c6d37ae993..9077cf0c58 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json @@ -90,6 +90,81 @@ } } ] + }, + { + "type": "dict", + "collapsible": true, + "checkbox_key": "enabled", + "key": "ExtractModelObj", + "label": "Extract Obj", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, + { + "type": "boolean", + "key": "active", + "label": "Active" + } + ] + }, + { + "type": "dict", + "collapsible": true, + "checkbox_key": "enabled", + "key": "ExtractModelFbx", + "label": "Extract FBX", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, + { + "type": "boolean", + "key": "active", + "label": "Active" + } + ] + }, + { + "type": "dict", + "collapsible": true, + "checkbox_key": "enabled", + "key": "ExtractModelUSD", + "label": "Extract Geometry (USD)", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, + { + "type": "boolean", + "key": "active", + "label": "Active" + } + ] } ] } diff --git a/server_addon/max/server/settings/publishers.py b/server_addon/max/server/settings/publishers.py index b48f14a064..04a4bcb875 100644 --- a/server_addon/max/server/settings/publishers.py +++ b/server_addon/max/server/settings/publishers.py @@ -68,6 +68,20 @@ class PublishersModel(BaseSettingsModel): default_factory=ValidateLoadedPluginModel, title="Validate Loaded Plugin" ) + ExtractModelObj: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Extract OBJ", + section="Publishers" + ) + ExtractModelFbx: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Extract FBX" + ) + ExtractModelUSD: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Extract Geometry (USD)" + ) + DEFAULT_PUBLISH_SETTINGS = { "ValidateFrameRange": { @@ -83,5 +97,20 @@ DEFAULT_PUBLISH_SETTINGS = { "enabled": False, "optional": True, "family_plugins_mapping": [] + }, + "ExtractModelObj": { + "enabled": False, + "optional": True, + "active": True + }, + "ExtractModelFbx": { + "enabled": False, + "optional": True, + "active": True + }, + "ExtractModelUSD": { + "enabled": False, + "optional": True, + "active": True } } diff --git a/server_addon/max/server/version.py b/server_addon/max/server/version.py index b3f4756216..ae7362549b 100644 --- a/server_addon/max/server/version.py +++ b/server_addon/max/server/version.py @@ -1 +1 @@ -__version__ = "0.1.2" +__version__ = "0.1.3" From bb5d48eae1b207cdff19c774636ddc13fdcf5f25 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 7 Dec 2023 16:48:43 +0100 Subject: [PATCH 312/327] Refactor code to skip instance creation for new assets - Removed the check for `newAssetPublishing` in `CollectResourcesPath.process()` - This change allows instances to be processed even if they are creating new assets --- openpype/plugins/publish/collect_resources_path.py | 5 ----- openpype/plugins/publish/extract_otio_audio_tracks.py | 1 + 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/openpype/plugins/publish/collect_resources_path.py b/openpype/plugins/publish/collect_resources_path.py index a7f12bdfdb..0f29fec054 100644 --- a/openpype/plugins/publish/collect_resources_path.py +++ b/openpype/plugins/publish/collect_resources_path.py @@ -68,11 +68,6 @@ class CollectResourcesPath(pyblish.api.InstancePlugin): ] def process(self, instance): - # editorial would fail since they might not be in database yet - new_asset_publishing = instance.data.get("newAssetPublishing") - if new_asset_publishing: - self.log.debug("Instance is creating new asset. Skipping.") - return anatomy = instance.context.data["anatomy"] diff --git a/openpype/plugins/publish/extract_otio_audio_tracks.py b/openpype/plugins/publish/extract_otio_audio_tracks.py index 4b73321f02..d5ab1d6032 100644 --- a/openpype/plugins/publish/extract_otio_audio_tracks.py +++ b/openpype/plugins/publish/extract_otio_audio_tracks.py @@ -319,6 +319,7 @@ class ExtractOtioAudioTracks(pyblish.api.ContextPlugin): Returns: str: temp fpath """ + name = name.replace("/", "_") return os.path.normpath( tempfile.mktemp( prefix="pyblish_tmp_{}_".format(name), From dda7c573bafe42716132adba2f4434a0122eb55e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 7 Dec 2023 16:56:06 +0100 Subject: [PATCH 313/327] Removed render instance (#6026) This test was created as simple model and workfile publish, without Deadline rendering. Cleaned up render elements. --- ...oject_test_asset_workfileTest_task_v001.ma | 208 ++++++------- .../Main/renderMain_metadata.json | 275 ------------------ .../test_project_test_asset_test_task_v001.ma | 208 ++++++------- .../test_project_test_asset_test_task_v002.ma | 204 +++++-------- .../test_project_test_asset_test_task_v001.ma | 208 ++++++------- 5 files changed, 320 insertions(+), 783 deletions(-) delete mode 100644 tests/integration/hosts/maya/test_publish_in_maya/expected/test_project/test_asset/work/test_task/renders/maya/test_project_test_asset_workfileTest_task_v001/Main/renderMain_metadata.json diff --git a/tests/integration/hosts/maya/test_publish_in_maya/expected/test_project/test_asset/publish/workfile/workfileTest_task/v001/test_project_test_asset_workfileTest_task_v001.ma b/tests/integration/hosts/maya/test_publish_in_maya/expected/test_project/test_asset/publish/workfile/workfileTest_task/v001/test_project_test_asset_workfileTest_task_v001.ma index e438d80d5f..2cc87c2f48 100644 --- a/tests/integration/hosts/maya/test_publish_in_maya/expected/test_project/test_asset/publish/workfile/workfileTest_task/v001/test_project_test_asset_workfileTest_task_v001.ma +++ b/tests/integration/hosts/maya/test_publish_in_maya/expected/test_project/test_asset/publish/workfile/workfileTest_task/v001/test_project_test_asset_workfileTest_task_v001.ma @@ -1,22 +1,22 @@ -//Maya ASCII 2022 scene -//Name: test_project_test_asset_test_task_v001.ma -//Last modified: Thu, Sep 14, 2023 06:31:00 PM +//Maya ASCII 2023 scene +//Name: test_project_test_asset_test_task_v002.ma +//Last modified: Thu, Dec 07, 2023 03:53:06 PM //Codeset: 1252 -requires maya "2022"; -requires -nodeType "polyDisc" "modelingToolkit" "0.0.0.0"; -requires "stereoCamera" "10.0"; -requires -nodeType "aiOptions" -nodeType "aiAOVDriver" -nodeType "aiAOVFilter" "mtoa" "5.2.2.1"; +requires maya "2023"; requires -nodeType "simpleSelector" -nodeType "renderSetupLayer" -nodeType "renderSetup" -nodeType "collection" "renderSetup.py" "1.0"; requires "stereoCamera" "10.0"; +requires -nodeType "aiOptions" -nodeType "aiAOVDriver" -nodeType "aiAOVFilter" "mtoa" "5.2.1.1"; +requires -nodeType "polyDisc" "modelingToolkit" "0.0.0.0"; +requires "stereoCamera" "10.0"; currentUnit -l centimeter -a degree -t pal; fileInfo "application" "maya"; -fileInfo "product" "Maya 2022"; -fileInfo "version" "2022"; -fileInfo "cutIdentifier" "202205171752-c25c06f306"; -fileInfo "osv" "Windows 10 Pro v2009 (Build: 19044)"; +fileInfo "product" "Maya 2023"; +fileInfo "version" "2023"; +fileInfo "cutIdentifier" "202211021031-847a9f9623"; +fileInfo "osv" "Windows 10 Pro v2009 (Build: 19045)"; fileInfo "license" "education"; -fileInfo "UUID" "019C7F50-40EF-1435-E27F-729F64685E67"; +fileInfo "UUID" "7A992745-4AD5-777F-5575-B4BFAC62B1D0"; fileInfo "OpenPypeContext" "eyJwdWJsaXNoX2F0dHJpYnV0ZXMiOiB7IlZhbGlkYXRlQ29udGFpbmVycyI6IHsiYWN0aXZlIjogdHJ1ZX19fQ=="; createNode transform -s -n "persp"; rename -uid "D52C935B-47C9-D868-A875-D799DD17B3A1"; @@ -142,19 +142,20 @@ createNode camera -n "perspShape1" -p "persp1"; setAttr ".hc" -type "string" "viewSet -p %camera"; setAttr ".dr" yes; createNode lightLinker -s -n "lightLinker1"; - rename -uid "6B9ADCD4-41B9-5BCC-826D-4A874A278510"; + rename -uid "09465BD3-42E5-18E4-7906-20A99BB2A6C0"; setAttr -s 2 ".lnk"; setAttr -s 2 ".slnk"; createNode shapeEditorManager -n "shapeEditorManager"; - rename -uid "5B4518C5-46C9-6921-690E-EFAF77B21A36"; + rename -uid "9F2E8009-4D69-046B-FCC4-28A8CE8F86DB"; createNode poseInterpolatorManager -n "poseInterpolatorManager"; - rename -uid "8518F293-4F06-BFF2-647A-72A099FBF025"; + rename -uid "6757AD81-40B0-A747-69C3-D9A56259571E"; createNode displayLayerManager -n "layerManager"; - rename -uid "04D880D5-4D86-0C58-CA3D-208ABE3E1E16"; + rename -uid "6F055ED5-4D91-8F85-7951-B4A13543A561"; createNode displayLayer -n "defaultLayer"; rename -uid "4A776D1B-401F-7069-1C74-A7AAE84CEE03"; + setAttr ".ufem" -type "stringArray" 0 ; createNode renderLayerManager -n "renderLayerManager"; - rename -uid "B719B8BE-46BF-12E6-BEBA-B0AD4DBDBA87"; + rename -uid "55626D2B-4FD5-61A1-7AB2-47B13F19D8AA"; setAttr -s 2 ".rlmi[1]" 1; setAttr -s 2 ".rlmi"; createNode renderLayer -n "defaultRenderLayer"; @@ -167,6 +168,7 @@ createNode polySphere -n "polySphere1"; createNode objectSet -n "modelMain"; rename -uid "A76AD4F8-4CF5-AA0D-4E98-BABEE6454CC3"; addAttr -ci true -sn "cbId" -ln "cbId" -dt "string"; + addAttr -ci true -sn "instance_id" -ln "instance_id" -dt "string"; addAttr -ci true -sn "id" -ln "id" -dt "string"; addAttr -ci true -sn "family" -ln "family" -dt "string"; addAttr -ci true -sn "subset" -ln "subset" -dt "string"; @@ -175,18 +177,19 @@ createNode objectSet -n "modelMain"; addAttr -ci true -sn "variant" -ln "variant" -dt "string"; addAttr -ci true -sn "asset" -ln "asset" -dt "string"; addAttr -ci true -sn "task" -ln "task" -dt "string"; - addAttr -ci true -sn "instance_id" -ln "instance_id" -dt "string"; addAttr -ci true -sn "writeColorSets" -ln "writeColorSets" -min 0 -max 1 -at "bool"; addAttr -ci true -sn "writeFaceSets" -ln "writeFaceSets" -min 0 -max 1 -at "bool"; - addAttr -ci true -sn "includeParentHierarchy" -ln "includeParentHierarchy" -min + addAttr -ci true -sn "includeParentHierarchy" -ln "includeParentHierarchy" -min 0 -max 1 -at "bool"; addAttr -ci true -sn "attr" -ln "attr" -dt "string"; addAttr -ci true -sn "attrPrefix" -ln "attrPrefix" -dt "string"; addAttr -ci true -sn "publish_attributes" -ln "publish_attributes" -dt "string"; - addAttr -ci true -sn "__creator_attributes_keys" -ln "__creator_attributes_keys" + addAttr -ci true -sn "creator_attributes" -ln "creator_attributes" -dt "string"; + addAttr -ci true -sn "__creator_attributes_keys" -ln "__creator_attributes_keys" -dt "string"; setAttr ".ihi" 0; setAttr ".cbId" -type "string" "60df31e2be2b48bd3695c056:7364ea6776c9"; + setAttr ".instance_id" -type "string" "6889d3db-b813-43db-96de-9ba555dc4472"; setAttr ".id" -type "string" "pyblish.avalon.instance"; setAttr ".family" -type "string" "model"; setAttr ".subset" -type "string" "modelMain"; @@ -195,59 +198,68 @@ createNode objectSet -n "modelMain"; setAttr ".variant" -type "string" "Main"; setAttr ".asset" -type "string" "test_asset"; setAttr ".task" -type "string" "test_task"; - setAttr ".instance_id" -type "string" "6889d3db-b813-43db-96de-9ba555dc4472"; setAttr -cb on ".writeColorSets"; setAttr -cb on ".writeFaceSets"; setAttr -cb on ".includeParentHierarchy"; setAttr ".attr" -type "string" ""; setAttr ".attrPrefix" -type "string" ""; - setAttr ".publish_attributes" -type "string" "{\"ValidateNodeIDsRelated\": {\"active\": true}, \"ValidateTransformNamingSuffix\": {\"active\": true}, \"ValidateColorSets\": {\"active\": true}, \"ValidateMeshArnoldAttributes\": {\"active\": true}, \"ValidateMeshHasUVs\": {\"active\": true}, \"ValidateMeshNonZeroEdgeLength\": {\"active\": true}, \"ExtractModel\": {\"active\": true}}"; + setAttr ".publish_attributes" -type "string" "{\"ValidateNodeIDsRelated\": {\"active\": true}, \"ValidateInstanceInContext\": {\"active\": true}, \"ValidateTransformNamingSuffix\": {\"active\": true}, \"ValidateColorSets\": {\"active\": true}, \"ValidateMeshHasUVs\": {\"active\": true}, \"ValidateMeshNonZeroEdgeLength\": {\"active\": true}, \"ExtractModel\": {\"active\": true}, \"ValidateMeshArnoldAttributes\": {\"active\": true}}"; + setAttr ".creator_attributes" -type "string" "{}"; setAttr ".__creator_attributes_keys" -type "string" "writeColorSets,writeFaceSets,includeParentHierarchy,attr,attrPrefix"; createNode script -n "uiConfigurationScriptNode"; rename -uid "4B7AFB53-452E-E870-63E1-CCA1DD6EAF13"; setAttr ".b" -type "string" ( "// Maya Mel UI Configuration File.\n//\n// This script is machine generated. Edit at your own risk.\n//\n//\n\nglobal string $gMainPane;\nif (`paneLayout -exists $gMainPane`) {\n\n\tglobal int $gUseScenePanelConfig;\n\tint $useSceneConfig = $gUseScenePanelConfig;\n\tint $nodeEditorPanelVisible = stringArrayContains(\"nodeEditorPanel1\", `getPanel -vis`);\n\tint $nodeEditorWorkspaceControlOpen = (`workspaceControl -exists nodeEditorPanel1Window` && `workspaceControl -q -visible nodeEditorPanel1Window`);\n\tint $menusOkayInPanels = `optionVar -q allowMenusInPanels`;\n\tint $nVisPanes = `paneLayout -q -nvp $gMainPane`;\n\tint $nPanes = 0;\n\tstring $editorName;\n\tstring $panelName;\n\tstring $itemFilterName;\n\tstring $panelConfig;\n\n\t//\n\t// get current state of the UI\n\t//\n\tsceneUIReplacement -update $gMainPane;\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"modelPanel\" (localizedPanelLabel(\"Top View\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tmodelPanel -edit -l (localizedPanelLabel(\"Top View\")) -mbv $menusOkayInPanels $panelName;\n" - + "\t\t$editorName = $panelName;\n modelEditor -e \n -camera \"|top\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"smoothShaded\" \n -activeOnly 0\n -ignorePanZoom 0\n -wireframeOnShaded 0\n -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n -useDefaultMaterial 0\n -bufferMode \"double\" \n -twoSidedLighting 0\n -backfaceCulling 0\n -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n -lineWidth 1\n -textureAnisotropic 0\n -textureHilight 1\n -textureSampling 2\n -textureDisplay \"modulate\" \n -textureMaxSize 16384\n -fogging 0\n -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n" + + "\t\t$editorName = $panelName;\n modelEditor -e \n -camera \"|top\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"smoothShaded\" \n -activeOnly 0\n -ignorePanZoom 0\n -wireframeOnShaded 0\n -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n -useDefaultMaterial 0\n -bufferMode \"double\" \n -twoSidedLighting 0\n -backfaceCulling 0\n -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n -lineWidth 1\n -textureAnisotropic 0\n -textureHilight 1\n -textureSampling 2\n -textureDisplay \"modulate\" \n -textureMaxSize 32768\n -fogging 0\n -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n" + " -depthOfFieldPreview 1\n -maxConstantTransparency 1\n -rendererName \"vp2Renderer\" \n -objectFilterShowInHUD 1\n -isFiltered 0\n -colorResolution 256 256 \n -bumpResolution 512 512 \n -textureCompression 0\n -transparencyAlgorithm \"frontAndBackCull\" \n -transpInShadows 0\n -cullingOverride \"none\" \n -lowQualityLighting 0\n -maximumNumHardwareLights 1\n -occlusionCulling 0\n -shadingModel 0\n -useBaseRenderer 0\n -useReducedRenderer 0\n -smallObjectCulling 0\n -smallObjectThreshold -1 \n -interactiveDisableShadows 0\n -interactiveBackFaceCull 0\n -sortTransparent 1\n -controllers 1\n -nurbsCurves 1\n -nurbsSurfaces 1\n -polymeshes 1\n -subdivSurfaces 1\n -planes 1\n -lights 1\n -cameras 1\n -controlVertices 1\n" - + " -hulls 1\n -grid 1\n -imagePlane 1\n -joints 1\n -ikHandles 1\n -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n -nCloths 1\n -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n -locators 1\n -manipulators 1\n -pluginShapes 1\n -dimensions 1\n -handles 1\n -pivots 1\n -textures 1\n -strokes 1\n -motionTrails 1\n -clipGhosts 1\n -greasePencils 1\n -shadows 0\n -captureSequenceNumber -1\n -width 1\n -height 1\n -sceneRenderFilter 0\n $editorName;\n modelEditor -e -viewSelected 0 $editorName;\n modelEditor -e \n -pluginObjects \"gpuCacheDisplayFilter\" 1 \n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n" - + "\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"modelPanel\" (localizedPanelLabel(\"Side View\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tmodelPanel -edit -l (localizedPanelLabel(\"Side View\")) -mbv $menusOkayInPanels $panelName;\n\t\t$editorName = $panelName;\n modelEditor -e \n -camera \"|side\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"smoothShaded\" \n -activeOnly 0\n -ignorePanZoom 0\n -wireframeOnShaded 0\n -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n -useDefaultMaterial 0\n -bufferMode \"double\" \n -twoSidedLighting 0\n -backfaceCulling 0\n -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n -lineWidth 1\n -textureAnisotropic 0\n -textureHilight 1\n -textureSampling 2\n" - + " -textureDisplay \"modulate\" \n -textureMaxSize 16384\n -fogging 0\n -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n -depthOfFieldPreview 1\n -maxConstantTransparency 1\n -rendererName \"vp2Renderer\" \n -objectFilterShowInHUD 1\n -isFiltered 0\n -colorResolution 256 256 \n -bumpResolution 512 512 \n -textureCompression 0\n -transparencyAlgorithm \"frontAndBackCull\" \n -transpInShadows 0\n -cullingOverride \"none\" \n -lowQualityLighting 0\n -maximumNumHardwareLights 1\n -occlusionCulling 0\n -shadingModel 0\n -useBaseRenderer 0\n -useReducedRenderer 0\n -smallObjectCulling 0\n -smallObjectThreshold -1 \n -interactiveDisableShadows 0\n -interactiveBackFaceCull 0\n" - + " -sortTransparent 1\n -controllers 1\n -nurbsCurves 1\n -nurbsSurfaces 1\n -polymeshes 1\n -subdivSurfaces 1\n -planes 1\n -lights 1\n -cameras 1\n -controlVertices 1\n -hulls 1\n -grid 1\n -imagePlane 1\n -joints 1\n -ikHandles 1\n -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n -nCloths 1\n -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n -locators 1\n -manipulators 1\n -pluginShapes 1\n -dimensions 1\n -handles 1\n -pivots 1\n -textures 1\n -strokes 1\n -motionTrails 1\n -clipGhosts 1\n -greasePencils 1\n -shadows 0\n -captureSequenceNumber -1\n -width 1\n -height 1\n" - + " -sceneRenderFilter 0\n $editorName;\n modelEditor -e -viewSelected 0 $editorName;\n modelEditor -e \n -pluginObjects \"gpuCacheDisplayFilter\" 1 \n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"modelPanel\" (localizedPanelLabel(\"Front View\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tmodelPanel -edit -l (localizedPanelLabel(\"Front View\")) -mbv $menusOkayInPanels $panelName;\n\t\t$editorName = $panelName;\n modelEditor -e \n -camera \"|front\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"smoothShaded\" \n -activeOnly 0\n -ignorePanZoom 0\n -wireframeOnShaded 0\n -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n -useDefaultMaterial 0\n -bufferMode \"double\" \n -twoSidedLighting 0\n -backfaceCulling 0\n" - + " -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n -lineWidth 1\n -textureAnisotropic 0\n -textureHilight 1\n -textureSampling 2\n -textureDisplay \"modulate\" \n -textureMaxSize 16384\n -fogging 0\n -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n -depthOfFieldPreview 1\n -maxConstantTransparency 1\n -rendererName \"vp2Renderer\" \n -objectFilterShowInHUD 1\n -isFiltered 0\n -colorResolution 256 256 \n -bumpResolution 512 512 \n -textureCompression 0\n -transparencyAlgorithm \"frontAndBackCull\" \n -transpInShadows 0\n -cullingOverride \"none\" \n -lowQualityLighting 0\n -maximumNumHardwareLights 1\n -occlusionCulling 0\n" - + " -shadingModel 0\n -useBaseRenderer 0\n -useReducedRenderer 0\n -smallObjectCulling 0\n -smallObjectThreshold -1 \n -interactiveDisableShadows 0\n -interactiveBackFaceCull 0\n -sortTransparent 1\n -controllers 1\n -nurbsCurves 1\n -nurbsSurfaces 1\n -polymeshes 1\n -subdivSurfaces 1\n -planes 1\n -lights 1\n -cameras 1\n -controlVertices 1\n -hulls 1\n -grid 1\n -imagePlane 1\n -joints 1\n -ikHandles 1\n -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n -nCloths 1\n -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n -locators 1\n -manipulators 1\n -pluginShapes 1\n -dimensions 1\n -handles 1\n -pivots 1\n" - + " -textures 1\n -strokes 1\n -motionTrails 1\n -clipGhosts 1\n -greasePencils 1\n -shadows 0\n -captureSequenceNumber -1\n -width 1\n -height 1\n -sceneRenderFilter 0\n $editorName;\n modelEditor -e -viewSelected 0 $editorName;\n modelEditor -e \n -pluginObjects \"gpuCacheDisplayFilter\" 1 \n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"modelPanel\" (localizedPanelLabel(\"Persp View\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tmodelPanel -edit -l (localizedPanelLabel(\"Persp View\")) -mbv $menusOkayInPanels $panelName;\n\t\t$editorName = $panelName;\n modelEditor -e \n -camera \"|persp\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"smoothShaded\" \n -activeOnly 0\n -ignorePanZoom 0\n" - + " -wireframeOnShaded 0\n -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n -useDefaultMaterial 0\n -bufferMode \"double\" \n -twoSidedLighting 0\n -backfaceCulling 0\n -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n -lineWidth 1\n -textureAnisotropic 0\n -textureHilight 1\n -textureSampling 2\n -textureDisplay \"modulate\" \n -textureMaxSize 16384\n -fogging 0\n -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n -depthOfFieldPreview 1\n -maxConstantTransparency 1\n -rendererName \"vp2Renderer\" \n -objectFilterShowInHUD 1\n -isFiltered 0\n -colorResolution 256 256 \n -bumpResolution 512 512 \n" - + " -textureCompression 0\n -transparencyAlgorithm \"frontAndBackCull\" \n -transpInShadows 0\n -cullingOverride \"none\" \n -lowQualityLighting 0\n -maximumNumHardwareLights 1\n -occlusionCulling 0\n -shadingModel 0\n -useBaseRenderer 0\n -useReducedRenderer 0\n -smallObjectCulling 0\n -smallObjectThreshold -1 \n -interactiveDisableShadows 0\n -interactiveBackFaceCull 0\n -sortTransparent 1\n -controllers 1\n -nurbsCurves 1\n -nurbsSurfaces 1\n -polymeshes 1\n -subdivSurfaces 1\n -planes 1\n -lights 1\n -cameras 1\n -controlVertices 1\n -hulls 1\n -grid 1\n -imagePlane 1\n -joints 1\n -ikHandles 1\n -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n" - + " -nCloths 1\n -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n -locators 1\n -manipulators 1\n -pluginShapes 1\n -dimensions 1\n -handles 1\n -pivots 1\n -textures 1\n -strokes 1\n -motionTrails 1\n -clipGhosts 1\n -greasePencils 1\n -shadows 0\n -captureSequenceNumber -1\n -width 1312\n -height 732\n -sceneRenderFilter 0\n $editorName;\n modelEditor -e -viewSelected 0 $editorName;\n modelEditor -e \n -pluginObjects \"gpuCacheDisplayFilter\" 1 \n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"outlinerPanel\" (localizedPanelLabel(\"ToggledOutliner\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\toutlinerPanel -edit -l (localizedPanelLabel(\"ToggledOutliner\")) -mbv $menusOkayInPanels $panelName;\n" - + "\t\t$editorName = $panelName;\n outlinerEditor -e \n -docTag \"isolOutln_fromSeln\" \n -showShapes 0\n -showAssignedMaterials 0\n -showTimeEditor 1\n -showReferenceNodes 1\n -showReferenceMembers 1\n -showAttributes 0\n -showConnected 0\n -showAnimCurvesOnly 0\n -showMuteInfo 0\n -organizeByLayer 1\n -organizeByClip 1\n -showAnimLayerWeight 1\n -autoExpandLayers 1\n -autoExpand 0\n -showDagOnly 1\n -showAssets 1\n -showContainedOnly 1\n -showPublishedAsConnected 0\n -showParentContainers 0\n -showContainerContents 1\n -ignoreDagHierarchy 0\n -expandConnections 0\n -showUpstreamCurves 1\n -showUnitlessCurves 1\n -showCompounds 1\n -showLeafs 1\n -showNumericAttrsOnly 0\n -highlightActive 1\n -autoSelectNewObjects 0\n" - + " -doNotSelectNewObjects 0\n -dropIsParent 1\n -transmitFilters 0\n -setFilter \"defaultSetFilter\" \n -showSetMembers 1\n -allowMultiSelection 1\n -alwaysToggleSelect 0\n -directSelect 0\n -isSet 0\n -isSetMember 0\n -displayMode \"DAG\" \n -expandObjects 0\n -setsIgnoreFilters 1\n -containersIgnoreFilters 0\n -editAttrName 0\n -showAttrValues 0\n -highlightSecondary 0\n -showUVAttrsOnly 0\n -showTextureNodesOnly 0\n -attrAlphaOrder \"default\" \n -animLayerFilterOptions \"allAffecting\" \n -sortOrder \"none\" \n -longNames 0\n -niceNames 1\n -selectCommand \"print(\\\"\\\")\" \n -showNamespace 1\n -showPinIcons 0\n -mapMotionTrails 0\n -ignoreHiddenAttribute 1\n -ignoreOutlinerColor 0\n -renderFilterVisible 0\n -renderFilterIndex 0\n" - + " -selectionOrder \"chronological\" \n -expandAttribute 0\n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"outlinerPanel\" (localizedPanelLabel(\"Outliner\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\toutlinerPanel -edit -l (localizedPanelLabel(\"Outliner\")) -mbv $menusOkayInPanels $panelName;\n\t\t$editorName = $panelName;\n outlinerEditor -e \n -showShapes 0\n -showAssignedMaterials 0\n -showTimeEditor 1\n -showReferenceNodes 0\n -showReferenceMembers 0\n -showAttributes 0\n -showConnected 0\n -showAnimCurvesOnly 0\n -showMuteInfo 0\n -organizeByLayer 1\n -organizeByClip 1\n -showAnimLayerWeight 1\n -autoExpandLayers 1\n -autoExpand 0\n -showDagOnly 1\n -showAssets 1\n -showContainedOnly 1\n -showPublishedAsConnected 0\n" - + " -showParentContainers 0\n -showContainerContents 1\n -ignoreDagHierarchy 0\n -expandConnections 0\n -showUpstreamCurves 1\n -showUnitlessCurves 1\n -showCompounds 1\n -showLeafs 1\n -showNumericAttrsOnly 0\n -highlightActive 1\n -autoSelectNewObjects 0\n -doNotSelectNewObjects 0\n -dropIsParent 1\n -transmitFilters 0\n -setFilter \"defaultSetFilter\" \n -showSetMembers 1\n -allowMultiSelection 1\n -alwaysToggleSelect 0\n -directSelect 0\n -displayMode \"DAG\" \n -expandObjects 0\n -setsIgnoreFilters 1\n -containersIgnoreFilters 0\n -editAttrName 0\n -showAttrValues 0\n -highlightSecondary 0\n -showUVAttrsOnly 0\n -showTextureNodesOnly 0\n -attrAlphaOrder \"default\" \n -animLayerFilterOptions \"allAffecting\" \n -sortOrder \"none\" \n" - + " -longNames 0\n -niceNames 1\n -showNamespace 1\n -showPinIcons 0\n -mapMotionTrails 0\n -ignoreHiddenAttribute 0\n -ignoreOutlinerColor 0\n -renderFilterVisible 0\n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"graphEditor\" (localizedPanelLabel(\"Graph Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Graph Editor\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = ($panelName+\"OutlineEd\");\n outlinerEditor -e \n -showShapes 1\n -showAssignedMaterials 0\n -showTimeEditor 1\n -showReferenceNodes 0\n -showReferenceMembers 0\n -showAttributes 1\n -showConnected 1\n -showAnimCurvesOnly 1\n -showMuteInfo 0\n -organizeByLayer 1\n" - + " -organizeByClip 1\n -showAnimLayerWeight 1\n -autoExpandLayers 1\n -autoExpand 1\n -showDagOnly 0\n -showAssets 1\n -showContainedOnly 0\n -showPublishedAsConnected 0\n -showParentContainers 0\n -showContainerContents 0\n -ignoreDagHierarchy 0\n -expandConnections 1\n -showUpstreamCurves 1\n -showUnitlessCurves 1\n -showCompounds 0\n -showLeafs 1\n -showNumericAttrsOnly 1\n -highlightActive 0\n -autoSelectNewObjects 1\n -doNotSelectNewObjects 0\n -dropIsParent 1\n -transmitFilters 1\n -setFilter \"0\" \n -showSetMembers 0\n -allowMultiSelection 1\n -alwaysToggleSelect 0\n -directSelect 0\n -displayMode \"DAG\" \n -expandObjects 0\n" - + " -setsIgnoreFilters 1\n -containersIgnoreFilters 0\n -editAttrName 0\n -showAttrValues 0\n -highlightSecondary 0\n -showUVAttrsOnly 0\n -showTextureNodesOnly 0\n -attrAlphaOrder \"default\" \n -animLayerFilterOptions \"allAffecting\" \n -sortOrder \"none\" \n -longNames 0\n -niceNames 1\n -showNamespace 1\n -showPinIcons 1\n -mapMotionTrails 1\n -ignoreHiddenAttribute 0\n -ignoreOutlinerColor 0\n -renderFilterVisible 0\n $editorName;\n\n\t\t\t$editorName = ($panelName+\"GraphEd\");\n animCurveEditor -e \n -displayValues 0\n -snapTime \"integer\" \n -snapValue \"none\" \n -showPlayRangeShades \"on\" \n -lockPlayRangeShades \"off\" \n -smoothness \"fine\" \n -resultSamples 1.041667\n" - + " -resultScreenSamples 0\n -resultUpdate \"delayed\" \n -showUpstreamCurves 1\n -keyMinScale 1\n -stackedCurvesMin -1\n -stackedCurvesMax 1\n -stackedCurvesSpace 0.2\n -preSelectionHighlight 0\n -constrainDrag 0\n -valueLinesToggle 1\n -highlightAffectedCurves 0\n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"dopeSheetPanel\" (localizedPanelLabel(\"Dope Sheet\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Dope Sheet\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = ($panelName+\"OutlineEd\");\n outlinerEditor -e \n -showShapes 1\n -showAssignedMaterials 0\n -showTimeEditor 1\n -showReferenceNodes 0\n -showReferenceMembers 0\n" - + " -showAttributes 1\n -showConnected 1\n -showAnimCurvesOnly 1\n -showMuteInfo 0\n -organizeByLayer 1\n -organizeByClip 1\n -showAnimLayerWeight 1\n -autoExpandLayers 1\n -autoExpand 0\n -showDagOnly 0\n -showAssets 1\n -showContainedOnly 0\n -showPublishedAsConnected 0\n -showParentContainers 0\n -showContainerContents 0\n -ignoreDagHierarchy 0\n -expandConnections 1\n -showUpstreamCurves 1\n -showUnitlessCurves 0\n -showCompounds 1\n -showLeafs 1\n -showNumericAttrsOnly 1\n -highlightActive 0\n -autoSelectNewObjects 0\n -doNotSelectNewObjects 1\n -dropIsParent 1\n -transmitFilters 0\n -setFilter \"0\" \n -showSetMembers 0\n" - + " -allowMultiSelection 1\n -alwaysToggleSelect 0\n -directSelect 0\n -displayMode \"DAG\" \n -expandObjects 0\n -setsIgnoreFilters 1\n -containersIgnoreFilters 0\n -editAttrName 0\n -showAttrValues 0\n -highlightSecondary 0\n -showUVAttrsOnly 0\n -showTextureNodesOnly 0\n -attrAlphaOrder \"default\" \n -animLayerFilterOptions \"allAffecting\" \n -sortOrder \"none\" \n -longNames 0\n -niceNames 1\n -showNamespace 1\n -showPinIcons 0\n -mapMotionTrails 1\n -ignoreHiddenAttribute 0\n -ignoreOutlinerColor 0\n -renderFilterVisible 0\n $editorName;\n\n\t\t\t$editorName = ($panelName+\"DopeSheetEd\");\n dopeSheetEditor -e \n -displayValues 0\n -snapTime \"integer\" \n" - + " -snapValue \"none\" \n -outliner \"dopeSheetPanel1OutlineEd\" \n -showSummary 1\n -showScene 0\n -hierarchyBelow 0\n -showTicks 1\n -selectionWindow 0 0 0 0 \n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"timeEditorPanel\" (localizedPanelLabel(\"Time Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Time Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"clipEditorPanel\" (localizedPanelLabel(\"Trax Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Trax Editor\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = clipEditorNameFromPanel($panelName);\n" - + " clipEditor -e \n -displayValues 0\n -snapTime \"none\" \n -snapValue \"none\" \n -initialized 0\n -manageSequencer 0 \n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"sequenceEditorPanel\" (localizedPanelLabel(\"Camera Sequencer\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Camera Sequencer\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = sequenceEditorNameFromPanel($panelName);\n clipEditor -e \n -displayValues 0\n -snapTime \"none\" \n -snapValue \"none\" \n -initialized 0\n -manageSequencer 1 \n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"hyperGraphPanel\" (localizedPanelLabel(\"Hypergraph Hierarchy\")) `;\n" - + "\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Hypergraph Hierarchy\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = ($panelName+\"HyperGraphEd\");\n hyperGraph -e \n -graphLayoutStyle \"hierarchicalLayout\" \n -orientation \"horiz\" \n -mergeConnections 0\n -zoom 1\n -animateTransition 0\n -showRelationships 1\n -showShapes 0\n -showDeformers 0\n -showExpressions 0\n -showConstraints 0\n -showConnectionFromSelected 0\n -showConnectionToSelected 0\n -showConstraintLabels 0\n -showUnderworld 0\n -showInvisible 0\n -transitionFrames 1\n -opaqueContainers 0\n -freeform 0\n -imagePosition 0 0 \n -imageScale 1\n -imageEnabled 0\n -graphType \"DAG\" \n" - + " -heatMapDisplay 0\n -updateSelection 1\n -updateNodeAdded 1\n -useDrawOverrideColor 0\n -limitGraphTraversal -1\n -range 0 0 \n -iconSize \"smallIcons\" \n -showCachedConnections 0\n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"hyperShadePanel\" (localizedPanelLabel(\"Hypershade\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Hypershade\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"visorPanel\" (localizedPanelLabel(\"Visor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Visor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n" - + "\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"nodeEditorPanel\" (localizedPanelLabel(\"Node Editor\")) `;\n\tif ($nodeEditorPanelVisible || $nodeEditorWorkspaceControlOpen) {\n\t\tif (\"\" == $panelName) {\n\t\t\tif ($useSceneConfig) {\n\t\t\t\t$panelName = `scriptedPanel -unParent -type \"nodeEditorPanel\" -l (localizedPanelLabel(\"Node Editor\")) -mbv $menusOkayInPanels `;\n\n\t\t\t$editorName = ($panelName+\"NodeEditorEd\");\n nodeEditor -e \n -allAttributes 0\n -allNodes 0\n -autoSizeNodes 1\n -consistentNameSize 1\n -createNodeCommand \"nodeEdCreateNodeCommand\" \n -connectNodeOnCreation 0\n -connectOnDrop 0\n -copyConnectionsOnPaste 0\n -connectionStyle \"bezier\" \n -defaultPinnedState 0\n -additiveGraphingMode 0\n -settingsChangedCallback \"nodeEdSyncControls\" \n -traversalDepthLimit -1\n -keyPressCommand \"nodeEdKeyPressCommand\" \n" - + " -nodeTitleMode \"name\" \n -gridSnap 0\n -gridVisibility 1\n -crosshairOnEdgeDragging 0\n -popupMenuScript \"nodeEdBuildPanelMenus\" \n -showNamespace 1\n -showShapes 1\n -showSGShapes 0\n -showTransforms 1\n -useAssets 1\n -syncedSelection 1\n -extendToShapes 1\n -editorMode \"default\" \n -hasWatchpoint 0\n $editorName;\n\t\t\t}\n\t\t} else {\n\t\t\t$label = `panel -q -label $panelName`;\n\t\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Node Editor\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = ($panelName+\"NodeEditorEd\");\n nodeEditor -e \n -allAttributes 0\n -allNodes 0\n -autoSizeNodes 1\n -consistentNameSize 1\n -createNodeCommand \"nodeEdCreateNodeCommand\" \n -connectNodeOnCreation 0\n -connectOnDrop 0\n" - + " -copyConnectionsOnPaste 0\n -connectionStyle \"bezier\" \n -defaultPinnedState 0\n -additiveGraphingMode 0\n -settingsChangedCallback \"nodeEdSyncControls\" \n -traversalDepthLimit -1\n -keyPressCommand \"nodeEdKeyPressCommand\" \n -nodeTitleMode \"name\" \n -gridSnap 0\n -gridVisibility 1\n -crosshairOnEdgeDragging 0\n -popupMenuScript \"nodeEdBuildPanelMenus\" \n -showNamespace 1\n -showShapes 1\n -showSGShapes 0\n -showTransforms 1\n -useAssets 1\n -syncedSelection 1\n -extendToShapes 1\n -editorMode \"default\" \n -hasWatchpoint 0\n $editorName;\n\t\t\tif (!$useSceneConfig) {\n\t\t\t\tpanel -e -l $label $panelName;\n\t\t\t}\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"createNodePanel\" (localizedPanelLabel(\"Create Node\")) `;\n" - + "\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Create Node\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"polyTexturePlacementPanel\" (localizedPanelLabel(\"UV Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"UV Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"renderWindowPanel\" (localizedPanelLabel(\"Render View\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Render View\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"shapePanel\" (localizedPanelLabel(\"Shape Editor\")) `;\n" - + "\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tshapePanel -edit -l (localizedPanelLabel(\"Shape Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"posePanel\" (localizedPanelLabel(\"Pose Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tposePanel -edit -l (localizedPanelLabel(\"Pose Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"dynRelEdPanel\" (localizedPanelLabel(\"Dynamic Relationships\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Dynamic Relationships\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"relationshipPanel\" (localizedPanelLabel(\"Relationship Editor\")) `;\n" - + "\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Relationship Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"referenceEditorPanel\" (localizedPanelLabel(\"Reference Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Reference Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"componentEditorPanel\" (localizedPanelLabel(\"Component Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Component Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"dynPaintScriptedPanelType\" (localizedPanelLabel(\"Paint Effects\")) `;\n" - + "\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Paint Effects\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"scriptEditorPanel\" (localizedPanelLabel(\"Script Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Script Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"profilerPanel\" (localizedPanelLabel(\"Profiler Tool\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Profiler Tool\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"contentBrowserPanel\" (localizedPanelLabel(\"Content Browser\")) `;\n" - + "\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Content Browser\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"Stereo\" (localizedPanelLabel(\"Stereo\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Stereo\")) -mbv $menusOkayInPanels $panelName;\n{ string $editorName = ($panelName+\"Editor\");\n stereoCameraView -e \n -camera \"|persp\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"wireframe\" \n -activeOnly 0\n -ignorePanZoom 0\n -wireframeOnShaded 0\n -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n -useDefaultMaterial 0\n -bufferMode \"double\" \n -twoSidedLighting 1\n" - + " -backfaceCulling 0\n -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n -lineWidth 1\n -textureAnisotropic 0\n -textureHilight 1\n -textureSampling 2\n -textureDisplay \"modulate\" \n -textureMaxSize 16384\n -fogging 0\n -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n -depthOfFieldPreview 1\n -maxConstantTransparency 1\n -objectFilterShowInHUD 1\n -isFiltered 0\n -colorResolution 4 4 \n -bumpResolution 4 4 \n -textureCompression 0\n -transparencyAlgorithm \"frontAndBackCull\" \n -transpInShadows 0\n -cullingOverride \"none\" \n" - + " -lowQualityLighting 0\n -maximumNumHardwareLights 0\n -occlusionCulling 0\n -shadingModel 0\n -useBaseRenderer 0\n -useReducedRenderer 0\n -smallObjectCulling 0\n -smallObjectThreshold -1 \n -interactiveDisableShadows 0\n -interactiveBackFaceCull 0\n -sortTransparent 1\n -controllers 1\n -nurbsCurves 1\n -nurbsSurfaces 1\n -polymeshes 1\n -subdivSurfaces 1\n -planes 1\n -lights 1\n -cameras 1\n -controlVertices 1\n -hulls 1\n -grid 1\n -imagePlane 1\n -joints 1\n -ikHandles 1\n -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n -nCloths 1\n" - + " -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n -locators 1\n -manipulators 1\n -pluginShapes 1\n -dimensions 1\n -handles 1\n -pivots 1\n -textures 1\n -strokes 1\n -motionTrails 1\n -clipGhosts 1\n -greasePencils 1\n -shadows 0\n -captureSequenceNumber -1\n -width 0\n -height 0\n -sceneRenderFilter 0\n -displayMode \"centerEye\" \n -viewColor 0 0 0 1 \n -useCustomBackground 1\n $editorName;\n stereoCameraView -e -viewSelected 0 $editorName;\n stereoCameraView -e \n -pluginObjects \"gpuCacheDisplayFilter\" 1 \n $editorName; };\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\tif ($useSceneConfig) {\n string $configName = `getPanel -cwl (localizedPanelLabel(\"Current Layout\"))`;\n" - + " if (\"\" != $configName) {\n\t\t\tpanelConfiguration -edit -label (localizedPanelLabel(\"Current Layout\")) \n\t\t\t\t-userCreated false\n\t\t\t\t-defaultImage \"vacantCell.xP:/\"\n\t\t\t\t-image \"\"\n\t\t\t\t-sc false\n\t\t\t\t-configString \"global string $gMainPane; paneLayout -e -cn \\\"single\\\" -ps 1 100 100 $gMainPane;\"\n\t\t\t\t-removeAllPanels\n\t\t\t\t-ap false\n\t\t\t\t\t(localizedPanelLabel(\"Persp View\")) \n\t\t\t\t\t\"modelPanel\"\n" - + "\t\t\t\t\t\"$panelName = `modelPanel -unParent -l (localizedPanelLabel(\\\"Persp View\\\")) -mbv $menusOkayInPanels `;\\n$editorName = $panelName;\\nmodelEditor -e \\n -cam `findStartUpCamera persp` \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 16384\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -greasePencils 1\\n -shadows 0\\n -captureSequenceNumber -1\\n -width 1312\\n -height 732\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName;\\nmodelEditor -e \\n -pluginObjects \\\"gpuCacheDisplayFilter\\\" 1 \\n $editorName\"\n" - + "\t\t\t\t\t\"modelPanel -edit -l (localizedPanelLabel(\\\"Persp View\\\")) -mbv $menusOkayInPanels $panelName;\\n$editorName = $panelName;\\nmodelEditor -e \\n -cam `findStartUpCamera persp` \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 16384\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -greasePencils 1\\n -shadows 0\\n -captureSequenceNumber -1\\n -width 1312\\n -height 732\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName;\\nmodelEditor -e \\n -pluginObjects \\\"gpuCacheDisplayFilter\\\" 1 \\n $editorName\"\n" + + " -hulls 1\n -grid 1\n -imagePlane 1\n -joints 1\n -ikHandles 1\n -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n -nCloths 1\n -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n -locators 1\n -manipulators 1\n -pluginShapes 1\n -dimensions 1\n -handles 1\n -pivots 1\n -textures 1\n -strokes 1\n -motionTrails 1\n -clipGhosts 1\n -bluePencil 1\n -greasePencils 0\n -shadows 0\n -captureSequenceNumber -1\n -width 477\n -height 276\n -sceneRenderFilter 0\n $editorName;\n modelEditor -e -viewSelected 0 $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"modelPanel\" (localizedPanelLabel(\"Side View\")) `;\n" + + "\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tmodelPanel -edit -l (localizedPanelLabel(\"Side View\")) -mbv $menusOkayInPanels $panelName;\n\t\t$editorName = $panelName;\n modelEditor -e \n -camera \"|side\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"smoothShaded\" \n -activeOnly 0\n -ignorePanZoom 0\n -wireframeOnShaded 0\n -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n -useDefaultMaterial 0\n -bufferMode \"double\" \n -twoSidedLighting 0\n -backfaceCulling 0\n -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n -lineWidth 1\n -textureAnisotropic 0\n -textureHilight 1\n -textureSampling 2\n -textureDisplay \"modulate\" \n -textureMaxSize 32768\n -fogging 0\n" + + " -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n -depthOfFieldPreview 1\n -maxConstantTransparency 1\n -rendererName \"vp2Renderer\" \n -objectFilterShowInHUD 1\n -isFiltered 0\n -colorResolution 256 256 \n -bumpResolution 512 512 \n -textureCompression 0\n -transparencyAlgorithm \"frontAndBackCull\" \n -transpInShadows 0\n -cullingOverride \"none\" \n -lowQualityLighting 0\n -maximumNumHardwareLights 1\n -occlusionCulling 0\n -shadingModel 0\n -useBaseRenderer 0\n -useReducedRenderer 0\n -smallObjectCulling 0\n -smallObjectThreshold -1 \n -interactiveDisableShadows 0\n -interactiveBackFaceCull 0\n -sortTransparent 1\n -controllers 1\n -nurbsCurves 1\n" + + " -nurbsSurfaces 1\n -polymeshes 1\n -subdivSurfaces 1\n -planes 1\n -lights 1\n -cameras 1\n -controlVertices 1\n -hulls 1\n -grid 1\n -imagePlane 1\n -joints 1\n -ikHandles 1\n -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n -nCloths 1\n -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n -locators 1\n -manipulators 1\n -pluginShapes 1\n -dimensions 1\n -handles 1\n -pivots 1\n -textures 1\n -strokes 1\n -motionTrails 1\n -clipGhosts 1\n -bluePencil 1\n -greasePencils 0\n -shadows 0\n -captureSequenceNumber -1\n -width 476\n -height 276\n -sceneRenderFilter 0\n $editorName;\n" + + " modelEditor -e -viewSelected 0 $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"modelPanel\" (localizedPanelLabel(\"Front View\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tmodelPanel -edit -l (localizedPanelLabel(\"Front View\")) -mbv $menusOkayInPanels $panelName;\n\t\t$editorName = $panelName;\n modelEditor -e \n -camera \"|front\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"smoothShaded\" \n -activeOnly 0\n -ignorePanZoom 0\n -wireframeOnShaded 0\n -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n -useDefaultMaterial 0\n -bufferMode \"double\" \n -twoSidedLighting 0\n -backfaceCulling 0\n -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n" + + " -lineWidth 1\n -textureAnisotropic 0\n -textureHilight 1\n -textureSampling 2\n -textureDisplay \"modulate\" \n -textureMaxSize 32768\n -fogging 0\n -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n -depthOfFieldPreview 1\n -maxConstantTransparency 1\n -rendererName \"vp2Renderer\" \n -objectFilterShowInHUD 1\n -isFiltered 0\n -colorResolution 256 256 \n -bumpResolution 512 512 \n -textureCompression 0\n -transparencyAlgorithm \"frontAndBackCull\" \n -transpInShadows 0\n -cullingOverride \"none\" \n -lowQualityLighting 0\n -maximumNumHardwareLights 1\n -occlusionCulling 0\n -shadingModel 0\n -useBaseRenderer 0\n -useReducedRenderer 0\n -smallObjectCulling 0\n" + + " -smallObjectThreshold -1 \n -interactiveDisableShadows 0\n -interactiveBackFaceCull 0\n -sortTransparent 1\n -controllers 1\n -nurbsCurves 1\n -nurbsSurfaces 1\n -polymeshes 1\n -subdivSurfaces 1\n -planes 1\n -lights 1\n -cameras 1\n -controlVertices 1\n -hulls 1\n -grid 1\n -imagePlane 1\n -joints 1\n -ikHandles 1\n -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n -nCloths 1\n -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n -locators 1\n -manipulators 1\n -pluginShapes 1\n -dimensions 1\n -handles 1\n -pivots 1\n -textures 1\n -strokes 1\n -motionTrails 1\n -clipGhosts 1\n -bluePencil 1\n" + + " -greasePencils 0\n -shadows 0\n -captureSequenceNumber -1\n -width 477\n -height 276\n -sceneRenderFilter 0\n $editorName;\n modelEditor -e -viewSelected 0 $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"modelPanel\" (localizedPanelLabel(\"Persp View\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tmodelPanel -edit -l (localizedPanelLabel(\"Persp View\")) -mbv $menusOkayInPanels $panelName;\n\t\t$editorName = $panelName;\n modelEditor -e \n -camera \"|persp\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"smoothShaded\" \n -activeOnly 0\n -ignorePanZoom 0\n -wireframeOnShaded 0\n -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n -useDefaultMaterial 0\n -bufferMode \"double\" \n" + + " -twoSidedLighting 0\n -backfaceCulling 0\n -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n -lineWidth 1\n -textureAnisotropic 0\n -textureHilight 1\n -textureSampling 2\n -textureDisplay \"modulate\" \n -textureMaxSize 32768\n -fogging 0\n -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n -depthOfFieldPreview 1\n -maxConstantTransparency 1\n -rendererName \"vp2Renderer\" \n -objectFilterShowInHUD 1\n -isFiltered 0\n -colorResolution 256 256 \n -bumpResolution 512 512 \n -textureCompression 0\n -transparencyAlgorithm \"frontAndBackCull\" \n -transpInShadows 0\n -cullingOverride \"none\" \n -lowQualityLighting 0\n" + + " -maximumNumHardwareLights 1\n -occlusionCulling 0\n -shadingModel 0\n -useBaseRenderer 0\n -useReducedRenderer 0\n -smallObjectCulling 0\n -smallObjectThreshold -1 \n -interactiveDisableShadows 0\n -interactiveBackFaceCull 0\n -sortTransparent 1\n -controllers 1\n -nurbsCurves 1\n -nurbsSurfaces 1\n -polymeshes 1\n -subdivSurfaces 1\n -planes 1\n -lights 1\n -cameras 1\n -controlVertices 1\n -hulls 1\n -grid 1\n -imagePlane 1\n -joints 1\n -ikHandles 1\n -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n -nCloths 1\n -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n -locators 1\n -manipulators 1\n -pluginShapes 1\n" + + " -dimensions 1\n -handles 1\n -pivots 1\n -textures 1\n -strokes 1\n -motionTrails 1\n -clipGhosts 1\n -bluePencil 1\n -greasePencils 0\n -shadows 0\n -captureSequenceNumber -1\n -width 476\n -height 276\n -sceneRenderFilter 0\n $editorName;\n modelEditor -e -viewSelected 0 $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"outlinerPanel\" (localizedPanelLabel(\"ToggledOutliner\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\toutlinerPanel -edit -l (localizedPanelLabel(\"ToggledOutliner\")) -mbv $menusOkayInPanels $panelName;\n\t\t$editorName = $panelName;\n outlinerEditor -e \n -docTag \"isolOutln_fromSeln\" \n -showShapes 0\n -showAssignedMaterials 0\n -showTimeEditor 1\n -showReferenceNodes 1\n -showReferenceMembers 1\n" + + " -showAttributes 0\n -showConnected 0\n -showAnimCurvesOnly 0\n -showMuteInfo 0\n -organizeByLayer 1\n -organizeByClip 1\n -showAnimLayerWeight 1\n -autoExpandLayers 1\n -autoExpand 0\n -showDagOnly 1\n -showAssets 1\n -showContainedOnly 1\n -showPublishedAsConnected 0\n -showParentContainers 0\n -showContainerContents 1\n -ignoreDagHierarchy 0\n -expandConnections 0\n -showUpstreamCurves 1\n -showUnitlessCurves 1\n -showCompounds 1\n -showLeafs 1\n -showNumericAttrsOnly 0\n -highlightActive 1\n -autoSelectNewObjects 0\n -doNotSelectNewObjects 0\n -dropIsParent 1\n -transmitFilters 0\n -setFilter \"defaultSetFilter\" \n -showSetMembers 1\n -allowMultiSelection 1\n -alwaysToggleSelect 0\n -directSelect 0\n" + + " -isSet 0\n -isSetMember 0\n -showUfeItems 1\n -displayMode \"DAG\" \n -expandObjects 0\n -setsIgnoreFilters 1\n -containersIgnoreFilters 0\n -editAttrName 0\n -showAttrValues 0\n -highlightSecondary 0\n -showUVAttrsOnly 0\n -showTextureNodesOnly 0\n -attrAlphaOrder \"default\" \n -animLayerFilterOptions \"allAffecting\" \n -sortOrder \"none\" \n -longNames 0\n -niceNames 1\n -selectCommand \"print(\\\"\\\")\" \n -showNamespace 1\n -showPinIcons 0\n -mapMotionTrails 0\n -ignoreHiddenAttribute 0\n -ignoreOutlinerColor 0\n -renderFilterVisible 0\n -renderFilterIndex 0\n -selectionOrder \"chronological\" \n -expandAttribute 0\n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"outlinerPanel\" (localizedPanelLabel(\"Outliner\")) `;\n" + + "\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\toutlinerPanel -edit -l (localizedPanelLabel(\"Outliner\")) -mbv $menusOkayInPanels $panelName;\n\t\t$editorName = $panelName;\n outlinerEditor -e \n -showShapes 0\n -showAssignedMaterials 0\n -showTimeEditor 1\n -showReferenceNodes 0\n -showReferenceMembers 0\n -showAttributes 0\n -showConnected 0\n -showAnimCurvesOnly 0\n -showMuteInfo 0\n -organizeByLayer 1\n -organizeByClip 1\n -showAnimLayerWeight 1\n -autoExpandLayers 1\n -autoExpand 0\n -showDagOnly 1\n -showAssets 1\n -showContainedOnly 1\n -showPublishedAsConnected 0\n -showParentContainers 0\n -showContainerContents 1\n -ignoreDagHierarchy 0\n -expandConnections 0\n -showUpstreamCurves 1\n -showUnitlessCurves 1\n -showCompounds 1\n -showLeafs 1\n" + + " -showNumericAttrsOnly 0\n -highlightActive 1\n -autoSelectNewObjects 0\n -doNotSelectNewObjects 0\n -dropIsParent 1\n -transmitFilters 0\n -setFilter \"defaultSetFilter\" \n -showSetMembers 1\n -allowMultiSelection 1\n -alwaysToggleSelect 0\n -directSelect 0\n -showUfeItems 1\n -displayMode \"DAG\" \n -expandObjects 0\n -setsIgnoreFilters 1\n -containersIgnoreFilters 0\n -editAttrName 0\n -showAttrValues 0\n -highlightSecondary 0\n -showUVAttrsOnly 0\n -showTextureNodesOnly 0\n -attrAlphaOrder \"default\" \n -animLayerFilterOptions \"allAffecting\" \n -sortOrder \"none\" \n -longNames 0\n -niceNames 1\n -showNamespace 1\n -showPinIcons 0\n -mapMotionTrails 0\n -ignoreHiddenAttribute 0\n -ignoreOutlinerColor 0\n -renderFilterVisible 0\n" + + " $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"graphEditor\" (localizedPanelLabel(\"Graph Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Graph Editor\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = ($panelName+\"OutlineEd\");\n outlinerEditor -e \n -showShapes 1\n -showAssignedMaterials 0\n -showTimeEditor 1\n -showReferenceNodes 0\n -showReferenceMembers 0\n -showAttributes 1\n -showConnected 1\n -showAnimCurvesOnly 1\n -showMuteInfo 0\n -organizeByLayer 1\n -organizeByClip 1\n -showAnimLayerWeight 1\n -autoExpandLayers 1\n -autoExpand 1\n -showDagOnly 0\n -showAssets 1\n -showContainedOnly 0\n" + + " -showPublishedAsConnected 0\n -showParentContainers 0\n -showContainerContents 0\n -ignoreDagHierarchy 0\n -expandConnections 1\n -showUpstreamCurves 1\n -showUnitlessCurves 1\n -showCompounds 0\n -showLeafs 1\n -showNumericAttrsOnly 1\n -highlightActive 0\n -autoSelectNewObjects 1\n -doNotSelectNewObjects 0\n -dropIsParent 1\n -transmitFilters 1\n -setFilter \"0\" \n -showSetMembers 0\n -allowMultiSelection 1\n -alwaysToggleSelect 0\n -directSelect 0\n -showUfeItems 1\n -displayMode \"DAG\" \n -expandObjects 0\n -setsIgnoreFilters 1\n -containersIgnoreFilters 0\n -editAttrName 0\n -showAttrValues 0\n -highlightSecondary 0\n -showUVAttrsOnly 0\n" + + " -showTextureNodesOnly 0\n -attrAlphaOrder \"default\" \n -animLayerFilterOptions \"allAffecting\" \n -sortOrder \"none\" \n -longNames 0\n -niceNames 1\n -showNamespace 1\n -showPinIcons 1\n -mapMotionTrails 1\n -ignoreHiddenAttribute 0\n -ignoreOutlinerColor 0\n -renderFilterVisible 0\n $editorName;\n\n\t\t\t$editorName = ($panelName+\"GraphEd\");\n animCurveEditor -e \n -displayValues 0\n -snapTime \"integer\" \n -snapValue \"none\" \n -showPlayRangeShades \"on\" \n -lockPlayRangeShades \"off\" \n -smoothness \"fine\" \n -resultSamples 1.041667\n -resultScreenSamples 0\n -resultUpdate \"delayed\" \n -showUpstreamCurves 1\n -keyMinScale 1\n -stackedCurvesMin -1\n -stackedCurvesMax 1\n" + + " -stackedCurvesSpace 0.2\n -preSelectionHighlight 0\n -constrainDrag 0\n -valueLinesToggle 1\n -highlightAffectedCurves 0\n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"dopeSheetPanel\" (localizedPanelLabel(\"Dope Sheet\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Dope Sheet\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = ($panelName+\"OutlineEd\");\n outlinerEditor -e \n -showShapes 1\n -showAssignedMaterials 0\n -showTimeEditor 1\n -showReferenceNodes 0\n -showReferenceMembers 0\n -showAttributes 1\n -showConnected 1\n -showAnimCurvesOnly 1\n -showMuteInfo 0\n -organizeByLayer 1\n -organizeByClip 1\n" + + " -showAnimLayerWeight 1\n -autoExpandLayers 1\n -autoExpand 0\n -showDagOnly 0\n -showAssets 1\n -showContainedOnly 0\n -showPublishedAsConnected 0\n -showParentContainers 0\n -showContainerContents 0\n -ignoreDagHierarchy 0\n -expandConnections 1\n -showUpstreamCurves 1\n -showUnitlessCurves 0\n -showCompounds 1\n -showLeafs 1\n -showNumericAttrsOnly 1\n -highlightActive 0\n -autoSelectNewObjects 0\n -doNotSelectNewObjects 1\n -dropIsParent 1\n -transmitFilters 0\n -setFilter \"0\" \n -showSetMembers 0\n -allowMultiSelection 1\n -alwaysToggleSelect 0\n -directSelect 0\n -showUfeItems 1\n -displayMode \"DAG\" \n -expandObjects 0\n" + + " -setsIgnoreFilters 1\n -containersIgnoreFilters 0\n -editAttrName 0\n -showAttrValues 0\n -highlightSecondary 0\n -showUVAttrsOnly 0\n -showTextureNodesOnly 0\n -attrAlphaOrder \"default\" \n -animLayerFilterOptions \"allAffecting\" \n -sortOrder \"none\" \n -longNames 0\n -niceNames 1\n -showNamespace 1\n -showPinIcons 0\n -mapMotionTrails 1\n -ignoreHiddenAttribute 0\n -ignoreOutlinerColor 0\n -renderFilterVisible 0\n $editorName;\n\n\t\t\t$editorName = ($panelName+\"DopeSheetEd\");\n dopeSheetEditor -e \n -displayValues 0\n -snapTime \"integer\" \n -snapValue \"none\" \n -outliner \"dopeSheetPanel1OutlineEd\" \n -showSummary 1\n -showScene 0\n -hierarchyBelow 0\n" + + " -showTicks 1\n -selectionWindow 0 0 0 0 \n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"timeEditorPanel\" (localizedPanelLabel(\"Time Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Time Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"clipEditorPanel\" (localizedPanelLabel(\"Trax Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Trax Editor\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = clipEditorNameFromPanel($panelName);\n clipEditor -e \n -displayValues 0\n -snapTime \"none\" \n -snapValue \"none\" \n -initialized 0\n -manageSequencer 0 \n" + + " $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"sequenceEditorPanel\" (localizedPanelLabel(\"Camera Sequencer\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Camera Sequencer\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = sequenceEditorNameFromPanel($panelName);\n clipEditor -e \n -displayValues 0\n -snapTime \"none\" \n -snapValue \"none\" \n -initialized 0\n -manageSequencer 1 \n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"hyperGraphPanel\" (localizedPanelLabel(\"Hypergraph Hierarchy\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Hypergraph Hierarchy\")) -mbv $menusOkayInPanels $panelName;\n" + + "\n\t\t\t$editorName = ($panelName+\"HyperGraphEd\");\n hyperGraph -e \n -graphLayoutStyle \"hierarchicalLayout\" \n -orientation \"horiz\" \n -mergeConnections 0\n -zoom 1\n -animateTransition 0\n -showRelationships 1\n -showShapes 0\n -showDeformers 0\n -showExpressions 0\n -showConstraints 0\n -showConnectionFromSelected 0\n -showConnectionToSelected 0\n -showConstraintLabels 0\n -showUnderworld 0\n -showInvisible 0\n -transitionFrames 1\n -opaqueContainers 0\n -freeform 0\n -imagePosition 0 0 \n -imageScale 1\n -imageEnabled 0\n -graphType \"DAG\" \n -heatMapDisplay 0\n -updateSelection 1\n -updateNodeAdded 1\n -useDrawOverrideColor 0\n -limitGraphTraversal -1\n" + + " -range 0 0 \n -iconSize \"smallIcons\" \n -showCachedConnections 0\n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"hyperShadePanel\" (localizedPanelLabel(\"Hypershade\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Hypershade\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"visorPanel\" (localizedPanelLabel(\"Visor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Visor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"nodeEditorPanel\" (localizedPanelLabel(\"Node Editor\")) `;\n\tif ($nodeEditorPanelVisible || $nodeEditorWorkspaceControlOpen) {\n" + + "\t\tif (\"\" == $panelName) {\n\t\t\tif ($useSceneConfig) {\n\t\t\t\t$panelName = `scriptedPanel -unParent -type \"nodeEditorPanel\" -l (localizedPanelLabel(\"Node Editor\")) -mbv $menusOkayInPanels `;\n\n\t\t\t$editorName = ($panelName+\"NodeEditorEd\");\n nodeEditor -e \n -allAttributes 0\n -allNodes 0\n -autoSizeNodes 1\n -consistentNameSize 1\n -createNodeCommand \"nodeEdCreateNodeCommand\" \n -connectNodeOnCreation 0\n -connectOnDrop 0\n -copyConnectionsOnPaste 0\n -connectionStyle \"bezier\" \n -defaultPinnedState 0\n -additiveGraphingMode 1\n -connectedGraphingMode 1\n -settingsChangedCallback \"nodeEdSyncControls\" \n -traversalDepthLimit -1\n -keyPressCommand \"nodeEdKeyPressCommand\" \n -nodeTitleMode \"name\" \n -gridSnap 0\n -gridVisibility 1\n -crosshairOnEdgeDragging 0\n" + + " -popupMenuScript \"nodeEdBuildPanelMenus\" \n -showNamespace 1\n -showShapes 1\n -showSGShapes 0\n -showTransforms 1\n -useAssets 1\n -syncedSelection 1\n -extendToShapes 1\n -showUnitConversions 0\n -editorMode \"default\" \n -hasWatchpoint 0\n $editorName;\n\t\t\t}\n\t\t} else {\n\t\t\t$label = `panel -q -label $panelName`;\n\t\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Node Editor\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = ($panelName+\"NodeEditorEd\");\n nodeEditor -e \n -allAttributes 0\n -allNodes 0\n -autoSizeNodes 1\n -consistentNameSize 1\n -createNodeCommand \"nodeEdCreateNodeCommand\" \n -connectNodeOnCreation 0\n -connectOnDrop 0\n -copyConnectionsOnPaste 0\n -connectionStyle \"bezier\" \n -defaultPinnedState 0\n" + + " -additiveGraphingMode 1\n -connectedGraphingMode 1\n -settingsChangedCallback \"nodeEdSyncControls\" \n -traversalDepthLimit -1\n -keyPressCommand \"nodeEdKeyPressCommand\" \n -nodeTitleMode \"name\" \n -gridSnap 0\n -gridVisibility 1\n -crosshairOnEdgeDragging 0\n -popupMenuScript \"nodeEdBuildPanelMenus\" \n -showNamespace 1\n -showShapes 1\n -showSGShapes 0\n -showTransforms 1\n -useAssets 1\n -syncedSelection 1\n -extendToShapes 1\n -showUnitConversions 0\n -editorMode \"default\" \n -hasWatchpoint 0\n $editorName;\n\t\t\tif (!$useSceneConfig) {\n\t\t\t\tpanel -e -l $label $panelName;\n\t\t\t}\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"createNodePanel\" (localizedPanelLabel(\"Create Node\")) `;\n\tif (\"\" != $panelName) {\n" + + "\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Create Node\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"polyTexturePlacementPanel\" (localizedPanelLabel(\"UV Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"UV Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"renderWindowPanel\" (localizedPanelLabel(\"Render View\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Render View\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"shapePanel\" (localizedPanelLabel(\"Shape Editor\")) `;\n\tif (\"\" != $panelName) {\n" + + "\t\t$label = `panel -q -label $panelName`;\n\t\tshapePanel -edit -l (localizedPanelLabel(\"Shape Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"posePanel\" (localizedPanelLabel(\"Pose Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tposePanel -edit -l (localizedPanelLabel(\"Pose Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"dynRelEdPanel\" (localizedPanelLabel(\"Dynamic Relationships\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Dynamic Relationships\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"relationshipPanel\" (localizedPanelLabel(\"Relationship Editor\")) `;\n" + + "\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Relationship Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"referenceEditorPanel\" (localizedPanelLabel(\"Reference Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Reference Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"dynPaintScriptedPanelType\" (localizedPanelLabel(\"Paint Effects\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Paint Effects\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"scriptEditorPanel\" (localizedPanelLabel(\"Script Editor\")) `;\n" + + "\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Script Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"profilerPanel\" (localizedPanelLabel(\"Profiler Tool\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Profiler Tool\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"contentBrowserPanel\" (localizedPanelLabel(\"Content Browser\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Content Browser\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"Stereo\" (localizedPanelLabel(\"Stereo\")) `;\n" + + "\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Stereo\")) -mbv $menusOkayInPanels $panelName;\n{ string $editorName = ($panelName+\"Editor\");\n stereoCameraView -e \n -camera \"|persp\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"wireframe\" \n -activeOnly 0\n -ignorePanZoom 0\n -wireframeOnShaded 0\n -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n -useDefaultMaterial 0\n -bufferMode \"double\" \n -twoSidedLighting 1\n -backfaceCulling 0\n -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n -lineWidth 1\n -textureAnisotropic 0\n -textureHilight 1\n -textureSampling 2\n" + + " -textureDisplay \"modulate\" \n -textureMaxSize 32768\n -fogging 0\n -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n -depthOfFieldPreview 1\n -maxConstantTransparency 1\n -objectFilterShowInHUD 1\n -isFiltered 0\n -colorResolution 4 4 \n -bumpResolution 4 4 \n -textureCompression 0\n -transparencyAlgorithm \"frontAndBackCull\" \n -transpInShadows 0\n -cullingOverride \"none\" \n -lowQualityLighting 0\n -maximumNumHardwareLights 0\n -occlusionCulling 0\n -shadingModel 0\n -useBaseRenderer 0\n -useReducedRenderer 0\n -smallObjectCulling 0\n -smallObjectThreshold -1 \n -interactiveDisableShadows 0\n" + + " -interactiveBackFaceCull 0\n -sortTransparent 1\n -controllers 1\n -nurbsCurves 1\n -nurbsSurfaces 1\n -polymeshes 1\n -subdivSurfaces 1\n -planes 1\n -lights 1\n -cameras 1\n -controlVertices 1\n -hulls 1\n -grid 1\n -imagePlane 1\n -joints 1\n -ikHandles 1\n -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n -nCloths 1\n -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n -locators 1\n -manipulators 1\n -pluginShapes 1\n -dimensions 1\n -handles 1\n -pivots 1\n -textures 1\n -strokes 1\n -motionTrails 1\n" + + " -clipGhosts 1\n -bluePencil 1\n -greasePencils 0\n -shadows 0\n -captureSequenceNumber -1\n -width 0\n -height 0\n -sceneRenderFilter 0\n -displayMode \"centerEye\" \n -viewColor 0 0 0 1 \n -useCustomBackground 1\n $editorName;\n stereoCameraView -e -viewSelected 0 $editorName; };\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\tif ($useSceneConfig) {\n string $configName = `getPanel -cwl (localizedPanelLabel(\"Current Layout\"))`;\n if (\"\" != $configName) {\n\t\t\tpanelConfiguration -edit -label (localizedPanelLabel(\"Current Layout\")) \n\t\t\t\t-userCreated false\n\t\t\t\t-defaultImage \"vacantCell.xP:/\"\n\t\t\t\t-image \"\"\n\t\t\t\t-sc false\n\t\t\t\t-configString \"global string $gMainPane; paneLayout -e -cn \\\"quad\\\" -ps 1 50 50 -ps 2 50 50 -ps 3 50 50 -ps 4 50 50 $gMainPane;\"\n\t\t\t\t-removeAllPanels\n\t\t\t\t-ap false\n\t\t\t\t\t(localizedPanelLabel(\"Top View\")) \n" + + "\t\t\t\t\t\"modelPanel\"\n" + + "\t\t\t\t\t\"$panelName = `modelPanel -unParent -l (localizedPanelLabel(\\\"Top View\\\")) -mbv $menusOkayInPanels `;\\n$editorName = $panelName;\\nmodelEditor -e \\n -camera \\\"|top\\\" \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 32768\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -bluePencil 1\\n -greasePencils 0\\n -shadows 0\\n -captureSequenceNumber -1\\n -width 477\\n -height 276\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName\"\n" + + "\t\t\t\t\t\"modelPanel -edit -l (localizedPanelLabel(\\\"Top View\\\")) -mbv $menusOkayInPanels $panelName;\\n$editorName = $panelName;\\nmodelEditor -e \\n -camera \\\"|top\\\" \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 32768\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -bluePencil 1\\n -greasePencils 0\\n -shadows 0\\n -captureSequenceNumber -1\\n -width 477\\n -height 276\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName\"\n" + + "\t\t\t\t-ap false\n\t\t\t\t\t(localizedPanelLabel(\"Persp View\")) \n\t\t\t\t\t\"modelPanel\"\n" + + "\t\t\t\t\t\"$panelName = `modelPanel -unParent -l (localizedPanelLabel(\\\"Persp View\\\")) -mbv $menusOkayInPanels `;\\n$editorName = $panelName;\\nmodelEditor -e \\n -cam `findStartUpCamera persp` \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 32768\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -bluePencil 1\\n -greasePencils 0\\n -shadows 0\\n -captureSequenceNumber -1\\n -width 476\\n -height 276\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName\"\n" + + "\t\t\t\t\t\"modelPanel -edit -l (localizedPanelLabel(\\\"Persp View\\\")) -mbv $menusOkayInPanels $panelName;\\n$editorName = $panelName;\\nmodelEditor -e \\n -cam `findStartUpCamera persp` \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 32768\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -bluePencil 1\\n -greasePencils 0\\n -shadows 0\\n -captureSequenceNumber -1\\n -width 476\\n -height 276\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName\"\n" + + "\t\t\t\t-ap false\n\t\t\t\t\t(localizedPanelLabel(\"Side View\")) \n\t\t\t\t\t\"modelPanel\"\n" + + "\t\t\t\t\t\"$panelName = `modelPanel -unParent -l (localizedPanelLabel(\\\"Side View\\\")) -mbv $menusOkayInPanels `;\\n$editorName = $panelName;\\nmodelEditor -e \\n -camera \\\"|side\\\" \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 32768\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -bluePencil 1\\n -greasePencils 0\\n -shadows 0\\n -captureSequenceNumber -1\\n -width 476\\n -height 276\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName\"\n" + + "\t\t\t\t\t\"modelPanel -edit -l (localizedPanelLabel(\\\"Side View\\\")) -mbv $menusOkayInPanels $panelName;\\n$editorName = $panelName;\\nmodelEditor -e \\n -camera \\\"|side\\\" \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 32768\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -bluePencil 1\\n -greasePencils 0\\n -shadows 0\\n -captureSequenceNumber -1\\n -width 476\\n -height 276\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName\"\n" + + "\t\t\t\t-ap false\n\t\t\t\t\t(localizedPanelLabel(\"Front View\")) \n\t\t\t\t\t\"modelPanel\"\n" + + "\t\t\t\t\t\"$panelName = `modelPanel -unParent -l (localizedPanelLabel(\\\"Front View\\\")) -mbv $menusOkayInPanels `;\\n$editorName = $panelName;\\nmodelEditor -e \\n -cam `findStartUpCamera front` \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 32768\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -bluePencil 1\\n -greasePencils 0\\n -shadows 0\\n -captureSequenceNumber -1\\n -width 477\\n -height 276\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName\"\n" + + "\t\t\t\t\t\"modelPanel -edit -l (localizedPanelLabel(\\\"Front View\\\")) -mbv $menusOkayInPanels $panelName;\\n$editorName = $panelName;\\nmodelEditor -e \\n -cam `findStartUpCamera front` \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 32768\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -bluePencil 1\\n -greasePencils 0\\n -shadows 0\\n -captureSequenceNumber -1\\n -width 477\\n -height 276\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName\"\n" + "\t\t\t\t$configName;\n\n setNamedPanelLayout (localizedPanelLabel(\"Current Layout\"));\n }\n\n panelHistory -e -clear mainPanelHistory;\n sceneUIReplacement -clear;\n\t}\n\n\ngrid -spacing 5 -size 12 -divisions 5 -displayAxes yes -displayGridLines yes -displayDivisionLines yes -displayPerspectiveLabels no -displayOrthographicLabels no -displayAxesBold yes -perspectiveLabelPosition axis -orthographicLabelPosition edge;\nviewManip -drawCompass 0 -compassAngle 0 -frontParameters \"\" -homeParameters \"\" -selectionLockParameters \"\";\n}\n"); setAttr ".st" 3; createNode script -n "sceneConfigurationScriptNode"; @@ -273,6 +285,7 @@ createNode aiAOVDriver -s -n "defaultArnoldDisplayDriver"; createNode objectSet -n "workfileMain"; rename -uid "3C9B5D6F-4579-8E3B-5B7D-4C88865A1C68"; addAttr -ci true -sn "cbId" -ln "cbId" -dt "string"; + addAttr -ci true -sn "instance_id" -ln "instance_id" -dt "string"; addAttr -ci true -sn "id" -ln "id" -dt "string"; addAttr -ci true -sn "family" -ln "family" -dt "string"; addAttr -ci true -sn "subset" -ln "subset" -dt "string"; @@ -281,13 +294,14 @@ createNode objectSet -n "workfileMain"; addAttr -ci true -sn "variant" -ln "variant" -dt "string"; addAttr -ci true -sn "asset" -ln "asset" -dt "string"; addAttr -ci true -sn "task" -ln "task" -dt "string"; - addAttr -ci true -sn "instance_id" -ln "instance_id" -dt "string"; addAttr -ci true -sn "publish_attributes" -ln "publish_attributes" -dt "string"; - addAttr -ci true -sn "__creator_attributes_keys" -ln "__creator_attributes_keys" + addAttr -ci true -sn "creator_attributes" -ln "creator_attributes" -dt "string"; + addAttr -ci true -sn "__creator_attributes_keys" -ln "__creator_attributes_keys" -dt "string"; setAttr ".ihi" 0; setAttr ".hio" yes; setAttr ".cbId" -type "string" "60df31e2be2b48bd3695c056:30d256dac64c"; + setAttr ".instance_id" -type "string" "911dc92a-ad29-41e5-bbf9-733d56174fb9"; setAttr ".id" -type "string" "pyblish.avalon.instance"; setAttr ".family" -type "string" "workfile"; setAttr ".subset" -type "string" "workfileTest_task"; @@ -296,16 +310,9 @@ createNode objectSet -n "workfileMain"; setAttr ".variant" -type "string" "Main"; setAttr ".asset" -type "string" "test_asset"; setAttr ".task" -type "string" "test_task"; - setAttr ".instance_id" -type "string" "911dc92a-ad29-41e5-bbf9-733d56174fb9"; - setAttr ".publish_attributes" -type "string" "{\"ExtractImportReference\": {\"active\": false}}"; + setAttr ".publish_attributes" -type "string" "{\"ValidateInstanceInContext\": {\"active\": true}, \"ExtractImportReference\": {\"active\": false}}"; + setAttr ".creator_attributes" -type "string" "{}"; setAttr ".__creator_attributes_keys" -type "string" ""; -createNode objectSet -n "renderingMain"; - rename -uid "8A999C2F-4922-B15D-8D5C-45A16465B69F"; - addAttr -ci true -sn "pre_creator_identifier" -ln "pre_creator_identifier" -dt "string"; - addAttr -ci true -sn "cbId" -ln "cbId" -dt "string"; - setAttr ".ihi" 0; - setAttr ".pre_creator_identifier" -type "string" "io.openpype.creators.maya.renderlayer"; - setAttr ".cbId" -type "string" "60df31e2be2b48bd3695c056:042447475732"; createNode renderSetupLayer -n "Main"; rename -uid "2202E438-4CEF-F64E-737C-F48C65E31126"; addAttr -ci true -sn "es" -ln "expandedState" -min 0 -max 1 -at "bool"; @@ -320,55 +327,6 @@ createNode collection -n "defaultCollection"; createNode simpleSelector -n "defaultCollectionSelector"; rename -uid "7CA2F6D8-483C-B020-BC03-EF9563A52163"; setAttr ".pat" -type "string" "*"; -createNode objectSet -n "_renderingMain:Main"; - rename -uid "1EEC3A3B-49CF-0C79-5340-39805174FB8A"; - addAttr -s false -ci true -sn "renderlayer" -ln "renderlayer" -at "message"; - addAttr -ci true -sn "id" -ln "id" -dt "string"; - addAttr -ci true -sn "family" -ln "family" -dt "string"; - addAttr -ci true -sn "subset" -ln "subset" -dt "string"; - addAttr -ci true -sn "active" -ln "active" -min 0 -max 1 -at "bool"; - addAttr -ci true -sn "creator_identifier" -ln "creator_identifier" -dt "string"; - addAttr -ci true -sn "variant" -ln "variant" -dt "string"; - addAttr -ci true -sn "asset" -ln "asset" -dt "string"; - addAttr -ci true -sn "task" -ln "task" -dt "string"; - addAttr -ci true -sn "instance_id" -ln "instance_id" -dt "string"; - addAttr -ci true -sn "review" -ln "review" -min 0 -max 1 -at "bool"; - addAttr -ci true -sn "extendFrames" -ln "extendFrames" -min 0 -max 1 -at "bool"; - addAttr -ci true -sn "overrideExistingFrame" -ln "overrideExistingFrame" -min 0 - -max 1 -at "bool"; - addAttr -ci true -sn "tileRendering" -ln "tileRendering" -min 0 -max 1 -at "bool"; - addAttr -ci true -sn "tilesX" -ln "tilesX" -at "long"; - addAttr -ci true -sn "tilesY" -ln "tilesY" -at "long"; - addAttr -ci true -sn "convertToScanline" -ln "convertToScanline" -min 0 -max 1 -at "bool"; - addAttr -ci true -sn "useReferencedAovs" -ln "useReferencedAovs" -min 0 -max 1 -at "bool"; - addAttr -ci true -sn "renderSetupIncludeLights" -ln "renderSetupIncludeLights" -min - 0 -max 1 -at "bool"; - addAttr -ci true -sn "publish_attributes" -ln "publish_attributes" -dt "string"; - addAttr -ci true -sn "__creator_attributes_keys" -ln "__creator_attributes_keys" - -dt "string"; - addAttr -ci true -sn "cbId" -ln "cbId" -dt "string"; - setAttr ".ihi" 0; - setAttr ".id" -type "string" "pyblish.avalon.instance"; - setAttr ".family" -type "string" "renderlayer"; - setAttr ".subset" -type "string" "renderMain"; - setAttr -cb on ".active" yes; - setAttr ".creator_identifier" -type "string" "io.openpype.creators.maya.renderlayer"; - setAttr ".variant" -type "string" "Main"; - setAttr ".asset" -type "string" "test_asset"; - setAttr ".task" -type "string" "test_task"; - setAttr ".instance_id" -type "string" "8a9cfb85-9602-4e5e-a4bc-27a2986bae7f"; - setAttr -cb on ".review" yes; - setAttr -cb on ".extendFrames"; - setAttr -cb on ".overrideExistingFrame" yes; - setAttr -cb on ".tileRendering"; - setAttr -cb on ".tilesX" 2; - setAttr -cb on ".tilesY" 2; - setAttr -cb on ".convertToScanline"; - setAttr -cb on ".useReferencedAovs"; - setAttr -cb on ".renderSetupIncludeLights" yes; - setAttr ".publish_attributes" -type "string" "{\"CollectDeadlinePools\": {\"primaryPool\": \"\", \"secondaryPool\": \"\"}, \"ValidateDeadlinePools\": {\"active\": true}, \"ValidateFrameRange\": {\"active\": true}, \"ExtractImportReference\": {\"active\": false}, \"MayaSubmitDeadline\": {\"priority\": 50, \"chunkSize\": 1, \"machineList\": \"\", \"whitelist\": false, \"tile_priority\": 50, \"strict_error_checking\": true}, \"ProcessSubmittedJobOnFarm\": {\"publishJobState\": \"Active\"}}"; - setAttr ".__creator_attributes_keys" -type "string" "review,extendFrames,overrideExistingFrame,tileRendering,tilesX,tilesY,convertToScanline,useReferencedAovs,renderSetupIncludeLights"; - setAttr ".cbId" -type "string" "60df31e2be2b48bd3695c056:69960f336351"; select -ne :time1; setAttr ".o" 1001; setAttr ".unw" 1001; @@ -435,8 +393,6 @@ select -ne :defaultColorMgtGlobals; select -ne :hardwareRenderGlobals; setAttr ".ctrs" 256; setAttr ".btrs" 512; -select -ne :ikSystem; - setAttr -s 4 ".sol"; connectAttr "rs_Main.ri" ":persp.rlio[0]"; connectAttr "rs_Main.ri" ":top.rlio[0]"; connectAttr "rs_Main.ri" ":front.rlio[0]"; @@ -459,7 +415,6 @@ connectAttr ":defaultArnoldDisplayDriver.msg" ":defaultArnoldRenderOptions.drive -na; connectAttr ":defaultArnoldFilter.msg" ":defaultArnoldRenderOptions.filt"; connectAttr ":defaultArnoldDriver.msg" ":defaultArnoldRenderOptions.drvr"; -connectAttr "_renderingMain:Main.msg" "renderingMain.dnsm" -na; connectAttr "rs_Main.msg" "Main.lrl"; connectAttr "renderSetup.lit" "Main.pls"; connectAttr "defaultCollection.msg" "Main.cl"; @@ -468,9 +423,8 @@ connectAttr "renderLayerManager.rlmi[1]" "rs_Main.rlid"; connectAttr "defaultCollectionSelector.c" "defaultCollection.sel"; connectAttr "Main.lit" "defaultCollection.pls"; connectAttr "Main.nic" "defaultCollection.pic"; -connectAttr "Main.msg" "_renderingMain:Main.renderlayer"; connectAttr "defaultRenderLayer.msg" ":defaultRenderingList1.r" -na; connectAttr "rs_Main.msg" ":defaultRenderingList1.r" -na; connectAttr "pSphere1_GEOShape1.iog" ":initialShadingGroup.dsm" -na; connectAttr "pDiscShape1.iog" ":initialShadingGroup.dsm" -na; -// End of test_project_test_asset_test_task_v001.ma +// End of test_project_test_asset_test_task_v002.ma diff --git a/tests/integration/hosts/maya/test_publish_in_maya/expected/test_project/test_asset/work/test_task/renders/maya/test_project_test_asset_workfileTest_task_v001/Main/renderMain_metadata.json b/tests/integration/hosts/maya/test_publish_in_maya/expected/test_project/test_asset/work/test_task/renders/maya/test_project_test_asset_workfileTest_task_v001/Main/renderMain_metadata.json deleted file mode 100644 index 8c9e4de21a..0000000000 --- a/tests/integration/hosts/maya/test_publish_in_maya/expected/test_project/test_asset/work/test_task/renders/maya/test_project_test_asset_workfileTest_task_v001/Main/renderMain_metadata.json +++ /dev/null @@ -1,275 +0,0 @@ -{ - "asset": "test_asset", - "comment": "", - "deadline_publish_job_id": "6540ba7fc27eabc1c1a3d387", - "fps": 25.0, - "frameEnd": 1001, - "frameStart": 1001, - "instances": [ - { - "asset": "test_asset", - "colorspace": null, - "comment": "", - "deadlineUrl": "http://127.0.0.1:8082", - "extendFrames": false, - "families": [ - "render", - "review", - "ftrack" - ], - "family": "render", - "fps": 25.0, - "frameEnd": 1001, - "frameEndHandle": 1001, - "frameStart": 1001, - "frameStartHandle": 1001, - "handleEnd": 0, - "handleStart": 0, - "inputVersions": [], - "jobBatchName": "", - "multipartExr": true, - "overrideExistingFrame": false, - "pixelAspect": 1.0, - "representations": [ - { - "colorspaceData": { - "colorspace": "scene-linear Rec 709/sRGB", - "config": { - "path": "C:/Program Files/Autodesk/Maya2024/resources/OCIO-configs/Maya-legacy/config.ocio", - "template": "C:/Program Files/Autodesk/Maya2024/resources/OCIO-configs/Maya-legacy/config.ocio" - }, - "display": "legacy", - "view": "sRGB gamma" - }, - "ext": "exr", - "files": "Main.1001.exr", - "fps": 25.0, - "frameEnd": 1001, - "frameStart": 1001, - "name": "exr", - "stagingDir": "{root[work]}/test_project/test_asset/work/test_task/renders/maya/test_project_test_asset_workfileTest_task_v001/Main", - "tags": [ - "review" - ] - } - ], - "resolutionHeight": 1080, - "resolutionWidth": 1920, - "review": true, - "source": "{root[work]}/test_project/test_asset/work/test_task/test_project_test_asset_test_task_v001.ma", - "stagingDir_persistent": false, - "subset": "renderMain_beauty", - "subsetGroup": "renderMain", - "useSequenceForReview": true - } - ], - "intent": null, - "job": { - "Aux": [], - "Bad": [], - "ComFra": 0, - "CompletedChunks": 0, - "ConcurrencyToken": null, - "DataSize": -1, - "Date": "2023-10-31T08:27:42.9465949+00:00", - "DateComp": "0001-01-01T00:00:00Z", - "DateStart": "0001-01-01T00:00:00Z", - "Errs": 0, - "ExtraElements": null, - "FailedChunks": 0, - "IsSub": false, - "Mach": "DESKTOP-969549J", - "Main": false, - "MainEnd": 0, - "MainStart": 0, - "OutDir": [ - "C:/Users/TOKEJE~1/AppData/Local/Temp/tmpcjp1zvgv/output/test_project/test_asset/work/test_task/renders/maya/test_project_test_asset_workfileTest_task_v001/Main" - ], - "OutFile": [ - "Main.1001.exr" - ], - "PendingChunks": 0, - "Plug": "MayaBatch", - "Props": { - "AWSPortalAssetFileWhitelist": [], - "AWSPortalAssets": [], - "AutoTime": false, - "AuxSync": false, - "Batch": "test_project_test_asset_test_task_v001.ma31102023082742", - "Chunk": 1, - "Cmmt": "", - "Conc": 1, - "ConcLimt": true, - "Dep": [], - "DepComp": true, - "DepDel": false, - "DepFail": false, - "DepFrame": false, - "DepFrameEnd": 0, - "DepFrameStart": 0, - "DepPer": -1.0, - "Dept": "", - "Env": { - "AVALON_APP_NAME": "maya/2024", - "AVALON_ASSET": "test_asset", - "AVALON_PROJECT": "test_project", - "AVALON_TASK": "test_task", - "FTRACK_SERVER": "https://pype.ftrackapp.com", - "OPENPYPE_LOG_NO_COLORS": "1", - "OPENPYPE_MONGO": "mongodb://localhost:2707/", - "OPENPYPE_RENDER_JOB": "1" - }, - "EnvOnly": false, - "EventDir": "", - "EventOI": [], - "Ex0": "", - "Ex1": "", - "Ex2": "", - "Ex3": "", - "Ex4": "", - "Ex5": "", - "Ex6": "", - "Ex7": "", - "Ex8": "", - "Ex9": "", - "ExDic": {}, - "FrameTimeout": false, - "Frames": "1001", - "FriStart": "-10675199.02:48:05.4775808", - "FriStop": "-10675199.02:48:05.4775808", - "Grp": "none", - "InitializePluginTime": 0, - "Int": false, - "IntPer": 100, - "JobFailErr": 0, - "JobFailOvr": false, - "Limits": [], - "ListedSlaves": [], - "MachLmt": 0, - "MachLmtProg": -1.0, - "MaxTime": 0, - "MinTime": 0, - "MonStart": "-10675199.02:48:05.4775808", - "MonStop": "-10675199.02:48:05.4775808", - "Name": "test_project_test_asset_test_task_v001.ma31102023082742 - renderMain", - "NoBad": false, - "NoEvnt": false, - "NotEmail": [], - "NotNote": "", - "NotOvr": false, - "NotUser": [ - "tokejepsen" - ], - "OnComp": 2, - "OptIns": {}, - "OverAutoClean": false, - "OverClean": false, - "OverCleanDays": 0, - "OverCleanType": 1, - "OvrTaskEINames": false, - "PathMap": [], - "PlugDir": "", - "PlugInfo": { - "OutputFilePath": "C:/Users/TOKEJE~1/AppData/Local/Temp/tmpcjp1zvgv/output/test_project/test_asset/work/test_task/renders/maya", - "OutputFilePrefix": "//", - "ProjectPath": "C:\\Users\\TOKEJE~1\\AppData\\Local\\Temp\\tmpcjp1zvgv\\output\\test_project\\test_asset\\work\\test_task", - "RenderLayer": "rs_Main", - "RenderSetupIncludeLights": "True", - "Renderer": "arnold", - "SceneFile": "C:\\Users\\TOKEJE~1\\AppData\\Local\\Temp\\tmpcjp1zvgv\\output\\test_project\\test_asset\\publish\\workfile\\workfileTest_task\\v001\\test_project_test_asset_workfileTest_task_v001.ma", - "StrictErrorChecking": "True", - "UsingRenderLayers": "True", - "Version": "2024" - }, - "PoJobScrp": "", - "PoTskScrp": "", - "Pool": "none", - "PrJobScrp": "", - "PrTskScrp": "", - "Pri": 50, - "Protect": false, - "Region": "", - "Reload": false, - "RemTmT": 0, - "ReqAss": [ - { - "EndOffset": 0, - "FileName": "C:\\Users\\TOKEJE~1\\AppData\\Local\\Temp\\tmpcjp1zvgv\\output\\test_project\\test_asset\\publish\\workfile\\workfileTest_task\\v001\\test_project_test_asset_workfileTest_task_v001.ma", - "FrameString": "", - "IgnoreFrameOffsets": false, - "IsFrameAware": false, - "Notes": "", - "OverrideFrameOffsets": false, - "StartOffset": 0 - } - ], - "SatStart": "-10675199.02:48:05.4775808", - "SatStop": "-10675199.02:48:05.4775808", - "Schd": 0, - "SchdDate": "2023-10-31T08:27:42.9465593+00:00", - "SchdDays": 1, - "SchdStop": "0001-01-01T00:00:00Z", - "ScrDep": [], - "SecPool": "none", - "Seq": false, - "SndEmail": false, - "SndPopup": false, - "SndWarn": true, - "StartTime": 0, - "SunStart": "-10675199.02:48:05.4775808", - "SunStop": "-10675199.02:48:05.4775808", - "TaskEx0": "", - "TaskEx1": "", - "TaskEx2": "", - "TaskEx3": "", - "TaskEx4": "", - "TaskEx5": "", - "TaskEx6": "", - "TaskEx7": "", - "TaskEx8": "", - "TaskEx9": "", - "Tasks": 1, - "ThuStart": "-10675199.02:48:05.4775808", - "ThuStop": "-10675199.02:48:05.4775808", - "TimeScrpt": false, - "Timeout": 1, - "TskFailErr": 0, - "TskFailOvr": false, - "TueStart": "-10675199.02:48:05.4775808", - "TueStop": "-10675199.02:48:05.4775808", - "User": "tokejepsen", - "WedStart": "-10675199.02:48:05.4775808", - "WedStop": "-10675199.02:48:05.4775808", - "White": false - }, - "Purged": false, - "QueuedChunks": 0, - "RenderingChunks": 0, - "SnglTskPrg": "0 %", - "Stat": 6, - "SuspendedChunks": 0, - "Tile": false, - "TileCount": 0, - "TileFile": [], - "TileFrame": 0, - "TileX": 0, - "TileY": 0, - "_id": "6540ba7ec27eabc1c1a3d386" - }, - "session": { - "AVALON_APP": "maya", - "AVALON_ASSET": "test_asset", - "AVALON_DB": "avalon_tests", - "AVALON_LABEL": "OpenPype", - "AVALON_PROJECT": "test_project", - "AVALON_PROJECTS": "", - "AVALON_SCENEDIR": "", - "AVALON_TASK": "test_task", - "AVALON_TIMEOUT": "3000", - "AVALON_WORKDIR": "C:\\Users\\TOKEJE~1\\AppData\\Local\\Temp\\tmpcjp1zvgv\\output\\test_project\\test_asset\\work\\test_task", - "schema": "openpype:session-3.0" - }, - "source": "{root[work]}/test_project/test_asset/work/test_task/test_project_test_asset_test_task_v001.ma", - "user": "tokejepsen", - "version": 1 -} diff --git a/tests/integration/hosts/maya/test_publish_in_maya/expected/test_project/test_asset/work/test_task/test_project_test_asset_test_task_v001.ma b/tests/integration/hosts/maya/test_publish_in_maya/expected/test_project/test_asset/work/test_task/test_project_test_asset_test_task_v001.ma index e438d80d5f..2cc87c2f48 100644 --- a/tests/integration/hosts/maya/test_publish_in_maya/expected/test_project/test_asset/work/test_task/test_project_test_asset_test_task_v001.ma +++ b/tests/integration/hosts/maya/test_publish_in_maya/expected/test_project/test_asset/work/test_task/test_project_test_asset_test_task_v001.ma @@ -1,22 +1,22 @@ -//Maya ASCII 2022 scene -//Name: test_project_test_asset_test_task_v001.ma -//Last modified: Thu, Sep 14, 2023 06:31:00 PM +//Maya ASCII 2023 scene +//Name: test_project_test_asset_test_task_v002.ma +//Last modified: Thu, Dec 07, 2023 03:53:06 PM //Codeset: 1252 -requires maya "2022"; -requires -nodeType "polyDisc" "modelingToolkit" "0.0.0.0"; -requires "stereoCamera" "10.0"; -requires -nodeType "aiOptions" -nodeType "aiAOVDriver" -nodeType "aiAOVFilter" "mtoa" "5.2.2.1"; +requires maya "2023"; requires -nodeType "simpleSelector" -nodeType "renderSetupLayer" -nodeType "renderSetup" -nodeType "collection" "renderSetup.py" "1.0"; requires "stereoCamera" "10.0"; +requires -nodeType "aiOptions" -nodeType "aiAOVDriver" -nodeType "aiAOVFilter" "mtoa" "5.2.1.1"; +requires -nodeType "polyDisc" "modelingToolkit" "0.0.0.0"; +requires "stereoCamera" "10.0"; currentUnit -l centimeter -a degree -t pal; fileInfo "application" "maya"; -fileInfo "product" "Maya 2022"; -fileInfo "version" "2022"; -fileInfo "cutIdentifier" "202205171752-c25c06f306"; -fileInfo "osv" "Windows 10 Pro v2009 (Build: 19044)"; +fileInfo "product" "Maya 2023"; +fileInfo "version" "2023"; +fileInfo "cutIdentifier" "202211021031-847a9f9623"; +fileInfo "osv" "Windows 10 Pro v2009 (Build: 19045)"; fileInfo "license" "education"; -fileInfo "UUID" "019C7F50-40EF-1435-E27F-729F64685E67"; +fileInfo "UUID" "7A992745-4AD5-777F-5575-B4BFAC62B1D0"; fileInfo "OpenPypeContext" "eyJwdWJsaXNoX2F0dHJpYnV0ZXMiOiB7IlZhbGlkYXRlQ29udGFpbmVycyI6IHsiYWN0aXZlIjogdHJ1ZX19fQ=="; createNode transform -s -n "persp"; rename -uid "D52C935B-47C9-D868-A875-D799DD17B3A1"; @@ -142,19 +142,20 @@ createNode camera -n "perspShape1" -p "persp1"; setAttr ".hc" -type "string" "viewSet -p %camera"; setAttr ".dr" yes; createNode lightLinker -s -n "lightLinker1"; - rename -uid "6B9ADCD4-41B9-5BCC-826D-4A874A278510"; + rename -uid "09465BD3-42E5-18E4-7906-20A99BB2A6C0"; setAttr -s 2 ".lnk"; setAttr -s 2 ".slnk"; createNode shapeEditorManager -n "shapeEditorManager"; - rename -uid "5B4518C5-46C9-6921-690E-EFAF77B21A36"; + rename -uid "9F2E8009-4D69-046B-FCC4-28A8CE8F86DB"; createNode poseInterpolatorManager -n "poseInterpolatorManager"; - rename -uid "8518F293-4F06-BFF2-647A-72A099FBF025"; + rename -uid "6757AD81-40B0-A747-69C3-D9A56259571E"; createNode displayLayerManager -n "layerManager"; - rename -uid "04D880D5-4D86-0C58-CA3D-208ABE3E1E16"; + rename -uid "6F055ED5-4D91-8F85-7951-B4A13543A561"; createNode displayLayer -n "defaultLayer"; rename -uid "4A776D1B-401F-7069-1C74-A7AAE84CEE03"; + setAttr ".ufem" -type "stringArray" 0 ; createNode renderLayerManager -n "renderLayerManager"; - rename -uid "B719B8BE-46BF-12E6-BEBA-B0AD4DBDBA87"; + rename -uid "55626D2B-4FD5-61A1-7AB2-47B13F19D8AA"; setAttr -s 2 ".rlmi[1]" 1; setAttr -s 2 ".rlmi"; createNode renderLayer -n "defaultRenderLayer"; @@ -167,6 +168,7 @@ createNode polySphere -n "polySphere1"; createNode objectSet -n "modelMain"; rename -uid "A76AD4F8-4CF5-AA0D-4E98-BABEE6454CC3"; addAttr -ci true -sn "cbId" -ln "cbId" -dt "string"; + addAttr -ci true -sn "instance_id" -ln "instance_id" -dt "string"; addAttr -ci true -sn "id" -ln "id" -dt "string"; addAttr -ci true -sn "family" -ln "family" -dt "string"; addAttr -ci true -sn "subset" -ln "subset" -dt "string"; @@ -175,18 +177,19 @@ createNode objectSet -n "modelMain"; addAttr -ci true -sn "variant" -ln "variant" -dt "string"; addAttr -ci true -sn "asset" -ln "asset" -dt "string"; addAttr -ci true -sn "task" -ln "task" -dt "string"; - addAttr -ci true -sn "instance_id" -ln "instance_id" -dt "string"; addAttr -ci true -sn "writeColorSets" -ln "writeColorSets" -min 0 -max 1 -at "bool"; addAttr -ci true -sn "writeFaceSets" -ln "writeFaceSets" -min 0 -max 1 -at "bool"; - addAttr -ci true -sn "includeParentHierarchy" -ln "includeParentHierarchy" -min + addAttr -ci true -sn "includeParentHierarchy" -ln "includeParentHierarchy" -min 0 -max 1 -at "bool"; addAttr -ci true -sn "attr" -ln "attr" -dt "string"; addAttr -ci true -sn "attrPrefix" -ln "attrPrefix" -dt "string"; addAttr -ci true -sn "publish_attributes" -ln "publish_attributes" -dt "string"; - addAttr -ci true -sn "__creator_attributes_keys" -ln "__creator_attributes_keys" + addAttr -ci true -sn "creator_attributes" -ln "creator_attributes" -dt "string"; + addAttr -ci true -sn "__creator_attributes_keys" -ln "__creator_attributes_keys" -dt "string"; setAttr ".ihi" 0; setAttr ".cbId" -type "string" "60df31e2be2b48bd3695c056:7364ea6776c9"; + setAttr ".instance_id" -type "string" "6889d3db-b813-43db-96de-9ba555dc4472"; setAttr ".id" -type "string" "pyblish.avalon.instance"; setAttr ".family" -type "string" "model"; setAttr ".subset" -type "string" "modelMain"; @@ -195,59 +198,68 @@ createNode objectSet -n "modelMain"; setAttr ".variant" -type "string" "Main"; setAttr ".asset" -type "string" "test_asset"; setAttr ".task" -type "string" "test_task"; - setAttr ".instance_id" -type "string" "6889d3db-b813-43db-96de-9ba555dc4472"; setAttr -cb on ".writeColorSets"; setAttr -cb on ".writeFaceSets"; setAttr -cb on ".includeParentHierarchy"; setAttr ".attr" -type "string" ""; setAttr ".attrPrefix" -type "string" ""; - setAttr ".publish_attributes" -type "string" "{\"ValidateNodeIDsRelated\": {\"active\": true}, \"ValidateTransformNamingSuffix\": {\"active\": true}, \"ValidateColorSets\": {\"active\": true}, \"ValidateMeshArnoldAttributes\": {\"active\": true}, \"ValidateMeshHasUVs\": {\"active\": true}, \"ValidateMeshNonZeroEdgeLength\": {\"active\": true}, \"ExtractModel\": {\"active\": true}}"; + setAttr ".publish_attributes" -type "string" "{\"ValidateNodeIDsRelated\": {\"active\": true}, \"ValidateInstanceInContext\": {\"active\": true}, \"ValidateTransformNamingSuffix\": {\"active\": true}, \"ValidateColorSets\": {\"active\": true}, \"ValidateMeshHasUVs\": {\"active\": true}, \"ValidateMeshNonZeroEdgeLength\": {\"active\": true}, \"ExtractModel\": {\"active\": true}, \"ValidateMeshArnoldAttributes\": {\"active\": true}}"; + setAttr ".creator_attributes" -type "string" "{}"; setAttr ".__creator_attributes_keys" -type "string" "writeColorSets,writeFaceSets,includeParentHierarchy,attr,attrPrefix"; createNode script -n "uiConfigurationScriptNode"; rename -uid "4B7AFB53-452E-E870-63E1-CCA1DD6EAF13"; setAttr ".b" -type "string" ( "// Maya Mel UI Configuration File.\n//\n// This script is machine generated. Edit at your own risk.\n//\n//\n\nglobal string $gMainPane;\nif (`paneLayout -exists $gMainPane`) {\n\n\tglobal int $gUseScenePanelConfig;\n\tint $useSceneConfig = $gUseScenePanelConfig;\n\tint $nodeEditorPanelVisible = stringArrayContains(\"nodeEditorPanel1\", `getPanel -vis`);\n\tint $nodeEditorWorkspaceControlOpen = (`workspaceControl -exists nodeEditorPanel1Window` && `workspaceControl -q -visible nodeEditorPanel1Window`);\n\tint $menusOkayInPanels = `optionVar -q allowMenusInPanels`;\n\tint $nVisPanes = `paneLayout -q -nvp $gMainPane`;\n\tint $nPanes = 0;\n\tstring $editorName;\n\tstring $panelName;\n\tstring $itemFilterName;\n\tstring $panelConfig;\n\n\t//\n\t// get current state of the UI\n\t//\n\tsceneUIReplacement -update $gMainPane;\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"modelPanel\" (localizedPanelLabel(\"Top View\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tmodelPanel -edit -l (localizedPanelLabel(\"Top View\")) -mbv $menusOkayInPanels $panelName;\n" - + "\t\t$editorName = $panelName;\n modelEditor -e \n -camera \"|top\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"smoothShaded\" \n -activeOnly 0\n -ignorePanZoom 0\n -wireframeOnShaded 0\n -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n -useDefaultMaterial 0\n -bufferMode \"double\" \n -twoSidedLighting 0\n -backfaceCulling 0\n -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n -lineWidth 1\n -textureAnisotropic 0\n -textureHilight 1\n -textureSampling 2\n -textureDisplay \"modulate\" \n -textureMaxSize 16384\n -fogging 0\n -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n" + + "\t\t$editorName = $panelName;\n modelEditor -e \n -camera \"|top\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"smoothShaded\" \n -activeOnly 0\n -ignorePanZoom 0\n -wireframeOnShaded 0\n -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n -useDefaultMaterial 0\n -bufferMode \"double\" \n -twoSidedLighting 0\n -backfaceCulling 0\n -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n -lineWidth 1\n -textureAnisotropic 0\n -textureHilight 1\n -textureSampling 2\n -textureDisplay \"modulate\" \n -textureMaxSize 32768\n -fogging 0\n -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n" + " -depthOfFieldPreview 1\n -maxConstantTransparency 1\n -rendererName \"vp2Renderer\" \n -objectFilterShowInHUD 1\n -isFiltered 0\n -colorResolution 256 256 \n -bumpResolution 512 512 \n -textureCompression 0\n -transparencyAlgorithm \"frontAndBackCull\" \n -transpInShadows 0\n -cullingOverride \"none\" \n -lowQualityLighting 0\n -maximumNumHardwareLights 1\n -occlusionCulling 0\n -shadingModel 0\n -useBaseRenderer 0\n -useReducedRenderer 0\n -smallObjectCulling 0\n -smallObjectThreshold -1 \n -interactiveDisableShadows 0\n -interactiveBackFaceCull 0\n -sortTransparent 1\n -controllers 1\n -nurbsCurves 1\n -nurbsSurfaces 1\n -polymeshes 1\n -subdivSurfaces 1\n -planes 1\n -lights 1\n -cameras 1\n -controlVertices 1\n" - + " -hulls 1\n -grid 1\n -imagePlane 1\n -joints 1\n -ikHandles 1\n -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n -nCloths 1\n -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n -locators 1\n -manipulators 1\n -pluginShapes 1\n -dimensions 1\n -handles 1\n -pivots 1\n -textures 1\n -strokes 1\n -motionTrails 1\n -clipGhosts 1\n -greasePencils 1\n -shadows 0\n -captureSequenceNumber -1\n -width 1\n -height 1\n -sceneRenderFilter 0\n $editorName;\n modelEditor -e -viewSelected 0 $editorName;\n modelEditor -e \n -pluginObjects \"gpuCacheDisplayFilter\" 1 \n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n" - + "\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"modelPanel\" (localizedPanelLabel(\"Side View\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tmodelPanel -edit -l (localizedPanelLabel(\"Side View\")) -mbv $menusOkayInPanels $panelName;\n\t\t$editorName = $panelName;\n modelEditor -e \n -camera \"|side\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"smoothShaded\" \n -activeOnly 0\n -ignorePanZoom 0\n -wireframeOnShaded 0\n -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n -useDefaultMaterial 0\n -bufferMode \"double\" \n -twoSidedLighting 0\n -backfaceCulling 0\n -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n -lineWidth 1\n -textureAnisotropic 0\n -textureHilight 1\n -textureSampling 2\n" - + " -textureDisplay \"modulate\" \n -textureMaxSize 16384\n -fogging 0\n -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n -depthOfFieldPreview 1\n -maxConstantTransparency 1\n -rendererName \"vp2Renderer\" \n -objectFilterShowInHUD 1\n -isFiltered 0\n -colorResolution 256 256 \n -bumpResolution 512 512 \n -textureCompression 0\n -transparencyAlgorithm \"frontAndBackCull\" \n -transpInShadows 0\n -cullingOverride \"none\" \n -lowQualityLighting 0\n -maximumNumHardwareLights 1\n -occlusionCulling 0\n -shadingModel 0\n -useBaseRenderer 0\n -useReducedRenderer 0\n -smallObjectCulling 0\n -smallObjectThreshold -1 \n -interactiveDisableShadows 0\n -interactiveBackFaceCull 0\n" - + " -sortTransparent 1\n -controllers 1\n -nurbsCurves 1\n -nurbsSurfaces 1\n -polymeshes 1\n -subdivSurfaces 1\n -planes 1\n -lights 1\n -cameras 1\n -controlVertices 1\n -hulls 1\n -grid 1\n -imagePlane 1\n -joints 1\n -ikHandles 1\n -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n -nCloths 1\n -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n -locators 1\n -manipulators 1\n -pluginShapes 1\n -dimensions 1\n -handles 1\n -pivots 1\n -textures 1\n -strokes 1\n -motionTrails 1\n -clipGhosts 1\n -greasePencils 1\n -shadows 0\n -captureSequenceNumber -1\n -width 1\n -height 1\n" - + " -sceneRenderFilter 0\n $editorName;\n modelEditor -e -viewSelected 0 $editorName;\n modelEditor -e \n -pluginObjects \"gpuCacheDisplayFilter\" 1 \n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"modelPanel\" (localizedPanelLabel(\"Front View\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tmodelPanel -edit -l (localizedPanelLabel(\"Front View\")) -mbv $menusOkayInPanels $panelName;\n\t\t$editorName = $panelName;\n modelEditor -e \n -camera \"|front\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"smoothShaded\" \n -activeOnly 0\n -ignorePanZoom 0\n -wireframeOnShaded 0\n -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n -useDefaultMaterial 0\n -bufferMode \"double\" \n -twoSidedLighting 0\n -backfaceCulling 0\n" - + " -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n -lineWidth 1\n -textureAnisotropic 0\n -textureHilight 1\n -textureSampling 2\n -textureDisplay \"modulate\" \n -textureMaxSize 16384\n -fogging 0\n -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n -depthOfFieldPreview 1\n -maxConstantTransparency 1\n -rendererName \"vp2Renderer\" \n -objectFilterShowInHUD 1\n -isFiltered 0\n -colorResolution 256 256 \n -bumpResolution 512 512 \n -textureCompression 0\n -transparencyAlgorithm \"frontAndBackCull\" \n -transpInShadows 0\n -cullingOverride \"none\" \n -lowQualityLighting 0\n -maximumNumHardwareLights 1\n -occlusionCulling 0\n" - + " -shadingModel 0\n -useBaseRenderer 0\n -useReducedRenderer 0\n -smallObjectCulling 0\n -smallObjectThreshold -1 \n -interactiveDisableShadows 0\n -interactiveBackFaceCull 0\n -sortTransparent 1\n -controllers 1\n -nurbsCurves 1\n -nurbsSurfaces 1\n -polymeshes 1\n -subdivSurfaces 1\n -planes 1\n -lights 1\n -cameras 1\n -controlVertices 1\n -hulls 1\n -grid 1\n -imagePlane 1\n -joints 1\n -ikHandles 1\n -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n -nCloths 1\n -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n -locators 1\n -manipulators 1\n -pluginShapes 1\n -dimensions 1\n -handles 1\n -pivots 1\n" - + " -textures 1\n -strokes 1\n -motionTrails 1\n -clipGhosts 1\n -greasePencils 1\n -shadows 0\n -captureSequenceNumber -1\n -width 1\n -height 1\n -sceneRenderFilter 0\n $editorName;\n modelEditor -e -viewSelected 0 $editorName;\n modelEditor -e \n -pluginObjects \"gpuCacheDisplayFilter\" 1 \n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"modelPanel\" (localizedPanelLabel(\"Persp View\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tmodelPanel -edit -l (localizedPanelLabel(\"Persp View\")) -mbv $menusOkayInPanels $panelName;\n\t\t$editorName = $panelName;\n modelEditor -e \n -camera \"|persp\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"smoothShaded\" \n -activeOnly 0\n -ignorePanZoom 0\n" - + " -wireframeOnShaded 0\n -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n -useDefaultMaterial 0\n -bufferMode \"double\" \n -twoSidedLighting 0\n -backfaceCulling 0\n -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n -lineWidth 1\n -textureAnisotropic 0\n -textureHilight 1\n -textureSampling 2\n -textureDisplay \"modulate\" \n -textureMaxSize 16384\n -fogging 0\n -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n -depthOfFieldPreview 1\n -maxConstantTransparency 1\n -rendererName \"vp2Renderer\" \n -objectFilterShowInHUD 1\n -isFiltered 0\n -colorResolution 256 256 \n -bumpResolution 512 512 \n" - + " -textureCompression 0\n -transparencyAlgorithm \"frontAndBackCull\" \n -transpInShadows 0\n -cullingOverride \"none\" \n -lowQualityLighting 0\n -maximumNumHardwareLights 1\n -occlusionCulling 0\n -shadingModel 0\n -useBaseRenderer 0\n -useReducedRenderer 0\n -smallObjectCulling 0\n -smallObjectThreshold -1 \n -interactiveDisableShadows 0\n -interactiveBackFaceCull 0\n -sortTransparent 1\n -controllers 1\n -nurbsCurves 1\n -nurbsSurfaces 1\n -polymeshes 1\n -subdivSurfaces 1\n -planes 1\n -lights 1\n -cameras 1\n -controlVertices 1\n -hulls 1\n -grid 1\n -imagePlane 1\n -joints 1\n -ikHandles 1\n -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n" - + " -nCloths 1\n -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n -locators 1\n -manipulators 1\n -pluginShapes 1\n -dimensions 1\n -handles 1\n -pivots 1\n -textures 1\n -strokes 1\n -motionTrails 1\n -clipGhosts 1\n -greasePencils 1\n -shadows 0\n -captureSequenceNumber -1\n -width 1312\n -height 732\n -sceneRenderFilter 0\n $editorName;\n modelEditor -e -viewSelected 0 $editorName;\n modelEditor -e \n -pluginObjects \"gpuCacheDisplayFilter\" 1 \n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"outlinerPanel\" (localizedPanelLabel(\"ToggledOutliner\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\toutlinerPanel -edit -l (localizedPanelLabel(\"ToggledOutliner\")) -mbv $menusOkayInPanels $panelName;\n" - + "\t\t$editorName = $panelName;\n outlinerEditor -e \n -docTag \"isolOutln_fromSeln\" \n -showShapes 0\n -showAssignedMaterials 0\n -showTimeEditor 1\n -showReferenceNodes 1\n -showReferenceMembers 1\n -showAttributes 0\n -showConnected 0\n -showAnimCurvesOnly 0\n -showMuteInfo 0\n -organizeByLayer 1\n -organizeByClip 1\n -showAnimLayerWeight 1\n -autoExpandLayers 1\n -autoExpand 0\n -showDagOnly 1\n -showAssets 1\n -showContainedOnly 1\n -showPublishedAsConnected 0\n -showParentContainers 0\n -showContainerContents 1\n -ignoreDagHierarchy 0\n -expandConnections 0\n -showUpstreamCurves 1\n -showUnitlessCurves 1\n -showCompounds 1\n -showLeafs 1\n -showNumericAttrsOnly 0\n -highlightActive 1\n -autoSelectNewObjects 0\n" - + " -doNotSelectNewObjects 0\n -dropIsParent 1\n -transmitFilters 0\n -setFilter \"defaultSetFilter\" \n -showSetMembers 1\n -allowMultiSelection 1\n -alwaysToggleSelect 0\n -directSelect 0\n -isSet 0\n -isSetMember 0\n -displayMode \"DAG\" \n -expandObjects 0\n -setsIgnoreFilters 1\n -containersIgnoreFilters 0\n -editAttrName 0\n -showAttrValues 0\n -highlightSecondary 0\n -showUVAttrsOnly 0\n -showTextureNodesOnly 0\n -attrAlphaOrder \"default\" \n -animLayerFilterOptions \"allAffecting\" \n -sortOrder \"none\" \n -longNames 0\n -niceNames 1\n -selectCommand \"print(\\\"\\\")\" \n -showNamespace 1\n -showPinIcons 0\n -mapMotionTrails 0\n -ignoreHiddenAttribute 1\n -ignoreOutlinerColor 0\n -renderFilterVisible 0\n -renderFilterIndex 0\n" - + " -selectionOrder \"chronological\" \n -expandAttribute 0\n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"outlinerPanel\" (localizedPanelLabel(\"Outliner\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\toutlinerPanel -edit -l (localizedPanelLabel(\"Outliner\")) -mbv $menusOkayInPanels $panelName;\n\t\t$editorName = $panelName;\n outlinerEditor -e \n -showShapes 0\n -showAssignedMaterials 0\n -showTimeEditor 1\n -showReferenceNodes 0\n -showReferenceMembers 0\n -showAttributes 0\n -showConnected 0\n -showAnimCurvesOnly 0\n -showMuteInfo 0\n -organizeByLayer 1\n -organizeByClip 1\n -showAnimLayerWeight 1\n -autoExpandLayers 1\n -autoExpand 0\n -showDagOnly 1\n -showAssets 1\n -showContainedOnly 1\n -showPublishedAsConnected 0\n" - + " -showParentContainers 0\n -showContainerContents 1\n -ignoreDagHierarchy 0\n -expandConnections 0\n -showUpstreamCurves 1\n -showUnitlessCurves 1\n -showCompounds 1\n -showLeafs 1\n -showNumericAttrsOnly 0\n -highlightActive 1\n -autoSelectNewObjects 0\n -doNotSelectNewObjects 0\n -dropIsParent 1\n -transmitFilters 0\n -setFilter \"defaultSetFilter\" \n -showSetMembers 1\n -allowMultiSelection 1\n -alwaysToggleSelect 0\n -directSelect 0\n -displayMode \"DAG\" \n -expandObjects 0\n -setsIgnoreFilters 1\n -containersIgnoreFilters 0\n -editAttrName 0\n -showAttrValues 0\n -highlightSecondary 0\n -showUVAttrsOnly 0\n -showTextureNodesOnly 0\n -attrAlphaOrder \"default\" \n -animLayerFilterOptions \"allAffecting\" \n -sortOrder \"none\" \n" - + " -longNames 0\n -niceNames 1\n -showNamespace 1\n -showPinIcons 0\n -mapMotionTrails 0\n -ignoreHiddenAttribute 0\n -ignoreOutlinerColor 0\n -renderFilterVisible 0\n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"graphEditor\" (localizedPanelLabel(\"Graph Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Graph Editor\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = ($panelName+\"OutlineEd\");\n outlinerEditor -e \n -showShapes 1\n -showAssignedMaterials 0\n -showTimeEditor 1\n -showReferenceNodes 0\n -showReferenceMembers 0\n -showAttributes 1\n -showConnected 1\n -showAnimCurvesOnly 1\n -showMuteInfo 0\n -organizeByLayer 1\n" - + " -organizeByClip 1\n -showAnimLayerWeight 1\n -autoExpandLayers 1\n -autoExpand 1\n -showDagOnly 0\n -showAssets 1\n -showContainedOnly 0\n -showPublishedAsConnected 0\n -showParentContainers 0\n -showContainerContents 0\n -ignoreDagHierarchy 0\n -expandConnections 1\n -showUpstreamCurves 1\n -showUnitlessCurves 1\n -showCompounds 0\n -showLeafs 1\n -showNumericAttrsOnly 1\n -highlightActive 0\n -autoSelectNewObjects 1\n -doNotSelectNewObjects 0\n -dropIsParent 1\n -transmitFilters 1\n -setFilter \"0\" \n -showSetMembers 0\n -allowMultiSelection 1\n -alwaysToggleSelect 0\n -directSelect 0\n -displayMode \"DAG\" \n -expandObjects 0\n" - + " -setsIgnoreFilters 1\n -containersIgnoreFilters 0\n -editAttrName 0\n -showAttrValues 0\n -highlightSecondary 0\n -showUVAttrsOnly 0\n -showTextureNodesOnly 0\n -attrAlphaOrder \"default\" \n -animLayerFilterOptions \"allAffecting\" \n -sortOrder \"none\" \n -longNames 0\n -niceNames 1\n -showNamespace 1\n -showPinIcons 1\n -mapMotionTrails 1\n -ignoreHiddenAttribute 0\n -ignoreOutlinerColor 0\n -renderFilterVisible 0\n $editorName;\n\n\t\t\t$editorName = ($panelName+\"GraphEd\");\n animCurveEditor -e \n -displayValues 0\n -snapTime \"integer\" \n -snapValue \"none\" \n -showPlayRangeShades \"on\" \n -lockPlayRangeShades \"off\" \n -smoothness \"fine\" \n -resultSamples 1.041667\n" - + " -resultScreenSamples 0\n -resultUpdate \"delayed\" \n -showUpstreamCurves 1\n -keyMinScale 1\n -stackedCurvesMin -1\n -stackedCurvesMax 1\n -stackedCurvesSpace 0.2\n -preSelectionHighlight 0\n -constrainDrag 0\n -valueLinesToggle 1\n -highlightAffectedCurves 0\n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"dopeSheetPanel\" (localizedPanelLabel(\"Dope Sheet\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Dope Sheet\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = ($panelName+\"OutlineEd\");\n outlinerEditor -e \n -showShapes 1\n -showAssignedMaterials 0\n -showTimeEditor 1\n -showReferenceNodes 0\n -showReferenceMembers 0\n" - + " -showAttributes 1\n -showConnected 1\n -showAnimCurvesOnly 1\n -showMuteInfo 0\n -organizeByLayer 1\n -organizeByClip 1\n -showAnimLayerWeight 1\n -autoExpandLayers 1\n -autoExpand 0\n -showDagOnly 0\n -showAssets 1\n -showContainedOnly 0\n -showPublishedAsConnected 0\n -showParentContainers 0\n -showContainerContents 0\n -ignoreDagHierarchy 0\n -expandConnections 1\n -showUpstreamCurves 1\n -showUnitlessCurves 0\n -showCompounds 1\n -showLeafs 1\n -showNumericAttrsOnly 1\n -highlightActive 0\n -autoSelectNewObjects 0\n -doNotSelectNewObjects 1\n -dropIsParent 1\n -transmitFilters 0\n -setFilter \"0\" \n -showSetMembers 0\n" - + " -allowMultiSelection 1\n -alwaysToggleSelect 0\n -directSelect 0\n -displayMode \"DAG\" \n -expandObjects 0\n -setsIgnoreFilters 1\n -containersIgnoreFilters 0\n -editAttrName 0\n -showAttrValues 0\n -highlightSecondary 0\n -showUVAttrsOnly 0\n -showTextureNodesOnly 0\n -attrAlphaOrder \"default\" \n -animLayerFilterOptions \"allAffecting\" \n -sortOrder \"none\" \n -longNames 0\n -niceNames 1\n -showNamespace 1\n -showPinIcons 0\n -mapMotionTrails 1\n -ignoreHiddenAttribute 0\n -ignoreOutlinerColor 0\n -renderFilterVisible 0\n $editorName;\n\n\t\t\t$editorName = ($panelName+\"DopeSheetEd\");\n dopeSheetEditor -e \n -displayValues 0\n -snapTime \"integer\" \n" - + " -snapValue \"none\" \n -outliner \"dopeSheetPanel1OutlineEd\" \n -showSummary 1\n -showScene 0\n -hierarchyBelow 0\n -showTicks 1\n -selectionWindow 0 0 0 0 \n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"timeEditorPanel\" (localizedPanelLabel(\"Time Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Time Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"clipEditorPanel\" (localizedPanelLabel(\"Trax Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Trax Editor\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = clipEditorNameFromPanel($panelName);\n" - + " clipEditor -e \n -displayValues 0\n -snapTime \"none\" \n -snapValue \"none\" \n -initialized 0\n -manageSequencer 0 \n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"sequenceEditorPanel\" (localizedPanelLabel(\"Camera Sequencer\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Camera Sequencer\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = sequenceEditorNameFromPanel($panelName);\n clipEditor -e \n -displayValues 0\n -snapTime \"none\" \n -snapValue \"none\" \n -initialized 0\n -manageSequencer 1 \n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"hyperGraphPanel\" (localizedPanelLabel(\"Hypergraph Hierarchy\")) `;\n" - + "\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Hypergraph Hierarchy\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = ($panelName+\"HyperGraphEd\");\n hyperGraph -e \n -graphLayoutStyle \"hierarchicalLayout\" \n -orientation \"horiz\" \n -mergeConnections 0\n -zoom 1\n -animateTransition 0\n -showRelationships 1\n -showShapes 0\n -showDeformers 0\n -showExpressions 0\n -showConstraints 0\n -showConnectionFromSelected 0\n -showConnectionToSelected 0\n -showConstraintLabels 0\n -showUnderworld 0\n -showInvisible 0\n -transitionFrames 1\n -opaqueContainers 0\n -freeform 0\n -imagePosition 0 0 \n -imageScale 1\n -imageEnabled 0\n -graphType \"DAG\" \n" - + " -heatMapDisplay 0\n -updateSelection 1\n -updateNodeAdded 1\n -useDrawOverrideColor 0\n -limitGraphTraversal -1\n -range 0 0 \n -iconSize \"smallIcons\" \n -showCachedConnections 0\n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"hyperShadePanel\" (localizedPanelLabel(\"Hypershade\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Hypershade\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"visorPanel\" (localizedPanelLabel(\"Visor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Visor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n" - + "\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"nodeEditorPanel\" (localizedPanelLabel(\"Node Editor\")) `;\n\tif ($nodeEditorPanelVisible || $nodeEditorWorkspaceControlOpen) {\n\t\tif (\"\" == $panelName) {\n\t\t\tif ($useSceneConfig) {\n\t\t\t\t$panelName = `scriptedPanel -unParent -type \"nodeEditorPanel\" -l (localizedPanelLabel(\"Node Editor\")) -mbv $menusOkayInPanels `;\n\n\t\t\t$editorName = ($panelName+\"NodeEditorEd\");\n nodeEditor -e \n -allAttributes 0\n -allNodes 0\n -autoSizeNodes 1\n -consistentNameSize 1\n -createNodeCommand \"nodeEdCreateNodeCommand\" \n -connectNodeOnCreation 0\n -connectOnDrop 0\n -copyConnectionsOnPaste 0\n -connectionStyle \"bezier\" \n -defaultPinnedState 0\n -additiveGraphingMode 0\n -settingsChangedCallback \"nodeEdSyncControls\" \n -traversalDepthLimit -1\n -keyPressCommand \"nodeEdKeyPressCommand\" \n" - + " -nodeTitleMode \"name\" \n -gridSnap 0\n -gridVisibility 1\n -crosshairOnEdgeDragging 0\n -popupMenuScript \"nodeEdBuildPanelMenus\" \n -showNamespace 1\n -showShapes 1\n -showSGShapes 0\n -showTransforms 1\n -useAssets 1\n -syncedSelection 1\n -extendToShapes 1\n -editorMode \"default\" \n -hasWatchpoint 0\n $editorName;\n\t\t\t}\n\t\t} else {\n\t\t\t$label = `panel -q -label $panelName`;\n\t\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Node Editor\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = ($panelName+\"NodeEditorEd\");\n nodeEditor -e \n -allAttributes 0\n -allNodes 0\n -autoSizeNodes 1\n -consistentNameSize 1\n -createNodeCommand \"nodeEdCreateNodeCommand\" \n -connectNodeOnCreation 0\n -connectOnDrop 0\n" - + " -copyConnectionsOnPaste 0\n -connectionStyle \"bezier\" \n -defaultPinnedState 0\n -additiveGraphingMode 0\n -settingsChangedCallback \"nodeEdSyncControls\" \n -traversalDepthLimit -1\n -keyPressCommand \"nodeEdKeyPressCommand\" \n -nodeTitleMode \"name\" \n -gridSnap 0\n -gridVisibility 1\n -crosshairOnEdgeDragging 0\n -popupMenuScript \"nodeEdBuildPanelMenus\" \n -showNamespace 1\n -showShapes 1\n -showSGShapes 0\n -showTransforms 1\n -useAssets 1\n -syncedSelection 1\n -extendToShapes 1\n -editorMode \"default\" \n -hasWatchpoint 0\n $editorName;\n\t\t\tif (!$useSceneConfig) {\n\t\t\t\tpanel -e -l $label $panelName;\n\t\t\t}\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"createNodePanel\" (localizedPanelLabel(\"Create Node\")) `;\n" - + "\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Create Node\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"polyTexturePlacementPanel\" (localizedPanelLabel(\"UV Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"UV Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"renderWindowPanel\" (localizedPanelLabel(\"Render View\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Render View\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"shapePanel\" (localizedPanelLabel(\"Shape Editor\")) `;\n" - + "\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tshapePanel -edit -l (localizedPanelLabel(\"Shape Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"posePanel\" (localizedPanelLabel(\"Pose Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tposePanel -edit -l (localizedPanelLabel(\"Pose Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"dynRelEdPanel\" (localizedPanelLabel(\"Dynamic Relationships\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Dynamic Relationships\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"relationshipPanel\" (localizedPanelLabel(\"Relationship Editor\")) `;\n" - + "\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Relationship Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"referenceEditorPanel\" (localizedPanelLabel(\"Reference Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Reference Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"componentEditorPanel\" (localizedPanelLabel(\"Component Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Component Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"dynPaintScriptedPanelType\" (localizedPanelLabel(\"Paint Effects\")) `;\n" - + "\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Paint Effects\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"scriptEditorPanel\" (localizedPanelLabel(\"Script Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Script Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"profilerPanel\" (localizedPanelLabel(\"Profiler Tool\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Profiler Tool\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"contentBrowserPanel\" (localizedPanelLabel(\"Content Browser\")) `;\n" - + "\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Content Browser\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"Stereo\" (localizedPanelLabel(\"Stereo\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Stereo\")) -mbv $menusOkayInPanels $panelName;\n{ string $editorName = ($panelName+\"Editor\");\n stereoCameraView -e \n -camera \"|persp\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"wireframe\" \n -activeOnly 0\n -ignorePanZoom 0\n -wireframeOnShaded 0\n -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n -useDefaultMaterial 0\n -bufferMode \"double\" \n -twoSidedLighting 1\n" - + " -backfaceCulling 0\n -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n -lineWidth 1\n -textureAnisotropic 0\n -textureHilight 1\n -textureSampling 2\n -textureDisplay \"modulate\" \n -textureMaxSize 16384\n -fogging 0\n -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n -depthOfFieldPreview 1\n -maxConstantTransparency 1\n -objectFilterShowInHUD 1\n -isFiltered 0\n -colorResolution 4 4 \n -bumpResolution 4 4 \n -textureCompression 0\n -transparencyAlgorithm \"frontAndBackCull\" \n -transpInShadows 0\n -cullingOverride \"none\" \n" - + " -lowQualityLighting 0\n -maximumNumHardwareLights 0\n -occlusionCulling 0\n -shadingModel 0\n -useBaseRenderer 0\n -useReducedRenderer 0\n -smallObjectCulling 0\n -smallObjectThreshold -1 \n -interactiveDisableShadows 0\n -interactiveBackFaceCull 0\n -sortTransparent 1\n -controllers 1\n -nurbsCurves 1\n -nurbsSurfaces 1\n -polymeshes 1\n -subdivSurfaces 1\n -planes 1\n -lights 1\n -cameras 1\n -controlVertices 1\n -hulls 1\n -grid 1\n -imagePlane 1\n -joints 1\n -ikHandles 1\n -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n -nCloths 1\n" - + " -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n -locators 1\n -manipulators 1\n -pluginShapes 1\n -dimensions 1\n -handles 1\n -pivots 1\n -textures 1\n -strokes 1\n -motionTrails 1\n -clipGhosts 1\n -greasePencils 1\n -shadows 0\n -captureSequenceNumber -1\n -width 0\n -height 0\n -sceneRenderFilter 0\n -displayMode \"centerEye\" \n -viewColor 0 0 0 1 \n -useCustomBackground 1\n $editorName;\n stereoCameraView -e -viewSelected 0 $editorName;\n stereoCameraView -e \n -pluginObjects \"gpuCacheDisplayFilter\" 1 \n $editorName; };\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\tif ($useSceneConfig) {\n string $configName = `getPanel -cwl (localizedPanelLabel(\"Current Layout\"))`;\n" - + " if (\"\" != $configName) {\n\t\t\tpanelConfiguration -edit -label (localizedPanelLabel(\"Current Layout\")) \n\t\t\t\t-userCreated false\n\t\t\t\t-defaultImage \"vacantCell.xP:/\"\n\t\t\t\t-image \"\"\n\t\t\t\t-sc false\n\t\t\t\t-configString \"global string $gMainPane; paneLayout -e -cn \\\"single\\\" -ps 1 100 100 $gMainPane;\"\n\t\t\t\t-removeAllPanels\n\t\t\t\t-ap false\n\t\t\t\t\t(localizedPanelLabel(\"Persp View\")) \n\t\t\t\t\t\"modelPanel\"\n" - + "\t\t\t\t\t\"$panelName = `modelPanel -unParent -l (localizedPanelLabel(\\\"Persp View\\\")) -mbv $menusOkayInPanels `;\\n$editorName = $panelName;\\nmodelEditor -e \\n -cam `findStartUpCamera persp` \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 16384\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -greasePencils 1\\n -shadows 0\\n -captureSequenceNumber -1\\n -width 1312\\n -height 732\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName;\\nmodelEditor -e \\n -pluginObjects \\\"gpuCacheDisplayFilter\\\" 1 \\n $editorName\"\n" - + "\t\t\t\t\t\"modelPanel -edit -l (localizedPanelLabel(\\\"Persp View\\\")) -mbv $menusOkayInPanels $panelName;\\n$editorName = $panelName;\\nmodelEditor -e \\n -cam `findStartUpCamera persp` \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 16384\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -greasePencils 1\\n -shadows 0\\n -captureSequenceNumber -1\\n -width 1312\\n -height 732\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName;\\nmodelEditor -e \\n -pluginObjects \\\"gpuCacheDisplayFilter\\\" 1 \\n $editorName\"\n" + + " -hulls 1\n -grid 1\n -imagePlane 1\n -joints 1\n -ikHandles 1\n -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n -nCloths 1\n -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n -locators 1\n -manipulators 1\n -pluginShapes 1\n -dimensions 1\n -handles 1\n -pivots 1\n -textures 1\n -strokes 1\n -motionTrails 1\n -clipGhosts 1\n -bluePencil 1\n -greasePencils 0\n -shadows 0\n -captureSequenceNumber -1\n -width 477\n -height 276\n -sceneRenderFilter 0\n $editorName;\n modelEditor -e -viewSelected 0 $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"modelPanel\" (localizedPanelLabel(\"Side View\")) `;\n" + + "\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tmodelPanel -edit -l (localizedPanelLabel(\"Side View\")) -mbv $menusOkayInPanels $panelName;\n\t\t$editorName = $panelName;\n modelEditor -e \n -camera \"|side\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"smoothShaded\" \n -activeOnly 0\n -ignorePanZoom 0\n -wireframeOnShaded 0\n -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n -useDefaultMaterial 0\n -bufferMode \"double\" \n -twoSidedLighting 0\n -backfaceCulling 0\n -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n -lineWidth 1\n -textureAnisotropic 0\n -textureHilight 1\n -textureSampling 2\n -textureDisplay \"modulate\" \n -textureMaxSize 32768\n -fogging 0\n" + + " -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n -depthOfFieldPreview 1\n -maxConstantTransparency 1\n -rendererName \"vp2Renderer\" \n -objectFilterShowInHUD 1\n -isFiltered 0\n -colorResolution 256 256 \n -bumpResolution 512 512 \n -textureCompression 0\n -transparencyAlgorithm \"frontAndBackCull\" \n -transpInShadows 0\n -cullingOverride \"none\" \n -lowQualityLighting 0\n -maximumNumHardwareLights 1\n -occlusionCulling 0\n -shadingModel 0\n -useBaseRenderer 0\n -useReducedRenderer 0\n -smallObjectCulling 0\n -smallObjectThreshold -1 \n -interactiveDisableShadows 0\n -interactiveBackFaceCull 0\n -sortTransparent 1\n -controllers 1\n -nurbsCurves 1\n" + + " -nurbsSurfaces 1\n -polymeshes 1\n -subdivSurfaces 1\n -planes 1\n -lights 1\n -cameras 1\n -controlVertices 1\n -hulls 1\n -grid 1\n -imagePlane 1\n -joints 1\n -ikHandles 1\n -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n -nCloths 1\n -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n -locators 1\n -manipulators 1\n -pluginShapes 1\n -dimensions 1\n -handles 1\n -pivots 1\n -textures 1\n -strokes 1\n -motionTrails 1\n -clipGhosts 1\n -bluePencil 1\n -greasePencils 0\n -shadows 0\n -captureSequenceNumber -1\n -width 476\n -height 276\n -sceneRenderFilter 0\n $editorName;\n" + + " modelEditor -e -viewSelected 0 $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"modelPanel\" (localizedPanelLabel(\"Front View\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tmodelPanel -edit -l (localizedPanelLabel(\"Front View\")) -mbv $menusOkayInPanels $panelName;\n\t\t$editorName = $panelName;\n modelEditor -e \n -camera \"|front\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"smoothShaded\" \n -activeOnly 0\n -ignorePanZoom 0\n -wireframeOnShaded 0\n -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n -useDefaultMaterial 0\n -bufferMode \"double\" \n -twoSidedLighting 0\n -backfaceCulling 0\n -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n" + + " -lineWidth 1\n -textureAnisotropic 0\n -textureHilight 1\n -textureSampling 2\n -textureDisplay \"modulate\" \n -textureMaxSize 32768\n -fogging 0\n -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n -depthOfFieldPreview 1\n -maxConstantTransparency 1\n -rendererName \"vp2Renderer\" \n -objectFilterShowInHUD 1\n -isFiltered 0\n -colorResolution 256 256 \n -bumpResolution 512 512 \n -textureCompression 0\n -transparencyAlgorithm \"frontAndBackCull\" \n -transpInShadows 0\n -cullingOverride \"none\" \n -lowQualityLighting 0\n -maximumNumHardwareLights 1\n -occlusionCulling 0\n -shadingModel 0\n -useBaseRenderer 0\n -useReducedRenderer 0\n -smallObjectCulling 0\n" + + " -smallObjectThreshold -1 \n -interactiveDisableShadows 0\n -interactiveBackFaceCull 0\n -sortTransparent 1\n -controllers 1\n -nurbsCurves 1\n -nurbsSurfaces 1\n -polymeshes 1\n -subdivSurfaces 1\n -planes 1\n -lights 1\n -cameras 1\n -controlVertices 1\n -hulls 1\n -grid 1\n -imagePlane 1\n -joints 1\n -ikHandles 1\n -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n -nCloths 1\n -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n -locators 1\n -manipulators 1\n -pluginShapes 1\n -dimensions 1\n -handles 1\n -pivots 1\n -textures 1\n -strokes 1\n -motionTrails 1\n -clipGhosts 1\n -bluePencil 1\n" + + " -greasePencils 0\n -shadows 0\n -captureSequenceNumber -1\n -width 477\n -height 276\n -sceneRenderFilter 0\n $editorName;\n modelEditor -e -viewSelected 0 $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"modelPanel\" (localizedPanelLabel(\"Persp View\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tmodelPanel -edit -l (localizedPanelLabel(\"Persp View\")) -mbv $menusOkayInPanels $panelName;\n\t\t$editorName = $panelName;\n modelEditor -e \n -camera \"|persp\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"smoothShaded\" \n -activeOnly 0\n -ignorePanZoom 0\n -wireframeOnShaded 0\n -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n -useDefaultMaterial 0\n -bufferMode \"double\" \n" + + " -twoSidedLighting 0\n -backfaceCulling 0\n -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n -lineWidth 1\n -textureAnisotropic 0\n -textureHilight 1\n -textureSampling 2\n -textureDisplay \"modulate\" \n -textureMaxSize 32768\n -fogging 0\n -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n -depthOfFieldPreview 1\n -maxConstantTransparency 1\n -rendererName \"vp2Renderer\" \n -objectFilterShowInHUD 1\n -isFiltered 0\n -colorResolution 256 256 \n -bumpResolution 512 512 \n -textureCompression 0\n -transparencyAlgorithm \"frontAndBackCull\" \n -transpInShadows 0\n -cullingOverride \"none\" \n -lowQualityLighting 0\n" + + " -maximumNumHardwareLights 1\n -occlusionCulling 0\n -shadingModel 0\n -useBaseRenderer 0\n -useReducedRenderer 0\n -smallObjectCulling 0\n -smallObjectThreshold -1 \n -interactiveDisableShadows 0\n -interactiveBackFaceCull 0\n -sortTransparent 1\n -controllers 1\n -nurbsCurves 1\n -nurbsSurfaces 1\n -polymeshes 1\n -subdivSurfaces 1\n -planes 1\n -lights 1\n -cameras 1\n -controlVertices 1\n -hulls 1\n -grid 1\n -imagePlane 1\n -joints 1\n -ikHandles 1\n -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n -nCloths 1\n -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n -locators 1\n -manipulators 1\n -pluginShapes 1\n" + + " -dimensions 1\n -handles 1\n -pivots 1\n -textures 1\n -strokes 1\n -motionTrails 1\n -clipGhosts 1\n -bluePencil 1\n -greasePencils 0\n -shadows 0\n -captureSequenceNumber -1\n -width 476\n -height 276\n -sceneRenderFilter 0\n $editorName;\n modelEditor -e -viewSelected 0 $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"outlinerPanel\" (localizedPanelLabel(\"ToggledOutliner\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\toutlinerPanel -edit -l (localizedPanelLabel(\"ToggledOutliner\")) -mbv $menusOkayInPanels $panelName;\n\t\t$editorName = $panelName;\n outlinerEditor -e \n -docTag \"isolOutln_fromSeln\" \n -showShapes 0\n -showAssignedMaterials 0\n -showTimeEditor 1\n -showReferenceNodes 1\n -showReferenceMembers 1\n" + + " -showAttributes 0\n -showConnected 0\n -showAnimCurvesOnly 0\n -showMuteInfo 0\n -organizeByLayer 1\n -organizeByClip 1\n -showAnimLayerWeight 1\n -autoExpandLayers 1\n -autoExpand 0\n -showDagOnly 1\n -showAssets 1\n -showContainedOnly 1\n -showPublishedAsConnected 0\n -showParentContainers 0\n -showContainerContents 1\n -ignoreDagHierarchy 0\n -expandConnections 0\n -showUpstreamCurves 1\n -showUnitlessCurves 1\n -showCompounds 1\n -showLeafs 1\n -showNumericAttrsOnly 0\n -highlightActive 1\n -autoSelectNewObjects 0\n -doNotSelectNewObjects 0\n -dropIsParent 1\n -transmitFilters 0\n -setFilter \"defaultSetFilter\" \n -showSetMembers 1\n -allowMultiSelection 1\n -alwaysToggleSelect 0\n -directSelect 0\n" + + " -isSet 0\n -isSetMember 0\n -showUfeItems 1\n -displayMode \"DAG\" \n -expandObjects 0\n -setsIgnoreFilters 1\n -containersIgnoreFilters 0\n -editAttrName 0\n -showAttrValues 0\n -highlightSecondary 0\n -showUVAttrsOnly 0\n -showTextureNodesOnly 0\n -attrAlphaOrder \"default\" \n -animLayerFilterOptions \"allAffecting\" \n -sortOrder \"none\" \n -longNames 0\n -niceNames 1\n -selectCommand \"print(\\\"\\\")\" \n -showNamespace 1\n -showPinIcons 0\n -mapMotionTrails 0\n -ignoreHiddenAttribute 0\n -ignoreOutlinerColor 0\n -renderFilterVisible 0\n -renderFilterIndex 0\n -selectionOrder \"chronological\" \n -expandAttribute 0\n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"outlinerPanel\" (localizedPanelLabel(\"Outliner\")) `;\n" + + "\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\toutlinerPanel -edit -l (localizedPanelLabel(\"Outliner\")) -mbv $menusOkayInPanels $panelName;\n\t\t$editorName = $panelName;\n outlinerEditor -e \n -showShapes 0\n -showAssignedMaterials 0\n -showTimeEditor 1\n -showReferenceNodes 0\n -showReferenceMembers 0\n -showAttributes 0\n -showConnected 0\n -showAnimCurvesOnly 0\n -showMuteInfo 0\n -organizeByLayer 1\n -organizeByClip 1\n -showAnimLayerWeight 1\n -autoExpandLayers 1\n -autoExpand 0\n -showDagOnly 1\n -showAssets 1\n -showContainedOnly 1\n -showPublishedAsConnected 0\n -showParentContainers 0\n -showContainerContents 1\n -ignoreDagHierarchy 0\n -expandConnections 0\n -showUpstreamCurves 1\n -showUnitlessCurves 1\n -showCompounds 1\n -showLeafs 1\n" + + " -showNumericAttrsOnly 0\n -highlightActive 1\n -autoSelectNewObjects 0\n -doNotSelectNewObjects 0\n -dropIsParent 1\n -transmitFilters 0\n -setFilter \"defaultSetFilter\" \n -showSetMembers 1\n -allowMultiSelection 1\n -alwaysToggleSelect 0\n -directSelect 0\n -showUfeItems 1\n -displayMode \"DAG\" \n -expandObjects 0\n -setsIgnoreFilters 1\n -containersIgnoreFilters 0\n -editAttrName 0\n -showAttrValues 0\n -highlightSecondary 0\n -showUVAttrsOnly 0\n -showTextureNodesOnly 0\n -attrAlphaOrder \"default\" \n -animLayerFilterOptions \"allAffecting\" \n -sortOrder \"none\" \n -longNames 0\n -niceNames 1\n -showNamespace 1\n -showPinIcons 0\n -mapMotionTrails 0\n -ignoreHiddenAttribute 0\n -ignoreOutlinerColor 0\n -renderFilterVisible 0\n" + + " $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"graphEditor\" (localizedPanelLabel(\"Graph Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Graph Editor\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = ($panelName+\"OutlineEd\");\n outlinerEditor -e \n -showShapes 1\n -showAssignedMaterials 0\n -showTimeEditor 1\n -showReferenceNodes 0\n -showReferenceMembers 0\n -showAttributes 1\n -showConnected 1\n -showAnimCurvesOnly 1\n -showMuteInfo 0\n -organizeByLayer 1\n -organizeByClip 1\n -showAnimLayerWeight 1\n -autoExpandLayers 1\n -autoExpand 1\n -showDagOnly 0\n -showAssets 1\n -showContainedOnly 0\n" + + " -showPublishedAsConnected 0\n -showParentContainers 0\n -showContainerContents 0\n -ignoreDagHierarchy 0\n -expandConnections 1\n -showUpstreamCurves 1\n -showUnitlessCurves 1\n -showCompounds 0\n -showLeafs 1\n -showNumericAttrsOnly 1\n -highlightActive 0\n -autoSelectNewObjects 1\n -doNotSelectNewObjects 0\n -dropIsParent 1\n -transmitFilters 1\n -setFilter \"0\" \n -showSetMembers 0\n -allowMultiSelection 1\n -alwaysToggleSelect 0\n -directSelect 0\n -showUfeItems 1\n -displayMode \"DAG\" \n -expandObjects 0\n -setsIgnoreFilters 1\n -containersIgnoreFilters 0\n -editAttrName 0\n -showAttrValues 0\n -highlightSecondary 0\n -showUVAttrsOnly 0\n" + + " -showTextureNodesOnly 0\n -attrAlphaOrder \"default\" \n -animLayerFilterOptions \"allAffecting\" \n -sortOrder \"none\" \n -longNames 0\n -niceNames 1\n -showNamespace 1\n -showPinIcons 1\n -mapMotionTrails 1\n -ignoreHiddenAttribute 0\n -ignoreOutlinerColor 0\n -renderFilterVisible 0\n $editorName;\n\n\t\t\t$editorName = ($panelName+\"GraphEd\");\n animCurveEditor -e \n -displayValues 0\n -snapTime \"integer\" \n -snapValue \"none\" \n -showPlayRangeShades \"on\" \n -lockPlayRangeShades \"off\" \n -smoothness \"fine\" \n -resultSamples 1.041667\n -resultScreenSamples 0\n -resultUpdate \"delayed\" \n -showUpstreamCurves 1\n -keyMinScale 1\n -stackedCurvesMin -1\n -stackedCurvesMax 1\n" + + " -stackedCurvesSpace 0.2\n -preSelectionHighlight 0\n -constrainDrag 0\n -valueLinesToggle 1\n -highlightAffectedCurves 0\n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"dopeSheetPanel\" (localizedPanelLabel(\"Dope Sheet\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Dope Sheet\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = ($panelName+\"OutlineEd\");\n outlinerEditor -e \n -showShapes 1\n -showAssignedMaterials 0\n -showTimeEditor 1\n -showReferenceNodes 0\n -showReferenceMembers 0\n -showAttributes 1\n -showConnected 1\n -showAnimCurvesOnly 1\n -showMuteInfo 0\n -organizeByLayer 1\n -organizeByClip 1\n" + + " -showAnimLayerWeight 1\n -autoExpandLayers 1\n -autoExpand 0\n -showDagOnly 0\n -showAssets 1\n -showContainedOnly 0\n -showPublishedAsConnected 0\n -showParentContainers 0\n -showContainerContents 0\n -ignoreDagHierarchy 0\n -expandConnections 1\n -showUpstreamCurves 1\n -showUnitlessCurves 0\n -showCompounds 1\n -showLeafs 1\n -showNumericAttrsOnly 1\n -highlightActive 0\n -autoSelectNewObjects 0\n -doNotSelectNewObjects 1\n -dropIsParent 1\n -transmitFilters 0\n -setFilter \"0\" \n -showSetMembers 0\n -allowMultiSelection 1\n -alwaysToggleSelect 0\n -directSelect 0\n -showUfeItems 1\n -displayMode \"DAG\" \n -expandObjects 0\n" + + " -setsIgnoreFilters 1\n -containersIgnoreFilters 0\n -editAttrName 0\n -showAttrValues 0\n -highlightSecondary 0\n -showUVAttrsOnly 0\n -showTextureNodesOnly 0\n -attrAlphaOrder \"default\" \n -animLayerFilterOptions \"allAffecting\" \n -sortOrder \"none\" \n -longNames 0\n -niceNames 1\n -showNamespace 1\n -showPinIcons 0\n -mapMotionTrails 1\n -ignoreHiddenAttribute 0\n -ignoreOutlinerColor 0\n -renderFilterVisible 0\n $editorName;\n\n\t\t\t$editorName = ($panelName+\"DopeSheetEd\");\n dopeSheetEditor -e \n -displayValues 0\n -snapTime \"integer\" \n -snapValue \"none\" \n -outliner \"dopeSheetPanel1OutlineEd\" \n -showSummary 1\n -showScene 0\n -hierarchyBelow 0\n" + + " -showTicks 1\n -selectionWindow 0 0 0 0 \n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"timeEditorPanel\" (localizedPanelLabel(\"Time Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Time Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"clipEditorPanel\" (localizedPanelLabel(\"Trax Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Trax Editor\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = clipEditorNameFromPanel($panelName);\n clipEditor -e \n -displayValues 0\n -snapTime \"none\" \n -snapValue \"none\" \n -initialized 0\n -manageSequencer 0 \n" + + " $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"sequenceEditorPanel\" (localizedPanelLabel(\"Camera Sequencer\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Camera Sequencer\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = sequenceEditorNameFromPanel($panelName);\n clipEditor -e \n -displayValues 0\n -snapTime \"none\" \n -snapValue \"none\" \n -initialized 0\n -manageSequencer 1 \n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"hyperGraphPanel\" (localizedPanelLabel(\"Hypergraph Hierarchy\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Hypergraph Hierarchy\")) -mbv $menusOkayInPanels $panelName;\n" + + "\n\t\t\t$editorName = ($panelName+\"HyperGraphEd\");\n hyperGraph -e \n -graphLayoutStyle \"hierarchicalLayout\" \n -orientation \"horiz\" \n -mergeConnections 0\n -zoom 1\n -animateTransition 0\n -showRelationships 1\n -showShapes 0\n -showDeformers 0\n -showExpressions 0\n -showConstraints 0\n -showConnectionFromSelected 0\n -showConnectionToSelected 0\n -showConstraintLabels 0\n -showUnderworld 0\n -showInvisible 0\n -transitionFrames 1\n -opaqueContainers 0\n -freeform 0\n -imagePosition 0 0 \n -imageScale 1\n -imageEnabled 0\n -graphType \"DAG\" \n -heatMapDisplay 0\n -updateSelection 1\n -updateNodeAdded 1\n -useDrawOverrideColor 0\n -limitGraphTraversal -1\n" + + " -range 0 0 \n -iconSize \"smallIcons\" \n -showCachedConnections 0\n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"hyperShadePanel\" (localizedPanelLabel(\"Hypershade\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Hypershade\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"visorPanel\" (localizedPanelLabel(\"Visor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Visor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"nodeEditorPanel\" (localizedPanelLabel(\"Node Editor\")) `;\n\tif ($nodeEditorPanelVisible || $nodeEditorWorkspaceControlOpen) {\n" + + "\t\tif (\"\" == $panelName) {\n\t\t\tif ($useSceneConfig) {\n\t\t\t\t$panelName = `scriptedPanel -unParent -type \"nodeEditorPanel\" -l (localizedPanelLabel(\"Node Editor\")) -mbv $menusOkayInPanels `;\n\n\t\t\t$editorName = ($panelName+\"NodeEditorEd\");\n nodeEditor -e \n -allAttributes 0\n -allNodes 0\n -autoSizeNodes 1\n -consistentNameSize 1\n -createNodeCommand \"nodeEdCreateNodeCommand\" \n -connectNodeOnCreation 0\n -connectOnDrop 0\n -copyConnectionsOnPaste 0\n -connectionStyle \"bezier\" \n -defaultPinnedState 0\n -additiveGraphingMode 1\n -connectedGraphingMode 1\n -settingsChangedCallback \"nodeEdSyncControls\" \n -traversalDepthLimit -1\n -keyPressCommand \"nodeEdKeyPressCommand\" \n -nodeTitleMode \"name\" \n -gridSnap 0\n -gridVisibility 1\n -crosshairOnEdgeDragging 0\n" + + " -popupMenuScript \"nodeEdBuildPanelMenus\" \n -showNamespace 1\n -showShapes 1\n -showSGShapes 0\n -showTransforms 1\n -useAssets 1\n -syncedSelection 1\n -extendToShapes 1\n -showUnitConversions 0\n -editorMode \"default\" \n -hasWatchpoint 0\n $editorName;\n\t\t\t}\n\t\t} else {\n\t\t\t$label = `panel -q -label $panelName`;\n\t\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Node Editor\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = ($panelName+\"NodeEditorEd\");\n nodeEditor -e \n -allAttributes 0\n -allNodes 0\n -autoSizeNodes 1\n -consistentNameSize 1\n -createNodeCommand \"nodeEdCreateNodeCommand\" \n -connectNodeOnCreation 0\n -connectOnDrop 0\n -copyConnectionsOnPaste 0\n -connectionStyle \"bezier\" \n -defaultPinnedState 0\n" + + " -additiveGraphingMode 1\n -connectedGraphingMode 1\n -settingsChangedCallback \"nodeEdSyncControls\" \n -traversalDepthLimit -1\n -keyPressCommand \"nodeEdKeyPressCommand\" \n -nodeTitleMode \"name\" \n -gridSnap 0\n -gridVisibility 1\n -crosshairOnEdgeDragging 0\n -popupMenuScript \"nodeEdBuildPanelMenus\" \n -showNamespace 1\n -showShapes 1\n -showSGShapes 0\n -showTransforms 1\n -useAssets 1\n -syncedSelection 1\n -extendToShapes 1\n -showUnitConversions 0\n -editorMode \"default\" \n -hasWatchpoint 0\n $editorName;\n\t\t\tif (!$useSceneConfig) {\n\t\t\t\tpanel -e -l $label $panelName;\n\t\t\t}\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"createNodePanel\" (localizedPanelLabel(\"Create Node\")) `;\n\tif (\"\" != $panelName) {\n" + + "\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Create Node\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"polyTexturePlacementPanel\" (localizedPanelLabel(\"UV Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"UV Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"renderWindowPanel\" (localizedPanelLabel(\"Render View\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Render View\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"shapePanel\" (localizedPanelLabel(\"Shape Editor\")) `;\n\tif (\"\" != $panelName) {\n" + + "\t\t$label = `panel -q -label $panelName`;\n\t\tshapePanel -edit -l (localizedPanelLabel(\"Shape Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"posePanel\" (localizedPanelLabel(\"Pose Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tposePanel -edit -l (localizedPanelLabel(\"Pose Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"dynRelEdPanel\" (localizedPanelLabel(\"Dynamic Relationships\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Dynamic Relationships\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"relationshipPanel\" (localizedPanelLabel(\"Relationship Editor\")) `;\n" + + "\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Relationship Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"referenceEditorPanel\" (localizedPanelLabel(\"Reference Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Reference Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"dynPaintScriptedPanelType\" (localizedPanelLabel(\"Paint Effects\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Paint Effects\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"scriptEditorPanel\" (localizedPanelLabel(\"Script Editor\")) `;\n" + + "\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Script Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"profilerPanel\" (localizedPanelLabel(\"Profiler Tool\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Profiler Tool\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"contentBrowserPanel\" (localizedPanelLabel(\"Content Browser\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Content Browser\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"Stereo\" (localizedPanelLabel(\"Stereo\")) `;\n" + + "\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Stereo\")) -mbv $menusOkayInPanels $panelName;\n{ string $editorName = ($panelName+\"Editor\");\n stereoCameraView -e \n -camera \"|persp\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"wireframe\" \n -activeOnly 0\n -ignorePanZoom 0\n -wireframeOnShaded 0\n -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n -useDefaultMaterial 0\n -bufferMode \"double\" \n -twoSidedLighting 1\n -backfaceCulling 0\n -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n -lineWidth 1\n -textureAnisotropic 0\n -textureHilight 1\n -textureSampling 2\n" + + " -textureDisplay \"modulate\" \n -textureMaxSize 32768\n -fogging 0\n -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n -depthOfFieldPreview 1\n -maxConstantTransparency 1\n -objectFilterShowInHUD 1\n -isFiltered 0\n -colorResolution 4 4 \n -bumpResolution 4 4 \n -textureCompression 0\n -transparencyAlgorithm \"frontAndBackCull\" \n -transpInShadows 0\n -cullingOverride \"none\" \n -lowQualityLighting 0\n -maximumNumHardwareLights 0\n -occlusionCulling 0\n -shadingModel 0\n -useBaseRenderer 0\n -useReducedRenderer 0\n -smallObjectCulling 0\n -smallObjectThreshold -1 \n -interactiveDisableShadows 0\n" + + " -interactiveBackFaceCull 0\n -sortTransparent 1\n -controllers 1\n -nurbsCurves 1\n -nurbsSurfaces 1\n -polymeshes 1\n -subdivSurfaces 1\n -planes 1\n -lights 1\n -cameras 1\n -controlVertices 1\n -hulls 1\n -grid 1\n -imagePlane 1\n -joints 1\n -ikHandles 1\n -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n -nCloths 1\n -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n -locators 1\n -manipulators 1\n -pluginShapes 1\n -dimensions 1\n -handles 1\n -pivots 1\n -textures 1\n -strokes 1\n -motionTrails 1\n" + + " -clipGhosts 1\n -bluePencil 1\n -greasePencils 0\n -shadows 0\n -captureSequenceNumber -1\n -width 0\n -height 0\n -sceneRenderFilter 0\n -displayMode \"centerEye\" \n -viewColor 0 0 0 1 \n -useCustomBackground 1\n $editorName;\n stereoCameraView -e -viewSelected 0 $editorName; };\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\tif ($useSceneConfig) {\n string $configName = `getPanel -cwl (localizedPanelLabel(\"Current Layout\"))`;\n if (\"\" != $configName) {\n\t\t\tpanelConfiguration -edit -label (localizedPanelLabel(\"Current Layout\")) \n\t\t\t\t-userCreated false\n\t\t\t\t-defaultImage \"vacantCell.xP:/\"\n\t\t\t\t-image \"\"\n\t\t\t\t-sc false\n\t\t\t\t-configString \"global string $gMainPane; paneLayout -e -cn \\\"quad\\\" -ps 1 50 50 -ps 2 50 50 -ps 3 50 50 -ps 4 50 50 $gMainPane;\"\n\t\t\t\t-removeAllPanels\n\t\t\t\t-ap false\n\t\t\t\t\t(localizedPanelLabel(\"Top View\")) \n" + + "\t\t\t\t\t\"modelPanel\"\n" + + "\t\t\t\t\t\"$panelName = `modelPanel -unParent -l (localizedPanelLabel(\\\"Top View\\\")) -mbv $menusOkayInPanels `;\\n$editorName = $panelName;\\nmodelEditor -e \\n -camera \\\"|top\\\" \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 32768\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -bluePencil 1\\n -greasePencils 0\\n -shadows 0\\n -captureSequenceNumber -1\\n -width 477\\n -height 276\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName\"\n" + + "\t\t\t\t\t\"modelPanel -edit -l (localizedPanelLabel(\\\"Top View\\\")) -mbv $menusOkayInPanels $panelName;\\n$editorName = $panelName;\\nmodelEditor -e \\n -camera \\\"|top\\\" \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 32768\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -bluePencil 1\\n -greasePencils 0\\n -shadows 0\\n -captureSequenceNumber -1\\n -width 477\\n -height 276\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName\"\n" + + "\t\t\t\t-ap false\n\t\t\t\t\t(localizedPanelLabel(\"Persp View\")) \n\t\t\t\t\t\"modelPanel\"\n" + + "\t\t\t\t\t\"$panelName = `modelPanel -unParent -l (localizedPanelLabel(\\\"Persp View\\\")) -mbv $menusOkayInPanels `;\\n$editorName = $panelName;\\nmodelEditor -e \\n -cam `findStartUpCamera persp` \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 32768\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -bluePencil 1\\n -greasePencils 0\\n -shadows 0\\n -captureSequenceNumber -1\\n -width 476\\n -height 276\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName\"\n" + + "\t\t\t\t\t\"modelPanel -edit -l (localizedPanelLabel(\\\"Persp View\\\")) -mbv $menusOkayInPanels $panelName;\\n$editorName = $panelName;\\nmodelEditor -e \\n -cam `findStartUpCamera persp` \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 32768\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -bluePencil 1\\n -greasePencils 0\\n -shadows 0\\n -captureSequenceNumber -1\\n -width 476\\n -height 276\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName\"\n" + + "\t\t\t\t-ap false\n\t\t\t\t\t(localizedPanelLabel(\"Side View\")) \n\t\t\t\t\t\"modelPanel\"\n" + + "\t\t\t\t\t\"$panelName = `modelPanel -unParent -l (localizedPanelLabel(\\\"Side View\\\")) -mbv $menusOkayInPanels `;\\n$editorName = $panelName;\\nmodelEditor -e \\n -camera \\\"|side\\\" \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 32768\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -bluePencil 1\\n -greasePencils 0\\n -shadows 0\\n -captureSequenceNumber -1\\n -width 476\\n -height 276\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName\"\n" + + "\t\t\t\t\t\"modelPanel -edit -l (localizedPanelLabel(\\\"Side View\\\")) -mbv $menusOkayInPanels $panelName;\\n$editorName = $panelName;\\nmodelEditor -e \\n -camera \\\"|side\\\" \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 32768\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -bluePencil 1\\n -greasePencils 0\\n -shadows 0\\n -captureSequenceNumber -1\\n -width 476\\n -height 276\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName\"\n" + + "\t\t\t\t-ap false\n\t\t\t\t\t(localizedPanelLabel(\"Front View\")) \n\t\t\t\t\t\"modelPanel\"\n" + + "\t\t\t\t\t\"$panelName = `modelPanel -unParent -l (localizedPanelLabel(\\\"Front View\\\")) -mbv $menusOkayInPanels `;\\n$editorName = $panelName;\\nmodelEditor -e \\n -cam `findStartUpCamera front` \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 32768\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -bluePencil 1\\n -greasePencils 0\\n -shadows 0\\n -captureSequenceNumber -1\\n -width 477\\n -height 276\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName\"\n" + + "\t\t\t\t\t\"modelPanel -edit -l (localizedPanelLabel(\\\"Front View\\\")) -mbv $menusOkayInPanels $panelName;\\n$editorName = $panelName;\\nmodelEditor -e \\n -cam `findStartUpCamera front` \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 32768\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -bluePencil 1\\n -greasePencils 0\\n -shadows 0\\n -captureSequenceNumber -1\\n -width 477\\n -height 276\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName\"\n" + "\t\t\t\t$configName;\n\n setNamedPanelLayout (localizedPanelLabel(\"Current Layout\"));\n }\n\n panelHistory -e -clear mainPanelHistory;\n sceneUIReplacement -clear;\n\t}\n\n\ngrid -spacing 5 -size 12 -divisions 5 -displayAxes yes -displayGridLines yes -displayDivisionLines yes -displayPerspectiveLabels no -displayOrthographicLabels no -displayAxesBold yes -perspectiveLabelPosition axis -orthographicLabelPosition edge;\nviewManip -drawCompass 0 -compassAngle 0 -frontParameters \"\" -homeParameters \"\" -selectionLockParameters \"\";\n}\n"); setAttr ".st" 3; createNode script -n "sceneConfigurationScriptNode"; @@ -273,6 +285,7 @@ createNode aiAOVDriver -s -n "defaultArnoldDisplayDriver"; createNode objectSet -n "workfileMain"; rename -uid "3C9B5D6F-4579-8E3B-5B7D-4C88865A1C68"; addAttr -ci true -sn "cbId" -ln "cbId" -dt "string"; + addAttr -ci true -sn "instance_id" -ln "instance_id" -dt "string"; addAttr -ci true -sn "id" -ln "id" -dt "string"; addAttr -ci true -sn "family" -ln "family" -dt "string"; addAttr -ci true -sn "subset" -ln "subset" -dt "string"; @@ -281,13 +294,14 @@ createNode objectSet -n "workfileMain"; addAttr -ci true -sn "variant" -ln "variant" -dt "string"; addAttr -ci true -sn "asset" -ln "asset" -dt "string"; addAttr -ci true -sn "task" -ln "task" -dt "string"; - addAttr -ci true -sn "instance_id" -ln "instance_id" -dt "string"; addAttr -ci true -sn "publish_attributes" -ln "publish_attributes" -dt "string"; - addAttr -ci true -sn "__creator_attributes_keys" -ln "__creator_attributes_keys" + addAttr -ci true -sn "creator_attributes" -ln "creator_attributes" -dt "string"; + addAttr -ci true -sn "__creator_attributes_keys" -ln "__creator_attributes_keys" -dt "string"; setAttr ".ihi" 0; setAttr ".hio" yes; setAttr ".cbId" -type "string" "60df31e2be2b48bd3695c056:30d256dac64c"; + setAttr ".instance_id" -type "string" "911dc92a-ad29-41e5-bbf9-733d56174fb9"; setAttr ".id" -type "string" "pyblish.avalon.instance"; setAttr ".family" -type "string" "workfile"; setAttr ".subset" -type "string" "workfileTest_task"; @@ -296,16 +310,9 @@ createNode objectSet -n "workfileMain"; setAttr ".variant" -type "string" "Main"; setAttr ".asset" -type "string" "test_asset"; setAttr ".task" -type "string" "test_task"; - setAttr ".instance_id" -type "string" "911dc92a-ad29-41e5-bbf9-733d56174fb9"; - setAttr ".publish_attributes" -type "string" "{\"ExtractImportReference\": {\"active\": false}}"; + setAttr ".publish_attributes" -type "string" "{\"ValidateInstanceInContext\": {\"active\": true}, \"ExtractImportReference\": {\"active\": false}}"; + setAttr ".creator_attributes" -type "string" "{}"; setAttr ".__creator_attributes_keys" -type "string" ""; -createNode objectSet -n "renderingMain"; - rename -uid "8A999C2F-4922-B15D-8D5C-45A16465B69F"; - addAttr -ci true -sn "pre_creator_identifier" -ln "pre_creator_identifier" -dt "string"; - addAttr -ci true -sn "cbId" -ln "cbId" -dt "string"; - setAttr ".ihi" 0; - setAttr ".pre_creator_identifier" -type "string" "io.openpype.creators.maya.renderlayer"; - setAttr ".cbId" -type "string" "60df31e2be2b48bd3695c056:042447475732"; createNode renderSetupLayer -n "Main"; rename -uid "2202E438-4CEF-F64E-737C-F48C65E31126"; addAttr -ci true -sn "es" -ln "expandedState" -min 0 -max 1 -at "bool"; @@ -320,55 +327,6 @@ createNode collection -n "defaultCollection"; createNode simpleSelector -n "defaultCollectionSelector"; rename -uid "7CA2F6D8-483C-B020-BC03-EF9563A52163"; setAttr ".pat" -type "string" "*"; -createNode objectSet -n "_renderingMain:Main"; - rename -uid "1EEC3A3B-49CF-0C79-5340-39805174FB8A"; - addAttr -s false -ci true -sn "renderlayer" -ln "renderlayer" -at "message"; - addAttr -ci true -sn "id" -ln "id" -dt "string"; - addAttr -ci true -sn "family" -ln "family" -dt "string"; - addAttr -ci true -sn "subset" -ln "subset" -dt "string"; - addAttr -ci true -sn "active" -ln "active" -min 0 -max 1 -at "bool"; - addAttr -ci true -sn "creator_identifier" -ln "creator_identifier" -dt "string"; - addAttr -ci true -sn "variant" -ln "variant" -dt "string"; - addAttr -ci true -sn "asset" -ln "asset" -dt "string"; - addAttr -ci true -sn "task" -ln "task" -dt "string"; - addAttr -ci true -sn "instance_id" -ln "instance_id" -dt "string"; - addAttr -ci true -sn "review" -ln "review" -min 0 -max 1 -at "bool"; - addAttr -ci true -sn "extendFrames" -ln "extendFrames" -min 0 -max 1 -at "bool"; - addAttr -ci true -sn "overrideExistingFrame" -ln "overrideExistingFrame" -min 0 - -max 1 -at "bool"; - addAttr -ci true -sn "tileRendering" -ln "tileRendering" -min 0 -max 1 -at "bool"; - addAttr -ci true -sn "tilesX" -ln "tilesX" -at "long"; - addAttr -ci true -sn "tilesY" -ln "tilesY" -at "long"; - addAttr -ci true -sn "convertToScanline" -ln "convertToScanline" -min 0 -max 1 -at "bool"; - addAttr -ci true -sn "useReferencedAovs" -ln "useReferencedAovs" -min 0 -max 1 -at "bool"; - addAttr -ci true -sn "renderSetupIncludeLights" -ln "renderSetupIncludeLights" -min - 0 -max 1 -at "bool"; - addAttr -ci true -sn "publish_attributes" -ln "publish_attributes" -dt "string"; - addAttr -ci true -sn "__creator_attributes_keys" -ln "__creator_attributes_keys" - -dt "string"; - addAttr -ci true -sn "cbId" -ln "cbId" -dt "string"; - setAttr ".ihi" 0; - setAttr ".id" -type "string" "pyblish.avalon.instance"; - setAttr ".family" -type "string" "renderlayer"; - setAttr ".subset" -type "string" "renderMain"; - setAttr -cb on ".active" yes; - setAttr ".creator_identifier" -type "string" "io.openpype.creators.maya.renderlayer"; - setAttr ".variant" -type "string" "Main"; - setAttr ".asset" -type "string" "test_asset"; - setAttr ".task" -type "string" "test_task"; - setAttr ".instance_id" -type "string" "8a9cfb85-9602-4e5e-a4bc-27a2986bae7f"; - setAttr -cb on ".review" yes; - setAttr -cb on ".extendFrames"; - setAttr -cb on ".overrideExistingFrame" yes; - setAttr -cb on ".tileRendering"; - setAttr -cb on ".tilesX" 2; - setAttr -cb on ".tilesY" 2; - setAttr -cb on ".convertToScanline"; - setAttr -cb on ".useReferencedAovs"; - setAttr -cb on ".renderSetupIncludeLights" yes; - setAttr ".publish_attributes" -type "string" "{\"CollectDeadlinePools\": {\"primaryPool\": \"\", \"secondaryPool\": \"\"}, \"ValidateDeadlinePools\": {\"active\": true}, \"ValidateFrameRange\": {\"active\": true}, \"ExtractImportReference\": {\"active\": false}, \"MayaSubmitDeadline\": {\"priority\": 50, \"chunkSize\": 1, \"machineList\": \"\", \"whitelist\": false, \"tile_priority\": 50, \"strict_error_checking\": true}, \"ProcessSubmittedJobOnFarm\": {\"publishJobState\": \"Active\"}}"; - setAttr ".__creator_attributes_keys" -type "string" "review,extendFrames,overrideExistingFrame,tileRendering,tilesX,tilesY,convertToScanline,useReferencedAovs,renderSetupIncludeLights"; - setAttr ".cbId" -type "string" "60df31e2be2b48bd3695c056:69960f336351"; select -ne :time1; setAttr ".o" 1001; setAttr ".unw" 1001; @@ -435,8 +393,6 @@ select -ne :defaultColorMgtGlobals; select -ne :hardwareRenderGlobals; setAttr ".ctrs" 256; setAttr ".btrs" 512; -select -ne :ikSystem; - setAttr -s 4 ".sol"; connectAttr "rs_Main.ri" ":persp.rlio[0]"; connectAttr "rs_Main.ri" ":top.rlio[0]"; connectAttr "rs_Main.ri" ":front.rlio[0]"; @@ -459,7 +415,6 @@ connectAttr ":defaultArnoldDisplayDriver.msg" ":defaultArnoldRenderOptions.drive -na; connectAttr ":defaultArnoldFilter.msg" ":defaultArnoldRenderOptions.filt"; connectAttr ":defaultArnoldDriver.msg" ":defaultArnoldRenderOptions.drvr"; -connectAttr "_renderingMain:Main.msg" "renderingMain.dnsm" -na; connectAttr "rs_Main.msg" "Main.lrl"; connectAttr "renderSetup.lit" "Main.pls"; connectAttr "defaultCollection.msg" "Main.cl"; @@ -468,9 +423,8 @@ connectAttr "renderLayerManager.rlmi[1]" "rs_Main.rlid"; connectAttr "defaultCollectionSelector.c" "defaultCollection.sel"; connectAttr "Main.lit" "defaultCollection.pls"; connectAttr "Main.nic" "defaultCollection.pic"; -connectAttr "Main.msg" "_renderingMain:Main.renderlayer"; connectAttr "defaultRenderLayer.msg" ":defaultRenderingList1.r" -na; connectAttr "rs_Main.msg" ":defaultRenderingList1.r" -na; connectAttr "pSphere1_GEOShape1.iog" ":initialShadingGroup.dsm" -na; connectAttr "pDiscShape1.iog" ":initialShadingGroup.dsm" -na; -// End of test_project_test_asset_test_task_v001.ma +// End of test_project_test_asset_test_task_v002.ma diff --git a/tests/integration/hosts/maya/test_publish_in_maya/expected/test_project/test_asset/work/test_task/test_project_test_asset_test_task_v002.ma b/tests/integration/hosts/maya/test_publish_in_maya/expected/test_project/test_asset/work/test_task/test_project_test_asset_test_task_v002.ma index 57bcfdae5b..6bd334466a 100644 --- a/tests/integration/hosts/maya/test_publish_in_maya/expected/test_project/test_asset/work/test_task/test_project_test_asset_test_task_v002.ma +++ b/tests/integration/hosts/maya/test_publish_in_maya/expected/test_project/test_asset/work/test_task/test_project_test_asset_test_task_v002.ma @@ -1,22 +1,22 @@ -//Maya ASCII 2024 scene +//Maya ASCII 2023 scene //Name: test_project_test_asset_test_task_v002.ma -//Last modified: Tue, Oct 31, 2023 08:24:19 AM +//Last modified: Thu, Dec 07, 2023 03:55:12 PM //Codeset: 1252 -requires maya "2024"; -requires -nodeType "polyDisc" "modelingToolkit" "0.0.0.0"; -requires "stereoCamera" "10.0"; -requires -nodeType "aiOptions" -nodeType "aiAOVDriver" -nodeType "aiAOVFilter" "mtoa" "5.3.0"; +requires maya "2023"; requires -nodeType "simpleSelector" -nodeType "renderSetupLayer" -nodeType "renderSetup" -nodeType "collection" "renderSetup.py" "1.0"; requires "stereoCamera" "10.0"; +requires -nodeType "aiOptions" -nodeType "aiAOVDriver" -nodeType "aiAOVFilter" "mtoa" "5.2.1.1"; +requires -nodeType "polyDisc" "modelingToolkit" "0.0.0.0"; +requires "stereoCamera" "10.0"; currentUnit -l centimeter -a degree -t pal; fileInfo "application" "maya"; -fileInfo "product" "Maya 2024"; -fileInfo "version" "2024"; -fileInfo "cutIdentifier" "202302170737-4500172811"; +fileInfo "product" "Maya 2023"; +fileInfo "version" "2023"; +fileInfo "cutIdentifier" "202211021031-847a9f9623"; fileInfo "osv" "Windows 10 Pro v2009 (Build: 19045)"; fileInfo "license" "education"; -fileInfo "UUID" "6C5CC57A-4CC3-7373-4411-B3B80BC40815"; +fileInfo "UUID" "7CC7E6D5-4F37-DB90-8A84-8493449019BF"; fileInfo "OpenPypeContext" "eyJwdWJsaXNoX2F0dHJpYnV0ZXMiOiB7IlZhbGlkYXRlQ29udGFpbmVycyI6IHsiYWN0aXZlIjogdHJ1ZX19fQ=="; createNode transform -s -n "persp"; rename -uid "D52C935B-47C9-D868-A875-D799DD17B3A1"; @@ -142,20 +142,20 @@ createNode camera -n "perspShape1" -p "persp1"; setAttr ".hc" -type "string" "viewSet -p %camera"; setAttr ".dr" yes; createNode lightLinker -s -n "lightLinker1"; - rename -uid "D0EBF10B-4952-5C9F-42A8-D6A660FF173F"; + rename -uid "FBA13844-432C-E5C2-040E-A0925F2F0B8F"; setAttr -s 2 ".lnk"; setAttr -s 2 ".slnk"; createNode shapeEditorManager -n "shapeEditorManager"; - rename -uid "30DE6463-4107-330C-8FE3-4EA1C402A632"; + rename -uid "E3FDBA44-4665-FFBF-74F3-BDBF4F8F7B32"; createNode poseInterpolatorManager -n "poseInterpolatorManager"; - rename -uid "64911358-4E6A-5ACC-75A4-4F965FCD606E"; + rename -uid "8834BA6F-47BE-8E76-4510-E2A7F3525077"; createNode displayLayerManager -n "layerManager"; - rename -uid "B3C9D791-45C2-E529-2A9A-9B88F2D5E17E"; + rename -uid "427D260A-43FC-DC22-4E80-46A0E90839B2"; createNode displayLayer -n "defaultLayer"; rename -uid "4A776D1B-401F-7069-1C74-A7AAE84CEE03"; setAttr ".ufem" -type "stringArray" 0 ; createNode renderLayerManager -n "renderLayerManager"; - rename -uid "68F97550-4CB6-1D4A-99B0-CCA5DBE5D6B1"; + rename -uid "F1B8B519-43D1-5DE5-00F6-42A9514335E8"; setAttr -s 2 ".rlmi[1]" 1; setAttr -s 2 ".rlmi"; createNode renderLayer -n "defaultRenderLayer"; @@ -168,6 +168,7 @@ createNode polySphere -n "polySphere1"; createNode objectSet -n "modelMain"; rename -uid "A76AD4F8-4CF5-AA0D-4E98-BABEE6454CC3"; addAttr -ci true -sn "cbId" -ln "cbId" -dt "string"; + addAttr -ci true -sn "instance_id" -ln "instance_id" -dt "string"; addAttr -ci true -sn "id" -ln "id" -dt "string"; addAttr -ci true -sn "family" -ln "family" -dt "string"; addAttr -ci true -sn "subset" -ln "subset" -dt "string"; @@ -176,18 +177,19 @@ createNode objectSet -n "modelMain"; addAttr -ci true -sn "variant" -ln "variant" -dt "string"; addAttr -ci true -sn "asset" -ln "asset" -dt "string"; addAttr -ci true -sn "task" -ln "task" -dt "string"; - addAttr -ci true -sn "instance_id" -ln "instance_id" -dt "string"; addAttr -ci true -sn "writeColorSets" -ln "writeColorSets" -min 0 -max 1 -at "bool"; addAttr -ci true -sn "writeFaceSets" -ln "writeFaceSets" -min 0 -max 1 -at "bool"; - addAttr -ci true -sn "includeParentHierarchy" -ln "includeParentHierarchy" -min + addAttr -ci true -sn "includeParentHierarchy" -ln "includeParentHierarchy" -min 0 -max 1 -at "bool"; addAttr -ci true -sn "attr" -ln "attr" -dt "string"; addAttr -ci true -sn "attrPrefix" -ln "attrPrefix" -dt "string"; addAttr -ci true -sn "publish_attributes" -ln "publish_attributes" -dt "string"; - addAttr -ci true -sn "__creator_attributes_keys" -ln "__creator_attributes_keys" + addAttr -ci true -sn "creator_attributes" -ln "creator_attributes" -dt "string"; + addAttr -ci true -sn "__creator_attributes_keys" -ln "__creator_attributes_keys" -dt "string"; setAttr ".ihi" 0; setAttr ".cbId" -type "string" "60df31e2be2b48bd3695c056:7364ea6776c9"; + setAttr ".instance_id" -type "string" "6889d3db-b813-43db-96de-9ba555dc4472"; setAttr ".id" -type "string" "pyblish.avalon.instance"; setAttr ".family" -type "string" "model"; setAttr ".subset" -type "string" "modelMain"; @@ -196,59 +198,68 @@ createNode objectSet -n "modelMain"; setAttr ".variant" -type "string" "Main"; setAttr ".asset" -type "string" "test_asset"; setAttr ".task" -type "string" "test_task"; - setAttr ".instance_id" -type "string" "6889d3db-b813-43db-96de-9ba555dc4472"; setAttr -cb on ".writeColorSets"; setAttr -cb on ".writeFaceSets"; setAttr -cb on ".includeParentHierarchy"; setAttr ".attr" -type "string" ""; setAttr ".attrPrefix" -type "string" ""; - setAttr ".publish_attributes" -type "string" "{\"ValidateNodeIDsRelated\": {\"active\": true}, \"ValidateTransformNamingSuffix\": {\"active\": true}, \"ValidateColorSets\": {\"active\": true}, \"ValidateMeshArnoldAttributes\": {\"active\": true}, \"ValidateMeshHasUVs\": {\"active\": true}, \"ValidateMeshNonZeroEdgeLength\": {\"active\": true}, \"ExtractModel\": {\"active\": true}}"; + setAttr ".publish_attributes" -type "string" "{\"ValidateNodeIDsRelated\": {\"active\": true}, \"ValidateInstanceInContext\": {\"active\": true}, \"ValidateTransformNamingSuffix\": {\"active\": true}, \"ValidateColorSets\": {\"active\": true}, \"ValidateMeshHasUVs\": {\"active\": true}, \"ValidateMeshNonZeroEdgeLength\": {\"active\": true}, \"ExtractModel\": {\"active\": true}, \"ValidateMeshArnoldAttributes\": {\"active\": true}}"; + setAttr ".creator_attributes" -type "string" "{}"; setAttr ".__creator_attributes_keys" -type "string" "writeColorSets,writeFaceSets,includeParentHierarchy,attr,attrPrefix"; createNode script -n "uiConfigurationScriptNode"; rename -uid "4B7AFB53-452E-E870-63E1-CCA1DD6EAF13"; setAttr ".b" -type "string" ( "// Maya Mel UI Configuration File.\n//\n// This script is machine generated. Edit at your own risk.\n//\n//\n\nglobal string $gMainPane;\nif (`paneLayout -exists $gMainPane`) {\n\n\tglobal int $gUseScenePanelConfig;\n\tint $useSceneConfig = $gUseScenePanelConfig;\n\tint $nodeEditorPanelVisible = stringArrayContains(\"nodeEditorPanel1\", `getPanel -vis`);\n\tint $nodeEditorWorkspaceControlOpen = (`workspaceControl -exists nodeEditorPanel1Window` && `workspaceControl -q -visible nodeEditorPanel1Window`);\n\tint $menusOkayInPanels = `optionVar -q allowMenusInPanels`;\n\tint $nVisPanes = `paneLayout -q -nvp $gMainPane`;\n\tint $nPanes = 0;\n\tstring $editorName;\n\tstring $panelName;\n\tstring $itemFilterName;\n\tstring $panelConfig;\n\n\t//\n\t// get current state of the UI\n\t//\n\tsceneUIReplacement -update $gMainPane;\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"modelPanel\" (localizedPanelLabel(\"Top View\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tmodelPanel -edit -l (localizedPanelLabel(\"Top View\")) -mbv $menusOkayInPanels $panelName;\n" - + "\t\t$editorName = $panelName;\n modelEditor -e \n -camera \"|top\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"smoothShaded\" \n -activeOnly 0\n -ignorePanZoom 0\n -wireframeOnShaded 0\n -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n -useDefaultMaterial 0\n -bufferMode \"double\" \n -twoSidedLighting 0\n -backfaceCulling 0\n -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n -lineWidth 1\n -textureAnisotropic 0\n -textureHilight 1\n -textureSampling 2\n -textureDisplay \"modulate\" \n -textureMaxSize 16384\n -fogging 0\n -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n" + + "\t\t$editorName = $panelName;\n modelEditor -e \n -camera \"|top\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"smoothShaded\" \n -activeOnly 0\n -ignorePanZoom 0\n -wireframeOnShaded 0\n -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n -useDefaultMaterial 0\n -bufferMode \"double\" \n -twoSidedLighting 0\n -backfaceCulling 0\n -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n -lineWidth 1\n -textureAnisotropic 0\n -textureHilight 1\n -textureSampling 2\n -textureDisplay \"modulate\" \n -textureMaxSize 32768\n -fogging 0\n -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n" + " -depthOfFieldPreview 1\n -maxConstantTransparency 1\n -rendererName \"vp2Renderer\" \n -objectFilterShowInHUD 1\n -isFiltered 0\n -colorResolution 256 256 \n -bumpResolution 512 512 \n -textureCompression 0\n -transparencyAlgorithm \"frontAndBackCull\" \n -transpInShadows 0\n -cullingOverride \"none\" \n -lowQualityLighting 0\n -maximumNumHardwareLights 1\n -occlusionCulling 0\n -shadingModel 0\n -useBaseRenderer 0\n -useReducedRenderer 0\n -smallObjectCulling 0\n -smallObjectThreshold -1 \n -interactiveDisableShadows 0\n -interactiveBackFaceCull 0\n -sortTransparent 1\n -controllers 1\n -nurbsCurves 1\n -nurbsSurfaces 1\n -polymeshes 1\n -subdivSurfaces 1\n -planes 1\n -lights 1\n -cameras 1\n -controlVertices 1\n" - + " -hulls 1\n -grid 1\n -imagePlane 1\n -joints 1\n -ikHandles 1\n -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n -nCloths 1\n -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n -locators 1\n -manipulators 1\n -pluginShapes 1\n -dimensions 1\n -handles 1\n -pivots 1\n -textures 1\n -strokes 1\n -motionTrails 1\n -clipGhosts 1\n -bluePencil 1\n -greasePencils 0\n -excludeObjectPreset \"All\" \n -shadows 0\n -captureSequenceNumber -1\n -width 1\n -height 1\n -sceneRenderFilter 0\n $editorName;\n modelEditor -e -viewSelected 0 $editorName;\n modelEditor -e \n -pluginObjects \"gpuCacheDisplayFilter\" 1 \n $editorName;\n" - + "\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"modelPanel\" (localizedPanelLabel(\"Side View\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tmodelPanel -edit -l (localizedPanelLabel(\"Side View\")) -mbv $menusOkayInPanels $panelName;\n\t\t$editorName = $panelName;\n modelEditor -e \n -camera \"|side\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"smoothShaded\" \n -activeOnly 0\n -ignorePanZoom 0\n -wireframeOnShaded 0\n -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n -useDefaultMaterial 0\n -bufferMode \"double\" \n -twoSidedLighting 0\n -backfaceCulling 0\n -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n -lineWidth 1\n -textureAnisotropic 0\n" - + " -textureHilight 1\n -textureSampling 2\n -textureDisplay \"modulate\" \n -textureMaxSize 16384\n -fogging 0\n -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n -depthOfFieldPreview 1\n -maxConstantTransparency 1\n -rendererName \"vp2Renderer\" \n -objectFilterShowInHUD 1\n -isFiltered 0\n -colorResolution 256 256 \n -bumpResolution 512 512 \n -textureCompression 0\n -transparencyAlgorithm \"frontAndBackCull\" \n -transpInShadows 0\n -cullingOverride \"none\" \n -lowQualityLighting 0\n -maximumNumHardwareLights 1\n -occlusionCulling 0\n -shadingModel 0\n -useBaseRenderer 0\n -useReducedRenderer 0\n -smallObjectCulling 0\n -smallObjectThreshold -1 \n -interactiveDisableShadows 0\n" - + " -interactiveBackFaceCull 0\n -sortTransparent 1\n -controllers 1\n -nurbsCurves 1\n -nurbsSurfaces 1\n -polymeshes 1\n -subdivSurfaces 1\n -planes 1\n -lights 1\n -cameras 1\n -controlVertices 1\n -hulls 1\n -grid 1\n -imagePlane 1\n -joints 1\n -ikHandles 1\n -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n -nCloths 1\n -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n -locators 1\n -manipulators 1\n -pluginShapes 1\n -dimensions 1\n -handles 1\n -pivots 1\n -textures 1\n -strokes 1\n -motionTrails 1\n -clipGhosts 1\n -bluePencil 1\n -greasePencils 0\n -excludeObjectPreset \"All\" \n" - + " -shadows 0\n -captureSequenceNumber -1\n -width 1\n -height 1\n -sceneRenderFilter 0\n $editorName;\n modelEditor -e -viewSelected 0 $editorName;\n modelEditor -e \n -pluginObjects \"gpuCacheDisplayFilter\" 1 \n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"modelPanel\" (localizedPanelLabel(\"Front View\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tmodelPanel -edit -l (localizedPanelLabel(\"Front View\")) -mbv $menusOkayInPanels $panelName;\n\t\t$editorName = $panelName;\n modelEditor -e \n -camera \"|front\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"smoothShaded\" \n -activeOnly 0\n -ignorePanZoom 0\n -wireframeOnShaded 0\n -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n" - + " -useDefaultMaterial 0\n -bufferMode \"double\" \n -twoSidedLighting 0\n -backfaceCulling 0\n -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n -lineWidth 1\n -textureAnisotropic 0\n -textureHilight 1\n -textureSampling 2\n -textureDisplay \"modulate\" \n -textureMaxSize 16384\n -fogging 0\n -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n -depthOfFieldPreview 1\n -maxConstantTransparency 1\n -rendererName \"vp2Renderer\" \n -objectFilterShowInHUD 1\n -isFiltered 0\n -colorResolution 256 256 \n -bumpResolution 512 512 \n -textureCompression 0\n -transparencyAlgorithm \"frontAndBackCull\" \n -transpInShadows 0\n" - + " -cullingOverride \"none\" \n -lowQualityLighting 0\n -maximumNumHardwareLights 1\n -occlusionCulling 0\n -shadingModel 0\n -useBaseRenderer 0\n -useReducedRenderer 0\n -smallObjectCulling 0\n -smallObjectThreshold -1 \n -interactiveDisableShadows 0\n -interactiveBackFaceCull 0\n -sortTransparent 1\n -controllers 1\n -nurbsCurves 1\n -nurbsSurfaces 1\n -polymeshes 1\n -subdivSurfaces 1\n -planes 1\n -lights 1\n -cameras 1\n -controlVertices 1\n -hulls 1\n -grid 1\n -imagePlane 1\n -joints 1\n -ikHandles 1\n -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n -nCloths 1\n -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n" - + " -locators 1\n -manipulators 1\n -pluginShapes 1\n -dimensions 1\n -handles 1\n -pivots 1\n -textures 1\n -strokes 1\n -motionTrails 1\n -clipGhosts 1\n -bluePencil 1\n -greasePencils 0\n -excludeObjectPreset \"All\" \n -shadows 0\n -captureSequenceNumber -1\n -width 1\n -height 1\n -sceneRenderFilter 0\n $editorName;\n modelEditor -e -viewSelected 0 $editorName;\n modelEditor -e \n -pluginObjects \"gpuCacheDisplayFilter\" 1 \n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"modelPanel\" (localizedPanelLabel(\"Persp View\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tmodelPanel -edit -l (localizedPanelLabel(\"Persp View\")) -mbv $menusOkayInPanels $panelName;\n\t\t$editorName = $panelName;\n modelEditor -e \n" - + " -camera \"|persp\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"smoothShaded\" \n -activeOnly 0\n -ignorePanZoom 0\n -wireframeOnShaded 0\n -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n -useDefaultMaterial 0\n -bufferMode \"double\" \n -twoSidedLighting 0\n -backfaceCulling 0\n -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n -lineWidth 1\n -textureAnisotropic 0\n -textureHilight 1\n -textureSampling 2\n -textureDisplay \"modulate\" \n -textureMaxSize 16384\n -fogging 0\n -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n -depthOfFieldPreview 1\n" - + " -maxConstantTransparency 1\n -rendererName \"vp2Renderer\" \n -objectFilterShowInHUD 1\n -isFiltered 0\n -colorResolution 256 256 \n -bumpResolution 512 512 \n -textureCompression 0\n -transparencyAlgorithm \"frontAndBackCull\" \n -transpInShadows 0\n -cullingOverride \"none\" \n -lowQualityLighting 0\n -maximumNumHardwareLights 1\n -occlusionCulling 0\n -shadingModel 0\n -useBaseRenderer 0\n -useReducedRenderer 0\n -smallObjectCulling 0\n -smallObjectThreshold -1 \n -interactiveDisableShadows 0\n -interactiveBackFaceCull 0\n -sortTransparent 1\n -controllers 1\n -nurbsCurves 1\n -nurbsSurfaces 1\n -polymeshes 1\n -subdivSurfaces 1\n -planes 1\n -lights 1\n -cameras 1\n -controlVertices 1\n -hulls 1\n -grid 1\n" - + " -imagePlane 1\n -joints 1\n -ikHandles 1\n -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n -nCloths 1\n -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n -locators 1\n -manipulators 1\n -pluginShapes 1\n -dimensions 1\n -handles 1\n -pivots 1\n -textures 1\n -strokes 1\n -motionTrails 1\n -clipGhosts 1\n -bluePencil 1\n -greasePencils 0\n -excludeObjectPreset \"All\" \n -shadows 0\n -captureSequenceNumber -1\n -width 1319\n -height 718\n -sceneRenderFilter 0\n $editorName;\n modelEditor -e -viewSelected 0 $editorName;\n modelEditor -e \n -pluginObjects \"gpuCacheDisplayFilter\" 1 \n $editorName;\n\t\tif (!$useSceneConfig) {\n" - + "\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"outlinerPanel\" (localizedPanelLabel(\"ToggledOutliner\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\toutlinerPanel -edit -l (localizedPanelLabel(\"ToggledOutliner\")) -mbv $menusOkayInPanels $panelName;\n\t\t$editorName = $panelName;\n outlinerEditor -e \n -docTag \"isolOutln_fromSeln\" \n -showShapes 0\n -showAssignedMaterials 0\n -showTimeEditor 1\n -showReferenceNodes 1\n -showReferenceMembers 1\n -showAttributes 0\n -showConnected 0\n -showAnimCurvesOnly 0\n -showMuteInfo 0\n -organizeByLayer 1\n -organizeByClip 1\n -showAnimLayerWeight 1\n -autoExpandLayers 1\n -autoExpand 0\n -showDagOnly 1\n -showAssets 1\n -showContainedOnly 1\n -showPublishedAsConnected 0\n -showParentContainers 0\n -showContainerContents 1\n" - + " -ignoreDagHierarchy 0\n -expandConnections 0\n -showUpstreamCurves 1\n -showUnitlessCurves 1\n -showCompounds 1\n -showLeafs 1\n -showNumericAttrsOnly 0\n -highlightActive 1\n -autoSelectNewObjects 0\n -doNotSelectNewObjects 0\n -dropIsParent 1\n -transmitFilters 0\n -setFilter \"defaultSetFilter\" \n -showSetMembers 1\n -allowMultiSelection 1\n -alwaysToggleSelect 0\n -directSelect 0\n -isSet 0\n -isSetMember 0\n -showUfeItems 1\n -displayMode \"DAG\" \n -expandObjects 0\n -setsIgnoreFilters 1\n -containersIgnoreFilters 0\n -editAttrName 0\n -showAttrValues 0\n -highlightSecondary 0\n -showUVAttrsOnly 0\n -showTextureNodesOnly 0\n -attrAlphaOrder \"default\" \n -animLayerFilterOptions \"allAffecting\" \n -sortOrder \"none\" \n" - + " -longNames 0\n -niceNames 1\n -selectCommand \"print(\\\"\\\")\" \n -showNamespace 1\n -showPinIcons 0\n -mapMotionTrails 0\n -ignoreHiddenAttribute 0\n -ignoreOutlinerColor 0\n -renderFilterVisible 0\n -renderFilterIndex 0\n -selectionOrder \"chronological\" \n -expandAttribute 0\n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"outlinerPanel\" (localizedPanelLabel(\"Outliner\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\toutlinerPanel -edit -l (localizedPanelLabel(\"Outliner\")) -mbv $menusOkayInPanels $panelName;\n\t\t$editorName = $panelName;\n outlinerEditor -e \n -showShapes 0\n -showAssignedMaterials 0\n -showTimeEditor 1\n -showReferenceNodes 0\n -showReferenceMembers 0\n -showAttributes 0\n -showConnected 0\n" - + " -showAnimCurvesOnly 0\n -showMuteInfo 0\n -organizeByLayer 1\n -organizeByClip 1\n -showAnimLayerWeight 1\n -autoExpandLayers 1\n -autoExpand 0\n -showDagOnly 1\n -showAssets 1\n -showContainedOnly 1\n -showPublishedAsConnected 0\n -showParentContainers 0\n -showContainerContents 1\n -ignoreDagHierarchy 0\n -expandConnections 0\n -showUpstreamCurves 1\n -showUnitlessCurves 1\n -showCompounds 1\n -showLeafs 1\n -showNumericAttrsOnly 0\n -highlightActive 1\n -autoSelectNewObjects 0\n -doNotSelectNewObjects 0\n -dropIsParent 1\n -transmitFilters 0\n -setFilter \"defaultSetFilter\" \n -showSetMembers 1\n -allowMultiSelection 1\n -alwaysToggleSelect 0\n -directSelect 0\n -showUfeItems 1\n -displayMode \"DAG\" \n" - + " -expandObjects 0\n -setsIgnoreFilters 1\n -containersIgnoreFilters 0\n -editAttrName 0\n -showAttrValues 0\n -highlightSecondary 0\n -showUVAttrsOnly 0\n -showTextureNodesOnly 0\n -attrAlphaOrder \"default\" \n -animLayerFilterOptions \"allAffecting\" \n -sortOrder \"none\" \n -longNames 0\n -niceNames 1\n -showNamespace 1\n -showPinIcons 0\n -mapMotionTrails 0\n -ignoreHiddenAttribute 0\n -ignoreOutlinerColor 0\n -renderFilterVisible 0\n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"graphEditor\" (localizedPanelLabel(\"Graph Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Graph Editor\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = ($panelName+\"OutlineEd\");\n" - + " outlinerEditor -e \n -showShapes 1\n -showAssignedMaterials 0\n -showTimeEditor 1\n -showReferenceNodes 0\n -showReferenceMembers 0\n -showAttributes 1\n -showConnected 1\n -showAnimCurvesOnly 1\n -showMuteInfo 0\n -organizeByLayer 1\n -organizeByClip 1\n -showAnimLayerWeight 1\n -autoExpandLayers 1\n -autoExpand 1\n -showDagOnly 0\n -showAssets 1\n -showContainedOnly 0\n -showPublishedAsConnected 0\n -showParentContainers 0\n -showContainerContents 0\n -ignoreDagHierarchy 0\n -expandConnections 1\n -showUpstreamCurves 1\n -showUnitlessCurves 1\n -showCompounds 0\n -showLeafs 1\n -showNumericAttrsOnly 1\n -highlightActive 0\n" - + " -autoSelectNewObjects 1\n -doNotSelectNewObjects 0\n -dropIsParent 1\n -transmitFilters 1\n -setFilter \"0\" \n -showSetMembers 0\n -allowMultiSelection 1\n -alwaysToggleSelect 0\n -directSelect 0\n -showUfeItems 1\n -displayMode \"DAG\" \n -expandObjects 0\n -setsIgnoreFilters 1\n -containersIgnoreFilters 0\n -editAttrName 0\n -showAttrValues 0\n -highlightSecondary 0\n -showUVAttrsOnly 0\n -showTextureNodesOnly 0\n -attrAlphaOrder \"default\" \n -animLayerFilterOptions \"allAffecting\" \n -sortOrder \"none\" \n -longNames 0\n -niceNames 1\n -showNamespace 1\n -showPinIcons 1\n -mapMotionTrails 1\n -ignoreHiddenAttribute 0\n -ignoreOutlinerColor 0\n" - + " -renderFilterVisible 0\n $editorName;\n\n\t\t\t$editorName = ($panelName+\"GraphEd\");\n animCurveEditor -e \n -displayValues 0\n -snapTime \"integer\" \n -snapValue \"none\" \n -showPlayRangeShades \"on\" \n -lockPlayRangeShades \"off\" \n -smoothness \"fine\" \n -resultSamples 1.041667\n -resultScreenSamples 0\n -resultUpdate \"delayed\" \n -showUpstreamCurves 1\n -keyMinScale 1\n -stackedCurvesMin -1\n -stackedCurvesMax 1\n -stackedCurvesSpace 0.2\n -preSelectionHighlight 0\n -constrainDrag 0\n -valueLinesToggle 1\n -highlightAffectedCurves 0\n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"dopeSheetPanel\" (localizedPanelLabel(\"Dope Sheet\")) `;\n" - + "\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Dope Sheet\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = ($panelName+\"OutlineEd\");\n outlinerEditor -e \n -showShapes 1\n -showAssignedMaterials 0\n -showTimeEditor 1\n -showReferenceNodes 0\n -showReferenceMembers 0\n -showAttributes 1\n -showConnected 1\n -showAnimCurvesOnly 1\n -showMuteInfo 0\n -organizeByLayer 1\n -organizeByClip 1\n -showAnimLayerWeight 1\n -autoExpandLayers 1\n -autoExpand 0\n -showDagOnly 0\n -showAssets 1\n -showContainedOnly 0\n -showPublishedAsConnected 0\n -showParentContainers 0\n -showContainerContents 0\n -ignoreDagHierarchy 0\n -expandConnections 1\n" - + " -showUpstreamCurves 1\n -showUnitlessCurves 0\n -showCompounds 1\n -showLeafs 1\n -showNumericAttrsOnly 1\n -highlightActive 0\n -autoSelectNewObjects 0\n -doNotSelectNewObjects 1\n -dropIsParent 1\n -transmitFilters 0\n -setFilter \"0\" \n -showSetMembers 0\n -allowMultiSelection 1\n -alwaysToggleSelect 0\n -directSelect 0\n -showUfeItems 1\n -displayMode \"DAG\" \n -expandObjects 0\n -setsIgnoreFilters 1\n -containersIgnoreFilters 0\n -editAttrName 0\n -showAttrValues 0\n -highlightSecondary 0\n -showUVAttrsOnly 0\n -showTextureNodesOnly 0\n -attrAlphaOrder \"default\" \n -animLayerFilterOptions \"allAffecting\" \n -sortOrder \"none\" \n" - + " -longNames 0\n -niceNames 1\n -showNamespace 1\n -showPinIcons 0\n -mapMotionTrails 1\n -ignoreHiddenAttribute 0\n -ignoreOutlinerColor 0\n -renderFilterVisible 0\n $editorName;\n\n\t\t\t$editorName = ($panelName+\"DopeSheetEd\");\n dopeSheetEditor -e \n -displayValues 0\n -snapTime \"integer\" \n -snapValue \"none\" \n -outliner \"dopeSheetPanel1OutlineEd\" \n -showSummary 1\n -showScene 0\n -hierarchyBelow 0\n -showTicks 1\n -selectionWindow 0 0 0 0 \n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"timeEditorPanel\" (localizedPanelLabel(\"Time Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Time Editor\")) -mbv $menusOkayInPanels $panelName;\n" - + "\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"clipEditorPanel\" (localizedPanelLabel(\"Trax Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Trax Editor\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = clipEditorNameFromPanel($panelName);\n clipEditor -e \n -displayValues 0\n -snapTime \"none\" \n -snapValue \"none\" \n -initialized 0\n -manageSequencer 0 \n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"sequenceEditorPanel\" (localizedPanelLabel(\"Camera Sequencer\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Camera Sequencer\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = sequenceEditorNameFromPanel($panelName);\n" - + " clipEditor -e \n -displayValues 0\n -snapTime \"none\" \n -snapValue \"none\" \n -initialized 0\n -manageSequencer 1 \n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"hyperGraphPanel\" (localizedPanelLabel(\"Hypergraph Hierarchy\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Hypergraph Hierarchy\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = ($panelName+\"HyperGraphEd\");\n hyperGraph -e \n -graphLayoutStyle \"hierarchicalLayout\" \n -orientation \"horiz\" \n -mergeConnections 0\n -zoom 1\n -animateTransition 0\n -showRelationships 1\n -showShapes 0\n -showDeformers 0\n -showExpressions 0\n -showConstraints 0\n" - + " -showConnectionFromSelected 0\n -showConnectionToSelected 0\n -showConstraintLabels 0\n -showUnderworld 0\n -showInvisible 0\n -transitionFrames 1\n -opaqueContainers 0\n -freeform 0\n -imagePosition 0 0 \n -imageScale 1\n -imageEnabled 0\n -graphType \"DAG\" \n -heatMapDisplay 0\n -updateSelection 1\n -updateNodeAdded 1\n -useDrawOverrideColor 0\n -limitGraphTraversal -1\n -range 0 0 \n -iconSize \"smallIcons\" \n -showCachedConnections 0\n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"hyperShadePanel\" (localizedPanelLabel(\"Hypershade\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Hypershade\")) -mbv $menusOkayInPanels $panelName;\n" - + "\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"visorPanel\" (localizedPanelLabel(\"Visor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Visor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"nodeEditorPanel\" (localizedPanelLabel(\"Node Editor\")) `;\n\tif ($nodeEditorPanelVisible || $nodeEditorWorkspaceControlOpen) {\n\t\tif (\"\" == $panelName) {\n\t\t\tif ($useSceneConfig) {\n\t\t\t\t$panelName = `scriptedPanel -unParent -type \"nodeEditorPanel\" -l (localizedPanelLabel(\"Node Editor\")) -mbv $menusOkayInPanels `;\n\n\t\t\t$editorName = ($panelName+\"NodeEditorEd\");\n nodeEditor -e \n -allAttributes 0\n -allNodes 0\n -autoSizeNodes 1\n -consistentNameSize 1\n -createNodeCommand \"nodeEdCreateNodeCommand\" \n" - + " -connectNodeOnCreation 0\n -connectOnDrop 0\n -copyConnectionsOnPaste 0\n -connectionStyle \"bezier\" \n -defaultPinnedState 0\n -additiveGraphingMode 0\n -connectedGraphingMode 1\n -settingsChangedCallback \"nodeEdSyncControls\" \n -traversalDepthLimit -1\n -keyPressCommand \"nodeEdKeyPressCommand\" \n -nodeTitleMode \"name\" \n -gridSnap 0\n -gridVisibility 1\n -crosshairOnEdgeDragging 0\n -popupMenuScript \"nodeEdBuildPanelMenus\" \n -showNamespace 1\n -showShapes 1\n -showSGShapes 0\n -showTransforms 1\n -useAssets 1\n -syncedSelection 1\n -extendToShapes 1\n -showUnitConversions 0\n -editorMode \"default\" \n -hasWatchpoint 0\n $editorName;\n\t\t\t}\n\t\t} else {\n\t\t\t$label = `panel -q -label $panelName`;\n" - + "\t\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Node Editor\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = ($panelName+\"NodeEditorEd\");\n nodeEditor -e \n -allAttributes 0\n -allNodes 0\n -autoSizeNodes 1\n -consistentNameSize 1\n -createNodeCommand \"nodeEdCreateNodeCommand\" \n -connectNodeOnCreation 0\n -connectOnDrop 0\n -copyConnectionsOnPaste 0\n -connectionStyle \"bezier\" \n -defaultPinnedState 0\n -additiveGraphingMode 0\n -connectedGraphingMode 1\n -settingsChangedCallback \"nodeEdSyncControls\" \n -traversalDepthLimit -1\n -keyPressCommand \"nodeEdKeyPressCommand\" \n -nodeTitleMode \"name\" \n -gridSnap 0\n -gridVisibility 1\n -crosshairOnEdgeDragging 0\n -popupMenuScript \"nodeEdBuildPanelMenus\" \n -showNamespace 1\n" - + " -showShapes 1\n -showSGShapes 0\n -showTransforms 1\n -useAssets 1\n -syncedSelection 1\n -extendToShapes 1\n -showUnitConversions 0\n -editorMode \"default\" \n -hasWatchpoint 0\n $editorName;\n\t\t\tif (!$useSceneConfig) {\n\t\t\t\tpanel -e -l $label $panelName;\n\t\t\t}\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"createNodePanel\" (localizedPanelLabel(\"Create Node\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Create Node\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"polyTexturePlacementPanel\" (localizedPanelLabel(\"UV Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"UV Editor\")) -mbv $menusOkayInPanels $panelName;\n" - + "\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"renderWindowPanel\" (localizedPanelLabel(\"Render View\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Render View\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"shapePanel\" (localizedPanelLabel(\"Shape Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tshapePanel -edit -l (localizedPanelLabel(\"Shape Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"posePanel\" (localizedPanelLabel(\"Pose Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tposePanel -edit -l (localizedPanelLabel(\"Pose Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n" - + "\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"dynRelEdPanel\" (localizedPanelLabel(\"Dynamic Relationships\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Dynamic Relationships\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"relationshipPanel\" (localizedPanelLabel(\"Relationship Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Relationship Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"referenceEditorPanel\" (localizedPanelLabel(\"Reference Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Reference Editor\")) -mbv $menusOkayInPanels $panelName;\n" - + "\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"dynPaintScriptedPanelType\" (localizedPanelLabel(\"Paint Effects\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Paint Effects\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"scriptEditorPanel\" (localizedPanelLabel(\"Script Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Script Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"profilerPanel\" (localizedPanelLabel(\"Profiler Tool\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Profiler Tool\")) -mbv $menusOkayInPanels $panelName;\n" - + "\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"contentBrowserPanel\" (localizedPanelLabel(\"Content Browser\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Content Browser\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"Stereo\" (localizedPanelLabel(\"Stereo\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Stereo\")) -mbv $menusOkayInPanels $panelName;\n{ string $editorName = ($panelName+\"Editor\");\n stereoCameraView -e \n -camera \"|persp\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"wireframe\" \n -activeOnly 0\n -ignorePanZoom 0\n -wireframeOnShaded 0\n" - + " -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n -useDefaultMaterial 0\n -bufferMode \"double\" \n -twoSidedLighting 1\n -backfaceCulling 0\n -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n -lineWidth 1\n -textureAnisotropic 0\n -textureHilight 1\n -textureSampling 2\n -textureDisplay \"modulate\" \n -textureMaxSize 16384\n -fogging 0\n -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n -depthOfFieldPreview 1\n -maxConstantTransparency 1\n -objectFilterShowInHUD 1\n -isFiltered 0\n -colorResolution 4 4 \n" - + " -bumpResolution 4 4 \n -textureCompression 0\n -transparencyAlgorithm \"frontAndBackCull\" \n -transpInShadows 0\n -cullingOverride \"none\" \n -lowQualityLighting 0\n -maximumNumHardwareLights 0\n -occlusionCulling 0\n -shadingModel 0\n -useBaseRenderer 0\n -useReducedRenderer 0\n -smallObjectCulling 0\n -smallObjectThreshold -1 \n -interactiveDisableShadows 0\n -interactiveBackFaceCull 0\n -sortTransparent 1\n -controllers 1\n -nurbsCurves 1\n -nurbsSurfaces 1\n -polymeshes 1\n -subdivSurfaces 1\n -planes 1\n -lights 1\n -cameras 1\n -controlVertices 1\n -hulls 1\n -grid 1\n -imagePlane 1\n -joints 1\n -ikHandles 1\n" - + " -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n -nCloths 1\n -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n -locators 1\n -manipulators 1\n -pluginShapes 1\n -dimensions 1\n -handles 1\n -pivots 1\n -textures 1\n -strokes 1\n -motionTrails 1\n -clipGhosts 1\n -bluePencil 1\n -greasePencils 0\n -shadows 0\n -captureSequenceNumber -1\n -width 0\n -height 0\n -sceneRenderFilter 0\n -displayMode \"centerEye\" \n -viewColor 0 0 0 1 \n -useCustomBackground 1\n $editorName;\n stereoCameraView -e -viewSelected 0 $editorName;\n stereoCameraView -e \n" - + " -pluginObjects \"gpuCacheDisplayFilter\" 1 \n $editorName; };\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\tif ($useSceneConfig) {\n string $configName = `getPanel -cwl (localizedPanelLabel(\"Current Layout\"))`;\n if (\"\" != $configName) {\n\t\t\tpanelConfiguration -edit -label (localizedPanelLabel(\"Current Layout\")) \n\t\t\t\t-userCreated false\n\t\t\t\t-defaultImage \"vacantCell.xP:/\"\n\t\t\t\t-image \"\"\n\t\t\t\t-sc false\n\t\t\t\t-configString \"global string $gMainPane; paneLayout -e -cn \\\"single\\\" -ps 1 100 100 $gMainPane;\"\n\t\t\t\t-removeAllPanels\n\t\t\t\t-ap false\n\t\t\t\t\t(localizedPanelLabel(\"Persp View\")) \n\t\t\t\t\t\"modelPanel\"\n" - + "\t\t\t\t\t\"$panelName = `modelPanel -unParent -l (localizedPanelLabel(\\\"Persp View\\\")) -mbv $menusOkayInPanels `;\\n$editorName = $panelName;\\nmodelEditor -e \\n -cam `findStartUpCamera persp` \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 16384\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -bluePencil 1\\n -greasePencils 0\\n -excludeObjectPreset \\\"All\\\" \\n -shadows 0\\n -captureSequenceNumber -1\\n -width 1319\\n -height 718\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName;\\nmodelEditor -e \\n -pluginObjects \\\"gpuCacheDisplayFilter\\\" 1 \\n $editorName\"\n" - + "\t\t\t\t\t\"modelPanel -edit -l (localizedPanelLabel(\\\"Persp View\\\")) -mbv $menusOkayInPanels $panelName;\\n$editorName = $panelName;\\nmodelEditor -e \\n -cam `findStartUpCamera persp` \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 16384\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -bluePencil 1\\n -greasePencils 0\\n -excludeObjectPreset \\\"All\\\" \\n -shadows 0\\n -captureSequenceNumber -1\\n -width 1319\\n -height 718\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName;\\nmodelEditor -e \\n -pluginObjects \\\"gpuCacheDisplayFilter\\\" 1 \\n $editorName\"\n" + + " -hulls 1\n -grid 1\n -imagePlane 1\n -joints 1\n -ikHandles 1\n -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n -nCloths 1\n -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n -locators 1\n -manipulators 1\n -pluginShapes 1\n -dimensions 1\n -handles 1\n -pivots 1\n -textures 1\n -strokes 1\n -motionTrails 1\n -clipGhosts 1\n -bluePencil 1\n -greasePencils 0\n -shadows 0\n -captureSequenceNumber -1\n -width 477\n -height 276\n -sceneRenderFilter 0\n $editorName;\n modelEditor -e -viewSelected 0 $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"modelPanel\" (localizedPanelLabel(\"Side View\")) `;\n" + + "\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tmodelPanel -edit -l (localizedPanelLabel(\"Side View\")) -mbv $menusOkayInPanels $panelName;\n\t\t$editorName = $panelName;\n modelEditor -e \n -camera \"|side\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"smoothShaded\" \n -activeOnly 0\n -ignorePanZoom 0\n -wireframeOnShaded 0\n -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n -useDefaultMaterial 0\n -bufferMode \"double\" \n -twoSidedLighting 0\n -backfaceCulling 0\n -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n -lineWidth 1\n -textureAnisotropic 0\n -textureHilight 1\n -textureSampling 2\n -textureDisplay \"modulate\" \n -textureMaxSize 32768\n -fogging 0\n" + + " -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n -depthOfFieldPreview 1\n -maxConstantTransparency 1\n -rendererName \"vp2Renderer\" \n -objectFilterShowInHUD 1\n -isFiltered 0\n -colorResolution 256 256 \n -bumpResolution 512 512 \n -textureCompression 0\n -transparencyAlgorithm \"frontAndBackCull\" \n -transpInShadows 0\n -cullingOverride \"none\" \n -lowQualityLighting 0\n -maximumNumHardwareLights 1\n -occlusionCulling 0\n -shadingModel 0\n -useBaseRenderer 0\n -useReducedRenderer 0\n -smallObjectCulling 0\n -smallObjectThreshold -1 \n -interactiveDisableShadows 0\n -interactiveBackFaceCull 0\n -sortTransparent 1\n -controllers 1\n -nurbsCurves 1\n" + + " -nurbsSurfaces 1\n -polymeshes 1\n -subdivSurfaces 1\n -planes 1\n -lights 1\n -cameras 1\n -controlVertices 1\n -hulls 1\n -grid 1\n -imagePlane 1\n -joints 1\n -ikHandles 1\n -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n -nCloths 1\n -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n -locators 1\n -manipulators 1\n -pluginShapes 1\n -dimensions 1\n -handles 1\n -pivots 1\n -textures 1\n -strokes 1\n -motionTrails 1\n -clipGhosts 1\n -bluePencil 1\n -greasePencils 0\n -shadows 0\n -captureSequenceNumber -1\n -width 476\n -height 276\n -sceneRenderFilter 0\n $editorName;\n" + + " modelEditor -e -viewSelected 0 $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"modelPanel\" (localizedPanelLabel(\"Front View\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tmodelPanel -edit -l (localizedPanelLabel(\"Front View\")) -mbv $menusOkayInPanels $panelName;\n\t\t$editorName = $panelName;\n modelEditor -e \n -camera \"|front\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"smoothShaded\" \n -activeOnly 0\n -ignorePanZoom 0\n -wireframeOnShaded 0\n -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n -useDefaultMaterial 0\n -bufferMode \"double\" \n -twoSidedLighting 0\n -backfaceCulling 0\n -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n" + + " -lineWidth 1\n -textureAnisotropic 0\n -textureHilight 1\n -textureSampling 2\n -textureDisplay \"modulate\" \n -textureMaxSize 32768\n -fogging 0\n -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n -depthOfFieldPreview 1\n -maxConstantTransparency 1\n -rendererName \"vp2Renderer\" \n -objectFilterShowInHUD 1\n -isFiltered 0\n -colorResolution 256 256 \n -bumpResolution 512 512 \n -textureCompression 0\n -transparencyAlgorithm \"frontAndBackCull\" \n -transpInShadows 0\n -cullingOverride \"none\" \n -lowQualityLighting 0\n -maximumNumHardwareLights 1\n -occlusionCulling 0\n -shadingModel 0\n -useBaseRenderer 0\n -useReducedRenderer 0\n -smallObjectCulling 0\n" + + " -smallObjectThreshold -1 \n -interactiveDisableShadows 0\n -interactiveBackFaceCull 0\n -sortTransparent 1\n -controllers 1\n -nurbsCurves 1\n -nurbsSurfaces 1\n -polymeshes 1\n -subdivSurfaces 1\n -planes 1\n -lights 1\n -cameras 1\n -controlVertices 1\n -hulls 1\n -grid 1\n -imagePlane 1\n -joints 1\n -ikHandles 1\n -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n -nCloths 1\n -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n -locators 1\n -manipulators 1\n -pluginShapes 1\n -dimensions 1\n -handles 1\n -pivots 1\n -textures 1\n -strokes 1\n -motionTrails 1\n -clipGhosts 1\n -bluePencil 1\n" + + " -greasePencils 0\n -shadows 0\n -captureSequenceNumber -1\n -width 477\n -height 276\n -sceneRenderFilter 0\n $editorName;\n modelEditor -e -viewSelected 0 $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"modelPanel\" (localizedPanelLabel(\"Persp View\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tmodelPanel -edit -l (localizedPanelLabel(\"Persp View\")) -mbv $menusOkayInPanels $panelName;\n\t\t$editorName = $panelName;\n modelEditor -e \n -camera \"|persp\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"smoothShaded\" \n -activeOnly 0\n -ignorePanZoom 0\n -wireframeOnShaded 0\n -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n -useDefaultMaterial 0\n -bufferMode \"double\" \n" + + " -twoSidedLighting 0\n -backfaceCulling 0\n -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n -lineWidth 1\n -textureAnisotropic 0\n -textureHilight 1\n -textureSampling 2\n -textureDisplay \"modulate\" \n -textureMaxSize 32768\n -fogging 0\n -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n -depthOfFieldPreview 1\n -maxConstantTransparency 1\n -rendererName \"vp2Renderer\" \n -objectFilterShowInHUD 1\n -isFiltered 0\n -colorResolution 256 256 \n -bumpResolution 512 512 \n -textureCompression 0\n -transparencyAlgorithm \"frontAndBackCull\" \n -transpInShadows 0\n -cullingOverride \"none\" \n -lowQualityLighting 0\n" + + " -maximumNumHardwareLights 1\n -occlusionCulling 0\n -shadingModel 0\n -useBaseRenderer 0\n -useReducedRenderer 0\n -smallObjectCulling 0\n -smallObjectThreshold -1 \n -interactiveDisableShadows 0\n -interactiveBackFaceCull 0\n -sortTransparent 1\n -controllers 1\n -nurbsCurves 1\n -nurbsSurfaces 1\n -polymeshes 1\n -subdivSurfaces 1\n -planes 1\n -lights 1\n -cameras 1\n -controlVertices 1\n -hulls 1\n -grid 1\n -imagePlane 1\n -joints 1\n -ikHandles 1\n -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n -nCloths 1\n -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n -locators 1\n -manipulators 1\n -pluginShapes 1\n" + + " -dimensions 1\n -handles 1\n -pivots 1\n -textures 1\n -strokes 1\n -motionTrails 1\n -clipGhosts 1\n -bluePencil 1\n -greasePencils 0\n -shadows 0\n -captureSequenceNumber -1\n -width 476\n -height 276\n -sceneRenderFilter 0\n $editorName;\n modelEditor -e -viewSelected 0 $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"outlinerPanel\" (localizedPanelLabel(\"ToggledOutliner\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\toutlinerPanel -edit -l (localizedPanelLabel(\"ToggledOutliner\")) -mbv $menusOkayInPanels $panelName;\n\t\t$editorName = $panelName;\n outlinerEditor -e \n -docTag \"isolOutln_fromSeln\" \n -showShapes 0\n -showAssignedMaterials 0\n -showTimeEditor 1\n -showReferenceNodes 1\n -showReferenceMembers 1\n" + + " -showAttributes 0\n -showConnected 0\n -showAnimCurvesOnly 0\n -showMuteInfo 0\n -organizeByLayer 1\n -organizeByClip 1\n -showAnimLayerWeight 1\n -autoExpandLayers 1\n -autoExpand 0\n -showDagOnly 1\n -showAssets 1\n -showContainedOnly 1\n -showPublishedAsConnected 0\n -showParentContainers 0\n -showContainerContents 1\n -ignoreDagHierarchy 0\n -expandConnections 0\n -showUpstreamCurves 1\n -showUnitlessCurves 1\n -showCompounds 1\n -showLeafs 1\n -showNumericAttrsOnly 0\n -highlightActive 1\n -autoSelectNewObjects 0\n -doNotSelectNewObjects 0\n -dropIsParent 1\n -transmitFilters 0\n -setFilter \"defaultSetFilter\" \n -showSetMembers 1\n -allowMultiSelection 1\n -alwaysToggleSelect 0\n -directSelect 0\n" + + " -isSet 0\n -isSetMember 0\n -showUfeItems 1\n -displayMode \"DAG\" \n -expandObjects 0\n -setsIgnoreFilters 1\n -containersIgnoreFilters 0\n -editAttrName 0\n -showAttrValues 0\n -highlightSecondary 0\n -showUVAttrsOnly 0\n -showTextureNodesOnly 0\n -attrAlphaOrder \"default\" \n -animLayerFilterOptions \"allAffecting\" \n -sortOrder \"none\" \n -longNames 0\n -niceNames 1\n -selectCommand \"print(\\\"\\\")\" \n -showNamespace 1\n -showPinIcons 0\n -mapMotionTrails 0\n -ignoreHiddenAttribute 0\n -ignoreOutlinerColor 0\n -renderFilterVisible 0\n -renderFilterIndex 0\n -selectionOrder \"chronological\" \n -expandAttribute 0\n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"outlinerPanel\" (localizedPanelLabel(\"Outliner\")) `;\n" + + "\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\toutlinerPanel -edit -l (localizedPanelLabel(\"Outliner\")) -mbv $menusOkayInPanels $panelName;\n\t\t$editorName = $panelName;\n outlinerEditor -e \n -showShapes 0\n -showAssignedMaterials 0\n -showTimeEditor 1\n -showReferenceNodes 0\n -showReferenceMembers 0\n -showAttributes 0\n -showConnected 0\n -showAnimCurvesOnly 0\n -showMuteInfo 0\n -organizeByLayer 1\n -organizeByClip 1\n -showAnimLayerWeight 1\n -autoExpandLayers 1\n -autoExpand 0\n -showDagOnly 1\n -showAssets 1\n -showContainedOnly 1\n -showPublishedAsConnected 0\n -showParentContainers 0\n -showContainerContents 1\n -ignoreDagHierarchy 0\n -expandConnections 0\n -showUpstreamCurves 1\n -showUnitlessCurves 1\n -showCompounds 1\n -showLeafs 1\n" + + " -showNumericAttrsOnly 0\n -highlightActive 1\n -autoSelectNewObjects 0\n -doNotSelectNewObjects 0\n -dropIsParent 1\n -transmitFilters 0\n -setFilter \"defaultSetFilter\" \n -showSetMembers 1\n -allowMultiSelection 1\n -alwaysToggleSelect 0\n -directSelect 0\n -showUfeItems 1\n -displayMode \"DAG\" \n -expandObjects 0\n -setsIgnoreFilters 1\n -containersIgnoreFilters 0\n -editAttrName 0\n -showAttrValues 0\n -highlightSecondary 0\n -showUVAttrsOnly 0\n -showTextureNodesOnly 0\n -attrAlphaOrder \"default\" \n -animLayerFilterOptions \"allAffecting\" \n -sortOrder \"none\" \n -longNames 0\n -niceNames 1\n -showNamespace 1\n -showPinIcons 0\n -mapMotionTrails 0\n -ignoreHiddenAttribute 0\n -ignoreOutlinerColor 0\n -renderFilterVisible 0\n" + + " $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"graphEditor\" (localizedPanelLabel(\"Graph Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Graph Editor\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = ($panelName+\"OutlineEd\");\n outlinerEditor -e \n -showShapes 1\n -showAssignedMaterials 0\n -showTimeEditor 1\n -showReferenceNodes 0\n -showReferenceMembers 0\n -showAttributes 1\n -showConnected 1\n -showAnimCurvesOnly 1\n -showMuteInfo 0\n -organizeByLayer 1\n -organizeByClip 1\n -showAnimLayerWeight 1\n -autoExpandLayers 1\n -autoExpand 1\n -showDagOnly 0\n -showAssets 1\n -showContainedOnly 0\n" + + " -showPublishedAsConnected 0\n -showParentContainers 0\n -showContainerContents 0\n -ignoreDagHierarchy 0\n -expandConnections 1\n -showUpstreamCurves 1\n -showUnitlessCurves 1\n -showCompounds 0\n -showLeafs 1\n -showNumericAttrsOnly 1\n -highlightActive 0\n -autoSelectNewObjects 1\n -doNotSelectNewObjects 0\n -dropIsParent 1\n -transmitFilters 1\n -setFilter \"0\" \n -showSetMembers 0\n -allowMultiSelection 1\n -alwaysToggleSelect 0\n -directSelect 0\n -showUfeItems 1\n -displayMode \"DAG\" \n -expandObjects 0\n -setsIgnoreFilters 1\n -containersIgnoreFilters 0\n -editAttrName 0\n -showAttrValues 0\n -highlightSecondary 0\n -showUVAttrsOnly 0\n" + + " -showTextureNodesOnly 0\n -attrAlphaOrder \"default\" \n -animLayerFilterOptions \"allAffecting\" \n -sortOrder \"none\" \n -longNames 0\n -niceNames 1\n -showNamespace 1\n -showPinIcons 1\n -mapMotionTrails 1\n -ignoreHiddenAttribute 0\n -ignoreOutlinerColor 0\n -renderFilterVisible 0\n $editorName;\n\n\t\t\t$editorName = ($panelName+\"GraphEd\");\n animCurveEditor -e \n -displayValues 0\n -snapTime \"integer\" \n -snapValue \"none\" \n -showPlayRangeShades \"on\" \n -lockPlayRangeShades \"off\" \n -smoothness \"fine\" \n -resultSamples 1.041667\n -resultScreenSamples 0\n -resultUpdate \"delayed\" \n -showUpstreamCurves 1\n -keyMinScale 1\n -stackedCurvesMin -1\n -stackedCurvesMax 1\n" + + " -stackedCurvesSpace 0.2\n -preSelectionHighlight 0\n -constrainDrag 0\n -valueLinesToggle 1\n -highlightAffectedCurves 0\n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"dopeSheetPanel\" (localizedPanelLabel(\"Dope Sheet\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Dope Sheet\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = ($panelName+\"OutlineEd\");\n outlinerEditor -e \n -showShapes 1\n -showAssignedMaterials 0\n -showTimeEditor 1\n -showReferenceNodes 0\n -showReferenceMembers 0\n -showAttributes 1\n -showConnected 1\n -showAnimCurvesOnly 1\n -showMuteInfo 0\n -organizeByLayer 1\n -organizeByClip 1\n" + + " -showAnimLayerWeight 1\n -autoExpandLayers 1\n -autoExpand 0\n -showDagOnly 0\n -showAssets 1\n -showContainedOnly 0\n -showPublishedAsConnected 0\n -showParentContainers 0\n -showContainerContents 0\n -ignoreDagHierarchy 0\n -expandConnections 1\n -showUpstreamCurves 1\n -showUnitlessCurves 0\n -showCompounds 1\n -showLeafs 1\n -showNumericAttrsOnly 1\n -highlightActive 0\n -autoSelectNewObjects 0\n -doNotSelectNewObjects 1\n -dropIsParent 1\n -transmitFilters 0\n -setFilter \"0\" \n -showSetMembers 0\n -allowMultiSelection 1\n -alwaysToggleSelect 0\n -directSelect 0\n -showUfeItems 1\n -displayMode \"DAG\" \n -expandObjects 0\n" + + " -setsIgnoreFilters 1\n -containersIgnoreFilters 0\n -editAttrName 0\n -showAttrValues 0\n -highlightSecondary 0\n -showUVAttrsOnly 0\n -showTextureNodesOnly 0\n -attrAlphaOrder \"default\" \n -animLayerFilterOptions \"allAffecting\" \n -sortOrder \"none\" \n -longNames 0\n -niceNames 1\n -showNamespace 1\n -showPinIcons 0\n -mapMotionTrails 1\n -ignoreHiddenAttribute 0\n -ignoreOutlinerColor 0\n -renderFilterVisible 0\n $editorName;\n\n\t\t\t$editorName = ($panelName+\"DopeSheetEd\");\n dopeSheetEditor -e \n -displayValues 0\n -snapTime \"integer\" \n -snapValue \"none\" \n -outliner \"dopeSheetPanel1OutlineEd\" \n -showSummary 1\n -showScene 0\n -hierarchyBelow 0\n" + + " -showTicks 1\n -selectionWindow 0 0 0 0 \n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"timeEditorPanel\" (localizedPanelLabel(\"Time Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Time Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"clipEditorPanel\" (localizedPanelLabel(\"Trax Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Trax Editor\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = clipEditorNameFromPanel($panelName);\n clipEditor -e \n -displayValues 0\n -snapTime \"none\" \n -snapValue \"none\" \n -initialized 0\n -manageSequencer 0 \n" + + " $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"sequenceEditorPanel\" (localizedPanelLabel(\"Camera Sequencer\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Camera Sequencer\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = sequenceEditorNameFromPanel($panelName);\n clipEditor -e \n -displayValues 0\n -snapTime \"none\" \n -snapValue \"none\" \n -initialized 0\n -manageSequencer 1 \n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"hyperGraphPanel\" (localizedPanelLabel(\"Hypergraph Hierarchy\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Hypergraph Hierarchy\")) -mbv $menusOkayInPanels $panelName;\n" + + "\n\t\t\t$editorName = ($panelName+\"HyperGraphEd\");\n hyperGraph -e \n -graphLayoutStyle \"hierarchicalLayout\" \n -orientation \"horiz\" \n -mergeConnections 0\n -zoom 1\n -animateTransition 0\n -showRelationships 1\n -showShapes 0\n -showDeformers 0\n -showExpressions 0\n -showConstraints 0\n -showConnectionFromSelected 0\n -showConnectionToSelected 0\n -showConstraintLabels 0\n -showUnderworld 0\n -showInvisible 0\n -transitionFrames 1\n -opaqueContainers 0\n -freeform 0\n -imagePosition 0 0 \n -imageScale 1\n -imageEnabled 0\n -graphType \"DAG\" \n -heatMapDisplay 0\n -updateSelection 1\n -updateNodeAdded 1\n -useDrawOverrideColor 0\n -limitGraphTraversal -1\n" + + " -range 0 0 \n -iconSize \"smallIcons\" \n -showCachedConnections 0\n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"hyperShadePanel\" (localizedPanelLabel(\"Hypershade\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Hypershade\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"visorPanel\" (localizedPanelLabel(\"Visor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Visor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"nodeEditorPanel\" (localizedPanelLabel(\"Node Editor\")) `;\n\tif ($nodeEditorPanelVisible || $nodeEditorWorkspaceControlOpen) {\n" + + "\t\tif (\"\" == $panelName) {\n\t\t\tif ($useSceneConfig) {\n\t\t\t\t$panelName = `scriptedPanel -unParent -type \"nodeEditorPanel\" -l (localizedPanelLabel(\"Node Editor\")) -mbv $menusOkayInPanels `;\n\n\t\t\t$editorName = ($panelName+\"NodeEditorEd\");\n nodeEditor -e \n -allAttributes 0\n -allNodes 0\n -autoSizeNodes 1\n -consistentNameSize 1\n -createNodeCommand \"nodeEdCreateNodeCommand\" \n -connectNodeOnCreation 0\n -connectOnDrop 0\n -copyConnectionsOnPaste 0\n -connectionStyle \"bezier\" \n -defaultPinnedState 0\n -additiveGraphingMode 1\n -connectedGraphingMode 1\n -settingsChangedCallback \"nodeEdSyncControls\" \n -traversalDepthLimit -1\n -keyPressCommand \"nodeEdKeyPressCommand\" \n -nodeTitleMode \"name\" \n -gridSnap 0\n -gridVisibility 1\n -crosshairOnEdgeDragging 0\n" + + " -popupMenuScript \"nodeEdBuildPanelMenus\" \n -showNamespace 1\n -showShapes 1\n -showSGShapes 0\n -showTransforms 1\n -useAssets 1\n -syncedSelection 1\n -extendToShapes 1\n -showUnitConversions 0\n -editorMode \"default\" \n -hasWatchpoint 0\n $editorName;\n\t\t\t}\n\t\t} else {\n\t\t\t$label = `panel -q -label $panelName`;\n\t\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Node Editor\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = ($panelName+\"NodeEditorEd\");\n nodeEditor -e \n -allAttributes 0\n -allNodes 0\n -autoSizeNodes 1\n -consistentNameSize 1\n -createNodeCommand \"nodeEdCreateNodeCommand\" \n -connectNodeOnCreation 0\n -connectOnDrop 0\n -copyConnectionsOnPaste 0\n -connectionStyle \"bezier\" \n -defaultPinnedState 0\n" + + " -additiveGraphingMode 1\n -connectedGraphingMode 1\n -settingsChangedCallback \"nodeEdSyncControls\" \n -traversalDepthLimit -1\n -keyPressCommand \"nodeEdKeyPressCommand\" \n -nodeTitleMode \"name\" \n -gridSnap 0\n -gridVisibility 1\n -crosshairOnEdgeDragging 0\n -popupMenuScript \"nodeEdBuildPanelMenus\" \n -showNamespace 1\n -showShapes 1\n -showSGShapes 0\n -showTransforms 1\n -useAssets 1\n -syncedSelection 1\n -extendToShapes 1\n -showUnitConversions 0\n -editorMode \"default\" \n -hasWatchpoint 0\n $editorName;\n\t\t\tif (!$useSceneConfig) {\n\t\t\t\tpanel -e -l $label $panelName;\n\t\t\t}\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"createNodePanel\" (localizedPanelLabel(\"Create Node\")) `;\n\tif (\"\" != $panelName) {\n" + + "\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Create Node\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"polyTexturePlacementPanel\" (localizedPanelLabel(\"UV Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"UV Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"renderWindowPanel\" (localizedPanelLabel(\"Render View\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Render View\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"shapePanel\" (localizedPanelLabel(\"Shape Editor\")) `;\n\tif (\"\" != $panelName) {\n" + + "\t\t$label = `panel -q -label $panelName`;\n\t\tshapePanel -edit -l (localizedPanelLabel(\"Shape Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"posePanel\" (localizedPanelLabel(\"Pose Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tposePanel -edit -l (localizedPanelLabel(\"Pose Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"dynRelEdPanel\" (localizedPanelLabel(\"Dynamic Relationships\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Dynamic Relationships\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"relationshipPanel\" (localizedPanelLabel(\"Relationship Editor\")) `;\n" + + "\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Relationship Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"referenceEditorPanel\" (localizedPanelLabel(\"Reference Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Reference Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"dynPaintScriptedPanelType\" (localizedPanelLabel(\"Paint Effects\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Paint Effects\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"scriptEditorPanel\" (localizedPanelLabel(\"Script Editor\")) `;\n" + + "\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Script Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"profilerPanel\" (localizedPanelLabel(\"Profiler Tool\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Profiler Tool\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"contentBrowserPanel\" (localizedPanelLabel(\"Content Browser\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Content Browser\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"Stereo\" (localizedPanelLabel(\"Stereo\")) `;\n" + + "\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Stereo\")) -mbv $menusOkayInPanels $panelName;\n{ string $editorName = ($panelName+\"Editor\");\n stereoCameraView -e \n -camera \"|persp\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"wireframe\" \n -activeOnly 0\n -ignorePanZoom 0\n -wireframeOnShaded 0\n -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n -useDefaultMaterial 0\n -bufferMode \"double\" \n -twoSidedLighting 1\n -backfaceCulling 0\n -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n -lineWidth 1\n -textureAnisotropic 0\n -textureHilight 1\n -textureSampling 2\n" + + " -textureDisplay \"modulate\" \n -textureMaxSize 32768\n -fogging 0\n -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n -depthOfFieldPreview 1\n -maxConstantTransparency 1\n -objectFilterShowInHUD 1\n -isFiltered 0\n -colorResolution 4 4 \n -bumpResolution 4 4 \n -textureCompression 0\n -transparencyAlgorithm \"frontAndBackCull\" \n -transpInShadows 0\n -cullingOverride \"none\" \n -lowQualityLighting 0\n -maximumNumHardwareLights 0\n -occlusionCulling 0\n -shadingModel 0\n -useBaseRenderer 0\n -useReducedRenderer 0\n -smallObjectCulling 0\n -smallObjectThreshold -1 \n -interactiveDisableShadows 0\n" + + " -interactiveBackFaceCull 0\n -sortTransparent 1\n -controllers 1\n -nurbsCurves 1\n -nurbsSurfaces 1\n -polymeshes 1\n -subdivSurfaces 1\n -planes 1\n -lights 1\n -cameras 1\n -controlVertices 1\n -hulls 1\n -grid 1\n -imagePlane 1\n -joints 1\n -ikHandles 1\n -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n -nCloths 1\n -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n -locators 1\n -manipulators 1\n -pluginShapes 1\n -dimensions 1\n -handles 1\n -pivots 1\n -textures 1\n -strokes 1\n -motionTrails 1\n" + + " -clipGhosts 1\n -bluePencil 1\n -greasePencils 0\n -shadows 0\n -captureSequenceNumber -1\n -width 0\n -height 0\n -sceneRenderFilter 0\n -displayMode \"centerEye\" \n -viewColor 0 0 0 1 \n -useCustomBackground 1\n $editorName;\n stereoCameraView -e -viewSelected 0 $editorName; };\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\tif ($useSceneConfig) {\n string $configName = `getPanel -cwl (localizedPanelLabel(\"Current Layout\"))`;\n if (\"\" != $configName) {\n\t\t\tpanelConfiguration -edit -label (localizedPanelLabel(\"Current Layout\")) \n\t\t\t\t-userCreated false\n\t\t\t\t-defaultImage \"vacantCell.xP:/\"\n\t\t\t\t-image \"\"\n\t\t\t\t-sc false\n\t\t\t\t-configString \"global string $gMainPane; paneLayout -e -cn \\\"quad\\\" -ps 1 50 50 -ps 2 50 50 -ps 3 50 50 -ps 4 50 50 $gMainPane;\"\n\t\t\t\t-removeAllPanels\n\t\t\t\t-ap false\n\t\t\t\t\t(localizedPanelLabel(\"Top View\")) \n" + + "\t\t\t\t\t\"modelPanel\"\n" + + "\t\t\t\t\t\"$panelName = `modelPanel -unParent -l (localizedPanelLabel(\\\"Top View\\\")) -mbv $menusOkayInPanels `;\\n$editorName = $panelName;\\nmodelEditor -e \\n -camera \\\"|top\\\" \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 32768\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -bluePencil 1\\n -greasePencils 0\\n -shadows 0\\n -captureSequenceNumber -1\\n -width 477\\n -height 276\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName\"\n" + + "\t\t\t\t\t\"modelPanel -edit -l (localizedPanelLabel(\\\"Top View\\\")) -mbv $menusOkayInPanels $panelName;\\n$editorName = $panelName;\\nmodelEditor -e \\n -camera \\\"|top\\\" \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 32768\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -bluePencil 1\\n -greasePencils 0\\n -shadows 0\\n -captureSequenceNumber -1\\n -width 477\\n -height 276\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName\"\n" + + "\t\t\t\t-ap false\n\t\t\t\t\t(localizedPanelLabel(\"Persp View\")) \n\t\t\t\t\t\"modelPanel\"\n" + + "\t\t\t\t\t\"$panelName = `modelPanel -unParent -l (localizedPanelLabel(\\\"Persp View\\\")) -mbv $menusOkayInPanels `;\\n$editorName = $panelName;\\nmodelEditor -e \\n -cam `findStartUpCamera persp` \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 32768\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -bluePencil 1\\n -greasePencils 0\\n -shadows 0\\n -captureSequenceNumber -1\\n -width 476\\n -height 276\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName\"\n" + + "\t\t\t\t\t\"modelPanel -edit -l (localizedPanelLabel(\\\"Persp View\\\")) -mbv $menusOkayInPanels $panelName;\\n$editorName = $panelName;\\nmodelEditor -e \\n -cam `findStartUpCamera persp` \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 32768\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -bluePencil 1\\n -greasePencils 0\\n -shadows 0\\n -captureSequenceNumber -1\\n -width 476\\n -height 276\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName\"\n" + + "\t\t\t\t-ap false\n\t\t\t\t\t(localizedPanelLabel(\"Side View\")) \n\t\t\t\t\t\"modelPanel\"\n" + + "\t\t\t\t\t\"$panelName = `modelPanel -unParent -l (localizedPanelLabel(\\\"Side View\\\")) -mbv $menusOkayInPanels `;\\n$editorName = $panelName;\\nmodelEditor -e \\n -camera \\\"|side\\\" \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 32768\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -bluePencil 1\\n -greasePencils 0\\n -shadows 0\\n -captureSequenceNumber -1\\n -width 476\\n -height 276\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName\"\n" + + "\t\t\t\t\t\"modelPanel -edit -l (localizedPanelLabel(\\\"Side View\\\")) -mbv $menusOkayInPanels $panelName;\\n$editorName = $panelName;\\nmodelEditor -e \\n -camera \\\"|side\\\" \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 32768\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -bluePencil 1\\n -greasePencils 0\\n -shadows 0\\n -captureSequenceNumber -1\\n -width 476\\n -height 276\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName\"\n" + + "\t\t\t\t-ap false\n\t\t\t\t\t(localizedPanelLabel(\"Front View\")) \n\t\t\t\t\t\"modelPanel\"\n" + + "\t\t\t\t\t\"$panelName = `modelPanel -unParent -l (localizedPanelLabel(\\\"Front View\\\")) -mbv $menusOkayInPanels `;\\n$editorName = $panelName;\\nmodelEditor -e \\n -cam `findStartUpCamera front` \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 32768\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -bluePencil 1\\n -greasePencils 0\\n -shadows 0\\n -captureSequenceNumber -1\\n -width 477\\n -height 276\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName\"\n" + + "\t\t\t\t\t\"modelPanel -edit -l (localizedPanelLabel(\\\"Front View\\\")) -mbv $menusOkayInPanels $panelName;\\n$editorName = $panelName;\\nmodelEditor -e \\n -cam `findStartUpCamera front` \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 32768\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -bluePencil 1\\n -greasePencils 0\\n -shadows 0\\n -captureSequenceNumber -1\\n -width 477\\n -height 276\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName\"\n" + "\t\t\t\t$configName;\n\n setNamedPanelLayout (localizedPanelLabel(\"Current Layout\"));\n }\n\n panelHistory -e -clear mainPanelHistory;\n sceneUIReplacement -clear;\n\t}\n\n\ngrid -spacing 5 -size 12 -divisions 5 -displayAxes yes -displayGridLines yes -displayDivisionLines yes -displayPerspectiveLabels no -displayOrthographicLabels no -displayAxesBold yes -perspectiveLabelPosition axis -orthographicLabelPosition edge;\nviewManip -drawCompass 0 -compassAngle 0 -frontParameters \"\" -homeParameters \"\" -selectionLockParameters \"\";\n}\n"); setAttr ".st" 3; createNode script -n "sceneConfigurationScriptNode"; @@ -274,6 +285,7 @@ createNode aiAOVDriver -s -n "defaultArnoldDisplayDriver"; createNode objectSet -n "workfileMain"; rename -uid "3C9B5D6F-4579-8E3B-5B7D-4C88865A1C68"; addAttr -ci true -sn "cbId" -ln "cbId" -dt "string"; + addAttr -ci true -sn "instance_id" -ln "instance_id" -dt "string"; addAttr -ci true -sn "id" -ln "id" -dt "string"; addAttr -ci true -sn "family" -ln "family" -dt "string"; addAttr -ci true -sn "subset" -ln "subset" -dt "string"; @@ -282,13 +294,14 @@ createNode objectSet -n "workfileMain"; addAttr -ci true -sn "variant" -ln "variant" -dt "string"; addAttr -ci true -sn "asset" -ln "asset" -dt "string"; addAttr -ci true -sn "task" -ln "task" -dt "string"; - addAttr -ci true -sn "instance_id" -ln "instance_id" -dt "string"; addAttr -ci true -sn "publish_attributes" -ln "publish_attributes" -dt "string"; - addAttr -ci true -sn "__creator_attributes_keys" -ln "__creator_attributes_keys" + addAttr -ci true -sn "creator_attributes" -ln "creator_attributes" -dt "string"; + addAttr -ci true -sn "__creator_attributes_keys" -ln "__creator_attributes_keys" -dt "string"; setAttr ".ihi" 0; setAttr ".hio" yes; setAttr ".cbId" -type "string" "60df31e2be2b48bd3695c056:30d256dac64c"; + setAttr ".instance_id" -type "string" "911dc92a-ad29-41e5-bbf9-733d56174fb9"; setAttr ".id" -type "string" "pyblish.avalon.instance"; setAttr ".family" -type "string" "workfile"; setAttr ".subset" -type "string" "workfileTest_task"; @@ -297,16 +310,9 @@ createNode objectSet -n "workfileMain"; setAttr ".variant" -type "string" "Main"; setAttr ".asset" -type "string" "test_asset"; setAttr ".task" -type "string" "test_task"; - setAttr ".instance_id" -type "string" "911dc92a-ad29-41e5-bbf9-733d56174fb9"; - setAttr ".publish_attributes" -type "string" "{\"ExtractImportReference\": {\"active\": false}}"; + setAttr ".publish_attributes" -type "string" "{\"ValidateInstanceInContext\": {\"active\": true}, \"ExtractImportReference\": {\"active\": false}}"; + setAttr ".creator_attributes" -type "string" "{}"; setAttr ".__creator_attributes_keys" -type "string" ""; -createNode objectSet -n "renderingMain"; - rename -uid "8A999C2F-4922-B15D-8D5C-45A16465B69F"; - addAttr -ci true -sn "pre_creator_identifier" -ln "pre_creator_identifier" -dt "string"; - addAttr -ci true -sn "cbId" -ln "cbId" -dt "string"; - setAttr ".ihi" 0; - setAttr ".pre_creator_identifier" -type "string" "io.openpype.creators.maya.renderlayer"; - setAttr ".cbId" -type "string" "60df31e2be2b48bd3695c056:042447475732"; createNode renderSetupLayer -n "Main"; rename -uid "2202E438-4CEF-F64E-737C-F48C65E31126"; addAttr -ci true -sn "es" -ln "expandedState" -min 0 -max 1 -at "bool"; @@ -321,55 +327,6 @@ createNode collection -n "defaultCollection"; createNode simpleSelector -n "defaultCollectionSelector"; rename -uid "7CA2F6D8-483C-B020-BC03-EF9563A52163"; setAttr ".pat" -type "string" "*"; -createNode objectSet -n "_renderingMain:Main"; - rename -uid "1EEC3A3B-49CF-0C79-5340-39805174FB8A"; - addAttr -s false -ci true -sn "renderlayer" -ln "renderlayer" -at "message"; - addAttr -ci true -sn "id" -ln "id" -dt "string"; - addAttr -ci true -sn "family" -ln "family" -dt "string"; - addAttr -ci true -sn "subset" -ln "subset" -dt "string"; - addAttr -ci true -sn "active" -ln "active" -min 0 -max 1 -at "bool"; - addAttr -ci true -sn "creator_identifier" -ln "creator_identifier" -dt "string"; - addAttr -ci true -sn "variant" -ln "variant" -dt "string"; - addAttr -ci true -sn "asset" -ln "asset" -dt "string"; - addAttr -ci true -sn "task" -ln "task" -dt "string"; - addAttr -ci true -sn "instance_id" -ln "instance_id" -dt "string"; - addAttr -ci true -sn "review" -ln "review" -min 0 -max 1 -at "bool"; - addAttr -ci true -sn "extendFrames" -ln "extendFrames" -min 0 -max 1 -at "bool"; - addAttr -ci true -sn "overrideExistingFrame" -ln "overrideExistingFrame" -min 0 - -max 1 -at "bool"; - addAttr -ci true -sn "tileRendering" -ln "tileRendering" -min 0 -max 1 -at "bool"; - addAttr -ci true -sn "tilesX" -ln "tilesX" -at "long"; - addAttr -ci true -sn "tilesY" -ln "tilesY" -at "long"; - addAttr -ci true -sn "convertToScanline" -ln "convertToScanline" -min 0 -max 1 -at "bool"; - addAttr -ci true -sn "useReferencedAovs" -ln "useReferencedAovs" -min 0 -max 1 -at "bool"; - addAttr -ci true -sn "renderSetupIncludeLights" -ln "renderSetupIncludeLights" -min - 0 -max 1 -at "bool"; - addAttr -ci true -sn "publish_attributes" -ln "publish_attributes" -dt "string"; - addAttr -ci true -sn "__creator_attributes_keys" -ln "__creator_attributes_keys" - -dt "string"; - addAttr -ci true -sn "cbId" -ln "cbId" -dt "string"; - setAttr ".ihi" 0; - setAttr ".id" -type "string" "pyblish.avalon.instance"; - setAttr ".family" -type "string" "renderlayer"; - setAttr ".subset" -type "string" "renderMain"; - setAttr -cb on ".active" yes; - setAttr ".creator_identifier" -type "string" "io.openpype.creators.maya.renderlayer"; - setAttr ".variant" -type "string" "Main"; - setAttr ".asset" -type "string" "test_asset"; - setAttr ".task" -type "string" "test_task"; - setAttr ".instance_id" -type "string" "8a9cfb85-9602-4e5e-a4bc-27a2986bae7f"; - setAttr -cb on ".review" yes; - setAttr -cb on ".extendFrames"; - setAttr -cb on ".overrideExistingFrame" yes; - setAttr -cb on ".tileRendering"; - setAttr -cb on ".tilesX" 2; - setAttr -cb on ".tilesY" 2; - setAttr -cb on ".convertToScanline"; - setAttr -cb on ".useReferencedAovs"; - setAttr -cb on ".renderSetupIncludeLights" yes; - setAttr ".publish_attributes" -type "string" "{\"CollectDeadlinePools\": {\"primaryPool\": \"\", \"secondaryPool\": \"\"}, \"ValidateDeadlinePools\": {\"active\": true}, \"ValidateFrameRange\": {\"active\": true}, \"ExtractImportReference\": {\"active\": false}, \"MayaSubmitDeadline\": {\"priority\": 50, \"chunkSize\": 1, \"machineList\": \"\", \"whitelist\": false, \"tile_priority\": 50, \"strict_error_checking\": true}, \"ProcessSubmittedJobOnFarm\": {\"publishJobState\": \"Active\"}}"; - setAttr ".__creator_attributes_keys" -type "string" "review,extendFrames,overrideExistingFrame,tileRendering,tilesX,tilesY,convertToScanline,useReferencedAovs,renderSetupIncludeLights"; - setAttr ".cbId" -type "string" "60df31e2be2b48bd3695c056:69960f336351"; select -ne :time1; setAttr ".o" 1001; setAttr ".unw" 1001; @@ -378,9 +335,7 @@ select -ne :hardwareRenderingGlobals; setAttr ".otfva" -type "Int32Array" 22 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 ; - setAttr ".dli" 1; setAttr ".fprt" yes; - setAttr ".rtfm" 1; select -ne :renderPartition; setAttr -s 2 ".st"; select -ne :renderGlobalsList1; @@ -394,7 +349,6 @@ select -ne :standardSurface1; setAttr ".b" 0.80000001192092896; setAttr ".bc" -type "float3" 1 1 1 ; setAttr ".s" 0.20000000298023224; - setAttr ".sr" 0.40000000596046448; select -ne :initialShadingGroup; setAttr -s 2 ".dsm"; setAttr ".ro" yes; @@ -439,8 +393,6 @@ select -ne :defaultColorMgtGlobals; select -ne :hardwareRenderGlobals; setAttr ".ctrs" 256; setAttr ".btrs" 512; -select -ne :ikSystem; - setAttr -s 4 ".sol"; connectAttr "rs_Main.ri" ":persp.rlio[0]"; connectAttr "rs_Main.ri" ":top.rlio[0]"; connectAttr "rs_Main.ri" ":front.rlio[0]"; @@ -463,7 +415,6 @@ connectAttr ":defaultArnoldDisplayDriver.msg" ":defaultArnoldRenderOptions.drive -na; connectAttr ":defaultArnoldFilter.msg" ":defaultArnoldRenderOptions.filt"; connectAttr ":defaultArnoldDriver.msg" ":defaultArnoldRenderOptions.drvr"; -connectAttr "_renderingMain:Main.msg" "renderingMain.dnsm" -na; connectAttr "rs_Main.msg" "Main.lrl"; connectAttr "renderSetup.lit" "Main.pls"; connectAttr "defaultCollection.msg" "Main.cl"; @@ -472,7 +423,6 @@ connectAttr "renderLayerManager.rlmi[1]" "rs_Main.rlid"; connectAttr "defaultCollectionSelector.c" "defaultCollection.sel"; connectAttr "Main.lit" "defaultCollection.pls"; connectAttr "Main.nic" "defaultCollection.pic"; -connectAttr "Main.msg" "_renderingMain:Main.renderlayer"; connectAttr "defaultRenderLayer.msg" ":defaultRenderingList1.r" -na; connectAttr "rs_Main.msg" ":defaultRenderingList1.r" -na; connectAttr "pSphere1_GEOShape1.iog" ":initialShadingGroup.dsm" -na; diff --git a/tests/integration/hosts/maya/test_publish_in_maya/input/workfile/test_project_test_asset_test_task_v001.ma b/tests/integration/hosts/maya/test_publish_in_maya/input/workfile/test_project_test_asset_test_task_v001.ma index e438d80d5f..2cc87c2f48 100644 --- a/tests/integration/hosts/maya/test_publish_in_maya/input/workfile/test_project_test_asset_test_task_v001.ma +++ b/tests/integration/hosts/maya/test_publish_in_maya/input/workfile/test_project_test_asset_test_task_v001.ma @@ -1,22 +1,22 @@ -//Maya ASCII 2022 scene -//Name: test_project_test_asset_test_task_v001.ma -//Last modified: Thu, Sep 14, 2023 06:31:00 PM +//Maya ASCII 2023 scene +//Name: test_project_test_asset_test_task_v002.ma +//Last modified: Thu, Dec 07, 2023 03:53:06 PM //Codeset: 1252 -requires maya "2022"; -requires -nodeType "polyDisc" "modelingToolkit" "0.0.0.0"; -requires "stereoCamera" "10.0"; -requires -nodeType "aiOptions" -nodeType "aiAOVDriver" -nodeType "aiAOVFilter" "mtoa" "5.2.2.1"; +requires maya "2023"; requires -nodeType "simpleSelector" -nodeType "renderSetupLayer" -nodeType "renderSetup" -nodeType "collection" "renderSetup.py" "1.0"; requires "stereoCamera" "10.0"; +requires -nodeType "aiOptions" -nodeType "aiAOVDriver" -nodeType "aiAOVFilter" "mtoa" "5.2.1.1"; +requires -nodeType "polyDisc" "modelingToolkit" "0.0.0.0"; +requires "stereoCamera" "10.0"; currentUnit -l centimeter -a degree -t pal; fileInfo "application" "maya"; -fileInfo "product" "Maya 2022"; -fileInfo "version" "2022"; -fileInfo "cutIdentifier" "202205171752-c25c06f306"; -fileInfo "osv" "Windows 10 Pro v2009 (Build: 19044)"; +fileInfo "product" "Maya 2023"; +fileInfo "version" "2023"; +fileInfo "cutIdentifier" "202211021031-847a9f9623"; +fileInfo "osv" "Windows 10 Pro v2009 (Build: 19045)"; fileInfo "license" "education"; -fileInfo "UUID" "019C7F50-40EF-1435-E27F-729F64685E67"; +fileInfo "UUID" "7A992745-4AD5-777F-5575-B4BFAC62B1D0"; fileInfo "OpenPypeContext" "eyJwdWJsaXNoX2F0dHJpYnV0ZXMiOiB7IlZhbGlkYXRlQ29udGFpbmVycyI6IHsiYWN0aXZlIjogdHJ1ZX19fQ=="; createNode transform -s -n "persp"; rename -uid "D52C935B-47C9-D868-A875-D799DD17B3A1"; @@ -142,19 +142,20 @@ createNode camera -n "perspShape1" -p "persp1"; setAttr ".hc" -type "string" "viewSet -p %camera"; setAttr ".dr" yes; createNode lightLinker -s -n "lightLinker1"; - rename -uid "6B9ADCD4-41B9-5BCC-826D-4A874A278510"; + rename -uid "09465BD3-42E5-18E4-7906-20A99BB2A6C0"; setAttr -s 2 ".lnk"; setAttr -s 2 ".slnk"; createNode shapeEditorManager -n "shapeEditorManager"; - rename -uid "5B4518C5-46C9-6921-690E-EFAF77B21A36"; + rename -uid "9F2E8009-4D69-046B-FCC4-28A8CE8F86DB"; createNode poseInterpolatorManager -n "poseInterpolatorManager"; - rename -uid "8518F293-4F06-BFF2-647A-72A099FBF025"; + rename -uid "6757AD81-40B0-A747-69C3-D9A56259571E"; createNode displayLayerManager -n "layerManager"; - rename -uid "04D880D5-4D86-0C58-CA3D-208ABE3E1E16"; + rename -uid "6F055ED5-4D91-8F85-7951-B4A13543A561"; createNode displayLayer -n "defaultLayer"; rename -uid "4A776D1B-401F-7069-1C74-A7AAE84CEE03"; + setAttr ".ufem" -type "stringArray" 0 ; createNode renderLayerManager -n "renderLayerManager"; - rename -uid "B719B8BE-46BF-12E6-BEBA-B0AD4DBDBA87"; + rename -uid "55626D2B-4FD5-61A1-7AB2-47B13F19D8AA"; setAttr -s 2 ".rlmi[1]" 1; setAttr -s 2 ".rlmi"; createNode renderLayer -n "defaultRenderLayer"; @@ -167,6 +168,7 @@ createNode polySphere -n "polySphere1"; createNode objectSet -n "modelMain"; rename -uid "A76AD4F8-4CF5-AA0D-4E98-BABEE6454CC3"; addAttr -ci true -sn "cbId" -ln "cbId" -dt "string"; + addAttr -ci true -sn "instance_id" -ln "instance_id" -dt "string"; addAttr -ci true -sn "id" -ln "id" -dt "string"; addAttr -ci true -sn "family" -ln "family" -dt "string"; addAttr -ci true -sn "subset" -ln "subset" -dt "string"; @@ -175,18 +177,19 @@ createNode objectSet -n "modelMain"; addAttr -ci true -sn "variant" -ln "variant" -dt "string"; addAttr -ci true -sn "asset" -ln "asset" -dt "string"; addAttr -ci true -sn "task" -ln "task" -dt "string"; - addAttr -ci true -sn "instance_id" -ln "instance_id" -dt "string"; addAttr -ci true -sn "writeColorSets" -ln "writeColorSets" -min 0 -max 1 -at "bool"; addAttr -ci true -sn "writeFaceSets" -ln "writeFaceSets" -min 0 -max 1 -at "bool"; - addAttr -ci true -sn "includeParentHierarchy" -ln "includeParentHierarchy" -min + addAttr -ci true -sn "includeParentHierarchy" -ln "includeParentHierarchy" -min 0 -max 1 -at "bool"; addAttr -ci true -sn "attr" -ln "attr" -dt "string"; addAttr -ci true -sn "attrPrefix" -ln "attrPrefix" -dt "string"; addAttr -ci true -sn "publish_attributes" -ln "publish_attributes" -dt "string"; - addAttr -ci true -sn "__creator_attributes_keys" -ln "__creator_attributes_keys" + addAttr -ci true -sn "creator_attributes" -ln "creator_attributes" -dt "string"; + addAttr -ci true -sn "__creator_attributes_keys" -ln "__creator_attributes_keys" -dt "string"; setAttr ".ihi" 0; setAttr ".cbId" -type "string" "60df31e2be2b48bd3695c056:7364ea6776c9"; + setAttr ".instance_id" -type "string" "6889d3db-b813-43db-96de-9ba555dc4472"; setAttr ".id" -type "string" "pyblish.avalon.instance"; setAttr ".family" -type "string" "model"; setAttr ".subset" -type "string" "modelMain"; @@ -195,59 +198,68 @@ createNode objectSet -n "modelMain"; setAttr ".variant" -type "string" "Main"; setAttr ".asset" -type "string" "test_asset"; setAttr ".task" -type "string" "test_task"; - setAttr ".instance_id" -type "string" "6889d3db-b813-43db-96de-9ba555dc4472"; setAttr -cb on ".writeColorSets"; setAttr -cb on ".writeFaceSets"; setAttr -cb on ".includeParentHierarchy"; setAttr ".attr" -type "string" ""; setAttr ".attrPrefix" -type "string" ""; - setAttr ".publish_attributes" -type "string" "{\"ValidateNodeIDsRelated\": {\"active\": true}, \"ValidateTransformNamingSuffix\": {\"active\": true}, \"ValidateColorSets\": {\"active\": true}, \"ValidateMeshArnoldAttributes\": {\"active\": true}, \"ValidateMeshHasUVs\": {\"active\": true}, \"ValidateMeshNonZeroEdgeLength\": {\"active\": true}, \"ExtractModel\": {\"active\": true}}"; + setAttr ".publish_attributes" -type "string" "{\"ValidateNodeIDsRelated\": {\"active\": true}, \"ValidateInstanceInContext\": {\"active\": true}, \"ValidateTransformNamingSuffix\": {\"active\": true}, \"ValidateColorSets\": {\"active\": true}, \"ValidateMeshHasUVs\": {\"active\": true}, \"ValidateMeshNonZeroEdgeLength\": {\"active\": true}, \"ExtractModel\": {\"active\": true}, \"ValidateMeshArnoldAttributes\": {\"active\": true}}"; + setAttr ".creator_attributes" -type "string" "{}"; setAttr ".__creator_attributes_keys" -type "string" "writeColorSets,writeFaceSets,includeParentHierarchy,attr,attrPrefix"; createNode script -n "uiConfigurationScriptNode"; rename -uid "4B7AFB53-452E-E870-63E1-CCA1DD6EAF13"; setAttr ".b" -type "string" ( "// Maya Mel UI Configuration File.\n//\n// This script is machine generated. Edit at your own risk.\n//\n//\n\nglobal string $gMainPane;\nif (`paneLayout -exists $gMainPane`) {\n\n\tglobal int $gUseScenePanelConfig;\n\tint $useSceneConfig = $gUseScenePanelConfig;\n\tint $nodeEditorPanelVisible = stringArrayContains(\"nodeEditorPanel1\", `getPanel -vis`);\n\tint $nodeEditorWorkspaceControlOpen = (`workspaceControl -exists nodeEditorPanel1Window` && `workspaceControl -q -visible nodeEditorPanel1Window`);\n\tint $menusOkayInPanels = `optionVar -q allowMenusInPanels`;\n\tint $nVisPanes = `paneLayout -q -nvp $gMainPane`;\n\tint $nPanes = 0;\n\tstring $editorName;\n\tstring $panelName;\n\tstring $itemFilterName;\n\tstring $panelConfig;\n\n\t//\n\t// get current state of the UI\n\t//\n\tsceneUIReplacement -update $gMainPane;\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"modelPanel\" (localizedPanelLabel(\"Top View\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tmodelPanel -edit -l (localizedPanelLabel(\"Top View\")) -mbv $menusOkayInPanels $panelName;\n" - + "\t\t$editorName = $panelName;\n modelEditor -e \n -camera \"|top\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"smoothShaded\" \n -activeOnly 0\n -ignorePanZoom 0\n -wireframeOnShaded 0\n -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n -useDefaultMaterial 0\n -bufferMode \"double\" \n -twoSidedLighting 0\n -backfaceCulling 0\n -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n -lineWidth 1\n -textureAnisotropic 0\n -textureHilight 1\n -textureSampling 2\n -textureDisplay \"modulate\" \n -textureMaxSize 16384\n -fogging 0\n -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n" + + "\t\t$editorName = $panelName;\n modelEditor -e \n -camera \"|top\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"smoothShaded\" \n -activeOnly 0\n -ignorePanZoom 0\n -wireframeOnShaded 0\n -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n -useDefaultMaterial 0\n -bufferMode \"double\" \n -twoSidedLighting 0\n -backfaceCulling 0\n -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n -lineWidth 1\n -textureAnisotropic 0\n -textureHilight 1\n -textureSampling 2\n -textureDisplay \"modulate\" \n -textureMaxSize 32768\n -fogging 0\n -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n" + " -depthOfFieldPreview 1\n -maxConstantTransparency 1\n -rendererName \"vp2Renderer\" \n -objectFilterShowInHUD 1\n -isFiltered 0\n -colorResolution 256 256 \n -bumpResolution 512 512 \n -textureCompression 0\n -transparencyAlgorithm \"frontAndBackCull\" \n -transpInShadows 0\n -cullingOverride \"none\" \n -lowQualityLighting 0\n -maximumNumHardwareLights 1\n -occlusionCulling 0\n -shadingModel 0\n -useBaseRenderer 0\n -useReducedRenderer 0\n -smallObjectCulling 0\n -smallObjectThreshold -1 \n -interactiveDisableShadows 0\n -interactiveBackFaceCull 0\n -sortTransparent 1\n -controllers 1\n -nurbsCurves 1\n -nurbsSurfaces 1\n -polymeshes 1\n -subdivSurfaces 1\n -planes 1\n -lights 1\n -cameras 1\n -controlVertices 1\n" - + " -hulls 1\n -grid 1\n -imagePlane 1\n -joints 1\n -ikHandles 1\n -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n -nCloths 1\n -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n -locators 1\n -manipulators 1\n -pluginShapes 1\n -dimensions 1\n -handles 1\n -pivots 1\n -textures 1\n -strokes 1\n -motionTrails 1\n -clipGhosts 1\n -greasePencils 1\n -shadows 0\n -captureSequenceNumber -1\n -width 1\n -height 1\n -sceneRenderFilter 0\n $editorName;\n modelEditor -e -viewSelected 0 $editorName;\n modelEditor -e \n -pluginObjects \"gpuCacheDisplayFilter\" 1 \n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n" - + "\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"modelPanel\" (localizedPanelLabel(\"Side View\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tmodelPanel -edit -l (localizedPanelLabel(\"Side View\")) -mbv $menusOkayInPanels $panelName;\n\t\t$editorName = $panelName;\n modelEditor -e \n -camera \"|side\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"smoothShaded\" \n -activeOnly 0\n -ignorePanZoom 0\n -wireframeOnShaded 0\n -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n -useDefaultMaterial 0\n -bufferMode \"double\" \n -twoSidedLighting 0\n -backfaceCulling 0\n -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n -lineWidth 1\n -textureAnisotropic 0\n -textureHilight 1\n -textureSampling 2\n" - + " -textureDisplay \"modulate\" \n -textureMaxSize 16384\n -fogging 0\n -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n -depthOfFieldPreview 1\n -maxConstantTransparency 1\n -rendererName \"vp2Renderer\" \n -objectFilterShowInHUD 1\n -isFiltered 0\n -colorResolution 256 256 \n -bumpResolution 512 512 \n -textureCompression 0\n -transparencyAlgorithm \"frontAndBackCull\" \n -transpInShadows 0\n -cullingOverride \"none\" \n -lowQualityLighting 0\n -maximumNumHardwareLights 1\n -occlusionCulling 0\n -shadingModel 0\n -useBaseRenderer 0\n -useReducedRenderer 0\n -smallObjectCulling 0\n -smallObjectThreshold -1 \n -interactiveDisableShadows 0\n -interactiveBackFaceCull 0\n" - + " -sortTransparent 1\n -controllers 1\n -nurbsCurves 1\n -nurbsSurfaces 1\n -polymeshes 1\n -subdivSurfaces 1\n -planes 1\n -lights 1\n -cameras 1\n -controlVertices 1\n -hulls 1\n -grid 1\n -imagePlane 1\n -joints 1\n -ikHandles 1\n -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n -nCloths 1\n -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n -locators 1\n -manipulators 1\n -pluginShapes 1\n -dimensions 1\n -handles 1\n -pivots 1\n -textures 1\n -strokes 1\n -motionTrails 1\n -clipGhosts 1\n -greasePencils 1\n -shadows 0\n -captureSequenceNumber -1\n -width 1\n -height 1\n" - + " -sceneRenderFilter 0\n $editorName;\n modelEditor -e -viewSelected 0 $editorName;\n modelEditor -e \n -pluginObjects \"gpuCacheDisplayFilter\" 1 \n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"modelPanel\" (localizedPanelLabel(\"Front View\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tmodelPanel -edit -l (localizedPanelLabel(\"Front View\")) -mbv $menusOkayInPanels $panelName;\n\t\t$editorName = $panelName;\n modelEditor -e \n -camera \"|front\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"smoothShaded\" \n -activeOnly 0\n -ignorePanZoom 0\n -wireframeOnShaded 0\n -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n -useDefaultMaterial 0\n -bufferMode \"double\" \n -twoSidedLighting 0\n -backfaceCulling 0\n" - + " -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n -lineWidth 1\n -textureAnisotropic 0\n -textureHilight 1\n -textureSampling 2\n -textureDisplay \"modulate\" \n -textureMaxSize 16384\n -fogging 0\n -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n -depthOfFieldPreview 1\n -maxConstantTransparency 1\n -rendererName \"vp2Renderer\" \n -objectFilterShowInHUD 1\n -isFiltered 0\n -colorResolution 256 256 \n -bumpResolution 512 512 \n -textureCompression 0\n -transparencyAlgorithm \"frontAndBackCull\" \n -transpInShadows 0\n -cullingOverride \"none\" \n -lowQualityLighting 0\n -maximumNumHardwareLights 1\n -occlusionCulling 0\n" - + " -shadingModel 0\n -useBaseRenderer 0\n -useReducedRenderer 0\n -smallObjectCulling 0\n -smallObjectThreshold -1 \n -interactiveDisableShadows 0\n -interactiveBackFaceCull 0\n -sortTransparent 1\n -controllers 1\n -nurbsCurves 1\n -nurbsSurfaces 1\n -polymeshes 1\n -subdivSurfaces 1\n -planes 1\n -lights 1\n -cameras 1\n -controlVertices 1\n -hulls 1\n -grid 1\n -imagePlane 1\n -joints 1\n -ikHandles 1\n -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n -nCloths 1\n -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n -locators 1\n -manipulators 1\n -pluginShapes 1\n -dimensions 1\n -handles 1\n -pivots 1\n" - + " -textures 1\n -strokes 1\n -motionTrails 1\n -clipGhosts 1\n -greasePencils 1\n -shadows 0\n -captureSequenceNumber -1\n -width 1\n -height 1\n -sceneRenderFilter 0\n $editorName;\n modelEditor -e -viewSelected 0 $editorName;\n modelEditor -e \n -pluginObjects \"gpuCacheDisplayFilter\" 1 \n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"modelPanel\" (localizedPanelLabel(\"Persp View\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tmodelPanel -edit -l (localizedPanelLabel(\"Persp View\")) -mbv $menusOkayInPanels $panelName;\n\t\t$editorName = $panelName;\n modelEditor -e \n -camera \"|persp\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"smoothShaded\" \n -activeOnly 0\n -ignorePanZoom 0\n" - + " -wireframeOnShaded 0\n -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n -useDefaultMaterial 0\n -bufferMode \"double\" \n -twoSidedLighting 0\n -backfaceCulling 0\n -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n -lineWidth 1\n -textureAnisotropic 0\n -textureHilight 1\n -textureSampling 2\n -textureDisplay \"modulate\" \n -textureMaxSize 16384\n -fogging 0\n -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n -depthOfFieldPreview 1\n -maxConstantTransparency 1\n -rendererName \"vp2Renderer\" \n -objectFilterShowInHUD 1\n -isFiltered 0\n -colorResolution 256 256 \n -bumpResolution 512 512 \n" - + " -textureCompression 0\n -transparencyAlgorithm \"frontAndBackCull\" \n -transpInShadows 0\n -cullingOverride \"none\" \n -lowQualityLighting 0\n -maximumNumHardwareLights 1\n -occlusionCulling 0\n -shadingModel 0\n -useBaseRenderer 0\n -useReducedRenderer 0\n -smallObjectCulling 0\n -smallObjectThreshold -1 \n -interactiveDisableShadows 0\n -interactiveBackFaceCull 0\n -sortTransparent 1\n -controllers 1\n -nurbsCurves 1\n -nurbsSurfaces 1\n -polymeshes 1\n -subdivSurfaces 1\n -planes 1\n -lights 1\n -cameras 1\n -controlVertices 1\n -hulls 1\n -grid 1\n -imagePlane 1\n -joints 1\n -ikHandles 1\n -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n" - + " -nCloths 1\n -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n -locators 1\n -manipulators 1\n -pluginShapes 1\n -dimensions 1\n -handles 1\n -pivots 1\n -textures 1\n -strokes 1\n -motionTrails 1\n -clipGhosts 1\n -greasePencils 1\n -shadows 0\n -captureSequenceNumber -1\n -width 1312\n -height 732\n -sceneRenderFilter 0\n $editorName;\n modelEditor -e -viewSelected 0 $editorName;\n modelEditor -e \n -pluginObjects \"gpuCacheDisplayFilter\" 1 \n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"outlinerPanel\" (localizedPanelLabel(\"ToggledOutliner\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\toutlinerPanel -edit -l (localizedPanelLabel(\"ToggledOutliner\")) -mbv $menusOkayInPanels $panelName;\n" - + "\t\t$editorName = $panelName;\n outlinerEditor -e \n -docTag \"isolOutln_fromSeln\" \n -showShapes 0\n -showAssignedMaterials 0\n -showTimeEditor 1\n -showReferenceNodes 1\n -showReferenceMembers 1\n -showAttributes 0\n -showConnected 0\n -showAnimCurvesOnly 0\n -showMuteInfo 0\n -organizeByLayer 1\n -organizeByClip 1\n -showAnimLayerWeight 1\n -autoExpandLayers 1\n -autoExpand 0\n -showDagOnly 1\n -showAssets 1\n -showContainedOnly 1\n -showPublishedAsConnected 0\n -showParentContainers 0\n -showContainerContents 1\n -ignoreDagHierarchy 0\n -expandConnections 0\n -showUpstreamCurves 1\n -showUnitlessCurves 1\n -showCompounds 1\n -showLeafs 1\n -showNumericAttrsOnly 0\n -highlightActive 1\n -autoSelectNewObjects 0\n" - + " -doNotSelectNewObjects 0\n -dropIsParent 1\n -transmitFilters 0\n -setFilter \"defaultSetFilter\" \n -showSetMembers 1\n -allowMultiSelection 1\n -alwaysToggleSelect 0\n -directSelect 0\n -isSet 0\n -isSetMember 0\n -displayMode \"DAG\" \n -expandObjects 0\n -setsIgnoreFilters 1\n -containersIgnoreFilters 0\n -editAttrName 0\n -showAttrValues 0\n -highlightSecondary 0\n -showUVAttrsOnly 0\n -showTextureNodesOnly 0\n -attrAlphaOrder \"default\" \n -animLayerFilterOptions \"allAffecting\" \n -sortOrder \"none\" \n -longNames 0\n -niceNames 1\n -selectCommand \"print(\\\"\\\")\" \n -showNamespace 1\n -showPinIcons 0\n -mapMotionTrails 0\n -ignoreHiddenAttribute 1\n -ignoreOutlinerColor 0\n -renderFilterVisible 0\n -renderFilterIndex 0\n" - + " -selectionOrder \"chronological\" \n -expandAttribute 0\n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"outlinerPanel\" (localizedPanelLabel(\"Outliner\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\toutlinerPanel -edit -l (localizedPanelLabel(\"Outliner\")) -mbv $menusOkayInPanels $panelName;\n\t\t$editorName = $panelName;\n outlinerEditor -e \n -showShapes 0\n -showAssignedMaterials 0\n -showTimeEditor 1\n -showReferenceNodes 0\n -showReferenceMembers 0\n -showAttributes 0\n -showConnected 0\n -showAnimCurvesOnly 0\n -showMuteInfo 0\n -organizeByLayer 1\n -organizeByClip 1\n -showAnimLayerWeight 1\n -autoExpandLayers 1\n -autoExpand 0\n -showDagOnly 1\n -showAssets 1\n -showContainedOnly 1\n -showPublishedAsConnected 0\n" - + " -showParentContainers 0\n -showContainerContents 1\n -ignoreDagHierarchy 0\n -expandConnections 0\n -showUpstreamCurves 1\n -showUnitlessCurves 1\n -showCompounds 1\n -showLeafs 1\n -showNumericAttrsOnly 0\n -highlightActive 1\n -autoSelectNewObjects 0\n -doNotSelectNewObjects 0\n -dropIsParent 1\n -transmitFilters 0\n -setFilter \"defaultSetFilter\" \n -showSetMembers 1\n -allowMultiSelection 1\n -alwaysToggleSelect 0\n -directSelect 0\n -displayMode \"DAG\" \n -expandObjects 0\n -setsIgnoreFilters 1\n -containersIgnoreFilters 0\n -editAttrName 0\n -showAttrValues 0\n -highlightSecondary 0\n -showUVAttrsOnly 0\n -showTextureNodesOnly 0\n -attrAlphaOrder \"default\" \n -animLayerFilterOptions \"allAffecting\" \n -sortOrder \"none\" \n" - + " -longNames 0\n -niceNames 1\n -showNamespace 1\n -showPinIcons 0\n -mapMotionTrails 0\n -ignoreHiddenAttribute 0\n -ignoreOutlinerColor 0\n -renderFilterVisible 0\n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"graphEditor\" (localizedPanelLabel(\"Graph Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Graph Editor\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = ($panelName+\"OutlineEd\");\n outlinerEditor -e \n -showShapes 1\n -showAssignedMaterials 0\n -showTimeEditor 1\n -showReferenceNodes 0\n -showReferenceMembers 0\n -showAttributes 1\n -showConnected 1\n -showAnimCurvesOnly 1\n -showMuteInfo 0\n -organizeByLayer 1\n" - + " -organizeByClip 1\n -showAnimLayerWeight 1\n -autoExpandLayers 1\n -autoExpand 1\n -showDagOnly 0\n -showAssets 1\n -showContainedOnly 0\n -showPublishedAsConnected 0\n -showParentContainers 0\n -showContainerContents 0\n -ignoreDagHierarchy 0\n -expandConnections 1\n -showUpstreamCurves 1\n -showUnitlessCurves 1\n -showCompounds 0\n -showLeafs 1\n -showNumericAttrsOnly 1\n -highlightActive 0\n -autoSelectNewObjects 1\n -doNotSelectNewObjects 0\n -dropIsParent 1\n -transmitFilters 1\n -setFilter \"0\" \n -showSetMembers 0\n -allowMultiSelection 1\n -alwaysToggleSelect 0\n -directSelect 0\n -displayMode \"DAG\" \n -expandObjects 0\n" - + " -setsIgnoreFilters 1\n -containersIgnoreFilters 0\n -editAttrName 0\n -showAttrValues 0\n -highlightSecondary 0\n -showUVAttrsOnly 0\n -showTextureNodesOnly 0\n -attrAlphaOrder \"default\" \n -animLayerFilterOptions \"allAffecting\" \n -sortOrder \"none\" \n -longNames 0\n -niceNames 1\n -showNamespace 1\n -showPinIcons 1\n -mapMotionTrails 1\n -ignoreHiddenAttribute 0\n -ignoreOutlinerColor 0\n -renderFilterVisible 0\n $editorName;\n\n\t\t\t$editorName = ($panelName+\"GraphEd\");\n animCurveEditor -e \n -displayValues 0\n -snapTime \"integer\" \n -snapValue \"none\" \n -showPlayRangeShades \"on\" \n -lockPlayRangeShades \"off\" \n -smoothness \"fine\" \n -resultSamples 1.041667\n" - + " -resultScreenSamples 0\n -resultUpdate \"delayed\" \n -showUpstreamCurves 1\n -keyMinScale 1\n -stackedCurvesMin -1\n -stackedCurvesMax 1\n -stackedCurvesSpace 0.2\n -preSelectionHighlight 0\n -constrainDrag 0\n -valueLinesToggle 1\n -highlightAffectedCurves 0\n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"dopeSheetPanel\" (localizedPanelLabel(\"Dope Sheet\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Dope Sheet\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = ($panelName+\"OutlineEd\");\n outlinerEditor -e \n -showShapes 1\n -showAssignedMaterials 0\n -showTimeEditor 1\n -showReferenceNodes 0\n -showReferenceMembers 0\n" - + " -showAttributes 1\n -showConnected 1\n -showAnimCurvesOnly 1\n -showMuteInfo 0\n -organizeByLayer 1\n -organizeByClip 1\n -showAnimLayerWeight 1\n -autoExpandLayers 1\n -autoExpand 0\n -showDagOnly 0\n -showAssets 1\n -showContainedOnly 0\n -showPublishedAsConnected 0\n -showParentContainers 0\n -showContainerContents 0\n -ignoreDagHierarchy 0\n -expandConnections 1\n -showUpstreamCurves 1\n -showUnitlessCurves 0\n -showCompounds 1\n -showLeafs 1\n -showNumericAttrsOnly 1\n -highlightActive 0\n -autoSelectNewObjects 0\n -doNotSelectNewObjects 1\n -dropIsParent 1\n -transmitFilters 0\n -setFilter \"0\" \n -showSetMembers 0\n" - + " -allowMultiSelection 1\n -alwaysToggleSelect 0\n -directSelect 0\n -displayMode \"DAG\" \n -expandObjects 0\n -setsIgnoreFilters 1\n -containersIgnoreFilters 0\n -editAttrName 0\n -showAttrValues 0\n -highlightSecondary 0\n -showUVAttrsOnly 0\n -showTextureNodesOnly 0\n -attrAlphaOrder \"default\" \n -animLayerFilterOptions \"allAffecting\" \n -sortOrder \"none\" \n -longNames 0\n -niceNames 1\n -showNamespace 1\n -showPinIcons 0\n -mapMotionTrails 1\n -ignoreHiddenAttribute 0\n -ignoreOutlinerColor 0\n -renderFilterVisible 0\n $editorName;\n\n\t\t\t$editorName = ($panelName+\"DopeSheetEd\");\n dopeSheetEditor -e \n -displayValues 0\n -snapTime \"integer\" \n" - + " -snapValue \"none\" \n -outliner \"dopeSheetPanel1OutlineEd\" \n -showSummary 1\n -showScene 0\n -hierarchyBelow 0\n -showTicks 1\n -selectionWindow 0 0 0 0 \n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"timeEditorPanel\" (localizedPanelLabel(\"Time Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Time Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"clipEditorPanel\" (localizedPanelLabel(\"Trax Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Trax Editor\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = clipEditorNameFromPanel($panelName);\n" - + " clipEditor -e \n -displayValues 0\n -snapTime \"none\" \n -snapValue \"none\" \n -initialized 0\n -manageSequencer 0 \n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"sequenceEditorPanel\" (localizedPanelLabel(\"Camera Sequencer\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Camera Sequencer\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = sequenceEditorNameFromPanel($panelName);\n clipEditor -e \n -displayValues 0\n -snapTime \"none\" \n -snapValue \"none\" \n -initialized 0\n -manageSequencer 1 \n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"hyperGraphPanel\" (localizedPanelLabel(\"Hypergraph Hierarchy\")) `;\n" - + "\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Hypergraph Hierarchy\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = ($panelName+\"HyperGraphEd\");\n hyperGraph -e \n -graphLayoutStyle \"hierarchicalLayout\" \n -orientation \"horiz\" \n -mergeConnections 0\n -zoom 1\n -animateTransition 0\n -showRelationships 1\n -showShapes 0\n -showDeformers 0\n -showExpressions 0\n -showConstraints 0\n -showConnectionFromSelected 0\n -showConnectionToSelected 0\n -showConstraintLabels 0\n -showUnderworld 0\n -showInvisible 0\n -transitionFrames 1\n -opaqueContainers 0\n -freeform 0\n -imagePosition 0 0 \n -imageScale 1\n -imageEnabled 0\n -graphType \"DAG\" \n" - + " -heatMapDisplay 0\n -updateSelection 1\n -updateNodeAdded 1\n -useDrawOverrideColor 0\n -limitGraphTraversal -1\n -range 0 0 \n -iconSize \"smallIcons\" \n -showCachedConnections 0\n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"hyperShadePanel\" (localizedPanelLabel(\"Hypershade\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Hypershade\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"visorPanel\" (localizedPanelLabel(\"Visor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Visor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n" - + "\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"nodeEditorPanel\" (localizedPanelLabel(\"Node Editor\")) `;\n\tif ($nodeEditorPanelVisible || $nodeEditorWorkspaceControlOpen) {\n\t\tif (\"\" == $panelName) {\n\t\t\tif ($useSceneConfig) {\n\t\t\t\t$panelName = `scriptedPanel -unParent -type \"nodeEditorPanel\" -l (localizedPanelLabel(\"Node Editor\")) -mbv $menusOkayInPanels `;\n\n\t\t\t$editorName = ($panelName+\"NodeEditorEd\");\n nodeEditor -e \n -allAttributes 0\n -allNodes 0\n -autoSizeNodes 1\n -consistentNameSize 1\n -createNodeCommand \"nodeEdCreateNodeCommand\" \n -connectNodeOnCreation 0\n -connectOnDrop 0\n -copyConnectionsOnPaste 0\n -connectionStyle \"bezier\" \n -defaultPinnedState 0\n -additiveGraphingMode 0\n -settingsChangedCallback \"nodeEdSyncControls\" \n -traversalDepthLimit -1\n -keyPressCommand \"nodeEdKeyPressCommand\" \n" - + " -nodeTitleMode \"name\" \n -gridSnap 0\n -gridVisibility 1\n -crosshairOnEdgeDragging 0\n -popupMenuScript \"nodeEdBuildPanelMenus\" \n -showNamespace 1\n -showShapes 1\n -showSGShapes 0\n -showTransforms 1\n -useAssets 1\n -syncedSelection 1\n -extendToShapes 1\n -editorMode \"default\" \n -hasWatchpoint 0\n $editorName;\n\t\t\t}\n\t\t} else {\n\t\t\t$label = `panel -q -label $panelName`;\n\t\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Node Editor\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = ($panelName+\"NodeEditorEd\");\n nodeEditor -e \n -allAttributes 0\n -allNodes 0\n -autoSizeNodes 1\n -consistentNameSize 1\n -createNodeCommand \"nodeEdCreateNodeCommand\" \n -connectNodeOnCreation 0\n -connectOnDrop 0\n" - + " -copyConnectionsOnPaste 0\n -connectionStyle \"bezier\" \n -defaultPinnedState 0\n -additiveGraphingMode 0\n -settingsChangedCallback \"nodeEdSyncControls\" \n -traversalDepthLimit -1\n -keyPressCommand \"nodeEdKeyPressCommand\" \n -nodeTitleMode \"name\" \n -gridSnap 0\n -gridVisibility 1\n -crosshairOnEdgeDragging 0\n -popupMenuScript \"nodeEdBuildPanelMenus\" \n -showNamespace 1\n -showShapes 1\n -showSGShapes 0\n -showTransforms 1\n -useAssets 1\n -syncedSelection 1\n -extendToShapes 1\n -editorMode \"default\" \n -hasWatchpoint 0\n $editorName;\n\t\t\tif (!$useSceneConfig) {\n\t\t\t\tpanel -e -l $label $panelName;\n\t\t\t}\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"createNodePanel\" (localizedPanelLabel(\"Create Node\")) `;\n" - + "\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Create Node\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"polyTexturePlacementPanel\" (localizedPanelLabel(\"UV Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"UV Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"renderWindowPanel\" (localizedPanelLabel(\"Render View\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Render View\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"shapePanel\" (localizedPanelLabel(\"Shape Editor\")) `;\n" - + "\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tshapePanel -edit -l (localizedPanelLabel(\"Shape Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"posePanel\" (localizedPanelLabel(\"Pose Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tposePanel -edit -l (localizedPanelLabel(\"Pose Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"dynRelEdPanel\" (localizedPanelLabel(\"Dynamic Relationships\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Dynamic Relationships\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"relationshipPanel\" (localizedPanelLabel(\"Relationship Editor\")) `;\n" - + "\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Relationship Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"referenceEditorPanel\" (localizedPanelLabel(\"Reference Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Reference Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"componentEditorPanel\" (localizedPanelLabel(\"Component Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Component Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"dynPaintScriptedPanelType\" (localizedPanelLabel(\"Paint Effects\")) `;\n" - + "\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Paint Effects\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"scriptEditorPanel\" (localizedPanelLabel(\"Script Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Script Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"profilerPanel\" (localizedPanelLabel(\"Profiler Tool\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Profiler Tool\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"contentBrowserPanel\" (localizedPanelLabel(\"Content Browser\")) `;\n" - + "\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Content Browser\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"Stereo\" (localizedPanelLabel(\"Stereo\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Stereo\")) -mbv $menusOkayInPanels $panelName;\n{ string $editorName = ($panelName+\"Editor\");\n stereoCameraView -e \n -camera \"|persp\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"wireframe\" \n -activeOnly 0\n -ignorePanZoom 0\n -wireframeOnShaded 0\n -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n -useDefaultMaterial 0\n -bufferMode \"double\" \n -twoSidedLighting 1\n" - + " -backfaceCulling 0\n -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n -lineWidth 1\n -textureAnisotropic 0\n -textureHilight 1\n -textureSampling 2\n -textureDisplay \"modulate\" \n -textureMaxSize 16384\n -fogging 0\n -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n -depthOfFieldPreview 1\n -maxConstantTransparency 1\n -objectFilterShowInHUD 1\n -isFiltered 0\n -colorResolution 4 4 \n -bumpResolution 4 4 \n -textureCompression 0\n -transparencyAlgorithm \"frontAndBackCull\" \n -transpInShadows 0\n -cullingOverride \"none\" \n" - + " -lowQualityLighting 0\n -maximumNumHardwareLights 0\n -occlusionCulling 0\n -shadingModel 0\n -useBaseRenderer 0\n -useReducedRenderer 0\n -smallObjectCulling 0\n -smallObjectThreshold -1 \n -interactiveDisableShadows 0\n -interactiveBackFaceCull 0\n -sortTransparent 1\n -controllers 1\n -nurbsCurves 1\n -nurbsSurfaces 1\n -polymeshes 1\n -subdivSurfaces 1\n -planes 1\n -lights 1\n -cameras 1\n -controlVertices 1\n -hulls 1\n -grid 1\n -imagePlane 1\n -joints 1\n -ikHandles 1\n -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n -nCloths 1\n" - + " -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n -locators 1\n -manipulators 1\n -pluginShapes 1\n -dimensions 1\n -handles 1\n -pivots 1\n -textures 1\n -strokes 1\n -motionTrails 1\n -clipGhosts 1\n -greasePencils 1\n -shadows 0\n -captureSequenceNumber -1\n -width 0\n -height 0\n -sceneRenderFilter 0\n -displayMode \"centerEye\" \n -viewColor 0 0 0 1 \n -useCustomBackground 1\n $editorName;\n stereoCameraView -e -viewSelected 0 $editorName;\n stereoCameraView -e \n -pluginObjects \"gpuCacheDisplayFilter\" 1 \n $editorName; };\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\tif ($useSceneConfig) {\n string $configName = `getPanel -cwl (localizedPanelLabel(\"Current Layout\"))`;\n" - + " if (\"\" != $configName) {\n\t\t\tpanelConfiguration -edit -label (localizedPanelLabel(\"Current Layout\")) \n\t\t\t\t-userCreated false\n\t\t\t\t-defaultImage \"vacantCell.xP:/\"\n\t\t\t\t-image \"\"\n\t\t\t\t-sc false\n\t\t\t\t-configString \"global string $gMainPane; paneLayout -e -cn \\\"single\\\" -ps 1 100 100 $gMainPane;\"\n\t\t\t\t-removeAllPanels\n\t\t\t\t-ap false\n\t\t\t\t\t(localizedPanelLabel(\"Persp View\")) \n\t\t\t\t\t\"modelPanel\"\n" - + "\t\t\t\t\t\"$panelName = `modelPanel -unParent -l (localizedPanelLabel(\\\"Persp View\\\")) -mbv $menusOkayInPanels `;\\n$editorName = $panelName;\\nmodelEditor -e \\n -cam `findStartUpCamera persp` \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 16384\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -greasePencils 1\\n -shadows 0\\n -captureSequenceNumber -1\\n -width 1312\\n -height 732\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName;\\nmodelEditor -e \\n -pluginObjects \\\"gpuCacheDisplayFilter\\\" 1 \\n $editorName\"\n" - + "\t\t\t\t\t\"modelPanel -edit -l (localizedPanelLabel(\\\"Persp View\\\")) -mbv $menusOkayInPanels $panelName;\\n$editorName = $panelName;\\nmodelEditor -e \\n -cam `findStartUpCamera persp` \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 16384\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -greasePencils 1\\n -shadows 0\\n -captureSequenceNumber -1\\n -width 1312\\n -height 732\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName;\\nmodelEditor -e \\n -pluginObjects \\\"gpuCacheDisplayFilter\\\" 1 \\n $editorName\"\n" + + " -hulls 1\n -grid 1\n -imagePlane 1\n -joints 1\n -ikHandles 1\n -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n -nCloths 1\n -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n -locators 1\n -manipulators 1\n -pluginShapes 1\n -dimensions 1\n -handles 1\n -pivots 1\n -textures 1\n -strokes 1\n -motionTrails 1\n -clipGhosts 1\n -bluePencil 1\n -greasePencils 0\n -shadows 0\n -captureSequenceNumber -1\n -width 477\n -height 276\n -sceneRenderFilter 0\n $editorName;\n modelEditor -e -viewSelected 0 $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"modelPanel\" (localizedPanelLabel(\"Side View\")) `;\n" + + "\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tmodelPanel -edit -l (localizedPanelLabel(\"Side View\")) -mbv $menusOkayInPanels $panelName;\n\t\t$editorName = $panelName;\n modelEditor -e \n -camera \"|side\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"smoothShaded\" \n -activeOnly 0\n -ignorePanZoom 0\n -wireframeOnShaded 0\n -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n -useDefaultMaterial 0\n -bufferMode \"double\" \n -twoSidedLighting 0\n -backfaceCulling 0\n -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n -lineWidth 1\n -textureAnisotropic 0\n -textureHilight 1\n -textureSampling 2\n -textureDisplay \"modulate\" \n -textureMaxSize 32768\n -fogging 0\n" + + " -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n -depthOfFieldPreview 1\n -maxConstantTransparency 1\n -rendererName \"vp2Renderer\" \n -objectFilterShowInHUD 1\n -isFiltered 0\n -colorResolution 256 256 \n -bumpResolution 512 512 \n -textureCompression 0\n -transparencyAlgorithm \"frontAndBackCull\" \n -transpInShadows 0\n -cullingOverride \"none\" \n -lowQualityLighting 0\n -maximumNumHardwareLights 1\n -occlusionCulling 0\n -shadingModel 0\n -useBaseRenderer 0\n -useReducedRenderer 0\n -smallObjectCulling 0\n -smallObjectThreshold -1 \n -interactiveDisableShadows 0\n -interactiveBackFaceCull 0\n -sortTransparent 1\n -controllers 1\n -nurbsCurves 1\n" + + " -nurbsSurfaces 1\n -polymeshes 1\n -subdivSurfaces 1\n -planes 1\n -lights 1\n -cameras 1\n -controlVertices 1\n -hulls 1\n -grid 1\n -imagePlane 1\n -joints 1\n -ikHandles 1\n -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n -nCloths 1\n -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n -locators 1\n -manipulators 1\n -pluginShapes 1\n -dimensions 1\n -handles 1\n -pivots 1\n -textures 1\n -strokes 1\n -motionTrails 1\n -clipGhosts 1\n -bluePencil 1\n -greasePencils 0\n -shadows 0\n -captureSequenceNumber -1\n -width 476\n -height 276\n -sceneRenderFilter 0\n $editorName;\n" + + " modelEditor -e -viewSelected 0 $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"modelPanel\" (localizedPanelLabel(\"Front View\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tmodelPanel -edit -l (localizedPanelLabel(\"Front View\")) -mbv $menusOkayInPanels $panelName;\n\t\t$editorName = $panelName;\n modelEditor -e \n -camera \"|front\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"smoothShaded\" \n -activeOnly 0\n -ignorePanZoom 0\n -wireframeOnShaded 0\n -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n -useDefaultMaterial 0\n -bufferMode \"double\" \n -twoSidedLighting 0\n -backfaceCulling 0\n -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n" + + " -lineWidth 1\n -textureAnisotropic 0\n -textureHilight 1\n -textureSampling 2\n -textureDisplay \"modulate\" \n -textureMaxSize 32768\n -fogging 0\n -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n -depthOfFieldPreview 1\n -maxConstantTransparency 1\n -rendererName \"vp2Renderer\" \n -objectFilterShowInHUD 1\n -isFiltered 0\n -colorResolution 256 256 \n -bumpResolution 512 512 \n -textureCompression 0\n -transparencyAlgorithm \"frontAndBackCull\" \n -transpInShadows 0\n -cullingOverride \"none\" \n -lowQualityLighting 0\n -maximumNumHardwareLights 1\n -occlusionCulling 0\n -shadingModel 0\n -useBaseRenderer 0\n -useReducedRenderer 0\n -smallObjectCulling 0\n" + + " -smallObjectThreshold -1 \n -interactiveDisableShadows 0\n -interactiveBackFaceCull 0\n -sortTransparent 1\n -controllers 1\n -nurbsCurves 1\n -nurbsSurfaces 1\n -polymeshes 1\n -subdivSurfaces 1\n -planes 1\n -lights 1\n -cameras 1\n -controlVertices 1\n -hulls 1\n -grid 1\n -imagePlane 1\n -joints 1\n -ikHandles 1\n -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n -nCloths 1\n -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n -locators 1\n -manipulators 1\n -pluginShapes 1\n -dimensions 1\n -handles 1\n -pivots 1\n -textures 1\n -strokes 1\n -motionTrails 1\n -clipGhosts 1\n -bluePencil 1\n" + + " -greasePencils 0\n -shadows 0\n -captureSequenceNumber -1\n -width 477\n -height 276\n -sceneRenderFilter 0\n $editorName;\n modelEditor -e -viewSelected 0 $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"modelPanel\" (localizedPanelLabel(\"Persp View\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tmodelPanel -edit -l (localizedPanelLabel(\"Persp View\")) -mbv $menusOkayInPanels $panelName;\n\t\t$editorName = $panelName;\n modelEditor -e \n -camera \"|persp\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"smoothShaded\" \n -activeOnly 0\n -ignorePanZoom 0\n -wireframeOnShaded 0\n -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n -useDefaultMaterial 0\n -bufferMode \"double\" \n" + + " -twoSidedLighting 0\n -backfaceCulling 0\n -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n -lineWidth 1\n -textureAnisotropic 0\n -textureHilight 1\n -textureSampling 2\n -textureDisplay \"modulate\" \n -textureMaxSize 32768\n -fogging 0\n -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n -depthOfFieldPreview 1\n -maxConstantTransparency 1\n -rendererName \"vp2Renderer\" \n -objectFilterShowInHUD 1\n -isFiltered 0\n -colorResolution 256 256 \n -bumpResolution 512 512 \n -textureCompression 0\n -transparencyAlgorithm \"frontAndBackCull\" \n -transpInShadows 0\n -cullingOverride \"none\" \n -lowQualityLighting 0\n" + + " -maximumNumHardwareLights 1\n -occlusionCulling 0\n -shadingModel 0\n -useBaseRenderer 0\n -useReducedRenderer 0\n -smallObjectCulling 0\n -smallObjectThreshold -1 \n -interactiveDisableShadows 0\n -interactiveBackFaceCull 0\n -sortTransparent 1\n -controllers 1\n -nurbsCurves 1\n -nurbsSurfaces 1\n -polymeshes 1\n -subdivSurfaces 1\n -planes 1\n -lights 1\n -cameras 1\n -controlVertices 1\n -hulls 1\n -grid 1\n -imagePlane 1\n -joints 1\n -ikHandles 1\n -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n -nCloths 1\n -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n -locators 1\n -manipulators 1\n -pluginShapes 1\n" + + " -dimensions 1\n -handles 1\n -pivots 1\n -textures 1\n -strokes 1\n -motionTrails 1\n -clipGhosts 1\n -bluePencil 1\n -greasePencils 0\n -shadows 0\n -captureSequenceNumber -1\n -width 476\n -height 276\n -sceneRenderFilter 0\n $editorName;\n modelEditor -e -viewSelected 0 $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"outlinerPanel\" (localizedPanelLabel(\"ToggledOutliner\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\toutlinerPanel -edit -l (localizedPanelLabel(\"ToggledOutliner\")) -mbv $menusOkayInPanels $panelName;\n\t\t$editorName = $panelName;\n outlinerEditor -e \n -docTag \"isolOutln_fromSeln\" \n -showShapes 0\n -showAssignedMaterials 0\n -showTimeEditor 1\n -showReferenceNodes 1\n -showReferenceMembers 1\n" + + " -showAttributes 0\n -showConnected 0\n -showAnimCurvesOnly 0\n -showMuteInfo 0\n -organizeByLayer 1\n -organizeByClip 1\n -showAnimLayerWeight 1\n -autoExpandLayers 1\n -autoExpand 0\n -showDagOnly 1\n -showAssets 1\n -showContainedOnly 1\n -showPublishedAsConnected 0\n -showParentContainers 0\n -showContainerContents 1\n -ignoreDagHierarchy 0\n -expandConnections 0\n -showUpstreamCurves 1\n -showUnitlessCurves 1\n -showCompounds 1\n -showLeafs 1\n -showNumericAttrsOnly 0\n -highlightActive 1\n -autoSelectNewObjects 0\n -doNotSelectNewObjects 0\n -dropIsParent 1\n -transmitFilters 0\n -setFilter \"defaultSetFilter\" \n -showSetMembers 1\n -allowMultiSelection 1\n -alwaysToggleSelect 0\n -directSelect 0\n" + + " -isSet 0\n -isSetMember 0\n -showUfeItems 1\n -displayMode \"DAG\" \n -expandObjects 0\n -setsIgnoreFilters 1\n -containersIgnoreFilters 0\n -editAttrName 0\n -showAttrValues 0\n -highlightSecondary 0\n -showUVAttrsOnly 0\n -showTextureNodesOnly 0\n -attrAlphaOrder \"default\" \n -animLayerFilterOptions \"allAffecting\" \n -sortOrder \"none\" \n -longNames 0\n -niceNames 1\n -selectCommand \"print(\\\"\\\")\" \n -showNamespace 1\n -showPinIcons 0\n -mapMotionTrails 0\n -ignoreHiddenAttribute 0\n -ignoreOutlinerColor 0\n -renderFilterVisible 0\n -renderFilterIndex 0\n -selectionOrder \"chronological\" \n -expandAttribute 0\n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"outlinerPanel\" (localizedPanelLabel(\"Outliner\")) `;\n" + + "\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\toutlinerPanel -edit -l (localizedPanelLabel(\"Outliner\")) -mbv $menusOkayInPanels $panelName;\n\t\t$editorName = $panelName;\n outlinerEditor -e \n -showShapes 0\n -showAssignedMaterials 0\n -showTimeEditor 1\n -showReferenceNodes 0\n -showReferenceMembers 0\n -showAttributes 0\n -showConnected 0\n -showAnimCurvesOnly 0\n -showMuteInfo 0\n -organizeByLayer 1\n -organizeByClip 1\n -showAnimLayerWeight 1\n -autoExpandLayers 1\n -autoExpand 0\n -showDagOnly 1\n -showAssets 1\n -showContainedOnly 1\n -showPublishedAsConnected 0\n -showParentContainers 0\n -showContainerContents 1\n -ignoreDagHierarchy 0\n -expandConnections 0\n -showUpstreamCurves 1\n -showUnitlessCurves 1\n -showCompounds 1\n -showLeafs 1\n" + + " -showNumericAttrsOnly 0\n -highlightActive 1\n -autoSelectNewObjects 0\n -doNotSelectNewObjects 0\n -dropIsParent 1\n -transmitFilters 0\n -setFilter \"defaultSetFilter\" \n -showSetMembers 1\n -allowMultiSelection 1\n -alwaysToggleSelect 0\n -directSelect 0\n -showUfeItems 1\n -displayMode \"DAG\" \n -expandObjects 0\n -setsIgnoreFilters 1\n -containersIgnoreFilters 0\n -editAttrName 0\n -showAttrValues 0\n -highlightSecondary 0\n -showUVAttrsOnly 0\n -showTextureNodesOnly 0\n -attrAlphaOrder \"default\" \n -animLayerFilterOptions \"allAffecting\" \n -sortOrder \"none\" \n -longNames 0\n -niceNames 1\n -showNamespace 1\n -showPinIcons 0\n -mapMotionTrails 0\n -ignoreHiddenAttribute 0\n -ignoreOutlinerColor 0\n -renderFilterVisible 0\n" + + " $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"graphEditor\" (localizedPanelLabel(\"Graph Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Graph Editor\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = ($panelName+\"OutlineEd\");\n outlinerEditor -e \n -showShapes 1\n -showAssignedMaterials 0\n -showTimeEditor 1\n -showReferenceNodes 0\n -showReferenceMembers 0\n -showAttributes 1\n -showConnected 1\n -showAnimCurvesOnly 1\n -showMuteInfo 0\n -organizeByLayer 1\n -organizeByClip 1\n -showAnimLayerWeight 1\n -autoExpandLayers 1\n -autoExpand 1\n -showDagOnly 0\n -showAssets 1\n -showContainedOnly 0\n" + + " -showPublishedAsConnected 0\n -showParentContainers 0\n -showContainerContents 0\n -ignoreDagHierarchy 0\n -expandConnections 1\n -showUpstreamCurves 1\n -showUnitlessCurves 1\n -showCompounds 0\n -showLeafs 1\n -showNumericAttrsOnly 1\n -highlightActive 0\n -autoSelectNewObjects 1\n -doNotSelectNewObjects 0\n -dropIsParent 1\n -transmitFilters 1\n -setFilter \"0\" \n -showSetMembers 0\n -allowMultiSelection 1\n -alwaysToggleSelect 0\n -directSelect 0\n -showUfeItems 1\n -displayMode \"DAG\" \n -expandObjects 0\n -setsIgnoreFilters 1\n -containersIgnoreFilters 0\n -editAttrName 0\n -showAttrValues 0\n -highlightSecondary 0\n -showUVAttrsOnly 0\n" + + " -showTextureNodesOnly 0\n -attrAlphaOrder \"default\" \n -animLayerFilterOptions \"allAffecting\" \n -sortOrder \"none\" \n -longNames 0\n -niceNames 1\n -showNamespace 1\n -showPinIcons 1\n -mapMotionTrails 1\n -ignoreHiddenAttribute 0\n -ignoreOutlinerColor 0\n -renderFilterVisible 0\n $editorName;\n\n\t\t\t$editorName = ($panelName+\"GraphEd\");\n animCurveEditor -e \n -displayValues 0\n -snapTime \"integer\" \n -snapValue \"none\" \n -showPlayRangeShades \"on\" \n -lockPlayRangeShades \"off\" \n -smoothness \"fine\" \n -resultSamples 1.041667\n -resultScreenSamples 0\n -resultUpdate \"delayed\" \n -showUpstreamCurves 1\n -keyMinScale 1\n -stackedCurvesMin -1\n -stackedCurvesMax 1\n" + + " -stackedCurvesSpace 0.2\n -preSelectionHighlight 0\n -constrainDrag 0\n -valueLinesToggle 1\n -highlightAffectedCurves 0\n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"dopeSheetPanel\" (localizedPanelLabel(\"Dope Sheet\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Dope Sheet\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = ($panelName+\"OutlineEd\");\n outlinerEditor -e \n -showShapes 1\n -showAssignedMaterials 0\n -showTimeEditor 1\n -showReferenceNodes 0\n -showReferenceMembers 0\n -showAttributes 1\n -showConnected 1\n -showAnimCurvesOnly 1\n -showMuteInfo 0\n -organizeByLayer 1\n -organizeByClip 1\n" + + " -showAnimLayerWeight 1\n -autoExpandLayers 1\n -autoExpand 0\n -showDagOnly 0\n -showAssets 1\n -showContainedOnly 0\n -showPublishedAsConnected 0\n -showParentContainers 0\n -showContainerContents 0\n -ignoreDagHierarchy 0\n -expandConnections 1\n -showUpstreamCurves 1\n -showUnitlessCurves 0\n -showCompounds 1\n -showLeafs 1\n -showNumericAttrsOnly 1\n -highlightActive 0\n -autoSelectNewObjects 0\n -doNotSelectNewObjects 1\n -dropIsParent 1\n -transmitFilters 0\n -setFilter \"0\" \n -showSetMembers 0\n -allowMultiSelection 1\n -alwaysToggleSelect 0\n -directSelect 0\n -showUfeItems 1\n -displayMode \"DAG\" \n -expandObjects 0\n" + + " -setsIgnoreFilters 1\n -containersIgnoreFilters 0\n -editAttrName 0\n -showAttrValues 0\n -highlightSecondary 0\n -showUVAttrsOnly 0\n -showTextureNodesOnly 0\n -attrAlphaOrder \"default\" \n -animLayerFilterOptions \"allAffecting\" \n -sortOrder \"none\" \n -longNames 0\n -niceNames 1\n -showNamespace 1\n -showPinIcons 0\n -mapMotionTrails 1\n -ignoreHiddenAttribute 0\n -ignoreOutlinerColor 0\n -renderFilterVisible 0\n $editorName;\n\n\t\t\t$editorName = ($panelName+\"DopeSheetEd\");\n dopeSheetEditor -e \n -displayValues 0\n -snapTime \"integer\" \n -snapValue \"none\" \n -outliner \"dopeSheetPanel1OutlineEd\" \n -showSummary 1\n -showScene 0\n -hierarchyBelow 0\n" + + " -showTicks 1\n -selectionWindow 0 0 0 0 \n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"timeEditorPanel\" (localizedPanelLabel(\"Time Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Time Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"clipEditorPanel\" (localizedPanelLabel(\"Trax Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Trax Editor\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = clipEditorNameFromPanel($panelName);\n clipEditor -e \n -displayValues 0\n -snapTime \"none\" \n -snapValue \"none\" \n -initialized 0\n -manageSequencer 0 \n" + + " $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"sequenceEditorPanel\" (localizedPanelLabel(\"Camera Sequencer\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Camera Sequencer\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = sequenceEditorNameFromPanel($panelName);\n clipEditor -e \n -displayValues 0\n -snapTime \"none\" \n -snapValue \"none\" \n -initialized 0\n -manageSequencer 1 \n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"hyperGraphPanel\" (localizedPanelLabel(\"Hypergraph Hierarchy\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Hypergraph Hierarchy\")) -mbv $menusOkayInPanels $panelName;\n" + + "\n\t\t\t$editorName = ($panelName+\"HyperGraphEd\");\n hyperGraph -e \n -graphLayoutStyle \"hierarchicalLayout\" \n -orientation \"horiz\" \n -mergeConnections 0\n -zoom 1\n -animateTransition 0\n -showRelationships 1\n -showShapes 0\n -showDeformers 0\n -showExpressions 0\n -showConstraints 0\n -showConnectionFromSelected 0\n -showConnectionToSelected 0\n -showConstraintLabels 0\n -showUnderworld 0\n -showInvisible 0\n -transitionFrames 1\n -opaqueContainers 0\n -freeform 0\n -imagePosition 0 0 \n -imageScale 1\n -imageEnabled 0\n -graphType \"DAG\" \n -heatMapDisplay 0\n -updateSelection 1\n -updateNodeAdded 1\n -useDrawOverrideColor 0\n -limitGraphTraversal -1\n" + + " -range 0 0 \n -iconSize \"smallIcons\" \n -showCachedConnections 0\n $editorName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"hyperShadePanel\" (localizedPanelLabel(\"Hypershade\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Hypershade\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"visorPanel\" (localizedPanelLabel(\"Visor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Visor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"nodeEditorPanel\" (localizedPanelLabel(\"Node Editor\")) `;\n\tif ($nodeEditorPanelVisible || $nodeEditorWorkspaceControlOpen) {\n" + + "\t\tif (\"\" == $panelName) {\n\t\t\tif ($useSceneConfig) {\n\t\t\t\t$panelName = `scriptedPanel -unParent -type \"nodeEditorPanel\" -l (localizedPanelLabel(\"Node Editor\")) -mbv $menusOkayInPanels `;\n\n\t\t\t$editorName = ($panelName+\"NodeEditorEd\");\n nodeEditor -e \n -allAttributes 0\n -allNodes 0\n -autoSizeNodes 1\n -consistentNameSize 1\n -createNodeCommand \"nodeEdCreateNodeCommand\" \n -connectNodeOnCreation 0\n -connectOnDrop 0\n -copyConnectionsOnPaste 0\n -connectionStyle \"bezier\" \n -defaultPinnedState 0\n -additiveGraphingMode 1\n -connectedGraphingMode 1\n -settingsChangedCallback \"nodeEdSyncControls\" \n -traversalDepthLimit -1\n -keyPressCommand \"nodeEdKeyPressCommand\" \n -nodeTitleMode \"name\" \n -gridSnap 0\n -gridVisibility 1\n -crosshairOnEdgeDragging 0\n" + + " -popupMenuScript \"nodeEdBuildPanelMenus\" \n -showNamespace 1\n -showShapes 1\n -showSGShapes 0\n -showTransforms 1\n -useAssets 1\n -syncedSelection 1\n -extendToShapes 1\n -showUnitConversions 0\n -editorMode \"default\" \n -hasWatchpoint 0\n $editorName;\n\t\t\t}\n\t\t} else {\n\t\t\t$label = `panel -q -label $panelName`;\n\t\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Node Editor\")) -mbv $menusOkayInPanels $panelName;\n\n\t\t\t$editorName = ($panelName+\"NodeEditorEd\");\n nodeEditor -e \n -allAttributes 0\n -allNodes 0\n -autoSizeNodes 1\n -consistentNameSize 1\n -createNodeCommand \"nodeEdCreateNodeCommand\" \n -connectNodeOnCreation 0\n -connectOnDrop 0\n -copyConnectionsOnPaste 0\n -connectionStyle \"bezier\" \n -defaultPinnedState 0\n" + + " -additiveGraphingMode 1\n -connectedGraphingMode 1\n -settingsChangedCallback \"nodeEdSyncControls\" \n -traversalDepthLimit -1\n -keyPressCommand \"nodeEdKeyPressCommand\" \n -nodeTitleMode \"name\" \n -gridSnap 0\n -gridVisibility 1\n -crosshairOnEdgeDragging 0\n -popupMenuScript \"nodeEdBuildPanelMenus\" \n -showNamespace 1\n -showShapes 1\n -showSGShapes 0\n -showTransforms 1\n -useAssets 1\n -syncedSelection 1\n -extendToShapes 1\n -showUnitConversions 0\n -editorMode \"default\" \n -hasWatchpoint 0\n $editorName;\n\t\t\tif (!$useSceneConfig) {\n\t\t\t\tpanel -e -l $label $panelName;\n\t\t\t}\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"createNodePanel\" (localizedPanelLabel(\"Create Node\")) `;\n\tif (\"\" != $panelName) {\n" + + "\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Create Node\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"polyTexturePlacementPanel\" (localizedPanelLabel(\"UV Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"UV Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"renderWindowPanel\" (localizedPanelLabel(\"Render View\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Render View\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"shapePanel\" (localizedPanelLabel(\"Shape Editor\")) `;\n\tif (\"\" != $panelName) {\n" + + "\t\t$label = `panel -q -label $panelName`;\n\t\tshapePanel -edit -l (localizedPanelLabel(\"Shape Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextPanel \"posePanel\" (localizedPanelLabel(\"Pose Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tposePanel -edit -l (localizedPanelLabel(\"Pose Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"dynRelEdPanel\" (localizedPanelLabel(\"Dynamic Relationships\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Dynamic Relationships\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"relationshipPanel\" (localizedPanelLabel(\"Relationship Editor\")) `;\n" + + "\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Relationship Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"referenceEditorPanel\" (localizedPanelLabel(\"Reference Editor\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Reference Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"dynPaintScriptedPanelType\" (localizedPanelLabel(\"Paint Effects\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Paint Effects\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"scriptEditorPanel\" (localizedPanelLabel(\"Script Editor\")) `;\n" + + "\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Script Editor\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"profilerPanel\" (localizedPanelLabel(\"Profiler Tool\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Profiler Tool\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"contentBrowserPanel\" (localizedPanelLabel(\"Content Browser\")) `;\n\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Content Browser\")) -mbv $menusOkayInPanels $panelName;\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\t$panelName = `sceneUIReplacement -getNextScriptedPanel \"Stereo\" (localizedPanelLabel(\"Stereo\")) `;\n" + + "\tif (\"\" != $panelName) {\n\t\t$label = `panel -q -label $panelName`;\n\t\tscriptedPanel -edit -l (localizedPanelLabel(\"Stereo\")) -mbv $menusOkayInPanels $panelName;\n{ string $editorName = ($panelName+\"Editor\");\n stereoCameraView -e \n -camera \"|persp\" \n -useInteractiveMode 0\n -displayLights \"default\" \n -displayAppearance \"wireframe\" \n -activeOnly 0\n -ignorePanZoom 0\n -wireframeOnShaded 0\n -headsUpDisplay 1\n -holdOuts 1\n -selectionHiliteDisplay 1\n -useDefaultMaterial 0\n -bufferMode \"double\" \n -twoSidedLighting 1\n -backfaceCulling 0\n -xray 0\n -jointXray 0\n -activeComponentsXray 0\n -displayTextures 0\n -smoothWireframe 0\n -lineWidth 1\n -textureAnisotropic 0\n -textureHilight 1\n -textureSampling 2\n" + + " -textureDisplay \"modulate\" \n -textureMaxSize 32768\n -fogging 0\n -fogSource \"fragment\" \n -fogMode \"linear\" \n -fogStart 0\n -fogEnd 100\n -fogDensity 0.1\n -fogColor 0.5 0.5 0.5 1 \n -depthOfFieldPreview 1\n -maxConstantTransparency 1\n -objectFilterShowInHUD 1\n -isFiltered 0\n -colorResolution 4 4 \n -bumpResolution 4 4 \n -textureCompression 0\n -transparencyAlgorithm \"frontAndBackCull\" \n -transpInShadows 0\n -cullingOverride \"none\" \n -lowQualityLighting 0\n -maximumNumHardwareLights 0\n -occlusionCulling 0\n -shadingModel 0\n -useBaseRenderer 0\n -useReducedRenderer 0\n -smallObjectCulling 0\n -smallObjectThreshold -1 \n -interactiveDisableShadows 0\n" + + " -interactiveBackFaceCull 0\n -sortTransparent 1\n -controllers 1\n -nurbsCurves 1\n -nurbsSurfaces 1\n -polymeshes 1\n -subdivSurfaces 1\n -planes 1\n -lights 1\n -cameras 1\n -controlVertices 1\n -hulls 1\n -grid 1\n -imagePlane 1\n -joints 1\n -ikHandles 1\n -deformers 1\n -dynamics 1\n -particleInstancers 1\n -fluids 1\n -hairSystems 1\n -follicles 1\n -nCloths 1\n -nParticles 1\n -nRigids 1\n -dynamicConstraints 1\n -locators 1\n -manipulators 1\n -pluginShapes 1\n -dimensions 1\n -handles 1\n -pivots 1\n -textures 1\n -strokes 1\n -motionTrails 1\n" + + " -clipGhosts 1\n -bluePencil 1\n -greasePencils 0\n -shadows 0\n -captureSequenceNumber -1\n -width 0\n -height 0\n -sceneRenderFilter 0\n -displayMode \"centerEye\" \n -viewColor 0 0 0 1 \n -useCustomBackground 1\n $editorName;\n stereoCameraView -e -viewSelected 0 $editorName; };\n\t\tif (!$useSceneConfig) {\n\t\t\tpanel -e -l $label $panelName;\n\t\t}\n\t}\n\n\n\tif ($useSceneConfig) {\n string $configName = `getPanel -cwl (localizedPanelLabel(\"Current Layout\"))`;\n if (\"\" != $configName) {\n\t\t\tpanelConfiguration -edit -label (localizedPanelLabel(\"Current Layout\")) \n\t\t\t\t-userCreated false\n\t\t\t\t-defaultImage \"vacantCell.xP:/\"\n\t\t\t\t-image \"\"\n\t\t\t\t-sc false\n\t\t\t\t-configString \"global string $gMainPane; paneLayout -e -cn \\\"quad\\\" -ps 1 50 50 -ps 2 50 50 -ps 3 50 50 -ps 4 50 50 $gMainPane;\"\n\t\t\t\t-removeAllPanels\n\t\t\t\t-ap false\n\t\t\t\t\t(localizedPanelLabel(\"Top View\")) \n" + + "\t\t\t\t\t\"modelPanel\"\n" + + "\t\t\t\t\t\"$panelName = `modelPanel -unParent -l (localizedPanelLabel(\\\"Top View\\\")) -mbv $menusOkayInPanels `;\\n$editorName = $panelName;\\nmodelEditor -e \\n -camera \\\"|top\\\" \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 32768\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -bluePencil 1\\n -greasePencils 0\\n -shadows 0\\n -captureSequenceNumber -1\\n -width 477\\n -height 276\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName\"\n" + + "\t\t\t\t\t\"modelPanel -edit -l (localizedPanelLabel(\\\"Top View\\\")) -mbv $menusOkayInPanels $panelName;\\n$editorName = $panelName;\\nmodelEditor -e \\n -camera \\\"|top\\\" \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 32768\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -bluePencil 1\\n -greasePencils 0\\n -shadows 0\\n -captureSequenceNumber -1\\n -width 477\\n -height 276\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName\"\n" + + "\t\t\t\t-ap false\n\t\t\t\t\t(localizedPanelLabel(\"Persp View\")) \n\t\t\t\t\t\"modelPanel\"\n" + + "\t\t\t\t\t\"$panelName = `modelPanel -unParent -l (localizedPanelLabel(\\\"Persp View\\\")) -mbv $menusOkayInPanels `;\\n$editorName = $panelName;\\nmodelEditor -e \\n -cam `findStartUpCamera persp` \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 32768\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -bluePencil 1\\n -greasePencils 0\\n -shadows 0\\n -captureSequenceNumber -1\\n -width 476\\n -height 276\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName\"\n" + + "\t\t\t\t\t\"modelPanel -edit -l (localizedPanelLabel(\\\"Persp View\\\")) -mbv $menusOkayInPanels $panelName;\\n$editorName = $panelName;\\nmodelEditor -e \\n -cam `findStartUpCamera persp` \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 32768\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -bluePencil 1\\n -greasePencils 0\\n -shadows 0\\n -captureSequenceNumber -1\\n -width 476\\n -height 276\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName\"\n" + + "\t\t\t\t-ap false\n\t\t\t\t\t(localizedPanelLabel(\"Side View\")) \n\t\t\t\t\t\"modelPanel\"\n" + + "\t\t\t\t\t\"$panelName = `modelPanel -unParent -l (localizedPanelLabel(\\\"Side View\\\")) -mbv $menusOkayInPanels `;\\n$editorName = $panelName;\\nmodelEditor -e \\n -camera \\\"|side\\\" \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 32768\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -bluePencil 1\\n -greasePencils 0\\n -shadows 0\\n -captureSequenceNumber -1\\n -width 476\\n -height 276\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName\"\n" + + "\t\t\t\t\t\"modelPanel -edit -l (localizedPanelLabel(\\\"Side View\\\")) -mbv $menusOkayInPanels $panelName;\\n$editorName = $panelName;\\nmodelEditor -e \\n -camera \\\"|side\\\" \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 32768\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -bluePencil 1\\n -greasePencils 0\\n -shadows 0\\n -captureSequenceNumber -1\\n -width 476\\n -height 276\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName\"\n" + + "\t\t\t\t-ap false\n\t\t\t\t\t(localizedPanelLabel(\"Front View\")) \n\t\t\t\t\t\"modelPanel\"\n" + + "\t\t\t\t\t\"$panelName = `modelPanel -unParent -l (localizedPanelLabel(\\\"Front View\\\")) -mbv $menusOkayInPanels `;\\n$editorName = $panelName;\\nmodelEditor -e \\n -cam `findStartUpCamera front` \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 32768\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -bluePencil 1\\n -greasePencils 0\\n -shadows 0\\n -captureSequenceNumber -1\\n -width 477\\n -height 276\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName\"\n" + + "\t\t\t\t\t\"modelPanel -edit -l (localizedPanelLabel(\\\"Front View\\\")) -mbv $menusOkayInPanels $panelName;\\n$editorName = $panelName;\\nmodelEditor -e \\n -cam `findStartUpCamera front` \\n -useInteractiveMode 0\\n -displayLights \\\"default\\\" \\n -displayAppearance \\\"smoothShaded\\\" \\n -activeOnly 0\\n -ignorePanZoom 0\\n -wireframeOnShaded 0\\n -headsUpDisplay 1\\n -holdOuts 1\\n -selectionHiliteDisplay 1\\n -useDefaultMaterial 0\\n -bufferMode \\\"double\\\" \\n -twoSidedLighting 0\\n -backfaceCulling 0\\n -xray 0\\n -jointXray 0\\n -activeComponentsXray 0\\n -displayTextures 0\\n -smoothWireframe 0\\n -lineWidth 1\\n -textureAnisotropic 0\\n -textureHilight 1\\n -textureSampling 2\\n -textureDisplay \\\"modulate\\\" \\n -textureMaxSize 32768\\n -fogging 0\\n -fogSource \\\"fragment\\\" \\n -fogMode \\\"linear\\\" \\n -fogStart 0\\n -fogEnd 100\\n -fogDensity 0.1\\n -fogColor 0.5 0.5 0.5 1 \\n -depthOfFieldPreview 1\\n -maxConstantTransparency 1\\n -rendererName \\\"vp2Renderer\\\" \\n -objectFilterShowInHUD 1\\n -isFiltered 0\\n -colorResolution 256 256 \\n -bumpResolution 512 512 \\n -textureCompression 0\\n -transparencyAlgorithm \\\"frontAndBackCull\\\" \\n -transpInShadows 0\\n -cullingOverride \\\"none\\\" \\n -lowQualityLighting 0\\n -maximumNumHardwareLights 1\\n -occlusionCulling 0\\n -shadingModel 0\\n -useBaseRenderer 0\\n -useReducedRenderer 0\\n -smallObjectCulling 0\\n -smallObjectThreshold -1 \\n -interactiveDisableShadows 0\\n -interactiveBackFaceCull 0\\n -sortTransparent 1\\n -controllers 1\\n -nurbsCurves 1\\n -nurbsSurfaces 1\\n -polymeshes 1\\n -subdivSurfaces 1\\n -planes 1\\n -lights 1\\n -cameras 1\\n -controlVertices 1\\n -hulls 1\\n -grid 1\\n -imagePlane 1\\n -joints 1\\n -ikHandles 1\\n -deformers 1\\n -dynamics 1\\n -particleInstancers 1\\n -fluids 1\\n -hairSystems 1\\n -follicles 1\\n -nCloths 1\\n -nParticles 1\\n -nRigids 1\\n -dynamicConstraints 1\\n -locators 1\\n -manipulators 1\\n -pluginShapes 1\\n -dimensions 1\\n -handles 1\\n -pivots 1\\n -textures 1\\n -strokes 1\\n -motionTrails 1\\n -clipGhosts 1\\n -bluePencil 1\\n -greasePencils 0\\n -shadows 0\\n -captureSequenceNumber -1\\n -width 477\\n -height 276\\n -sceneRenderFilter 0\\n $editorName;\\nmodelEditor -e -viewSelected 0 $editorName\"\n" + "\t\t\t\t$configName;\n\n setNamedPanelLayout (localizedPanelLabel(\"Current Layout\"));\n }\n\n panelHistory -e -clear mainPanelHistory;\n sceneUIReplacement -clear;\n\t}\n\n\ngrid -spacing 5 -size 12 -divisions 5 -displayAxes yes -displayGridLines yes -displayDivisionLines yes -displayPerspectiveLabels no -displayOrthographicLabels no -displayAxesBold yes -perspectiveLabelPosition axis -orthographicLabelPosition edge;\nviewManip -drawCompass 0 -compassAngle 0 -frontParameters \"\" -homeParameters \"\" -selectionLockParameters \"\";\n}\n"); setAttr ".st" 3; createNode script -n "sceneConfigurationScriptNode"; @@ -273,6 +285,7 @@ createNode aiAOVDriver -s -n "defaultArnoldDisplayDriver"; createNode objectSet -n "workfileMain"; rename -uid "3C9B5D6F-4579-8E3B-5B7D-4C88865A1C68"; addAttr -ci true -sn "cbId" -ln "cbId" -dt "string"; + addAttr -ci true -sn "instance_id" -ln "instance_id" -dt "string"; addAttr -ci true -sn "id" -ln "id" -dt "string"; addAttr -ci true -sn "family" -ln "family" -dt "string"; addAttr -ci true -sn "subset" -ln "subset" -dt "string"; @@ -281,13 +294,14 @@ createNode objectSet -n "workfileMain"; addAttr -ci true -sn "variant" -ln "variant" -dt "string"; addAttr -ci true -sn "asset" -ln "asset" -dt "string"; addAttr -ci true -sn "task" -ln "task" -dt "string"; - addAttr -ci true -sn "instance_id" -ln "instance_id" -dt "string"; addAttr -ci true -sn "publish_attributes" -ln "publish_attributes" -dt "string"; - addAttr -ci true -sn "__creator_attributes_keys" -ln "__creator_attributes_keys" + addAttr -ci true -sn "creator_attributes" -ln "creator_attributes" -dt "string"; + addAttr -ci true -sn "__creator_attributes_keys" -ln "__creator_attributes_keys" -dt "string"; setAttr ".ihi" 0; setAttr ".hio" yes; setAttr ".cbId" -type "string" "60df31e2be2b48bd3695c056:30d256dac64c"; + setAttr ".instance_id" -type "string" "911dc92a-ad29-41e5-bbf9-733d56174fb9"; setAttr ".id" -type "string" "pyblish.avalon.instance"; setAttr ".family" -type "string" "workfile"; setAttr ".subset" -type "string" "workfileTest_task"; @@ -296,16 +310,9 @@ createNode objectSet -n "workfileMain"; setAttr ".variant" -type "string" "Main"; setAttr ".asset" -type "string" "test_asset"; setAttr ".task" -type "string" "test_task"; - setAttr ".instance_id" -type "string" "911dc92a-ad29-41e5-bbf9-733d56174fb9"; - setAttr ".publish_attributes" -type "string" "{\"ExtractImportReference\": {\"active\": false}}"; + setAttr ".publish_attributes" -type "string" "{\"ValidateInstanceInContext\": {\"active\": true}, \"ExtractImportReference\": {\"active\": false}}"; + setAttr ".creator_attributes" -type "string" "{}"; setAttr ".__creator_attributes_keys" -type "string" ""; -createNode objectSet -n "renderingMain"; - rename -uid "8A999C2F-4922-B15D-8D5C-45A16465B69F"; - addAttr -ci true -sn "pre_creator_identifier" -ln "pre_creator_identifier" -dt "string"; - addAttr -ci true -sn "cbId" -ln "cbId" -dt "string"; - setAttr ".ihi" 0; - setAttr ".pre_creator_identifier" -type "string" "io.openpype.creators.maya.renderlayer"; - setAttr ".cbId" -type "string" "60df31e2be2b48bd3695c056:042447475732"; createNode renderSetupLayer -n "Main"; rename -uid "2202E438-4CEF-F64E-737C-F48C65E31126"; addAttr -ci true -sn "es" -ln "expandedState" -min 0 -max 1 -at "bool"; @@ -320,55 +327,6 @@ createNode collection -n "defaultCollection"; createNode simpleSelector -n "defaultCollectionSelector"; rename -uid "7CA2F6D8-483C-B020-BC03-EF9563A52163"; setAttr ".pat" -type "string" "*"; -createNode objectSet -n "_renderingMain:Main"; - rename -uid "1EEC3A3B-49CF-0C79-5340-39805174FB8A"; - addAttr -s false -ci true -sn "renderlayer" -ln "renderlayer" -at "message"; - addAttr -ci true -sn "id" -ln "id" -dt "string"; - addAttr -ci true -sn "family" -ln "family" -dt "string"; - addAttr -ci true -sn "subset" -ln "subset" -dt "string"; - addAttr -ci true -sn "active" -ln "active" -min 0 -max 1 -at "bool"; - addAttr -ci true -sn "creator_identifier" -ln "creator_identifier" -dt "string"; - addAttr -ci true -sn "variant" -ln "variant" -dt "string"; - addAttr -ci true -sn "asset" -ln "asset" -dt "string"; - addAttr -ci true -sn "task" -ln "task" -dt "string"; - addAttr -ci true -sn "instance_id" -ln "instance_id" -dt "string"; - addAttr -ci true -sn "review" -ln "review" -min 0 -max 1 -at "bool"; - addAttr -ci true -sn "extendFrames" -ln "extendFrames" -min 0 -max 1 -at "bool"; - addAttr -ci true -sn "overrideExistingFrame" -ln "overrideExistingFrame" -min 0 - -max 1 -at "bool"; - addAttr -ci true -sn "tileRendering" -ln "tileRendering" -min 0 -max 1 -at "bool"; - addAttr -ci true -sn "tilesX" -ln "tilesX" -at "long"; - addAttr -ci true -sn "tilesY" -ln "tilesY" -at "long"; - addAttr -ci true -sn "convertToScanline" -ln "convertToScanline" -min 0 -max 1 -at "bool"; - addAttr -ci true -sn "useReferencedAovs" -ln "useReferencedAovs" -min 0 -max 1 -at "bool"; - addAttr -ci true -sn "renderSetupIncludeLights" -ln "renderSetupIncludeLights" -min - 0 -max 1 -at "bool"; - addAttr -ci true -sn "publish_attributes" -ln "publish_attributes" -dt "string"; - addAttr -ci true -sn "__creator_attributes_keys" -ln "__creator_attributes_keys" - -dt "string"; - addAttr -ci true -sn "cbId" -ln "cbId" -dt "string"; - setAttr ".ihi" 0; - setAttr ".id" -type "string" "pyblish.avalon.instance"; - setAttr ".family" -type "string" "renderlayer"; - setAttr ".subset" -type "string" "renderMain"; - setAttr -cb on ".active" yes; - setAttr ".creator_identifier" -type "string" "io.openpype.creators.maya.renderlayer"; - setAttr ".variant" -type "string" "Main"; - setAttr ".asset" -type "string" "test_asset"; - setAttr ".task" -type "string" "test_task"; - setAttr ".instance_id" -type "string" "8a9cfb85-9602-4e5e-a4bc-27a2986bae7f"; - setAttr -cb on ".review" yes; - setAttr -cb on ".extendFrames"; - setAttr -cb on ".overrideExistingFrame" yes; - setAttr -cb on ".tileRendering"; - setAttr -cb on ".tilesX" 2; - setAttr -cb on ".tilesY" 2; - setAttr -cb on ".convertToScanline"; - setAttr -cb on ".useReferencedAovs"; - setAttr -cb on ".renderSetupIncludeLights" yes; - setAttr ".publish_attributes" -type "string" "{\"CollectDeadlinePools\": {\"primaryPool\": \"\", \"secondaryPool\": \"\"}, \"ValidateDeadlinePools\": {\"active\": true}, \"ValidateFrameRange\": {\"active\": true}, \"ExtractImportReference\": {\"active\": false}, \"MayaSubmitDeadline\": {\"priority\": 50, \"chunkSize\": 1, \"machineList\": \"\", \"whitelist\": false, \"tile_priority\": 50, \"strict_error_checking\": true}, \"ProcessSubmittedJobOnFarm\": {\"publishJobState\": \"Active\"}}"; - setAttr ".__creator_attributes_keys" -type "string" "review,extendFrames,overrideExistingFrame,tileRendering,tilesX,tilesY,convertToScanline,useReferencedAovs,renderSetupIncludeLights"; - setAttr ".cbId" -type "string" "60df31e2be2b48bd3695c056:69960f336351"; select -ne :time1; setAttr ".o" 1001; setAttr ".unw" 1001; @@ -435,8 +393,6 @@ select -ne :defaultColorMgtGlobals; select -ne :hardwareRenderGlobals; setAttr ".ctrs" 256; setAttr ".btrs" 512; -select -ne :ikSystem; - setAttr -s 4 ".sol"; connectAttr "rs_Main.ri" ":persp.rlio[0]"; connectAttr "rs_Main.ri" ":top.rlio[0]"; connectAttr "rs_Main.ri" ":front.rlio[0]"; @@ -459,7 +415,6 @@ connectAttr ":defaultArnoldDisplayDriver.msg" ":defaultArnoldRenderOptions.drive -na; connectAttr ":defaultArnoldFilter.msg" ":defaultArnoldRenderOptions.filt"; connectAttr ":defaultArnoldDriver.msg" ":defaultArnoldRenderOptions.drvr"; -connectAttr "_renderingMain:Main.msg" "renderingMain.dnsm" -na; connectAttr "rs_Main.msg" "Main.lrl"; connectAttr "renderSetup.lit" "Main.pls"; connectAttr "defaultCollection.msg" "Main.cl"; @@ -468,9 +423,8 @@ connectAttr "renderLayerManager.rlmi[1]" "rs_Main.rlid"; connectAttr "defaultCollectionSelector.c" "defaultCollection.sel"; connectAttr "Main.lit" "defaultCollection.pls"; connectAttr "Main.nic" "defaultCollection.pic"; -connectAttr "Main.msg" "_renderingMain:Main.renderlayer"; connectAttr "defaultRenderLayer.msg" ":defaultRenderingList1.r" -na; connectAttr "rs_Main.msg" ":defaultRenderingList1.r" -na; connectAttr "pSphere1_GEOShape1.iog" ":initialShadingGroup.dsm" -na; connectAttr "pDiscShape1.iog" ":initialShadingGroup.dsm" -na; -// End of test_project_test_asset_test_task_v001.ma +// End of test_project_test_asset_test_task_v002.ma From 355722d04f51fe0a4c7870c5ed95a56299dd4eb5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 7 Dec 2023 17:38:25 +0100 Subject: [PATCH 314/327] Fix error message formatting (#6028) --- .../deadline/repository/custom/plugins/GlobalJobPreLoad.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py b/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py index 642608f991..96f131b922 100644 --- a/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py +++ b/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py @@ -429,7 +429,7 @@ def inject_ayon_environment(deadlinePlugin): "separated list \"{}\"." "The path to the render executable can be configured" " from the Plugin Configuration in the Deadline Monitor." - ).format(";".join(exe_list))) + ).format(exe_list)) print("--- Ayon executable: {}".format(exe)) From d9b18cc0f92a1b37a2fbe512bd775b2d120aa2ab Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 8 Dec 2023 00:47:09 +0800 Subject: [PATCH 315/327] add extract model and extract max scene --- .../defaults/project_settings/max.json | 10 ++++ .../schemas/schema_max_publish.json | 50 +++++++++++++++++++ .../max/server/settings/publishers.py | 18 +++++++ 3 files changed, 78 insertions(+) diff --git a/openpype/settings/defaults/project_settings/max.json b/openpype/settings/defaults/project_settings/max.json index 359db19226..ace7b7bdc2 100644 --- a/openpype/settings/defaults/project_settings/max.json +++ b/openpype/settings/defaults/project_settings/max.json @@ -71,6 +71,16 @@ "enabled": false, "optional": true, "active": true + }, + "ExtractModel": { + "enabled": true, + "optional": true, + "active": true + }, + "ExtractMaxSceneRaw": { + "enabled": true, + "optional": true, + "active": true } } } diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json index 9077cf0c58..b4d85bda98 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json @@ -165,6 +165,56 @@ "label": "Active" } ] + }, + { + "type": "dict", + "collapsible": true, + "checkbox_key": "enabled", + "key": "ExtractModel", + "label": "Extract Geometry (Alembic)", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, + { + "type": "boolean", + "key": "active", + "label": "Active" + } + ] + }, + { + "type": "dict", + "collapsible": true, + "checkbox_key": "enabled", + "key": "ExtractMaxSceneRaw", + "label": "Extract Max Scene (Raw)", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, + { + "type": "boolean", + "key": "active", + "label": "Active" + } + ] } ] } diff --git a/server_addon/max/server/settings/publishers.py b/server_addon/max/server/settings/publishers.py index 04a4bcb875..3977eaf075 100644 --- a/server_addon/max/server/settings/publishers.py +++ b/server_addon/max/server/settings/publishers.py @@ -81,6 +81,14 @@ class PublishersModel(BaseSettingsModel): default_factory=BasicValidateModel, title="Extract Geometry (USD)" ) + ExtractModel: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Extract Geometry (Alembic)" + ) + ExtractMaxSceneRaw: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Extract Max Scene (Raw)" + ) DEFAULT_PUBLISH_SETTINGS = { @@ -112,5 +120,15 @@ DEFAULT_PUBLISH_SETTINGS = { "enabled": False, "optional": True, "active": True + }, + "ExtractModel": { + "enabled": False, + "optional": True, + "active": True + }, + "ExtractMaxSceneRaw": { + "enabled": True, + "optional": True, + "active": True } } From b57d585d9f6e65dc500fbf183bacf8dcb748b22f Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 8 Dec 2023 00:47:41 +0800 Subject: [PATCH 316/327] add extract alembic and extract max raw scene --- server_addon/max/server/settings/publishers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/max/server/settings/publishers.py b/server_addon/max/server/settings/publishers.py index 3977eaf075..4674ab25e7 100644 --- a/server_addon/max/server/settings/publishers.py +++ b/server_addon/max/server/settings/publishers.py @@ -71,7 +71,7 @@ class PublishersModel(BaseSettingsModel): ExtractModelObj: BasicValidateModel = Field( default_factory=BasicValidateModel, title="Extract OBJ", - section="Publishers" + section="Extractors" ) ExtractModelFbx: BasicValidateModel = Field( default_factory=BasicValidateModel, From 483ba26cb5c7bec05f3b988f6d604c7fd8036598 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Thu, 7 Dec 2023 18:03:30 +0100 Subject: [PATCH 317/327] Change ASCII art in AYON mode --- .../window/widgets.py | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/openpype/modules/python_console_interpreter/window/widgets.py b/openpype/modules/python_console_interpreter/window/widgets.py index 49a5f62165..28950f8369 100644 --- a/openpype/modules/python_console_interpreter/window/widgets.py +++ b/openpype/modules/python_console_interpreter/window/widgets.py @@ -13,6 +13,7 @@ from openpype.style import load_stylesheet from openpype.lib import JSONSettingRegistry + openpype_art = """ . . .. . .. _oOOP3OPP3Op_. . @@ -28,6 +29,18 @@ openpype_art = """ ~P3.OPPPO3OP~ . .. . . ' '. . .. . . . .. . +""" + +ayon_art = r""" + + ▄██▄ + ▄███▄ ▀██▄ ▀██▀ ▄██▀ ▄██▀▀▀██▄ ▀███▄ █▄ + ▄▄ ▀██▄ ▀██▄ ▄██▀ ██▀ ▀██▄ ▄ ▀██▄ ███ + ▄██▀ ██▄ ▀ ▄▄ ▀ ██ ▄██ ███ ▀██▄ ███ + ▄██▀ ▀██▄ ██ ▀██▄ ▄██▀ ███ ▀██ ▀█▀ + ▄██▀ ▀██▄ ▀█ ▀██▄▄▄▄██▀ █▀ ▀██▄ + + · · - =[ by YNPUT ]:[ http://ayon.ynput.io ]= - · · """ @@ -42,8 +55,12 @@ class PythonInterpreterRegistry(JSONSettingRegistry): """ def __init__(self): - self.vendor = "pypeclub" - self.product = "openpype" + if AYON_SERVER_ENABLED: + self.vendor = "ynput" + self.product = "ayon" + else: + self.vendor = "pypeclub" + self.product = "openpype" name = "python_interpreter_tool" path = appdirs.user_data_dir(self.product, self.vendor) super(PythonInterpreterRegistry, self).__init__(name, path) @@ -390,7 +407,10 @@ class PythonInterpreterWidget(QtWidgets.QWidget): self._tab_widget = tab_widget self._line_check_timer = line_check_timer - self._append_lines([openpype_art]) + if AYON_SERVER_ENABLED: + self._append_lines([ayon_art]) + else: + self._append_lines([openpype_art]) self._first_show = True self._splitter_size_ratio = None From b6e58ef8ab8fffa1b81b1f9b109f1852f271bb91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 7 Dec 2023 18:22:05 +0100 Subject: [PATCH 318/327] :art: show slightly different info in AYON mode --- openpype/tools/tray/pype_info_widget.py | 13 +++++++++++++ openpype/tools/tray/pype_tray.py | 5 ++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/openpype/tools/tray/pype_info_widget.py b/openpype/tools/tray/pype_info_widget.py index 985a23f64c..acd937e711 100644 --- a/openpype/tools/tray/pype_info_widget.py +++ b/openpype/tools/tray/pype_info_widget.py @@ -6,6 +6,7 @@ import ayon_api from qtpy import QtCore, QtGui, QtWidgets from openpype import style +import openpype.version from openpype import resources from openpype import AYON_SERVER_ENABLED from openpype.settings.lib import get_local_settings @@ -518,4 +519,16 @@ class PypeInfoSubWidget(QtWidgets.QWidget): info_layout.addWidget( value_label, row, 1, 1, 1 ) + if AYON_SERVER_ENABLED: + row = info_layout.rowCount() + info_layout.addWidget( + QtWidgets.QLabel("OpenPype Addon:"), row, 0, 1, 1 + ) + value_label = QtWidgets.QLabel(openpype.version.__version__) + value_label.setTextInteractionFlags( + QtCore.Qt.TextSelectableByMouse + ) + info_layout.addWidget( + value_label, row, 1, 1, 1 + ) return info_widget diff --git a/openpype/tools/tray/pype_tray.py b/openpype/tools/tray/pype_tray.py index db391b469a..769ed4272b 100644 --- a/openpype/tools/tray/pype_tray.py +++ b/openpype/tools/tray/pype_tray.py @@ -599,7 +599,10 @@ class TrayManager: subversion = os.environ.get("OPENPYPE_SUBVERSION") client_name = os.environ.get("OPENPYPE_CLIENT") - version_string = openpype.version.__version__ + if AYON_SERVER_ENABLED: + version_string = os.getenv("AYON_VERSION", "AYON Info") + else: + version_string = openpype.version.__version__ if subversion: version_string += " ({})".format(subversion) From 58001ee12e2ecc33ce7333f9c6ee114aeb59b450 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 8 Dec 2023 01:40:59 +0800 Subject: [PATCH 319/327] make sure some extractor not active while some active and all enabled --- .../settings/defaults/project_settings/max.json | 12 ++++++------ server_addon/max/server/settings/publishers.py | 14 +++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/openpype/settings/defaults/project_settings/max.json b/openpype/settings/defaults/project_settings/max.json index ace7b7bdc2..19c9d10496 100644 --- a/openpype/settings/defaults/project_settings/max.json +++ b/openpype/settings/defaults/project_settings/max.json @@ -58,19 +58,19 @@ "family_plugins_mapping": [] }, "ExtractModelObj": { - "enabled": false, + "enabled": true, "optional": true, - "active": true + "active": false }, "ExtractModelFbx": { - "enabled": false, + "enabled": true, "optional": true, - "active": true + "active": false }, "ExtractModelUSD": { - "enabled": false, + "enabled": true, "optional": true, - "active": true + "active": false }, "ExtractModel": { "enabled": true, diff --git a/server_addon/max/server/settings/publishers.py b/server_addon/max/server/settings/publishers.py index 4674ab25e7..d40d85a99b 100644 --- a/server_addon/max/server/settings/publishers.py +++ b/server_addon/max/server/settings/publishers.py @@ -107,22 +107,22 @@ DEFAULT_PUBLISH_SETTINGS = { "family_plugins_mapping": [] }, "ExtractModelObj": { - "enabled": False, + "enabled": True, "optional": True, - "active": True + "active": False }, "ExtractModelFbx": { - "enabled": False, + "enabled": True, "optional": True, - "active": True + "active": False }, "ExtractModelUSD": { - "enabled": False, + "enabled": True, "optional": True, - "active": True + "active": False }, "ExtractModel": { - "enabled": False, + "enabled": True, "optional": True, "active": True }, From c4570200ff7add65a9d06cebd94247c93c3af4ff Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 7 Dec 2023 22:08:19 +0100 Subject: [PATCH 320/327] Refactor code to handle missing "representations" key in instance data The code change modifies the `ExtractThumbnail` class in `extract_thumbnail.py`. It updates the loop that iterates over representations to handle cases where the "representations" key is missing from the instance data. This change ensures that cleanup occurs for representations with both "delete" and "need_thumbnail" tags. --- openpype/plugins/publish/extract_thumbnail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 2b4a61845d..2b4ea0529a 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -56,7 +56,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # Make sure cleanup happens to representations which are having both # tags `delete` and `need_thumbnail` - for repre in tuple(instance.data["representations"]): + for repre in tuple(instance.data.get("representations", [])): tags = repre.get("tags") or [] # skip representations which are going to be published on farm if "publish_on_farm" in tags: From b855c72557f64f126b95ef5d38408a250eace688 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 8 Dec 2023 17:39:28 +0100 Subject: [PATCH 321/327] Refactor clip name extraction in create_editorial.py The code change updates the way the clip name is extracted from the OTIO file. The ".lower()" method has been removed to preserve case sensitivity. --- openpype/hosts/traypublisher/plugins/create/create_editorial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index 26cce35d55..dce4a051fd 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -663,7 +663,7 @@ or updating already created. Publishing will create OTIO file. variant_name = instance_data["variant"] # basic unique asset name - clip_name = os.path.splitext(otio_clip.name)[0].lower() + clip_name = os.path.splitext(otio_clip.name)[0] project_doc = get_project(self.project_name) shot_name, shot_metadata = self._shot_metadata_solver.generate_data( From 41bdcf305e30155adb55f77dd6fde74e7ae37da2 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Sat, 9 Dec 2023 03:25:59 +0000 Subject: [PATCH 322/327] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index 48688d5651..cdaafa0559 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.17.7-nightly.6" +__version__ = "3.17.7-nightly.7" From 8a1a1c3b15790859985c1120764b8a1dd32e91c3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 9 Dec 2023 03:26:34 +0000 Subject: [PATCH 323/327] chore(): update bug report / version --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index f827d275a6..6f651076ce 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,7 @@ body: label: Version description: What version are you running? Look to OpenPype Tray options: + - 3.17.7-nightly.7 - 3.17.7-nightly.6 - 3.17.7-nightly.5 - 3.17.7-nightly.4 @@ -134,7 +135,6 @@ body: - 3.15.3-nightly.3 - 3.15.3-nightly.2 - 3.15.3-nightly.1 - - 3.15.2 validations: required: true - type: dropdown From 6d36857fe8026f793d30cabc15bb5e1d432e3570 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Mon, 11 Dec 2023 11:19:43 +0000 Subject: [PATCH 324/327] Testing: Validate errors and failed status from Deadline jobs. (#5986) * Validate errors from Deadline jobs. * Check dependency chain and failed jobs. * Houd * Fix wrong datatype It failed on expecting string but receiving dictionary. --------- Co-authored-by: kalisp --- tests/lib/testing_classes.py | 57 +++++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/tests/lib/testing_classes.py b/tests/lib/testing_classes.py index 7a90f76662..ade38d60c8 100644 --- a/tests/lib/testing_classes.py +++ b/tests/lib/testing_classes.py @@ -481,7 +481,7 @@ class DeadlinePublishTest(PublishTest): while not valid_date_finished: time.sleep(0.5) if time.time() - time_start > timeout: - raise ValueError("Timeout for DL finish reached") + raise ValueError("Timeout for Deadline finish reached") response = requests.get(url, timeout=10) if not response.ok: @@ -491,6 +491,61 @@ class DeadlinePublishTest(PublishTest): if not response.json(): raise ValueError("Couldn't find {}".format(deadline_job_id)) + job = response.json()[0] + + def recursive_dependencies(job, results=None): + if results is None: + results = [] + + for dependency in job["Props"]["Dep"]: + dependency = requests.get( + "{}/api/jobs?JobId={}".format( + deadline_url, dependency["JobID"] + ), + timeout=10 + ).json()[0] + results.append(dependency) + grand_dependencies = recursive_dependencies( + dependency, results=results + ) + for grand_dependency in grand_dependencies: + if grand_dependency not in results: + results.append(grand_dependency) + return results + + job_status = { + 0: "Unknown", + 1: "Active", + 2: "Suspended", + 3: "Completed", + 4: "Failed", + 6: "Pending" + } + + jobs_to_validate = [job] + jobs_to_validate.extend(recursive_dependencies(job)) + failed_jobs = [] + errors = [] + for job in jobs_to_validate: + if "Failed" == job_status[job["Stat"]]: + failed_jobs.append(str(job)) + + resp_error = requests.get( + "{}/api/jobreports?JobID={}&Data=allerrorcontents".format( + deadline_url, job["_id"] + ), + timeout=10 + ) + errors.extend(resp_error.json()) + + msg = "Errors in Deadline:\n" + msg += "\n".join(errors) + assert not errors, msg + + msg = "Failed in Deadline:\n" + msg += "\n".join(failed_jobs) + assert not failed_jobs, msg + # '0001-...' returned until job is finished valid_date_finished = response.json()[0]["DateComp"][:4] != "0001" From 2d73f6a6aaa5e54134cf7b43dc4402609529cf06 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 11 Dec 2023 12:51:58 +0100 Subject: [PATCH 325/327] Chore: Staging mode determination (#5895) * use 'is_staging_enabled' to determine if staging resource is used * fix bug in 'is_running_staging' --- openpype/lib/openpype_version.py | 2 +- openpype/resources/__init__.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/lib/openpype_version.py b/openpype/lib/openpype_version.py index 1c8356d5fe..5618eb0c2e 100644 --- a/openpype/lib/openpype_version.py +++ b/openpype/lib/openpype_version.py @@ -140,7 +140,7 @@ def is_running_staging(): latest_version = get_latest_version(local=False, remote=True) staging_version = latest_version - if current_version == production_version: + if current_version == staging_version: return True return is_staging_enabled() diff --git a/openpype/resources/__init__.py b/openpype/resources/__init__.py index b33d1bf023..c429fb8c3e 100644 --- a/openpype/resources/__init__.py +++ b/openpype/resources/__init__.py @@ -1,6 +1,6 @@ import os from openpype import AYON_SERVER_ENABLED -from openpype.lib.openpype_version import is_running_staging +from openpype.lib.openpype_version import is_staging_enabled RESOURCES_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -59,7 +59,7 @@ def get_openpype_icon_filepath(staging=None): return get_resource("icons", "AYON_icon_dev.png") if staging is None: - staging = is_running_staging() + staging = is_staging_enabled() if staging: return get_openpype_staging_icon_filepath() @@ -68,7 +68,7 @@ def get_openpype_icon_filepath(staging=None): def get_openpype_splash_filepath(staging=None): if staging is None: - staging = is_running_staging() + staging = is_staging_enabled() if AYON_SERVER_ENABLED: if os.getenv("AYON_USE_DEV") == "1": From c1cba8640fb7da4222acde29b8261c77e71aa65b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 11 Dec 2023 15:52:12 +0100 Subject: [PATCH 326/327] convert the createAt value to local timezone --- openpype/client/server/conversion_utils.py | 2 +- openpype/tools/ayon_loader/models/products.py | 2 +- openpype/tools/ayon_workfiles/models/workfiles.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/client/server/conversion_utils.py b/openpype/client/server/conversion_utils.py index 51af99e722..e8d3c4cbe4 100644 --- a/openpype/client/server/conversion_utils.py +++ b/openpype/client/server/conversion_utils.py @@ -606,7 +606,7 @@ def convert_v4_version_to_v3(version): output_data[dst_key] = version[src_key] if "createdAt" in version: - created_at = arrow.get(version["createdAt"]) + created_at = arrow.get(version["createdAt"]).to("local") output_data["time"] = created_at.strftime("%Y%m%dT%H%M%SZ") output["data"] = output_data diff --git a/openpype/tools/ayon_loader/models/products.py b/openpype/tools/ayon_loader/models/products.py index daa36aefdc..135f28df97 100644 --- a/openpype/tools/ayon_loader/models/products.py +++ b/openpype/tools/ayon_loader/models/products.py @@ -44,7 +44,7 @@ def version_item_from_entity(version): # NOTE There is also 'updatedAt', should be used that instead? # TODO skip conversion - converting to '%Y%m%dT%H%M%SZ' is because # 'PrettyTimeDelegate' expects it - created_at = arrow.get(version["createdAt"]) + created_at = arrow.get(version["createdAt"]).to("local") published_time = created_at.strftime("%Y%m%dT%H%M%SZ") author = version["author"] version_num = version["version"] diff --git a/openpype/tools/ayon_workfiles/models/workfiles.py b/openpype/tools/ayon_workfiles/models/workfiles.py index 907b9b5383..d74a8e164d 100644 --- a/openpype/tools/ayon_workfiles/models/workfiles.py +++ b/openpype/tools/ayon_workfiles/models/workfiles.py @@ -606,7 +606,7 @@ class PublishWorkfilesModel: print("Failed to format workfile path: {}".format(exc)) dirpath, filename = os.path.split(workfile_path) - created_at = arrow.get(repre_entity["createdAt"]) + created_at = arrow.get(repre_entity["createdAt"].to("local")) return FileItem( dirpath, filename, From d66bac0f145e39228804dace2f42190131ad4b68 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 11 Dec 2023 17:50:48 +0100 Subject: [PATCH 327/327] Tests: update after thumbnail default change (#6040) * Updates to tests because of default Thumbnail non integration Some previous PR changed default behavior of Thumbnail, they are not integrated anymore. Tests were expecting they are. Change in AE local render is required to match behavior and provide Colorspace handling. * Updates to tests because of default Thumbnail non integration Missed values * Updates to tests because of default Thumbnail non integration Missed values --- .../plugins/publish/extract_local_render.py | 5 +++-- .../test_deadline_publish_in_aftereffects.py | 4 ++-- ..._publish_in_aftereffects_multicomposition.py | 4 ++-- .../test_publish_in_aftereffects.py | 4 ++-- .../test_publish_in_aftereffects_legacy.py | 6 +++--- .../test_publish_in_aftereffects_multiframe.py | 4 ++-- .../hosts/maya/test_deadline_publish_in_maya.py | 4 ++-- ...st_asset_renderTest_taskMain_beauty_v001.jpg | Bin 77035 -> 0 bytes .../hosts/nuke/test_deadline_publish_in_nuke.py | 4 ++-- 9 files changed, 18 insertions(+), 17 deletions(-) delete mode 100644 tests/integration/hosts/maya/test_deadline_publish_in_maya/expected/test_project/test_asset/publish/render/renderTest_taskMain_beauty/v001/test_project_test_asset_renderTest_taskMain_beauty_v001.jpg diff --git a/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py b/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py index bdb48e11f8..b44e986d83 100644 --- a/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py +++ b/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py @@ -60,8 +60,9 @@ class ExtractLocalRender(publish.Extractor): first_repre = not representations if instance.data["review"] and first_repre: repre_data["tags"] = ["review"] - thumbnail_path = os.path.join(staging_dir, files[0]) - instance.data["thumbnailSource"] = thumbnail_path + # TODO return back when Extract from source same as regular + # thumbnail_path = os.path.join(staging_dir, files[0]) + # instance.data["thumbnailSource"] = thumbnail_path representations.append(repre_data) diff --git a/tests/integration/hosts/aftereffects/test_deadline_publish_in_aftereffects.py b/tests/integration/hosts/aftereffects/test_deadline_publish_in_aftereffects.py index 30761693a8..1aa612207d 100644 --- a/tests/integration/hosts/aftereffects/test_deadline_publish_in_aftereffects.py +++ b/tests/integration/hosts/aftereffects/test_deadline_publish_in_aftereffects.py @@ -60,7 +60,7 @@ class TestDeadlinePublishInAfterEffects(AEDeadlinePublishTestClass): name="renderTest_taskMain")) failures.append( - DBAssert.count_of_types(dbcon, "representation", 4)) + DBAssert.count_of_types(dbcon, "representation", 3)) additional_args = {"context.subset": "workfileTest_task", "context.ext": "aep"} @@ -77,7 +77,7 @@ class TestDeadlinePublishInAfterEffects(AEDeadlinePublishTestClass): additional_args = {"context.subset": "renderTest_taskMain", "name": "thumbnail"} failures.append( - DBAssert.count_of_types(dbcon, "representation", 1, + DBAssert.count_of_types(dbcon, "representation", 0, additional_args=additional_args)) additional_args = {"context.subset": "renderTest_taskMain", diff --git a/tests/integration/hosts/aftereffects/test_deadline_publish_in_aftereffects_multicomposition.py b/tests/integration/hosts/aftereffects/test_deadline_publish_in_aftereffects_multicomposition.py index 0e9cd3b00d..4254b951b5 100644 --- a/tests/integration/hosts/aftereffects/test_deadline_publish_in_aftereffects_multicomposition.py +++ b/tests/integration/hosts/aftereffects/test_deadline_publish_in_aftereffects_multicomposition.py @@ -71,7 +71,7 @@ class TestDeadlinePublishInAfterEffectsMultiComposition(AEDeadlinePublishTestCla name="renderTest_taskMain2")) failures.append( - DBAssert.count_of_types(dbcon, "representation", 5)) + DBAssert.count_of_types(dbcon, "representation", 4)) additional_args = {"context.subset": "workfileTest_task", "context.ext": "aep"} @@ -89,7 +89,7 @@ class TestDeadlinePublishInAfterEffectsMultiComposition(AEDeadlinePublishTestCla additional_args = {"context.subset": "renderTest_taskMain", "name": "thumbnail"} failures.append( - DBAssert.count_of_types(dbcon, "representation", 1, + DBAssert.count_of_types(dbcon, "representation", 0, additional_args=additional_args)) additional_args = {"context.subset": "renderTest_taskMain", diff --git a/tests/integration/hosts/aftereffects/test_publish_in_aftereffects.py b/tests/integration/hosts/aftereffects/test_publish_in_aftereffects.py index 2e4f343a5a..f13043d8bb 100644 --- a/tests/integration/hosts/aftereffects/test_publish_in_aftereffects.py +++ b/tests/integration/hosts/aftereffects/test_publish_in_aftereffects.py @@ -58,7 +58,7 @@ class TestPublishInAfterEffects(AELocalPublishTestClass): name="renderTest_taskMain")) failures.append( - DBAssert.count_of_types(dbcon, "representation", 4)) + DBAssert.count_of_types(dbcon, "representation", 3)) additional_args = {"context.subset": "workfileTest_task", "context.ext": "aep"} @@ -75,7 +75,7 @@ class TestPublishInAfterEffects(AELocalPublishTestClass): additional_args = {"context.subset": "renderTest_taskMain", "name": "thumbnail"} failures.append( - DBAssert.count_of_types(dbcon, "representation", 1, + DBAssert.count_of_types(dbcon, "representation", 0, additional_args=additional_args)) additional_args = {"context.subset": "renderTest_taskMain", diff --git a/tests/integration/hosts/aftereffects/test_publish_in_aftereffects_legacy.py b/tests/integration/hosts/aftereffects/test_publish_in_aftereffects_legacy.py index a62036e4a7..b99db24e75 100644 --- a/tests/integration/hosts/aftereffects/test_publish_in_aftereffects_legacy.py +++ b/tests/integration/hosts/aftereffects/test_publish_in_aftereffects_legacy.py @@ -60,7 +60,7 @@ class TestPublishInAfterEffects(AELocalPublishTestClass): name="renderTest_taskMain")) failures.append( - DBAssert.count_of_types(dbcon, "representation", 4)) + DBAssert.count_of_types(dbcon, "representation", 2)) additional_args = {"context.subset": "workfileTest_task", "context.ext": "aep"} @@ -77,7 +77,7 @@ class TestPublishInAfterEffects(AELocalPublishTestClass): additional_args = {"context.subset": "renderTest_taskMain", "name": "thumbnail"} failures.append( - DBAssert.count_of_types(dbcon, "representation", 1, + DBAssert.count_of_types(dbcon, "representation", 0, additional_args=additional_args)) additional_args = {"context.subset": "renderTest_taskMain", @@ -89,7 +89,7 @@ class TestPublishInAfterEffects(AELocalPublishTestClass): additional_args = {"context.subset": "renderTest_taskMain", "name": "thumbnail"} failures.append( - DBAssert.count_of_types(dbcon, "representation", 1, + DBAssert.count_of_types(dbcon, "representation", 0, additional_args=additional_args)) additional_args = {"context.subset": "renderTest_taskMain", diff --git a/tests/integration/hosts/aftereffects/test_publish_in_aftereffects_multiframe.py b/tests/integration/hosts/aftereffects/test_publish_in_aftereffects_multiframe.py index dcf34844d1..bd9d3e9b50 100644 --- a/tests/integration/hosts/aftereffects/test_publish_in_aftereffects_multiframe.py +++ b/tests/integration/hosts/aftereffects/test_publish_in_aftereffects_multiframe.py @@ -45,7 +45,7 @@ class TestPublishInAfterEffects(AELocalPublishTestClass): name="renderTest_taskMain")) failures.append( - DBAssert.count_of_types(dbcon, "representation", 4)) + DBAssert.count_of_types(dbcon, "representation", 3)) additional_args = {"context.subset": "workfileTest_task", "context.ext": "aep"} @@ -62,7 +62,7 @@ class TestPublishInAfterEffects(AELocalPublishTestClass): additional_args = {"context.subset": "renderTest_taskMain", "name": "thumbnail"} failures.append( - DBAssert.count_of_types(dbcon, "representation", 1, + DBAssert.count_of_types(dbcon, "representation", 0, additional_args=additional_args)) additional_args = {"context.subset": "renderTest_taskMain", diff --git a/tests/integration/hosts/maya/test_deadline_publish_in_maya.py b/tests/integration/hosts/maya/test_deadline_publish_in_maya.py index 7d2b409db3..79d6c22a26 100644 --- a/tests/integration/hosts/maya/test_deadline_publish_in_maya.py +++ b/tests/integration/hosts/maya/test_deadline_publish_in_maya.py @@ -54,7 +54,7 @@ class TestDeadlinePublishInMaya(MayaDeadlinePublishTestClass): DBAssert.count_of_types(dbcon, "subset", 1, name="workfileTest_task")) - failures.append(DBAssert.count_of_types(dbcon, "representation", 8)) + failures.append(DBAssert.count_of_types(dbcon, "representation", 7)) # hero included additional_args = {"context.subset": "modelMain", @@ -85,7 +85,7 @@ class TestDeadlinePublishInMaya(MayaDeadlinePublishTestClass): additional_args = {"context.subset": "renderTest_taskMain_beauty", "context.ext": "jpg"} failures.append( - DBAssert.count_of_types(dbcon, "representation", 1, + DBAssert.count_of_types(dbcon, "representation", 0, additional_args=additional_args)) additional_args = {"context.subset": "renderTest_taskMain_beauty", diff --git a/tests/integration/hosts/maya/test_deadline_publish_in_maya/expected/test_project/test_asset/publish/render/renderTest_taskMain_beauty/v001/test_project_test_asset_renderTest_taskMain_beauty_v001.jpg b/tests/integration/hosts/maya/test_deadline_publish_in_maya/expected/test_project/test_asset/publish/render/renderTest_taskMain_beauty/v001/test_project_test_asset_renderTest_taskMain_beauty_v001.jpg deleted file mode 100644 index 79ff799e8b063ba841c0566523d91de5c87a0c4e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 77035 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 diff --git a/tests/integration/hosts/nuke/test_deadline_publish_in_nuke.py b/tests/integration/hosts/nuke/test_deadline_publish_in_nuke.py index a4026f195b..586078ead6 100644 --- a/tests/integration/hosts/nuke/test_deadline_publish_in_nuke.py +++ b/tests/integration/hosts/nuke/test_deadline_publish_in_nuke.py @@ -69,7 +69,7 @@ class TestDeadlinePublishInNuke(NukeDeadlinePublishTestClass): name="workfileTest_task")) failures.append( - DBAssert.count_of_types(dbcon, "representation", 4)) + DBAssert.count_of_types(dbcon, "representation", 3)) additional_args = {"context.subset": "workfileTest_task", "context.ext": "nk"} @@ -86,7 +86,7 @@ class TestDeadlinePublishInNuke(NukeDeadlinePublishTestClass): additional_args = {"context.subset": "renderTest_taskMain", "name": "thumbnail"} failures.append( - DBAssert.count_of_types(dbcon, "representation", 1, + DBAssert.count_of_types(dbcon, "representation", 0, additional_args=additional_args)) additional_args = {"context.subset": "renderTest_taskMain",

G+>DJ?e&gjtJv$ZP-J* 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 literal 0 HcmV?d00001 diff --git a/openpype/hosts/aftereffects/api/panel_failure.png b/openpype/hosts/aftereffects/api/panel_failure.png deleted file mode 100644 index 6e52a77d22d54708b8bf0d1abac676ce723bd254..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13115 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 Extensions > Ayon`. Once launched you should be presented with a panel like this: +The Photoshop extension can be found under `Window > Extensions > Avalon`. Once launched you should be presented with a panel like this: -![Ayon Panel](panel.png "AYON Panel") +![Avalon Panel](panel.PNG "Avalon Panel") ## Developing @@ -37,7 +37,7 @@ When developing the extension you can load it [unsigned](https://github.com/Adob When signing the extension you can use this [guide](https://github.com/Adobe-CEP/Getting-Started-guides/tree/master/Package%20Distribute%20Install#package-distribute-install-guide). ``` -ZXPSignCmd -selfSignedCert NA NA Ayon Ayon-Photoshop Ayon extension.p12 +ZXPSignCmd -selfSignedCert NA NA Avalon Avalon-Photoshop avalon extension.p12 ZXPSignCmd -sign {path to avalon-core}\avalon\photoshop\extension {path to avalon-core}\avalon\photoshop\extension.zxp extension.p12 avalon ``` diff --git a/openpype/hosts/photoshop/api/extension.zxp b/openpype/hosts/photoshop/api/extension.zxp index 26a73a37fdd2c064a8438f595e8612a7adbcb323..39b766cd0d354e63c5b0e5b874be47131860e3c6 100644 GIT binary patch delta 9331 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 F^*&K&>% diff --git a/openpype/hosts/photoshop/api/extension/.debug b/openpype/hosts/photoshop/api/extension/.debug index 4cea03cb41..a0e2f3c9e0 100644 --- a/openpype/hosts/photoshop/api/extension/.debug +++ b/openpype/hosts/photoshop/api/extension/.debug @@ -1,9 +1,9 @@ - + - + \ No newline at end of file diff --git a/openpype/hosts/photoshop/api/extension/CSXS/manifest.xml b/openpype/hosts/photoshop/api/extension/CSXS/manifest.xml index 16d85be9b4..2089d06da1 100644 --- a/openpype/hosts/photoshop/api/extension/CSXS/manifest.xml +++ b/openpype/hosts/photoshop/api/extension/CSXS/manifest.xml @@ -1,7 +1,7 @@ - + - + @@ -16,7 +16,7 @@ - + ./index.html @@ -32,7 +32,7 @@ Panel -

G+>DJ?e&gjtJv$ZP-J* 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 diff --git a/openpype/hosts/aftereffects/api/panel_failure.png b/openpype/hosts/aftereffects/api/panel_failure.png new file mode 100644 index 0000000000000000000000000000000000000000..6e52a77d22d54708b8bf0d1abac676ce723bd254 GIT binary patch literal 13115 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 Extensions > Avalon`. Once launched you should be presented with a panel like this: +The Photoshop extension can be found under `Window > Extensions > Ayon`. Once launched you should be presented with a panel like this: -![Avalon Panel](panel.PNG "Avalon Panel") +![Ayon Panel](panel.png "AYON Panel") ## Developing @@ -37,7 +37,7 @@ When developing the extension you can load it [unsigned](https://github.com/Adob When signing the extension you can use this [guide](https://github.com/Adobe-CEP/Getting-Started-guides/tree/master/Package%20Distribute%20Install#package-distribute-install-guide). ``` -ZXPSignCmd -selfSignedCert NA NA Avalon Avalon-Photoshop avalon extension.p12 +ZXPSignCmd -selfSignedCert NA NA Ayon Ayon-Photoshop Ayon extension.p12 ZXPSignCmd -sign {path to avalon-core}\avalon\photoshop\extension {path to avalon-core}\avalon\photoshop\extension.zxp extension.p12 avalon ``` diff --git a/openpype/hosts/photoshop/api/extension.zxp b/openpype/hosts/photoshop/api/extension.zxp index 39b766cd0d354e63c5b0e5b874be47131860e3c6..9801a0ddc543c83e91332ac926ec1d2b48a98664 100644 GIT binary patch delta 10876 zcmcI~1yCH@^8VrkcXubay9NoC5PWeb*rLHK9b@4c;>o!y=7)89FBdb+>vF35vht%T!IRf2=Z1A#!upbL*6?YKe5-l*Ge zh%mPY$PUDAZejYuN?k@9lym#*?VmXMcDkU4Fn9_9GE>h_$?-#99^HW zT0fyB5SW|~F%5m{Mt;TupGQY!s*J^jK$QuyESCIy_!bqUseukc;+wU%uvxRW5UNnN zh>4OU<1s4dE`L`o!$GA}PQ}H+C;y-bqhx}%dKmJ0PHK zu(kGYAO3hl0zp7#cGeaUR}OU<@SkWWOcz-0&_4fnXk#tz!zF;El43I&fF*Xng3uFf!fx$)pFE7@8*>PiqT)N z^P)3?K+UNj5dQ6*`eX8s)D(tf6kf&%JZQIDRge+{!oA%WR5Aqr(|wU4Lx93G;T!aeQ6StJ#Uphc^?x!6;6F48 z^;`4(yGh(3Xkcq-aDxSb(h)%*?Asgg8v=V1h_$7Kiz~Z_z1?rS@;5X7>v{5T;ryST zC-=d`YZ^FAapL(}j$Q{Q5~ja@Bx0>+Q9zW4>|J!w zduxZ&!$UymtX*I+v6z<{wL4-|aO1Rap59+g9%VgnZ@FD_-{s8t{F*$Ygw zRi{$EnSh9%GXgPv)HMQZVvRMbOhso1J*jp%`Z4b5Y2I?9v8T`aoLx%}y>W7C=VisJ zvqIv!#=48!+NJNQ4-cvZTHyvWuhu*GAzAru>SiE`+YL@r%9*BKQBZu>JIKCcvLbm5 z7OaD$;+W|i!MrfHu1l(SI7_{vmBV@Pa+|Y2*w?d4h(SDrF*KEr^;}sgMsn6QJfPyh8q1D^i-IMxA2+|XF2xyx2XSkb=i_>U;t%nRPHtTH zu&e=1lPCx`XMH#QTa=9j(o9Y)ehRfb&X@QR_Q{U5A+{@rD>fx#8pgcp$nmxIV`BUZ zJOZT7Y`Bib`OE&#OirNOTOCsT9R5`g)m%_ZyLZVaB$0rPlgC)L#VRE{IJ=_=kL*g& z0)mU_onCOS?$XdU8LAtrslB4K6CJN`$jB-fkf{E)7xm;aU zld>um>9z!9?4?u5k2YOr;7T+shdfg!_C-s0h!8D8gr-Zjo8VYqxf2^NMCuRq+D9Ca zyQ1(L3Gkp8M5Fg-N@%Q*`0ZV$+9%4tu~j{u#JHp+xA*{Isv@vH>(l~Vex5sY@!SB_ zW+}o}rG>|Z$LVv)Z{#zUPkG<4$72VcS z77*w#5(L7yHOb$gSVPP$JZ_(e9)EfXr2m?K|HVzXs|V5o#sl3%4yEpZRoF9f9YylP zS0QdF&!#tN^jk~!o}LFBHZaz+ucd~Jx9};68u{m|2Zwva;~!2cIXOS9VW9$GPCn|5 zS}}Ss_D+f^@?^EO2QMo{(=5D*{1lu zE+tTF7>?Np7|m?qh^=+0zE!C{;M#gXPYSJ2K5gr4x-jg>ViTH=slC*1Q>PAlb�V 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 I?C#tD12ih)cmMzZ delta 9312 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 diff --git a/openpype/hosts/photoshop/api/extension/.debug b/openpype/hosts/photoshop/api/extension/.debug index a0e2f3c9e0..4cea03cb41 100644 --- a/openpype/hosts/photoshop/api/extension/.debug +++ b/openpype/hosts/photoshop/api/extension/.debug @@ -1,9 +1,9 @@ - + - \ No newline at end of file + diff --git a/openpype/hosts/photoshop/api/extension/CSXS/manifest.xml b/openpype/hosts/photoshop/api/extension/CSXS/manifest.xml index 2089d06da1..72bdab525c 100644 --- a/openpype/hosts/photoshop/api/extension/CSXS/manifest.xml +++ b/openpype/hosts/photoshop/api/extension/CSXS/manifest.xml @@ -1,7 +1,7 @@ - + - + @@ -16,7 +16,7 @@ - + ./index.html @@ -44,7 +44,7 @@ - ./icons/avalon-logo-48.png + ./icons/ayon_logo.png diff --git a/openpype/hosts/photoshop/api/extension/icons/avalon-logo-48.png b/openpype/hosts/photoshop/api/extension/icons/avalon-logo-48.png deleted file mode 100644 index 33fe2a606bd1ac9d285eb0d6a90b9b14150ca3c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1362 zcmV-Y1+DstP)5JpvKiH|A7SK4P~kT{6`M*9LrdQ!Ns9=$qj3(E_W@4gmP#=ht)#^0_psT^(5Px6qr0)mBB%5&-P}{Y*9ph@Pcn`!ete zwiE<#115v#ScdV2GBk!NTFTzWbF>Xip`p8%&KqcqI~Jb6tC``Vaf&07o~axnzSGF( z(ok|5&-4zgtV5rc$qke?7a8cU$D55m^%IcuOgXaxfTb~yegblyEaWJw%`Qe=-M%S@ zhOXSbt2KkcJv{&)s&PL6vC{g1Y-aKYBs(yc@x{whhk_0fK#=N=)Uup zs)>qe=dc=h3&3Gwr10?^8zc#g%1L4Xs{p!rj(uw=)9Szs&#`@sH{=+ zG+fz{pjE0VR%8l+hOX;W8`PbV32glOJ!~I2VXJkTz5Ufkuk(!F8z4>Ok_kkI+Kb}3)n06_ssJy4_*!y{BAe4)9jbBbSR!>UnLxyMT9bL9_?YdfL@K^^G6aZ)C$Qje z(NzKf2bZq2#ed1=gx1ZJQM{TNMk>CBw!wSvUjy@gS4qs1_a85GREVYsFz!+tU$`&M%7iR@HuBiw5bSa5S}|?)>G0PCUMb-Q{Pf zZt0{hEhroOCi1l=h%&q$mkBdG$MzLns~iea1>hEds{qcP5QbL){0`u*@Qfwke+13^ UGpuMiD*ylh07*qoM6N<$g1d2qT>t<8 diff --git a/openpype/hosts/photoshop/api/extension/icons/ayon_logo.png b/openpype/hosts/photoshop/api/extension/icons/ayon_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..3a96f8e2b499baa337cdc5a4d3cdf547f9ded972 GIT binary patch literal 3538 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 E-zj>|X#fBK literal 0 HcmV?d00001 diff --git a/openpype/hosts/photoshop/api/panel.PNG b/openpype/hosts/photoshop/api/panel.PNG deleted file mode 100644 index be5db3b8df08aa426b92de61eacc9afd00e93d45..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8756 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 diff --git a/openpype/hosts/photoshop/api/panel_failure.png b/openpype/hosts/photoshop/api/panel_failure.png new file mode 100644 index 0000000000000000000000000000000000000000..6e52a77d22d54708b8bf0d1abac676ce723bd254 GIT binary patch literal 13115 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 Date: Mon, 4 Dec 2023 15:13:23 +0000 Subject: [PATCH 253/327] Skip Arnold license for test rendering. (#5984) --- .../input/workfile/test_project_test_asset_test_task_v001.ma | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/integration/hosts/maya/test_deadline_publish_in_maya/input/workfile/test_project_test_asset_test_task_v001.ma b/tests/integration/hosts/maya/test_deadline_publish_in_maya/input/workfile/test_project_test_asset_test_task_v001.ma index c476a78086..2e882a5baa 100644 --- a/tests/integration/hosts/maya/test_deadline_publish_in_maya/input/workfile/test_project_test_asset_test_task_v001.ma +++ b/tests/integration/hosts/maya/test_deadline_publish_in_maya/input/workfile/test_project_test_asset_test_task_v001.ma @@ -236,6 +236,7 @@ createNode polyDisc -n "polyDisc1"; rename -uid "9ED8A7BD-4FFD-6107-4322-35ACD1D3AC42"; createNode aiOptions -s -n "defaultArnoldRenderOptions"; rename -uid "31A81965-48A6-B90D-503D-2FA162B7C982"; + setAttr ".skip_license_check" yes; createNode aiAOVFilter -s -n "defaultArnoldFilter"; rename -uid "77A2BCB1-4613-905E-080E-B997FD5E1C6F"; setAttr ".ai_translator" -type "string" "gaussian"; From 71badb50ccaab5c4e1e3801e8268721c0ae7f133 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Mon, 4 Dec 2023 15:14:59 +0000 Subject: [PATCH 254/327] Do not persist data by default. (#5987) --- tests/integration/hosts/maya/test_deadline_publish_in_maya.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/hosts/maya/test_deadline_publish_in_maya.py b/tests/integration/hosts/maya/test_deadline_publish_in_maya.py index c2ef342600..7d2b409db3 100644 --- a/tests/integration/hosts/maya/test_deadline_publish_in_maya.py +++ b/tests/integration/hosts/maya/test_deadline_publish_in_maya.py @@ -21,7 +21,7 @@ class TestDeadlinePublishInMaya(MayaDeadlinePublishTestClass): {OPENPYPE_ROOT}/.venv/Scripts/python.exe {OPENPYPE_ROOT}/start.py runtests ../tests/integration/hosts/maya # noqa: E501 """ - PERSIST = True + PERSIST = False TEST_FILES = [ ("test_deadline_publish_in_maya", "", "") From 120c0d0b608f684af947cb8d30781cd8c458f27d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 4 Dec 2023 17:00:49 +0100 Subject: [PATCH 255/327] avoiding duplicity of code --- .../MenuScripts/menu.py | 0 .../deploy/MenuScripts/openpype_menu.py | 48 ------------------- .../ayon}/Config/menu.fu | 4 +- .../ayon}/fusion_shared.prefs | 3 +- .../Config/menu.fu} | 4 +- .../deploy/{ => openpype}/fusion_shared.prefs | 3 +- .../fusion/deploy_ayon/MenuScripts/README.md | 6 --- .../MenuScripts/install_pyside2.py | 29 ----------- .../fusion/hooks/pre_fusion_profile_hook.py | 4 +- .../hosts/fusion/hooks/pre_fusion_setup.py | 8 +--- 10 files changed, 12 insertions(+), 97 deletions(-) rename openpype/hosts/fusion/{deploy_ayon => deploy}/MenuScripts/menu.py (100%) delete mode 100644 openpype/hosts/fusion/deploy/MenuScripts/openpype_menu.py rename openpype/hosts/fusion/{deploy_ayon => deploy/ayon}/Config/menu.fu (88%) rename openpype/hosts/fusion/{deploy_ayon => deploy/ayon}/fusion_shared.prefs (75%) rename openpype/hosts/fusion/deploy/{Config/openpype_menu.fu => openpype/Config/menu.fu} (87%) rename openpype/hosts/fusion/deploy/{ => openpype}/fusion_shared.prefs (73%) delete mode 100644 openpype/hosts/fusion/deploy_ayon/MenuScripts/README.md delete mode 100644 openpype/hosts/fusion/deploy_ayon/MenuScripts/install_pyside2.py diff --git a/openpype/hosts/fusion/deploy_ayon/MenuScripts/menu.py b/openpype/hosts/fusion/deploy/MenuScripts/menu.py similarity index 100% rename from openpype/hosts/fusion/deploy_ayon/MenuScripts/menu.py rename to openpype/hosts/fusion/deploy/MenuScripts/menu.py diff --git a/openpype/hosts/fusion/deploy/MenuScripts/openpype_menu.py b/openpype/hosts/fusion/deploy/MenuScripts/openpype_menu.py deleted file mode 100644 index 1c58ee50e4..0000000000 --- a/openpype/hosts/fusion/deploy/MenuScripts/openpype_menu.py +++ /dev/null @@ -1,48 +0,0 @@ -import os -import sys - -if sys.version_info < (3, 7): - # hack to handle discrepancy between distributed libraries and Python 3.6 - # mostly because wrong version of urllib3 - # TODO remove when not necessary - from openpype import PACKAGE_DIR - FUSION_HOST_DIR = os.path.join(PACKAGE_DIR, "hosts", "fusion") - - vendor_path = os.path.join(FUSION_HOST_DIR, "vendor") - if vendor_path not in sys.path: - sys.path.insert(0, vendor_path) - - print(f"Added vendorized libraries from {vendor_path}") - -from openpype.lib import Logger -from openpype.pipeline import ( - install_host, - registered_host, -) - - -def main(env): - # This script working directory starts in Fusion application folder. - # However the contents of that folder can conflict with Qt library dlls - # so we make sure to move out of it to avoid DLL Load Failed errors. - os.chdir("..") - from openpype.hosts.fusion.api import FusionHost - from openpype.hosts.fusion.api import menu - - # activate resolve from pype - install_host(FusionHost()) - - log = Logger.get_logger(__name__) - log.info(f"Registered host: {registered_host()}") - - menu.launch_openpype_menu() - - # Initiate a QTimer to check if Fusion is still alive every X interval - # If Fusion is not found - kill itself - # todo(roy): Implement timer that ensures UI doesn't remain when e.g. - # Fusion closes down - - -if __name__ == "__main__": - result = main(os.environ) - sys.exit(not bool(result)) diff --git a/openpype/hosts/fusion/deploy_ayon/Config/menu.fu b/openpype/hosts/fusion/deploy/ayon/Config/menu.fu similarity index 88% rename from openpype/hosts/fusion/deploy_ayon/Config/menu.fu rename to openpype/hosts/fusion/deploy/ayon/Config/menu.fu index 2846497a9e..deecc0f806 100644 --- a/openpype/hosts/fusion/deploy_ayon/Config/menu.fu +++ b/openpype/hosts/fusion/deploy/ayon/Config/menu.fu @@ -10,7 +10,7 @@ Composition = { Execute = _Lua [=[ - local scriptPath = app:MapPath("AYON:MenuScripts/menu.py") + local scriptPath = app:MapPath("DEPLOY:MenuScripts/menu.py") if bmd.fileexists(scriptPath) == false then print("[AYON Error] Can't run file: " .. scriptPath) else @@ -31,7 +31,7 @@ Composition = { Execute = _Lua [=[ - local scriptPath = app:MapPath("AYON:MenuScripts/install_pyside2.py") + local scriptPath = app:MapPath("DEPLOY:MenuScripts/install_pyside2.py") if bmd.fileexists(scriptPath) == false then print("[AYON Error] Can't run file: " .. scriptPath) else diff --git a/openpype/hosts/fusion/deploy_ayon/fusion_shared.prefs b/openpype/hosts/fusion/deploy/ayon/fusion_shared.prefs similarity index 75% rename from openpype/hosts/fusion/deploy_ayon/fusion_shared.prefs rename to openpype/hosts/fusion/deploy/ayon/fusion_shared.prefs index b5e8e3d024..90296c898e 100644 --- a/openpype/hosts/fusion/deploy_ayon/fusion_shared.prefs +++ b/openpype/hosts/fusion/deploy/ayon/fusion_shared.prefs @@ -3,7 +3,8 @@ Locked = true, Global = { Paths = { Map = { - ["AYON:"] = "$(AYON_FUSION)/deploy_ayon", + ["DEPLOY:"] = "$(OPENPYPE_FUSION)/deploy", + ["AYON:"] = "$(OPENPYPE_FUSION)/deploy/ayon", ["Config:"] = "UserPaths:Config;AYON:Config", ["Scripts:"] = "UserPaths:Scripts;Reactor:System/Scripts", }, diff --git a/openpype/hosts/fusion/deploy/Config/openpype_menu.fu b/openpype/hosts/fusion/deploy/openpype/Config/menu.fu similarity index 87% rename from openpype/hosts/fusion/deploy/Config/openpype_menu.fu rename to openpype/hosts/fusion/deploy/openpype/Config/menu.fu index 8b8d448259..6b325917c6 100644 --- a/openpype/hosts/fusion/deploy/Config/openpype_menu.fu +++ b/openpype/hosts/fusion/deploy/openpype/Config/menu.fu @@ -10,7 +10,7 @@ Composition = { Execute = _Lua [=[ - local scriptPath = app:MapPath("OpenPype:MenuScripts/openpype_menu.py") + local scriptPath = app:MapPath("DEPLOY:MenuScripts/menu.py") if bmd.fileexists(scriptPath) == false then print("[OpenPype Error] Can't run file: " .. scriptPath) else @@ -31,7 +31,7 @@ Composition = { Execute = _Lua [=[ - local scriptPath = app:MapPath("OpenPype:MenuScripts/install_pyside2.py") + local scriptPath = app:MapPath("DEPLOY:MenuScripts/install_pyside2.py") if bmd.fileexists(scriptPath) == false then print("[OpenPype Error] Can't run file: " .. scriptPath) else diff --git a/openpype/hosts/fusion/deploy/fusion_shared.prefs b/openpype/hosts/fusion/deploy/openpype/fusion_shared.prefs similarity index 73% rename from openpype/hosts/fusion/deploy/fusion_shared.prefs rename to openpype/hosts/fusion/deploy/openpype/fusion_shared.prefs index 93b08aa886..8360423076 100644 --- a/openpype/hosts/fusion/deploy/fusion_shared.prefs +++ b/openpype/hosts/fusion/deploy/openpype/fusion_shared.prefs @@ -3,7 +3,8 @@ Locked = true, Global = { Paths = { Map = { - ["OpenPype:"] = "$(OPENPYPE_FUSION)/deploy", + ["DEPLOY:"] = "$(OPENPYPE_FUSION)/deploy", + ["OpenPype:"] = "$(OPENPYPE_FUSION)/deploy/openpype", ["Config:"] = "UserPaths:Config;OpenPype:Config", ["Scripts:"] = "UserPaths:Scripts;Reactor:System/Scripts", }, diff --git a/openpype/hosts/fusion/deploy_ayon/MenuScripts/README.md b/openpype/hosts/fusion/deploy_ayon/MenuScripts/README.md deleted file mode 100644 index 9076f240ad..0000000000 --- a/openpype/hosts/fusion/deploy_ayon/MenuScripts/README.md +++ /dev/null @@ -1,6 +0,0 @@ -### Ayon deploy MenuScripts - -Note that this `MenuScripts` is not an official Fusion folder. -Ayon only uses this folder in `{fusion}/deploy/` to trigger the Ayon menu actions. - -They are used in the actions defined in `.fu` files in `{fusion}/deploy_ayon/Config`. diff --git a/openpype/hosts/fusion/deploy_ayon/MenuScripts/install_pyside2.py b/openpype/hosts/fusion/deploy_ayon/MenuScripts/install_pyside2.py deleted file mode 100644 index e1240fd677..0000000000 --- a/openpype/hosts/fusion/deploy_ayon/MenuScripts/install_pyside2.py +++ /dev/null @@ -1,29 +0,0 @@ -# This is just a quick hack for users running Py3 locally but having no -# Qt library installed -import os -import subprocess -import importlib - - -try: - from qtpy import API_NAME - - print(f"Qt binding: {API_NAME}") - mod = importlib.import_module(API_NAME) - print(f"Qt path: {mod.__file__}") - print("Qt library found, nothing to do..") - -except ImportError: - print("Assuming no Qt library is installed..") - print('Installing PySide2 for Python 3.6: ' - f'{os.environ["FUSION16_PYTHON36_HOME"]}') - - # Get full path to python executable - exe = "python.exe" if os.name == 'nt' else "python" - python = os.path.join(os.environ["FUSION16_PYTHON36_HOME"], exe) - assert os.path.exists(python), f"Python doesn't exist: {python}" - - # Do python -m pip install PySide2 - args = [python, "-m", "pip", "install", "PySide2"] - print(f"Args: {args}") - subprocess.Popen(args) diff --git a/openpype/hosts/fusion/hooks/pre_fusion_profile_hook.py b/openpype/hosts/fusion/hooks/pre_fusion_profile_hook.py index 0b6626777e..59053ba62a 100644 --- a/openpype/hosts/fusion/hooks/pre_fusion_profile_hook.py +++ b/openpype/hosts/fusion/hooks/pre_fusion_profile_hook.py @@ -165,10 +165,10 @@ class FusionCopyPrefsPrelaunch(PreLaunchHook): if AYON_SERVER_ENABLED: master_prefs = Path( - FUSION_HOST_DIR, "deploy_ayon", "fusion_shared.prefs") + FUSION_HOST_DIR, "deploy", "ayon", "fusion_shared.prefs") else: master_prefs = Path( - FUSION_HOST_DIR, "deploy", "fusion_shared.prefs") + FUSION_HOST_DIR, "deploy", "openpype", "fusion_shared.prefs") self.log.info(f"Setting {master_prefs_variable}: {master_prefs}") self.launch_context.env[master_prefs_variable] = str(master_prefs) diff --git a/openpype/hosts/fusion/hooks/pre_fusion_setup.py b/openpype/hosts/fusion/hooks/pre_fusion_setup.py index bd7f35f900..073f551b6f 100644 --- a/openpype/hosts/fusion/hooks/pre_fusion_setup.py +++ b/openpype/hosts/fusion/hooks/pre_fusion_setup.py @@ -65,9 +65,5 @@ class FusionPrelaunch(PreLaunchHook): self.launch_context.env[py3_var] = py3_dir - if AYON_SERVER_ENABLED: - self.log.info(f"Setting AYON_FUSION: {FUSION_HOST_DIR}") - self.launch_context.env["AYON_FUSION"] = FUSION_HOST_DIR - else: - self.log.info(f"Setting OPENPYPE_FUSION: {FUSION_HOST_DIR}") - self.launch_context.env["OPENPYPE_FUSION"] = FUSION_HOST_DIR + self.log.info(f"Setting OPENPYPE_FUSION: {FUSION_HOST_DIR}") + self.launch_context.env["OPENPYPE_FUSION"] = FUSION_HOST_DIR From aedb7d13649fcb8a86ea4dc1db5be0f741e6abd8 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 4 Dec 2023 17:02:22 +0100 Subject: [PATCH 256/327] hound --- openpype/hosts/fusion/hooks/pre_fusion_setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/fusion/hooks/pre_fusion_setup.py b/openpype/hosts/fusion/hooks/pre_fusion_setup.py index 073f551b6f..576628e876 100644 --- a/openpype/hosts/fusion/hooks/pre_fusion_setup.py +++ b/openpype/hosts/fusion/hooks/pre_fusion_setup.py @@ -1,5 +1,4 @@ import os -from openpype import AYON_SERVER_ENABLED from openpype.lib.applications import ( PreLaunchHook, LaunchTypes, From 1b1d1ff1fe19125b7378dc40d828185f54c982ca Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 4 Dec 2023 17:24:58 +0100 Subject: [PATCH 257/327] suggestion to do relative path mapping rather then DEPLOY key --- openpype/hosts/fusion/deploy/ayon/Config/menu.fu | 4 ++-- openpype/hosts/fusion/deploy/ayon/fusion_shared.prefs | 3 +-- openpype/hosts/fusion/deploy/openpype/Config/menu.fu | 4 ++-- openpype/hosts/fusion/deploy/openpype/fusion_shared.prefs | 3 +-- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/fusion/deploy/ayon/Config/menu.fu b/openpype/hosts/fusion/deploy/ayon/Config/menu.fu index deecc0f806..79ef4595d9 100644 --- a/openpype/hosts/fusion/deploy/ayon/Config/menu.fu +++ b/openpype/hosts/fusion/deploy/ayon/Config/menu.fu @@ -10,7 +10,7 @@ Composition = { Execute = _Lua [=[ - local scriptPath = app:MapPath("DEPLOY:MenuScripts/menu.py") + local scriptPath = app:MapPath("AYON:../MenuScripts/menu.py") if bmd.fileexists(scriptPath) == false then print("[AYON Error] Can't run file: " .. scriptPath) else @@ -31,7 +31,7 @@ Composition = { Execute = _Lua [=[ - local scriptPath = app:MapPath("DEPLOY:MenuScripts/install_pyside2.py") + local scriptPath = app:MapPath("AYON:../MenuScripts/install_pyside2.py") if bmd.fileexists(scriptPath) == false then print("[AYON Error] Can't run file: " .. scriptPath) else diff --git a/openpype/hosts/fusion/deploy/ayon/fusion_shared.prefs b/openpype/hosts/fusion/deploy/ayon/fusion_shared.prefs index 90296c898e..731f26682b 100644 --- a/openpype/hosts/fusion/deploy/ayon/fusion_shared.prefs +++ b/openpype/hosts/fusion/deploy/ayon/fusion_shared.prefs @@ -3,10 +3,9 @@ Locked = true, Global = { Paths = { Map = { - ["DEPLOY:"] = "$(OPENPYPE_FUSION)/deploy", ["AYON:"] = "$(OPENPYPE_FUSION)/deploy/ayon", ["Config:"] = "UserPaths:Config;AYON:Config", - ["Scripts:"] = "UserPaths:Scripts;Reactor:System/Scripts", + ["Scripts:"] = "UserPaths:Scripts;Reactor:System/Scripts;AYON:../Script", }, }, Script = { diff --git a/openpype/hosts/fusion/deploy/openpype/Config/menu.fu b/openpype/hosts/fusion/deploy/openpype/Config/menu.fu index 6b325917c6..715fa98aa3 100644 --- a/openpype/hosts/fusion/deploy/openpype/Config/menu.fu +++ b/openpype/hosts/fusion/deploy/openpype/Config/menu.fu @@ -10,7 +10,7 @@ Composition = { Execute = _Lua [=[ - local scriptPath = app:MapPath("DEPLOY:MenuScripts/menu.py") + local scriptPath = app:MapPath("OpenPype:../MenuScripts/menu.py") if bmd.fileexists(scriptPath) == false then print("[OpenPype Error] Can't run file: " .. scriptPath) else @@ -31,7 +31,7 @@ Composition = { Execute = _Lua [=[ - local scriptPath = app:MapPath("DEPLOY:MenuScripts/install_pyside2.py") + local scriptPath = app:MapPath("OpenPype:../MenuScripts/install_pyside2.py") if bmd.fileexists(scriptPath) == false then print("[OpenPype Error] Can't run file: " .. scriptPath) else diff --git a/openpype/hosts/fusion/deploy/openpype/fusion_shared.prefs b/openpype/hosts/fusion/deploy/openpype/fusion_shared.prefs index 8360423076..1425f8c317 100644 --- a/openpype/hosts/fusion/deploy/openpype/fusion_shared.prefs +++ b/openpype/hosts/fusion/deploy/openpype/fusion_shared.prefs @@ -3,10 +3,9 @@ Locked = true, Global = { Paths = { Map = { - ["DEPLOY:"] = "$(OPENPYPE_FUSION)/deploy", ["OpenPype:"] = "$(OPENPYPE_FUSION)/deploy/openpype", ["Config:"] = "UserPaths:Config;OpenPype:Config", - ["Scripts:"] = "UserPaths:Scripts;Reactor:System/Scripts", + ["Scripts:"] = "UserPaths:Scripts;Reactor:System/Scripts;OpenPype:../Script", }, }, Script = { From 96d1cc0d5311d5c5fa33e9233c8a6318fae40c65 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 4 Dec 2023 17:42:18 +0100 Subject: [PATCH 258/327] Fix name in PS --- openpype/hosts/photoshop/api/extension.zxp | Bin 55653 -> 55656 bytes .../photoshop/api/extension/CSXS/manifest.xml | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/photoshop/api/extension.zxp b/openpype/hosts/photoshop/api/extension.zxp index 9801a0ddc543c83e91332ac926ec1d2b48a98664..26a73a37fdd2c064a8438f595e8612a7adbcb323 100644 GIT binary patch delta 2868 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 delta 2827 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 Panel -