2017年01月23日

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

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

今回はブラシでペイントした時の事を調べてみたい。

「getHitPointData()」メソッドによって、ブラシのストロークでヒットポイント データを取得できる。

<void>thePainterInterface.getHitPointData <&point3>localHit <&point3>localNormal <&point3>worldHit <&point3>worldNormal <&float>radius <&float>str tabIndex
  • localHit : ヒットしたノードのローカル スペース内のヒット位置
  • localNormal : ヒットしたノードのローカル スペース内の、ヒットした面の法線
  • worldHit : ワールド空間のヒット ポイント
  • worldNormal : ヒット面のワールド空間の法線
  • radius : ヒット時のブラシの半径
  • str : ヒット時のブラシの強度
  • tabIndex : 取得するヒット ポイントのインデックス。この値が 0 の場合は、最後のヒット ポイントが返される

このメソッドに与えるパラメータは「tabIndex」以外全て入出力パラメータなので、値を与えることも受け取ることも出来るが、目的は1つのメソッドで各パラメータを一挙に取得する事にあり、呼び出すと各変数の値をヒットしたポイントデータで書き換えて戻るようになっている。

前回「offMeshHitType」を調べるために作ったプログラムはストローク中に呼び出される「paintStroke」ハンドラ内でこのメソッドを使い、リスナパネルに「worldHit」の値を出力していた。

    fn paintStroke = (
      hits = thePainterInterface.getHitCount() 
      thePainterInterface.getHitPointData &localHit &localNormal &worldHit &worldNormal &radius &str tabIndex
      print worldHit  
    ) 

「paintStroke」のハンドラはストローク中に逐一呼び出されるので、呼び出された時にブラシがヒットしていたポイントのデータが欲しければ、「tabIndex」を0にして、最後のヒットポイントを取得すればいいようだ。

「thePainterInterface」はストロークした時のヒットポイントを記録するから、ストローク終了で呼び出される「endStroke」ハンドラの中で「tabIndex」を1から切り替えながら「getHitPointData()」メソッドを呼び出せばストローク中のポイントデータを全て取得する事が出来る。その際問題になるのがストローク中のポイント数で、

thePainterInterface.getHitCount() 

で求める事が出来る。例えば前回のプログラムの「endStroke()」ハンドラを以下のように書き換えてストロークのテストをしてみると、

    fn endStroke = (
		print "--- endStroke ---"
		for i = 1 to thePainterInterface.getHitCount() do (
           thePainterInterface.getHitPointData &localHit &localNormal &worldHit &worldNormal &radius &str i
           print worldHit  
		)
    )

このようにストローク中に得られる最後のポイントデータの羅列とストローク後に得られるストローク中の全ポイントのデータが一致する。

"Handle start stroke here"
[-1.40804,21.7613,22.1627]
[-1.66102,21.7658,22.1386]
[-1.91405,21.7702,22.1145]
[-1.93369,21.4699,22.3594]
[-1.95325,21.1708,22.6032]
[-1.97276,20.8724,22.8466]
[-1.99223,20.5747,23.0893]
"--- endStroke ---"
[-1.40804,21.7613,22.1627]
[-1.66102,21.7658,22.1386]
[-1.91405,21.7702,22.1145]
[-1.93369,21.4699,22.3594]
[-1.95325,21.1708,22.6032]
[-1.97276,20.8724,22.8466]
[-1.99223,20.5747,23.0893]

ミラーブラシ側から取得するバージョンのメソッドもある。

<void>thePainterInterface.getMirrorHitPointData <&point3>localMirrorHit <&point3>localMirrorNormal <&point3>worldMirrorHit <&point3>worldMirrorNormal <integer>tabIndex

続きはまた次回。

maxまとめページ



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

2017年01月20日

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

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

今回は「MeshPolygon」クラスについて調べてみたい。ポリゴンは「polygons」プロパティから「new()」メソッドに「MeshVertex」オブジェクトの配列を渡す事で作成する事が出来る。面の裏表は配列内の頂点の順番によって決まり、頂点の順番が反時計回りに見える側がポリゴンの表になる。

import modo
mesh = modo.Mesh('Mesh')
geo = mesh.geometry
v0 = geo.vertices.new((1,1,0))
v1 = geo.vertices.new((0,1,-1))
v2 = geo.vertices.new((-1,1,0))
v3 = geo.vertices.new((0,1,1))
v4 = geo.vertices.new((1,-1,0))
v5 = geo.vertices.new((0,-1,-1))
v6 = geo.vertices.new((-1,-1,0))
v7 = geo.vertices.new((0,-1,1))
p0 = geo.polygons.new((v0,v1,v2,v3))
p1 = geo.polygons.new((v0,v4,v5,v1))
p2 = geo.polygons.new((v1,v5,v6,v2))
p3 = geo.polygons.new((v2,v6,v7,v3))
p4 = geo.polygons.new((v3,v7,v4,v0))
p5 = geo.polygons.new((v4,v7,v6,v5))

