Saturday, October 17, 2015

Auto-killing the Skype Call Quality Feedback dialog

As a work-from-home nerdo, I find Skype to be incredibly useful. My only complaint is that after every call, an annoying "Call quality feedback" windows pops up.


I'll try to limit my first world rage to two points:

  1. Because I pay for a subscription, I should have the option to disable this dialog. I already think the service is excellent. That is why I am paying for it. But this request has fallen on deaf ears at Skype / Microsoft for four years now.
  2. It seems like a more effective approach would be for the software itself to evaluate the call quality. If the engineers at Skype have figured out real-time language translation, perhaps audio quality detection is tractable. Who knows.
In searching for a solution to my unwanted pop-up problem, I came across this post from someone who solved the exact same problem using AutoIt. AutoIt is a scripting language and utility for manipulating anything you can dream of on a desktop interface, including closing windows. Perfect! Unfortunately, the link to the script created on that blog entry is dead, so I decided to give it a shot myself.

I downloaded and installed AutoIt, which is free. I poked around the examples and docs for a bit and came up with this little guy:

 ;Poll at 4hz for the stupid Skype call quality feedback window. If it's found, kill it.  
   
 While True  
   WinWait("[CLASS:TCallQualityForm]")  
   WinClose("[CLASS:TCallQualityForm]")  
 WEnd  

Too easy, right? Here's how it works.

WinWait pauses the execution of the script for a quarter of a second and then checks for the existence of a certain window. If the window does not exist, it pauses and checks again later. I'm using the default value of 0 for the timeout which means that it will hunt for the Skype "Call quality feedback" window, four times per second, FOREVER. 

When a Skype feedback window does appear, WinWait completes, and the script proceeds to the next line, which is WinClose. Not surprisingly, it closes the window. And because all of this is wrapped in a loop that never exits, the script continues its quest to obliterate the feedback dialog for as long as I'm logged in.

The single argument I pass to both functions is "[CLASS:TCallQualityForm]". AutoIt allows you search for and kill a window by title or by internal window class name using the special [CLASS:] syntax. I chose this approach so that if Skype changes the title of the feedback window, my script will probably keep working since it's less likely that this internal class name will change. You can discover the internal class name and all sorts of other stuff using the AutoIt Window Info application that is bundled with AutoIt:


Next I used the convenient context-menu based compilation feature to create an executable:


And now I have kill_skype_feedback.exe. When this runs, it adds itself to the Windows system tray and stays there running quietly in the background. 

I've been using it for a week now and it works great! I have to be watching very closely to even see the Skype popup before it's closed a fraction of a second later.

The script is very resource friendly since it's tiny and spends most of its time sleeping:


This works so well that I have it launch automatically at startup time. I copied the executable to the all-users startup folder for Windows 10:

C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp

And now the feedback window killer starts whenever I log in. It remains to be seen if this plays nice with games and other unusual windowing configurations, but so far I haven't run into any issues.

If you'd like all four lines of my script, you can grab it from github.

Saturday, October 10, 2015

Setting AtMatrix values in Python

When building an Arnold scene in memory, especially when converting from another scene format, one of the most common operations is setting the transform on the new Arnold objects. This is done by setting the matrix attribute which is either an individual AtMatrix, or an array of AtMatrix in the case of several motion steps. 

AtMatrix is defined as a simple 2d array (as of this writing):

typedef float AtMatrix [4][4]
4-by-4 matrix 

The AtMatrix C++ API is pretty straightforward for setting values on an existing matrix:

 AiNodeGetMatrix(node, "matrix", m);  
 // ...
 m[0][0] = 1.0f;  
 m[0][1] = 0.0f;  

The Python API is slightly trickier:

 >>> m[0][0] = 0.0  
 Traceback (most recent call last):  
 File "<stdin>", line 1, in <module>  
 TypeError: 'AtMatrix' object does not support indexing  
   
 >>> m[0] = 0.0  
 Traceback (most recent call last):  
 File "<stdin>", line 1, in <module>  
 TypeError: 'AtMatrix' object does not support item assignment  

