はじめに
アドオン開発をしているとInfo(情報)ウインドウのログを全消去したいときがあるのですが、ワンボタンでできないのでスクリプトで実現しました。
記事後半にはボタンを実装したアドオン用コードもあります。
ウインドウではなくエリアと言った方がコード的に合っていますが、分かりやすさからウインドウを使っています。
テキストエディターから実行する場合
import bpy
# 全ウインドウを調べて "INFO" (情報)ウインドウを見つける
# 全ウインドウを調べないと、INFOを独立ウインドウにしていた時に特定できない
for window in bpy.context.window_manager.windows:
screen = window.screen
for area in screen.areas:
if area.type == 'INFO':
# INFOの WINDOW(ログが表示される場所(region)) を対象とした処理
# INFOを特定できれば region の指定が無くても動くが、あった方が堅牢
for region in area.regions:
if region.type == 'WINDOW':
# temp_override により一時的にコンテキストを切り替え
with bpy.context.temp_override(window=window, area=area, region=region):
# ログを全選択
bpy.ops.info.select_all(action='SELECT')
# 選択したログを削除
bpy.ops.info.report_delete()
オペレーター識別子の調べ方
bpy.ops.info.select_all(action='SELECT')
とbpy.ops.info.report_delete()
は英語版のプリファレンスから調べられます。
bpy.ops.info.select_all
は選択解除も兼ねているため、(action='SELECT')
が必要というわけですね。
最初にINFO (情報)ウインドウを特定する理由
他の処理は無くしてbpy.ops.info.select_all(action='SELECT')
とbpy.ops.info.report_delete()
だけを実行するシンプル処理で動きそうな気がしますがエラーが出ます。
RuntimeError: Operator bpy.ops.info.select_all.poll() failed, context is incorrect
bpy.ops.info.select_all(action='SELECT')
とbpy.ops.info.report_delete()
はarea.type == 'INFO'
でしか動きませんが、処理を実行するウインドウはテキストエディタなため、まずInfoで処理を進める工程が必要です。
アドオンとしてインストールしてボタン押下やショートカットキーで実行する場合
ディレクトリ構成
Info log Clear(任意のフォルダ名)
∟ __init__.py
アドオンのコード
bl_info = {
"name": "Info log Clear 情報ログ全消去",
"blender": (4, 0, 0),
"description": "Info(情報)ログを全消去するボタン。ショートカット名は「Info(情報)ログ全消去」。",
"category": "Interface",
}
import bpy
from bpy.types import Operator
# --- 共通ロジック ---
def _clear_reports():
bpy.ops.info.select_all(action="SELECT")
bpy.ops.info.report_delete()
def _info_window_region(area):
for r in area.regions:
if r.type == "WINDOW":
return r
# --- ヘッダーボタン用 ---
class INFO_OT_clear_in_area(Operator):
bl_idname = "info.clear_in_area" # オペレーターID
bl_label = "Info(情報)ログ全消去(Infoエリアのボタンから実行用)" # ショートカットキーのラベル
bl_description = "ログを全消去します" # マウスホバーしたときに表示される説明
# Infoかどうか確認
@classmethod
def poll(cls, context):
return context.area and context.area.type == "INFO"
# Infoのheader(上のメニューバー)のボタンから実行するため、全ウインドウからINFOを探す処理は割愛
def invoke(self, context, _event=None):
a = context.area
r = _info_window_region(a)
if not r:
return {"CANCELLED"}
with context.temp_override(window=context.window, area=a, region=r):
_clear_reports()
return {"FINISHED"}
# --- ショートカットキー用 ---
class WM_OT_info_clear_global(Operator):
bl_idname = "wm.info_clear_global"
bl_label = "Info(情報)ログ全消去"
bl_description = "Info(情報)ログを全消去します"
# グローバルで実行されるため、まず全ウインドウからINFOを探す処理を行う
def execute(self, _):
done = 0
for w in bpy.context.window_manager.windows:
for a in w.screen.areas:
if a.type != "INFO":
continue
r = _info_window_region(a)
if not r:
continue
with bpy.context.temp_override(window=w, area=a, region=r):
_clear_reports()
done += 1
return {"CANCELLED"} if done == 0 else {"FINISHED"}
# --- ヘッダーにボタン追加 ---
def _draw_info_header(self, _ctx):
layout = self.layout
layout.separator_spacer() # 右寄せ
layout.operator(INFO_OT_clear_in_area.bl_idname, text="全消去", icon="TRASH")
# --- wm.info_clear_globalをショートカットキー登録(Ctrl+Alt+A) ---
_keymaps = []
# --- 登録 ---
def register():
for c in (INFO_OT_clear_in_area, WM_OT_info_clear_global):
bpy.utils.register_class(c)
bpy.types.INFO_HT_header.append(_draw_info_header)
kc = bpy.context.window_manager.keyconfigs.addon
if kc:
km = kc.keymaps.new(name="Window", space_type="EMPTY", region_type="WINDOW")
kmi = km.keymap_items.new(
"wm.info_clear_global", "A", "PRESS", ctrl=True, alt=True
)
_keymaps.append((km, kmi))
def unregister():
kc = bpy.context.window_manager.keyconfigs.addon
if kc:
for km, kmi in _keymaps:
km.keymap_items.remove(kmi)
_keymaps.clear()
# INFO_HT_header からの削除
bpy.types.INFO_HT_header.remove(_draw_info_header)
for c in (WM_OT_info_clear_global, INFO_OT_clear_in_area):
bpy.utils.unregister_class(c)
if __name__ == "__main__":
register()
Infoウインドウ特定処理の有無
Infoのヘッダーボタンから実行する場合は、保険としてInfoかどうか検証するものの、context.area
で処理を開始できます。
ショートカットキーの場合はどこで発動されても動くようにまずInfoを特定します。
ヘッダーボタン、ショートカットキー共にショートカットキー用処理を使っても負荷は無視できる範囲ですが、今回は分けてみました。