The SOHO API consists of 25 functions. These are documented in the HDK_SOHO_API namespace, though they are currently only available in a Python API.
These simple API functions have been wrapped in pure Python modules: soho.py and sohog.py, which can be found in $HFS/houdini/soho. It is the soho.py and sohog.py modules which are typically used.
A new output driver can be created by starting Houdini, then choosing "File -> New Operator Type" then choosing the "Output Driver Type".
The string soho_program parameter is used to specify a Python program which gets run.
Before the Python program is invoked, the output driver may open files.
The output driver evaluates int soho_outputmode. This parameter can have three possible values:
string soho_pipecmd parameter is opened as a pipe. The file descriptor of this pipe is passed to the SOHO program (see HDK_SOHO_API::getFile())string soho_diskfile parameter is opened for writing. The file descriptor for this file is passed to the SOHO program (see HDK_SOHO_API::getFile()).In addition, the output driver will open a temporary file to trap errors from the Python program. This is passed through HDK_SOHO_API::getFile(2).
The process to invoke the Python program is given as follows:
string soho_program
The parameters are all defined in $HFS/houdini/soho/parameters/SohoIntrinsics.ds, but are not visible in the Houdini interface by default. There are instructions in that file to make the parameters visible.
The easiest way to enable these properties is to set the environment variable HOUDINI_SOHO_DEVELOPER before starting Houdini. For example:
% setenv HOUDINI_SOHO_DEVELOPER 1; houdini -foreground
# export HOUDINI_SOHO_DEVELOPER=1; houdini -foreground
string soho_program (mandatory)
int soho_outputmode
string soho_diskfile soho_outputmode evaluates to 1, this parameter specifies the disk file which is opened and passed to HDK_SOHO_API::getFile(1)
string soho_pipecmd soho_outputmode evaluates to 0, this parameter specifies the command which is opened as a pipe and passed to HDK_SOHO_API::getFile(1)
int soho_foreground soho_pipecmd, this determines whether the SOHO output driver will block and wait for the command to finish. The rps and rkill hscript commands can be used to manage background renders.
int soho_multiframe soho_program will only be invoked one time, regardless of the frame range of the ROP.
int soho_initsim soho_program.This traverses all nodes in the scene and invokes specific methods on certain node types.
string soho_shopstyle int soho_precision int soho_indentstep
float soho_almostzero abs(value) < soho_almostzero will be output as 0 exactly.
int soho_safename int soho_autoheadlight Add a light source at the camera's location if there are no other light sources in the scene. int soho_ipr_support
int soho_viewport_menu SHOP_Clerk is a class which packages the parameters of a SHOP into a form usable by a particular renderer. Clerks can be written in C++ (SHOP_Clerk) or Python (see $HFS/scripts/python/shopclerks).
To be used by a renderer, the parameters have to be gathered and processed into a form that the renderer can use. For example, for mantra, the parameters need to be built into a single string. The class which does this processing is the SHOP_Clerk class.
The clerk is called in several places
shopstring(string shop_path, string render_type) expression function. The render_type determines which clerk is used to process the SHOP string.hou.ShopNode.shaderString(render_type). Again, render_type is used to determine which SHOP_Clerk is invoked.type is 'shader' (or 'bounds'). The clerk type is determined by the soho_shopstyle parameter defined on the output driver (see HDK_SOHO_ROP_Parameters).On the other hand, if users need to add a rendering property to the camera or light object, then, they have to unlock the asset and manually dive in to modify the contents.
Wrangling is a process which allows a Python script to sit between the soho_program and the Houdini object interface. The wrangling process is implemented solely in Python and has been implemented for IFD.py (mantra), RIB.py (RenderMan) and MI.py (mentalray).
When processing a light or camera object, these scripts will look for a parameter named light_wrangler or camera_wrangler. These specify Python scripts which are called to evaluate parameters (rather than calling SOHO to evaluate the parameters directly. The wrangler is free to evaluate any parameters it chooses to evaluate any parameters on the renderer.
For example, a light wrangler might have a parameter for a shadow map which gets evaluated during shadow map generation, but also when evaluating the light shader string.
By using a wrangler, you can design the interface the way you want, but let the user make modifications easily (without having to unlock assets).
state:previewmode parameter. The value of this parameter is controlled by the IPR viewer, and may be one ofdefault: Standard rendering modegenerate: Generation phase of IPR rendering generate mode, SOHO will keep the pipe (soho_pipecmd) command open between invocations of the soho_program.update: Send updated changes from previous generation objlist:dirtyinstance, objlist:dirtylight, objlist:dirtyspace and objlist:dirtyfog will contain the list of all objects modified since the last render (whether a generate or update).
As well, the parameters objlist:deletedinstance, objlist:deletedlight, objlist:deletedspace and objlist:deletedfog will list all objects which have been deleted from the scene.
slo2otl.py. This parses the output of sloinfo to query the parameters defined on a prman shader object. The program then invokes the rmands.py module to generate a SHOP OTL for the shader.
This process is fairly trivial, and hinges on the stand-alone tool hotl (see http://www.sidefx.com/docs/current/assets/advanced_otl)
The rmands module builds a dialog script and uses hotl to package it up into the appropriate form.
VOPs are set up to use a wrapper program to compile shader code (hrmanshader). The source code for this application can be found in $HFS/houdini/public/hrmanshader.
Each of these different calling contexts has an override mechanism similar to the HDK_SOHO_API::pushOverrides() method.
There are several files in $HFS/houdini/soho/overrides which define parameters and their override values. The different contexts pass information to the overrides through a set of variables which are specific to the context.
For example, the ViewportRender override file is called when the user chooses to render from the render menu in the viewport. The viewport passes the viewing camera transform in the $cam_xform variable and the file overrides the space:world parameter (which is an intrinsic SOHO parameter) with the value of that transform.
These files are used to force rendering to be routed to flipbooks, to force the soho_outputmode etc.
HOUDINI_PATH. Your module will be picked up before the factory version and so you're free to make any changes you want.The problem with this approach is that occasionally new features or bugs will be fixed by Side Effects Software in the factory module. That means that when you start using a new version of Houdini, you need to verify that your modified version gets updated with the correct changes.
Monkey Patching (http://en.wikipedia.org/wiki/Monkey_patch) is a method of modifying runtime code of a module without altering the original code. Using Monkey Patching allows you to use the factory code, but to make surgical changes to the code by replacing single functions in a module.
An example of Monkey Patching would be changing the sin() function in the math module.
def mySin(x):
# Use Taylor Series Approximation
return x - x**3/6 + x**5/120 - x**7/5040
# Import the standard math module
import math
# Change the sin() function in math to point to my function
def patchMath():
math.sin = mySin
math.sin() function to call the mySin() function instead of its original function. If you call patchMath(), then, all other code that uses math.sin() will now call your function instead.
Here's an example which replaces the LightSource function in the RIBapi module.
import os, RIBapi factory_LightSource = RIBapi.LightSource def LightSource(shader, handle, parmlist): # Process the shader string shader = processShaderString(shader) # Call the factory LightSource function with the modified shader handle = factory_LightSource(shader, handle, parmlist) # Change the LightSource function in RIBapi module RIBapi.LightSource = LightSource execfile('%s/houdini/soho/RIB.py' % os.getenv('HFS')) # Restore the method in the RIBapi module RIBapi.LightSource = factory_LightSource
$HOME/houdini$VERSION/soho/RIB.py, then, it will be called instead of $HFS/houdini/soho/RIB.api.One way of thinking about Monkey Patching (philosophically) is that you
The RIB/IFD modules (i.e. RIB.py RIBframe.py) have callbacks defined through the hook modules. By placing a file named RIBuserhooks.py (or IFDuserhooks.py) in the Python path (including $HOUDINI_PATH/soho), your hooks will be picked up and called during the RIB generation.
Please note that if your hook functions need to access other parts of SOHO (i.e. importing RIBsettings.py), you may run into recursion errors. That is, RIBsettings will import RIBhooks, which then imports RIBsettings, etc. Please look at RIBhooks.py for possible ways around this issue.
A simple example which uses this would be something like:
''' RIBuserhooks.py A simple example to illustrate the RIBhooks features. ''' import traceback import RIBapi Ri = RIBapi def pre_lockObjects(parmlist, objparms, now, camera): ''' Called before SOHO locks objects. This allows you to modify the objects that are visible to SOHO ''' Ri.Comment('RIB Hook - rendering from camera: %s' % repr(camera)) return True def pre_render(camlist, now, objlist, lightlist, spacelist, foglist, fromlight, cubemap): ''' Called before RiFrameBegin() for every frame ''' Ri.Comment('RIB Hook - Render at time: %g %s' % (now, repr(camlist))) return False ''' List of hooks in this file ''' _HOOKS = { 'pre_lockObjects' : pre_lockObjects, 'pre_render' : pre_render, } def call(name='', *args, **kwargs): ''' Hook callback function ''' method = _HOOKS.get(name, None) if method: try: if method(*args, **kwargs): return True else: return False except Exception, err: Ri.Comment('Hook Error[%s]: %s %s' % (name, __file__, str(err))) Ri.Comment('Traceback:\n# %s\n' % '\n#'.join(traceback.format_exc().split('\n'))) return False
1.5.9