# -*- coding: utf-8 -*-
"""
The base_component provides the base class, ART_Component, from which all components are derived.
"""
import inspect
import os
from abc import abstractmethod, ABCMeta
import pymel.core as pm
import artv2_utilities.error_utilities as errors
import artv2_utilities.general_utilities as utils
import artv2_utilities.component_utilities as component_utils
from aim_helper import AimHelper
from joint_mover import JointMover
# noinspection PyPep8Naming
[docs]class ART_Component(object):
"""
Base class for defining a component (arm, leg, torso, single joint, etc) in ARTv2. All components should inherit
from this class.
When creating a new component, here is a list of common attributes and functions you will want or need to override.
+------------+------------------------+----------------------------------------------------------------------------+
| Type | Name | Description |
+============+========================+============================================================================+
| attribute | nice_name | The string nice name of the component (as it will appear in UIs) |
+------------+------------------------+----------------------------------------------------------------------------+
| attribute | base_name | The string base name of the component. This is a simple string used to |
| | | identify the component type and append onto nodes created by the component.|
| | | For example, on a leg component, the base_name is simply: "leg" |
+------------+------------------------+----------------------------------------------------------------------------+
| attribute | has_sides | Bool for whether or not the component supports different sides,like a leg |
| | | or arm do. |
+------------+------------------------+----------------------------------------------------------------------------+
| attribute | joint_mover_file | The relative path from the ARTv2 directory to the joint mover file of the |
| | | component. For example, self.joint_mover_file = |
| | | "resources/rigging_guides/root.ma" |
+------------+------------------------+----------------------------------------------------------------------------+
| function | _add_metadata | This method adds attributes you want to keep track of that are unique to |
| | | your component. Make sure that you still call on the base class's |
| | | _add_metadata method to setup the required attributes. |
+------------+------------------------+----------------------------------------------------------------------------+
"""
__metaclass__ = ABCMeta
nice_name = ""
base_name = None
has_sides = False
up = pm.upAxis(q=True, ax=True)
joint_mover_file = "replace_me_with_valid_path.ma"
@abstractmethod
def __init__(self, prefix="", suffix="", network_node=None):
if network_node is not None:
network_node = pm.PyNode(network_node)
prefix = component_utils.validate_prefix(network_node, prefix)
suffix = component_utils.validate_suffix(network_node, suffix)
if network_node is None:
self._unique_name = prefix + self.base_name + suffix
self._create_component(prefix, suffix)
self.joint_mover = JointMover(self.joint_mover_file, self.network_node)
self.joint_mover.add_joint_mover()
else:
if pm.objExists(network_node):
network_node = pm.PyNode(network_node)
prefix = network_node.prefix.get()
suffix = network_node.suffix.get()
self._unique_name = prefix + self.base_name + suffix
self.joint_mover = JointMover(self.joint_mover_file, network_node)
self.prefix = prefix
self.suffix = suffix
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
[docs] def _create_component(self, prefix, suffix):
"""
Method for adding the component to the Maya scene. Calls on various functions to accomplish.
"""
character_node = component_utils.get_rig_asset_node()
network_node = self._create_network_node()
self._add_metadata(network_node, prefix, suffix)
character_node.components.connect(network_node.asset)
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
[docs] def _create_network_node(self):
"""
Creates the metanode that will store metadata for the component.
:return: Returns the metanode.
:rtype: PyNode
"""
existing_nodes = component_utils.return_all_components()
for node in existing_nodes:
if node.componentName.get() == self._unique_name:
errors.raise_error("This component instance has already been created! Aborting.")
return
network_node = pm.shadingNode("network", name=self._unique_name + "_metadata", asUtility=True)
network_node.lock()
return network_node
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
def change_num_joints(self, new_value, attr_min, attr_max, attr_name, base_string):
"""
Handles logic for when changing number of joints on a component. For example, changing the number of calf
twists or index fingers, etc.
:param new_value: The new number for the property (num_calf_twists = 2)
:param attr_min: The minimum amount for that property.
:param attr_max: The maximum amount for that property.
:param attr_name: The name of the attribute on the network node that represents that property.
:param base_string: The string to search for when looking for joints or other nodes.
"""
if (attr_min - 1) <= new_value <= attr_max:
utils.set_attribute(self.network_node, attr_name, new_value)
for i in range(attr_min, attr_max):
joint = self.prefix + base_string + str(i + 1) + self.suffix
group = pm.PyNode(self._unique_name + "_" + base_string + str(i + 1) + "_mover_grp")
self.joint_mover.toggle_build_joint(joint, group, False)
if i + 1 <= new_value:
self.joint_mover.toggle_build_joint(joint, group)
else:
errors.raise_error("Invalid number for property: {0}. "
"Integer must be between {1} and {2}.".format(attr_name, attr_min, attr_max))
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
@property
def parent(self):
"""
This property holds the joint name of the parent of this component.
"""
return self.network_node.parentComponentBone.get()
@parent.setter
def parent(self, new_parent):
all_joints = component_utils.get_all_created_joints()
if new_parent in all_joints:
utils.set_attribute(self.network_node, "parentComponentBone", new_parent, "string")
self.joint_mover.set_mover_parent(new_parent)
else:
errors.raise_error("Invalid parent: {0}! No component creates such a joint.".format(new_parent))
return
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
@property
def prefix(self):
"""
This property holds the component's prefix (optional)
:return: Returns the component's prefix.
:rtype: str
"""
return self.network_node.prefix.get()
@prefix.setter
def prefix(self, new_prefix):
valid_prefix = component_utils.validate_prefix(self.network_node, new_prefix)
if valid_prefix is not False:
old_prefix = self.prefix
suffix = self.suffix
utils.set_attribute(self.network_node, "prefix", valid_prefix, "string")
utils.set_attribute(self.network_node, "componentName", valid_prefix + self.base_name + suffix, "string")
self._unique_name = valid_prefix + self.base_name + suffix
pynode = pm.PyNode(self.network_node)
utils.rename_object(pynode, self._unique_name + "_metadata")
containers = self.joint_mover.get_containers()
utils.rename_object(containers[0], self._unique_name + "_component")
utils.rename_object(containers[1], self._unique_name + "_mover_guides")
utils.rename_object(containers[2], self._unique_name + "_mover_utilities")
self.joint_mover.metanode = pm.PyNode(self._unique_name + "_metadata")
self.joint_mover.rename_movers(old_prefix, self.suffix)
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
@property
def suffix(self):
"""
This property holds the component's suffix (optional)
:return: Returns the component's suffix.
:rtype: str
"""
return self.network_node.suffix.get()
@suffix.setter
def suffix(self, new_suffix):
valid_suffix = component_utils.validate_suffix(self.network_node, new_suffix)
if valid_suffix is not False:
old_suffix = self.suffix
prefix = self.prefix
utils.set_attribute(self.network_node, "suffix", valid_suffix, "string")
utils.set_attribute(self.network_node, "componentName", prefix + self.base_name + valid_suffix, "string")
self._unique_name = prefix + self.base_name + valid_suffix
pynode = pm.PyNode(self.network_node)
utils.rename_object(pynode, self._unique_name + "_metadata")
containers = self.joint_mover.get_containers()
utils.rename_object(containers[0], self._unique_name + "_component")
utils.rename_object(containers[1], self._unique_name + "_mover_guides")
utils.rename_object(containers[2], self._unique_name + "_mover_utilities")
self.joint_mover.metanode = pm.PyNode(self._unique_name + "_metadata")
self.joint_mover.rename_movers(self.prefix, old_suffix)
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
@property
def network_node(self):
"""
This property holds the network node that contains this component's metadata.
"""
return_node = None
components = component_utils.return_all_components()
for component in components:
if component.componentName.get() == self._unique_name:
return_node = component
break
if return_node is None:
errors.raise_error("ART_Component.network_node: No network node found.")
return
return pm.PyNode(return_node)