domingo, 13 de diciembre de 2015

Driving A Vector's Direction By Euler With Maya Node Editor

Introduction

Past thursday one my fellow mates at the office in charge of refining the shots and animating dynamics asked me to help him with a problem he was facing: he needed to map the euler rotation angles from a CTRL transform to the nucleus' gravity direction vector. In short: control with angles the direction of a vector.

He was trying with the "angleBetween" node looking for some way to solve his problem. I have to say that at first he wouldnt explain  to me correctly what he needed but that's because he was striving for it himself.

The next day, this is last friday, while at the bus in the morning to the office i remembered the conversation he had a couple of days earlier asking for this to my fellow riggers. They seemed to fill the expectations of my comrade in need with their answers. It wasnt clear for me but i was too busy with something else and didnt want to intercede.... Until now, where this was something that was puzzling him for quite a few days now.

So i started to suppose what was the problem and started to think how i would solve it with nodes.

Controlling Vectors through Rotation

In fact, once aware of the problem the solution is just applying some simple math. We have a curve/CTRL's transform that represents rotations with Euler angles.

Well, we need to apply the rotation of the control to the vector. In terms of maths, we need to get the matrix representation of the rotation. Maya's node editor has a ComposeMatrix node that generates a matrix with information on translation, rotation, scale, shear depending on what the input is.

Next step is to multiply this matrix by the vector we want to transform. Again, Maya has VectorProduct node for this that can do different operations such as Dot, Cross, VectorMatrix and PointMatrix.

v' = M * v

All that is left is just attach the output of the operation to the nucleus's gravity vector....and voilà!!!

Voilà????? Hold on a second, there is a little problem.

One little problem

The way i approached for the first time the node network i was convinced it had to work from the beginning since these are simple maths. But i was committing one error that stems from the fact that i was a little unaware of how nodes work.

I was using the current gravity vector direction to feed the operation and then the result attached back to the nucleus as input..... And i tried to test with known trigonometric values and i was getting a strange behaviour: the numbers just didn't correspond to a valid result.

My network suffered from a "cycle" that never stopped: i'm feeding the input of the attribute with the output of the same attribute after doing some calculation....

But i found one workaround for this.



Workaround

After trying inefficiently to break that cycle using an intermediate transform where i would store the result and pass it on again to nucleus i decided to write a post in a Maya forum convinced of the fact that this can be made without using a transform or any other supplemental entities.

But i kept thinking after the post, enbraved by the fact that i wasnt getting a quick question and i was stuck. Well, if all i need is the starting value of the nucleus how about putting that same initial value into a transform and use it instead of the nucleus to feed the calculation? That way  i can attach directly the result to the nucleus gravity vector. All i have to make sure is that both vector values: nucleus initial value and transform's value are the same!! It doesnt matter really which channels of the transfrom i use provided they represent the three vector componentes X, Y,Z. I decided to use the translate.






Additional Comments

It's worth noting that since the initial value of the vector is such that the module is 1.0, the results after the matrix multiplication must be also of module 1.0. Since a rotation matrix doesnt change the module of a vector. 

This was helpful to rapidly notice wrong numbers and therefore there was a problem. Also, testing with cosine and sine values of most known angles such as 0,30,45,60,90 degrees.... One is used to see numbers like 0.707 or 0.5 and 0.866 etc....

Also, note that because we are using the rotation the translation of the control/curve doesnt matter.

viernes, 20 de noviembre de 2015

Overriding Python str class setter.. How?

Coding another tool in python as usual i was getting tired of printing several debug messages. The script performs some calls to multiple main methods that in turn call other secondary  methods. I wanted to track the final status of each call so i thought that it would be a good idea to print a message each time a "msg" object changes its value. Thus recurring to the 'set value triggers function call' paradigm.

One solution i found was to create a new class that inherits from "object" and use @property and @<member>.setter with an intermediate string object to store the text.

But i wasn't satisfied enough and coming from C++ i was wondering if it was possible to inherit directly from "str" class and just override its setter.

Since i found no info on the web, i resorted to write to a tech forum. 

Here is my post:


I'm coding a script that performs different complex tasks and i want to output status messages regarding the execution of the script.
Rather than doing a "print" statement after each main function call i 've thought it would be nicer to have a string object that automatically prints something each time the variable's value changes.
So this lead me to write a custom class with a variable and a setter to it like the following:

Code:
class Msg(object):
    def __init__(self):
        self._s = None
    
    @property
    def status(self):
        return self._s
    @status.setter
    def status(self, value):
        self._s = value
        call_custom_function()
