Closed18

「どんな operator でも実行できる operator 」を KeyMapItem を流用してより簡単に実装する

nikogolinikogoli

概要 🔝

問題

  • どんな operator でも実行可能にするには、operator の情報を格納する汎用 class が必要
  • 汎用 class を機能させるためには、Blender 起動後の動的な class の設定が必要
  • 動的に設定した内容は Blender の終了で破棄されるので、起動の度に設定を復元する処理が必要

その結果、「やたらと記述が多く、何をやっているのかも分かりにくい script」になる

新しいアプローチ

汎用クラスではなく「存在するが利用はされない KeyMapItem」に operator の情報を格納する

  • 利点1:Blender 内にデータが保存されるので、復元処理が不要
  • 利点2:operator にわたす引数用のプロパティが、自動的に作成・設定される
  • 利点3:キーマップの UI が流用できるので、operator の設定変更 UI を自作しなくてよい

その結果、記述する処理が減ることで、同じ機能をよりシンプルな script で実装できる、という話

nikogolinikogoli

具体例で考える 🔝

具体例として、『「Shift + Q」を押した状態でもう1つキーを押すと、そのキーに設定した operator が実行される』機能を追加する Addon を考える。

nikogolinikogoli

0:operator も実行時パラメータも固定されている 🔝

以下の機能を追加する Addon を考える。

  • 「Shift + Q」を押した状態でWERTのどれかを押すと、 transform.rotateが、特定のパラメータで実行される
    (W→ x軸で 45度、E→ x軸で -45度、R→ z軸で 45度、T→ z軸で -45度)

このときの script の中身は以下のような雰囲気になる。

import bpy

bl_info = { ... }

# ----- operator ---------------------
class メインのオペレーター(bpy.types.Operator):
  bl_idname = ...
  bl_label = ...
  bl_options = {'REGISTER', 'UNDO'}

  def modal(self, context, event):
    ...
    if 押されたキー == "W": bpy.ops.transform.rotate(= "X",  角度 = 45)
    elif 押されたキー == "E": bpy.ops.transform.rotate(= "X",  角度 = -45)
    elif 押されたキー == "R": bpy.ops.transform.rotate(= "Z",  角度 = 45)
    elif 押されたキー == "T": bpy.ops.transform.rotate(= "Z",  角度 = -45)

  def invoke(self, contex, event):
    context.window_manager.modal_handler_add(self)
    return {'RUNNING_MODAL'}

# ----- preference --------------------
class AddonPrefs(bpy.types.AddonPreferences):
  bl_idname = __name__

#---------------------------------------
def register():
  クラスの登録と「Shift + Q」にメインのオペレーターを登録する処理

def unregister():
  クラスの登録解除と「Shift + Q」に設定されたキーバインドを削除する処理

if __name__ == "__main__":
  register()
nikogolinikogoli

1a:operator は固定だが、実行時パラメータは変更できる 🔝

以下の機能を追加する Addon を考える。

  • 「Shift + Q」を押した状態でWERTのどれかを押すと、 transform.rotateが、[ユーザーが設定した軸と角度] で実行される
  • プリファレンスから、実行時パラメータ(回転軸・角度)をユーザーが設定・変更できる

このときの script の中身は以下のような雰囲気になる。

 # 同じなので省略
+# ----- property class --------------
+class パラメータを格納するクラス(bpy.types.PropertyGroup):
+  入力キー: bpy.types.StringProperty()
+  基準軸:  bpy.types.StringProperty()
+  回転角度: bpy.types.IntProperty()
+
+  def draw(self, context, layout):
+    基準軸や回転角度を設定するための UI を作る処理
 
 # ----- operator ---------------------
 class メインのオペレーター(bpy.types.Operator):
   ...

   def modal(self, context, event):
     ...
+    if 押されたキー in ["W", "E", "R", "T"]:
+      prefs = context.preferences.addons[__name__].preferences
+      for インスタンス in prefs.パラメータ格納インスタンスたち:
+        if 押されたキー == インスタンス.入力キー:
+          基準軸 = インスタンス.基準軸
+          回転角度 = インスタンス.回転角度
+          bpy.ops.transform.rotate(= 基準軸,  角度 = 回転角度)

   def invoke(self, contex, event):
     context.window_manager.modal_handler_add(self)
     return {'RUNNING_MODAL'}
 
 # ----- preference --------------------
 class AddonPrefs(bpy.types.AddonPreferences):
   bl_idname = __name__
