Move existing Parms into Folder, paste Folder from another node

I am building kind of "archetype" node types, and adding my custom Parm Folders before the initial default Folders:


# group initial parms
PTG_initial = node_target.parmTemplateGroup()
PTG_new     = hou.ParmTemplateGroup()
entries     = PTG_initial.entries()

FOLDER_init = hou.FolderParmTemplate("folder", "Initial Parms")
FOLDER_init.setFolderType(hou.folderType.Tabs)

for entry in entries :
    FOLDER_init.addParmTemplate(entry)

PTG_new.append(FOLDER_init)
node_target.setParmTemplateGroup(PTG_new)


# copy parms
which_folder         = "JOB"
insert_before_folder = "Initial Parms"
node_src             = node_archetype
node_dst             = node_target
copypaste_ptg_folder( which_folder, insert_before_folder, node_src, node_dst )

Here is the copypaste_ptg_folder() definition:

def copypaste_ptg_folder( which_folder, insert_before_folder, node_src, node_dst ) :

    ptg_dst = node_dst.parmTemplateGroup()
    ptg_src = node_src.parmTemplateGroup()

    # remove target folder if already exists
    try:
        ptg_dst.remove(  ptg_dst.findFolder( which_folder )  )
        node_dst.setParmTemplateGroup(ptg_dst)
    except:
        pass
    
    # find and insert the folder, set PTG
    cargo    = ptg_src.findFolder( which_folder )
    location = ptg_dst.findFolder( insert_before_folder )
    ptg_dst.insertBefore( location , cargo )
    node_dst.setParmTemplateGroup(ptg_dst)

Here is the use of saveItemsToFile()

def copypaste_all_contents( container_src, container_dst ) :

    # copy contents

    temp_path = hou.getenv("wf_path") + "/temp/__archetype.temp"
    items     = container_src.allItems()
    container_src.saveItemsToFile(items, temp_path)

    # paste contents
    container_dst.loadItemsFromFile(temp_path)

Find NodeType, all versions

Original post by Tomas Slancik is here.
Documentation is here.

import hou
import nodesearch

root = hou.node("/obj")
matcher = nodesearch.NodeType( "xb::xform", hou.objNodeTypeCategory() )
nodes = matcher.nodes( root, recursive=True )
print (nodes)

Extend polyline in the direction of N

Nothing special, but it comes in handy from time to time.

int   reverse_normal = chi("reverse_normal");  // toggle
float distance       = chf("distance");        // 0.5 in 0.0 to 1.0
int   segments       = chi("segments");        // 1 in 1 to 10
float normal_mult    = 1;

if ( reverse_normal == 1 )
    normal_mult = -1;

int    vertices [] = primvertices(0, @primnum);
int    vertex      = vertices[-1];
int    point       = vertexpoint(0, vertex);

vector pos = vertex(0, "P", vertex);
vector nor = point(0, "N", point);

for (int i = 1; i < segments+1; i++)
{
    float  dist    = distance / float(segments);
    vector offset  = i * dist * normal_mult * nor;
    int    ptnum   = addpoint (0, pos + offset);
    int    vertnum = addvertex (0, @primnum, ptnum);
}

 

HDA snippets without UI

I have always been tangled in this triange:
A) build complete, versatile, high level tools with proper UI
B) have big library of low level snippets, with the default UI
C) fast search, easy updates, lightweight solution

I hope I figured it out now. I made an undoable OnCreated HDA script, which extracts contents of the HDA (and deletes the created HDA). This way, I can use all the power of HDAs (fuzzy Tab search, fast Shift Enter placement, easy updates, manage big library easily at the same time) and also avoid some of the HDA cons, relevant in certain situations.

I look forward to what my library will look like in a year.

Here is the script:

# -------------------------
# contents of the wf_hda.py

import hou
import hdefereval
def extract(node) :
    def extract_hda(hda_node) :
        label = "Extract HDA: " + str(hda_node.type().name())
        with hou.undos.group(label):
            hda_node.extractAndDelete()
    hdefereval.executeDeferred(lambda: extract_hda(node) )

# -------------------------
# OnCreated scipt in the Type Properties:
# import wf_hda
# wf_hda.extract(  kwargs["node"]  )

Iterate downstream (outputs and dependents)

I made a script, which recursively iterates down the network, over all the selected nodes' outputs and dependents. I use it to find all the Filecache SOP nodes (just not to overlook something). It can be also used to generate TOP chain.

import wf_network_utils
reload(wf_network_utils)

