2017年03月24日

modo10.2v1のスクリプトについて調べてみた その69

引き続き「TD SDK」について調べてみたい。

今回も「modo.util.makeQuickCommand()」ファンクション内の「CmdMyCustomCommand」クラスについて調べてみたい。

「cmd_Query()」はオーバーライドされてるけど中身は「lx.notoimpl()」で親クラスと同じなのでコピーした時の消し忘れみたいだな。

 def cmd_Query(self, index, vaQuery):
    lx.notimpl()

この「cmd_Query()」は「index」に引数の番号、「vaQuery」に「ILxValueArray」インターフェースを実装したオブジェクトが渡されてmodoから呼び出されて、「index」で示すパラメータの値を「vaQuery」に入れて、うまくいったら「lx.result.OK」を返すようになっている。「vaQuery」はそのままでは扱えないので「lx.object.ValueArray」クラスでラップして使う。だから実装すると下のような感じのコードになる。

 def cmd_Query(self, index, vaQuery):
    va = lx.object.ValueArray(vaQuery)
    if index == 0:
       va.AddString(self.strvalue)
    elif index == 1:
       va.AddInt(self.intvalue)
    return lx.result.OK

「CmdMyCustomCommand」クラスの継承元の「BasicCommand」クラスのさらに継承元の「lx.ifc.Command」クラスは「modoのインストールフォルダ/extra/Python/modules」フォルダの「lxifc.py」ファイルの中に定義が書いてあって、ここに書いてあるメソッドがコマンドとmodoとのインターフェースになっている。「Command」クラスは下のようになっていて、全て中身は「lx.notimpl()」つまり未実装になっている。

class Command:
    def cmd_Tag(self):
        lx.notimpl()
    def cmd_Name(self):
        lx.notimpl()
    def cmd_UserName(self):
        lx.notimpl()
    def cmd_ButtonName(self):
        lx.notimpl()
    def cmd_Desc(self):
        lx.notimpl()
    def cmd_Tooltip(self):
        lx.notimpl()


     :
     :

    def cmd_Query(self,index,vaQuery):
        lx.notimpl()
    def cmd_NotifyAddClient(self,argument,object):
        lx.notimpl()
    def cmd_NotifyRemoveClient(self,object):
        lx.notimpl()
    def cmd_DialogFormatting(self):
        lx.notimpl()
    def cmd_IconImage(self,w,h):
        lx.notimpl()

コマンドじゃなくてツールを作りたい場合は「Tool」インターフェースを定義するクラスなんかも入っている。

class Tool:
    def tool_Reset(self):
        lx.notimpl()
    def tool_Evaluate(self,vts):
        lx.notimpl()
    def tool_VectorType(self):
        lx.notimpl()
    def tool_Order(self):
        lx.notimpl()
    def tool_Task(self):
        lx.notimpl()
    def tool_Sequence(self,seq):
        lx.notimpl()
    def tool_ShouldBeAttribute(self,task):
        return 0
    def tool_GetOp(self,flags):
        lx.notimpl()
    def tool_CompareOp(self,vts,toolop):
        return lx.symbol.iTOOLOP_DIFFERENT
    def tool_UpdateOp(self,toolop):
        lx.notimpl()

「cmd_UserName()」、「cmd_Desc()」、「cmd_Tooltip()」についても「BasicCommand」クラスじゃなくて「Command」クラスからのオーバーライドだ。

 def cmd_UserName(self):
    return userName

 def cmd_Desc(self):
    return description

 def cmd_Tooltip(self):
     return toolTip

これらは「makeQuickCommand()」の引数で与えられた値をそれぞれmodoの問い合わせに対して返すようになっている。

def makeQuickCommand(name, func,
    arguments=None,
    userName='myCmdName',
    description='This command ...',
    toolTip='Tooltip'):

続きはまた次回。

modo10ブログ目次



take_z_ultima at 11:30|この記事のURLComments(0)TrackBack(0)modo | CG

2017年03月23日

ビットマップペイントツールのチュートリアルをやってみた その35 3dsmax 2017

