Friday, November 28, 2008

Pythonic English

I'm learning English for decades and still haven't mastered it yet. What confuses me most are articles (a, an, the), tense, and pronunciation like "l", "r" difference. Instead of learning English, let's modify English referencing The Zen of Python.

Simple is better than complex.
In Japanese, there's no future tense nor
perfect form. When we talk about something in the future we use present tense, like "I do homework soon" and when we need to mean that it's not present, we use a word indicating time, e.g. "We go shopping next Sunday". The fact that there's a language without them means it's not necessary. Let's remove them from English. How about past tense? Do we need them? "I went to school yesterday" can be "I go to school yesterday". No problem, let's remove it as well.

Explicit is better than implicit
Articles are nightmares to me. What people knows from articles are,

the: if something is specific or not.
a, an: if the number is one or not.

Let's make them explicit by replacing "a" and "an" to "one", and "the" to "this" or "that". "I have an apple" can be "I have one apple", "The earth" can be "this earth" or "that earth". Good.

If the implementation is hard to explain, it's a bad idea.
Native people often have difficulty in explaining the difference between "l", "r" pronunciations. Let's remove "r" sound from English.

Isn't it a good idea? Let's make it world common. Now is better than never.

Version control/Bug tracking at Rising Sun Pictures

You may have seen it because it's a famous slide but I'll link to it anyway.
"Adelaide University takes on the Earth" blog entry

Each movie might be running a different version of each piece of software

How they do it? I'm interested in its detail.
The only way I know is using an interface name (soname in this post).
but in a production it is not always good to fix a bug whenever you have find it, since what is the most important is sometimes to open a file later and see exactly the same scene as the one when it was created including bug behaviors, but it is troublesome to have artists update a software manually when you find a bug in it. It's a contradiction. Maybe a 3d software uses the latest software (plug-in etc.) automatically, but once a scene ihas been created, it keeps using the same version of the softwares? but how?

Monday, November 24, 2008

PyPy

