2016年12月

2016年12月22日

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

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

前回は「ステップ8」のピックボタンのハンドラについて調べた。今回は「Get UV Coordinates」メニューのハンドラについて調べてみたい。

「Get UV Coordinates」メニューは「Edit」メニューに追加されている。

  subMenu "Edit"
  (
    menuItem commit_menu "Commit Changes"
    separator edit_menu_1
    menuItem uv_menu "Get UV Coordinates..."
  )

「Edit」メニューを選ぶと下のように出てくる。

fig04

そしてこのメニューを選んだ時に起動されるハンドラが以下のコードだ。

  fn unwrapTexture =
  (
    if theObj != undefined then
    (
      theMesh = snapshotAsMesh theObj
      if meshop.getMapSupport theMesh theChannel do
      (
        faceCount = meshop.getNumMapFaces theMesh theChannel
        for f = 1 to faceCount do
        (
          theFace = meshop.getMapFace theMesh theChannel f
          vert1= meshop.getMapVert theMesh theChannel theFace.x
          vert2= meshop.getMapVert theMesh theChannel theFace.y
          vert3= meshop.getMapVert theMesh theChannel theFace.z
          drawStroke [vert1.x * bitmapx_1, bitmapy_1 - vert1.y * bitmapy_1] [vert2.x * bitmapx_1, bitmapy_1 - vert2.y * bitmapy_1] drawIt:false
          drawStroke [vert1.x * bitmapx_1, bitmapy_1 - vert1.y * bitmapy_1] [vert3.x * bitmapx_1, bitmapy_1 - vert3.y * bitmapy_1] drawIt:false
          drawStroke [vert3.x * bitmapx_1, bitmapy_1 - vert3.y * bitmapy_1] [vert2.x * bitmapx, bitmapy_1 - vert2.y * bitmapy_1] drawIt:false
        )
      ) 
      theCanvas.bitmap = theCanvasBitmap
      save theCanvasBitmap
      if theObj.material == undefined do theObj.material = Standard()
      if theObj.material.diffusemap == undefined do
        theObj.material.diffusemap = bitmapTexture filename:temp_bitmap_filename
      showTextureMap theObj.material true
      autoSave.checked = true
    )
  )

まず前回出てきたピックボタンでペイント対象のオブジェクトを選択していれば、「theObj」にそのオブジェクトがセットされているはずなので、それがない「undefined」だったら実行をスキップする。

次に「snapshotAsMesh」メソッドを使ってノードの現在の形状のスナップショットをとって<mesh>値を得る。これでメッシュデータのスナップショットにアクセス出来るようになる。スナップショットはオブジェクトの移動や変形も適用された状態の形状が<mesh>値としてコピーされる。今回利用するのはUVマップなのでジオメトリが変形していても特に影響は無い。

theMesh = snapshotAsMesh theObj

次にこのメッシュの「theChannel」が示すマップチャンネルがサポートされているかをチェック。「theChannel」の値は1に設定されている。だからこのプログラムではマップチャンネル1にUVマップが無いオブジェクトからはUVマップは取得できない。

if meshop.getMapSupport theMesh theChannel do

maxではメッシュは多数の頂点に三角形の面を貼ったものとしてデータ化されている。エッジが重なる三角形はエッジ両端の頂点も重なる。そこで各三角形は三角形ごとに個別に頂点のデータを持たずに頂点のデータは三角形のデータと分離し、その頂点の番号だけを持つことで、同じ位置の頂点を共有する仕組みをとっている。だから三角形の各頂点の座標値やマップ値が欲しい時はその三角形を構成する頂点の番号のリストを取得して、頂点データのリストからその番号の頂点を見つけ出してアクセスする事になる。

そこでまず、「meshop.getNumMapFaces」でこのメッシュのマップチャンネル1にセットされているマップの三角ポリゴンの数を取得して、「for」文で変数「f」に処理するマップポリゴン番号を1から順に入れて全てのマップポリゴンに対して繰り返し処理するようにする。

faceCount = meshop.getNumMapFaces theMesh theChannel
for f = 1 to faceCount do

変数「f」に入っている番号のマップポリゴンを構成する3つの頂点の頂点番号を「meshop.getMapFace」で取得する。この時得られるのは<point3>値になるので、3つの頂点の番号は「x」「y」「z」としてアクセス出来る。

theFace = meshop.getMapFace theMesh theChannel f

得られた頂点番号から「f」番目の三角ポリゴンの3つの頂点のUV値を取得する。

