Shade3D 公式

重心の測定についての質問

Shade 3D Basic ver.14 を使用している初級者です。
形状の質量中心となる重心を測定したいのですが、そのようなスクリプトはありますでしょうか?
または何か解決法をご存知の方がいらっしゃいましたら、アドバイスをお願いします。

Funkateer さん、こんにちは

詳細な説明は省かせて戴きますが、最も単純な方法で重心を求める script です。

条件:

  • 球, 自由曲面, polygon mesh のいずれか一つを選択して実行
  • polygon mesh の場合、全ての面の面法線が揃っていること
  • 自由曲面, polygon mesh は閉じていること
  • 開いた 自由曲面, polygon mesh に対しても計算されてしまうが、得られる値は意味をなさない

出力:

  • 重心位置に小さな球を出力
  • 体積、重心位置を report

少し手を加えれば、複数の object 全体に対する 重心位置 も求められます。
必要なら、おしらせ下さい。

def main() :

	#  選択 object check
	obL = scene.active_shapes
	if len(obL) > 1 :
		xshade.beep()
		print '球,  polygon mesh, 自由曲面 のいずれか一つを選択して下さい。'
		return
		
	ob = scene.active_shape()
	type = ob.type
	
	if type == 5 :									#  球
		pass
	elif type == 7 :									#  polygon mesh
		pass
	elif (type == 2) and (ob.part_type == 1) :		#  自由曲面
		pass
	else :
		xshade.beep()
		print '球,  polygon mesh, 自由曲面 のいずれかを選択して下さい。'
		return
		
		
	#  選択 object の copy を作り polygon mesh に変換して dummy part 内に格納
	scene.create_part()
	ob2 = scene.active_shape()						#  dummy part
	ob2.cancel_transformation()						#  上位 matrix を相殺
	
	ob.copy_object(None, None, None, None)
	ob3 = ob.bro									#  copy object
	ob3.activate()

	ob3.convert_to_polygon_mesh_with_subdivision_level(4)
	ob3 = scene.active_shape()									#  dummy object = converted object
	ob3.place_child(1)
	
	
	#  dummy object の全ての面を 3 角面に分割
	nof = ob3.number_of_faces
	L = [ i for i in range(nof)]
	ob3.triangulate_faces(L)

	

	#  dummy pobject の 3 角面の各頂点座標 + 原点 の 4点で構成される 4面体の 符合付き体積を求めながら、重心を求める
	hc = [0, 0, 0]								#  重心位置
	volume = 0									#  体積
	
	nof = ob3.number_of_faces
	L = [ i for i in range(nof)]
	for f in L :
		fL = ob3.face(f).vertex_indices			#  面 f の 面 - 頂点 llist
		p1 = ob3.vertex(fL[0]).position
		p2 = ob3.vertex(fL[1]).position
		p3 = ob3.vertex(fL[2]).position
		
		vol = (p1[0]*p2[1]*p3[2] + p1[2]*p2[0]*p3[1] + p1[1]*p2[2]*p3[0] - p1[2]*p2[1]*p3[0] - p1[0]*p2[2]*p3[1] - p1[1]*p2[0]*p3[2])/6
		volume += vol
		for i in range(3) :
			hc[i] += (p1[i] + p2[i] + p3[i])*vol/4
		
	for i in range(3) :
		hc[i] /= volume
			

	
	#  重心位置に球を作成	
	bb = ob.bounding_box_size
	r = (bb[0] + bb[1] + bb[2])/50
	scene.create_sphere('重心位置', hc, r)
	scene.copy()
	ob.activate()
	scene.paste()
	ob4 = scene.active_shape()				#  重心位置を示す球
	
	
	#  後処理
	ob2.remove()							#  dummy part, dummy object, 重心球 削除
	scene.active_shapes = [ob, ob4]			#  初期選択 object と 重心球 を選択
	
	
	#  重心位置 report
	coeff = scene.native_to_user_unit
	for i in range(3) :
		hc[i] *= coeff
	volume *= coeff**3
	
	print '体積 : ' + str(volume)
	print '重心位置 : ' + str(hc)

	

	
	
