特にアドオン化したときは、コンソールにメッセージが出るのはユーザーフレンドリーではない。
以下のようにメッセージボックス表示関数を作れる。
見るからにポップアップウィンドウ表示用関数を使った方法なので、応用が利きそうではある。
import bpy
# @brief GUIでメッセージを表示する # @param [in] message メッセージ本文 # @param [in] tilte メッセージボックスのタイトル # @param [in] icon 使用するアイコン def ShowMessageBox(message = "", title = "Message Box", icon = 'INFO'): def draw(self, context): self.layout.label(text=message) bpy.context.window_manager.popup_menu(draw, title = title, icon = icon)
################################ # 使用例 obj = bpy.context.active_object if not obj.type == 'MESH': # メッシュが選択されていなかったらエラーを表示 ShowMessageBox("object is not a mesh")
実行するとマウスの位置にメッセージボックスが表示される。
アイコンは、以下に使用できる一覧があるが、メッセージボックスとして使うのはせいぜい'NONE', 'QUESTION', 'ERROR', 'CANCEL','INFO'くらいだと思う。
基本的に以下そのまま。
メッシュの面はmesh.polygonsに入っている。マテリアルは各面のmaterial_indexにindexの形で指定できるが、マテリアルのインデクスの取得はbpy.data.materials.findで行う。
import bpy # カラーテーブルを作っておく ctable = [] ctable.append( (0,0,1,1) ) ctable.append( (0,1,0,1) ) ctable.append( (0,1,1,1) ) ctable.append( (1,0,0,1) ) ctable.append( (1,0,1,1) ) ctable.append( (1,1,0,1) ) obj = bpy.context.active_object mesh = obj.data i = 0 for face in mesh.polygons: # 全ての面について処理する # マテリアルの存在確認 mat_name = "mat" + str(i) material = bpy.data.materials.get(mat_name) # マテリアルがなければ作成する if material is None: material = bpy.data.materials.new(mat_name) # マテリアルをオブジェクトに設定する obj.data.materials.append(material) material.use_nodes = True # 色を設定 pBSDF = material.node_tree.nodes["Principled BSDF"] pBSDF.inputs[0].default_value = ctable[i] # マテリアルのIndexを取得する matindex = bpy.data.materials.find(mat_name) # 面に使用するマテリアルを設定する face.material_index = matindex i += 1
参考:
https://docs.blender.org/api/current/bpy.msgbus.html
まず、Locationから位置変更した場合にメッセージを表示させる
import bpy #https://docs.blender.org/api/current/bpy.msgbus.html # Any Python object can act as the subscription's owner. owner = object() # この関数が呼び出される def msgbus_callback(*args): print("Something changed!", args) # locationを指定して、位置移動の時に呼び出されるようにする # ただしサイドバーのLocationの操作のみ対応 # [G]などで移動した場合は反応しない subscribe_to = bpy.context.object.location bpy.msgbus.subscribe_rna( key=subscribe_to, owner=owner, args=("hello",), # タプルなので引数一つの場合は(a,)のような形にしないといけない notify=msgbus_callback, )
さて回転の場合なのだが、
などとやってもエラーになる。
意訳 bpy.context.objectにrotationなんてない
ではどうするかというと、次のようにしてbpy.context.objectでつかえそうなものを調べる
import bpy li = dir( bpy.context.object ) for i in li: print(i)
以下出力。大量に出てくるが、locationのほかに、rotation_eulerなどを見つけられる。
__doc__
__module__
__slots__
active_material
active_material_index
active_shape_key
...
lineart
local_view_get
local_view_set
location
lock_location
lock_rotation
...
rotation_axis_angle
rotation_euler
rotation_mode
rotation_quaternion
scale
select_get
select_set
...
user_remap
users
users_collection
users_scene
vertex_groups
visible_get
visible_in_viewport_get
というわけで回転時に動かすにはbpy.context.object.rotation_eulerを指定する。
import bpy #https://docs.blender.org/api/current/bpy.msgbus.html # Any Python object can act as the subscription's owner. owner = object() # この関数が呼び出される def msgbus_callback(*args): print("Something changed!", args)
# rotation_eulerを指定して、回転の時に呼び出されるようにする # ただしサイドバーのRotationの操作のみ対応 # [R]などで移動した場合は反応しない subscribe_to = bpy.context.object.rotation_euler
bpy.msgbus.subscribe_rna( key=subscribe_to, owner=owner, args=("rotation-euler",), # タプルなので引数一つの場合は(a,)のような形にしないといけない notify=msgbus_callback, )
from → to を指すempty arrowを追加する。以下では、現在のアクティブオブジェクトのoriginから3D cursorへ矢印を作成する
import bpy import math import numpy import mathutils # @brief オブジェクトを指定座標へ向けるための回転軸と回転角を計算する # @param [in] オブジェクト # @param [in] 向ける方向(座標) # @return 回転軸,回転角(ラジアン) def calc_lookat_param(obj,lookat): # オブジェクトのローカルのZ軸のベクトルを取得 mat = obj.matrix_world localZ = mathutils.Vector((mat[0][2],mat[1][2],mat[2][2])) print(localZ) va = mathutils.Vector(lookat) - obj.location vb = mathutils.Vector(localZ) va.normalize() vb.normalize() # 外積 axis = mathutils.Vector.cross(va,vb) axis.normalize() # 内積 th = mathutils.Vector.dot(va,vb) # 角度算出 rad = -math.acos( numpy.clip(th,-1.0,1.0) ) return axis , rad # @brief 任意軸回転 # @param [in,out] obj 回転するオブジェクト # @param [in] axis 回転軸 # @param [in] radian 回転角をラジアンで指定 # @return なし # @sa https://www.study.suzulang.com/bpy-calculation/bpy-arbitrary-axis-rotation def rotate_object(obj,axis,radian): rot_mat= mathutils.Matrix.Rotation( radian, 4, axis ) # decompose world_matrix's components, and from them assemble 4x4 matrices orig_loc, orig_rot, orig_scale = obj.matrix_world.decompose() # orig_loc_mat = mathutils.Matrix.Translation(orig_loc) orig_rot_mat = orig_rot.to_matrix().to_4x4() orig_scale_mat = (mathutils.Matrix.Scale(orig_scale[0],4,(1,0,0)) @ mathutils.Matrix.Scale(orig_scale[1],4,(0,1,0)) @ mathutils.Matrix.Scale(orig_scale[2],4,(0,0,1))) # # assemble the new matrix obj.matrix_world = orig_loc_mat @ rot_mat @ orig_rot_mat @ orig_scale_mat
# @brief _fromから_toを指すEmptyArrowを作成 # @param [in] 矢印の根元の座標 # @param [in] 矢印の先端の座標 # @return 作成したオブジェクト def make_empty_arrow(_from,_to): # 新しいEmpty ObjectのArrowを作成 bpy.ops.object.empty_add(type="SINGLE_ARROW",location=_from) aobj = bpy.context.object # 方向を一致させるためのパラメータ取得 axis , rad = calc_lookat_param(aobj,_to) # 回転 rotate_object(aobj,axis,rad) # _from → _to の距離を計算 objlen = ( _from - _to ).length # 矢印の長さ==スケール aobj.scale[0] = objlen aobj.scale[1] = objlen aobj.scale[2] = objlen return aobj
# 使用例 obj = bpy.context.active_object arrow = make_empty_arrow( obj.location, bpy.context.scene.cursor.location ) print(arrow.name)
import bpy import bmesh import mathutils import numpy import math
# @brief オブジェクトの指定したpolygonの法線を取得 # @param [in] obj オブジェクト # @param [in] polyIndex ポリゴン番号 # @return 法線(グローバル座標系) def got_polygon_normal(obj,polyIndex): # オブジェクトの行列を取得 local44 = obj.matrix_world ll_inv = local44.inverted() ll_inv_norm = ll_inv.transposed().to_3x3() vn = obj.data.polygons[polyIndex].normal return ll_inv_norm @ vn
# @brief 任意軸回転 # @sa https://www.study.suzulang.com/bpy/bpy-arbitrary-axis-rotation # @param [in,out] obj 回転するオブジェクト # @param [in] axis 回転軸 # @param [in] radian 回転角をラジアンで指定 # @return なし def rotate_object(obj,axis,radian): rot_mat= mathutils.Matrix.Rotation( radian, 4, axis ) # decompose world_matrix's components, and from them assemble 4x4 matrices orig_loc, orig_rot, orig_scale = obj.matrix_world.decompose() # orig_loc_mat = mathutils.Matrix.Translation(orig_loc) orig_rot_mat = orig_rot.to_matrix().to_4x4() orig_scale_mat = (mathutils.Matrix.Scale(orig_scale[0],4,(1,0,0)) @ mathutils.Matrix.Scale(orig_scale[1],4,(0,1,0)) @ mathutils.Matrix.Scale(orig_scale[2],4,(0,0,1))) # # assemble the new matrix obj.matrix_world = orig_loc_mat @ rot_mat @ orig_rot_mat @ orig_scale_mat
# @brief (0,0,1)のベクトルを法線方向に回転するための回転軸と回転角度を求める # @param [in] the_normal 今回使用する法線 # @return 回転軸,回転角(ラジアン) def calc_to_normal(the_normal): Zvec = mathutils.Vector((0,0,1)) vn = the_normal onorm = mathutils.Vector(vn) onorm.normalize() # 外積 axis = mathutils.Vector.cross(Zvec,onorm) axis.normalize() # 内積 th = mathutils.Vector.dot(Zvec,onorm) # 角度算出 rad = math.acos( numpy.clip(th,-1.0,1.0) ) return axis , rad
# @brief オブジェクトの0番目のポリゴンの法線を計算し、empty arrowで表示する # @param [in] target 目的のオブジェクト def disp_polygon_normal(target): tnormal = got_polygon_normal(target,0) axis , rad = calc_to_normal(tnormal) bpy.ops.object.empty_add(type="SINGLE_ARROW") arrow = bpy.context.active_object rotate_object(arrow,axis,rad) arrow.location = target.location
# 実行例 target = bpy.context.active_object disp_polygon_normal(target)
以前のコードが汎用性に欠けるというか使いにくかったのでもう一度書き直した。
import bpy import math import mathutils
#################################################################### #################################################################### #################################################################### #################################################################### # @brief 任意軸回転 # @sa https://www.study.suzulang.com/bpy/bpy-arbitrary-axis-rotation # @param [in,out] obj 回転するオブジェクト # @param [in] axis 回転軸 # @param [in] radian 回転角をラジアンで指定 # @return なし def rotate_object(obj,axis,radian): rot_mat= mathutils.Matrix.Rotation( radian, 4, axis ) # decompose world_matrix's components, and from them assemble 4x4 matrices orig_loc, orig_rot, orig_scale = obj.matrix_world.decompose() # orig_loc_mat = mathutils.Matrix.Translation(orig_loc) orig_rot_mat = orig_rot.to_matrix().to_4x4() orig_scale_mat = (mathutils.Matrix.Scale(orig_scale[0],4,(1,0,0)) @ mathutils.Matrix.Scale(orig_scale[1],4,(0,1,0)) @ mathutils.Matrix.Scale(orig_scale[2],4,(0,0,1))) # # assemble the new matrix obj.matrix_world = orig_loc_mat @ rot_mat @ orig_rot_mat @ orig_scale_mat
#################################################################### #################################################################### #################################################################### #################################################################### #################################################################### # @brief 指定した位置に矢印を作成 # @param [in] tail 矢印の根本 # @param [in] head 矢印の頭 # @param [in] body_radius 矢印根元の半径 # @return 作成した矢印のオブジェクト def create_arrow(tail,head,body_radius): ########################################### ########################################### # tail->head の長さと方向を計算 avec = head - tail aveclen = avec.length avec.normalize() ########################################### ########################################### # 回転軸と回転量 zvec = mathutils.Vector([ 0.0 , 0.0 , 1.0]) zvec.normalize() rotvec = mathutils.Vector.cross(zvec,avec) rotvec.normalize() rotang = math.acos(mathutils.Vector.dot(zvec,avec) ) ########################################### ########################################### # 各パラメータ計算 # bodyとheadの長さの比率 head_ratio = 0.3 # 頭部長さ変更 head_length = aveclen * head_ratio # 頭部が長くなりすぎないように調整 if head_length > 0.3*4: head_length = 0.3*4 body_length = aveclen - head_length # 矢印の頭部分の付け根の大きさ(本体に対する倍率) head_root_ratio = 2.0 ########################################### ########################################### # サークル追加 # 半径は body_radius bpy.ops.mesh.primitive_circle_add( location=tail, radius=body_radius ) myarrow = bpy.context.object # 作成したサークルを回転して目標へ向ける rotate_object(myarrow,rotvec,rotang) ####################### # サークルを面にする bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.fill() ####################### # step 1 - 押し出し # 押し出し量は 矢印全体の長さ×比率 bpy.ops.mesh.extrude_region_move( TRANSFORM_OT_translate={"value":avec*body_length } ) ####################### # step 2 -| 矢印頭部部分付け根拡大 bpy.ops.mesh.extrude_region_move( TRANSFORM_OT_translate={"value":(0,0,0)} ) bpy.ops.transform.resize( value=( head_root_ratio, head_root_ratio, head_root_ratio) ) ####################### # step 3 -|= 矢印頭部部分押し出し bpy.ops.mesh.extrude_region_move( TRANSFORM_OT_translate={"value":avec*head_length} ) ####################### # step 4 -|> 矢印を閉じる bpy.ops.transform.resize(value=(0,0,0) ) bpy.ops.mesh.remove_doubles() bpy.ops.object.mode_set(mode='OBJECT') return myarrow
################################ #実行例 t = mathutils.Vector((1,2,1))#始点 h = mathutils.Vector((-1,2,3))#先端 arrow = create_arrow(t,h,0.1) arrow.name = "my-arrow" print (arrow)
import bpy import mathutils
# @brief モデルのポリゴンの重心をリストで返す # @param [in] obj オブジェクト # @return 重心座標のリスト def face_gravities(obj): mesh = obj.data vcoords = mesh.vertices # オブジェクトの行列を取得 local44 = obj.matrix_world; ret = [] # 全てのポリゴンに対するループ for poly in mesh.polygons: grav = mathutils.Vector((0,0,0)) for v in poly.vertices: # v はpolyの各頂点のID grav += vcoords[ v ].co # 頂点座標の合計を頂点数で割る grav /= len(poly.vertices) # グローバル座標に変換してリストへ追加 ret.append( local44 @ grav ) return ret
# 使用例 gravs = face_gravities( bpy.context.active_object ) # 計算した座標へ球を設置 for g in gravs: bpy.ops.mesh.primitive_uv_sphere_add( radius=0.1, segments=8, ring_count=8, enter_editmode=False, location=g )
オブジェクトのローカル座標系となるXYZの各ベクトルを求める
import bpy import mathutils def get_local_axis(obj): # オブジェクトのローカルのZ軸のベクトルを取得 mat = obj.matrix_world localX = mathutils.Vector((mat[0][0],mat[1][0],mat[2][0])) localY = mathutils.Vector((mat[0][1],mat[1][1],mat[2][1])) localZ = mathutils.Vector((mat[0][2],mat[1][2],mat[2][2])) return localX,localY,localZ; # obj = bpy.context.active_object local = get_local_axis(obj) print( local[0] ) print( local[1] ) print( local[2] ) bpy.context.scene.cursor.location = local[2]*3
Pythonでmath.acos(theta)を使うとthetaのとれる範囲が-1.0~1.0までなのでmath domain errorが発生することがある。
import math rad = math.acos( 0.9 ) rad = math.acos( 1.0 ) rad = math.acos( 1.1 ) # ValueError: math domain error
import math import numpy angle = 1.1 rad = math.acos( numpy.clip(angle,-1.0,1.0) )# acosへの入力を-1.0~1.0に修正 print(rad) # 0.0
import bpy import math import mathutils import numpy
# @brief 任意軸回転 # @param [in,out] obj 回転するオブジェクト # @param [in] 回転軸 # @param [in] radian 回転角をラジアンで指定 # @return なし def rotate_object(obj,axis,radian): rot_mat= mathutils.Matrix.Rotation( radian, 4, axis ) # decompose world_matrix's components, and from them assemble 4x4 matrices orig_loc, orig_rot, orig_scale = obj.matrix_world.decompose() # orig_loc_mat = mathutils.Matrix.Translation(orig_loc) orig_rot_mat = orig_rot.to_matrix().to_4x4() orig_scale_mat = (mathutils.Matrix.Scale(orig_scale[0],4,(1,0,0)) @ mathutils.Matrix.Scale(orig_scale[1],4,(0,1,0)) @ mathutils.Matrix.Scale(orig_scale[2],4,(0,0,1))) # # assemble the new matrix obj.matrix_world = orig_loc_mat @ rot_mat @ orig_rot_mat @ orig_scale_mat
# @brief オブジェクトをカーソルに向けるため回転軸と回転角を計算する # @return 回転軸,回転角(ラジアン) def calc_lookat_param(obj): # オブジェクトのローカルのZ軸のベクトルを取得 mat = obj.matrix_world localZ = mathutils.Vector((mat[0][2],mat[1][2],mat[2][2])) # 3Dカーソルの座標取得 curpos = bpy.context.scene.cursor.location va = mathutils.Vector(curpos) - obj.location vb = mathutils.Vector(localZ) va.normalize() vb.normalize() # 外積 axis = mathutils.Vector.cross(va,vb) axis.normalize() # 内積 th = mathutils.Vector.dot(va,vb) # 角度算出 rad = -math.acos( numpy.clip(th,-1.0,1.0) ) return axis , rad
# @brief オブジェクトをカーソルに向ける関数 # @param [in,out] オブジェクト # @return なし def lookat_cursor(obj): axis,rad = calc_lookat_param(obj) rotate_object( obj ,axis,rad)
# 実行例 obj = bpy.context.active_object lookat_cursor(obj)