Wednesday, December 24, 2008

Christmas

Merry Christmas.



Every year the first floor of the building I live in becomes like this. It's a building owner's preference ;)













Tuesday, December 23, 2008

OpenReality SDK first look

It is a large SDK so It'll need some time to get used to it but it seems to be a straight forward implementation and doesn't seem weird (like Houdini SDK :p), so it won't be difficult.

I still don't know several most basic stuff such as what a plug owner means, but it'll be enough for now until I can use motionbuilder itself (now I use only PLE). I'll test some on it, look at and run the sample programs which come with it then.


Class hierarchy

FBWrapperHolder
FBPlug
FBComponent
FBBox etc. A lot other
FBProperty
FBPropertyBasicList : abstruct
FBPropertyBaseList<> : template, abstruct
FBPropertyListFCurveKey, etc. A lot other
FBPropertyListComponentBase
FBPropertyListBox, etc. A lot other
FBPropertyListComponent
FBPropertyListCamera, etc. A lot other
FBPropertyBase : template
FBPropertyBaseComponent
FBPropertyBaseEnum
Other (FBPropertyStringList, FBPropertyAnimatable, FBPropertyEvent)


-----------------------------------------------------------
[FBWrapperHolder]
Probably Python wrapper related? Let's ignore this for now.


-----------------------------------------------------------
[FBPlug]
Connect*()
Disconnect*()
Standard plug implementation

HFBPlug GetOwner()
Nested hierarchy. Looks a plug can be owned by another. Not the same as parent.
Maybe used for a plug(component) to own a plug(propery)? as property/component has
no methods to get "who owns it"/"owned by whom" info.

int GetOwnerCount()
Implies multiple owners. A plug can be shared by multiple plugs.

virtual char* ClassName()
virtual int GetTypeId()
These two implies property is typed (different from Property type)

bool MoveSrcAt(int pIndex, int pAtIndex): Move source connection at pIndex to pAtIndex.
Multiple sources allowed and its order is important.


-----------------------------------------------------------
[FBComponent]
Represent objects (camera, curve, constraint...). Property holder.
Components with callback funcs have a FBPropertyEvent type property,
which has add()/remove() methods that accept a pointer to a user defined callback function.
e.g. FBLayout::OnIdle/OnResize etc.

virtual int PropertyAdd(FBProperty *Property)
void PropertyRemove(FBProperty *Property)
Behaves as a property holder

FBObjectFlag GetObjectFlags()
selectable, savable, deletable, etc.


void SetObjectStatus(FBObjectStatus pStatus, bool pValue)
bool GetObjectStatus(FBObjectStatus pStatus)
creating, deleting, storing, etc.
Used within a callback? Why is there set? Can the SDK user set it?

FBPropertyManager PropertyList Read Only Property: Manages all of the properties for the component.

FBPropertyListComponent Component List: List of components.
What's this? Children?

FBPropertyListComponent Parents List: Parents.
Similar hierarchy to Property? Why no get/setChildren() stuff?

FBPropertyBool Selected Read Write Property: Selected property.

FBPropertyString Name Read Write Property: Unique name of object.
Component is Named, Component has a concept of namespace.


-----------------------------------------------------------
[FBProperty] holder for function callbacks into the internals of the application
Holder for func callbacks? Not just a data representation? What are the correspondent methods?

char* GetName()
void SetName(char *pName)
Has name

virtual FBPropertyType GetPropertyType()
char* GetPropertyTypeName()
int, float, ... (different from Plug type)

int AsInt()
bool SetInt(int pInt)
virtual char* AsString()
virtual bool SetString(char *pString)
Value get/set.
Why can set*() fail?
Why string methods are virtual? expression related?

FBPropertyFlag GetPropertyFlags()
notset, hide, forcestatic, disable, animated, notsavable, readonly, notuserdletable

void SetMinMax(double pMin, double pMax)
Similar to Maya

void* GetParent()
Not the same as owner. Why void*?

virtual void SetData(void *pData)
virtual void GetData (void *pData, int pSize, FBEvaluateInfo *pEvalInfo=NULL) const
Looks for more complex type of data


