ayon-core/openpype/hosts/max/api/plugin.py
2023-08-03 16:55:26 +08:00

270 lines
8.4 KiB
Python

# -*- coding: utf-8 -*-
"""3dsmax specific Avalon/Pyblish plugin definitions."""
from abc import ABCMeta
import six
from pymxs import runtime as rt
from openpype.lib import BoolDef
from openpype.pipeline import CreatedInstance, Creator, CreatorError
from .lib import imprint, lsattr, read
MS_CUSTOM_ATTRIB = """attributes "openPypeData"
(
parameters main rollout:OPparams
(
all_handles type:#maxObjectTab tabSize:0 tabSizeVariable:on
sel_list type:#stringTab tabSize:0 tabSizeVariable:on
)
rollout OPparams "OP Parameters"
(
listbox list_node "Node References" items:#()
button button_add "Add to Container"
button button_del "Delete from Container"
fn node_to_name the_node =
(
handle = the_node.handle
obj_name = the_node.name
handle_name = obj_name + "<" + handle as string + ">"
return handle_name
)
fn nodes_to_add node =
(
sceneObjs = #()
if classOf node == Container do return false
n = node as string
for obj in Objects do
(
tmp_obj = obj as string
append sceneObjs tmp_obj
)
if sel_list != undefined do
(
for obj in sel_list do
(
idx = findItem sceneObjs obj
if idx do
(
deleteItem sceneObjs idx
)
)
)
idx = findItem sceneObjs n
if idx then return true else false
)
fn nodes_to_rmv node =
(
n = node as string
idx = findItem sel_list n
if idx then return true else false
)
on button_add pressed do
(
current_selection = selectByName title:"Select Objects to add to
the Container" buttontext:"Add" filter:nodes_to_add
if current_selection == undefined then return False
temp_arr = #()
i_node_arr = #()
for c in current_selection do
(
handle_name = node_to_name c
node_ref = NodeTransformMonitor node:c
idx = finditem list_node.items handle_name
if idx do (
continue
)
name = c as string
append temp_arr handle_name
append i_node_arr node_ref
append sel_list name
)
all_handles = join i_node_arr all_handles
list_node.items = join temp_arr list_node.items
)
on button_del pressed do
(
current_selection = selectByName title:"Select Objects to remove
from the Container" buttontext:"Remove" filter: nodes_to_rmv
if current_selection == undefined then return False
temp_arr = #()
i_node_arr = #()
new_i_node_arr = #()
new_temp_arr = #()
for c in current_selection do
(
node_ref = NodeTransformMonitor node:c as string
handle_name = node_to_name c
n = c as string
tmp_all_handles = #()
for i in all_handles do
(
tmp = i as string
append tmp_all_handles tmp
)
idx = finditem tmp_all_handles node_ref
if idx do
(
new_i_node_arr = DeleteItem all_handles idx
)
idx = finditem list_node.items handle_name
if idx do
(
new_temp_arr = DeleteItem list_node.items idx
)
idx = finditem sel_list n
if idx do
(
sel_list = DeleteItem sel_list idx
)
)
all_handles = join i_node_arr new_i_node_arr
list_node.items = join temp_arr new_temp_arr
)
on OPparams open do
(
if all_handles.count != 0 then
(
temp_arr = #()
for x in all_handles do
(
handle_name = node_to_name x.node
append temp_arr handle_name
)
list_node.items = temp_arr
)
)
)
)"""
class OpenPypeCreatorError(CreatorError):
pass
class MaxCreatorBase(object):
@staticmethod
def cache_subsets(shared_data):
if shared_data.get("max_cached_subsets") is not None:
return shared_data
shared_data["max_cached_subsets"] = {}
cached_instances = lsattr("id", "pyblish.avalon.instance")
for i in cached_instances:
creator_id = rt.GetUserProp(i, "creator_identifier")
if creator_id not in shared_data["max_cached_subsets"]:
shared_data["max_cached_subsets"][creator_id] = [i.name]
else:
shared_data[
"max_cached_subsets"][creator_id].append(i.name)
return shared_data
@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 isinstance(node, str):
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
@six.add_metaclass(ABCMeta)
class MaxCreator(Creator, MaxCreatorBase):
selected_nodes = []
def create(self, subset_name, instance_data, pre_create_data):
if pre_create_data.get("use_selection"):
self.selected_nodes = rt.GetCurrentSelection()
instance_node = self.create_instance_node(subset_name)
instance_data["instance_node"] = instance_node.name
instance = CreatedInstance(
self.family,
subset_name,
instance_data,
self
)
if pre_create_data.get("use_selection"):
node_list = []
for i in self.selected_nodes:
node_ref = rt.NodeTransformMonitor(node=i)
node_list.append(node_ref)
# Setting the property
rt.setProperty(
instance_node.modifiers[0].openPypeData,
"all_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_subsets(self.collection_shared_data)
for instance in self.collection_shared_data["max_cached_subsets"].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
}
imprint(
instance_node,
new_values,
)
def remove_instances(self, instances):
"""Remove specified instance from the scene.
This is only removing `id` parameter 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)
def get_pre_create_attr_defs(self):
return [
BoolDef("use_selection", label="Use selection")
]