Blender pythonで座標変換の練習。
ちょっと前から少しずつ書いていた座標変換練習です。
今のところ、すごく単純な変換しかできませんが。。。(笑
現在の機能としては、
・選択されているオブジェクトがメッシュオブジェクトだった場合には、
そのオブジェクトの頂点情報をファイルに出力する。・開始と終了時刻を指定して、その時刻の間の頂点の状態を出力する。
(オブジェクトがIpoを持っていても、そうでなくても)・頂点情報はワールド座標で出力する。
ローカル→ワールド座標への変換は、「こ、これでいいのかな。。」っていう感じですが、
変換された位置と、Blender上で確認したメッシュの頂点位置が一致しているので、
たぶん間違ってはいないと思います。
(無駄な計算をしている可能性はありますけどね)
出力例(時刻 1.0〜11.0まで10.0間隔で出力)
======================================== name: Cube ======================================== <<Mesh Vert>> time: 1.0 <<Center>> LOC (X: 0.071756, Y: 0.000000, Z: 3.372549) ROT (X: 0.000000, Y: 0.000000, Z: 0.000000) SCALE (X: 1.000000, Y: 1.000000, Z: 1.000000) <<Vert>> (X: 1.000000, Y: 1.000000, Z: -1.000000) (X: 1.000000, Y: -1.000000, Z: -1.000000) (X: -1.000000, Y: -1.000000, Z: -1.000000) (X: -1.000000, Y: 1.000000, Z: -1.000000) (X: 1.000000, Y: 0.999999, Z: 1.000000) (X: 0.999999, Y: -1.000001, Z: 1.000000) (X: -1.000000, Y: -1.000000, Z: 1.000000) (X: -1.000000, Y: 1.000000, Z: 1.000000) time: 11.0 <<Center>> LOC (X: 0.260066, Y: 0.390590, Z: 3.941661) ROT (X: 0.000000, Y: 0.000000, Z: 0.000000) SCALE (X: 1.000000, Y: 1.000000, Z: 1.000000) <<Vert>> (X: 1.188309, Y: 1.390590, Z: -0.430888) (X: 1.188309, Y: -0.609410, Z: -0.430888) (X: -0.811691, Y: -0.609410, Z: -0.430888) (X: -0.811690, Y: 1.390590, Z: -0.430888) (X: 1.188310, Y: 1.390589, Z: 1.569112) (X: 1.188308, Y: -0.609411, Z: 1.569112) (X: -0.811691, Y: -0.609410, Z: 1.569112) (X: -0.811691, Y: 1.390590, Z: 1.569112)
少し長いですが、スクリプトを載せておきます。
(内緒ですが、たぶんGUIの部分でメモリリークしてます。。えへへ。。今度こっそり直しときます)
# !BPY # -*- coding: utf-8 -*- ''' Name: Blender:248 Group: "Mesh" Tooltip: "Export the mesh object information." ''' __author__ = 'Jkani4' __url__ = '' __version__ = '0.1' __bpydoc__ = """ This script exports the geometry information of the mesh objects selected. """ import os from Blender import Window, Draw, Object, Mesh, Ipo from Blender.BGL import * from Blender.Mathutils import RotationMatrix, ScaleMatrix, TranslationMatrix, Vector class GeoInfo(object): ''' ジオメトリ情報を保持させるためのオブジェクト。 時刻ごとに様々な情報を保持する。 ''' def __init__(self, name): ''' 初期化。 name: 情報名(メッシュオブジェクト名など) ''' self.name = name self.vertDict = {} def set_verts(self, time, centerLoc, centerRot, centerScale, verts): ''' 頂点情報を設定する。 time: centerLoc: タプル。 オブジェクト中心の座標(x, y, z) centerRot: タプル。 オブジェクト中心の回転(x, y, z) centerScale: タプル。 オブジェクト中心のスケール(x, y, z) verts: オブジェクト頂点座標(x, y, z)のリストまたはタプル。 ''' self.vertDict[time] = (centerLoc, centerRot, centerScale, verts) def get_text(self): ''' 保持しているすべての情報をテキストに変換して返す ARGS: なし。 RETURN:テキスト ''' text = '========================================\n' text += 'name: %s\n' % self.name text += '========================================\n' text += '<<Mesh Vert>>\n' for time, (cl, cr, cs, vertList) in sorted(self.vertDict.items()): text += ' time: %s\n' % time text += ' <<Center>>\n' text += ' LOC (X: %f,\tY: %f,\tZ: %f)\n' % cl text += ' ROT (X: %f,\tY: %f,\tZ: %f)\n' % cr text += ' SCALE (X: %f,\tY: %f,\tZ: %f)\n' % cs text += ' <<Vert>>\n' for x, y, z in vertList: text += ' (X: %f,\tY: %f,\tZ: %f)\n' % (x, y, z) return text def get_ipo_value(ipo, ipoTypes, time): ''' Ipoオブジェクトから任意の時刻における情報を抽出する。 ipo: 情報をとりだす対象のIpoオブジェクト。 ipoTypes: 取り出したい情報のタイプ。(タプルまたはリストで指定) 指定可能なタイプはBlenderリファレンスIpoオブジェクトを参照のこと。 time: 時刻(少数値) RETURN: 値のタプル。 ipiTypesで指定した値が同じ順序で格納されている。 (使用例) Ipoオブジェクトから5フレーム時のX軸、Y軸の位置情報を取り出したい場合、 result = get_ipo_value(ipo, (Ipo.OB_LOCX, Ipo.OB_LOCY), 5.0) resultは(X軸の座標値, Y軸の座標値)のタプルとなる。 ''' if not ipo: return None result = [] for tp in ipoTypes: ipoCurve = ipo[tp] if not ipoCurve: result.append(None) else: result.append(ipoCurve[time]) return tuple(result) def create_world_co_matrix(locX, locY, locZ, rotX, rotY, rotZ, scaleX, scaleY, scaleZ): ''' local -> world座標変換マトリクスを生成。 ''' # Scale scaleMatrix = ScaleMatrix(scaleX, 4) scaleMatrix *= ScaleMatrix(scaleY, 4) scaleMatrix *= ScaleMatrix(scaleZ, 4) # Rot rotMatrix = RotationMatrix(rotX, 4, 'x') rotMatrix *= RotationMatrix(rotY, 4, 'y') rotMatrix *= RotationMatrix(rotZ, 4, 'z') # Loc transMatrix = TranslationMatrix(Vector(locX, locY, locZ)) return scaleMatrix * rotMatrix * transMatrix def mult_matrix_to_verts(verts, matrix): ''' 頂点座標にマトリクスを掛け合わせる。 verts: 変換対象の座標(x, y, z)のリストまたはタプル matrix: 座標変換マトリクス RETURN: 変換戯れた座標(x, y, z)のリスト ''' result = [] for v in verts: x, y, z = v.co * matrix result.append((x, y, z)) return result def get_obj_vert_position(obj, time): ''' ある時刻におけるオブジェクトの位置情報を取得する。 obj: 計測対象のオブジェクト。 time: フレーム(少数値) RETURN: タプル。(center, vert-positions) center: 入れ子のタプル。 ((loc.x, loc.y, loc.z), (rot.x, rot.y, rot.z), (scale.x, scale.y, scale.z)) vert-positions: 入れ子のタプル ((x, y, z), (x, y, z), ....) ''' oMesh = Mesh.Get(obj.getName()) oMatrix = obj.getMatrix() ipo = obj.getIpo() # Ipoを持っているオブジェクトの場合にはIpoカーブの情報をもとに # 指定された時刻範囲の頂点位置をワールド座標に変換する。 # # Ipoを持っていないオブジェクトの場合にはオブジェクトの保持している # マトリクスを使用して頂点をワールド座標に変換する。 if ipo: loc = get_ipo_value(ipo, (Ipo.OB_LOCX, Ipo.OB_LOCY, Ipo.OB_LOCZ), time) rot = get_ipo_value(ipo, (Ipo.OB_ROTX, Ipo.OB_ROTY, Ipo.OB_ROTZ), time) scale = get_ipo_value(ipo, (Ipo.OB_SCALEX, Ipo.OB_SCALEY, Ipo.OB_SCALEZ), time) center = (loc, rot, scale) # Ipoの値(object centerのスケール、回転、移動)をもとに # 頂点座標変換(local -> world)マトリクスを生成 matrix = create_world_co_matrix(loc[0], loc[1], loc[2], rot[0], rot[1], rot[2], scale[0], scale[1], scale[2]) vertTup = mult_matrix_to_verts(oMesh.verts, matrix) else: e = obj.getEuler('worldspace') center = (obj.getLocation('worldspace'), (e.x, e.y, e.z), obj.getSize('worldspace')) vertTup = mult_matrix_to_verts(oMesh.verts, oMatrix) return center, vertTup def get_mesh_geo_info(start, end, step): ''' シーン上で現在洗濯中のオブジェクトのうち、 メッシュオブジェクトに関して座標情報を取得する。 この関数で取得される座標情報はワールド座標となっているため、 必要に応じて座標変換すること。 start: 計測開始フレーム番号。(小数値) end: 計測終了フレーム番号。(小数値) step: 開始〜終了区間の計測間隔(小数値) RETURN: GeoInfoオブジェクトのリスト。 (使用例) 1〜10フレームまでの区間を1フレーム刻みで情報を 取り出したい場合には、以下のように記述する。 result = get_mesh_geo_info(1.0, 10.0, 1.0) ''' result = [] for obj in Object.GetSelected(): if obj.getType() != 'Mesh': continue geoInfo = GeoInfo(obj.getName()) time = start while time <= end: center, vertPosList = get_obj_vert_position(obj, time) geoInfo.set_verts(time, center[0], center[1], center[2], vertPosList) time += step result.append(geoInfo) return result # ========================================== # User Interface # ========================================== EVENT_NUMBER_START = 100 EVENT_NUMBER_END = 101 EVENT_NUMBER_STEP = 102 EVENT_BUTTON_EXPORT = 103 EVENT_BUTTON_FILE_SEL = 104 EVENT_BUTTON_FILE_NAME = 105 g_startNum = Draw.Create(1.0) g_endNum = Draw.Create(250.0) g_stepNum = Draw.Create(1.0) g_exportBt = None g_fileBt = None g_filenameBt = Draw.Create('~/') def cb_draw(): ''' 描画が発生した際に呼び出されるコールバック。 ''' # 開始フレーム番号入力用 global g_startNum g_startNum = Draw.Number('start:', EVENT_NUMBER_START, 10, 80, 90, 20, g_startNum.val, 1.0, 250.0, 'start frame number') # 終了フレーム番号入力用 global g_endNum g_endNum = Draw.Number('end:', EVENT_NUMBER_END, 105, 80, 90, 20, g_endNum.val, 0.0, 250.0, 'start frame number') # フレームのステップ数入力用 global g_stepNum g_stepNum = Draw.Number('step:', EVENT_NUMBER_STEP, 200, 80, 90, 20, g_stepNum.val, 1.0, 100.0, 'frame steps') # ファイル選択用 global g_fileBt g_fileBt = Draw.Button('SEL', EVENT_BUTTON_FILE_SEL, 260, 40, 30, 20, 'select file to export' ) # ファイル名入力用 global g_filenameBt g_filenameBt = Draw.String('', EVENT_BUTTON_FILE_NAME, 10, 40, 250, 20, g_filenameBt.val, 256, 'file name to export') # 処理開始ボタン global g_exportBt g_exportBt = Draw.Button('export', EVENT_BUTTON_EXPORT, 10, 10, 280, 20, 'export infomation to file' ) def cb_event(event, val): ''' マウス操作、キーボード操作が行われた際に呼び出されるコールバック。 event: イベント番号(数値定数) BlenderのDrawクラスのリファレンスを参照。 val: ボタン/キーの状態。 0: ボタンまたはキーがはなされた。 1: ボタンまたはキーが押された。 ''' if not val: if event in [Draw.QKEY, Draw.ESCKEY]: Draw.Exit() def cb_button_event(event): ''' ボタン操作が行われた場合に呼び出されるコールバック。 event: イベント番号(数値定数) BlenderのDrawクラスのリファレンスを参照。 ''' if event == EVENT_BUTTON_EXPORT: ### 処理開始 export_info_to_file(g_filenameBt.val) elif event == EVENT_BUTTON_FILE_SEL: ### ファイル選択 default = os.path.normpath(os.path.expanduser(g_filenameBt.val)) Window.FileSelector(cb_file_choose, 'Choose export file', default) def cb_file_choose(filename): ''' ファイル選択ダイアログから呼び出されるコールバック。 filename: 選択されたファイル名(パス文字列) ''' g_filenameBt.val = filename Draw.Redraw() def export_info_to_file(path): ''' 情報をファイルへ出力。 path: 出力ファイルのパス。 ''' expName = os.path.normpath(os.path.expanduser(path)) if not expName: print '[ERR]: Output file is not selected.' return if os.path.isdir(expName): print '[ERR]: Output file is not selected.' return infoList = get_mesh_geo_info(g_startNum.val, g_endNum.val, g_stepNum.val) try: f = open(expName, 'w') f.write('\n'.join([info.get_text() for info in infoList])) f.close() except IOError, err: print '[ERR]:', err return print '######## EXPORTED #######' # # Register callback Draw.Register(cb_draw, cb_event, cb_button_event)