SideFX Houdini customization package
git clone https://git.lucas.co/hou-control.git
houdini-agent/bridge.py (2.9K)
1 #!/usr/bin/env python3
2 import os
3 import sys
4 import rpyc
5 import traceback
6 import io
7 import contextlib
8
9 HOUDINI_HOST = os.environ.get("HOUDINI_HOST", "localhost")
10 HOUDINI_PORT = int(os.environ.get("HOUDINI_PORT", "18811"))
11
12 _REMOTE_RUNNER_SRC = r'''
13 def _hcrun(code, globals_dict=None):
14 import io, contextlib, traceback, sys, os
15 if globals_dict is None:
16 globals_dict = sys.modules["__main__"].__dict__
17
18 workspace = "''' + os.getcwd() + r'''"
19 if workspace not in sys.path:
20 sys.path.append(workspace)
21
22 try:
23 import hou
24 if "hou" not in globals_dict:
25 globals_dict["hou"] = hou
26 except ImportError:
27 pass
28
29 def _work():
30 buf = io.StringIO()
31 result_repr = None
32 error = None
33 try:
34 with contextlib.redirect_stdout(buf), contextlib.redirect_stderr(buf):
35 # Compile to handle multiline exec/eval correctly
36 try:
37 tree = compile(code, "<agent>", "eval")
38 result = eval(tree, globals_dict)
39 if result is not None:
40 result_repr = repr(result)
41 except SyntaxError:
42 tree = compile(code, "<agent>", "exec")
43 exec(tree, globals_dict)
44 except Exception:
45 error = traceback.format_exc()
46 return buf.getvalue(), result_repr, error
47
48 # rpyc's ThreadedServer serves this call on a connection thread, and HOM
49 # is not thread-safe: a hou call off Houdini's main thread segfaults it
50 # inside SWIG. Hand the work to the main-thread pump uiready installs,
51 # published on hou.session so this injected source needs no import of
52 # its own. No pump (hython, no UI, startup not finished) means no UI
53 # thread to protect, so run inline.
54 _hc_call = getattr(
55 getattr(sys.modules.get("hou"), "session", None),
56 "_hc_main_thread_call",
57 None,
58 )
59 if _hc_call is None:
60 return _work()
61 return _hc_call(_work)
62 '''
63
64 def run_in_houdini(code):
65 try:
66 conn = rpyc.classic.connect(HOUDINI_HOST, HOUDINI_PORT)
67 except ConnectionRefusedError:
68 return f"ERROR: Could not connect to Houdini at {HOUDINI_HOST}:{HOUDINI_PORT}. Is Houdini running?"
69
70 try:
71 conn.execute(_REMOTE_RUNNER_SRC)
72 stdout, result_repr, error = conn.namespace["_hcrun"](code)
73
74 output = []
75 if stdout: output.append(stdout.strip())
76 if result_repr: output.append(f"=> {result_repr}")
77 if error: output.append(f"ERROR:\n{error}")
78
79 return "\n".join(output) if output else "(no output)"
80 finally:
81 conn.close()
82
83 if __name__ == "__main__":
84 if len(sys.argv) < 2:
85 print("Usage: bridge.py \"python code\"")
86 sys.exit(1)
87
88 code = sys.argv[1]
89 if code == "--ping":
90 code = "hou.applicationVersionString()"
91
92 print(run_in_houdini(code))