Commit Graph
14168 Commits
Author SHA1 Message Date
ExPikaPaka fbaed8a512 Project the texture in the model's own orientation, and stop the preview tiling
The bake frame took its orientation from the world, so turning a part on the
plate slid the decoration across its surface. Turning a part is a print-time
decision - clearance, supports, warping - and it should not be a design one.
The frame now takes its orientation from the model and keeps world scale, so a
tile is still tiling_scale printed millimetres whatever the instance's scale.

The rotation is separated out by polar decomposition rather than by assuming
the linear part is a rotation times a scale, since a sheared or mirrored
placement is legal here. A reflection is deliberately left with the scale, so a
mirrored instance still makes the frame's determinant negative and the bake
still flips the normals it derives.

The preview shaders rebuild that frame themselves from a world position, so
they take the same rotation through a uniform; without it the fast preview and
the bake would disagree, which is the class of bug this is fixing. The gizmo's
own way back out to world carries the rotation too, which it did not need to
when the frame was world-aligned.

Tiling in the fast preview:

- the sampler wrap mode was set on the height texture but not on the colour
  texture, which sits on its own unit and is sampled at the same uv. So with
  Tile off the relief stopped at one placement while the colour carried on
  repeating around it.
- outside a single placement the colour then came out black, because a zero
  texture border cannot say "no colour here", only "the colour is black here".
  The shader now tests the uv the way sample_layer_color() does and leaves the
  surface its own colour.

The placement gizmo is a tile of a planar projection, so it is offered only for
the mappings that have one. An unwrap cuts the surface into islands and packs
them, and a model's own UV channel is whatever its author drew: one tile lands
there as an arbitrary curved patch with no rectangle to take hold of. Those two
say so and point at the UV editor instead.
2026-10-15 15:45:47 +02:00
ExPikaPaka 597b2abac4 Merge remote-tracking branch 'origin/main' into HEAD 2026-10-15 13:13:26 +02:00
ExPikaPaka ba4de8c1fc Measure the placement gizmo in the frame the texture is projected in
The gizmo worked in mesh-local millimetres while the texture is projected in
the bake frame - world orientation and scale, anchored at the volume's origin.
On an unscaled object the two agree, which is why this went unnoticed; on a
scaled one the drawn tile differed from the rendered one by exactly the
object's scale, and a rotated one would have skewed its axes as well.

The anchor is carried into that frame at the point of use rather than stored
converted, since the object can be moved, turned or scaled without leaving the
mode. Getting from the frame to the screen is then a translation and nothing
more, which is the point: rotation and scale are what a tile's millimetres must
not be measured through.

Also in this commit: a pale casing outside the dark halo, because a dark halo
alone disappears against a dark backdrop and a photographic texture offers both
within one tile; one rotate ring instead of four, moved to whichever corner the
cursor is nearest and held there by a margin so it does not flit between two;
one constant for the casing's width, after the move grip's dots turned out to
carry nearly twice the casing of everything around them; a request for one more
frame when a panel edit changes a preview parameter, since the panel is drawn
after the 3D view and the edit was otherwise a frame late; and an animated
illustration for the square-tile lock.
2026-10-15 13:13:26 +02:00
Lam Wei Lun 81ec931990 Keep global shortcuts out of in-canvas ImGui text fields
Space opened the Speed Dial and Ctrl+P the Preferences while the text tool's
box or a sketch value field was being typed into. Those fields are ImGui drawn
on the GL canvas, so the canvas is the focused wx window and focus_keeps_space()
cannot see them; the CHAR_HOOK handler took the chord first and the field never
saw the key.

Skip the global shortcut dispatch while ImGui reports that one of its items owns
the keyboard and the focused window is not a wxControl.
2026-10-15 17:20:56 +08:00
ExPikaPaka 7bce53a773 Merge branch 'main' into texture-placement-gizmo
Three files conflicted, all in the texture displacement subsystem that main
has been working on in parallel.

apply_uv_transform and UVTransform gained main's normalized_uv parameter for
TextureProjectionMethod::ModelUV. A normalized uv has no millimetre tile to
divide by, so the extents are skipped for it, but the mirroring still applies:
a mirror is a placement choice and means the same thing on a model's own UV
layout as on a projected one. The aspect correction stays skipped, as main has
it.

