Skip to main content

License: LGPL v3 PyPI Python Qt Tests

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 over PySide6. Runs standalone or hosted inside DCCs (Maya, Blender, 3ds Max) through a pluggable handler ecosystem, and ships a marking-menu subsystem for radial-menu tool shells.

Why

UITK comes from years of building artist tooling for DCC pipelines, where you don't need one big application — you need dozens of small ones, and each traditionally pays the same Qt tax before doing anything useful: load the .ui, findChild() every widget, .connect() every signal, restore and save state, style it, handle standalone-vs-hosted. None of that code is the tool, and hand-rolling it per tool is how toolkits drift into thirty apps by thirty authors.

UITK's intent is to make the well-behaved version of a tool the cheapest one to build: names do the wiring, persistence and theming are defaults rather than features, and one shared convention makes a fleet of tools feel like a single application. Every convention has an escape hatch (@Signals, handlers, per-widget opt-outs), so growing out of the defaults never means fighting them.

Install

pip install uitk PySide6   # standalone (PySide6 is the supported binding)

Inside a DCC, pip install uitk is enough — the host provides its own Qt binding (uitk pulls in the pure-Python qtpy shim but deliberately no binding).

Live demo

The package ships an interactive example window that exercises the full feature set — option_box plugins, the pythontk logging console, header/footer, themes, and more:

python -m uitk.examples.example

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.

No .connect() calls. No findChild(). No manual state restore.


How it wires up

Convention Example Result
UI file → slot class editor.uiEditorSlots 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_savedef btn_save_init(self, widget) Called once on registration
UI hierarchy menu#file.ui is child of menu.ui Resolvable via sb.get_ui_relatives(ui, upstream=True)
Tags panel#floating.ui Exposed as 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 — declare one or more signals to connect instead of the default. @Signals.blockSignals is a companion decorator that suppresses widget signals while the slot runs (useful for programmatic state changes):

from uitk import Signals

@Signals("released")           # override default (e.g. "clicked") on a button
def btn_confirm(self):
    self.commit()

@Signals("textChanged", "editingFinished")  # connect to multiple signals
def txt_search(self, *args):
    self.filter_results()

@Signals.blockSignals          # run without firing widget signals
def refresh_spinbox(self):
    self.ui.spn_count.setValue(10)

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       # opt this slot into Esc-cancel after 60s (runtime form)
widget.refresh_on_show = True  # call *_init again on every subsequent show
ui.default_slot_timeout = 360  # UI-wide opt-in fallback (not auto-set by marking menu)

# Or declare at the slot site (recommended for static intent):
from uitk.switchboard import Cancelable
@Cancelable(60)
def btn_heavy(self, widget): ...

Cancellation is cooperative — the slot stops at its next checkpoint (sb.progress's update() returning False, or ptk.CancelScope.check()); see SLOTS.md.


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. sb.editors.show("style") opens a live theme editor — tweak any variable at runtime, export/import overrides.

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 tags
  • ui.has_tags("floating") — check
  • ui.edit_tags(add="active", remove="inactive")
  • sb.get_ui_relatives(ui, upstream=True) — ancestors
  • sb.get_ui_relatives(ui, downstream=True) — children
  • sb.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 signalson_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 propertiesui.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 positioningui.show(pos="screen" | "cursor" | QPoint, app_exec=False). app_exec=True starts the Qt event loop and exits the process on close.

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)

Beyond the basics