#  Shade  上で 体積を持つ object を一つ選択して実行
#
#	球, 自由曲面, polygon mesh のいずれか一つを選択して実行
#	polygon mesh の場合、全ての面の面法線が揃っていること
#	自由曲面, polygon mesh は閉じていること
#	開いた 自由曲面, polygon mesh に対しても計算されてしまうが、得られる値は意味をなさない

scene = xshade.scene()

main()

Toshiaki Katoh さん、こんにちは

丁寧なご回答、誠にありがとうございます。

複数の object 全体に対する重心位置の求め方ですが、複数の object を入れた パート を polygon mesh に変換して求めてみました。

もしこのやり方で不完全であれば、より良い方法を教えていただきたいです。

それと、単体の球体や立方体などでも試してみたところ、体積、重心位置ともにごくわずかな誤差が生じました。

これらは避けられないものなのでしょうか?
許容範囲内なので問題はないのですが、気になったもので。

複数の object 全体に対する重心位置の求め方ですが、複数の object を入れた パート を polygon mesh に変換して求めてみました。

はい、それで OK です。

ただし、以下の条件に気をつけて下さい。

  • 全ての object が閉じていること
  • 全ての object の面法線が揃っていること
  • object どうしが overlap していないこと

体積、重心位置ともにごくわずかな誤差が生じました。
これらは避けられないものなのでしょうか?

はい、所詮は多角面に近似して求めているので、誤差は避けられませんし、python で出力される数値が計算上の有効桁数をはるかに超えて表示されてしまう ということもあります。

了解です。
大変助かりました。
深く感謝いたします。
どうもありがとうございました。

おっと、大事なことを見逃していました。

複数の object 全体に対する重心位置の求め方ですが、複数の object を入れた パート を polygon mesh に変換して求めてみました

「 OK です 」と申し上げましたが、flip face がかかっている object が含まれている場合、その方法では正しい値が得られなくなってしまいます。

flip face についての簡単な説明と、解決方法については後ほど改めて、

flip face について


次のような操作を行ってみます。

  • polygon 球を作成
  • メニュー > ツール > 複製 > 数値入力 を選択
  • X軸方向の拡大縮小を -1 にして複製


	左:オリジナル		右:複製

どちらの polygon mesh も面法線は外側を向いています.

しかし、それぞれについて先の script で重心を求めると、どちらも正しい重心位置を示しますが、複製側の体積は 負の値 が示されます。

右側では [ X軸方向に -1 倍の拡大縮小 ] = [ X軸方向に反転 ] によって複製されているため、幾何的に定義される面法線は本来内向きになるのですが、 面法線方向の定義を逆転 することで外側に向かせる仕様となっています。

先の script はあくまでも 幾何的に定義される面法線の向きをベースにして計算されるので、体積が負の値となってしまいます。

この「 面法線方向の逆転 」が flip face です。

object や part に対する flip face の read / write は、Shade のユーザーインターフェース上からはできず、script 等を使用するしかありません。( 以前は 情報パレット に check box がありましたが、いつのまにか廃止に )

さらに part に適用された flip face は内包する object に対して累積的に作用するので、さらにややこしくなっています。

このややこしさが 見ただけでは解らない のがちょっと曲者です。

多くの 作成 / 編集 作業では flip face を気にすることはほとんどありませんが、いくつか問題となるケースもあります。

例 )

  • flip face 環境の異なる part 内への object の移動 や copy / paste
  • 複数の polygon mesh を一つの polygon mesh に統合
  • 自由曲面の polygon mesh へ変換
  • link object の実体化

