15 KiB
Preset Plugin API — High Level Design
Purpose and scope
The preset plugin API lets Python plugins update the presets OrcaSlicer is using, so users do not have to perform every routine setting change by hand. It covers process, printer, and filament presets. All three use one staged update lifecycle; the way a target is selected is specific to the preset type.
The API edits Orca's managed edited preset, not a detached copy or a saved preset behind the UI's back. It does not save to disk. Saving, discarding, and transferring unsaved edits remain under Orca's existing preset workflow.
The API does not create or delete presets or provide off-tab editing. For filament,
choosing a different preset_name as the update target also selects that preset for
the requested slot, following the normal UI flow. A standalone slot-assignment or
preset-fork operation is outside this API's contract.
Preset and slot model
Each preset collection maintains a managed edited copy for its selected preset. Settings tabs apply changes to that copy and own the normal dirty-state, validation, save/discard/transfer, and downstream refresh behavior. Plugin updates must use the same path so that the settings tab, dirty indicators, and effective print configuration observe a consistent state.
Filament selection adds a slot-to-preset mapping. PresetBundle::filaments is the
catalog, while PresetBundle::filament_presets maps each slot to a preset name.
Several slots may refer to the same preset. A filament update edits that shared
preset, so all slots referring to it are affected. Slot numbers exposed by the API
are 1-based, matching the UI.
API shape
Each preset API exposes begin_update(), which selects and binds one target, and
returns a transaction. The transaction stages setting changes using set() and
applies them only on explicit commit().
# Filament is targeted by its 1-based slot. Omitting preset_name keeps the
# preset currently assigned to the slot; providing it selects that preset for
# the slot before staging updates.
tx = orca.host.preset.filament.begin_update(
slot_number=2,
preset_name="Generic PLA"
)
tx.set("filament_flow_ratio", "0.98")
tx.set("filament_retract_length", "0.8")
result = tx.commit()
# Process and printer default to their active presets. A caller can request a
# different preset that is selectable in the corresponding UI dropdown.
tx = orca.host.preset.process.begin_update(
preset_name="0.20mm Standard"
)
tx.set("layer_height", "0.2")
tx.commit()
The example is the API contract, and the Python bridge provides it. begin_update()
is synchronous: it blocks the background Script worker until the target selection and
any unsaved-edit dialog settle, then returns the bound transaction. Orca's UI thread
stays responsive because the Script capability runs on its own worker. set() and
commit() run synchronously on that same worker. The Script worker and its optional
coroutine loop are described in
python-plugin-async-bridge.md.
Target resolution
- Filament:
slot_numberidentifies the slot. Ifpreset_nameis omitted, the transaction targets the preset currently assigned to that slot. If supplied, it requests that preset for the slot and selects it through the normal UI flow; the name must be among the presets offered by that slot's dropdown, with one exception: the preset currently assigned to the slot stays targetable even when the slot's dropdown hides it (for example an incompatible-but-selected preset). Selecting a different preset changes the slot-to-preset mapping. In both cases the transaction edits the shared preset, not an isolated slot override. - Process and printer: omitting
preset_nametargets the currently active preset. Supplyingpreset_namerequests the preset by the name shown in its dropdown. It must be one of the choices the dropdown currently offers, rather than merely any preset present in the collection. This is particularly important for process presets, whose selectable choices can depend on the current context. - A transaction targets exactly one preset. Multi-preset transactions are not supported: applying them would require switching among targets while the application has only one managed edited copy per collection.
Targets are resolved through Orca's normal preset-selection model. The API should not accept raw preset pointers or filesystem paths as target identities. A missing, unavailable, or ambiguous target fails explicitly instead of silently selecting a different preset.
Selection and user interaction
begin_update() resolves its target through shared binding helpers
(bind_preset_target for process/printer, bind_filament_target for filament) and
performs the selection only when the requested target differs from the active target;
otherwise it returns a transaction without switching. The helpers use Orca's normal
preset selection flow, Tab::select_preset(), including updating a filament slot
assignment when requested. If the normal selection flow requires user input, it opens
the relevant existing dialog(s), including unsaved-edit choices such as save, discard,
or transfer. The call remains blocked on the Script worker until those interactions
finish; it returns the bound transaction, or raises on a cancellation, without
applying staged settings. A canceled selection does not change the requested target or
filament slot assignment, but it does not roll back decisions the user already accepted
in an earlier dialog. The helpers then confirm that the requested preset actually
became the edited preset; a successful selection request alone is not sufficient. There
is no separate switch_preset plugin API.
If the requested target is already active, begin_update() returns without a
selection prompt, apart from the UI-thread dispatch needed to bind the transaction.
If a different preset is requested, Orca selects it through the normal UI flow. For
filament, this also updates the requested slot's assignment when preset_name
differs from its current assignment. When the current preset has unsaved changes,
the Script worker stays blocked until the user chooses among Orca's existing options,
such as transfer, discard, or cancel.
The API never suppresses that warning, chooses an option for the user, or switches
away from dirty edits silently. Cancel aborts the operation before any staged target
changes are applied. Process and printer updates follow this rule too when the
plugin requests a different preset_name; they normally return immediately when
the active preset is the target.
Staging and commit semantics
set(key, value) stages a serialized config value and does not mutate the preset.
The transaction is the boundary for a related group of changes, avoiding
intermediate partially-updated states. commit() runs synchronously on the background
Script worker. At commit, the API validates keys and values against the target preset
type and applies the batch through Tab::apply_preset_config_values, the existing tab
and settings-group update path. Existing setting-specific validation, correction, and
callbacks remain authoritative; plugin updates do not create a parallel setter with
different behavior.
A successful commit updates dirty state and downstream configuration/Plater state
once for the batch. The edited preset remains unsaved; persistence continues through
Orca's normal save action. A validation or commit failure restores the edited preset's
config to its pre-batch state and recomputes the state derived from it (compatibility
marks, the preset selector, dependent tabs, the Plater and the printer agent), so no
staged setting is left partially applied. That guarantee is limited to the edited
preset and its config-derived state: an on_change callback is a GUI method, not a pure
function of the config, so the transaction does not reverse a preset or slot selection
made while resolving the target, a dialog decision the user already accepted, an undo
snapshot, a dependent preset's rewritten config, or anything already sent to a device.
Exact all-or-nothing across those external effects is not achievable while the
authoritative GUI callbacks run; the boundary is documented on
Tab::apply_preset_config_values. A filament slot assignment made as part of target
selection is likewise not a staged setting: after the user accepts the selection flow it
may remain changed even if a later commit fails, and the API does not switch back or
reverse the user's selection or any accepted transfer/discard decision for the previous
preset.
Outcomes and consistency
The API distinguishes target resolution failures, invalid setting/value, user cancellation, stale filament-slot mapping, and commit failure. A result identifies the target preset, whether preset selection changed, and (for filament) whether the slot assignment changed and which slots share the edited preset. A canceled selection applies neither the requested slot assignment nor staged target changes; a failed commit applies none of the staged target settings.
Filament slot assignments can change between begin_update() and commit(). The API
detects that and rejects a stale operation instead of silently editing the new
slot assignment. It must not hold raw preset pointers across reloads. Conflicting
duplicate writes to one setting in a transaction are rejected rather than resolved
by incidental call order.
The existing one-setting filament call,
orca.host.preset.filament.update(slot_number, key, value), remains available as a
compatibility shorthand. It shares the staged API's target resolution, selection,
validation, and commit path, and it is synchronous like begin_update(): both block
the background Script worker while any selection dialog settles.
Design principles
- Reduce routine interaction, preserve agency. Clean switches should be seamless; unsaved user work must still receive the normal warning and decision.
- One source of truth. UI and plugins share the managed edited copy and setting update path, including validation and callbacks.
- The UI workflow is authoritative. The API must enter through the same
selection and setting-change workflows as the UI, not reproduce their logic or
write config values directly. This keeps existing and future side effects active:
on_changehandling for mutually exclusive, invalid, or out-of-range settings; compatibility and dependent-preset updates; and printer-agent changes when the selected printer changes. - Explicit target and explicit commit. Plugins know which preset they edit and when staged changes become visible.
- No hidden persistence. Plugin edits remain unsaved until the user saves them through Orca.
- Shared means shared. Editing a filament preset through one slot affects every
slot assigned to it. Choosing another preset for a slot is explicit in
preset_name; slot-specific divergence would require a separate fork operation. - Consistent lifecycle, type-specific targeting. Process, printer, and filament share transaction and error semantics while respecting their existing selection models.
Implementation in the current codebase
The transaction API is implemented. The current plugin surface exposes begin_update(),
PresetTransaction with set()/commit(), and the per-setting update() compatibility
shorthand. For filament, update_filament_slot() resolves the preset already assigned
to a slot, selects it through Tab::select_preset(), and applies one setting through
Tab::apply_preset_config_values(); it does not change the slot assignment.
begin_update() and update() are synchronous blocking calls
(begin_preset_update / begin_filament_update). They marshal the target binding to
the UI thread and block the background Script worker until the selection settles, so
the wait never blocks the UI thread. They are valid only on that worker, never from the
UI thread or a slicing pipeline hook.
Tab::select_preset() is the basis for switching to process, printer, or filament
presets and for invoking the normal unsaved-edit flow. The flow is type-specific: a
process selection can trigger compatibility handling for filaments, and a printer
selection can involve technology changes and dependent preset compatibility. It may
therefore show more than one dialog. Cancellation of a later step does not necessarily
undo decisions the user already accepted in an earlier dialog. Target resolution
preserves those side effects and then verifies the resulting edited target.
Filament preset_name is an additional integration on top of Tab::select_preset():
selecting a different name for a slot also updates the slot-to-preset mapping and its
dependent UI/configuration state, mirroring the filament branch of the Plater's normal
selection path (PresetBundle::set_filament_preset() plus the project/selection refresh,
the dynamic filament list, the conditional support-filament flushing recalc, the slot
combo refresh and its AMS sync badge). A mixed-color virtual slot is not targetable.
Process and printer dropdown choices are constrained by visibility and compatibility,
and compatibility may depend on the active context. Target resolution checks the tab
dropdown's actual items before the selection flow, so a target the user cannot pick
fails without raising that flow's unsaved-edit prompt; a name two entries carry is
rejected as ambiguous. A filament preset_name resolves against the requested slot's
own sidebar selector rather than the tab's, so that slot's compatibility filter is the
one that applies. A name the slot's dropdown does not show is rejected unless it names
that slot's current assignment (a hidden or incompatible-but-selected preset), which
stays targetable.
commit() applies its batch through Tab::apply_preset_config_values, which parses and
validates the whole batch before touching the edited preset and restores the edited
preset and its config-derived state on failure (see "Staging and commit semantics" for
the limit of that rollback). Since begin_update() performs target selection before the
caller stages values, invalid staged values can still follow a user-visible selection
prompt; that is a consequence of this API shape and remains explicit in its behavior.
Integration boundary
The plugin API coordinates target resolution, preset selection, staged values, and
the settings commit path. It does not own or duplicate preset dirty-state logic,
selection prompts, setting validation, or persistence. Target changes flow through
the same UI selection path so dependent behavior—such as selecting the printer
agent—still runs. Setting changes flow through the same UI change path so on_change
and related validation/correction behavior still runs. The integration bridges UI
prompts to the Python operation through the blocking begin_update() and reuses
the normal Plater slot-selection behavior for explicit filament targets, and commits a
batch without losing existing per-setting behavior.