Now the question: I've come to the above solution but i'm wondering if it's possible to inherit directly from str class and just override the setter... thus not needing an intermediate variable like _s and be able to do something like
Code:
msg = Msg()
msg = "hello"
instead of the current:
Code:
msg = Msg()
msg.status = "hello"
I've searched the web and i haven't found how the str class works besides the fact that it's a "sequencer" type just like a list...

The same day i received several answers that despite interesting were missing the point of my question, until i got finally the explanation.

Here is the final reply that enlighted me:
Code:
msg = Msg()
msg = "hello"
to anyone who write python code, these lines mean this:

1. Create an instance of the class Msg and assign it to msg
2. msg is now the string "hello"

Your instance of the Msg class has been overwritten. Thats why it makes no sense. Why create an isntance of a class just to over-write it with a string.

Ahhhh this is what i was getting wrong!!! In a language like C++ one can overload the "=" operator and hence the second line would call the overloaded method from the "=" operator from the msg instance of Msg() Class. The type of "msg" doesnt change!!!!

But in Python we dont have such behaviour, so when we assign "hello" we are just changing the type of the variable to be a string!! 


sábado, 14 de noviembre de 2015

How To Get Rid of PyQt Widgets Correctly

Introduction. The Tool.


In recent weeks i was told it would be nice to have some kind of reference editor outside Maya. Something simple that allowed animators to chose which references they wanted to load in the scene and which ones they didn't want.

What are the advantages for this requirement? The main reason is although we have at the studio powerful workstations in terms of Ram, CPU and Graphics processor some assets like set, props etc are really big, one single prop can take 3 GB!! and depending on the scene you can have almost 400 references. If each prop took that much space.. you can do the math.. it's simply unmanageable. It's not that huge in reality but it remains a big problem also if you take into account the amount of time it takes to load them all and finally open the scene. An animator would normally only want to load the character he/she is about to work with leaving aside all the props and set elements that don't interact with the character. This enables everyone to work faster.

Obviously the external reference editor must be "non-destructive". What i mean for this is it should not delete the reference node in Maya. Why? Obviously this external reference editor is useful for opening a scene file for the first time. Once the scene is loaded in Maya the animator must use the Maya reference editor to load/unload assets. In this case, to load all the necessary assets once the animation is finished, so that everything is in place when the playblast is published. So we need to let the animator the chance to load in Maya the rest of the assets and for this, he needs the reference node of the asset to be present in the scene.

After analyzing the Maya ASCII scene file it was clear what changes to do to the file to unload a specific asset.

Design. The problem.

Here is what i thought it would be a good design: i would use a dynamic list of widgets where each line would be composed of a QCheckBox showing the current state of the reference and the reference node of the asset.

I used the same approach as other times when i needed to code a dynamic list of widgets which consisted mainly in two steps:

A) we have a widget that triggers the fullfillment of the dynamic list. It can be something like a QComboBox to select the file's work area.

B) each time the dynamic list is filled we need to create a "line widget" with its proper layout which contains the QCheckbox and the QLineEdit. Those widgets are created each time which also means they need to be properly deleted, otherwise we will run into memory problems. And that was the origin of the bug i had.

When i first coded a dynamic list like this and wasn't that much versed into python i googled to look for the proper way to delete a widget, and i found this site in stackoverflow to be very useful although somewhat confusing. So many ways to apparently delete QWidgets!!

Digging into the proper solution.


There were three methods that apparently reached the same result:

1) the close() method in the QWidget class
2) the setParent() to None method also in the QWidget class also
3) the deleteLater() also in the QWidget class

I always thought the setParent() to None in each parent widget worked well. So in the method before filling the list i called a cleanup_scrollArea() method which was coded like this:

for i in reversed(range(layout.count())): 
        layout.itemAt(i).widget().setParent(None)
Relying on the fact that in the documentation they say: "the new widget is deleted when its parent is deleted".

I wont explain much. Only tell that this apparently works. Setting the the parent of a widget to None breaks the connection of the PyQt tree and causes all the children to not show anymore.

But there was a big bug. Whenever i tried repeatidly to test the tool with different files the tool crashed within the third or fourth iteration. The dynamic list's behaviour was apparently correct and working well, everything looked alright and i had no error message to give a hint of the problem.

I had the suspicion it had to do with a problem in the deletion of the widgets because the memory increased in each iteration even if the file had less refereneces to show than the previous one!. And obviously it was crashing when you tried to repeatidly use it. It must be a problem in the dynamic list!!