+  パラメータ格納インスタンスたち: bpy.types.CollectionProperty( type = パラメータを格納するクラス )

+  def draw(self, context):
+    base: UILayout = self.layout.column()
+    for item in self.パラメータ格納インスタンスたち:
+      item.draw(context, base)
 
 #---------------------------------------
 def register():
   クラスの登録と「Shidt + Q」にメインのオペレーターを登録する処理
+  prefs = bpy.context.preferences.addons[__name__].preferences
+  for キー,, 角度 in [ ["W","X",45], ["E","X",-45], ["R","Z",45], ["T","Z",-45] ]:
+    インスタンス = prefs.パラメータ格納インスタンスたち.add()
+    インスタンス.入力キー = キー
+    インスタンス.基準軸=+    インスタンス.回転角度 = 角度
 
 # 以下省略
nikogolinikogoli

変更点

  1. ユーザーが設定した値を保存する、パラメータを格納するクラスが追加される
  2. ユーザーが実行時パラメータを変更するための UI の設定が追加される
  3. メインのオペレーターにおいて、「押されたキーに対応したパラメータを格納するクラスのインスタンス」を取得する処理が追加される
  4. どこかに、パラメータを格納するクラスの CollectionProperty が追加される
  5. 登録処理に、パラメータを格納するクラスの初期インスタンスの作成処理が追加される
nikogolinikogoli

1b:実行時パラメータは固定だが、operator は変更できる 🔝

以下の機能を追加する Addon を考える。

  • 「Shift + Q」を押した状態でWERTのどれかを押すと、 [ユーザーが設定した operator] が、デフォルトのパラメータで実行される
  • プリファレンスから、operator をユーザーが設定・変更できる

このときの script の中身は以下のような雰囲気になる。

 # 同じなので省略
+# ----- functions -------------------
+def オペレーター名リスト取得関数(self, context, edit_tect):
+  オペレーター名のリストを取得する処理
+  return items

+def 名前からオペレーターを取得する関数(name):
+  名前からオペレーターを取得する処理
+  return op

+# ----- property class --------------
+class パラメータを格納するクラス(bpy.types.PropertyGroup):
+  入力キー: bpy.types.StringProperty()
+  オペレーター名: bpy.types.StringProperty( search = オペレーター名リスト取得関数 )
+
+  def draw(self, context, layout):
+    オペレーター名を設定するための UI を作る処理

 # ----- operator ---------------------
 class メインのオペレーター(bpy.types.Operator):
   ...
   
   def modal(self, context, event):
     ...
     if 押されたキー in ["W", "E", "R", "T"]:
+    prefs = context.preferences.addons[__name__].preferences
+    for インスタンス in prefs.パラメータ格納インスタンスたち:
+      if 押されたキー == インスタンス.入力キー:
+        オペレーター = 名前からオペレーターを取得する関数(インスタンス.オペレーター名)
+        オペレーター()

   def invoke(self, contex, event):
     context.window_manager.modal_handler_add(self)
     return {'RUNNING_MODAL'}

 # ----- preference --------------------
 class AddonPrefs(bpy.types.AddonPreferences):
   bl_idname = __name__
+  パラメータ格納インスタンスたち: bpy.types.CollectionProperty( type = パラメータを格納するクラス )

+  def draw(self, context):
+    base: UILayout = self.layout.column()
+    for item in self.パラメータ格納インスタンスたち:
+      item.draw(context, base)

 #---------------------------------------
 def register():
   クラスの登録と「Shift + Q」にメインのオペレーターを登録する処理
+   prefs = bpy.context.preferences.addons[__name__].preferences
+   for キー in ["W", "E", "R", "T"]:
+     インスタンス = prefs.パラメータ格納インスタンスたち.add()
+     インスタンス.入力キー = キー
+     インスタンス.オペレーター名 = ""
 
 # 以下省略

オペレーター自体はインスタンスに格納できないので代わりにオペレーター名を格納することになり、それを補助する関数もいくつか必要になる。

nikogolinikogoli

「operator 変更をできる + 実行時パラメータも変更できる」ためには? 🔝

これまでの話から、以下の2点がわかる

  1. パラメータを格納するクラスに対応するプロパティを持たせると、パラメータは変更可能になる
  2. オペレーター名の保存とそれから operator を得る処理があれば、 operator は変更可能になる

ところが、この2つを組み合わせて「operator もパラメータも変更できる」ようにすることは難しい。なぜなら、2. によってオペレーターが不確定になると、1. で持たせるプロパティを決めきれなくなるためである。

