Open

Blender関連の雑多なメモ類

22

ぬっち氏の「Blenderプラグインの作り方」の出来が良すぎて、これの2.8対応版があればそれで十分な感じはある

◇ メニューの追加登録候補について(本体はまとめに移動)

  • ユーザー設定 (USERPREF) はオペレーター関連 (_OT_) だけ PREFERENCEに分離されている
  • メニューの append・prepend・remove は、scripts/modules/bpy_types.py で定義されているので、(たぶん)変更可能
    • → 同名のメニューを自作メニューで置き換えるとかできるかも?
    • appendはこんな感じ
      @classmethod
      def append(cls, draw_func):
          """
          Append a draw function to this menu,
          takes the same arguments as the menus draw function
          """
          draw_funcs = cls._dyn_ui_initialize()
          cls._dyn_owner_apply(draw_func)
          draw_funcs.append(draw_func)
      
    • ちなみに、context.copy()もこのファイルで定義されていた

オペレーターやクラスのプロパティの情報を取得したい

bpy.types.SubsurfModifierboundary_smoothPRESERVE_CORNERSの説明文や、 bpy.ops.object.data_transfervert_mappingEDGEINTERP_NEARESTの説明文を流用したいが、いちいち API Document をコピペするのはちょっと... というとき

types.Hogeがある場合

struct = bpy.types.SubsurfModifier.bl_rna  #.bl_rna で bpy_struct を取得
prop = struct.properties['boundary_smooth']  # properties["名前"] でプロパティを取得
text = prop.enum_items['PRESERVE_CORNERS'].description  #Enum の場合はこんな感じ

types.Hogeがない場合(オペレーター)

struct = bpy.ops.object.data_transfer.get_rna_type()  #.get_rna_type() で bpy_struct を取得
prop = struct.properties['vert_mapping']  # あとは types.Hoge と同じ
text = prop.enum_items['EDGEINTERP_NEAREST'].description

参考:How can I access view3d.select_circle radius value?

コピペでは駄目な理由:「同じテキスト(のはず)なのに、なぜか違う訳になる」という現象

例(2.91で発生、2.92は未確認)

txt_solid=bpy.types.UILayout.enum_item_name(C.active_object, 'display_type', 'SOLID')
txt_solid == "Solid"  #英語コンソールでは True

##何かのパネルの draw()
txt_solid=bpy.types.UILayout.enum_item_name(context.active_object, 'display_type', 'SOLID')
self.layout.label(text=txt_solid)  #これは「ソリッド」と翻訳される
self.layout.label(text="Solid")  #これも「ソリッド」と翻訳される

##パイメニューで使われるdraw() :bpy.ops.wm.call_menu_pie(name=hoge.bl_idname)
class hoge(bpy.types.Menu):
#…
    def draw(self, context):
        txt_solid=bpy.types.UILayout.enum_item_name(context.active_object, 'display_type', 'SOLID')
        self.layout.menu_pie().operator(
                huga.bl_idname, text=txt_solid)  #これは「ソリッド」と翻訳される
        self.layout.menu_pie().operator(
                huga.bl_idname, text="Solid")  #これは「立体」と翻訳される

同様にしてstruct.functionsでメソッドが取れた はず

◇ Blender のスクリーンショット作成機能とか

  • python のテストではアドオンの機能テストはできるけどパネル表示のテストはできないよね問題の解決に使えないか?
    • と思ったけどやっぱ無理? invoke_default → スクショ → キャンセルってできるんだろうか
  • UI というか各エディターの基本状態の画像一覧をこれで自動的に作りたい
  • あと mouse_gesture でやってるやつ
    • これはスクリーンショット機能使ってなかったかも

Blender のスクリーンショット作成機能では、UIは映らないっぽい まあ当然か

  • bpy.ops.buttons.context_menu
    • Display properties editor context_menu
      プロパティエディターのコンテクストメニューを表示する

    • つまり、Buttons = プロパティエディター と理解して良い?
    • layout.operator(こいつ)で draw すると、パネル上で右クリックしたときのメニュー(APIリファを参照とか)が出てくる
      • これは適当なエディターのヘッダーとかで出るやつと同じ
      • → 説明とは異なり、別にプロパティエディターに限定されないのでは?
      • \Rightarrow 実際、TOPBAR_MT_editor_menus というクラスがあるので、これらの draw を呼んでいるような気がする
        • チェックしたいけど、どうやって?