The Solution.

It  took me a short but intense moment  to figure out what was happening. And here my experience with a language such as C/C++ that deals with memory management helped me a lot since PyQt is a bind for Nokia's Qt written in C++.

What was happening?

Setting the  widget's parent to None only breaks the connection in the Qt widgets tree and causes the python reference to be deleted by the garbage collector. But what about the C++ QWidget Object that python was referencing? C++ does not have a garbage collector, so the C++ object's memory has to be deleted manually.

Here is why we have to use deleteLater() 's QWidget method. That's what it does, it frees the memory the C++ object is using..That's what we were missing! Furthermore deleting the C++ object makes the python references invalid therefore we don't need to set anymore to None the parent's widget,

The cleanup_scrollArea() method became:

while aLayout.count() > 0:

    item = aLayout.takeAt(0)
    widget = item.widget()
    if not widget:
        continue

    widget.deleteLater()

Design Improvement Quick Note

Creating and deleting widgets is expensive. It's a very stressful task even for a language like C++ with it's new and delete methods. So it may look like this behaviour for a dynamic list is not the best fit.

In the PyQt documentation they say that for this it may be better to use a QStackedWidget and play with the show() / hide() methods of the widgets which i believe reserves memory for a set of widgets and in the next iteration it reuses the same widgets changing their properties, hiding and adding new widgets on demand as needed. Might want to try this sometime!









jueves, 17 de septiembre de 2015

Nested References in Maya

I've found myself in the need at work to be able to get all the references a shot had. What you can get with file(q = True, r = True) are only the top level references leaving behind the nested ones.





I've read somewhere that is a good practice trying to avoid such levels of depth, but in this case we were in a closet.

So after a while searching the web and the official Maya python documentation i hit with referenceQuery and the "children" flag.

Finally i developed this simple method that fills a python list with reference filenames:


    def add_nested_references(self,parent_ref_list):
       
        for parent_ref in parent_ref_list:
            child_list = mc.referenceQuery(parent_ref, f = True, ch = True)
            if child_list != None:
                   self.add_nested_references(child_list)
                   parent_ref_list.extend(child_list)




martes, 8 de septiembre de 2015

How to Recover Linux Grub Boot Loader

Normally, the order of installation should be first windows then linux so that the grub boot loader is installed and recognizes the windows partition too.

At my job i had to do it the other way around: we had a system with Centos 7 linux and we needed to install Windows to make this machine we useful either to rigging either to production staff.

I inserted the windows dvd and made a new partition in the unallocated space then install.

When i restarted the machine the linux boot loader had disappeared so i was googling how to recover it. Truth is it's pretty simple and after a really few minor mistakes i managed to make it work:

We need first to enter in RESCUE mode with a linux installation dvd/cd.

The system will be most probably mounted in /mnt/sysimage so we make the

chroot /mnt/sysimage

after that we do:

/sbin/grub2-install /dev/sda

to make grub recognize our windows entry just type:

sudo grub2-mkconfig > /dev/null

If everything went well the windows loader entry has been detected, all we need now is to save it to the grub.cfg:

grub2-mkconfig -o /boot/grub2/grub.cfg

Restart and voilà, we have our boot loader back with our windows O.S. entry!!!

domingo, 2 de agosto de 2015

Advanced Multithreading GUI Programming with PySide

One of the tasks i was in charge of was to look for a video player in linux that met several requirements from the art director. Some of them were:

- available for linux
- free
- frame by frame backwards and forward playing
- hotkeys for the above, not only buttons
- able to play several clips in a row
- no blank frame between clips
- able to play the audio of the video as well
- autoloop feature
- plays .mov, .mp4, .avi, compatible with the most possible codecs out there.

The autodesk RV Player is the best one according to some experienced animators but we couldn't afford licenceses for each individual.

After testing the obvious ones such as VLC (with jump in time extension), Openshot, MPlayer (with SMPlayer front-end)... All of them were lacking any of the listed features. Concretely i couldnt restrain my disappointment when i found that VLC's extension wasnt working as claimed on the web.

Several days passed by, and in the meantime i was busy doing other stuff and the animators had to deal with openshot which is not properly a video player but rather a video editor.

Anyways i found out that openshot was built upon the media lovin't toolkit video engine (MELT from now on)

So i decided to install it and give it a go.

Couldn't compile the last version 0.9.6 so i tried the 0.9.2 and yeah it worked!!

