Jump to content
Sign in to follow this  
Atom

A Geometry Object For Each File On Disk

Recommended Posts

Hi All,

I put together a small shelf tool that populates a subnet with an object for each .bgeo.sc file found in a supplied folder path. The end goal was to leverage s@instance for file based objects. s@instance can only reference objects, not disk files.

 

In this hard coded example file I am fetching 144 debris shards into 144 geometry objects which instancing can reference.

# Populates an existing subnet with a geometry node for each file found in the supplied folder.
# The File node inside the default geometry node is populated with the filename from the folder.
# The end result is you get an object for each disk object in a folder.
import os, re


def returnValidHoudiniNodeName(passedItem):
    # Thanks to Graham on OdForce for this function!
    # Replace any illegal characters for node names here.
    return re.sub("[^0-9a-zA-Z\.]+", "_", passedItem)
    
def returnFilesLike(passedFolderName, passedFileExtension = ".obj"):
    result = []
    for file in os.listdir(passedFolderName):
        if file.endswith(passedFileExtension):
            result.append(os.path.join(passedFolderName,file))
    return result
    
def createSubnetOfFileNodes(passedPath, passedType):
    root = hou.node("/obj/geo_debris_container")        # Make sure it exists before running, choose your own name.
    if root != None:
        lst_files = returnFilesLike(passedPath, passedType)
        if len(lst_files):
            for i,file in enumerate(lst_files):
                # Create a file node for each geometry in our list.
                local_name = os.path.basename(file)
                local_name_only = os.path.splitext(local_name)[0]
                geo_name = returnValidHoudiniNodeName("geo_%s" % local_name_only)
                geo_name = geo_name.replace(".","_")

                node_geo = root.createNode("geo", geo_name)
                if node_geo:
                    # The first child is a default File node.
                    n = node_geo.children()[0]
                    if n:
                        # Assign this filename as the filepath for this file node.
                        n.parm("file").set(file)
        else:
                print "No subnet container found to contain generated results."
                
createSubnetOfFileNodes ("F:/Keep/Models/Rocks/debris_sharp",".bgeo.sc")

 

Share this post


Link to post
Share on other sites

Hmm..never heard of that attribute.

Do you know how to use it?

I have a working instance that is successfully instancing objects from within the scene using s@instance. When I change to s@instancefile and reference valid geometry path on my disk I get no output at all?

 

I tried both full and fast point instancing.

Untitled-1.jpg

ap_instancefile.hipnc

Edited by Atom

Share this post


Link to post
Share on other sites

It's been awhile since I did instancing in Houdini last time, but it was working back then. Maybe there something else you have to toggle on.

  • Like 1

Share this post


Link to post
Share on other sites

After exploring @instancefile a bit more I discovered that it is useful for instancing Redshift proxies directly from the disk. The only problem is it can be tedious to setup a .rs proxy file for each standard .obj or .bgeo.sc file that you might already have located somewhere on a disk.

This script will scan a folder on the disk and make an object in a subnet for each object it finds in the folder. The code also installs a Python node after the File node that runs code to scan the imported geometry looking for unique @shop_materialpath. The code then creates a unique material named like what the geometry references. These materials are created in the Redshift format, but a Mantra implementation should not be the hard to do as well. The result is that a default material with a random color will exist in the scene for every primitive that references a unique material name.

The objects are stored inside the subnet in a sequential fashion named like the folder they reside within. EXAMPLE: Folder is named trees, objects inside the subnet will be named geo_trees_01, geo_trees_02 etc... This allows for easy proxy output from the /out context using the standard Geometry node or the Redshift Proxy Output node.

Untitled-1.jpg.7bf3eb9d11ae5616b20e55000740038d.jpg

 

Place this code in a shelf tool button and alter the path to point to your folder of .obj files (debris, grass, trees, rocks etc..)

# Populates an existing subnet with a geometry node for each file found in the supplied folder.
# The File node inside the default geometry node is populated with the filename from the folder.
# The end result is you get an object for each disk object in a folder.
import os, re

def code ():
    """
import random

node = hou.pwd()
geo = node.geometry()
lst_materials = []

# Scan primitives for material assignments.
for prim in geo.prims():
    s = prim.attribValue("shop_materialpath")
    if s not in lst_materials:
        lst_materials.append(s)

# Create materials if they do not exist.
shop_path = "/shop"
for (i,s) in enumerate(lst_materials):
    n = hou.node(s)
    if n == None:
        # Material missing, need to create.
        ary = s.split("/")
        l = len(ary)-1
        rs_vop = hou.node(shop_path).createNode("redshift_vopnet",ary[l])
        if rs_vop != None:
            # Detect the default closure node that should be created by the redshift_vopnet.
            rs_output = hou.node("%s/%s/redshift_material1" % (shop_path, ary[l]))  
            if rs_output != None:
                # Create.
                rs_mat = rs_vop.createNode("redshift::Material","rs_Mat")
                if rs_mat != None:
                    # Assign a random color.
                    random.seed(i)
                    rs_mat.parm("diffuse_colorr").set(random.random())
                    rs_mat.parm("diffuse_colorg").set(random.random())
                    rs_mat.parm("diffuse_colorb").set(random.random())
                    
                    # Wire to closure.
                    rs_output.setInput(0,rs_mat)

                    # Layout.
                    rs_vop.moveToGoodPosition() 
                    rs_mat.moveToGoodPosition()
                    rs_output.moveToGoodPosition()
                else:
                    print "No mat."
            else:
                print "No closure."
        else:
            print "No VOP."
    else:
        print "Skipping [%s], it already exists." % s

"""
def returnValidHoudiniNodeName(passedItem):
    # Thanks to Graham on OdForce for this function!
    # Replace any illegal characters for node names here.
    return re.sub("[^0-9a-zA-Z\.]+", "_", passedItem)
    
def returnFilesLike(passedFolderName, passedFileExtension = ".obj"):
    result = []
    for file in os.listdir(passedFolderName):
        if file.endswith(passedFileExtension):
            result.append(os.path.join(passedFolderName,file))
    return result
    
def createSubnetOfFileNodes(passedPath, passedType):
    root = hou.node("/obj/folder_of_models")        # Make sure it exists before running, choose your own name.
    if root == None:
        # Create new object to contain internal geometry objects.
        root = hou.node('/obj').createNode('subnet', "folder_of_models")
    if root != None:
        lst_files = returnFilesLike(passedPath, passedType)
        if len(lst_files):
            for i,file in enumerate(lst_files):
                # Create a file node for each geometry in our list.
                path = os.path.split(file)[0]
                last_folder = os.path.split(path)[1]
                node_name = "%s_%03d" % (last_folder,i) 
                geo_name = returnValidHoudiniNodeName("geo_%s" % node_name)
                geo_name = geo_name.replace(".","_")

                node_geo = root.createNode("geo", geo_name)
                if node_geo:
                    node_geo.moveToGoodPosition()
                    # The first child is a default File node.
                    n = node_geo.children()[0]
                    if n:
                        # Assign this filename as the filepath for this file node.
                        n.parm("file").set(file)
                        # Create a python node.
                        node_python = node_geo.createNode("python")
                        if node_python != None:
                            # Install code that will detect and create materials from shop_materialpath.
                            node_python.parm("python").set(code.__doc__)
                            node_python.setInput(0,n)
                            node_python.setDisplayFlag(True)
                            n.moveToGoodPosition()
                            node_python.moveToGoodPosition()
        else:
                print "No subnet container found to contain generated results."
                
folder_path = r"D:\Documents\Models\Plants\Bushes\bushes"
createSubnetOfFileNodes (folder_path,".obj")

 

Edited by Atom
  • Like 1
  • Thanks 1

Share this post


Link to post
Share on other sites
On 11/14/2017 at 7:22 PM, Atom said:

After exploring @instancefile a bit more I discovered that it is useful for instancing Redshift proxies directly from the disk. The only problem is it can be tedious to setup a .rs proxy file for each standard .obj or .bgeo.sc file that you might already have located somewhere on a disk.

This script will scan a folder on the disk and make an object in a subnet for each object it finds in the folder. The code also installs a Python node after the File node that runs code to scan the imported geometry looking for unique @shop_materialpath. The code then creates a unique material named like what the geometry references. These materials are created in the Redshift format, but a Mantra implementation should not be the hard to do as well. The result is that a default material with a random color will exist in the scene for every primitive that references a unique material name.

The objects are stored inside the subnet in a sequential fashion named like the folder they reside within. EXAMPLE: Folder is named trees, objects inside the subnet will be named geo_trees_01, geo_trees_02 etc... This allows for easy proxy output from the /out context using the standard Geometry node or the Redshift Proxy Output node.

Untitled-1.jpg.7bf3eb9d11ae5616b20e55000740038d.jpg

 