So what's going on here?

 >>> help(AtMatrix)  
 Help on class AtMatrix in module arnold.ai_matrix:  
   
 class AtMatrix(_ctypes.Structure)  
 | Method resolution order:  
 | AtMatrix  
 | _ctypes.Structure  
 | _ctypes._CData  
 | builtins.object  
 |  
 | Data descriptors defined here:  
 |  
 | __dict__  
 | dictionary for instance variables (if defined)  
 |  
 | __weakref__  
 | list of weak references to the object (if defined)  
 |  
 | a00  
 | Structure/Union member  
 |  
 | a01  
 | Structure/Union member  
 |  
 | a02  
 | Structure/Union member  
 |  
 | a03  
 | Structure/Union member  
 |  
 | a10  
 | Structure/Union member  
 ...  
 |  
 | a32  
 | Structure/Union member  
 |  
 | a33  
 | Structure/Union member  
 |  

Ah! Each matrix value is represented by its own member variable of the AtMatrix class. No problem! Python can make quick work of this. In my application, my transforms are exported as 16-length arrays, so I wrote two small functions to do the conversion and update the matrix of an AiNode. The xform_to_matrix  function can either update an existing AtMatrix using __setattr__, or create a new matrix using argument expansion.

def set_node_xform(node, transform):  
    """Set an AtNode matrix from one or more transform arrays.  
    
    Set a node transform from a 16-entry python array,  
    or an array of several transform arrays if you want  
    motion blur (max 15 samples)  
     
    Args:  
        node (AtNode): Node to set 'matrix' attribute  
        transform (list(float), or list(list(float))): List, or list of lists  
            containing 16 entries representating a transform matrix.  
    """  
    list_size = len(transform)  
    if list_size < 16:  
        xform_array = AiArrayAllocate(1, list_size, AI_TYPE_MATRIX)  
        for i in range(list_size):  
            AiArraySetMtx(xform_array, i, xform_to_matrix(transform[i]))  

        AiNodeSetArray(node, "matrix", xform_array)     
        AiMsgInfo(b"Setting {0} time samples for {1}".format(  
            list_size,  
            AiNodeGetName(node)  
        ))  
    else:  
        AiNodeSetMatrix(node, "matrix", xform_to_matrix(transform))  
   
       
def xform_to_matrix(transform16, matrix=None):
    """Update or make a new AtMatrix from an array
    
    Create an AtMatrix and set its values from 
    a transform array of 16 entries.
    
    Args:
        transform16 (list(float)): 16-entry python array
        matrix (AtMatrix): existing matrix, or None if creating a new one
        
    Returns:
        AtMatrix: New, initialized matrix
    """
    if matrix is None:
        # Can early-out here with argument expansion if creating
        # a new matrix
        return AtMatrix(*transform16)
    
    for y in range(4):
        for x in range(4):
            matrix.__setattr__("a%d%d"%(y, x), transform16[y*4+x])    
    return matrix 

To demonstrate, I put together a super-complex simulation in Maya and exported the transforms to a text file. A also wrote a short Python script to render all of the frames from the simulation, updating the scene with the correct transforms for each frame:

if __name__ == "__main__":  
    # Read the transfroms dumped from Maya  
    mats_file = open("mats.txt", "r")  
    as_txt = mats_file.read()  
    mats_file.close()  
   
    lines = as_txt.split("\n")  
    mats = []  
    for line in lines:  
        mats.append([float(i) for i in line.split()])  
   
    # Warm up Arnold  
    AiBegin()  
   
    # Show EVERYTING in the log  
    AiMsgSetConsoleFlags(AI_LOG_ALL)  
     
    # Read in the scene file  
    AiASSLoad("cube_bounce.ass")  
   
    cube = AiNodeLookUpByName("cube")  
    driver = AiNodeLookUpByName("exr")  
   
    for frame in range(START, END):  
        # Set the file output name per-frame  
        fileName = "renders/bounce.%03d.exr" % frame  
        AiNodeSetStr(driver, "filename", fileName)  
       
        # Apply the cube transform for this frame  
        xforms = [mats[frame], mats[frame+1]]  
        set_node_xform(cube, xforms)  
       
        # Render this frame  
        result = AiRender()  
        if result != AI_SUCCESS:  
            AiMsgError(b"Quitting because I failed somehow")  
            break  
   
    # All done, tell Arnold to quit  
    AiEnd()  

I think you'll be impressed with the result:



Or generate your very own cube! Grab the code sample from github