I was testing it and realized it met all the primary requisites only drawback: it had to be launched from the terminal and this was not as much user-friendly as it should for an animator.

So i was talking to my boss and he suggested coding a gui for maya that listed all the videos in the work directory and launch the player through Maya. That's when i got "hands on".

CODING THE TOOL

I was getting pretty advanced with the gui programming in PySide/Python time to do a test and launch the player came.

And here i discovered a big problem for me that took me some time to resolve. The problem was that the video player was correctly launched if done manually from an opened terminal but when done from the python script with:

import subprocess

command = ["gnome-terminal", "-x", "bash", "-c", "/usr/bin/melt <videofilefortest>"]
subprocess.call(command, shell = False, env = os.environ.copy())

it was giving an error regarding one of the dynamic link libraries (the famous "DLL" files in windows).

TWO WAYS TO SOLVE IT

Further investigation revealed that doing "ldconfig" in that terminal and launching again the player was a solution. "ldconfig" apparently rebuilds a cache file that specifies the name and path of each dynamic library so the system is able to find them. That opened a way to go. I just had to be able to login as root in the script, do a "ldconfig" and then launch MELT.

Another logical way was to mimic the same environment configuration between the two terminals setting and unsetting the ENV variables until both matched. It was clear that both terminals had different configuration. A "printenv > env_good.txt" and "printenv > env_bad.txt" helped to list the ENV variables of both terminals.

SOLUTION

I was jumping from one strategy to the other always testing. Had some difficulties trying to login as root in a script and also i wasn't confortable hardcoding the root password for obvious security reasons.

Finally i noticed that some important ENV variables werent set in the "good terminal" such as PYTHONHOME PYTHONPATH etc. So I decided to UNSET them prior to launching the player. With some help from a linux forum a user suggested also to UNSET not only those variables but also PATH, LD_LIBRARY_PATH. I agreed and finally it worked!!

EXPLANATION

Apparently, the way subprocess.call works is it executes the commands in a child process. Child processes inherit by default the environment from the parent process which in this case is Maya. But Maya itself is launched with specific configuration that doesnt necessarily match the default one.

It was the LD_LIBRARY_PATH what made it work. Makes sense since the concrete problem was the system wasnt finding where the libraries were.


ADVANCED GUI PROGRAMMING

That problem solved the rest was straightforward.

I noticed that the "search-the-filesystem-for-videos" was taking a bit too much time causing the tool to be apparently "frozen". So I decided to add something showing the tool was busy while searching. Something like a "living" waiting pattern.

This obviously led to the use of multithreading: one showing the waiting state while the other performs the search.

First i was wrong because i tried to run in a background process the GUI waiting icon. And i was wrong because a simple search in google showed that all the GUI processing has to be done in the main thread, and not in secondary ones. That is a good thing to know. I just had to switch the tasks and.. voilá.

I was really proud of it, not that it mattered too much since it was more like an aesthetic thing but i have to admit i haven't done much multithreading programming in all my years of programming.

domingo, 19 de julio de 2015

Selection Button In Maya MEL

It's been a while since my last post. The reason is that work's keeping me really busy and i'm learning a lot about Python and MEL. 

The last couple of weeks i 've left temporarily rigging the props and i 've got back to scripting.

It happens that some of the main characters' rigs cannot use FK because the dynamics don't work, so i was talking to one of the lead animators and he came up with an idea, and i was the one to code it and make it possible. I did several scripts that accomplish all the tasks the lead animator asked for the main and secondary characters. Still it has to be tested by the art director and the other lead animators but it feels like it's really useful since it mimics an FK chain with IK controls.

One of the scripts is a panel with selection buttons. Those buttons select each locator so they can animate the locators and then bake the animation.

In MEL we have the "select -r <whatever>" which selects the new by replacing the old. Also we have "select -tgl <whatever>" which adds the selected to the current selection.

The MEL code for a button is:

button -label $label -command $command

Where $command stands for some sort of  "select" as described before. The problem is none of them 
works as it should. I wanted to "select -r" replace but with the ability to Shift Select other locators, this is to add them to the selection if i use Shift just like Maya works.

The solution is to add the following line of code to the $command:

So that finally the button code results like this:

string $command = "string $object=\"object_name_to_select\"; int $mods = `getModifiers`; if (($mods)%2 == 1) select -add $object; else select $object;";

button -label $label -command $command

I'd like to post some rigs i've done but i'm afraid i cannot show the models due to confidentiality terms.