# -------------------
# iteration
def fc_iterate_downstream (node,go_below_fc) :
    global dependents
    global filecaches
    candidates = list(set(  node.outputs() + node.dependents()  ))

    for candidate in candidates :
        if candidate not in dependents :
            dependents.append(candidate)

            # candidate is filecache
            if candidate.type().name() == "filecache" :
                filecaches.append(candidate)

                # go below this filecache
                if go_below_fc == True :
                    fc_iterate_downstream(candidate,go_below_fc)

            # candidate is not a filecache
            else:
                fc_iterate_downstream(candidate,go_below_fc)

        # If candidate's parent is a dopnet, check also dopnet,
        # because Dop I/O is dependent on Dop Object,
        # which is often independent.
        parent = candidate.parent()
        if parent.type().name() == "dopnet" :
            if parent not in dependents :
                dependents.append(parent)
                fc_iterate_downstream(parent,go_below_fc)
# -------------------

# init lists
dependents  = []
filecaches  = []
node        = hou.selectedNodes()[0]
go_below_fc = True

# iterate
fc_iterate_downstream(node, go_below_fc)

# print list and copy to clipboard
wf_network_utils.fc_list_print(filecaches)

 

def fc_list_print (filecaches) :
    import wf_network_ui
    reload(wf_network_ui)

    # remove duplicates
    filecaches = list(dict.fromkeys(filecaches))

    # create search pattern
    print '\n-------    found:  -------'
    pattern       = ''
    pattern_count = 0

    for fc in filecaches :
        # set session Id
        session_id = fc.sessionId()
        wf_network_ui.parm_create (fc, "integer", "session_id", "session_id")
        wf_network_ui.parm_update (fc, "integer", "session_id", hidden="True")
        fc.parm("session_id").set(session_id)

        # create search pattern
        if pattern_count > 0 :
            pattern   += ' | '
        pattern       += "session_id" + '=' + str(session_id) + ''
        pattern_count += 1
        print 'FILECACHE: ' + str(fc.path()) + ''

    hou.ui.copyTextToClipboard(pattern)
    print '--------------------------'

 

Render multiple OpenGL ROPs simultaneously

I use OpenGL ROP a lot, and I usually have more of them in the scene. They can often render simultaneously without significant decrease of the speed. So I did a script, which creates .py file (with a simple render script) for each selected ROP and also creates Windows .bat file, which launches hython for each created .py to run them simultaneously.

 

I will investigate the possibility to assign different GPUs to each hython (HOUDINI_OCL_DEVICENUMBER variable is not taken into account here).

 

# create .py script for each selected ROP, to render it
# create .bat file, which launches hython for each created .py to run them simultaneously
def batch_script_rop () :

    import os

    path_hip     = hou.expandString('$HIP')
    path_hipfile = hou.expandString('$HIPFILE')
    path_hython  = hou.expandString('$HB') + '/hython.exe'
    path_scripts = path_hip + '/scripts/'

    if not os.path.exists(path_scripts):
        os.makedirs(path_scripts)

    script_bat = 'REM This batch file runs multiple .py scripts at once \n'

    for rop in hou.selectedNodes() :
        # .py script
        script_rop  = '# This .py script opens a file and renders single rop\n'
        script_rop += 'hou.hipFile.load("' + path_hipfile + '")\n'
        script_rop += 'node = hou.node("' + rop.path() + '")\n'
        script_rop += 'node.render(verbose=True,output_progress=True)'
        
        # write .py to disk
        path_py = path_scripts + rop.name() + '.py'
        file_py = open( path_py, "w")
        file_py.write(script_rop)

        # .bat script
        script_bat += 'start "Render: ' + rop.name() + '" "'
        script_bat += path_hython + '" "'
        script_bat += path_py + '"\n'


    # write .bat to disk
    path_bat = path_hip + '/_render.bat'
    file_rop = open( path_bat, "w")
    file_rop.write(script_bat)

.sim files toolset

I made few little scripts to work with .sim checkpoint files. Before, I just deleted them manually. Now I have few hotkeys. The toolset is intended for long sims with keyframed behavior. Now I work on an animation for a dancer performance, which is ~ 7200 frames. I plan to update the scripts asap ... to reset multiple related DOPs (pops driven by pyro etc.) with one hotkey press.

The script contains os.remove(file_path), so check it before use.

 

  • reset the sim, hotkey: F5
    delete all the .sim cache files in the playrange
    but don't delete the first one, I want to built on it
    sim_cache_reset ()
    sim_cache_delete_playrange ()

 

  • trim the playback range start to the playhead's precedent .sim file
    hotkey: /
    playrange_to_precedent ()

 

  • add or remove one .sim spacing in playrange
    hotkeys: . ,
    playrange_extend_spacing (direction)


import os
import hou
import math
import bisect
import toolutils


#####################
###     resim     ###
#####################

# return the current dopnet node
def sim_dopnet () :
    dopnet = hou.currentDopNet()
    if dopnet == None :
        cache_on       = 0
        cache_name     = None
        cache_substeps = 1
        cache_start    = 1
        cache_spacing  = 1
    else :
        cache_on       = dopnet.parm("explicitcache").eval()
        cache_name     = dopnet.parm("explicitcachename").rawValue()
        cache_substeps = dopnet.parm("substep").eval()
        cache_start    = dopnet.parm("startframe").eval()
        cache_spacing  = dopnet.parm("explicitcachecheckpointspacing").eval()
    return [dopnet, cache_on, cache_name, cache_substeps, cache_start, cache_spacing]