One line each — the deep docs are linked below:

  • Timeline sequencerSequencerWidget, an NLE-style timeline: tracks, draggable clips and keyframes, markers, shot lanes, range/gap overlays, undo/redo, transport controls, audio scrubbing. Powers mayatk's Shot Sequencer.
  • Compiled UI loading.ui files compile to hash-stamped _ui.py modules (python -m uitk.compile, --check for CI); stale output recompiles automatically, precompile_async() warms a fleet in the background, and compilation uses the running binding's own uic so output stays loadable across mixed PySide versions (a venv's PySide6 vs. Maya's bundled one).
  • Shortcut registry & editor — slot hotkeys, app-global shortcuts (GlobalShortcut), and named commands share one registry; sb.editors.show("shortcut") opens a table editor with in-cell key capture, collision checking, and preset import/export.
  • Named presetsui.presets (PresetManager) snapshots widget values into named presets backed by pythontk's PresetStore; make_preset_combo() returns a ready-wired picker.
  • Scrubbable tablesTableWidget columns can be drag- or wheel-scrubbed like DCC channel-box cells, with per-cell formatters, action columns, and in-cell shortcut/choice-capture delegates.
  • Bridge panels — parameterised script panels that drive external DCC processes: AttributeSpecs render as form widgets, values serialize per target language (Python / Lua / JS, or raw CLI strings), and templates, presets, and logging share one engine.
  • Script consoleScriptOutput, a host-agnostic syntax-highlighted console, plus TextEditLogHandler to pipe Python logging into any text panel.
  • External-app launchingExternalAppHandler registers, installs on demand, and launches external Python tools as subprocesses; sb.editors.show("browser") is a searchable, tag-filtered launcher over every handler entry.

Deeper documentation

Rendered from the GitHub repository:

License

LGPL-3.0-or-later — see COPYING.LESSER.

Release files for uitk 1.3.101

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distribution (wheel)

Table of built distributions (wheels) for uitk 1.3.101
File Interpreter ABI Platform
uitk-1.3.101-py3-none-any.whl Python 3 none any Details

Release files / uitk-1.3.101-py3-none-any.whl

Download URL uitk-1.3.101-py3-none-any.whl
Size 1.1 MB
Tags Python 3
SHA-256 checksum
How to use checksums
521d1729a4519eace447a0998e31ffb326598a03cae1528a64d5a5beb9b3a63a
BLAKE2b-256 checksum
How to use checksums
044a69e67e1dac0f6f53664d0798ffde53f00f6d1bccc80d9b922943a54c2cbd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.16

Release history Release notifications | RSS feed

1.5.0

1 release file

1.4.2

1 release file

1.4.1

1 release file

1.4.0

1 release file

1.3.105

1 release file

1.3.104

1 release file

1.3.103

1 release file

1.3.102

1 release file

This release

1.3.101 This release

1 release file

1.3.99

1 release file

1.3.98

1 release file

1.3.97

1 release file

1.3.96

1 release file

1.3.95

1 release file

1.3.94

1 release file

1.3.93

1 release file

1.3.90

1 release file

1.3.87

1 release file

1.3.84

1 release file

1.3.81

1 release file

1.3.78

1 release file

1.3.76

1 release file

1.3.73

1 release file

1.3.70

1 release file

1.3.67

1 release file

1.3.64

1 release file

1.3.60

1 release file

1.3.57

1 release file

1.3.54

1 release file

1.3.51

1 release file

1.3.48

1 release file

1.3.45

1 release file

1.3.42

1 release file

1.3.39

1 release file

1.3.37

1 release file

1.3.35

1 release file

1.3.32

1 release file

1.3.29

1 release file

1.3.25

1 release file

1.3.22

1 release file

1.3.19

1 release file

1.3.17

1 release file

1.3.15

1 release file

1.3.13

1 release file

1.3.11

1 release file

1.3.8

1 release file

1.3.5

1 release file

1.2.99

1 release file

1.2.98

1 release file

1.2.96

1 release file

1.2.94

1 release file

1.2.90

1 release file

1.2.88

1 release file

1.2.86

1 release file

1.2.83

1 release file

1.2.80

1 release file

1.2.77

1 release file

1.2.76

1 release file

1.2.73

1 release file

1.2.70

1 release file

1.2.67

1 release file

1.2.66

1 release file

1.2.63

1 release file

1.2.60

1 release file

1.2.57

1 release file

1.2.54

1 release file

1.2.51

1 release file

1.2.50

1 release file

1.2.47

1 release file

1.2.46

1 release file

1.2.43

1 release file

1.2.42

1 release file

1.2.40

1 release file

1.2.36

1 release file

1.2.35

1 release file

1.2.31

1 release file

1.2.30

1 release file

1.2.26

1 release file

1.2.24

1 release file

1.2.21

1 release file

1.2.19

1 release file

1.2.16

1 release file

1.2.14

1 release file

1.2.10

1 release file

1.2.6

1 release file

1.2.4

1 release file

1.1.101

1 release file

1.1.96

1 release file