例えば、bpy.ops.transform.rotate()を選択したユーザーがパラメータを変更できるようにするには、
パラメータを格納するクラスに rotate() の25個のパラメータに対応する25個のプロパティが必要になる。同様に、bpy.ops.text.open()を選択したユーザーのためにはパラメータを格納するクラスにopen() の23個のパラメータに対応する23個のプロパティが必要となる。これらのパラメータには重複はないので、この2つのオペレーターを選択可能にするなら48個のプロパティの設定を行うことになる。

なので、どんな operator でも設定できるためには、全 operator の全パラメータに対応するプロパティをパラメータを格納するクラスに事前に設定しておくことが必要になる。これは現実的ではない。

nikogolinikogoli

動的な class の設定

事前に全ての operator 用のプロパティを設定するのではなく、operator が選択された際にそれ用のプロパティを設定することで手間を減らそうというのが「動的な class の設定」である。

  1. パラメータを格納するクラスには『オペレーター識別用のプロパティ』を持たせ、そのプロパティの update に「動的なプロパティ設定関数」を設定する
class パラメータを格納するクラス(bpy.types.PropertyGroup):
  オペレーター名: bpy.types.StringProperty( update = 動的なプロパティ設定関数)
  1. 「動的なプロパティ設定関数」は、operator から実行時パラメータの情報を得て対応するプロパティを作成し、setattr()を用いてそれをパラメータを格納するクラスの属性に追加する
def 動的なプロパティ設定関数(self, context):
    op = 名前からオペレーターを取得する関数(パラメータを格納するクラス.オペレーター名)
    パラメータリスト = [p for p in op.get_rna_type().properties if p.identifier != 'rna_type']
    for パラメータ in パラメータリスト:
        属性名 = オペレーター名 と パラメータ.identifier から作成した、識別可能な文字列
        プロパティ= パラメータからプロパティを作成する関数(パラメータ)
        setattr(パラメータを格納するクラス, 属性名, プロパティ)
  1. これにより、ユーザーが operator 名を選択すると「動的なプロパティ設定関数」が実行され、その operator 用のプロパティがパラメータを格納するクラス.xxxに追加される。

  2. なので、 UI ではlayout.prop(self, "xxx")で、 オペレーター実行時にはself.xxxによってパラメータにアクセスでき、オペレーターの実行時パラメータを調整・利用できる

動的なプロパティ設定の実際の script
nikogolinikogoli

動的な class の設定における「設定の復元」

残念ながら、動的に class に設定した属性は、Blender が終了されると全て破棄される。
そのため、一度でも Blender を終了すると、それ以降はパラメータを格納するクラス.xxxxは AttributeError になってしまう。

ただし、この class のインスタンスに格納された情報自体は破棄されない(らしい)。つまり、各インスタンスにはインスタンス.xxxxが存在し、ユーザーが設定した値もちゃんと残っている。

つまり、Blender 再起動後の挙動は、以下のようになる

  • 何もしないでインスタンス.xxxxにアクセスすると AttributeError になる
  • もう一度setattr(パラメータを格納するクラス, "xxx", xxx のプロパティ)を実行した上でインスタンス.xxxxにアクセスすると、エラーにならず過去に設定した値が取得できる

なので、動的な class の設定を利用する場合、Blender 起動後に毎回「動的なプロパティ設定」を行って以前に設定した値(へのアクセス)を復元する、という処理が必要になる。

具体的な復元処理

方法の1つは、bpy.app.handlers.load_postにハンドラを追加し「.blend ファイル読み込み後に既存のパラメータを格納するクラスを対象に動的なプロパティ設定をもう一度行う」こと。

# ----- preference --------------------
class AddonPrefs(bpy.types.AddonPreferences):
    bl_idname = __name__
    operators: CollectionProperty( name='Operator', type=ExperimentOp )

    def draw(self, context: Context):
       ...

    def prop_restore(self):
        for item in self.operators:
            dynamic_prop_setter(item, bpy.context)

#---------------------------------------------------
...

@bpy.app.handlers.persistent
def load_handler(dummy):
    #ここで preferences.operators を呼ぶと復元ポイントが Addon 登録時になってしまう
    bpy.context.preferences.addons[__name__].preferences.prop_restore()

def register():
    ...
    bpy.app.handlers.load_post.append(load_handler)

def unregister():
    ...
    bpy.app.handlers.load_post.remove(load_handler)
nikogolinikogoli

2a:operator も実行時パラメータも変更できる 🔝

