iTranslated by AI
Houdini: Creating a Font SOP with a Preview Feature
Is it already Advent Calendar season? You've got to be kidding me... (3rd time)
Putting that aside, how is everyone doing? I've been spending my days fully immersed in Houdini again this year.
In this post, I'd like to talk about creating a FontSOP with preview functionality.
This article is for Day 19 of the Houdini Advent Calendar 2024.
Data Distribution
Background and Features of Tool Creation
FontSOP is frequently used for creating test geometries or as a form of expression itself, but I've always found the UI inconvenient because you have to select fonts from a menu list every single time.
Therefore, I created a tool that generates preview string geometries in bulk from an arbitrary string, and switches to a FontSOP with the desired font set simply by clicking the string you want to use. Please refer to the video below for the behavior.

To explain briefly, it operates with the following steps:
-
Font PlusSOP(Select this tool) - Enter any text
- String geometries are generated for all available fonts (Preview Mode)
- Press the Enter key in the viewport
- Click the string geometry using the font you like
- The font is set in the built-in
FontSOPnode
For a simpler tool, please refer to the article Houdini: Adding FontSOP Functionality - My First Python in the Houdini Apprentice Advent Calendar.

The behavior looks like this. It could be called a minimal feature addition. Since it's an Apprentice article, it's also recommended for those trying Houdini Python for the first time, and it includes detailed explanations of Python itself.
Houdini and Python
In Houdini, Python can be used for various purposes. In this tool as well, Python is used for several functions. They can be broadly classified as follows:
- Dynamic node usage (nodeVerb)
- Parameter switching
- Interactive operations in the viewport (Viewer State)
In the simpler tool introduced in the Houdini Apprentice Advent Calendar, Python was used only for the "Parameter switching" mentioned above, but this tool utilizes it from other perspectives as well.
Regarding dynamic node usage (nodeVerb), I have described it in detail in another Advent Calendar article, Houdini: Introducing an HDA for Loading Multiple Geometries.
This tool combines the techniques from the two aforementioned articles with interactive operations in the viewport.
Tool Configuration Description
From here, I will explain the tool configuration step by step while looking at the actual network.
Parameters
The UI of this tool is simple: it exposes all the parameters of the FontSOP and adds a button to switch between Release and Preview (the ispreview parameter).
I will now introduce the settings for the ispreview parameter. (The images below show the Parameter tab and the menu tab.)


There isn't anything particularly difficult here, so an explanation shouldn't be necessary. With this, the basic settings are complete, but I've added one more setting as a personal preference.

This setting makes it so that "the font selection dialog is grayed out while in the preview state." Specifically, it is implemented by setting { ispreview == 1 } in the Disable when parameter.
While this isn't strictly necessary, I've included it as a UI element to more explicitly convey the intention that the font is currently being determined via viewport operations rather than the dialog.
Tool Network
Let's dive into the HDA. The stream on the right is for preview geometry generation, while the stream on the left is a simple FontSOP stream.

The most important point in this network is that the first input is not connected to the PythonSOP (python_create_fonts).
The design uses the FontSOP only for passing information, while delegating the actual geometry generation to the PythonSOP (python_create_fonts). There's nothing particularly difficult about it, but it might be surprising if you're not used to it.
PythonSOP (python_create_fonts) Code
node = hou.pwd()
geo = node.geometry()
# Get the font node
font = node.inputs()[1]
# Get the font parameters
parm_file = font.parm("file")
labels = {label for label in parm_file.menuLabels() if label != "_separator_"}
text = font.parm("text").eval()
fontverb = font.verb()
tmp = hou.Geometry()
for label in labels:
try:
# Set font parameters and execute
fontverb.setParms({"file": label, "text": text})
fontverb.execute(tmp, [])
# Add name primitive attribute
attrib = tmp.addAttrib(hou.attribType.Prim, "name", "")
for prim in tmp.prims():
prim.setAttribValue(attrib, label)
# Merge geometry
geo.merge(tmp)
except Exception as e:
print(f"Error occurred during processing of '{label}': {str(e)}")
I will leave the usage of nodeVerb to the previous article, but the key point is that this code sets the font name as a name primitive attribute while dynamically generating the FontSOP geometry with nodeVerb. This is used later to assign it to the Packed Primitives.
For-Each Loop Processing
In this part, we perform the process of making font selection easier.
Specifically, the implementation involves creating a bounding box around the font geometry to ensure a clickable area. This is because if only the areas with polygons were clickable, it would cause issues with thin fonts or characters with many holes.