1.1.94

1 release file

1.1.89

1 release file

1.1.86

1 release file

1.1.83

1 release file

1.1.81

1 release file

1.1.77

1 release file

1.1.74

1 release file

1.1.69

1 release file

1.1.66

1 release file

1.1.65

1 release file

1.1.63

1 release file

1.1.59

1 release file

1.1.55

1 release file

1.1.44

1 release file

1.1.43

1 release file

1.1.39

1 release file

1.1.37

1 release file

1.1.36

1 release file

1.1.34

1 release file

1.1.32

1 release file

1.1.31

1 release file

1.1.30

1 release file

1.1.29

1 release file

1.1.26

1 release file

1.1.23

1 release file

1.1.22

1 release file

1.1.20

1 release file

1.1.19

1 release file

1.1.18

1 release file

1.1.17

1 release file

1.1.16

1 release file

1.1.14

1 release file

1.1.12

1 release file

1.1.9

1 release file

1.1.6

1 release file

1.1.4

1 release file

1.1.2

1 release file

1.0.97

1 release file

1.0.94

1 release file

1.0.92

1 release file

1.0.89

1 release file

1.0.88

1 release file

1.0.86

1 release file

1.0.83

1 release file

1.0.82

1 release file

1.0.79

1 release file

1.0.76

1 release file

1.0.73

1 release file

1.0.71

1 release file

1.0.69

1 release file

1.0.66

1 release file

1.0.65

1 release file

1.0.64

1 release file

1.0.63

1 release file

1.0.62

1 release file

1.0.61

1 release file

1.0.60

1 release file

1.0.59

1 release file

1.0.58

1 release file

1.0.57

1 release file

1.0.56

1 release file

1.0.55

1 release file

1.0.54

1 release file

1.0.53

1 release file

1.0.52

1 release file

1.0.51

1 release file

1.0.50

1 release file

1.0.49

1 release file

1.0.48

1 release file

1.0.47

1 release file

1.0.46

1 release file

1.0.45

1 release file

1.0.44

1 release file

1.0.43

1 release file

1.0.42

1 release file

1.0.41

1 release file

1.0.40

1 release file

1.0.39

1 release file

1.0.38

1 release file

1.0.37

1 release file

1.0.36

1 release file

1.0.35

2 release files

1.0.34

2 release files

1.0.33

2 release files

1.0.32

2 release files

1.0.29

2 release files

1.0.28

2 release files

1.0.27

2 release files

1.0.26

2 release files

1.0.22

2 release files

1.0.19

2 release files

1.0.18

2 release files

1.0.17

2 release files

1.0.15

2 release files

1.0.14

2 release files

1.0.13

2 release files

1.0.11

2 release files

1.0.10

2 release files

1.0.9

2 release files

1.0.8

2 release files

1.0.7

2 release files

1.0.6

2 release files

1.0.5

2 release files

1.0.4

2 release files

1.0.3

2 release files

1.0.2

2 release files

1.0.1

2 release files

1.0.0

2 release files

0.9.9

2 release files

0.9.8

2 release files

0.9.7

2 release files

0.9.6

2 release files

0.9.5

2 release files

0.9.4

2 release files

0.9.3

2 release files

0.9.2

2 release files

0.9.1

2 release files

0.9.0

2 release files

0.8.9

2 release files

0.8.8

2 release files

0.8.7

2 release files

0.8.6

2 release files

0.8.5

2 release files

0.8.4

2 release files

0.8.3

2 release files

0.8.2

2 release files

0.8.1

2 release files

0.8.0

2 release files

0.7.9

2 release files

0.7.8

2 release files

0.7.7

2 release files

0.7.6

2 release files

0.7.5

2 release files

0.7.4

2 release files

0.7.3

2 release files

0.7.2

2 release files

0.7.1

2 release files

0.7.0

2 release files

0.6.8

2 release files

0.6.7

2 release files

0.6.6

2 release files

0.6.5

2 release files

0.6.4

2 release files

0.6.3

2 release files

0.6.2

2 release files

0.6.1

2 release files

0.5.9

2 release files

0.5.7

2 release files

0.5.6

2 release files

0.5.5

2 release files

0.5.4

2 release files

0.5.3

2 release files

0.5.2

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page