Skip to content
40 changes: 38 additions & 2 deletions client/ayon_max/api/lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -607,14 +607,18 @@ def render_resolution(width, height):
rt.renderHeight = current_renderHeight


def get_tyflow_export_operators():
def get_tyflow_export_operators(operator_type="exportParticle"):
"""Get Tyflow Export Particles Operators.

Returns:
list: Particle operators

"""
operators = []
operator_prop = (
"exportMode" if operator_type == "exportParticle"
else "gridsSDF"
)
members = [obj for obj in rt.Objects if rt.ClassOf(obj) == rt.tyFlow]
for member in members:
obj = member.baseobject
Expand All @@ -626,7 +630,7 @@ def get_tyflow_export_operators():
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"):
if rt.hasProperty(node_sub_anim, operator_prop):
operators.append(node_sub_anim)
return operators

Expand All @@ -646,3 +650,35 @@ def suspended_refresh():
finally:
rt.enableSceneRedraw()
rt.resumeEditing()


@contextlib.contextmanager
def animation_range_without_loop(frameStart, frameEnd):
"""Ensure animation range is played without
loop during context
"""
previous_playbackLoop = (
rt.timeConfiguration.playbackLoop
)
previous_realtime_playback = (
rt.timeConfiguration.realTimePlayback
)
previous_animation_range = (
rt.animationRange
)
previous_current_frame = rt.currentTime.frame
rt.timeConfiguration.playbackLoop = False
rt.timeConfiguration.realTimePlayback = True
set_timeline(frameStart, frameEnd)
try:
yield

finally:
rt.timeConfiguration.playbackLoop = (
previous_playbackLoop
)
rt.timeConfiguration.realTimePlayback = (
previous_realtime_playback
)
rt.currentTime.frame = previous_current_frame
rt.animationRange = previous_animation_range
231 changes: 157 additions & 74 deletions client/ayon_max/api/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,116 @@
)
)"""


MS_VDB_ATTRIB = """attributes "AYONTyFlowVDBData"
(
parameters main rollout:Cacheparams
(
vdb_exports type:#stringTab tabSize:0 tabSizeVariable:on
vdb_handles type:#stringTab tabSize:0 tabSizeVariable:on
vdb_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 vdb_node "VDB Export Operators" items:#()

on button_add pressed do
(
i_node_arr = #()
temp_arr = #()
current_sel = vdb_node.selected
idx = finditem export_node.items current_sel
if idx == 0 do (
append i_node_arr current_sel
append temp_arr current_sel
)
vdb_exports = join i_node_arr vdb_exports
export_node.items = join temp_arr export_node.items
vdb_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 vdb_exports current_sel
if idx do (
new_temp_arr = DeleteItem vdb_exports idx
)
vdb_exports = join i_node_arr updated_node_arr
export_node.items = join temp_arr new_temp_arr
vdb_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 "gridsSDF" do
(
node_str = node_sub_anim.name as string
append handle_arr node_str
)
)
)
)
)
)
vdb_handles = handle_arr
vdb_node.items = handle_arr
)
on Cacheparams open do
(
temp_arr = #()
tyflow_id_arr = #()
if vdb_handles.count != 0 then
(
for x in vdb_handles do
(
if x == undefined do continue
tyflow_op_name = x as string
append temp_arr tyflow_op_name
)
vdb_node.items = temp_arr
)
if vdb_sel_list.count != 0 then
(
sel_arr = #()
for x in vdb_sel_list do
(
append sel_arr x
)
export_node.items = sel_arr
)
)
)
)"""


class MaxCreatorBase(object):

@staticmethod
Expand All @@ -284,7 +394,7 @@ def cache_instance_data(shared_data):
return shared_data

@staticmethod
def create_instance_node(node):
def create_instance_node(node, attribute="general_data"):
"""Create instance node.

If the supplied node is existing node, it will be used to hold the
Expand All @@ -300,36 +410,22 @@ def create_instance_node(node):
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)
node.modifiers[0].name = "OP Data"
rt.custAttributes.add(node.modifiers[0], attrs)

return node


