Hi. I am making integration tests to verify that our production otl is always able to publish and load from the artists point of view. The issue that that I am having is that the callbacks happen after the script ends. For example a script that changes "parm1" which has a callback to set "parm2 with the same value as "parm1" won't trigger until my original script is over. In a case of a unittest I'd like to trigger those events. Another example of this issue is that I can't capture raised Errors from a Parm.pressButton(). I used a subnet with button that had a `raise ValueError('test')` as a callback. Which allowed me to test with
I think you'll probably need to provide an example file of what you're trying to do since there should be no issue running callbacks via non-graphical Houdini and a quick test results in the expected behavior.
In terms of catching errors related to pressButton(), you'll never be able to. While calling it is done in Python, there's no guarantee that the callback is executing Python so there's no real expectation that any errors experienced during the callback would bubble up as something useful. It's mildly inconvenient that it doesn't throw some general exception like hou.OperationFailed when something seems to go wrong though.
Graham Thompson, Technical Artist @ Rockstar Games
Yeah, this context is isolated and don't reraise which is unfortunate.
I think one solution is emulate this context and execute callback statement as string. It's just a bit tricky, depends on how artists use callbacks and do they use hda python modules. It's just you can trigger callback like hou.phm().on_parm()or kwargs["node"].hm().on_parm() I guess something like this could work (py 3.9)
try:# emulate expression contextexpression_ctx=globals()|{"kwargs":{"node":hou.node("/obj/geo1/subnet"),"parm":hou.parm("/obj/geo1/subnet/button")}}callback=hou.parm("/obj/geo1/subnet/button").parmTemplate().scriptCallback()callback=f"hou.cd('/obj/geo1/subnet')\n"+callback# if you use hou.phm() for callbackexec(callback,expression_ctx)exceptExceptionaserr:print(f"Catched:\n{err}")
@elovikov thanks for the time to answer. As I was preparing a scene file for Graham today, I was thinking too of emulating the callback by running it through python exec. Luckily I am working on a package that doesn't use the global kwargs, so as long as I replace the hou.pwd() or hou.phm() calls by hou.node("/full_path_here") I should be good to run my tests.
@Graham I made this scene quickly to illustrate callback issues. Ideally I would have also given a hython script to go with it that opens the scene and does the small test, but instead I made a scene with two subnets and in one of them you have a payload that runs a test and the other holds a callback setup. subnet1 has a string parm called string1 that when changed should change string2 value. In the current example the callback is never executed for me. Again I knwo that ideally I should have given a hython test, but I need more time to look at how to run hython from windows. I usually work with linux and so this a quick setup to illustrate while I also build a hython example if this still get's attention.
I think elovikov has the right answer in terms that we might not have the possibility to test user interface using the conventionnal callback implementation. We might need to make a custom test function that sets a value on a parm and then emulate the callback through trickery :P
It would have been nice if there was a manual way to trigger a pass of callback evaluation through the hou module.
So the problem with your hip file, and tests assuming that they matching what this is doing, is that when you set a parameter via Python (or hscript) it doesn't actually cause the parameter callback to execute: you need to call hou.Parm.PressButton() to execute it. Annoyingly there is no kwarg on set() that allows you to trigger it in one go like the hscript opparm -C flag.
In terms of the issue with hou.Parm.pressButton() being lame and not helpful for discerning if anything happened I would probably forgo any attempts at using it. If it were me I would have things set up so that my callback was calling into the PythonModule (or I guess it could just be importing and calling all at once) such that to "test" it I would just call the function in the HDA module directly.
To ensure nobody messes with the callback code I'd probably access the parameter template and validate the code it's running is what I expect and that the language is set to Python. Not necessarily ideal but that's how I'd just do this call. Alternatively depending on what the callback is calling into, you could maybe just mock that, actually run the callback, and validate it was called with the expected args.
Graham Thompson, Technical Artist @ Rockstar Games