# list of all $SF that may be cached with current dopnet settings
# and list of appropriate $F frames
def sim_cache_framelists() :
    dopnet, cache_on, cache_name, cache_substeps, cache_start, cache_spacing = sim_dopnet()
    framerange_end = hou.playbar.frameRange()[1]

    candidate_sf = 0
    candidate_f  = cache_start
    list_sf      = [candidate_sf]
    list_f       = [candidate_f]

    while candidate_f <= framerange_end :
        candidate_sf  += cache_spacing
        candidate_f   = math.floor((candidate_sf-1) / cache_substeps) + cache_start
        list_sf.append(candidate_sf) 
        list_f.append (candidate_f) 

    return list_sf, list_f


# find the given frame's precedent cached $F
def sim_cache_precedent_index ( frame ) :
    list_sf, list_f = sim_cache_framelists()
    precedent_index = bisect.bisect_right(list_f,frame)
    precedent_index = max (precedent_index - 1, 0)
    return precedent_index


# delete the .sim cache files in the playrange
# but don't delete the first one, we want to built on it
def sim_cache_delete_playrange () :
    dopnet, cache_on, cache_name, cache_substeps, cache_start, cache_spacing = sim_dopnet()
    hou.cd(dopnet.path()) # to expandString() correctly
    list_sf, list_f = sim_cache_framelists()
    index = 1 + sim_cache_precedent_index(  hou.playbar.playbackRange()[0]  )

    candidate_frames = list_sf[index:]
    
    # try to delete the files, if they exist
    for frame in candidate_frames :
        file_path = cache_name.replace("$SF",str(frame))
        file_path = hou.expandString(file_path)
        file_path = file_path.replace("*","_")

        if file_path.endswith(".sim") :
            try :
                os.remove(file_path)
            except :
                frame_was_not_cached = 1


# hotkey: F5
# reset the sim
def sim_cache_reset () :
    dopnet, cache_on, cache_name, cache_substeps, cache_start, cache_spacing = sim_dopnet()

    if cache_on:
        # delete .sim files
        message = "Delete .sim files in playback range? \n\n" + str(dopnet.path())
        if hou.ui.displayMessage(message, buttons=("OK", "Cancel")) == 0:
            sim_cache_delete_playrange()

    if dopnet:
        dopnet.parm("resimulate").pressButton()

    hou.setFrame(hou.playbar.playbackRange()[0]) 


# hotkey: /
# trim the playback start to the playhead's precedent cached .sim file
def playrange_to_precedent () :
    list_sf, list_f = sim_cache_framelists()
    index = sim_cache_precedent_index(  hou.intFrame()  )

    frame_start = list_f[index]
    frame_end   = hou.playbar.playbackRange()[1]
    hou.playbar.setPlaybackRange( frame_start, frame_end )


# hotkeys: . ,
# add or remove one cached spacing in playrange
def playrange_extend_spacing (direction) :
    list_sf, list_f = sim_cache_framelists()
    index = sim_cache_precedent_index(  hou.playbar.playbackRange()[0]  )
    index = max (index + direction, 0)

    frame_start = list_f[index]
    frame_end   = hou.playbar.playbackRange()[1]
    hou.playbar.setPlaybackRange( frame_start, frame_end )


#####################
###   resim end   ###
#####################

Screen space silhouette and volume trails

I would like to experiment with "multiple spaces" effects.
This is the first attempt ... screen space silhouette and volume trails. It evaluates ~realtime.

 

It uses prim neighbours array search made by Wooshum, https://bit.ly/2NvDLP9, from odforce.
It also uses geo cull made by F1, https://bit.ly/2PhINjT. Links are included in the file.

The silhouette trick:

  • create array of primitive neighbours, preferably on the static geo
  • transform the geo into the camera space (toNDC)
  • almost flatten the geo in the camera space (@P.z). This exaggerates any normal angle "gradient" and flattens any peaks pointing to/from the camera.
  • angle of neighbouring primitive normals to get the silhouette

Here is the .hip file.

OpenGL Vertex Cache

I have built a small HDA to utilize the GPU OpenGL Vertex Cache. I use it for fast realtime SOP viewport previews. It is much faster then the Cache SOP, but it uses Alembic as a disk cache, so it is usually a dead end. Any tips how to utilize GPU Vertex Cache without .abc are highly appreciated! An example of speed comparison, the scene is attached here:

Cache SOP ~ 25 fps
OpenGL Vertex Cache ~ 45 fps
OpenGL Vertex Cache, point cloud  ~90 fps