-----------------------------------------------------------
[FBPropertyBasicList]
Abstruct class. Property container.
Type independent methods are here.

virtual int GetCount ()=0 Get the number of properties in the list.
virtual void RemoveAt (int pIndex)=0 Remove property at pIndex.
Methods to act as a container.


-----------------------------------------------------------
[FBPropertyBaseList<>]
Abstruct template class. Type independent methods are in FBPropertyBasicList.
Container of non-component properties.
Base class of FBPropertyListRigidBody, FBPropertyListMotionClip, etc.

virtual int Add(tType pItem)=0 Add a property to the list.
virtual tType operator[](int pIndex)=0 [] operator overload.
virtual int Find(tType pItem) Locate a property in the list.
virtual int Remove(tType pItem) Remove pItem from the list.
virtual tType GetAt(int pIndex) Get a property at pIndex.
Methods to act as a container.


-----------------------------------------------------------
[FBPropertyListComponentBase]
Abstruct *non-template* class. Type independent methods are in FBPropertyBasicList.
Container of component properties (i.e. FBComponent).

virtual int Add(FBComponent *pItem)
virtual void RemoveAt(int pIndex)
virtual FBComponent* GetAt(int pIndex)=0
FBComponent* operator[](int pIndex)
virtual int GetCount()
virtual int Find(FBComponent *pItem)
virtual int Remove(FBComponent *pItem)
Methods to act as a container.
It doesn't inherit FBPropertyBaseList but all the methods are implemented here with it's template type tType=FBComponent.
Bbefore FBPropertyBaseList was used for components as well. It is epareted for some reason.


-----------------------------------------------------------
[FBPropertyListComponent]
Implements only GetAt() method.

virtual FBComponent* GetAt(int pIndex)


-----------------------------------------------------------
[FBPropertyBase, FBPropertyBaseComponent, FBPropertyBaseEnum]
No idea yet.

Monday, December 22, 2008

Using doctest module for if __name__ == '__main__' test

Many people including me write a test code in __name__ == '__main__' like


def somefunction(intvalue):
return intvalue * 2

if __name__ == '__main__':
#Tests whatever.
if somefunction(2) != 4:
print "NG"

Previsously I did something like this.

if __name__ == '__main__':

def test(cmd):
print cmd,
exec cmd

ph = PreferenceHolder(pref1=1.0, pref2=100, pref3='tamtam')
prefs = ph.getPreferences().items()
test("print prefs == [('pref1', 1.0), ('pref2', 100), ('pref3', 'tamtam')]")
test("print ph.pref1 == 1.0")

I wanted a nicer way so looked around documents for Python unit test modules. Python has two modules for unit testing, unittest and doctest. unittest is kind of overkill for a test in __name__ == '__main__' so I looked at doctest, which is better because it's purpose is to offer tools to write "readable" tests. I serched for a function to let me write a test code like

if __name__ == '__main__':
teststr = """
>>> ph = PreferenceHolder(pref1=1.0, pref2=100, pref3='tamtam')
>>> ph.getPreferences().items()
[('pref1', 1.0), ('pref2', 100), ('pref3', 'tamtam')]
>>> ph.pref1
1.0
"""
import doctest
doctest.teststring(teststr) #Note: it doesn't exist

Unfortunately I found I cannot do this. I could find a function that takes a filename to get a docstring but couldn't find one that takes a Python string directly like above code. I think this function is not provided because doctest module is designed to write the whole test in docstrings (It is provided as a class but not as a userfriendly function. Readability is important because everybody looks at this test code as an example). I was just looking for an easy way to give the users usage examples (primary interest) and have it work as a test (secondary), so I didn't want to write the whole tests in each doc string. I found an alternative function run_docstring_examples() which tests only a docstring for a specified object given by one of its arguments. The document says

There’s also a function to run the doctests associated with a single object. This function is provided for backward compatibility. There are no plans to deprecate it, but it’s rarely useful