vert1= meshop.getMapVert theMesh theChannel theFace.x
vert2= meshop.getMapVert theMesh theChannel theFace.y
vert3= meshop.getMapVert theMesh theChannel theFace.z

得られるUV値はU値もV値も0〜1の範囲にあるので、ビットマップのサイズに広げるにはそのビットマップの縦横の長さをそれぞれかけてやればいい。さらにV軸方向はビットマップのY軸と方向が逆なので、

(1 - vert1.y) * bitmapy_1

として反転してやればいい。上の式で「vert1.y」が1なら0に、0ならbitmapy_1になるのがわかる。こうして得られた3つの座標値を「drawStroke」で直線で結んでビットマップに描けばUVマップがビットマップいっぱいに描かれる。「drawStroke」はマウスドラッグでペイントする時に使ってるものが流用されてるからブラシサイズとかの影響もそのまま受けるよ。

drawStroke [vert1.x * bitmapx_1, bitmapy_1 - vert1.y * bitmapy_1] [vert2.x * bitmapx_1, bitmapy_1 - vert2.y * bitmapy_1] drawIt:false
drawStroke [vert1.x * bitmapx_1, bitmapy_1 - vert1.y * bitmapy_1] [vert3.x * bitmapx_1, bitmapy_1 - vert3.y * bitmapy_1] drawIt:false
drawStroke [vert3.x * bitmapx_1, bitmapy_1 - vert3.y * bitmapy_1] [vert2.x * bitmapx, bitmapy_1 - vert2.y * bitmapy_1] drawIt:false

ビットマップに描画が終わったらダイアログのビットマップコントロールの表示を更新する。

theCanvas.bitmap = theCanvasBitmap

そしてビットマップをほぞんする。

save theCanvasBitmap

「theCanvasBitmap」ビットマップのファイルパスはプログラムの冒頭で設定されている。

local temp_bitmap_filename = (getDir #preview +"/microPaint_temp.bmp")
local theCanvasBitmap = bitmap bitmapX bitmapY color:white filename:temp_bitmap_filename

オブジェクトのマテリアルが未設定なら「Standard」マテリアルを作成して割り当てて、マテリアルのディフューズマップが未設定なら上記のファイルパスのビットマップを設定する。そしてこのマテリアルをビューポートで表示をONにする。これでマップがオブジェクト表面に表示される。前にも書いたけどこういう仕組みだからもともとディフューズマップがあったらペイントできない。

if theObj.material == undefined do theObj.material = Standard()
if theObj.material.diffusemap == undefined do
  theObj.material.diffusemap = bitmapTexture filename:temp_bitmap_filename
showTextureMap theObj.material true
autoSave.checked = true

だから強制的に割り当てを変更しちゃうのも手かもね。オブジェクトをピックした時に割り当てられている画像はビットマップにスケーリングされてコピーされてるはずだからオリジナルの画像ファイルに影響を与えずにマッピングされていた画像のコピーを貼り付けてペイントできるようになる。

if theObj.material == undefined do theObj.material = Standard()
theObj.material.diffusemap = bitmapTexture filename:temp_bitmap_filename
showTextureMap theObj.material true
autoSave.checked = true

続きはまた来年。

定期更新は新年1月10日から再開します。

それではみなさん良いお年を。

maxまとめページ



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

2016年12月21日

modo10のスクリプトについて調べてみた その44

前回に引き続き「TD SDK」の「Channel」クラス関連について調べてみたい。

前回書いた通り「Keyframes」クラスには「__getattr__()」メソッドが定義されていて、それは「_keyframe」オブジェクトのアトリビュートにアクセスする仕組みになっている。よって「Keyframes」クラスには「_keyframe」オブジェクトのクラスのアトリビュートもまんま使えるって事だ。そこで「_keyframe」オブジェクトのクラスを調べてみたら下のように出てきた。

class Keyframe(lx.object.Keyframe)
 |  Method resolution order:
 |      Keyframe
 |      lx.object.Keyframe
 |      __builtin__.object
 |  
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)
 |  
 |  ----------------------------------------------------------------------
 |  Methods inherited from lx.object.Keyframe:
 |  
 |  AddF(...)
 |      AddF(float time,float value)
 |  
 |  AddI(...)
 |      AddI(float time,integer value)
 |  
 |  Delete(...)
 |      Delete()
 |  
 |  Find(...)
 |      Find(float time,integer side)
 |  
 |  First(...)
 |      First()
 |  
 |  GetBroken(...)
 |      (integer breaks,integer side) = GetBroken()
 |  
 |  GetSlope(...)
 |      float slope = GetSlope(integer side)
 |  
 |  GetSlopeType(...)
 |      (integer type,integer weighted) = GetSlopeType(integer side)
 |  
 |  GetTime(...)
 |      float time = GetTime()
 |  
 |  GetValueF(...)
 |      float value = GetValueF(integer side)
 |  
 |  GetValueI(...)
 |      integer value = GetValueI(integer side)
 |  
 |  GetWeight(...)
 |      float weight = GetWeight(integer side)
 |  
 |  Last(...)
 |      Last()
 |  
 |  Next(...)
 |      Next()
 |  
 |  Previous(...)
 |      Previous()
 |  
 |  SetSlope(...)
 |      SetSlope(float slope,integer side)
 |  
 |  SetSlopeType(...)
 |      SetSlopeType(integer type,integer side)
 |  
 |  SetTime(...)
 |      SetTime(float time)
 |  
 |  SetValueF(...)
 |      SetValueF(float value,integer side)
 |  
 |  SetValueI(...)
 |      SetValueI(integer value,integer side)
 |  
 |  SetWeight(...)
 |      SetWeight(float weight,integer reset,integer side)
 |  
 |  __eq__(...)
 |  
 |  __peekobj__(...)
 |      obj id = __peekobj__()
 |  
 |  set(...)
 |      bool = set(object source)
 |  
 |  test(...)
 |      bool = test()
 |  
 |  ----------------------------------------------------------------------
 |  Data and other attributes inherited from lx.object.Keyframe:
 |  
 |  __new__ = 
 |      T.__new__(S, ...) -> a new object with type S, a subtype of T

