2017年07月

2017年07月31日

modo11.0v3のスクリプトについて調べてみた その108

引き続き「TD SDK」の「meshgeometry.py」を調べてみたい。今回も「MeshGeometry」クラスから。

「setMeshEdits()」はメッシュへの変更を適用するために呼び出す必要があるメソッドだ。下がそのコードで、内容は「MeshProvider」クラスの「applyLayerScan()」が呼び出されているだけだ。

 def setMeshEdits(self, editType=lx.symbol.f_MESHEDIT_GEOMETRY):
     """Updates mesh edits applying previous changes.

     :param lx.symbol.f_MESHEDIT_* editType: The type of change to set. Defaults to all.
     """
     self.provider.applyLayerScan(editType)

どうやらmodoでメッシュを編集する場合は一旦アイテムのメッシュチャンネルからデータを読み出して、編集後に戻してやるような操作を行う必要があるようだ。それを行うのが「MeshProvider」クラスの「beginLayerScan()」と「applyLayerScan()」だ。

下が「beginLayerScan()」のコードだ。引数の「flags」には上のコードからデフォルトで「lx.symbol.f_MESHEDIT_GEOMETRY」が渡される。

def beginLayerScan(self, flags=lx.symbol.f_LAYERSCAN_EDIT):
        
     if self.readOnly is True:
         raise RuntimeError ("Attempted to access geometry which is set to read only mode")
        
     if self.layerEdit is not None:
         return
        
     self.layerEdit       = LayerScanItemEdit(self.item, flags)
        
     self._mesh           = self.layerEdit.getMesh()
     self.PolygonAccessor = self._mesh.PolygonAccessor()
     self.PointAccessor   = self._mesh.PointAccessor()
     self.EdgeAccessor    = self._mesh.EdgeAccessor()
     self.MeshMapAccessor = self._mesh.MeshMapAccessor()

最初に「readOnly」がチェックされて値がTrueなら編集不可だから編集開始の「beginLayerScan()」が実行出来るのはおかしいのでエラーになる。また、「layerEdit」をチェックしてNoneじゃない時はすでに編集が開始されているので何もしないでリターンする。それもパスしたら「LayerScanItemEdit」クラスのインスタンスを生成して、そこから「Mesh」オブジェクトを取得して、そこから各エレメントのアクセッサを取得している。

「LayerScanItemEdit」クラスは「LayerScan」クラスのヘルパークラスで、「LayerScan」オブジェクトはメッシュを編集する時に必要なアイテムをレイヤーで取得して、そこから辿って各アイテムのエレメントを操作するメソッドを取得するようになっている。これはグローバルなオブジェクトで他でも使われるので操作が終わったらすぐに開放してやる必要があるようだ。

「LayerScanItemEdit」クラスの初期化メソッドではまず「LayerScan」を一旦開放して、直前の作業で取得されっぱなしになっているオブジェクトがあったら開放してから、レイヤーサービスの「ScanAllocateItem()」で編集対象として引き渡されてきたアイテムを「MESHEDIT_GEOMETRY」レイヤーとして「LayerScan」に割り当てて、そこからさらに「Mesh」オブジェクトを取得している。レイヤーアイテムは1つだけなので、このコードでは0番目のメッシュレイヤが「_mesh」に取得されているけど、複数のメッシュレイヤが取得されている場合は「LayerScanItemEdit.layerScan.MeshEdit()」でn番目のメッシュレイヤにアクセスする事が出来る。

そしてこの「Mesh」オブジェクトから「beginLayerScan()」メソッドの中で各エレメントのアクセッサが取得され、そのアクセッサのメソッドを使って各エレメントが操作出来るようになる。

 class LayerScanItemEdit(object):

    layerScanService = lx.service.Layer()
    layerScan = None

    def __init__(self, item, flags=lx.symbol.f_LAYERSCAN_EDIT):
        
        self._item = item
        self._flags = lx.symbol.f_MESHEDIT_GEOMETRY
        LayerScanItemEdit.release()
    
        if not self.isItemValid():
            raise RuntimeError('Invalid item')
                    
        layerScanService = type(self).layerScanService
        try:
            LayerScanItemEdit.layerScan = layerScanService.ScanAllocateItem(self._item, flags)
            if LayerScanItemEdit.layerScan.Count() == 0:
                raise RuntimeError("No valid mesh found during layer scan allocation")
            
        except RuntimeError:            
            # Clean up all potential previous layer scans
            LayerScanItemEdit.release()

            raise RuntimeError("Layer scan allocation failed. Did you forget to call setMeshEdits earlier?")
        
        self._mesh = LayerScanItemEdit.layerScan.MeshEdit(0)

