A comprehensive UI toolkit extending Qt Designer workflows with dynamic loading, custom widgets, and automatic signal-slot management.
Project description
uitk
Name it, and it connects. UITK is a convention-driven Qt framework that eliminates boilerplate. Design in Qt Designer, name your widgets, write matching Python methods — UITK discovers the files, auto-wires signals, persists state, and applies themes. Every convention is overridable when you need control.
Built on qtpy (PySide2 / PySide6). Runs standalone or hosted inside Maya, Blender, and 3ds Max via a pluggable handler ecosystem.
Install
pip install uitk
Quickstart
from uitk import Switchboard
class EditorSlots:
def __init__(self, **kwargs):
self.sb = kwargs["switchboard"]
self.ui = self.sb.loaded_ui.editor
def btn_save_init(self, widget): # runs once when btn_save registers
widget.setText("Save")
def btn_save(self): # runs on clicked (QPushButton default signal)
self.sb.message_box("Saved")
sb = Switchboard(ui_source="editor.ui", slot_source=EditorSlots)
sb.loaded_ui.editor.show(pos="screen", app_exec=True)
Widget btn_save in editor.ui is connected to EditorSlots.btn_save because the names match.
How it wires up
| Convention | Example | Result |
|---|---|---|
| UI file → slot class | editor.ui → EditorSlots |
Class discovered, instantiated with switchboard= kwarg |
| Widget → slot method | btn_save (objectName) → def btn_save(self) |
Widget's default signal connected |
| Widget → init hook | btn_save → def btn_save_init(self, widget) |
Called once on registration |
| UI hierarchy | menu#file.ui is child of menu.ui |
sb.get_ui_relatives(ui, upstream=True) |
| Tags | panel#floating.ui |
ui.tags == {"floating"} |
Default signals by base Qt type:
| Widget | Signal | Callback arg |
|---|---|---|
| QPushButton | clicked |
— |
| QCheckBox | toggled |
checked: bool |
| QRadioButton | toggled |
checked: bool |
| QComboBox | currentIndexChanged |
index: int |
| QLineEdit | textChanged |
text: str |
| QTextEdit | textChanged |
— |
| QSpinBox / QDoubleSpinBox | valueChanged |
value |
| QSlider / QDial / QScrollBar | valueChanged |
value: int |
| QListWidget / QTreeWidget | itemClicked |
item[, column] |
| QTableWidget | cellChanged |
row, column |
| QTabWidget / QStackedWidget / QToolBox | currentChanged |
index: int |
Override with @Signals:
from uitk import Signals
@Signals("textChanged") # QLineEdit default is textChanged already,
def txt_search(self, text): # but e.g. override "released" on a button
self.filter_results(text)
@Signals() # Empty - no auto-connection (you wire manually)
def manual_widget(self): ...
Parameter injection — slots can request widget by name; UITK introspects the signature:
def btn_save(self): ... # no params
def btn_save(self, widget): ... # widget injected
def cmb_font(self, index): ... # signal arg only
def cmb_font(self, index, widget): ... # signal arg + widget
Widget enhancements
Every registered widget gains these lazy-initialized properties.
.menu — dynamic popup menu
def btn_options_init(self, widget):
widget.menu.add("QCheckBox", setText="Auto-save", setObjectName="chk_auto")
widget.menu.add("QSpinBox", setPrefix="Interval: ", setObjectName="spn_int")
widget.menu.add("QSeparator")
widget.menu.add("QPushButton", setText="Apply", setObjectName="btn_apply")
def btn_options(self):
auto = self.ui.btn_options.menu.chk_auto.isChecked()
interval = self.ui.btn_options.menu.spn_int.value()
menu.add() accepts a widget class string, a list of strings (shorthand for multiple items), a dict (text → data), or another widget instance. Added widgets are accessible by objectName on the menu.
.option_box — action panel attached to input widgets
def txt_path_init(self, widget):
widget.option_box.menu.add("QPushButton", setText="Browse...", setObjectName="btn_browse")
widget.option_box.menu.btn_browse.clicked.connect(self.browse)
Pluggable option system: ClearOption, BrowseOption, PinValuesOption, ActionOption, MenuOption, ContextMenuOption, RecentValuesOption. See WIDGETS.md.
State persistence
Widget values save on change, restore on show:
# User sets spinbox to 5, closes app. Next launch: spinbox is 5 again.
widget.restore_state = False # per-widget opt-out
ui.restore_widget_states = False # per-UI opt-out
ui.restore_window_size = False # skip window geometry
widget.block_signals_on_restore = True # restore without firing slot
Window geometry also persists automatically, debounced to 500ms on resize/move.
Slot-level controls
widget.debounce = 300 # coalesce rapid signals into one slot call after 300ms
widget.slot_timeout = 60 # warn if slot takes > 60s; allow Esc to cancel
widget.refresh_on_show = True # call *_init again on every subsequent show
ui.default_slot_timeout = 360 # fallback timeout for all slots in this UI
Theming
ui.style.set(theme="dark", style_class="translucentBgWithBorder")
Themes (light, dark) are palette dicts — WIDGET_BACKGROUND, BUTTON_HOVER, BORDER_COLOR, etc. QSS variables are substituted at apply time. Monochrome SVG icons in uitk/icons/ are auto-colored to match ICON_COLOR.
Hierarchy & tags
UI filenames with # encode hierarchy and metadata:
menu.ui # base
menu#file.ui # child of menu (tag "file")
menu#file#recent.ui # grandchild (tags "file", "recent")
panel#floating.ui # base "panel" with tag "floating"
ui.tags— set of tagsui.has_tags("floating")— checkui.edit_tags(add="active", remove="inactive")sb.get_ui_relatives(ui, upstream=True)— ancestorssb.get_ui_relatives(ui, downstream=True)— childrensb.get_ui_relatives(ui, exact=True)— siblings
Cross-UI widget value sync: when a widget's value changes, MainWindow.on_child_changed syncs that widget's value to same-named widgets in related UIs via get_ui_relatives.
MainWindow
Every UI is wrapped in a MainWindow instance.
Lifecycle signals — on_show, on_first_show, on_hide, on_close, on_focus_in, on_focus_out, on_child_registered(widget), on_child_changed(widget, value), on_pinned_changed(bool).
Key properties — ui.sb, ui.widgets (set), ui.slots (slot instance), ui.settings (SettingsManager branch), ui.state (StateManager), ui.style (StyleSheet), ui.tags (set), ui.path (str), ui.is_initialized, ui.is_current_ui, ui.is_pinned, ui.header, ui.footer, ui.presets.
Show positioning — ui.show(pos="screen" | "cursor" | QPoint | (x, y), app_exec=False).
Handler ecosystem
Extend UITK without editing it. Handlers are classes with a DEFAULTS dict that register under sb.handlers.<name>:
sb = Switchboard(
ui_source="...",
handlers={"ui": MyCustomUiHandler}, # replaces the default UiHandler
)
sb.handlers.ui.apply_styles(ui)
sb.handlers.ui.show(ui, pos="cursor")
sb.configurable.ui.default_position.set("cursor") # handler DEFAULTS merged here
The built-in UiHandler applies default styling and positions windows. MarkingMenu registers itself as sb.handlers.marking_menu. Consumers like tentacle ship subclassed handlers (MayaUiHandler) for DCC-specific behavior.
Consumer patterns
Standalone app
sb = Switchboard(ui_source="./ui", slot_source="./slots")
sb.loaded_ui.main.show(app_exec=True)
Hosted-or-standalone tool (mayatk pattern)
def launch(sb=None):
if sb is None:
sb = Switchboard(ui_source="my_tool.ui", slot_source=MyToolSlots)
ui = sb.loaded_ui.my_tool
ui.show(pos="screen")
else:
ui = sb.handlers.marking_menu.show("my_tool")
return ui
Marking-menu DCC shell (tentacle pattern)
from uitk import MarkingMenu
class TclMaya(MarkingMenu):
def __init__(self, parent=None, **kwargs):
bindings = {
"Key_F12": "main#startmenu",
"Key_F12|LeftButton": "cameras#startmenu",
"Key_F12|RightButton": "scene#startmenu",
}
super().__init__(parent, ui_source="ui", slot_source="slots",
bindings=bindings, **kwargs)
Deeper documentation
Rendered from the GitHub repository:
- User Guide — building your first real app
- Slots Contract — naming, signals, parameter injection, debounce, timeout, refresh
- Widgets — per-widget reference with the sequencer and editors subpackages
- Marking Menu — radial menu subsystem
- Architecture — Switchboard mixins, registries, lifecycle
- Cookbook — recipes from real consumers
- Tutorial — step-by-step walkthrough
- API Reference — public signatures
License
LGPL-3.0-or-later — see COPYING.LESSER.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file uitk-1.1.34-py3-none-any.whl.
File metadata
- Download URL: uitk-1.1.34-py3-none-any.whl
- Upload date:
- Size: 450.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2563ee3b2c4abdb48bd466eee78be6c74f6bb18206131e99b69ca8bb91033701
|
|
| MD5 |
39435513041de791ee3add784dba1cea
|
|
| BLAKE2b-256 |
d8740cef97750626cfcc77b0a2948d6ff57734f2e8e605d5521ac8ad1a8181c2
|