[{"data":1,"prerenderedAt":1944},["ShallowReactive",2],{"blog-\u002Fblog\u002Fsetting-multiple-houdini-parameters-with-setparms":3},{"article":4,"newer":1940,"older":1926},{"id":5,"title":6,"body":7,"canonical":1926,"date":1927,"description":1928,"draft":1926,"extension":1929,"image":1926,"imageAlt":1926,"meta":1930,"navigation":174,"path":1931,"seo":1932,"status":1933,"stem":1934,"tags":1935,"updated":1926,"__hash__":1939},"blog\u002Fblog\u002Fsetting-multiple-houdini-parameters-with-setparms.md","Setting Multiple Houdini Parameters with hou.Node.setParms()",{"type":8,"value":9,"toc":1914},"minimark",[10,14,92,99,182,185,190,193,325,339,393,403,407,410,542,549,566,569,665,668,672,681,687,690,951,954,1008,1025,1029,1035,1051,1057,1060,1145,1154,1161,1250,1253,1259,1266,1303,1306,1375,1379,1382,1732,1735,1807,1810,1814,1821,1840,1846,1850,1881,1885,1910],[11,12,13],"p",{},"This started as a note to myself after another Houdini setup script turned into a wall of repeated parameter calls:",[15,16,21],"pre",{"className":17,"code":18,"language":19,"meta":20,"style":20},"language-python shiki shiki-themes github-light github-dark","node.parm(\"tx\").set(1.0)\nnode.parm(\"ty\").set(2.0)\nnode.parm(\"tz\").set(3.0)\nnode.parm(\"scale\").set(1.25)\n","python","",[22,23,24,47,62,77],"code",{"__ignoreMap":20},[25,26,29,33,37,40,44],"span",{"class":27,"line":28},"line",1,[25,30,32],{"class":31},"sVt8B","node.parm(",[25,34,36],{"class":35},"sZZnC","\"tx\"",[25,38,39],{"class":31},").set(",[25,41,43],{"class":42},"sj4cs","1.0",[25,45,46],{"class":31},")\n",[25,48,50,52,55,57,60],{"class":27,"line":49},2,[25,51,32],{"class":31},[25,53,54],{"class":35},"\"ty\"",[25,56,39],{"class":31},[25,58,59],{"class":42},"2.0",[25,61,46],{"class":31},[25,63,65,67,70,72,75],{"class":27,"line":64},3,[25,66,32],{"class":31},[25,68,69],{"class":35},"\"tz\"",[25,71,39],{"class":31},[25,73,74],{"class":42},"3.0",[25,76,46],{"class":31},[25,78,80,82,85,87,90],{"class":27,"line":79},4,[25,81,32],{"class":31},[25,83,84],{"class":35},"\"scale\"",[25,86,39],{"class":31},[25,88,89],{"class":42},"1.25",[25,91,46],{"class":31},[11,93,94,95,98],{},"For two or three values, that is perfectly fine. Once a tool configures most of a node, a dictionary passed to ",[22,96,97],{},"setParms()"," is much easier for me to scan and change.",[15,100,102],{"className":17,"code":101,"language":19,"meta":20,"style":20},"values = {\n    \"tx\": 1.0,\n    \"ty\": 2.0,\n    \"tz\": 3.0,\n    \"scale\": 1.25,\n}\n\nnode.setParms(values)\n",[22,103,104,116,129,140,151,163,169,176],{"__ignoreMap":20},[25,105,106,109,113],{"class":27,"line":28},[25,107,108],{"class":31},"values ",[25,110,112],{"class":111},"szBVR","=",[25,114,115],{"class":31}," {\n",[25,117,118,121,124,126],{"class":27,"line":49},[25,119,120],{"class":35},"    \"tx\"",[25,122,123],{"class":31},": ",[25,125,43],{"class":42},[25,127,128],{"class":31},",\n",[25,130,131,134,136,138],{"class":27,"line":64},[25,132,133],{"class":35},"    \"ty\"",[25,135,123],{"class":31},[25,137,59],{"class":42},[25,139,128],{"class":31},[25,141,142,145,147,149],{"class":27,"line":79},[25,143,144],{"class":35},"    \"tz\"",[25,146,123],{"class":31},[25,148,74],{"class":42},[25,150,128],{"class":31},[25,152,154,157,159,161],{"class":27,"line":153},5,[25,155,156],{"class":35},"    \"scale\"",[25,158,123],{"class":31},[25,160,89],{"class":42},[25,162,128],{"class":31},[25,164,166],{"class":27,"line":165},6,[25,167,168],{"class":31},"}\n",[25,170,172],{"class":27,"line":171},7,[25,173,175],{"emptyLinePlaceholder":174},true,"\n",[25,177,179],{"class":27,"line":178},8,[25,180,181],{"class":31},"node.setParms(values)\n",[11,183,184],{},"The dictionary keys are Houdini parameter names, not their labels in the parameter editor.",[186,187,189],"h2",{"id":188},"parameter-tuples-work-too","Parameter tuples work too",[11,191,192],{},"You do not have to address every component separately. Houdini also accepts the base name of a parameter tuple.",[15,194,196],{"className":17,"code":195,"language":19,"meta":20,"style":20},"node = hou.node(\"\u002Fobj\u002Fgeo1\")\n\nif node is None:\n    raise hou.NodeError(\"Could not find \u002Fobj\u002Fgeo1\")\n\nnode.setParms({\n    \"t\": (0.0, 1.0, 0.0),\n    \"r\": (0.0, 45.0, 0.0),\n    \"s\": (1.25, 1.25, 1.25),\n})\n",[22,197,198,213,217,234,247,251,256,279,299,319],{"__ignoreMap":20},[25,199,200,203,205,208,211],{"class":27,"line":28},[25,201,202],{"class":31},"node ",[25,204,112],{"class":111},[25,206,207],{"class":31}," hou.node(",[25,209,210],{"class":35},"\"\u002Fobj\u002Fgeo1\"",[25,212,46],{"class":31},[25,214,215],{"class":27,"line":49},[25,216,175],{"emptyLinePlaceholder":174},[25,218,219,222,225,228,231],{"class":27,"line":64},[25,220,221],{"class":111},"if",[25,223,224],{"class":31}," node ",[25,226,227],{"class":111},"is",[25,229,230],{"class":42}," None",[25,232,233],{"class":31},":\n",[25,235,236,239,242,245],{"class":27,"line":79},[25,237,238],{"class":111},"    raise",[25,240,241],{"class":31}," hou.NodeError(",[25,243,244],{"class":35},"\"Could not find \u002Fobj\u002Fgeo1\"",[25,246,46],{"class":31},[25,248,249],{"class":27,"line":153},[25,250,175],{"emptyLinePlaceholder":174},[25,252,253],{"class":27,"line":165},[25,254,255],{"class":31},"node.setParms({\n",[25,257,258,261,264,267,270,272,274,276],{"class":27,"line":171},[25,259,260],{"class":35},"    \"t\"",[25,262,263],{"class":31},": (",[25,265,266],{"class":42},"0.0",[25,268,269],{"class":31},", ",[25,271,43],{"class":42},[25,273,269],{"class":31},[25,275,266],{"class":42},[25,277,278],{"class":31},"),\n",[25,280,281,284,286,288,290,293,295,297],{"class":27,"line":178},[25,282,283],{"class":35},"    \"r\"",[25,285,263],{"class":31},[25,287,266],{"class":42},[25,289,269],{"class":31},[25,291,292],{"class":42},"45.0",[25,294,269],{"class":31},[25,296,266],{"class":42},[25,298,278],{"class":31},[25,300,302,305,307,309,311,313,315,317],{"class":27,"line":301},9,[25,303,304],{"class":35},"    \"s\"",[25,306,263],{"class":31},[25,308,89],{"class":42},[25,310,269],{"class":31},[25,312,89],{"class":42},[25,314,269],{"class":31},[25,316,89],{"class":42},[25,318,278],{"class":31},[25,320,322],{"class":27,"line":321},10,[25,323,324],{"class":31},"})\n",[11,326,327,328,269,331,334,335,338],{},"Here, ",[22,329,330],{},"t",[22,332,333],{},"r",", and ",[22,336,337],{},"s"," refer to the translate, rotate, and scale tuples on the object node. The same call can mix tuple values and individual scalar parameters.",[15,340,342],{"className":17,"code":341,"language":19,"meta":20,"style":20},"node.setParms({\n    \"t\": (0.0, 1.0, 0.0),\n    \"ry\": 90.0,\n    \"scale\": 1.5,\n})\n",[22,343,344,348,366,378,389],{"__ignoreMap":20},[25,345,346],{"class":27,"line":28},[25,347,255],{"class":31},[25,349,350,352,354,356,358,360,362,364],{"class":27,"line":49},[25,351,260],{"class":35},[25,353,263],{"class":31},[25,355,266],{"class":42},[25,357,269],{"class":31},[25,359,43],{"class":42},[25,361,269],{"class":31},[25,363,266],{"class":42},[25,365,278],{"class":31},[25,367,368,371,373,376],{"class":27,"line":64},[25,369,370],{"class":35},"    \"ry\"",[25,372,123],{"class":31},[25,374,375],{"class":42},"90.0",[25,377,128],{"class":31},[25,379,380,382,384,387],{"class":27,"line":79},[25,381,156],{"class":35},[25,383,123],{"class":31},[25,385,386],{"class":42},"1.5",[25,388,128],{"class":31},[25,390,391],{"class":27,"line":153},[25,392,324],{"class":31},[11,394,395,396,398,399,402],{},"I generally pick one style per tuple. Mixing ",[22,397,333],{}," and ",[22,400,401],{},"ry"," in the same dictionary makes the result depend on assignment order and is harder to review.",[186,404,406],{"id":405},"the-production-use-case-building-a-node-preset","The production use case: building a node preset",[11,408,409],{},"The pattern becomes more useful when the values come from a preset, project configuration, or UI option.",[15,411,413],{"className":17,"code":412,"language":19,"meta":20,"style":20},"import hou\n\ngeo = hou.node(\"\u002Fobj\").createNode(\"geo\", \"pyro_preset_example\")\nsimulation_node = geo.createNode(\"pyrosolver\", \"pyro_solver1\")\n\nsimulation_settings = {\n    \"startframe\": int(hou.playbar.frameRange()[0]),\n    \"substeps\": 2,\n    \"timescale\": 1.0,\n}\n\nsimulation_node.setParms(simulation_settings)\n",[22,414,415,423,427,452,472,476,485,504,516,527,531,536],{"__ignoreMap":20},[25,416,417,420],{"class":27,"line":28},[25,418,419],{"class":111},"import",[25,421,422],{"class":31}," hou\n",[25,424,425],{"class":27,"line":49},[25,426,175],{"emptyLinePlaceholder":174},[25,428,429,432,434,436,439,442,445,447,450],{"class":27,"line":64},[25,430,431],{"class":31},"geo ",[25,433,112],{"class":111},[25,435,207],{"class":31},[25,437,438],{"class":35},"\"\u002Fobj\"",[25,440,441],{"class":31},").createNode(",[25,443,444],{"class":35},"\"geo\"",[25,446,269],{"class":31},[25,448,449],{"class":35},"\"pyro_preset_example\"",[25,451,46],{"class":31},[25,453,454,457,459,462,465,467,470],{"class":27,"line":79},[25,455,456],{"class":31},"simulation_node ",[25,458,112],{"class":111},[25,460,461],{"class":31}," geo.createNode(",[25,463,464],{"class":35},"\"pyrosolver\"",[25,466,269],{"class":31},[25,468,469],{"class":35},"\"pyro_solver1\"",[25,471,46],{"class":31},[25,473,474],{"class":27,"line":153},[25,475,175],{"emptyLinePlaceholder":174},[25,477,478,481,483],{"class":27,"line":165},[25,479,480],{"class":31},"simulation_settings ",[25,482,112],{"class":111},[25,484,115],{"class":31},[25,486,487,490,492,495,498,501],{"class":27,"line":171},[25,488,489],{"class":35},"    \"startframe\"",[25,491,123],{"class":31},[25,493,494],{"class":42},"int",[25,496,497],{"class":31},"(hou.playbar.frameRange()[",[25,499,500],{"class":42},"0",[25,502,503],{"class":31},"]),\n",[25,505,506,509,511,514],{"class":27,"line":178},[25,507,508],{"class":35},"    \"substeps\"",[25,510,123],{"class":31},[25,512,513],{"class":42},"2",[25,515,128],{"class":31},[25,517,518,521,523,525],{"class":27,"line":301},[25,519,520],{"class":35},"    \"timescale\"",[25,522,123],{"class":31},[25,524,43],{"class":42},[25,526,128],{"class":31},[25,528,529],{"class":27,"line":321},[25,530,168],{"class":31},[25,532,534],{"class":27,"line":533},11,[25,535,175],{"emptyLinePlaceholder":174},[25,537,539],{"class":27,"line":538},12,[25,540,541],{"class":31},"simulation_node.setParms(simulation_settings)\n",[11,543,544],{},[545,546],"img",{"alt":547,"src":548},"Houdini 21 Pyro Solver with Start Frame, Time Scale, and Max Substeps visible","\u002Fblog\u002Fhoudini-pyro-solver-setparms.png",[11,550,551],{},[552,553,554,555,558,559,562,563,565],"em",{},"The Houdini 21 Pyro Solver SOP using the values from this example: Start Frame ",[22,556,557],{},"1001",", Time Scale ",[22,560,561],{},"1",", and Max Substeps ",[22,564,513],{},".",[11,567,568],{},"Because the settings are data rather than a sequence of calls, I can merge overrides, print the final state for debugging, or save it elsewhere.",[15,570,572],{"className":17,"code":571,"language":19,"meta":20,"style":20},"base_settings = {\n    \"substeps\": 2,\n    \"timescale\": 1.0,\n}\n\nshot_overrides = {\n    \"substeps\": 4,\n}\n\nfinal_settings = {**base_settings, **shot_overrides}\nsimulation_node.setParms(final_settings)\n",[22,573,574,583,593,603,607,611,620,631,635,639,660],{"__ignoreMap":20},[25,575,576,579,581],{"class":27,"line":28},[25,577,578],{"class":31},"base_settings ",[25,580,112],{"class":111},[25,582,115],{"class":31},[25,584,585,587,589,591],{"class":27,"line":49},[25,586,508],{"class":35},[25,588,123],{"class":31},[25,590,513],{"class":42},[25,592,128],{"class":31},[25,594,595,597,599,601],{"class":27,"line":64},[25,596,520],{"class":35},[25,598,123],{"class":31},[25,600,43],{"class":42},[25,602,128],{"class":31},[25,604,605],{"class":27,"line":79},[25,606,168],{"class":31},[25,608,609],{"class":27,"line":153},[25,610,175],{"emptyLinePlaceholder":174},[25,612,613,616,618],{"class":27,"line":165},[25,614,615],{"class":31},"shot_overrides ",[25,617,112],{"class":111},[25,619,115],{"class":31},[25,621,622,624,626,629],{"class":27,"line":171},[25,623,508],{"class":35},[25,625,123],{"class":31},[25,627,628],{"class":42},"4",[25,630,128],{"class":31},[25,632,633],{"class":27,"line":178},[25,634,168],{"class":31},[25,636,637],{"class":27,"line":301},[25,638,175],{"emptyLinePlaceholder":174},[25,640,641,644,646,649,652,655,657],{"class":27,"line":321},[25,642,643],{"class":31},"final_settings ",[25,645,112],{"class":111},[25,647,648],{"class":31}," {",[25,650,651],{"class":111},"**",[25,653,654],{"class":31},"base_settings, ",[25,656,651],{"class":111},[25,658,659],{"class":31},"shot_overrides}\n",[25,661,662],{"class":27,"line":533},[25,663,664],{"class":31},"simulation_node.setParms(final_settings)\n",[11,666,667],{},"The override is visible instead of being hidden several lines later in the function.",[186,669,671],{"id":670},"validate-parameter-names-before-setting-them","Validate parameter names before setting them",[11,673,674,676,677,680],{},[22,675,97],{}," raises ",[22,678,679],{},"hou.OperationFailed"," when a key is not a valid parameter or parameter tuple. That catches the mistake, but in a production tool I still want the error to name the missing fields and the node that received them.",[11,682,683,684,686],{},"One important detail: ",[22,685,97],{}," is not atomic. If an invalid key appears after valid keys in the dictionary, Houdini can apply the earlier values before raising the exception. Validate the complete dictionary first whenever a partial update would be unsafe.",[11,688,689],{},"I usually validate the dictionary first:",[15,691,693],{"className":17,"code":692,"language":19,"meta":20,"style":20},"from typing import Mapping\n\nimport hou\n\n\ndef set_existing_parameters(node: hou.OpNode, values: Mapping[str, object]) -> None:\n    \"\"\"Set parameter values after checking that every name exists.\n\n    Args:\n        node: Houdini node to configure.\n        values: Mapping of parameter or parameter-tuple names to values.\n\n    Raises:\n        ValueError: If one or more parameter names do not exist on the node.\n    \"\"\"\n    missing_names = sorted(\n        name\n        for name in values\n        if node.parm(name) is None and node.parmTuple(name) is None\n    )\n\n    if missing_names:\n        missing_text = \", \".join(missing_names)\n        raise ValueError(f\"Missing parameters on {node.path()}: {missing_text}\")\n\n    node.setParms(dict(values))\n",[22,694,695,708,712,718,722,726,754,759,763,768,773,778,782,788,794,800,814,820,835,859,865,870,879,893,934,939],{"__ignoreMap":20},[25,696,697,700,703,705],{"class":27,"line":28},[25,698,699],{"class":111},"from",[25,701,702],{"class":31}," typing ",[25,704,419],{"class":111},[25,706,707],{"class":31}," Mapping\n",[25,709,710],{"class":27,"line":49},[25,711,175],{"emptyLinePlaceholder":174},[25,713,714,716],{"class":27,"line":64},[25,715,419],{"class":111},[25,717,422],{"class":31},[25,719,720],{"class":27,"line":79},[25,721,175],{"emptyLinePlaceholder":174},[25,723,724],{"class":27,"line":153},[25,725,175],{"emptyLinePlaceholder":174},[25,727,728,731,735,738,741,743,746,749,752],{"class":27,"line":165},[25,729,730],{"class":111},"def",[25,732,734],{"class":733},"sScJk"," set_existing_parameters",[25,736,737],{"class":31},"(node: hou.OpNode, values: Mapping[",[25,739,740],{"class":42},"str",[25,742,269],{"class":31},[25,744,745],{"class":42},"object",[25,747,748],{"class":31},"]) -> ",[25,750,751],{"class":42},"None",[25,753,233],{"class":31},[25,755,756],{"class":27,"line":171},[25,757,758],{"class":35},"    \"\"\"Set parameter values after checking that every name exists.\n",[25,760,761],{"class":27,"line":178},[25,762,175],{"emptyLinePlaceholder":174},[25,764,765],{"class":27,"line":301},[25,766,767],{"class":35},"    Args:\n",[25,769,770],{"class":27,"line":321},[25,771,772],{"class":35},"        node: Houdini node to configure.\n",[25,774,775],{"class":27,"line":533},[25,776,777],{"class":35},"        values: Mapping of parameter or parameter-tuple names to values.\n",[25,779,780],{"class":27,"line":538},[25,781,175],{"emptyLinePlaceholder":174},[25,783,785],{"class":27,"line":784},13,[25,786,787],{"class":35},"    Raises:\n",[25,789,791],{"class":27,"line":790},14,[25,792,793],{"class":35},"        ValueError: If one or more parameter names do not exist on the node.\n",[25,795,797],{"class":27,"line":796},15,[25,798,799],{"class":35},"    \"\"\"\n",[25,801,803,806,808,811],{"class":27,"line":802},16,[25,804,805],{"class":31},"    missing_names ",[25,807,112],{"class":111},[25,809,810],{"class":42}," sorted",[25,812,813],{"class":31},"(\n",[25,815,817],{"class":27,"line":816},17,[25,818,819],{"class":31},"        name\n",[25,821,823,826,829,832],{"class":27,"line":822},18,[25,824,825],{"class":111},"        for",[25,827,828],{"class":31}," name ",[25,830,831],{"class":111},"in",[25,833,834],{"class":31}," values\n",[25,836,838,841,844,846,848,851,854,856],{"class":27,"line":837},19,[25,839,840],{"class":111},"        if",[25,842,843],{"class":31}," node.parm(name) ",[25,845,227],{"class":111},[25,847,230],{"class":42},[25,849,850],{"class":111}," and",[25,852,853],{"class":31}," node.parmTuple(name) ",[25,855,227],{"class":111},[25,857,858],{"class":42}," None\n",[25,860,862],{"class":27,"line":861},20,[25,863,864],{"class":31},"    )\n",[25,866,868],{"class":27,"line":867},21,[25,869,175],{"emptyLinePlaceholder":174},[25,871,873,876],{"class":27,"line":872},22,[25,874,875],{"class":111},"    if",[25,877,878],{"class":31}," missing_names:\n",[25,880,882,885,887,890],{"class":27,"line":881},23,[25,883,884],{"class":31},"        missing_text ",[25,886,112],{"class":111},[25,888,889],{"class":35}," \", \"",[25,891,892],{"class":31},".join(missing_names)\n",[25,894,896,899,902,905,908,911,914,917,920,922,924,927,929,932],{"class":27,"line":895},24,[25,897,898],{"class":111},"        raise",[25,900,901],{"class":42}," ValueError",[25,903,904],{"class":31},"(",[25,906,907],{"class":111},"f",[25,909,910],{"class":35},"\"Missing parameters on ",[25,912,913],{"class":42},"{",[25,915,916],{"class":31},"node.path()",[25,918,919],{"class":42},"}",[25,921,123],{"class":35},[25,923,913],{"class":42},[25,925,926],{"class":31},"missing_text",[25,928,919],{"class":42},[25,930,931],{"class":35},"\"",[25,933,46],{"class":31},[25,935,937],{"class":27,"line":936},25,[25,938,175],{"emptyLinePlaceholder":174},[25,940,942,945,948],{"class":27,"line":941},26,[25,943,944],{"class":31},"    node.setParms(",[25,946,947],{"class":42},"dict",[25,949,950],{"class":31},"(values))\n",[11,952,953],{},"Usage stays simple:",[15,955,957],{"className":17,"code":956,"language":19,"meta":20,"style":20},"values = {\n    \"t\": (0.0, 1.0, 0.0),\n    \"ry\": 45.0,\n}\n\nset_existing_parameters(node, values)\n",[22,958,959,967,985,995,999,1003],{"__ignoreMap":20},[25,960,961,963,965],{"class":27,"line":28},[25,962,108],{"class":31},[25,964,112],{"class":111},[25,966,115],{"class":31},[25,968,969,971,973,975,977,979,981,983],{"class":27,"line":49},[25,970,260],{"class":35},[25,972,263],{"class":31},[25,974,266],{"class":42},[25,976,269],{"class":31},[25,978,43],{"class":42},[25,980,269],{"class":31},[25,982,266],{"class":42},[25,984,278],{"class":31},[25,986,987,989,991,993],{"class":27,"line":64},[25,988,370],{"class":35},[25,990,123],{"class":31},[25,992,292],{"class":42},[25,994,128],{"class":31},[25,996,997],{"class":27,"line":79},[25,998,168],{"class":31},[25,1000,1001],{"class":27,"line":153},[25,1002,175],{"emptyLinePlaceholder":174},[25,1004,1005],{"class":27,"line":165},[25,1006,1007],{"class":31},"set_existing_parameters(node, values)\n",[11,1009,1010,1011,1015,1016,269,1019,334,1022,565],{},"This has saved time when a node interface changed between Houdini versions, and when a callback was handed the wrong node type. For example, the settings above are real parameter names on Houdini 21's ",[1012,1013,1014],"strong",{},"Pyro Solver"," SOP: ",[552,1017,1018],{},"Start Frame",[552,1020,1021],{},"Max Substeps",[552,1023,1024],{},"Time Scale",[186,1026,1028],{"id":1027},"be-deliberate-with-animated-parameters","Be deliberate with animated parameters",[11,1030,1031,1032,1034],{},"My original note came from a Pyro Solver setup script that did this before calling ",[22,1033,97],{},":",[15,1036,1038],{"className":17,"code":1037,"language":19,"meta":20,"style":20},"pyro_solver.parm(\"startframe\").deleteAllKeyframes()\n",[22,1039,1040],{"__ignoreMap":20},[25,1041,1042,1045,1048],{"class":27,"line":28},[25,1043,1044],{"class":31},"pyro_solver.parm(",[25,1046,1047],{"class":35},"\"startframe\"",[25,1049,1050],{"class":31},").deleteAllKeyframes()\n",[11,1052,1053,1054,1056],{},"That line is easy to overlook. Setting a value is not the same as saying, \"This parameter should no longer be animated.\" On an animated parameter, ",[22,1055,97],{}," keeps the channel and adds or updates a key at the current frame.",[11,1058,1059],{},"If the tool is supposed to replace animation with a static value, remove the keyframes explicitly first.",[15,1061,1063],{"className":17,"code":1062,"language":19,"meta":20,"style":20},"start_frame = pyro_solver.parm(\"startframe\")\n\nif start_frame is None:\n    raise hou.NodeError(f\"{pyro_solver.path()} has no startframe parameter\")\n\nstart_frame.deleteAllKeyframes()\npyro_solver.setParms({\"startframe\": int(hou.playbar.frameRange()[0])})\n",[22,1064,1065,1079,1083,1096,1118,1122,1127],{"__ignoreMap":20},[25,1066,1067,1070,1072,1075,1077],{"class":27,"line":28},[25,1068,1069],{"class":31},"start_frame ",[25,1071,112],{"class":111},[25,1073,1074],{"class":31}," pyro_solver.parm(",[25,1076,1047],{"class":35},[25,1078,46],{"class":31},[25,1080,1081],{"class":27,"line":49},[25,1082,175],{"emptyLinePlaceholder":174},[25,1084,1085,1087,1090,1092,1094],{"class":27,"line":64},[25,1086,221],{"class":111},[25,1088,1089],{"class":31}," start_frame ",[25,1091,227],{"class":111},[25,1093,230],{"class":42},[25,1095,233],{"class":31},[25,1097,1098,1100,1102,1104,1106,1108,1111,1113,1116],{"class":27,"line":79},[25,1099,238],{"class":111},[25,1101,241],{"class":31},[25,1103,907],{"class":111},[25,1105,931],{"class":35},[25,1107,913],{"class":42},[25,1109,1110],{"class":31},"pyro_solver.path()",[25,1112,919],{"class":42},[25,1114,1115],{"class":35}," has no startframe parameter\"",[25,1117,46],{"class":31},[25,1119,1120],{"class":27,"line":153},[25,1121,175],{"emptyLinePlaceholder":174},[25,1123,1124],{"class":27,"line":165},[25,1125,1126],{"class":31},"start_frame.deleteAllKeyframes()\n",[25,1128,1129,1132,1134,1136,1138,1140,1142],{"class":27,"line":171},[25,1130,1131],{"class":31},"pyro_solver.setParms({",[25,1133,1047],{"class":35},[25,1135,123],{"class":31},[25,1137,494],{"class":42},[25,1139,497],{"class":31},[25,1141,500],{"class":42},[25,1143,1144],{"class":31},"])})\n",[11,1146,1147,1150,1151,1153],{},[22,1148,1149],{},"deleteAllKeyframes()"," leaves the parameter at the value it evaluates to on the current frame. After that, ",[22,1152,97],{}," applies a static value rather than modifying an animated channel.",[11,1155,1156,1157,1160],{},"For a tuple, use ",[22,1158,1159],{},"parmTuple()"," and clear the complete tuple:",[15,1162,1164],{"className":17,"code":1163,"language":19,"meta":20,"style":20},"translate = node.parmTuple(\"t\")\n\nif translate is None:\n    raise hou.NodeError(f\"{node.path()} has no translate tuple\")\n\ntranslate.deleteAllKeyframes()\nnode.setParms({\"t\": (0.0, 0.0, 0.0)})\n",[22,1165,1166,1181,1185,1198,1219,1223,1228],{"__ignoreMap":20},[25,1167,1168,1171,1173,1176,1179],{"class":27,"line":28},[25,1169,1170],{"class":31},"translate ",[25,1172,112],{"class":111},[25,1174,1175],{"class":31}," node.parmTuple(",[25,1177,1178],{"class":35},"\"t\"",[25,1180,46],{"class":31},[25,1182,1183],{"class":27,"line":49},[25,1184,175],{"emptyLinePlaceholder":174},[25,1186,1187,1189,1192,1194,1196],{"class":27,"line":64},[25,1188,221],{"class":111},[25,1190,1191],{"class":31}," translate ",[25,1193,227],{"class":111},[25,1195,230],{"class":42},[25,1197,233],{"class":31},[25,1199,1200,1202,1204,1206,1208,1210,1212,1214,1217],{"class":27,"line":79},[25,1201,238],{"class":111},[25,1203,241],{"class":31},[25,1205,907],{"class":111},[25,1207,931],{"class":35},[25,1209,913],{"class":42},[25,1211,916],{"class":31},[25,1213,919],{"class":42},[25,1215,1216],{"class":35}," has no translate tuple\"",[25,1218,46],{"class":31},[25,1220,1221],{"class":27,"line":153},[25,1222,175],{"emptyLinePlaceholder":174},[25,1224,1225],{"class":27,"line":165},[25,1226,1227],{"class":31},"translate.deleteAllKeyframes()\n",[25,1229,1230,1233,1235,1237,1239,1241,1243,1245,1247],{"class":27,"line":171},[25,1231,1232],{"class":31},"node.setParms({",[25,1234,1178],{"class":35},[25,1236,263],{"class":31},[25,1238,266],{"class":42},[25,1240,269],{"class":31},[25,1242,266],{"class":42},[25,1244,269],{"class":31},[25,1246,266],{"class":42},[25,1248,1249],{"class":31},")})\n",[11,1251,1252],{},"Do not clear animation as a generic safety step. A pipeline utility should only remove keyframes when that is part of its stated behavior.",[186,1254,1256,1258],{"id":1255},"setparms-is-for-values-not-expressions",[22,1257,97],{}," is for values, not expressions",[11,1260,1261,1262,1265],{},"For parameter expressions, use ",[22,1263,1264],{},"setParmExpressions()"," instead.",[15,1267,1269],{"className":17,"code":1268,"language":19,"meta":20,"style":20},"node.setParmExpressions({\n    \"tx\": 'ch(\"ty\")',\n    \"sy\": \"sin($F)\",\n})\n",[22,1270,1271,1276,1287,1299],{"__ignoreMap":20},[25,1272,1273],{"class":27,"line":28},[25,1274,1275],{"class":31},"node.setParmExpressions({\n",[25,1277,1278,1280,1282,1285],{"class":27,"line":49},[25,1279,120],{"class":35},[25,1281,123],{"class":31},[25,1283,1284],{"class":35},"'ch(\"ty\")'",[25,1286,128],{"class":31},[25,1288,1289,1292,1294,1297],{"class":27,"line":64},[25,1290,1291],{"class":35},"    \"sy\"",[25,1293,123],{"class":31},[25,1295,1296],{"class":35},"\"sin($F)\"",[25,1298,128],{"class":31},[25,1300,1301],{"class":27,"line":79},[25,1302,324],{"class":31},[11,1304,1305],{},"Keeping values and expressions in separate dictionaries makes the intent clearer.",[15,1307,1309],{"className":17,"code":1308,"language":19,"meta":20,"style":20},"static_values = {\n    \"tz\": 2.0,\n}\n\nexpressions = {\n    \"tx\": 'ch(\"ty\")',\n}\n\nnode.setParms(static_values)\nnode.setParmExpressions(expressions)\n",[22,1310,1311,1320,1330,1334,1338,1347,1357,1361,1365,1370],{"__ignoreMap":20},[25,1312,1313,1316,1318],{"class":27,"line":28},[25,1314,1315],{"class":31},"static_values ",[25,1317,112],{"class":111},[25,1319,115],{"class":31},[25,1321,1322,1324,1326,1328],{"class":27,"line":49},[25,1323,144],{"class":35},[25,1325,123],{"class":31},[25,1327,59],{"class":42},[25,1329,128],{"class":31},[25,1331,1332],{"class":27,"line":64},[25,1333,168],{"class":31},[25,1335,1336],{"class":27,"line":79},[25,1337,175],{"emptyLinePlaceholder":174},[25,1339,1340,1343,1345],{"class":27,"line":153},[25,1341,1342],{"class":31},"expressions ",[25,1344,112],{"class":111},[25,1346,115],{"class":31},[25,1348,1349,1351,1353,1355],{"class":27,"line":165},[25,1350,120],{"class":35},[25,1352,123],{"class":31},[25,1354,1284],{"class":35},[25,1356,128],{"class":31},[25,1358,1359],{"class":27,"line":171},[25,1360,168],{"class":31},[25,1362,1363],{"class":27,"line":178},[25,1364,175],{"emptyLinePlaceholder":174},[25,1366,1367],{"class":27,"line":301},[25,1368,1369],{"class":31},"node.setParms(static_values)\n",[25,1371,1372],{"class":27,"line":321},[25,1373,1374],{"class":31},"node.setParmExpressions(expressions)\n",[186,1376,1378],{"id":1377},"a-small-helper-for-tool-code","A small helper for tool code",[11,1380,1381],{},"This is the version I would actually keep in a shared Houdini utility module:",[15,1383,1385],{"className":17,"code":1384,"language":19,"meta":20,"style":20},"from typing import Mapping, Sequence\n\nimport hou\n\n\ndef configure_node(\n    node: hou.OpNode, values: Mapping[str, object], clear_animation: Sequence[str] = ()\n) -> None:\n    \"\"\"Apply a validated parameter preset to a Houdini node.\n\n    Args:\n        node: Houdini node to configure.\n        values: Static parameter or parameter-tuple values.\n        clear_animation: Names whose keyframes should be removed first.\n\n    Raises:\n        ValueError: If a requested parameter name does not exist.\n    \"\"\"\n    requested_names = set(values) | set(clear_animation)\n    missing_names = sorted(\n        name\n        for name in requested_names\n        if node.parm(name) is None and node.parmTuple(name) is None\n    )\n\n    if missing_names:\n        missing_text = \", \".join(missing_names)\n        raise ValueError(f\"Missing parameters on {node.path()}: {missing_text}\")\n\n    for name in clear_animation:\n        parm_tuple = node.parmTuple(name)\n        if parm_tuple is not None:\n            parm_tuple.deleteAllKeyframes()\n            continue\n\n        parm = node.parm(name)\n        if parm is not None:\n            parm.deleteAllKeyframes()\n\n    node.setParms(dict(values))\n",[22,1386,1387,1398,1402,1408,1412,1416,1425,1449,1458,1463,1467,1471,1475,1480,1485,1489,1493,1498,1502,1523,1533,1537,1548,1566,1570,1574,1580,1591,1622,1627,1640,1651,1668,1674,1680,1685,1696,1712,1718,1723],{"__ignoreMap":20},[25,1388,1389,1391,1393,1395],{"class":27,"line":28},[25,1390,699],{"class":111},[25,1392,702],{"class":31},[25,1394,419],{"class":111},[25,1396,1397],{"class":31}," Mapping, Sequence\n",[25,1399,1400],{"class":27,"line":49},[25,1401,175],{"emptyLinePlaceholder":174},[25,1403,1404,1406],{"class":27,"line":64},[25,1405,419],{"class":111},[25,1407,422],{"class":31},[25,1409,1410],{"class":27,"line":79},[25,1411,175],{"emptyLinePlaceholder":174},[25,1413,1414],{"class":27,"line":153},[25,1415,175],{"emptyLinePlaceholder":174},[25,1417,1418,1420,1423],{"class":27,"line":165},[25,1419,730],{"class":111},[25,1421,1422],{"class":733}," configure_node",[25,1424,813],{"class":31},[25,1426,1427,1430,1432,1434,1436,1439,1441,1444,1446],{"class":27,"line":171},[25,1428,1429],{"class":31},"    node: hou.OpNode, values: Mapping[",[25,1431,740],{"class":42},[25,1433,269],{"class":31},[25,1435,745],{"class":42},[25,1437,1438],{"class":31},"], clear_animation: Sequence[",[25,1440,740],{"class":42},[25,1442,1443],{"class":31},"] ",[25,1445,112],{"class":111},[25,1447,1448],{"class":31}," ()\n",[25,1450,1451,1454,1456],{"class":27,"line":178},[25,1452,1453],{"class":31},") -> ",[25,1455,751],{"class":42},[25,1457,233],{"class":31},[25,1459,1460],{"class":27,"line":301},[25,1461,1462],{"class":35},"    \"\"\"Apply a validated parameter preset to a Houdini node.\n",[25,1464,1465],{"class":27,"line":321},[25,1466,175],{"emptyLinePlaceholder":174},[25,1468,1469],{"class":27,"line":533},[25,1470,767],{"class":35},[25,1472,1473],{"class":27,"line":538},[25,1474,772],{"class":35},[25,1476,1477],{"class":27,"line":784},[25,1478,1479],{"class":35},"        values: Static parameter or parameter-tuple values.\n",[25,1481,1482],{"class":27,"line":790},[25,1483,1484],{"class":35},"        clear_animation: Names whose keyframes should be removed first.\n",[25,1486,1487],{"class":27,"line":796},[25,1488,175],{"emptyLinePlaceholder":174},[25,1490,1491],{"class":27,"line":802},[25,1492,787],{"class":35},[25,1494,1495],{"class":27,"line":816},[25,1496,1497],{"class":35},"        ValueError: If a requested parameter name does not exist.\n",[25,1499,1500],{"class":27,"line":822},[25,1501,799],{"class":35},[25,1503,1504,1507,1509,1512,1515,1518,1520],{"class":27,"line":837},[25,1505,1506],{"class":31},"    requested_names ",[25,1508,112],{"class":111},[25,1510,1511],{"class":42}," set",[25,1513,1514],{"class":31},"(values) ",[25,1516,1517],{"class":111},"|",[25,1519,1511],{"class":42},[25,1521,1522],{"class":31},"(clear_animation)\n",[25,1524,1525,1527,1529,1531],{"class":27,"line":861},[25,1526,805],{"class":31},[25,1528,112],{"class":111},[25,1530,810],{"class":42},[25,1532,813],{"class":31},[25,1534,1535],{"class":27,"line":867},[25,1536,819],{"class":31},[25,1538,1539,1541,1543,1545],{"class":27,"line":872},[25,1540,825],{"class":111},[25,1542,828],{"class":31},[25,1544,831],{"class":111},[25,1546,1547],{"class":31}," requested_names\n",[25,1549,1550,1552,1554,1556,1558,1560,1562,1564],{"class":27,"line":881},[25,1551,840],{"class":111},[25,1553,843],{"class":31},[25,1555,227],{"class":111},[25,1557,230],{"class":42},[25,1559,850],{"class":111},[25,1561,853],{"class":31},[25,1563,227],{"class":111},[25,1565,858],{"class":42},[25,1567,1568],{"class":27,"line":895},[25,1569,864],{"class":31},[25,1571,1572],{"class":27,"line":936},[25,1573,175],{"emptyLinePlaceholder":174},[25,1575,1576,1578],{"class":27,"line":941},[25,1577,875],{"class":111},[25,1579,878],{"class":31},[25,1581,1583,1585,1587,1589],{"class":27,"line":1582},27,[25,1584,884],{"class":31},[25,1586,112],{"class":111},[25,1588,889],{"class":35},[25,1590,892],{"class":31},[25,1592,1594,1596,1598,1600,1602,1604,1606,1608,1610,1612,1614,1616,1618,1620],{"class":27,"line":1593},28,[25,1595,898],{"class":111},[25,1597,901],{"class":42},[25,1599,904],{"class":31},[25,1601,907],{"class":111},[25,1603,910],{"class":35},[25,1605,913],{"class":42},[25,1607,916],{"class":31},[25,1609,919],{"class":42},[25,1611,123],{"class":35},[25,1613,913],{"class":42},[25,1615,926],{"class":31},[25,1617,919],{"class":42},[25,1619,931],{"class":35},[25,1621,46],{"class":31},[25,1623,1625],{"class":27,"line":1624},29,[25,1626,175],{"emptyLinePlaceholder":174},[25,1628,1630,1633,1635,1637],{"class":27,"line":1629},30,[25,1631,1632],{"class":111},"    for",[25,1634,828],{"class":31},[25,1636,831],{"class":111},[25,1638,1639],{"class":31}," clear_animation:\n",[25,1641,1643,1646,1648],{"class":27,"line":1642},31,[25,1644,1645],{"class":31},"        parm_tuple ",[25,1647,112],{"class":111},[25,1649,1650],{"class":31}," node.parmTuple(name)\n",[25,1652,1654,1656,1659,1661,1664,1666],{"class":27,"line":1653},32,[25,1655,840],{"class":111},[25,1657,1658],{"class":31}," parm_tuple ",[25,1660,227],{"class":111},[25,1662,1663],{"class":111}," not",[25,1665,230],{"class":42},[25,1667,233],{"class":31},[25,1669,1671],{"class":27,"line":1670},33,[25,1672,1673],{"class":31},"            parm_tuple.deleteAllKeyframes()\n",[25,1675,1677],{"class":27,"line":1676},34,[25,1678,1679],{"class":111},"            continue\n",[25,1681,1683],{"class":27,"line":1682},35,[25,1684,175],{"emptyLinePlaceholder":174},[25,1686,1688,1691,1693],{"class":27,"line":1687},36,[25,1689,1690],{"class":31},"        parm ",[25,1692,112],{"class":111},[25,1694,1695],{"class":31}," node.parm(name)\n",[25,1697,1699,1701,1704,1706,1708,1710],{"class":27,"line":1698},37,[25,1700,840],{"class":111},[25,1702,1703],{"class":31}," parm ",[25,1705,227],{"class":111},[25,1707,1663],{"class":111},[25,1709,230],{"class":42},[25,1711,233],{"class":31},[25,1713,1715],{"class":27,"line":1714},38,[25,1716,1717],{"class":31},"            parm.deleteAllKeyframes()\n",[25,1719,1721],{"class":27,"line":1720},39,[25,1722,175],{"emptyLinePlaceholder":174},[25,1724,1726,1728,1730],{"class":27,"line":1725},40,[25,1727,944],{"class":31},[25,1729,947],{"class":42},[25,1731,950],{"class":31},[11,1733,1734],{},"Example:",[15,1736,1738],{"className":17,"code":1737,"language":19,"meta":20,"style":20},"values = {\n    \"t\": (0.0, 1.0, 0.0),\n    \"ry\": 45.0,\n}\n\nconfigure_node(node, values, clear_animation=(\"t\", \"ry\"))\n",[22,1739,1740,1748,1766,1776,1780,1784],{"__ignoreMap":20},[25,1741,1742,1744,1746],{"class":27,"line":28},[25,1743,108],{"class":31},[25,1745,112],{"class":111},[25,1747,115],{"class":31},[25,1749,1750,1752,1754,1756,1758,1760,1762,1764],{"class":27,"line":49},[25,1751,260],{"class":35},[25,1753,263],{"class":31},[25,1755,266],{"class":42},[25,1757,269],{"class":31},[25,1759,43],{"class":42},[25,1761,269],{"class":31},[25,1763,266],{"class":42},[25,1765,278],{"class":31},[25,1767,1768,1770,1772,1774],{"class":27,"line":64},[25,1769,370],{"class":35},[25,1771,123],{"class":31},[25,1773,292],{"class":42},[25,1775,128],{"class":31},[25,1777,1778],{"class":27,"line":79},[25,1779,168],{"class":31},[25,1781,1782],{"class":27,"line":153},[25,1783,175],{"emptyLinePlaceholder":174},[25,1785,1786,1789,1793,1795,1797,1799,1801,1804],{"class":27,"line":165},[25,1787,1788],{"class":31},"configure_node(node, values, ",[25,1790,1792],{"class":1791},"s4XuR","clear_animation",[25,1794,112],{"class":111},[25,1796,904],{"class":31},[25,1798,1178],{"class":35},[25,1800,269],{"class":31},[25,1802,1803],{"class":35},"\"ry\"",[25,1805,1806],{"class":31},"))\n",[11,1808,1809],{},"I keep this helper deliberately boring. It does not swallow Houdini exceptions, skip unknown names, or guess whether animation should be removed. The calling tool should make those decisions explicitly.",[186,1811,1813],{"id":1812},"when-i-would-not-use-it","When I would not use it",[11,1815,1816,1817,1820],{},"I still use individual ",[22,1818,1819],{},"parm().set()"," calls when:",[1822,1823,1824,1828,1831,1834,1837],"ul",{},[1825,1826,1827],"li",{},"I need different error handling for each parameter.",[1825,1829,1830],{},"Later values depend on evaluating earlier changes.",[1825,1832,1833],{},"I am setting ramp data or other values that need special preparation.",[1825,1835,1836],{},"The code is only changing one or two parameters.",[1825,1838,1839],{},"I need to preserve or deliberately manage channel references.",[11,1841,1842,1843,1845],{},"For ordinary node presets and setup scripts, a dictionary passed to ",[22,1844,97],{}," is usually the clearest picture of the state I want the node to end up in.",[186,1847,1849],{"id":1848},"wip-checklist","WIP checklist",[1822,1851,1854,1863,1869,1875],{"className":1852},[1853],"contains-task-list",[1825,1855,1858,1862],{"className":1856},[1857],"task-list-item",[1859,1860],"input",{"checked":174,"disabled":174,"type":1861},"checkbox"," Double check everything.",[1825,1864,1866,1868],{"className":1865},[1857],[1859,1867],{"checked":174,"disabled":174,"type":1861}," Add a Pyro Solver preset example with current parameter names.",[1825,1870,1872,1874],{"className":1871},[1857],[1859,1873],{"checked":174,"disabled":174,"type":1861}," Confirm and document the behavior when one dictionary key is invalid.",[1825,1876,1878,1880],{"className":1877},[1857],[1859,1879],{"checked":174,"disabled":174,"type":1861}," Add a Pyro Solver screenshot showing the UI labels and configured values.",[186,1882,1884],{"id":1883},"references","References",[1822,1886,1887,1896,1903],{},[1825,1888,1889],{},[1890,1891,1895],"a",{"href":1892,"rel":1893},"https:\u002F\u002Fwww.sidefx.com\u002Fdocs\u002Fhoudini\u002Fhom\u002Fhou\u002FOpNode.html#setParms",[1894],"nofollow","hou.OpNode.setParms",[1825,1897,1898],{},[1890,1899,1902],{"href":1900,"rel":1901},"https:\u002F\u002Fwww.sidefx.com\u002Fdocs\u002Fhoudini\u002Fhom\u002Fhou\u002FOpNode.html#setParmExpressions",[1894],"hou.OpNode.setParmExpressions",[1825,1904,1905],{},[1890,1906,1909],{"href":1907,"rel":1908},"https:\u002F\u002Fwww.sidefx.com\u002Fdocs\u002Fhoudini\u002Fhom\u002Fhou\u002FParm.html#deleteAllKeyframes",[1894],"hou.Parm.deleteAllKeyframes",[1911,1912,1913],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":20,"searchDepth":49,"depth":49,"links":1915},[1916,1917,1918,1919,1920,1922,1923,1924,1925],{"id":188,"depth":49,"text":189},{"id":405,"depth":49,"text":406},{"id":670,"depth":49,"text":671},{"id":1027,"depth":49,"text":1028},{"id":1255,"depth":49,"text":1921},"setParms() is for values, not expressions",{"id":1377,"depth":49,"text":1378},{"id":1812,"depth":49,"text":1813},{"id":1848,"depth":49,"text":1849},{"id":1883,"depth":49,"text":1884},null,"2026-07-11","A practical pattern for configuring Houdini nodes from Python dictionaries, including parameter tuples, validation, and animated parameters.","md",{},"\u002Fblog\u002Fsetting-multiple-houdini-parameters-with-setparms",{"title":6,"description":1928},"published","blog\u002Fsetting-multiple-houdini-parameters-with-setparms",[1936,1937,1938],"Houdini","Python","Pipeline","mKew94fVMcuFf60qua8NLgqHQD-8SSyxj6jLNFuw9A4",{"path":1941,"title":1942,"date":1943},"\u002Fblog\u002Fbuilding-rv-the-easy-way","Building RV the Easy Way: Abusing CI Minutes Instead of Downloading the World","2026-07-13",1784707438337]