mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-02 00:44:52 +01:00
add tycache family
This commit is contained in:
parent
93fb76f359
commit
5f0ce4f88d
5 changed files with 361 additions and 56 deletions
34
openpype/hosts/max/plugins/create/create_tycache.py
Normal file
34
openpype/hosts/max/plugins/create/create_tycache.py
Normal 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")
|
||||
]
|
||||
67
openpype/hosts/max/plugins/load/load_tycache.py
Normal file
67
openpype/hosts/max/plugins/load/load_tycache.py
Normal 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)
|
||||
183
openpype/hosts/max/plugins/publish/extract_tycache.py
Normal file
183
openpype/hosts/max/plugins/publish/extract_tycache.py
Normal 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
|
||||
|
||||
"""
|
||||
|
|
@ -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"]
|
||||
|
|
|
|||
74
openpype/hosts/max/plugins/publish/validate_tyflow_data.py
Normal file
74
openpype/hosts/max/plugins/publish/validate_tyflow_data.py
Normal 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue