Provided we have a Personal Package set up, you can create a python3.9libs folder, and inside that, a nodegraphhooks.py file.
There is a writeup from SideFX on some basics, but I found the docs to be a little thin when searching for info on this part.
Back to nodegraphhooks.py, you need to setup up and event handler that gets passed an event from Houdini, which accepts a uievent and pending actions as its arguments.
import hou
from canvaseventtypes import *
def createEventHandler(uievent, pending_actions):
return None, False
This is one of those great Houdini things that needs a restart every time, so for live devving, I suggest setting it up as follows:
import hou
from canvaseventtypes import *
import nodegraphdisplay as display
import nodegraphview as view
import extra
from importlib import reload
def createEventHandler(uievent, pending_actions):
reload(extra)
if extra.do_stuff(uievent):
return None, True
else:
return None, False
Because of the reload function, we can now create an extra.py next to the nodegraphhooks.py and do all our work there and have it update.
Inside of extra.py I have set up the do_stuff function and now we can get started.
import hou
from canvaseventtypes import *
import nodegraphdisplay as display
import nodegraphview as view
from datetime import datetime
def do_stuff(uievent):
if isinstance(uievent, MouseEvent):
print("there is a mouse event")
return False
If we load up Houdini now and move out mouse around in the network view, you will see lines being printed in the console. Now that we have connected ourselves, we can experiment with what info we get. One thing to note is that the mouse moved event is fired very often, so I will just set up a condition so we miss that bit.
import hou
from canvaseventtypes import *
import nodegraphdisplay as display
import nodegraphview as view
from datetime import datetime
def do_stuff(uievent):
if not uievent.eventtype == 'mousemove':
print(uievent)
return False
If we click once and press a key on the keyboard, we can get these two to analyze:
MouseEvent(editor=<hou.NetworkEditor panetab7>, eventtype='mouseenter', selected=NetworkComponent
(item=None, name='invalid', index=0), located=NetworkComponent(item=None, name='invalid', index=0
), mousepos=<hou.Vector2 [142, 670]>, mousestartpos=<hou.Vector2 [142, 670]>, mousestate=MouseSta
te(lmb=0, mmb=0, rmb=0), dragging=0, wheelvalue=0, modifierstate=ModifierState(alt=0, ctrl=0, shi
ft=0), time=183989.407)
KeyboardEvent(editor=<hou.NetworkEditor panetab7>, eventtype='keyhit', located=N
etworkComponent(item=None, name='invalid', index=0), mousepos=<hou.Vector2 [256,
623]>, mousestate=MouseState(lmb=0, mmb=0, rmb=0), key='C', modifierstate=Modif
ierState(alt=0, ctrl=0, shift=0), time=183986.282)
MouseEvent(editor=<hou.NetworkEditor panetab7>, eventtype='mouseexit', selected=
NetworkComponent(item=None, name='invalid', index=0), located=NetworkComponent(i
tem=None, name='invalid', index=0), mousepos=<hou.Vector2 [-1, 664]>, mousestart
pos=<hou.Vector2 [-1, 664]>, mousestate=MouseState(lmb=0, mmb=0, rmb=0), draggin
g=0, wheelvalue=0, modifierstate=ModifierState(alt=0, ctrl=0, shift=0), time=183
987.416)
Lots of info to digest in those, and in Intercepting Mouse Events we will look further at these.
Now that we are set up, we can get down into how to leverage more from this system. If you scroll up and look at how I set up nodegraphhooks.py you will see that I have an if statement, that returns either None, True or None, False. If we look into the definition for the createEventHandler function, we see the 2 args are passed off down the line to some mystical Houdini event pipeline. The bottom line is, if we reurn the second arg as True, we are essentially saying "don't use the native event handler, we are doind our own stuff here". I set the if statement up so we dont need to keep adjusting the intial file and restarting Houdini. Now in our extra.py we can return True or False wherever we break out, and control what is hijacked and not.
For example:
def do_stuff(uievent):
if isinstance(uievent, MouseEvent):
print("Nobody move! Im hijacking this mouse event")
return True
else:
return False
Now if we drop down a node, by default it will be selected, and if we try click off it to deselect it... we can't! That is by design, above we returned True if our event is a mouse event. Now we are pretty much just writing up if statements to refine until we only wrap the case we want, otherwise we block half of the native functionality.
Let's say for example that we want to be able to shift + ctrl click a node and create a Null connected to it.
import hou
from canvaseventtypes import *
import nodegraphdisplay as display
import nodegraphview as view
from datetime import datetime
def do_stuff(uievent):
if isinstance(uievent, MouseEvent):
if (uievent.eventtype == 'mousedown'):
ctrl = uievent.modifierstate.ctrl
shift = uievent.modifierstate.shift
if (ctrl and shift):
print("we made it")
else:
return False
Here we see we can we checked the modifierstate from our uievent to check which modifiers were down when the event fired.
Let's add that Null functionality:
import hou
from canvaseventtypes import *
import nodegraphdisplay as display
import nodegraphview as view
from datetime import datetime
def do_stuff(uievent):
if isinstance(uievent, MouseEvent):
if (uievent.eventtype == 'mousedown'):
ctrl = uievent.modifierstate.ctrl
shift = uievent.modifierstate.shift
if (ctrl and shift):
try:
pos = uievent.mousepos
editor = uievent.editor
items = editor.networkItemsInBox(pos, pos, for_select=True)
node = items[-1][0]
name = node.name()
name = "OUT_" + name
null = node.createOutputNode('null', name)
null.moveToGoodPosition()
return True
except:
return False
else:
return False
The fun part of this setup is that it doesnt use the selected node, it grabs the one under your cursor. Nice and snappy!