> [!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.