mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge pull request #5624 from ynput/enhancement/OP-6352_tycache-family
This commit is contained in:
commit
658aa3e30b
12 changed files with 432 additions and 59 deletions
11
openpype/hosts/max/plugins/create/create_tycache.py
Normal file
11
openpype/hosts/max/plugins/create/create_tycache.py
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Creator plugin for creating TyCache."""
|
||||
from openpype.hosts.max.api import plugin
|
||||
|
||||
|
||||
class CreateTyCache(plugin.MaxCreator):
|
||||
"""Creator plugin for TyCache."""
|
||||
identifier = "io.openpype.creators.max.tycache"
|
||||
label = "TyCache"
|
||||
family = "tycache"
|
||||
icon = "gear"
|
||||
64
openpype/hosts/max/plugins/load/load_tycache.py
Normal file
64
openpype/hosts/max/plugins/load/load_tycache.py
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
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 TyCacheLoader(load.LoaderPlugin):
|
||||
"""TyCache 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():
|
||||
for tyc in node_list:
|
||||
tyc.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)
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
import pyblish.api
|
||||
|
||||
from openpype.lib import EnumDef, TextDef
|
||||
from openpype.pipeline.publish import OpenPypePyblishPluginMixin
|
||||
|
||||
|
||||
class CollectTyCacheData(pyblish.api.InstancePlugin,
|
||||
OpenPypePyblishPluginMixin):
|
||||
"""Collect Channel Attributes for TyCache Export"""
|
||||
|
||||
order = pyblish.api.CollectorOrder + 0.02
|
||||
label = "Collect tyCache attribute Data"
|
||||
hosts = ['max']
|
||||
families = ["tycache"]
|
||||
|
||||
def process(self, instance):
|
||||
attr_values = self.get_attr_values_from_data(instance.data)
|
||||
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: {attributes}"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_attribute_defs(cls):
|
||||
# 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",
|
||||
"tycacheCreateObject",
|
||||
"tycacheCreateObjectIfNotCreated",
|
||||
"tycacheAdditionalCloth",
|
||||
"tycacheAdditionalSkin",
|
||||
"tycacheAdditionalSkinID",
|
||||
"tycacheAdditionalSkinIDValue",
|
||||
"tycacheAdditionalTerrain",
|
||||
"tycacheAdditionalVDB",
|
||||
"tycacheAdditionalSplinePaths",
|
||||
"tycacheAdditionalGeo",
|
||||
"tycacheAdditionalGeoActivateModifiers",
|
||||
"tycacheSplines",
|
||||
"tycacheSplinesAdditionalSplines"
|
||||
]
|
||||
tyc_default_attrs = ["tycacheChanGroups", "tycacheChanPos",
|
||||
"tycacheChanRot", "tycacheChanScale",
|
||||
"tycacheChanVel", "tycacheChanShape",
|
||||
"tycacheChanMatID", "tycacheChanMapping",
|
||||
"tycacheChanMaterials",
|
||||
"tycacheCreateObjectIfNotCreated"]
|
||||
return [
|
||||
EnumDef("tycacheAttributes",
|
||||
tyc_attr_enum,
|
||||
default=tyc_default_attrs,
|
||||
multiselection=True,
|
||||
label="TyCache Attributes"),
|
||||
TextDef("tycacheLayer",
|
||||
label="TyCache Layer",
|
||||
tooltip="Name of tycache layer",
|
||||
default="$(tyFlowLayer)"),
|
||||
TextDef("tycacheObjectName",
|
||||
label="TyCache Object Name",
|
||||
tooltip="TyCache Object Name",
|
||||
default="$(tyFlowName)_tyCache")
|
||||
]
|
||||
157
openpype/hosts/max/plugins/publish/extract_tycache.py
Normal file
157
openpype/hosts/max/plugins/publish/extract_tycache.py
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
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.
|
||||
|
||||
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
|
||||
|
||||
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["frameStart"])
|
||||
end = int(instance.context.data.get("frameEnd"))
|
||||
self.log.debug("Extracting Tycache...")
|
||||
|
||||
stagingdir = self.staging_dir(instance)
|
||||
filename = "{name}.tyc".format(**instance.data)
|
||||
path = os.path.join(stagingdir, filename)
|
||||
filenames = self.get_files(instance, start, end)
|
||||
additional_attributes = instance.data.get("tyc_attrs", {})
|
||||
|
||||
with maintained_selection():
|
||||
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", [])
|
||||
representation = {
|
||||
'name': 'tyc',
|
||||
'ext': 'tyc',
|
||||
'files': filenames if len(filenames) > 1 else filenames[0],
|
||||
"stagingDir": stagingdir,
|
||||
}
|
||||
representations.append(representation)
|
||||
|
||||
# Get the tyMesh filename for extraction
|
||||
mesh_filename = f"{instance.name}__tyMesh.tyc"
|
||||
mesh_repres = {
|
||||
'name': 'tyMesh',
|
||||
'ext': 'tyc',
|
||||
'files': mesh_filename,
|
||||
"stagingDir": stagingdir,
|
||||
"outputName": '__tyMesh'
|
||||
}
|
||||
representations.append(mesh_repres)
|
||||
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.
|
||||
|
||||
Set the filenames accordingly to the tyCache file
|
||||
naming extension(.tyc) for the publishing purpose
|
||||
|
||||
Actual File Output from tyFlow in tyCache format:
|
||||
<InstanceName>__tyPart_<frame>.tyc
|
||||
|
||||
e.g. tycacheMain__tyPart_00000.tyc
|
||||
|
||||
Args:
|
||||
instance (pyblish.api.Instance): instance.
|
||||
start_frame (int): Start frame.
|
||||
end_frame (int): End frame.
|
||||
|
||||
Returns:
|
||||
filenames(list): list of filenames
|
||||
|
||||
"""
|
||||
filenames = []
|
||||
for frame in range(int(start_frame), int(end_frame) + 1):
|
||||
filename = f"{instance.name}__tyPart_{frame:05}.tyc"
|
||||
filenames.append(filename)
|
||||
return filenames
|
||||
|
||||
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.
|
||||
|
||||
Args:
|
||||
members (list): Member nodes of the instance.
|
||||
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.
|
||||
|
||||
"""
|
||||
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
|
||||
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: 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)
|
||||
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
|
||||
|
|
@ -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"]
|
||||
|
|
|
|||
88
openpype/hosts/max/plugins/publish/validate_tyflow_data.py
Normal file
88
openpype/hosts/max/plugins/publish/validate_tyflow_data.py
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
import pyblish.api
|
||||
from openpype.pipeline import PublishValidationError
|
||||
from pymxs import runtime as rt
|
||||
|
||||
|
||||
class ValidateTyFlowData(pyblish.api.InstancePlugin):
|
||||
"""Validate TyFlow plugins or relevant operators are 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
|
||||
|
||||
"""
|
||||
|
||||
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 invalid_object or invalid_operator:
|
||||
raise PublishValidationError(
|
||||
"issues occurred",
|
||||
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)
|
||||
and editable mesh(es)
|
||||
|
||||
Args:
|
||||
instance (pyblish.api.Instance): instance
|
||||
|
||||
Returns:
|
||||
list: invalid nodes which are not tyFlow
|
||||
object(s) and editable mesh(es).
|
||||
"""
|
||||
container = instance.data["instance_node"]
|
||||
self.log.debug(f"Validating tyFlow container for {container}")
|
||||
|
||||
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
|
||||
connections.
|
||||
|
||||
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 = []
|
||||
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 name of the related tyFlow node
|
||||
sub_anim = rt.GetSubAnim(obj, anim_name)
|
||||
# check if there is export particle operator
|
||||
if rt.IsProperty(sub_anim, "Export_Particles"):
|
||||
has_export_particles = True
|
||||
break
|
||||
|
||||
if not has_export_particles:
|
||||
invalid.append(member)
|
||||
return invalid
|
||||
|
|
@ -63,7 +63,8 @@ class CollectResourcesPath(pyblish.api.InstancePlugin):
|
|||
"staticMesh",
|
||||
"skeletalMesh",
|
||||
"xgen",
|
||||
"yeticacheUE"
|
||||
"yeticacheUE",
|
||||
"tycache"
|
||||
]
|
||||
|
||||
def process(self, instance):
|
||||
|
|
|
|||
|
|
@ -141,7 +141,8 @@ class IntegrateAsset(pyblish.api.InstancePlugin):
|
|||
"online",
|
||||
"uasset",
|
||||
"blendScene",
|
||||
"yeticacheUE"
|
||||
"yeticacheUE",
|
||||
"tycache"
|
||||
]
|
||||
|
||||
default_template_name = "publish"
|
||||
|
|
|
|||
|
|
@ -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}.{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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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": [
|
||||
|
|
|
|||
|
|
@ -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": [
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
__version__ = "0.1.2"
|
||||
__version__ = "0.1.3"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue