dd2a1734eb
- Shelf tool to open/close command port in Houdini - hhb client script (eval, script, get-selected, set-param, etc.) - Hermes skill with standard hou module workflows - Example: create pyro setup - hou module quick reference
189 lines
5.8 KiB
Bash
Executable File
189 lines
5.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# ──────────────────────────────────────────────────────────
|
|
# hhb — Hermes-Houdini Bridge Client
|
|
# Sends Python/hou commands to a running Houdini session
|
|
# via SSH + hcommand.
|
|
# ──────────────────────────────────────────────────────────
|
|
set -euo pipefail
|
|
|
|
HOST="${HHB_HOST:-niklas@192.168.178.39}"
|
|
PORT="${HHB_PORT:-12345}"
|
|
HCOMMAND="${HHB_HCOMMAND:-/opt/hfs21.0/bin/hcommand}"
|
|
WORKSPACE="${HHB_WORKSPACE:-/opt/data/projects/hermes-houdini-bridge}"
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Usage: hhb <command> [args]
|
|
|
|
Commands:
|
|
eval <python_code> Execute Python code in the running Houdini session.
|
|
Example: hhb eval "print(hou.selectedNodes())"
|
|
|
|
script <file.py> Send a Python script file to Houdini and execute it.
|
|
Example: hhb script ./examples/create_pyro_setup.py
|
|
|
|
get-selected Print paths and types of selected nodes.
|
|
get-param <parm> Get value of a parameter on the first selected node.
|
|
set-param <parm> <val> Set a parameter on the first selected node.
|
|
get-inputs Print input connections of the first selected node.
|
|
status Check if the bridge port is open.
|
|
help Show this help.
|
|
|
|
EOF
|
|
exit 0
|
|
}
|
|
|
|
# ── Helpers ──────────────────────────────────────────────
|
|
|
|
run_remote_python() {
|
|
# Write Python code to a temp file, execute via hcommand.
|
|
# This avoids quoting hell with complex Python snippets.
|
|
local python_code="$1"
|
|
local tmpfile="/tmp/hhb_$$.py"
|
|
|
|
# Upload script
|
|
printf '%s' "$python_code" | ssh "$HOST" "cat > $tmpfile"
|
|
|
|
# Execute via hscript's python command — reads file and execs
|
|
local output
|
|
output=$(ssh "$HOST" \
|
|
"$HCOMMAND $PORT \"python exec(open('$tmpfile').read())\"" 2>&1) || {
|
|
local rc=$?
|
|
ssh "$HOST" "rm -f $tmpfile" 2>/dev/null
|
|
echo "hhb: hcommand failed (exit $rc)" >&2
|
|
echo "$output" >&2
|
|
return $rc
|
|
}
|
|
|
|
# Cleanup
|
|
ssh "$HOST" "rm -f $tmpfile" 2>/dev/null
|
|
|
|
# hcommand output has a prefix line like "-> foobar", strip it
|
|
echo "$output" | sed 's/^->\s*//'
|
|
}
|
|
|
|
# ── Commands ──────────────────────────────────────────────
|
|
|
|
cmd_status() {
|
|
local output
|
|
output=$(ssh "$HOST" "$HCOMMAND $PORT openport" 2>&1) || {
|
|
echo "Bridge INACTIVE — Houdini not reachable or port $PORT closed."
|
|
return 1
|
|
}
|
|
echo "$output" | sed 's/^->\s*//'
|
|
}
|
|
|
|
cmd_eval() {
|
|
[[ $# -ge 1 ]] || { echo "Usage: hhb eval <python_code>"; exit 1; }
|
|
run_remote_python "$*"
|
|
}
|
|
|
|
cmd_script() {
|
|
[[ $# -ge 1 ]] || { echo "Usage: hhb script <file.py>"; exit 1; }
|
|
local script_path="$1"
|
|
[[ -f "$script_path" ]] || { echo "File not found: $script_path"; exit 1; }
|
|
|
|
scp -q "$script_path" "$HOST:/tmp/hhb_script_$$.py"
|
|
local output
|
|
output=$(ssh "$HOST" \
|
|
"$HCOMMAND $PORT \"python exec(open('/tmp/hhb_script_$$.py').read())\"" 2>&1) || {
|
|
local rc=$?
|
|
ssh "$HOST" "rm -f /tmp/hhb_script_$$.py" 2>/dev/null
|
|
echo "hhb: hcommand failed (exit $rc)" >&2
|
|
echo "$output" >&2
|
|
return $rc
|
|
}
|
|
ssh "$HOST" "rm -f /tmp/hhb_script_$$.py" 2>/dev/null
|
|
echo "$output" | sed 's/^->\s*//'
|
|
}
|
|
|
|
cmd_get_selected() {
|
|
run_remote_python '
|
|
import hou
|
|
nodes = hou.selectedNodes()
|
|
if not nodes:
|
|
print("No nodes selected.")
|
|
else:
|
|
for n in nodes:
|
|
print(f"{n.path():50s} [{n.type().name()}]")
|
|
'
|
|
}
|
|
|
|
cmd_get_param() {
|
|
[[ $# -ge 1 ]] || { echo "Usage: hhb get-param <parm_name>"; exit 1; }
|
|
local parm="$1"
|
|
run_remote_python "
|
|
import hou
|
|
nodes = hou.selectedNodes()
|
|
if not nodes:
|
|
print('No nodes selected.')
|
|
else:
|
|
p = nodes[0].parm('$parm')
|
|
if p is None:
|
|
print(f'Parameter \"$parm\" not found on {nodes[0].path()}')
|
|
else:
|
|
print(f'{nodes[0].path()}.{p.name()} = {p.eval()}')
|
|
"
|
|
}
|
|
|
|
cmd_set_param() {
|
|
[[ $# -ge 2 ]] || { echo "Usage: hhb set-param <parm_name> <value>"; exit 1; }
|
|
local parm="$1"
|
|
local val="$2"
|
|
run_remote_python "
|
|
import hou
|
|
nodes = hou.selectedNodes()
|
|
if not nodes:
|
|
print('No nodes selected.')
|
|
else:
|
|
p = nodes[0].parm('$parm')
|
|
if p is None:
|
|
print(f'Parameter \"$parm\" not found on {nodes[0].path()}')
|
|
else:
|
|
p.set('$val')
|
|
print(f'{nodes[0].path()}.{p.name()} = {p.eval()} ✓')
|
|
"
|
|
}
|
|
|
|
cmd_get_inputs() {
|
|
run_remote_python '
|
|
import hou
|
|
nodes = hou.selectedNodes()
|
|
if not nodes:
|
|
print("No nodes selected.")
|
|
else:
|
|
for n in nodes:
|
|
inputs = n.inputConnectors()
|
|
print(f"\n{n.path()} [{n.type().name()}]")
|
|
for i, conn in enumerate(inputs):
|
|
in_nodes = n.inputConnections(i) if hasattr(n, "inputConnections") else []
|
|
if in_nodes:
|
|
for c in in_nodes:
|
|
print(f" input {i} ← {c.inputNode().path()} [{c.inputNode().type().name()}]")
|
|
else:
|
|
print(f" input {i} ← (none)")
|
|
'
|
|
}
|
|
|
|
# ── Main ─────────────────────────────────────────────────
|
|
|
|
[[ $# -ge 1 ]] || usage
|
|
|
|
CMD="$1"
|
|
shift || true
|
|
|
|
case "$CMD" in
|
|
eval) cmd_eval "$@" ;;
|
|
script) cmd_script "$@" ;;
|
|
get-selected) cmd_get_selected ;;
|
|
get-param) cmd_get_param "$@" ;;
|
|
set-param) cmd_set_param "$@" ;;
|
|
get-inputs) cmd_get_inputs ;;
|
|
status) cmd_status ;;
|
|
help|--help|-h) usage ;;
|
|
*)
|
|
echo "Unknown command: $CMD"
|
|
usage
|
|
;;
|
|
esac
|