以下の機能を追加する Addon を考える。

  • 「Shift + Q」を押した状態でWERTのどれかを押すと、 [ユーザーが設定した operator] が、[ユーザーが設定したパラメータ] で実行される
  • プリファレンスから、operator やその実行時パラメータをユーザーが設定・変更できる

ここまでの話から、このときの script の中身は以下のような雰囲気になる。

 # 同じなので省略
 # ----- functions -------------------
 def オペレーター名リスト取得関数(self, context, edit_tect):
   オペレーター名のリストを取得する処理
   return items

 def 名前からオペレーターを取得する関数(name):
   オペレーターを取得する処理
   return op

+def パラメータからプロパティを作成する関数(prop):
+  # 無駄に長いので省略
+  return StringProperty とか IntProperty とか

+def 動的なプロパティ設定関数(self, context):
+  op = 名前からオペレーターを取得する関数(パラメータを格納するクラス.オペレーター名)
+  if op == None: return
+  パラメータリスト = [p for p in op.get_rna_type().properties if p.identifier != 'rna_type']
+  for パラメータ in パラメータリスト:
+    属性名 =  オペレーター名 と パラメータ.identifier から作成した、識別可能な文字列
+    プロパティ = パラメータからプロパティを作成する関数(パラメータ)
+    setattr(パラメータを格納するクラス, 属性名, プロパティ)

 # ----- property class --------------
 class パラメータを格納するクラス(bpy.types.PropertyGroup):
   入力キー: bpy.types.StringProperty()
+  オペレーター名: bpy.types.StringProperty( search=オペレータ名リスト取得関数, update=動的なプロパティ設定関数 )
 
   def draw(self, context, layout):
     オペレーター名を設定する UI を作る処理
     オペレータのパラメータを設定する UI を作る処理

 # ----- operator ---------------------
 class メインのオペレーター(bpy.types.Operator):
   ...
   def modal(self, context, event):
     ...
     if 押されたキー in ["W", "E", "R", "T"]:
     prefs = context.preferences.addons[__name__].preferences
     for インスタンス in prefs.パラメータ格納インスタンスたち:
       if  押されたキー == インスタンス.入力キー:
         オペレーター = 名前からオペレーターを取得する関数(インスタンス.オペレーター名)
+        パラメータリスト = dir(インスタンス) からいい感じに作ったオペレーターのパラメータ名のリスト
+        kwargs = { パラメータ名: インスタンス.パラメータ名 for パラメータ名 in パラメータリスト }
+        オペレーター(**kwargs)

   def invoke(self, contex, event):
     context.window_manager.modal_handler_add(self)
     return {'RUNNING_MODAL'}

 # ----- preference --------------------
 class AddonPrefs(bpy.types.AddonPreferences):
   bl_idname = __name__
   パラメータ格納インスタンスたち: bpy.types.CollectionProperty( type = パラメータを格納するクラス )

   def draw(self, context):
     ...

+  def 設定を復元する関数(self, context):
+    for インスタンス in self.パラメータ格納インスタンスたち:
+      動的なプロパティ設定関数(インスタンス, bpy.context)

 #---------------------------------------
+@bpy.app.handlers.persistent
+def ファイル読み込み時のハンドラ(dummy):
+  bpy.context.preferences.addons[__name__].preferences.設定を復元する関数()

 def register():
   クラスの登録と「Shift + Q」にメインのオペレーターを登録する処理
   prefs = bpy.context.preferences.addons[__name__].preferences
   for キー in ["W", "E", "R", "T"]:
     インスタンス = prefs.パラメータ格納インスタンスたち.add()
     インスタンス.入力キー = キー
     インスタンス.オペレーター名 = ""
+  bpy.app.handlers.load_post.append(ファイル読み込み時のハンドラ) 

 def unregister():
   クラスの登録解除と「Shift + Q」に設定されたキーバインドを削除する処理
+  bpy.app.handlers.load_post.remove(ファイル読み込み時のハンドラ)

 # 以下省略

いろいろな部分にいろいろな処理が増えるため、ごちゃごちゃして、長い

nikogolinikogoli

クラスの代わりに KeyMapItem を使う 🔝

やっと本題。

ここまで色々とやってきたが、結局のところ欲しいのは「UI で自由に operator とそのパラメータを選択・変更できる operator」である。もっと言うと、「キーマップの UI みたいな UI で設定を調整できる operator 」が欲しい。

ところで、UIlayout には template_keymap_item_properties(item)というメソッドがあり

