Houdini Python
Getting the topmost input of a node
Let's say you want to get the first node in a tree (like a file sop) that's connected to a given SOP. This goes up inputs (the first one) until it won't find one anymore. Simple but useful.
import hou myNode = hou.node('/obj/geo1/null1') while myNode.input(0): myNode = myNode.input(0) print(myNode.path())
Delete children of a node that have their display flags turned off
import hou obj = hou.selectedNodes()[0] for curNode in obj.children(): if(curNode.type().name() == 'geo' and curNode.isDisplayFlagSet() == False): #print(curNode) curNode.destroy()
Move all keyframes to the beginning of the timeline
Story is I have some tracked camera+plate that are TC'ed at range at frame 24878933 for instance which is a pain in the ass, and no shotgun/grid tool to put that to an artist-friendly frame start like 101 or 1001
This will take a node selection (or a node as argument), figure out the range of all the keyframes on the node and subnodes (optionally), and offset the range of these keyframes to match the start of your current timeline.
import hou def offsetParmKeyframes( parmTup, offset ): '''given a parm, move its keyframes in time by the offset. Deletes an recreates the keyframes''' keyframes = parmTup.keyframes() if keyframes: parmTup.deleteAllKeyframes() for kf in keyframes: kf.setFrame( kf.frame() + offset ) parmTup.setKeyframe( kf ) def keyframesToPlaybar(node=None,recursive=False): '''given a node or a selection, go through keyframes and move all keys to the timeline''' #gather all parms and subitem parms nodelist = [] if node is None: nodelist = hou.selectedNodes() if not nodelist: hou.ui.displayMessage('Select a node') return if recursive: newnodelist = () for node in nodelist: newnodelist = newnodelist + node.allSubChildren() nodelist = nodelist + newnodelist else: nodelist.append(node) #keep only those that have >1 keyframes, find out the min and max framerange of all the keyframes found parms = [] frameRangeMinMax = [999999999,-999999999] for node in nodelist: for parm in node.parms(): #only keep parms that have more than one keyframe, otherwise it's an expression or single keyframe >= doesn't need to be moved if len(parm.keyframes())>1: parms.append(parm) frameRangeMinMax[0] = min([parm.keyframes()[0].frame(),frameRangeMinMax[0]]) frameRangeMinMax[1] = max([parm.keyframes()[-1].frame(),frameRangeMinMax[1]]) #print(frameRangeMinMax) #print(parms) offset = hou.playbar.playbackRange()[0] - frameRangeMinMax[0] for parm in parms: offsetParmKeyframes( parm, offset ) return offset keyframesToPlaybar(None,1)
After Effects camera to Houdini camera
Pretty crappy/hacky code but works enough for me to post here. This assumes it's a baked, every-frame-is-keyframed camera, should work with linear keyframes too.
Howto:
- Put this code in a shelf
- in AE select the position and rotation properties (that have keyframes. I need to generalize the code to add more properties)
- Ctrl-c
- In Houdini press your newly created shelf button. Should be good!
import hou, re def clipboardToCamera(): ''' takes a clipboard containing AE keyframe data and creates and returns camera ''' #todo: generalize to other items, add aperture/zoom #baked constants matchFps = False # match scene fps from comp size matchRez = True # match camera rez from comp size translateResize = -0.01 # scene scale change, this one worked for me AEclipboard = hou.ui.getTextFromClipboard() #check if we have AE data if 'Keyframe Data' not in AEclipboard: hou.ui.displayMessage('No keyframe data from AE found') return #match FPS if matchFps: try: AEfps = float(AEclipboard.split('Units Per Second')[1].splitlines()[0]) hou.setFps(AEfps) hou.ui.displayMessage('Changing FPS to '+string(AEfps)) except: print('Units Per Second not found in keyframe data') #check for transform keyword, if so, create camera and add keys if 'Transform' in AEclipboard: camera = hou.node('/obj').createNode('cam') #I found I needed to change rotation order to match AE<>Houdini camera rotation camera.parm('rOrd').set(5) camera.parm('iconscale').set(50) #match Resolution if matchRez: try: w = float(AEclipboard.split('Source Width')[1].splitlines()[0]) h = float(AEclipboard.split('Source Height')[1].splitlines()[0]) camera.parm('resx').set(w) camera.parm('resy').set(h) except: print('Source Width and/or Source Height not found in keyframe data') #aeNames = ['Camera Options Aperture','Transform Position','Transform Orientation'] aeNames = ['Transform Position','Transform Orientation'] houdiniParms = ['t','r'] itemN = -1 for index, item in enumerate( aeNames ): aeName = item.replace(' ', '\\t*\\s*') # print('split @ '+aeName) # the followin is nasty but should give us tuples [[frame#1,x,y,z][frame#2,x,y,z]] clipboardSplit = re.split(aeName, AEclipboard) clipboarSplitKeyframes = clipboardSplit[1].split('\n\n')[0].splitlines() kf = [x.split('\t')[1:-1] for x in clipboarSplitKeyframes[2:]] #print(kf) # make a keyframe for each line for line in kf: itemN = itemN + 1 #if itemN < 5: # print(line) for index2, subparm in enumerate(['x','y','z']): curParm = camera.path()+'/'+houdiniParms[index]+subparm if houdiniParms[index] == 't': scale = translateResize else: scale = 1 frameOffset = 0 #if itemN < 5: # print(curParm+' '+str(int(line[0])+frameOffset)+' '+str(float(line[index2+1])*scale)) setKeyFrame(curParm,int(line[0])+frameOffset,float(line[index2+1])*scale) def setKeyFrame(parm,time,value): hou_keyframe = hou.Keyframe() hou_keyframe.setFrame(time) hou_keyframe.setValue(value) hou_keyframe.setSlope(0) hou_keyframe.setInSlope(0) hou_keyframe.useSlope(False) hou_keyframe.setAccel(0) hou_keyframe.useAccel(False) hou_keyframe.interpretAccelAsRatio(False) hou_keyframe.setExpression("constant()", hou.exprLanguage.Hscript) hou.parm(parm).setKeyframe(hou_keyframe) clipboardToCamera()
Python node cook timer
Poor man's Performance Monitor. Probably better versions out there but it worked for my HDA.
Uses Python3's timeit
and hou.session
'global' var to to a time delta by calling two python scripts sandwiching your heavy Sops.
First script node
import timeit;hou.session.t0=timeit.default_timer()
(python sleep 3 seconds in my example: import time;time.sleep(3)
)
And the timer stop. Should be cleaned up as there's probably node cooking/caching shenanigans going on.
import timeit t1=timeit.default_timer() tdelta = t1-hou.session.t0 hou.pwd().setComment(str(f"{tdelta:.03f} secs.")) hou.pwd().setGenericFlag(hou.nodeFlag.DisplayComment,True)
Get start and end file number from sequence
import re, hou, glob f = hou.node('/obj/geo1/file2').parm('file') def outputSequenceStartEnd(fileparm): '''given a file parm return the beginning and end of sequence''' fileparm = f.rawValue() matchObj = re.match( r'^(.*)\$F(\d*)(.*)', parmval, re.M|re.I) #expects $F* in the filename before = matchObj.groups()[0] extension = matchObj.groups()[2] fileGlob = before + '*' + extension files = glob.glob(fileGlob) files = sorted(files) #eww startframe = files[0].split(before[-6:])[-1].split(extension)[0] #dirty hack to get the file number because reusing the regex clashes with the glob return endframe = files[-1].split(before[-6:])[-1].split(extension)[0] return [startframe,endframe] print(outputSequenceStartEnd(f)) #returns ['200000', '204805']
Because I'm tired of having to navigate if there is more than one Octane ROP in my file. As a bonus saves last used IPR as default
import hou def chooseIPRdialog(buttons): try: c = hou.session.choice except: hou.session.choice = 0 buttons.append('Cancel') dialog = hou.ui.displayMessage('IPR choice', buttons=buttons,default_choice=hou.session.choice) hou.session.choice = dialog if dialog == len(buttons)-1: dialog = False return dialog #grab iprs iprs = [] for node in hou.node('/').allSubChildren(): if node.type().name() == 'Octane_ROP': iprs.append(node) #launch dialog, return ipr, launch ipr if not cancelled names = [x.name() for x in iprs] if len(names) == 1: iprs[0].parm('HO_IPR').pressButton() else: chosenIPR = chooseIPRdialog(names) if chosenIPR is not False: iprs[chosenIPR].parm('HO_IPR').pressButton()
Log for loop progress to console
Kinda dirty but you can just use a print argument in a python sop it should log out and be visible in your renderfarm logs. Here I showed progress every 500 items.
#i have two spare parameters fetching current iteration and total number of iterations #using detail(1,'iteration',0) expression or -1 if you use a spare iterFloat = float(`chs("iter")`) maxFloat = float(`chs("numiter")`) if(iterFloat%500==0): print(str(round(iterFloat/maxFloat*100,1))+ " % "+str(int(iterFloat))+"/"+str(int(maxFloat)))
Auto-add frame offset parameter
I've written padzero(...)
too many times in production so I decided to waste a few hours writing a way to add it to the right-click menu available for parms
For this to work I need two things:
An XML file called PARMmenu.xml
(doc), in the python path or more generally the base folder of your preferences.
I loosely based myself on code from qLib and other ressources. But the important thing to note is that there are two 'important' sections:
- A 'context' tag that is a python code that parses the parameter you right click and returns True or False -- which will tell Houdini if you need to show the menu (it is generated on the fly). In my case it looks for '$F#' in the parameter value
- A 'scriptCode' tag that is the actual code that is run
To make things cleaner I call code from my personal python library (see below) instead of putting the full code here.
/!\ Pain in the Butt Houdini19 update: since it's Python3+ I can't use 'reload(module)' anymore. Code below work for Python2.7
<?xml version="1.0" encoding="UTF-8"?> <menuDocument> <menu> <subMenu id="bernie_rclick"> <label>Bernie's</label> <modifyItem><insertAfter>motion_effects_menu</insertAfter></modifyItem> <scriptItem id="bernie_set_frameoffset_parm"> <label>Add a frame offset parm to $F#</label> <context> <expression> import bernie_tools return bernie_tools.validate_sequence_parm(kwargs) </expression> </context> <scriptCode><![CDATA[import bernie_tools;reload(bernie_tools);bernie_tools.add_sequence_offset_spareparm(kwargs)]]></scriptCode> </scriptItem> </subMenu> </menu> </menuDocument>
This is the actual code, that I place in a pythonpath (here it is in $HOUDINI_USER_PREF/python2.7libs/bernie_tools.py
)
import hou import traceback import re def validate_sequence_parm(kwargs): '''Checks if the first parm contains a value with $F''' returnvalue = False try: returnvalue = '$F' in kwargs['parms'][0].rawValue() except: print("ERROR: %s" % traceback.format_exc()) return returnvalue def add_sequence_offset_spareparm(kwargs): '''Adds an int spare parm below a parm that has a file sequence expression of type '$F#' using hscript padzero''' parm = kwargs['parms'][0] parmval = parm.rawValue() #check if there is a $F(plus digit) matchObj = re.match( r'^(.*)\$F(\d*)(.*)', parmval, re.M|re.I) if matchObj: n = parm.node() parmGrp = n.parmTemplateGroup() existing_parm = parmGrp.find(parm.name()) #create an offset parm with a similar name. No error checking! plabel = existing_parm.label() + " Offset" pname = parm.name()+"_offset" offsetParmTemplate = hou.IntParmTemplate( pname, plabel, 1, default_expression=(["$F + 0"])) parmGrp.insertAfter(existing_parm, offsetParmTemplate) n.setParmTemplateGroup(parmGrp) #display the expression as it's most like we want to mmb through the offset n.parm(pname).showExpression(1) #figure out if there is any padding in the original parm using the regex result, otherwise padding at 1 (no padding) padding = 1 if matchObj.groups()[1]: padding = matchObj.groups()[1] expression = matchObj.groups()[0]+"`padzero("+str(padding)+",ch('" + pname + "'))`"+matchObj.groups()[2] parm.set(expression)
Set path parameter true for all alembic nodes
Don't know why it's not on by default, thanks maya ? Will probably add it to a more general right click menu.
import hou sel = hou.selectedNodes() for item in sel: for node in item.allSubChildren(): if node.type().name() == 'alembic': node.parm('addpath').set(1)
Houdini set random layercolorid for Octane
Piece of shit integration. Sorry Juanjo
from random import randint, uniform from colorsys import hsv_to_rgb s = 1.0 v = 1.0 for o in hou.selectedItems(): h = uniform(0.0, 1.0) s = uniform(0.5, 1.0) v = uniform(0.5, 1.0) r, g, b = hsv_to_rgb(h, s, v) o.parmTuple('octane_objprop_color').set([r,g,b])
Selecting other nodes of similar type
- Will select all nodes that correspond to the type of the currently selected nodes, in the same hierachy.
- Todo: allow to select items that are visible only in the region of the viewpane we are in. is it useful ?
import hou selection = hou.selectedNodes() nodetypes = [] for node in selection: nodetype = node.type() if nodetype not in nodetypes: nodetypes.append(node.type()) selectednodes = [] for node in hou.selectedNodes()[0].parent().allSubChildren(): if node.type() in nodetypes: selectednodes.append(node) hou.clearAllSelected() for node in selectednodes: node.setSelected(1,0) selection[-1].setSelected(1,0) hou.ui.setStatusMessage(str(len(selectednodes)) + " nodes selected")
get complementary color
MMhh . This seems overly complicated for something so simple in other languages. Will modify it if I find a better solution
r = parm('octane_gradient3_1cr').eval() g = parm('octane_gradient3_1cg').eval() b = parm('octane_gradient3_1cb').eval() #create color object col = hou.Color((r,g,b)) #create hsv object from rgb color hsv = hou.Color.hsv(col) #get complementary color hue (accross the color wheel) h = (hsv[0]+180)%360 #create new color c = hou.Color() #set color from hsv values c.setHSV([h,hsv[1],hsv[2]]) #return red return c.rgb()[0]
screenshot to background image
work in progress, interactively take a screenshot and puts it in the network view
- saves image in a screenshot subfolder next to .hip file
- is attached to a null that you can move/delete/template
- uses Minicap as my capture software on Windows
- is probably buggy
- For future reference for myself, this is where houdini stores (some of?) its UI python functions C:\Program Files\Side Effects Software\Houdini ####\houdini\python2.7libs, maybe it's overloadable to edit the interface ? Also good nographutils hidden doc from Juraj Tomori :https://jtomori.github.io/houdini_additional_python_docs/nodegraphutils.html
- Similar code has been embedded to Prism pipeline: https://prism-pipeline.com/
TBD:
- make it work in all contexts and levels (right now it breaks at / level and haven't tried in CHOPs/ROPs
- embed images in HDAs ?
- make it OS agnostic and no dependencies (screenshot via Python?)>>> linux version on Odforce: https://forums.odforce.net/topic/32581-networkimage/?tab=comments#comment-203986
Code moved to https://berniebernie.fr/wiki/Houdini_UI_Customization#Screenshot_to_background_image
load all objs in list by frame
Generate obj list:
import os dir_path = os.path.dirname(os.path.realpath(__file__)) listfilename = 'objs_list.txt' objlist = [] for root, dirs, files in os.walk(dir_path): for file in files: if file.lower().endswith('.obj'): objlist.append(os.path.join(root, file)) print('Writing '+str(len(objlist))+'to '+dir_path+'/'+listfilename) with open(dir_path+'/'+listfilename, 'w') as file_handler: for item in objlist: file_handler.write("{}\n".format(item))
In read file expression (with an added 'file' parm pointing to the above filelist.txt, called filelist) :
import os, linecache fl = hou.parm('filelist').eval() path = linecache.getline(fl, int(hou.frame())).rstrip() return path.replace(os.sep,'/')
ROP output driver to animated gif
To place in post render script. Linux for now, imagemagick is finicky. Matt Estela has a great page as usual https://www.tokeru.com/cgwiki/index.php?title=GeneralUtilties#Gifs
import distutils.spawn, subprocess convert = distutils.spawn.find_executable('convert') if convert: convertCmd = 'convert -loop 0 -delay 4 {}{} -layers OptimizePlus -colorspace sRGB +map {}' splitpath = hou.pwd().parm('picture').eval().split('.') gifPath = '.'.join(splitpath[:-2])+'.gif' wildcardPath = '.'.join(splitpath[:-2])+'.*.'+splitpath[-1] convertCmd = convertCmd.format(wildcardPath,'[600x600]',gifPath) convertCmd = convertCmd + '&& xdg-open '+gifPath subprocess.Popen(convertCmd,shell=True) else: print('imgmagick convert to gif not found')
Selected node as python code
(linux/windows may vary)
import hou import os import subprocess sel = hou.selectedNodes() strCode = sel[0].asCode(1,1) fp = hou.getenv('tmp')+"/houdini_node_output.txt" with open(fp, "w") as text_file: text_file.write(strCode) #os.system('xdg-open \"%s\"' % fp) os.startfile(fp)
#Hey look at me, I'm a big idiot and I wrote the script from scratch because I had forgotten I had already coded it. How well, here's another version import hou, tempfile, os new_file, filename = tempfile.mkstemp(suffix='.txt') text = '' for node in hou.selectedNodes(): text += node.asCode() os.write(new_file, str.encode(text)) os.close(new_file) os.startfile(filename)
gives:
Paste clipboard nodes to object merges
# this snippet will paste nodes in clipboard to object merges # use it with a shortcut (I overrode 'alt-v' in network pane context) import hou network = hou.ui.curDesktop().paneTabUnderCursor() networkpath = network.pwd().path() pos = network.cursorPosition() clipboard = hou.ui.getTextFromClipboard() n = 0 if clipboard: list = clipboard.split() for item in list: if hou.node(item) != None: merge = hou.node(networkpath).createNode('object_merge','merge_'+item.split('/')[-1]) merge.parm('objpath1').set(str(item)) merge.setPosition(pos) merge.move([n*2,0]) if n == 0: merge.setSelected(True,True) else: merge.setSelected(True,False) n = n + 1
Toggle off/on separate AOVs Arnold ROPs
Just saving it in case I need it
for child in hou.node('/out').children(): #print child.type().name() if 'ppArnold' in child.type().name(): #if child.name() == 'osacks_electric_brain_january_visual_cortex': for p in child.parms(): if 'ar_aov_separate' in str(p) and 'file' not in str(p): p.set(0) print(child.name()+"."+str(p)+" "+str(p.eval()))
Set expresions linking light visible parameter to display flag
idk why the candidate light didn't work on my machine so i used this
for child in hou.node('/obj').children(): if 'light' in child.type().name(): child.parm('light_enable').setExpression('hou.pwd().isDisplayFlagSet()', language=hou.exprLanguage.Python)
Skip if frame is in list
Works if there's a parm called skip on the same (switch) node
#true only on frames specified. Dirty but works if str(int(hou.frame())) in hou.pwd().parm('skip').eval().split(' '): return 1 else: return 0
and on a button if you want to easily add current frame to the list, make sure it's set to python
hou.pwd().parm('skip').set(hou.pwd().parm('skip').eval()+' '+str(int(hou.frame())))
Import neuron swc data
wip
# reads SWC and creates geo from it # http://www.neuronland.org/NLMorphologyConverter/MorphologyFormats/SWC/Spec.html # # 0 ¦ 1 ¦ 2 3 4 ¦ 5 ¦ 6 # sample number ¦ structure identifier ¦ x y z ¦ radius ¦ parent sample (connectivity) import os from itertools import * node = hou.pwd() geo = node.geometry() # procedures def isBadLine(line): return line[0]=='#' #def createPoint(geo,data): curDir = hou.node('/obj/swc_to_poly/directory_to_read').parm('readin').eval() print("processing: "+curDir) #read swc files in dir files = [] for name in os.listdir(curDir): if name.endswith('.swc'): files.append(os.path.join(curDir, name)) files.sort() files = files[-1:] for file in files: print(" --- processing file: "+file) with open(file) as f: for line in dropwhile(isBadLine, f): # only read lines that don't have hashtag data = line.lstrip().rstrip().split(" ") data = map(float,data) point = geo.createPoint() # create one point per line and fill attributes point.setPosition(hou.Vector3(*data[2:5])) point.setAttribValue('id', int(data[0])) point.setAttribValue('structid', int(data[1])) point.setAttribValue('radius', data[5]) point.setAttribValue('parent_id', int(data[6])) #print file
Create nodes according to primitive groups
node = hou.pwd() #custom undo stack with hou.undos.group("Poly Reduce Groups"): #uses parent, otherwise recursion happens with python sop node node = node.inputs()[0] geo = node.geometry() groups = geo.primGroups() count = 0 mainReduce = '' merge = node.parent().createNode('merge') for o in groups: #keep a single prim group blast = node.parent().createNode('blast') blast.setInput(0,node) blast.moveToGoodPosition() blast.parm('negate').set(True) blast.parm('group').set(o.name()) #use polyreduce with keep points on with an expression reduce = node.parent().createNode('polyreduce') reduce.setInput(0,blast) reduce.moveToGoodPosition() reduce.parm('originalpoints').set(True) reduce.parm('referframe').set(True) reduce.parm('framereference').setExpression('round((\$F+'+str(count)+')/ch("step"))*ch("step")') #create the 'step by' channel on this node parmTemplateGrp = reduce.parmTemplateGroup() stepByParm = hou.FloatParmTemplate('step', 'StepBy', 1,default_value=([10])) parmTemplateGrp.append(stepByParm) reduce.setParmTemplateGroup(parmTemplateGrp) if count == 0: reduce.setColor(hou.Color([0.6,1,0.6])) mainReduce = reduce reduce.parm('percentage').set(10) else: params = ['percentage','optimizationbias','borderweight','attribweight','topologicalweight','step'] for element in params: reduce.parm(element).set(mainReduce.parm(element)) merge.setInput(count,reduce,0) count = count + 1 merge.moveToGoodPosition() merge.setDisplayFlag(True) mainReduce.setSelected(True, clear_all_selected=True)
Load a folder of objs and merge them
import hou import glob obj = hou.node("/obj") s = obj.createNode('geo', 'loader', run_init_scripts=False) m = s.createNode('merge') path = '/users/me/Downloads/body/*.obj' i = -1 for file in glob.glob(path): i += 1 f = s.createNode('file') f.parm('file').set(file) f.moveToGoodPosition() m.setInput(i,f,0) m.moveToGoodPosition()
Best fitting bbox from eigenvectors
thanks to petz @ https://forums.odforce.net/topic/14879-bounding-box/
# This code is called when instances of this SOP cook. geo = hou.pwd().geometry() prims = geo.prims() points = geo.points() # parms output = 0 maxIter = 10 threshold = 0.001 centroid = sum([point.position() for point in points], hou.Vector3()) * (1.0 / len(points)) # build covariance matrix val11 = 0; val12 = 0; val13 = 0 val21 = 0; val22 = 0; val23 = 0 val31 = 0; val32 = 0; val33 = 0 for point in points: pos = point.position() val11 += (pos[0] - centroid[0]) * (pos[0] - centroid[0]) val12 += (pos[0] - centroid[0]) * (pos[1] - centroid[1]) val13 += (pos[0] - centroid[0]) * (pos[2] - centroid[2]) val21 += (pos[1] - centroid[1]) * (pos[0] - centroid[0]) val22 += (pos[1] - centroid[1]) * (pos[1] - centroid[1]) val23 += (pos[1] - centroid[1]) * (pos[2] - centroid[2]) val31 += (pos[2] - centroid[2]) * (pos[0] - centroid[0]) val32 += (pos[2] - centroid[2]) * (pos[1] - centroid[1]) val33 += (pos[2] - centroid[2]) * (pos[2] - centroid[2]) mat = hou.Matrix3(((val11, val12, val13), (val21, val22, val23), (val31, val32, val33))) mat = hou.Matrix4(mat.inverted()) # search for eigenvector with lowest eigenvalue vec1 = hou.Vector3(1.0, 1.0, 1.0) vecTemp = vec1 * mat vec2 = vecTemp * (1.0 / vecTemp.length()) i = 0 while not vec1.isAlmostEqual(vec2) and i < 100: vec1 = vec2 vecTemp = vec1 * mat vec2 = vecTemp * (1.0 / vecTemp.length()) i += 1 minAxis = vec2.normalized() # build matrix to transform geometry to initial position and orientation up = hou.Vector3(0.0, 1.0, 0.0) matUp = hou.hmath.buildTranslate(-centroid) matUp *= minAxis.matrixToRotateTo(up) geo.transform(matUp) # initialize attributes initRot = 9 angleSum = 0 ratio = 1 i = 0 # adaptively rotate geometry until best orientation for smallest bounding box is found while i < maxIter and ratio > threshold: angle = 0 angleHold = 0 angleRotBest = 0 bboxSize = geo.boundingBox().sizevec() vol = bboxSize[0] * bboxSize[1] * bboxSize[2] volMin = vol volMinHold = vol for n in range(10): angle += initRot matRot = hou.hmath.buildRotateAboutAxis(up, initRot) geo.transform(matRot) bboxSize = geo.boundingBox().sizevec() vol = bboxSize[0] * bboxSize[1] * bboxSize[2] if vol < volMin: volMin = vol angleRotBest = angle # check ratio of vol and vol of previous bounding box ratio = abs(volMinHold - volMin) volMinHold = volMin if ratio == 0.0 and i == 0: ratio = 1.0 angleRot = angle - angleRotBest + initRot angleSum += angleRotBest - initRot matRot = hou.hmath.buildRotateAboutAxis(up, -angleRot) geo.transform(matRot) initRot *= 0.2 i += 1 # bounding box bbox = geo.boundingBox() size = bbox.sizevec() vol = size[0] * size[1] * size[2] # build matrix to transform geometry back to oiginal position and orientation mat = hou.hmath.buildRotateAboutAxis(up, -angleSum) * matUp.inverted() matAttrib = geo.findGlobalAttrib("mat") if not matAttrib: matAttrib = geo.addAttrib(hou.attribType.Global, "mat", ( 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)) geo.setGlobalAttribValue(matAttrib, mat.asTuple())
#create objects from voronoi from math import cos, sin, pi obj = hou.selectedNodes()[0] node = obj.displayNode() prims = node.geometry().prims() lastprim = prims[ len(prims)-1 ] lastprimName = lastprim.attribValue("name") lastprimName = lastprimName.partition("piece") lastprimName = int( lastprimName[ len(lastprimName)-1 ] ) print lastprimName subnet = hou.node("obj").createNode("subnet") for i in range(0,lastprimName+1): geo = subnet.createNode("geo") geo.setPosition([cos( (1.0*i/lastprimName+1 ) * 2.0 * pi )*4.0, sin( (1.0*i/lastprimName+1 ) * 2.0 * pi )*4.0 ]) # lol objectMerge = geo.createNode("object_merge") delete = objectMerge.createOutputNode("delete") objectMerge.parm("objpath1").set(node.path()) delete.parm("negate").set(1) delete.parm("group").set("@name=piece"+str(i)) delete.setDisplayFlag(1)
Get functions in an OTL
#in button callback hou.pwd().hdaModule().hello() #in script window def hello: ....
Preroll output to stdout (good for HQUEUE), put in a python node that gets fetched each step (source?). Also add f1 f2 parms to the python node with $RFSTART and $RFEND
import sys start = hou.node(".").parm("f1").eval() end = hou.node(".").parm("f2").eval() current = hou.frame() progress = str(int(hou.hmath.fit(current, start, end, 0.0, 100.0))).zfill(3) print("\rFrame {0} done".format(current)) print("\rALF_PROGRESS {0}%".format(progress)) sys.stdout.flush()