機械学習基礎理論独習

誤りがあればご指摘いただけると幸いです。数式が整うまで少し時間かかります。リンクフリーです。

勉強ログです。リンクフリーです
目次へ戻る

Blender Addon 開発メモ - その3

BoneとMode

Copilotに聞きました。Bone は Mode に応じて参照すらできないので Bone を参照するにはまずはモードを確認しておいたほうが良さそうです。


bpy.types.Panel をそのまま継承するのではなく、親クラスを作っておくと便利

親クラスのコードは以下です。
なぜ抽象クラスにしなかったかですが、抽象クラスにしてもも本環境ではインスタンスを作成しようとして失敗します。
なので普通のクラスとして作成して、blenderにregisterしないようにします。とりあえずこれで動いています。

import bpy

class RH_PT_Base(bpy.types.Panel):
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = "Rig Helper"

    toggle_panel: str = ""
    icon_header: str = "BLANK1"
    icon_preset: str = "BLANK1"

    @classmethod
    def poll(cls, context):
        prop_name = cls.toggle_panel
        if not prop_name:
            return True
        return getattr(context.scene, prop_name, False)

    def draw_header(self, context):
        self.layout.label(text="", icon=self.icon_header)

    def draw_header_preset(self, context):
        self.layout.label(text="", icon=self.icon_preset)

    def draw(self, context):
        pass

呼び出し方は以下。
親クラスでbl_space_type, bl_region_type, bl_category は定義してあるのでこのクラスではいちいち定義しなくて済む。
私は親でアイコンの表示もできるようにしてある。デフォルトでブランクなので必要であれば指定すればよい。

from . import transition, utils, errors, messages, ui_base
import bpy

class RH_PT_transition(ui_base.RH_PT_Base):
    bl_idname, bl_label = "RH_PT_transition", "Transition"
    toggle_panel, icon_header = "toggle_transition", "INTERNET"

    def draw(self, context):
        layout = self.layout
        row = layout.row(align=False)
        row.operator(RH_OT_weight_mesh.bl_idname, icon="OUTLINER_OB_MESH")        
        row.operator(RH_OT_pose_armature.bl_idname, icon="ARMATURE_DATA")
        row = layout.row(align=False)
        row.operator(RH_OT_edit_armature.bl_idname, icon="ARMATURE_DATA")
        row.operator(RH_OT_edit_mesh.bl_idname, icon="OUTLINER_OB_MESH")

子の中で Bone の順番を入れ替えるには

まず、特定の Bone (Old Bone と呼ぶ)を最後尾の子にするメソッドを作成します。以下の手順です。
1. Bone を再作成する。(New Bone と呼ぶ)
2. Old Bone を削除する
3. New Bone に Old Bone 属性をコピーする。(Bone Constraint 等はコピー不要)
※属性をコピーする時に Old Bone の属性を記憶しておく必要があります。

import bpy

def reorder_edit_bone_to_tail(bone_name):
    obj = bpy.context.object
    if not obj or obj.type != 'ARMATURE':
        print("アクティブなアーマチュアが必要です。")
        return

    bpy.ops.object.mode_set(mode='EDIT')
    edit_bones = obj.data.edit_bones

    old_bone = edit_bones.get(bone_name)
    if not old_bone:
        print(f"ボーン '{bone_name}' が見つかりません。")
        return

    # 編集可能な属性をすべて保存(読み取り専用は除外)
    preserved_attrs = {}
    for prop in old_bone.bl_rna.properties:
        if prop.is_readonly or prop.identifier in {"rna_type", "name"}:
            continue
        try:
            preserved_attrs[prop.identifier] = getattr(old_bone, prop.identifier)
        except Exception:
            pass

    # 削除して末尾に再作成
    edit_bones.remove(old_bone)
    print(old_bone)
    new_bone = edit_bones.new(bone_name)

    for key, value in preserved_attrs.items():
        try:
            setattr(new_bone, key, value)
        except Exception:
            pass  # 非互換または古いプロパティ(例: layers)を無視

    bpy.ops.object.mode_set(mode='OBJECT')
    print(f"'{bone_name}' を末尾に再作成し、属性も保持しました。")

reorder_edit_bone_to_tail("Bone.001")

bpy.types.Operator もそのまま継承するのではなく、親クラスを作っておくと便利

class RH_OT_Base(bpy.types.Operator):
    bl_options = {"UNDO"}
    prefix = "rig_helper."

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        if hasattr(cls, "bl_idname") and not cls.bl_idname.startswith(cls.prefix):
            cls.bl_idname = cls.prefix + cls.bl_idname

find_armature ってどんなメソッド?

find_armature() は、メッシュオブジェクト(Object)から親アーマチュアを取得するための Blender の便利なメソッドです。

context.active_object と context.view_layer.objects.active

context.active_object は読み取り専用
object mode では context.active_object 参照できない。context.view_layer.objects.active を参照する。書き込みもできる。
基本的に同じ参照を指しており(詳細は理解していない)context.active_object を参照する方が良いらしい。
以上より mode != "OBJECT" の時にcontext.active_object を参照すればよい 。

operatorを呼び出すと Info には表示されない

bpy.app.timers.registerを使っても、Infoにはログは出ない。Console logには出る。
そういうもんかと思って本件は諦めた。

getattr(bpy.ops.rig_helper, self.append_method_prop)()

クラス名は Camel Case か Snake Case か

FILE_OT_hello_operator のような命名が多いのですが、AIに聞くと私の思っているのと同じ回答が来たので採用することにします。
今後以下のように命名します。


register 時に bpy.context.scene.prop を参照できないのを解決する

解決方法は、非同期で参照することです。
要はタイマーでいけます。

def register():
    def timer(): 
        print(bpy.context.scene.sn_load_interval)
    bpy.app.timers.register(timer, first_interval=0.1)

このように書かないとエラーが発生する。恐らく初期化が終わってないんじゃないかな。

目次へ戻る