Place this code in a shelf tool button and alter the path to point to your folder of .obj files (debris, grass, trees, rocks etc..)


# Populates an existing subnet with a geometry node for each file found in the supplied folder.
# The File node inside the default geometry node is populated with the filename from the folder.
# The end result is you get an object for each disk object in a folder.
import os, re

def code ():
    """
import random

node = hou.pwd()
geo = node.geometry()
lst_materials = []

# Scan primitives for material assignments.
for prim in geo.prims():
    s = prim.attribValue("shop_materialpath")
    if s not in lst_materials:
        lst_materials.append(s)

# Create materials if they do not exist.
shop_path = "/shop"
for (i,s) in enumerate(lst_materials):
    n = hou.node(s)
    if n == None:
        # Material missing, need to create.
        ary = s.split("/")
        l = len(ary)-1
        rs_vop = hou.node(shop_path).createNode("redshift_vopnet",ary[l])
        if rs_vop != None:
            # Detect the default closure node that should be created by the redshift_vopnet.
            rs_output = hou.node("%s/%s/redshift_material1" % (shop_path, ary[l]))  
            if rs_output != None:
                # Create.
                rs_mat = rs_vop.createNode("redshift::Material","rs_Mat")
                if rs_mat != None:
                    # Assign a random color.
                    random.seed(i)
                    rs_mat.parm("diffuse_colorr").set(random.random())
                    rs_mat.parm("diffuse_colorg").set(random.random())
                    rs_mat.parm("diffuse_colorb").set(random.random())
                    
                    # Wire to closure.
                    rs_output.setInput(0,rs_mat)

                    # Layout.
                    rs_vop.moveToGoodPosition() 
                    rs_mat.moveToGoodPosition()
                    rs_output.moveToGoodPosition()
                else:
                    print "No mat."
            else:
                print "No closure."
        else:
            print "No VOP."
    else:
        print "Skipping [%s], it already exists." % s

"""
def returnValidHoudiniNodeName(passedItem):
    # Thanks to Graham on OdForce for this function!
    # Replace any illegal characters for node names here.
    return re.sub("[^0-9a-zA-Z\.]+", "_", passedItem)
    
def returnFilesLike(passedFolderName, passedFileExtension = ".obj"):
    result = []
    for file in os.listdir(passedFolderName):
        if file.endswith(passedFileExtension):
            result.append(os.path.join(passedFolderName,file))
    return result
    
def createSubnetOfFileNodes(passedPath, passedType):
    root = hou.node("/obj/folder_of_models")        # Make sure it exists before running, choose your own name.
    if root == None:
        # Create new object to contain internal geometry objects.
        root = hou.node('/obj').createNode('subnet', "folder_of_models")
    if root != None:
        lst_files = returnFilesLike(passedPath, passedType)
        if len(lst_files):
            for i,file in enumerate(lst_files):
                # Create a file node for each geometry in our list.
                path = os.path.split(file)[0]
                last_folder = os.path.split(path)[1]
                node_name = "%s_%03d" % (last_folder,i) 
                geo_name = returnValidHoudiniNodeName("geo_%s" % node_name)
                geo_name = geo_name.replace(".","_")

                node_geo = root.createNode("geo", geo_name)
                if node_geo:
                    node_geo.moveToGoodPosition()
                    # The first child is a default File node.
                    n = node_geo.children()[0]
                    if n:
                        # Assign this filename as the filepath for this file node.
                        n.parm("file").set(file)
                        # Create a python node.
                        node_python = node_geo.createNode("python")
                        if node_python != None:
                            # Install code that will detect and create materials from shop_materialpath.
                            node_python.parm("python").set(code.__doc__)
                            node_python.setInput(0,n)
                            node_python.setDisplayFlag(True)
                            n.moveToGoodPosition()
                            node_python.moveToGoodPosition()
        else:
                print "No subnet container found to contain generated results."
                
folder_path = r"D:\Documents\Models\Plants\Bushes\bushes"
createSubnetOfFileNodes (folder_path,".obj")

 

Just wanted to thank you for this very clever approach to making a tool

  • Like 1

Share this post


Link to post
Share on other sites

Trying to get this tool working on H18.5, would really need it for my current project with lots of imports. Unfortunately i'm stuck with this error:

Traceback (most recent call last):
  File "geofolder-to-RsProxy-Import", line 108, in <module>
  File "geofolder-to-RsProxy-Import", line 91, in createSubnetOfFileNodes
IndexError: tuple index out of range

 

Any idea how i might fix this?

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

×