I have also prepared simple UI to render remotely on my HQueue farm.

With HScript we may set the memory limits:
http://www.sidefx.com/docs/houdini/commands/glcache.html

I got an answer from SideFX (twod) so I just repost it here:

You can't use it (the Vertex Cache) directly. Packed Alembic and Packed Disk Primitives/Sequences can cache geometry there, and all currently displayed geometry will use the cache until it's deleted. But there's no way to manually cache things.

HQueue on Windows 10

I have successfully set up my Indie farm after all the new Windows 10 security updates. One Indie license allows to run 4 PCs simultaneously, as it gets shipped with 3 Indie Engines and 3 Mantra nodes. Main issues were:

  • Lanman Workstation - logons security
  • CredSSP encryption (Remote desktop)
  • SMB 1.0/CIFS File Sharing Support
  • Use of IP addresses everywhere in the setup, instead of using PC names

If anybody is having issues, I will be happy to help.

Find all parms referencing this parm

The "Find Node" in 16.5 is really good, I use it quite a lot. But when I want to find all nodes, whose (any) Raw Parameter references Source Volume's "scale_velocity", then I have to know the name of the Parameter. Wildcard in the "Raw Parameter" in the Find Node filter is not accepted.

One way to find all the parms, which reference the "scale_velocity" is the Parm.parmsReferencingThis() function. But the "Find Node" has great usability, it selects and auto-focuses nodes.  So I have written a script, which finds all the references, prints them in the console and generates a pattern as it is required by the Find Node form (http://www.sidefx.com/docs/houdini/network/find.html). Then I just Paste it from Clipboard to the Find Node.

I have also added the functionality to the PARMmenu.xml in a similar way, as Juraj Tomori describes it on his great blog.

def find_parm(parmname) :

    if parmname == None :
        text = hou.ui.readInput("Search text:", buttons=("Search", "Cancel"))[1]
    else :
        text = parmname

    container     = wf_selection.container()
    nodes         = container.allSubChildren()
    pattern       = ''
    pattern_count = 0

    print '----    found:   ----'
    for node in nodes :
        parms = node.parms()
        for parm in parms :
            raw = parm.rawValue()
            if raw.find(text) > -1 :
                if pattern_count > 0 :
                    pattern   += ' | '
                pattern       += parm.name() + '~=*' + text + '*'
                pattern_count += 1
                print 'NODE: ' + str(node) + '   // PARM: ' + parm.description() + "   // RAW: " + raw
    print '--------------------------'

    hou.ui.copyTextToClipboard(pattern)

 

Approximate normal distribution, bias and ease

This is faster than a ramp and it is quite predictable, and the ends are smooth:

@P.y = 4 * smooth(0, 1, @P.x) * smooth(0, 1, 1-@P.x);

Also you may shape it further with ease and bias functions, ends are still smooth:

void distort_bias(float dimension, bias) 
{
    if (dimension <= 0) dimension = 0; else if (dimension >= 1)
        dimension = 1;
    else
        dimension = (1-bias) / (((1.0 / dimension) - 2) * (bias) + 1);
}
float distort_bias(float dimension, bias) 
{
    float eval;
    if (dimension <= 0) eval = 0; else if (dimension >= 1)
        eval = 1;
    else
        eval = (1-bias) / (((1.0 / dimension) - 2) * (bias) + 1);
    return eval;
}
void distort_ease(float dimension, ease) 
{
    if(dimension<0.5)
        dimension = distort_bias(dimension*2,ease)/2;
    else
        dimension = 1 - distort_bias((1-dimension)*2,ease)/2;
}

Python snippets

list all nodetypes

nt_categories = hou.nodeTypeCategories().items()
for nt_category in nt_categories :
    nodetypes = nt_category[1].nodeTypes()
    for nodetype in nodetypes :
        print nodetype

node list pattern glob

pattern   = "font_bar_*"
node_glob = hou.node("..").glob(pattern)
str_out   = ""

for node in node_glob: 
    str_out += pwd().relativePathTo( node ) + " "
    
return str_out

ctrl shift pressed when selectGeometry

import hou
import toolutils
from PySide2 import QtGui, QtCore, QtWidgets
def sel():
    selection = toolutils.sceneViewer().selectGeometry(prompt='Select POINTS', sel_index=0,   \
            allow_drag=False,  # drag and transform, we don't want that  \  
            quick_select=True,   # select on mouse_up, immediately, don't expect to be accepted  \
            use_existing_selection=True, initial_selection = None,   \
            initial_selection_type = None, ordered=False,   \
            geometry_types=(hou.geometryType.Points,),   \
            primitive_types=(), allow_obj_sel=True,   \
            icon=None, label=None, prior_selection_paths=[],   \
            prior_selection_ids=[], prior_selections=[],   \
            allow_other_sops=True, consume_selections=True)
    
    
    node = selection.nodes()[0]
    sel_geo = node.geometry()
    sel_pattern = str(selection)
    key_pressed = QtWidgets.QApplication.keyboardModifiers()
    if key_pressed == QtCore.Qt.ShiftModifier:
        print "shift"
    if key_pressed == (QtCore.Qt.ControlModifier | QtCore.Qt.ShiftModifier) :
        print "shift-ctrl"
        
    for sel_point in sel_geo.globPoints(sel_pattern):
        try:
            path_hda = sel_point.stringAttribValue("path_hda")
            frame = sel_point.intAttribValue("frame")
            print path_hda + "/" + str(frame)
        except:
            attributes_missing = 1
     # repeat
    sel()
    
sel()

enlarge bounding boxes

thanks F1!

node = hou.node('/obj/geo1/file1')
geo = node.geometry()
bba = geo.boundingBox()


node = hou.node('/obj/geo1/transform1')
geo = node.geometry()
bbb = geo.boundingBox()

bba.enlargeToContain(bbb)

add event callback

has to be a tuple of events, thanks Graham!

def name_changed(node, event_type, **kwargs):
   print("The geometry object is now named", node.name())

hou.node("/obj/geo1").addEventCallback( (hou.nodeEventType.NameChanged,) , name_changed)

reference copy

Thanks to Henry, Toadstorm!

parent = hou.node('/path/to/parent')
nodeToCopy = parent.node('./nodeToCopy')
parent.copyItems( (nodeToCopy, ), channel_reference_originals = True)

load from file

Thanks to Alex!

geo = hou.pwd().geometry()
for f in random_file_list:
 geo.loadFromFile(f)

create attribute

Thanks to F1!

geo = hou.pwd().geometry()
new_p = geo.addAttrib(hou.attribType.Point, 'new_p', 0.0)
old_p = geo.findPointAttrib('old_p')

for point in geo.points():
    val = point.attribValue(old_p)
    point.setAttribValue(new_p, val)

### Or do it this way:

geo = hou.pwd().geometry()
new_p = geo.addAttrib(hou.attribType.Point, 'new_p', 0.0)

values = geo.pointFloatAttribValues('old_p')
geo.setPointFloatAttribValues('new_p', values)

first keyframe's time

thanks to Toadstorm!

keyframes = hou.node('/path/to/node').parm('someParameter').keyframes()
min = 999999
for k in keyframes:
    if k.frame() < min:
        min = k.frame()
return min

event callback on parm change

thanks to MrScienceOfficer!

node.addEventCallback(hou.nodeEventType.ParmTupleChanged, call_func)

all the tabs

thanks to Bonsak!

tabs = hou.ui.paneTabs() # Get all the tabs

for tab in tabs: # Loop over them
    if tab.type() == hou.paneTabType.NetworkEditor: # Test the type
        tab.setPref('showdep','2') # Set

#get
#networkeditor.getPrefs()

pane under cursor

thanks to Varomix!

def getNetworkType():
    # get desktop
    curdesk = hou.ui.curDesktop()
    activepane = curdesk.paneTabUnderCursor()
    return activepane.type()

Bind light's "Enable" checkbox to the display flag

obj = hou.node(".")
return obj.isDisplayFlagSet()

Reference SOP input from chopnet Geometry

chopnode = hou.node("..")
chopname = chopnode.name()                  # chopnet_smooth
chopnet,task = chopname.split('_')          # chopnet smooth
channelnodepath = "../../channel_" + task   # ../../channel_smooth
channelnode = hou.node(channelnodepath)
inputpath = channelnode.inputs()[0].path()
return inputpath

instances() nodes of type in the scene

thanks to julian johnson!

node_type = hou.objNodeTypeCategory().nodeTypes()['bone']
for x in node_type.instances():
    print x

hou.playbar.setPlaybackRange(self, start, end)

print node.type().name() # file, filecache, delete, solver, dopimport, ...
node = hou.pwd()
path = node.path()
objContextNodeName = path.split('/)[-2]

VEX Arrays and Matrices

To create:

f[]@myFarray = {0,1,2,3,4,5,6};
i[]@myIarray = {0,1,2,3,4,5,6};
v[]@myVarray = {{1,2,3},{4,5,6}};
v@readvect = @myVarray[1];

3[]@myMatrixarray = { {0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0}};

matrix3 mat = {1,2,3,4,5,6,7,8,9};
3[]@myMatrixarray[0] = mat; //assign

Read from prim:

3[]@rotmatrixarray[index] = prim(0, "rotmatrix", index );

Read from attrib:

vector array[] = v[]@atrail;
vector position = array[i];

Append:

float solve_beats[] = {};
for (int i = 0; i<700; i++)
    {
    if (val_curr >= 0.1) append(solve_beats, i);
    }
    
int lenght = len(solve_beats);

Create empty, resize, faster:

f[]@array;
resize(@array, len);

VEX Quaternion orient, N, up

Without @orient:

v@N; // +Z axis of the copy
v@up; // +Y axis of the copy

Not ambiguous @orient, thanks to Matt Estela!

matrix3 m = maketransform(@N,@up);
@orient = quaternion(m);

Representing a quaternion from an angle and axis. The angle is specified in radians:

float angle = radians(90);
vector axis = set(0, 1, 0);
p@rot = quaternion(angle, axis);

Representing a quaternion from a 3×3 rotational matrix:

float angle = radians(90);
vector axis = {0,1,0};
matrix3 m = ident();
rotate(m, angle, axis);
@orient = quaternion(m);

PI eight digits

#include "math.h";
float e = M_E; //2.7182818
f@angle = PI; //3.1415926
f@angle = radians(180);

Quaternion to Euler rotations
thanks to DJ

vector  qToE(vector4 q_value){
    float   q_0 = q_value.w ; 
    float   q_1 = q_value.x ; 
    float   q_2 = q_value.y ; 
    float   q_3 = q_value.z ; 
    vector  out = {0,0,0} ; 
            out.x = degrees(atan2(2*(q_0*q_1+q_2*q_3), (1-2*(q_1*q_1+q_2*q_2)))) ; 
            out.y = degrees(asin(2*(q_0*q_2-q_3*q_1))) ;
            out.z = degrees(atan2(2*(q_0*q_3+q_1*q_2), (1-2*(q_2*q_2+q_3*q_3)))) ;
    return out ; 
}

v@ro = qToE(p@orient);

VEX snippets

regex string replace

thanks to F1!

@name = re_replace(r'(.+?)\d+', r'\1', @name);

https://regexr.com/3k7l4

from space - to space - and back again

thanks to F1!

vector P = ptransform("/obj/cam1", @P);

vector pos = set(abs(P.x), abs(P.y), @Time);
vector noise = efit(vector(noise(pos)), 0, 1, -1, 1);
vector mirror = set(sign(P.x), sign(P.y), 1);

@P = ptransform("/obj/cam1", "space:current", P + noise * mirror);

for each group

thanks to acey195!

string grps[] = detailintrinsic(0, "primitivegroups");
foreach(string grp; grps)
{
    int groupCenterPt = addpoint(0,getbbox_center(0, grp));
}

to and from camera space

thanks to F1!

@P = toNDC(chs("camera"), @P);
@P.z = -ch("depth");
@P = fromNDC(chs("camera"), @P);

evaluate @ as string

thanks to Jake Rice!

int selector = chi("test");
string group = "\@class=" + itoa(selector);
int handle = pcfind(0, group, "P", @P, 10, 10);

Find min / max value

thanks to petz!

setdetailattrib(geoself(), "min_val", @uv.y, "min");
setdetailattrib(geoself(), "max_val", @uv.y, "max");

List of Primitive neighbours by half edges

thanks to petz!

int     prim_edge, edge, prim, i, n, num;
string  neighbours = "";

i = 0;
prim_edge = primhedge(@OpInput1, @primnum);
while(i < primvertexcount(@OpInput1, @primnum))
{
    num = hedge_equivcount(@OpInput1, prim_edge);
    n = 0;
    while(n < num)
    {
        edge = hedge_nextequiv(@OpInput1, prim_edge);
        prim = hedge_prim(@OpInput1, edge);
        if(prim != @primnum)
            neighbours += sprintf("%g ", prim);

        prim_edge = edge;
        n++;
    }

    prim_edge = hedge_next(@OpInput1, prim_edge);
    i++;      
}

s@neighbours = neighbours;

Insert a point to the middle of the curve

thanks to awong from discord

int prim_num = 0;
int new_point = addpoint(0, {0,0,0});
int insert_position = 3;

// store the old vertices
int old_vertices[] = primvertices(0, prim_num);

// add a placeholder using an arbitrary point
addvertex(0, prim_num, 0);

// replace the vertex at the desired position
setvertexpoint(0, prim_num, insert_position, new_point);

// replace the vertices after the inserted position
for(int i = insert_position; i < len(old_vertices); i++)
    setvertexpoint(0, prim_num, i + 1, old_vertices[i]);

U coordinate on closed prim

thanks to f1480187

// Detail wrangle.
float length = primintrinsic(0, "measuredperimeter", 0);

// Set to false if value of 1.0 is unwanted for the last point
// of closed prims and instead need to be treated as if point with
// u=1 is coincident with the first point (like in unrolled prims).
int as_open = true;

// Compensate for closing auto-edge length.
if (as_open && primintrinsic(0, "closed", 0))
{
    vector auto_edge = point(0, "P", 0) - point(0, "P", @numpt-1);
    length -= length(auto_edge);
}

// Compute curve u.
float passed = 0;
for (int i = 1; i < @numpt; i++)
{
    vector d = point(0, "P", i) - point(0, "P", i-1);
    passed += length(d);
    setpointattrib(0, "u", i, passed/length);
}

Print to console

printf("OUT=%f;  ", out); //  %s string,   %i integer

Compilable For Each Material

s@shop_materialpath = "/obj/butterflies/shop/hud";
s@shop_materialpath += itoa( detail(1,"iteration") %10 );

Random unit vector in the direction

thanks to Javier

vector center = {0,0,1};
float  maxangle = chf("maxangle");
v@N = sample_direction_cone(center, maxangle, rand(@ptnum) );

Other inputs

v@P.y = v@opinput1_P.y;
f@y = v@opinput1_P.y;

//2015: @opinput1_P binds as a float

Group

@group_my = 1;
if (@group_my) v@N = {0,1,0};

Group adhoc syntax / expression

n-m:step
@myattr="foo bar"
@P.y>0
@ptnum%(@numpt-1)==0 // first and last point

String

s@name = sprintf("piece%d", @class);
s@name = sprintf("nameX%dY%d",@P.x,@P.y);

// padzero
int padzero = chi("padzero");
string format = '%0' + itoa(padzero) + 'd';
s@frame = sprintf(format, @Frame) ;

Time Globals

@Time //Float time ($T)
@Frame //Float frame ($FF)
@SimTime //Float simulation time ($ST), only present in DOP contexts.
@SimFrame //Float simulation frame ($SF), only present in DOP contexts.
@TimeInc //Float time step (1/$FPS)

Centroid

vector min, max;
getbbox(0, min, max);
v@center = (min+max)/2;

Attrib type info

setattribtypeinfo(0, "point", "myvector", "vector");

Comparisons

==, !=, <, <=, >, >=

The logical (&&, ||, and !) and bitwise (& |, ^, and ~) operators are only defined for integers. AND OR:

if( res.x<precis || t>tmax ) break;

Shortcut to if statement

float a = condition? b: c;

 

@Cd

@Cd.g;
@Cd.y;

Find and delete ngons

int np[] = primpoints(0,@primnum);
int lnp = len(np);

if( lnp > 3 ) {
    removeprim(0, @primnum, 1); // 1 = and points
}

P smooth

int maxpoints = chi("maxpoints");
float radius = chf("radius");

int handle = pcopen(0, "P", @P, radius, maxpoints);
@P = pcfilter(handle,"P");
pcclose(handle);

Init Attrib Interpolate

//random prim
int prim = floor( rand(@ptnum) * nprimitives(1) );
i@sourceprim = prim;

//random speed, looping
float speed = fit01( rand(@ptnum), chf("speed_min"), chf("speed_max") );
float dist = (@Time * speed) % 1;
v@sourceprimuv = set(dist,0.5,1.0);

Prims from point array

thanks to @petz from odforce:

int point = addpoint(0, @P);
int points[] = primpoints(0, @primnum);
for(int i = 0; i < len(points); i++)
{
    int point_array[] = array(points[i - 1], points[i], point);
    int prim = addprim(0, "poly", point_array); 
}
removeprim(0, @primnum, 0);

 

The geometry functions, table from the docs

http://www.sidefx.com/docs/houdini/vex/geometry

vertexpoint()
pointvertex()
vertexnext()
vertexprev()
vertexindex()
primvertexcount()
vertexprim()
vertexprimindex()

Write point numbers to an array

thanks to @petz from odforce

//in detail mode
i[]@points = expandpointgroup(@OpInput1, "!");

//in point mode
int point[] = array(@ptnum);
setdetailattrib(geoself(), "points", point, "append");

Carve

#include <groom.h>

adjustPrimLength(0, @primnum, @perimeter, @perimeter*@dist);

Visibility

thanks to houdinitricks.com

@gl_wireframe = true;
@gl_lit = true;

Split string

thanks to Chris, http://blog.cerebero.com

//Split based on '_'
string bars[] = split(s@shop_materialpath, "_");

//Use last element as attribute value
s@mtlGrpName = bars[-1];

0-360 angle between vectors

thanks to f1480187

#define PI 3.1415926535897932384

float angle = acos(dot(v@up, v@aim));
int first_half = sign(dot(v@z, cross(v@up, v@aim))) >= 0;
angle = first_half ? angle : 2*PI - angle;

@angle = degrees(angle);

Arrays

append() // end
push() // end
insert() // index (negative from the end, inserting past the end will grow the array)

 

op expressions

Input path

//this digits
opdigits('.')

//this path
op:`opfullpath('.')`

//parent's digits, 0 is the input
opdigits("`opinputpath('.',0)`")

//string detail from the first input
`details(0, "myname")`

//from opname "node_MY" to "MY"
`strreplace(opname("."), "node_", "")`

Relative op: references inside POP VEX expression

The path after op: must be an absolute path (that is, starting from the root of the scene hierarchy with /). However, you can work around this by converting a relative path to an absolute path using the opfullpath expression function inside backticks:

op:`opfullpath('../../volume1')`

VEX Banked Turn

Thanks to Javier Toledo for the initial script. I have added the side_mult_error multiplier and few other things.

Polyframe the @tangentu, correct @up and gen @side

//create perpendicular SIDE
v@up = {0,1,0};
v@side = cross(v@up, v@tangentu);

//correct the UP
v@up = cross(v@tangentu, v@side);
v@N = v@tangentu;

 

Add Curvature and then Smooth

int prevId = clamp(@ptnum-1,0,@numpt);
int nextId = clamp(@ptnum+1,0,@numpt);

vector prevPos = point(0,"P",prevId);
vector nextPos = point(0,"P",nextId);

vector prevSide = point(0,"side",prevId);
vector nextSide = point(0,"side",nextId);

float side_mult_error = chf("side_mult_error") * 0.001;

vector prevDisp = prevPos + prevSide * side_mult_error;
vector nextDisp = nextPos + nextSide * side_mult_error;

float dist = length(prevPos - nextPos);
float dispDist = length(prevDisp - nextDisp);

//ratio by distances
f@ratio = (dispDist/dist) - 1;

f@ratio *= chf("curvature_mult") * 10;
f@ratio /= side_mult_error;
f@ratio = clamp(f@ratio, -chf("curvature_clamp"), chf("curvature_clamp"));

//rotate matrix
matrix3 rot = ident();
rotate(rot, f@ratio, v@tangentu);

//apply
v@side *= rot;
v@up *= rot;

 

Node Shapes setUserData()

This is a note for me, because I don't remember the nodeshape names and from time to time, I want to change them:

>>> node.setUserData("nodeshape", "trapezoid_up")

('rect', 'bone', 'bulge', 'bulge_down', 'burst', 'camera', 'chevron_down', 'chevron_up', 'cigar', 
'circle', 'clipped_left', 'clipped_right', 'cloud', 'diamond', 'ensign', 'gurgle', 'light', 'null'
, 'oval', 'peanut', 'pointy', 'slash', 'squared', 'star', 'tabbed_left', 'tabbed_right', 'tilted',
 'trapezoid_down', 'trapezoid_up', 'wave')

Network Editor, remember the previously flagged node and unflag to it

I like to walk through the nodes and preview different states of the stream. Houdini has its own logic to "unflag" the Display Flag. It doesn't jump back to its last position. I customized my own unflagging to the previously flagged node. If you like it, you may use my script (and hotkey it). Or you may customize it to fit your needs, for example always unflag to the last node.

The python function responsible for parameter UI generation is called flag_display () and its code is here.

Pipeline notes

Graphical Function Explorer

www.mathopenref.com/graphfunctions.html

C4D camera properties

Sensor Size (Film Gate) = Aperture
Focal Length = Focal Length
1920 × 1080 - Focal Length = 150 mm (full = full)
1920 × 360 - Focal Length = 50 mm (third = third)
1920 × 360 - right cam = Screen Window X/Y = 1
1920 × 360 - left cam = Screen Window X/Y = -1

Sublime

Settings:

{
	"close_windows_when_empty": true,
	"color_scheme": "Packages/Color Scheme - Default/Monokai.sublime-color-scheme",
	"font_size": 10,
	"ignored_packages":
	[
		"Vintage"
	],
	"translate_tabs_to_spaces": true,
    "mini_diff": false
}

Key bindings:

[
  { "keys": ["alt+shift+up"], "command": "swap_line_up" },
  { "keys": ["alt+shift+down"], "command": "swap_line_down" },  
  { "keys": ["ctrl+alt+q"], "command": "toggle_record_macro" },
  { "keys": ["ctrl+q"], "command": "close" },
  { "keys": ["f1"], "command": "vex_helpcard" },

  { "keys": ["alt+pagedown"], "command": "next_view" },
  { "keys": ["alt+pageup"], "command": "prev_view" },
  { "keys": ["ctrl+tab"], "command": "next_view" },
  { "keys": ["ctrl+shift+tab"], "command": "prev_view" },
]

Hotkeys:
Alt F3 - multiple cursors
Ctrl D - search and select
Ctrl Shift P - search command palette
Ctrl Alt A - Align

Packages:
Text Pastry - Numbers, multiple cursors
Alignment
Automatic Backups

Package settings
Preferences > Package Settings > VEX

{
    "popup_max_width": 900,
    "popup_max_height": 900,
}

External editor file watcher
here

Rigging

BVH, BIP

Regex

all the content in braces
\{[^\}|^\{]*\}

all multilines
^\n

mozilla stylish

code {
    font-size: 15px;
    background-color: rgba(100, 100, 190, 0.2);
    padding: 0.1em;
}

midi

http://flashmusicgames.com/midi/mid2txt.php