そしてメッシュ編集作業が終わったら今度は「applyLayerScan()」メソッドの出番だ。「apply()」で更新してから再び「useMeshChannel()」を呼び出して、アクセッサも更新する。

 def applyLayerScan(self, flags=lx.symbol.f_MESHEDIT_GEOMETRY):
     if self.layerEdit is not None:
            
         # Apply edits and clear object, so it will be re-created by the next getMesh() call.
         self.layerEdit.apply(flags)
         self.layerEdit = None
                    
         # Go to read-only mode unless setup mode is active (
         accessMode = 'write' if self.item.Context().SetupMode() else 'read'            
         self.useMeshChannel(accessMode)
            
     else:
         flags = lx.symbol.f_MESHEDIT_GEOMETRY if flags is None else flags
         self._mesh.SetMeshEdits(flags)  

「apply()」メソッドの内容は以下の通りで、「SetMeshChange」で0番のメッシュレイヤが変更された事をmodoに知らせて、「Apply()」で更新してもらう仕組みのようだ。

 def apply(self, flags=lx.symbol.f_MESHEDIT_GEOMETRY):
     '''Pass None for the flags argument if no changes were made. Might fail otherwise'''
        
     if not self.isItemValid():
         LayerScanItemEdit.release()
         raise RuntimeError('Invalid item')
        
     if flags:
         LayerScanItemEdit.layerScan.SetMeshChange(0, lx.symbol.f_MESHEDIT_GEOMETRY)
                    
     LayerScanItemEdit.layerScan.Apply()  

続きはまた次回。

modo10-11ブログ目次



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

2017年07月27日

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

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

前回のプログラムで自由な位置と方向にペイントができるようになったので、今度は運用方法を考えてみたい。

そこで思いついたのはプログラムを構造体に落し込んで、他のプログラムから使う事だ。MaxScriptは既存のオブジェクトのクラスは扱えるのにユーザー定義クラスが作れなくて、構造体しかないんだよね。でも構造体ならローカルに変数もファンクションも持てて1つのまとめて扱う事が出来る。

構造体は以下の構文で定義できる。名前とコンマで区切った「<member>」を必要なぶんだけ並べた形だ。

 struct <struct_name> ( <member> { , <member> } )

「<member>」になれるのは以下の2つ。

 <name> [ = <expr> ]
 <function_def>

さらに「<member>」には「public」と「private」の指定が出来て、「private」は外部から参照できない。下のリストは構造体を定義してみたものだ。

struct car (
	public
		name,
		weight,
		power,
	private
		speed = 0,
	public
		fn howfast = (
			format "% : %km/h\n" name speed
		),
	
		fn accel sw = (
			if sw == on then (
				speed += power/weight
			) else (
				speed -= (10/weight + speed/weight)
			)
		),
		
		fn break = (
			speed -= 20
			if speed < 0 then speed = 0
		)
)

定義して構造体はインスタンスを作成して使用する。その時下のようにメンバーの初期値を引数として与える事が出来る。引数を与えない時は「()」を付け、部分的に初期値を与える時は「メンバー名:値」の形で渡す。

 mycar = car "skyline" 1500 10000.0
 yourcar = car()
 hiscar = car power:5000.0

定義した構造体を「show()」で中身を表示してみると下のように「Public」「Private」を付けて出力される。

show car
  weight:; Public
  power:; Public
  break:; Public
  accel:; Public
  speed:; Private
  name:; Public
  howfast:; Public

メンバーへのアクセスはインスタンス名とメンバー名をピリオドで繋いで書く以外は変数やファンクションと扱いは一緒だ。

下のリストは上で定義した構造体を利用してみたものだ。

(
	mycar = car "skyline" 1500 10000.0
	yourcar = car weight:1000
	yourcar.name = "corolla"
	yourcar.power = 3000.0
	
	format "\n----- % -----\n" mycar.name
	mycar.howfast()
	mycar.accel on
	mycar.howfast()
	mycar.accel on
	mycar.howfast()
	mycar.accel on
	mycar.howfast()
	mycar.accel off
	mycar.howfast()
	mycar.accel off
	mycar.howfast()
	mycar.break()
	mycar.howfast()
	
	format "\n----- % -----\n" yourcar.name
	yourcar.howfast()
	yourcar.accel on
	yourcar.howfast()
	yourcar.accel on
	yourcar.howfast()
	yourcar.accel on
	yourcar.howfast()
	yourcar.accel off
	yourcar.howfast()
	yourcar.accel off
	yourcar.howfast()
	yourcar.break()
	yourcar.howfast()

)

そしてこれが実行結果。

#Struct:car(
  weight:; Public,
  power:; Public,
  break:; Public,
  accel:; Public,
  speed:; Private,
  name:; Public,
  howfast:; Public)
----- skyline -----
skyline : 0km/h
skyline : 6.66667km/h
skyline : 13.3333km/h
skyline : 20.0km/h
skyline : 19.9867km/h
skyline : 19.9733km/h
skyline : 0km/h

----- corolla -----
corolla : 0km/h
corolla : 3.0km/h
corolla : 6.0km/h
corolla : 9.0km/h
corolla : 8.991km/h
corolla : 8.98201km/h
corolla : 0km/h
OK

このようにプログラムを構造体にしてしまえば中身は隠蔽してパッケージとして扱いやすくなる。

前回作ったペイントプログラムは何をパブリックメンバーにすればいいかな。

続きはまた来週。

明日はお休みします。

maxまとめページ



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

2017年07月26日

modo11.0v3のスクリプトについて調べてみた その107

引き続き「TD SDK」の「meshgeometry.py」を調べてみたい。

「Mesh」クラスの「geometry」プロパティは以下のようになっていて、アクセスすると「MeshGeometry」クラスのインスタンスがあればそのまま返され、無ければ新規に作って返される。

 @property
 def geometry(self):
     if not self._geometry:
         self._geometry = MeshGeometry(self._item)
     return self._geometry

前々回調べたように、「MeshGeometry」クラスには「_cache」クラス変数に「weakref.WeakValueDictionary」クラスの辞書があって、メッシュアイテムをキーとして「MeshGeometry」のインスタンスが登録されていて、そこに登録が見つかればそれが返されるので、常に「Mesh」アイテム1つに対して「MeshGeometry」インスタンスは1つしか生成されないようになっている。

「accessMode()」は「geometry」のアクセスモードを調べるメソッド。このメソッドは「self.__accessMode」の値を戻すだけなんだけど、なぜかこのインスタンス変数は「MeshGeometry」が生成される時に初期化されない。

 @property
 def accessMode(self):
     #  Set or change the kind of access to the mesh.
     #
     # :param value: Mode in which to access the mesh. Possible values: "write", "read", "deformed"
     # :param time: Only relevant for "deformed" mode - will set the mesh up to read positions at the given time
     # """
     return self.__accessMode

アクセスモードをセットするには「setAccessMode()」メソッドを使う。モードは「read」、「write」、「deformed」の3種類の文字列で指定する。「read」はメッシュを読み込み専用に、「write」は書き換え可能に、「deformed」は変形後のメッシュにアクセスする時に使う。コードを見ると「deformed」も「readOnly」値がTrueにセットされるので読み込み専用だ。「readOnly」は「MeshGeometry」が生成される時にデフォルトでFalseに初期化される。

 def setAccessMode(self, value='write', time=None):
     """ possible values: 'read', 'write' or 'deformed' """
     self.__accessMode = value
     self.provider.readOnly = (value == 'read')
        
     if value is not 'write':
         self.provider.readOnly = True
         self.provider.useMeshChannel(mode=value, time=time)   

「deformed」モードについてちょっと試してみた。

下のシーンはモーフデーフォーマーで0〜15フレームで1m角のBOXが半分の大きさの0.5m角になるようにジオメトリのスケールアニメーションをしてみたものだ。

fig01

下のプログラムはタイムスライダを0〜15まで1フレームずつ動かして、その時の0番目のポイントの座標値を出力するものだ。アクセスモードは「deformed」にしてある。

from modo import *
mesh = Mesh('Mesh')
geo = mesh.geometry
geo.setAccessMode('deformed')
lx.eval('select.time 0.0')
for t in range(16):
	lx.eval('select.time %s' % (1.0/24 * t))
	print geo.vertices[0].position

そしてこれが実行結果。確かに値は変化しているけど、変化の仕方がおかしいし、最後の値が(-0.25, -0.25, -0.25)になっていない。

