2017年02月21日

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

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

今回は「PainterInterface」の「getRandomHitOnPoint()」「getRandomHitAlongStroke()」について調べてみたい。

<boolean>thePainterInterface.getRandomHitOnPoint <integer>index

<boolean>thePainterInterface.getRandomHitAlongStroke <integer>index  

この2つのメソッドはマニュアルには以下のように書いてある。

指定したポイント上のランダム ヒットを返します。

指定したストロークに沿ったランダム ヒットを返します。

マニュアルにはこれしか書いてないので機能がよくわからない。近い形のものに「getIsHit()」メソッドがあるけど、こっちは引数が「tabIndex」になってる。マニュアルを読む限り「tabIndex」はストローク内のヒットポイントの配列のインデックスで、「index」はメッシュポイントのインデックスをあらわしているように思える。

<boolean>thePainterInterface.getIsHit <integer>tabIndex

そこで以下のプログラムを作って試してみた。

(
  local theObj = undefined
  
  rollout TestPinterIterfaceRollout "MicroPaint"
  (
    pickbutton pickMesh "Pick Mesh" autodisplay:true
    checkbutton paint3D "3D PAINT"
    radiobuttons random_type labels:#("random on point", "random along stroke")
    checkbutton pgather "PointGather"
    button btnOptions "Option"
    
    fn startStroke = (
      thePainterInterface.undoStart()
    )    
    fn paintStroke = (
      local rp = #{}
      local ra = #{}
      for v = 1 to theObj.GetNumVertices() do (
        if (thePainterInterface.getRandomHitAlongStroke v) then
          append ra v
        if (thePainterInterface.getRandomHitOnPoint v) then
          append rp v
      )
      if random_type.state == 1 then
        theObj.SetSelection #Vertex rp
      else
        theObj.SetSelection #Vertex ra
    )  
    fn endStroke = (
      print "--- endStroke ---"
      thePainterInterface.clearStroke() 
      thePainterInterface.undoAccept()
      thePainterInterface.EndPaintSession()
      paint3D.checked = false
    )
    fn cancelStroke = thePainterInterface.undoCancel() 
    fn systemEnd = print "Handle system end here"

    on TestPinterIterfaceRollout open do (
      thePainterInterface.drawRing =  true
      thePainterInterface.drawTrace = true
      thePainterInterface.maxSize = 20
      if thePainterInterface.InPaintMode() then
      (
        thePainterInterface.EndPaintSession()
      )
    )
  
    on TestPinterIterfaceRollout close do (
      if thePainterInterface.InPaintMode() then
      (
        thePainterInterface.EndPaintSession()
      )
    )
  
    on pickMesh picked obj do
    (
      if obj != undefined do
      (
        theObj = Obj
      ) 
    ) 
  
    on paint3D changed state do 
    (
      if thePainterInterface.InPaintMode() or theObj == undefined then
      (
        thePainterInterface.EndPaintSession()
        paint3D.checked = false
      )
      else
      (
        paint3D.checked = true
        thePainterInterface.initializeNodes 0 #(theObj)
        thePainterInterface.ScriptFunctions startStroke paintStroke endStroke cancelStroke SystemEndPaintSession
        thePainterInterface.startPaintSession()
      )
    )
    
    on pgather changed state do 
    (
      if thePainterInterface.pointGatherEnable == true then
      (
        thePainterInterface.pointGatherEnable = false
        pgather.checked = false
      )
      else
      (
        thePainterInterface.pointGatherEnable = true
        pgather.checked = true
      )
    )
    on btnOptions pressed do (
      thePainterInterface.paintOptions() 
    )
  
  )
  createDialog TestPinterIterfaceRollout 
)

このプログラムを実行すると下のパネルが出る。

fig01

これで2つのメソッドの動作を確認できる。ついでに影響があるのかなと思って「PointGather」のオプションのON/OFFも付けてみた。

平面を30X30の編集可能ポリゴンにして頂点サブオブジェクトモードでペイントすると、メソッドがtrueを返した番号に対応するポイントのみ選択状態になる。

fig02

結論から言うと「index」はストローク内のヒットポイントのインデックス番号のようだ。

