git clone https://git.lucas.co/hou-control.git
CLAUDE.md (6.2K)
1 # CLAUDE.md
2
3 This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
5 ## Overview
6
7 `hou-control` (hc) is a SideFX Houdini customization package — not a standalone application. There is no build, no test suite, and no package manager. Code here is loaded by Houdini at runtime via the `houdini-studio-utils.json` package manifest (which adds this directory to `HOUDINI_PATH`) and the `HC_PATH` env var, which the code reads with `hou.getenv("HC_PATH")` to locate config/scripts on disk.
8
9 Everything runs inside Houdini's embedded Python 3.11. To exercise changes, reload from within Houdini:
10 - From the `hc` main menu: **Reload Hotkeys**, **Reload Colors**, **Reload Keycam**.
11 - Full package reload: `hou.ui.reloadPackage(...)` (see `HCSession.reloadHC`).
12 - Hotkey JSON changes take effect via `HCSession.reloadHotkeys()` which calls `HCBindings().load()` (assigns from `hc_hotkeys.json`, auto-clearing any conflicts via `hou.hotkeys.findConflicts`).
13
14 ## Architecture
15
16 ### The `hc` Python package (`python3.11libs/hc/`)
17
18 The core abstraction is a set of wrapper classes around Houdini's `hou` API objects. Each wrapper holds the underlying `hou_*` object and adds higher-level behavior. Classes are re-exported from `hc/__init__.py`.
19
20 Key hierarchy:
21 - **`HCSession`** (`hcsession.py`) — top-level entry point. Enumerates panes/tabs/viewports, controls desktop-wide visibility (menus, stowbars, shelf), update mode, hotkey reloads, and launches the HC Panel. Most menu items and hotkey bindings go through `HCSession`.
22 - **`HCPane`** wraps `hou.Pane`; knows how to split, resize, convert its current `hou.PaneTab` into the right `HC*` subclass via `HCPane.convertTab`.
23 - **`HCTab`** → **`HCPathTab`** → **`HCNetworkEditor`** / **`HCSceneViewer`**. `convertTab` dispatches on `hou.paneTabType` to the correct subclass; `HCPathTab` is used for Parm and DetailsView tabs. Tab type is also identified via a string `.type()` method (e.g. `'HCNetworkEditor'`) used in `isinstance`-style branching throughout `HCSession` and `HCMaps`.
24 - **`HCBindings`** — loads hotkey assignments by reading `hc_hotkeys.json` and calling `hou.hotkeys.addAssignment`. Before each assignment, `hou.hotkeys.findConflicts` identifies any ancestor/descendant bindings using the same key and clears them. The JSON keys are Houdini symbol paths like `h.pane.gview.foo`; the context is derived via `symbol.rpartition('.')[0]`.
25 - **`HCMaps`** (`hcmaps.py`) — central registry mapping human-readable command names → bound methods on session/pane/tab. Composed at runtime by `HCSession.hcPanel()` based on the current tab's type (`tab_map_base` always, plus one of `tab_map_path` / `tab_map_network_editor` / `tab_map_scene_viewer`). Used to populate the HC Panel selection dialog.
26 - **`HCWidgets`** (`hcwidgets.py`) — PySide6 widgets, notably `SelectionDialog` used for the HC Panel.
27
28 When adding a new command: implement it on the appropriate wrapper (`HCSession`/`HCPane`/`HC*Tab`), then register it in the matching `HCMaps.tab_map_*` so it appears in the HC Panel. If it should be hotkey-bound, add a Houdini symbol entry to `hc_hotkeys.json`.
29
30 ### Non-package Python (`python3.11libs/`)
31
32 These are recognized by Houdini's startup/event system by filename convention:
33 - `uiready.py` — runs once when the UI is ready; instantiates `HCSession` and calls `reloadHotkeys` + `toggleStowbars`.
34 - `nodegraphhooks.py` — Houdini's network-editor event hook. Implements `createEventHandler(uievent, pending_actions)` and dispatches `KeyboardEvent`s through a local `keymap` dict that calls into `HCNetworkEditor`. Return `(None, True)` to consume the event, `(None, False)` to let Houdini handle it.
35
36 ### Scripts (`scripts/`)
37
38 - `123.py` / `456.py` are Houdini's magic filenames: `123.py` runs when Houdini starts without a `.hip`, `456.py` runs after any `.hip` load. Currently used for "open last file" tracking via `$HOUDINI_USER_PREF_DIR/st_data/state.json`.
39 - `OnCreated.py` — node OnCreated event script.
40 - `hc_hotkeys.json` (at repo root) — source of truth for keybindings, loaded by `HCBindings`. Conflicts with existing bindings are detected and cleared automatically.
41 - `settings.json` — runtime settings for `keycam` viewer state and node graph defaults.
42
43 ### Viewer states (`viewer_states/`)
44
45 `keycam.py` is a custom Houdini viewer state ("keycam" navigator) registered via the viewer state API. `HCSession.reloadKeycam()` calls `hou.ui.reloadViewerState('keycam')`.
46
47 ### Configuration (`config/`)
48
49 Houdini-format config files:
50 - `UIDark.hcs` — UI color scheme.
51 - `3DSceneColors.dark` — viewport color scheme.
52 - `NodeGraphDark.inc` + `NodeGraphCommon.inc` — node graph styling (included via Houdini's `.inc` mechanism).
53 - `NodeShapes/`, `NodeShapeFlags/` — custom node shape definitions.
54
55 These are reloaded via `HCSession.reloadColorSchemes()` (`hou.ui.reloadColorScheme()` + `hou.ui.reloadViewportColorSchemes()`).
56
57 ### Other asset directories
58
59 - `otls/` — HDAs (Houdini Digital Assets). `otls/backup/` is gitignored.
60 - `desktop/hc_attached.desk` — main desktop layout (single-window, multi-pane). `desktop/hc_detached.desk` — minimal layout (single SceneViewer pane) used when `desktop_mode` is `detached` and floating panels handle the rest.
61 - `radialmenu/`, `toolbar/`, `presets/`, `vex/`, `help/` — Houdini conventional subdirectories loaded by path.
62 - `MainMenuCommon.xml`, `OPmenu.xml`, `PARMmenu.xmlx`, `ParmGearMenu.xml` — menu definitions. Each `scriptItem` typically does `from hc import HCSession; HCSession().someMethod()`.
63
64 ## Conventions
65
66 - Wrappers never subclass `hou.*` types; they store the hou object on `self.hou_tab` / `self.hou_pane` and delegate.
67 - `.type()` methods return string discriminators (`'HCNetworkEditor'`, `'HCSceneViewer'`, `'HCParameterTab'`, `'HCPathTab'`) — this is the idiomatic way to branch on tab kind in this codebase, not `isinstance`. When branching, remember that `'HCParameterTab'` is a sibling of `'HCPathTab'` (Parm tabs return the former, DetailsView returns the latter) — code that wants both should check `tab.type() in ('HCPathTab', 'HCParameterTab')`.
68 - Toggle-style methods often use a small string map (e.g. `{'0': '1', '1': '0'}`) because Houdini prefs are stored as strings.
69 - Paths to bundled files are built from `hou.getenv("HC_PATH")` — don't hardcode absolute paths.