From 5f0ce4f88dd09caee68a04e94db672620c6d0416 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 15 Sep 2023 18:38:40 +0800 Subject: [PATCH 01/31] add tycache family --- .../max/plugins/create/create_tycache.py | 34 ++++ .../hosts/max/plugins/load/load_tycache.py | 67 +++++++ .../max/plugins/publish/extract_tycache.py | 183 ++++++++++++++++++ .../plugins/publish/validate_pointcloud.py | 59 +----- .../plugins/publish/validate_tyflow_data.py | 74 +++++++ 5 files changed, 361 insertions(+), 56 deletions(-) create mode 100644 openpype/hosts/max/plugins/create/create_tycache.py create mode 100644 openpype/hosts/max/plugins/load/load_tycache.py create mode 100644 openpype/hosts/max/plugins/publish/extract_tycache.py create mode 100644 openpype/hosts/max/plugins/publish/validate_tyflow_data.py diff --git a/openpype/hosts/max/plugins/create/create_tycache.py b/openpype/hosts/max/plugins/create/create_tycache.py new file mode 100644 index 0000000000..0fe0f32eed --- /dev/null +++ b/openpype/hosts/max/plugins/create/create_tycache.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +"""Creator plugin for creating TyCache.""" +from openpype.hosts.max.api import plugin +from openpype.pipeline import EnumDef + + +class CreateTyCache(plugin.MaxCreator): + """Creator plugin for TyCache.""" + identifier = "io.openpype.creators.max.tycache" + label = "TyCache" + family = "tycache" + icon = "gear" + + def create(self, subset_name, instance_data, pre_create_data): + from pymxs import runtime as rt + instance_data = pre_create_data.get("tycache_type") + super(CreateTyCache, self).create( + subset_name, + instance_data, + pre_create_data) + + def get_pre_create_attr_defs(self): + attrs = super(CreateTyCache, self).get_pre_create_attr_defs() + + tycache_format_enum = ["tycache", "tycachespline"] + + + return attrs + [ + + EnumDef("tycache_type", + tycache_format_enum, + default="tycache", + label="TyCache Type") + ] diff --git a/openpype/hosts/max/plugins/load/load_tycache.py b/openpype/hosts/max/plugins/load/load_tycache.py new file mode 100644 index 0000000000..657e743087 --- /dev/null +++ b/openpype/hosts/max/plugins/load/load_tycache.py @@ -0,0 +1,67 @@ +import os + +from openpype.hosts.max.api import lib, maintained_selection +from openpype.hosts.max.api.lib import ( + unique_namespace, + +) +from openpype.hosts.max.api.pipeline import ( + containerise, + get_previous_loaded_object, + update_custom_attribute_data +) +from openpype.pipeline import get_representation_path, load + + +class PointCloudLoader(load.LoaderPlugin): + """Point Cloud Loader.""" + + families = ["tycache"] + representations = ["tyc"] + order = -8 + icon = "code-fork" + color = "green" + + def load(self, context, name=None, namespace=None, data=None): + """Load tyCache""" + from pymxs import runtime as rt + filepath = os.path.normpath(self.filepath_from_context(context)) + obj = rt.tyCache() + obj.filename = filepath + + namespace = unique_namespace( + name + "_", + suffix="_", + ) + obj.name = f"{namespace}:{obj.name}" + + return containerise( + name, [obj], context, + namespace, loader=self.__class__.__name__) + + def update(self, container, representation): + """update the container""" + from pymxs import runtime as rt + + path = get_representation_path(representation) + node = rt.GetNodeByName(container["instance_node"]) + node_list = get_previous_loaded_object(node) + update_custom_attribute_data( + node, node_list) + with maintained_selection(): + rt.Select(node_list) + for prt in rt.Selection: + prt.filename = path + lib.imprint(container["instance_node"], { + "representation": str(representation["_id"]) + }) + + def switch(self, container, representation): + self.update(container, representation) + + def remove(self, container): + """remove the container""" + from pymxs import runtime as rt + + node = rt.GetNodeByName(container["instance_node"]) + rt.Delete(node) diff --git a/openpype/hosts/max/plugins/publish/extract_tycache.py b/openpype/hosts/max/plugins/publish/extract_tycache.py new file mode 100644 index 0000000000..8fcdd6d65c --- /dev/null +++ b/openpype/hosts/max/plugins/publish/extract_tycache.py @@ -0,0 +1,183 @@ +import os + +import pyblish.api +from pymxs import runtime as rt + +from openpype.hosts.max.api import maintained_selection +from openpype.pipeline import publish + + +class ExtractTyCache(publish.Extractor): + """ + Extract tycache format with tyFlow operators. + Notes: + - TyCache only works for TyFlow Pro Plugin. + + Args: + self.export_particle(): sets up all job arguments for attributes + to be exported in MAXscript + + self.get_operators(): get the export_particle operator + + self.get_files(): get the files with tyFlow naming convention + before publishing + """ + + order = pyblish.api.ExtractorOrder - 0.2 + label = "Extract TyCache" + hosts = ["max"] + families = ["tycache"] + + def process(self, instance): + # TODO: let user decide the param + start = int(instance.context.data.get("frameStart")) + end = int(instance.context.data.get("frameEnd")) + self.log.info("Extracting Tycache...") + + stagingdir = self.staging_dir(instance) + filename = "{name}.tyc".format(**instance.data) + path = os.path.join(stagingdir, filename) + filenames = self.get_file(path, start, end) + with maintained_selection(): + job_args = None + if instance.data["tycache_type"] == "tycache": + job_args = self.export_particle( + instance.data["members"], + start, end, path) + elif instance.data["tycache_type"] == "tycachespline": + job_args = self.export_particle( + instance.data["members"], + start, end, path, + tycache_spline_enabled=True) + + for job in job_args: + rt.Execute(job) + + representation = { + 'name': 'tyc', + 'ext': 'tyc', + 'files': filenames if len(filenames) > 1 else filenames[0], + "stagingDir": stagingdir + } + instance.data["representations"].append(representation) + self.log.info(f"Extracted instance '{instance.name}' to: {path}") + + def get_file(self, filepath, start_frame, end_frame): + filenames = [] + filename = os.path.basename(filepath) + orig_name, _ = os.path.splitext(filename) + for frame in range(int(start_frame), int(end_frame) + 1): + actual_name = "{}_{:05}".format(orig_name, frame) + actual_filename = filepath.replace(orig_name, actual_name) + filenames.append(os.path.basename(actual_filename)) + + return filenames + + def export_particle(self, members, start, end, + filepath, tycache_spline_enabled=False): + """Sets up all job arguments for attributes. + + Those attributes are to be exported in MAX Script. + + Args: + members (list): Member nodes of the instance. + start (int): Start frame. + end (int): End frame. + filepath (str): Output path of the TyCache file. + + Returns: + list of arguments for MAX Script. + + """ + job_args = [] + opt_list = self.get_operators(members) + for operator in opt_list: + if tycache_spline_enabled: + export_mode = f'{operator}.exportMode=3' + has_tyc_spline = f'{operator}.tycacheSplines=true' + job_args.extend([export_mode, has_tyc_spline]) + else: + export_mode = f'{operator}.exportMode=2' + job_args.append(export_mode) + start_frame = f"{operator}.frameStart={start}" + job_args.append(start_frame) + end_frame = f"{operator}.frameEnd={end}" + job_args.append(end_frame) + filepath = filepath.replace("\\", "/") + tycache_filename = f'{operator}.tyCacheFilename="{filepath}"' + job_args.append(tycache_filename) + additional_args = self.get_custom_attr(operator) + job_args.extend(iter(additional_args)) + tycache_export = f"{operator}.exportTyCache()" + job_args.append(tycache_export) + + return job_args + + @staticmethod + def get_operators(members): + """Get Export Particles Operator. + + Args: + members (list): Instance members. + + Returns: + list of particle operators + + """ + opt_list = [] + for member in members: + obj = member.baseobject + # TODO: to see if it can be used maxscript instead + anim_names = rt.GetSubAnimNames(obj) + for anim_name in anim_names: + sub_anim = rt.GetSubAnim(obj, anim_name) + boolean = rt.IsProperty(sub_anim, "Export_Particles") + if boolean: + event_name = sub_anim.Name + opt = f"${member.Name}.{event_name}.export_particles" + opt_list.append(opt) + + return opt_list + +""" +.exportMode : integer +.frameStart : integer +.frameEnd : integer + + .tycacheChanAge : boolean + .tycacheChanGroups : boolean + .tycacheChanPos : boolean + .tycacheChanRot : boolean + .tycacheChanScale : boolean + .tycacheChanVel : boolean + .tycacheChanSpin : boolean + .tycacheChanShape : boolean + .tycacheChanMatID : boolean + .tycacheChanMapping : boolean + .tycacheChanMaterials : boolean + .tycacheChanCustomFloat : boolean + .tycacheChanCustomVector : boolean + .tycacheChanCustomTM : boolean + .tycacheChanPhysX : boolean + .tycacheMeshBackup : boolean + .tycacheCreateObject : boolean + .tycacheCreateObjectIfNotCreated : boolean + .tycacheLayer : string + .tycacheObjectName : string + .tycacheAdditionalCloth : boolean + .tycacheAdditionalSkin : boolean + .tycacheAdditionalSkinID : boolean + .tycacheAdditionalSkinIDValue : integer + .tycacheAdditionalTerrain : boolean + .tycacheAdditionalVDB : boolean + .tycacheAdditionalSplinePaths : boolean + .tycacheAdditionalTyMesher : boolean + .tycacheAdditionalGeo : boolean + .tycacheAdditionalObjectList_deprecated : node array + .tycacheAdditionalObjectList : maxObject array + .tycacheAdditionalGeoActivateModifiers : boolean + .tycacheSplinesAdditionalSplines : boolean + .tycacheSplinesAdditionalSplinesObjectList_deprecated : node array + .tycacheSplinesAdditionalObjectList : maxObject array + +""" diff --git a/openpype/hosts/max/plugins/publish/validate_pointcloud.py b/openpype/hosts/max/plugins/publish/validate_pointcloud.py index 295a23f1f6..3ccc9dfda8 100644 --- a/openpype/hosts/max/plugins/publish/validate_pointcloud.py +++ b/openpype/hosts/max/plugins/publish/validate_pointcloud.py @@ -14,29 +14,16 @@ class ValidatePointCloud(pyblish.api.InstancePlugin): def process(self, instance): """ Notes: - - 1. Validate the container only include tyFlow objects - 2. Validate if tyFlow operator Export Particle exists - 3. Validate if the export mode of Export Particle is at PRT format - 4. Validate the partition count and range set as default value + 1. Validate if the export mode of Export Particle is at PRT format + 2. Validate the partition count and range set as default value Partition Count : 100 Partition Range : 1 to 1 - 5. Validate if the custom attribute(s) exist as parameter(s) + 3. Validate if the custom attribute(s) exist as parameter(s) of export_particle operator """ report = [] - invalid_object = self.get_tyflow_object(instance) - if invalid_object: - report.append(f"Non tyFlow object found: {invalid_object}") - - invalid_operator = self.get_tyflow_operator(instance) - if invalid_operator: - report.append(( - "tyFlow ExportParticle operator not " - f"found: {invalid_operator}")) - if self.validate_export_mode(instance): report.append("The export mode is not at PRT") @@ -52,46 +39,6 @@ class ValidatePointCloud(pyblish.api.InstancePlugin): if report: raise PublishValidationError(f"{report}") - def get_tyflow_object(self, instance): - invalid = [] - container = instance.data["instance_node"] - self.log.info(f"Validating tyFlow container for {container}") - - selection_list = instance.data["members"] - for sel in selection_list: - sel_tmp = str(sel) - if rt.ClassOf(sel) in [rt.tyFlow, - rt.Editable_Mesh]: - if "tyFlow" not in sel_tmp: - invalid.append(sel) - else: - invalid.append(sel) - - return invalid - - def get_tyflow_operator(self, instance): - invalid = [] - container = instance.data["instance_node"] - self.log.info(f"Validating tyFlow object for {container}") - selection_list = instance.data["members"] - bool_list = [] - for sel in selection_list: - obj = sel.baseobject - anim_names = rt.GetSubAnimNames(obj) - for anim_name in anim_names: - # get all the names of the related tyFlow nodes - sub_anim = rt.GetSubAnim(obj, anim_name) - # check if there is export particle operator - boolean = rt.IsProperty(sub_anim, "Export_Particles") - bool_list.append(str(boolean)) - # if the export_particles property is not there - # it means there is not a "Export Particle" operator - if "True" not in bool_list: - self.log.error("Operator 'Export Particles' not found!") - invalid.append(sel) - - return invalid - def validate_custom_attribute(self, instance): invalid = [] container = instance.data["instance_node"] diff --git a/openpype/hosts/max/plugins/publish/validate_tyflow_data.py b/openpype/hosts/max/plugins/publish/validate_tyflow_data.py new file mode 100644 index 0000000000..de8d161b9d --- /dev/null +++ b/openpype/hosts/max/plugins/publish/validate_tyflow_data.py @@ -0,0 +1,74 @@ +import pyblish.api +from openpype.pipeline import PublishValidationError +from pymxs import runtime as rt + + +class ValidatePointCloud(pyblish.api.InstancePlugin): + """Validate that TyFlow plugins or + relevant operators being set correctly.""" + + order = pyblish.api.ValidatorOrder + families = ["pointcloud", "tycache"] + hosts = ["max"] + label = "TyFlow Data" + + def process(self, instance): + """ + Notes: + 1. Validate the container only include tyFlow objects + 2. Validate if tyFlow operator Export Particle exists + + """ + report = [] + + invalid_object = self.get_tyflow_object(instance) + if invalid_object: + report.append(f"Non tyFlow object found: {invalid_object}") + + invalid_operator = self.get_tyflow_operator(instance) + if invalid_operator: + report.append(( + "tyFlow ExportParticle operator not " + f"found: {invalid_operator}")) + if report: + raise PublishValidationError(f"{report}") + + def get_tyflow_object(self, instance): + invalid = [] + container = instance.data["instance_node"] + self.log.info(f"Validating tyFlow container for {container}") + + selection_list = instance.data["members"] + for sel in selection_list: + sel_tmp = str(sel) + if rt.ClassOf(sel) in [rt.tyFlow, + rt.Editable_Mesh]: + if "tyFlow" not in sel_tmp: + invalid.append(sel) + else: + invalid.append(sel) + + return invalid + + def get_tyflow_operator(self, instance): + invalid = [] + container = instance.data["instance_node"] + self.log.info(f"Validating tyFlow object for {container}") + selection_list = instance.data["members"] + bool_list = [] + for sel in selection_list: + obj = sel.baseobject + anim_names = rt.GetSubAnimNames(obj) + for anim_name in anim_names: + # get all the names of the related tyFlow nodes + sub_anim = rt.GetSubAnim(obj, anim_name) + # check if there is export particle operator + boolean = rt.IsProperty(sub_anim, "Export_Particles") + bool_list.append(str(boolean)) + # if the export_particles property is not there + # it means there is not a "Export Particle" operator + if "True" not in bool_list: + self.log.error("Operator 'Export Particles' not found!") + invalid.append(sel) + + return invalid From b7ceeaa3542046c20899ac3e3979a40a3ff3410e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 15 Sep 2023 20:21:34 +0800 Subject: [PATCH 02/31] hound & add tycache family into integrate --- .../max/plugins/create/create_tycache.py | 4 +-- .../max/plugins/publish/extract_tycache.py | 28 ++++++++++++++++--- .../plugins/publish/collect_resources_path.py | 3 +- openpype/plugins/publish/integrate.py | 3 +- 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/max/plugins/create/create_tycache.py b/openpype/hosts/max/plugins/create/create_tycache.py index 0fe0f32eed..b99ca37c9b 100644 --- a/openpype/hosts/max/plugins/create/create_tycache.py +++ b/openpype/hosts/max/plugins/create/create_tycache.py @@ -12,7 +12,6 @@ class CreateTyCache(plugin.MaxCreator): icon = "gear" def create(self, subset_name, instance_data, pre_create_data): - from pymxs import runtime as rt instance_data = pre_create_data.get("tycache_type") super(CreateTyCache, self).create( subset_name, @@ -24,11 +23,10 @@ class CreateTyCache(plugin.MaxCreator): tycache_format_enum = ["tycache", "tycachespline"] - return attrs + [ EnumDef("tycache_type", tycache_format_enum, default="tycache", label="TyCache Type") - ] + ] diff --git a/openpype/hosts/max/plugins/publish/extract_tycache.py b/openpype/hosts/max/plugins/publish/extract_tycache.py index 8fcdd6d65c..9bef175d27 100644 --- a/openpype/hosts/max/plugins/publish/extract_tycache.py +++ b/openpype/hosts/max/plugins/publish/extract_tycache.py @@ -5,6 +5,7 @@ from pymxs import runtime as rt from openpype.hosts.max.api import maintained_selection from openpype.pipeline import publish +from openpype.lib import EnumDef class ExtractTyCache(publish.Extractor): @@ -93,7 +94,7 @@ class ExtractTyCache(publish.Extractor): opt_list = self.get_operators(members) for operator in opt_list: if tycache_spline_enabled: - export_mode = f'{operator}.exportMode=3' + export_mode = f'{operator}.exportMode=3' has_tyc_spline = f'{operator}.tycacheSplines=true' job_args.extend([export_mode, has_tyc_spline]) else: @@ -133,12 +134,31 @@ class ExtractTyCache(publish.Extractor): sub_anim = rt.GetSubAnim(obj, anim_name) boolean = rt.IsProperty(sub_anim, "Export_Particles") if boolean: - event_name = sub_anim.Name - opt = f"${member.Name}.{event_name}.export_particles" - opt_list.append(opt) + event_name = sub_anim.Name + opt = f"${member.Name}.{event_name}.export_particles" + opt_list.append(opt) return opt_list + def get_custom_attr(operators): + additonal_args = + ] + + + @classmethod + def get_attribute_defs(cls): + tycache_enum ={ + "Age": "tycacheChanAge", + "Groups": "tycacheChanGroups", + } + return [ + EnumDef("dspGeometry", + items=tycache_enum, + default="", + multiselection=True) + ] + + """ .exportMode : integer .frameStart : integer diff --git a/openpype/plugins/publish/collect_resources_path.py b/openpype/plugins/publish/collect_resources_path.py index f96dd0ae18..2fa944718f 100644 --- a/openpype/plugins/publish/collect_resources_path.py +++ b/openpype/plugins/publish/collect_resources_path.py @@ -62,7 +62,8 @@ class CollectResourcesPath(pyblish.api.InstancePlugin): "effect", "staticMesh", "skeletalMesh", - "xgen" + "xgen", + "tycache" ] def process(self, instance): diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 7e48155b9e..2b4c054fdc 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -139,7 +139,8 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "simpleUnrealTexture", "online", "uasset", - "blendScene" + "blendScene", + "tycache" ] default_template_name = "publish" From ab90af0f5e485f069550dde8ffa83697b8bb03dc Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 15 Sep 2023 20:23:17 +0800 Subject: [PATCH 03/31] hound --- .../max/plugins/publish/extract_tycache.py | 65 +------------------ 1 file changed, 1 insertion(+), 64 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/extract_tycache.py b/openpype/hosts/max/plugins/publish/extract_tycache.py index 9bef175d27..bbb6dc115f 100644 --- a/openpype/hosts/max/plugins/publish/extract_tycache.py +++ b/openpype/hosts/max/plugins/publish/extract_tycache.py @@ -107,8 +107,7 @@ class ExtractTyCache(publish.Extractor): filepath = filepath.replace("\\", "/") tycache_filename = f'{operator}.tyCacheFilename="{filepath}"' job_args.append(tycache_filename) - additional_args = self.get_custom_attr(operator) - job_args.extend(iter(additional_args)) + # TODO: add the additional job args for tycache attributes tycache_export = f"{operator}.exportTyCache()" job_args.append(tycache_export) @@ -139,65 +138,3 @@ class ExtractTyCache(publish.Extractor): opt_list.append(opt) return opt_list - - def get_custom_attr(operators): - additonal_args = - ] - - - @classmethod - def get_attribute_defs(cls): - tycache_enum ={ - "Age": "tycacheChanAge", - "Groups": "tycacheChanGroups", - } - return [ - EnumDef("dspGeometry", - items=tycache_enum, - default="", - multiselection=True) - ] - - -""" -.exportMode : integer -.frameStart : integer -.frameEnd : integer - - .tycacheChanAge : boolean - .tycacheChanGroups : boolean - .tycacheChanPos : boolean - .tycacheChanRot : boolean - .tycacheChanScale : boolean - .tycacheChanVel : boolean - .tycacheChanSpin : boolean - .tycacheChanShape : boolean - .tycacheChanMatID : boolean - .tycacheChanMapping : boolean - .tycacheChanMaterials : boolean - .tycacheChanCustomFloat : boolean - .tycacheChanCustomVector : boolean - .tycacheChanCustomTM : boolean - .tycacheChanPhysX : boolean - .tycacheMeshBackup : boolean - .tycacheCreateObject : boolean - .tycacheCreateObjectIfNotCreated : boolean - .tycacheLayer : string - .tycacheObjectName : string - .tycacheAdditionalCloth : boolean - .tycacheAdditionalSkin : boolean - .tycacheAdditionalSkinID : boolean - .tycacheAdditionalSkinIDValue : integer - .tycacheAdditionalTerrain : boolean - .tycacheAdditionalVDB : boolean - .tycacheAdditionalSplinePaths : boolean - .tycacheAdditionalTyMesher : boolean - .tycacheAdditionalGeo : boolean - .tycacheAdditionalObjectList_deprecated : node array - .tycacheAdditionalObjectList : maxObject array - .tycacheAdditionalGeoActivateModifiers : boolean - .tycacheSplinesAdditionalSplines : boolean - .tycacheSplinesAdditionalSplinesObjectList_deprecated : node array - .tycacheSplinesAdditionalObjectList : maxObject array - -""" From d6dc61c031a1661485bf5234ed72590480ac3fd9 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 15 Sep 2023 21:54:29 +0800 Subject: [PATCH 04/31] make sure all instanceplugins got the right class --- openpype/hosts/max/plugins/create/create_tycache.py | 5 +++-- openpype/hosts/max/plugins/publish/extract_tycache.py | 3 +-- openpype/hosts/max/plugins/publish/validate_tyflow_data.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/max/plugins/create/create_tycache.py b/openpype/hosts/max/plugins/create/create_tycache.py index b99ca37c9b..c48094028a 100644 --- a/openpype/hosts/max/plugins/create/create_tycache.py +++ b/openpype/hosts/max/plugins/create/create_tycache.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Creator plugin for creating TyCache.""" from openpype.hosts.max.api import plugin -from openpype.pipeline import EnumDef +from openpype.lib import EnumDef class CreateTyCache(plugin.MaxCreator): @@ -12,7 +12,8 @@ class CreateTyCache(plugin.MaxCreator): icon = "gear" def create(self, subset_name, instance_data, pre_create_data): - instance_data = pre_create_data.get("tycache_type") + instance_data["tycache_type"] = pre_create_data.get( + "tycache_type") super(CreateTyCache, self).create( subset_name, instance_data, diff --git a/openpype/hosts/max/plugins/publish/extract_tycache.py b/openpype/hosts/max/plugins/publish/extract_tycache.py index bbb6dc115f..242d12bd4c 100644 --- a/openpype/hosts/max/plugins/publish/extract_tycache.py +++ b/openpype/hosts/max/plugins/publish/extract_tycache.py @@ -5,7 +5,6 @@ from pymxs import runtime as rt from openpype.hosts.max.api import maintained_selection from openpype.pipeline import publish -from openpype.lib import EnumDef class ExtractTyCache(publish.Extractor): @@ -61,7 +60,7 @@ class ExtractTyCache(publish.Extractor): "stagingDir": stagingdir } instance.data["representations"].append(representation) - self.log.info(f"Extracted instance '{instance.name}' to: {path}") + self.log.info(f"Extracted instance '{instance.name}' to: {filenames}") def get_file(self, filepath, start_frame, end_frame): filenames = [] diff --git a/openpype/hosts/max/plugins/publish/validate_tyflow_data.py b/openpype/hosts/max/plugins/publish/validate_tyflow_data.py index de8d161b9d..4574950495 100644 --- a/openpype/hosts/max/plugins/publish/validate_tyflow_data.py +++ b/openpype/hosts/max/plugins/publish/validate_tyflow_data.py @@ -3,7 +3,7 @@ from openpype.pipeline import PublishValidationError from pymxs import runtime as rt -class ValidatePointCloud(pyblish.api.InstancePlugin): +class ValidateTyFlowData(pyblish.api.InstancePlugin): """Validate that TyFlow plugins or relevant operators being set correctly.""" From 28a3cf943dec1320b070381982cf44f20cc6fd1e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 15 Sep 2023 22:26:08 +0800 Subject: [PATCH 05/31] finished up the extractor --- .../publish/collect_tycache_attributes.py | 70 +++++++++++++++++++ .../max/plugins/publish/extract_tycache.py | 38 ++++++++-- 2 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 openpype/hosts/max/plugins/publish/collect_tycache_attributes.py diff --git a/openpype/hosts/max/plugins/publish/collect_tycache_attributes.py b/openpype/hosts/max/plugins/publish/collect_tycache_attributes.py new file mode 100644 index 0000000000..e312dd8826 --- /dev/null +++ b/openpype/hosts/max/plugins/publish/collect_tycache_attributes.py @@ -0,0 +1,70 @@ +import pyblish.api + +from openpype.lib import EnumDef +from openpype.pipeline.publish import OpenPypePyblishPluginMixin + + +class CollectTyCacheData(pyblish.api.InstancePlugin, + OpenPypePyblishPluginMixin): + """Collect Review Data for Preview Animation""" + + order = pyblish.api.CollectorOrder + 0.02 + label = "Collect tyCache attribute Data" + hosts = ['max'] + families = ["tycache"] + + def process(self, instance): + all_tyc_attributes_dict = {} + attr_values = self.get_attr_values_from_data(instance.data) + tycache_boolean_attributes = attr_values.get("all_tyc_attrs") + if tycache_boolean_attributes: + for attrs in tycache_boolean_attributes: + all_tyc_attributes_dict[attrs] = True + self.log.debug(f"Found tycache attributes: {tycache_boolean_attributes}") + + @classmethod + def get_attribute_defs(cls): + tyc_attr_enum = ["tycacheChanAge", "tycacheChanGroups", "tycacheChanPos", + "tycacheChanRot", "tycacheChanScale", "tycacheChanVel", + "tycacheChanSpin", "tycacheChanShape", "tycacheChanMatID", + "tycacheChanMapping", "tycacheChanMaterials", + "tycacheChanCustomFloat" + ] + + return [ + EnumDef("all_tyc_attrs", + tyc_attr_enum, + default=None, + multiselection=True + + ) + ] +""" + + .tycacheChanCustomFloat : boolean + .tycacheChanCustomVector : boolean + .tycacheChanCustomTM : boolean + .tycacheChanPhysX : boolean + .tycacheMeshBackup : boolean + .tycacheCreateObject : boolean + .tycacheCreateObjectIfNotCreated : boolean + .tycacheLayer : string + .tycacheObjectName : string + .tycacheAdditionalCloth : boolean + .tycacheAdditionalSkin : boolean + .tycacheAdditionalSkinID : boolean + .tycacheAdditionalSkinIDValue : integer + .tycacheAdditionalTerrain : boolean + .tycacheAdditionalVDB : boolean + .tycacheAdditionalSplinePaths : boolean + .tycacheAdditionalTyMesher : boolean + .tycacheAdditionalGeo : boolean + .tycacheAdditionalObjectList_deprecated : node array + .tycacheAdditionalObjectList : maxObject array + .tycacheAdditionalGeoActivateModifiers : boolean + .tycacheSplines: boolean + .tycacheSplinesAdditionalSplines : boolean + .tycacheSplinesAdditionalSplinesObjectList_deprecated : node array + .tycacheSplinesAdditionalObjectList : maxObject array + +""" diff --git a/openpype/hosts/max/plugins/publish/extract_tycache.py b/openpype/hosts/max/plugins/publish/extract_tycache.py index 242d12bd4c..e98fad5c2b 100644 --- a/openpype/hosts/max/plugins/publish/extract_tycache.py +++ b/openpype/hosts/max/plugins/publish/extract_tycache.py @@ -38,16 +38,20 @@ class ExtractTyCache(publish.Extractor): filename = "{name}.tyc".format(**instance.data) path = os.path.join(stagingdir, filename) filenames = self.get_file(path, start, end) + additional_attributes = instance.data.get("tyc_attrs", {}) + with maintained_selection(): job_args = None if instance.data["tycache_type"] == "tycache": job_args = self.export_particle( instance.data["members"], - start, end, path) + start, end, path, + additional_attributes) elif instance.data["tycache_type"] == "tycachespline": job_args = self.export_particle( instance.data["members"], start, end, path, + additional_attributes, tycache_spline_enabled=True) for job in job_args: @@ -74,7 +78,8 @@ class ExtractTyCache(publish.Extractor): return filenames def export_particle(self, members, start, end, - filepath, tycache_spline_enabled=False): + filepath, additional_attributes, + tycache_spline_enabled=False): """Sets up all job arguments for attributes. Those attributes are to be exported in MAX Script. @@ -94,8 +99,7 @@ class ExtractTyCache(publish.Extractor): for operator in opt_list: if tycache_spline_enabled: export_mode = f'{operator}.exportMode=3' - has_tyc_spline = f'{operator}.tycacheSplines=true' - job_args.extend([export_mode, has_tyc_spline]) + job_args.append(export_mode) else: export_mode = f'{operator}.exportMode=2' job_args.append(export_mode) @@ -107,6 +111,11 @@ class ExtractTyCache(publish.Extractor): tycache_filename = f'{operator}.tyCacheFilename="{filepath}"' job_args.append(tycache_filename) # TODO: add the additional job args for tycache attributes + if additional_attributes: + additional_args = self.get_additional_attribute_args( + operator, additional_attributes + ) + job_args.extend(additional_args) tycache_export = f"{operator}.exportTyCache()" job_args.append(tycache_export) @@ -137,3 +146,24 @@ class ExtractTyCache(publish.Extractor): opt_list.append(opt) return opt_list + + def get_additional_attribute_args(self, operator, attrs): + """Get Additional args with the attributes pre-set by user + + Args: + operator (str): export particle operator + attrs (dict): a dict which stores the additional attributes + added by user + + Returns: + additional_args(list): a list of additional args for MAX script + """ + additional_args = [] + for key, value in attrs.items(): + tyc_attribute = None + if isinstance(value, bool): + tyc_attribute = f"{operator}.{key}=True" + elif isinstance(value, str): + tyc_attribute = f"{operator}.{key}={value}" + additional_args.append(tyc_attribute) + return additional_args From 8e25677aa73e6034e8254c99921fac4e9d9a303f Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 18 Sep 2023 15:48:47 +0800 Subject: [PATCH 06/31] finish the tycache attributes collector --- .../publish/collect_tycache_attributes.py | 81 ++++++++++--------- 1 file changed, 42 insertions(+), 39 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/collect_tycache_attributes.py b/openpype/hosts/max/plugins/publish/collect_tycache_attributes.py index e312dd8826..122b0d6451 100644 --- a/openpype/hosts/max/plugins/publish/collect_tycache_attributes.py +++ b/openpype/hosts/max/plugins/publish/collect_tycache_attributes.py @@ -1,6 +1,6 @@ import pyblish.api -from openpype.lib import EnumDef +from openpype.lib import EnumDef, TextDef from openpype.pipeline.publish import OpenPypePyblishPluginMixin @@ -20,51 +20,54 @@ class CollectTyCacheData(pyblish.api.InstancePlugin, if tycache_boolean_attributes: for attrs in tycache_boolean_attributes: all_tyc_attributes_dict[attrs] = True - self.log.debug(f"Found tycache attributes: {tycache_boolean_attributes}") + tyc_layer_attr = attr_values.get("tycache_layer") + if tyc_layer_attr: + all_tyc_attributes_dict["tycacheLayer"] = ( + tyc_layer_attr) + tyc_objname_attr = attr_values.get("tycache_objname") + if tyc_objname_attr: + all_tyc_attributes_dict["tycache_objname"] = ( + tyc_objname_attr) + self.log.debug( + f"Found tycache attributes: {all_tyc_attributes_dict}") @classmethod def get_attribute_defs(cls): - tyc_attr_enum = ["tycacheChanAge", "tycacheChanGroups", "tycacheChanPos", - "tycacheChanRot", "tycacheChanScale", "tycacheChanVel", - "tycacheChanSpin", "tycacheChanShape", "tycacheChanMatID", - "tycacheChanMapping", "tycacheChanMaterials", - "tycacheChanCustomFloat" + # TODO: Support the attributes with maxObject array + tyc_attr_enum = ["tycacheChanAge", "tycacheChanGroups", + "tycacheChanPos", "tycacheChanRot", + "tycacheChanScale", "tycacheChanVel", + "tycacheChanSpin", "tycacheChanShape", + "tycacheChanMatID", "tycacheChanMapping", + "tycacheChanMaterials", "tycacheChanCustomFloat" + "tycacheChanCustomVector", "tycacheChanCustomTM", + "tycacheChanPhysX", "tycacheMeshBackup", + "tycacheCreateObjectIfNotCreated", + "tycacheAdditionalCloth", + "tycacheAdditionalSkin", + "tycacheAdditionalSkinID", + "tycacheAdditionalSkinIDValue", + "tycacheAdditionalTerrain", + "tycacheAdditionalVDB", + "tycacheAdditionalSplinePaths", + "tycacheAdditionalGeo", + "tycacheAdditionalGeoActivateModifiers", + "tycacheSplines", + "tycacheSplinesAdditionalSplines" ] return [ EnumDef("all_tyc_attrs", tyc_attr_enum, default=None, - multiselection=True - - ) + multiselection=True, + label="TyCache Attributes"), + TextDef("tycache_layer", + label="TyCache Layer", + tooltip="Name of tycache layer", + default=""), + TextDef("tycache_objname", + label="TyCache Object Name", + tooltip="TyCache Object Name", + default="") ] -""" - - .tycacheChanCustomFloat : boolean - .tycacheChanCustomVector : boolean - .tycacheChanCustomTM : boolean - .tycacheChanPhysX : boolean - .tycacheMeshBackup : boolean - .tycacheCreateObject : boolean - .tycacheCreateObjectIfNotCreated : boolean - .tycacheLayer : string - .tycacheObjectName : string - .tycacheAdditionalCloth : boolean - .tycacheAdditionalSkin : boolean - .tycacheAdditionalSkinID : boolean - .tycacheAdditionalSkinIDValue : integer - .tycacheAdditionalTerrain : boolean - .tycacheAdditionalVDB : boolean - .tycacheAdditionalSplinePaths : boolean - .tycacheAdditionalTyMesher : boolean - .tycacheAdditionalGeo : boolean - .tycacheAdditionalObjectList_deprecated : node array - .tycacheAdditionalObjectList : maxObject array - .tycacheAdditionalGeoActivateModifiers : boolean - .tycacheSplines: boolean - .tycacheSplinesAdditionalSplines : boolean - .tycacheSplinesAdditionalSplinesObjectList_deprecated : node array - .tycacheSplinesAdditionalObjectList : maxObject array - -""" From fd6c6f3b3cbd6c4d820f1725c117ef43e9cde89d Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 25 Sep 2023 21:59:03 +0800 Subject: [PATCH 07/31] add docstrings for the functions in tycache families --- .../max/plugins/publish/extract_tycache.py | 19 ++++++++++++++++++ .../plugins/publish/validate_tyflow_data.py | 20 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/openpype/hosts/max/plugins/publish/extract_tycache.py b/openpype/hosts/max/plugins/publish/extract_tycache.py index e98fad5c2b..c3e9489d43 100644 --- a/openpype/hosts/max/plugins/publish/extract_tycache.py +++ b/openpype/hosts/max/plugins/publish/extract_tycache.py @@ -67,6 +67,25 @@ class ExtractTyCache(publish.Extractor): self.log.info(f"Extracted instance '{instance.name}' to: {filenames}") def get_file(self, filepath, start_frame, end_frame): + """Get file names for tyFlow in tyCache format. + + Set the filenames accordingly to the tyCache file + naming extension(.tyc) for the publishing purpose + + Actual File Output from tyFlow in tyCache format: + _.tyc + + e.g. tyFlow_cloth_CCCS_blobbyFill_001_00004.tyc + + Args: + fileapth (str): Output directory. + start_frame (int): Start frame. + end_frame (int): End frame. + + Returns: + filenames(list): list of filenames + + """ filenames = [] filename = os.path.basename(filepath) orig_name, _ = os.path.splitext(filename) diff --git a/openpype/hosts/max/plugins/publish/validate_tyflow_data.py b/openpype/hosts/max/plugins/publish/validate_tyflow_data.py index 4574950495..c0a6d23022 100644 --- a/openpype/hosts/max/plugins/publish/validate_tyflow_data.py +++ b/openpype/hosts/max/plugins/publish/validate_tyflow_data.py @@ -34,6 +34,16 @@ class ValidateTyFlowData(pyblish.api.InstancePlugin): raise PublishValidationError(f"{report}") def get_tyflow_object(self, instance): + """Get the nodes which are not tyFlow object(s) + and editable mesh(es) + + Args: + instance (str): instance node + + Returns: + invalid(list): list of invalid nodes which are not + tyFlow object(s) and editable mesh(es). + """ invalid = [] container = instance.data["instance_node"] self.log.info(f"Validating tyFlow container for {container}") @@ -51,6 +61,16 @@ class ValidateTyFlowData(pyblish.api.InstancePlugin): return invalid def get_tyflow_operator(self, instance): + """_summary_ + + Args: + instance (str): instance node + + Returns: + invalid(list): list of invalid nodes which do + not consist of Export Particle Operators as parts + of the node connections + """ invalid = [] container = instance.data["instance_node"] self.log.info(f"Validating tyFlow object for {container}") From ebaaa448086a7fefc7214813884d356b942947cc Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 29 Sep 2023 15:44:20 +0800 Subject: [PATCH 08/31] make sure tycache output filenames in representation data are correct --- .../max/plugins/publish/extract_tycache.py | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/extract_tycache.py b/openpype/hosts/max/plugins/publish/extract_tycache.py index c3e9489d43..409ade8f76 100644 --- a/openpype/hosts/max/plugins/publish/extract_tycache.py +++ b/openpype/hosts/max/plugins/publish/extract_tycache.py @@ -37,7 +37,8 @@ class ExtractTyCache(publish.Extractor): stagingdir = self.staging_dir(instance) filename = "{name}.tyc".format(**instance.data) path = os.path.join(stagingdir, filename) - filenames = self.get_file(path, start, end) + filenames = self.get_file(instance, start, end) + self.log.debug(f"filenames: {filenames}") additional_attributes = instance.data.get("tyc_attrs", {}) with maintained_selection(): @@ -66,19 +67,20 @@ class ExtractTyCache(publish.Extractor): instance.data["representations"].append(representation) self.log.info(f"Extracted instance '{instance.name}' to: {filenames}") - def get_file(self, filepath, start_frame, end_frame): + def get_file(self, instance, start_frame, end_frame): """Get file names for tyFlow in tyCache format. Set the filenames accordingly to the tyCache file naming extension(.tyc) for the publishing purpose Actual File Output from tyFlow in tyCache format: - _.tyc + __tyPart_.tyc + __tyMesh.tyc - e.g. tyFlow_cloth_CCCS_blobbyFill_001_00004.tyc + e.g. tycacheMain__tyPart_00000.tyc Args: - fileapth (str): Output directory. + instance (str): instance. start_frame (int): Start frame. end_frame (int): End frame. @@ -87,13 +89,11 @@ class ExtractTyCache(publish.Extractor): """ filenames = [] - filename = os.path.basename(filepath) - orig_name, _ = os.path.splitext(filename) + # should we include frame 0 ? for frame in range(int(start_frame), int(end_frame) + 1): - actual_name = "{}_{:05}".format(orig_name, frame) - actual_filename = filepath.replace(orig_name, actual_name) - filenames.append(os.path.basename(actual_filename)) - + filename = "{}__tyPart_{:05}.tyc".format(instance.name, frame) + filenames.append(filename) + filenames.append("{}__tyMesh.tyc".format(instance.name)) return filenames def export_particle(self, members, start, end, From be353bd4395dce0450dbdd4ad6811089a1cc8b34 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 29 Sep 2023 15:47:04 +0800 Subject: [PATCH 09/31] remove unnecessary debug check --- openpype/hosts/max/plugins/publish/extract_tycache.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/max/plugins/publish/extract_tycache.py b/openpype/hosts/max/plugins/publish/extract_tycache.py index 409ade8f76..5fa8642809 100644 --- a/openpype/hosts/max/plugins/publish/extract_tycache.py +++ b/openpype/hosts/max/plugins/publish/extract_tycache.py @@ -38,7 +38,6 @@ class ExtractTyCache(publish.Extractor): filename = "{name}.tyc".format(**instance.data) path = os.path.join(stagingdir, filename) filenames = self.get_file(instance, start, end) - self.log.debug(f"filenames: {filenames}") additional_attributes = instance.data.get("tyc_attrs", {}) with maintained_selection(): From be09038e222f833a2eb3290006f08d02ea88d9a3 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 3 Oct 2023 14:54:54 +0800 Subject: [PATCH 10/31] separating tyMesh and tyType into two representations --- .../hosts/max/plugins/publish/extract_tycache.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/extract_tycache.py b/openpype/hosts/max/plugins/publish/extract_tycache.py index 5fa8642809..f4a5e0f4a6 100644 --- a/openpype/hosts/max/plugins/publish/extract_tycache.py +++ b/openpype/hosts/max/plugins/publish/extract_tycache.py @@ -66,6 +66,17 @@ class ExtractTyCache(publish.Extractor): instance.data["representations"].append(representation) self.log.info(f"Extracted instance '{instance.name}' to: {filenames}") + # Get the tyMesh filename for extraction + mesh_filename = "{}__tyMesh.tyc".format(instance.name) + mesh_repres = { + 'name': 'tyMesh', + 'ext': 'tyc', + 'files': mesh_filename, + "stagingDir": stagingdir + } + instance.data["representations"].append(mesh_repres) + self.log.info(f"Extracted instance '{instance.name}' to: {mesh_filename}") + def get_file(self, instance, start_frame, end_frame): """Get file names for tyFlow in tyCache format. @@ -74,7 +85,6 @@ class ExtractTyCache(publish.Extractor): Actual File Output from tyFlow in tyCache format: __tyPart_.tyc - __tyMesh.tyc e.g. tycacheMain__tyPart_00000.tyc @@ -92,7 +102,6 @@ class ExtractTyCache(publish.Extractor): for frame in range(int(start_frame), int(end_frame) + 1): filename = "{}__tyPart_{:05}.tyc".format(instance.name, frame) filenames.append(filename) - filenames.append("{}__tyMesh.tyc".format(instance.name)) return filenames def export_particle(self, members, start, end, From 74a6d33baa3061363df4795c44c0547af2fb505d Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 3 Oct 2023 14:55:54 +0800 Subject: [PATCH 11/31] hound --- openpype/hosts/max/plugins/publish/extract_tycache.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/publish/extract_tycache.py b/openpype/hosts/max/plugins/publish/extract_tycache.py index f4a5e0f4a6..56fd39406e 100644 --- a/openpype/hosts/max/plugins/publish/extract_tycache.py +++ b/openpype/hosts/max/plugins/publish/extract_tycache.py @@ -75,7 +75,8 @@ class ExtractTyCache(publish.Extractor): "stagingDir": stagingdir } instance.data["representations"].append(mesh_repres) - self.log.info(f"Extracted instance '{instance.name}' to: {mesh_filename}") + self.log.info( + f"Extracted instance '{instance.name}' to: {mesh_filename}") def get_file(self, instance, start_frame, end_frame): """Get file names for tyFlow in tyCache format. From fa11a2bfdcddb6b085641f1c3c078ee34c5405aa Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 5 Oct 2023 19:17:25 +0800 Subject: [PATCH 12/31] small tweaks on loader and extractor & use raise PublishValidationError for each instead of using reports append list of error. --- openpype/hosts/max/plugins/load/load_tycache.py | 3 +-- .../hosts/max/plugins/publish/extract_tycache.py | 13 ++++++------- .../max/plugins/publish/validate_tyflow_data.py | 16 ++++++++-------- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/openpype/hosts/max/plugins/load/load_tycache.py b/openpype/hosts/max/plugins/load/load_tycache.py index 657e743087..7eac0de3e5 100644 --- a/openpype/hosts/max/plugins/load/load_tycache.py +++ b/openpype/hosts/max/plugins/load/load_tycache.py @@ -49,8 +49,7 @@ class PointCloudLoader(load.LoaderPlugin): update_custom_attribute_data( node, node_list) with maintained_selection(): - rt.Select(node_list) - for prt in rt.Selection: + for prt in node_list: prt.filename = path lib.imprint(container["instance_node"], { "representation": str(representation["_id"]) diff --git a/openpype/hosts/max/plugins/publish/extract_tycache.py b/openpype/hosts/max/plugins/publish/extract_tycache.py index 56fd39406e..0327564b3a 100644 --- a/openpype/hosts/max/plugins/publish/extract_tycache.py +++ b/openpype/hosts/max/plugins/publish/extract_tycache.py @@ -42,18 +42,17 @@ class ExtractTyCache(publish.Extractor): with maintained_selection(): job_args = None + has_tyc_spline = ( + True + if instance.data["tycache_type"] == "tycachespline" + else False + ) if instance.data["tycache_type"] == "tycache": - job_args = self.export_particle( - instance.data["members"], - start, end, path, - additional_attributes) - elif instance.data["tycache_type"] == "tycachespline": job_args = self.export_particle( instance.data["members"], start, end, path, additional_attributes, - tycache_spline_enabled=True) - + tycache_spline_enabled=has_tyc_spline) for job in job_args: rt.Execute(job) diff --git a/openpype/hosts/max/plugins/publish/validate_tyflow_data.py b/openpype/hosts/max/plugins/publish/validate_tyflow_data.py index c0a6d23022..59dafef901 100644 --- a/openpype/hosts/max/plugins/publish/validate_tyflow_data.py +++ b/openpype/hosts/max/plugins/publish/validate_tyflow_data.py @@ -23,15 +23,14 @@ class ValidateTyFlowData(pyblish.api.InstancePlugin): invalid_object = self.get_tyflow_object(instance) if invalid_object: - report.append(f"Non tyFlow object found: {invalid_object}") + raise PublishValidationError( + f"Non tyFlow object found: {invalid_object}") invalid_operator = self.get_tyflow_operator(instance) if invalid_operator: - report.append(( + raise PublishValidationError(( "tyFlow ExportParticle operator not " f"found: {invalid_operator}")) - if report: - raise PublishValidationError(f"{report}") def get_tyflow_object(self, instance): """Get the nodes which are not tyFlow object(s) @@ -46,7 +45,7 @@ class ValidateTyFlowData(pyblish.api.InstancePlugin): """ invalid = [] container = instance.data["instance_node"] - self.log.info(f"Validating tyFlow container for {container}") + self.log.debug(f"Validating tyFlow container for {container}") selection_list = instance.data["members"] for sel in selection_list: @@ -61,7 +60,8 @@ class ValidateTyFlowData(pyblish.api.InstancePlugin): return invalid def get_tyflow_operator(self, instance): - """_summary_ + """Check if the Export Particle Operators in the node + connections. Args: instance (str): instance node @@ -73,7 +73,7 @@ class ValidateTyFlowData(pyblish.api.InstancePlugin): """ invalid = [] container = instance.data["instance_node"] - self.log.info(f"Validating tyFlow object for {container}") + self.log.debug(f"Validating tyFlow object for {container}") selection_list = instance.data["members"] bool_list = [] for sel in selection_list: @@ -88,7 +88,7 @@ class ValidateTyFlowData(pyblish.api.InstancePlugin): # if the export_particles property is not there # it means there is not a "Export Particle" operator if "True" not in bool_list: - self.log.error("Operator 'Export Particles' not found!") + self.log.error("Operator 'Export Particles' not found.") invalid.append(sel) return invalid From 291fd65b0969f07ce2767971ba5a095233c682fa Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 5 Oct 2023 19:20:14 +0800 Subject: [PATCH 13/31] hound --- openpype/hosts/max/plugins/publish/validate_tyflow_data.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/validate_tyflow_data.py b/openpype/hosts/max/plugins/publish/validate_tyflow_data.py index 59dafef901..dc2de55e4f 100644 --- a/openpype/hosts/max/plugins/publish/validate_tyflow_data.py +++ b/openpype/hosts/max/plugins/publish/validate_tyflow_data.py @@ -19,12 +19,10 @@ class ValidateTyFlowData(pyblish.api.InstancePlugin): 2. Validate if tyFlow operator Export Particle exists """ - report = [] - invalid_object = self.get_tyflow_object(instance) if invalid_object: - raise PublishValidationError( - f"Non tyFlow object found: {invalid_object}") + raise PublishValidationError( + f"Non tyFlow object found: {invalid_object}") invalid_operator = self.get_tyflow_operator(instance) if invalid_operator: From ad252347c0bf59db86ee46a19525d254837c092b Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 5 Oct 2023 20:27:22 +0800 Subject: [PATCH 14/31] improve the validation report --- .../max/plugins/publish/validate_tyflow_data.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/validate_tyflow_data.py b/openpype/hosts/max/plugins/publish/validate_tyflow_data.py index dc2de55e4f..bcdc9c6dfe 100644 --- a/openpype/hosts/max/plugins/publish/validate_tyflow_data.py +++ b/openpype/hosts/max/plugins/publish/validate_tyflow_data.py @@ -19,16 +19,20 @@ class ValidateTyFlowData(pyblish.api.InstancePlugin): 2. Validate if tyFlow operator Export Particle exists """ + report = [] + invalid_object = self.get_tyflow_object(instance) - if invalid_object: - raise PublishValidationError( - f"Non tyFlow object found: {invalid_object}") + self.log.error(f"Non tyFlow object found: {invalid_object}") invalid_operator = self.get_tyflow_operator(instance) if invalid_operator: - raise PublishValidationError(( - "tyFlow ExportParticle operator not " - f"found: {invalid_operator}")) + self.log.error( + "Operator 'Export Particles' not found in tyFlow editor.") + if report: + raise PublishValidationError( + "issues occurred", + description="Container should only include tyFlow object\n " + "and tyflow operator Export Particle should be in the tyFlow editor") def get_tyflow_object(self, instance): """Get the nodes which are not tyFlow object(s) @@ -86,7 +90,6 @@ class ValidateTyFlowData(pyblish.api.InstancePlugin): # if the export_particles property is not there # it means there is not a "Export Particle" operator if "True" not in bool_list: - self.log.error("Operator 'Export Particles' not found.") invalid.append(sel) return invalid From e364f4e74c86e3e5e6779617ca6fb5a835b18e3b Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 5 Oct 2023 20:41:44 +0800 Subject: [PATCH 15/31] hound --- openpype/hosts/max/plugins/publish/validate_tyflow_data.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/validate_tyflow_data.py b/openpype/hosts/max/plugins/publish/validate_tyflow_data.py index bcdc9c6dfe..a359100e6e 100644 --- a/openpype/hosts/max/plugins/publish/validate_tyflow_data.py +++ b/openpype/hosts/max/plugins/publish/validate_tyflow_data.py @@ -19,20 +19,21 @@ class ValidateTyFlowData(pyblish.api.InstancePlugin): 2. Validate if tyFlow operator Export Particle exists """ - report = [] invalid_object = self.get_tyflow_object(instance) + if invalid_object: self.log.error(f"Non tyFlow object found: {invalid_object}") invalid_operator = self.get_tyflow_operator(instance) if invalid_operator: self.log.error( "Operator 'Export Particles' not found in tyFlow editor.") - if report: + if invalid_object or invalid_operator: raise PublishValidationError( "issues occurred", description="Container should only include tyFlow object\n " - "and tyflow operator Export Particle should be in the tyFlow editor") + "and tyflow operator 'Export Particle' should be in \n" + "the tyFlow editor") def get_tyflow_object(self, instance): """Get the nodes which are not tyFlow object(s) From 02e1bfd22ba501f28efa2dfff4a94c87a17630c7 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 9 Oct 2023 18:20:34 +0800 Subject: [PATCH 16/31] add default channel data for tycache export --- .../max/plugins/publish/collect_tycache_attributes.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/collect_tycache_attributes.py b/openpype/hosts/max/plugins/publish/collect_tycache_attributes.py index 122b0d6451..d735b2f2c0 100644 --- a/openpype/hosts/max/plugins/publish/collect_tycache_attributes.py +++ b/openpype/hosts/max/plugins/publish/collect_tycache_attributes.py @@ -55,11 +55,15 @@ class CollectTyCacheData(pyblish.api.InstancePlugin, "tycacheSplines", "tycacheSplinesAdditionalSplines" ] - + tyc_default_attrs = ["tycacheChanGroups", "tycacheChanPos", + "tycacheChanRot", "tycacheChanScale", + "tycacheChanVel", "tycacheChanShape", + "tycacheChanMatID", "tycacheChanMapping", + "tycacheChanMaterials"] return [ EnumDef("all_tyc_attrs", tyc_attr_enum, - default=None, + default=tyc_default_attrs, multiselection=True, label="TyCache Attributes"), TextDef("tycache_layer", From 7431f6e9ef68b95ad0dc6c7ac0e9b1c1656672ce Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 16 Oct 2023 16:06:58 +0800 Subject: [PATCH 17/31] make sure original basename is used during publishing as the tyc export needs very strict naming --- openpype/hosts/max/plugins/load/load_tycache.py | 2 +- .../plugins/publish/collect_tycache_attributes.py | 4 +++- .../hosts/max/plugins/publish/extract_tycache.py | 15 ++++++++------- .../defaults/project_anatomy/templates.json | 6 ++++++ .../defaults/project_settings/global.json | 11 +++++++++++ server_addon/core/server/settings/tools.py | 11 +++++++++++ server_addon/core/server/version.py | 2 +- 7 files changed, 41 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/max/plugins/load/load_tycache.py b/openpype/hosts/max/plugins/load/load_tycache.py index 7eac0de3e5..ff3a26fbd6 100644 --- a/openpype/hosts/max/plugins/load/load_tycache.py +++ b/openpype/hosts/max/plugins/load/load_tycache.py @@ -13,7 +13,7 @@ from openpype.hosts.max.api.pipeline import ( from openpype.pipeline import get_representation_path, load -class PointCloudLoader(load.LoaderPlugin): +class TyCacheLoader(load.LoaderPlugin): """Point Cloud Loader.""" families = ["tycache"] diff --git a/openpype/hosts/max/plugins/publish/collect_tycache_attributes.py b/openpype/hosts/max/plugins/publish/collect_tycache_attributes.py index d735b2f2c0..56cf6614e2 100644 --- a/openpype/hosts/max/plugins/publish/collect_tycache_attributes.py +++ b/openpype/hosts/max/plugins/publish/collect_tycache_attributes.py @@ -42,6 +42,7 @@ class CollectTyCacheData(pyblish.api.InstancePlugin, "tycacheChanMaterials", "tycacheChanCustomFloat" "tycacheChanCustomVector", "tycacheChanCustomTM", "tycacheChanPhysX", "tycacheMeshBackup", + "tycacheCreateObject", "tycacheCreateObjectIfNotCreated", "tycacheAdditionalCloth", "tycacheAdditionalSkin", @@ -59,7 +60,8 @@ class CollectTyCacheData(pyblish.api.InstancePlugin, "tycacheChanRot", "tycacheChanScale", "tycacheChanVel", "tycacheChanShape", "tycacheChanMatID", "tycacheChanMapping", - "tycacheChanMaterials"] + "tycacheChanMaterials", "tycacheCreateObject", + "tycacheCreateObjectIfNotCreated"] return [ EnumDef("all_tyc_attrs", tyc_attr_enum, diff --git a/openpype/hosts/max/plugins/publish/extract_tycache.py b/openpype/hosts/max/plugins/publish/extract_tycache.py index 0327564b3a..a787080776 100644 --- a/openpype/hosts/max/plugins/publish/extract_tycache.py +++ b/openpype/hosts/max/plugins/publish/extract_tycache.py @@ -37,7 +37,7 @@ class ExtractTyCache(publish.Extractor): stagingdir = self.staging_dir(instance) filename = "{name}.tyc".format(**instance.data) path = os.path.join(stagingdir, filename) - filenames = self.get_file(instance, start, end) + filenames = self.get_files(instance, start, end) additional_attributes = instance.data.get("tyc_attrs", {}) with maintained_selection(): @@ -55,14 +55,14 @@ class ExtractTyCache(publish.Extractor): tycache_spline_enabled=has_tyc_spline) for job in job_args: rt.Execute(job) - + representations = instance.data.setdefault("representations", []) representation = { 'name': 'tyc', 'ext': 'tyc', 'files': filenames if len(filenames) > 1 else filenames[0], - "stagingDir": stagingdir + "stagingDir": stagingdir, } - instance.data["representations"].append(representation) + representations.append(representation) self.log.info(f"Extracted instance '{instance.name}' to: {filenames}") # Get the tyMesh filename for extraction @@ -71,13 +71,14 @@ class ExtractTyCache(publish.Extractor): 'name': 'tyMesh', 'ext': 'tyc', 'files': mesh_filename, - "stagingDir": stagingdir + "stagingDir": stagingdir, + "outputName": '__tyMesh' } - instance.data["representations"].append(mesh_repres) + representations.append(mesh_repres) self.log.info( f"Extracted instance '{instance.name}' to: {mesh_filename}") - def get_file(self, instance, start_frame, end_frame): + def get_files(self, instance, start_frame, end_frame): """Get file names for tyFlow in tyCache format. Set the filenames accordingly to the tyCache file diff --git a/openpype/settings/defaults/project_anatomy/templates.json b/openpype/settings/defaults/project_anatomy/templates.json index e5e535bf19..aa3f8d4843 100644 --- a/openpype/settings/defaults/project_anatomy/templates.json +++ b/openpype/settings/defaults/project_anatomy/templates.json @@ -53,6 +53,11 @@ "file": "{originalBasename}<.{@frame}><_{udim}>.{ext}", "path": "{@folder}/{@file}" }, + "tycache": { + "folder": "{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/{@version}", + "file": "{originalBasename}<_{@version}><_{@frame}>.{ext}", + "path": "{@folder}/{@file}" + }, "source": { "folder": "{root[work]}/{originalDirname}", "file": "{originalBasename}.{ext}", @@ -66,6 +71,7 @@ "simpleUnrealTextureHero": "Simple Unreal Texture - Hero", "simpleUnrealTexture": "Simple Unreal Texture", "online": "online", + "tycache": "tycache", "source": "source", "transient": "transient" } diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 06a595d1c5..9ccf5cae05 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -546,6 +546,17 @@ "task_types": [], "task_names": [], "template_name": "online" + }, + { + "families": [ + "tycache" + ], + "hosts": [ + "max" + ], + "task_types": [], + "task_names": [], + "template_name": "tycache" } ], "hero_template_name_profiles": [ diff --git a/server_addon/core/server/settings/tools.py b/server_addon/core/server/settings/tools.py index 7befc795e4..d7c7b367b7 100644 --- a/server_addon/core/server/settings/tools.py +++ b/server_addon/core/server/settings/tools.py @@ -487,6 +487,17 @@ DEFAULT_TOOLS_VALUES = { "task_types": [], "task_names": [], "template_name": "publish_online" + }, + { + "families": [ + "tycache" + ], + "hosts": [ + "max" + ], + "task_types": [], + "task_names": [], + "template_name": "publish_tycache" } ], "hero_template_name_profiles": [ diff --git a/server_addon/core/server/version.py b/server_addon/core/server/version.py index b3f4756216..ae7362549b 100644 --- a/server_addon/core/server/version.py +++ b/server_addon/core/server/version.py @@ -1 +1 @@ -__version__ = "0.1.2" +__version__ = "0.1.3" From 4a0f87c5179959e6375ace26c0307ff7c29c0e9e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 16 Oct 2023 16:09:08 +0800 Subject: [PATCH 18/31] updated the file naming convention --- openpype/settings/defaults/project_anatomy/templates.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_anatomy/templates.json b/openpype/settings/defaults/project_anatomy/templates.json index aa3f8d4843..5694693c97 100644 --- a/openpype/settings/defaults/project_anatomy/templates.json +++ b/openpype/settings/defaults/project_anatomy/templates.json @@ -55,7 +55,7 @@ }, "tycache": { "folder": "{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/{@version}", - "file": "{originalBasename}<_{@version}><_{@frame}>.{ext}", + "file": "{originalBasename}<_{@frame}>.{ext}", "path": "{@folder}/{@file}" }, "source": { From 3461cbed58efeb3c901e66b7c677002543021f03 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 18 Oct 2023 12:17:58 +0800 Subject: [PATCH 19/31] use originalbasename entirely for the publishing tycache --- .../hosts/max/plugins/publish/collect_tycache_attributes.py | 2 +- openpype/settings/defaults/project_anatomy/templates.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/collect_tycache_attributes.py b/openpype/hosts/max/plugins/publish/collect_tycache_attributes.py index 56cf6614e2..fa27a9a9d6 100644 --- a/openpype/hosts/max/plugins/publish/collect_tycache_attributes.py +++ b/openpype/hosts/max/plugins/publish/collect_tycache_attributes.py @@ -60,7 +60,7 @@ class CollectTyCacheData(pyblish.api.InstancePlugin, "tycacheChanRot", "tycacheChanScale", "tycacheChanVel", "tycacheChanShape", "tycacheChanMatID", "tycacheChanMapping", - "tycacheChanMaterials", "tycacheCreateObject", + "tycacheChanMaterials", "tycacheCreateObjectIfNotCreated"] return [ EnumDef("all_tyc_attrs", diff --git a/openpype/settings/defaults/project_anatomy/templates.json b/openpype/settings/defaults/project_anatomy/templates.json index 5694693c97..5766a09100 100644 --- a/openpype/settings/defaults/project_anatomy/templates.json +++ b/openpype/settings/defaults/project_anatomy/templates.json @@ -55,7 +55,7 @@ }, "tycache": { "folder": "{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/{@version}", - "file": "{originalBasename}<_{@frame}>.{ext}", + "file": "{originalBasename}.{ext}", "path": "{@folder}/{@file}" }, "source": { From aee1ac1be9690a0bcf4eb4ea62a35342edd90dc8 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 19 Oct 2023 13:39:14 +0800 Subject: [PATCH 20/31] clean up the code in collector and extractor --- .../max/plugins/create/create_tycache.py | 22 ---------- .../publish/collect_tycache_attributes.py | 35 ++++++++-------- .../max/plugins/publish/extract_tycache.py | 41 ++++++++----------- .../plugins/publish/validate_tyflow_data.py | 2 +- 4 files changed, 33 insertions(+), 67 deletions(-) diff --git a/openpype/hosts/max/plugins/create/create_tycache.py b/openpype/hosts/max/plugins/create/create_tycache.py index c48094028a..92d12e012f 100644 --- a/openpype/hosts/max/plugins/create/create_tycache.py +++ b/openpype/hosts/max/plugins/create/create_tycache.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- """Creator plugin for creating TyCache.""" from openpype.hosts.max.api import plugin -from openpype.lib import EnumDef class CreateTyCache(plugin.MaxCreator): @@ -10,24 +9,3 @@ class CreateTyCache(plugin.MaxCreator): label = "TyCache" family = "tycache" icon = "gear" - - def create(self, subset_name, instance_data, pre_create_data): - instance_data["tycache_type"] = pre_create_data.get( - "tycache_type") - super(CreateTyCache, self).create( - subset_name, - instance_data, - pre_create_data) - - def get_pre_create_attr_defs(self): - attrs = super(CreateTyCache, self).get_pre_create_attr_defs() - - tycache_format_enum = ["tycache", "tycachespline"] - - return attrs + [ - - EnumDef("tycache_type", - tycache_format_enum, - default="tycache", - label="TyCache Type") - ] diff --git a/openpype/hosts/max/plugins/publish/collect_tycache_attributes.py b/openpype/hosts/max/plugins/publish/collect_tycache_attributes.py index fa27a9a9d6..779b4c1b7e 100644 --- a/openpype/hosts/max/plugins/publish/collect_tycache_attributes.py +++ b/openpype/hosts/max/plugins/publish/collect_tycache_attributes.py @@ -14,22 +14,19 @@ class CollectTyCacheData(pyblish.api.InstancePlugin, families = ["tycache"] def process(self, instance): - all_tyc_attributes_dict = {} attr_values = self.get_attr_values_from_data(instance.data) - tycache_boolean_attributes = attr_values.get("all_tyc_attrs") - if tycache_boolean_attributes: - for attrs in tycache_boolean_attributes: - all_tyc_attributes_dict[attrs] = True - tyc_layer_attr = attr_values.get("tycache_layer") - if tyc_layer_attr: - all_tyc_attributes_dict["tycacheLayer"] = ( - tyc_layer_attr) - tyc_objname_attr = attr_values.get("tycache_objname") - if tyc_objname_attr: - all_tyc_attributes_dict["tycache_objname"] = ( - tyc_objname_attr) + attributes = {} + for attr_key in attr_values.get("tycacheAttributes", []): + attributes[attr_key] = True + + for key in ["tycacheLayer", "tycacheObjectName"]: + attributes[key] = attr_values.get(key, "") + + # Collect the selected channel data before exporting + instance.data["tyc_attrs"] = attributes self.log.debug( - f"Found tycache attributes: {all_tyc_attributes_dict}") + f"Found tycache attributes: {attributes}" + ) @classmethod def get_attribute_defs(cls): @@ -63,17 +60,17 @@ class CollectTyCacheData(pyblish.api.InstancePlugin, "tycacheChanMaterials", "tycacheCreateObjectIfNotCreated"] return [ - EnumDef("all_tyc_attrs", + EnumDef("tycacheAttributes", tyc_attr_enum, default=tyc_default_attrs, multiselection=True, label="TyCache Attributes"), - TextDef("tycache_layer", + TextDef("tycacheLayer", label="TyCache Layer", tooltip="Name of tycache layer", - default=""), - TextDef("tycache_objname", + default="$(tyFlowLayer)"), + TextDef("tycacheObjectName", label="TyCache Object Name", tooltip="TyCache Object Name", - default="") + default="$(tyFlowName)_tyCache") ] diff --git a/openpype/hosts/max/plugins/publish/extract_tycache.py b/openpype/hosts/max/plugins/publish/extract_tycache.py index a787080776..9262219b7a 100644 --- a/openpype/hosts/max/plugins/publish/extract_tycache.py +++ b/openpype/hosts/max/plugins/publish/extract_tycache.py @@ -13,9 +13,9 @@ class ExtractTyCache(publish.Extractor): Notes: - TyCache only works for TyFlow Pro Plugin. - Args: - self.export_particle(): sets up all job arguments for attributes - to be exported in MAXscript + Methods: + self.get_export_particles_job_args(): sets up all job arguments + for attributes to be exported in MAXscript self.get_operators(): get the export_particle operator @@ -30,7 +30,7 @@ class ExtractTyCache(publish.Extractor): def process(self, instance): # TODO: let user decide the param - start = int(instance.context.data.get("frameStart")) + start = int(instance.context.data["frameStart"]) end = int(instance.context.data.get("frameEnd")) self.log.info("Extracting Tycache...") @@ -42,17 +42,11 @@ class ExtractTyCache(publish.Extractor): with maintained_selection(): job_args = None - has_tyc_spline = ( - True - if instance.data["tycache_type"] == "tycachespline" - else False - ) if instance.data["tycache_type"] == "tycache": - job_args = self.export_particle( + job_args = self.get_export_particles_job_args( instance.data["members"], start, end, path, - additional_attributes, - tycache_spline_enabled=has_tyc_spline) + additional_attributes) for job in job_args: rt.Execute(job) representations = instance.data.setdefault("representations", []) @@ -66,7 +60,7 @@ class ExtractTyCache(publish.Extractor): self.log.info(f"Extracted instance '{instance.name}' to: {filenames}") # Get the tyMesh filename for extraction - mesh_filename = "{}__tyMesh.tyc".format(instance.name) + mesh_filename = f"{instance.name}__tyMesh.tyc" mesh_repres = { 'name': 'tyMesh', 'ext': 'tyc', @@ -90,7 +84,7 @@ class ExtractTyCache(publish.Extractor): e.g. tycacheMain__tyPart_00000.tyc Args: - instance (str): instance. + instance (pyblish.api.Instance): instance. start_frame (int): Start frame. end_frame (int): End frame. @@ -101,13 +95,12 @@ class ExtractTyCache(publish.Extractor): filenames = [] # should we include frame 0 ? for frame in range(int(start_frame), int(end_frame) + 1): - filename = "{}__tyPart_{:05}.tyc".format(instance.name, frame) + filename = f"{instance.name}__tyPart_{frame:05}.tyc" filenames.append(filename) return filenames - def export_particle(self, members, start, end, - filepath, additional_attributes, - tycache_spline_enabled=False): + def get_export_particles_job_args(self, members, start, end, + filepath, additional_attributes): """Sets up all job arguments for attributes. Those attributes are to be exported in MAX Script. @@ -117,6 +110,8 @@ class ExtractTyCache(publish.Extractor): start (int): Start frame. end (int): End frame. filepath (str): Output path of the TyCache file. + additional_attributes (dict): channel attributes data + which needed to be exported Returns: list of arguments for MAX Script. @@ -125,12 +120,7 @@ class ExtractTyCache(publish.Extractor): job_args = [] opt_list = self.get_operators(members) for operator in opt_list: - if tycache_spline_enabled: - export_mode = f'{operator}.exportMode=3' - job_args.append(export_mode) - else: - export_mode = f'{operator}.exportMode=2' - job_args.append(export_mode) + job_args.append(f"{operator}.exportMode=2") start_frame = f"{operator}.frameStart={start}" job_args.append(start_frame) end_frame = f"{operator}.frameEnd={end}" @@ -192,6 +182,7 @@ class ExtractTyCache(publish.Extractor): if isinstance(value, bool): tyc_attribute = f"{operator}.{key}=True" elif isinstance(value, str): - tyc_attribute = f"{operator}.{key}={value}" + tyc_attribute = f'{operator}.{key}="{value}"' additional_args.append(tyc_attribute) + self.log.debug(additional_args) return additional_args diff --git a/openpype/hosts/max/plugins/publish/validate_tyflow_data.py b/openpype/hosts/max/plugins/publish/validate_tyflow_data.py index a359100e6e..4b2bf975ee 100644 --- a/openpype/hosts/max/plugins/publish/validate_tyflow_data.py +++ b/openpype/hosts/max/plugins/publish/validate_tyflow_data.py @@ -40,7 +40,7 @@ class ValidateTyFlowData(pyblish.api.InstancePlugin): and editable mesh(es) Args: - instance (str): instance node + instance (pyblish.api.Instance): instance Returns: invalid(list): list of invalid nodes which are not From 96cc3dd778d3b1b7f3c4aba01f5b2c64ee290b6c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 19 Oct 2023 13:45:50 +0800 Subject: [PATCH 21/31] clean up the code in the extractor --- openpype/hosts/max/plugins/publish/extract_tycache.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/extract_tycache.py b/openpype/hosts/max/plugins/publish/extract_tycache.py index 9262219b7a..33dde03667 100644 --- a/openpype/hosts/max/plugins/publish/extract_tycache.py +++ b/openpype/hosts/max/plugins/publish/extract_tycache.py @@ -41,12 +41,10 @@ class ExtractTyCache(publish.Extractor): additional_attributes = instance.data.get("tyc_attrs", {}) with maintained_selection(): - job_args = None - if instance.data["tycache_type"] == "tycache": - job_args = self.get_export_particles_job_args( - instance.data["members"], - start, end, path, - additional_attributes) + job_args = self.get_export_particles_job_args( + instance.data["members"], + start, end, path, + additional_attributes) for job in job_args: rt.Execute(job) representations = instance.data.setdefault("representations", []) From c93c26462d9bda7bc87937d109c535691b06da37 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 19 Oct 2023 18:51:32 +0800 Subject: [PATCH 22/31] clean up the code for the tycache publishing --- .../hosts/max/plugins/load/load_tycache.py | 3 +- .../publish/collect_tycache_attributes.py | 2 +- .../max/plugins/publish/extract_tycache.py | 35 +++++++++---------- .../plugins/publish/validate_tyflow_data.py | 33 ++++++++--------- 4 files changed, 32 insertions(+), 41 deletions(-) diff --git a/openpype/hosts/max/plugins/load/load_tycache.py b/openpype/hosts/max/plugins/load/load_tycache.py index ff3a26fbd6..f878ed9f1c 100644 --- a/openpype/hosts/max/plugins/load/load_tycache.py +++ b/openpype/hosts/max/plugins/load/load_tycache.py @@ -46,8 +46,7 @@ class TyCacheLoader(load.LoaderPlugin): path = get_representation_path(representation) node = rt.GetNodeByName(container["instance_node"]) node_list = get_previous_loaded_object(node) - update_custom_attribute_data( - node, node_list) + update_custom_attribute_data(node, node_list) with maintained_selection(): for prt in node_list: prt.filename = path diff --git a/openpype/hosts/max/plugins/publish/collect_tycache_attributes.py b/openpype/hosts/max/plugins/publish/collect_tycache_attributes.py index 779b4c1b7e..0351ca45c5 100644 --- a/openpype/hosts/max/plugins/publish/collect_tycache_attributes.py +++ b/openpype/hosts/max/plugins/publish/collect_tycache_attributes.py @@ -6,7 +6,7 @@ from openpype.pipeline.publish import OpenPypePyblishPluginMixin class CollectTyCacheData(pyblish.api.InstancePlugin, OpenPypePyblishPluginMixin): - """Collect Review Data for Preview Animation""" + """Collect Channel Attributes for TyCache Export""" order = pyblish.api.CollectorOrder + 0.02 label = "Collect tyCache attribute Data" diff --git a/openpype/hosts/max/plugins/publish/extract_tycache.py b/openpype/hosts/max/plugins/publish/extract_tycache.py index 33dde03667..49721f47fe 100644 --- a/openpype/hosts/max/plugins/publish/extract_tycache.py +++ b/openpype/hosts/max/plugins/publish/extract_tycache.py @@ -115,26 +115,23 @@ class ExtractTyCache(publish.Extractor): list of arguments for MAX Script. """ - job_args = [] - opt_list = self.get_operators(members) - for operator in opt_list: - job_args.append(f"{operator}.exportMode=2") - start_frame = f"{operator}.frameStart={start}" - job_args.append(start_frame) - end_frame = f"{operator}.frameEnd={end}" - job_args.append(end_frame) - filepath = filepath.replace("\\", "/") - tycache_filename = f'{operator}.tyCacheFilename="{filepath}"' - job_args.append(tycache_filename) - # TODO: add the additional job args for tycache attributes - if additional_attributes: - additional_args = self.get_additional_attribute_args( - operator, additional_attributes - ) - job_args.extend(additional_args) - tycache_export = f"{operator}.exportTyCache()" - job_args.append(tycache_export) + settings = { + "exportMode": 2, + "frameStart": start, + "frameEnd": end, + "tyCacheFilename": filepath.replace("\\", "/") + } + settings.update(additional_attributes) + job_args = [] + for operator in self.get_operators(members): + for key, value in settings.items(): + if isinstance(value, str): + # embed in quotes + value = f'"{value}"' + + job_args.append(f"{operator}.{key}={value}") + job_args.append(f"{operator}.exportTyCache()") return job_args @staticmethod diff --git a/openpype/hosts/max/plugins/publish/validate_tyflow_data.py b/openpype/hosts/max/plugins/publish/validate_tyflow_data.py index 4b2bf975ee..67c35ec01c 100644 --- a/openpype/hosts/max/plugins/publish/validate_tyflow_data.py +++ b/openpype/hosts/max/plugins/publish/validate_tyflow_data.py @@ -52,12 +52,7 @@ class ValidateTyFlowData(pyblish.api.InstancePlugin): selection_list = instance.data["members"] for sel in selection_list: - sel_tmp = str(sel) - if rt.ClassOf(sel) in [rt.tyFlow, - rt.Editable_Mesh]: - if "tyFlow" not in sel_tmp: - invalid.append(sel) - else: + if rt.ClassOf(sel) not in [rt.tyFlow, rt.Editable_Mesh]: invalid.append(sel) return invalid @@ -75,22 +70,22 @@ class ValidateTyFlowData(pyblish.api.InstancePlugin): of the node connections """ invalid = [] - container = instance.data["instance_node"] - self.log.debug(f"Validating tyFlow object for {container}") - selection_list = instance.data["members"] - bool_list = [] - for sel in selection_list: - obj = sel.baseobject + members = instance.data["members"] + for member in members: + obj = member.baseobject + + # There must be at least one animation with export + # particles enabled + has_export_particles = False anim_names = rt.GetSubAnimNames(obj) for anim_name in anim_names: - # get all the names of the related tyFlow nodes + # get name of the related tyFlow node sub_anim = rt.GetSubAnim(obj, anim_name) # check if there is export particle operator - boolean = rt.IsProperty(sub_anim, "Export_Particles") - bool_list.append(str(boolean)) - # if the export_particles property is not there - # it means there is not a "Export Particle" operator - if "True" not in bool_list: - invalid.append(sel) + if rt.IsProperty(sub_anim, "Export_Particles"): + has_export_particles = True + break + if not has_export_particles: + invalid.append(member) return invalid From 15532b06c5277b78c54ace0829445d08c3050987 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 19 Oct 2023 19:06:01 +0800 Subject: [PATCH 23/31] remove the unused function --- .../max/plugins/publish/extract_tycache.py | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/extract_tycache.py b/openpype/hosts/max/plugins/publish/extract_tycache.py index 49721f47fe..03a6b55f93 100644 --- a/openpype/hosts/max/plugins/publish/extract_tycache.py +++ b/openpype/hosts/max/plugins/publish/extract_tycache.py @@ -159,25 +159,3 @@ class ExtractTyCache(publish.Extractor): opt_list.append(opt) return opt_list - - def get_additional_attribute_args(self, operator, attrs): - """Get Additional args with the attributes pre-set by user - - Args: - operator (str): export particle operator - attrs (dict): a dict which stores the additional attributes - added by user - - Returns: - additional_args(list): a list of additional args for MAX script - """ - additional_args = [] - for key, value in attrs.items(): - tyc_attribute = None - if isinstance(value, bool): - tyc_attribute = f"{operator}.{key}=True" - elif isinstance(value, str): - tyc_attribute = f'{operator}.{key}="{value}"' - additional_args.append(tyc_attribute) - self.log.debug(additional_args) - return additional_args From eb28f19fcfd7380ed9cc7b855263ca03ea53ade5 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 19 Oct 2023 19:17:29 +0800 Subject: [PATCH 24/31] change info to debug --- openpype/hosts/max/plugins/publish/extract_tycache.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/extract_tycache.py b/openpype/hosts/max/plugins/publish/extract_tycache.py index 03a6b55f93..d9d7c17cff 100644 --- a/openpype/hosts/max/plugins/publish/extract_tycache.py +++ b/openpype/hosts/max/plugins/publish/extract_tycache.py @@ -32,7 +32,7 @@ class ExtractTyCache(publish.Extractor): # TODO: let user decide the param start = int(instance.context.data["frameStart"]) end = int(instance.context.data.get("frameEnd")) - self.log.info("Extracting Tycache...") + self.log.debug("Extracting Tycache...") stagingdir = self.staging_dir(instance) filename = "{name}.tyc".format(**instance.data) @@ -55,7 +55,6 @@ class ExtractTyCache(publish.Extractor): "stagingDir": stagingdir, } representations.append(representation) - self.log.info(f"Extracted instance '{instance.name}' to: {filenames}") # Get the tyMesh filename for extraction mesh_filename = f"{instance.name}__tyMesh.tyc" @@ -67,8 +66,7 @@ class ExtractTyCache(publish.Extractor): "outputName": '__tyMesh' } representations.append(mesh_repres) - self.log.info( - f"Extracted instance '{instance.name}' to: {mesh_filename}") + self.log.debug(f"Extracted instance '{instance.name}' to: {filenames}") def get_files(self, instance, start_frame, end_frame): """Get file names for tyFlow in tyCache format. From 404a4dedfcfd798cbcdbab8dc3637d36b5e059e8 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 20 Oct 2023 11:20:58 +0800 Subject: [PATCH 25/31] clean up the code across the tycache families --- .../hosts/max/plugins/load/load_tycache.py | 2 +- .../max/plugins/publish/extract_tycache.py | 6 ++--- .../plugins/publish/validate_tyflow_data.py | 25 ++++++++----------- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/openpype/hosts/max/plugins/load/load_tycache.py b/openpype/hosts/max/plugins/load/load_tycache.py index f878ed9f1c..ff9598b33f 100644 --- a/openpype/hosts/max/plugins/load/load_tycache.py +++ b/openpype/hosts/max/plugins/load/load_tycache.py @@ -25,7 +25,7 @@ class TyCacheLoader(load.LoaderPlugin): def load(self, context, name=None, namespace=None, data=None): """Load tyCache""" from pymxs import runtime as rt - filepath = os.path.normpath(self.filepath_from_context(context)) + filepath = self.filepath_from_context(context) obj = rt.tyCache() obj.filename = filepath diff --git a/openpype/hosts/max/plugins/publish/extract_tycache.py b/openpype/hosts/max/plugins/publish/extract_tycache.py index d9d7c17cff..baed8a9e44 100644 --- a/openpype/hosts/max/plugins/publish/extract_tycache.py +++ b/openpype/hosts/max/plugins/publish/extract_tycache.py @@ -8,8 +8,7 @@ from openpype.pipeline import publish class ExtractTyCache(publish.Extractor): - """ - Extract tycache format with tyFlow operators. + """Extract tycache format with tyFlow operators. Notes: - TyCache only works for TyFlow Pro Plugin. @@ -89,7 +88,6 @@ class ExtractTyCache(publish.Extractor): """ filenames = [] - # should we include frame 0 ? for frame in range(int(start_frame), int(end_frame) + 1): filename = f"{instance.name}__tyPart_{frame:05}.tyc" filenames.append(filename) @@ -146,7 +144,7 @@ class ExtractTyCache(publish.Extractor): opt_list = [] for member in members: obj = member.baseobject - # TODO: to see if it can be used maxscript instead + # TODO: see if it can use maxscript instead anim_names = rt.GetSubAnimNames(obj) for anim_name in anim_names: sub_anim = rt.GetSubAnim(obj, anim_name) diff --git a/openpype/hosts/max/plugins/publish/validate_tyflow_data.py b/openpype/hosts/max/plugins/publish/validate_tyflow_data.py index 67c35ec01c..c0f29422ec 100644 --- a/openpype/hosts/max/plugins/publish/validate_tyflow_data.py +++ b/openpype/hosts/max/plugins/publish/validate_tyflow_data.py @@ -4,8 +4,7 @@ from pymxs import runtime as rt class ValidateTyFlowData(pyblish.api.InstancePlugin): - """Validate that TyFlow plugins or - relevant operators being set correctly.""" + """Validate TyFlow plugins or relevant operators are set correctly.""" order = pyblish.api.ValidatorOrder families = ["pointcloud", "tycache"] @@ -31,9 +30,9 @@ class ValidateTyFlowData(pyblish.api.InstancePlugin): if invalid_object or invalid_operator: raise PublishValidationError( "issues occurred", - description="Container should only include tyFlow object\n " - "and tyflow operator 'Export Particle' should be in \n" - "the tyFlow editor") + description="Container should only include tyFlow object " + "and tyflow operator 'Export Particle' should be in " + "the tyFlow editor.") def get_tyflow_object(self, instance): """Get the nodes which are not tyFlow object(s) @@ -43,19 +42,17 @@ class ValidateTyFlowData(pyblish.api.InstancePlugin): instance (pyblish.api.Instance): instance Returns: - invalid(list): list of invalid nodes which are not - tyFlow object(s) and editable mesh(es). + list: invalid nodes which are not tyFlow + object(s) and editable mesh(es). """ - invalid = [] container = instance.data["instance_node"] self.log.debug(f"Validating tyFlow container for {container}") - selection_list = instance.data["members"] - for sel in selection_list: - if rt.ClassOf(sel) not in [rt.tyFlow, rt.Editable_Mesh]: - invalid.append(sel) - - return invalid + allowed_classes = [rt.tyFlow, rt.Editable_Mesh] + return [ + member for member in instance.data["members"] + if rt.ClassOf(member) not in allowed_classes + ] def get_tyflow_operator(self, instance): """Check if the Export Particle Operators in the node From ec70706ab5ef53d8d71a4e0802d77ac28f9707d2 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 20 Oct 2023 11:22:26 +0800 Subject: [PATCH 26/31] hound --- openpype/hosts/max/plugins/load/load_tycache.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/max/plugins/load/load_tycache.py b/openpype/hosts/max/plugins/load/load_tycache.py index ff9598b33f..a860ecd357 100644 --- a/openpype/hosts/max/plugins/load/load_tycache.py +++ b/openpype/hosts/max/plugins/load/load_tycache.py @@ -1,5 +1,3 @@ -import os - from openpype.hosts.max.api import lib, maintained_selection from openpype.hosts.max.api.lib import ( unique_namespace, From 39b44e126450f5c12ba18f3db6a13485a507b259 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 23 Oct 2023 16:07:18 +0800 Subject: [PATCH 27/31] fix the indentation --- openpype/hosts/max/plugins/publish/extract_tycache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/publish/extract_tycache.py b/openpype/hosts/max/plugins/publish/extract_tycache.py index baed8a9e44..9bfe74f679 100644 --- a/openpype/hosts/max/plugins/publish/extract_tycache.py +++ b/openpype/hosts/max/plugins/publish/extract_tycache.py @@ -144,7 +144,7 @@ class ExtractTyCache(publish.Extractor): opt_list = [] for member in members: obj = member.baseobject - # TODO: see if it can use maxscript instead + # TODO: see if it can use maxscript instead anim_names = rt.GetSubAnimNames(obj) for anim_name in anim_names: sub_anim = rt.GetSubAnim(obj, anim_name) From 4c837a6a9e0b859626cafe0e688572a5bb57175c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 24 Oct 2023 15:15:18 +0800 Subject: [PATCH 28/31] restore the os.path.normpath in loader for test --- openpype/hosts/max/plugins/load/load_tycache.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/max/plugins/load/load_tycache.py b/openpype/hosts/max/plugins/load/load_tycache.py index a860ecd357..858297dd8e 100644 --- a/openpype/hosts/max/plugins/load/load_tycache.py +++ b/openpype/hosts/max/plugins/load/load_tycache.py @@ -1,3 +1,4 @@ +import os from openpype.hosts.max.api import lib, maintained_selection from openpype.hosts.max.api.lib import ( unique_namespace, @@ -23,7 +24,7 @@ class TyCacheLoader(load.LoaderPlugin): def load(self, context, name=None, namespace=None, data=None): """Load tyCache""" from pymxs import runtime as rt - filepath = self.filepath_from_context(context) + filepath = os.path.normpath(self.filepath_from_context(context)) obj = rt.tyCache() obj.filename = filepath @@ -46,8 +47,8 @@ class TyCacheLoader(load.LoaderPlugin): node_list = get_previous_loaded_object(node) update_custom_attribute_data(node, node_list) with maintained_selection(): - for prt in node_list: - prt.filename = path + for tyc in node_list: + tyc.filename = path lib.imprint(container["instance_node"], { "representation": str(representation["_id"]) }) From 35a53598542d3233bbd2f3d46b37e1b92c02ed5a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 24 Oct 2023 16:18:14 +0800 Subject: [PATCH 29/31] docstring edit --- openpype/hosts/max/plugins/load/load_tycache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/load/load_tycache.py b/openpype/hosts/max/plugins/load/load_tycache.py index 858297dd8e..41ea267c3d 100644 --- a/openpype/hosts/max/plugins/load/load_tycache.py +++ b/openpype/hosts/max/plugins/load/load_tycache.py @@ -13,7 +13,7 @@ from openpype.pipeline import get_representation_path, load class TyCacheLoader(load.LoaderPlugin): - """Point Cloud Loader.""" + """TyCache Loader.""" families = ["tycache"] representations = ["tyc"] From d11b00510388a797f5e0229d13a2f3bf6b837460 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 24 Oct 2023 21:08:54 +0800 Subject: [PATCH 30/31] make sure the path is normalized during the update for the loaders --- openpype/hosts/max/plugins/load/load_tycache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/load/load_tycache.py b/openpype/hosts/max/plugins/load/load_tycache.py index 41ea267c3d..1373404274 100644 --- a/openpype/hosts/max/plugins/load/load_tycache.py +++ b/openpype/hosts/max/plugins/load/load_tycache.py @@ -42,7 +42,7 @@ class TyCacheLoader(load.LoaderPlugin): """update the container""" from pymxs import runtime as rt - path = get_representation_path(representation) + path = os.path.normpath(get_representation_path(representation)) node = rt.GetNodeByName(container["instance_node"]) node_list = get_previous_loaded_object(node) update_custom_attribute_data(node, node_list) From dfd239172cb324d4bff8e5fa89cf39c672abb5d0 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 25 Oct 2023 17:20:06 +0800 Subject: [PATCH 31/31] do not do normapath in update function --- openpype/hosts/max/plugins/load/load_tycache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/load/load_tycache.py b/openpype/hosts/max/plugins/load/load_tycache.py index 1373404274..41ea267c3d 100644 --- a/openpype/hosts/max/plugins/load/load_tycache.py +++ b/openpype/hosts/max/plugins/load/load_tycache.py @@ -42,7 +42,7 @@ class TyCacheLoader(load.LoaderPlugin): """update the container""" from pymxs import runtime as rt - path = os.path.normpath(get_representation_path(representation)) + path = get_representation_path(representation) node = rt.GetNodeByName(container["instance_node"]) node_list = get_previous_loaded_object(node) update_custom_attribute_data(node, node_list)