PyPy is a project to implement Python(CPython compatible) in Python(RPython). What interests a PyPy end user like me is that it's good for converting Python code to various languages. According to PyPy document, it currently translates a Python script into C(full featured), llvm(full), .Net intermediate language(full), Java(not full but getting close), and JavaScript. Due to Python's super flexible features, not all the Python scripts can be translated. They introduced restrictions to CPython specification and defined RPython, a subset of CPython (well, they don't say they have clearly *defined* RPython spec since those restrictions introduced to RPython are evolving). While They make RPython, they have removed every Python features that makes scripts difficult to be statistically compiled. You can read details about RPython here, for example, all module global variables are constant.
PyPy has lots of documents but some of them are not easy to read because it deals with PyPy internal architecture as well as PyPy usage, several comments are only for implementing Python on Python, while others are more generic and for every RPython scripts, and there are lots of levels of things, CPython v.s. RPython, application level objects v.s. interpreter level objects ... so I will suggest in what order you read PyPy documents.
The order I recommend is, you first read getting-started just briefly ignoring every stuff you cannot understand, then architecture then coding-guide then translation. Do not read documents regarding new features such as What PyPy can do for your objects first, though it's the first thing you see in the PyPy documentation list.


Here I will make some more comments on different PyPy topics randomly.

One good thing about outputting llvm code is that it can take advantage of llvm's powerful optimization, both llvm bit code level and processor dependent. From the llvm point of view, PyPy is a llvm Python front end(more precisely RPython frontend). I haven't tested a lot but it's definitely a cool topic.

PyPy translates RPython into other languages from a Python object, not the script directly. It means what you pass to the translator is your function object, not a string nor a file path that contains RPython script.

They have added some to PyPy that doesn't exist in CPython such as optional stackless features. Lazy computed objects is another.

Talking about PyPy implementation, "object space" is interesting. PyPy uses a "standard object space" to execute Python scripts. When it generates a control flow graph (which is used when it translates RPython into another language), it just replaces the standard object space with "flow object space", so for the rest of PyPy, it looks as if the script is executed but the result is a control flow graph, not script execution. I'm not interested in the details but understanding the concept made it easier to read the document.

I just found a tiny bug.


def f():
for a in [1, 2, 3]:
print a,
>>> f()
1 2 3

f() prints nothing after it is c-compiled. It works as expected if I remove comma after "print a".

Sunday, November 23, 2008

pypy first test

I installed pypy and tested just a little bit.
my RPython code is


def myfunc():
for i in range(10000):
print "tamtam", i

and it took 0.06 sec to print 10000 lines, not bad.

$ python bin/translatorshell.py
>>> import translator.test.mytest as mytest
>>> t = Translation(mytest.myfunc)
>>> t.rtype()
>>> f = t.compile_c()
>>> print time.time();f();print time.time()
1227481538.02
tamtam 0
tamtam 1
...
tamtam 9999
1227481538.08

I also wanted to use pypy as a llvm python frontend. The sample code worked but my function didn't. The pypy getting-started doc says I should use llvm1.9 but I installed the latest (ver 2.4). It is said that llvm doesn't pay big attention to backward compatibility so maybe it's due to llvm version mismatch, or my wrong pypy/llvm usage.

Wednesday, November 19, 2008

FBX SDK

I've been having fun with FBX SDK. FBX SDK is seemingly well documented but what you really need are not so much. I'll write some of them I found briefly (I could write them much more in detail but not sure if the licence grants it). I didn't write basic stuff which you can find easily, I rather focused on the data structure. The FBX SDK version I used is 6.1.0


- KFbxLink is obsolete. If you find it in a document, the document is old.

Now KFbxLink is a typedef of KFbxCluster. But in the KFbxCluster document and method name, a "link" (typically seems to) means a node which has a KFbxSkeletion object as its attribute.

- A patch has no information on deformation (skeleton, etc.) by any means.

- A skeleton has no information on geometry (patch, etc.) by any means.

- A cluster connects a patch and a skeleton. It has control point weights, as well as its binding information such as transform matrix of a patch and transform matrix of a skeleton. which are all kept in the world coordinate.

You can register
a skin to a Node that has KFbxGeometry with AddDeformer(), and
a cluster to a skin with AddCluster(), and
a node that has KFbxSkeleton to a cluster with SetLink()

- You can get the transform matrix of the patch with GetTransformMatrix() which is used to bind the patch and the skeleton (not animation)

- You can get the transform matrix of the skeleton with GetTransformLinkMatrix() which is used to bind the patch and the skeleton (not animation)

- A cluster doesn't have a skeleton animation data.

- A node itself can have its animation data, not somewhere else.

More precisely, A node can have multiple KFbxTakeNode objects and each KFbxTakeNode object can contain a set of animation curves which are needed for the animation of the node corresponding to the take.

- Every KFbxTakeNode has its name in it. The whole animation of a take is obtained by summarizing all the information each node has in its KFbxTakeNode corresponding to the take (which is identified by the take name).

Sunday, November 16, 2008

Tuesday, November 11, 2008

Chalana - ASF to MEL converter

http://code.google.com/p/chalana/ via http://www.mento.info/dekekincaidLink

What is Chalana ?

Chalana is a freeware program which you can use to convert AMC and ASF file to MEL so you can import the motion capture data into Maya.

How does it work ?

Chalana works by using the Biomichanics inc's AMC to MEL and ASF to MOV executables which you can download for free at CMU Graphics Lab motion capture database. Chalana also includes the same programs in the application directory.

With Chalana you can convert the MEL files generated by the above programs in to your Maya projects frame rate and/or grid scale.

For whom ?

Chalana can be used by Anyone who wishes to insert motion capture data to their animation. Especially for game developers and crowd animators.

Hair plug-in

This is my hair plug-in.
At first I didn't make the plug-in for hair (the third movie is the closest one to the original purpose) but called it so later since it looked like hair, when I made it Maya was 4 and there was no hair.



















































Sunday, November 9, 2008

Why are there so many symbolic links?

Why are there so many symbolic links? Beta

This document is still beta.
If you find anything is wrong or inaccurate, please let me know.


You can see lots of symbolic links to shared libraries on Linux. Why are there so many and how it works? I couldn't find a detailed explanation on the web so I'll try to make a very easy tut here.

In short they are there for version control of the shared libraries. The key is that there are two types of library modifications, implementation modification with no interface change of a library, and interface modification. An "interface" of a function, classes, etc. is the meaning of it. If the declaration has been changed, like

old: void foo(int bar);
new: void foo(double bar);

You can say the interface of the function has been changed. And even if there's no change of the declaration, the interface may have been changed. It gets changed if the meaning of the argument or return value has been changed.

old: void foo(int bar); //bar means your age.
new: void foo(int bar); //bar means the number of chocolate bars you have eaten today.

and an "implementation" means how the function that satisfies the interface is made. You can make a sort function using bubblesort, heapsort, quicksort, or bogosort ;) all have the same interface but implementations are different. A modification to a function implementation with no interface change is often made for bug fix and performance tuning.


