Python exercises in Houdini ?

   19165   40   1
User Avatar
Member
250 posts
Joined: 2月 2013
Offline
Nodoby ?
https://vimeo.com/obreadytom [vimeo.com]
User Avatar
Member
678 posts
Joined: 7月 2005
Offline
Ok, I got some free time.

1. It doesn't make sense to make make this tool so it could also work on SOP level. Why? Well, if you have selected nodes on SOP level, it's easier to just press bypass flag on one of them and they all get Bypassed. Of course you may want to for example have tool that finds all the nodes of type you have currently selected in current network, but this wasn't the goal for this tool. So lets stick to making this tool work only on OBJ level. Later you may expand it if you want.
2. I will show you begin of this tool, how I would write it, and then you will continue from there looking at my code as a reference.

Since we only want OBJ nodes, with selectObjects() function we can be sure that we will be on OBJ level and that we can only select ‘geo’ nodes.
As you can also see I got two return statements there. One returns (None, None) and second returns selected nodes. (None, None) is in case user by accident didn't selected any nods and pressed <Enter>. Why return None two times? Because selectObjects() function return tuple, so we would like to also return tuple if we didn't selected anything. I'm also displaying error window instead of StatusBar message to make sure that user noticed that he made mistake.

import toolutils

def SelectGeoNodes():
“”“ ”“”
sViewer = toolutils.sceneViewer()
geoNodes = sViewer.selectObjects('Select geometry, press enter to confirm', allowed_types'geo',), allow_multisel=True)
if geoNodes == ():
hou.ui.displayMessage('Geometry not selected, terminating tool', severity=hou.severityType.Error)
return (None, None)

return geoNodes


You have to write implementation for this function. As you can see it returns tuple of two values, because readInput() function that you will use there also return two values, button that was pressed and string that you passed into it. (state, None) should be returned if user pressed cancel button. Otherwise return (state, nodetypes). Look at previous function how you should approach this.

def SpecifyNodeTypeAndState():
“”“ ”“”
return (None, None)


You have to write implementation for this function. Just like in two previous functions you should return None only if there was no specified node types found, otherwise return all the nodes of the types user specified in previous function. Since we only return nodes and nothing beside them, for case when we didn't found any nodes we should return None only once two.

def FindSpecifiedNodes(geoNodes, nodeTypes):
“”“ ”“”
return None


You have to write implementation for this function. This function shouldn't return anything. Pass keyword is there as a placeholder because otherwise python will throw error if you leave blank function without any code. When you write implementation for it, you can remove this keyword. What this function should do is to break execution if user pressed cancel button when he was picking nodes types. This should be the first thing your code should do. Only after making sure that user didn't pressed Cancel you should execute the rest of the code in this function.

def DisableOrEnableNodes(nodes, state):
“”“ ”“”
pass


As you can see, this function is mostly for executing other functions and making checks do we even want to continue executing next function in it.

def DisableOrEnableNodes_Tool():
“”“ Disables or enables node(s) of specified type in selected OBJ geo nodes ”“”

#select geometry
geoNodes = SelectGeoNodes()
if geoNodes == (None, None):
return

#specify node type to find
state, nodeType = SpecifyNodeTypeAndState()
if nodeType == None:
return

#find specified nodes
nodes = FindSpecifiedNodes(geoNodes, nodeType)
if nodes == None:
return

#set nodes state
DisableOrEnableNodes(nodes, state)


DisableOrEnableNodes_Tool()


This should push you a little further. Hopefully

EDIT: One more thing. You may want to add another function to this, that will take care of filtering string that was passed in SpecifyNodeTypeAndState(). So before you return nodeTypes from SpecifyNodeTypeAndState() make sure to filter it correctly to extract each node type and construct list of nodeTypes that you will return with state. Think about it.
User Avatar
Member
250 posts
Joined: 2月 2013
Offline
Hi Mantragora,

Thanks, I'll study all of this tonight !

“Hopefully” don't give up on me please I know that I'm a bit slow haha :'(
https://vimeo.com/obreadytom [vimeo.com]
User Avatar
Member
678 posts
Joined: 7月 2005
Offline
Thinking about this more I think that I made a mistake. DisableOrEnableNodes() function shouldn't check if the user pressed “Cancel” button, this check should be made in DisableOrEnableNodes_Tool() function and than and only then, if user didn't pressed “Cancel”, DisableOrEnableNodes() function should be executed. DisableOrEnableNodes() should only care about turning on/off nodes, so the only two states it should care is 0 and 1.

Below fixed version of DisableOrEnableNodes_Tool() function. Note the change in last two lines of the function body.

def DisableOrEnableNodes_Tool():
“”“ Disables or enables node(s) of specified type in selected OBJ geo nodes ”“”