これを使えばキーのブレーク状態やらスロープの値やウェイト値なんかを取得したり設定したりが出来る。例えば下の画像は「Mesh」オブジェクトの「位置」の「X」チャンネルのグラフだ。

fig01

下のコードを実行すれば、

import modo
mesh = modo.Mesh('Mesh')
chx = mesh.position.x
evx=chx.envelope
keys=evx.keyframes
for i in range(keys.numKeys):
   keys.setIndex(i)
   print keys.GetBroken()
   print keys.GetSlopeType(0)
   print keys.GetSlopeType(1)
   print keys.GetSlope(0)
   print keys.GetSlope(1)
   print keys.GetWeight(0)
   print keys.GetWeight(1)
   print

このように各キーのスロープやウェイトの値、ブレークの状態などが取得出来る。

# Result: 
(0L, 1L)
(5L, 0L)
(5L, 0L)
0.0
0.0
0.138888888889
0.138888888889

(2L, 1L)
(0L, 0L)
(0L, 0L)
3.20181935612
-4.89922514579
0.652234237225
0.6944778746

(6L, 1L)
(0L, 1L)
(0L, 1L)
-4.87300285723
3.07556833239
1.51245998809
0.575136326439

(0L, 1L)
(5L, 0L)
(5L, 0L)
0.0
0.0
0.25
0.25

このようにせっかくいろんな機能があるのに、残念なことにスクリプトエディタのヒントにはこれらのメソッドが表示されないんだよねぇ。その解消のために作ったはずの「getSlopeType()」はあんな事になっちゃってるけど、「Keyframes」クラスでちゃんとラッパーメソッドを実装すればここにも出てくるようになるから今後に期待だね。

fig02

それではまた次回。

modo10ブログ目次



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

2016年12月20日

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

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

「ステップ8」のテーマはUVマップの取得とビットマップのオブジェクトへの割り当てだ。

fig01

これを実現するためにパネルには新たに2つのコントロールと

fig02fig03

1つのメニューアイテムが追加される。

fig04

「Pick Mesh」ボタンはビットマップを割り当てるメッシュオブジェクトを選択するためのピックボタン。「AutoSave」はペイント後にビットマップを自動的に保存してメッシュに割り当てられているビットマップを更新するかどうかを設定するチェックボタン。「Get UV Coordinates」はUVマップを取り込みビットマップに描画する機能を呼び出すメニューだ。

「Pick Mesh」ボタンのコントロールの定義とそのハンドラは以下のようになっている。

  --NEW PICK BUTTON AND HANDLER:
  pickbutton pickMesh "Pick Mesh" width:90 height:30 pos:[bitmapX+5,140] filter:mesh_filter autodisplay:true
 
  on pickMesh picked obj do
  (
    if obj != undefined do
    (
      theObj = Obj
      try
      (
        copy theObj.material.diffusemap.bitmap theCanvasBitmap
        copy theObj.material.diffusemap.bitmap theBackgroundBitmap
        theCanvas.bitmap = theCanvasBitmap
      )catch() 
    )
  )
  --END NEW PICK BUTTON AND HANDLER