I will first tell you how it works and then why it works.

How it works

[Three types of names]

There are three types of names for one shared library.

(1)libfoo.so
(2)libfoo.so.1
(3)libfoo.so.1.0

Usually (1) and (2) are symbolic links. libfoo.so -> libfoo.so.1 -> libfoo.so.1.0



(1)libfoo.so
It is called "linker name". It is used when building (linking) a shared library like

gcc -lfoo -o myapp *.o

(2)libfoo.so.1
It is called "soname". th number indicates interface version. It is incremented on every interface modification of the library. A program that uses the library doesn't always assume the latest interface, since the program may only work on old interface. Remember an interface means the meaning of the function. In a shared library, its soname can be embedded (i.e. information about the interface version of the library is held in the library itself) by building (linking) like

gcc -shared -Wl,-soname,libfoo.so.1 -o libfoo.so.1.0 *.o

(the reason is described later).


(3)libfoo.so.1.0
It is called "real name". It is (usually) not a symbolic link. The first number (in this case 1) is the interface version of the library which is correspondent to the soname, and the second number (and sometimes the third) indicates the implementation version of the library, which gets incremented on every implementation modification of the library, like libfoo.so.1.1, libfoo.so.1.2 All the programs that use the library assume the latest implementation version of the library.


[When linking]
When a program (myapp) is built, like

gcc -lfoo -o myapp *.o

libfoo.so is searched and used (unless libfoo.a is found first). libfoo.so is a symbolic link: libfoo.so -> libfoo.so.1 -> libfoo.so.1.0 so what is really used is libfoo.so.1.0 The linker finds its soname libfoo.so.1, which is embedded in the libfoo.so.1.0, and put the soname to the executable (myapp) as a library that the executable is dependent on. It will sound a roundabout way but you'll see why the linker do such a thing when you've finished reading this blog entry.

[At the start up of myapp execution]
When myapp is started, libfoo.so.1 is searched because the name (soname) is recorded in myapp as a dependent library. Because libfoo.so.1 is a symbolic link to libfoo.so.1.0, libfoo.so.1.0 is loaded and used.


Why it works
Why is it good for the library's version control? I'll explain it by taking examples.


[When libfoo has a bug]
The library's author fixes the bug and release it as libfoo.so.1.1

gcc -shared -Wl,-soname,libfoo.so.1 -o libfoo.so.1.1 *.o

Because the interface is not changed, soname is not changed. Now libfoo.so.1.1 is the latest and the libfoo.so.1 's link should be updated.
old: libfoo.so.1 -> libfoo.so.1.0
new: libfoo.so.1 -> libfoo.so.1.1
This link is done by the system automatically (When you restart linux a program called ldconfig is executed, ldconfig finds wrong links and updates them). Now myapp uses libfoo.so.1.1 because libfoo.so.1 is a symbolic link to libfoo.so.1.1 In this way my app uses the latest implementation version of the library.

[When libfoo has a interface modification]

The library's author makes a new libfoo which has a different interface and release it as libfoo.so.2.0

gcc -shared -Wl,-soname,libfoo.so.2 -o libfoo.so.2.0 *.o

Because the interface has been changed, its soname should be updated (libfoo.so.2). Now a symbolic link libfoo.so.2 -> libfoo.so.2.0 is created. If he rebuilds myapp like before

gcc -lfoo -o myapp *.o

and runs it, libfoo.so.1.1 is still used, because myapp is still told to be dependent on libfoo.so.1 (because libfoo.so -> libfoo.so.1 when building) and libfoo.so.1 is still a symbolic link to libfoo.so.1.1

