The startup probes for download_path and last_backup_path used the throwing
boost::filesystem overloads, so a path that could not be probed at all, such as
one whose parent is not searchable, raised filesystem_error instead of falling
back. init_download_path() runs from the GUI_App constructor inside GUI_Run()'s
try, which left the application showing a message box and exiting with no window
and no log file, the throw preceding the log sink. has_restore_data() runs from
the queued EVT_RESTORE_PROJECT handler, whose try starts after the call.
Five probes in those three functions take the error_code overload instead.
Nothing branches on ec, and readable or missing paths are unaffected because both
overloads return the same value for them. A new test covers has_restore_data()
against a backup directory whose parent has no search permission, and skips on
Windows, as root, and where the mode change is refused or not enforced.
Orca sent `X-Api-Key: 88888888` to Moonraker whenever the printer preset's API Key was empty, which Moonraker rejects with HTTP 401 since Arksine/moonraker@93f5d9c. The preset's API Key is now the only thing that sets the header, and an empty key sends none.
Logging out of a printer now marks it revoked rather than only clearing its code, since an empty code is a valid Moonraker binding. Discovery events are delivered through the network agent and dropped if their printer agent was replaced while queued.
* Keep User Filament Presets on Printers Their Parent Listed in 2.4.2
User presets store only their diff, so they inherit the parent's
compatible_printers. Profile changes since 2.4.2 renamed and narrowed many
system filament presets, so user presets made against them disappear from
printers they were made for and the saved selection silently falls back to
another filament at startup.
orca_profile_tool.py legacy-compat writes, per current system filament
preset, the printers it covered in an older release and no longer lists,
kept only where a preset with the same filament_id still covers the printer
and they share an extruder variant. The loader gives these printers to user
presets that inherit their parent's list, without writing any file; saving
a preset detached, cloned or with its own list drops them.
* Ask Before Migrating User Filament Presets to Their Legacy Printers
Instead of giving user presets their parent's 2.4.2 printers in memory,
detect the user presets that inherit their parent's compatible_printers
and are missing printers from the legacy map, and offer to migrate them
at startup or from Help > Migrate Filament Presets.
Migrating first backs up the user folder to
filament_preset_migration_<datetime>_backup.zip in the configuration
folder, and migrates nothing if that fails. It then writes the printers
into each preset's compatible_printers, patching only that key so the
preset keeps the parent name it was saved with and older releases can
still resolve it. Cloud-synced presets are marked for upload.
No asks again at the next launch; "Don't remind me again" stops the
startup prompt.
* Key the legacy filament preset map by the parent's 2.4.2 name
* Check the legacy filament preset map in CI
* Limit the 2.4.2 filament preset migration to presets that need it
Only presets that still name their parent as 2.4.2 did and have no printer
list of their own are offered, and a preset changed after it was found is
left alone. The startup prompt appears once, after upgrading from 2.4.2 or
earlier, as a standard dialog that keeps "Don't remind me again"; Help >
Migrate Filament Presets is unchanged. Migrating no longer overwrites an
unsaved printer-list edit or marks the project modified.
* Ask on Every Start and Take the User Presets Lock Once When Migrating Filament Presets
The startup prompt no longer depends on the version the previous run
recorded: whoever still has user filament presets that lost printers is
asked, including users already on a 2.5.0 nightly or alpha. Answering No
asks again at the next start; "Don't remind me again" stops it.
Migration takes the user presets lock once, around the backup and every
file write, instead of once per preset. A lock another instance holds is
reported once ("another OrcaSlicer instance is saving presets") with
nothing written, rather than as one failed preset after another once the
lock's cool-down kicks in. Detection records each preset's file and the
list it will get, so the writes need no collection lock while the user
presets lock is held, the order InstanceLock requires; the loaded presets
are updated afterwards.
* Run the Legacy Compatible Printers Map Check in check_profile.sh and .ps1
CI's "Check profiles" job checks resources/info/legacy_compatible_printers.json
against the v2.4.2 profiles, and its failure message points at these scripts
as the local reproduction, so they run the same check: legacy_compat, right
after profile_tool. The v2.4.2 tree comes from git archive of the tag, is
cached in the work dir like the other downloads, re-extracted with --refresh,
and fetched from upstream first when the checkout lacks the tag.
* Back Up Before the User Presets Lock and Merge Migrated Printers Into the Live List
The lock guard is meant for a few file operations, and zipping a large user
folder is not one, so the backup is taken before it and the guard covers the
writes only; each write re-reads its file, so a save that slipped in before
is still respected.
The in-memory update adds the lost printers to the list the preset holds at
that moment rather than replacing it with the one found at detection, and
leaves a pending cloud sync state alone: a download waiting to be saved kept
its newer list and its "save" flag, which "update" would have cancelled.
The lock message no longer blames another instance alone, since the lock is
also refused for a while after a miss and when the lock file cannot be
opened.
---------
Co-authored-by: SoftFever <softfeverever@gmail.com>
Auto-arrange centered the pile on `best_object_pos`, so on printers with an off-center position (A1 mini, H2C, H2D, H2D Pro, X2D) a large pile could end up partly off the bed. The final alignment now always clamps the pile to the bed, and the `clamp_to_bin` flag is removed. Belt printers already clamped, and centered alignment is unchanged.
The check against fixed parts and excluded regions now tests the position the pile is actually moved to.
Fixes#15727
Print::validate sizes the pre-slice prime tower from Print::extruders(),
which lists every mixed filament slot under its own id, so the estimator
planned one purge block per slot. The tool ordering resolves a mixed slot
to one physical component on every layer, with or without colour
sublayers, and the generated tower only purges those. The GUI preview and
its placement clamp already expand the slots, so the tower was drawn
inside the bed and then refused as partially outside it: with 8 mixed
slots at 0.08 mm layers the estimate was 114 mm for a tower that prints
at 64 mm.
The estimator now expands mixed slots to their components itself, so
validation, the GUI and the CLI placement size the same tower.
* Add orca-translations Claude skill and PO tool
Add a .claude skill that guides translation passes over the localization/i18n gettext catalogs. It covers the workflow: run gettext, settle new glossary terms, translate in batches, verify, and report.
Includes translation-rules.md, which condenses the placeholder, line-break and glossary rules plus the guidance on review notes.
Also adds scripts/po_tool.py (standard library only). It lists pending entries with glossary hints, validates and applies translations, adding '# AI Translated' and '# Needs human review' comments, and rewrites only the entries it changes. It can also check catalogs, look up existing translations, suggest glossary candidates and update the wiki glossary TSV.
* Let po_tool apply translations by entry id
pending now gives each entry a short id, so a translation file can name
entries by id instead of repeating the English msgid. apply accepts either.
A "review": "" item clears an earlier review note when overwriting an
entry, and check gives each msgfmt run its own temporary output file so
parallel checks do not collide.
* Fill all empty translations
Regenerate the template with run_gettext --full and translate every empty
entry in the 22 catalogs (7,089 entries) with the orca-translations skill,
following the localization glossary, including the new belt printer,
IDEX/IQEX and plugin terms. Every new entry is marked "# AI Translated";
122 also carry "# Needs human review" with what to check.
* Show entry ids in po_tool lookup and search translations
lookup now prints each entry's id, so an entry found there can be
rewritten with apply --overwrite without repeating the English, and
--msgstr searches the translations instead of the msgids, which finds
every entry that still uses a term being changed.
SKILL.md describes how to change an established term: check the
catalog's usage first, update the glossary cell, then rewrite only the
entries where the term has that meaning.
* Fix mistranslated terms and align catalogs with the glossary
Correct labels that the glossary had copied, and the entries that used
them:
- ca: Raft was rendered as the Brim term; it is now "Rai". Exhaust fan,
Monotonic line, Unretract, Slice, Lateral Honeycomb and PA Tower
labels fixed.
- cs: Lightning infill is "Blesk", not "Osvětlení" (illumination).
- sv: Retraction is "Retraktion", not "Reduktion"; Unretract,
Lagerhöjd, Delkylningsfläkt, Utsugsfläkt, Aligned Rectilinear and the
Preset label fixed.
- de: Brim is "Rand" and Skirt is "Umrandung", as most entries already
had them.
- lt: brim entries used the Skirt word "apvadas"; they now use "kraštas".
- es: Sparse infill is "relleno de baja densidad", the catalog majority.
- eu: nozzle wipe is "garbiketa", not purge.
- th: the project plate is "เพลต", distinct from the bed.
- uk: Quarter Cubic, Latin "PA" in PA Tower, and Belt purge tower.
- pl: Cross Hatch, Tri-hexagon, Volumetric speed and Raft labels.
- zh_TW: Brim stays "Brim", as the existing labels have it.
- tr: "Prime Tower" read "Prime Minister Tower".
- ko: the plugin "Capabilities" label read "performance".
- Build Volume labels fixed in tr, de, cs and eu.
All 242 rewritten entries are marked "# AI Translated".
* Make more UI text translatable and fix pluralization and context
Audit the sources for text that translators could not translate
correctly and fix it.
Mark text that reached users in English only: IDEX/IQEX role names,
plugin permission prompts and errors, 3MF/preset loading errors, G-code
placeholder parser errors, calibration and device messages, notification
texts, file dialog filters, and the config validation messages.
Replace sentences built from translated fragments, such as the prime
tower warnings, "left"/"right" nozzle names, and "Filament " + number,
with single msgids that have placeholders, so translators can reorder
and inflect them.
Use plural forms for texts with a count ("%1% mixed filaments",
"%d objects", "%1% triangles") instead of "(s)" and mismatched verbs.
Fix the swapped singular/plural pair in "The following presets inherit
this preset".
Add translation contexts where one English word has several meanings
(Subscribed/Local, Standard, Pause, View, Step, Model, Camera, Flow,
Filter, Others, Prepare, the Design tools). Show "Size" instead of
"Build Volume" in the Cut tool, which displays the object size.
Fix code that compared translated text with English (the Edit G-code
dialog group, the plugin registration failure check), and add the
bundled web pages' missing string-table entries. Use the single
character for degrees Celsius, say "G-code", and describe the head
filament map without internal jargon.
Install the libslic3r translate callback before the presets load, so
their load errors are translated, and list the 28 source files that had
marked strings but were never extracted into localization/i18n/list.txt.
* Suggest earlier translations and list fuzzy entries in po_tool pending
pending now attaches the closest earlier translation of a similar
English text (from obsolete or translated entries), so a reworded or
split string keeps its wording, and it lists fuzzy entries by default,
since users never see them. A fuzzy entry also carries its stale
translation as previous_translation.
* Fix wording of the non-boolean operands error
The logical operation error said "operators" where it means the
operands the operation was applied to.
* Translate the newly translatable strings in all languages
Regenerate the template after the translatability changes and fill every
new or changed entry in the 22 catalogs (about 12,800 entries): the
strings that became translatable, the plural forms, the entries split by
context, and the previously fuzzy singular entries that became plurals.
Every new entry is marked "# AI Translated", and about 360 also carry a
"# Needs human review" note saying what to check. Terms follow the
localization glossary. Earlier translations of reworded strings were
reused where the meaning was unchanged. Existing entries were not
changed, and obsolete entries are kept.
* Document the source translatability audit in the orca-translations skill
Add a reference describing how strings reach translators, what to look
for (untranslated text, spliced sentences, missing plurals, ambiguous
words), what not to translate, where a context cannot be carried, and
what to do after the edits, plus a scanner that lists untranslated
user-facing string literals.
* Document how to write translatable strings in AGENTS.md
Tell developers to keep each message complete in one msgid: use
placeholders instead of concatenating translated fragments, plural forms
for counts, and contexts for ambiguous words, and to list new source
files in localization/i18n/list.txt. Add a matching code review item.
* Translate the strings added since the rebase
Regenerate the template against the current main and translate the 26
new entries per language: the Fix Model commands and messages, and the
texture displacement image adjustment panel. Each new entry is marked
"# AI Translated"; existing entries are unchanged.
* tidy
A 3mf saved without a plate thumbnail, such as a CLI export with no OpenGL context or a backup, still listed /Metadata/plate_1.png and plate_1_small.png in _rels/.rels. lib3mf rejects a package whose thumbnail relationship points at a missing part. The writer now records which plate thumbnails it wrote and only declares those.
* Carry existing colour across a bake instead of reindexing it
Baking, painting colour, then baking again left colour debris on surfaces
nothing had painted and destroyed what the earlier bake had laid down.
TextureDisplacementBakeJob::finalize() deserialized the volume's pre-bake
mmu_segmentation_facets into a TriangleSelector built on the post-bake mesh,
on the assumption that the bake is topology-preserving. That holds for the
classic path, which moves vertices and leaves the topology alone, but not for
the one-run pipeline, which rebuilds the mesh. TriangleSelector::deserialize()
only checks that the stored triangle ids are in range, and a bake almost
always grows the triangle count, so every stale id passed that check and
landed on an unrelated face.
Measured on a 26 mm slab baked twice at 0.19 mm with a 750 k budget: 7580
triangles ended up carrying paint neither bake produced, as far as 31 mm from
any colour that was actually laid down, while the first pass's colour was lost
where the second bake left the surface alone.
TriangleSelector::remap_painting() already transfers paint between meshes by
position, and ModelVolume::restore_painting() uses it for the same reason, so
the bake job now uses it too. It runs in process(), on the worker thread: it
builds an AABB tree over both meshes and took around a second on the case
above, which would have frozen the UI in finalize(). The bake's own colour is
still applied last, so it wins where it painted and the rest is kept, which is
what makes successive bakes accumulate rather than overwrite. If the volume is
painted while the bake is running, a timestamp check falls back to the live
mask so that stroke is not dropped.
The out_triangle comment in TextureColorRequest claimed the bake is
topology-preserving, which is what justified the original indexing; it now
says which mesh the colours belong to.
The regression test bakes twice, checks both results are closed, and checks
the first bake's colour survives the second at the positions where it was
laid down and nowhere else.
* Carry the paint across with transfer_facet_paint
The branch used TriangleSelector::remap_painting(), which pairs a source
paint triangle with a target triangle overlapping it to within
TriangleCursor::tolerance. That is right after a remesh, which leaves the
surface where it was, and wrong after a bake, which has moved it by the
relief depth - most of the paint finds no partner. transfer_facet_paint()
asks the other question: every triangle of the new mesh takes the state
painted at its foot point on the old one, which is exactly where it came
from however deep the relief.
It also does all four paint channels rather than colour alone, and it runs
where the volume is read, so the snapshot and the timestamp that guarded
against the user painting while the bake ran are no longer needed: the
annotation is read at commit time and is by definition current.
The function and its call site are byte for byte what the model-UV branch
carries, so the two merge in any order and in either order produce one
mechanism rather than two fighting over mmu_segmentation_facets. The
regression test is unchanged except for the call it makes.
---------
Co-authored-by: SoftFever <103989404+SoftFever@users.noreply.github.com>
The cloud upload sent only the keys that differ from the parent, so a
variant layout equal to the parent's was left out. On download the
loader sized every per-variant value by the missing layout, cut it to
one value and kept the parent's values for the other nozzles and nozzle
volume types. On dual-nozzle printers the right nozzle's settings
silently reverted to the parent's after a sync.
Upload the variant layout always, as the local save already does, and
read copies stored without it against the parent's layout when they
carry one value per parent variant.
* Add CGAL-based mesh repair, drop Netfabb remnants
Add repair_mesh() in MeshRepair.cpp, a CGAL-based repair that replaces MeshBoolean::cgal::repair(). It welds cracks, cancels coincident triangles, fixes orientation, fills holes (including planar annular regions), removes local self-intersections, and unites crossing shells with libigl's exact boolean while following the nonzero winding rule used by slicing.
FixModelByCgal now repairs each volume in place without splitting it into parts, and reports whether the mesh was modified so unchanged meshes keep their painting and ids. The texture import repair prompt no longer depends on the Windows SDK, and the FIX_THROUGH_CGAL_ALWAYS conditionals are removed. The commented-out Netfabb/WIN10SDK build code is deleted.
libslic3r_cgal now builds with CGAL_LINKED_WITH_TBB and links TBB. Add docs/HLSD/mesh-repair.md and unit tests in test_mesh_repair.cpp.
* Parallelize repair while preserving separate shells
Repair connected mesh units and shells independently in parallel, keep crossing shells separate, and limit self-intersection remeshing to local defects. Update mesh-repair documentation and tests for the revised behavior.
* Make mesh repair deterministic and report its progress
Flags written from parallel loops were std::vector<bool>, whose neighbouring
bits share a word, so shells could be kept or dropped at random; they are now
std::vector<char>. Holes of up to 100 edges are filled with the search over all
triangles: the search restricted to a 3D Delaunay triangulation picks between
equally good fills differently from run to run.
Shells of fewer than four faces are dropped, as the volumes of two triangles
back to back need not cancel exactly, and a shell that self-intersection removal
changed is split again so the pieces it cut off are dropped too.
repair_mesh() takes a progress callback in place of the cancel callback, and
Fix Model reports each stage. The object's bounding box is now invalidated even
when a cancel or an error stops the repair after some volumes were replaced.
The edge-use sort, union-find and box-overlap code shared by several stages is
factored out, the topology analysed for welding is reused to build the work
units, the orientation pass checks only the edges it cut, and the output is
written from the shells without joining them first.
* Fix Split to parts crashing on flat pieces and staying offered after a repair
ModelVolume::split() added every piece to the object and then deleted the
pieces without a convex hull. When the first piece was flat, as often in a
triangle soup, that deleted the volume being split while split() and its
caller kept using it. A part made only of flat pieces lost all its volumes,
and a soup added thousands of pieces, saving a backup of the object for each
one it deleted. Pieces without a convex hull are now dropped before any joins
the object, the volume being split keeps the first remaining piece, and a part
with no solid piece is left whole. Other parts of the object without a convex
hull are no longer deleted as a side effect.
ModelVolume caches whether its mesh can be split, and set_mesh() kept the
cached value, so after Fix Model, Simplify or Emboss replaced a mesh, Split to
parts could still be offered for a single piece. set_mesh() and reset_mesh()
now clear the cache.
* Import STEP surface models with Split compound enabled
Splitting a compound kept only its solids, so a surface model, which OCCT
reads as a compound of loose faces, gave nothing to mesh and the import failed
with "Meshing of a model file failed or no valid shape". Each shell outside a
solid now becomes a part too, and loose faces stay together as one part, as
they do without splitting.
* Add Fix All for plates and re-enable --repair on the command line
ModelVolume::repair() applies repair_mesh() to a part and replaces the mesh
only when the repair changed it. Fix Model, the new Fix All and Fix All Plates
items of the plate context menu, and the command line's --repair option, which
had been commented out since the Win10 SDK repair was dropped, all use it.
With Keep painted feature after mesh change off, Fix Model cleared the painting
of every part of an object before repairing. Now only a part whose mesh changes
loses its painting, so fixing a whole project does not wipe it. The
notification counts the models that needed no repair instead of listing them as
repaired, and objects that did not change are not refreshed.
They were appended at the end of test_texture_displacement.cpp, where every
other open texture-displacement branch also appends, so the four of them
cannot be merged in any order without a conflict in a file none of them
really disagree about. Same tests, moved next to the patch-frame test they
belong with.
A project no longer reads texture files from outside its archive, and a
malformed value or layer slot drops only the texture layers instead of
failing the whole load. Copies of an object that share a mesh keep their
own texture paint when saved, and projects without texture paint no
longer use extra memory to load.
The importer assigned pick_file.empty() and pattern_bbox_file.empty(), a bool, to the string fields, storing a one-character string instead of the path.
An image laid onto a model came out upside down under the ordinary box
(tri-planar) projection, and on three of a cube's six faces it was also
mirrored left to right, which reads as a 180 degree rotation.
DecodedHeightTexture::sample() defines v = 0 as the height map's first
pixel row, i.e. its top. Every projection built v from world +Z, so v
grew upward and the image landed bottom row up. The one path that was
already right is the projective "project from view" matrix, which
measures window y downward.
Both halves of the box basis were wrong:
- v is now negated on all three world planes, so the top of an image
sits at the top of the model.
- u now carries a sign for the side of the plane the face is on, chosen
so that u x v == -normal. Without it the two faces sharing a plane get
one u and one of them is mirrored. The sign flips exactly where that
plane's blend weight (the fourth power of a normal component) reaches
zero, so the blended tri-planar sum stays continuous and the hard axis
switch the old code removed does not come back.
The same v convention was missing from the other mapping modes:
cylindrical (which also had a left handed frame, right x up rather than
up x right, so its wrap ran mirrored), spherical, the affine branch of
view projection, and the flat chart fallback of the LSCM unwrap, which
now reuses the box basis so a cube face unwraps to the orientation
tri-planar would have given it.
The basis table is exported as box_projection_axes() and the gizmo's
adjust_tangent_basis() calls it instead of restating it, so the
placement ring and offset handle cannot drift from the bake. All four
preview shaders get the matching change, including projection_axes(),
whose tangent and bitangent have to flip with the coordinate or the
relief shading inverts.
Re-baking an existing project now places its texture upright, so it will
differ from how it looked before. Geometry already baked into a 3mf is
untouched, so nothing changes until the user re-bakes.
Verified by measuring the sampled texture row and column against world
position for every mapping mode on all six faces of a cube, and by a
full bake of a quadrant coded image, which lands each quadrant within
1e-3 mm of its expected depth.
* feat(preset): pack bed texture and model into printer bundles
Export copies bed_custom_texture and bed_custom_model into the .orca_printer zip under bed/. The manifest lists them in printer_asset. Import extracts each file into the bundle folder and rewrites the preset to that path. A missing file is skipped. A path that leaves the bundle directory is rejected. An old bundle with no printer_asset imports as before.
* Add includes flagged by clang-tidy misc-include-cleaner
* Gate bed asset packing on an export checkbox and key printer_asset per printer
The Export Configs checkbox can leave a custom bed out of a shared printer bundle. printer_asset is keyed by each printer_config zip entry, with files under bed/<n>/, so a later multi-printer bundle does not need a second manifest. The unshipped flat printer_asset object is not read.
---------
Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
* Fix decomposition of mirrored transformations
computeRotationScaling() may place the mirror on any axis, giving unstable rotation/scale for mirrored matrices. Decompose them by flipping each local axis and choosing the one needing the least rotation (lowest axis on ties). Merge the extract helpers into a single extract_rotation_scale() and simplify contains_skew() to check that the scale is diagonal.
set_scaling_factor() now preserves the mirror sign on the scaled axis. Selection::scale_and_translate() uses Transformation's rotation and scale instead of calling computeRotationScaling() directly, so scaling a mirrored instance adds no skew.
Add tests for mirrored decomposition, scaling, and skew detection.
* Use Eigen polar decomposition for instance scaling
Selection::scale_and_translate now splits the instance matrix with computeRotationScaling instead of get_rotation_matrix().
Adds a Model test checking that an instance created from another's offset, scale, rotation and mirror reproduces its matrix (as Add instance and Fill bed do).
* tidy
Arrange read the zones of the selected plate only and applied them to
every bed it packed. Arranging all plates with a plate in Primary
selected spread a parallel plate's parts across its whole bed, and with
a parallel plate selected, a Primary plate's parts were squeezed into
the zone. Arranging a single plate other than the first also ignored its
collision strips: they were tagged with the plate's index, while the
plate packs into the arranger's bed 0.
The IDEX/IQEX constraints now live in ImexArranger (IMEXArrange), and
ArrangeJob only snapshots each plate's zones on the main thread and
calls it:
- When every bed, plates the arrange may add included, has the same
primary zone, the bed shape is that zone, so parts stay centered in it.
- Otherwise each plate in a parallel mode fences off the rest of its bed
with fixed items on its own bed, and its parts are then arranged again
inside the zone, so they sit centered rather than piled against the
zone edge nearest the bed's center. Plates in Primary keep the whole
bed, and plates the arrange adds take the process preset's mode.
- Zone edges inside the bed get the bed's own edge margin, and strips
also keep room for the brim.
- A part too big for its zone, which libnest2d's first-fit retry places
across the fixed items, is left unarranged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Description
Follow-up to #16242, which turned texture colour mixes into mixed
filament slots but created a slot for every possible mix, even when only
previewing. A new Mixed colors setting (default 8) caps how many mixed
filaments a bake adds. They are picked from the texture's own colours,
and only the ones the bake actually paints with are created. Previews no
longer touch the project's filaments, both previews show a mix in the
colour its slot will have, and the bake paints each mix with the slot it
got, which went wrong whenever the project already held mixed slots.
It also improves the colour preview: the green paint highlight no longer
covers the colours, and the colours a bake writes stay visible in the
gizmo.
Only texture displacement changes. Models without a colour layer behave
as before, and there is no change to project or profile formats.
# Screenshots/Recordings/Graphs
**Before**
https://github.com/user-attachments/assets/554a0d0d-cd9f-4095-840e-ca764dde6d52
**After**
https://github.com/user-attachments/assets/7fe10431-8c3f-4759-bd3f-78b4f7a12fc7
## 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 green highlight and tint no longer cover a colour preview, and return while a stroke is
painted. The Fast view also keeps the other parts of a multi-part object.
The new Mixed colors setting caps how many mixed filaments a bake adds. They are picked from the
texture's colours, and only the ones the bake paints with are created. Previewing no longer creates
filament slots, both previews show a mix in its slot's colour, and the bake paints each mix with the
slot it actually got.
A `<metadata>` element without a `type` attribute makes
`get_attribute()` return nullptr, which was then assigned to a
`std::string` and read as a C string.
Check it the way the sibling metadata handler already does, and stop the
parse.
Regression test in `tests/libslic3r/test_amf.cpp`, a new file: the suite
had no AMF test at all.
The project carried the layer stack but not the user's place in it: a reopened
project landed on slot 0 in Standard, whatever they had been editing. The active
layer and Standard/Pro now live on the volume, next to the rest of its options,
and come back with it. Docked/undocked goes to the application config instead -
it describes the user's window, not the model, so opening someone else's project
must not move their panel.
A layer whose image_data was empty at save time was written with no texture and
came back with none. It now falls back to the file the layer came from, which the
importer put in the user's own texture library, so a project reopened on the same
machine keeps its pixels.
The texture is always stored as .png: the importer converts whatever the user
picked, so the extension was being taken from a name that no longer described the
bytes.
Also adds v2_flip_edges to the cereal field list, which the undo stack was
silently dropping.
The round-trip test named a TextureTileMethod enumerator that does not
exist (MirroredRepeat, not Mirror) and used a bare Approx, which Catch2 v3
keeps in its own namespace. The rest of the file compares floats with
WithinAbs, so these follow it rather than reaching for Catch::Approx.
bbs_3mf.cpp reads and writes the layer JSON but picked up the declarations
through another header, and the same for boost::to_lower.
libpng reports a bad file by longjmp()ing back to the buffer set with
setjmp(), and the frame it lands in must own nothing that needs
destroying: with exceptions enabled MSVC unwinds the stack as part of
longjmp, and returning from a frame unwound that way crashes. It did on
Windows while working everywhere else.
The read callback also returned quietly on a short read, leaving libpng
to decode whatever happened to be in the output buffer.
The calls that can fail now sit in two helpers that own nothing but
pointers, so every C++ object the decoders need stays in their own
frames, and a short read is reported through png_error().
46 files include the same header twice at file scope, outside any #if,
66 times in all:
Model.cpp included Model.hpp twice, Utils.hpp <algorithm> and
<string_view> twice, seven GUI headers <wx/dataview.h> and
<wx/artprov.h> twice. The second include of each is removed.
GCodeSender.cpp and GCodeSender.hpp have been commented out of
libslic3r/CMakeLists.txt since 2022 and their only two includes are
commented out as well. Both files go, with the commented lines, and
the CMake entry for SLA/SupportTreeIGL.cpp, a file that no longer
exists.
The mixed-colour metadata options are parallel per-slot arrays in the project
config. A project saved before they were sized per slot stores a single value
for the gradient ones, and one saved before they existed stores none. The GUI
sizes all seven to the filament count when it opens a project; the CLI kept
the stored arrays and exported one-element defaults for absent ones, so a
project it exported carried one-element arrays where the GUI writes one entry
per filament. Slicing is unaffected, every reader treats a missing entry as
not mixed / no gradient, but the GUI-vs-CLI comparison reported the four
gradient keys on every mixed-filament project.
The resize helper moves from PresetBundle.cpp, where it was file-local, to
PrintConfig.cpp next to set_filament_dev_options(). It creates an option the
config lacks before sizing it, a no-op for the bundle's project config where
all seven always exist. The CLI calls it with its filament count once the
project and loaded filaments are merged, after the check that every mixed
slot has a filament of its own.