プログラムを実行すると、「Mesh」という名前のメッシュアイテムに下のようなBOXが生成される。

fig01

メッシュアイテムから「MeshPolygon」オブジェクトを取得する方法は「MeshVertex」と同様に「polygons」からインデックス番号で取得するか、コンストラクタから生成する方法、「fromId()」でIDから取得する方法、「polygons」の「selected」で選択頂点をタプルで得る方法もある。

p0 = geo.polygons[0]
p0 = modo.meshgeometry.MeshPolygon(0,geo)
id0 = p0.id
p0 = modo.meshgeometry.MeshPolygon.fromId(id0,geo)
p0 = modo.meshgeometry.MeshPolygon.fromMesh(0,geo)
selpolys = geo.polygons.selected

ポリゴンのID、インデックス、削除、選択、選択解除など「MeshVertex」とプロパティやメソッド名は同じだ。

print p0.id
print p0.index
p0.remove()
p1.select()
p1.deselect()

選択は「replace=False」パラメータを指定できて、既存値のFalseでは追加選択、Trueにすると選択をクリアしてから選択される。

その他に以下のメソッドがある。

  • getIsland()
     このポリゴンに直接接続するポリゴンのリストを返す

  • getTag(tagType)
     指定のポリゴンタグの値を返す
     :tagType: lx.symbol.i_POLYTAG_*

  • numTriangles(keepTriangles=False)
     このポリゴンを構成する三角ポリゴンの数を返す  :keepTriangles:Falseならカウントするために生成された三角ポリゴンリストをクリアする。このリストはSDKの「TriangleByIndex()」を利用する場合にあらかじめ作っておく必要がある。「TD SDK」では「triangles」が同じ値を返すプロパティで、こちらを使うと自動的に三角ポリゴンリストが生成されるのでTrueにする必要はない。

  • setTag(tagType, value)
     指定のポリゴンタグに値をセットする

  • tags()
     このポリゴンのタグリストを返す

  • vertexNormal(index)
     このポリゴン内の指定インデックス番号の頂点の法線ベクトルをタプルで返す

  • iterTriangles(self, asIndices=True)
    このポリゴンを構成する三角ポリゴンの3頂点のセットのセットを生成するジェネレータを返す。
    :asIndices:Trueなら頂点はインデックスで、Falseならオブジェクトで返す

  • iterVertexNormals(self)
    このポリゴンを構成する頂点の法線のタプルを生成するジェネレータを返す

「iterVertexNormals()」はコードにバグがあるようでエラーが出る。下がそのコードだけど「num_vertices」と「vertex_normal()」が存在しないのでエラーになる。どう見ても入力ミスじゃないだろうか?

    def iterVertexNormals(self):
        """Iterator for vertex normals
        :return: Generator object that returns a vertex normal per polygon vertex
        """
        for i in range(self.num_vertices):
            yield self.vertex_normal(i)

ちなみにこのように修正すればちゃんと動くよ。

    def iterVertexNormals(self):
        """Iterator for vertex normals
        :return: Generator object that returns a vertex normal per polygon vertex
        """
        for i in range(self.numVertices):
            yield self.vertexNormal(i)

「iterTriangles()」と「iterVertexNormals()」の修正版を使ってタプルを生成してみると、以下のように結果が得られた。

print tuple(p1.iterTriangles(False))
print tuple(p1.iterTriangles())
print tuple(p1.iterVertexNormals())

# Result: ((modo.MeshVertex(0, 'mesh022'), modo.MeshVertex(4, 'mesh022'), modo.MeshVertex(1, 'mesh022')), (modo.MeshVertex(4, 'mesh022'), modo.MeshVertex(5, 'mesh022'), modo.MeshVertex(1, 'mesh022'))) ((0L, 4L, 1L), (4L, 5L, 1L)) ((0.7071067690849304, 0.0, -0.7071067690849304), (0.7071067690849304, 0.0, -0.7071067690849304), (0.7071067690849304, 0.0, -0.7071067690849304), (0.7071067690849304, 0.0, -0.7071067690849304))

