Houdini: find all unused materials

Hey here is a python function that returns all unused materials of a scene. After a while Houdini scenes tend to have more and more materials that are not needed. This function helps to find all stuff that has no dependency in the scene. I’ve added a searchContext List that defines all the types of materials we were working with. So if you are working with other renderes (like vray or renderman), it is needed to add this type of materials to the list. If you don’t know your type of material, you can use the function in the third line. Simply put this into the python source editor, select your material and hit apply. The console should give you informations over the type.

 

 

  
import hou

#debug Line if you want to add new searchContexts
#print hou.selectedItems()[0].type()

#findUnUsedMaterails function
#returns a tupple of materialpathes
def findUnUsedMaterials():
    #define an empty list that will returned in the end
    outList = []
    #set the search Contexts
    searchContext = [["mat", "principledshader::2.0"],\
                    ["mat", "redshift_vopnet"],\
                    ["mat", "redshift::Material"], \
                    ["mat", "materialbuilder"],\
                    ["mat", "arnold_materialbuilder"],\
                    ["shop", "RS_Material"],\
                    ["shop", "vopsurface"],\
                    ["shop", "redshift_vopnet"],\
                    ["shop", "arnold_vopnet"]]
    #loop over the searchContext variable
    for inContext in searchContext:
        #define the node Context Type, mat or shop
        if inContext[0] == "mat":
            node_type = hou.nodeType(hou.vopNodeTypeCategory(), inContext[1])
        if inContext[0] == "shop":
            node_type = hou.nodeType(hou.shopNodeTypeCategory(), inContext[1])
        #get all Instances of the Mat type
        Mats = node_type.instances()
        for Mat in Mats:            
            #set a checker variable for adding materials to the outList
            checker = 0
            #get all dependencies of the current material instance
            allDepents = Mat.dependents() 
            #check if there are dependencies
            if allDepents:
                #loop over all dependencies of the material instance
                for currDepents in allDepents:
                    #if there is an dependency set the checker to 1
                    if (currDepents.type().name() != inContext[1]):
                        checker = 1 
                #if the checker is still == 0, meaning that there is no dependency append the material path to the outList
                if(checker == 0):
                    outList.append(str(Mat.path()))
            else:
                #if there is no dependency append the material paht the outList
                outList.append(str(Mat.path()))
    #return the list of unused materaials
    return outList

print findUnUsedMaterials()


        
        

PS: If you are a tough guy, loop over the returned list and delete the nodes 😀

  
unusedMats = findUnUsedMaterials()
for unusedMat in unusedMats:
    hou.node(unusedMat).destroy()

Function of the Week: sample_direction_cone

Hey,

 

today I’ll establish my personal Node/Function of the week. This week it’s the vex function “sample_direction_cone”.

 

The help says: “Returns a unit vector, i.e. a vector of length 1, based on u. Given uniform random u pairs of values in [0,1), the returned unit vectors will be uniform random and continuous with respect to u on the surface of the unit sphere, in the area within maxangle of the direction indicated by center.”

 

So what to use this for? My usual cases are randomization of velocity vectors, ray cast operations or additive spread-transforms of scattered pieces. XSI operators will know this kind of function from the ICE function “randomize vector by cone” – This is the equal Houdini function.

Here is an point wrangle example, that demonstrates the function in combination with an raycast function.

v@dir = sample_direction_cone(chv("initVector"),ch("Angle") ,set(ch("spreadU")*rand(@ptnum*20), ch("spreadV")*rand(@ptnum)));
vector pos;
vector uvw;
int prim = intersect(1, @P, @dir*20, pos, uvw);
if(prim>=0){
@P = primuv(1, "P", prim, uvw);
}

moving the privot of packed primitives

Today I had to move all pivots of an packed primitive object for an rbd simulation. If the centroid, center of mass or origin doesn’t fit your needs, maybe this could be interesting.

// move the pivot of all packed primitives

vector oMoveIntrinsicPivot = chv("MoveIntrinsicPivot");
@P += oMoveIntrinsicPivot;
setprimintrinsic(0, "pivot",@primnum,oMoveIntrinsicPivot,"add");       
        

Creating bgeo files from selected objects

After importing FBX files, hip files are getting very large. To avoid saving all geometry for every scene it makes sense to save the data to disk. For 3-4 objects it’s no big deal to do this by hand, but when dealing with hundreds of objects, this is getting annoying. Here is a small snipped, that reads the current houdini selection and exports bgeo.sc files to $HIP/geo/`opname(“..”)`/`opname(“..”)`.bgeo.sc. This is really the basic approach without any versioning, it gives you a good starting point how to set this up.

import hou  
oSelection = hou.selectedItems()

if oSelection == 0:
    print "nothing is selected"
else:
    for oSelectedNode in oSelection:
        print oSelectedNode.name()
        c = oSelectedNode.children()
        for n in c:
            p = n.path()
            node = hou.node(p)
            type = hou.nodeFlag.Render
            flag = node.isGenericFlagSet(type)
            if flag == 1:
                oFlaggedNode = n
                #print (p + " Render Flag is " + str(flag))
        oNewROPNode = oSelectedNode.createNode("rop_geometry")
        oNewROPNode.setInput(0, oFlaggedNode )
        oNewROPNode.parm("sopoutput").set('$HIP/geo/`opname("..")`/`opname("..")`.bgeo.sc')
        oNewROPNode.render()
        oNewFileNode = oSelectedNode.createNode("file")
        oNewFileNode.parm("file").set('$HIP/geo/`opname("..")`/`opname("..")`.bgeo.sc')
        oNewFileNode.setDisplayFlag(True)
        oNewFileNode.setRenderFlag(True)
        c = oSelectedNode.children()
        for n in c:
            p = n.path()
            node = hou.node(p)
            type = hou.nodeFlag.Render
            flag = node.isGenericFlagSet(type)
            if flag == 0:
                n.destroy()
        
        

Select objects with this material

Like the “Assign Material to Selection” function, all Softimage/Maya Guys know the “Select objects with his martial” button. I know this is already an option under the material palette. But who uses this? So here is the code to get the same functionality in Houdini. Just copy/paste the code into this file: C:\Users\USER\Documents\houdini17.0\OPmenu.xml And you are done. On the next houdini restart it should pop up on the right mouse button menu.

 

<?xml version="1.0" encoding="UTF-8"?>
<menuDocument>
    <menu>
        <scriptItem id="TM_Util_SelectObjectsWithThisMaterial">
            <label>TM Tools: Select Objects with this Material</label>
<scriptCode><![CDATA[
import hou
oselectedNodes = hou.selectedNodes()
oShaderTypes = ["redshift::Material", "principledshader::2.0", "redshift_vopnet", "material" ]

for mats in oselectedNodes:
    oShaderCheck = 0
    oShaderType = str(mats.type().name())
    for item in oShaderTypes:
        if oShaderType in item:
                oShaderCheck = 1
    #print mats.name()
    if oShaderCheck == 1:
        oNodes = mats.dependents()
        mats.setSelected(0)
        for oNode in oNodes:
            if str(oNode.type().name()) == "geo":
                #print oNode.path()
                oNode.setSelected(1)

]]></scriptCode>
        </scriptItem>


  	</menu>
</menuDocument>

Assign Material to Selection

All Maya and Softimage Guys know the option “Assign to Selection”. Here is the Houdini equivalent ;). Just copy/paste this code into the this file: C:\Users\USER\Documents\houdini17.0\OPmenu.xml

Node: if there is no file yet, just create one. Houdini should pick it up on the next startup. And as you see, currently it works exclusively for redshift materials and the standard principed shader. If you want to use other shader types add them to the oShaderTypes variable.

<?xml version="1.0" encoding="UTF-8"?>
<menuDocument>
    <menu>
        <scriptItem id="Util_AssignMaterialToSelection">
            <label>Tools: Assign Material to Selection</label>
<scriptCode><![CDATA[
import hou
shader = kwargs.get("node", None)
oselectedNodes = hou.selectedNodes()
oShaderCheck = 0
oShaderTypes = ["redshift::Material", "principledshader::2.0", "redshift_vopnet", "material" ]
oShaderType = str(shader.type().name())


for item in oShaderTypes:
    if oShaderType in item:
        oShaderCheck = 1

if oShaderCheck == 1:
    for oNode in oselectedNodes:
        if str(oNode.type().name()) == "geo":
        	#print "assign" + str(shader.path()) + " to " + str(oNode.path())
        	oNode.parm("shop_materialpath").set(shader.path())


]]></scriptCode>

Retiming Stuff (the old way)

Hey, last week I had the case that I had to do some liquid retiming within Houdini 16.5. Meaning, that there is no proper retime SOP as in Houdini 17. So I did it the classical way with findattrib. If you are in Houdini 16.x this is definitively a way to check out. I have attached an image of my tree. It works really nice, also with changing pointcounts. The only thing that I had to care about was to save an id attribute within my flip simulation. 

Merge Objects into new Geometry

Hey! long time no post. But, now it’s time to post some stuff again. The first small script I want to share is a script, that has been handy during the last productions. It merges a geo object selection to an new geo and adds it’s material as primitive material. Maybe you were asking, why we sometimes are working like this. Houdini doesn’t display instanced subnets in the viewport and redshift doesn’t render instanced subnets. This is a workaround to fully instance subnet/objects in the viewport and at rendertime. Sure this is just a workflow for small assets, but for smaller products that need to be instanced this can be a solution. Also If you have to do some multi object editing. :).

import hou

#Get Houdini Selection
oSelection = hou.selectedItems()
#Create an empty List
oAllObjsforSubnet = []
#Create an empty Counter
counter = 0

#Check if the selection is empty
if oSelection:
    #The the obj context to the variable obj
    obj = hou.node("/obj")
    #create a new Geo Node on /obj level
    newNode = obj.createNode("geo", str("merged_geo"))
    #Delete the build in File node of the new Geo
    newNode.children()[0].destroy()
    #Create the hero merge node
    oMergeNode = newNode.createNode("merge")
    #Loop over the selected node
    for oNode in oSelection:
        #Check if the type is geo
        if oNode.type().name() == "geo":
            #Create an Obeject Merge node
            currentOBJMerge = newNode.createNode("object_merge")
            #Set the path to the base obj
            currentOBJMerge.parm("objpath1").set(oNode.path())
            #Check if there is an Material assigned
            if(oNode.evalParm("shop_materialpath") != ""):
                #Create a SOP materail node
                currentMaterial = newNode.createNode("material")
                #Convert relative Pathes to absolute pathes
                relPath = oNode.evalParm("shop_materialpath")
                relNode = oNode.node(relPath)
                fullpath = relNode.path()
                #Fill in the right materail path
                currentMaterial.parm("shop_materialpath1").set(fullpath)
                #Connect the current obj merge node the the materail node
                currentMaterial.setInput(0,currentOBJMerge)
                #Connect the material node the the hero merge node
                oMergeNode.setInput(counter,currentMaterial)     
            #If there is no Material assigned
            else:
                #Connect the Object Merge to the 
                oMergeNode.setInput(counter,currentOBJMerge)     
            #Increase the ounter, for the next merge node port
            counter +=1
    #Set the diplay Flag
    oMergeNode.setDisplayFlag(True)