LoginSignup
12
4

More than 3 years have passed since last update.

ネットワークエディタにスクリプトショートカットメニューを表示する

Last updated at Posted at 2019-12-24

この記事はHoudini Advent Calender 2019の25日目の記事です。最終日!!

初めに

ネットワークエディタ上でShift+Tab(変更可)で独自メニューを表示します。
houdini_network_editor.gif
キャラ画像はただの趣味です。出さなくてOKです。

Houdini自体にHDA, シェルフ, プリセットと便利ツールが充実しているのでこのような改造をする必要はあんまりないのですが、改造することが好きという方にHoudiniはいじりがいのある楽しいソフトだと思います。

一応作ってみて良いと感じた点を挙げるとすると

  • シェルフに登録するよりもマウスの動きが少ない。
  • スクリプトからノードを作成する際にマウスの位置に作成できる。

まぁシェルフに登録したツールは通常のTABメニューにも表示できるので・・・。
デメリットとしては作成自体に苦労します。コードに記述ミスがあるとHoudiniが延々とエラーメッセージを出力しつづけたりします。

楽しそうと思った方はぜひ。
以下作成方法を書いていきます。使用しているHoudiniは18.0.287でWindows環境です。

公式ドキュメントはこちら
実際に動いているネットワークエディタのスクリプトが(Houdiniインストール先)/houdini/python2.7libs/nodegraph.pyにあるので何かあればこちらを参照するのがおすすめです。

ネットワークエディタ上でキーイベントをフックする

まずはスクリプトファイルを追加します。ドキュメントの方には
$HOUDINI_USER_PREF_DIR/python2.7libs/nodegraphhooks.py
例: C:\Users\(ユーザー名)\Documents\houdini18.0\python2.7libs\nodegraphhooks.py
を追加すると書かれています。
個人的にはスクリプトの管理をしやすいように別のディレクトリを作成してpythonrc.pyでそのパスを指定しています。

pythonrc.py
import sys
sys.path.insert(0,'スクリプト置き場のパス')

どちらでも好きな方で良いかと思います。
nodegraphhooks.pyの動作確認コードは以下になります。

nodegraphhooks.py
#coding:utf-8 
from canvaseventtypes import *

def createEventHandler(uievent, pending_actions):
    """ ネットワークエディタからのイベントをフックする """
    if isinstance(uievent, KeyboardEvent):
        if uievent.eventtype == 'keyhit' and uievent.key=='Shift+Tab':
            uievent.editor.flashMessage(None, 'nodegraphhook!!', 2.0)
    return None, False

これでShift+Tabでネットワークエディタにメッセージが表示されます。
nodegraphhook.gif
使われていないホットキーを探してそれに対応するコードを書いていくだけでエディタ拡張になります。
頻繁に使用するスクリプト処理があるならここに追加しておくと便利かもしれません。

メニューを表示する

ここではhou.qt.Menuを使用してショートカットメニューを表示します。
しばらく継続する処理を書くにはnodegraphbase.EventHandlerを継承したクラスを作成しhandleEventという関数で処理していくのが作法のようです。

hou.qt.Menuの元のQMenuはexec_という関数を使って表示することが多いのですがこの関数を使うと警告が表示されることがあるのでshowで表示します。

custom_tabmenu_handler.pyのファイルを追加しています。pythonのパスが通っている場所に置いてください。こだわりがなければnodegraphhooks.pyと同ディレクトリで良いと思います。

nodegraphhooks.py
#coding:utf-8 
from canvaseventtypes import *
from custom_tabmenu_handler import CustomTabMenuHandler

def createEventHandler(uievent, pending_actions):
    """ ネットワークエディタからのイベントをフックする """
    if isinstance(uievent, KeyboardEvent):
        if uievent.eventtype == 'keyhit' and uievent.key=='Shift+Tab':
            # カスタムタブメニュー用のイベントハンドラを返す 
            return CustomTabMenuHandler(uievent), True

    return None, False
custom_tabmenu_handler.py
#coding:utf-8 
import hou
import nodegraphbase as base
from hutil.Qt import QtGui
from hutil.Qt import QtWidgets

