Skip to content

Commit

Permalink
Merge pull request #1 from ynput/enhancement/OP-7406_3dsmax-tyCache-e…
Browse files Browse the repository at this point in the history
…nhancements

Max: TyCache Family Enhancement
  • Loading branch information
moonyuet committed Jul 17, 2024
2 parents d37b933 + 5621730 commit 4b9506e
Show file tree
Hide file tree
Showing 13 changed files with 608 additions and 203 deletions.
24 changes: 24 additions & 0 deletions client/ayon_max/api/lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,30 @@ def render_resolution(width, height):
rt.renderHeight = current_renderHeight


def get_tyflow_export_operators():
"""Get Tyflow Export Particles Operators.
Returns:
list: Particle operators
"""
operators = []
members = [obj for obj in rt.Objects if rt.ClassOf(obj) == rt.tyFlow]
for member in members:
obj = member.baseobject
anim_names = rt.GetSubAnimNames(obj)
for anim_name in anim_names:
sub_anim = rt.GetSubAnim(obj, anim_name)
if not rt.isKindOf(sub_anim, rt.tyEvent):
continue
node_names = rt.GetSubAnimNames(sub_anim)
for node_name in node_names:
node_sub_anim = rt.GetSubAnim(sub_anim, node_name)
if rt.hasProperty(node_sub_anim, "exportMode"):
operators.append(node_sub_anim)
return operators


@contextlib.contextmanager
def suspended_refresh():
"""Suspended refresh for scene and modify panel redraw.
Expand Down
217 changes: 214 additions & 3 deletions client/ayon_max/api/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
AVALON_INSTANCE_ID,
)

from .lib import imprint, lsattr, read
from .lib import imprint, lsattr, read, get_tyflow_export_operators

MS_CUSTOM_ATTRIB = """attributes "openPypeData"
(
Expand Down Expand Up @@ -156,6 +156,114 @@
)"""


MS_TYCACHE_ATTRIB = """attributes "AYONTyCacheData"
(
parameters main rollout:Cacheparams
(
tyc_exports type:#stringTab tabSize:0 tabSizeVariable:on
tyc_handles type:#stringTab tabSize:0 tabSizeVariable:on
sel_list type:#stringTab tabSize:0 tabSizeVariable:on
)
rollout Cacheparams "AYON TyCache Parameters"
(
listbox export_node "Export Nodes" items:#()
button button_add "Add to Exports"
button button_del "Delete from Exports"
button button_refresh "Refresh"
listbox tyflow_node "TyFlow Export Operators" items:#()
on button_add pressed do
(
i_node_arr = #()
temp_arr = #()
current_sel = tyflow_node.selected
idx = finditem export_node.items current_sel
if idx == 0 do (
append i_node_arr current_sel
append temp_arr current_sel
)
tyc_exports = join i_node_arr tyc_exports
export_node.items = join temp_arr export_node.items
sel_list = export_node.items
)
on button_del pressed do
(
i_node_arr = #()
temp_arr = #()
updated_node_arr = #()
new_temp_arr = #()
current_sel = export_node.selected
idx = finditem export_node.items current_sel
if idx do (
updated_node_arr = DeleteItem export_node.items idx
)
idx = finditem tyc_exports current_sel
if idx do (
new_temp_arr = DeleteItem tyc_exports idx
)
tyc_exports = join i_node_arr updated_node_arr
export_node.items = join temp_arr new_temp_arr
sel_list = export_node.items
)
on button_refresh pressed do
(
handle_arr = #()
for obj in Objects do
(
if classof obj == tyflow then
(
member = obj.baseobject
anim_names = GetSubAnimNames member
for anim_name in anim_names do
(
sub_anim = GetSubAnim member anim_name
if isKindOf sub_anim tyEvent do
(
node_names = GetSubAnimNames sub_anim
for node_name in node_names do
(
node_sub_anim = GetSubAnim sub_anim node_name
if hasProperty node_sub_anim "exportMode" do
(
node_str = node_sub_anim.name as string
append handle_arr node_str
)
)
)
)
)
)
tyc_handles = handle_arr
tyflow_node.items = handle_arr
)
on Cacheparams open do
(
temp_arr = #()
tyflow_id_arr = #()
if tyc_handles.count != 0 then
(
for x in tyc_handles do
(
if x == undefined do continue
tyflow_op_name = x as string
append temp_arr tyflow_op_name
)
tyflow_node.items = temp_arr
)
if sel_list.count != 0 then
(
sel_arr = #()
for x in sel_list do
(
append sel_arr x
)
export_node.items = sel_arr
)
)
)
)"""

class MaxCreatorBase(object):

@staticmethod
Expand Down Expand Up @@ -191,9 +299,10 @@ def create_instance_node(node):
Returns:
instance
"""
if isinstance(node, str):
node = rt.Container(name=node)
if not isinstance(node, str):
raise CreatorError("Instance node is not at the string value.")