#select geometry
geoNodes = SelectGeoNodes()
if geoNodes == (None, None):
return

#specify node type to find
state, nodeType = SpecifyNodeTypeAndState()
if nodeType == None:
return

#find specified nodes
nodes = FindSpecifiedNodes(geoNodes, nodeType)
if nodes == None:
return

#set nodes state
if state != 2:
DisableOrEnableNodes(nodes, state)

—————————————————————-
DisableOrEnableNodes_Tool()
User Avatar
Member
250 posts
Joined: 2月 2013
Offline
I'm only starting, but it seems that the hou.SceneViewer() function allow me to only select in the scene view, but what if all my objects are already bypass ? Then I can't “non” bypass them, because there nothing to select. I there the same in the newtork view ? But only at object level of course.

EDIT : And I think that I'm doing wrong in the main function, DisableOrEnableNodes_Tool(), I don't really know how to use the variables that I returned for starting or not the other functions… I always have "global name ‘blabla’ is not defined', even if it seems to defined.

For example “state”, it's return by the SpecifyNodeTypeAndState() function, but if I'm calling it in the main function, DisableOrEnableNodes_Tool(), for continuing or not the tool, it's telling me that it's not defined.

EDIT 2 : COME ON I'm such a dick, I read your message at least three times and I'm not even capable of reading it correcly. Forget the last edit I think I got my answer.
https://vimeo.com/obreadytom [vimeo.com]
User Avatar
Member
678 posts
Joined: 7月 2005
Offline
Lewul
I'm only starting, but it seems that the hou.SceneViewer() function allow me to only select in the scene view, but what if all my objects are already bypass ? Then I can't “non” bypass them, because there nothing to select. I there the same in the newtork view ? But only at object level of course.

What? o_O

You can select nodes too, it's just that you have to accept selection while your cursor is in viewport.
User Avatar
Member
250 posts
Joined: 2月 2013
Offline
Oh, right…

The code is working now, but, you did all the work here. But it helped me a lot, I start to understand how the return works. I will try to add another function, as you suggested.
https://vimeo.com/obreadytom [vimeo.com]
User Avatar
Member
678 posts
Joined: 7月 2005
Offline
One more thing. The variable you are returing doesn't have to be called the same like the variable you are storing it in. Example:


def Foo():
calculation = 1 + 1

return calculation

—————————–
val = Foo()
something = Foo()
addition = Foo()


See?

One more thing to the previous post. This behavior that you accept in viewport is common for all shelf tools in Houdini. Shelf tools shouldn't accept while you are in network view.

You can later on make check where it was called, it it was from shelf than it will need confirmation in viewport. But if if was called from TAB menu in network view, than it could behave differently.

But lets stick to one task at a time and make it work from shelf for now.
User Avatar
Member
250 posts
Joined: 2月 2013
Offline
Yes, I see !


“One more thing to the previous post. This behavior that you accept in viewport is common for all shelf tools in Houdini. Shelf tools shouldn't accept while you are in network view.”

Indeed, I wasn't realizing when scripting…


One more question :

why the return alone in this case ? :

def DisableOrEnableNodes_Tool():
“”“ Disables or enables node(s) of specified type in selected OBJ geo nodes ”“”

#select geometry
geoNodes = SelectGeoNodes()
if geoNodes == (None, None):
return

#specify node type to find
state, nodeType = SpecifyNodeTypeAndState()
if nodeType == None:
return

#find specified nodes
nodes = FindSpecifiedNodes(geoNodes, nodeType)
if nodes == None:
return

#set nodes state
if state != 2:
DisableOrEnableNodes(nodes, state)
https://vimeo.com/obreadytom [vimeo.com]
User Avatar
Member
678 posts
Joined: 7月 2005
Offline
Return doesn't have to return value with it. It just signals that you want to finish execution of the function in this particular place. Example:


def Foo():
text = “whoa”

return

print (text)


——————–
Foo()


When you look at this function you probably think that it will print ‘whoa’? It will not because it stops execution of this function when it hits return keyword.

That is why we have two or more return keyword in some functions. Because once we have what we want we don't want to execute the rest of the code and just exit from the function.
User Avatar
Member
250 posts
Joined: 2月 2013
Offline
Oh okay, good to know. Thanks !
https://vimeo.com/obreadytom [vimeo.com]
User Avatar
Member
678 posts
Joined: 7月 2005
Offline
…AND?

Man, 300 $ million production stopped because you didn't delivered this tool on time! Thanks to you another couple post-production houses will go down.

Happy weekend!
User Avatar
Member
250 posts
Joined: 2月 2013
Offline
Damn So this is why the trailer of The Hobbit is getting late…

I'm sorry, it's finished but I didn't have time to add the function (but I know it's not big), I'm still in class (yes it finishes late. But no excuses, I should've given you the last version earlier).

So here is the last version.

