Load FBX sop

github.com/Calvinatorr/CommercialHDAs/blob/master/FBX_sops.hda
To install just point your houdini.env file to the hda/folder of your hdas, or use the install hda feature.

I developed this tool because the current import FBX sop isn’t very procedural as it’s only accessed through a menu (though you can call it from Python, which is what I am doing essentially).

Said tool generates an objnet consisting of a series of nodes which represents the hierarchy of the FBX – this can be a pain to work with though, especially compared to the File sop which just pulls in geo as is.

This tool just wraps up that tool in a sop context and pulls all references in to the local HDA level, and builds the subsequent networks to output and organsie the geometry.

How it works

As this tool is just a sop wrapper which calls the FBX API already exposed, it should be maintainable going forward as all the heavy lifting is already done.

This is also useful as it builds the necessary matnets, transforms, hiearchies etc which I can utilise – I just move this whole objnet to an editable objnet inside my HDA context.
My tool then builds 2 objmerge networks to pull in the geo from the objnet.

For example this subway train has a load of submeshes & material IDs which you can see in this hiearchy
The Load FBX sop will build the objmerge networks in these subnets and output them (including groups and attributes), pretty simple
The actual network looks like this.. and unfortunately you can’t just use a single objmerge to combine all these nodes
And for the unaware – you can define Editable Nodes in your HDA. This means even when the HDA is locked you can change the state of those nodes!
Thanks Luiz Kruel for pointing me to this, I felt pretty dumb when I realised this is of course what they are for!
Subsequently, the user only has to see this – simple! If they want to reload the geometry, they can just call pressButton() on the button param.
I’ve even exposed the collision net as guide geo – neat!
sop level access & parameter panel
Can be embedded into other HDAs just like a file sop

All the work in this tool is in the Python module attached to the HDA, where the LoadFBX() method is called by a callback on the load button callback, and the text field callback.

I’m doing this with this very simple line to be able to call my methods from a Py module on the current node context we’re executing from.

hou.phm().LoadFBX()

And this is the Python module.
Edit 06/01/2020: Updated with import transform functionality, vertex colour preservation (prevent mismatch of Cd attribute), added ClearGeometry() method, & added suite of import options

import os, sys

def ClearNetwork(network):
    for child in network.children():
        child.destroy()


def SetupMergeNetwork(network):
    result = {"network":network}
    ClearNetwork(network)
    result["merge"] = network.createNode("merge", "merge")
    result["merge"].setDisplayFlag(True)
    result["merge"].setInput(0, result["network"].indirectInputs()[0])
    return result


def ClearGeometry():
    with hou.undos.group("Clearing geometry loaded from '" + hou.parm("file").eval() + "'"):
        ClearNetwork(hou.node("IMPORT"))
        ClearNetwork(hou.node("GEO_MERGE"))
        ClearNetwork(hou.node("COLLISION_MERGE"))


def LoadFBX():
        # Get file path
        file = hou.parm("file").eval() # Evaluate path
        file = os.path.normpath(file)
        if not os.path.exists(file):
                raise hou.NodeError("File doesn't exist")
                return


        # Find import objnet (editable)
        objnet = hou.node("IMPORT")
        ClearNetwork(objnet)

        # Import FBX
        #hou.hscript("fbximport {}".format(file))
        materialMode = hou.fbxMaterialMode.FBXShaderNodes if hou.parm("materialMode").eval()==1 else hou.fbxMaterialMode.VopNetworks
        compatabilityMode = hou.fbxCompatibilityMode.FBXStandard if hou.parm("compatabilityMode").eval()==1 else hou.fbxCompatibilityMode.Maya
        rawFbx = hou.hipFile.importFBX(file,
            import_animation=hou.parm("importAnimation").eval(),
            import_joints_and_skin=hou.parm("importJointsAndSkin").eval(),
            resample_animation=hou.parm("resampleAnimation").eval(),
            resample_interval=hou.parm("resampleInterval").eval(),
            import_materials=hou.parm("importMaterials").eval(),
            material_mode=materialMode,
            compatibility_mode=compatabilityMode)
        fbx = rawFbx[0].copyTo(objnet)
        rawFbx[0].destroy()


        # Link import transform
                
        fbx.parm("xOrd").set(hou.parm("xOrd"))
        fbx.parm("rOrd").set(hou.parm("rOrd"))

        fbx.parm("tx").set(hou.parm("tx"))
        fbx.parm("ty").set(hou.parm("ty"))
        fbx.parm("tz").set(hou.parm("tz"))

        fbx.parm("rx").setExpression('ch("../../rx") + if(ch("../../axis")==1, 90, 0)', language=hou.exprLanguage.Hscript) # Link scale
        fbx.parm("ry").set(hou.parm("ry"))
        fbx.parm("rz").set(hou.parm("rz"))

        fbx.parm("sx").set(hou.parm("sx"))
        fbx.parm("sy").set(hou.parm("sy"))
        fbx.parm("sz").set(hou.parm("sz"))

        fbx.parm("px").set(hou.parm("px"))
        fbx.parm("py").set(hou.parm("py"))
        fbx.parm("pz").set(hou.parm("pz"))

        fbx.parm("prx").set(hou.parm("prx"))
        fbx.parm("pry").set(hou.parm("pry"))
        fbx.parm("prz").set(hou.parm("prz"))

        fbx.parm("scale").setExpression('ch("../../importScale") * ch("../../scale")', language=hou.exprLanguage.Hscript) # Link scale



        # Build merge networks

        # Clear networks first
        geo = SetupMergeNetwork(hou.node("GEO_MERGE"))
        collision = SetupMergeNetwork(hou.node("COLLISION_MERGE"))

        # Generate objmerge nodes
        for child in fbx.children():
                if not child.type().name() == "geo":
                        continue # Skip this iteration
                        
                isCollider = any(s in child.name() for s in {"UBX", "UCP", "USP", "UCX"})
                mergeNet = collision if isCollider else geo

                objMerge = mergeNet["network"].createNode("object_merge", child.name()) # Create objmerge node

                # Set-up parameters
                objMerge.parm("xformtype").set("local")
                objMerge.parm("createprimstring").set(True)
                objMerge.parm("pathattrib").set("path")
                objMerge.parm("objpath1").set(mergeNet["merge"].relativePathTo(child))
                objMerge.parm("createprimgroups").set(True)
                objMerge.parm("primgroupprefix").set(objMerge.name() + "_node")
                objMerge.parm("suffixfirstgroup").set(False)
                mergeNet["merge"].setInput(len(mergeNet["merge"].inputConnections()), objMerge) # Connect to merge
                objMerge.moveToGoodPosition()
                
                
                geo["merge"].moveToGoodPosition()
                collision["merge"].moveToGoodPosition()
                
        # Select this node to bring parameter panel back
        hou.pwd().setSelected(True, True, True)

What’s next?

FBX Export sop of course!

An FBX rop exists of course, but I would like to simplify the process of building a hiearchy from an objnet level, to a sop level and be data-driven to make proceduralism easier.

I am still thinking of my approach to this, but at the simplest level I believe it’d be at least useful to include a collision pin and allow my sop to build the relevant hiearchy and node names, and perhaps some helper sops to categorise collisions.. we will see!

One thought on “Load FBX sop

Leave a comment