Tutorial

Create a session and instantiate HDA

You can start houdini engine with code, and instantiate your houdini digital asset.

import logging
import pyhapi as ph

logging.basicConfig(level=logging.INFO)
session = ph.HSessionManager.get_or_create_default_session()

#load hda asset and instantiate
hda_asset = ph.HAsset(session, "hda/FourShapes.hda")
asset_node = hda_asset.instantiate(node_name="Processor").cook()

Connect and disconnect nodes

You can instantiate any operator in houdini engine’s session, connect and disconnect any of them.

import logging
import pyhapi as ph

logging.basicConfig(level=logging.INFO)
session = ph.HSessionManager.get_or_create_default_session()

#load hda asset and instantiate
hda_asset = ph.HAsset(session, "hda/FourShapes.hda")
asset_node = hda_asset.instantiate(node_name="Processor")

#create a sop node, set input
another_box = ph.HNode(session, "geo", "ProgrammaticBox", parent_node=asset_node)
input_node = another_box\
            .connect_node_input(asset_node.get_child_nodes()[0])\
                .cook()\
                .get_node_input(0)
print("ProgrammaticBox's input node is {0}".format(input_node.name))

#delete sop node
another_box\
            .disconnect_node_input(0)\
            .delete()

Set and get parameter of node

You can get and set all paramters on an node’s interface, even press a button

import logging
import pyhapi as ph

logging.basicConfig(level=logging.INFO)
session = ph.HSessionManager.get_or_create_default_session()

#load hda asset and instantiate
hda_asset = ph.HAsset(session, "hda/SideFX_spaceship.otl")
asset_node = hda_asset.instantiate(node_name="Spaceship")

#Query node's parameters
for name in asset_node.get_param_names():
    print("Query param: {0} has value: {1}".format(name, asset_node.get_param_value(name)))

#Set node's parameters
asset_node.set_param_value("seed", 1.0)
asset_node.set_param_value("rop_geometry1_sopoutput", "$HIP/spaceship.obj")

#Press button
asset_node.press_button("rop_geometry1_execute", status_report_interval=1.0)

hda_asset = ph.HAsset(session, "hda/dummy_params.hda")
asset_node = hda_asset.instantiate(node_name="params")

#Setting multi-size parameters
asset_node.set_param_value("intvec3", [1, 1, 1])
asset_node.set_param_value("vec4", [0.5, 0.6, 0.7, 0.8])
asset_node.set_param_value("color", [0.3, 0.4, 0.5])
asset_node.set_param_value("toggle", True)
asset_node.set_param_value("strings5", ["str0", "str1", "str2", "str3", "str4"])

#Getting/Setting choice
parm = asset_node.get_param("ordermenu")
print("{0} is type {1}, has choice {2}".format(parm.get_name(), parm, parm.get_choice_values()))
parm.set_value(index=1)

Save/Load HIP file

You can save current session to hip file for debug, as well as load an hip file into session.

import logging
import pyhapi as ph

logging.basicConfig(level=logging.INFO)
session = ph.HSessionManager.get_or_create_default_session()
session.save_hip("debug.hip")
session.load_hip("debug.hip")

Marshall Data

You can marshal curve and mesh data in numpy format in/out houdini engine. Data should be in numpy.ndarray type.

Marshall Curve In

Vertices should be in shape (num_vertices, 3)

import logging
import numpy as np
import pyhapi as ph

logging.basicConfig(level=logging.INFO)
session = ph.HSessionManager.get_or_create_default_session()

#create an inputnode where you can set geometry
geo_inputnode = ph.HInputNode(session, "Curve")

#create a geocurve
curve_geo = ph.HGeoCurve(
    vertices=np.array(
        [[-4.0, 0.0, 4.0],
         [-4.0, 0.0, -4.0],
         [4.0, 0.0, -4.0],
         [4.0, 0.0, 4.0]], dtype=np.float32),
    curve_knots=np.array(
        [0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0], dtype=np.float32),
    curve_type=ph.CurveType.NURBS)

#set this geocurve as geometry of inputnode
geo_inputnode.set_geometry(curve_geo)

Marshall Curve Out

import logging
import pyhapi as ph

logging.basicConfig(level=logging.INFO)
session = ph.HSessionManager.get_or_create_default_session()

#load hda asset and instantiate
hda_asset = ph.HAsset(session, "hda/nurbs_curve.hda")
asset_node = hda_asset.instantiate(node_name="Curve").cook()

#get node's all display geo, print curveinfo and P
all_geos = asset_node.get_display_geos()
for geo in all_geos:
    print(geo.get_attrib_data(ph.AttributeOwner.POINT, "P"))
    if isinstance(geo, ph.HGeoCurve):
        print(geo.curve_info)