上の画像でストローク開始点はメッシュの上で、そこからメッシュの外にはみ出てストロークを続けると、頂点番号1から順に頂点が数個選択されたあとはずっと非選択状態が続く。ストロークのポイント数を超えた頂点はブラシがメッシュにかかる割合によってランダムに選択される。

fig03

2つのメソッドで差が出たのはブラシを平面の上でストロークさせた時で、「random along stroke」の場合はブラシを平面上のどこに置いても全ポイントが選択状態になったのに対して、

fig04

「random on point」ではブラシにポイントが含まれる割合によって選択・非選択の割合が変化した。ブラシサイズを大きくしていくとほぼ全てが選択状態になったが、ブラシを小さくしてポイントを含みにくくなると選択されるポイントが減った。

fig05

つまり「random along stroke」ではポリゴン上にヒットポイントを設定するのに対して「random on point」はメッシュ頂点上でヒットをチェックしてる感じなのかな。

以上、なんとなく違いや現象は観察できたけど、結局なんなんだかよくわからない。

続きはまた次回。

maxまとめページ



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

2017年02月20日

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

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

前回に続いて「MeshEdge」クラスを調べていたらおかしなところを見つけたので書いておくね。それは「vertex_indices」プロパティだ。これがおかしいので内部でこれを使っている「vertices」プロパティもエラーになる。

下が「vertex_indices」にアクセスした時に出るエラー。「vertex_indices」なんて無いよとか言われちゃってる。これを見るとどのファイルに「vertex_indices」があるのかわかる。

# Result: 
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "C:\Program Files\Luxology\modo\10.2v1j\extra\Python\modules\modo\meshgeometry.py", line 1305, in __getattr__
return getattr(self._accessor, attr)
AttributeError: 'Edge' object has no attribute 'vertex_indices'

で、下が「vertex_indices」の定義部分だ。

@property
def vertex_indices(self):
    """:getter: Returns the connected vertex indices as tuple
        
    Note that indices do not exist immediately after adding a vertex, the edits need to be applied with setMeshEdits to produce indices.        
    """
    pac = self.provider.point

    ids = self._accessor.Endpoints()
    if self.provider.layerEdit is not None:
        return ids

    vertexIndices = []
    for ID in ids:
        pac.Select(ID)
        vertexIndices.append (int (pac.Index()))
            
    return tuple(vertexIndices)

このコードの中で、「self.provider」を参照している箇所が2箇所あるけど、エラーの原因はこれだ。「self」である「MeshEdge」暮らすには「provider」というプロパティは無いので「__getattr__()」を使って「_accessor」にそのプロパティを投げた結果、そこにもそんなプロパティが無いので「__getattr__()」のメソッド内でエラーが起きてるってわけだ。

じゃあ「provider」はどこにあるのかと言うと、「MeshGeometry」だ。「MeshEdge」クラスからは「_geometry」からアクセス出来る。だから以下のようにプログラムを修正するとエラーは出なくなるし、ちゃんと動くようになるようだ。

@property
def vertex_indices(self):
    """:getter: Returns the connected vertex indices as tuple
        
    Note that indices do not exist immediately after adding a vertex, the edits need to be applied with setMeshEdits to produce indices.        
    """
    pac = self._geometry.provider.point

    ids = self._accessor.Endpoints()
    if self._geometry.provider.layerEdit is not None:
        return ids

    vertexIndices = []
    for ID in ids:
        pac.Select(ID)
        vertexIndices.append (int (pac.Index()))
            
    return tuple(vertexIndices)

書き換えるのが抵抗あるならPythonの仕組みを使ってクラスのプロパティを上書きする手もある。下のプログラムはクラスのプロパティを書き換えて頂点(0ー1)間のエッジオブジェクトの「vertex_indices」と「vertices」を取得してみたものだ。

import modo

mesh = modo.Mesh('Mesh')
geo = mesh.geometry
e1 = geo.edges[(0,1)]