その他のプロパティは以下の通り。

  • area
    :getter: ポリゴンの面積を返す

  • edges
    :getter: Returns the :class:`edges<MeshEdge>` connected to this polygon

  • materialTag
    :getter: 指定のマテリアルタグの値を返す
    :setter: 指定のマテリアルタグの値をセットする

  • neighbours
    :getter:このポリゴンに隣接する全てのポリゴンをタプルで返す

  • normal
    :getter: このポリゴンの表方向の法線ベクトルをタプルで返す

  • numVertices
    :getter: このポリゴンを構成する頂点の数を返す

  • triangles
    :getter : このポリゴンを構成する全ての三角ポリゴンを構成する頂点のインデックスのタプルをタプルで返す

  • vertices
    :getter: このポリゴンを構成する全ての頂点を「MeshVertex」オブジェクトのタプルで返す

UVマップについてはマップについて調べる時に一緒に調べたい。

それではまた次回。

modo10ブログ目次



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

2017年01月19日

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

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

今回は「offMeshHitType」プロパティから。

thePainterInterface.offMeshHitType : integer : Read|Write 

このプロパティについてはマニュアルに以下のように書かれている。

メッシュの外側をペイントする場合またはメッシュを使用しないでペイントする場合のヒット タイプを定義します。

0 - 最後のヒット ポイントまたは法線に基づいて平面を作成します。このタイプを使用するときは、ペイントするためのメッシュが必要です。

1 - 画面内への Z 深度。ペイント可能なビュー内に平面の X 単位が作成されます。この場合、メッシュは必要ありません。

2 - 現在のビューに位置合わせされたワールド空間内のポイント上に平面を作成します。この場合、メッシュは必要ありません。

どうやら3Dブラシがメッシュから外れた時の振る舞いを決めるもののようだ。

いまいち読んでもよくわからないので下のプログラムを作って試してみた。

(
  local theObj = undefined
  local localHit = [0,0,0]
  local localNormal = [0,0,0]
  local worldHit=[0,0,0]
  local worldNormal=[0,0,0]
  local radius=1
  local str=1
  local tabIndex = 0
  local offmeshhittype = 0
  local offMeshHitLocator = undefined

  rollout TestPinterIterfaceRollout "MicroPaint"
  (
    fn startStroke = print "Handle start stroke here"
    fn paintStroke = (
      hits = thePainterInterface.getHitCount() 
      thePainterInterface.getHitPointData &localHit &localNormal &worldHit &worldNormal &radius &str tabIndex
      print worldHit  
    )  
    fn endStroke = print "Handle end stroke here"
    fn cancelStroke = print "Handle cancel stroke here"
    fn systemEnd = print "Handle system end here"

    pickbutton pickMesh "Pick Mesh" autodisplay:true
    checkbutton paint3D "3D PAINT"
    dropdownlist hitType "Off Mesh Hit Type" items:#("0", "1", "2") fieldwidth:40
    spinner  offMeshHitZDepth "offMeshHitZDepth" range:[-9999,999,100] type:#float fieldwidth:40
    pickbutton pickLocator "Pick Hit Position Locator" autodisplay:true
    button btnOptions "Option"
  
    on TestPinterIterfaceRollout open do (
      if thePainterInterface.InPaintMode() then
      (
        thePainterInterface.EndPaintSession()
      )
      thePainterInterface.offMeshHitZDepth = offMeshHitZDepth.value
    )

    on TestPinterIterfaceRollout close do (
      if thePainterInterface.InPaintMode() then
      (
        thePainterInterface.EndPaintSession()
      )
    )

    on hitType selected i do
    (
      offmeshhittype = i - 1
      if thePainterInterface.InPaintMode() then
      (
        thePainterInterface.EndPaintSession()
        paint3D.checked = false
      )
    )

    on pickMesh picked obj do
    (
      if obj != undefined do
      (
        theObj = Obj
      ) 
    ) 

    on offMeshHitZDepth changed val do
    (
      if thePainterInterface.InPaintMode() then
      (
        thePainterInterface.EndPaintSession()
        paint3D.checked = false
      )
      thePainterInterface.offMeshHitZDepth = offMeshHitZDepth.value
    )

    on pickLocator picked obj do
    (
      if obj != undefined do
      (
        thePainterInterface.EndPaintSession()
        paint3D.checked = false
        offMeshHitLocator = Obj
        thePainterInterface.offMeshHitPos = offMeshHitLocator.pos
      ) 
    )

    on paint3D changed state do 
    (
      if thePainterInterface.InPaintMode() or theObj == undefined  or (offmeshhittype==2 and offMeshHitLocator == undefined ) then
      (
        thePainterInterface.EndPaintSession()
        paint3D.checked = false
      )
      else
      (
        paint3D.checked = true
        thePainterInterface.initializeNodes 0 #(theObj)
        thePainterInterface.offMeshHitType = offmeshhittype
        thePainterInterface.ScriptFunctions startStroke paintStroke endStroke cancelStroke SystemEndPaintSession
        thePainterInterface.startPaintSession()
      )
    )
    on btnOptions pressed do (
      thePainterInterface.paintOptions() 
    )
  )
  createDialog TestPinterIterfaceRollout 
)