ボタンを押してターゲットになるメッシュをクリックすると、ハンドラの「obj」引数にクリックしたオブジェクトが代入されてハンドラが呼び出される。ハンドラはピックされたオブジェクトを「theObj」にセットしてハンドラが終了してもオブジェクトを保持できるようにする。「theObj」はマクロスクリプトのローカル変数としてこのハンドラの外で宣言されている。

local theObj = undefined

そしてそのオブジェクトに割り当てられているマテリアルのディフューズマップに割り当てられているビットマップを「theCanvasBitmap」と「theBackgroundBitmap」にコピーする。これらはペイント対象のビットマップと取り消し用の背景ビットマップだったね。

copy theObj.material.diffusemap.bitmap theCanvasBitmap
copy theObj.material.diffusemap.bitmap theBackgroundBitmap

コピーが終わったらパネルにビットマップを表示するためのビットマップコントロールの「bitmap」プロパティにそのビットマップを代入することでビットマップコントロールの表示を更新する。

theCanvas.bitmap = theCanvasBitmap

このビットマップのコピーから更新までの処理はピックしたオブジェクトのディフューズマップにビットマップが割り当てられていないとエラーになるので「try 〜 catch」でトラップしてエラーが起きたら実行をスキップするだけでプログラムが停止しないようにしてある。

ビットマップ値の「copy」メソッドでは画像はコピーされるけど、「filename」プロパティの値はコピーされない。このプログラムではペイントしたあとでビットマップを保存することでマッピング画像を更新するようになっているので、マッピングされている画像を取り込んでペイントしても、自動更新は出来ない。ただこれを簡単に出来るようにしちゃうと、オブジェクトに貼ってある画像が簡単に書き換わっちゃって元に戻せなくなるからそのままの方が安心ではあるね。

解決策としては「theCanvasBitmap」に最初からセットされているファイルでマテリアルに割り当てられているビットマップファイルを置き換えちゃう事だ。これならオリジナルを書き換えちゃう心配は無い。ただ、ファイル名が固定されているから、何度もこのプログラムを使ってペイントして行くと、直前のペイントの結果が次々に書き換えられてしまって画像ファイルは最後に描いたものしか残らないけどね。

on pickMesh picked obj do
  (
    if obj != undefined do
    (
      theObj = Obj
      try
      (
        copy theObj.material.diffusemap.bitmap theCanvasBitmap
        copy theObj.material.diffusemap.bitmap theBackgroundBitmap
        theCanvas.bitmap = theCanvasBitmap
    save theCanvasBitmap
        theObj.material.diffusemap = bitmapTexture filename:temp_bitmap_filename
      )catch() 
    )
  )

続きはまた次回。

maxまとめページ



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

2016年12月19日

modo10のスクリプトについて調べてみた その43

前回に引き続き「TD SDK」の「Channel」クラス関連について調べてみたい。

「Keyframes」クラスについては以前に「getSlopeType()」のところでひっかかったけど、あれからよくコードを見てみたら以下の記述を見つけた。これはアトリビュートにアクセスする仕組みを提供するPythonの特殊メソッドで、「Keyframes」のインスタンスに続けて「.名前」をつけた時にそれが未定義ならこのファンクションが呼び出されて「名前」の部分が下のコードの「attr」に渡される仕組みだ。そしてこのファンクションでは「getattr()」を呼び出して「_keyframe」オブジェクトの「attr」にアクセスするようになっている。

def __getattr__(self, attr):
    # pass any unknown attribute access on to the wrapped lxu.object.Keyframe class to see if
    # it's a recognised call there.
    return getattr(self._keyframe, attr)

だから以前の記事ではわざわざ「_keyframe」を書いて下のようにして「GetSlopeType()」にアクセスしたけど、

slopeTypeIn = keys._keyframe.GetSlopeType(0)

「_keyframe」を省略してこう書けばいいわけだ。

slopeTypeIn = keys.GetSlopeType(0)

気をつけなきゃならないのは「GetSlopeType()」の頭文字が大文字ってところ。スクリプトエディタでピリオドを打ってTABキーを押すと出るポップアップメニューにある「getSlopeType()」は以前の記事に書いたようにバグがあるからね。

それではまた次回。

modo10ブログ目次



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

2016年12月13日

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

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

「ステップ7」のテーマは「変更の消去」だ。今までは左ボタンドラッグでペイントするだけだったけど、これに追加して右ボタンドラッグによって左ボタンドラッグで塗ったペイントを背景画像で塗ることが出来るようになって、左ペイントを右ペイントで打ち消せるようになる。

