aboutsummaryrefslogtreecommitdiff
path: root/docs/source/sections/tech_docs/components/creating_custom_component.rst
blob: 7c12643e8b67bcea4c95a9ef1f5ec6d36187d00b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
===========================
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.