adjust_tangent_basis now calls main's box_projection_axes instead of restating
the dominant-axis choice, which also picks up main's fix to the texture's
orientation. The gizmo derives its whole frame from that basis, so it follows
the new conventions without needing to know about them.

The basic settings rows took main's float_row, which carries the tooltip and
its media as arguments, and its disabling of tile size under ModelUV. The
square lock and the second extent join that disabled block, being the same
millimetres from another angle.

Layers now persist to the .3mf as JSON, which they did not when this branch
started, so tiling_scale_v, mirror_u and mirror_v are written and read there
too. The reader keeps the default for a missing key, so a project written
before the tile could be rectangular or mirrored still loads as a square,
unmirrored one.
2026-10-15 09:54:52 +02:00
Noisyfoxandyw4z 26f5fd6251 Add a themed WebView color picker with persistent favorites (#16197)
* Define validated color picker selection contract

* Integrate web color picker with gradient and alpha controls

Co-authored-by: yw4z <ywsyildiz@gmail.com>

* Apply shared Orca theme to web color picker

* Host color picker in a validated WebView dialog

* Persist color picker favorites in user preferences

* Integrate localized web color picker with filament actions

* Add color picker browser regressions and verify direct includes

* Fit color picker content in a borderless floating panel

* Anchor color picker panels to their triggering controls

* Use accent foreground for selected color picker switches

* Integrate web color picker into generic color fields

* Use web color picker for AMS custom colors

* Use shared web color picker from Linux sidebar menu

* Use web color picker when adding texture import material

* Preserve alpha in filament color picker actions

* Clean up color picker asset whitespace

* Use application translations for color picker validation

* Preserve existing formatting in color picker integration

* Match SpeedDial rounded window handling on macOS

* Align color picker host behavior with SpeedDial

* Consolidate color picker implementation into its dialog

* Remove native color picker fallback

* Limit color picker interfaces to production consumers

* Use shared WebView translation field name

* Reapply anchored color picker placement after GTK show

* update

* fix increment buttons not working on spin inputs

* Use thumb color from its own channel

* init gradients with shifted color for second color

* fix hsl sliders cannot move freely and lock downs other one

* unify save button and color preview & increase stored color count to 32

* switch to hsl based calculation and fix plot and slider drag events

* update

* add support for multi colored gradients

* Update test_filament_color_picker.cpp

* add filtering color palette and store sidebar and combo state

* open dialog with calculated size

* apply shadow to dialog on windows

* add tooltip for gradient add color button

* Restore draggable fixed-size color picker frame

* Fix color picker sidebar sizing in WebKit

* Fix invisible default filament color button on macOS

* Remove duplicated color defs for color picker

* Remove duplicate HTML color picker frame

* Match color picker tests to current theme roles

* update icons

* use focus color on background of filter box

* align gradient switch with hex input

* fix initial window sizing

* Preserve caller color order when drawing filament icons

---------

Co-authored-by: yw4z <ywsyildiz@gmail.com>
2026-10-15 15:29:49 +08:00
HanifKoh 5d73410281 Tell the Setup Wizard's Loading Page Only Once It Has Loaded (#16475)
The wizard's loading page waits for HandleStudio to be called with
userguide_profile_load_finish and jumps to its target page on it; if the
call never arrives, a fallback timer in the page jumps after 180 s.

GuideFrame started the profile load from the first wxEVT_WEBVIEW_NAVIGATED
event and ran HandleStudio in the page as soon as the data was ready. That
event is the navigation's commit, before the page's scripts have run, and
with the wizard's derived cache the data is ready about 100 ms later.
Since the web view is created with an empty URL so the translated strings
can be injected before the first load, the first navigated event is the
commit of about:blank, so the data is ready before the real page has even
begun loading and the call reaches a page without HandleStudio. WebKit
drops the error and nothing retries, so on macOS the wizard often sits on
"Loading..." for three minutes before the printer list appears.

The loading page now reports in from its OnInit, the way the other wizard
pages request what they need, and the host sends the message once both
the data is ready and the page has reported; every instance of the page
reports again, so one that replaced an earlier one gets its own message.
2026-10-15 13:44:06 +08:00
Kiss Lorand b75af485ce Fix DPI scaling for Move gizmo alignment icons (#16498)
Fix Move gizmo alignment icon scaling

Apply the native Windows DPI factor when sizing Move gizmo alignment icons.

This matches the sizing path used by other 16 px gizmo controls,
including support paint, seam, fuzzy-skin, and MMU painting tools.
2026-10-15 06:03:09 +03:00
6a7763f08d ENH: Add "Drop" button to move gizmo for objects, parts, and modifiers. And unify all Drop actions / checks (#16323)
* Add Drop to bed button for objects, parts and modifiers to the Position panel

Ported from BambuStudio PR #11511. The button appears under the position
inputs when the selection is not resting on the bed and moves it along
global Z. Works for a single full instance as well as a single part or
modifier, and is undoable.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7uDtamzhNGT6PFwmVdRJ8

* Reuse existing 'Drop to Bed' string so the button is already translated

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7uDtamzhNGT6PFwmVdRJ8

* Add tooltip to the Drop to Bed button

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7uDtamzhNGT6PFwmVdRJ8

* unify function usage across UI

* Update GizmoObjectManipulation.cpp

* show tooltip even its disabled

* update

* Update GizmoObjectManipulation.cpp

* update

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: yw4z <ywsyildiz@gmail.com>
2026-10-15 03:22:28 +03:00
Rodrigo FaselliandIan Bassi 303d7a1a07 Option: Focus Mode (Show/ Hide inactive plates) (#16325)
* Option: Show/ Hide inactive plates

* Hide inactive plate volumes by bounds

Switch inactive-plate volume hiding to use geometric overlap with the current plate bounding box instead of object and wipe tower ID mapping. This also guards against a missing current plate and keeps any active volume visible when its transformed bounds overlap the current plate footprint.

* Hide inactive plates per instance and keep them unpickable

Decide what is shown per object instance, from its footprint over the
current plate, so the parts of an object are never split; a selected
instance stays visible.

Keep the hidden state between frames instead of restoring it after each
one. GLVolume::on_hidden_plate records what this update hid, so it only
shows that again and leaves the volumes gizmos hide alone, and the raycasts
made while handling the mouse see the same scene as the frame. Nothing runs
while the inactive plates are shown.

Deactivate the raycasters of the other plates, whose icons still took
clicks where nothing was drawn, move the axes to the current plate, and
limit the option to Prepare, as Preview already shows the current plate
alone.

* Follow the selected plate with the camera while the others are hidden

Selecting a plate in Prepare while the inactive plates are hidden moves
the camera target over it and keeps the angle, distance and height, so
the plate comes into view where the previous one was. A zoom to all
plates, as after loading a project or adding a plate, frames the current
plate instead.

* Add a compact plate switcher while inactive plates are hidden

The hidden plates can no longer be clicked, and the object list gets
crowded on large projects. While they are hidden in Prepare, a column of
numbered buttons in the top left corner, a compact counterpart of the
Preview plate toolbar, selects the plate to show. It highlights the
current plate, dims the empty ones and gives the plate name in the
tooltip.

* Simplify the inactive plates update and the plate switcher layout

Decide and apply each volume's state in one pass instead of filling a
per-volume vector first, and hoist the switcher's loop-invariant colors.
Keep the height the bottom-left corner takes next to the constants it is
made of, with the canvas toolbar's button spacing named, rather than
repeating that layout in the switcher.

* Rename the option to Focus mode

The canvas menu entry reads Focus mode, lit while the other plates are
hidden, and is off by default, so nothing changes until it is turned on.
The app config key is focus_mode. Turning it on zooms to the current
plate, so the plate fills the view.

* Show plate thumbnails in the plate switcher

The switcher shows the thumbnails of the Preview plate toolbar instead of
numbered buttons, with rounded corners concentric with the window's.
Plater::plate_toolbar_image_version() lets it refresh them without
clearing the Preview toolbar's dirty flag.

* Don't draw the sinking contours of inactive volumes

GLVolume::render() skips an inactive volume, but its sinking contour was
still drawn, so an object on a hidden plate left its outline behind.

* Show the plate switcher's scrollbar only on hover, as in Preview

The plate switcher now shares the Preview plate toolbar's scrollbar: the
same width and colors, shown only while the mouse is over the column or
drags the bar, with the mouse wheel scrolling it either way, and tighter
window corners while it shows. Both toolbars draw it through the same
helpers, so they cannot drift apart; the Preview toolbar behaves as before.

Drop the frame style the switcher no longer uses since it draws
thumbnails instead of buttons.

* Make the plate switcher's thumbnails cheaper and match the Preview toolbar

The thumbnail version moves after almost every edit, and the switcher
redrew both thumbnails of every plate each time. It now redraws only the
current plate's, the only one edited while the others are hidden, and the
other plates' once their thumbnails are invalidated, through the new
Plater::update_plate_thumbnails().

IMToolbarItem::set_thumbnail() uploads a plate thumbnail without keeping
a copy of its pixels, a megabyte per plate, and replaces the item setup
the Preview toolbar and the switcher each repeated. The switcher's hover
border takes the Preview toolbar's color.

---------

Co-authored-by: Ian Bassi <ian.bassi@outlook.com>
2026-10-14 14:23:41 -03:00
Ian Bassi c0e6db227d Export G-code statistics as JSON (#16487)
* Export G-code statistics as JSON

Add an export of a plate's G-code statistics for scripts and
spreadsheets that compare prints or settings: print time, travel,
filament and feature totals, the same per layer, and the filament of
each object instance and its supports. The times are the preview's
estimates, summed the way the preview sums them.

The GUI writes <name>.statistics.json next to an exported G-code when
"Export statistics with G-code" is enabled in Preferences (Control >
Slicing). The CLI writes plate_N.statistics.json to the output directory
with --export-statistics, and exits with -25 when it cannot.

The per-instance masses now carry the object's name and the instance's
index, so the export can name them. The vertices the processor adds
inside a move for the actual speed view repeat the move's extrusion, so
the filament sums skip them.

* Move the statistics export preference to the Developer tab

"Export statistics with G-code" now sits in Developer > Settings
instead of Control > Slicing.

* Store the G-code statistics inside exported 3MF files

With the preference on, an exported sliced 3MF file holds each plate's
statistics as Metadata/plate_N.statistics.json, next to its G-code. The
print job sent to a printer and the autosave backup leave them out.

The CLI does the same with --export-3mf, and writes the separate file
only next to the G-code in --outputdir, as without it the G-code is a
temporary file. The file is written in binary mode, so it reads the
same on every platform and matches the copy in the 3MF.

* Address review of the statistics export

- Number each object instance by its position among its model object's
  copies. A copy of another rotation or scale, and every copy on a belt
  printer, slices as a print object of its own, where it was always
  instance 0; a copy after a non-printable one shifted too.
- Take the filament totals from the processor's statistics, which count
  the flush as the preview's filament table does, and list each
  filament's model, support, prime tower and flush use. The summary's
  cost is now filament_cost, as the preview's cost adds the time cost.
- State when the version goes up.
- Drop the preference's default, which get_bool already gives.
2026-10-14 14:22:04 -03:00
Rodrigo Faselli 3b6e27d725 add missing header GLGizmoTextureDisplacement.cpp (#16488)
Update GLGizmoTextureDisplacement.cpp
2026-10-14 14:14:05 -03:00
Ian Chua 9ab0178dd0 feat: add orcacloud to homepage (#16402)
* feat: add orcacloud to homepage

* feat: add OrcaCloud webview pane and session handoff from the app

* feat: OrcaCloud pane crash, per-open re-sign-in, and web-session persistence

* fix: clang-tidy
2026-10-15 01:01:25 +08:00
SoftFever 7a2fe7d64b Merge branch 'main' into feat/texdisp-tooltip-media 2026-10-15 00:02:11 +08:00
SoftFever 96464ccdf6 Fix Align/Distribute and add it to the Move gizmo (#16479)
# Description

Follow-up to #13373, which added Align/Distribute to the object context
menu. This fixes the problems found in its review and brings
BambuStudio's align and distribute controls to the Move gizmo.

Align and distribute now work when all copies of an object are selected,
on parts of rotated, scaled or mirrored objects, and on objects with
several copies, whose other copies used to lose the part move. Aligning
a single object arranges its solid parts and leaves modifiers and
negative volumes in place. Moves in Z are offered only when Auto-Drop
would not undo them. Commands that would do nothing are disabled, each
operation is a single undo step, and other plates keep their slicing
result. The same fix stops the automatic drop to the bed after a move,
rotate, scale or mirror from also moving copies of other selected
objects.

The Move gizmo gets BambuStudio's align row. It aligns or distributes
the selected objects or parts, or aligns them to their object or to the
plate, and previews the target while you hover an icon. While the Move
gizmo is open, Ctrl+click adds objects to the selection, and Alt+click
adds or removes parts of the selected object.

No change to slicing output or project files.

# Screenshots/Recordings/Graphs



https://github.com/user-attachments/assets/44c308c3-faff-4f51-a3b7-88f264f03743



## 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)
2026-10-14 23:39:36 +08:00
yw4z bb7dfdc808 update layout, icons and terms 2026-10-14 18:08:14 +03:00
Eldenroot 888012c30e Painter gizmos: reset out-of-range paint states at load (#16341)
Supports, seam, fuzzy skin and texture displacement loaded paint data with the default max_ebt, so a 3mf with a paint state above what the gizmo paints read past the 3-entry color table. Each gizmo now passes its highest paint state as max_ebt, as the MMU gizmo already does, and higher states load as unpainted.
2026-10-14 09:35:27 -05:00
Ian Bassi 6e7d5b0a9b Ask about clumping detection whenever the prime tower is disabled
The prompt that clumping detection needs a prime tower was nested inside
the smooth timelapse check, so disabling the tower with clumping
detection on and traditional timelapse gave no prompt; the problem only
surfaced as a warning after slicing. The prompt now appears whenever
clumping detection is on. The smooth timelapse prompt is unchanged, and
answering it with No still skips the clumping one.
2026-10-14 10:30:48 -03:00
Ian Bassi 721ec56f22 Link the printable area and compatibility conditions to their wiki sections
extruder_printable_area linked to the Extruder offset Position section,
which describes a different setting; the wiki now has an Extruder
printable area section. The two compatibility conditions linked to their
parent sections instead of the Printer Condition and Process Condition
sub-sections that describe them.
2026-10-14 10:30:48 -03:00
Ian Bassi 7d6e1f85c8 Link the new wiki sections from the settings tabs
The wiki now has sections for options that were linked to a page top or
not linked at all: the polyhole sub-options, accel to decel, extrusion
rate smoothing on external features, max bridge length, the tree support
brim, seven prime tower options, Flush into this object and clumping
detection. Point each link at its section, and follow the corrected
spelling of the wall transitioning threshold angle heading.
2026-10-14 10:30:48 -03:00
SoftFever 987c57dc15 Add align and distribute to the Move gizmo 2026-10-14 19:47:03 +08:00
HanifKoh 0a336941f4 Restore the Tagged User-Agent on Linux with WebKitGTK's Own Prefix (#16466)
Linux webviews lost the BBL-Slicer, dark|light and BBL-Language tags
with the stock WebKitGTK User-Agent, so the bundled pages stayed light
and dropped every message to the host. The tags come back on Linux, now
appended to the string WebKitGTK reports for itself instead of the Mac
one. Windows and macOS are untouched.
2026-10-14 19:03:53 +08:00
SoftFever 48d07a831d Simplify the Align/Distribute context menu 2026-10-14 18:25:16 +08:00
SoftFever 4e7fa93981 Fix align and distribute on copies, rotated parts and Z 2026-10-14 18:19:47 +08:00
ea90f45127 Add Align/Distribute objects on the print plate (#13373)
* Add Align/Distribute objects on the print plate

Ports the Align/Distribute feature from BambuStudio 2.5 (AGPLv3,
same license as OrcaSlicer). Adds a new GLGizmoAlignment helper
that operates on the GLCanvas3D selection, twelve thin Plater
wrappers, and an Align/Distribute submenu attached to the same
five context menus BambuStudio attaches it to: object menu, text
part, BBL part, and the single/multi-volume context menus.

GLCanvas3D::do_move gains a defaulted force_volume_move parameter
so the helper can move multiple selected volumes within a single
object even when selection mode is Instance. GLGizmosManager gains
check_object_located_outside_plate, which switches the active plate
to the one containing the selection before align/distribute runs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix build errors

---------
Co-authored-by: yw4z <ywsyildiz@gmail.com>
Co-authored-by: SoftFever <103989404+SoftFever@users.noreply.github.com>
2026-10-14 17:06:56 +08:00
SoftFever e77ba5f672 Open .gcode files with their own settings, as .gcode.3mf files are (#16463) 2026-10-14 14:13:58 +08:00
46b3871f8d fix: login crash when network plugin is unavailable (#16438)
* fix: login crash when network plugin is unavailable

* fix: directly use !config_version.empty() for the control block

* Use if/else for the empty plug-in version check

* Fix multi-device task list using a deleted network agent after plug-in reload

---------

Co-authored-by: SoftFever <103989404+SoftFever@users.noreply.github.com>
Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-10-14 13:19:04 +08:00
Lam Wei Lun 84768da6a1 fix(gui): scroll the main preset list while its submenu is open
The filament list groups presets by vendor: hovering a vendor in the main
popup opens a side popup with that vendor's materials. On GTK the side popup
holds the popup grab, and gtk_main_do_event hands every GDK_SCROLL of the popup
chain to the grab widget. GDK names the event for the window under the cursor,
so the side popup received the wheel events raised over the main list and
consumed them: the main list could not be scrolled while a vendor was hovered.
The problem shows on both X11 and Wayland, because the rerouting happens in
GTK, not in the compositor.

The side popup now inspects the event's GDK window and, when the event belongs
to the main popup, replays it there. The replay goes through
gtk_propagate_event(GetConnectWidget()): wxGTK connects its mouse handlers to
the inner client widget of a popup (a wxPizza child of the outer GtkWindow), so
a signal emitted on the outer GtkWindow reaches no handler at all.

The widget reference in .claude/skills/orca-wxwidgets records the mechanism and
the connect-widget fact.
2026-10-14 12:00:40 +08:00
Javad Shafique bb0d8ee1c4 fix: use default webkitgtk User-Agent instead of MacOS User-Agent for linux builds (#15104)
Use default Linux webview user agent
2026-10-14 11:36:21 +08:00
packerlschupfer cb752d38a3 Probe config-supplied paths without throwing on an unreadable path (#15669)
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.
2026-10-13 19:05:36 -05:00
Ian Bassi 5bce937d07 Fix wiki links in the process settings tab (#16451)
- Slicing pipeline plugin linked to others_settings_plugin_picker, a page
  that doesn't exist. It now opens the Slicing Pipeline section of the
  plugin types page.
- Infill shift step linked to the Cross Hatch pattern, which doesn't use
  it. It only applies to Cross Zag and Locked Zag, so it now opens Cross
  Zag.
- Fuzzy skin and Fuzzy skin generator mode were crossed: the generator
  mode opened the section that documents Fuzzy skin's values. Each now
  opens its own section.
- The process Change extrusion role G-code had no wiki link; it now has
  one, like the printer and filament versions.
- The hidden Frequent page linked Wall loops to the top of the walls page
  instead of its section, which made the wiki sync write its metadata
  twice.
2026-10-13 20:41:03 -03:00
TheLegendTubaGuy de3af0b7de Add Moonraker trusted-client auth support (#15550)
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.
2026-10-13 15:13:07 -05:00
SoftFever 7131dd30e7 Lower peak memory when the G-code preview loads (#16440) 2026-10-14 00:37:47 +08:00
HanifKohandSoftFever 5c8a287b5e Keep User Filament Presets on Printers Their Parent Listed in 2.4.2 (#16367)
* 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>
2026-10-14 00:02:34 +08:00
Noisyfox a338f6ca7d Fix instance Z synchronization (#15465) 2026-10-13 22:26:53 +08:00
Ian Bassi e821665e4b Add orca-translations Claude skill and PO tool (#16436)
* 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
2026-10-13 10:53:57 -03:00
ExPikaPaka 4bc300933e Texture displacement: keep a tooltip on screen long enough to be seen
A tooltip appeared only on the next pixel of mouse movement after its wait
had finished. ImGui hides a window on the first frame it is submitted, to
measure it before showing it, and does so every time a tooltip reappears
because those windows are recycled (the Popup|Tooltip branch of Begin()).
So the frame a tooltip is decided on is never the frame it is seen on, and
one carrying no animation asked for nothing further - leaving it in its
hidden measuring frame until some other event happened to produce the next
one. Before the hover delay this never showed, because the tooltip was
submitted on every mouse-move frame and the movement supplied the second.

It now asks for frames until it has been drawn twice in a row.
2026-10-13 14:30:53 +02:00
ExPikaPaka 5cfd91a309 Texture displacement: no tooltip while a control is being used, and a wait
that finishes on its own

The hover delay only finished while the mouse was moving: a pointer resting
on a control produces no events, so nothing redrew the canvas and the wait
never reached its end - the tooltip appeared on the next pixel of movement
instead. It asked for its frames with schedule_extra_frame(), which only
arranges a timer whose event an idle event is assumed to follow, and
on_idle renders nothing unless the canvas was already dirty when it ran.
The wait and the animations now both use request_extra_frame(), which ends
on_idle with evt.RequestMore() and so obliges wx to deliver the next idle;
it is what 3DBed's fade uses. Asking for frames as fast as they come is
safe now that which frame is drawn comes from the clock rather than from
the frame count - a redraw that arrives early draws the same frame again.

Nothing explains a control while the control is being used, so no tooltip
appears while a slider is being dragged, a value typed into one, a combo
open or a layer card dragged. The pointer sits on the very thing being
edited, so a tooltip there covered the model exactly while the user was
watching it change. The full wait comes back afterwards rather than the
short one: having just let go of a slider is not a reason to be shown a
picture of it.

AnimatedTexture::texture_id() loses its time-to-next-frame out-parameter,
which nothing asks for any more.
2026-10-13 14:21:55 +02:00
ExPikaPaka db0e788d35 Texture displacement: tooltip timing on a real clock, and a hover delay
The animations crawled when left alone and raced while the mouse moved.
They were timed by ImGui::GetTime(), and nothing in OrcaSlicer ever sets
io.DeltaTime, so it keeps imgui's default of 1/60 per frame: imgui's clock
counts frames, not seconds. On a canvas that renders only when something
asks it to, that is not a clock. Playback now reads a steady_clock, so a
frame is due when it is due whatever the canvas is doing.

An animation also restarts when its tooltip opens again, not only when a
different one opens: a gap in the frames it was drawn on is what that
looks like from here.

Tooltips now wait for the pointer to rest on a control for 0.45 s. The
panel is dense and a tooltip lands on the model beside it, so with no wait
a sweep from the top of the panel down to Bake flashed a dozen large panels
over the scene. After one has just been up the wait drops to 0.1 s for 0.7
s, so reading several settings in a row stays quick - but not to zero, or
that same sweep would flash again once one tooltip had been seen. A pointer
resting on a control produces no events, so the wait asks for the frame it
will appear on itself.
2026-10-13 13:58:52 +02:00
ExPikaPaka d2ba88c93d Texture displacement: animations that play on their own, and art for the
rest of the panel

The animations advanced one frame per mouse movement and stood still
whenever the mouse did. schedule_extra_frame() only guarantees an idle
event at the moment the next frame is due; GLCanvas3D::on_idle() returns
without rendering anything unless something marked the canvas dirty by
then, and nothing had. set_dirty() on the gizmo is what on_idle reads,
through GLGizmoBase::update_items_state(), to find out there are frames
still to draw.

Thirty-odd more pictures, covering the paint tools, brush size, the
connected-area fill, the five views, the five projections, subdivision,
feature-adaptive refinement, remeshing, hard edges, slivers, geometry
smoothing, filament mixing and denoise.

The generator grew three more drawing languages beside the shaded relief,
each picked by what the setting is rather than by taste: a flat mesh of
triangles where the subject is the mesh or a tool, a section where the
subject is where the surface sits relative to where it was, and a flat ramp
of greys where the subject is which filament a spot ends up as. Lighting a
colour illustration or looking down on a vertical change would both add a
second thing moving between two pictures meant to differ in one.

Mapping is one ball with the checker laid on it five ways, which is the
whole of what choosing a projection does - except Unwrap, which is not a
projection onto the model and so is drawn as the flat layout it produces.
2026-10-13 13:53:06 +02:00
ExPikaPaka f546551b3f Use a model's own UV channel for texture displacement, and keep its colours through a bake (#16394) 2026-10-13 19:51:07 +08:00
ExPikaPaka fc5ac6d2a5 Texture displacement: illustrate the settings that are a shape
Ten more tooltips get a picture: Depth, Tile size, Repeat/Mirror, Invert,
Smoothing, Edge fade and Blend as stills, Depth, Rotation and Midlevel as
animations. All drawn by scripts/gen_texture_displacement_tips.py from the
same arithmetic the feature uses, one motif throughout, so that whatever
differs between two of them is the setting and nothing else.

Midlevel is drawn in section instead of as a relief. Shading reads slopes,
so a height field moved up or down as a whole shades exactly as it did
before - and where the relief sits relative to the surface is the entire
meaning of that setting. From the side, against the bare surface, it is the
one thing visible.

A single picture is now capped at 10 em tall and centred in its column,
rather than drawn at the tooltip's full width, where a square one came out
as tall as the text beside it was long.
2026-10-13 13:33:05 +02:00
ExPikaPaka 64ddc1fbba Texture displacement: tooltips beside the panel, with illustrations
The panel is pinned to the right edge of the canvas, so a tooltip left to
follow the mouse covers the very control it explains. panel_tooltip()
places every tooltip in the gizmo to the left of the panel instead - or of
the texture picker, while a tooltip is coming from inside that popup - the
same placement the picker itself already uses.

A tooltip can now carry pictures under its text: stills, or GIFs that loop
while it is open. Several settings in this panel describe a shape rather
than a number, and a sentence about a shape is read twice and understood
once. Files live in resources/images/texture_displacement and are named by
a tooltip; one that is missing is skipped, so a tooltip without its art yet
is exactly the text tooltip it was before.

AnimatedTexture decodes a file once and uploads a GL texture per frame -
wxImage for stills, wxGIFDecoder for GIFs, whose frames are composited here
with the disposal method each one asks for. An animation restarts when its
tooltip opens and asks the canvas for a redraw timed to its next frame.

Tile and Rotation are wired up as the first two; scripts/
gen_texture_displacement_tips.py draws their relief from the same
arithmetic the feature uses. docs/HLSD/tooltip-illustrations.md covers the
rest.
2026-10-13 13:21:17 +02:00
ExPikaPakaandSoftFever 39c77325ce Carry existing colour across a bake instead of reindexing it (#16392)
* 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>
2026-10-13 19:05:05 +08:00
ExPikaPaka af29e4d553 Image adjustment panel for texture displacement layers (#16391) 2026-10-13 18:45:21 +08:00
SoftFever 11493a2236 Merge branch 'main' into fix-texture-uv-orientation 2026-10-13 18:16:02 +08:00
Lam Wei Lun 01499e536e fix(gui): stop the UV editor pane from hijacking the shared GL context
The texture-displacement gizmo pushes pane state to the UV editor from the
preview rebuild that runs on every frame of a slider drag. Tile size is the
only one of the layer card's sliders whose value appears in that pane state,
so dragging it changes the "N.N mm tile" label, the panel relays out, and the
UV canvas receives a synchronous wxEVT_SIZE. Its on_size() flushes a paint
with Update(), and render() then makes the app's single shared wxGLContext
current on the UV drawable in the middle of the 3D canvas's frame.

On X11/GLX that out-of-turn context activation is fatal: the drawable the
3D canvas later swaps no longer belongs to the current context, the server
returns BadMatch, and GDK turns the unhandled X error into g_error(). EGL on
Wayland reports only a status, so the same theft silently corrupts the frame
instead of aborting - which is why the guard is not conditioned on the
display backend.

Add GLCanvas3D::is_frame_in_progress(), backed by a counter that
_render_frame() maintains through its existing scope guard, and let the UV
canvas skip the synchronous paint while a frame is on the stack, re-queueing
it with Refresh() so nothing is dropped. Rebind the context with
_set_current() before SwapBuffers() as well, so a surface that steals it by
some other route - a modal GL dialog a gizmo opens from its panel - cannot
turn the swap into the same fatal error.
2026-10-13 16:49:06 +08:00
Downtime 237586675f fix(gui): remove legacy fixed-function OpenGL texture calls (#16270)
Debug builds asserted on startup because these calls raise GL errors on a core-profile context. Both draws already use shaders, so removing the calls changes nothing on compatibility contexts.
2026-10-12 20:01:57 -05:00
Ian Bassi 6d35ff6b9a Rewrite Fix Model on CGAL 6.2, fix Split to parts, remove Win10 SDK leftovers (#16366)
* 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.
2026-10-12 21:59:17 -03:00
Downtimeandkenrap a28c9c3a82 fix(gui): guard gizmo actions before deferred initialization (#16268)
GLGizmosManager can be called while its gizmo list is empty, either at startup before the canvas has loaded the gizmos or after their init() failed. reset_all_states(), open_gizmo(), open_gizmo_by_shortcut() and activate_gizmo() now return early in that case. This fixes a Debug-build assert when a new project resets the gizmos at startup, and an out-of-bounds read in release builds when a gizmo shortcut key is pressed after gizmo init failed. activate_gizmo(Undefined) still returns true, since no gizmo is open. The reset_all_states() guard was first proposed in #16074 by kenrap.

Co-authored-by: kenrap <101818165+kenrap@users.noreply.github.com>
2026-10-12 18:25:17 -05:00