Maya のシミュレート 1

Maya のしくみ

Mayaのシーンは、DGとDAGによって作られています。
そのうちの、DGと同じ原理の仕組みを作ってみましょう。
DGでは、あるノードのアトリビュート値が変更されると、 そのアトリビュートにコネクトされたアトリビュートの値が、 自動的に同じ値に変更される必要があります。 この仕組みを作るためには、デザインパターンの一種であるオブザーバ(Observer)パターンを応用します。

オブザーバ・パターン

あるオブジェクトの状態が変化したときに、他のオブジェクトにそれを知らせる仕組みです。
オブザーバ・パターンでは、そのために状態が変化したことを知らせる専用のメソッドを定義します。
以下の例では、nmaya1.pyのnotify_observersメソッドなどが、 コネクトしたノードに状態が変化したことを知らせるメソッドになっています。

オブザーバ・パターンの例

以下のスクリプトは、オブザーバ・パターンの原理を利用して、 MayaのDGと似た動作をシミュレートしたものです。
なるべく、スクリプトをシンプルにするために、ノードのアトリビュートは一つだけとします。 また、アトリビュートにコネクトできるアトリビュートも一つだけです。
このスクリプトを実行するとプロンプトが表示されるので、 ノードを作成し、メソッドを実行してゆきます。

作成できるノードの種類は以下の1種類だけです。

Node(attr)
attrをアトリビュート値としてインスタンスを作成します。

ノードで使用できるメソッドの種類は以下の通りです。

getAttr()
アトリビュートの値を返します。
setAttr(val)
アトリビュートに値valを設定します。
connectAttr(node)
他のnodeのアトリビュートと、自分のアトリビュートをコネクトします。
自分のアトリビュートが変化すると、nodeのアトリビュート値も同じ値になります。

その他、以下のメソッドを使用します。

sys.exit(0)
コマンドの実行を終了します。
  1. 以下のPythonスクリプトを nmaya1.py というファイル名で保存します。
    import sys
    
    class Node(object):
        def __init__(self, attr=0):
            self.attr = attr
            self.observer = None
        def update(self, node):
            self.setAttr(node.getAttr())
        def notify_observers(self):
            if self.observer:
                self.observer.update(self)
        def connectAttr(self, node):
            self.observer = node
            self.notify_observers()
        def getAttr(self):
            return self.attr
        def setAttr(self, val):
            self.attr = val
            self.notify_observers()
    
    while(1):
        s = raw_input('command: ')
        exec(s)
    
  2. シェル上で、以下のコマンドを実行します。
    % python nmaya1.py
  3. プロンプト(command:)が表示されたら、以下のPython関数を実行します。
    command: n1 = Node(1)
    command: n2 = Node(2)
    これで、n1, n2 というノードが作成されました。
  4. 各ノードのアトリビュート値を調べてみます。
    command: print n1.getAttr()
    1
    command: print n2.getAttr()
    2
    最初に設定したアトリビュート値(1 と 2)が表示されます。
  5. n1ノードをn2ノードにコネクトしてみます。
    command: n1.connectAttr(n2)
  6. n2ノードのアトリビュート値を調べてみます。
    command: print n2.getAttr()
    1
    n2ノードのアトリビュート値が、n1ノードのアトリビュート値と同じになっているのがわかります。
  7. n1ノードのアトリビュート値を変更してみます。
    command: n1.setAttr(3)
  8. n2ノードのアトリビュート値を調べてみます。
    command: print n2.getAttr()
    3
    n2ノードのアトリビュート値が自動的に変更されているのがわかります。
  9. スクリプトを終了します。
    command: sys.exit(0)

スクリプトの解説

import sys
whileループ内で、Python式の入力を終了するためにインポートします。
nmaya1.py内では使用していません。
class Node(object):
ノードクラスを定義します。
def __init__(self, attr=0):
ノードの初期化メソッドです。
引数attr はアトリビュートの初期値を表します。
attr が与えられない場合は 0 になります。
self.attr = attr
インスタンス変数 self.attr に初期値 attr を代入します。
self.observer = None
最初は、オブザーバ(アトリビュートを監視するノード)は無し(None)にしておきます。
def update(self, node):
自分のアトリビュートをオブザーバによって更新するメソッドです。
self.setAttr(node.getAttr())
オブザーバ(node)からアトリビュートの値を取り出して(getAttr())、 自分のアトリビュートに設定(setAttr())します。
def notify_observers(self):
オブザーバによって、アトリビュートを更新するメソッドです。
if self.observer:
オブザーバが存在するかどうか調べます。
self.observer.update(self)
オブザーバが存在すれば、自分自身のupdateメソッドを実行します。
def connectAttr(self, node):
オブザーバを登録するメソッドです。
self.observer = node
インスタンス変数 self.observer にオブザーバノードを代入します。
self.notify_observers()
このクラスのnotify_observersメソッドを実行します。
def getAttr(self):
このクラスのアトリビュート値を得るメソッドです。
return self.attr
アトリビュート値を返します。
def setAttr(self, val):
このクラスのアトリビュートに値を設定するメソッドです。
self.attr = val
インスタンス変数self.attrに値(val)を代入します。
self.notify_observers()
notify_observersメソッドを実行して、オブザーバのアトリビュートも変更します。
while(1):
コマンドを入力するために、永久ループを実行します。
s = raw_input('command: ')
プロンプト(command:)を表示し、標準入力から入力した文字列をsに代入します。
exec(s)
sの文字列をPythonの式として実行します。