# 書き換え用のメソッド
def vertex_indices(self):
	pac = self._geometry.provider.point
	ids = self._accessor.Endpoints()
	if self._geometry.provider.layerEdit is not None:
		return ids
	vertexIndices = []
	for ID in ids:
		pac.Select(ID)
		vertexIndices.append (int (pac.Index()))
	return tuple(vertexIndices)
# プロパティとして書き換える
modo.MeshEdge.vertex_indices = property(vertex_indices)

# テスト
print e1.vertex_indices
print e1.vertices

下記は「BOX」が入った「Mesh」アイテムに対して実行した結果。

# Result: 
(0, 1)
(modo.MeshVertex(0, 'mesh002'), modo.MeshVertex(1, 'mesh002'))

fig01

BOXは頂点0と1がエッジで繋がっているので、エッジオブジェクトが取得出来て、クラスレベルでプロパティを書き換えているので、そのオブジェクトのプロパティにアクセスすれば、2つのポイントのインデックス番号のタプルと2つのポイントのオブジェクトのタプルが取得出来る。

それではまた次回。

modo10ブログ目次



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

2017年02月17日

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

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

今回も「PainterInterface」について調べてみたい。前回「addToStroke()」でカーソル位置を指定していく事でストロークを作り出せる事がわかった。

そこで今回は「addToStroke()」の逆の働きをする「getHitMousePos()」を使ってストローク上のマウスの位置を取得して、ストロークの軌跡をコピーしてみた。

<point2>thePainterInterface.getHitMousePos <integer>tabIndex 

「getHitMousePos()」は「tabIndex」で指定したストロークのポイントに対応したマウス位置がスクリーン座標で得られる。ただし他のメソッドのように「tabIndex」を0にしても最後の点の位置は取得出来ない。最後の点が欲しければ、「thePainterInterface.getHitCount()」を使って

thePainterInterface.getHitMousePos(thePainterInterface.getHitCount())

とやればいい。

下がそのプログラムだ。

(
  local theObj = undefined
  local pghba
  local mma = #()
  local stroke=true
  
  rollout TestPinterIterfaceRollout "MicroPaint"
  (
    pickbutton pickMesh "Pick Mesh" autodisplay:true
    checkbutton paint3D "3D PAINT"
    button btnTrace "Trace Stroke"
    button btnOptions "Option"
    
    fn startStroke = (
      thePainterInterface.undoStart()
    )    
    fn paintStroke = (
      print "--- paintStroke ---"
    )  
    fn endStroke = (
      print "--- endStroke ---"
      ba = thePainterInterface.getPointGatherHits theObj
      for v in ba do (
        w=5
        n= thePainterInterface.getPointGatherNormal theObj v
        polyop.moveVert theObj #{v} (w*n)
      )
      if stroke then (
        for i=1 to  thePainterInterface.getHitCount() do (
          append mma (thePainterInterface.getHitMousePos i)
        )
      )
      thePainterInterface.clearStroke() 
      thePainterInterface.undoAccept()
      thePainterInterface.EndPaintSession()
      paint3D.checked = false
    )
    fn cancelStroke = thePainterInterface.undoCancel() 
    fn systemEnd = print "Handle system end here"

    on TestPinterIterfaceRollout open do (
      thePainterInterface.pointGatherEnable = true
      thePainterInterface.buildNormals = true
      if thePainterInterface.InPaintMode() then
      (
        thePainterInterface.EndPaintSession()
      )
    )
  
    on TestPinterIterfaceRollout close do (
      if thePainterInterface.InPaintMode() then
      (
        thePainterInterface.EndPaintSession()
      )
    )
  
    on pickMesh picked obj do
    (
      if obj != undefined do
      (
        theObj = Obj
      ) 
    ) 
  
    on paint3D changed state do 
    (
      if thePainterInterface.InPaintMode() or theObj == undefined then
      (
        thePainterInterface.EndPaintSession()
        paint3D.checked = false
      )
      else
      (
        paint3D.checked = true
        mma = #()
        stroke = true
        thePainterInterface.initializeNodes 0 #(theObj)
        thePainterInterface.ScriptFunctions startStroke paintStroke endStroke cancelStroke SystemEndPaintSession
        thePainterInterface.startPaintSession()
      )
    )
    on btnOptions pressed do (
      thePainterInterface.paintOptions() 
    )
  
    on btnTrace pressed do 
    (
      print "--- Trace Stroke ---"
      stroke = false
      thePainterInterface.initializeNodes 0 #(theObj)
      thePainterInterface.startPaintSession()
      startStroke()
      for i=1 to mma.count do (  
        thePainterInterface.addToStroke mma[i] true true
      )
      endStroke()
      thePainterInterface.EndPaintSession()
    )
  )
  createDialog TestPinterIterfaceRollout 
)