Once the bounding box is prepared, we refer to the name primitive attribute created earlier to transfer the attribute to the Packed Primitive. I used Attribute Create here, but please use whichever method works best for you.

After exiting the loop, the Packed Primitive geometries are generated, each carrying its font name as a primitive attribute and having a clickable region covering the string.
Implementing Viewport Operations
Viewport operations are implemented using a Viewer State. Create a new Python script in the State Script section under the Interactive tab of the Edit Operator Type Properties window, as shown in the image below.

Viewer States are typically created from a template. For details on that process, please refer to the following documentation.
How to write custom Viewer States in Python
Next, let's look at the Viewer State for this tool.
"""
State: Kickbase::font plus
State type: kickbase::font_plus
Description: Kickbase::font plus
Author: kickbase
Date Created: November 18, 2024 - 19:57:57
"""
import hou
import viewerstate.utils as su
class State(object):
MSG = "Click Font what you want"
def __init__(self, state_name, scene_viewer):
self.state_name = state_name
self.scene_viewer = scene_viewer
self.node = None
self.geometry = None
self.name = ""
def onEnter(self, kwargs):
self.node = kwargs["node"]
self.geometry = self.node.geometry()
self.scene_viewer.setPromptMessage(State.MSG)
def onMouseEvent(self, kwargs):
ui_event = kwargs["ui_event"]
(origin, dir) = ui_event.ray()
hitprim, _, _, _ = su.sopGeometryIntersection(self.geometry, origin, dir)
device = ui_event.device()
# Left Button Click
if device.isLeftButton():
if 0 <= hitprim and self.node.parm("ispreview").eval() == 1:
self.name = self.geometry.iterPrims()[hitprim].attribValue("name")
# print(self.name, hitprim)
self.node.parm("file").set(self.name)
self.node.parm("ispreview").set(0)
def createViewerStateTemplate():
"""Mandatory entry point to create and return the viewer state
template to register."""
state_typename = kwargs["type"].definition().sections()["DefaultState"].contents()
state_label = "Kickbase::font plus"
state_cat = hou.sopNodeTypeCategory()
template = hou.ViewerStateTemplate(state_typename, state_label, state_cat)
template.bindFactory(State)
template.bindIcon(kwargs["type"].icon())
return template
I'll omit the details as there isn't anything particularly difficult, but the onMouseEvent starting from line 28 is the core of this code.
- Intersection detection between mouse position and geometry
- Left-click detection
- Method to retrieve arbitrary attributes from the geometry under the cursor
- Modifying node parameters
It packs several processes into a short piece of code. Please feel free to use it in your own tools.
Controlling the Distribution of String Geometries
This tool was not made for actual production use but was quickly put together for a study group presentation, so the distribution control is left entirely to the Labs Align and Distribute SOP.
Ideally, this part should be built from scratch to be easier to control, and parameters should be exposed to ensure a good UX.
Conclusion
Thank you to everyone who read until the end. Even with built-in nodes, improving areas you find "inconvenient" can lead to deeper understanding and help you gain multifaceted perspectives.
It's also simply fun as a thought experiment, and creating these kinds of disposable tools can help sharpen your quick-thinking skills.
Well then, have a wonderful Houdini life next year too!
Development Environment
- Windows 10
- Houdini 20.0.688
Discussion