シンプルなモデリングツール 1

上のnmaya1.pyを応用して、シンプルなモデリングができるシステムに改良してみます。

作成できるノードの種類は以下の通りです。
これらのノードは、Nodebaseクラスを継承して作成してあります。

MakeTriPoly
三角ポリゴン作成ノード
ポリゴンの各頂点座標は、 (0, 0), (0, 1), (1, 0) で固定です。
MoveTriPoly
三角ポリゴンの1頂点を移動するノード
ノードを作成するときに、移動する頂点番号と相対的な移動距離を指定します。
MoveTriPoly(頂点番号, X方向の移動距離, Y方向の移動距離)
これらの、引数の値はノード作成以後に変更できません。
Shape
Meshデータを保持するシェイプノード

ノード、その他で使用できるメソッドの種類は以下の通りです。

getAttr()
アトリビュートの値を返します。
setAttr(val)
アトリビュートに値valを設定します。
connectAttr(node)
他のnodeのアトリビュートと、自分のアトリビュートをコネクトします。
自分のアトリビュートが変化すると、nodeのアトリビュートに値も同じ値になります。
Shape.objset.save()
モデリングされたデータを標準出力へ出力します。
sys.exit(0)
コマンドの実行を終了します。
  1. 以下のPythonスクリプトをnmaya2.pyというファイル名で保存します。
    import sys
    
    class ObjectSet(object):
        def __init__(self, sobjs=None):
            self.objs = []
            self.saveobjs = sobjs
        def setObj(self, obj):
            self.objs.append(obj)
        def save(self):
            if self.saveobjs == None:
                return
            self.saveobjs(self.objs)
    
    class Point(object):
        def __init__(self, x=0, y=0):
            self.x = x
            self.y = y
    
    class TriPoly(object):
        def __init__(self, p1, p2, p3):
            self.p = [[p1, p2, p3]]
    
    class Nodebase(object):
        def __init__(self, attr=None):
            self.attr = attr
            self.observer = None
        def update(self, node):
            self.setAttr(node.getAttr())
        def notify_observers(self):
            if self.observer:
                self.observer.update(self)
        def connectAttr(self, node):
            self.observer = node
            self.notify_observers()
        def getAttr(self):
            return self.attr
        def setAttr(self, val):
            self.attr = val
            self.notify_observers()
    
    class MakeTriPoly(Nodebase):
        def __init__(self):
            tp = TriPoly(Point(0, 0), Point(0, 1), Point(1, 0))
            Nodebase.__init__(self, tp)
    
    class MoveTriPoly(Nodebase):
        def __init__(self, n, x, y):
            self.num = n
            self.x = x
            self.y = y
            Nodebase.__init__(self)
        def update(self, node):
            tp = node.getAttr()
            f = tp.p[0]
            f[self.num].x += self.x
            f[self.num].y += self.y
            self.setAttr(tp)
    
    class Shape(Nodebase):
        objset = None
        def __init__(self):
            if self.objset:
                self.objset.setObj(self)
            Nodebase.__init__(self)
    
    def saveAllObjs(objs):
        n = 0
        for o in objs:
            print 'g tri' + str(n)
            fs = o.getAttr()
            for p in fs.p:
                print 'vn 0 1 0'
                print 'v ',p[0].x, ' 0 ',p[0].y
                print 'v ',p[1].x, ' 0 ',p[1].y
                print 'v ',p[2].x, ' 0 ',p[2].y
                print 'f ' + str(3*n+1) + '//' + str(n+1) + ' ' + str(3*n+2) + '//' + str(n+1) + ' ' + str(3*n+3) + '//' + str(n+1)
            n += 1
    
    Shape.objset = ObjectSet(saveAllObjs)
    
    execfile(sys.argv[1])
    
  2. 以下のPythonスクリプトを作成し、spoly1.pyというファイル名で保存します。
    m1 = MakeTriPoly()
    s1 = Shape()
    m1.connectAttr(s1)
    Shape.objset.save()
    sys.exit(0)
    
  3. シェル上で、以下のコマンドを実行します。
    % python nmaya2.py spoly1.py > spoly1.obj
  4. Maya上で、 ファイル → インポート によって spoly1.obj を読み込みます。
    三角ポリゴンが表示されます。

