Resolve the filament list's printer model from the agent-reported model id or the selected printer profile, so the AMS Materials Setting dropdown and its Confirm button work on OrcaSonar. Third-party trays pre-fill and allow editing their temps, and the popup's initial selection no longer overwrites them.
Render explicitly-empty trays as "Empty" instead of "?" using a wire-derived DevAmsTray::is_empty, while keeping the slot editable.
Hide the flow-dynamics K/N controls for non-Bambu agents — tile, dialog, validation and sends — with Bambu behavior unchanged.
Tests cover model resolution, the K/N gate and empty-tray classification.
Drop the index-correlation gate so mapped prints are gated only on the filament_mapping capability
Resolve AMS trays set on the printer UI to the matching Generic preset using their material type and color
Skip the forced external-spool selection on printers without an AMS and add coverage for the mapping gates
Parse connector-scope capabilities from get_capabilities: supported_features, capabilities.protocol.features and supported_commands are read for orca devices even when the reply has no flags block, reset before each parse, and left untouched for Bambu.
Gate macro-backed AMS commands on fms plus the advertised command (change filament, select tray, control, user settings, RFID, drying stop); gate slot metadata writes on filament_slots. Unknown capability is not support.
Serialize PrintParams::ams_mapping2 into print.gcode_file.filament_mapping with the array position as filament_index and {255,255} dropped. The field is omitted when empty, so an unmapped print stays byte-identical. A mapped print is refused visibly while ORCA_FILAMENT_MAPPING_CORRELATION_VERIFIED is false or the connector did not advertise filament_mapping; the GUI predicate shares the serializer's definition so both gates agree. Refuse a partially mapped print that leaves a used filament without a target.
Return a non-success result from start_local_print_with_record so PrintJob falls back to start_print instead of treating an unsent print as success.
Tests cover serializer keying and omission, GUI/agent gate agreement, payload shape, the with_record result, capability parsing, and the per-command AMS gate.
# Description
This PR introduces lifecycle events to the plugin API.
For all plugin capabilities, you can define a `on_lifecycle_event`
function in the plugin that takes in a event enum and a small payload
for some generic information on the lifecycle event.
The idea is to keep the payload generic and small, and if you want to
get more information, you should invoke other more targeted APIs to get
more information.
For example, lets say you are keeping track of the the `ObjectAdded*`
event hook for model transformation, addition or deletion. The payload
would tell you the name of the model, and you should use a targeted API
such as `orca.host.plater().model()` to get more information on the
model. This is the overall design principle of the API.
Currently the lifecycle events are the following:
```cpp
enum class LifecycleEvent {
// Project (3mf)
NewProject,
ProjectOpened,
ProjectBeforeSave,
ProjectAfterSave,
ProjectClosed,
ProjectDirtyChanged,
// Slicing pipeline
SliceStarted,
SliceGeometryFinished,
GCodeExportStarted,
GCodeExportFinished,
SlicingJobComplete,
// Plate/model editing
ObjectAdded,
ObjectDeleted,
ObjectTransformed,
ObjectChanged,
ObjectRenamed,
PlateCreated,
PlateDeleted,
PlateSelected,
PlateRenamed,
// Preset
PresetSelected,
PresetSaved,
// Printer/device
PrintStateChanged,
DeviceOnlineChanged,
DeviceDiscovered,
DeviceSelected,
DeviceConnected,
DeviceDisconnected,
UploadStarted,
UploadFinished,
// Print/send jobs
PrintJobStarted,
PrintJobFinished,
SendJobStarted,
SendJobFinished,
};
```
This is an initial draft and lifecycle events can be included later on.
[orca_telegram_notifier_plugin_any.py](https://github.com/user-attachments/files/31220973/orca_telegram_notifier_plugin_any.py)
If you're familiar with telegram bots, after you install the telegram
bot, in the config of this plugin, you can enter the Bot ID and the Chat
ID with said bot.
# Screenshots/Recordings/Graphs
<!--
> Please attach relevant screenshots to showcase the UI changes.
> Please attach images that can help explain the changes.
-->
## Tests
<!--
> Please describe the tests that you have conducted to verify the
changes made in this PR.
-->
For this plugin, I am testing it with a telegram bot that sends me a
message on lifecycle event.
<!--
> A guide for users on how to download the artifacts from this PR.
-->
[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
* Answer the Preview's Per-Frame Time Query From a Cached Sum
The G-code preview's cost is linear in the number of toolpath vertices, and on a
tall multi-filament print the wipe tower dominates that count: it emits a roughly
constant 160-180 moves on every layer whatever the object is, measured at 57-61%
of all moves on a three-filament print.
Four places scanned or allocated across the whole vertex array. None of them
needed to.
get_estimated_time_at re-accumulated the estimated time from vertex 0 on every
call, and its caller is the tool marker tooltip, which ImGui re-renders every
frame while the properties panel is unfolded. It now starts from a running sum
kept at each layer's first vertex, built at load in vertex order, and adds only
that layer's vertices: the same additions in the same order, so the float result
is unchanged, at a cost of one float per layer and time mode rather than per
vertex. At the 351k vertices of a 636-layer test print the call scanned the whole
print (238us); it now scans one layer.
update_view_full_range walked from vertex 0 to find where the layer range starts,
on every slider tick. It now starts at the first vertex of that layer. The index
is derived from the vertices rather than from Layers::Item::range, because
Layers::update folds a vertex whose layer_id arrives out of order into whichever
bucket is open, which makes that range the wrong answer in general; the index
costs four bytes per layer, not per vertex.
update_colors_texture allocated one float per vertex of the whole print on every
slider tick. It now reuses a buffer.
render_legend fetched the layer Zs and the per-layer times from inside loops over
the custom G-code items, and built whole vectors only to test them for emptiness.
The times are hoisted, the Zs are built lazily so a print with no colour change
does not pay for them at all, and the emptiness tests use the existing counters.
No rendering behaviour changes.
* Draw the Preview's Toolpath Segments From an Index Buffer
The preview's frame cost is dominated by one call: a single instanced draw of
every visible toolpath segment. On a tall multi-filament print the wipe tower
supplies most of those segments, which is why the preview of a large tower is
slow and why shrinking the layer range speeds it up again.
That draw is not fill bound. Shrinking the model to about a fortieth of its
screen area moved the frame from 419 ms to 401 ms, so the cost is per segment,
not per pixel, and it is paid in the vertex shader: five texelFetch calls plus
several cross/normalize per invocation.
Each segment is a box of eight corners, but it was submitted with
glDrawArraysInstanced over a 24 entry array, so every corner was transformed
once per triangle that touches it and the shader ran 24 times per segment. The
same 24 entries are now an element buffer over the eight distinct corners, which
lets the post-transform cache reuse them and drops the shader to 8 runs per
segment. The triangles, their winding and the vertex_id each corner receives are
unchanged.
Measured over 100 frames on the 636-layer, 351k-vertex three-filament fixture,
the segment draw goes from 381 ms to 322 ms per frame. That is a software
rasterizer, where triangle setup dominates and understates the win; the drop in
shader invocations is the transferable part.
Verified by loading the same project in this build and in a build of the parent
commit and comparing the canvas across three states - the default view, a
rotated camera, and a reduced layer range: pixel identical in all three. The
rotated case matters because the shader picks its corner offsets from the camera
direction. The only pixels that differ anywhere on screen are in the G-code text
panel, which prints a per-process object id that varies between any two runs.
orca.host.ui.create_dock_panel(html, title, width, height, on_message,
on_close, dock) hosts plugin HTML in a pane of the Plater's dock manager,
next to the sidebar, and returns a UiDockPanel handle
(post/show/hide/close/is_open). The arguments follow create_window(). The
panel uses the window.orca bridge of plugin windows, restores its position
and size from the saved window layout, hides with the Plater off the Prepare
and Preview tabs when floating, and is closed with its plugin; plugin panes
are removed in MainFrame::shutdown().
The web view hosting moves out of PluginPage into a shared WebPanel base:
bootstrap page and swap to the plugin HTML, theme, element-default and
bridge scripts, window.orca message parsing, delivery to the page, and live
re-theming, also re-applied on every load after the swap. Pages tabs and
docked panels both derive from it. Pages tabs now re-theme in place on a
theme change instead of being reloaded, and a window.orca call a host does
not support is logged.
What the hosts share no longer lives in one of them: the bootstrap page, the
base URL and the plugin-window bridge move to Widgets/WebHosting, used by
WebDialog and WebPanel alike. The Plater restores plugin panes with a new
saved-layout parser, GUI/AuiPaneLayout, kept in its own small header so
slic3rutils can test it without pulling in the Plater.
The web hosting classes carry no plugin name, so other hosts can reuse them:
PluginWebDialog becomes WebDialog (its bootstrap page moves to
resources/web/dialog/WebDialog), and destroy_for_plugin(),
load_plugin_content() and plugin_defaults_user_script() become
destroy_silently(), load_page_html() and element_defaults_user_script().
Includes a sample plugin (sandboxes/orca_dock_panel_plugin_any.py) and
binding and layout-helper tests in slic3rutils.
Plugin dialog content is injected with SetPage, so on the WebKit backends a
reload (context menu, keyboard shortcut or location.reload()) re-fetches the
SetPage base URL instead of the injected document, and the plugin UI is gone
for good: load_plugin_content() returned early once m_content_loaded was set.
Re-inject the plugin HTML when a main-frame load after the initial swap is
neither that swap nor a page the plugin linked to. m_own_page_load marks the
load our own SetPage caused, and the URL test recognises the reload: the
injected document and the directory a reload re-fetches both report the base
URL, so a load of any other URL is left alone. The test ignores a fragment the
page navigated to, and undoes the escaping the web view applies to what the
resources path holds.
A load reaching the base URL is not enough on its own, because WebKitGTK reports
a navigation that never committed against the document that stayed and then
finishes that document again: a link to a missing file therefore arrives as a
load of the base URL and reads exactly like a reload. So the re-injection also
requires a navigation to the base URL to have committed, which a reload always
does and a failure never does.
A bootstrap page that cannot be loaded is still not recovered from: WebKitGTK
substitutes a stock error page for it, and that load supersedes the swap
whichever way the swap is ordered around it. The file ships, so this is a
broken-install path; nothing here makes it worse than it already was.
No separate MSW path is needed: wxWebViewEdge ignores the SetPage base URL, so
its documents report about:blank and the test never matches there, and WebView2
reloads NavigateToString content from its own history entry anyway.
* Add Ultimaker S5 Profile - WIP
* Add Ultimaker physical printer base
* Ultimaker API WIP for base update.
* Fix undefined reference error.
* Add UMS5 profile, add placeholders for testAuth stuff.
* Fix non-const func definitions, make func names align with style guide.
* Localization stuff? IDK if this does anything or is required.
* Add cover image
* Auth code cleanup, add UI button for auth cred generation, implement various auth checks and tests.
* Fix auth stuff
* Clean up code
* Get upload code sort-of working, fix typo
* Update printer settings and start/end gcodes
* Add makeGriffinCompatible preprocessor script to prevent machine crash, update machine profile.
* Fix buildplate size, fix time missing bug, add S5 buildplate model
* Correct capitalization to UltiMaker
* Fix display bug, fix capitalization bug
* Implement credential generation button and logic
* Fix generate auth creds button, add todos, fix capitalization.
* Actually fix generate auth credentials.
* Fix generate auth creds message.
* Update UM S5 machine limits
* Revert accidental commit.
* Fix postprocessor, clean up code.
* Update presets for multi-extruder printing.
* Add Single Extruder and Fast profiles.
* Code cleanup
* Register and validate the UltiMaker S5 presets
* Show the Generate API Key button only for UltiMaker print hosts
The PR added it to the Physical Printer dialog for every host type, where pressing it runs an ordinary connection test and reports "API Key created". Gate it on the selected host type instead.
* Drop unused lambda captures in the UltiMaker host code
clang promotes -Wunused-lambda-capture to an error under the project's -Werror, so the file failed to build on macOS; the five callbacks do not touch this.
* Fix the Windows build of the UltiMaker print host
---------
Co-authored-by: SoftFever <softfeverever@gmail.com>