def draw(self, context, layout):
  item = なんかの KeyMap.keymap_items.new('transform.rotate', 適当なキー設定)
  layout.box().template_keymap_item_properties(item)

と書くだけで以下の UI が表示される。当然自由に変更可能で、変更は KeyMapItem に保存される。

またキーマップの UI でわかるように

  • layout.prop(KeyMapItem, "idname")の入力欄ではクリックするとオペレータ名の一覧が表示される。つまり、オペレータ名リスト表示機能が既に搭載されている。
  • layout.prop(KeyMapItem, "idname")の値を変更するとパラメータ設定の表示も変わる。つまり、「idname の変更に追随して KeyMapItem.properties を設定する機能」も既に搭載されている
                      ↓

「キーマップの UI みたいな UI」が欲しいなら、KeyMapItem を使うのが一番簡単なのでは?

nikogolinikogoli

「KeyMapItem を使う」ことに必要な条件

「キーマップの UI みたいな UI が欲しいので KeyMapItem を使う」のイメージ

  • WERT の各キーについて KeyMapItem が設定されていて
  • UI はこの KeyMapItem から作られているのでキーマップの UI のように値を調整できる
  • ユーザーが設定・変更した値は KeyMapItem に保存されるので、復元は必要ない
  • 「Shift + Q」を押した状態でWERT のどれかが押されたとき、対応する KeyMapItem からオペレーターとそのパラメータが取得され、実行される

ポイントは、キーマップを使っているがキーマップとしては機能させないこと。つまり、ここで設定されている KeyMapItem は、『KeyMapItem に設定されたキーが押されたときに Blender によって呼び出される』ことがあってはならない。でなければ、この Addon によって既存のキーバインドが"汚染"されてしまう。

つまり、 KeyMapItem を作成しても既存のキーバインドは一切変更されないこと、言い換えれば、『存在するが Blender には参照されない KeyMapItem 』であることが必要になる。

nikogolinikogoli

「Blender には参照されない KeyMapItem 」は存在できるのか?

Addon では通常keyconfigs.addonを対象にして、以下のように KeyMapItem を追加する

kc = bpy.context.window_manager.keyconfigs.addon
km = kc.keymaps.new('3D View', space_type='VIEW_3D', region_type='WINDOW')
kmi = km.keymap_items.new(オペレーター.bl_idname, 'Q', 'PRESS')

KeyMap をまとめた KeyConfig は複数存在するが、Blender 内部ではデフォルトの KeyMaps にkeyconfigs.addonなどを結合してkeyconfigs.userが作成され、これが各所で参照される。

その際、この『結合』によってkeyconfigs.addon['3D View']に追加した KeyMapItem がkeyconfigs.user['3D View']にも追加されることになり、Blender 内で実際に利用可能になる。

さて、実はメソッドKeyMaps.new()はどんな名前も指定可能で、keyconfigs.addon['PIYO']を作ることもできる。このとき、.addon['PIYO']の中身はkeyconfigs.userに反映されるのだろうか?

  • KeyMaps の結合による keyconfigs.user の作成処理の性質
    keyconfigs.user の作成では、.keyconfigs.addon 以下の KeyMap は、デフォルトの KeyMap と同じ名前を持つものだけが処理の対象になる。言い換えれば、デフォルトのものにはない名前を持つ KeyMap は keyconfigs.user に結合されることはない。

したがって.addon['PIYO']keyconfigs.userには反映されず、 Blender からは参照されない。そのため、.addon['PIYO'].keymap_items に入れた KeyMapItem のキー設定も無視され、既存のキーバインドに悪影響を与えることはない、という結果になる。

nikogolinikogoli

要するに

keyconfigs.addon.new("変な名前")で作った KeyMap ならば、作成した KeyMapItem のキー設定が既存の設定を上書きしてしまうことを心配しなくてよい

nikogolinikogoli

クラスの代わりに KeyMapItem を使う:使い方 🔝

流れとしては、以下のような感じになる

  1. Addon 登録時、bpy.context.window_manager.keyconfigs.addonに適当な名前でキーマップを作成
  2. その後、パラメータを格納するクラスのインスタンスを作成するたびに、同時にこのキーマップの下に適当なキー設定で KeyMapItem を作成する。そのとき、KeyMapItem.idパラメータを格納するクラス.インデックスに保存しておく
  3. UI では このパラメータを格納するクラス.インデックスを使って KeyMapItem を取得し、それを template_keymap_item_properties()に渡すことでユーザーが値を変更できる UI を作る
  4. また UI では、KeyMapItem.idnameをユーザーに提示する。これによって、オペレータ名の一覧表示も実行時パラメータに対応するプロパティの作成も、全て Blender が自動的にやってくれる
  5. オペレーターを実行するときも同様にインデックスから KeyMapItem を取得し、 KeyMapItem.propertiesから実行時パラメータの値を取得してオペレーターに渡して実行する
