add tycache family

This commit is contained in:
Kayla Man 2023-09-15 18:38:40 +08:00
parent 93fb76f359
commit 5f0ce4f88d
5 changed files with 361 additions and 56 deletions

View file

@ -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")
]

View file

@ -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)

View file

@ -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
"""

View file

@ -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"]

View file

@ -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