class CustomTabMenuHandler(base.EventHandler):
    """ Shift+Tabで開くカスタムタブメニュー処理 """
    def __init__(self, uievent):
        """ 初期化処理 """
        base.EventHandler.__init__(self, uievent)
        # Menuの作成 
        self.menu = hou.qt.Menu()
        # アクションの登録 
        self.addAction(self.menu, 'test', self.testFunc1)
        self.addAction(self.menu, 'test2', self.testFunc2)
        self.menu.addSeparator()
        self.addAction(self.menu, 'test3', self.testFunc3)
        # Menuの表示
        cursor_pos = QtGui.QCursor.pos()
        self.menu.move(cursor_pos)
        self.menu.show()

    def handleEvent(self, uievent, pending_actions):
        """ イベント処理 """
        # イベント処理を継続する場合はイベントハンドラを返す 
        if self.menu.isVisible():
            return self
        # メニューが消えたらイベント処理を終了する 
        return None

    def addAction(self, menu, name, func):
        """ アクション登録用のヘルパー関数 """
        act = QtWidgets.QAction(name, menu)
        act.triggered.connect(func)
        menu.addAction(act)

    def testFunc1(self):
        """ ネットワークエディタにメッセージを表示 """
        # 初期化で渡したuieventはself.start_uieventで取得可能 
        self.start_uievent.editor.flashMessage(None, 'test1', 2.0)

    def testFunc2(self):
        self.start_uievent.editor.flashMessage(None, 'test2', 2.0)

    def testFunc3(self):
        self.start_uievent.editor.flashMessage(None, 'test3', 2.0)

アクションを追加していくにはメンバ関数を追加してaddActionで紐づけしていきます。nodegraphhook3.gif

メニューと一緒に画像を表示する

画像だけを表示するウィジェットを作成してhou.qt.Menuと一緒に表示しているだけです。
こんなウィジェットを使用しています。

class PopupImage(QtWidgets.QWidget):
    """ 賑やかし用の画像を表示するだけの存在 """

    image = None

    def __init__(self, parent=None):
        super(PopupImage, self).__init__(parent)
        self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.NoDropShadowWindowHint)
        self.setAttribute(QtCore.Qt.WA_NoSystemBackground)
        self.setAttribute(QtCore.Qt.WA_TranslucentBackground)

        if PopupImage.image is None:
            # 画像パスは良い感じに調整してください。
            imagepath = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'images', 'popup_image.png'))
            PopupImage.image = QtGui.QImage()
            PopupImage.image.load(imagepath)

    def paintEvent(self, e):
        # 描画イベント 
        painter = QtGui.QPainter(self)
        painter.drawImage(self.rect(), PopupImage.image, PopupImage.image.rect())

もう少し実用的なサンプル

コードはGitHubに挙げているので詳細はこちらからご確認ください。
https://github.com/towazumi/HouNetworkEditorExtendSample

OUTという名前のNullノードを追加して色を黒にする

元ネタはHoudiniで始めるPython
元では選択したノードの名前と色を変更していましたがこちらは直接Nullノードを追加していきます。
気を付けるのはネットワークエディタがSOPレベルにいることを確認してメニューに表示するかチェックすることです。

nodegraphhook5.gif

選択中のノードにPythonのテキスト枠と実行ボタンを追加する

元ネタはWavefront.mtlファイルをコールバックで読み込む2:Stringを利用する手法です。
この設定をPythonから行います。

nodegraphhook6.gif

選択中のノードをPythonコードとして保存する/復元する

元ネタは[Houdini Snippet] Save Node as Python Codeです。
asCodeで生成されるスクリプトに処理を追加して復元するときにその情報を使えるようにしています。

nodegraphhook7.gif

まとめ

Python3も控えているということでもしかしたら手を出すには危険な時期かもしれませんが自分好みに改造していくのは苦労の分だけ楽しいものがあります。この記事が興味も持ってくれた方の役に立ってもらえれば幸いです。
最後までお読みいただき、ありがとうございました。

12
4
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
4