git.lucas.co / hou-control
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))