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

scripts/123.py (6.2K)

  1 from pathlib import Path
  2 import json
  3 
  4 import hou
  5 from PySide6 import QtWidgets
  6 from PySide6.QtCore import Qt, QTimer
  7 
  8 
  9 def _houdini_font():
 10     main_window = hou.qt.mainWindow()
 11     if main_window is not None:
 12         return main_window.font()
 13 
 14     app = QtWidgets.QApplication.instance()
 15     return app.font() if app is not None else None
 16 
 17 
 18 def _read_settings():
 19     hc_path = hou.getenv("HC_PATH")
 20     if not hc_path:
 21         return {}
 22     settings_path = Path(hc_path) / "hc_settings.json"
 23     try:
 24         return json.loads(settings_path.read_text())
 25     except Exception:
 26         return {}
 27 
 28 
 29 def _write_settings(data):
 30     hc_path = hou.getenv("HC_PATH")
 31     if not hc_path:
 32         return
 33     settings_path = Path(hc_path) / "hc_settings.json"
 34     settings_path.write_text(json.dumps(data, indent=4) + "\n")
 35 
 36 
 37 # Fallbacks for the settings this script reads. It deliberately does not
 38 # import hc -- this runs before the package is needed, and an exception in a
 39 # startup script costs the whole launch -- so it cannot use HCSettings and its
 40 # DEFAULTS merge. tools/check.py asserts these still agree with hcschema.
 41 _DEFAULTS = {
 42     "show_prompt": True,
 43     "default_autosave_state": True,
 44 }
 45 
 46 
 47 def _startup_setting(key):
 48     value = _read_settings().get("startup", {}).get(key, _DEFAULTS[key])
 49     return bool(value)
 50 
 51 
 52 def _current_desktop_mode():
 53     return _read_settings().get("desktop_mode", "attached")
 54 
 55 
 56 class StartupPrompt(QtWidgets.QDialog):
 57     def __init__(self, last_file, parent=None):
 58         super().__init__(parent or hou.qt.mainWindow())
 59         self._open_last_file = False
 60 
 61         self.setWindowTitle("Startup")
 62         self.setWindowFlags(self.windowFlags() | Qt.Tool)
 63         self.resize(820, 240)
 64 
 65         font = _houdini_font()
 66         if font is not None:
 67             self.setFont(font)
 68 
 69         layout = QtWidgets.QVBoxLayout(self)
 70 
 71         label = QtWidgets.QLabel(f"Open last file?\n{last_file}")
 72         label.setWordWrap(True)
 73         label.setTextInteractionFlags(Qt.TextSelectableByMouse)
 74         label.setFocusPolicy(Qt.NoFocus)
 75         layout.addWidget(label)
 76 
 77         self.autosave_checkbox = QtWidgets.QCheckBox("Enable autosave")
 78         self.autosave_checkbox.setChecked(_startup_setting("default_autosave_state"))
 79         self.autosave_checkbox.setFocusPolicy(Qt.NoFocus)
 80         layout.addWidget(self.autosave_checkbox)
 81 
 82         mode_layout = QtWidgets.QHBoxLayout()
 83         mode_label = QtWidgets.QLabel("Layout mode:")
 84         mode_label.setFocusPolicy(Qt.NoFocus)
 85         self.mode_combo = QtWidgets.QComboBox()
 86         self.mode_combo.addItem("Attached", "attached")
 87         self.mode_combo.addItem("Detached", "detached")
 88         current = _current_desktop_mode()
 89         idx = self.mode_combo.findData(current)
 90         if idx >= 0:
 91             self.mode_combo.setCurrentIndex(idx)
 92         mode_layout.addWidget(mode_label)
 93         mode_layout.addWidget(self.mode_combo)
 94         mode_layout.addStretch()
 95         layout.addLayout(mode_layout)
 96 
 97         buttons = QtWidgets.QDialogButtonBox()
 98         yes_button = buttons.addButton("Yes", QtWidgets.QDialogButtonBox.AcceptRole)
 99         no_button = buttons.addButton("No", QtWidgets.QDialogButtonBox.RejectRole)
100         yes_button.clicked.connect(self._acceptOpen)
101         no_button.clicked.connect(self.reject)
102         layout.addWidget(buttons)
103 
104         QTimer.singleShot(0, self._clearInitialFocus)
105 
106     def _clearInitialFocus(self):
107         widget = QtWidgets.QApplication.focusWidget()
108         if widget is not None and self.isAncestorOf(widget):
109             widget.clearFocus()
110 
111     def _acceptOpen(self):
112         self._open_last_file = True
113         self.accept()
114 
115     @property
116     def open_last_file(self):
117         return self._open_last_file
118 
119     @property
120     def desktop_mode(self):
121         data = self.mode_combo.currentData()
122         return data if data is not None else "attached"
123 
124 
125 # This file runs when Houdini is opened without a .hip file.
126 
127 data_dir = Path(hou.getenv("HOUDINI_USER_PREF_DIR")) / "st_data"
128 data_dir.mkdir(parents=True, exist_ok=True)
129 
130 state_file = data_dir / "state.json"
131 if not state_file.exists():
132     state_file.write_text(json.dumps({"last_file": ""}, indent=4))
133 else:
134     try:
135         loaded_state = json.loads(state_file.read_text())
136     except (OSError, ValueError):
137         loaded_state = {}
138     last_file = loaded_state.get("last_file", "")
139 
140     # A recorded path goes stale whenever the scene is moved, renamed or
141     # deleted, and a never-saved scene reports a phantom $HOME/untitled.hip
142     # that was never written to disk. Offering either one made hou.hipFile.load
143     # raise hou.OperationFailed out of this startup script. Only offer a file
144     # that is really there.
145     if last_file and not Path(last_file).is_file():
146         last_file = ""
147 
148     # Also runs under hython and batch, where there is no hou.qt and
149     # StartupPrompt raises AttributeError before the rest of the package
150     # has loaded. Skip the prompt and leave the session file-less.
151     show_prompt = (last_file and hou.isUIAvailable()
152                    and _startup_setting("show_prompt"))
153 
154     if hou.isUIAvailable() and not show_prompt:
155         # Nothing asked, so nothing is opened -- not asking is the same answer
156         # as clicking No. The autosave preference the dialog would have set is
157         # still applied, or turning the prompt off would quietly take autosave
158         # with it.
159         hou.setPreference(
160             "autoSave", "1" if _startup_setting("default_autosave_state") else "0")
161 
162     if show_prompt:
163         dialog = StartupPrompt(last_file)
164         dialog.exec()
165 
166         hou.setPreference("autoSave", "1" if dialog.autosave_checkbox.isChecked() else "0")
167 
168         settings = _read_settings()
169         settings["desktop_mode"] = dialog.desktop_mode
170         _write_settings(settings)
171 
172         if dialog.open_last_file:
173             # Still guard the load: the file can be unreadable or corrupt even
174             # when it exists, and an exception here aborts the rest of startup.
175             try:
176                 hou.hipFile.load(last_file, suppress_save_prompt=True)
177             except hou.OperationFailed as exc:
178                 hou.ui.displayMessage(
179                     "Could not open last file:\n%s\n\n%s" % (last_file, exc),
180                     severity=hou.severityType.Warning,
181                 )