引き続きMAXScriptマニュアルに載っている「チュートリアル-ビットマップペイントツールを9つの簡単なステップで作成する」 の続きを考えて見たい。

今回はまずサーフェス上の矩形上にランダムなポイントを生成して、それをUVマップ座標値で出力するファンクションを作ってみた。

まず、前に作ったヒットポイントを原点とするストローク方向と法線方向、そしてそれらに垂直の単位ベクトルを求めるプログラムを使って、そのローカル座標をワールド座標に変換するマトリクスを生成した。それが下のプログラムの「(matrix3 du dv dw (worldHit + offset))」の部分。これをさらにUV座標系に変換するマトリクスを求めて掛け合わせることで、ヒットポイント上のローカル座標をUVマップの座標に変換するマトリクスが求まる。それを返すのが「toRectMatrix」ファンクションだ。

 fn toRectMatrix =
 (
   drawVec = normalize(thePainterInterface.getHitVec 0)
   thePainterInterface.getHitPointData &localHit &localNormal &worldHit &worldNormal &radius &str 0
   sideVec = normalize(cross drawVec worldNormal)
   ms2t = getS2TMatrix localNormal worldNormal
   return  (matrix3 sideVec drawVec worldNormal (worldHit + offset)) * ms2t
 )

あとはローカル座標系上のX−Y平面に1辺の長さ「size」の正方形を想定して、その中のランダムな点の座標は、

 random [-size/2.0,-size/2.0,0][size/2.0,size/2,0])