◇ property の add() というか公式の PropertyGroup Example のやつ

  • bpy.types.PropertyGroupを継承したプロパティグループのクラスMySetを定義
  1. 使い方その1どこか.prop = bpy.props.PointerProperty(type=MySet)とする
    • プロパティグループが1つ設定された状態
    • どこか.prop.nameのようにMySetの各attrの name を使って個別プロパティを取得する
    • これが公式の Exapmle の上のやつ

  2. 使い方その2どこか.prop = bpy.props.CollectionProperty(type=MySet)とする
    • コレクションプロパティが1つ設定された状態 = 設定時点では 枠 だけできて、中身のグループはない
      • len(どこか.prop)=0であり、真偽判定では Falseとして扱われる
      • ただしリストというわけではなく、どこか.prop == [] → False
    • どっか.hoge.add()を行うとコレクションの中にMySetのインスタンス?が追加される
      • add()は引数を取らない → コレクションの中身はアクセスのための固有の識別子?を持てない
      • \Rightarrowどっか.hogeの各要素はどっか.hoge[0]のようにインデックスで取得する
      • あるいは x for x in どっか.hogeのように for で回してあれこれする
    • add()は追加したインスタンスを返すので、item = どっか.hoge.add()としておけばitem.nameのように個別プロパティを取得できる
    • これが公式の Example の下のやつ

要するにどっか.hoge.add()・・・初期設定の何かのプロパティでコレクションを埋めている
  → そのあとにx for x in どっか.hogeがあり、そこで実際のプロパティの値を設定しているはず

def prop_from_text_enum(self, context):
    items = []
    for text in bpy.data.texts:
        items.append((text.name, text.name, ''))
    items = tuple(items)
    prop_from_text_enum.items = items
    return items
  • D.textsから enum_item用のリストを作って、それを返す
  • 関数オブジェクト prop_from_text_enumitemsattr に作成したリストへのアクセスを保存する
  • defした時点?でprop_from_text_enumにこのオブジェクト?が代入され、以降はprop_from_text_enumを呼ぶたびに同じものが出てくる・・・で良いのか?
  • 実行すればitemsにリストが保存されて_enum.itemsAttributeErrorでなくなり、実行するたびにリストが再作成される

要するにenum_item用のリストがほしい + 外部?からアクセスする手段を確保したい + クラスは作りたくない という要望を叶える手段?

properties_object.py
class OBJECT_PT_context_object(ObjectButtonsPanel, Panel):
    bl_label = ""
    bl_options = {'HIDE_HEADER'}

    def draw(self, context):
        layout = self.layout
        space = context.space_data

        if space.use_pin_id:
            layout.template_ID(space, "pin_id")
        else:
            row = layout.row()
            row.template_ID(context.view_layer.objects, "active", filter='AVAILABLE')
  • 通常時はbpy.context.view_layer.objects.activeが判定に使用される (AVAILABLE?)
    • ちゃんと Global Conetxt の要素(view_layer)を使用している
    • = (定義?上は) Local な active_objectの有無には依存していない
  • ピン留めされたときはbpy.context.space_data.pin_idにあるidが判定に使用される
    • パネル表示処理?上ではcontext.objectは重要でない
    • ピン留めはpin_idに『何』を投げているのか?
      • そもそも投げていない = bpy.context.view_layer.objects.activepin_idにセット の可能性が高そう

bpy.types.UILayout.context_pointer_set

以下のようにして使う (ここの真似)

class MyMenu(bpy.types.Menu):
	bl_label = "My Menu"
	bl_idname = "どこか.my_menu"
		...
	def draw(self, context):
		row = self.layout.row()
		row.context_pointer_set("my_camera", camera) #row() や column() に使う
		row.menu(何か別のメニュー.bl_idname)
		row.operator(何かのオペレーター.bl_idname)
  • 古い情報だが、これCのコードらしい
  • 上記の場合、rowにセットされたmenuoperatorにおいて、context.my_cameraが使用可能になる
  • あくまでもrowの中に入らないと有効にならないようで、上記のMyMenu内でrowをいじってもcontext.my_cameraは使えない
  • 基本はmenuで使うもののようで、operator内でcontext.my_cameraを呼ぶとよくわからない挙動
    1. 直接executeが動くもの:context.my_cameraが使える
    2. drawするもの:最初のdraw実行時は使えるが、再度drawするとcontext.my_cameraがなくなっていてAttributeErrorになる
    3. drawしてOKボタン等でexecuteするもの:executeするときにはcontext.my_cameraがなくなっていてAttributeErrorになる
  • そもそもオペレーターは普通にoprator().camera = camera すれば良い