下にいくつかの sample file を upload しました。

  • 1 polygon mesh.shd
  • 2 自由曲面.shd
  • 3 球.shd
  • 4 掃引体.shd
  • 5 link.shd

down load

sample file では flip face のありなしで ブラウザ上での色表示を変えています。

							黄緑:flip face なし
							赤  :flip face あり

     250750542

sample file の 1 polygon mesh.shd を開き、part を選択して一つの polygon mesh に変換すると、いくつかの object では面法線が逆向きになってしまいます。

メニュー > ツール > 変換 > ポリゴンメッシュに変換

     779676437



そこで重心位置を求めるための前処理ツールとして、選択 part 内の object を一つの polygon mesh に総合する script を作ってみました。

polygon mesh / 自由曲面 / link object に関する sample file でこの script を実行すると、Shade メニューからのマニュアル操作との違いがわかります。

参考までに。

機能:

  • 選択 part 内の object を一つの polygon mesh に総合

使い方:

  • part を一つ選択して実行

仕様:

  • part 内に part が何層にも入れ子状になっていても可
  • polygon mesh に変換できない object が含まれていても可 ( 無視される )
  • link object は 実体化 してから polygon mesh に変換
  • 変換可能な object: 自由曲面, polygon mesh, 線形状 , 掃引体, 回転体 , NURBS 等
  • 変換不可な object: camera , 光源 等( link object は実体化してから変換 )



< script >

#  機能:	選択 part 内の object を一つの polygon mesh に総合
#  
#  使い方:	part を一つ選択して実行
#  
#  仕様:	part 内に part が何層にも入れ子状になっていても可
#  			polygon mesh に変換できない object が含まれていても可 ( 無視される )
#  			link object は 実体化 してから polygon mesh に変換
			
			
			
			
#  ob1 を polygon mesh に変換
#
#	ob1 :			part object
#	initial_flip :	bool		ob1 が 上位 part から受ける 累積 flip face
#
def convert(ob1, initial_flip) :

	#  initial_flip の更新 ( ob1 の flip face 設定を加味 )
	if ob1.flip_face :
		initial_flip = not initial_flip

	
	#  ob1 内の各 object を polygon mesh へ変換
	remove_obL = []								#  削除 object list
												#  polygon mesh への変換対象ではない object を格納
	ob = ob1.son
	while ob.has_bro :
		ob = ob.bro								#  ob1 内の child object
		ob.activate()
		
		#  type 分類
		if (ob.type == 2) and (ob.part_type == 0) :		#  simple part
			type = 'simple part'		
		elif (ob.type == 2) and (ob.part_type == 1) :	#  自由曲面
			type = 'surface part'	
		elif ob.type == 7 :								#  polygon mesh
			type = 'polygon mesh'	
		else :											#  その他 ( polygon mesh への変換不可なるものも含む )
			type = 'others'								#		変換可 :	線形状, 掃引体, 回転体, NURBS 等
														#		変換不可 :	camera, 光源 等
														#					link object は、ここに入る前に polygon meh へ変換済み
				
		#  polygon mesh への変換
		
		#  simple part
		if type == 'simple part' :								
			ob = convert(ob, initial_flip)				#  再帰
			
			
		#  others
		elif type == 'others' :								
			ob.convert_to_polygon_mesh_with_subdivision_level(4)	#  ob が polygon mesh への変換対象 でない場合、この操作は無視され、そのまま残る
			ob = scene.active_shape()									
			if ob.type != 7 :										#  polygon mesh への変換対象 でなかった
				remove_obL.append(ob)								#  削除 list に登録 ( 変換処理後にまとめて削除 )
			
			
		#  polygon mesh / 自由曲面
		else :	
			#  total_flip :	自身を含む上位 part からの 累積 flip face								
			total_flip = initial_flip
			if ob.flip_face :
				total_flip = not total_flip
				
			#   total_flip 設定がある場合
			if total_flip :	
				if type == 'polygon mesh' :		#  polygon mesh
					nof = ob.number_of_faces
					for f in range(nof) :
						ob.face(f).flip()				#  polygon mesh の各面の面法線を反転
					
				elif type == 'surface part' :	#  自由曲面
					ob_ = ob.son
					while ob_.has_bro :
						ob_ = ob_.bro
						ob_.reverse()					#  自由曲面内の線形状を逆転
						
			#  分割 level を最高にして polygon mesh に変換
			ob.convert_to_polygon_mesh_with_subdivision_level(4)	
			ob = scene.active_shape()

					
					
	#  polygon mesh への変換非対象 object を削除
	if len(remove_obL) > 0 :
		for ob in remove_obL :
			ob.remove()
			
			
	#  ob1 part 内に含まれる全ての polygon mesh を統合 
	ob1.activate()
	ob1.convert_to_polygon_mesh_with_subdivision_level(0)			
	return scene.active_shape()
	

	

