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?
triggering Qt Python Panel reloads
8758 11 0- nhillier
- Member
- 17 posts
- Joined: March 2013
- Offline
- edward
- Member
- 7868 posts
- Joined: July 2005
- Offline
Try using hou.Node.addEventCallback
http://www.sidefx.com/docs/houdini14.0/hom/hou/Node#addEventCallback [sidefx.com]
http://www.sidefx.com/docs/houdini14.0/hom/hou/Node#addEventCallback [sidefx.com]
- nhillier
- 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.
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.
- edward
- Member
- 7868 posts
- Joined: July 2005
- Offline
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.
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
- goldleaf
- Staff
- 4193 posts
- Joined: Sept. 2007
- Offline
- nhillier
- 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 meI 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.
- edward
- Member
- 7868 posts
- Joined: July 2005
- Offline
- nhillier
- 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!
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
- edward
- Member
- 7868 posts
- Joined: July 2005
- Offline
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.
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.
- schiho
- Member
- 101 posts
- Joined: Dec. 2012
- Offline
- edward
- Member
- 7868 posts
- Joined: July 2005
- Offline
http://www.sidefx.com/docs/houdini15.0/examples/python_panels/linkedparameters.pypanel [sidefx.com]
Look for addEventCallback.
Look for addEventCallback.
- schiho
- Member
- 101 posts
- Joined: Dec. 2012
- Offline
-
- Quick Links