triggering Qt Python Panel reloads

   8108   11   0
User Avatar
Member
17 posts
Joined: March 2013
Offline
What's the best way to keep a Python Panel in sync with the rest of Houdini? I have a simple test where I populate a QListView based on the inputAncestors of a node.

When I make changes in the Node Editor I find I have to at least mouse over my interface to see the update. Sometimes when adding a node, the interface only updates existing rows, but does not add an additional row. In this case I have to click the reload button at the top of the Python Panel Pane. Otherwise the interface falls out of step with Houdini.

Here is my model which adapts a node into a list of the names of its input ancestors:

from PySide import QtGui, QtCore
import hou

class NodeInputListModel(QtCore.QAbstractListModel):
def __init__(self, node = None, parent = None):
QtCore.QAbstractListModel.__init__(self, parent)
self.__node = node

def rowCount(self, parent):
return len(self.__node.inputAncestors(include_ref_inputs=False))

def data(self, index, role):
if role == QtCore.Qt.DisplayRole:
row = index.row()

if self.__node is not None:
inputs = self.__node.inputAncestors(include_ref_inputs=False)
return inputs.name()


And here is my createInterface function:

def createInterface():
# Create a listView and model
listView = QtGui.QListView()
model = NodeInputListModel(hou.node('/obj/geo1/OUT'))
listView.setModel(model)

# Create a widget with a vertical box layout.
# Add the listView to the layout.
root_widget = QtGui.QWidget()
layout = QtGui.QVBoxLayout()
layout.addWidget(listView)
root_widget.setLayout(layout)

# Return the top-level widget.
return root_widget


Is it possible to trigger refreshes of my panel whenever my node network changes?
User Avatar
Member
7714 posts
Joined: July 2005
Online
Try using hou.Node.addEventCallback
http://www.sidefx.com/docs/houdini14.0/hom/hou/Node#addEventCallback [sidefx.com]
User Avatar
Member
17 posts
Joined: March 2013
Offline
Thanks, Edward. That moves me closer to what I want, but I'm still seeing some issues.

I can now trigger refreshes with this callback:


def refreshCallback(**kwargs):
root_widget.update()
QtCore.QCoreApplication.processEvents()


This means I don't need to mouse over the Python Panel to get updates, but things still act weirdly if I'm adding additional nodes to the input chain.

If my node has three input ancestors and they appear in the list like so:

THREE
TWO
ONE

Adding an additional node “FOUR” bumps an item off the list until I click reload:

FOUR
THREE
TWO

After clicking reload:

FOUR
THREE
TWO
ONE

The really weird thing is that if I have n input ancestors, and I have previously had at least n+1 ancestors, adding an additional node works properly. In other words, there seems to be some memory somewhere of the largest number of items that have ever been in the list, and adding removing nodes up to that number works properly, but going above that number requires a manual reload.
User Avatar
Member
7714 posts
Joined: July 2005
Online
It sounds like you have 2 different problems?

1. Whether your callback gets called at the right time.

2. What's get shown after your callback updates.

For 1), it depends on which events, and on which node, you're adding your callback to. For a new node, I think you need to add a callback for the child creation event on the *parent* (eg. hou.node(“/obj”)) to catch those changes.

For 2), I'm not sure. Sounds kinda like a pyqt layout thing to me. I don't know Qt to say how to solve it. Maybe you need to update/dirty the UI layout of your widget.
Edited by - March 3, 2015 00:12:20
User Avatar
Staff
4159 posts
Joined: Sept. 2007
Online
For #2: in non-H14 PyQt scripts, I've been able to update using show(); it was for icons in a tray applet, but all widgets have a show() method too. Maybe try that?
I'm o.d.d.
User Avatar
Member
17 posts
Joined: March 2013
Offline
1. Whether your callback gets called at the right time.
Yeah, I'm definitely finding this to be pretty finicky. So far my best solution is to add the callback to my target node, and then to all of its input ancestors. This seems to be the only way I can catch name changes on all the nodes. Right now I'm catching all event types, just to make sure nothing slips by me:
event_types = (hou.nodeEventType.BeingDeleted,
hou.nodeEventType.NameChanged,
hou.nodeEventType.FlagChanged,
hou.nodeEventType.FlagChanged,
hou.nodeEventType.AppearanceChanged,
hou.nodeEventType.PositionChanged,
hou.nodeEventType.InputRewired,
hou.nodeEventType.InputDataChanged,
hou.nodeEventType.ParmTupleChanged,
hou.nodeEventType.ChildSelectionChanged,
hou.nodeEventType.ChildCreated,
hou.nodeEventType.ChildDeleted,
hou.nodeEventType.ChildSwitched,
)