class MaxTyFlowDataCreatorBase(MaxCreatorBase):
@staticmethod
def create_instance_node(node):
"""Create instance node.
if attribute == "tycache_data":
attrs = rt.Execute(MS_TYCACHE_ATTRIB)
elif attribute == "ty_vdb_data":
attrs = rt.Execute(MS_VDB_ATTRIB)
else:
attrs = rt.Execute(MS_CUSTOM_ATTRIB)

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"
if attribute == "tycache_data":
node.modifiers[0].name = "AYON TyCache Data"
elif attribute == "ty_vdb_data":
node.modifiers[0].name = "AYON TyFlow VDB Data"
else:
node.modifiers[0].name = "OP Data"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why "OP Data"?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the name of general container attributes for other product type. It would be renamed after the related PR for this issue as the backward compatibility needs to be implemented for this.


rt.custAttributes.add(node.modifiers[0], attrs)

return node
Expand Down Expand Up @@ -429,14 +525,16 @@ def get_pre_create_attr_defs(self):
]


class MaxCacheCreator(Creator, MaxTyFlowDataCreatorBase):
class MaxCacheCreator(MaxCreator):
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_node = self.create_instance_node(
product_name, attribute="tycache_data")
instance_data["instance_node"] = instance_node.name
instance = CreatedInstance(
self.product_type,
Expand All @@ -454,52 +552,37 @@ def create(self, product_name, instance_data, pre_create_data):

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
def get_pre_create_attr_defs(self):
return []

imprint(
instance_node,
created_inst.data_to_store(),
)

def remove_instances(self, instances):
"""Remove specified instance from the scene.
class MaxTyflowVDBCacheCreator(MaxCreator):
settings_category = "max"

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.
def create(self, product_name, instance_data, pre_create_data):
tyflow_op_nodes = get_tyflow_export_operators(operator_type="exportVDB")
if not tyflow_op_nodes:
raise CreatorError("No Export VDB Operators"
" found in tyCache Editor.")
instance_node = self.create_instance_node(
product_name, attribute="ty_vdb_data")
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].AYONTyFlowVDBData,
"vdb_handles", node_list
)
self._add_instance_to_context(instance)
imprint(instance_node.name, instance.data_to_store())

"""
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)
return instance

self._remove_instance_from_context(instance)
def get_pre_create_attr_defs(self):
return []
8 changes: 8 additions & 0 deletions client/ayon_max/plugins/create/create_tyflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,11 @@ class CreateTyFlow(plugin.MaxCacheCreator):
label = "TyFlow"
product_type = "tyflow"
icon = "gear"


class CreateTyVDB(plugin.MaxTyflowVDBCacheCreator):
"""Creator plugin for TyFlow VDB."""
identifier = "io.ayon.creators.max.vdb"
label = "VDB (TyFlow)"
product_type = "tyflow_vdb"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that a lot of VDB based loaders currently target the vdbcache product type, and hence will not allow loading this product type.

For example, these all target vdbcache product type.

ayon_maya/plugins/load/load_vdb_to_vray.py
ayon_maya/plugins/load/load_vdb_to_redshift.py
ayon_maya/plugins/load/load_vdb_to_arnold.py
ayon_houdini/plugins/load/load_vdb.py

icon = "gear"
7 changes: 6 additions & 1 deletion client/ayon_max/plugins/publish/collect_frame_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ class CollectFrameRange(pyblish.api.InstancePlugin):
families = ["camera", "maxrender",
"pointcache", "pointcloud",
"review", "tycache",
"tyspline", "redshiftproxy"]
"tyspline", "redshiftproxy",
"vdb"]

def process(self, instance):
if instance.data["productType"] == "maxrender":
Expand All @@ -23,6 +24,10 @@ def process(self, instance):
operator = instance.data["operator"]
instance.data["frameStartHandle"] = rt.getProperty(operator, "frameStart")
instance.data["frameEndHandle"] = rt.getProperty(operator, "frameEnd")
elif instance.data["productType"] == "vdb" and instance.data.get("is_tyflow", False):
operator = instance.data["operator"]
instance.data["frameStartHandle"] = rt.getProperty(operator, "timingIntervalStart")
instance.data["frameEndHandle"] = rt.getProperty(operator, "timingIntervalEnd")
else:
instance.data["frameStartHandle"] = int(rt.animationRange.start)
instance.data["frameEndHandle"] = int(rt.animationRange.end)
2 changes: 1 addition & 1 deletion client/ayon_max/plugins/publish/collect_members.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class CollectMembers(pyblish.api.InstancePlugin):

def process(self, instance):
if instance.data["productType"] in {
"workfile", "tyflow", "tycache", "tyspline"}:
"workfile", "tyflow", "tycache", "tyspline", "tyflow_vdb"}:
self.log.debug(
"Skipping Collecting Members for workfile "
"and tyflow product type."
Expand Down
Loading