but it's nearly perfect for my purpose. So now, I can write the test like this.

if __name__ == '__main__':
def test():
"""
>>> ph = PreferenceHolder(pref1=1.0, pref2=100, pref3='tamtam')
>>> ph.getPreferences().items()
[('pref1', 1.0), ('pref2', 100), ('pref3', 'tamtam')]
>>> ph.pref1
1.0
"""
import doctest
doctest.run_docstring_examples(test, globals(), False, __name__)

Because it's a doctest, it prints nothing if all the tests finish successfully (unless you set True to the third argument of run_docstring_examples). If it fails, it prints an error message

$ python preferenceholder.py
**********************************************************************
File "preferenceholder.py", line 94, in __main__
Failed example:
ph.pref1
Expected:
0.0
Got:
1.0
$

Nice isn't it?
Please see the previous blog entry for the actual usage.

By the way, one common pitfall of using doctest is you don't update comments in a docstring when you modify its implementation. I've seen lots of wrong comments in existing dectests. Try to write only comments about specs and not about implementation details unless it is necessary.

Sunday, December 21, 2008

Preference class (beta)

===BETA VERSION===

I made a generic preference (or config) data class. It is designed to hold system wide preference data. It has a flag "isPrefValueChanged" flag which is set when a preference is added/deleted/modified, and reset when it is saved/loaded. All the property data must be picklable.



You can set/get/delete preference attributes directly.
Direct data setting to a property will set isPrefValueChanged flag.

In addition there are four method.

isPrefValueChanged(self):
Return true if preference has been changed or not saved.

savePreference(self, filename):
Save preferences to a file. isPrefValueChanged flag will be reset.

loadPreference(self, filename):
Load preferences from a file. isPrefValueChanged flag will be reset.

getPreferences(self):
Get preference data in a {preference name : value} dictionary.
It returns a deep copy of the preference data. Changing a value will not modify
the preference value held in this object.




import copy
import cPickle as pickle

class PreferenceHolder(object):
"""System wide preference holder class.
"""

def __init__(self, *args, **kwargs):
self.__dict__['_pa'] = pa = type("", (), {})() #Private attributes
pa.hcls = hcls = type("", (), {})
pa.hobj = hcls()
pa.isPrefValueChanged = True
pa.filename = ''
for name, value in kwargs.items():
setattr(self, name, value)

def __setattr__(self, name, value):
pa = self._pa
if not hasattr(pa.hcls, name):
privatename = '__' + name
def getvalue(self):
return getattr(self, privatename)
def setvalue(self, value):
setattr(self, privatename, value)
def delvalue(self):
delattr(self, privatename)
p = property(getvalue, setvalue, delvalue)
setattr(pa.hcls, name, p)

setattr(pa.hobj, name, value)
pa.isPrefValueChanged = True

def __getattr__(self, name):
return getattr(self._pa.hobj, name)

def __delattr__(self, name):
pa = self._pa
delattr(pa.hobj, name)
delattr(pa.hcls, name)
pa.isPrefValueChanged = True

def isPrefValueChanged(self):
"""Return true if preference has been changed or not saved.
"""
return self._pa.isPrefValueChanged

def getPreferences(self):
"""Get preference data in a {preference name : value} dictionary.
It returns a deep copy of the preference data. Changing a value will not modify
the preference value held in this object.
"""
result = {}
for name in [n for n in dir(self._pa.hcls) if n[0] != '_']:
result[name] = getattr(self, copy.deepcopy(name))
return result

def savePreference(self, filename):
"""Save preferences to a file. isPrefValueChanged flag will be reset.
"""
pa = self._pa
if filename == pa.filename and not self.isPrefValueChanged():
return

try:
fileobject = open(filename, 'w')
pickle.dump(self.getPreferences(), fileobject)
pa.isPrefValueChanged = False
pa.filename = filename
finally:
fileobject.close()

def loadPreference(self, filename):
"""Load preferences from a file. isPrefValueChanged flag will be reset.
"""
pa = self._pa
try:
fileobject = open(filename, 'U')