他のサンプルスクリプト

シンプルなモデリングツール 2

上のnmaya2.pyから、アトリビュートをいくつでも追加できるように改良してみます。

作成できるノードの種類と、アトリビュートは以下の通りです。

MakeTriPoly
三角ポリゴン作成ノード
out
Meshのデータを出力するアトリビュート
ポリゴンの各頂点座標は、 (0, 0), (0, 1), (1, 0) で固定です。
MoveTriPoly
Meshデータの1頂点を移動するノード
num
移動するポリゴン頂点の番号
mx
X方向の移動距離
my
Y方向の移動距離
in
Meshデータを入力するアトリビュート
out
Meshデータを出力するアトリビュート
Shape
Meshデータを保持するシェイプノード
in
ポリゴンのデータを入力するアトリビュート
  1. 以下のPythonスクリプトをnmaya3.pyというファイル名で保存します。
    import sys
    
    class ObjectSet(object):
        def __init__(self, sobjs=None):
            self.objs = []
            self.saveobjs = sobjs
        def setObj(self, obj):
            self.objs.append(obj)
        def save(self):
            if self.saveobjs == None:
                return
            self.saveobjs(self.objs)
    
    class Point(object):
        def __init__(self, x=0, y=0):
            self.x = x
            self.y = y
    
    class TriPoly(object):
        def __init__(self, p1, p2, p3):
            self.p = [[p1, p2, p3]]
    
    class Nodebase(object):
        def __init__(self, attr={}):
            self.attr = attr
            self.observer = {}
        def update(self, node, src, dest):
            self.setAttr(dest, node.getAttr(src))
            self.compute()
        def notify_observers(self, src):
            if self.observer.has_key(src):
                obs = self.observer[src]
                obs[0].update(self, src, obs[1])
        def compute(self):
            pass
        def connectAttr(self, src, node, dest):
            self.observer[src] = [node, dest]
            self.notify_observers(src)
        def getAttr(self, aname):
            return self.attr[aname]
        def setAttr(self, aname, val):
            self.attr[aname] = val
            self.notify_observers(aname)
        def addAttr(self, aname, default=0.0):
            self.attr[aname] = default
    
    class MakeTriPoly(Nodebase):
        def __init__(self):
            tp = TriPoly(Point(0, 0), Point(0, 1), Point(1, 0))
            attr = {'out': tp}
            Nodebase.__init__(self, attr)
    
    class MoveTriPoly(Nodebase):
        def __init__(self, n=0, x=0.0, y=0.0):
            attr = {'in': None, 'out': None, 'num': n, 'mx': x, 'my': y}
            Nodebase.__init__(self, attr)
        def compute(self):
            tp = self.getAttr('in')
            f = tp.p[0]
            num = self.getAttr('num')
            f[num].x += self.getAttr('mx')
            f[num].y += self.getAttr('my')
            self.setAttr('out', tp)
            self.notify_observers('out')
    
    class Shape(Nodebase):
        objset = None
        def __init__(self):
            if self.objset:
                self.objset.setObj(self)
            attr = {'in': None, 'out': None}
            Nodebase.__init__(self, attr)
        def compute(self):
            tp = self.getAttr('in')
            self.setAttr('out', tp)
            self.notify_observers('out')
    
    def saveAllObjs(objs):
        n = 0
        for o in objs:
            print 'g tri' + str(n)
            fs = o.getAttr('out')
            for p in fs.p:
                print 'vn 0 1 0'
                print 'v ',p[0].x, ' 0 ',p[0].y
                print 'v ',p[1].x, ' 0 ',p[1].y
                print 'v ',p[2].x, ' 0 ',p[2].y
                print 'f ' + str(3*n+1) + '//' + str(n+1) + ' ' + str(3*n+2) + '//' + str(n+1) + ' ' + str(3*n+3) + '//' + str(n+1)
            n += 1
    
    Shape.objset = ObjectSet(saveAllObjs)
    
    execfile(sys.argv[1])
    

サンプルスクリプト

練習問題

注意

これらのサンプルスクリプトでは、以下のような問題点・改良点があります。

参考


Prev
Home | Contents
Mail