node = rt.Container(name=node)
attrs = rt.Execute(MS_CUSTOM_ATTRIB)
modifier = rt.EmptyModifier()
rt.addModifier(node, modifier)
Expand All @@ -203,6 +312,32 @@ def create_instance_node(node):
return node


class MaxTyFlowDataCreatorBase(MaxCreatorBase):
@staticmethod
def create_instance_node(node):
"""Create instance node.
If the supplied node is existing node, it will be used to hold the
instance, otherwise new node of type Dummy will be created.
Args:
node (rt.MXSWrapperBase, str): Node or node name to use.
Returns:
instance
"""
if not isinstance(node, str):
raise CreatorError("Instance node is not at the string value.")
node = rt.Container(name=node)
attrs = rt.Execute(MS_TYCACHE_ATTRIB)
modifier = rt.EmptyModifier()
rt.addModifier(node, modifier)
node.modifiers[0].name = "AYON TyCache Data"
rt.custAttributes.add(node.modifiers[0], attrs)

return node


@six.add_metaclass(ABCMeta)
class MaxCreator(Creator, MaxCreatorBase):
selected_nodes = []
Expand Down Expand Up @@ -296,3 +431,79 @@ def get_pre_create_attr_defs(self):
return [
BoolDef("use_selection", label="Use selection")
]


class MaxCacheCreator(Creator, MaxTyFlowDataCreatorBase):
settings_category = "max"
def create(self, product_name, instance_data, pre_create_data):
tyflow_op_nodes = get_tyflow_export_operators()
if not tyflow_op_nodes:
raise CreatorError("No Export Particle Operators"
" found in tyCache Editor.")
instance_node = self.create_instance_node(product_name)
instance_data["instance_node"] = instance_node.name
instance = CreatedInstance(
self.product_type,
product_name,
instance_data,
self
)
# Setting the property
node_list = [sub_anim.name for sub_anim in tyflow_op_nodes]
rt.setProperty(
instance_node.modifiers[0].AYONTyCacheData,
"tyc_handles", node_list)
self._add_instance_to_context(instance)
imprint(instance_node.name, instance.data_to_store())

return instance

def collect_instances(self):
self.cache_instance_data(self.collection_shared_data)
for instance in self.collection_shared_data["max_cached_instances"].get(self.identifier, []): # noqa
created_instance = CreatedInstance.from_existing(
read(rt.GetNodeByName(instance)), self
)
self._add_instance_to_context(created_instance)

def update_instances(self, update_list):
for created_inst, changes in update_list:
instance_node = created_inst.get("instance_node")
new_values = {
key: changes[key].new_value
for key in changes.changed_keys
}
product_name = new_values.get("productName", "")
if product_name and instance_node != product_name:
node = rt.getNodeByName(instance_node)
new_product_name = new_values["productName"]
if rt.getNodeByName(new_product_name):
raise CreatorError(
"The product '{}' already exists.".format(
new_product_name))
instance_node = new_product_name
created_inst["instance_node"] = instance_node
node.name = instance_node

imprint(
instance_node,
created_inst.data_to_store(),
)

