{"id":1012,"date":"2023-05-18T13:25:16","date_gmt":"2023-05-18T20:25:16","guid":{"rendered":"https:\/\/www.toadstorm.com\/blog\/?p=1012"},"modified":"2023-05-18T13:25:16","modified_gmt":"2023-05-18T20:25:16","slug":"customizing-stock-nodes-in-houdini","status":"publish","type":"post","link":"https:\/\/www.toadstorm.com\/blog\/?p=1012","title":{"rendered":"Customizing stock nodes in Houdini"},"content":{"rendered":"\n<p><a rel=\"noreferrer noopener\" href=\"https:\/\/caseyhupke.com\" data-type=\"URL\" data-id=\"https:\/\/caseyhupke.com\" target=\"_blank\">Casey Hupke<\/a> asked an interesting question today: &#8220;how can I get a Color SOP to automatically update its node color to match the picked color?&#8221; This reminded me of some other pipeline work I&#8217;d done in the past to customize existing nodes in Houdini; for example, adding a few spare parms to the File Cache SOP or similar nodes to enable better default naming conventions and version control. <\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"1252\" height=\"648\" src=\"https:\/\/www.toadstorm.com\/blog\/wp-content\/uploads\/2023\/05\/download-3.gif\" alt=\"\" class=\"wp-image-1014\"\/><figcaption class=\"wp-element-caption\">The Color SOP updating its node color from the picked color, in real-time! (Sorry about the compression)<\/figcaption><\/figure>\n\n\n\n<p>If you&#8217;ve only dabbled in writing your own tools for Houdini, your first instinct when trying to solve a problem that an existing node doesn&#8217;t quite solve would be to write your own HDA, but that opens up a new can of worms: now you have a new node dependency in your scene that needs to either exist at your whole facility, or be embedded into the file if you need to share it for any reason. In a case like Casey&#8217;s, you really don&#8217;t need an entirely new tool that just wraps around a Color SOP&#8230; you just need a very slight tweak to the Color SOP that automates a node property. If you share this file with someone else who doesn&#8217;t have your custom configuration, it&#8217;s still just a plain old Color SOP to them!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Parameter Callbacks<\/h2>\n\n\n\n<p>Normally in Houdini, if you&#8217;re writing your own digital asset it&#8217;s pretty easy to get a custom script to fire when a parameter on your asset is modified. From the Type Properties window, you can select any parameter and look for the Callback Script:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"1915\" height=\"1171\" src=\"https:\/\/www.toadstorm.com\/blog\/wp-content\/uploads\/2023\/05\/instahcer_parmcallback.png\" alt=\"\" class=\"wp-image-1015\"\/><figcaption class=\"wp-element-caption\">The circled option is the callback script for the &#8220;Quick Add Objects&#8221; button.<\/figcaption><\/figure>\n\n\n\n<p>This script will fire anytime the parameter is modified by the user (meaning, not by some other automated process). If you&#8217;re not familiar with parameter callbacks using Python, let&#8217;s dissect this line:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">hou.phm().do_quick_select(kwargs)<\/pre>\n\n\n\n<p>The function <code>hou.phm()<\/code> means &#8220;this node definition&#8217;s Python module&#8221;. An HDA always has an included Python Module where you can store custom scripts related to that node. In this case, when this parameter is modified (it&#8217;s a button, so whenever the button is pushed) I want to find the function called <code>do_quick_select<\/code> in the Python Module, and run it with the mysterious <code>kwargs<\/code> argument.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">WTF is kwargs?<\/h3>\n\n\n\n<p>The <code>kwargs<\/code> argument shows up a lot in Houdini callbacks of all kinds, and it&#8217;s a little weird but very important to understand. When a callback function fires, it needs to be potentially aware of a lot of different things about the event that just happened: what node was modified, what parameter was modified, and so on. Each event is different and potentially carries different arguments across. Rather than require your Python functions to have individually named arguments for all of these possible values, it just stuffs all of them into a single Python dictionary full of these keyword arguments, or <code>kwargs<\/code>. So if you need to know what node was modified, <code>kwargs['node']<\/code> is the hou.Node object. If you need to know what parameter specifically changed, you have <code>kwargs['parm']<\/code>. A full list of these keyword arguments <em>for parameter callbacks specifically<\/em> is available <a rel=\"noreferrer noopener\" href=\"https:\/\/www.sidefx.com\/docs\/houdini\/hom\/locations.html#parameter_callback_scripts\" data-type=\"URL\" data-id=\"https:\/\/www.sidefx.com\/docs\/houdini\/hom\/locations.html#parameter_callback_scripts\" target=\"_blank\">here<\/a>. Note that there are other kinds of callbacks aside from parameter callbacks that will have different <code>kwargs<\/code>, such as Python tool states.<\/p>\n\n\n\n<p>In the case of <code>do_quick_select(kwargs)<\/code> shown above, the code stored in the HDA&#8217;s Python Module looks a bit like this:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\ndef do_quick_select(kwargs):\r\n    \"\"\" Fires when Quick Add parm is modified. Add all object paths to the multiparm list. \"\"\"\r\n    me = kwargs&#x5B;'node']\r\n    paths = me.evalParm('quick_add').split()\r\n    me.parm('quick_add').set(\"\")\r\n    if paths:\r\n        for i in paths:\r\n            index = me.parm('instanceobjects').evalAsInt()\r\n            # get path relative to instancer\r\n            relpath = me.relativePathTo(hou.node(i))\r\n            me.parm('instanceobjects').insertMultiParmInstance(index)\r\n            me.parm('instancepath' + str(index + 1)).set(relpath)\n<\/pre><\/div>\n\n\n<p>It&#8217;s not terribly important what this function is actually doing (it&#8217;s grabbing all your selected objects and stuffing them into the Instancer), but pay attention to how the function is defined: a single argument <code>kwargs<\/code>, and then the hou.Node object representing the Instancer that owns the button is quickly fetched from <code>kwargs['node']<\/code> and bound to <code>me<\/code> for convenience.<\/p>\n\n\n\n<p>Anyways, if we were writing a custom HDA for our modified Color SOP, we could just write one of these parameter callbacks on the Color parameter and be done with it. But if we don&#8217;t want to write a custom HDA just for this functionality, what needs to happen?<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">File-Based Digital Asset Event Handlers<\/h2>\n\n\n\n<p>If you&#8217;ve ever made your own HDAs in Houdini, you might have seen or played with the various <em>event handlers<\/em> that are available to HDAs. These event handlers reflect various things that can happen to HDAs during use: &#8220;On Created&#8221;, &#8220;On Input Changed&#8221;, etc. For example, here&#8217;s the &#8220;On Created&#8221; event script that fires inside the MOPs Instancer:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"1656\" height=\"1161\" src=\"https:\/\/www.toadstorm.com\/blog\/wp-content\/uploads\/2023\/05\/instancer_hda.png\" alt=\"\" class=\"wp-image-1013\"\/><figcaption class=\"wp-element-caption\">The OnCreated event script for the MOPs Instancer. It&#8217;s just setting the name to something easy to read (and lowercase so your OUT node stays on top!).<\/figcaption><\/figure>\n\n\n\n<p>Most often you find these event scripts buried inside the Type Properties window, but this isn&#8217;t the only place you can put them! It&#8217;s possible to place these event scripts as files on disk that Houdini will automatically recognize if they&#8217;re in the correct location. Check this <a rel=\"noreferrer noopener\" href=\"https:\/\/www.sidefx.com\/docs\/houdini\/hom\/locations.html#node_event_files\" data-type=\"URL\" data-id=\"https:\/\/www.sidefx.com\/docs\/houdini\/hom\/locations.html#node_event_files\" target=\"_blank\">easy-to-miss description<\/a> from the documentation:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>Files matching the pattern&nbsp;<code>HOUDINIPATH\/scripts\/<var>category<\/var>\/<var>nodename<\/var>_<var>event<\/var>.py<\/code>&nbsp;(for example,&nbsp;<code>$HOUDINI_USER_PREF_DIR\/scripts\/obj\/geo_OnCreated.py<\/code>) will run when the given event type occurs to a node of the given type.<\/p>\n<\/blockquote>\n\n\n\n<p>What this means is that for the Color SOP, for example, we can create a Python file at the location <code>$HOUDINI_PATH\/scripts\/sop\/color_OnCreated.py<\/code> and that script will run automatically anytime a Color SOP is created. No need to write a wrapper, we can just start making modifications from here!<\/p>\n\n\n\n<p>By the way, if you don&#8217;t know what the &#8220;programmatic&#8221; name of a SOP is for the purposes of these scripts, just check the Type Properties window and look at the very top. The name of the node will be just to the right of &#8220;Operator Type:&#8221; in bold. The Color SOP is mercifully just named &#8220;color&#8221;. <strong>Remember that this is case-sensitive, including the event name!<\/strong><\/p>\n\n\n\n<p>Now we have a possible hook into modifying this SOP for our own purposes, without creating any new nodes or whatever. There&#8217;s a bit more work to do, though, to create the equivalent of a parameter callback without making a new HDA.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Node Event Callbacks<\/h2>\n\n\n\n<p>Any node in Houdini can be instructed to fire what&#8217;s called a &#8220;callback&#8221; when certain properties of the node are changed. These changes are called <strong>events<\/strong>: for example, when an input is changed, when an upstream node cooks, or when a parameter is changed. A full list of these node-based events is <a rel=\"noreferrer noopener\" href=\"https:\/\/www.sidefx.com\/docs\/houdini\/hom\/hou\/nodeEventType.html\" data-type=\"URL\" data-id=\"https:\/\/www.sidefx.com\/docs\/houdini\/hom\/hou\/nodeEventType.html\" target=\"_blank\">here<\/a>. These callbacks can be added via the HOM function <code>hou.Node.addEventCallback()<\/code>. The documentation for this function is <a rel=\"noreferrer noopener\" href=\"https:\/\/www.sidefx.com\/docs\/houdini\/hom\/hou\/Node.html#addEventCallback\" data-type=\"URL\" data-id=\"https:\/\/www.sidefx.com\/docs\/houdini\/hom\/hou\/Node.html#addEventCallback\" target=\"_blank\">here<\/a>. Note one very important line here:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>Callbacks only persist for the current session. For example, they are not saved to the&nbsp;<code>.hip<\/code>&nbsp;file<\/p>\n<\/blockquote>\n\n\n\n<p>This means that we&#8217;ll need to apply our callback both when a Color SOP is created (the <code>OnCreated<\/code> event), and when it&#8217;s loaded from an existing file (the <code>OnLoaded<\/code> event). For the purposes of this example, both of these events are going to effectively be the same code. So let&#8217;s see some code:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\nimport hou\nimport traceback\ndef color_changed(node, event_type, **kwargs):\n    parm_tuple = kwargs&#x5B;'parm_tuple']\n    if parm_tuple is not None:\n        # print(parm_tuple.name())\n        if parm_tuple.name() == \"color\":\n            # the color parm was just modified\n            color = parm_tuple.eval()\n            hcolor = hou.Color(color)\n            try:\n                node.setColor(hcolor)\n            except:\n                # the node is probably locked. just ignore it.\n                pass\ntry:\n    me = kwargs&#x5B;'node']\n    if me is not None:\n        # print(\"creating callback\")\n        me.addEventCallback((hou.nodeEventType.ParmTupleChanged, ), color_changed)\nexcept:\n    print(traceback.format_exc())\n<\/pre><\/div>\n\n\n<p>There&#8217;s a little bit to break down here. First of all, check the formatting of the <code>color_changed<\/code> callback itself. The arguments are <code>node<\/code>, <code>event_type<\/code>, and <code>**kwargs<\/code>. All node event callback functions <strong>require <\/strong>the first two arguments, <code>node<\/code> and <code>event_type<\/code>. The third argument, <code>**kwargs<\/code>, represents the <code>kwargs<\/code> dictionary with all those handy values we might want. <\/p>\n\n\n\n<p>As an aside: the asterisks in front of <code>**kwargs<\/code> are something in Python called an <em>unpacking operator<\/em>. It more or less takes a list of positional arguments, like <code>pee=poo<\/code> and <code>butt=fart<\/code>, and turns them into a dictionary. It&#8217;s not terribly important to remember exactly why this is, but just remember to format your callbacks like this.<\/p>\n\n\n\n<p>Second, look towards the bottom there for <code>addEventCallback()<\/code>. This method of <code>hou.Node<\/code> has two arguments: a tuple of <code>hou.nodeEventType<\/code> names, and a callback function name. In this case, the only event that we want to trigger our callback is <code>hou.nodeEventType.ParmTupleChanged<\/code>, which fires whenever a parameter is modified. Again, in the <a href=\"https:\/\/www.sidefx.com\/docs\/houdini\/hom\/hou\/nodeEventType.html\" data-type=\"URL\" data-id=\"https:\/\/www.sidefx.com\/docs\/houdini\/hom\/hou\/nodeEventType.html\" target=\"_blank\" rel=\"noreferrer noopener\">documentation<\/a> for <code>hou.NodeEventType<\/code>, check out the description of <code>ParmTupleChanged<\/code>: <\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>Runs after a parameter value changes. You can get the new value using&nbsp;<a href=\"https:\/\/www.sidefx.com\/docs\/houdini\/hom\/hou\/ParmTuple.html#eval\">hou.ParmTuple.eval()<\/a>.<\/p>\n\n\n\n<p><strong>Extra keyword argument<\/strong>:&nbsp;<code>parm_tuple<\/code>&nbsp;(<a href=\"https:\/\/www.sidefx.com\/docs\/houdini\/hom\/hou\/ParmTuple.html\">hou.ParmTuple<\/a>).<\/p>\n<\/blockquote>\n\n\n\n<p>That <code>parm_tuple<\/code> keyword argument is what&#8217;s going to get passed along to <code>**kwargs<\/code> in our <code>color_changed<\/code> callback function! That&#8217;s how we know exactly which parameter was just changed. Now let&#8217;s scan the code again&#8230; at the bottom, we&#8217;re adding an event callback to our node that fires whenever a parameter is changed, and that callback&#8217;s name is color_changed. At the top, our <code>color_changed<\/code> function gets the <code>parm_tuple<\/code> keyword argument and makes sure it&#8217;s valid (is not None), and then checks the name of the parameter. If the parameter&#8217;s name is &#8220;color&#8221;, which is the actual name of the &#8220;Color&#8221; parameter on the Color SOP, then we evaluate that parameter and convert it to a <code>hou.Color<\/code> object, then set the node&#8217;s color to that <code>hou.Color<\/code>. That&#8217;s the whole thing!<\/p>\n\n\n\n<p>Save that entire script to <code>$HOUDINI_PATH\/scripts\/sop\/color_OnCreated.py<\/code> and <code>$HOUDINI_PATH\/scripts\/sop\/color_OnLoaded.py<\/code> and restart Houdini, and you should see the node color update as you pick new colors. Amazing!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Here&#8217;s a cat<\/h2>\n\n\n\n<p>Too much text at once. Here&#8217;s a cat.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"4032\" height=\"1908\" src=\"https:\/\/www.toadstorm.com\/blog\/wp-content\/uploads\/2023\/05\/20230514_193058.jpg\" alt=\"\" class=\"wp-image-1033\"\/><figcaption class=\"wp-element-caption\">Dita sporting a fresh new haircut.<\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Adding spare parms<\/h2>\n\n\n\n<p>Here&#8217;s another example. Let&#8217;s say you want the ROP Alembic Output SOP to have a version control parameter, similar to the updated File Cache SOP. You <em>could<\/em> again write a wrapper HDA, but you could also just add a few spare parameters to the ROP Alembic Output SOP and automate this whole thing without adding extra dependencies to your files. Here&#8217;s how this could look:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\nimport hou\nimport traceback\n\ntry:\n    # get the node that was just created\n    me = kwargs&#x5B;'node']\n\n    # create \"version\" spare parameter.\n    # first get the ParmTemplateGroup of the HDA. this is the overall parameter layout.\n    parm_group = me.parmTemplateGroup()\n\n    # now create a new ParmTemplate for the \"version\" spare parm. This is an integer slider.\n    version_template = hou.IntParmTemplate(name=\"version\", label=\"Version\", num_components=1, min=1, default_value=(1, 1, 1))\n\n    # we'll put this new spare parm right before the \"frame range\" parameter, named \"trange\".\n    range_template = parm_group.find(\"trange\")\n    parm_group.insertBefore(range_template, version_template)\n\n    # now we need to write this modified ParmTemplateGroup back to the individual node's ParmTemplateGroup.\n    # this is effectively how you add a spare parm to a node without modifying the HDA itself.\n    me.setParmTemplateGroup(parm_group)\n\n    # finally, change the default output path so that it uses this version number (with 3-digit padding).\n    me.parm(\"filename\").set('$HIP\/output_`padzero(3, ch(\"version\"))`.abc')\n\nexcept:\n    # just in case a ROP Alembic Output SOP is created as a locked node by something else. don't need to see errors.\n    pass\n<\/pre><\/div>\n\n\n<p>This is a lot of code, but basically this is adding a spare parameter to any newly created ROP Alembic Output SOP named &#8220;version&#8221; and then setting the default output path to an expression that references this new version control. In HOM terms, you&#8217;re reading the node&#8217;s <code>ParmTemplateGroup<\/code>, creating a new <code>IntParmTemplate<\/code> (a spare parameter definition), inserting it into the <code>ParmTemplateGroup<\/code> we read earlier, and then writing this new <code>ParmTemplateGroup<\/code> definition back onto the node because you can&#8217;t modify them in-place.<\/p>\n\n\n\n<p>Save this to <code>$HOUDINI_PATH\/scripts\/sop\/rop_alembic_OnCreated.py<\/code> and your newly-created ROP Alembic Output SOPs will look like this:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"1233\" height=\"820\" src=\"https:\/\/www.toadstorm.com\/blog\/wp-content\/uploads\/2023\/05\/rop_alembic_update.png\" alt=\"\" class=\"wp-image-1017\"\/><figcaption class=\"wp-element-caption\">Check out our fancy new version slider!<\/figcaption><\/figure>\n\n\n\n<p>I hope this helps open up some new doors for your own personal pipeline (or your studio&#8217;s pipeline!). Avoiding introducing new node dependencies just for minor tweaks to existing nodes can really help keep your pipeline lean and make it easier to share files in the future.<\/p>\n\n\n<p><!-- \/wp:preformatted --><\/p>","protected":false},"excerpt":{"rendered":"<p>Casey Hupke asked an interesting question today: &#8220;how can I get a Color SOP to automatically update its node color to match the picked color?&#8221; This reminded me of some other pipeline work I&#8217;d done in the past to customize existing nodes in Houdini; for example, adding a few spare [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1014,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[30],"tags":[31,11,9],"_links":{"self":[{"href":"https:\/\/www.toadstorm.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1012"}],"collection":[{"href":"https:\/\/www.toadstorm.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.toadstorm.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.toadstorm.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.toadstorm.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1012"}],"version-history":[{"count":18,"href":"https:\/\/www.toadstorm.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1012\/revisions"}],"predecessor-version":[{"id":1035,"href":"https:\/\/www.toadstorm.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1012\/revisions\/1035"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.toadstorm.com\/blog\/index.php?rest_route=\/wp\/v2\/media\/1014"}],"wp:attachment":[{"href":"https:\/\/www.toadstorm.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1012"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.toadstorm.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1012"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.toadstorm.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1012"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}