> [!abstract] Summary
> **Guidelines for AI-generated VEX and Python code in Houdini SOPs and LOPs**. Conventions, patterns and pitfalls when asking an AI assistant (Claude, ChatGPT, etc.) to write VEX/Python for Houdini 21 SOP (geometry) and LOP/Solaris (USD) contexts, in a USD-based pipeline driven by Prism 2.x. Distills SideFX docs, CGWiki notes and Prism docs into concrete rules and templates.
---
# Guidelines for AI-Generated VEX and Python Code in Houdini SOPs and LOPs
## Overview
This document defines conventions and patterns for an AI system that generates VEX and Python code for Houdini 21 SOP (geometry) and LOP/Solaris (USD) contexts, integrated into a USD-based pipeline driven by Prism 2.x.
It distills SideFX documentation, CGWiki notes, and Prism docs into concrete rules, code templates, and pitfalls specifically targeted at automated code generation.
## Houdini Contexts and Execution Model
Houdini supports multiple execution contexts; this document focuses on SOPs (geometry networks) and LOPs/Solaris (USD stage networks). VEX and Python behave differently in each context and must not be mixed up by the AI.
### SOPs (Geometry Context)
- Operate on Houdini geometry: points, primitives (polygons, curves, volumes), vertices, and detail.
- VEX in SOPs is usually authored in Attribute Wrangles or VOP networks and runs over the chosen element class automatically (no explicit loop required).
- Attributes are stored on geometry components (e.g. `@P`, `@Cd`, `@v`, `@uv`); groups are boolean labels attached to elements.
### LOPs/Solaris (Stage Context)
- Operate on a USD stage: a scene graph of primitives (transforms, meshes, lights, materials, variants, etc.).
- LOP VEX runs in Wrangle LOP and VOP LOP nodes over **USD primitives or array elements**, not SOP geometry elements.
- The primary iteration target is prims via patterns (e.g. `/set/*`), or array elements when "Run on Elements of Array Attributes" is enabled.
### Python / HOM
- HOM (Houdini Object Model) is accessed via the `hou` module and controls nodes, parameters, and scenes using Python.
- In Solaris, SideFX recommends Python for complex scripting and stage traversal, and VEX for localized per-prim or array operations, especially when mixing SOP and USD data.
## VEX Language Essentials for Code Generation
### General Properties
- VEX is a C-like SIMD language designed for high-performance per-element operations; it is not a general-purpose scripting replacement.
- VEX snippets are evaluated once per element (point, prim, etc.) in the chosen context, with built-in variables such as `@P`, `@ptnum`, `@primnum`, `@Frame`, and `@Time` available as appropriate.
### Types and Declarations
- Basic numeric: `int`, `float` (`f`), `vector`, `vector2`, `vector4`, `matrix3`, `matrix`.
- String: `string` (`s`).
- Arrays: `int[]`, `vector[]`, `string[]`, etc.
- Attributes use type prefixes in VEX: `f@name`, `i@name`, `v@name`, `s@name`, `f[]@name`.
### Attribute Access
- SOPs: attributes correspond to geometry data; e.g. `v@P`, `v@Cd`, `i@id`, `f@pscale`.
- LOPs: attributes are USD properties or primvars; primvars often use namespaced forms like `primvars:displayColor` and may be arrays.
- Use `usd_attrib`, `usd_primvar`, and their `*_element` and `*_len` variants to read attribute and primvar values directly from the USD stage.
### Example: SOP Attribute Patterns
```c
// Point wrangle: wave motion
@P.y += sin(@Time + @ptnum * 0.1);
// Random color per point
v@Cd = rand(@ptnum);
// Create a group: every 3rd point
i@group_every3 = (@ptnum % 3) == 0;
```
## VEX in SOPs: Rules and Templates
### Element Classes
- **Point wrangle**: default for deformation and scattering logic; operates on `@P`, `@N`, `@Cd`, etc.
- **Primitive wrangle**: operates on per-primitive attributes (e.g. `@primnum`, custom flags, polygon data).
- **Detail wrangle**: runs once per geometry; used for aggregations and control logic.
### Common Attribute Conventions
- Transform-like data: `@P` (position), `@N` (normal), `@up`, `@orient` (quaternion), `@scale` or `@pscale`.
- Shading attributes: `@Cd`, `@Alpha`, `@uv` (often mapped to USD `st` primvars later).
- Group creation via `i@group_name = condition;` rather than string group operations for AI simplicity.
### SOP Code Templates
**Deformation template:**
```c
// INPUT: geometry, OUTPUT: adjusted @P
float speed = chf("speed");
float amp = chf("amplitude");
@P.y += amp * sin(@Time * speed + @ptnum * 0.1);
```
**Attribute-driven instancing data:**
```c
// Setup attributes for copy-to-points or instancing
f@pscale = fit01(rand(@ptnum), 0.8, 1.2);
vector dir = normalize(rand(@ptnum) - 0.5);
@orient = quaternion(radians(rand(@ptnum + 123) * 360), dir);
```
The AI should favor such simple, parameter-driven patterns for predictability and for easier mapping into USD instancers later.
## VEX in LOPs/Solaris: Concepts and Built-ins
### Built-in Variables in LOPs
LOPs have specific built-in variables exposed in wrangles:
- `@primpath` – current USD prim path (e.g. `/set/pig`).
- `@elemnum` – current element number (prim index or array element index).
- `@numelem` – total number of elements being processed.
- `@primtype` – USD type name (e.g. `Mesh`, `Xform`, `Scope`).
- `@primname` – name of the primitive (last segment of `@primpath`).
- `@primkind`, `@primpurpose`, `@primdrawmode`, `@primactive`, `@primvisible` – metadata-like properties of the prim.
The AI must **never** assume geometry-like attributes (e.g. `@P`) exist in a LOP wrangle; instead, it should query USD properties via `usd_*` functions when needed.
### Run Modes
- **Run Over Primitives** (default): code runs once per matching prim based on the primitive pattern parameter.
- **Run on Elements of Array Attributes**: code iterates over array elements such as `primvars:displayColor`, point instancer transforms, or similar per-instance data.
The AI should select the array mode when operating on PointInstancer per-instance attributes or mesh point data stored as arrays on a prim.
### USD-Specific VEX Function Groups
The following function families are central for AI-generated LOPs VEX code.
#### Attribute and Primvar Access
- `usd_attrib`, `usd_attribelement`, `usd_attriblen`, `usd_attribnames`, `usd_attribtypename` – low-level USD attributes.
- `usd_primvar`, `usd_primvarelement`, `usd_primvarlen`, `usd_primvarnames`, `usd_primvartimesamples`, `usd_primvartypename` – primvars attached to geometry-like prims.
- `usd_iprimvar*` variants – access primvars considering inheritance from ancestors.
**Template:**
```c
// Read a float primvar directly from USD
float width = usd_primvar(0, @primpath, "primvars:width");
// Safe array access
int len = usd_primvarlen(0, @primpath, "primvars:displayColor");
if (len > 0)
{
vector first = usd_primvarelement(0, @primpath, "primvars:displayColor", 0);
}
```
#### Transform and Spatial Utilities
- `usd_addtranslate`, `usd_addrotate`, `usd_addscale`, `usd_addtransform`, `usd_addorient`, `usd_addtotransformorder`, `usd_addinversetotransformorder` – append transform ops to prims.
- `usd_localtransform`, `usd_worldtransform`, `usd_transformorder`, `usd_transformname`, `usd_uniquetransformname` – query or manage transform stacks.
- `usd_getbbox_*`, `usd_pointinstance_getbbox_*`, `usd_relbbox`, `usd_pointinstance_relbbox` – spatial queries for bounds.
**Template:**
```c
// Animate Y translation over time
vector t = set(0, sin(@Frame * 0.1 + @elemnum * 0.5), 0);
usd_addtranslate(0, @primpath, "", t);
usd_addtotransformorder(0, @primpath, "xformOp:translate");
```
#### Prim and Stage Introspection
- `usd_childnames`, `usd_parentpath`, `usd_name`, `usd_typename` – scene graph navigation.
- `usd_isprim`, `usd_isinstance`, `usd_istype`, `usd_iskind`, `usd_isvisible`, `usd_isactive`, `usd_isprimvar` – property checks.
- `usd_makevalidprimname`, `usd_makevalidprimpath` – sanitize names to be USD-valid, especially important when converting SOP `@name` attributes to prim paths.
#### Metadata and Collections
- `usd_metadata*` – get/set metadata arrays and values.
- `usd_collection*` – manage USD collections, includes and excludes lists.
#### Materials and Relationships
- **`usd_bindmaterial` does NOT exist in VEX.** Material binding must be done via relationship functions.
- `usd_setrelationshiptargets`, `usd_relationshiptargets`, `usd_removerelationshiptarget` – relationship manipulation (including material binding).
- To bind a material in VEX, set the `material:binding` relationship targets:
```c
string targets[] = {"/materials/myMaterial_MAT"};
usd_setrelationshiptargets(0, @primpath, "material:binding", targets);
```
- To query the current material binding:
```c
string targets[] = usd_relationshiptargets(0, @primpath, "material:binding");
```
The AI should use `usd_setrelationshiptargets` with `"material:binding"` for material operations. Never generate `usd_bindmaterial` — it will fail to compile.
## LOPs VEX: Design Patterns from Production Notes
CGWiki (Tokeru) provides practical Solaris patterns that are critical for AI-generated code.
### Hierarchy and Primpaths
- LOP networks act as procedural hierarchy editors; primpaths and parenting are the main control levers.
- Top-level primitives are commonly created with a Primitive LOP using `/$OS` naming, then refined (e.g. `/set`).
- Reparenting is often achieved by changing primitive paths (e.g. `/sphere1` → `/set/sphere1`).
The AI should respect the pattern of top-level containers (`/set`, `/env`, `/characters`) and avoid hard-coding deep paths when a parameter is available.
### Instancing
- Point instancing uses a PointInstancer prim with prototypes under `/prototypes` and point data from SOPs or arrays on the prim.
- A typical workflow is: SOP RBD chunks or scatter points → SOP Import → Point Instancer LOP with prototype mapping via `@name` or similar attributes.
- Attribute naming must follow USD rules: names must start with a letter or underscore and then contain letters, digits, or underscores; hyphens cause issues and should be replaced with underscores.
**AI rules:**
- When generating SOP code for instancing into LOPs, avoid hyphens in attribute values intended for USD names; use underscores instead.
- Prefer `@name` for matching prototypes, with values that match leaf prim names or sanitized versions of them.
### Variants and Value Clips
- Variants provide switchable configurations (e.g. tree vs tree+fire) that propagate across DCCs when authored in USD.
- Clean setups separate changing pieces (e.g. fire VDB) from static ones (tree mesh) to minimize duplication across variants.
- Value clips and clip stitching provide file-per-frame USD sequences encapsulated in a single logical prim for looping and performance.
The AI should avoid reinventing clip logic in VEX; instead, it should use the appropriate LOP nodes (Value Clip LOP, Geometry Clip Sequence LOP) and reserve VEX for small per-prim tweaks.
### Inline USD LOP
- The Inline USD LOP accepts USD text that can be parameterized via Houdini expressions, for example using `ch()` in backticks to bind to sliders.
- This is useful for very small procedurally-defined structures, but large inline schemas should be avoided.
The AI should reserve Inline USD for simple demos and prefer proper LOP nodes and VEX for robust pipelines.
## SOP–LOP Interoperability with USD VEX Functions
VEX in SOPs can read from Solaris stages using `usd_*` functions with `op:` paths, enabling geometry to react to USD data.
**Template:**
```c
// SOP wrangle that reads size attribute from a LOP prim
string lop = "op:/stage/OUT_set";
float size = usd_attrib(lop, "/cube1", "size");
f@scale = size;
```
The AI should use these patterns when geometry in SOPs must be driven by LOP-stage parameters, ensuring paths are parameterized where possible.
## Python / HOM for SOPs and LOPs
### HOM Overview
- The `hou` module exposes nodes, networks, parameters, geometry, and LOP stages to Python.
- Python scripts can live in parameter expressions, shelf tools, HDA scripts, or external files.
### Accessing a LOP Stage via HOM
**Template:**
```python
import hou
from pxr import Usd, UsdGeom, UsdShade
node = hou.pwd() # Current LOP node
stage = node.editableStage() # Usd.Stage for this node
prim = stage.GetPrimAtPath("/set/pig")
if prim and prim.IsValid():
children = list(prim.GetChildren())
```
The AI should use `editableStage()` for in-place modifications and `stage = node.stage()` (read-only) when inspection is sufficient.
### When to Use Python vs VEX in Solaris
- Use **VEX** for:
- Per-prim or per-array-element operations that map cleanly to wrangles (e.g. randomizing per-instance transforms, setting primvars, simple filters).
- Mixed SOP/USD operations where the same VEX snippet reads from a stage and writes to geometry or vice versa.
- Use **Python/HOM + pxr** for:
- Complex stage traversal, non-local decisions, or heavy metadata editing.
- HDA logic, pipeline integration, and coordination across many nodes or files (e.g. Prism publishing hooks).
The AI should default to Python for multi-node orchestration and to VEX for localized, data-parallel operations.
## Prism Pipeline Considerations
Prism provides an asset- and shot-based pipeline that integrates with Houdini and USD.
### High-Level Concepts
- Central project configuration: assets, shots, and tasks are defined in a project database and referenced via environment variables or Prism APIs.
- Houdini integration offers project browser panels, publish dialogs, and automatic versioning for geometry and USD exports.
- Plugins enable USD workflows (e.g. Solaris integration), where publish nodes and paths follow studio-defined conventions.
### AI Coding Guidelines for Prism
- Do not hard-code absolute paths; use Prism variables or environment variables exposed by the integration (e.g. project root, asset name).
- For export nodes (USD ROPs, Geometry ROPs), prefer patterns like `$PRISM_ASSET`, `$PRISM_SHOT`, or documented Prism tokens when constructing filenames and directories.
- When generating Python, import and use Prism’s Python APIs rather than re-implementing their logic; keep all publishing and path resolution centralized in Prism.
## Naming and Attribute Conventions for USD Pipelines
### Valid USD Names
- Prim names and property names must start with an alphabetic character or underscore and only contain letters, digits, and underscores thereafter.
- Hyphens, spaces, and many special characters are invalid and may cause failures in instancers and LOPs.
**AI rule:**
- When converting SOP attributes (e.g. `@name`) into USD prim names or collections, sanitize them with `usd_makevalidprimname()` or by replacing disallowed characters with underscores.
### Primvars vs Attributes
- Data intended to be used for rendering or shading in USD should be stored as primvars (e.g. `primvars:displayColor`, `primvars:st`).
- Internal USD attributes or pipeline flags can be stored as regular attributes; the AI should use the `primvars:` namespace only for data meant to be consumed by renderers or downstream DCCs.
### Example: Shadow Control via Attributes
CGWiki demonstrates using SOP attributes (e.g. `@noshadow`) imported into LOPs to control render properties such as shadow casting on per-prim basis.
**Pattern:**
1. In SOPs, set `i@noshadow = (@ptnum % 2 == 0);` on packed primitives.
2. Import via SOP Import LOP; attribute appears as `primvars:noshadow` (often as an array) per prim.
3. In a Render Geometry Settings LOP, use a prim pattern expression like `{ @noshadow == 1 }` to select prims and disable shadow casting.
The AI should respect this attribute-based selection pattern where possible instead of manually listing primpaths.
## AI Coding Rules and Heuristics
### 1. Choose Context Correctly
- If the user’s intent involves **geometry deformation, attribute creation on points/prims, or copy-to-points**, generate **SOP VEX** in a wrangle.
- If the intent involves **hierarchy manipulation, USD attributes, primvars, materials, or instancing**, generate **LOP VEX** or Python with HOM/pxr.
### 2. Respect Execution Granularity
- Prefer small, self-contained snippets that operate on one element at a time (per point, per prim, or per instance).
- Avoid global loops; rely on Houdini/LOPs to iterate over all elements.
### 3. Use Parameters for User Control
- Any hard-coded constants that impact behavior (thresholds, speeds, scales) should be exposed as channel references (`ch()`, `chf()`, `chi()`) or node parameters rather than literals.
### 4. Prefer `usd_*` Functions in LOPs
- In Solaris, use `usd_setprimvar`, `usd_setattrib`, `usd_bindmaterial`, and `usd_addtransform` rather than trying to write raw `@` attributes with USD namespace strings.
- Use the `*_element` and `*_len` variants when working with arrays and always bounds-check indices.
### 5. Sanitize and Validate Paths
- Always build primpaths using string concatenation and `usd_makevalidprimname()` where appropriate.
- Never assume that SOP group or `@name` values are already valid USD identifiers.
### 6. Avoid Heavy Logic in VEX on Stages
- As SideFX notes, reading from stages can be multi-threaded but writing is single-threaded and subject to USD limitations; heavy logic can be slow.
- If an operation requires many dependent decisions or large graph traversal, generate Python/HOM with pxr APIs instead of VEX.
### 7. Align with Prism
- Use Prism’s publish/load APIs in Python to resolve file paths and asset versions, not custom `os.path` logic.
- Keep shot and asset naming consistent with Prism’s configured conventions and variables.
## Example End-to-End Patterns for the AI
### A. Randomize Display Color of All Meshes Under /set in LOPs
**Wrangle LOP (Run Over Primitives, pattern `/set//*`):**
```c
// Only affect mesh-like prims
if (!usd_istype(0, @primpath, "Mesh"))
return;
vector col = rand(@elemnum + @Frame * 0.01);
// Store as primvars:displayColor array with a single entry
vector colors[];
append(colors, col);
v[]@primvars:displayColor = colors;
```
### B. Per-Instance Rotation Offset on PointInstancer
**Wrangle LOP (Run on Elements of Array Attributes, prim pattern set to the PointInstancer prim):**
```c
// Each iteration corresponds to one instance
float angle = rand(@elemnum) * 360.0;
vector axis = set(0, 1, 0);
// Assume an orientations array primvar exists
quaternion q = quaternion(radians(angle), axis);
quaternion orient[] = q[]@primvars:orientations;
if (@elemnum < len(orient))
orient[@elemnum] = q * orient[@elemnum];
q[]@primvars:orientations = orient;
```
### C. Python: Rebind All Meshes to a New Material in Solaris
```python
import hou
from pxr import Usd, UsdGeom, UsdShade
node = hou.pwd()
stage = node.editableStage()
new_mat_path = "/materials/new_mtl"
new_mat_prim = stage.GetPrimAtPath(new_mat_path)
material = UsdShade.Material(new_mat_prim)
for prim in stage.Traverse():
if prim.GetTypeName() != "Mesh":
continue
UsdShade.MaterialBindingAPI(prim).Bind(material)
```
This pattern is preferable in Python rather than VEX because it traverses the whole stage and changes many relationships.
## Conclusion
By following the context-aware rules, attribute conventions, and USD-specific function usage described above, an AI can reliably generate VEX and Python code that behaves correctly in Houdini SOP and LOP/Solaris contexts and fits into a Prism-managed USD pipeline.
The key is to choose SOP vs LOP vs Python correctly, use `usd_*` APIs for USD manipulation, sanitize names, and parameterize behavior for human control.