Files
hermes-houdini-bridge/toolbar/hermes_bridge.shelf
T

165 lines
6.2 KiB
XML

<?xml version="1.0" encoding="UTF-8"?>
<shelfDocument>
<toolshelf name="hermes_bridge" label="Hermes Bridge">
<memberTool name="hermes_bridge_dialog"/>
</toolshelf>
<tool name="hermes_bridge_dialog" label="Hermes Bridge" icon="MISC_python">
<helpText><![CDATA[Open the Hermes Bridge control panel.
Manage port, open/close the bridge, copy connection info.]]></helpText>
<script scriptType="python"><![CDATA[
from PySide6 import QtWidgets, QtCore, QtGui
import hou
import socket
import json
import os
CONFIG_FILE = os.path.join(hou.homeHoudiniDirectory(), "hermes_bridge.json")
DEFAULT_PORT = 12345
def load_config():
try:
with open(CONFIG_FILE) as f:
return json.load(f)
except Exception:
return {"port": DEFAULT_PORT}
def save_config(cfg):
with open(CONFIG_FILE, "w") as f:
json.dump(cfg, f, indent=2)
def bridge_is_open(port):
return f"Port {port} is open" in hou.hscript("openport")[0]
# ── Window (QWidget, not QDialog — per SideFX HOM cookbook) ─
class HermesBridgeWindow(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.cfg = load_config()
self.port = self.cfg.get("port", DEFAULT_PORT)
self.hostname = socket.gethostname()
self._build_ui()
self._refresh()
def _build_ui(self):
self.setWindowTitle("Hermes Bridge")
self.setMinimumWidth(420)
self.setStyleSheet("""
QWidget { background: #1e1e1e; color: #e0e0e0; font-size: 13px; }
QLabel#title { font-size: 16px; font-weight: bold; color: #fff; }
QPushButton { border: none; border-radius: 4px; padding: 8px 16px;
font-weight: bold; }
QPushButton#btnToggle { padding: 12px; font-size: 15px; }
QPushButton#btnToggle[active="true"] {
background: #5c1a1a; color: #ff7777; border: 1px solid #a44; }
QPushButton#btnToggle[active="false"] {
background: #1a4a1a; color: #77cc77; border: 1px solid #4a4; }
QPushButton#btnCopy { background: #1565c0; color: #fff; }
QPushButton#btnCopy:hover { background: #1976d2; }
QSpinBox { background: #2d2d2d; border: 1px solid #555; color: #e0e0e0;
padding: 5px 8px; border-radius: 3px; }
QLineEdit { background: #2d2d2d; border: 1px solid #555; color: #aaa;
padding: 5px 8px; border-radius: 3px; }
""")
layout = QtWidgets.QVBoxLayout(self)
layout.setSpacing(12)
layout.setContentsMargins(20, 20, 20, 20)
title = QtWidgets.QLabel("\U0001f916 Hermes Bridge")
title.setObjectName("title")
layout.addWidget(title)
self.status_label = QtWidgets.QLabel()
layout.addWidget(self.status_label)
port_row = QtWidgets.QHBoxLayout()
port_row.addWidget(QtWidgets.QLabel("Port:"))
self.port_spin = QtWidgets.QSpinBox()
self.port_spin.setRange(1024, 65535)
self.port_spin.setValue(self.port)
self.port_spin.setFixedWidth(100)
self.port_spin.valueChanged.connect(self._on_port_changed)
port_row.addWidget(self.port_spin)
port_row.addStretch()
layout.addLayout(port_row)
self.btn_toggle = QtWidgets.QPushButton("Open Bridge")
self.btn_toggle.setObjectName("btnToggle")
self.btn_toggle.released.connect(self._toggle)
layout.addWidget(self.btn_toggle)
layout.addWidget(QtWidgets.QLabel("Tell Hermes:"))
prompt_row = QtWidgets.QHBoxLayout()
self.prompt_field = QtWidgets.QLineEdit()
self.prompt_field.setReadOnly(True)
prompt_row.addWidget(self.prompt_field)
self.btn_copy = QtWidgets.QPushButton("Copy")
self.btn_copy.setObjectName("btnCopy")
self.btn_copy.clicked.connect(self._copy)
prompt_row.addWidget(self.btn_copy)
layout.addLayout(prompt_row)
layout.addStretch()
def _refresh(self):
is_open = bridge_is_open(self.port)
if is_open:
self.status_label.setText(
f"\u25cf Connected \u2014 {self.hostname}:{self.port}")
self.status_label.setStyleSheet(
"color: #4caf50; font-size: 14px; font-weight: bold;")
self.btn_toggle.setText("Close Bridge")
self.btn_toggle.setProperty("active", "true")
else:
self.status_label.setText("\u25cf Not connected")
self.status_label.setStyleSheet(
"color: #ff5555; font-size: 14px; font-weight: bold;")
self.btn_toggle.setText("Open Bridge")
self.btn_toggle.setProperty("active", "false")
self.btn_toggle.style().unpolish(self.btn_toggle)
self.btn_toggle.style().polish(self.btn_toggle)
self.prompt_field.setText(
f"Hey Hermes, I have a Houdini session running "
f"on {self.hostname} (port {self.port}).")
def _toggle(self):
try:
if bridge_is_open(self.port):
hou.hscript(f"closeport {self.port}")
else:
hou.hscript(f"closeport {self.port}")
hou.hscript(f"openport {self.port} 0.0.0.0")
self._refresh()
except Exception as e:
hou.ui.displayMessage(
f"Toggle failed: {e}",
severity=hou.severityType.Error,
title="Hermes Bridge")
def _on_port_changed(self, value):
was_open = bridge_is_open(self.port)
if was_open:
hou.hscript(f"closeport {self.port}")
self.port = value
self.cfg["port"] = value
save_config(self.cfg)
if was_open:
hou.hscript(f"openport {self.port} 0.0.0.0")
self._refresh()
def _copy(self):
QtWidgets.QApplication.clipboard().setText(
self.prompt_field.text())
self.btn_copy.setText("\u2713 Copied!")
QtCore.QTimer.singleShot(2000,
lambda: self.btn_copy.setText("Copy"))
# ── Store on hou.session to prevent GC (per SideFX docs) ──
win = HermesBridgeWindow()
hou.session.hermes_bridge_win = win
win.show()
]]></script>
</tool>
</shelfDocument>