def main() :

	#  選択 object check
	obL = scene.active_shapes
	if len(obL) > 1 :
		xshade.beep()
		print '一つの part を選択して下さい。'
		return
		
	ob1 = scene.active_shape()						#  選択 part
	type = ob1.type
	
	if not ((type == 2) and (ob1.part_type == 0)) :	#  選択 object が simple part でない
		xshade.beep()
		print 'part を選択して下さい。'
		return
		
	elif ob1.number_of_sons == 0 :					#  空 part
		xshade.beep()
		print '空 part です。'
		return
	
	
	#  if scene.is_modify_mode :
		scene.exit_modify_mode()					#  一応 念のため
		

	#  選択 object の copy を作成
	scene.copy_object(None, None, None, None)
	ob2 = scene.active_shape()						#  copy object
		
		
	#  part ob2 内の link object を実体化
	obL = []
	ob = ob2.son
	while ob.has_bro :
		ob = ob.bro
		obL.append(ob)
		
	for ob in obL :
		if (ob.type == 2) and (ob.part_type == 0) :		#  simple part
			ob_ = ob.son
			while ob_.has_bro :
				ob_ = ob_.bro
				obL.append(ob_)
				
		elif (ob.type == 2) and (ob.part_type == 101) : #  link object
			obM = ob.link_master						#  link 元 object
			if obM.flip_face :							#  link 元 object に flip face が設定されているなら
				ob.flip_face = not ob.flip_face			#  自身の flip face 設定を反転
				
			ob.activate()
			ob.realize_link()							#  link を実体化
			ob = scene.active_shape()
			
		else :
			pass										#  link object 以外は何もしない
	
	
	# initial_flip :	上位 part から掛けられる flip face 設定
	initial_flip = False
	ob = ob2
	while ob.has_dad :
		ob = ob.dad
		if ob.flip_face :
			initial_flip = not initial_flip				#  上位 part による ob2 part に対する累積 flip face 設定
			
	
	#  ob2 を一つの polygon mesh へ変換		
	ob3 = convert(ob2, initial_flip)					#  一つの polygon mesh へ変換
	ob3.activate()
	
	if ob3.number_of_faces == 0 :						#  選択 part 内には polygon mesh への変換対象 object がなかった
		ob3.remove()
		ob1.activate()
		xshade.beep()
		print '選択 part 内には polygon mesh への変換対象 object はありませんでした。'
												

	

	
#  part 内に格納した object 群を一つの polygon mesh に変換
#
# 	変換する object を格納した part を選択して実行
#
scene = xshade.scene()

main()

flip face に関して - Shade 20.1 での状況


現在別の問題で、はっきりとは断言できないのですが、Shade 20.1 では、どうやら flip face に関連して問題となるケースとして述べたもののうち、polygon mesh と 自由曲面 に関する変換については bug fix として改善されているようです。

でも link object の実体化についてはそのままです。

正確に確認できましたなら、改めて、

さらに詳しいご説明、どうもありがとうございました。