[When myapp's author wants to test the new library]

He wants to test the new library. He wants to make "yourapp" which uses the new libfoo. He can do so by changing the symbolic link libfoo.so

old: libfoo.so -> libfoo.so.1
new: libfoo.so -> libfoo.so.2

When he builds yourapp

gcc -lfoo -o yourapp *.o

and runs it. Now libfoo.so is a symbolic link to libfoo.so -> libfoo.so.2 -> libfoo.so.2.0, soname libfoo.so.2 is embedded in libfoo.so.2.0, and libfoo.so.2 is recorded in yourapp as a dependent library. On yourapp application startup, libfoo.so.2 -> libfoo.so.2.0 is loaded and used. myapp still uses libfoo.so.1 unless he rebuilds it after libfoo.so -> libfoo.so.2 relinking.


Conclusion
Every executable uses the latest implementation of a fixed interface which is recorded as a "soname" in the executable.
The soname which is recorded in an executable is the one that the symbolic link which name is "linker name" links to.

Saturday, November 8, 2008

Hooking library calls on Mac using DYLD_INSERT_LIBRARIES

Mac offers a way to override functions in a shared library with DYLD_INSERT_LIBRARIES environment variable (which is similar to LD_PRELOAD on Linux). When you make a twin brother of a function that is defined in an existing shared library, put it in you a shared library, and you register your shared library name in DYLD_INSERT_LIBRARIES, your function is used instead of the original one. This is my simple test. Here I've replaced f() in mysharedlib.dylib with f() in openhook.dylib.


$ cat mysharedlib.h
void f();
$ cat mysharedlib.c
#include <stdio.h>
#include "mysharedlib.h"

void f()
{
printf("hello");
}
$ cat main.c
#include <stdio.h>
#include "mysharedlib.h"

int main()
{
f();
return 0;
}
$ cat openhook.c
#include <stdio.h>
#include <dlfcn.h>
#include <unistd.h>
#include "mysharedlib.h"

typedef void (*fType)();
static void (*real_f)() = NULL;

void f()
{
if ( ! real_f)
{
void* handle = dlopen("mysharedlib.dylib", RTLD_NOW);
real_f = (fType)dlsym(handle, "f");
if ( ! real_f) printf("NG");
}
printf("--------zzz--------");
real_f();
}
$ cat bat
#!/bin/bash
gcc -flat_namespace -dynamiclib -o openhook.dylib openhook.c
gcc -dynamiclib -o mysharedlib.dylib mysharedlib.c
gcc mysharedlib.dylib main.c
export DYLD_FORCE_FLAT_NAMESPACE=
export DYLD_INSERT_LIBRARIES=openhook.dylib
./a.out
$ ./bat
--------zzz--------hello

You also need to define DYLD_FORCE_FLAT_NAMESPACE (doesn't matter what value it has). In general it makes the command (in this case a.out) unstable, not a lot in my opinion if we use it just for debugging purpose, but it increases the chance of symbol name conflicts.

You can use the same technique to override a method in a C++ class. Say there's a method named "fff" in a class AAA, like

class AAA
{
public:
int m;
AAA(){m = 1234;}
void fff(int a);
};

To override it, you first need to know the mangled symbol name of the method.

$ nm somelibrary.dylib | grep "T "
00000ed6 T __ZN3AAA3fffEi

Then what you need to define is _ZN3AAA3fffEi. Don't forget removing the first '_'. If you see multiple symbols in the shared library and not sure which one to override, you can check it by demangling a symbol like

$ c++filt __ZN3AAA3fffEi
AAA::fff(int)

Now you can override it like this.

$ cat mysharedlib.h
class AAA
{
public:
int m;
AAA(){m = 1234;}
void fff(int a);
};
$ cat mysharedlib.cpp
#include <stdio.h>
#include "mysharedlib.h"

void AAA::fff(int a)
{

printf("--ORIGINAL:%d--", a);
}
$ cat main.cpp
#include <stdio.h>
#include "mysharedlib.h"

int main()
{
AAA a;
printf("--------main1--------");
a.fff(50);
printf("--------main2--------");
return 0;
}
$ cat openhook.cpp
#include <stdio.h>
#include <dlfcn.h>
#include <unistd.h>
#include "mysharedlib.h"

typedef void (*AAAfffType)(AAA*, int);
static void (*real_AAAfff)(AAA*, int);

extern "C"
{

void _ZN3AAA3fffEi(AAA* a, int b)
{
printf("--------AAA::fff--------");
printf("%d, %d", b, a->m);
void* handle = dlopen("mysharedlib.dylib", RTLD_NOW);
real_AAAfff = (AAAfffType)dlsym(handle, "_ZN3AAA3fffEi");
if (real_AAAfff) printf("OK");
real_AAAfff(a, b);
}

}
$ cat bat
#!/bin/bash

gcc -flat_namespace -dynamiclib -lstdc++ -o openhook.dylib openhook.cpp
gcc -dynamiclib -lstdc++ -o mysharedlib.dylib mysharedlib.cpp
gcc -lstdc++ mysharedlib.dylib main.cpp
export DYLD_FORCE_FLAT_NAMESPACE=
export DYLD_INSERT_LIBRARIES=openhook.dylib
./a.out
$ ./bat
--------main1----------------AAA::fff--------50, 1234OK--ORIGINAL:50----------main2--------

Note that the first argument of the function call is this pointer, just like Python passes self to a bound method. C++ just does it implicitely. I believe this is compiler (in this case gcc) implementation specific, and there may be a case it is not true. Please use this technique at your own risk.

Here I assumed you have access to the header file that declares the function to get (the size of) argument data being passed to the function. If the size of arguments are all known (int, float, pointer, ...) you can wrap it even if you don't have the header but if not, you'll need to write assembler to modify the stack to pass the arguments to the original function.

Sunday, November 2, 2008

So how do I use Shake Command Window?

I use it for plug-in development. You don't have to restart Shake every time you modify a code. You can see the internal state of nodes interactively, you can test your plug-in after modifying the node state, and you can see how things work, monitor plug changes to see if node evaluation chain is working as you expected. There are two programs I was mainly using. I made these two programs bit by bit interactively with the Command Window.

[dispTree] Displays a node tree.
name: the root tree
recurseNode: set true if you want to show child node
showPlug: set true if you also want to show plugs and their values
recursePlug: set true if you want to show child plugs and their values

























[dispPlugInfo] displays informations about a plug
nodename: node name which has the plug to display
plugname: plug name to display



























You may need to add include directives to the header include (settings - header include).
#include <string>, etc.

[dispTree]


const char* name = "NRiScript1";
bool recurseNode = true;
bool showPlug = false;
bool recursePlug = false;

#define print(x) NRiSys::error((NRiName() + x).getString());

const NRiName operator +(const NRiName lhs, const std::string rhs)
{
return lhs + rhs.c_str();
}

void printSpace(int nestcount)
{
for (int n = 0; n < nestcount; ++n)
{
print(" ");
}
}


void dispPlug(NRiPlug* p)
{
NRiName val;
switch(p->getType())
{
case kString:
val = p->asString();
break;
case kInt:
val = p->asInt();
break;
case kFloat:
val = p->asFloat();
break;
case kDouble:
val = p->asDouble();
break;
case kPtr:
val.sprintf("0x%x", p->asPtr());
break;
default:
break;
}
print(".." + p->getName() + "=" + val + "\n");
}

void dispPlugTree(NRiPlug* plug, int nestcount, bool recursePlug)
{
printSpace(nestcount);
dispPlug(plug);
if (recursePlug)
{
int ncp = plug->getNbChildren();
for (int i = 0; i < ncp; ++i)
{
dispPlugTree(plug->getNthChild(i), nestcount + 1, recursePlug);
}
}
}

void dispNodeTree(NRiNode* node, int nestcount, bool showPlug, bool recurseNode = false, bool recursePlug = false)
{
printSpace(nestcount);
print(node->getName() + ":" + node->getClassName() + "\n");
if (showPlug)
{
int np = node->getNbPlugs();
for (int ip = 0; ip < np; ++ip)
{
dispPlugTree(node->getNthPlug(ip), nestcount + 1, recursePlug);
}
}

if (recurseNode)
{
int numkids = node->getNbChildren();
for (int i = 0; i < numkids; ++i)
{
dispNodeTree(node->getNthChild(i), nestcount + 1, showPlug, recurseNode, recursePlug);
}
}
}

void shakeCommandWinEntryFunc()
{
NRiNode* node = NRiNode::findNode(name);
dispNodeTree(node, 0, showPlug, recurseNode, recursePlug);
}

[dispPlugInfo]

const char* nodename = "NRiScript1.Gamma1";
const char* plugname = "In";


#define print(x) NRiSys::error((NRiName() + x).getString());


NRiName value(NRiPlug* p)
{
NRiName val;
switch(p->getType())
{
case kString:
val = p->asString();
break;
case kInt:
val = p->asInt();
break;
case kFloat:
val = p->asFloat();
break;
case kDouble:
val = p->asDouble();
break;
case kPtr:
val.sprintf("0x%x", p->asPtr());
break;
default:
break;
}
return val;
}

NRiName ioString(NRiPlug* p)
{
const char* types[] = {"ng", "in", "out", "inout"};
return types[p->getIO()];
}

NRiName typeString(NRiPlug* p)
{
switch(p->getType())
{
case kInt:
return "int";
case kFloat:
return "float";
case kPtr:
return "ptr";
case kString:
return "string";
default:
return "ng";
}
}

NRiName flagString(NRiPlug* p)
{
NRiPlug::Flags flags[] = {NRiPlug::kInternal, NRiPlug::kNotify, NRiPlug::kNotifyConnect,
NRiPlug::kInherit, NRiPlug::kDisconnectOnSet, NRiPlug::kPersistent,
NRiPlug::kRecompile, NRiPlug::kLoadable, NRiPlug::kPreUpdate,
NRiPlug::kOwnerScope, NRiPlug::kIgnoreConnect, NRiPlug::kAutoPlug,
NRiPlug::kLocal, NRiPlug::kIgnoreDependencies, NRiPlug::kDeleting,
NRiPlug::kXReadWrite, NRiPlug::kIgnoreType, NRiPlug::kAlwaysCallOwner,
NRiPlug::kMonitor, NRiPlug::kNoExpr, NRiPlug::kInterrupt, NRiPlug::kFiltered,
NRiPlug::kSerializeChildren, NRiPlug::kLookupSrc, NRiPlug::kExpressionSrc,
NRiPlug::kUserDefined, NRiPlug::kPassThrough, NRiPlug::kCurveDefined,
NRiPlug::kLocked};

const char* flagStrings[] = {
"Internal", "Notify", "NotifyConnect", "Inherit", "DisconnectOnSet",
"Persistent", "Recompile", "Loadable", "PreUpdate", "OwnerScope",
"IgnoreConnect", "AutoPlug", "Local", "IgnoreDependencies", "Deleting",
"XReadWrite", "IgnoreType", "AlwaysCallOwner", "Monitor", "NoExpr",
"Interrupt", "Filtered", "SerializeChildren", "LookupSrc", "ExpressionSrc",
"UserDefined", "PassThrough", "CurveDefined", "Locked"};
NRiName result;
for (int i = 0; i < 29; ++i)
{
if (p->getFlag(flags[i]))
{
result += flagStrings[i];
result += " ";
}
}
return result;
}

NRiName hasErrorString(NRiPlug* p)
{
if (p->hasError())
{
return "Yes";
}
else
{
return "No";
}
}

void printPlug(NRiPlug* p)
{
if (p)
{
print(p->getOwner()->getFullName() + ".." + p->getFullName());
}
print("\n");
}

void printPA(const NRiPArray < NRiPlug >& pa)
{
for(unsigned i = 0; i < pa.getLength(); ++i)
{
print(" ");
printPlug(pa[i]);
}
}

void shakeCommandWinEntryFunc()
{
NRiNode* node = NRiNode::findNode(nodename);
if ( ! node)
{
print("Node not found.\n");
return;
}
NRiPlug* plug = node->getPlug(plugname);
if ( ! plug)
{
print("Plug not found.\n");
return;
}

print("<<<");
print(node->getFullName() + ".." + plug->getFullName() + ">>>\n");
print(NRiName() + "[Value] " + value(plug) + "\n");
print(NRiName() + "[Expr] " + plug->asExpr() + "\n");
print(NRiName() + "[IO] " + ioString(plug) + "\n");
print(NRiName() + "[Type] " + typeString(plug) + "\n");
print(NRiName() + "[Flags] " + flagString(plug) + "\n");
print(NRiName() + "[HasError] " + hasErrorString(plug) + "\n");
print("[Input] ");
printPlug(plug->getInput());
print("[LogicalInput] ");
printPlug(plug->getLogicalInput());
//print("[Ouput] ");
//printPlug(plug->getOutput());
print("[Outputs]...\n");
NRiPArray < NRiPlug > pa;
plug->getOutputs(pa);
printPA(pa);
pa.clear();
print("[getLogicalOutputs]...\n");
plug->getLogicalOutputs(pa);
printPA(pa);
pa.clear();
print("[getDependencies]...\n");
const NRiPArray < NRiPlug > * pap;
pap = plug->getDependencies();
printPA(*pap);
print("[getDependents]...\n");
pap = plug->getDependents();
printPA(*pap);
}

Nice blog

Today I found a nice blog which was made by members of feeling software. I knew the company but didn't know they have started a blog. They are especially interested in image recognition including computer vision (reconstructing a model from images etc.), and interactive technologies. Because they have a sound knowledge of theories behind them, their words are quite realistic. Just browsing the blog will make you fun if you have no time to read all.

enlighten3d - An expert blog on computer vision and 3D graphics