* Sanitize Server-Supplied Download File Names
The URL downloader used the file name from the Content-Disposition header
as given, without the cleaning and unused-name search applied to the
URL-derived name.
Reduce the header name to a sanitized base name with the new
sanitize_file_basename helper, which splits on both path separators and
rejects names made only of dots and spaces. Run the result through the
same unused-name search as the URL-derived name, now shared in
find_unused_filename, and fall back to the URL-derived name when nothing
usable remains.
* Sanitize Download Names Before Choosing an Unused One
The unused-name search probed the name as given and sanitized the
result afterwards, so a name whose special characters are replaced
could be mapped onto a file that already exists.
Move the search into libslic3r as find_unused_filename, sanitize first
and probe the name that is actually written. The download marker path
is shared through download_marker_path. Restore the last tried name in
the error reported when no free name is found, and cover the search
with unit tests.
* Keep Downloads on an Unused Name Until They Complete
When the server supplied the name, the download marker stayed under the
URL-derived name, so the adopted name was not reserved against other
downloads. The final rename also replaced any file that took the name
while the download ran.
Move the marker to the adopted name before any data is written, and
check the name again right before the final rename, picking the next
free name if it is taken by then.
* Sanitize the File Name of Model Import Links
The model import took the file name from the link as given and only
avoided an existing file with a substring match on the folder listing.
Reduce the name to a sanitized base name, falling back to untitled.3mf,
choose the name with the shared unused-name search, and check it again
before the final rename.
* Handle Filesystem Errors When Finishing a Model Import Download
Choosing the final name and moving the downloaded project into place
could throw from inside the download callback. Any such error now removes
the temporary file and reports the existing import failure message.
# Description
Extruder variants (Standard, High Flow, extra high flow) now work on any
printer. Any vendor profile can declare them, and a multi-variant
filament picks up the right variant on every printer. In the sidebar,
users can switch the printer variant and set the nozzle volume type of
each extruder on multi-extruder printers. This also fixes a later
filament printing at the first filament's temperature on a P1S or X1C
with a High Flow nozzle. The profile checks now reject variant arrays of
the wrong size and outdated variant strings. Every shipped vendor
profile passes them, and the orca-profiles skill documents the rules.
Slicing output changes only where the wrong variant was used before.
# Screenshots/Recordings/Graphs
<img width="393" height="218" alt="Screenshot 2026-09-28 at 11 28 07 PM"
src="https://github.com/user-attachments/assets/8bee4b0e-c38c-4039-8c11-096ace6fbad0"
/>
<img width="448" height="267" alt="Screenshot 2026-09-28 at 11 28 37 PM"
src="https://github.com/user-attachments/assets/e767cd97-a5d0-4728-b010-c8ea2bc94ca7"
/>
<img width="746" height="603" alt="Screenshot 2026-09-28 at 11 28 54 PM"
src="https://github.com/user-attachments/assets/23c8af3b-c679-46e5-9fd0-2e43df0449b3"
/>
https://github.com/user-attachments/assets/10d75d72-86a3-42e8-8a95-b1627bd58e91
## Tests
<!--
> Please describe the tests that you have conducted to verify the
changes made in this PR.
-->
<!--
> 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)
Fix size_t/%d mismatch in MsgDialog button keys
`m_buttons.size()` is a `size_t`, which does not match the `%d` conversion in
printf-style variadics. Build the key with `std::to_string` instead; exact
for any size_t, no behavior change for realistic counts.
Co-authored-by: yw4z <ywsyildiz@gmail.com>
Guard the smooth-normals preference read when CLI thumbnail generation initializes geometry without a wx application. Use the existing flat-normal path in that case and preserve GUI preferences.
Use DPI-aware 20 DIP vertical scrolling for general-purpose GUI
scroll areas.
Keep list-based views aligned to their item height so one wheel increment follows the visible row rhythm.
This makes Preferences and other dialogs scroll consistently across
Windows DPI settings.
get_dpi_for_window's pre-8.1 fallback and get_font_list_by_enumeration
both called GetDC without a matching ReleaseDC, leaking a GDI handle
each call. get_dpi_for_window runs on every mouse-move over the 3D
viewport, so the leak exhausts the per-process GDI handle limit and
hangs the app within minutes on Windows 7/8.
Co-authored-by: Fernando Marino <f.marino@rheagroup.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
# Description
This brings back the Bambu Lab profile sync with BambuStudio, reverted
from main, along with its bed-model offset fix. The synced printers and
filaments share their start and end G-code and dual-nozzle settings
through template files that Orca did not read, so those printers had no
machine G-code. Orca now loads these templates the same way BambuStudio
does, so every synced preset comes in complete. `profile_include_dump`,
a new developer tool, compares the result with BambuStudio's.
Other vendors' profiles are unaffected. Bambu Lab profiles move to
version 02.08.00.10, so installs that took the earlier sync update too.
<!--
> Please provide a summary of the changes made in this PR. Include
details such as:
> * What issue does this PR address or fix?
> * What new features or enhancements does this PR introduce?
> * Are there any breaking changes or dependencies that need to be
considered?
-->
# Screenshots/Recordings/Graphs
<!--
> Please attach relevant screenshots to showcase the UI changes.
> Please attach images that can help explain the changes.
-->
## Tests
Unit tests cover how template settings combine with inherited and preset
settings, loaded from JSON and from the preset cache. The profile
validator passes on every shipped vendor, and every Bambu Lab preset
gets the same template values in Orca as in BambuStudio.
<!--
> 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)
Since #15811 the settings page is built when its tab is shown, so on a
Home start that opens a project the Quality page is built on screen. Its
flow-compensation-model field is a multiline text view that GTK maps as
it is created and that is hidden, because compensation is off, before
GTK first allocates it. The loading dialog's wxWindowDisabler then
desensitizes the frame, and GTK crashes in
gtk_text_layout_cursors_changed() on that text view.
On GTK the page view is now hidden while a page is built, so a control
that starts hidden is never realized and GTK realizes it when it is
shown. The deferred build is unchanged.
* fix: resolve filament vendor/type across split base presets
WebGuide's filament list dropped presets whose vendor and type came from different ancestors, and looped forever on inherits cycles. Walk the full inherits chain with a path-scoped guard, fill only missing values, and skip presets that still lack vendor or type. Add tests for split-base resolution and validate the real profile tree.
* Merge branch 'main' into feat/filament_dialog_fix
* Fixes unit test
* Merge branch 'main' into feat/filament_dialog_fix
* Merge branch 'main' into feat/filament_dialog_fix
* revert tests/fff_print/test_gcodewriter.cpp changes
Unwrapped, it ran past the edge of the panel and pulled the layout with
it. Counts under a million now read in thousands, so a 119 k budget is
no longer shown as 0.1 M.
They described the implementation, and a few no longer matched the code.
Each now says what the control does and in what units; five controls that
had no tooltip have one. The catalogs need regenerating.
Relief preserved from an earlier bake is counted on top of the budget
instead of eating into it: baking a second area went from ~214 k
triangles to the ~1000 k it was given.
When the resolution needs more triangles than the budget allows, it is
said before the bake under Resolution and again afterwards.
# 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.