def resetSkinnedJoints(joints):
""" Reset skin deformation for selected joint(s) """
for joint in joints:
# Get connected skinCluster nodes
skinClusterPlugs = mc.listConnections(joint + ".worldMatrix[0]", type="skinCluster", p=1)
if skinClusterPlugs:
# for each skinCluster connection
for skinClstPlug in skinClusterPlugs:
index = skinClstPlug[ skinClstPlug.index("[")+1 : -1 ]
skinCluster = skinClstPlug[ : skinClstPlug.index(".") ]
curInvMat = mc.getAttr(joint + ".worldInverseMatrix")
mc.setAttr( skinCluster + ".bindPreMatrix[{0}]".format(index), type="matrix", *curInvMat )
else:
print "No skinCluster attached to {0}".format(joint)
16 September, 2012
Resetting skin deformation for joints
There are times when you need to update the bind transforms for joints after applying the skinCluster deformer. Maya provides a tool called "Move Skinned Joints Tool" for doing the same. It works great if you want to move joints that are not constrained. But if you want to move a skinned joint that is constrained to a surface this tool does not work well. Here is a small function that will reset skin deformation for given skinned joints. First move/rotate all the skinned joints in desired position then run this function. This will reset the skinned mesh to its non-deformed state while keeping the joints in place.
09 September, 2012
Muscle using single chain stretchy IK
One way to create a simplified muscle movement is to have two joints in a single chain act as origin and insertion points. These joints are orient constrained such that they always aim at each other in a fixed axis. So when we move either of the joints the chain should stretch/squash and orient themselves pointing at each other. One of the ways to achieve this is using a single chain IK solver. Here is a script to do that.
def makeSingleChainStretchyIK(prefix, joint1, joint2, settings=None):
ikHandle, effector = mc.ikHandle(n=prefix+"_ikHandle", sj=joint1, ee=joint2, sol="ikSCsolver")
distNode = mc.createNode("distanceBetween", n=prefix+"_jntDist")
mc.connectAttr(joint1+".translate", distNode+".point1")
mc.connectAttr(ikHandle+".translate", distNode+".point2")
mc.connectAttr(distNode+".distance", joint2+".tx")
06 September, 2012
Bottom-up rigging approach & Decoupling rigs
The general approach for rigging a character is to start building
skeleton, define how it will move (control system) and then dive into
deformation(enveloping process). This approach assumes that the target
bind skeleton driven by the control rig is going to produce good
deformation. Usually one should do some deformation tests first to make
sure bind skeleton would deform the character nicely and then build the
control rig. However if this is not done thoughtfully then you could get
stuck with a rig that does not deform well. Of course we have tools at
our disposal to correct the deformation if required, however this could
significantly lengthen the skinning process and make it tedious.
Another approach is to start thinking about deformation and do the skinning first then build its control rig. I am working on a realistic character model for studying deformation based on musculature. Hence the focus is on getting the deformation looking as good as possible. The first stage is to built the bind skeleton and add most of the secondary deformation joints along with the enveloping process. This should give a better idea of how the skeleton would deform the mesh. We can go on refining the bind skeleton and deformation weights until we get a solid base. My goal here is actually go get the skinning closer to the final look and hook up the control rig afterwards that is created based on the bind skeleton.
Regardless of the approach we take, the goal should be to be able to focus on both deformation and control rigs while keeping them independent processes. By keeping control rig and deformation rig separate we can focus on one task without worrying about the other and work on them in parallel. Generally there will be some dependency between both rigs, for example matching the pivots for the movement and deformation or a joint driven by a surface that is deforming the mesh . But we can still decouple both the systems to a good extent. Also, it is possible to keep the pose based deformation independent of control rig reducing the coupling further.
In programming it's a good practice to keep the objects of the system independent so that changing one does not require to change the others. This is highly applicable to Rigging as well. If you are more into programming you should read Design Patterns by the Gang of Four. It's an excellent read.
Another approach is to start thinking about deformation and do the skinning first then build its control rig. I am working on a realistic character model for studying deformation based on musculature. Hence the focus is on getting the deformation looking as good as possible. The first stage is to built the bind skeleton and add most of the secondary deformation joints along with the enveloping process. This should give a better idea of how the skeleton would deform the mesh. We can go on refining the bind skeleton and deformation weights until we get a solid base. My goal here is actually go get the skinning closer to the final look and hook up the control rig afterwards that is created based on the bind skeleton.
Regardless of the approach we take, the goal should be to be able to focus on both deformation and control rigs while keeping them independent processes. By keeping control rig and deformation rig separate we can focus on one task without worrying about the other and work on them in parallel. Generally there will be some dependency between both rigs, for example matching the pivots for the movement and deformation or a joint driven by a surface that is deforming the mesh . But we can still decouple both the systems to a good extent. Also, it is possible to keep the pose based deformation independent of control rig reducing the coupling further.
In programming it's a good practice to keep the objects of the system independent so that changing one does not require to change the others. This is highly applicable to Rigging as well. If you are more into programming you should read Design Patterns by the Gang of Four. It's an excellent read.
24 June, 2012
GroupConstraint Intro Demo!
Finally, here is a demo of the dynamic multi-way constraint system that I have been working on.
Following are some of the features of this constraint system:
Your feedback is welcome! :)
GroupConstraint Intro Demo from Maulik Kataria on Vimeo.
Following are some of the features of this constraint system:
- Multi-way constraint allows invertible relationship without cycle issues.
- The driver of the system can be animated.
- Only one keyframe required for switching the driver.
- All the follower nodes can be animated while constrained by driver of the system.
- Auto matching for drivers at switching frames to avoid popping nodes effect.
- Current driver indicator.
Your feedback is welcome! :)
GroupConstraint Intro Demo from Maulik Kataria on Vimeo.
22 May, 2012
No-pop implementation challenges
One of the key features of the constraint system I am developing is auto correction of "pop effect" when animation changes. This problem is quite complex due to a lot of different scenarios involved and based on how Maya architecture is designed.
It is very easy to calculate offset and store it when a constraint switches between different spaces to avoid pop. That's the general idea, however maintaining this offset when animation is constantly changing is difficult due to some of the reasons mentioned below:
Below is a simple example:
We have two objects A(bluish) and B(reddish). They are animated as shown in the image below.
Questions:
Think of the following scenarios after switching at frame 3:
As for the status of GroupConstraint, the basics work quite nice with the animation. The problem arises when the animation changes creating a pop. The constraint handles pop for certain cases, but not all. I have finished writing logic for detecting keyframe changes and checking if it affects the constraint output or not based on graph connections and also DAG hierarchy. I also have all the functions ready to update offset information at any switch keyframe. The last (hopefully last) part now is to make sure that the auto-pop correction works when changing the existing animation in different cases. For me, I have to look at few more scenarios since I am also working hard to make switching possible with only one keyframe instead of two consecutive keyframes. And the possibility of animating the followers independently while driven by the driver of the system also adds to the difficulty.
It is very easy to calculate offset and store it when a constraint switches between different spaces to avoid pop. That's the general idea, however maintaining this offset when animation is constantly changing is difficult due to some of the reasons mentioned below:
- User can update keys in the graph editor at any time value, this does not cause the compute since you might be on a frame that is not related to the changes. Hence we have to detect this as animation callback.
- Maya's animation callbacks are not much useful in giving details about which keyframe was deleted so in this case we have to update all the key offsets from the beginning to make sure we resolve pop. Because deleting a driver key means that space will change and hence the offset at the next nearest keyframe.
- A space can be animated not only by its own transform but any parent in the hierarchy or a constraint. The constraint driver(master) itself might be animated by a transform up in the hierarchy.
- This means that when animation for a space changes in the scene we have to first detect if the animation affects the constraint. In addition to using generic animation callbacks we also have to look at the input/output graph and also the hierarchy to detect if our constraint is affected.
- What if drivers are not keyed exactly on the switching keyframes? This means that if I update animation curve handles it will update the driver transform at the switch and hence creating a pop.
- One change in the earlier keyframe can cause update of offsets in all the subsequent keyframes to remove pop effect. To remove the pop we need to update drivers by either updating offset or adding extra transform on the affected switching keys, however that could mean that the offset/positioning in next switching frame is invalidated and will create a pop. This can lead to a cascading effect, creating a pop in each next frame when fixing the current one. This is true in my case especially since all the driven nodes are free to move while constrained to the driver of the system. I have some ideas to avoid this cascading effect, but I still need to test them first.
- To remove the pop we can match the current driver and previous drivers of the system at switching keyframe, but there are few tricky situations to look for:
- What if both, current and previous, drivers are updated at the switch? Which one should be matched to the other one?
- What if a driver has incoming connections and its transform cannot be keyed directly?
Below is a simple example:
We have two objects A(bluish) and B(reddish). They are animated as shown in the image below.
Questions:
Think of the following scenarios after switching at frame 3:
- If B, the current driver, moves at frame 3
This means that A will move as well since B is the current driver - If A, the previous driver, moves at frame 3
In this case B does not move since A is not the current driver
As for the status of GroupConstraint, the basics work quite nice with the animation. The problem arises when the animation changes creating a pop. The constraint handles pop for certain cases, but not all. I have finished writing logic for detecting keyframe changes and checking if it affects the constraint output or not based on graph connections and also DAG hierarchy. I also have all the functions ready to update offset information at any switch keyframe. The last (hopefully last) part now is to make sure that the auto-pop correction works when changing the existing animation in different cases. For me, I have to look at few more scenarios since I am also working hard to make switching possible with only one keyframe instead of two consecutive keyframes. And the possibility of animating the followers independently while driven by the driver of the system also adds to the difficulty.
10 March, 2012
Group Constraint Update
It has been a great learning experience while building this constraint system. As I posted before, I have been working on a constraint that allows dynamic relationship between a group of transforms. Hence I have called this a "Group Constraint". I think this constraint will be very useful in building rigs where the driver in the group needs to be switched during animation. One such example could be a foot rig where multiple contact points are required. This constraint should allow bi-directionality(or multi-way directionality) so that between two or more transforms anyone can be the driver at any time during animation.
First step for me was to find out how to calculate the final output when switching driver transform in the group. I was suspecting that I would need some complex calculations, however after doing some research and learning a great deal about visualizing matrix multiplication I found that I just needed a simple calculation. Having a proper structure for attribute also helped in simplifying the calculations. First I used python to experiment and confirm that my logic would work the way I want and then I started building plugin using C++. I finished the basics first, building input-output attribtue structure, using setDependentsDirty to establish relationship and reading the attributes from DataBlock. When I tested this code I always got zero values in all the attributes in compute method. Later I found what the problem was and how to fix it (read about it Here).
Now I have got basic switching between drivers working. And during the switch all the offsets maintained. One of the advantages of the method that I am using is that all the followers are free to move while being controlled by the driver node. This allows for more freedom in animating. My main goal is to make this system such that there shall be no pop when switching between drivers, even when animation keys are updated. I am also looking for a way to avoid having to key driver attribute twice in consecutive frames to make a switch. Enum attribute type allows such stepped keys as default behavior, however the challenge is to calculate the output at this switch point. The way Group Constraint stores the offsets and how final output is calculated should allow for such feature, but I have to test it to see if it works in all cases.
The biggest challenge and what will actually be the backbone for many of the features is linked with detecting animation events. Exo Switch Constraint also utilizes callback functions to detect keying on the driver (master) node. However, I intend to use it for different purposes and it will allow me to correct any pop at the switching points when animation is updated. There are two options available in Maya to listen on animation update events:
I haven't gotten around to create a demo of the first stage yet. But I would really like to share it some time soon to get some feedback. Let me know if you guys have any particular feature in mind for such constraint.
First step for me was to find out how to calculate the final output when switching driver transform in the group. I was suspecting that I would need some complex calculations, however after doing some research and learning a great deal about visualizing matrix multiplication I found that I just needed a simple calculation. Having a proper structure for attribute also helped in simplifying the calculations. First I used python to experiment and confirm that my logic would work the way I want and then I started building plugin using C++. I finished the basics first, building input-output attribtue structure, using setDependentsDirty to establish relationship and reading the attributes from DataBlock. When I tested this code I always got zero values in all the attributes in compute method. Later I found what the problem was and how to fix it (read about it Here).
Now I have got basic switching between drivers working. And during the switch all the offsets maintained. One of the advantages of the method that I am using is that all the followers are free to move while being controlled by the driver node. This allows for more freedom in animating. My main goal is to make this system such that there shall be no pop when switching between drivers, even when animation keys are updated. I am also looking for a way to avoid having to key driver attribute twice in consecutive frames to make a switch. Enum attribute type allows such stepped keys as default behavior, however the challenge is to calculate the output at this switch point. The way Group Constraint stores the offsets and how final output is calculated should allow for such feature, but I have to test it to see if it works in all cases.
The biggest challenge and what will actually be the backbone for many of the features is linked with detecting animation events. Exo Switch Constraint also utilizes callback functions to detect keying on the driver (master) node. However, I intend to use it for different purposes and it will allow me to correct any pop at the switching points when animation is updated. There are two options available in Maya to listen on animation update events:
- Listen for changes in all animation curves and in the callback check if animation change is related to the constraint node.
- Listen for only particular animation curves that are linked to input transforms of the constraint.
I haven't gotten around to create a demo of the first stage yet. But I would really like to share it some time soon to get some feedback. Let me know if you guys have any particular feature in mind for such constraint.
25 January, 2012
Notes: Building a multi-way (group) constraint
I have been highly inspired by the work that Andrea Maiolo and Tim Naylor did on multi-way constraint system, particularly what they presented at Siggraph 2006 on "Bi-Directional Constraining". I think I first saw their Siggraph work when I was at school in 2008 and that's what got me into understanding Maya's DG and matrix mathematics for 3D transformations. Since then I have tried to solve this kind of constraint system from time to time and I always got stuck as I didn't have enough knowledge at that time. Between 2008 and 2011 I have learned many things by challenging myself to new problems and also from inspirational work of talented technical artists. So I have picked up this project again and I think I have made some progress compared to my previous failed attempts. However, the progress it not hurdle free. Here are some notes on the issues I have encountered.
Still a long way to go for finishing a working multi-way constraint. I always get more hopeful when I solve a problem on the way. But I think I should look forward to the next problems on my path and be ready to challenge my small brain for some exercise :)
The challenges:
The first challenge we encounter when building a two-way constraint is the cyclic dependency. However, that's not really the main and only issue. It is possible to create connections and do calculations in such a way that we can avoid this cyclic dependency. The real challenge is that we can't really have true Bi-directional constraint (I say true for lack of a better word). When I started, my idea of a bi-directional constraint was that both nodes involved are the masters and both nodes affect each other at the same time without any switching. Based on this definition the main question is how to interpolate from one state to the other when both objects are moving each other. This leads us to another question, what should be the sequence of operations when calculating for the goal state? This is an important question and more so because the final state is the sum of transformations of both nodes.
Sequence dependency:
One of the most important properties of rotational transformations is that the operations are non-commutative. We already know this based on what we know about rotation orders. Let's take an example. Let's say we have two transform nodes A and B in their initial state set apart by some distance with zero rotations on both. Let's assume that both A and B affect each other (bi-directionality). Now we apply 90 degree rotation in z-axis on both nodes. Now based on the sequence of operations we get different locations in the end result as illustrated below.
Solution?
Main problem here is that we are trying to treat both nodes A and B as masters at the same time. If we consider only one node as the master at a given point of time, we can avoid the problem of sequence dependency since only one node affects the others at a given time. ExoSwitch constraint uses a concept of driver nodes and driven nodes. Using this concept, we assign one driver for the constraint system at a given point of time to drive all the other nodes. So ExoSwitch constraint does not have a problem of finding the right sequence at a single point of time.
Alternatives?
I can't think of any simple way to record a sequence in which a user is manipulating the nodes involved in a multi-way constraint. However, I think it should be possible to implement a system where all the nodes are treated as masters (or drivers) at the same time. One idea would be to have some kind of iteration based solver that calculates the interpolation to reach the goal state when all the nodes are driving one another. Maya's FBIK comes to my mind, but it seems that it takes a bit different approach. This approach is to solve the system when user moves one of the nodes(effectors) and update all the nodes with their final coordinates. When you animate these coordinates on effectors, each node is interpolated independently. Even though it works for FBIK, this behavior is not quite desirable for a multi-way constraint system. Still a long way to go for finishing a working multi-way constraint. I always get more hopeful when I solve a problem on the way. But I think I should look forward to the next problems on my path and be ready to challenge my small brain for some exercise :)
12 January, 2012
Mysterious "geometry" attribute and Geometry Constraint
Geometry constraint is a bit special because it does not connect its output to transform attributes of a constrained node allowing us to key them. Out of curiosity I looked at the connections in hyper graph and I see that output of the constraint node is connected to "geometry" attribute of the constrained node (any transform node). One would think that this attribute would have something to do with geometric data for display or deformation. However, the doc says "Geometry attribute used for positional constraints". Strange!
Here is an example of this connection used by geometry constraint:
We know following things about this "geometry" attribute based on the documentation:
Based on above information let's do some experiments and see what happens.
So this makes me think that "geometry" attribute was added when geometry constraint was added. Who knows!
Here is an example of this connection used by geometry constraint:
locator1_geoConstraint.constraintGeometry -> locator1.geometry
We know following things about this "geometry" attribute based on the documentation:
- It's a generic type attribute (it takes nurbsCurve, nurbsSurface, mesh etc. as input)
- It affects translation attribute of a transform node (we can use it to control position)
Based on above information let's do some experiments and see what happens.
- Create a nurbs sphere
- Create a locator
- Connect nurbsSphereShape1.worldSpace[0] -> locator1.geometry
So this makes me think that "geometry" attribute was added when geometry constraint was added. Who knows!
05 January, 2012
MPoint '=' assignment operator and float[3]
All 3d packages define a 3D point as (x,y,z,w). Last property 'w' is included based on how homogeneous coordinate system works. And this 'w' always needs to be 1 when we do calculations. Maya's implementation of this 3D point is done in MPoint class. This class also defines '=' operator which will copy the values of a float[3] to the given point. But it's really not a good idea to do the following.
The problem here is that Maya will assign values to x,y,z from myPt but not to 'w' leaving it 0. This is an invalid homogeneous coordinate as w=0 means the point is at infinity! So keep in mind that 'w' is important :)
float myPt[3] = {1,1,1};
MPoint mayaPt = myPt;
The problem here is that Maya will assign values to x,y,z from myPt but not to 'w' leaving it 0. This is an invalid homogeneous coordinate as w=0 means the point is at infinity! So keep in mind that 'w' is important :)
04 January, 2012
Freedom of a constrained node
When you constrain a transform node using Maya's constraints, it stops inheriting transformations from its top hierarchy. However, if you just connect (for example) translation attributes directly or using simple arithmetic nodes, the connected node continues to inherit transformations from its parent. The difference is because of how a constraint calculates the final output. Let's do a simple experiment.
The locator1 should be stuck to nurbsCircle1. Notice that locator1 does not move if you move its parent (group1). Now zero them out, so everything is on the origin. Then move group1 2 units in y-direction. Check the position values of locator1, it changed to (0,-2,0)! So the constraint recalculates position of locator1 to keep it locked to nurbsCircle1. To do this, constraint node uses transformations of nurbsCircle1 and group1(parent of locator1). And the position of locator1 is calculated by converting world-space coordinates of nurbsCircle1 to local coordinates under group1 (parent of the constrained node). This calculation uses 'worldInverseMatrix' of group1 which is the same as 'parentInverseMatrix' of locator1. To get more idea about this have a look at this nice article by Hamish Mckenzie.
Now let's change this behavior. Disconnect this connection, locator1.parentInverseMatrix[0] -> locator1_pointConstraint1.constraintParentInverseMatrix and move group1, you should see that locator1 is moving with its parent now! Both group1 and nurbsCircle1 should affect position of locator1. What happened here is that by disconnecting parentInverseMatrix we stopped constraint node from compensating for movement of group1(parent of constrained node). Or the space in which final coordinates for locator1 needs to be calculated is fixed and not affected by locator1's parent.
That explains the reason behind why after constraining a node it stops inheriting transform values from its top hierarchy.
To make things interesting, let's try the following:
What we did here is made point constraint consider group2 as the parent of locator1 instead of group1. So when constraint calculates position for locator1, it will compensate for group2, but not group1. This means that if you move group2, locator1 will not move but if you move group1 then locator1 will move!
Here is a diagram I put together that might be useful in understanding what I wrote. I have just repeated the same thing actually, but in different ways as I understood this.
- Create a nurbsCircle and a locator.
- Group the locator (group1->locator1).
- Now point constrain locator1 by nurbsCircle1 (keep offset option off).
The locator1 should be stuck to nurbsCircle1. Notice that locator1 does not move if you move its parent (group1). Now zero them out, so everything is on the origin. Then move group1 2 units in y-direction. Check the position values of locator1, it changed to (0,-2,0)! So the constraint recalculates position of locator1 to keep it locked to nurbsCircle1. To do this, constraint node uses transformations of nurbsCircle1 and group1(parent of locator1). And the position of locator1 is calculated by converting world-space coordinates of nurbsCircle1 to local coordinates under group1 (parent of the constrained node). This calculation uses 'worldInverseMatrix' of group1 which is the same as 'parentInverseMatrix' of locator1. To get more idea about this have a look at this nice article by Hamish Mckenzie.
Now let's change this behavior. Disconnect this connection, locator1.parentInverseMatrix[0] -> locator1_pointConstraint1.constraintParentInverseMatrix and move group1, you should see that locator1 is moving with its parent now! Both group1 and nurbsCircle1 should affect position of locator1. What happened here is that by disconnecting parentInverseMatrix we stopped constraint node from compensating for movement of group1(parent of constrained node). Or the space in which final coordinates for locator1 needs to be calculated is fixed and not affected by locator1's parent.
That explains the reason behind why after constraining a node it stops inheriting transform values from its top hierarchy.
To make things interesting, let's try the following:
- Zero out all the transforms.
- Group "group1" (we get group2->group1->locator1).
- connect "group2.worldInverseMatrix[0]" to "locator1_pointConstraint1.constraintParentInverseMatrix;"
What we did here is made point constraint consider group2 as the parent of locator1 instead of group1. So when constraint calculates position for locator1, it will compensate for group2, but not group1. This means that if you move group2, locator1 will not move but if you move group1 then locator1 will move!
Here is a diagram I put together that might be useful in understanding what I wrote. I have just repeated the same thing actually, but in different ways as I understood this.
20 December, 2011
Rigenerator: A Short Introduction
I am updating Rigenerator slowly, but steadily with exciting updates coming soon. Before I post any updates I thought it would be a good idea to post some introduction about this tool.
At a basic level Rigenerator is a tool to regenerate rigs in Maya. It allows you to gather information about selected rig by doing advanced traversal through Maya's scene graph. After gathering information and generating metadata the tool allows you to see what (dag & dg) nodes are part of your rig and then generate MA style code to reproduce the same rig in a single click. However, this is just the basic framework. We can build on top of this to add some really helpful and advanced features. The main features of Rigenerator are listed below.
The next module is closer to MA exporter plugin, but with more functionalities. There are few things about maya Ascii code that are not helpful if you want to execute it directly in Maya. For example, -ci (cache internally) flag in addAttr will generate warning. And the major problem comes with all keyframe nodes as mentioned here. Apparently, MA file uses attribute ".ktv" to set key value pairs which cannot be used outside saved MA file because that attribute is configured to work only when reading MA file by Maya. So there are some challenges that can only be solved iteratively as they are discovered. To solve such problems and to support the fourth feature on the list, I have started writing a module that will parse MA commands and based on rules a set of MA commands will be replaced by regular mel commands.
Mirroring a rig is another project. First I had to learn the logic behind mirroring transforms and orientation. Instead of using mel commands I decided to write functions to mirror transform values giving me the flexibility of playing with any numbers and not just maya nodes. For example, transformGeometry node stores frozen transform values in a matrix attribute ".txf". To decompose or mirror its value I need to feed this attribute to matrix class and use matrix methods. Mirroring logic is actually quite simple once you figure out the logic and math behind it. So I took some time to understand Transform matrix and vector math to be able to code them in python classes and then use them to mirror orientation. I will keep the details for another post. However, these are still very simple things compared to generalization of mirroring any rig. There are many challenges involved in generalized mirroring, some of which includes mirroring constraints based on how other objects are mirrored, using connection sequence to predict the mirroring of custom attributes etc. I believe that it's going to be very difficult to create a perfect solution that always mirrors the rig automatically as intended by the artist/rigger. However we should be able to provide a toolset and workflow to the users so they are able to specify inputs/settings and get the mirrored rig as they want.
Once I finish the brain tearing task of mirroring logic, I want to take Rigenerator further to convert MA style code into proper mel commands. For example if you create a constraint in the rig, MA code actually creates constraint by manually creating the node, setting all the attributes and connecting the constraint node to its input and output nodes. My goal is to convert all these MA commands into just a couple of lines of code by utilizing maya's built-in commands, e.g. pointConstraint, aimConstraint etc. Now that's actually very difficult as I have come to realize after thinking about the logic for a while (say around a year). The most difficult task is to come up with rules for each type of node and specifying logic for each node. The second part makes it very difficult to generalize the main logic. However, by iterative process if I am able to create a database of such rules and some toolset to make the rule/logic generation easier then it should be possible to accomplish this. What this means is that you actually don't have to write the code for your rig from scratch! You get a ready function with inputs to create your rig and you just need to optimize the code! I would love something like that and that's why I am very excited to build this functionality.
In summary, what I want to accomplish using Rigenerator is to allow riggers to focus more on inventing and experimenting in the rig creation process and not worry about reproducing it. Hopefully when the tool becomes stable enough I would like to release it for free and later make it open source when the code is a bit more cleaner (easily readable to others :) ). Stay tuned!
At a basic level Rigenerator is a tool to regenerate rigs in Maya. It allows you to gather information about selected rig by doing advanced traversal through Maya's scene graph. After gathering information and generating metadata the tool allows you to see what (dag & dg) nodes are part of your rig and then generate MA style code to reproduce the same rig in a single click. However, this is just the basic framework. We can build on top of this to add some really helpful and advanced features. The main features of Rigenerator are listed below.
- Extract the part of the rig
- Rename and regenerate the rig using MA style code
- Mirror the extracted rig
- Abstract the rig code into a generalized module
The next module is closer to MA exporter plugin, but with more functionalities. There are few things about maya Ascii code that are not helpful if you want to execute it directly in Maya. For example, -ci (cache internally) flag in addAttr will generate warning. And the major problem comes with all keyframe nodes as mentioned here. Apparently, MA file uses attribute ".ktv" to set key value pairs which cannot be used outside saved MA file because that attribute is configured to work only when reading MA file by Maya. So there are some challenges that can only be solved iteratively as they are discovered. To solve such problems and to support the fourth feature on the list, I have started writing a module that will parse MA commands and based on rules a set of MA commands will be replaced by regular mel commands.
Mirroring a rig is another project. First I had to learn the logic behind mirroring transforms and orientation. Instead of using mel commands I decided to write functions to mirror transform values giving me the flexibility of playing with any numbers and not just maya nodes. For example, transformGeometry node stores frozen transform values in a matrix attribute ".txf". To decompose or mirror its value I need to feed this attribute to matrix class and use matrix methods. Mirroring logic is actually quite simple once you figure out the logic and math behind it. So I took some time to understand Transform matrix and vector math to be able to code them in python classes and then use them to mirror orientation. I will keep the details for another post. However, these are still very simple things compared to generalization of mirroring any rig. There are many challenges involved in generalized mirroring, some of which includes mirroring constraints based on how other objects are mirrored, using connection sequence to predict the mirroring of custom attributes etc. I believe that it's going to be very difficult to create a perfect solution that always mirrors the rig automatically as intended by the artist/rigger. However we should be able to provide a toolset and workflow to the users so they are able to specify inputs/settings and get the mirrored rig as they want.
Once I finish the brain tearing task of mirroring logic, I want to take Rigenerator further to convert MA style code into proper mel commands. For example if you create a constraint in the rig, MA code actually creates constraint by manually creating the node, setting all the attributes and connecting the constraint node to its input and output nodes. My goal is to convert all these MA commands into just a couple of lines of code by utilizing maya's built-in commands, e.g. pointConstraint, aimConstraint etc. Now that's actually very difficult as I have come to realize after thinking about the logic for a while (say around a year). The most difficult task is to come up with rules for each type of node and specifying logic for each node. The second part makes it very difficult to generalize the main logic. However, by iterative process if I am able to create a database of such rules and some toolset to make the rule/logic generation easier then it should be possible to accomplish this. What this means is that you actually don't have to write the code for your rig from scratch! You get a ready function with inputs to create your rig and you just need to optimize the code! I would love something like that and that's why I am very excited to build this functionality.
In summary, what I want to accomplish using Rigenerator is to allow riggers to focus more on inventing and experimenting in the rig creation process and not worry about reproducing it. Hopefully when the tool becomes stable enough I would like to release it for free and later make it open source when the code is a bit more cleaner (easily readable to others :) ). Stay tuned!
10 December, 2011
Some python goodies
I love coding in python and every time I work with it I feel enthusiastic about this programming language. While coding it is also exciting to learn new features and figure out what you can do with it. Following are some useful snippets that I am using in my rigging framework.
1. Assign values from a list that is shorter or longer than the original list
I find this simple function handy since this allows me to assign values from a list that is shorter or longer than the target list. It acts like partial assignment, leaving elements intact in my original list if source list is shorter.
2. Overriding [] (indexing) in python for 2d indexing We know that we can use __getitem__ to assign an array like behavior (or make object iterable). But how about making object behave like 2 or 3 dimensional array? You can actually pass tuple as a key to __getitem__ and then use unpacking feature of python. I found the following code online on activestate recipe website.
3. Some great recipes on python website Some great code recipes and a very useful module. http://docs.python.org/library/itertools.html#recipes
1. Assign values from a list that is shorter or longer than the original list
I find this simple function handy since this allows me to assign values from a list that is shorter or longer than the target list. It acts like partial assignment, leaving elements intact in my original list if source list is shorter.
def assignValues(list1, list2):
mapFunc = lambda a,b: a if b is None else b
origLen = len(list1)
list1[:] = map(mapFunc, list1, list2)[:origLen]
tgtList = [1,2,3,4]
srcList = [0,0]
assignValues(tgtList, srcList)
2. Overriding [] (indexing) in python for 2d indexing We know that we can use __getitem__ to assign an array like behavior (or make object iterable). But how about making object behave like 2 or 3 dimensional array? You can actually pass tuple as a key to __getitem__ and then use unpacking feature of python. I found the following code online on activestate recipe website.
def __getitem__(self, (row, column)):
return self._data[row][column]
3. Some great recipes on python website Some great code recipes and a very useful module. http://docs.python.org/library/itertools.html#recipes
15 October, 2011
Eggroll setup update
I meant to post this for a while, but never got around to finish a demo of it. Inspired by Mike Best's eggroll setup, I wanted to do a similar setup because it was a good challenge. After my previously failed attempt I switched to a special matrix(found from google) to calculate rotation of the object around an axis using maya expression. This worked very well and I finally got the rolling working. However I still had some minor problem with small offset from the ground in some positions, it might be the pivot location that might be off. But more challenging is the sliding problem, though it's not very noticeable in the demo. I would like to come back to it later. Deformation of the egg should not be difficult to add to this setup as I am already counting in the bounding box volume in nodes calculation.
Later Mike was kind enough to share the script which gave me insight into his setup. Mine is not as good as his, but I was happy to get the egg rolling :)
This challenge sparked my interest in rigid body simulation. Hopefully in the future I will be able to take time into reading more about it (and begin to understand something!) to write my own little simulator. I am looking particularly for a real egg rolling simulation where the center of mass of rigid body is shifting based on the properties of the fluid (egg yolk and egg white).
Later Mike was kind enough to share the script which gave me insight into his setup. Mine is not as good as his, but I was happy to get the egg rolling :)
This challenge sparked my interest in rigid body simulation. Hopefully in the future I will be able to take time into reading more about it (and begin to understand something!) to write my own little simulator. I am looking particularly for a real egg rolling simulation where the center of mass of rigid body is shifting based on the properties of the fluid (egg yolk and egg white).
Bare essential commands that create Maya's scene
If you open .MA maya file in notepad, you will see many lines of code based on your scene size. If you go through the code, no matter how big the scene is or how many nodes are there, the whole scene is created by only 4 commands! That's it! It's very easy to find this information, but what amazes me is that the programmers were able to abstract the logic of scene creation to the following atomic tasks:
- Creating nodes with parenting info
- Adding attributes
- Setting changed attributes
- Connecting the nodes
14 April, 2011
Making Animatable Pivot: Part 2
In the first part, we looked at the basics of how pivot works and what is the main challenge in making pivot animatable. In this part we take a look at the implementation detail to build a system which will calculate movement around given pivot point. Here is the hierarchy I am using for this system.
Instead of step by step tutorial, I will try to describe main logic of how it works. First, let's examine our goal and see what kind of result we are looking for. Here is an image that shows the initial status and what we expect after rotating the cube.
If we rotate the cube right now in our current initial state, then what we get is shown in the image below. It's what you would expect since our pivot is parented under the cube. But this is not what we want.
So how do we go from our initial state to the final result? There are different ways to solve this problem. Here, we look at one way to do it, which may not necessarily be the easiest.
First thing we need to keep in mind is that our pivot is parented under the cube, that means our pivot is operating under cube's coordinate space (V.IMP.). Now, let's see the steps to get the final result.
#1) We move the pivot point so that it lies on the origin. (Keep in mind, this origin is in our cube's space)
Why do we do this? So we can apply rotation matrix which always rotates around (0,0,0)
#2) Now, we apply the rotation, which will be around the origin by default.
See it already looks closer to the result we want!
#3) Then we reverse the first step. We move everything back so that pivot goes to it's original location.
And we have our final result!
The matrix equation to get the above result is:
Now prepare the network to calculate the object movement around pivot using above matrix equation. Plug object's rotation into object_rot which will be our rotation matrix. Once you have the matrix calculation network ready, plug the final .matrixSum to a decomposeMatrix node's .inputMatrix attribute. What we will get here (in .outputTranslate) is the translation value that the object should move to get the pivoting movement around pivot_loc. Just to test the result you can plug .outputTranslate attribute of decomposeMatrix node to netPivotShift node and see how it works. Give it a try, it's fun! :)
Phew! That took the whole brain out of me! Now, remains the offsetting logic. Let's keep it for the next post to give my brain processor some rest :)
netPivotShift
|_object
|_ pivot_loc
|_ object_rotInstead of step by step tutorial, I will try to describe main logic of how it works. First, let's examine our goal and see what kind of result we are looking for. Here is an image that shows the initial status and what we expect after rotating the cube.
If we rotate the cube right now in our current initial state, then what we get is shown in the image below. It's what you would expect since our pivot is parented under the cube. But this is not what we want.
So how do we go from our initial state to the final result? There are different ways to solve this problem. Here, we look at one way to do it, which may not necessarily be the easiest.
First thing we need to keep in mind is that our pivot is parented under the cube, that means our pivot is operating under cube's coordinate space (V.IMP.). Now, let's see the steps to get the final result.
#1) We move the pivot point so that it lies on the origin. (Keep in mind, this origin is in our cube's space)
Why do we do this? So we can apply rotation matrix which always rotates around (0,0,0)
#2) Now, we apply the rotation, which will be around the origin by default.
See it already looks closer to the result we want!
#3) Then we reverse the first step. We move everything back so that pivot goes to it's original location.
And we have our final result!
The matrix equation to get the above result is:
shift = pivot_loc.inverseMatrix * object_rot.matrix * pivot_loc.matrix
(this can be a bit confusing if compared to original equation in first post, because we are taking a bit different approach here)Now prepare the network to calculate the object movement around pivot using above matrix equation. Plug object's rotation into object_rot which will be our rotation matrix. Once you have the matrix calculation network ready, plug the final .matrixSum to a decomposeMatrix node's .inputMatrix attribute. What we will get here (in .outputTranslate) is the translation value that the object should move to get the pivoting movement around pivot_loc. Just to test the result you can plug .outputTranslate attribute of decomposeMatrix node to netPivotShift node and see how it works. Give it a try, it's fun! :)
Phew! That took the whole brain out of me! Now, remains the offsetting logic. Let's keep it for the next post to give my brain processor some rest :)
09 April, 2011
WIP: Unsuccessful attempt at rolling an egg(unknown type)
I can think of two ways to implement rolling egg.
1. Use aim constraint to point in the direction of movement and have rolling axis as your up vector. The object can then roll about this up vector. I will implement this approach later.
2. Calculate rolling rotation around the rolling axis using matrix calculations.
!These approaches do not simulate the actual physics.!
I am currently working on 2nd approach. Here are some notes for both the approaches:
# First one will follow a path and will need some work to blend the direction changes so the object orientation does not jump.
# First method is more like driving a car so the object's front/rear side follows the direction of movement, egg however may or may not follow this kind of movement.
# Second method adds the rotation on top of the current rotation, so egg starts rolling in the direction of movement without any jump in it's orientation.
# First one will rely on expression only for small amount of work, hence reliable undo operations.
# Second method depends mainly on expression to avoid cycle and use of getAttr, setAttr is required. This leads to non-user friendly undo.
The main difficulty in 2nd method is in getting the rotation around the rolling axis. I understand the theoretical part of it, but I am having a bit difficult time implementing it. May be I am doing something wrong, but it's hard to debug it at this stage. Here is what I am doing:
- Find the direction of movement
- Find rolling axis using cross product
- Find a matrix difference between so that z-axis of egg rotation matrix matches rolling axis (Diff)
- Do this operation to rotate egg around rolling axis by R
F = Egg * Diff * R * Diff(-1)
- In above equation we first take the current egg rotation and then multiply it by "Diff" matrix to align z-axis with rolling axis. Then apply the rotation amount around z-axis and then just apply inverse of Diff matrix.
And here is the result.
You can see that in the beginning it rolls fine, but when direction changes rotation does not follow the rolling axis.
03 April, 2011
Making Animatable Pivot: Part 1
First concept here to understand is the rotation operation and how it is affected by pivot point. Here is a post I wrote on my blog last year to understand how pivot works.
First we will look at how rotation around pivot point is calculated in simple steps.
1. Translate the object from its current location to the pivot point location
2. Rotate the object
3. Translate the object back to its place from pivot point location
What the last step does is that it moves the object in the rotated transform axis. Hence it moves the object to the final location which gives the effect of rotation around given pivot point. Mathematically here is what we will do using matrices to get the final result:
F = T * R * T'
Where,
T = translation matrix to move from current position to the pivot position
R = rotation matrix
T' = inverse matrix of T
This was just the first step, in calculating the rotation around arbitrary point. However, the main challenge is to make pivot point animatabale.
If you have rotated the object first and then try to change the pivot location, the object will move as well. What happens here is that the object already had some rotation applied to it based on previous pivot position. Now, we change the pivot location which will cause the final matrix to be recalculated based on existing rotation and new pivot point. This results in object shifting to a new location. This seems quite obvious, but in many cases it is unwanted. Maya deals with this problem by storing an offset value in ".translateRotatePivot" attribute when you are moving the pivot point. This stops object from shifting.
But the problem with Maya's approach is that all the rotation calculations are always based on single pivot location. So even if object does not move while moving pivot point, it does affect all the previously keyframed transform values.
So to solve this problem we calculate the offset at each change of pivot location and then store this offset which can be animated on a pivot control. I will write about this in detail in my next post.
First we will look at how rotation around pivot point is calculated in simple steps.
1. Translate the object from its current location to the pivot point location
2. Rotate the object
3. Translate the object back to its place from pivot point location
What the last step does is that it moves the object in the rotated transform axis. Hence it moves the object to the final location which gives the effect of rotation around given pivot point. Mathematically here is what we will do using matrices to get the final result:
F = T * R * T'
Where,
T = translation matrix to move from current position to the pivot position
R = rotation matrix
T' = inverse matrix of T
This was just the first step, in calculating the rotation around arbitrary point. However, the main challenge is to make pivot point animatabale.
If you have rotated the object first and then try to change the pivot location, the object will move as well. What happens here is that the object already had some rotation applied to it based on previous pivot position. Now, we change the pivot location which will cause the final matrix to be recalculated based on existing rotation and new pivot point. This results in object shifting to a new location. This seems quite obvious, but in many cases it is unwanted. Maya deals with this problem by storing an offset value in ".translateRotatePivot" attribute when you are moving the pivot point. This stops object from shifting.
But the problem with Maya's approach is that all the rotation calculations are always based on single pivot location. So even if object does not move while moving pivot point, it does affect all the previously keyframed transform values.
So to solve this problem we calculate the offset at each change of pivot location and then store this offset which can be animated on a pivot control. I will write about this in detail in my next post.
20 March, 2011
Removing multiple items from textScrollList
I have started working more with Maya UI and I can see why people get frustrated sometimes with handling UI interaction using mel. For example, there is no function to get an item at a given index.
Deletion of multiple items from a list or an array is always tricky because, while deleting multiple items the indices are updated automatically. One solution is to start deleting at the highest index so that the indices to be deleted later are always unaffected.
Here is the function I wrote for the extended textScrollList class. Python's built-in function to sort a list comes in handy here.
Deletion of multiple items from a list or an array is always tricky because, while deleting multiple items the indices are updated automatically. One solution is to start deleting at the highest index so that the indices to be deleted later are always unaffected.
Here is the function I wrote for the extended textScrollList class. Python's built-in function to sort a list comes in handy here.
def removeAt(self, inds): """ Remove item(s) at given indices from textScrollList @inds: takes a single int or a list of int """ if not isinstance(inds,list): inds = [inds] # indices should be sorted first into descending order # so that the indices to be deleted next are unaffected inds.sort(reverse=True) for i in inds: self.edit(removeIndexedItem=i)
14 March, 2011
Rigenerator Update
I have switched back to rigging related work. I have resumed my work on Rigenerator by analyzing my existing code. It can be very helpful to take a break from the work and revisit it later. I found many ideas to improve and more importantly simplify the logic for Rigenerator. Earlier it was not very bad, but I always felt that the code and logic I had written was a bit twisted and difficult to understand. I have put so many comments in the code, but still I was not satisfactory with the logical structure. So I have gone through the code many times as I kept refining it and simplifying the logic.
The first thing I did was to divide the code into proper modules (which I had to do anyway). While doing so I also kept the UI in mind. This will make plugging the UI process easier. There are some changes in logic for exploring the graph. It will allow more possibilities in conditional gathering of graph nodes. Finally I have moved Rigenerator out of my rigging framework project and it is a tool by itself now.
The major changes are in the graph rule structure. It is much more flexible and easy to follow now. The new rule structure allows the following,
# A value can be a node type or a node name
# Neighbor node(s) can be specified for rule condition
# Also, connection attributes can be specified for the rule
# Any value in the above conditions can use simple regular expression (RegEx.)
# Simple RegEx. can be extended in the future to allow more complicated RegEx. syntax
# Rule conditions can be grouped to force AND operation (all conditions should match)
Since I have made a lot of changes, this also requires for more testing. But I am looking forward to finishing the new algorithm and start testing the results!
The first thing I did was to divide the code into proper modules (which I had to do anyway). While doing so I also kept the UI in mind. This will make plugging the UI process easier. There are some changes in logic for exploring the graph. It will allow more possibilities in conditional gathering of graph nodes. Finally I have moved Rigenerator out of my rigging framework project and it is a tool by itself now.
The major changes are in the graph rule structure. It is much more flexible and easy to follow now. The new rule structure allows the following,
# A value can be a node type or a node name
# Neighbor node(s) can be specified for rule condition
# Also, connection attributes can be specified for the rule
# Any value in the above conditions can use simple regular expression (RegEx.)
# Simple RegEx. can be extended in the future to allow more complicated RegEx. syntax
# Rule conditions can be grouped to force AND operation (all conditions should match)
Since I have made a lot of changes, this also requires for more testing. But I am looking forward to finishing the new algorithm and start testing the results!
12 February, 2011
New Year New Adventure
It's been very very long since I posted any updates. I have been working on many projects simultaneously so it's been difficult to find time. I have done facial rigging for a friend of mine and I am also working on my Rigenerator project. For Rigenerator, I have started working on converting code for special nodes into the code that uses node's own command, e.g. cluster. Later I have a plan to add support for Python. But there is a lot to be done before that.
However, I have taken a break from my rigging work and I have started diving into two other development projects, a web app and an IPhone game.
However, I have taken a break from my rigging work and I have started diving into two other development projects, a web app and an IPhone game.
I was already building my website using CodeIgniter framework for php and during that time my wife suggested some great ideas for building a web application. Since then I have become passionate about this idea. So I have started building some parts of the app as a prototype. I am using PHP (with CodeIgniter as framework), jQuery and MySQL to build this web app. It is going to be a long term project so I will be switching between other projects and this one.
I am also exploring some game ideas for iphone while learning Objective C along the way. There are many aspects to consider when designing a game. A good game should keep users interested from beginning to the end and also a good game users will want to play again and again. I am considering coscos2d-iphone game engine for the game and later plan to add box2d physics engine. This will also help me with my technical animation reel.
Here is another blog I started to take notes about the new projects and programming in general.
http://maulik13tech.blogspot.com
Here is another blog I started to take notes about the new projects and programming in general.
http://maulik13tech.blogspot.com
Subscribe to:
Posts (Atom)