2. Sounds kinda like a pyqt layout thing to me
I suspect you're right about this. I'm pretty green at Qt, so I guess I'm missing something about how widgets/views resize.

in non-H14 PyQt scripts, I've been able to update using show()

Yeah, this doesn't seem to work for me. I'm calling everything I can think of to try to get updates now:
def refreshCallback(**kwargs):
list_view.update()
list_view.show()
layout.update()
root_widget.update()
root_widget.show()
QtCore.QCoreApplication.processEvents()

But to no avail. I also noticed some suspect members on QListView and QLayout, that I thought I might need to set to make sure everything rezizes properly when updating:
list_view.setResizeMode(QtGui.QListView.ResizeMode.Adjust)
layout.setSizeConstraint(QtGui.QLayout.SizeConstraint.SetNoConstraint)
Still no luck. Very perplexing indeed. Hopefully a Qt wizard will see this post. If I figure it out I'll post my solution.
User Avatar
Member
7714 posts
Joined: July 2005
Online
Have you tried this type of updating in PyQT outside of Houdini? Just populate with some dummy entries. Just to narrow things down.
User Avatar
Member
17 posts
Joined: March 2013
Offline
I just ran through this in a standalone app and figured out how to get things working. Previous to this project I did a tutorial on model/view programming in PyQt. As a part of that I implemented insertRows and removeRows on a custom list model. This was the way in which data was added and removed from the model.

Since my current project gets the data by dynamically querying the inputAncestors() of a node, I saw no reason to implement these methods. Something very important was happening in the tutorial code that escaped me. The body of insertRows() is surounded by a call to beginInsertRows() and endInsertRows(). These functions are responsible for notifying the view of which items will need to be updated.

As I am not using my model to insert or remove (at least not currently). I found that using the reset functions works to let the view know that the model has changed:

model.beginResetModel()
model.endResetModel()


That does the trick of refreshing properly. This one liner also seems to work fine:

model.reset()


Although the documentation advises against it. (http://qt-project.org/doc/qt-4.8/qabstractitemmodel.html#reset [qt-project.org])
I don't fully understand the reasoning, and it might not apply to my example, as I am not actively invalidating the model, but responding to changes in the Houdini scene.

I think we can call this one solved

Using node event callbacks still leaves something to be desired. Keeping in sync with my node network by recursively attaching callbacks to all my nodes seems rather kludgey. I wish there was some mechanism that was a bit more universal, but that may be a larger topic than this thread.

Thanks for the help!
Edited by - March 2, 2015 00:44:04
User Avatar
Member
7714 posts
Joined: July 2005
Online
Reading the Qt documentation link you posted, I think it sounds right to call it to me. Your “data model” in this case is the Houdini scene, so you need to “reset” it so that all the dependent Qt views know to refetch the data again.

As for the ChildCreated events, you really need an callback on all the parents that you want observe child creation events. This is even how the native node lister is implemented in Houdini.
User Avatar
Member
101 posts
Joined: Dec. 2012
Offline
Could anyone post a working minimal hip example with callbacks? Would be awesome
User Avatar
Member
7714 posts
Joined: July 2005
Online
http://www.sidefx.com/docs/houdini15.0/examples/python_panels/linkedparameters.pypanel [sidefx.com]

Look for addEventCallback.
User Avatar
Member
101 posts
Joined: Dec. 2012
Offline
awesome, thanks!
  • Quick Links