で、書いてて思ったけど、これbpy.types.Menuがプロパティを持てない問題への対処法なのでは?

  • 上のリンクも "How to create submenus with dynamic content?" への解答としてcontext_pointer_setが提示されているわけだし
  • \Rightarrow オペレーターのように.menu(サブメニュー).content = targetができないので、代替としてcontext_pointer_set("content", target)を行う と考えると綺麗

別ウィンドウでテキストエディタを出し、出力を貼り付けるオペレーター

def execute(self, context):
	if len(context.window_manager.windows) == 1:
		bpy.ops.screen.userpref_show()
	text_area = context.window_manager.windows[-1].screen.areas[-1]
	text_area.type = 'TEXT_EDITOR'
	if "temp_text" in bpy.data.texts:
		text_area.spaces[0].text = bpy.data.texts["temp_text"]
	else:
		text_area.spaces[0].text = bpy.data.texts.new(name="temp_text")
	pre_clipboard = context.window_manager.clipboard
	override = context.copy()
	context.window_manager.clipboard = "\n".join(dir(bpy.types)) # textで見たい情報
	override['area'] = text_area
	bpy.ops.text.select_all(override)
	bpy.ops.text.paste(override)
	bpy.ops.text.jump(override, line=1)
	return {'FINISHED'}
  • 別にクリップボードを介する必要はなくて、text_area.spaces[0].text.write("\n".join(dir(bpy.types)))すればいいだけなんだけどね

bl_rna_get_subclass_py:クラス名からクラスオブジェクトを取得する 参考

プロパティエディタのタブを取り出したポップアップを出す
def invoke(self, context, event):
	return context.window_manager.invoke_props_dialog(self)

def draw(self, context):
	areas = context.window_manager.windows[0].screen.areas
	for a in areas:
		if a.type == "PROPERTIES":
			sp_data = a.spaces[0]
	def myEmpty(data):
		myEmpty.space_data = sp_data
		return None
	myEmpty(sp_data) # ↓ "_PT_"なら Panel のように、引数と合わせる
	clas = bpy.types.Panel.bl_rna_get_subclass_py('PROPERTIES_PT_navigation_bar')
	clas.draw(self, myEmpty) # draw 内で attr として xx.space_data が呼ばれる 辞書では駄目

もうちょっとちゃんとした手順というかbl_rna_get_subclass_pyを使う流れ

# my_addon.py の中でオペレータを定義
# class MyOprator(bpy.types.Operator):
#     bl_idname = "wm.my_operator"

op_struct = bpy.ops.wm.my_operator.get_rna_type()  # get_rna_type() で bpy_struct を取得
    # op_struct: <bpy_struct, Struct("WM_OT_my_operator") as 0x.... >
    #  ↓
op_cls_in_types = op_struct.__class__  # __clas__ で Blender 内部でのクラス?を取得
    # op_cls_in_types: <class 'bpy.types.WM_OT_my_operator' >
    #  ↓
op_cls_name = op_cls_in_types.__name__  # __name__ で クラス名を str として取得
    # op_cls_name: WM_OT_my_operator
    #  ↓
op_class = bpy.types.Operator.bl_rna_get_subclass_py(op_cls_name)
   # op_class: <class 'my_addon.MyOperator' >

op_class まで到達すると、AttributeErrorを出さずに、op_class.xxのようにクラス変数にアクセスしたり、op_class.draw()のようにメソッドを呼んだりできる

ユーザーが追加したオペレータでは上記の手順でクラスオブジェクトの取得に成功できる
しかし、デフォルトのオペレータでは name は取れても bl_rna_get_subclass_py(name)Noneになってしまう