Marshall Mesh In

Vertices should be in shape (num_vertices, 3) Faces should be in shape (num_faces, num_vertices_per_face)

import logging
import numpy as np
import pyhapi as ph

logging.basicConfig(level=logging.INFO)
session = ph.HSessionManager.get_or_create_default_session()

#create an inputnode where you can set geometry
geo_inputnode = ph.HInputNode(session, "Cube")

#create a geomesh
cube_geo = ph.HGeoMesh(
    vertices=np.array(
        [[0.0, 0.0, 0.0],
         [0.0, 0.0, 1.0],
         [0.0, 1.0, 0.0],
         [0.0, 1.0, 1.0],
         [1.0, 0.0, 0.0],
         [1.0, 0.0, 1.0],
         [1.0, 1.0, 0.0],
         [1.0, 1.0, 1.0]], dtype=np.float32),
    faces=np.array(
        [[0, 2, 6, 4],
         [2, 3, 7, 6],
         [2, 0, 1, 3],
         [1, 5, 7, 3],
         [5, 4, 6, 7],
         [0, 4, 5, 1]], dtype=np.int32))

#set this geomesh as geometry of inputnode
geo_inputnode.set_geometry(cube_geo)

#create a node whose input is inputnode
ph.HNode(session, "Sop/subdivide", "Cube Subdivider").connect_node_input(geo_inputnode)

Marshall Mesh Out

import logging
import pyhapi as ph

logging.basicConfig(level=logging.INFO)
session = ph.HSessionManager.get_or_create_default_session()

# load hda asset and instantiate
hda_asset = ph.HAsset(session, "hda/FourShapes.hda")
asset_node = hda_asset.instantiate(node_name="TestObject").cook()
asset_geos = asset_node.get_display_geos()

for geo in asset_geos:
    print("Geo {0} has attribute {1}".format(geo, geo.get_attrib_names()))

print(asset_geos[0].get_attrib_data(ph.AttributeOwner.POINT, "P"))

Marshall Heightfield In

import logging
import numpy as np
import pyhapi as ph

logging.basicConfig(level=logging.INFO)
session = ph.HSessionManager.get_or_create_default_session()

#create a heightfield input node, used to marshal in height and mask
#it will create three node for height, mask and merge.
height_input_node = ph.HHeightfieldInputNode(session, "height_input", 500, 500, 1)

#a random height marshal into height node
height_geo = ph.HGeoHeightfield(
    np.random.random_sample((500, 500, 1)).astype(np.float32)*100,
    "height")
height_geo.commit_to_node(session, height_input_node.height_node.node_id)

#a random mask marshal into mask node
mask_geo = ph.HGeoHeightfield(
    np.random.random_sample((500, 500, 1)).astype(np.float32),
    "mask")
mask_geo.commit_to_node(session, height_input_node.mask_node.node_id)

#create a heightfield input volume node, usually used to marshal in custom mask
heightvolume_input_node = ph.HHeightfieldInputVolumeNode(session, "water_mask", 500, 500, 1)

#a random mask marshal in as water mask
water_mask = ph.HGeoHeightfield(
    np.random.random_sample((500, 500, 1)).astype(np.float32)*0.5,
    "water_mask")
water_mask.commit_to_node(session, heightvolume_input_node.node_id)

#merge the watermask into heightfield input node
#now the heightfield has three layer: height, mask and water_mask
height_input_node.merge_node\
    .connect_node_input(heightvolume_input_node, input_index=2)\
    .cook()

Marshall Heightfield Out

import logging
import pyhapi as ph

logging.basicConfig(level=logging.INFO)
session = ph.HSessionManager.get_or_create_default_session()

#load hda asset and instantiate
hda_asset = ph.HAsset(session, "hda/heightfield_test.hda")
asset_node = hda_asset.instantiate(node_name="HF").cook()

#get node's all display geo, print volume's data shape and name
all_geos = asset_node.get_display_geos()
for geo in all_geos:
    if isinstance(geo, ph.HGeoHeightfield):
        print(geo.volume.shape)
        print(geo.volume_name)

Session Pool

Process Multiple Tasks with HSessionPool

You need to

  • add @ph.HSessionTask decorator to your task function
  • use session as first parameter of function
import asyncio
import pyhapi as ph

@ph.HSessionTask
async def session_task(session, index1, index2):
    print("execute {0} - {1}".format(index1, index2))
    hda_asset = ph.HAsset(session, "hda/save_cube.hda")
    asset_node = hda_asset.instantiate(node_name="cube")
    asset_node.set_param_value("filename", "{0}-{1}".format(index1, index2))
    await asset_node.press_button_async("execute", status_report_interval=0.1)