# Result: 
(-0.25, -0.25, -0.25)
(-0.25, -0.25, -0.25)
(-0.25, -0.25, -0.25)
(-0.4878518581390381, -0.4878518581390381, -0.4878518581390381)
(-0.4878518581390381, -0.4878518581390381, -0.4878518581390381)
(-0.4561481475830078, -0.4561481475830078, -0.4561481475830078)
(-0.4561481475830078, -0.4561481475830078, -0.4561481475830078)
(-0.41200000047683716, -0.41200000047683716, -0.41200000047683716)
(-0.41200000047683716, -0.41200000047683716, -0.41200000047683716)
(-0.3625185191631317, -0.3625185191631317, -0.3625185191631317)
(-0.3625185191631317, -0.3625185191631317, -0.3625185191631317)
(-0.31481480598449707, -0.31481480598449707, -0.31481480598449707)
(-0.31481480598449707, -0.31481480598449707, -0.31481480598449707)
(-0.2759999930858612, -0.2759999930858612, -0.2759999930858612)
(-0.2759999930858612, -0.2759999930858612, -0.2759999930858612)
(-0.2531851828098297, -0.2531851828098297, -0.2531851828098297)

しかし15フレームの時に手で、

print geo.vertices[0].position

と打って実行すると、確かに(-0.25, -0.25, -0.25)と出力される。どうやらフレーム更新後のどこかの時点でメッシュ情報は更新されるようなんだけど、そのタイミングと値の取得のタイミングが合わないようだ。それを合わせるには強制的に更新させればいいだろうと言う事で、値の取得前にもう一度アクセスモードをセットしなおすようにしてみた。

from modo import *
mesh = Mesh('Mesh')
geo = mesh.geometry
geo.setAccessMode('deformed')
lx.eval('select.time 0.0')
for t in range(16):
lx.eval('select.time %s' % (1.0/24 * t))
geo.setAccessMode('deformed')
print geo.vertices[0].position

これが実行結果。今度は値が期待通りになった。

# Result: 
(-0.5, -0.5, -0.5)
(-0.4968148171901703, -0.4968148171901703, -0.4968148171901703)
(-0.4878518581390381, -0.4878518581390381, -0.4878518581390381)
(-0.4740000069141388, -0.4740000069141388, -0.4740000069141388)
(-0.4561481475830078, -0.4561481475830078, -0.4561481475830078)
(-0.43518519401550293, -0.43518519401550293, -0.43518519401550293)
(-0.41200000047683716, -0.41200000047683716, -0.41200000047683716)
(-0.3874814808368683, -0.3874814808368683, -0.3874814808368683)
(-0.3625185191631317, -0.3625185191631317, -0.3625185191631317)
(-0.33799999952316284, -0.33799999952316284, -0.33799999952316284)
(-0.31481480598449707, -0.31481480598449707, -0.31481480598449707)
(-0.2938518524169922, -0.2938518524169922, -0.2938518524169922)
(-0.2759999930858612, -0.2759999930858612, -0.2759999930858612)
(-0.2621481418609619, -0.2621481418609619, -0.2621481418609619)
(-0.2531851828098297, -0.2531851828098297, -0.2531851828098297)
(-0.25, -0.25, -0.25)

また、アクセスモードを設定する時に時刻の指定も一緒にする方法でも同様の結果が得られた。

from modo import *
mesh = Mesh('Mesh')
geo = mesh.geometry
for t in range(16):
geo.setAccessMode('deformed',(1.0/24 * t))
print geo.vertices[0].position

続きはまた次回。

modo10-11ブログ目次



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

2017年07月25日

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

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

「ボリューム選択」を大きめにとって頂点から外れたエリアでもブラシと干渉する面を取得する方式でプログラムを作り変えてみた。「maxFaceRadius」がメッシュを構成する面の外接円の最大半径サイズだ。ブラシサイズやブラシシェイプもプログラムの外に出してグローバル変数にした。これはコントロールと置き換えるための準備だ。

threshold = 0.00000001
maxFaceRadius = 30.0
brushsize = 10
BrushShape = 2