def remove_instances(self, instances):
"""Remove specified instance from the scene.
This is only removing AYON-related parameters based on the modifier
so instance is no longer instance, because it might contain
valuable data for artist.
"""
for instance in instances:
instance_node = rt.GetNodeByName(
instance.data.get("instance_node"))
if instance_node:
count = rt.custAttributes.count(instance_node.modifiers[0])
rt.custAttributes.delete(instance_node.modifiers[0], count)
rt.Delete(instance_node)

self._remove_instance_from_context(instance)
13 changes: 0 additions & 13 deletions client/ayon_max/plugins/create/create_tycache.py

This file was deleted.

11 changes: 11 additions & 0 deletions client/ayon_max/plugins/create/create_tyflow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# -*- coding: utf-8 -*-
"""Creator plugin for creating TyFlow."""
from ayon_max.api import plugin


class CreateTyFlow(plugin.MaxCacheCreator):
"""Creator plugin for TyFlow."""
identifier = "io.ayon.creators.max.tyflow"
label = "TyFlow"
product_type = "tyflow"
icon = "gear"
28 changes: 28 additions & 0 deletions client/ayon_max/plugins/load/load_tycache.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,31 @@ def remove(self, container):
from pymxs import runtime as rt
node = rt.GetNodeByName(container["instance_node"])
remove_container_data(node)
rt.Delete(node)


class TySplineCacheLoader(TyCacheLoader):
"""TyCache(Spline) Loader."""

product_types = {"tyspline"}
representations = {"tyc"}
order = -8
icon = "code-fork"
color = "green"

def load(self, context, name=None, namespace=None, data=None):
from pymxs import runtime as rt
filepath = os.path.normpath(self.filepath_from_context(context))
obj = rt.tyCache()
obj.filename = filepath
tySplineCache_modifier = rt.tySplineCache()
rt.addModifier(obj, tySplineCache_modifier)
namespace = unique_namespace(
name + "_",
suffix="_",
)
obj.name = f"{namespace}:{obj.name}"

return containerise(
name, [obj], context,
namespace, loader=self.__class__.__name__)
10 changes: 8 additions & 2 deletions client/ayon_max/plugins/publish/collect_frame_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,23 @@
class CollectFrameRange(pyblish.api.InstancePlugin):
"""Collect Frame Range."""

order = pyblish.api.CollectorOrder + 0.01
order = pyblish.api.CollectorOrder + 0.011
label = "Collect Frame Range"
hosts = ['max']
families = ["camera", "maxrender",
"pointcache", "pointcloud",
"review", "redshiftproxy"]
"review", "tycache",
"tyspline", "redshiftproxy"]

def process(self, instance):
if instance.data["productType"] == "maxrender":
instance.data["frameStartHandle"] = int(rt.rendStart)
instance.data["frameEndHandle"] = int(rt.rendEnd)

elif instance.data["family"] in {"tycache", "tyspline"}:
operator = instance.data["operator"]
instance.data["frameStartHandle"] = rt.getProperty(operator, "frameStart")
instance.data["frameEndHandle"] = rt.getProperty(operator, "frameEnd")
else:
instance.data["frameStartHandle"] = int(rt.animationRange.start)
instance.data["frameEndHandle"] = int(rt.animationRange.end)
16 changes: 9 additions & 7 deletions client/ayon_max/plugins/publish/collect_members.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,17 @@ class CollectMembers(pyblish.api.InstancePlugin):
hosts = ['max']

def process(self, instance):
if instance.data["productType"] == "workfile":
self.log.debug(
"Skipping Collecting Members for workfile product type."
)
return
if instance.data.get("instance_node"):
if instance.data["productType"] in {
"workfile", "tyflow", "tycache", "tyspline"}:
self.log.debug(
"Skipping Collecting Members for workfile "
"and tyflow product type."
)
return

elif instance.data.get("instance_node"):
container = rt.GetNodeByName(instance.data["instance_node"])
instance.data["members"] = [
member.node for member
in container.modifiers[0].openPypeData.all_handles
]
self.log.debug("{}".format(instance.data["members"]))
Loading

0 comments on commit 4b9506e

Please sign in to comment.