The test that checks the guard yields to a lock held elsewhere forked a
child to hold it, so it was left out on Windows. The OS lock belongs to
the handle on Windows and to the open file description elsewhere, so a
second handle in the same process is refused like another instance
would be. The test now holds the lock that way and runs everywhere.
Every running instance shares one OrcaSlicer.conf and one user preset
tree, and nothing kept their writers apart. Two instances saving at the
same moment, or the cloud preset sync thread writing while the GUI thread
saved, could interleave, and a reader in another instance could open a
preset JSON or .info file between truncate and close and get a partial
file, dropping that preset for the session with a parse error.
Add InstanceLock, a scoped guard that serialises the threads of one
process through a recursive mutex and other processes through an advisory
OS file lock: flock on POSIX, held on the guard's own descriptor so no
other close in the process can drop it, and LockFileEx on Windows. The
outermost guard opens the lock file and closes it on release, so nothing
stays open between saves and a data dir can be removed once nothing is
saving into it; the file itself is kept, since deleting it would let a
third instance lock a fresh file while the second still holds the old
one. It is best effort: when the lock file cannot be opened or locked, or
another instance still holds it after a second, the guard logs once and
lets the write proceed, then leaves the file alone for ten seconds, so a
hung instance never blocks every other one and a holder stuck in a
debugger does not cost a stall per save. The guard sits at the leaf
readers and writers: set_sync_info_and_save() calls save_info() under the
preset collection mutex, so a batch lock around save_user_presets() would
invert the order against the sync thread. The user preset scan reads its
files on worker threads without the guard, since the mutex would
serialise them, and takes it per file in the serial commit step, so a
save never waits for the whole scan. Each read keeps the bytes of the
preset and its .info as they were before parsing; commit compares them
with the disk under the guard and reads a file that changed again, so it
never deletes or writes back over another instance's newer save, nor
installs a .json and .info from two different saves; a preset another
instance removed in the meantime is not installed. Without the guard, in
a cool-down, the scan still loads the presets but leaves their files
alone: an unreadable file stays for the next scan, and a derived
compatible printer is not written back. Read-only scans, which is what
the CLI does, take no lock and create no lock file.
AppConfig holds OrcaSlicer.conf.lock in load() and save(); load is
included because the Windows path restores from the .bak copy. Every
user preset writer and reader holds user.lock: Preset::save(), which
writes no .info when the preset itself could not be written, since an
.info without its preset reads as a cloud deletion request, save_info(),
reload() and remove_files(), each preset the scan commits, the
bundle metadata reads and write, the .info removal after a cloud-confirmed
delete, the orphaned-.info scan on the sync thread, the bundle folder
removal on unsubscribe and the physical printer writers and delete
paths. A bundle import extracts under cache/ into a folder per process
and per import, where no scan reads.
Preset JSON, .info, bundle metadata, physical printer and config files,
and the caches and state files that already used a temporary by hand,
now go through write_file_atomically(), which writes <file>.<pid>.<n>.tmp
beside the target and renames it over, so a reader that never waits sees
a complete old or new file. A symlink is followed; a target that is not
a regular file is written in place; and when no temporary can be created
beside an existing target, or the rename itself is refused, by a Windows
reader holding the file open or a mount that cannot replace in one step,
the helper writes in place as before, since losing the save is worse
than a torn read. On POSIX the rename replaces the
target atomically where the old code removed it first and left a window
with no file at all; only a mount that refuses a one-step replace gets
the old remove-then-rename. A crash between temporary and rename leaves
the temporary behind, which no scan reads. Preset::save() returns
whether it wrote the preset, so the scan counts a compatible printer it
could not write back as an error. A failed config write keeps
the config dirty, and the idle handler waits ten seconds before retrying
while an explicit save always tries.
The misplaced-key test put its bad key only in the inherited base. A second
one in the included template checks that an include diff cannot carry a key
the collection default lacks into the presets that include it.
* Validate OBJ Texture-Coordinate Indices
load_obj read the texture coordinates of a face without checking the
vt index, so a face referencing a vt past the end of the list read out
of bounds and crashed, and a face vertex with no vt read index -1.
Out-of-range or missing indices now fall back to a zero UV. The face
keeps its entry in the per-face UV list, so the following faces stay
aligned, and the geometry loads as before.
Negative (relative) vt indices were also rebased by dividing the float
count by 3, but each vt stores two floats.
* Reject DRC Meshes Without Positions or with Invalid Face Indices
load_drc dereferenced the POSITION attribute without checking that the
mesh has one, and trusted the decoded face indices, which the Draco
decoder does not check against the point count. Both now fail the load
cleanly. A failed vertex conversion is treated the same way.
The libslic3r tests link Draco so they can encode the malformed meshes
in-test.
* Keep OBJ Texture Coordinates That Carry a W Component
The vt parser stopped reading the optional third component when texture
coordinates were cut down to u and v, but the check that nothing is left
on the line stayed. A legal "vt u v w" line was therefore rejected and
silently dropped, shifting every later texture index. The w component is
parsed again and discarded.
The texture coordinate stride is now a named constant, OBJ_TEXCOORD_LENGTH,
used by the parser and the importer, so the relative-index rebase cannot
drift from the storage layout again.
# Description
The Ender-3 V3 SE machine start G-code performs its purge using
absolute-style extrusion positions:
```gcode
G1 ... E15
G1 ... E30
```
However, the machine start G-code does not explicitly initialize the
positioning or extrusion mode before these commands.
OrcaSlicer emits the printer's custom `machine_start_gcode` before its
own generated `G90` / `M82` or `M83` preamble. This means the purge can
inherit the extrusion mode left active by the printer.
For example, if `M83` relative extrusion is still active, such as after
a cancelled print where normal end G-code was not executed:
- `E15` extrudes 15 mm
- `E30` extrudes another 30 mm
Instead of the intended 15 mm followed by another 15 mm.
This change explicitly adds:
```gcode
G90 ;Absolute positioning
M82 ;Absolute extrusion mode
```
before the purge sequence so startup behaviour is deterministic and does
not depend on inherited printer state.
The change is applied consistently to all Ender-3 V3 SE nozzle variants:
- 0.2 mm
- 0.4 mm
- 0.6 mm
- 0.8 mm
No print speeds, temperatures, retraction values, machine limits, or
other profile settings are changed.
# Screenshots/Recordings/Graphs
Not applicable. This is a machine start G-code profile fix.
## Tests
- Confirmed all four Ender-3 V3 SE profiles use the same `E15` / `E30`
purge sequence.
- Confirmed the current profiles do not explicitly issue `G90`, `M82`,
or `M83` before that purge.
- Confirmed OrcaSlicer emits `machine_start_gcode` before its generated
positioning/extrusion-mode preamble.
- Verified the modified JSON for all four machine profiles parses
successfully.
- Verified the added commands make the purge explicitly use absolute XYZ
and absolute extrusion state.
# Description
Follow-up to #15950, where a `filename_format` using
`initial_no_support_extruder` shipped broken because the profile
validator's slice sweep never expands `filename_format`. The sweep now
expands every custom G-code and `filename_format` text shipped in any
system profile. Each printer's slice also fires the pause, template
custom G-code and clumping-detection hooks and names the output file,
and the first printer shipping a `printing_by_object_gcode` also slices
by object. Beyond each printer's default process and filament, every
compatible system process and filament carrying a template text no
earlier slice has expanded is sliced once, which takes the sweep from
1,110 to 1,248 slices (about 49 s locally, up from 43 s). While the
sweep runs, the placeholder parser also resolves variable names inside
`{if}` branches a slice does not take, so one expansion checks every
branch.
The stricter sweep found two profile bugs, fixed here: the Anycubic
Kobra X filament change G-code carried an unreachable block reading
variables only Anycubic's own slicer defines, and the Wanhao France D12
template custom G-code had an unterminated `{if}`, so adding a template
custom G-code on those printers failed the slice. It also fixes a parser
bug where a declaration such as `{local a = layer_height + 1}` failed to
parse inside a branch that is not taken.
No change to slicing output for templates that already worked: the
untaken-branch check is enabled only by the validator's slice mode, and
the parser fix only lets previously rejected templates parse.
# Screenshots/Recordings/Graphs
<!--
> Please attach relevant screenshots to showcase the UI changes.
> Please attach images that can help explain the changes.
-->
## Tests
New placeholder-parser cases cover the parse fix and the untaken-branch
check: off by default it changes nothing; on, it rejects undefined names
in branches not taken, accepts names declared there, and leaves boolean
expressions (compatibility conditions) alone. The full sweep passes on
all system profiles, and planting an undefined variable in an untaken
branch of a printer's start G-code, of a non-default filament's start
G-code or of a non-default process's `filename_format`, or reverting
#15950, each fails it and names the preset. `scripts/check_profile.sh`,
`libslic3r_tests` and `fff_print_tests` pass.
<!--
> 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)
The 3MF importers read XML entries into a single expat buffer whose size is
an int, while the archive extraction used the entry's 64-bit declared size.
The two could disagree for entries declaring more than INT_MAX bytes.
Reject such entries before allocating, and use one size for the buffer, the
extraction and the parse. This applies to the BBS importer, the PrusaSlicer
importer and the PrusaSlicer fingerprint probe. The load now fails with an
error instead.
* Reject 3MF Plate IDs Below 1 Instead of Indexing Before the Plate List
The plate importer copied each plater_id from model_settings.config into the
1-based plate list after checking only the upper bound, so plater_id="0"
wrote to plate_data_list[-1] and crashed on load. Both copy sites now reject
ids below 1 with the same "invalid plate index" error already used for ids
past the end.
* Drop Malformed 3MF Paint Data Instead of Reading Past the Bitstream
Painted facets are decoded from a bitstream a nibble at a time with no bound
check, so a truncated or corrupt paint string in a 3MF (for example split
codes with no children behind them) read past the end and crashed on load and
slice. A one- or two-side split naming side 3 also indexed past the triangle's
vertices.
Every nibble read now goes through a bounds-checked reader. Loading validates
each triangle's tree and drops a malformed one with a warning, so the stored
data, used extruder states and later decoding all agree. deserialize() also
unwinds and clears any triangle whose tree is incomplete or malformed, and
has_facets() stops at a truncated triangle. Valid streams decode unchanged.
* 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.
* Non-crossing infill optimization
* test triangles
* test grid
* cleaning
* Align and clip rectilinear infill paths
Generate infill coverage in the pattern's local frame, rotate triangular patterns by layer, and clip centerlines to the surface vicinity. Start closed outlines outside the surface so clipping splits them cleanly.
* Update test_fill.cpp
* Update multiline-infill.md
---------
Co-authored-by: Ian Bassi <ian.bassi@outlook.com>
# 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)
The block ran only when flush_length_4 is -1392 and read ace_t_box_vector / ace_t_slot_vector, which only Anycubic's own slicer defines. It emitted comments only, so the printed G-code is unchanged.
Preset::save throws a runtime_error only when it cannot create the preset's
folder; a failed write is silent, and invalid UTF-8 throws a type_error that
is not caught here. The comment now claims only the case the catch handles.
# Description
<!--
> 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?
-->
This PR re-orders the OFL OTA auto-publish workflow by using successful
cron runs as checkpoints, marking and waiting for every dispatched
profile publisher to finish, and clearing the pending queue once
centrally only after all publishers succeed.
# 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.
-->
<!--
> 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)
Merged by /bot merge on behalf of @peachismomo (id 52488812).
Grants: resources/profiles/OrcaFilamentLibrary/filament/Elegoo, resources/profiles/OrcaFilamentLibrary.json, resources/profiles/Elegoo, resources/profiles/Elegoo.json
Head: d4a18c633e
* Add missing TopTools includes to GeometryEngine.cpp
* Add missing TDF_LabelSequence include in STEP.cpp
---------
Co-authored-by: yw4z <ywsyildiz@gmail.com>
# Description
<!--
> 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?
-->
This PR fixes some problems with the current profiles for Blocks
printers
- **Fix**: start gcode for the Blocks 0.6mm RF50 printer where the start
gcode command had no newline separation on two commands so they were
concatenated and would make printing fail.
- **Added**: multi material plates selection for the RF50 printers,
right now only textured PEI and smooth high temp plates are available,
but in the future more to come.
- **Naming consistancy** for Blocks Pro S100 processes and filaments,
target labels used bare `@Blocks` while RD50 and RF50 used
`@Blocks_RD50` and `@Blocks_RF50`. Renamed Pro S100 to
`@Blocks_Pro_S100` to match the structure.
- **Filament restructure**: replaced Blocks tuned-generic filaments
(e.g. `Generic PLA @Blocks`) with real Blocks-branded filaments. One for
each printer model. Every material keeps the same coverage we already
had.
- **Fix**: `sparse_infill_pattern` was misspelled `sparse_infill_patter`
on 13 `@Blocks_RF50` process presets. Harmless due to inheritances but
fixed non the less.
- **Fix**: `Blocks RD50 V2 0.4 nozzle.json` was missing
`printer_variant`.
- **Normalize** all Blocks profiles to standard formatting (tab
indentation, key order) and bumped `Blocks.json` version.
- **RF50 retraction length** increased on all RF50 machines.
## Tests
- [x] `orca_profile_tool.py check --vendor Blocks` - clean
- [x] `orca_profile_tool.py check` - clean
- [x] `normalize` / `generate-id` / `update-index --dry-run` — fully
settled, no pending changes
- [x] `check_profile.sh --vendor Blocks` → `profile_tool` check — passes
- [ ] `validate_system` / `validate_slice` /
`validate_filament_subtypes` / `validate_custom` —
could not run locally (dev host's glibc 2.36 is older than the nightly
validator binary's
required 2.38); needs CI or a compatible host
- [x] In-app confirmation that the Blocks bundle loads
<!--
> 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)
The segments come in print order, bottom layer first, which seen from above is back to
front: every hidden fragment is shaded before the one that covers it, and on an integrated
GPU that overdraw is most of the frame. Drawing the instances last to first whenever the
camera looks down lets the depth test reject the hidden fragments instead. Side views and
views from below keep the print order, and the shadow-caster pass is unchanged.
# Description
<!--
> 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?
-->
Avoid calling Print::output_filename() when a slice is canceled or
fails, since unresolved filename placeholders can throw before G-code
export completes.
Instead, we should use the model name in ctx.name and the stable model
ID in ctx.id, consistently across slice and G-code export events.
# 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.
-->
<!--
> 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#15885
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.
load_vendors merged each vendor's bundle into the startup bundle in turn,
inserting every preset at its sorted place in a deque, so each insert shifted
every preset after it.
PresetBundle::merge_presets now takes every loaded bundle at once, and each
collection merges its sorted presets with theirs in one pass. A preset name this
bundle or an earlier one of the list already has is still left out and reported
under the vendor that repeats it, in the same order as before.
Installing a vendor's filaments looks up the filament library, so load_vendors
loads the library first. Reading a vendor, from its cache or its JSONs, needs
nothing from the library, yet every other vendor waited for it before starting.
load_vendor_configs_from_json splits into read_vendor and install_vendor_read.
load_vendors reads the other vendors in parallel while the library loads, and
installs each against it as soon as both its read and the library are done. A
cache the library can no longer install is still replaced by a parse of the
vendor's JSONs, now when it installs; a canceled load is not mistaken for such
a cache. Vendors start costliest first, and a cache older than the profile
beside it is ordered as a parse.
load_vendors also names the vendors that failed, the library included, which
keeps what it installed before failing and so stays among the bundle's vendors.
The setup wizard falls back to its own scan on that list rather than on which
vendors the bundle holds.
A vendor keeps a copy of each config its other entries inherit, and the filament
library keeps every one of them for the vendors that inherit from it. The copy
was made in commit, one entry at a time on the calling thread while the workers
waited, and the library alone makes 960 of them.
Whether an entry's config is kept depends only on names known before any entry
resolves, so resolve_vendor_preset makes the copy alongside the rest of its work
and commit moves it in.
When the wizard's profile-data cache is stale, which any vendor's version change
makes it, the printer and filament selection dialogs rebuild it by loading every
shipped vendor, 66 of them here, one after another. Startup loads its vendors in
parallel once the filament library is in.
That step moves out of load_system_presets_from_json into
PresetBundle::load_vendors, which takes each vendor with the directory it is
installed in, and the wizard calls it too. A vendor that fails to load still
sends the wizard to its hand-written JSON scan, as the exception it used to throw
did, now once the others have loaded and with the error logged. merge_presets
goes private, since the wizard no longer merges bundles itself.
Opening either dialog with a stale cache goes from 3.38-3.55 s to 2.33-2.47 s on
a 16-core desktop, and the vendor rebuild inside it from 2.34-2.48 s to
1.27-1.41 s. The catalog it writes is byte-identical to the one main writes, and
a dump of every preset startup loads is unchanged.
A cache load installed the entries others inherit from or include first and
resolved the rest together, while the JSON parse installed each entry straight
after parsing it. The two orders only agree while every preset is listed after
what it depends on. An include listed after the preset naming it was missing for
one and found by the other, and every new kind of reference between presets
would have needed the same care in two places.
Both loads now go through install_vendor, which installs processes, filaments and
printers in listing order. Entries resolve together in runs and commit one at a
time, and a run ends before an entry that inherits or includes one already in
it. No entry resolves against a registration from its own run, so the result is
what installing one at a time gives, in any listing order.
The JSON parse reads every sub-file first, under one numeric locale setter, and
stops at the first that fails. The ones before it are installed before the
failure is raised, as they were one at a time, and the filament library's maps
are published only from a complete load. Each entry's parse errors are logged and
counted when it installs, so the log and a load that fails partway come out as
one-at-a-time loading leaves them.
Every preset already in the collection counts as taken for a new one's name.
resolve_vendor_preset and commit_vendor_preset take a VendorInstall with the
collection's shared state in place of a dozen parameters, const for resolve.
The first launch after an update, which parses every profile, goes from
1510-1533 ms to 806-849 ms on a 16-core desktop, and a cached launch from
342-357 ms to 328-334 ms.