fn chckClip p1 p2 w2b direction =
(
	cplist = #()
	tlist = #()
	hsize = brushsize / 2.0
	bp1 = p1 * w2b
	bp2 = p2 * w2b

	st = 0
	t = 0.0
	case direction of (
		-- +x
		0:	(
				if bp1.x >= hsize then st = 1
				if bp2.x >= hsize then st = st + 2
				if st == 1 or st ==2 then (
					dx = bp2.x - bp1.x
					t = (hsize - bp1.x)/dx
				)
			)
		-- -x
		1:	(
				if bp1.x <= -hsize then st = 1
				if bp2.x <= -hsize then st = st + 2
				if st == 1 or st ==2 then (
					dx = bp2.x - bp1.x
					t = (-hsize - bp1.x)/dx
				)
			)
		-- +y
		2:	(
				if bp1.y >= hsize then st = 1
				if bp2.y >= hsize then st = st + 2
				if st == 1 or st ==2 then ( 
					dy = bp2.y - bp1.y
					t = (hsize - bp1.y)/dy
				)
			)
		-- -y
		3:	(
				if bp1.y <= -hsize then st = 1
				if bp2.y <= -hsize then st = st + 2
				if st == 1 or st ==2 then (
					dy = bp2.y - bp1.y
					t = (-hsize - bp1.y)/dy
				)
			)
		-- +z
		4:	(
				if bp1.z >= hsize then st = 1
				if bp2.z >= hsize then st = st + 2
				if st == 1 or st ==2 then (
					dz = bp2.z - bp1.z
					t = (hsize - bp1.z)/dz
				)
			)
		-- -z
		5:	(
				if bp1.z <= -hsize then st = 1
				if bp2.z <= -hsize then st = st + 2
				if st == 1 or st ==2 then (
					dz = bp2.z - bp1.z
					t = (-hsize - bp1.z)/dz
				)
			)
	)
	return #(st,t)	
)

fn innerpoint p1 p2 t = (
	dx = p2.x - p1.x
	dy = p2.y - p1.y
	dz = p2.z - p1.z
	x = dx * t + p1.x
	y = dy * t + p1.y
	z = dz * t + p1.z
	return [x,y,z]
)

fn clipface &vlist brushtransform = ( 
	for i = 0 to 5 do (
		tmp = #()
		for j = 1 to (vlist.count - 1) do (
			st = chckClip vlist[j] vlist[j+1] brushtransform i
			if st[1] == 0 then (
				append tmp vlist[j] 
			) else if st[1] == 2 then (
				append tmp vlist[j] 
				append tmp (innerpoint vlist[j] vlist[j+1] st[2])
			) else if st[1] == 1 then (
				append tmp (innerpoint vlist[j] vlist[j+1] st[2])
			)
		)
		if vlist.count == 0 then exit
		st = chckClip vlist[vlist.count] vlist[1] brushtransform i
		if st[1] == 0 then (
				append tmp vlist[vlist.count] 
		) else if st[1] == 2 then (
			append tmp vlist[vlist.count]
			append tmp (innerpoint vlist[vlist.count] vlist[1] st[2])
		) else if st[1] == 1 then (
			append tmp (innerpoint vlist[vlist.count] vlist[1] st[2])
		)
		vlist = tmp
	)
)