50X50のメッシュ平面の編集可能ポリゴンで試してみたのが下のGIFアニメだ。コピーされるのはあくまでマウスの位置だ。

fig01

続きはまた来週。

maxまとめページ



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

2017年02月16日

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

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

前回「MeshEdge」のコンストラクタの引数「vertices」にポイントのインデックス以外を入力してもおかしな事になる事を確認した。以下がそのプログラム。

import modo
import lx
lx.eval('select.type edge')
mesh=modo.Mesh('Mesh')
geo=mesh.geometry
v0=geo.vertices[0]
v1=geo.vertices[1]
e01=geo.edges[(v0,v1)]e01.select()

実行すると以下のエラーが出る。

# Result: 
Traceback (most recent call last):
File "<string>", line 8, in <module>
File "C:\Program Files\Luxology\modo\10.2v1j\extra\Python\modules\modo\meshgeometry.py", line 1383, in __getitem__
return MeshEdge(vertexIndices, self._geometry)
File "C:\Program Files\Luxology\modo\10.2v1j\extra\Python\modules\modo\meshgeometry.py", line 1189, in __init__
edge.SelectEndpoints(id1, id2)
LookupError: not found

これはインデックスの組み合わせが存在しないエッジだった場合のエラーと同じだ。

そこでプログラムを下のようにしてみたら、

import modo
import lx
lx.eval('select.type edge')
mesh=modo.Mesh('Mesh')
geo=mesh.geometry
v0=geo.vertices[0]
v1=geo.vertices[1]
print v0.id
print v1.id

結果がこのようになった。

# Result: 
238541136
238541136

なんと2つのポイントのIDが一緒になってる。そこでこのプロパティの定義を調べたら以下のようになっていた。なんといきなりポイントアクセッサからIDを取得してる。ポイントアクセッサは先に操作対象のエレメントを「SelectByIndex()」などで指定してから操作するのが基本だ。このメソッドではそれが抜けている。でもそもそも「MeshVertex」には「self._id」にID番号が記録されているからそれを使った方が早い。

@property
def id(self):
    """:getter: Returns the pointer ID of this vertex"""
    return self.provider.PointAccessor.ID()

だからこのメソッドは以下のように書き換えればちゃんと動く。

@property
def id(self):
    """:getter: Returns the pointer ID of this vertex"""
    return self._id

書き換えるのが嫌ならPythonには実行時にクラスのプロパティを置き換える方法が用意されている。下のプログラムを実行すると「Mesh」アイテム内の(0,1)のエッジを選択出来る。

import modo
import lx
lx.eval('select.type edge')
mesh=modo.Mesh('Mesh')
geo=mesh.geometry

def id(self):
	return self._id
modo.MeshVertex.id = property(id)

v0 = geo.vertices[0]
v1 = geo.vertices[1]
e01=geo.edges[(v0,v1)]
e01.select()

続きはまた次回。

modo10ブログ目次



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

2017年02月15日

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

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

今回も「PainterInterface」について調べてみたい。

今回は「addToStroke()」だ。

<void>thePainterInterface.addToStroke <point2>pos <boolean>updatePointGather <boolean>updateViewPort 

マニュアルには以下のように書かれている。

ストロークにポイントを追加します。 updatePointGather が true の場合、ポイントはリストに追加されます。 updateViewPort が true の場合、ポイントはビューポートに表示されます。

相変らず説明が少ないので試してみるしか無いw

「pos」が<point2>型なので位置の指定がビューポート上のスクリーン座標のようだと見当がつく。また、パラメータの「updatePointGather」から「Gather」に反応しそうな感じだ。そこで下のプログラムを書いてテストをしてみた。