def main():
    """Main
    """
    logging.basicConfig(level=logging.INFO)

    session_pool = ph.HSessionManager.get_or_create_session_pool(3)

    for i in range(2):
        for j in range(2):
            session_pool.enqueue_task(session_task, i, j)

    # run all task by now and close
    session_pool.run_all_tasks()

if __name__ == "__main__":
    main()

Use Task Producer Coroutine

For tasks, you need to

  • add @ph.HSessionTask decorator to your task function
  • use HSession as first parameter of function
import logging
import asyncio
import random
import pyhapi as ph

@ph.HSessionTask
async def session_task(session : ph.HSession, index1, index2):
    print("execute {0} - {1}".format(index1, index2))
    hda_asset = ph.HAsset(session, "hda/save_cube.hda")
    asset_node = hda_asset.instantiate(node_name="cube")
    asset_node.set_param_value("filename", "{0}-{1}".format(index1, index2))
    await asset_node.press_button_async("execute", status_report_interval=0.1)

async def producer():
    while True:
        val1 = random.randint(1, 10)
        val2 = random.randint(10, 20)
        await asyncio.sleep(random.random())
        await ph.HSessionManager.get_or_create_session_pool().enqueue_task_async(session_task, val1, val2)

def main():
    """Main
    """
    logging.basicConfig(level=logging.INFO)
    session_pool = ph.HSessionManager.get_or_create_session_pool()

    # run producer and consumer forever
    session_pool.run_on_task_producer(producer)

if __name__ == "__main__":
    main()

Use Multiple Thread to Generate Tasks

For tasks, you need to

  • add @ph.HSessionTask decorator to your task function
  • use HSession as first parameter of function
import logging
import asyncio
import random
import threading
import time
from concurrent.futures import ThreadPoolExecutor
import pyhapi as ph

@ph.HSessionTask
async def session_task(session : ph.HSession, index1, index2):
    print("execute {0} - {1}".format(index1, index2))
    hda_asset = ph.HAsset(session, "hda/save_cube.hda")
    asset_node = hda_asset.instantiate(node_name="cube")
    asset_node.set_param_value("filename", "{0}-{1}".format(index1, index2))
    await asset_node.press_button_async("execute", status_report_interval=0.1)

def producer(n):
    while True:
        val1 = random.randint(1, 10)
        val2 = random.randint(10, 20)
        time.sleep(5*random.random())
        print("Task Start on {0}-{1}".format(n, threading.currentThread().getName()))
        try:
            fut = ph.HSessionManager.get_or_create_session_pool().enqueue_task(session_task, val1, val2)
            # block this producer thread
            while not fut.done():
                time.sleep(0.5)
        except Exception as e:
            logging.exception(e)
        finally:
            print("Task Completed on {0}-{1}".format(n, threading.currentThread().getName()))

def main():
    """Main
    """
    logging.basicConfig(level=logging.INFO)
    session_pool = ph.HSessionManager.get_or_create_session_pool()

    # run consumer on background thread forever
    session_pool.run_task_consumer_on_background()

    executor = ThreadPoolExecutor(max_workers=4)
    for i in range(0,4):
        executor.submit(producer, i)

if __name__ == "__main__":
    main()

A Flask Houdini Server Demo

A demo to run houdini on server and respond to restful api

import os, time, datetime
from flask import Flask, request, send_file
import pyhapi as ph

app = Flask(__name__)

@ph.HSessionTask
async def session_task(session : ph.HSession, filename, seed):
    hda_asset = ph.HAsset(session, "hda/save_cube.hda")
    asset_node = hda_asset.instantiate(node_name="cube")
    asset_node.set_param_value("filename", "{0}".format(filename))
    asset_node.set_param_value("seed", seed)
    await asset_node.press_button_async("execute", status_report_interval=0.1)

@app.route('/sample', methods=['POST'])
def sample():
    try:
        seed = request.json['seed']
        filename = datetime.datetime.now().strftime("%m%d%Y%H%M%S")
        fut = ph.HSessionManager.get_or_create_session_pool().enqueue_task(session_task, filename, seed)
        while not fut.done():
            time.sleep(0.1)
        return send_file("{0}.obj".format(filename))
    except Exception as e:
        print(e)

def main():
    session_pool = ph.HSessionManager.get_or_create_session_pool()
    session_pool.run_task_consumer_on_background()
    app.run(threaded=True, host='127.0.0.1')

if __name__ == "__main__":
    main()