実行すると下のダイアログが表示される。ペイント対象のアイテムを「Pick Mesh」で選んで「3D PAINT」ボタンを押すとストロークを開始できる。ストロークでヒットしたポイントの座標値はリスナーパネルに逐一表示される。

fig02

このプログラムで調べた結果、値を0にした時はカーソルの下にメッシュが無ければペイントしないか、ストローク途中なら、ブラシがメッシュ上にある最後の点の法線方向を向いた、その点を通る平面上でストロークが継続される動作になる事がわかった。

fig01

値を1にするとビューポートの視点から奥行き方向に「offMeshHitZDepth」値だけ進んだ位置にビューポートの視線方向に垂直な仮想平面が設定されて、メッシュ以外でストロークした場合にその平面上でストロークされる。

fig03

thePainterInterface.offMeshHitZDepth : float : Read|Write 

値を2にすると「thePainterInterface.offMeshHitPos」に設定されたポイントにビューポートの視線方向に垂直な仮想平面が設定されて、メッシュ以外でストロークした場合にその平面上でストロークされる。

fig04

thePainterInterface.offMeshHitPos : point3 : Read|Write 

続きはまた次回。

maxまとめページ



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

2017年01月18日

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

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

今回はメッシュの頂点を扱う「MeshVertex」クラスについて調べてみたい。このクラスは頂点オブジェクトを参照してるだけのようで、「del」でオブジェクトを削除しても頂点自体は削除されない。

頂点の生成は前回調べたように「vertices」プロパティから頂点の座標値をタプルかリストで「new()」メソッドに与えて呼び出せばいい。

import modo
mesh=modo.Mesh('Mesh')
geo=mesh.geometry
v0 = geo.vertices.new((1,0,0))
v1 = geo.vertices.new((0,0,-1))
v2 = geo.vertices.new((-1,0,0))
v3 = geo.vertices.new((0,0,1))

既存のメッシュの頂点を「MeshVertex」オブジェクトとして取得するには「vertices」からインデックス番号で取得するか、コンストラクタから生成する方法、「fromId()」でIDから取得する方法、「vertices」の「selected」で選択頂点をタプルで得る方法もある。

v0 = geo.vertices[0]
v0 = modo.meshgeometry.MeshVertex(0,geo)
id0 = v0.id
v0 = modo.meshgeometry.MeshVertex.fromId(id0,geo)
selverts = geo.vertices.selected

頂点の削除には「remove()」メソッドを使う。

v0.remove()

頂点の選択、選択解除は「select()」「deselect()」

v1.select()
v1.deselect()

頂点のIDは「id」、インデックス番号は「index」で取得できる。IDはオブジェクトに振られるユニークな番号で、インデックスはメッシュアイテム内での頂点の0から始まる通し番号だ。

print v1.id
print v1.index

頂点の位置は「positon」プロパティから読み書きでき、各要素は「x」「y」「z」プロパティから読み書き出来る。

print v1.position
v1.position = (2,0,0)
print v1.x
v1.y = -1
print v1.position

# Result:
(0.0, 0.0, -1.0)
2.0
(2.0, -1.0, 0.0)

頂点が含まれるポリゴンは「polygons」、エッジで繋がる隣の頂点は「vertices」から得られる。

print v1.polygons
print v1.vertices

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

また、ポイントは「+」「-」「*」「/」「==」演算を実装している。四則演算は演算子の前の頂点の座標値を後ろの座標値と各要素ごとに演算して書き変える。また、「==」演算は頂点のID番号を比較する。だから座標値が同じでも違う頂点オブジェクトなら演算結果はfalseになる。

例えば「+」演算子の定義は以下のようになっている。

def __add__(self, other):
    """Adds another position"""
    if isinstance(other, MeshVertex):
        other = other.position
    self._accessor.SelectByIndex(self._index)
    pos = list(self._accessor.Pos())
    pos = [a+b for a, b in zip(pos, other)]
    self._accessor.SetPos(pos)
    return self

