# -*- 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 ) 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 ) on button_add pressed do ( current_selection = selectByName title:"Select Objects to add to the Container" buttontext:"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 append temp_arr handle_name append i_node_arr node_ref ) 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" if current_selection == undefined then return False temp_arr = #() i_node_arr = #() new_i_node_arr = #() for c in current_selection do ( node_ref = NodeTransformMonitor node:c handle_name = node_to_name c idx = finditem all_handles node_ref if idx do ( DeleteItem all_nodes idx for i in all_nodes do append new_i_node_arr i ) idx = finditem list_node.items handle_name if idx do ( DeleteItem list_node.items idx ) ) all_handles = new_i_node_arr list_node.items = 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"): 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) rt.custAttributes.add(node.baseObject, 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.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: if instance_node := rt.GetNodeByName(instance.data.get("instance_node")): # noqa count = rt.custAttributes.count(instance_node) rt.custAttributes.delete(instance_node, 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") ]