nikogolinikogoli

2B:operator も実行時パラメータも変更できる (KeyMapItem 版) 🔝

本題の本題。

以下の機能を追加する Addon を、KeyMapItem を使う形式で実現する。

  • 「Shift + Q」を押した状態でWERTのどれかを押すと、 [ユーザーが設定した operator] が、[ユーザーが設定したパラメータ] で実行される
  • プリファレンスから、operator やその実行時パラメータをユーザーが設定・変更できる

このときの script の中身は以下のような雰囲気になる。

 # 同じなので省略
 # ----- functions -------------------
 def 名前からオペレーターを取得する関数(name):
   オペレーターを取得する処理
   return op

 # ----- property class --------------
 class パラメータを格納するクラス(bpy.types.PropertyGroup):
   入力キー: bpy.types.StringProperty()
+  インデックス: bpy.types.IntProperty()

   def draw(self, context, layout):
     オペレーター名を設定する UI を作る処理 ( .layout.prop(KeyMapItem, "idname") する)
     オペレータのパラメータを設定する UI を作る処理

 # ----- operator ---------------------
 class メインのオペレーター(bpy.types.Operator):
   ...
   def modal(self, context, event):
     ...
     if 押されたキー in ["W", "E", "R", "T"]:
       prefs = context.preferences.addons[__name__].preferences
       for インスタンス in prefs.パラメータ格納インスタンスたち:
         if  押されたキー == インスタンス.入力キー:
+          入れ物用キーマップ = context.window_manager.keyconfigs.addon.keymaps.find(__name__)
+          キーマップアイテム = 入れ物用キーマップ.keymap_items.from_id(インスタンス.インデックス)
+          オペレーター = 名前からオペレーターを取得する関数(キーマップアイテム.idname)
+          パラメータリスト = dir(キーマップアイテム.properties) からいい感じに作ったオペレーターのパラメータ名のリスト
           kwargs = { パラメータ名: インスタンス.パラメータ名 for パラメータ名 in パラメータリスト }
           オペレーター(**kwargs)

   def invoke(self, contex, event):
     context.window_manager.modal_handler_add(self)
     return {'RUNNING_MODAL'}

 # ----- preference --------------------
 class AddonPrefs(bpy.types.AddonPreferences):
   bl_idname = __name__
   パラメータ格納インスタンスたち: bpy.types.CollectionProperty( type = パラメータを格納するクラス )

   def draw(self, context):
     ...

 #---------------------------------------
 def register():
   クラスの登録と「Shift + Q」にメインのオペレーターを登録する処理
+  入れ物用キーマップ = context.window_manager.keyconfigs.addon.keymaps.new(__name__)
   prefs = bpy.context.preferences.addons[__name__].preferences
   for キー in ["W", "E", "R", "T"]:
     インスタンス = prefs.パラメータ格納インスタンスたち.add()
+    キーマップアイテム = 入れ物用キーマップ.keymap_item.new(適当なキー設定)
     インスタンス.入力キー = キー
+    インスタンス.インデックス = キーマップアイテム.id

 # 以下省略

動的な class の設定とその復元に関する部分が全て不要になり、script がシンプルになった

nikogolinikogoli

まとめ 🔝

どんな operator でも実行できる operator では、あらゆるオペレーターの設定を格納できる『汎用的な入れ物』が必要になる。1つの方法は「動的な class の設定」だが、script が複雑になるのが欠点

別の方法として、「KeyMapItem を流用することで汎用的な入れ物を確保する」ことができる

  • 特徴1:KeyMapItem を使うと、UI 用の設定やプロパティの作成を Blender がやってくれる
  • 特徴2:.keydonfig.addon["変な名前"]以下の KeyMapItem は、既存のキーバインドに影響しない

結論:KeyMapItem は「オペレーターの設定を格納する場所」に最適 + 悪影響もない

まとめのまとめ

operator の設定の格納先を「汎用クラス」ではなく「KeyMapItem」にすることで、「どんな operator でも実行できる operator 」をよりシンプルに実現できる

実際の Addon の script

このスクラップは2022/11/01にクローズされました