Host UI¶
orca.host.ui lets a plugin show host-owned UI: a native message box, a native progress
dialog, a modal HTML dialog, non-modal interactive windows, and a panel docked beside the
3D view.
A plugin must never import its own GUI toolkit (PyQt/wxPython/tkinter): a script plugin
shares the host's UI thread, so a second toolkit's event loop would clash with wxWidgets, and
a gcode / printer-agent plugin runs off the main thread where toolkit calls would crash.
These host calls run on the main thread for you and block the calling code until they return.
Dialogs and Windows¶
# Native message box -> returns "ok" | "cancel" | "yes" | "no"
choice = orca.host.ui.message("Export finished. Open the folder?",
title="My Plugin", buttons="yes_no", icon="question")
# Modal HTML dialog -> returns the orca.submit() payload (dict), or None if dismissed
result = orca.host.ui.show_dialog(html="<h2>Hello</h2> ...", title="Report",
width=820, height=600)
# Non-modal, persistent, interactive window -> returns a UiWindow handle
win = orca.host.ui.create_window(html=PAGE, title="Panel",
on_message=self.on_message, on_close=self.on_close)
win.post({"type": "data", "rows": [...]}) # push a payload to the page
win.is_open() # bool
win.close()
message arguments: buttons is "ok"|"ok_cancel"|"yes_no"|"yes_no_cancel"; icon is
"info"|"warning"|"error"|"question".
Docked Panels¶
create_dock_panel() puts your page in a pane of the same dock area as the sidebar, instead
of a separate window. The user can drag it to another edge, float it, resize it or close it.
Opened again, it comes back where and how big it was when the window layout was last saved,
which happens when a new project is started, not on exit.
# Docked HTML panel -> returns a UiDockPanel handle
panel = orca.host.ui.create_dock_panel(html=PAGE, title="My Panel", width=320, height=480,
on_message=self.on_message, on_close=self.on_close,
dock="right")
panel.post({"type": "data", "rows": [...]}) # push a payload to the page
panel.hide() # keep it open but out of the way
panel.show()
panel.is_open() # True until closed; a hidden panel is still open
panel.close() # fires on_close
dock is "left", "right", "bottom" or "float"; width and height are in DIPs, so
they mean the same size on every display. Floating is unavailable on Wayland, where a panel
asking to float starts docked instead.
The page talks to the plugin exactly as in a window — see Page Messaging
and Theming below — except orca.submit(), which only applies to modal dialogs.
Panel-specific notes:
- The panel belongs to the Prepare and Preview tabs: it is hidden while the user is on Device, Project or Calibration, and comes back with the 3D view.
- Open it from
execute()oron_load(); the host closes a plugin's panels when it is unloaded, reloaded, the app language is switched or the app exits, the same as its windows.on_closeruns only for a close the user or the page asked for, not for those. - A
post()issued immediately aftercreate_dock_panel()can be dropped while the page is still loading. Have the page ask for its first data instead (orca.postMessage(...)on load), which also keeps a reopened panel in sync — and a reloaded one: the user can reload the page from its context menu, and what comes back is a fresh document with your HTML again, so anything the page held in script state is gone. Keep the authoritative state in the plugin. - Running the same capability twice would open a second panel; keep the handle and call
show()on it (orclose()it first) if you want exactly one. - Global shortcuts still reach the app while the user types in a panel — Ctrl/Cmd with R, G, J, N, O, S, F, P, I, Shift+G, Shift+S or Shift+E. Avoid binding those in your page; Ctrl+A/C/V/X/Z are free.
Progress Dialogs¶
Use create_progress_dialog() for host-owned native progress. It returns a ProgressDialog
handle and also works as a context manager, so close() is called on exit. The default style
is PD_APP_MODAL | PD_AUTO_HIDE; add PD_CAN_ABORT if the user should be able to cancel.
maximum defaults to 100 (values <= 0 are treated as 100).
For script plugins, put the dialog inside execute(self) and update it between chunks of
work. Do not create the dialog and then run one long uninterrupted operation such as a single
time.sleep(...) or blocking network call; the dialog only gets useful repaint/cancel
checkpoints when you call update() or pulse().
style = (orca.host.ui.PD_APP_MODAL |
orca.host.ui.PD_AUTO_HIDE |
orca.host.ui.PD_CAN_ABORT |
orca.host.ui.PD_ELAPSED_TIME |
orca.host.ui.PD_REMAINING_TIME)
with orca.host.ui.create_progress_dialog("My Plugin",
"Preparing...",
maximum=len(items),
style=style) as progress:
for index, item in enumerate(items, start=1):
process(item)
# update() returns False if the dialog was closed or cancelled.
if not progress.update(index, f"Processed {index}/{len(items)}"):
return orca.ExecutionResult.skipped("Cancelled by user")
For indeterminate work, pulse the dialog instead of setting a numeric value:
with orca.host.ui.create_progress_dialog("My Plugin",
"Waiting for printer...",
style=orca.host.ui.PD_APP_MODAL |
orca.host.ui.PD_CAN_ABORT) as progress:
while not finished():
if not progress.pulse("Waiting for printer..."):
return orca.ExecutionResult.skipped("Cancelled by user")
wait_for_next_poll()
Handle methods:
| Python call | Effect |
|---|---|
progress.update(value, message="") |
set the determinate progress value; returns False if closed/cancelled |
progress.pulse(message="") |
advance an indeterminate progress step; returns False if closed/cancelled |
progress.start_pulse(interval_ms=100, message="") |
start timer-driven pulsing on the UI thread |
progress.stop_pulse() |
stop timer-driven pulsing |
progress.close() |
close the dialog |
progress.is_open() |
return whether the host still has the dialog registered |
Because start_pulse() has no return value, use explicit update() or pulse() calls at
natural cancellation points if the dialog includes PD_CAN_ABORT.
The style constants exposed by orca.host.ui mirror wxProgressDialog: PD_APP_MODAL,
PD_AUTO_HIDE, PD_CAN_ABORT, PD_CAN_SKIP, PD_ELAPSED_TIME, PD_ESTIMATED_TIME, and
PD_REMAINING_TIME. PD_CAN_SKIP is available for style parity, but the current Python
handle does not expose a separate "skip" state.
Page Messaging¶
The page talks back through window.orca, which is injected automatically. The page supplies
raw, self-contained HTML/CSS/JS.
| JS call | Effect |
|---|---|
orca.postMessage(obj) |
deliver obj to the plugin's on_message(obj) |
orca.onMessage(cb) |
cb(data) runs for each win.post(data) (and modal pushes) |
orca.submit(obj) |
(modal) close and return obj from show_dialog |
orca.close() |
close the dialog / window |
Theming¶
The host injects a stylesheet that matches OrcaSlicer's current theme (the active
light/dark mode, fonts, background/foreground, accent and border colors) before your page
renders. An unstyled page already looks native: <body>, headings, button,
input / select / textarea, table, links and scrollbars get sensible themed defaults,
and the theme is also exposed as CSS variables so you can match the rest of the UI:
| Variable | Meaning |
|---|---|
--orca-bg |
window/background color |
--orca-fg |
primary text color |
--orca-muted |
secondary / label text color |
--orca-accent |
accent color (buttons, links, focus) |
--orca-accent-fg |
text color on the accent |
--orca-border |
subtle border / separator / row-hover color |
--orca-font |
UI font stack |
The injected rules use only low specificity and never !important, so any CSS your page
ships overrides them. Prefer the variables (for example,
border:1px solid var(--orca-border)) over hardcoded colors so your dialog follows light
and dark mode automatically. The UI sample (host_ui_panel.py) relies on this and uses no
fixed colors.
Threading and Lifecycle¶
- Host UI calls run on the main thread and block the calling code until they return
(
message/show_dialogwhen the dialog closes;create_window/create_progress_dialogas soon as the window/dialog is shown; progress updates after the host applies them). From ascriptplugin, already on the UI thread, they run inline; from a background-thread plugin (gcode/printer-agent) they marshal to the main thread first. on_message(data)runs on the UI thread; keep it quick; offload heavy work to athreading.Threadand push results back withwin.post(...).- A modal dialog (
show_dialog) fits a one-shotexecute(). A persistent panel (create_window) is best opened fromon_load()so it lives for the plugin's lifetime; the host closes a plugin's windows automatically when it is unloaded/reloaded or the app exits. - Content is loaded as raw HTML; prefer self-contained pages (inline CSS/JS). There is no CSP and developer tools are disabled.