めも

  • Topbar の space_data は それ用のサブクラスがなく Space クラスがそのまま使われている
  • PoseBone.bone == 正しいBoneTrue BUT PoseBone.bone is 正しいBoneFalse
  • 座標に関連する複数のプロパティ
    • matrix: Final 4x4 matrix after constraints and drivers are applied (object space)
    • matrix_basis: Alternative access to location/scale/rotation relative to the parent and own rest bone
    • matrix_channel: 4x4 matrix, before constraints
    • とりあえず、ポーズボーンが変化していない(レスト位置のまま)のときは
      • PoseBone のmatrix ≒ EditBone の matrix
      • PoseBone のmatrix_basis → 単位行列
      • PoseBone のmatrix_channel → ?

  • ctypes.windll.user32.keybd_event(0x1B) Esc キー イベントを起こす
startup\bl_ui\space_view3d.py
class VIEW3D_PT_context_properties(Panel):
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = "Item"
    bl_label = "Properties"
    bl_options = {'DEFAULT_CLOSED'}

    @staticmethod
    def _active_context_member(context):
        obj = context.object
        if obj:
            object_mode = obj.mode
            if object_mode == 'POSE':
                return "active_pose_bone"
            elif object_mode == 'EDIT' and obj.type == 'ARMATURE':
                return "active_bone"
            else:
                return "object"

        return ""

context.objectcontext.active_objectの違いの話

公式:context.objectはボタン/パネル用だよ (超意訳)
  ↓
プロパティエディタ(Button Cont.)が context.objectを持つのは理解できる
では、なぜプロパティエディタの外、つまり Screen Cont. にも context.objectがあるのか?
= 3Dビューでのcontext.objectcontext.active_objectの、想定されている使い分けとは?
  ↓
3Dビューにもパネルはある・・・Screen Cont. のcontext.objectは、サイドバーなどで利用するためでは?
  ↓
予想__PT__系は .object(もしかしたら__MT__系も) 、__OT__系は.active_objectを使うのが公式の作法

space_view3d.py の各クラスでの `context.object` /` active_object` の使い分け
クラス名 context.object context.active_object
VIEW3D_HT_tool_header def draw_mode_settings
_draw_tool_settings_context_mode: def PAINT_GPENCIL
VIEW3D_HT_header def draw
VIEW3D_MT_editor_menus def draw
VIEW3D_MT_transform_armature def draw
TOPBAR_MT_edit_curve_add def draw
VIEW3D_MT_object def draw
VIEW3D_MT_object_context_menu def draw
VIEW3D_MT_hook def draw
VIEW3D_MT_vertex_group def draw
VIEW3D_MT_gpencil_vertex_group def draw
VIEW3D_MT_pose_group def draw
VIEW3D_MT_edit_mesh_context_menu def draw
VIEW3D_MT_edit_mesh_extrude def draw
VIEW3D_MT_edit_font_kerning def draw
VIEW3D_MT_assign_material def draw
VIEW3D_MT_gpencil_copy_layer def draw
VIEW3D_MT_gpencil_animation def poll
VIEW3D_MT_pivot_pie def draw
VIEW3D_PT_snapping def draw
VIEW3D_PT_overlay_gpencil_options def draw
VIEW3D_PT_context_properties def _active_context_member
VIEW3D_PT_gpencil_draw_context_menu def draw
TOPBAR_PT_gpencil_materials def poll
TOPBAR_PT_gpencil_vertexcolor def poll

・・・いい思いつきだと思ったんだけどなあ

どっちかだけで良くない?

source/blender/editors/screen/screen_context.c
static eContextResult screen_ctx_active_object(const bContext *C, bContextDataResult *result)
{
  wmWindow *win = CTX_wm_window(C);
  ViewLayer *view_layer = WM_window_get_active_view_layer(win);
  Object *obact = view_layer->basact ? view_layer->basact->object : NULL;

  if (obact) {
    CTX_data_id_pointer_set(result, &obact->id);
  }

  return CTX_RESULT_OK;
}
static eContextResult screen_ctx_object(const bContext *C, bContextDataResult *result)
{
  wmWindow *win = CTX_wm_window(C);
  ViewLayer *view_layer = WM_window_get_active_view_layer(win);
  Object *obact = view_layer->basact ? view_layer->basact->object : NULL;

  if (obact) {
    CTX_data_id_pointer_set(result, &obact->id);
  }

  return CTX_RESULT_OK;
}
作成者以外のコメントは許可されていません