で求められるので、その座標値に「toRectMatrix()」で求めた変換マトリクスを後ろからかけてやれば、UVマップ上のランダムな点が得られる。下のプログラムはチュートリアルの「PaintStroke()」にこれを組み込んでみたもの。今回ブラシサイズはとりあえず「BrushSize.value」にしてある。

 fn PaintStroke =
 (
   theMesh = theObj.mesh
   thePainterInterface.getHitFaceData &bary &faceIndex theObj 0
   if not strokestart then (  
     if airBrush.checked then (
       size = BrushSize.value
       toM = toRectMatrix()
         for b = 1 to (BrushSize.value / AirBrushSpeed.value) do(
            p = (random [-size/2.0,-size/2.0,0][size/2.0,size/2,0]) * toM
            drawStroke [p.x * bitmapx_1, bitmapy_1 - p.y * bitmapy_1] [p.x * bitmapx_1, bitmapy_1 - p.y * bitmapy_1]
         )
     )

下の画像は「AirBrush」をONにして「Speed」を0.1、「Size」を2にしてストロークしてみたものだ。

fig02

続きはまた次回。

maxまとめページ



take_z_ultima at 11:30|この記事のURLComments(0)TrackBack(0)3ds Max | CG

2017年03月22日

modo10.2v1のスクリプトについて調べてみた その68

引き続き「TD SDK」について調べてみたい。

今回も「modo.util.makeQuickCommand()」ファンクション内の「CmdMyCustomCommand」クラスについて調べてみたい。

「cmd_Flags()」ファンクションはこのコマンドの動作をmodoに教えるためにフラグを組み合わせて返す。「cmd_」が付いてるファンクションは「lxifc.Command」インターフェースをオーバーロードするもので、modoから直接呼び出される。

 def cmd_Flags(self):
return lx.symbol.fCMD_MODEL | lx.symbol.fCMD_UNDO

「lx.symbol.fCMD_MODEL」は、このコマンドが、ジオメトリの変更、モーションの変更、または他のモデルレベルの変更の実行など、プログラムの内部状態に影響を与えるものだと言う事を示すフラグ。

「lx.symbol.fCMD_UNDO」は、このコマンドが動作を取り消せるMODELコマンドである事を示すフラグ。これが入っていない場合はUndoが出来ないコマンドになる。

「basic_Enable()」はコマンドが実行出来るかどうかの事前チェックに使われるファンクションで、「False」を返すとコマンドの実行が中断されて「無効なコマンド」のエラーになる。「basic_」が付くファンクションは「BasicCommand」クラスのファンクションで、他のファンクションから呼び出されて間接的に使われる。

 def basic_Enable(self, msg):
return True

modoから直接は「cmd_Enable()」が呼び出されて、modoから渡されたメッセージオブジェクトを「Message」クラスでラップしてハンドリングし、「basic_Enable()」がNoneを返したらメッセージオブジェクトに「CMD_NOT_AVAILABLE」を、Falseを返したら「CMD_DISABLED」をセットして「CMD_NOT_AVAILABLE」「CMD_DISABLED」例外を発生する。それ以外の時はメッセージオブジェクトに「OK」をセットしてmodoに戻る。

 def cmd_Enable(self, msg):
     msg = lx.object.Message(msg)
     res = self.basic_Enable(msg)
     if res == None:
         msg.SetCode(lx.result.CMD_NOT_AVAILABLE)
         lx.throw(lx.result.CMD_NOT_AVAILABLE)
     elif not res:
         msg.SetCode(lx.result.CMD_DISABLED)
         lx.throw(lx.result.CMD_DISABLED)
     else:
         msg.SetCode(lx.result.OK)

ちなみに前回出て来た「basic_Execute()」も実際にはmodoは「cmd_Execute()」を呼び出して、その中から呼び出される。これを観ると例外が発生した時にトラップするようになっている。

 def cmd_Execute(self, flags):
     try:
         self.basic_Execute(self._msg, flags)
     except:
         lx.outEx("basic_Execute failed")
         self._msg.SetCode(lx.result.FAILED)
         raise   # outEx doesn't work -- only way to see the error is to raise it again

続きはまた次回。

modo10ブログ目次



take_z_ultima at 11:30|この記事のURLComments(0)TrackBack(0)modo | CG

2017年03月21日

ビットマップペイントツールのチュートリアルをやってみた その34 3dsmax 2017

引き続きMAXScriptマニュアルに載っている「チュートリアル-ビットマップペイントツールを9つの簡単なステップで作成する」 の続きを考えて見たい。

正方形までは描けるようになったので、今度は円形に塗りつぶしたい。オリジナルではピクセルを塗る時にそれがヒットポイントを中心とした円内なのかどうかを判定して円内ならピクセルを塗るような処理をする必要がある。チュートリアルのプログラムでは「paintBrush()」ファンクションの下の部分がそれだ。

 case BrushShape.selection of
 (
    1: (
      if distance pos currentPos <= BrushSize.value/2 do
        setPixels theCanvasBitmap pos #(thePaintColor)
    )
    2: setPixels theCanvasBitmap pos #(thePaintColor)
    3: (
      theFactor = (distance pos currentPos) / (BrushSize.value/2.0)
      if theFactor <= 1.0 do
      (
        theFactor = sin ( 90.0 * theFactor)
        thePixels = getPixels theCanvasBitmap pos 1
        if thePixels[1] != undefined do
        (
          thePixels[1] = (thePixels[1] * theFactor) + (thePaintColor * (1.0 - theFactor))
          setPixels theCanvasBitmap pos thePixels
        )
      )
    )--end case 3
 )--end case

「currentPos」がペイントブラシの中心位置で、「pos」が塗るかどうかの判定位置だ。どちらもキャンバス座標系の座標値だ。

今回はメッシュサーフェス上で正方形になるようにしたので、円もメッシュサーフェス上で考えなきゃならない。だから「pos」が示す座標に対応したメッシュサーフェス上の座標を求めて、2点間の距離を求めてそれがブラシサイズより小さいかどうかを調べる事になる。

それなら最初からメッシュサーフェス上で処理をして、最後にテクスチャ座標に変換すれば良さそうだけど、それだとピクセル単位で処理出来ないから塗りつぶした時に隙間が出来たり重複して塗ったりって事になりかねない。

そこで今度はキャンバス座標系からワールド座標系へ変換する変換が必要になる。

また、オリジナルのプログラムでは正方形の中を塗っていたけどそれもキャンバス上では歪んだものになるので塗りつぶしのためのピクセル位置の生成方法も変更する必要がある。オリジナルのプログラムでは「drawStroke()」の下の太字の部分がその部分だ。

  fn drawStroke lastPos pos drawIt: =
  (
    currentPos = lastPos
    deltaX = pos.x - lastPos.x
    deltaY = pos.y - lastPos.y
    maxSteps = amax #(abs(deltaX),abs(deltaY))
    deltaStepX = deltaX / maxSteps
    deltaStepY = deltaY / maxSteps
    for i = 0 to maxSteps do
    (
      if airBrush.checked then
        for b = 1 to (BrushSize.value / AirBrushSpeed.value) do
          paintBrush (currentPos + (random [-BrushSize.value/2,-BrushSize.value/2] [BrushSize.value/2,BrushSize.value/2] ))
      else
        for b = -BrushSize.value/2 to BrushSize.value/2 do
          for c = -BrushSize.value/2 to BrushSize.value/2 do
            paintBrush (currentPos + [c,b])
      currentPos += [deltaStepX, deltaStepY]
    )
    if drawIt== true or drawIt == unsupplied do theCanvas.bitmap = theCanvasBitmap
  )

エアブラシにした場合は単純にメッシュサーフェス上にランダムな位置を生成して、それをキャンバス座標に変換して点を打てば良さそうだ。それ以外の時はキャンバス座標上に生成した塗りつぶしたい四角形をY軸方向に1ピクセルごとスライスしてX軸に平行な線分に分解して、その線分を1ピクセルごとに分解して1つの描画点にする必要がある。

fig01

次回はこれらを実現するためのプログラムを作ってみたい。

それではまた次回。

maxまとめページ



take_z_ultima at 11:30|この記事のURLComments(0)TrackBack(0)3ds Max | CG

2017年03月17日

modo10.2v1のスクリプトについて調べてみた その67

引き続き「TD SDK」について調べてみたい。

今回も「modo.util.makeQuickCommand()」ファンクション内の「CmdMyCustomCommand」クラスの「basic_Execute()」メソッドの続きだ。

 def basic_Execute(self, msg, flags):
     kwargs = {}

     if self._argumentList is not None:

         if not all([self.dyna_IsSet(i) for i in range(self._numArgs)]):
             raise ValueError('Missing argument')

         for i, pair in enumerate(self._argumentList.iteritems()):
             name, defaultValue = pair
             if isinstance(defaultValue, basestring):
                 value = self.dyna_String(i, defaultValue)
                 kwargs[name] = value
             elif isinstance(defaultValue, (int, long)):
                 value = self.dyna_Int(i, defaultValue)
                 kwargs[name] = value

     func(**kwargs)

前回までのところでデフォルト値を使ってパラメータの種類を文字列か整数か識別してmodoから渡されたValue値から値を取り出して、パラメータ名をキーに「kwargs」の辞書に値を辞書登録した。

そして最後にその辞書オブジェクトに「**」を付けてユーザー定義のファンクションに渡して実行させる。

 func(**kwargs)

これがPythonのオモシロ機能のようで、このように辞書オブジェクトに「**」を付けてファンクションに渡すと、キーを引数名として値を渡すことが出来る。

例えばこんなプログラムを作って実行してみると、

def foo(a,b):
	print 'a=',a,' b=',b

foo(10,20)

dic={'a':200,'b':500}
foo(**dic)

ちゃんとパラメータが渡されている事が確認できる。

# Result: 
a= 10  b= 20
a= 200  b= 500

さらにファンクションの定義の時に引数名の前に「*」と「**」を付けると、可変引数のファンクションが簡単に作れる。「*」を付けたものは引数名を指定しないで値を渡す場合に使えて、渡した値は順にタプルに入る。「**」を付けたものは引数名を指定して値を渡す時に使えて、引数名をキーとした辞書になって渡される。

def foo(*vals,**vars):
	print vals
	print vars

foo(0,100,50,a=10,b=20)

これが実行結果。

# Result: 
(0, 100, 50)
{'a': 10, 'b': 20}

それではまた次回。

modo10ブログ目次



take_z_ultima at 11:30|この記事のURLComments(0)TrackBack(0)modo | CG
Archives