=========================== Creating Your Own Component =========================== .. codeauthor:: Jeremy Ernst In order to create your own component, there are two things you need: a python file for the class, and a maya ascii file that defines the joints that component can create. Creating the Maya File ---------------------- This file is generally referred to as the 'joint mover file'. In a new Maya scene, simply create the joints you want your component to create. If your component has options like number of twist joints, create the maximum configuration in this Maya scene. The exception to this are components where we want to insert joints into the hierarchy, like the chain and spine components. With these types of components, I create the minimum configuration. Let's compare what the joint mover files for a biped leg and a chain look like: .. figure:: /images/joint_mover_compare.png :width: 404px :align: center :height: 256px :figclass: align-center As you can see, the leg has the maximum configuration and the spine has the minimum configuration. How those configurations change is defined in the properties of the python class. You'll also notice that some default names have been given to each joint. In the leg, for example, I don't want the twist joints to be available by default (their property will be set to 0), so I will hide those joints. Let's build a component as a demonstration. In a new Maya scene, I will create a basic hinge setup for an arm and name the joints shoulder, elbow, and wrist. Make sure the rotations are frozen and the joint orients are clean. .. image:: /images/joint_mover_demo_01.png Now, I will add three twist joints to the shoulder, naming them shoulder_twist_01, shoulder_twist_02, and shoulder_twist_03. These are parented under the shoulder. Because I don't want them to be there by default, I will hide them. .. image:: /images/joint_mover_demo_01.gif The last thing we need to do with the maya scene is markup these joints with some attributes for how the joint mover rig will be created. .. _joint-mover-markup-ref: Joint Mover Markup Tool ----------------------- Under the ART v2 menu, in the Development sub-menu, click on the Joint Mover Markup menu item. .. image:: /images/joint_mover_markup_01.png Once the interface is displayed, clicking on 'Markup Joints' will put attributes on all of the joints in the scene. Let's talk about each of these attributes and what they do. .. figure:: /images/joint_mover_markup_02.png :width: 263px :align: center :height: 348px :figclass: align-center The markup attributes that were created can be seen here. .. glossary:: Can Aim Whether this joint should aim at another joint when aim mode is turned on. Aim Joint Which joint this joint should aim at when aim mode is turned on. (This is ignored if Can Aim is False) Aim Axis Which axis represents the aim axis. (This is ignored if Can Aim is False) Invert Aim Axis If the aim axis should be inverted. (If your aim axis is set to X, but needs to be -X, this would be True) (This is ignored if Can Aim is False) Up Axis The axis of the joint that is closes to the world up axis. (This is ignored if Can Aim is False) Maintain Offset This will probably not ever need to be used, but in the case of some special circumstance, this will create the aim constraint while maintaining offsets. (This is ignored if Can Aim is False) Twist Joint Whether the joint is to be setup as a twist joint. A twist joint gets no global mover control. Instead it gets an offset mover that is only unlocked along the length axis, and is automatically driven to keep equal spacing between the start joint and end joint (calf and foot for example). Control Type The shape that the joint mover control should have. This list is populated by the files located in /resources/control_shapes. Control Size The scale factor to create the control at. If you use the "Create Preview Movers" button in the markup tool, you can then set this value in order to see how the control will be built and at what size. Control Offset X, Y, Z When creating the control, the rotational offset to apply on creation. You can use the "Create Preview Movers" button to see how the control will be created given your offsets. So in this case, let's set shoulder and elbow's ".canAim" to True, then set the shoulder's aim joint to elbow, and the elbow's to wrist. The up axis for both of those will be Z. I'll set all three of those joints to use a circle shape and set the size to 10. For the three twist joints, set their ".twistJoint" attribute to True, and let's put their control size at 8. You can use the "Create Preview Movers" and "Delete Preview Movers" to see how your controls will be created given these settings. .. figure:: /images/joint_mover_markup_03.png :width: 265px :align: center :height: 348px :figclass: align-center The shoulder with markup data set. .. note:: Create Preview Movers works on a selection! Make sure any preview movers have been deleted using the "Delete Preview Movers" and save the scene in: ARTv2/resources/rigging_guides as a maya ascii file. Creating the Python Class ------------------------- Create a new python file in artv2/components. In this file, add an import for the base component: .. code-block:: python import artv2.components.base_components.base_component as base Now create your class and inherit from base. There are some attributes you need to add to the class (above any constructor) and fill out their values. These are the attributes and what they do: +------------+------------------------+----------------------------------------------------------------------------+ | Type | Name | Description | +============+========================+============================================================================+ | attribute | nice_name | (string) The nice name of the component (as it will appear in UIs) | +------------+------------------------+----------------------------------------------------------------------------+ | attribute | category | (string) The name of the category where this component will show up in the | | | | user interface. For example: "Limbs", "Primitives", etc. | +------------+------------------------+----------------------------------------------------------------------------+ | 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 | can_overwrite_names | Bool for whether or not the user can overwrite the names of the joints of | | | | this component. | +------------+------------------------+----------------------------------------------------------------------------+ | 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" | +------------+------------------------+----------------------------------------------------------------------------+ | attribute | mirror_table | A dictionary of translate and rotate attributes with their multiplier for | | | | mirroring values to mirrored controls. | +------------+------------------------+----------------------------------------------------------------------------+ At this point, your class should look something like this: .. code-block:: python import artv2.components.base_components.base_component as base class MyComponent(base.ART_Component): nice_name = "Test" category = "Primitives" base_name = "test" has_sides = True can_overwrite_names = True joint_mover_file = "resources\\rigging_guides\\test.ma" mirror_table = {"translateX": -1, "translateY": -1, "translateZ": -1, "rotateX": 1, "rotateY": 1, "rotateZ": 1} Usually, you will not need to implement an __init__ method, since the base class should take care of anything you need there, but if not, you would want to implement that, and make sure you call on the base class's constructor as well. The main method you need to implement is _add_metadata. This method adds attributes to the network node of the component that coincide with properties on the class. For example, in our test, we had three twist joints, so I could add a property called num_twist_joints. There are already some methods in the base class that will help with the property setter implementation ( we'll go over that in a minute). Because we will be adding this property, we also want to add an attribute on the network node for num_twist_joints. So in this example, here is what our _add_metadata method would look like: .. code-block:: python def _add_metadata(self, network_node, prefix, suffix): # call on the base class's method to get the default attributes first, then unlock the network node and add our # own. Remember to re-lock the network node after! super(MyComponent, self)._add_metadata(network_node, prefix, suffix) network_node.unlock() # notice how the minimum and maximum are set to match our configuration on the maya file. Since our twist joints # are hidden by default, the default value is 0. network_node.addAttr("num_twist_joints", min=0, max=3, dv=0, keyable=False) network_node.num_upperarm_twists.set(lock=True) network_node.lock() Now let's define that property that coincides with our attribute. .. code-block:: python @property def num_twist_joints(self): # the getter of our property will just read the value on the network node of the same attribute! return self.network_node.num_twist_joints.get() Our property setter is where the implementation of any configuration changes happens. The base class already has a method for dealing with this situation, so our setter is pretty simple here: .. code-block:: python @num_upperarm_twists.setter def num_upperarm_twists(self, new_number): # here we pass in the attribute, the new number, the min and max, and a keyword to search for on the joints. # because our joints are named shoulder_twist_01, etc, we can pass in the key _shoulder_twist_0 to find any # relevant twist joints. self.set_twist_joints("num_twist_joints", new_number, 0, 3, "_shoulder_twist_0") .. note:: To see a more complex implementation of a property setter, check out the chain component's num_joints property. At this point, you should be able to reload the scripts (or restart Maya) and your component should be in the UI, and properly load into the scene, creating a joint mover rig, and a widget for changing your properties.