git.lucas.co / hou-control
SideFX Houdini customization package
git clone https://git.lucas.co/hou-control.git

houdini-agent/hou_geo.py (3.1K)

 1 #!/usr/bin/env python3
 2 import sys
 3 from bridge import run_in_houdini
 4 
 5 def get_grid_spacing():
 6     try:
 7         code = "hou.ui.paneTabOfType(hou.paneTabType.NetworkEditor).getPrefs()"
 8         import ast
 9         res = run_in_houdini(code)
10         if "=>" in res:
11             res = res.split("=>", 1)[1].strip()
12         prefs = ast.literal_eval(res)
13         return float(prefs.get('gridxstep', 2.0)), float(prefs.get('gridystep', 1.0))
14     except Exception:
15         return 2.0, 1.0
16 
17 def move_node(node_path, x, y):
18     gx, gy = get_grid_spacing()
19     snapped_x = round(float(x) / gx) * gx
20     snapped_y = round(float(y) / gy) * gy
21     
22     code = f"""
23 n = hou.node({repr(node_path)})
24 if n:
25     parent = n.parent()
26     target_pos = hou.Vector2({snapped_x}, {snapped_y})
27     
28     # Check for conflict
29     for sibling in parent.children():
30         if sibling != n and sibling.position().distanceTo(target_pos) < 0.1:
31             # Shift conflicting neighbor to the right
32             sibling.move(hou.Vector2({gx}, 0))
33             
34     n.setPosition(target_pos)
35 """
36     return run_in_houdini(code)
37 
38 def create_node(type_name, name=None, parent="/obj", input_node=None):
39     gx, gy = get_grid_spacing()
40     code = f"""
41 p = hou.node('{parent}')
42 n = p.createNode('{type_name}', node_name={repr(name)} if {repr(name)} else None)
43 if {repr(input_node)}:
44     in_node = hou.node({repr(input_node)})
45     if in_node:
46         n.setInput(0, in_node)
47 print(n.path())
48 """
49     res = run_in_houdini(code)
50     # Extract path from stdout or result_repr
51     node_path = res.strip()
52     if "=>" in node_path:
53         node_path = node_path.split("=>", 1)[0].strip()
54     if not node_path.startswith("/"):
55         # fallback
56         node_path = f"{parent}/{name}"
57         
58     if input_node:
59         try:
60             res_pos = run_in_houdini(f"print(tuple(hou.node('{input_node}').position()))")
61             pos_str = res_pos.split("=>", 1)[0].strip()
62             import ast
63             ix, iy = ast.literal_eval(pos_str)
64             move_node(node_path, ix, iy - gy)
65         except Exception: pass
66     else:
67         move_node(node_path, 0, 0)
68     return node_path
69 
70 def create_color_node(name, parent, input_node, color=(1, 0, 0)):
71     c_path = create_node("color", name, parent, input_node)
72     run_in_houdini(f"hou.node('{c_path}').parm('colorr').set({color[0]})")
73     run_in_houdini(f"hou.node('{c_path}').parm('colorg').set({color[1]})")
74     run_in_houdini(f"hou.node('{c_path}').parm('colorb').set({color[2]})")
75     return c_path
76 
77 if __name__ == "__main__":
78     if len(sys.argv) < 2: sys.exit(1)
79     cmd = sys.argv[1]
80     if cmd == "create":
81         print(create_node(sys.argv[2], sys.argv[3] if len(sys.argv) > 3 else None, sys.argv[4] if len(sys.argv) > 4 else "/obj", sys.argv[5] if len(sys.argv) > 5 else None))
82     elif cmd == "move":
83         print(move_node(sys.argv[2], sys.argv[3], sys.argv[4]))
84     elif cmd == "ls":
85         print(run_in_houdini(f"print([n.name() for n in hou.node('{sys.argv[2] if len(sys.argv) > 2 else '/obj'}').children()])"))
86     elif cmd == "delete":
87         print(run_in_houdini(f"n = hou.node('{sys.argv[2]}'); n.destroy() if n else None"))