I want to thank you for everything you did, I learned a lot from you and with this little exercice.

Do you think that you can give me another exercice for the summer ? Since I will be in internship, I don't know if I will have enough time but I really want to force myself to study Python in Houdini. If you can't or don't want I totally understand, I'm slow and I don't want to bother you with this.

Thanks again !

Tom

Attachments:
Bypass.shelf (4.7 KB)

https://vimeo.com/obreadytom [vimeo.com]
User Avatar
Member
250 posts
Joined: 2月 2013
Offline
Did I said something wrong ?
https://vimeo.com/obreadytom [vimeo.com]
User Avatar
Member
48 posts
Joined: 6月 2011
Offline
Well, I could suggest a thoroughly useful exercise (to me!)…

I myself just took my first steps into Python quite literally yesterday (despite being an experienced Houdini effects artist otherwise) and while I'm having a hard time getting my head around the basics, it's looking promising.

The tool I'm hoping to work out is to have a “cache” node (ie, a subnetwork with a ROP Output, and a File node to read it back in), as already exists, except that it will automatically handle cache versioning.

I'm picturing it working something like this:
The default output path is typically something like:
$HIP/$OS_v001/$OS.$F4.bgeo

- When you hit “render”, it will automatically search inside $HIP for existing directories with the root $OS_ and ensure it updates the render output path to the next available version number.
- It populates a list/pulldown/something with all the available version numbers which match the $OS_ pattern, and which you can explicitly pick from for reading the cache back in.
- It has a checkbox override “use latest version”, which will cause it to ignore the version list, and always pick the latest. It should auto-update once you cache a new version.
- Maybe it would need a “Refresh” button, rather than just having the script run on demand… depends how effective it would be at picking up changes I suppose.


I'm currently poking around with os.listdir inside Menu Scripts on Order Menu parameters, but I'm having a hard time getting anywhere so far.


So yep, if you felt like giving that a try, I would be overjoyed to shamelessly benefit from your efforts :-)
User Avatar
Member
250 posts
Joined: 2月 2013
Offline
Sounds complicated
https://vimeo.com/obreadytom [vimeo.com]
User Avatar
Member
48 posts
Joined: 6月 2011
Offline
Haha, well, I was hoping it would be fairly straightforward to begin with… I could possibly have managed most of it with hscript, except there's no way to look outside of Houdini that way to actually get directory listings and such.

Getting the right output from python's os functions and working out how to manipulate strings are my first stumbling blocks. I suspect that part at least would be a walk in the park for an experience python coder… but for the moment I'm lost.
User Avatar
Member
387 posts
Joined: 11月 2008
Offline
It's not that hard.
Look at os.path python module. It can handle most of the string manipulation of this task.

You can dynamicaly build menu items on Menu Script tab in Parameter Description.
matchingDirVersions =
matchingDirLabels =

if len(matchingDirVersions) != len(matchingDirLabels):
return #both item and label list must have same length

menuItems =
for menuItem, menuLabel in zip(matchingDirVersions, matchingDirLabels):
menuItems.append(menuItem)
menuItems.append(menuLabel) #or you can use menuItem for label too instead
return menuItems

For the correct update you can use Callback Script on each element. For example you can add calls of your own functions with the Callback Script of the Render button (of the File Cache HDA) so it updates output path to the new location before it calls render() function of rop_sop and add another function call at the end of this Callback Script to refresh version list when “Use Latest Version” toggle is on.

Also look at PythonModule Event Handler. It's easier to have your code in module inside asset and then in Callback Script only call functions from this module.
User Avatar
Member
678 posts
Joined: 7月 2005
Offline
Lewul
Did I said something wrong ?

No, I just had no time. I will write something more this night or tomorrow morning.

So how it's going with your second task?
User Avatar
Member
48 posts
Joined: 6月 2011
Offline
Well, after pezetko's helpful post, I started to make some amount of progress learning my first basic python and making some headroom on the task I mentioned. Given that I sort of hijacked the thread for my own ends, I've attached my own progress in case it's helpful. (or just for you to laugh at…)

As it stands, I'm trying to work out an elegant way to have it realise whenever there are no found caches, and have it auto-fill a _v001 path in those cases, which works when the list has previously been filled with “none”, and that option is selected (ie, the list = 0). But if previously you've picked another option on the list, and then renamed the node, it keeps option # selected, and so doesn't find the word “none” to flag that nothing was found… and I can't seem to get it to match “” :-)

I suspect I'm making a complete mess of things, but at least the first stages are looking promising.

(edit: incidentally, so far I'm only doing some string-handling to get the path to update correctly on the correct events… the rop output and file nodes are just placeholders, and there's nothing actually causing any caching to happen)

Attachments:
cacheHandlerWIP.otl (6.0 KB)

  • Quick Links