(
  local theObj = undefined
  local pghba
  thePainterInterface.pointGatherEnable = true

  rollout TestPinterIterfaceRollout "MicroPaint"
  (
    fn startStroke = thePainterInterface.undoStart() 
    fn endStroke = (
      print "--- endStroke ---"
      ba = thePainterInterface.getPointGatherHits theObj
      for v in ba do (
        w=5
        n= thePainterInterface.getPointGatherNormal theObj v
        polyop.moveVert theObj #{v} (w*n)
      )
      thePainterInterface.undoAccept()
      thePainterInterface.clearStroke() 
    )
    fn cancelStroke = thePainterInterface.undoCancel() 
    fn systemEnd = print "Handle system end here"

    pickbutton pickMesh "Pick Mesh" autodisplay:true
    button btnTest "stroke"
    button btnOptions "Option"
  
    on TestPinterIterfaceRollout open do (
      thePainterInterface.pointGatherEnable = true
      thePainterInterface.buildNormals = true
      thePainterInterface.maxSize = 5
      if thePainterInterface.InPaintMode() then
      (
        thePainterInterface.EndPaintSession()
      )
    )
  
    on TestPinterIterfaceRollout close do (
      if thePainterInterface.InPaintMode() then
      (
        thePainterInterface.EndPaintSession()
      )
    )
  
    on pickMesh picked obj do
    (
      if obj != undefined do
      (
        theObj = Obj
      ) 
    ) 
  
    on btnOptions pressed do (
      thePainterInterface.paintOptions() 
    )
    on btnTest pressed do 
    (
      print "--- paintStroke ---"
      thePainterInterface.initializeNodes 0 #(theObj)
      thePainterInterface.startPaintSession()
      startStroke()
      thePainterInterface.addToStroke [200,100] true true
      thePainterInterface.addToStroke [300,300] true true
      thePainterInterface.addToStroke [500,200] true true
      endStroke()
      thePainterInterface.EndPaintSession()
    )
  )
  createDialog TestPinterIterfaceRollout 
)

このプログラムではスクリーン座標で[200,100]→[300,300]→[500,200]の順にカーソルを動かしてストロークする感じになるかなと思ってやってみた。

下の画像がその結果。50X50のメッシュ形状の編集可能ポリゴンに対して操作してみたら下のようにストロークに沿ってメッシュを変形できた。

fig01

どうやらこのメソッドを使うとストロークをマウスを使わず出来るようだ。ただし上のプログラムではブラシ強度は指定していないので、このプログラムでは5の固定値にしている。

ここでやってウェイト値が日の目を見るのかな?ブラシ強度は0でもウエイト値の方はポイントにしっかり出ているはず。そこで「endStroke()」を下のように書き換えてみた。

    fn endStroke = (
      print "--- endStroke ---"
      ba = thePainterInterface.getPointGatherHits theObj
      for v in ba do (
        w= (thePainterInterface.getPointGatherWeight theObj v) * 5
        n= thePainterInterface.getPointGatherNormal theObj v
        polyop.moveVert theObj #{v} (w*n)
      )
      thePainterInterface.undoAccept()
      thePainterInterface.clearStroke() 
    )

案の定ウエイト値は生きていたのでストロークの中心からリニアな傾斜を持った形状になった。あとは必要な断面形状になるようにこのウェイト値を適当な関数に渡して変換してやれば、ブラシ強度を使ったストロークと同じような事が再現できるね。

fig02

それからこのプログラムを作ってわかったんだけどストロークした後でもう一度ストロークをすると、前のストロークの最後の位置が残っちゃうみたいで、最初に作ったプログラムでは下のようにループ状になってしまった。

fig03

処理が終わったらちゃんとデータはクリアしとかないとダメなんだな。

その処理をするのが、「clearStroke()」メソッドだ。これを「endStroke()」の最後に付けて前のデータを引きずる問題は解決できた。

<void>thePainterInterface.clearStroke() 

と、ここまではなんとかわかったところだ。

続きはまた次回。

maxまとめページ



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