for name in self.getPreferences().keys():
delattr(self, name)

savedprefholder = pickle.load(fileobject)
for name, value in savedprefholder.items():
setattr(self, name, value)
pa.isPrefValueChanged = False
pa.filename = filename
finally:
fileobject.close()

if __name__ == '__main__':
def test():
"""
>>> ph = PreferenceHolder(pref1=1.0, pref2=100, pref3='tamtam')
>>> ph.getPreferences().items()
[('pref1', 1.0), ('pref2', 100), ('pref3', 'tamtam')]
>>> ph.pref1
1.0
>>> ph._pa.isPrefValueChanged = False #Do not do it!
>>> ph.pref4 = 'new pref'
>>> ph.pref4
'new pref'
>>> ph.isPrefValueChanged()
True
>>> ph._pa.isPrefValueChanged = False #Do not do it!
>>> del ph.pref1
>>> ph.pref1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "preferenceholder.py", line 34, in __getattr__
return getattr(self._pa.hobj, name)
AttributeError: '' object has no attribute 'pref1'
>>> ph.isPrefValueChanged()
True
>>> #Tests that create files on disk
...
>>> #ph.pref1 = 1.0
>>> #ph.pref1
1.0
>>> #ph.savePreference('preffile')
>>> #ph.isPrefValueChanged()
False
>>> #ph.pref1
1.0
>>> #ph.isPrefValueChanged()
False
>>> #ph.loadPreference('preffile')
>>> #ph.isPrefValueChanged()
False
>>> #ph.pref1
1.0
"""

import doctest
doctest.run_docstring_examples(test, globals(), False, __name__)

Friday, December 19, 2008

Started looking at Open Reality SDK

Getting a couple of documents MotionBuilder Open Reality SDK Help and MotionBuilder Python Scripting Help from an Autodesk page, I started looking at Open Reality SDK. I've just started learning it so I don't have a lot to tell except that it lets us to execute Python scripts over tcp port 4242. I'll write a review when I've read them more.

A very good site and the author's blog Stumbling Toward ‘Awesomeness’.

Saturday, December 13, 2008

Japanese has no subject

I found a book about Japanese grammar titled "Japanese needs no subject". I haven't read it but I agree with what the title says, since when I studied Japanese grammer at school I couldn't understand why we had to distinguish subject from others. Many Japanese grammar books say that in spoken Japanese subjects are often omitted. But in my opinion Japanese has no concept of "subject". To explain that I'll modify English slightly (modifying English again, sorry ;). The new English grammar has no subject, but it has additional proposition "sub" which is followed by a noun indicating the subject of the sentence.

Went to school sub me in the morning.
Should see a dentist in the afternoon sub you.
Is hungry sub her.

They are quite weird but all makes perfect sense. These sentences written in new English have all semantic subject but there's no special grammatical subject. And just like other propositional phrases, it is not added to a sentence if you don't need to clarify it. I think it simulates Japanese sense of subject quite correctly.

Thursday, December 4, 2008

Ideal version control in a production

This is an entry that follows this entry and related to this.
I've been thinking what an ideal software version control in a production looks like.

In my opinion an ideal way of version control should satisfy all of these.

a) A software bug fix must be reflected to the work environment automatically.
b) An interface modification of a software must be version controlled and artists must be able to chose which version of the software to use for each project.
c) An artist must be able to open a scene data as it was when it was modified last time. It means softwares (plug-ins etc.) must be the same as what it was including bugs.

I think a) and b) can be solved by defining naming rules carefully referencing this idea. For c) we will need to use a version control system. It'll be good to version control all the softwares in a file server and offer artists, access it through a symbolic link, and offer some utility tool with GUI for artists to retrieve a snapshot of the softwares and change the symbolic link.

Of course I know there are lots of reasons that cannot be simply done, historical reason, human resource, data size, data convert, artist preference, tools/scene data that depend on existing systems, network requirement, and sometimes a boundary between data and tools are not clear ... but it's still important to think about a symple ideal case to get a picture of a good production infrastructure.

Wednesday, December 3, 2008