fn selectcrossface theObj brushtransform = (
	searchLength = sqrt(maxFaceRadius^2+brushsize^2) * 1.05
	vmod = theObj.modifiers[#Vol__Select]
	vmod.level = 2
	vmod.type = 1
	vmod.volume = 0
	sanim = vmod.gizmo
	bbox = nodeGetBoundingBox theObj theObj.transform
	sx = abs(bbox[1][1] - bbox[2][1])
	sy = abs(bbox[1][2] - bbox[2][2])
	sz = abs(bbox[1][3] - bbox[2][3])
	sanim.transform = brushtransform
	sanim.scale = [searchLength / sx,searchLength/sy, searchLength/sz]
	selfaces = getFaceSelection theObj
	return selfaces
)

fn crossFaceMaps &maps theObj theMesh seedface theChannel brushtransform  mt2b =
(
	invBrushTransform = inverse( brushtransform)
	crossfaces = selectcrossface theObj brushtransform

	for faceIndex in crossfaces do (
		vlist = #()
		sufFace = getFace theMesh  faceIndex
		append vlist (getVert theMesh sufFace.x)
		append vlist (getVert theMesh sufFace.y)
		append vlist (getVert theMesh sufFace.z)
		mg2w=(matrix3 [vlist[1].x,vlist[1].y,vlist[1].z] [vlist[2].x,vlist[2].y,vlist[2].z] [vlist[3].x,vlist[3].y,vlist[3].z] [0,0,0])

		texFace = meshop.getMapFace theMesh theChannel faceIndex
		tv1= meshop.getMapVert theMesh theChannel texFace.x
		tv2= meshop.getMapVert theMesh theChannel texFace.y
		tv3= meshop.getMapVert theMesh theChannel texFace.z
		mg2t=(matrix3 [tv1.x,tv1.y,1] [tv2.x,tv2.y,1] [tv3.x,tv3.y,1] [0,0,0])
		
		mg2b = mg2t * mt2b
		mb2g = inverse mg2b
		mb2w = mb2g * mg2w

		clipface &vlist invBrushTransform

		if vlist.count == 0 then continue
		maplist =  #()
		for v in vlist do (
			mp = (meshop.getBaryCoords theMesh faceIndex v) * mg2t
			append maplist mp
		)
		append maps #(maplist , mb2w)
	)
)

fn scanx p1 p2 y = (
	dx = p2[1] - p1[1]
	dy = p2[2] - p1[2]
	return int( dx * (y - p1[2]) / dy + p1[1] )
)

fn drawline x1 x2 y theBitmap mb2w bpos = (
	for x = x1 to x2 do (
		if BrushShape == 1 then (
			p = [x,y,1.0] * mb2w
			if distance p bpos <= BrushSize/2 then setPixels theBitmap  [x,y] #(black)
		)
		else if BrushShape == 2 then setPixels theBitmap  [x,y] #(black)
	)
)

fn paintFace vface theBitmap mb2w bpos = (
	maxy = miny = vface[1][2]
	top = bottom = 1
	for i = 2 to vface.count do
	(
		if vface[i][2] >= maxy then (
			maxy = vface[i][2]
			bottom = i
		)
		if vface[i][2] <= miny then (
			miny = vface[i][2]
			top = i
		)
	)
	side = #(#(),#())
		
-- side1 vertex list
	sptr = 1
	vptr = top
	nvptr =  int(mod (vptr) vface.count) + 1
	safty = 1
	while vface[vptr][2] == vface[nvptr][2] do
	(
		vptr = int(mod (vptr) vface.count) + 1
		nvptr =  int(mod (vptr) vface.count) + 1
		safty +=1
		if safty > vface.count then return()
	)
	append side[sptr] vface[vptr]
	safty = 1
	do (
		vptr = int(mod (vptr) vface.count) + 1
		append side[sptr] vface[vptr]
		safty +=1
		if safty > vface.count then return()
	)
	while vface[vptr][2] != maxy
	
-- side2 vertex list
	sptr = 2
	vptr = bottom
	nvptr =  int(mod (vptr) vface.count) + 1
	safty = 1
	while vface[vptr][2] == vface[nvptr][2] do
	(
		vptr = int(mod (vptr) vface.count) + 1
		nvptr =  int(mod (vptr) vface.count) + 1
		safty +=1
		if safty > vface.count then return()
	)
	
	append side[sptr] vface[vptr]
	safty = 1
	do (
		vptr = int(mod (vptr) vface.count) + 1
		append side[sptr] vface[vptr]
		safty +=1
		if safty > vface.count then return()
	)
	while vface[vptr][2] != miny
--line scan
	side2ptr = side[2].count
	ix1 = scanx side[1][1] side[1][2] miny
	ix2 = scanx side[2][side2ptr] side[2][side2ptr-1] miny
	drawline ix1 ix2  miny theBitmap mb2w bpos

	for i = 1 to ( side[1].count - 1) do
	(
		for iy = side[1][i][2]+1 to side[1][i+1][2] do
		(
			while iy > side[2][side2ptr - 1][2] do (
				side2ptr -= 1	
			)
			if side2ptr == 1 then side2ptr = 2
			ix1 = scanx side[1][i] side[1][i+1] iy
			ix2 = scanx side[2][side2ptr] side[2][side2ptr-1] iy
			drawline ix1 ix2 iy theBitmap mb2w bpos
		)
	)
)

fn delmodifier obj mtype = (
	local index = -1
	for i = 1 to obj.modifiers.count do (
	if (classof obj.modifiers[i]) == mtype then index = i
	)
	if index != -1 then (
		deletemodifier obj index
	)
)

(
	local theObj =  $GeoSphere001
	
	local bitmapX = bitmapY = 512
	local temp_bitmap_filename = (getDir #preview +"/microPaint_temp2.tga")
	local theBitmap = bitmap bitmapX bitmapY color:white filename:temp_bitmap_filename
    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
	
	local theMesh = snapshotAsMesh theObj
	local theChannel = 1
	local brushtransform = $Box001.transform

	--select theObj
	if theObj.modifiers[#Vol__Select] == undefined do (
		addModifier theObj (volumeselect())
	)
	if (classof theObj) != Editable_mesh do (
		addModifier theObj  (Turn_to_Mesh ())
	)

	maps = #()
	mt2b=(matrix3 [bitmapX,0,0] [0,-bitmapY,0] [0,bitmapY,1] [0,0,0])
	crossFaceMaps &maps theObj theMesh seedface theChannel brushtransform mt2b
	for map in maps do (
		vface = #()
		for p in map[1] do (
			append vface [int(p.x * bitmapx), int(bitmapy - p.y * bitmapy)]
		)
		paintFace vface theBitmap map[2] $Box001.pos
	)
	save theBitmap
    theObj.material.diffusemap.bitmap = theBitmap
	delmodifier theObj Vol__Select
	delmodifier theObj Turn_to_Mesh
)

これが実行したもの。ブラシに頂点がかかっていないけどちゃんとペイント出来ている。

fig01

今回のプログラムはメッシュの面の探索開始位置無しで動いているのでブラシの位置はどこにとっても接触した位置の面をペイントできる。出来ない場合は「maxFaceRadius」を広げて対応する事になる。

続きはまた次回。

maxまとめページ



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

2017年07月24日

modo11.0v3のスクリプトについて調べてみた その106

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

「MeshGeometry」はメッシュアイテム内の頂点やポリゴンに直接アクセス出来る方法を提供する。それが「vertices」や「polygons」と言ったプロパティだ。「__new__()」メソッド内でこれらは「MeshProvider」から得られる。

class MeshGeometry(object):
    def __new__(cls, item=None, mode='write'):

            ;
        
        # This initializes our mesh and access-attributes
        self.provider = MeshProvider(self._item_, readOnly = (mode=='read'))
        mesh = self.provider.mesh
    
        self.vertices = MeshVertices(mesh, self, "Point")
        self.edges    = MeshEdges(mesh, self)
        self.polygons = MeshPolygons(mesh, self, "Polygon")
        self.vmaps    = MeshMaps(mesh, self, "MeshMap")
                
        return self

「MeshProvider」の初期化メソッドは以下のようになっていて、「item」「layerEdit」「readOnly」が初期化されて、さらに「useMeshChannel()」が呼び出されている。

class MeshProvider(object):    
'''
Given an item, returns access to a mesh object.
For read-only access (the initial default state), the mesh object obtained from the item's mesh-channel is used.
Edits to the geometry can be made after calling beginLayerScan()
It is then required to call applyLayerScan() in order to finish the edits and release the LayerScan.
Once the LayerScan is released, MeshProvider goes back to read-only state.

'''

def __init__(self, item, readOnly=False):
self.item = item
self.layerEdit = None
self.readOnly = readOnly

self.useMeshChannel()

これがそのメソッドで、冒頭でまず「meshFromMeshChannel()」が呼び出されて、そこで得られた「_mesh」から各エレメントのクラスのオブジェクトを得ている。

 def useMeshChannel(self, mode='read', time=None):
self._mesh = MeshProvider.meshFromMeshChannel(self.item, mode, time)
self.PolygonAccessor = self._mesh.PolygonAccessor()
self.PointAccessor = self._mesh.PointAccessor()
self.EdgeAccessor = self._mesh.EdgeAccessor()
self.MeshMapAccessor = self._mesh.MeshMapAccessor()

これが「meshFromMeshChannel()」のコードで、「CLxUser_Mesh」をラップした「Mesh」クラスのインスタンスが「item」の「CHAN_MESH_MESH」チャンネルから取得される。この辺のコードはヘルプの「SDK開発者向けWiki」の「SDK」の「」FAQの「Q: How do I get a CLxUser_Mesh from a mesh item?」のところにちょっとだけ解説がある。

 @staticmethod
 def meshFromMeshChannel(item, value='write', time=None):
                
     if not value in ['write', 'read', 'deformed']:
         raise ValueError('Mode must be "write", "read" or "deformed"')
    
     time = lx.service.Selection().GetTime() if not time else time
     scene = item.Context()
     mesh = None
    
     if value is "write":
         # Get writeable mesh from channel
         chanWrite = lx.object.ChannelWrite(scene.Channels(lx.symbol.s_ACTIONLAYER_SETUP, time))
         write_mesh_obj = chanWrite.ValueObj(item, item.ChannelLookup(lx.symbol.sICHAN_MESH_MESH))
         mesh = lx.object.Mesh(write_mesh_obj)
    
     elif value is "read":
         # Get writeable mesh from channel
         chanRead = scene.Channels(lx.symbol.s_ACTIONLAYER_ANIM, time)
         read_mesh_obj = chanRead.ValueObj(item, item.ChannelLookup(lx.symbol.sICHAN_MESH_MESH))
         mesh = lx.object.Mesh(read_mesh_obj)
    
     # Fetch the mesh channel and filter to update for the case that deformers were added or removed
     elif value is "deformed":
         chanRead_deformed = scene.Channels(None, time)
         channelmesh = item.ChannelLookup(lx.symbol.sICHAN_MESH_MESH)
         read_deformed_mesh_obj = chanRead_deformed.ValueObj(item, channelmesh)
         mesh_filter = lxu.object.MeshFilter(read_deformed_mesh_obj)
         mesh = mesh_filter.Generate()
    
     if not mesh.test():
         raise LookupError("Invalid mesh")
        
     return mesh

「MeshProvider」オブジェクトが取得できたらここから「mesh」プロパティを取り出す。これは「self._mesh 」が渡される。そしてそれを「MeshVertices」「MeshEdges」「MeshPolygons」「MeshMaps」に渡して各エレメントにアクセスするためのプロパティを設定する。実はプログラムを辿ってみるとこの渡している「mesh」は呼び出される「__init__()」で使われていないんだよね。謎だ。

class MeshGeometry(object):
    def __new__(cls, item=None, mode='write'):

            ;
        
        # This initializes our mesh and access-attributes
        self.provider = MeshProvider(self._item_, readOnly = (mode=='read'))
        mesh = self.provider.mesh
    
        self.vertices = MeshVertices(mesh, self, "Point")
        self.edges    = MeshEdges(mesh, self)
        self.polygons = MeshPolygons(mesh, self, "Polygon")
        self.vmaps    = MeshMaps(mesh, self, "MeshMap")
                
        return self

「MeshVertices」「MeshPolygons」「MeshMaps」は「__new__()」も「__init__()」も無く、「MeshEdges」も含めて全て「MeshComponentContainer」を継承したクラスになっている。だから「__init__()」は「MeshComponentContainer」のものが呼び出される。「MeshEdges」はなぜか「__init__()」があるけど、パラメータが置き換えられて、スーパークラスである「MeshComponentContainer」の「__init()」が呼び出されて、結局やっている事は他の3つのクラスと一緒で、こんな事しないで「__init__()」を無くして「MeshEdges(mesh, self, "Edge")」で呼び出しても良さそうな感じだ。

class MeshVertices(MeshComponentContainer):

class MeshEdges(MeshComponentContainer):
def __init__(self, mesh, parent):
super(MeshEdges, self).__init__(mesh, parent, "Edge")
class MeshPolygons(MeshComponentContainer):
class MeshMaps(MeshComponentContainer):

そしてこれが「MeshComponentContainer」の「__init()」だ。見ての通り引数で渡された「mesh」は使われていない。「geometry」には「MeshGeometry」のインスタンスが渡り、「function_keyword」には「Point」「Edge」「Polygon」「MeshMap」の文字列が入る。

class MeshComponentContainer(object):
"""
Base class for vertex, edge- and polygon containers to share common behaviour
"""
def __init__(self, mesh, geometry, function_keyword="Point"):
self.__accessorKeyword = function_keyword
self._geometry = geometry
self.provider = geometry.provider
self.getAccessor = lambda : getattr(self.provider, self.__accessorKeyword + "Accessor")

これが最後の行で、「getattr()」で「self.provider」の「function_keyword」に入っている文字列の名前のプロパティにアクセスするコードが組み立てられる。「function_keyword」が「Point」「Edge」「Polygon」「MeshMap」それぞれで以下のように置き換わる。

self.provider.PointAccessor
self.provider.EdgeAccessor
self.provider.PolygonAccessor
self.provider.MeshMapAccessor

ここでアクセスされるプロパティは「MeshProvider」クラスの「__init__()」の「useMeshChannel()」で生成したものだ。

それを「lambda:」でファンクション化して「self.getAccessor」に格納している。「MeshVertices」のインスタンスから「PointAccessor」を取得するには

 pointaccessor = meshvertices.getAccessor()

とやってやればいい事になる。

続きはまた次回。

modo10-11ブログ目次



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