fig01

右ドラッグでペイントする背景画像は「Edit」メニューの「Commit Changes」を選んだ時に現在の画像で更新できる。初期状態では真っ白で、ファイルを読み込んだ場合は読み込んだ画像が初期状態の背景画像になる。

fig02

この機能を実現するのは簡単だ。ペイントするビットマップの他に背景画像を記憶するもう一枚のビットマップを用意して、左ドラッグでペイントする時はペイント色で塗って、右ドラッグでペイントする時は同じ位置の背景色を読み込んでその色でペイントすればいい。

そこで、マウスの移動がペイントするためのものかどうかを示すために作られた「isDrawing」に加えて、そのペイントが消去のためなのかどうかを表す「isErasing」を作成し、背景画像を記録するためのビットマップ「theBackgroundBitmap」を追加してある。

local isErasing =isDrawing = false
local bitmapX = bitmapY = 512
local theCanvasBitmap = bitmap bitmapX bitmapY color:white
local theBackgroundBitmap = bitmap bitmapX bitmapY color:white

そして、左ボタンが押された時は通常のペイントだから「isDrawing」はtrueで、「isErasing」はfalseに、右ボタンが押された時は「isDrawing」も「isErasing」もはtrueに、ボタンを離した時はどちらのボタンでも両方の変数をfalseにするようにして、どちらのボタンでドラッグしてペイントしているのかわかるようにした。

  on MicroPaint_CanvasRollout lbuttondown pos do
  (
    lastPos = pos
    isDrawing = true
    isErasing = false
    drawStroke lastPos pos
  )
  on MicroPaint_CanvasRollout rbuttondown pos do
  (
    lastPos = pos
    isErasing = isDrawing = true
    drawStroke lastPos pos
  )
  on MicroPaint_CanvasRollout lbuttonup pos do isErasing =isDrawing = false
  on MicroPaint_CanvasRollout rbuttonup pos do isErasing =isDrawing = false

ビットマップに直接ペイントするのは「paintBrush」ファンクションだ。

前のプログラムではペイントする色が「inkColor」コントロールから直接読み込まれて使われていた。

setPixels theCanvasBitmap pos #(inkColor.color)

今回からは変数「thePaintColor」に格納されている色がペイントされるように変更された。

setPixels theCanvasBitmap pos #(thePaintColor)

この変数「thePaintColor」の値が「inkColor.color」なら元のプログラム同様にコントロールに設定されている色でビットマップがペイントされる事になる。そしてこの変数「thePaintColor」の値が「paintBrush」ファンクションの引数「pos」で与えられるこれからペイントする位置にある背景画像のピクセルの色の値であれば、ペイントは背景画像をコピーすることになり、ペイントがキャンセルされる事になるわけだ。

そこで「paintBrush」ファンクションの冒頭で、以下のコードでこれから塗る色の値を決めている。

fn paintBrush pos =
  (
    if isErasing then
      thePaintColor = (getPixels theBackgroundBitmap pos 1)[1]
    else
      thePaintColor = inkColor.color
    if thePaintColor == undefined do thePaintColor = white

「isErasing」がtrueなら、ペイントするビットマップと同じ位置の背景画像「theBackgroundBitmap」のピクセルの色を読み込んで変数「thePaintColor」にセットし、falseならパネルに配置された「inkColor」コントロールの色を変数「thePaintColor」にセットしている。また、色が取得出来なければ白にセットしている。

背景画像の更新は下のように「Commit Changes」のメニューが選択された時のハンドラで、「copy」でペイントしているビットマップ(theCanvasBitmap)を背景画像のビットマップ(theBackgroundBitmap)にコピーしている。

  subMenu "Edit"
  (
     menuItem commit_menu "Commit Changes"
  )
  on commit_menu picked do copy theCanvasBitmap theBackgroundBitmap

また、画像ファイルを読み込んだ時に、その画像が背景画像にもなるように、読み込んだビットマップを「theBackgroundBitmap」にもコピーしている。

  on open_menu picked do
  (
    theOpenBitmap= selectBitmap()
    if theOpenBitmap != undefined do
    (
      copy theOpenBitmap theCanvasBitmap
      copy theOpenBitmap theBackgroundBitmap
      close theOpenBitmap
      MicroPaint_CanvasRollout.theCanvas.bitmap = theCanvasBitmap
    )
  )

これで右ドラッグでペイントの打ち消しが実現できた。

今週はちょっと忙しいので定期更新はここまで。続きはまた来週。

maxまとめページ



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