例えば下のように式を書くと、上のプログラムの「self」が「v1」、「other」が「v2」になり、「v2」は「MeshVertex」のインスタンスなので「positon」プロパティから「v2」の座標値が取得されて「other」が座標値に置き換えられてから「v1」の座標値に要素ごとに加算されてセットされる。

print v1.position
print v2.position
v4 = v1 + v2
print v1.position
print v2.position
print v4.position

# Result:
(2.0, -1.0, 0.0)
(-1.0, 0.0, 0.0)
(1.0, -1.0, 0.0)
(-1.0, 0.0, 0.0)
(1.0, -1.0, 0.0)

だから上の式で「v4」に代入したものは値が変更されたv1なので、

print v4 == v1

式はTrueになる。後ろの値が頂点オブジェクトじゃなくてタプルやリストなら、そのままそれが「other」で使われて同様に加算される。

v1 = v1 + (1,-2,0)

頂点を編集したら「setMeshEdits()」メソッドを呼び出してmodoに頂点が編集された事を伝える事で表示が更新される。

geo.setMeshEdits()

ガイドラインには以下のように書かれている。

Any assigment operation such as g.vertex[0].position = (1,2,3) implicitly puts the geometry into an editing stage (LayerScan). It is very important to finish the editing stage with setMeshEdit() to apply the changes to the geometry and hence make them visible.

Unlike before, derived geometry objects such as polygons or vertex maps that are created or fetched from the geometry during the editing phase may become invalid after applying the edits with setMeshEdit() or return out of date-results. In such cases one should simply fetch the desired object from the geometry again after applying the edits.

今回のプログラムの実行ではこれをしなくてもビューポートが更新されてたけど、どのタイミングで更新されていたかは不明だ。確実に更新したいならちゃんと呼んで置いた方がいいんだろうな。「new()」メソッドや「with modo.Mesh().geometry as geo:」のように「with」を使った場合は自動で呼び出されるので手動で呼び出す必要は無いようだ。また上の文章に書かれている通り、ポリゴンや頂点マップは「setMeshEdits()」メソッド後に無効になってしまい再フェッチが必要になる事もあるようだ。

頂点マップに関するメソッドについては頂点マップを調べる時に一緒に調べたい。

それではまた次回。

modo10ブログ目次



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

2017年01月17日

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

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

前回に引き続き「PainterInterface」の「ペインタオプション」パネルに現れるパラメータに対応するプロパティを調べてみたい。

fig01

このダイアログボックスはMaxのペイントツールでも使われていて、真ん中にあるグラフはブラシの中心から周辺にかけてのブラシ強度の変化をコントロールするために使われている。左側がブラシの中心で、右側がブラシの外縁だ。この部分について「PainterInterface」のプロパティを調べてみたんだけど対応するものは見つからなかった。この部分はこのダイアログから手動で操作するしかないようだ。

fig01[加算](Additive)チェックボックスはONにするとペイント値が既存の値に加算されるようになる。これに対応するのが「additiveMode」プロパティだ。

thePainterInterface.additiveMode : boolean : Read|Write

fig02 />ミラーはONにすると現在のブラシとX,Y,Z平面に対して対称位置にもう一つのブラシを出して対称なペイントを行う事が出来る。

thePainterInterface.mirrorEnable : boolean : Read|Write 

対称平面はX,Y,Zの3方向から選択出来る。

thePainterInterface.mirrorAxis : integer : Read|Write|Validated by Range: 1 to 3
 1 - X 2 - Y 3 - Z

fig05

「オフセット」によって対称平面(ギズモ)を面と垂直方向に動かす事が出来る。「ギズモサイズ」は対称平面のギズモの縦横の辺の長さを設定するもの。

fig03

thePainterInterface.mirrorOffset : float : Read|Write|Validated by Range: -1e+006 to 1e+006

thePainterInterface.mirrorGizmoSize : float : Read|Write|Validated by Range: 0.0 to 1e+006

fig04その他のパラメータは「描画更新レート」に対応するものが見つからなかった。「ツリー深度」は2〜10の値に設定できて、数値が大きいほどレスポンスは上がるけどメモリーは多く消費する。「マウスリリース時に更新」はONにするとペイントストローク中にイベントハンドラを呼び出さなくなり、ストローク終了後にストローク終了イベントハンドラが呼び出されるので、そこで一括してペイント処理を行う事になる。

thePainterInterface.treeDepth : integer : Read|Write|Validated by Range: 2 to 10 

thePainterInterface.updateOnMouseUp : boolean : Read|Write

続きはまた次回。

maxまとめページ



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