Showing posts with label Houdini. Show all posts
Showing posts with label Houdini. Show all posts

Monday, 30 January 2012

Maya Alembic Export

Whilst this blog is mainly concerned with setting up alembic maya export within the University network, most of this is also applicable to other systems. You just need to change all the paths to the corresponding ones on your system.

First you need to download and build your alembic maya plugins. These come as part of the Alembic package and you should follow the build instructions. Once everything is built you will have a directory within the source tree called maya/plug-ins.

On the University system this is in the directory /public/bin/alembic/maya/plug-ins and you should see the two plugins AbcExport.so  AbcImport.so

University Setup
In the root of your home directory (type cd and press enter) execute the following commands

cd
mkdir MayaPlug
mkdir MayaScript
cp /public/bin/alembic/maya/plug-ins/* ~/MayaPlug
This will copy the Alembic plugins into a pre-defined directory which we will tell maya to search when starting up. This is controlled by the file Maya.env. Again the location of this file will differ depending upon the install, but in the University this is located here $HOME/maya/2011-x64/ If this file doesn't exist in the directory you can create your own using the following command
cd $HOME/maya/2011-x64/
touch Maya.env
gedit Maya.env
This will open the file and allow us to edit the maya environment variables used when maya starts. We need to add to this file the following
MAYA_PLUG_IN_PATH=/home/jmacey/MayaPlug
MAYA_SCRIPT_PATH=/home/jmacey/MayaScripts
PYTHONPATH=MAYA_SCRIPT_PATH
In the above example you will need to change the /home/jmacey to your own home directory path. This will then setup two areas that maya will search when looking for plugins ( $HOME/MayaPlug ) and scripts ( $HOME/MayaScript ) when you now start maya you should get the following list when opening the Menu Windows->Settings / Preferences -> Plugin-Manager
You should now see the AbcExport.so ( this screen shot from my mac is different as it uses a .bundle) and AbcImport.so. If you click on the Loaded button it will load the plugin and you should be able to type AbcExport -h in the Mel tab of the script editor as shown below
For more info on this read the blog post here. As the command line is a little bit complex, I decided to create a simple GUI to make life easier. The main design for this came from the output of AbcExport -h and all the options printed in the help that actually work have been translated into gui items.

AlembicExport.py

The AlembicExport.py script can be downloaded from here and it should be saved in the $HOME/MayaScripts directory.

When using the script you will need to select all the geometry you wish to export (if you select all Alembic will attempt to export all it can ) and type the following in the python script editor
from AlembicExport import *
AlembicExport()
This will give you the following GUI
The current frame range is selected and by default uv's and normals will be exported. Other options are available and you should read the AlembicExport help for more details. 

The actual alembic jobstring and command line is placed in the job string text field so you can copy this if you wish to use the command line at a later date.

Code outline
The code is fairly self explanatory, however I will outline a couple of areas.
First we check to see if the AlembicExport plugin is installed. This is done with the following code
# check to see if plugin is loaded
plugs=cmds.pluginInfo( query=True, listPlugins=True )
if "AbcExport" not in plugs :
  print "AbcExport not loaded please load it"
To build up the jobstring for the actual export we use the following code
jobstring="AbcExport "
if self.verbose == True :
  jobstring+=" -v "
jobstring+="-j \" -fr %d %d -s %d" %(self.start,self.end,self.steps)
.....
This will build up a complete export command which we will then execute using the eval command as follows
mel.eval(jobstring)

Monday, 16 January 2012

Pipeline Stuff

As we are starting the group project section of the Masters term I thought it would be a good idea to do some basic pipeline stuff.

This example is going to write some simulation data created in a C++ program and then we will write some simple Python scripts to load this data into Maya and Houdini.

The following video shows the program in action as well as the Maya and Houdini versions.

As this is an ad-hoc system the output file format is very simple, the C++ program writes out the following data :-


NumParticles 500
Frame 0
P0 64.301 -57.7284 49.8516
....
Frame 1
....

Where the particle data consists of The name of the Particle (in this case P0,P1....Pn) and the x,y,z positions of the particle per frame.

Maya Version
The maya version of the program will popup a dialog box to prompt the user to select an input file, it will then parse the file and create a locator for each of the particle names found.
Then for every frame a keyframe is created for the locator replicating the animation from the C++ simulation.
import maya.OpenMaya as OM
import maya.OpenMayaAnim as OMA
import maya.cmds as cmds
The code above loads in the maya elements we need, OpenMaya contains the core maya elements we need, the OpenMayaAnim namespace has the controls for all the animation transports such as setting the current frame and maya.cmds gives us access to all the maya cmds similar to the mel commands printed in the console when we generate things.

def createLocator(_name,_x,_y,_z) :
    cmds.spaceLocator( name=_name)
    cmds.move(_x,_y,_z)
    cmds.setKeyframe()

The createLocator function creates a simple space locator then moves it to the current x,y,z position we then set the keyframe to make the initial key at the currently set frame (more on this later)

def moveLocator(_name,x,y,z) :
    cmds.select(_name)
    cmds.move(x,y,z)
    cmds.setKeyframe()

The move locator function, first selects the locator (based on the name passed in), it then calles the move command which will move the currently selected object, finally we set the keyframe.
def importParticleFile() :
    basicFilter = "*.out"

    fileName=cmds.fileDialog2(caption="Please select file to import",fileFilter=basicFilter, fm=1)
    if fileName[0] !=None :

 file=open(str(fileName[0]))
 frame=0
 numParticles=0
 #set to frame 0
 animControl=OMA.MAnimControl()
 animControl.setCurrentTime(OM.MTime(frame))

 for line in file :
  line=line.split(" ")
  if line[0]=="NumParticles" :
   numParticles=int(line[1])
  elif line[0]=="Frame" :
   frame=int(line[1])
   animControl.setCurrentTime(OM.MTime(frame))
  else :
   name=line[0]
   x=float(line[1])
   y=float(line[2])
   z=float(line[3])
   if frame==0 :
    #we need to create our initial locators
    createLocator(name,x,y,z)
   else :
    moveLocator(name,x,y,z)
Houdini Version
The houdini version of the script will generate a null which is the houdini equivalent of a locator. By default a houdini null doesn't contain any geometry so we also need to parent this to some axis (houdini call these controls).


To start with we need to get the name of the file, this is done using the getAbsoluteFilename function described here http://jonmacey.blogspot.com/2011/01/houdini-python-ascode.html


def createNull(parent,_name,x,y,z) :
 #create a null this will set loads of default values
 null = parent.createNode("null", _name, run_init_scripts=False, load_contents=True)
 # set the x,y,z values
 null.parm("tx").set(x)
 null.parm("ty").set(y)
 null.parm("tz").set(z)
 # now add a control to the null so we have something to visualise
 null.createNode("control", "ctrl"+_name, run_init_scripts=False, load_contents=True)
 # now grab the keyframe
 setKey = hou.Keyframe()
 # set to frame 0
 setKey.setFrame(0)
 # now key the tx/y and z values
 setKey.setValue(x)
 null.parm("tx").setKeyframe(setKey)
 setKey.setValue(y)
 null.parm("ty").setKeyframe(setKey)
 setKey.setValue(z)
 null.parm("tz").setKeyframe(setKey)
 # now add to our network node
This function is passed the parent node, which in this case will be the houdini path "/obj/" from this we create a new node called a "null" and set it's parameters. In Houdini all parameters can be access using the parm(...) method of the object passing in the name of the parameter we wish to access, in this case "tx/ty/tz". Next we set the keyframes using the hou.keyframe class.
def moveNull(_name,frame,x,y,z) :
 null=hou.node("/obj/"+_name)
 setKey = hou.Keyframe()
 setKey.setFrame(frame)
 setKey.setValue(x)
 null.parm("tx").setKeyframe(setKey)
 setKey.setValue(y)
 null.parm("ty").setKeyframe(setKey)
 setKey.setValue(z)
 null.parm("tz").setKeyframe(setKey)
In this function we grab the object by name then set the keyframes using the same method as above. Finally we are going to create our nodes and import the file, the file reading code is exactly the same, however we create a subNetwork first to make the houdini scene neater.
def importParticleFile() :

 fileName=GetAbsoluteFileName("Select particle File","*.out",hou.fileType.Any)
 # get the the object leve as our parent
 if locals().get("hou_parent") is None:
  parent = hou.node("/obj")

 # make sure we got a filename
 if fileName !=None :
  subNetName=hou.ui.readInput("Please Enter name for the subnet")
  ## @brief we now copy this to a new string
  subNetName=subNetName[1]

  subNet=parent.createNode("subnet", subNetName, run_init_scripts=False, load_contents=True)
  #open the file (could do a proper check here)
  file=open(fileName)
  frame=0
  numParticles=0
  # now process the file and create the nulls
  for line in file :
   line=line.split(" ")
   if line[0]=="NumParticles" :
    numParticles=int(line[1])
   elif line[0]=="Frame" :
    frame=int(line[1])
   else :
    name=line[0]
    x=float(line[1])
    y=float(line[2])
    z=float(line[3])
    if frame==0 :
     #we need to create our initial locators
     createNull(subNet,name,x,y,z)
    else :
     moveNull(subNetName+"/"+name,frame,x,y,z)


To grab the full code use the following

DemoProgram (requires NGL)
particles.out (the output of the program which the python files read)
Houdini Script
Maya Script

Wednesday, 19 January 2011

Houdini Python .asCode()

Been writing some Houdini python code recently to help with the import and export of animation data from our 3 main animation packages (Maya, XSI, Houdini) as well as to my C++ / Python engines.

Houdini has a good python model and I discovered a really useful feature in most of the nodes which is a method called .asCode(). What this does it returns as a string the code used to create the current node.

This is really good as you can get output and inspect for you own code. I decided to write a simple shelf tool for automating this process and dumping the data out to a file.

GetAbsoluteFileName
The first issue I had was with the Houdini file manager shown below
If a file is chosen from the normal file system we get an absolute file name, however Houdini has the ability to set up certain local environment variables which it uses internally. These however are not translated into the python scripts. These variables are $HIP, $JOP, $HOME which will be returned with any filename selected for example $HIP/myGeo.bgeo.

This needs to be translated into an absolute filename before python can access it, so a simple function was written to pop up the dialog and then strip any of the environment variables and replace them with the actual values. This is shown in the function below (which I use in a lot of other code)
########################################################################################################################
##  @brief a basic function to return a file name / absolute path stripping off $HIP etc
##  @param[in] _title the title to be displayed in the file box
##  @param[in] _wildCard the file selection wildcard i.e. *.obj etc
##  @param[in] _fileType the houdini file type option e.g. hou.fileType.Any
##  @returns a fully qualified file path or None
########################################################################################################################

def GetAbsoluteFileName(_title,_wildCard,_fileType) :
 # launch a file select and get the data
 file=hou.ui.selectFile(None,"Select File To Save",False,_fileType,"*.py","",False,False,hou.fileChooserMode.Write)
 # if it was empty bomb out and return none
 if file =="" :
  return None
 else :
  # so we got some data we need to split it as we could have $JOB $HIP or $HOME prepended
  # to it  if we partition based on the / we get a tuple with "", "/","/....." where the
  # first element is going to be an environment var etc.
  file=file.partition("/")
  # we have $HOME so extract the full $HOME path and use it
  if file[0]=="$HOME" :
   prefix=str(hou.getenv("HOME"))
  elif file[0] == "$HIP" :
  #we have $HIP so extract the full $HIP path
   prefix=str(hou.getenv("HIP"))
  # we have a $JOB so extract the full $JOB path
  elif file[0] == "$JOB" :
   prefix=str(hou.getenv("JOB"))
  # nothing so just blank the string
  else :
   prefix=str("")
  #now construct our new file name from the elements we've found
  return "%s/%s" %(prefix,file[2])

Now this has been written we can use it to popup a dialog and return the filename

HouAsCode.py
The rest of the function is simple. We see which nodes are selected in houdini iterate through all of them calling the asCode() method

file=GetAbsoluteFileName("Enter File to Save ","*.py",hou.fileType.Any)
if file != None :
 fileOut=open(file,'w')
 fileOut.write("#############################################\n")
 fileOut.write("# generated by Hou as Code script \n")
 fileOut.write("#############################################\n")

 for objects in hou.selectedNodes() :
  fileOut.write("#############################################\n")
  fileOut.write("# from object %s \n" %(objects.name()))
  fileOut.write("#############################################\n")
  fileOut.write("%s \n" %(objects.asCode()))
  fileOut.write("#############################################\n\n")
 fileOut.close()

You can download the full script here

Installation
The easiest way to run this script is to place a shelf button into Houdini with the script embedded into it. First right click onto the shelf you wish to place the button on as shown
Next we edit the tool and add the name etc as shown
Finally we can paste the script into the tool as shown in the next dialog

Make sure the script language combo box is set to python and then press the accept button.

Output
The following is a sample output from the tool

#############################################
# generated by Hou as Code script 
#############################################
#############################################
# from object helixRename 
#############################################
# Initialize parent node variable.
if locals().get("hou_parent") is None:
    hou_parent = hou.node("/obj/helixObjectImport/helixBakeChannel")

# Code for /obj/helixObjectImport/helixBakeChannel/helixRename
hou_node = hou_parent.createNode("rename", "helixRename", run_init_scripts=False, load_contents=True)
hou_node.move(hou.Vector2(0, 0))
hou_node.setAudioFlag(False)
hou_node.setExportFlag(False)
hou_node.bypass(False)
hou_node.setDisplayFlag(False)
hou_node.setSelected(True)
hou_node.setUnloadFlag(False)

# Code for /obj/helixObjectImport/helixBakeChannel/helixRename/stdswitcher1 parm 
if locals().get("hou_node") is None:
    hou_node = hou.node("/obj/helixObjectImport/helixBakeChannel/helixRename")
hou_parm = hou_node.parm("stdswitcher1")
hou_parm.set(0)
hou_parm.setAutoscope(False)
hou_parm.lock(False)

# Code for /obj/helixObjectImport/helixBakeChannel/helixRename/renamefrom parm 
if locals().get("hou_node") is None:
    hou_node = hou.node("/obj/helixObjectImport/helixBakeChannel/helixRename")
hou_parm = hou_node.parm("renamefrom")
hou_parm.set("?[xyz]")
hou_parm.setAutoscope(False)
hou_parm.lock(False)

# Code for /obj/helixObjectImport/helixBakeChannel/helixRename/renameto parm 
if locals().get("hou_node") is None:
    hou_node = hou.node("/obj/helixObjectImport/helixBakeChannel/helixRename")
hou_parm = hou_node.parm("renameto")
hou_parm.set("t[xyz]0")
hou_parm.setAutoscope(False)
hou_parm.lock(False)

# Code for /obj/helixObjectImport/helixBakeChannel/helixRename/scope parm 
if locals().get("hou_node") is None:
    hou_node = hou.node("/obj/helixObjectImport/helixBakeChannel/helixRename")
hou_parm = hou_node.parm("scope")
hou_parm.set("*")
hou_parm.setAutoscope(False)
hou_parm.lock(False)

# Code for /obj/helixObjectImport/helixBakeChannel/helixRename/srselect parm 
if locals().get("hou_node") is None:
    hou_node = hou.node("/obj/helixObjectImport/helixBakeChannel/helixRename")
hou_parm = hou_node.parm("srselect")
hou_parm.set("max")
hou_parm.setAutoscope(False)
hou_parm.lock(False)

# Code for /obj/helixObjectImport/helixBakeChannel/helixRename/units parm 
if locals().get("hou_node") is None:
    hou_node = hou.node("/obj/helixObjectImport/helixBakeChannel/helixRename")
hou_parm = hou_node.parm("units")
hou_parm.set("seconds")
hou_parm.setAutoscope(False)
hou_parm.lock(False)

# Code for /obj/helixObjectImport/helixBakeChannel/helixRename/timeslice parm 
if locals().get("hou_node") is None:
    hou_node = hou.node("/obj/helixObjectImport/helixBakeChannel/helixRename")
hou_parm = hou_node.parm("timeslice")
hou_parm.set(0)
hou_parm.setAutoscope(False)
hou_parm.lock(False)

# Code for /obj/helixObjectImport/helixBakeChannel/helixRename/unload parm 
if locals().get("hou_node") is None:
    hou_node = hou.node("/obj/helixObjectImport/helixBakeChannel/helixRename")
hou_parm = hou_node.parm("unload")
hou_parm.set(0)
hou_parm.setAutoscope(False)
hou_parm.lock(False)

# Code for /obj/helixObjectImport/helixBakeChannel/helixRename/export parm 
if locals().get("hou_node") is None:
    hou_node = hou.node("/obj/helixObjectImport/helixBakeChannel/helixRename")
hou_parm = hou_node.parm("export")
hou_parm.set("/obj")
hou_parm.setAutoscope(False)
hou_parm.lock(False)

# Code for /obj/helixObjectImport/helixBakeChannel/helixRename/gcolor parm tuple
if locals().get("hou_node") is None:
    hou_node = hou.node("/obj/helixObjectImport/helixBakeChannel/helixRename")
hou_parm_tuple = hou_node.parmTuple("gcolor")
hou_parm_tuple.set((0.9, 0.9, 0))
hou_parm_tuple.setAutoscope((False, False, False))
hou_parm_tuple.lock((False, False, False))

# Code for /obj/helixObjectImport/helixBakeChannel/helixRename/gcolorstep parm 
if locals().get("hou_node") is None:
    hou_node = hou.node("/obj/helixObjectImport/helixBakeChannel/helixRename")
hou_parm = hou_node.parm("gcolorstep")
hou_parm.set(0.05)
hou_parm.setAutoscope(False)
hou_parm.lock(False)

hou_node.setColor(hou.Color([0.8, 0.8, 0.8]))
hou_node.setExpressionLanguage(hou.exprLanguage.Python)

# Code to establish connections for /obj/helixObjectImport/helixBakeChannel/helixRename
hou_node = hou_parent.node("helixRename")
if hou_parent.node("helixImportData") is not None:
    hou_node.setInput(0, hou_parent.node("helixImportData"), 0)
hou_node.setUserData("___toolcount___", "1")
hou_node.setUserData("___toolid___", "NCCAPointBakeImport")
 
#############################################