Compare commits

...
Author SHA1 Message Date
Lam Wei Lun 2c97343811 Replace wxGridCellChoiceEditor::OnComboCloseUp() with wxGridCellChoiceEditor::DismissEditor() 2026-10-11 22:50:07 +08:00
Lam Wei Lun cff514cd15 build(wx): move to upstream wxWidgets 3.3.4
Orca pinned the SoftFever fork at branch v3.3.2. That tracks a moving tip and
leaves the 3.3.3 image-decoder buffer overflows unfixed. Build upstream
wxWidgets at tag v3.3.4 instead and carry the fork's one extra commit as an
in-tree patch.

- deps/wxWidgets: GIT_REPOSITORY -> wxWidgets/wxWidgets, GIT_TAG -> v3.3.4
- add 0002-clang-cl-sibling-compiler-prefix.patch (fork PR #7, WIN32 only):
  the generated wxWidgetsConfig.cmake must search the sibling vc_/clang_
  install layout, because cl and clang-cl share an ABI but wx names the
  layout after the compiler that built it
- flatpak: upstream git source, tag v3.3.4, commit 6a42a4b9
- skill: update the pin, add the 3.3.3/3.3.4 sections, re-base the
  changes.txt line citations, and retire the wxGetTranslation() by-value rule
  (3.3.2 returned by value, 3.3.3 reverted to const wxString&)
2026-10-11 22:19:16 +08:00
HanifKoh 7369674d8f Add #pragma once to Headers Without an Include Guard (#16362) 2026-10-11 17:26:05 +08:00
HanifKoh 70da7e89bb Release the Slice Button's Mouse Capture When a Press Loses Its Release (#16358) 2026-10-11 15:49:04 +08:00
HanifKoh a671bf605d Dismiss the Slice-Plate Popup on Every Path Into the Filament Grouping Dialog (#16359)
#16284 closed the Slice-plate hover popup only when the Slice button opened
the filament grouping dialog. The dialog also opens from the plate's filament
grouping icon, the preview legend, slicing notification links and the send
dialog, and a popup still shown on those paths keeps the mouse capture on
macOS and starves the modal dialog of clicks. Dismiss it in
FilamentMapDialog::ShowModal() instead, so every path is covered.

The popup could also stay up on its own on macOS: an enter event stopped its
close timer, and when the timer fired with the cursor inside it returned
without re-arming, so without a later leave event the popup never closed. On
macOS the timer now keeps running while the popup is shown and closes it once
the cursor is outside. The popup also stops and frees its timer when destroyed.
2026-10-11 15:48:49 +08:00
SoftFever bae8f64b8b Complete and refine Simplified Chinese translations (#16356)
* Complete Simplified Chinese translations

* Fix Simplified Chinese 3D view, camera and extruder terms

* Use Chinese CAD view names on the navigator cube
2026-10-11 15:35:29 +08:00
HanifKoh 2df07b7ed8 Verify the TLS Certificate When Downloading a Cloud Plugin (#16253) 2026-10-11 14:25:50 +08:00
Klober81andRodrigo Faselli 295094a594 Pack bed texture and model into printer bundles (#15965)
* 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>
2026-10-11 13:32:07 +08:00
Ian Bassi 210bc927bc Revert "Add Thai Localization (Educational / Hybrid Translation)" (#16349) 2026-10-10 22:39:45 -03:00
SoftFever 00cfb5b259 Refresh translation catalogs and fix xgettext warnings (#16346) 2026-10-11 08:45:44 +08:00
chettasit 2bd105b0ce Add Thai Localization (Educational / Hybrid Translation) (#15940)
Hi OrcaSlicer Team,I have completed the Thai localization for OrcaSlicer (over 7,000 lines).Please note: This is a "Hybrid Translation" designed specifically for educational purposes. Instead of direct literal translations (which often confuse beginners due to technical jargon), I have included brief explanations of the mechanical and physics principles directly within the UI strings.Example: For "TPU is not supported by AMS", the translation briefly explains why (because the material is soft, rubber-like, and will jam the long tubes).This approach was developed for the EDrobot project in Khon Kaen, Thailand to help STEM students and beginners understand 3D printing concepts immediately without a steep language barrier.[cite: 8] We focus on empowering students to learn robotics and 3D design effectively.[cite: 8]   I understand this makes some strings longer than standard localizations, but it has proven incredibly helpful for beginners. I would be honored if you consider adding this to the official release as the Thai language option. I am happy to maintain this file for future updates.Thank you for building such an amazing slicer!Best regards,Chetsit Srichan (EDrobot)
2026-10-11 08:34:19 +08:00
Ian BassiandRodrigo Faselli b81d761605 Refresh plate tab after bed type change (#16344)
Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
2026-10-10 21:06:29 -03:00
6922edfc20 Fix Anycubic Vyper start G-code hotend wait (#16216)
* Fix Anycubic Vyper start gcode hotend wait

* Bump Anycubic profile version

---------

Co-authored-by: 陈诗健 <chenshijian@chenshijiandeMacBook-Air.local>
Co-authored-by: SoftFever <softfeverever@gmail.com>
Co-authored-by: SoftFever <103989404+SoftFever@users.noreply.github.com>
2026-10-11 07:34:11 +08:00
Javad Shafique 7ec147603f Default Bambu profiles to 3MF output (#14767) 2026-10-11 06:55:50 +08:00
jorymorrison 8ee9e5915a Fix Prusa CORE One pressure advance condition in filament profiles (#16093) 2026-10-11 06:39:53 +08:00
SoftFever cd5b786704 Add AnkerMake PLA+ filament profiles (Basic, Matte, Metallic, Silk, Glitter) (#16293)
# Description

Adds Anker's own AnkerMake PLA+ filament line to the Anker vendor
bundle: **Basic, Matte, Metallic, Silk, Glitter**. Until now the bundle
only had `Generic PLA+ @Anker`.

Each filament follows the existing Anker layout. A `@Anker base` profile
(`instantiation: false`, inherits `Generic PLA+ @Anker base`,
`filament_vendor: AnkerMake`) holds the settings. An instantiated
`@Anker` profile holds only `compatible_printers`. `filament_id` and
`setting_id` were minted with `scripts/orca_profile_tool.py
generate-id`, and `Anker.json` was re-indexed with `update-index`. The
Anker vendor version goes from `02.04.00.05` to `02.04.00.06`.

## Source of the values

The values come from the AnkerMake filament profiles bundled with
**eufyMake Studio** (Anker's PrusaSlicer 2.6 fork, AGPL-3.0, source at
https://github.com/eufymake/eufyMake-PrusaSlicer-Release), in
`profiles/Anker-ini/AnkerMake base/base.ini`. OrcaSlicer is also
AGPL-3.0.

Conversion notes:
- PrusaSlicer keys are mapped to their Orca equivalents. For example,
`bed_temperature`/`first_layer_bed_temperature` go to `hot_plate_temp*`
and `textured_plate_temp*`, `fan_below_layer_time` goes to
`fan_cooling_layer_time`, `slowdown_below_layer_time` goes to
`slow_down_layer_time`, and `extrusion_multiplier` goes to
`filament_flow_ratio`. Only keys that differ from the inherited `Generic
PLA+ @Anker base` chain are written.
- Where Studio sets retraction but leaves deretraction unset (Metallic,
Silk), `filament_deretraction_speed` is set equal to the retraction
speed. That matches Prusa's "0 = same as retract" meaning.
- **Max volumetric speed is new.** Studio ships
`filament_max_volumetric_speed = 0` (uncapped) for these filaments, and
Orca has no equivalent. The caps were derived from the peak flow that
Studio's own M5C "Fast" process pushes: Basic/Matte/Glitter 22 mm³/s,
Metallic 20, Silk 13. Please treat these as the least-validated numbers
in the PR.
- Studio has pressure advance disabled for these filaments, so no PA
values are carried over.
- Colour variants are not included because they differ only in colour.
`default_filament_colour` uses each family's base colour.
- `compatible_printers` follows Studio's `compatible_printers_condition`
for each family, limited to the 0.4/0.6 printers that `Generic PLA+
@Anker` covers:
  - Basic, Glitter: M5, M5 All-Metal, M5C at 0.4 and 0.6
- Silk: 0.4 on all three, plus M5C 0.6 and M5 All-Metal 0.6 (Studio
excludes M5 0.6)
  - Matte, Metallic: 0.4 only (Studio restricts these to 0.4)
- Studio also allows Basic on 0.2 nozzles. That variant is not added,
because the 0.2 Generic profiles use a separate low-flow cap.

# Screenshots/Recordings/Graphs

N/A (profile-only change).

## Tests

- `scripts/check_profile.sh` (local twin of the CI job, run against the
full profile tree with the nightly macOS validator) passes all five
checks: `profile_tool`, `validate_system`, `validate_slice`,
`validate_filament_subtypes`, `validate_custom` (fixtures v1.9.0 to
v2.4.2).
- `python3 -m unittest discover -s scripts/tests -t scripts`: OK.
- The submitter printed with these filament settings in eufyMake Studio
on the M5C's stock brass 0.4 nozzle without issues. This Orca conversion
has been loaded and sliced in Orca 2.4.2 (as user presets inheriting the
Anker generic PLA+) but not yet printed on brass. The printer now has a
different nozzle fitted.

[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-10-11 06:26:26 +08:00
HanifKoh d715bb760c Let Plugin Windows Be Maximized on X11 (#16335)
wxGTK types every wxDialog as a dialog window, and Mutter offers no
maximize for anything but a normal window, so the maximize button and
shortcut did nothing on GNOME under X11. Modeless plugin windows are now
normal windows, kept off the taskbar as before. Modal ones stay dialogs
so the window manager keeps routing focus from the main window to them.
2026-10-11 04:40:56 +08:00
HanifKoh de5f2cb3b9 Expose App, GL, Printer and Project Facts to Plugins (#16324)
* Expose App, GL, Printer and Project Facts to Plugins

Plugins that help users file bug reports could only guess these from log lines: the app config is
deny-listed, and the project path, a project export and the device list had no API.

orca.host gains app_info(), gl_info() and selected_printer(), and Plater gains project_path() and
export_3mf_copy(path). The copy export leaves the project's file name, saved state and model
unchanged, does not write the signed-in account as the designer, and raises the audit event of
open(path, "w") so the plugin gets the same permissions as for writing the file itself.

* Remove app_language in Favour of app_info

app_info() returns the same language. app_language() was added after the last release, so no released build has it.
2026-10-11 04:35:37 +08:00
Riccardo BRAMATI 07a12f5fe4 Read a local plugin's version from its file header (#16118)
* Read a local plugin's version from its file header

read_install_state() let the sidecar's installed_version override the version
parsed from the entry file's PEP 723 header. That is right for cloud plugins,
whose header may be stale, but a local plugin has no other source of truth, so
the UI kept showing the version recorded at first install after the file was
edited. For local installs, use the header version.

* Keep the cloud installed version of an unsubscribed plugin

A cloud plugin kept as local after unsubscribing is rewritten with installed_from=local and no cloud_uuid, which the local-header rule could not tell apart from a real local install. Its installed_version is still the cloud one while the header may lag, so the next scan showed the stale header version. Record the conversion in the install state and exclude those plugins from the header rule. Add tests for a local plugin following its header and for an unsubscribed cloud plugin keeping its cloud version.

* Add tests for the version of a local plugin

A local plugin's version comes from its entry file header. Cover the case of a plugin edited after install, and a plugin unsubscribed from the cloud, which is now a normal local package.
2026-10-11 03:09:32 +08:00
Ian Bassi ea10c84d6b Fix scale and mirror of rotated mirrored objects (#16131)
* 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
2026-10-10 16:03:34 -03:00
SoftFever e5d5fb47ca Fix BBL infill retraction reduction default (#16201)
# Description

Restore `"reduce_infill_retraction": "1"` in the shared Bambu Lab
process profile. The BambuStudio profile sync (#15851) replaced it with
`reduce_infill_retraction_mode`, which OrcaSlicer does not support,
causing the setting to fall back to disabled.

This restores the pre-sync behavior and bumps the BBL bundle version to
02.08.00.13.

# Validation

- All 233 selectable BBL process presets inherit the restored setting
with no other resolved settings change.
- Static profile validation, system loading, filament validation, and
all 1,263 slice cases pass.

 Fixes #16194
<!--
> 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-10 22:44:44 +08:00
SoftFever 63918b44a8 Merge branch 'main' into anker-ankermake-pla-plus-filaments 2026-10-10 22:41:16 +08:00
SoftFever 53c57c94fd Fix the 3D view stuttering every few seconds while signed in to Orca Cloud (#16340)
# Description

Orbiting or panning the 3D view no longer hitches every five seconds on
Windows and macOS while signed in to Orca Cloud. The stutter is a
regression from #15710.

No change to slicing or to the Orca Cloud connection status shown in the
GUI.

# Screenshots/Recordings/Graphs

<!--
> Please attach relevant screenshots to showcase the UI changes.
> Please attach images that can help explain the changes.
-->

## Tests

<!--
> Please describe the tests that you have conducted to verify the
changes made in this PR.
-->

<!--
> A guide for users on how to download the artifacts from this PR.
-->

[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
2026-10-10 22:39:45 +08:00
SoftFever 53eeedb642 Fix the 3D view stuttering every few seconds while signed in to Orca Cloud 2026-10-10 21:15:45 +08:00
SoftFever aa14598e32 iXex: Parallel Printing Support for IDEX and IQEX Printers (#13086)
## Summary

This PR adds first-class parallel printing support to OrcaSlicer for
printers with multiple independent X-axis carriages — the **IDEX** and
**IQEX** hardware families. The feature is named **IMEX** (independent
multi-extruder) internally, which captures the supported topology space
more accurately than either acronym alone, and is exposed in the UI
under the user-facing label **IDEX/IQEX Configuration**. It is designed
to be printer-agnostic and firmware-flexible, with Klipper,
RepRapFirmware, and Marlin all supported for per-tool Pressure Advance
and per-layer temperature management. MMU/AFC setups where multiple
logical filament slots share one physical extruder are supported via a
`physical_extruder_map` profile option. Printers whose firmware handles
copy/mirror placement internally — RepRapFirmware IDEX duplication mode
on Flashforge Creator Pro 2 / Creator 3 Pro is the canonical example —
are supported through a firmware-managed-zones flag that emits a
centered single-half slice for the firmware to fan out.

The implementation spans printer configuration, process settings UI, bed
visualization, per-plate mode selection, ghost-object rendering with
per-plate filament overrides, placement validation, pre-slice conflict
warnings, G-code generation (with per-tool PA, per-layer temperature
management, and optional center-origin slice frame), and layer preview
animation. It supports five distinct topology paradigms:

- **1 gantry × 2–4 tools** — classic IDEX with 2 tools (BCN3D Sigma,
Snapmaker J1, Tenlog Hands-2 style — two independent X carriages on a
shared Y gantry); multi-extruder on a shared rail when extended to 3 or
4.
- **2 gantries × 1 tool each** — two fully-independent XY systems
sharing a bed (Vivedino Xplorer style). Both Y rails independent —
effectively "half an IQEX."
- **2×2 grid, independent quadrants** — IQEX running four separate
prints in parallel.
- **2×2 grid, paired-gantry multicolor** — IQEX where cross-gantry tools
share color responsibilities via the **Span** tile state.
- **Firmware-managed center-origin** — slicer emits a centered
single-half slice; firmware decides where to physically fan it out.
Overlays on any of the above hardware topologies; canonical example is
Flashforge IDEX with RepRapFirmware.

---

## Feature Walkthrough

### Printer Configuration

Printer preset options that declare IMEX capability and geometry:

| Option | Type | Description |
|--------|------|-------------|
| `is_imex` | bool | Marks this printer as IMEX-capable |
| `imex_firmware_managed_zones` | bool | Emit centered single-half
slice; firmware handles copy/mirror placement (default `false`) |
| `imex_gantry_count` | int | Number of independent Y-axis gantries
(rows) |
| `imex_tools_per_gantry` | int (1–4) | Toolheads per gantry along X
(columns) |
| `imex_nozzle_clearance_x` / `imex_nozzle_clearance_y` | float |
Nozzle-to-collision-edge distance in mm (literal, not halved) |
| `imex_tool_layout` | string | Physical orientation: front-left /
front-right / rear-left / rear-right |
| `imex_mode_names` | string[] | Names of user-defined parallel modes;
first entry is the reserved `primary` sentinel |
| `imex_mode_active_tools` | string[] | Tool role assignments per mode
(e.g. `0:P,1:C,2:M,3:M`, or `0:P,1:S,2:M,3:M` with Span) |
| `imex_mode_gcodes` | string[] | Firmware macro to activate per mode |
| `physical_extruder_map` | string[] | 0-indexed map from logical
filament slots to physical extruders (for MMU/AFC — see below) |

Tool roles per mode: **Primary** (P), **Copy** (C), **Mirror** (M),
**Span** (S), **Inactive**.

### Parallel Mode Editor

[Kooha-2026-05-14-13-45-35.webm](https://github.com/user-attachments/assets/af7650e6-d636-478b-9398-3f02ec662f03)

The **IDEX/IQEX Configuration** and **IDEX/IQEX Parallel Modes**
sections live in the printer preset's **Multimaterial** page (visible
only when `is_imex = true`). The mode editor is a visual grid:

- Each row defines one named parallel mode. The first row is a reserved,
non-deletable **Primary** row stored as the sentinel `primary` in
`imex_mode_names`.
- The Primary tool is always T0; **Tool 0 Position** chooses which
physical corner it occupies, and its tile is read-only. The other tool
buttons cycle Inactive → Copy → Mirror → Span → Inactive, color-coded,
with Span offered only where it applies.
- **Span** is only offered when `imex_gantry_count >= 2` AND the tile is
on the primary's gantry row — it declares "this tool is the multi-color
partner of Primary on the same gantry," distinct from a Copy/Mirror role
on a non-primary gantry.
- A G-code field per mode holds the firmware macro to activate that mode
(e.g. `IMEX_COPY` for Klipper). A placeholder-browser button per row
opens the `EditGCodeDialog` for quick insertion.
- Modes can be added and removed (removed via a dedicated
`imex_remove.svg` ScalableButton).
- Deleting the active mode resets affected plates to Primary.
- Tool assignments are preserved across `imex_gantry_count` changes:
going from IQEX (4 carriages) → IDEX (2 carriages) → IQEX restores all
previously assigned roles, and the visible grid anchors to the gantry
row containing the Primary assignment.

<!-- TODO: replace this placeholder with a re-recorded
parallel-mode-editor video
showing the Span tile state, the remove button, and the
placeholder-browser button.
Old URL (out of date):
https://github.com/user-attachments/assets/cabf1f70-206d-4f84-9c2e-594b38b83951
-->

### Span Tile State — Paired-Gantry Multicolor
<img width="2833" height="1300" alt="2026-05-14-135412_grim"
src="https://github.com/user-attachments/assets/52203133-7380-459a-973f-0502a1edc3c2"
/>

For IQEX printers (`imex_gantry_count >= 2`), the **Span (S)** tile role
enables paired-gantry multicolor mirror mode. The same active-tools
string `0:P,1:C,2:M,3:M` is ambiguous between two distinct hardware jobs
— four independent quadrants vs. paired-gantry multicolor mirror — so
the topology has to be declared explicitly rather than inferred. Span on
the primary's gantry row is the declaration.

Behavior driven by Span presence:

- **Multicolor block rule**: requires Span on primary's gantry to allow
multi-color slicing in a parallel mode. Without Span, multi-color
slicing in a parallel mode is blocked with an actionable error message.
- **Ghost aggregation**: one aggregated ghost per non-primary gantry
when Span is present, using the column-paired representative tool.
Mixed-role gantries fall back to per-tool.
- **Zone aggregation**: one row-strip zone per non-primary gantry when
Span is present (cell at primary's column collapses col-sep;
`make_boxes` expands to full-X strip).
- **Aggregated mirror drag**: ghost translates 1:1 with primary in X
(copy-style), with X-flip baked into the mesh-local frame so geometry
still reads as mirrored. Gantries don't share an X rail, so reflecting
motion serves no collision purpose.
- **Carriage collision strip audit**: X-boundary checks require `zr ==
pri_row_k` (matching the existing Y-boundary `c == pri_col` constraint).
Prevents spurious strips on primary's right edge in paired-gantry
mc-mirror, where T3 sits diagonally and can't actually collide with
primary's carriage.

Single source of truth:
`IMEXHelpers::group_imex_active_tools_by_gantry(active_tools_str,
tools_per_gantry)`. Ghost factory and zone calculator both consume it,
so pairing logic lives in one place.

### Per-Plate Mode Selection

Each build plate has an IMEX mode icon in its toolbar (normal, hover,
dark, and dark-hover SVG variants). The mode can be set independently
per plate:

- **Left-click** cycles through all non-sentinel modes in order.
- **Right-click** opens a popup menu listing all modes as radio items
for direct selection.
- Mode changes are recorded in the undo/redo snapshot system.
- The selected mode is persisted in the 3MF project file per plate
(`imex_parallel_mode` key).


https://github.com/user-attachments/assets/5ab497e3-9c2f-476a-ac67-ef34a592395b

When a parallel mode is active alongside multi-material objects on the
same plate, a warning badge (`obj_warning.svg`) overlays the plate icon
— see **Pre-Slice Warning System** below.

### Bed Visualization + Auto-Arrange Constraints

When an IMEX mode is active, the build plate renders the carriage grid:

<img width="2761" height="1447" alt="IMEX bed visualization with active
primary zone and dimmed secondary zones"
src="https://github.com/user-attachments/assets/3ee64b7b-a3ed-40aa-ad07-fa2f1cb74edf"
/>

- The **active (primary) zone** is full brightness.
- **Inactive zones** are dimmed with a color-coded overlay (blue for
copy, orange for mirror).
- **Zone dividers** are rendered as lines across the bed.
- The grid is a full 2D layout: `imex_tools_per_gantry` columns ×
`imex_gantry_count` rows. Zone sizing is based on the **active** tool
count only — inactive tools donate their bed share to active neighbors.
- Span-present configs render as a single row-strip zone per non-primary
gantry rather than per-tool quadrants, reflecting that the firmware will
paint the multi-color across the entire strip.
- Colors are drawn from the Okabe-Ito palette for colorblind
accessibility, with a deuteranopia/protanopia-safe alternate theme.
- **Auto-arrange** is constrained to the primary zone when a parallel
mode is active — `ArrangeJob::process()` replaces full-bed `bedpts` with
the primary zone corners via `PartPlate::imex_primary_zone()`. Collision
strips are additionally registered as hard obstacles through
`m_unselected` (the working NFP placer input, not the dead
`excluded_regions` field), so placement cannot drop parts into the
danger strips.

### Placement Validation


https://github.com/user-attachments/assets/5cb5a94a-981e-4fe9-898e-7e2f891b4f40

Objects placed outside the primary zone block slicing:

- `has_imex_placement_violations()` in `PartPlate` checks each object's
convex hull against the primary zone boundaries and collision strips.
- Violations inject into the existing `update_background_process`
validation pathway — the Slice button is disabled and an error
notification is shown.
- Mirror tools additionally generate X-axis collision strips (copy tools
move in the same direction and cannot collide). Y-direction strips are
scoped to same-column tools to avoid false positives from diagonal
mirror pairs.
- Strip width is taken **literally** from `imex_nozzle_clearance_x` (the
measurement is nozzle-to-collision-edge distance, not carriage
half-width).
- Multi-color block path: a pre-slice rule blocks multi-color slicing in
parallel modes that can't physically support it (e.g. a "fake IMEX" mode
where all tools sit on a single gantry without a Span partner). The rule
is centralized in `imex_multicolor_block_reason()` with unit-test
coverage of every gating case.

### Ghost Object Rendering + Per-Plate Filament Picker

When a parallel mode is active, the slicer renders colored, transparent
**ghost copies** of primary-head instances on the plate — one per
secondary active head, transformed under its Copy/Mirror role (or
aggregated per gantry when Span is present).

- Ghosts track the primary through drag/rotate/scale/mirror and
invalidate on mode, filament-map, or `physical_extruder_map` changes.
- **Left-click** on a ghost opens the `IMEXFilamentPickerPopover` for
that ghost's head — a compact `BitmapComboBox` that writes directly to
the per-plate `imex_head_filament_map` (MMU lane override).
- Mirror ghost geometry is a **true reflection about the zone-boundary
plane** (`x = primary_zone_center.x + gantry_offset.x/2`), so the ghost
stays anchored in the target zone as the primary moves and drag reflects
correctly (primary +X → ghost −X, Y tracks 1:1).
- Hover tooltip: `"Tn → filament N"` with a color swatch. When no
filament resolves to a head, the tooltip surfaces an actionable message
directing the user to extend the extruder count in the Machine tab.
- Ghost rendering + picking are scoped to the active plate — non-active
plates don't draw stale ghosts during arrange/preview transitions, and
click-picking never falls through to a non-current plate's ghost
geometry.
- Per-plate `imex_head_filament_map` round-trips through the 3MF project
file.

### Pre-Slice Warning System
<img width="1786" height="911" alt="2026-05-14-135714_grim"
src="https://github.com/user-attachments/assets/16532d47-2da5-4472-ab9c-4b8b00303bae"
/>

Before a plate slices, IMEX parallel-mode plates are checked for three
classes of conflict:

1. **Multi-material on secondary tools** — when the mode has Copy/Mirror
tools and the plate has multiple filaments active; plate icon gets a
warning badge.
2. **Bed temperature mismatch** — any two carriages configured >5 °C
apart.
3. **Filament type incompatibility** — filaments from different type
families on active carriages.

A dismissible Yes/No `RichMessageDialog` fires once per user action from
both `on_action_slice_plate` and `on_action_slice_all`. The dialog has a
**"Don't show again"** checkbox that persists to `app_config` as
`imex_pre_slice_warnings=false`. A re-enable toggle lives in **Printer
Settings → Multimaterial → IDEX/IQEX Configuration** so suppressed
warnings can be restored.

### Firmware-Managed Zones — Center-Origin Slice
<img width="701" height="197" alt="2026-05-14-135842_grim"
src="https://github.com/user-attachments/assets/3e2dfaab-fb4f-42b1-bf9d-c88dc0c95110"
/>

The `imex_firmware_managed_zones` printer-config option (default
`false`) supports IDEX/IQEX printers whose firmware applies its own
copy/mirror offsets in non-primary modes. Canonical examples:
**RepRapFirmware IDEX duplication mode** on **Flashforge Creator Pro 2 /
Creator 3 Pro**. These printers expect a centered single-half slice at
bed origin and fan toolheads out from there — the slicer-managed
paradigm of placing toolpaths at zone-relative positions produces gcode
the firmware can't reconcile, since it'd double-apply offsets.

When the flag is on and the active mode is non-primary, the slicer
subtracts the primary zone's plate-local center from the gcode emission
frame:

- **Writer offset** is augmented by the primary zone center so emitted
gcode is centered at bed origin.
- **Processor offset** stays at plate_origin only, so the gcode-preview
visualizer renders the centered toolpath at bed center rather than at
the prepare-view zone placement. The user sees what'll physically print
after firmware fan-out.
- **`translate_to_print_space()`** is augmented for frame coherence so
`first_layer_print_min/max` placeholders consumed by user start_gcode
(e.g. Flashforge's M118 "max delta from zero" header) reflect the
centered frame.

Slice-handoff runs per-slice: `PartPlate::refresh_imex_slice_offset()`
is called from `Plater::priv::update_background_process` after
`Print::apply()`, so reslicing with mode toggled but no plate change
picks up the updated offset.

When the flag is off, all related code paths reduce to no-ops
byte-identical to standard slicer-managed behavior. The two stock-code
touches at `Print.cpp:2553-2554` (gcode_offset composition) and
`Print.cpp:2823-2829` (translate_to_print_space) are explicit additive
shifts that collapse to identity when the offset is `Vec2d::Zero()`.

### G-code Injection

The selected parallel mode's G-code is written into the output file
immediately before `machine_start_gcode`:

- Looks up the plate's active mode name in `imex_mode_names` and writes
the corresponding `imex_mode_gcodes` entry.
- Processed through `placeholder_parser_process()` first, so
Klipper-style variable substitution works **and** any `{global}`
declarations flow forward into `machine_start_gcode`.
- Primary mode's G-code field is emitted too.
- Guarded against headless CLI slicing — `ensure_imex_zones()`
short-circuits when `m_plater` is null so the CLI path (used by
upstream's regression-test CI step) doesn't segfault on
`wxGetApp().preset_bundle` dereference.

### Placeholder Parser Integration
<img width="535" height="393" alt="2026-05-14-140227_grim"
src="https://github.com/user-attachments/assets/6d059c77-38f6-4418-bfe9-3bf963e8c836"
/>

Three placeholders register under **Slicing State** and are settable
from anywhere downstream:

| Placeholder | Type | Description |
|---|---|---|
| `imex_mode` | string | The active mode name |
| `imex_mode_index` | int | Index into `imex_mode_names` |
| `imex_mode_gcode` | string | The resolved mode G-code (post-parser) |

### Per-Tool Pressure Advance — Firmware-Agnostic

`set_pressure_advance()` takes an optional tool index (default `-1`,
preserving existing behavior for all non-IMEX call sites). Per firmware:

- **Klipper**: `EXTRUDER=extruder[N]` when `tool >= 0`, bare command
otherwise
- **RepRapFirmware**: `M572 D<N>` when `tool >= 0`, `M572 D0` otherwise
(preserves the pre-IMEX output; a bare `M572` applies to whatever tool
is selected and errors when there is none)
- **Marlin 2**: `M900 K<X> T<N>` when `tool >= 0`, bare `M900` otherwise
- **Marlin Legacy / fallback**: `M900 K<X>` always
- **Repetier**: `M233 X<X> Y<X>` (X is quadratic, Y is linear; same
value applied to both)

`m_imex_parallel_mode` is set once per export from the active plate
mode. PA tool-qualification is gated on this being a **non-primary**
parallel mode — primary-mode prints emit ordinary tool-change PA exactly
like any non-IMEX printer. Secondary active tools in parallel modes
receive explicit per-tool PA at print start since they never go through
a tool-change sequence.

### Per-Layer Temperature Management

In IMEX parallel modes, all active tools (primary + secondaries) get
temperature commands in `layer_change_gcode`:

- Layer 1 temperatures only emit on the first layer; subsequent layers
use normal layer-change temperatures.
- IMEX temperature handling is consolidated into the second-layer
transition.
- Layer-change temperature commands use `M104 T<N>` with
`physical_extruder_map` translation when applicable.

### MMU / AFC Support via `physical_extruder_map`

For printers where multiple logical filament slots share one physical
extruder (MMU, AFC, toolchangers), the `physical_extruder_map` profile
option translates tool slot indices to physical extruder qualifiers
before G-code emission.

- **Fallback**: on IMEX printers, `Print::apply()` uses the profile's
map only when it has one entry per extruder (the `nozzle_diameter`
count). Anything else, including the single-entry default, is replaced
by the identity map `0..n-1`.
- **Used by**: IMEX PA emission, layer-change temperature commands
(`M104 T`, `EXTRUDER=`, `M572 D`), ghost color resolution, ghost cache
key, tooltip lookup, click gate.
- **Profile authoring example** for a 7-slot printer with a 4-lane MMU
on extruder 0 and three independent direct drives on extruders 1/2/3:
  ```json
  "physical_extruder_map": ["0","0","0","0","1","2","3"]
  ```
  Non-MMU printers need no action — the identity fallback handles them.
- A centralized helper in `IMEXHelpers`,
`effective_physical_extruder_map(explicit_pem, nozzle_count)` (with a
`PresetBundle` overload that prefers the project's map over the
printer's), routes all PA/temp/ghost-color lookups through a single code
path.
- **No UI for editing the map** in this PR — non-trivial MMU/AFC layouts
require profile-authoring (hand-edit the printer JSON) or an updated
printer profile shipped by a vendor. A future enhancement would expose a
per-slot extruder picker in the Multimaterial section.

### Layer Preview Multi-Carriage Animation

The sequential preview (scrubber) animates all active carriages
simultaneously:

- One toolhead marker (colored cube) per active carriage, in addition to
the primary.
- Secondary marker colors: cyan (T1), yellow (T2), magenta (T3).
- **Copy** tools: marker placed at the same relative position within
their bed zone as the primary is in the primary zone.
- **Mirror** tools: reflect across the target zone's facing edge
(left-of-copy reflects across copy zone's left edge; right-of-copy
across the right edge). Y position is always zone-relative copy (all
tools on a row share a physical Y rail).
- Carriage footprint boxes use per-carriage `box_offset_x/y` so the
nozzle marker sits at the physically correct edge of the footprint —
zone-based X by default, collision-side edge for Mirror; gantry-behind Y
for back-row primaries, flipped for front-row primaries with a back-row
secondary.
- The filament usage legend notes the active carriage count and mode
name (e.g. `IMEX: ×2 (copy_mode)`).
- **View menu toggle**: `View → Show IDEX/IQEX Toolhead` (visible only
on the Preview tab, only when the active printer is IDEX/IQEX)
hides/shows the per-carriage toolhead representation during preview
playback. State persists to `app_config` as `show_imex_toolhead_boxes`.
Useful when scrubbing through dense toolpaths and the boxes get in the
way of seeing the underlying geometry:


[Kooha-2026-05-14-14-04-09.webm](https://github.com/user-attachments/assets/9869cd4d-3b60-4a6b-8dca-7ee1fc573ac1)

Copy mode:


https://github.com/user-attachments/assets/0774d285-ba36-4b86-a594-fb6572c3aede

Mirror mode:


https://github.com/user-attachments/assets/51528926-94dc-4d90-9b1a-1b42d9044be7

---

## Known Limitations

**Per-layer G-code collision detection**
The placement-time zone check catches gross violations (object placed in
wrong zone) but does not verify that toolpaths on any given layer
maintain adequate X separation between adjacent carriages. A per-layer
check via `ConflictChecker` was designed but deferred. Without it, a
print that passes placement validation could still crash carriages if
the primary object's toolpaths reach too close to a zone boundary.

**Brim avoidance of IMEX zones**
Standard `bed_exclude_area` exclusion zones are already respected by
brim generation. IMEX collision strips are not — the brim generator has
no visibility into them. The correct fix (feeding computed strip
polygons from `PartPlate` through the `Print` object to `Brim.cpp`) was
designed but deferred. In practice, users should leave adequate
clearance between printed objects and zone boundaries to account for
brim width.

**`extruder_printable_area` integration**
IMEX zones are not clipped against per-extruder printable polygons, and
there is no violation check for placing an object outside the
intersection of its active extruder's printable area and the IMEX
primary zone. Deferred pending clarification on the tool→extruder index
mapping.

**Ghost rendering in firmware-managed mode**
When `imex_firmware_managed_zones` is on, ghost rendering is suppressed
entirely. The existing `imex_head_transform` math is slicer-managed
semantics (places ghosts at `primary_zone_center + gantry_offset`) and
produces wrong positions when the toolpath is being emitted in a
centered frame. Proper firmware-managed ghost rendering — showing where
copies/mirrors will physically print after firmware fan-out — needs new
transforms designed around firmware-frame positions rather than a
coordinate-flip of the slicer-managed ones. Deferred to a follow-up.

**Filament-accurate multi-region ghost color**
For paired-gantry multicolor mirror (Span mode), the aggregated ghost
currently uses the representative tool's filament color as a single
solid swatch. A future enhancement would render the ghost split into
per-source-tool regions, each colored by the secondary tool that mirrors
it. Slicer-side correctness is already shipped — the G-code emits the
right T-codes; the ghost is a visual aid only.

**Ghost path overlays**
Secondary carriage toolpaths are not rendered in the layer preview. The
secondary markers animate correctly, but the paths they would trace are
not drawn. Adding ghost path rendering would require duplicating and
offsetting the toolpath geometry per secondary carriage, which is a
significant addition to the libvgcode rendering pipeline.

**No per-mode slicing**
All carriages in a mode execute the same sliced toolpaths (transformed
per zone). There is no support for slicing different objects for each
carriage independently within one mode.

**Global default IMEX mode**
There is no job-level IMEX default mode. Each plate's mode must be set
individually (default is always Primary). A future improvement would add
a global default in the sidebar (following the same pattern as bed type
and nozzle diameter), with per-plate overrides.

**Dynamic GL-rendered mode icons**
The per-plate icon currently uses static SVGs. A GL-rendered
carriage-grid icon that visually represents the mode's tool layout would
be a nicer UX but is deferred.

**Slice-all thumbnail icon refresh**
On the "Slice All" path, plates flagged as IMEX-violated do not refresh
their toolbar icon. Root cause and fix identified, not yet applied.

**Start-G-code filament placeholder**
The resolved per-head filament map is not yet exposed as a
`PlaceholderParser` vector. Exposing it would let MMU firmware macros
pre-load lanes before print start.

**No UI for `physical_extruder_map` authoring**
The MMU/AFC slot-to-physical-extruder map is currently profile-only —
there's no in-app dropdown or editor for it. Users with non-trivial
MMU/AFC setups must hand-edit the printer-preset JSON (or rely on a
vendor-supplied profile). A future enhancement would expose a per-slot
extruder picker in the Multimaterial section so users can declare the
mapping without touching JSON.

**Bundled IQEX printer profile**
A full IQEX printer profile with cover image, bed mesh, and matched
process/filament profiles is not bundled with this PR. Users must
currently author their own printer preset. Deferred to a follow-up
profile-only PR.

**Mode lifecycle gap**
Per-plate mode is stored as a string (the mode name). If a mode is
renamed or deleted from the printer preset after a project is saved, the
plate's saved mode name will not resolve and will silently fall back to
Primary on next load. A warning on load would be a useful addition.

**Firmware-managed prepare→preview frame jump**
In firmware-managed-zones mode, the prepare view shows the part at its
placed-in-zone position while the preview view shows the centered slice.
This is intentional — the views truthfully represent the prepare frame
vs. the post-firmware-fan-out frame — but the visual jump can be
confusing on first use.

---

## Firmware Assumptions

- The implementation assumes the firmware handles all carriage
synchronization and offset math. OrcaSlicer only injects the
mode-activation macro before machine start and is entirely dependent on
user configuration.
- Per-tool Pressure Advance is firmware-aware (Klipper / RRF / Marlin 2
/ Marlin Legacy / Repetier). Layer-change temperature commands use `M104
T<N>` with `physical_extruder_map` translation.
- Tested against Klipper on a real IQEX printer. RepRapFirmware gcode
emission is exercised via profile-driven slice tests (Flashforge Creator
Pro 2 profile); Marlin and RRF paths have not been validated on real
hardware print runs.
- Mid-print mode switching is explicitly not supported. The mode is
locked at print start.
- Firmware-managed-zones mode requires the printer's firmware to
translate centered slice coordinates into physical toolhead positions;
the slicer does not attempt to model the firmware's offset logic.

---

## Files Changed (High-Level)

| File | Change |
|------|--------|
| `src/libslic3r/PrintConfig.{cpp,hpp}` | IMEX config option definitions
+ declarations, `IMEXMode` enum, `physical_extruder_map`,
`imex_head_filament_map` (plate option), `imex_firmware_managed_zones` |
| `src/libslic3r/Preset.cpp` | IMEX keys registered in printer and
process preset option lists |
| `src/libslic3r/PrintApply.cpp` | `physical_extruder_map` identity
fallback on IMEX printers |
| `src/libslic3r/Print.{cpp,hpp}` | IMEX slice-offset field + accessors;
`translate_to_print_space` augmentation for frame coherence |
| `src/libslic3r/GCode.{cpp,hpp}` | Mode G-code injection, per-tool PA
emission (firmware-agnostic), per-layer temperatures for all active
tools, `m_imex_parallel_mode` state, `set_gcode_offset_with_imex_shift`
writer/processor split |
| `src/libslic3r/GCodeWriter.{cpp,hpp}` |
`set_pressure_advance(tool_index = -1)` per-firmware implementation |
| `src/libslic3r/IMEXHelpers.{cpp,hpp}` | `imex_head_transform`
(Primary/Copy/Mirror), `parse_imex_active_tools`,
`imex_primary_tool_for_mode`, `effective_physical_extruder_map`,
per-head filament resolution, `group_imex_active_tools_by_gantry` (Span
pairing), `compute_imex_slice_offset` (firmware-managed) |
| `src/libslic3r/Format/bbs_3mf.cpp` | Per-plate IMEX mode and
`imex_head_filament_map` serialization |
| `src/slic3r/GUI/Tab.{cpp,hpp}` | `IMEXModesCtrl` in Multimaterial
page, tool assignment persistence across gantry count changes, `primary`
sentinel handling, Span tile state, firmware-managed-zones checkbox,
null guard in `clear_pages()` |
| `src/slic3r/GUI/PartPlate.{cpp,hpp}` | Zone visualization (Span
row-strip aggregation), placement violation detection, per-plate mode
icon, ghost volume rebuild on mode/map/object mutation, warning-badge
overlay, pre-slice warning collection, `refresh_imex_slice_offset()`,
ghost suppression in firmware-managed mode, headless-CLI guard |
| `src/slic3r/GUI/Plater.cpp` | Validation pathway injection, per-plate
mode popup, ghost click handling, pre-slice warning dialog with "Don't
show again", IMEX multimaterial conflict routing, per-slice IMEX offset
refresh in `update_background_process` |
| `src/slic3r/GUI/GLCanvas3D.cpp` | Ghost rendering with per-head
filament color and translucent blending, picking via volume composite
id, active-plate scoping |
| `src/slic3r/GUI/GCodeViewer.cpp` | Multi-carriage marker animation,
Mirror math fix, carriage footprint box offsets, legend annotation |
| `src/slic3r/GUI/IMEXFilamentPickerPopover.{cpp,hpp}` | Ghost-click
filament picker |
| `src/slic3r/GUI/Jobs/ArrangeJob.cpp` | Auto-arrange constrained to
IMEX primary zone + collision strip exclusion via `m_unselected` |
| `src/slic3r/GUI/OG_CustomCtrl.cpp` | Empty `option_set` guard for
widget-only lines |
| `resources/images/plate_imex_mode*.svg`, `imex_remove.svg` | Per-plate
mode icons (light/dark/hover variants), remove button |
| `tests/libslic3r/test_imex_helpers.cpp` | Coverage for IMEX head
transforms, `effective_physical_extruder_map`,
`imex_multicolor_block_reason` gating,
`group_imex_active_tools_by_gantry` Span pairing,
`compute_imex_slice_offset` |

---

## Testing Notes

Validated on an IQEX printer (4 carriages, 2×2 grid, Klipper firmware)
and against the Flashforge Creator Pro 2 profile (2-tool IDEX, RRF
flavor, center-origin bed) with the following configurations:

- **Primary only** — baseline, no regression vs. standard
single-extruder workflow
- **Copy mode** (T0 Primary, T1 Copy, same row) — carriage markers
animate in sync offset by strip width; ghost tracks drag/rotate/scale
- **Mirror mode** (T0 Primary, T1 Mirror, same row) — T1 marker reflects
T0 across zone-boundary plane; ghost drag reflects X correctly while Y
tracks 1:1
- **Cross-row copy + mirror** (T0 Primary row 0, T2 Copy row 1, T3
Mirror row 1) — T2 follows T0's zone-relative position; T3 mirrors T2's
X; Y shared per row
- **4-tool copy mode** (T0 Primary, T1/T2/T3 Copy) — zone sizing
correct, all four markers + ghosts render in sync
- **Paired-gantry multicolor (mc-mirror, Span)** — `0:P,1:S,2:M,3:M`:
ghost aggregates to one per non-primary gantry, X-flipped, drag tracks
1:1 with primary; collision strip audit doesn't fire spurious strips on
primary's right edge
- **Firmware-managed copy mode** (Flashforge Creator Pro 2) — emitted
gcode coordinates are centered at bed origin (X = −cube_half_width …
+cube_half_width), `first_layer_print_min/max` placeholders evaluate
symmetrically, M118 header produces correct "max delta from zero" values
for Flashforge's existing template, ghosts suppressed
- **Firmware-managed flag toggle** — flipping the checkbox off restores
slicer-managed iMEX behavior (toolpath at zone position)
byte-identically; flipping on restores center-origin slice
- **Delete active mode** — no crash; plate resets to Primary
- **Placement outside primary zone** — slicing blocked with error
notification
- **Multi-material conflict warning** — plate icon gets warning badge;
Yes/No dialog fires on slice; "Don't show again" checkbox persists
- **Multi-color block rule** — `0:P,1:C` on a single gantry blocks at
slice time with actionable error message
- **Ghost filament picker** — left-click on ghost opens picker;
selection writes to `imex_head_filament_map` and round-trips through 3MF
save/load
- **Per-plate mode selection** — left-click cycles modes, right-click
shows popup, undo/redo correctly reverts mode changes, mode persists
through project save/load
- **Gantry count change round-trip** — reducing from IQEX (4 carriages)
to IDEX (2 carriages) and back restores all previously assigned tool
roles
- **MMU/AFC `physical_extruder_map`** — explicit map routes PA and
temperature commands to correct physical extruder qualifiers;
auto-derive fallback preserves 1:1 behavior for non-MMU printers
- **Headless CLI slicing** — `orca-slicer --slice project.3mf` does not
segfault on IMEX-enabled printers
- **Dark mode** — all four plate-icon variants render correctly
- **Pre-slice warnings suppress + restore** — `app_config` flag flips on
checkbox; re-enable toggle in Multimaterial config restores dialog
- **Stock non-IMEX printer regression check** — slicing a single-head
Voron Trident 350 profile produces byte-identical gcode vs. baseline;
the firmware-managed flag and Span tile state both no-op when not
applicable
- **Full unit test suite** — 247/247 passing, including Layer 1 coverage
for `compute_imex_slice_offset`, `group_imex_active_tools_by_gantry`
Span pairing, and `imex_multicolor_block_reason` gating
2026-10-10 20:34:56 +08:00
Kris Austin cf5f7b778d Show OK instead of Yes on notice dialogs (#16332)
Several warning and info dialogs passed wxYES as their only button, so
the only choice was "Yes" even though nothing is asked. Use wxOK.

None of the callers act on wxID_YES. They ignore the result, except
Field.cpp, which only checks that it's nonzero, and every button id is.
wxYES_DEFAULT is 0, so dropping it next to wxOK changes nothing.

Also drop the empty `if (ShowModal() == wxID_YES) {}` in
Plater::priv::load_files.
2026-10-10 15:30:17 +03:00
SoftFever b14ed3e0c1 Multi nozzle UI Fixes / Improvements (#16075)
# New multi nozzle icon for guidance

new icon has tooltip for guidance
<img width="407" height="77" alt="Screenshot-20261005181353"
src="https://github.com/user-attachments/assets/f27bbf79-7570-4f30-ab3c-52fa972c9fa3"
/>

**Before**
<img width="414" height="228" alt="Screenshot-20261002144627"
src="https://github.com/user-attachments/assets/7941891a-5a27-478b-a63f-49cf386e430f"
/>

**After - Moved icon to pre label position**
• reduces possibility of wrapping of labels
• Aligns labels and improves readability

<img width="399" height="239" alt="Screenshot-20261005181511"
src="https://github.com/user-attachments/assets/93f6b555-bbec-496a-b708-8c12c3b3185b"
/>

# Fix > Modified state not applied on tabs
**Before - modified colors not applied to control**
<img width="751" height="157" alt="Screenshot-20261001004828"
src="https://github.com/user-attachments/assets/0c149fce-45e8-498f-8daa-7b54803399e9"
/>

**After**
<img width="752" height="150" alt="Screenshot-20261005181614"
src="https://github.com/user-attachments/assets/5a930bb2-6487-4987-b58d-850de6ae6779"
/>

Applied color as border because changing text color was not readable
<img width="159" height="71" alt="Screenshot-20261002152522"
src="https://github.com/user-attachments/assets/f694fc8d-c9f6-46d7-9e06-7762003c9f20"
/>

and applying as background color was too eye catchy
<img width="184" height="80" alt="Screenshot-20261002152533"
src="https://github.com/user-attachments/assets/2b67c1dd-8977-46ea-88fc-ce57bfad55bd"
/>

# Usable tabs on Printer settings
• Fix > Using long string makes "Motion ability" and "Notes" tab
unreachable
• Matches T1,T2.. terminology with nozzle section
• Makes https://github.com/OrcaSlicer/OrcaSlicer/pull/14737 available to
use now
 
**Before**
<img width="751" height="126" alt="Screenshot-20261002152643"
src="https://github.com/user-attachments/assets/0241d3c4-acc1-491e-ab85-fa60385c5e85"
/>

**After**
<img width="748" height="129" alt="Screenshot-20261002152628"
src="https://github.com/user-attachments/assets/c475d6c8-4f0f-47df-aa04-9d894b1c358d"
/>

No issue with 8 items
<img width="748" height="124" alt="Screenshot-20261002153018"
src="https://github.com/user-attachments/assets/9a8c4e12-2cdc-4508-b82b-134476589d80"
/>

# Dynamic Padding
• buttons uses dynamic horizontal padding now depends on item count.
reduces padding size to reduce scrolling

**Before**
<img width="416" height="91" alt="Screenshot-20261002153309"
src="https://github.com/user-attachments/assets/977b22db-e686-4967-95be-13772d77cefd"
/>

**After** 
<img width="403" height="79" alt="Screenshot-20261005181728"
src="https://github.com/user-attachments/assets/9aac0d17-0729-408f-9237-62b17236488f"
/>

**Before**
<img width="745" height="153" alt="Screenshot-20261002153659"
src="https://github.com/user-attachments/assets/06951759-419a-40fb-8fc2-d93f5b04d6ea"
/>

**After** 
<img width="748" height="149" alt="Screenshot-20261005181833"
src="https://github.com/user-attachments/assets/9ccec344-1dd0-4b03-ac12-20d298f413c8"
/>

**After** 
<img width="756" height="178" alt="Screenshot-20261009144637"
src="https://github.com/user-attachments/assets/1343c39e-5cbd-4f9e-b501-df3797579961"
/>
<img width="761" height="198" alt="Screenshot-20261009144459"
src="https://github.com/user-attachments/assets/efb1ef75-a602-4d3a-a17f-9354ed633357"
/>
<img width="758" height="196" alt="Screenshot-20261009144510"
src="https://github.com/user-attachments/assets/df1d88c5-1b6f-4748-a6bd-a96659a83fe0"
/>
<img width="764" height="190" alt="Screenshot-20261009154210"
src="https://github.com/user-attachments/assets/b876ed71-5fa0-47e4-83c8-a33100e0a229"
/>

# Keeping compact look & Making it more readable
• Removed ":" from labels to gain space as much as possible and
increased font size a bit but UI didnt allowed bigger font

• Matched font sizes of nozzle section while keeping height of items.
Previous font was too small. removed "mm" text to gain more space but
added it in tooltip
**Before-After** 
<img width="800" height="240" alt="Screenshot-20261002154132"
src="https://github.com/user-attachments/assets/bc4ddcb1-2248-4fc7-9492-ab4fb68cb659"
/>

• Matched font sizes for BBL nozzle selectors
**Before-After** 
<img width="806" height="200" alt="Screenshot-20261002154513"
src="https://github.com/user-attachments/assets/b3d38317-8e4d-4c1e-a490-ce4959ed700a"
/>

<img width="794" height="156" alt="Screenshot-20261002154649"
src="https://github.com/user-attachments/assets/00917584-8e1a-44b0-8c0b-79cec30059bf"
/>

# Layout and Styling fixes
• Matched background color of non active buttons with (Global/Objects)
switch
**Before**
<img width="405" height="159" alt="Screenshot-20261002154947"
src="https://github.com/user-attachments/assets/8a8adcc8-431a-48be-aabd-a981335b6ab9"
/>

**After** 
<img width="407" height="159" alt="Screenshot-20261005182106"
src="https://github.com/user-attachments/assets/7f837be3-ce85-49e6-909a-05350b3d8b30"
/>

# Other effected dialogs
<img width="541" height="195" alt="Screenshot-20261002165003"
src="https://github.com/user-attachments/assets/109abbc3-d7c0-4384-9b85-d943e2027963"
/>
<img width="547" height="308" alt="Screenshot-20261002165307"
src="https://github.com/user-attachments/assets/951d9a8d-56f9-4659-aebe-77f236e8c838"
/>


<!--
> 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-10 20:27:37 +08:00
packerlschupfer d59155b27f Moonraker: pass print=true in upload — fix Upload & Print race (fixes #14945) (#15032)
* Moonraker: pass print=true in upload — fixes Upload & Print race with power-on-upload

Closes #14945.

Upload & Print on the Moonraker (Klipper) host type failed with
HTTP 503 "Klippy Host not connected" on any printer that Moonraker
powers up in response to an upload (the [power] on_when_upload_queued
feature). The file landed on disk, the print never started, and the
user hit an error dialog.

Root cause: after POST /server/files/upload succeeds we immediately
fire POST /printer/print/start. On a cold printer that Moonraker just
powered up, Klippy is still coming up when /printer/print/start
arrives, so Moonraker returns 503.

Fix: add `print=true` to the upload multipart form. Moonraker's own
upload endpoint queues the print inside the upload response — and
when a [power] device with on_when_upload_queued is configured, it
powers the printer on and waits for Klippy READY before starting.
That's the whole point of the power-on-upload feature; our second
POST was defeating it.

Also read `result.print_started` from the upload response — when
true, skip our explicit /printer/print/start (Moonraker handled it);
when false (older Moonraker or buddy-fork that ignores the print
flag), fall back to the explicit call so the existing behaviour is
preserved for those servers.

Reporter and root-cause identification: @RubenOllesch.

(cherry picked from commit bd442155ba)

* Moonraker: treat print_queued as Moonraker owning the print

Reading only result.print_started missed the exact case this PR set out to
fix. Moonraker's upload response carries two flags:

    print_started : it began the print immediately
    print_queued  : it accepted the job but has not started it yet

The power-on path (`[power] on_when_upload_queued`) is the second one:
Moonraker queues the job, powers the printer up and waits for Klippy to
report READY, so it answers print_started=false, print_queued=true.

With only print_started read, moonraker_started_print stayed false, the
fallback fired, and our explicit /printer/print/start hit the same not-ready
Klippy that produced the original 503 — i.e. the fix did not fix #14945 for
the configuration that reported it.

Verified the response schema against Moonraker v0.11.0 (API 1.5.0); an
upload with print=true on a ready printer returns:

    {"action": "create_file", "item": {...},
     "print_started": true, "print_queued": false}

Both fields are present, so reading print_queued is safe on this version and
the `false` default keeps older hosts on the existing fallback path.

Caught by @raistlin7447 in review of #15032; the fix is their suggestion.

(cherry picked from commit 43e8eff003)

* Moonraker: read the upload reply's fields at top level

Moonraker's FileUploadHandler writes the upload result straight to the
response instead of wrapping it in {"result": ...} like the endpoints
registered through register_endpoint. item.path, print_started and
print_queued are therefore top-level keys. Reading them under result.
silently fell back to the local filename and to "not started", so the
explicit /printer/print/start still ran after every upload.

Also correct the comments on when Moonraker queues a job instead of
starting it, and on what it renames on upload.
2026-10-10 09:27:05 -03:00
SoftFever 640dfad6e9 List IDEX/IQEX parallel printing in the README's main features 2026-10-10 19:11:57 +08:00
SoftFever 0a9e7f33e1 Link the IDEX/IQEX settings to their wiki sections 2026-10-10 19:09:14 +08:00
SoftFever 74493429fd Name extruders in the unsaved changes dialog like the printer tabs 2026-10-10 15:02:16 +08:00
Clifford GarwoodandClaude Opus 5.5 2ede6d1631 Arrange every IDEX/IQEX plate inside its own primary zone
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>
2026-10-10 01:44:42 -04:00
Clifford GarwoodandClaude Opus 5.5 8b5280098f Merge upstream main: center of mass markers, adaptive TPMS, texture bake color mixing, post-processing preview fix, GLEW/OpenCSG removal
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 01:44:42 -04:00
SoftFever e4142db820 Let texture bakes create a chosen number of mixed colours (#16320)
# 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)
2026-10-10 12:00:39 +08:00
SoftFever d629922060 Keep the paint highlight off every texture preview between strokes 2026-10-10 11:44:09 +08:00
Kris Austin 4a20168742 Fix loading a multi-toolhead 3MF that has no filament_self_index (#16331) 2026-10-09 22:07:33 -03:00
Kris Austin 1466c0e57f Fix rare hang in mcut mesh booleans (#16330) 2026-10-09 21:55:06 -03:00
yw4z c23ea82ad1 revert extruder term on printer settings and optimize dynamic paddings 2026-10-10 01:17:33 +03:00
Kris Austin cd02116242 ci: replace the Docker tag action with git commands (#16327) 2026-10-09 19:17:16 -03:00
Kris Austin 35bac4cb69 Remove unused OpenCSG, GLEW and GLU dependencies (#16318) 2026-10-09 16:50:19 -03:00
0f3e8fbf27 Add center of mass markers to Prepare and Preview (#16291)
Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
Co-authored-by: Kris Austin <kris.austin@gmail.com>
2026-10-09 16:48:48 -03:00
TheLegendTubaGuy 93c6ab93dc Merge branch 'main' into fix/16194-bbl-infill-retraction 2026-10-09 13:57:18 -05:00
SoftFever 901b14a723 Merge branch 'main' into feature/texture-color-number 2026-10-10 02:46:28 +08:00
SoftFever bef49a330b Keep extruder switch button padding after a DPI change 2026-10-10 02:45:18 +08:00
SoftFever 3755618146 Keep nozzle row label fonts after a DPI change 2026-10-10 02:32:18 +08:00
SoftFever ebaff19754 Merge branch 'main' into extruder-variants-ui-2 2026-10-10 00:42:48 +08:00
Misterff1 b5ef24e7ff Fix regression: Arc Fitting setting for BBL P2S (#16319)
Disable Arc Fitting for BBL P2S
2026-10-10 00:32:46 +08:00
Ian Chua 8585eae816 fix: hide symbols of the bundled static openssl (#16317)
* fix: hide symbols of the bundled static openssl

* fix: hide the bundled static OpenSSL symbols on Linux

* fix: relink _ssl/_hashlib when OpenSSL recipe changes
2026-10-10 00:26:02 +08:00
Lam Wei Lun e72ace164b feat(speed-dial): add plugin page capabilities as actions
Plugin Pages capabilities (top-level notebook tabs) were missing from the Speed Dial because ActionRegistry only ingested Script capabilities.

Enumerate and subscribe to Pages as well. Launching a page action switches the notebook to that page, swapping it into the visible slot first when it lives behind the overflow dropdown.
2026-10-09 23:44:52 +08:00
Ian BassiandRodrigo Faselli b5f5b50157 Adaptive TPMS (#16005)
Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
2026-10-09 12:36:09 -03:00
e0b35f9ec9 Fix garbled G-code preview when a post-processing script is used (#15005)
* Rebuild the G-code line offsets after post-processing scripts run in place

* Clamp the G-code window reads to the mapped file size

* Add tests for rebuilding the G-code line offsets

* Include <mutex>, <ios> and boost/filesystem/operations.hpp where they are used

* Keep the preview's G-code lines and highlight in step with post-processing scripts

---------

Co-authored-by: SoftFever <103989404+SoftFever@users.noreply.github.com>
Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-10-09 23:08:43 +08:00
SoftFever b8665b69b0 Merge branch 'main' into feature/texture-color-number 2026-10-09 22:58:10 +08:00
SoftFever 7d141bd691 Keep the texture displacement gizmo out of the assemble view 2026-10-09 22:26:19 +08:00
SoftFever 10787dd59d Show the model's painted colours in the texture displacement gizmo 2026-10-09 22:25:36 +08:00
SoftFever 1fb5da4148 Keep the paint highlight off the texture colour preview between strokes
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.
2026-10-09 22:24:40 +08:00
SoftFever 0473da4ef8 Let texture bakes create a chosen number of mixed colours
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.
2026-10-09 22:23:07 +08:00
Ian Chua eb28daf0fe fix: persist user id so that setting_id doesn't get wiped (#16246) 2026-10-09 21:00:27 +08:00
yw4z 96a1877447 optimize dynamic padding for printer settings dialog 2026-10-09 14:44:55 +03:00
Clifford GarwoodandClaude Opus 5.5 f970a607ae Merge upstream main: belt printing, texture displacement color mixing, 3MF component cycle checks, undo blocked during background jobs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 07:44:12 -04:00
ExPikaPaka 6ac7ae24a3 Block undo and redo while a background job runs (#16015)
* Block undo and redo while a background job runs

A job is queued against the model as it stands and hands its result back
when it finishes, so undoing underneath it leaves that result landing on
geometry it was never computed for. Undo and redo now wait for the job
and say so, and can_undo()/can_redo() report the same, so the toolbar
and the menu items stay in step.

* Say what to do about the running operation, not just that it blocks

Review feedback: "Stop it first" is the only way out the old text offered,
and stopping is rarely what the user wants. Waiting for the operation to
finish works just as well, so the notification now names both.
2026-10-09 19:26:37 +08:00
ExPikaPaka d55e32fed4 Stop reading a missing AMF metadata type as a string (#16060)
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.
2026-10-09 19:25:53 +08:00
SoftFever cd0b529e31 Reject 3MF component references that form a cycle (#16059) 2026-10-09 19:23:20 +08:00
yw4z 81e523e8fb fix m_variant_combo expands vertically 2026-10-09 14:21:52 +03:00
yw4z 86a6841a06 optimize dynamic padding 2026-10-09 14:21:27 +03:00
SoftFever a124b7d2df Reject cyclic 3MF components without running out of memory, however many there are 2026-10-09 19:15:24 +08:00
yw4z f2d3e14f2b fix scaling of multi_extruder and extruder_sync icons on top sizer 2026-10-09 13:59:10 +03:00
yw4z d496ebfd69 Update OG_CustomCtrl.cpp 2026-10-09 13:38:05 +03:00
yw4z b83e4d2890 fix build 2026-10-09 13:33:28 +03:00
yw4z 1b310c1a6d macos placement fix 2026-10-09 13:27:39 +03:00
SoftFever 3d2b219d6a Merge branch 'main' into fix/3mf-component-cycle 2026-10-09 18:14:00 +08:00
ExPikaPaka 62b829d1cb Texture displacement: mix filament colours in the slicer (#16242) 2026-10-09 17:52:35 +08:00
TheLegendTubaGuy a3005a96ac Merge branch 'main' into fix/16194-bbl-infill-retraction 2026-10-09 02:33:14 -05:00
ExPikaPaka 39e6249e5b Include <tuple> in bbs_3mf 2026-10-09 09:12:44 +02:00
ExPikaPaka b3f4ea62a8 Merge remote-tracking branch 'origin/main' into fix/3mf-component-cycle
# Conflicts:
#	tests/libslic3r/test_3mf.cpp
2026-10-09 08:57:16 +02:00
Clifford GarwoodandClaude Opus 5.5 dafc8c5f92 Let the pre-slice warnings checkbox redraw when clicked
Its toggle handler did not call Skip(), so CheckBox's own handler, which
redraws the tick, never ran. Each click still flipped and saved the
setting, but the box kept showing it ticked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 01:29:18 -04:00
Clifford GarwoodandClaude Opus 5.5 7c2566488a Keep the IDEX/IQEX settings out of Simple mode
The Multimaterial page showed an IDEX/IQEX section in Simple mode on
every FFF printer. The pre-slice warnings row's hand-built option
definition left its mode at comSimple; it now uses comAdvanced, like the
IMEX options around it. The parallel modes grid had a group of its own
holding only a full-width widget line, which records no mode, so that
group showed in every mode. The grid now sits in the configuration
group, which follows its Advanced rows. That group shows or hides
everything in it, so the grid keeps itself hidden on non-IMEX printers
(IMEXModesCtrl::set_applicable).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 01:22:04 -04:00
Clifford GarwoodandClaude Opus 5.5 d4edf59301 Merge upstream main: vase settings from plate settings, infill density with modifiers, STEP chamfers, plugin thread permission
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 23:31:15 -04:00
Clifford GarwoodandClaude Opus 5.5 45932dd956 Leave the AFC lane sync out of the IMEX work
MoonrakerPrinterAgent filled physical_extruder_map from AFC's per-lane
extruder_index whenever the printer's filaments were synced. IMEX does
not need it: printers carry their map in the printer profile, and IMEX
reads it from there. The sync also wrote device state into the edited
printer preset, so a sync could mark the preset modified or replace a
hand-tuned map. The two Moonraker agent files go back to upstream's
version. The sync can return separately, writing the map somewhere other
than the printer preset.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 19:11:18 -04:00
mateuszandClaude Opus 5.5 64d09c9bca Add AnkerMake PLA+ filament profiles for Anker printers
Adds AnkerMake PLA+ Basic, Matte, Metallic, Silk and Glitter, converted from eufyMake Studio's bundled AnkerMake profiles (AGPL-3.0). Bumps the Anker vendor version to 02.04.00.06.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 14:13:00 -05:00
Rodrigo Faselli d09850c528 Merge branch 'main' into main 2026-10-08 10:45:49 -03:00
SoftFever bf59db1053 Merge branch 'main' into extruder-variants-ui-2 2026-10-08 20:31:57 +08:00
Clifford GarwoodandClaude Opus 5.5 dbaec929bc test: rely on upstream's tests for the variant truncation guard
Remove three update_non_diff_values_to_base_config scenarios that
tested the truncation guard from upstream #13316. IMEX does not change
that code, and upstream's own tests cover it: removing the guard fails
#13316's test, and loosening it to `>=` fails #16107's.

The equal-size scenario broke when #16107 changed how variants are
matched, and the scalar-key scenario could not fail at all.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 08:54:18 -04:00
SoftFever 27418cea0f Merge branch 'main' into extruder-variants-ui-2
# Conflicts:
#	src/slic3r/GUI/Tab.cpp
2026-10-07 19:40:01 +08:00
SoftFever 7fdfea6efa Merge branch 'main' into main 2026-10-07 19:31:24 +08:00
TheLegendTubaGuy ff3c151417 Fix BBL infill retraction reduction default 2026-10-06 03:26:24 -05:00
Ioannis Giannakas 6561c4d35b Merge branch 'main' into main 2026-10-06 08:01:59 +01:00
Clifford GarwoodandClaude Opus 5.5 53dad9f4fb Include what the IMEX code uses
Upstream's clang-tidy gate now checks that the lines a pull request changes
include the header for every symbol they use. The IMEX sources, their tests,
and the lines this PR adds to shared files relied on the precompiled header and
transitive includes. This adds the includes clang-tidy names; no code changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 11:55:18 -04:00
Rodrigo Faselli 881f2e0c12 Merge branch 'main' into main 2026-10-05 12:17:33 -03:00
yw4z 0dbd9f01fd update icon color 2026-10-05 18:13:42 +03:00
yw4z 19cc65f43a Merge branch 'main' into extruder-variants-ui-2 2026-10-05 18:05:41 +03:00
Rodrigo Faselli 88eb219868 Merge branch 'main' into main 2026-10-03 14:41:20 -03:00
Clifford GarwoodandClaude Opus 5.5 3ae74ff952 Count layer slider tool changes in the parallel-mode checks
A tool change added from the layer slider switches heads mid-print like a
painted color, but the parallel-mode checks only looked at the filaments of the
plate's objects, support and prime tower. A one-filament plate with a slider
change to a copying head's filament passed them. They now include those tool
changes, as the plate's warning badge already did.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 00:33:57 -04:00
Clifford GarwoodandClaude Opus 5.5 d4d79fa328 Refuse Span-mode colors on tools that cannot print them
Only the primary and its Span tools print a Span plate's colors; the other
active tools replay them. The multicolor rule never checked where each color
went, so a filament on a copying or mirroring tool, an unused one, or past the
end of the physical extruder map was accepted. It is now refused, and the
message names the tools that print colors and where the map sends the
offending filament.

The plate's warning badge also read the raw physical_extruder_map rather than
the effective one slicing uses, so on printers that set no map it disagreed
with the slicer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 00:33:57 -04:00
Clifford GarwoodandClaude Opus 5.5 3637a6d164 Merge upstream, including cached config lookups in G-code export
Three commits: G-code export caches its filament config slot and repeated
option lookups, per-plate bed type overrides follow the printer's multi-bed
support, and the gizmo checkboxes and texture displacement panel get styling
and refresh fixes. No conflicts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 23:19:48 -04:00
Clifford GarwoodandClaude Opus 5.5 9c51f7339e Keep the mode name list shut when it has nothing to offer
The name field rebuilt its suggestions in wxEVT_COMBOBOX_DROPDOWN, which
ComboBox sends after it has sized and shown the popup from the items it
already held. With every suggested name taken, the popup opened around an
empty list: a small empty box on GTK, a black one on Windows. The list is now
rebuilt from the field's own mouse-down and double-click, which run ahead of
ComboBox's handlers. With nothing to offer the popup stays closed and the
click focuses the field instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 23:17:21 -04:00
Clifford GarwoodandClaude Opus 5.5 9c5f4ebe47 Stop duplicate mode names from trapping a plate's mode cycle
The editor only replaced an empty name, so a row could be given a name another
row already had. A plate stores its mode by name and find_imex_mode() takes the
first row with it, so the second row was unreachable, and the plate's mode
list repeated the name: left-click stuck on it, or looped without getting back
to Primary.

An edited name that another row already carries, or the reserved Primary name
in any case, is now replaced when the edit is committed: "copy" becomes
"copy 2". The edited row yields, so plates keep resolving to the row they
meant, and tabbing through a field without changing it checks nothing.
Resetting a row to a saved name that another row has since taken does the
same. The plate's mode list comes from imex_plate_mode_choices(), which lists
each name once, so a profile that already has duplicates still cycles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 23:17:21 -04:00
Clifford GarwoodandClaude Opus 5.5 8af5fa45e7 Keep the parallel modes editor on the settings page's colors
The editor took its background from the app's window default, which in dark
mode is a gray the dark-mode walk has no entry for. In dark mode it never
matched the page's palette color, and an editor built in dark mode kept that
gray after a switch to light while the controls inside it changed. It now
takes the page's color, which the walk remaps both ways.

Its labels and G-code text had no color of their own, so on Windows they took
the system's text color, which follows Windows' theme rather than Orca's. They
now start from the page's label and input text colors, which the walk maps
with the page.

The walk also ran the tool tiles' role colors through the palette map, which
turned their labels gray after a theme change. The tiles carry wxBU_AUTODRAW,
the flag it skips, so the role colors stay as set.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 23:17:20 -04:00
Clifford GarwoodandClaude Opus 5.5 697e6c7cf3 Reuse Orca's own icons in the parallel modes editor
The remove button drew imex_remove.svg, a redrawn delete.svg, and the help
button drew the mascot question icon. They now use delete and icon_qusetion,
the tip icon the send-print dialog uses, and imex_remove.svg is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 23:17:20 -04:00
Clifford GarwoodandClaude Opus 5.5 ee30dc1de0 Merge the PR's upstream update without the assembly view painter block
The update merged the same upstream commits this branch already carries. Its
GLCanvas3D resolution kept the assembly view painter block after the ghost
render, which section view removed along with the members it calls, so that
tree does not build. This merge keeps the removal; nothing else differs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:48:30 -04:00
Rodrigo Faselli 6bd90798dc Merge branch 'main' into main 2026-10-02 17:46:18 -03:00
Clifford GarwoodandClaude Opus 5.5 cafbac4816 Merge upstream, including the move to Clipper2 2.0.1
One commit: libslic3r migrates to Clipper2 2.0.1 for performance. No conflicts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:38:41 -04:00
yw4z 489b24bc0c Merge branch 'main' into extruder-variants-ui-2 2026-10-02 22:39:48 +03:00
Clifford GarwoodandClaude Opus 5.5 b1e44199c1 Merge upstream, including section view and a faster preview
Five commits: a section view for the 3D canvas, a faster G-code preview, user
preset values kept on extruder variants they don't list, debug build CMake
fixes, and a 30 minute timeout on macOS notarization.

Two conflicts, both in GLCanvas3D. The header's were neighboring
declarations, kept from both sides. In the transparent pass, the section view
removed the assemble view's painter block that followed our ghost render;
the ghost render stays where it was, at the end of that pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 15:14:03 -04:00
0d36324323 Show the plate's IMEX mode on its mode button
The plate's mode button showed one icon whatever the mode. It now shows the
mode the plate slices as, the one its ghosts follow. imex_mode_kind() reads it
from the heads beside the mode's primary: none is Normal, any Span head is
Custom (the multicolor modes), otherwise any Mirror head is Mirror, and heads
that all copy are Copy. One Mirror head is enough because an IQEX mirror mode
copies within the primary's gantry.

The icons are Felix14-v2's: the four kinds, each in light and dark with a hover
state, replacing the single mode icon. Two fixes to them: the light Normal
border used the dark theme's gray, and an opacity="undefined" attribute hid one
of its strokes in nanosvg. The knight outlines are drawn at 1.0 rather than
0.8, matching the other plate icons and keeping Copy and Mirror apart when
zoomed out.

Co-authored-by: Felix14_v2 <75726196+Felix14-v2@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 11:29:33 -04:00
yw4z cffd1ab053 Update UnsavedChangesDialog.cpp 2026-10-02 17:07:02 +03:00
Clifford GarwoodandClaude Opus 5.5 e8cd4be675 Merge upstream, including deterministic painted multi-material slicing
Seven commits: painted multi-material segmentation made deterministic, missing
includes added across src/slic3r/GUI, U1 high-flow nozzle variants, Windows
ARM64 build and HTTPS fixes, and a rule added to the wxWidgets agent skill.

Three conflicts, all include lists: upstream's include pass and ours each added
to the same lists in GCodeViewer.hpp, PartPlate.hpp and PartPlate.cpp. Resolved
as the union of both. Three includes both sides had added at different places,
which git kept twice, are kept once: Color.hpp and <set> in PartPlate.cpp,
<sstream> in Plater.cpp.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 08:22:47 -04:00
47de490647 Size the IMEX test fixtures' flush matrix per nozzle
The IMEX fixtures set seven nozzles but kept multifilament_config's single
filaments x filaments flush block. get_flush_volumes_matrix splits that block
across the nozzles, leaving each with 7 values, and
ToolOrdering::reorder_extruders_for_minimum_flush_volume then reads them as a
7 x 7 matrix, past the end of the buffer. One of the affected tests segfaulted
on Windows x64; ASan reproduces the overflow in that test on Linux, where it
passed only by luck.

The fixtures now repeat the block once per nozzle, as the GUI does, and size
flush_multiplier to match, since append_full_config takes the nozzle count from
it. The helper is shared in test_helpers.

Co-authored-by: HanifKoh <76276251+HanifKoh@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 08:22:36 -04:00
yw4z b8166f9fb7 Update OG_CustomCtrl.cpp 2026-10-02 15:21:25 +03:00
yw4z e7f5d7be30 init 2026-10-02 14:39:05 +03:00
Clifford GarwoodandClaude Opus 5 c9b2b89b2b Correct the variant-keying note on the wipe tower estimate
filament_minimal_purge_on_wipe_tower became variant-keyed upstream, which
invalidated the comment claiming none of full_config's keys were.
WipeTower2::extract_wipe_volumes indexes it by raw filament slot. Not reachable
with shipped presets; noted rather than worked around.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-01 21:40:39 -04:00
Clifford GarwoodandClaude Opus 5 6c46942c57 Fix the mode name being cleared when a suggestion is picked
ComboBox::SetLabel is overridden and writes the text control when the text
control is shown, which it is on an editable combo. Clearing the label after a
pick therefore erased the name. Removed; the label is never written on this
control.

Suggestions are also rebuilt when the list opens. Built in add_row they filtered
against only the rows that already existed, so a row was offered names the rows
below it had taken.

Drops the 1px inset on the Primary label, which matched a frame the name field
no longer has.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-01 21:08:58 -04:00
Clifford GarwoodandClaude Opus 5 1f8310c727 Merge upstream, including texture displacement and the instance lock
138 commits. The count is large because texture displacement merged with its
whole branch history behind it, going back to July, alongside config and preset
file locking across instances, a foundation for configurable printer agent
connections, and a day of smaller fixes and CI work.

Two conflicts, both the same shape: each side had appended to a sorted list and
git could not choose an order. libslic3r's CMakeLists gained InstanceLock
alongside our IMEXHelpers and IMEXZones, and the preset bundle loading test
gained an include for ParallelResolve alongside ours for IMEXHelpers. Both sides
kept, alphabetical. No logic conflicted.

Verified: 740 targets build clean under -Werror, and the Release suite passes
1740 of 1740, up from 1665 -- the 75 new cases arrived with the merge and all
pass. That mattered more than usual here, since preset loading and config
locking are both areas the IMEX preset code touches.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-01 18:46:19 -04:00
Clifford GarwoodandClaude Opus 5 3c7b62bbca Offer the mode names this printer can carry in the name field
The name field becomes an editable ComboBox, the same pattern the sidebar uses
for parameters like sparse infill anchor length: predefined entries in a
drop-down, with the text still typeable. The mode table is authored for whatever
hardware the user has, so a closed list would be wrong -- nothing in the slicer
reads a mode's name except as the key a plate stores -- but the conventional
names are worth offering rather than leaving everyone to retype them.

What is offered follows the tool grid rather than a fixed list. Four carriages
get iq-copy and iq-mirror; multicolor needs a Span partner beside the primary
and a second gantry to copy the pair onto, so mc-copy and mc-mirror appear only
on a grid that can hold one. imex_resolve_routing() already refuses a multicolor
mode with no Span on the primary's gantry, and suggesting a name it would then
reject is worse than not suggesting it. Names a row already uses are dropped, so
the list only ever offers what is still free.

Two things about ComboBox matter when it is editable, which nothing else in the
tree does -- the other 78 call sites all pass wxCB_READONLY:

GetValue() returns the drop-down selection whenever there is one, so a name
typed after picking a suggestion would be silently discarded. Every read goes
through GetTextCtrl() instead. Field.cpp reconciles the same way for its own
open enums.

The constructor hands its value to TextInput as the LABEL -- the small
right-aligned slot a unit like "mm" occupies -- because a read-only combo hides
the text control and shows the label in its place. Left there, the name rendered
as a greyed echo beside the hint while the field itself sat empty. The combo is
built empty and the value written to the text control, and the selection handler
clears the label again afterwards, since SetSelection() writes there too.

The mode column widens to 176 to leave room for the drop-down arrow, with the
header spacer deriving from the same constant.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-01 18:37:53 -04:00
ExPikaPaka ae3e41eb02 Add a regression test for a cyclic component reference
Stores a painted cube, points its component back at the object that holds it and
expects the load to fail. Without the bound the test does not finish: the work
list grows until the process is killed.
2026-10-01 09:14:46 +02:00
ExPikaPaka 92ab583ecc Reject 3MF component references that form a cycle
_generate_current_object_list expands component references through a work list
with no bound. An object whose component points back at itself, or a pair that
point at each other, makes the list grow until the process runs out of memory:
a few hundred bytes of XML take the slicer past 20 GB of resident size.

Bound the expansion by the number of objects in the file. A reference chain
longer than that has to revisit an object, so this rejects every cycle and no
acyclic file, however deeply nested. A second bound on the number of expanded
components stops an acyclic graph that fans out exponentially.
2026-10-01 08:50:22 +02:00
158ada9054 Merge the finer Xplorer 0.4 tiers and the IQEX toolchange prime
Community work from Rob Niccum, adding 0.04mm Ultra Fine and 0.08mm Extra Fine
for the 0.4 nozzle, a toolchange prime on the three IQEX machines, and
recompressed cover art and build plate model that are byte-identical in content.

Bundle version bumped to 02.04.00.13 here rather than in his branch, so the two
of us were not editing the same line while his review was open. Without the bump
the updater refuses the bundle outright, and nothing in CI catches that.

Verified rather than assumed, since his own validator predates the parallel
printing code and the fine tiers changed shape during review: the full profile
check passes all five stages tree-wide, and both new tiers slice clean on all
four 0.4 machines, 8 of 8. His last revision dropped a bottom_shell_thickness
override that was making those tiers thinner against our common process rather
than thicker against his; the emitted G-code now carries the inherited 1.0,
which is what that fix was for.

Co-Authored-By: Rob Niccum <klober81@users.noreply.github.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-01 00:01:48 -04:00
Clifford GarwoodandClaude Opus 5 74409f1d3b Merge upstream, and move the IMEX placeholders onto the new def macro
Eleven commits, including a typed-config G-code export speedup, a printer agent
refactor that generalizes the infrastructure beyond Bambu, gyroid optimization,
and three CLI crash fixes.

One conflict, in PrintConfig.cpp. Upstream introduced a new_def macro and began
migrating the placeholder table onto it, adding curr_bed_type that way in the
same block where this branch had added imex_mode, imex_mode_index and
imex_mode_gcode in the older def = this->add(...) form. Both sides are kept and
ours are converted to the macro, which expands to the same three statements and
wraps label and tooltip in L() exactly as before, so nothing changes about what
is registered or what is translatable.

Note for anyone building this branch: the agent refactor adds a dependency,
LibDataChannel, so the deps tree needs dep_DataChannel built before the app will
configure. A distribution package of the same name will be found first if one is
installed, and the resulting error names a missing RelWithDebInfo location
rather than the wrong package, so point LibDataChannel_DIR at the dependency
prefix if that happens.

Verified: 789 targets build clean under -Werror, and the Release suite passes
1665 of 1665, up from 1630 before the merge -- the 35 new cases arrived with it
and all pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-30 23:55:21 -04:00
Clifford GarwoodandClaude Opus 5 dafc4ce39c Rework the parallel modes editor from the UI review
Addresses the interface notes on the IDEX/IQEX modes editor.

Add Mode moves from below the rows to the top of the panel, beside a "?" button
that now carries the overview text as its tooltip. At the bottom the button
shifted down the page every time a mode was added, so where it sat depended on
how many modes already existed. It is an Orca Button in the Confirm style, width
matched to the mode column it creates a row in, and the panel opens on the
legend rather than on a paragraph.

Remove moves out of the right-hand column, where it sat one icon away from
Edit -- a destructive control beside the one pressed most -- to under the name
field it deletes, and its icon becomes a boxed minus rather than an X, which
read as "close". Reset joins Edit in the right-hand column, which is now
top-aligned so the icons hold position regardless of row height. Tool tiles are
square at 24px, and the header spacer tracks that width so the column titles
stay over their columns when the grid changes shape.

The two text fields were landing on GTK's near-black default border, invisible
against the panel: measured 45,45,49 against a 43,43,43 background, where the
settings fields above use 74,74,81. wxTextCtrl cannot color its own border, so
each sits in a one pixel frame taking the color TextInput derives for the
theme, and carries wxBORDER_NONE so Windows and macOS do not draw a native edge
inside it. The G-code boxes also take the monospace face EditGCodeDialog uses.

Bed zone fills drop to roughly half opacity in the Standard theme. They cover
whole quadrants for a whole session, so at swatch saturation they dominate the
scene. The collision strip is dimmed less, since it marks where a head hits
something. The deuteranopia, tritanopia and high contrast themes keep their
alphas: those are chosen for discriminability, which is the opposite trade.

Also fixes the icon size never applying. All four ScalableButton call sites
passed eight arguments, so the size bound to use_default_disabled_bitmap and
bmp_px_cnt kept its default of 16. Both are passed now.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-30 23:38:37 -04:00
Clifford GarwoodandClaude Opus 5 e7fad16137 State the no-tool pressure advance contract per firmware
set_filament_pressure_advance's declaration said -1 omits the tool qualifier,
without qualification. That holds on Klipper, Marlin and BBL but not on
RepRapFirmware, where no-tool keeps the historical `D0`: a bare M572 applies to
whichever tool is selected and errors when none is, so omitting the qualifier
would make pressure advance depend on tool-selection state for every RRF user,
none of whom are using IMEX. The behavior is deliberate and unchanged; only the
declaration overstated it. GCodeWriter::set_pressure_advance and the index-space
notes in IMEXHelpers.hpp already described it correctly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-30 21:00:55 -04:00
Clifford GarwoodandClaude Opus 5 d5d940d6b4 Cover the per-carriage pressure advance and temperature emissions
In a parallel mode no tool changes occur, so two loops address each active
carriage explicitly: pressure advance before the print, and the second-layer
drop off the initial-layer temperature. Neither had coverage, and both fail
silently -- a carriage missing from one emits nothing at all, so it holds the
initial-layer temperature for the whole job, or runs on whatever pressure
advance the firmware was last given. The new case pins that the carriages
addressed are exactly the ones the mode declares active, each with the values of
the slot physical_extruder_map routes its head to.

The test needs filament_self_index, set here on imex_7x4_printer() so the whole
file has it. Production authors that key 1..n; its all-1s default collapses
every per-filament vector to filament 1's value through get_config_index_base(),
which leaves a per-slot assertion comparing a value against itself.

Also bounds the second-layer loop on filament_diameter alone. The bound belongs
in slot space, and filament_diameter is the one per-filament vector never
expanded per variant; the previous min() against nozzle_temperature mixed the
two index spaces without changing the result. The pressure advance loop already
bounds this way, so the two now read alike.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-30 20:54:17 -04:00
Clifford GarwoodandClaude Opus 5 477fb2eca9 Pair the AFC lane map with the device it was polled from
The lane map fetched over Moonraker was applied to whichever printer preset
happened to be edited when the response arrived, with no check that it was the
machine polled. Not a race, as the whole chain runs on the GUI thread with the UI
blocked, but a steady-state mismatch: two IMEX printers of different models with
the same logical extruder count both pass every existing guard, and the map is
written to the wrong preset and dirties it with no user action.

The pairing is now evaluated when the callback runs, against the selected device,
on the predicate update_sync_status() uses. Capturing an identity at connect time
would instead ask whether the edited preset had changed since then, and would
reject the user who selects a machine and only then switches to its matching
preset.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-30 20:54:17 -04:00
Clifford GarwoodandClaude Opus 5 a3eccdc8b7 Resolve the IMEX mode the same way everywhere it is read
A plate can carry its own parallel mode, or leave it on Primary and inherit the
process preset's. set_imex_mode() erases the key on Primary precisely so the
preset's value survives the config merge, and the slicer honors it: Print::validate()
and the G-code path both read the merged object config, so such a plate slices, and
emits, in the preset's mode.

Three places that describe that plate did not resolve it the same way. They read
the plate's own value and stopped, so on a plate left at Primary:

  the multi-material conflict badge stayed dark on a plate validate() will refuse,
  which is the one invariant the comment above it claims to keep;
  the bed-temperature and filament-type warnings never ran, so the job went out in
  copy or mirror with a mismatched bed and no notice;
  the plate tooltip reported Primary for a plate about to print in another mode.

The geometric badge beside the first of those already resolved correctly, because
it goes through the zone layout, so one badge fired while its neighbour stayed
dark on the same plate.

The fallback had been written out by hand four times. Three are now collapsed onto
one accessor, get_effective_imex_mode(); the fourth is in libslic3r, which cannot
call a GUI method and resolves the two modes itself from arguments. The remaining
callers of the raw get_imex_mode() want the raw value and keep it: the accessor
itself, the zone layout call that passes both modes separately, the mode menu and
the left-click cycle, which act on what the plate stores, and the reset that looks
for plates whose own mode was removed.

The tooltip is the one place the two readings meet. It headlines the stored mode,
because it sits on the button whose menu and click act on that value, and names
the inherited mode after it when they differ -- so one control no longer says
three different things while still telling the user what will actually print.

Reachability, honestly: nothing in the UI writes the process preset's copy today,
so this needs a hand-edited preset, a vendor process profile or a project that
carries one. It is latent rather than live -- and it stops being latent the moment
a process-level mode selector exists. Note the key lives in the process preset, so
one value there would govern every Primary plate in every project using it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-30 20:54:17 -04:00
Rob Niccum 2f3545e27b Vivedino Xplorer: fine tiers inherit bottom_shell_thickness from common 2026-09-29 23:05:37 -04:00
Clifford Garwood 8350bac123 Merge the per-toolhead extruder colors 2026-09-29 21:43:55 -04:00
Clifford GarwoodandClaude Opus 5 0c09327c1a Merge upstream, and index pressure advance by extruder variant
Upstream moved pressure advance onto the extruder variant: enable_pressure_advance,
pressure_advance and the four adaptive keys joined filament_options_with_variant,
the repeated inline blocks in set_extruder() became a helper, and the lookups
moved from the filament id to get_filament_config_index().

All three conflicts were the same collision, because this branch had modified two
of those same inline blocks to pass a tool qualifier so each carriage is addressed
explicitly in parallel modes. Taking either side whole would have lost something:
upstream's drops the qualifier and leaves parallel carriages with no pressure
advance, ours drops the per-variant indexing and reads the wrong column on a
multi-variant printer. The helper now takes an optional tool, defaulting to -1,
which omits the qualifier. imex_pem_tool_for() already returns -1 off IMEX and in
primary mode, so non-IMEX output is unchanged, and the three call sites that never
passed a tool keep upstream's behavior exactly.

The third conflict was two test cases appended at the same place. Both are kept.

Separately, one defect that merged cleanly and so was not flagged: the loop that
emits pressure advance for secondary carriages at the start of a print still
bounded and indexed those vectors with a raw filament id. They are variant
expanded now, so their length is columns rather than filament slots -- the value
read was the wrong column, and the bound no longer sat in slot space, letting an
out-of-slot filament through. It now bounds on filament_diameter and translates
with get_filament_config_index(), which is what the sibling second-layer
temperature loop already does.

Verified: both changed translation units compile clean under -Werror. The merge
was resolved independently twice and the two resolutions agree on every line of
code. Not yet run: the Release test suite and a parallel-mode slice sweep.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-29 21:43:47 -04:00
Clifford GarwoodandClaude Opus 5 bfac55335a Give each Xplorer toolhead its own color
Every extruder on every model carried the same yellow. The key exists so the
plater, the preview and the filament mapping can tell toolheads apart, which
matters most on the machine this bundle exists to demonstrate: four independent
heads that were indistinguishable at a glance.

Yellow, blue, plum and orange, assigned in tool order, so the two-tool models
take the first two. Red and green are avoided as a pair because they are the
hardest to separate for the commonest color vision deficiency.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-29 20:35:42 -04:00
Rob Niccum c8e4e74024 Vivedino Xplorer: finer 0.4 layers, IQEX toolchange prime, lighter assets
- IQEX (0.4/0.6/0.8): retract_restart_extra_toolchange 1 -> 0 on all four
  tools. With the silicone ooze blockers the parked nozzle stays primed,
  so an extra 1 mm after each toolchange can over-extrude at the restart.
- Add 0.04mm Ultra Fine and 0.08mm Extra Fine @Xplorer 0.4 on the existing
  fdm_process_xplorer_common ladder for all four 0.4 printers. Same speeds,
  accelerations and line widths as the 0.12mm Fine tier; only the layer
  height, shell layers, bottom_shell_thickness 0.6 and (0.04) top solid
  infill flow differ. Support stays off, as on the other tiers.
- Xplorer_buildplate_model.stl: decimated from 82,508 to 10,000 triangles
  (4.1 MB -> 500 KB). Same bounding box and origin, so bed_model is unchanged.
- Covers: one distinct 240x240 image per model instead of the same image
  copied four times (39 KB each -> about 11 KB each).
2026-09-29 15:18:34 -04:00
Clifford Garwood 3334c2ed2a Merge the Vivedino Xplorer printer profiles
Adds the four Xplorer configurations, their twelve machines and fifteen
processes, so the parallel printing modes can be exercised on a real printer
rather than only on hand-built configurations.
2026-09-29 05:03:26 -04:00
Clifford Garwood b6b2f0d3b8 Merge upstream into the parallel printing work
Brings in nine commits, including stricter slice validation of custom G-code and
filename formats across system profiles, OBJ and DRC import hardening, and the
Ender-3 V3 SE extrusion mode fix. None of them touch the files this branch
changes.
2026-09-29 05:03:21 -04:00
9a7e2681d4 Add Vivedino Xplorer printer profiles
The Xplorer ships in four configurations that differ in how many toolheads they
carry and how those heads are arranged: Single, IDEX with two heads on one
gantry, Dual Gantry with one head on each of two, and IQEX with two on each of
two. Each is offered at 0.4, 0.6 and 0.8 mm, giving four machine models, twelve
machines over a shared base, and a five-tier process ladder per nozzle.

The dual-gantry machines park the second gantry's tools over the plate, so the
area both gantries can reach is 57.5 mm shallower than the plate itself. Those
two declare 400 x 342.5 where Single and IDEX declare 400 x 400. That depth is
what the parallel print modes divide into equal zones, so it has to be the
reachable area rather than the physical one.

The bed textures are drawn to match. Texture coordinates are normalised per
axis, so a canvas whose aspect differs from printable_area is stretched and
anything drawn outside the plate is pulled onto it; each viewBox equals its own
model's area. They mark the real reach limits and nothing else, because the
print zones are computed and drawn per plate at run time and a static copy of
them only disagrees with the live one.

Motion limits are the firmware's: 5000 acceleration, 300 mm/s, 100 on Z, 120 on
the extruder, and a square corner velocity of 5 rather than a jerk, which Klipper
does not have. Every per-extruder and per-variant value is written at full width,
because padding a short array repeats its first value rather than its last pair
and would otherwise hand every extruder past the first a normal-mode figure in
its silent slot.

The processes are based on a profile tuned on the hardware. Line widths are
expressed as percentages of the nozzle so one statement serves all three sizes,
and the ladder varies only what belongs to layer height. Extruder variants are
declared rather than left to a default that would have capped volumetric flow at
a figure describing a plain V6.

Verified by slicing all fifteen tiers on all four models: every one completes,
emitted accelerations and widths match what the profiles resolve to, no
coordinate leaves the printable area, and the two pre-existing Troodon models in
the same bundle are unaffected. The bed graphics, the print zones and the
volumetric cap resolve through paths the command line does not exercise and want
confirming in the application. Values tuned against hardware the author does not
have - the 0.8 ladder in particular - are a starting point rather than a result.

Co-Authored-By: Dan_3dp <corexy.diy@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-29 04:58:22 -04:00
Clifford GarwoodandClaude Opus 5 bd8b7ced6a Colour the carriage markers by filament where the preview does
Multi-material previews now open in Filament view, where toolpaths are coloured
by the filament printing them, and a marker carrying a fixed palette colour can
sit on toolpaths of that same colour. The plan already resolves which physical
head each carriage is, so in that view every carriage - the primary marker
included - takes its head's filament colour, from the accessor the ghosts
restamp themselves with, and marker, ghost and toolpath agree on what is loaded
where. The primary marker is the one every preview draws, so it goes back to its
own colour when no parallel mode is active.

Every other view keeps the Okabe-Ito palette, which is what identifies the
carriages when colour means something else, and so does a head whose filament
does not resolve. Each head is resolved once per frame and shared by its marker
and its toolhead box, the way the ghosts already hoist the map they all read.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-28 17:31:27 -04:00
Clifford GarwoodandClaude Opus 5 4e65b741cc Draw the IDEX/IQEX ghosts in the X-Ray pass that replaced their own
X-Ray takes both shaded passes over and returns, and it draws only the volume
collection, which the ghosts are not part of. They disappeared while their
picking pass, hover tooltip and filament picker all kept answering, so a plate
in a parallel mode offered an invisible click target.

They go through the X-Ray shader rather than their own, so a ghost reads as one
more see-through body, which is what the mode is for. The pass supplies z_range
and clipping_plane itself, since the volume collection sets them for the whole
pass it runs and the vertex shader discards everything outside z_range. It also
draws two-sided, so a hollow ghost shows its far wall like a real body does, and
hands the shader an opaque colour: X-Ray derives coverage from the view angle
and multiplies the colour's alpha into it, so a ghost carrying its own
translucency as well would composite far fainter than the body it mirrors.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-28 17:31:27 -04:00
Clifford Garwood 299093844f Merge upstream main: multi-variant printers, X-Ray view, preview and startup work
The two conflicts are both places where upstream landed on top of IDEX/IQEX
code. In GCode.cpp the relocated file header block meets the IMEX placeholder
block, and the placeholders are set first: file_start_gcode is processed through
the placeholder parser now, which throws on a name it does not know, so a script
naming {imex_mode} would abort the export if the header ran first. In
test_multifilament.cpp both sides appended a case at the end of the file.
2026-09-28 17:12:53 -04:00
Rodrigo Faselli 468b0bb94d Merge branch 'main' into main 2026-09-24 09:15:04 -03:00
Clifford GarwoodandClaude Opus 5 af1cf71a4c Read the IDEX/IQEX enum settings back as the values they were saved as
A coEnum config value has two representations: the typed ConfigOptionEnum<T> a
config cloned from the static classes carries, and the ConfigOptionEnumGeneric
that a config assembled from the option definitions creates - which is what a
preset, a project's own settings and the CLI all hold. imex_cfg_enum() accepted
only the first, so every IDEX/IQEX reader took the option default instead: a
printer saved as rear-left came back front-left in the settings, the bed zones
and the carriage markers, while the preset on disk still held rear-left.

Read the generic form too, keyed on the value map it carries, since only T's own
map yields a T. The last reader that matched on the coEnum tag alone and cast
across the two hierarchies now goes through the helper with everything else.

Declaring the three keys the static classes were missing is what lets a change
to imex_tool_layout invalidate the slice it moves, which it never did before.
The two that are only ever drawn stay out of that: a colour scheme and the
advisory margin bands do not reach a slice, so changing one must not discard it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 18:09:40 -04:00
Clifford GarwoodandClaude Opus 5 42a47d6bd7 Keep the plate mode name visible beside its conflict warning
set_hover_tooltip records one string per frame, so the multi-material warning
replaced the mode tooltip instead of joining it, and hovering the icon on a
conflicted plate no longer said which mode was active or that clicking cycles
it. The two are composed into one string, paragraph separated.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 17:10:23 -04:00
Clifford GarwoodandClaude Opus 5 1f1d13e3d9 Describe the estimate's actual blind spots in the tower comments
The estimate no longer reports a tower for a single filament whose flush matrix
purges, so the comments that justify calling prime_tower_is_printed() instead of
reading a depth now cite what still holds: it reads neither enable_prime_tower
nor print_sequence. GCodeViewer's comments name render_scene(), the function
that replaced the render() they still pointed at, and the pass contract mentions
the toolhead boxes it draws.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 17:10:23 -04:00
Clifford GarwoodandClaude Opus 5 2d80c6a24e Drop the single-filament tower case the estimate no longer reports
The estimate now returns no tower for a lone filament whatever the flush matrix
says, so the discrepancy this case pinned between it and normalize_fdm_2 is gone
and the sibling case covers what remains.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 09:56:44 -04:00
Clifford c078765f95 Merge pull request #3: port the IDEX/IQEX rendering onto the render_scene/render_overlay split
Fix the GCodeViewer.cpp conflict resolution from the upstream merge
2026-09-21 09:56:36 -04:00
raistlin7447 068dc4346f fix(gcode-viewer): port the IDEX/IQEX rendering onto the render_scene/render_overlay split
The merge of upstream main (dc021c6ef6) resolved the GCodeViewer.cpp conflict
from #15674 by keeping both sides. That left a stray `}` in
SequentialView::render_overlay, so the file no longer compiles. It also kept
the IMEX carriage update and the toolhead-box GL draw in the ImGui overlay
pass, which #15674 no longer uses for 3D geometry.

The carriage update and the toolhead boxes now run in render_scene next to the
primary marker, and render_marker draws the secondary markers. render_overlay
goes back to what upstream has, without the duplicated marker-position block
and the unused bottom_margin the merge left behind.

The same upstream change renamed PartPlate::show_tooltip to set_hover_tooltip;
the two IMEX call sites follow it.
2026-09-21 08:22:07 -05:00
Rodrigo Faselli dc021c6ef6 Merge branch 'main' into main 2026-09-21 09:47:39 -03:00
Clifford GarwoodandClaude Opus 5 1c42bb0cd6 Merge upstream main: Design tab CAD, OTA OPC CI, JAYO filament profiles
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 08:00:12 -04:00
Clifford GarwoodandClaude Opus 5 84c13d4913 Merge upstream main: wipe tower port, per-filament bounds checks, CLI mixed rules
Four files overlapped, and each resolution favours upstream where the two sides
had done the same work:

WipeTower's M104/M109 tool qualifier. Both sides bounds-checked the physical
extruder map lookup; upstream omits the T qualifier when the map cannot answer,
where this branch fell back to the logical index and so named a carriage that may
not be the one printing. Upstream's behaviour is what this branch documents
elsewhere, so its version is taken and the local helper is dropped.

get_extruders' mixed-slot switch. Upstream added the same concept to the CLI
overload as expand_mixed_slots, so the GUI overloads' parameter is renamed to
match rather than carrying two names for one idea.

GLCanvas3D's sequential-clearance branch gains upstream's
update_compacted_wipe_tower_clearance for the by-layer case.

The printer_agent re-sync in TabPrinter::reload_config was upstream's and their
preset-undo fix removed it, so it goes; the IMEX modes grid re-sync beside it
stays, since it spans three options and is not a Field.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 01:50:53 -04:00
Clifford GarwoodandClaude Opus 5 b6a2c76e5d Share the prime-tower rule with the slicer instead of copying it
imex_wipe_tower_hull() open-coded "does this plate print a tower?" as one
filament and no forcing reason. normalize_fdm_2, which is what actually
clears enable_prime_tower before slicing, has a second arm the copy omitted,
and reads the mixed-filament flag project-wide where the copy read the
plate. Two cases went wrong in opposite directions.

A ByObject plate with several objects prints no tower and the scene draws
none, yet the copy validated one and could refuse the slice with "the prime
tower overlaps an area reserved for IDEX/IQEX parallel printing" - with
nothing on screen to move.

A plate using one plain slot while some other slot in the project is a blend
does print a tower, because normalize_fdm_2 keeps it for any mixed filament
in the project, and the copy skipped validation entirely, so the tower could
be placed in a carriage zone and sliced.

prime_tower_is_printed() in libslic3r states the rule once, and both the
gate and a test use it. The counts are the ones normalize_fdm_2 is handed:
filament slots as authored, so a mixed slot counts once, and distinct
objects rather than instances. filament_is_mixed is a project option, so it
is passed in rather than read from the print preset.

The test drives every combination the rule looks at and compares the verdict
against normalize_fdm_2 itself, so the two cannot drift again without
failing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 01:41:42 -04:00
Clifford GarwoodandClaude Opus 5 0e44269c5e Correct the prime-tower validation comments after the upstream rename
The block describing what imex_wipe_tower_hull() does and does not cover had
drifted. It named estimate_wipe_tower_size, which upstream replaced with
estimate_wipe_tower_footprint; it described the plate filament count as an
override where the rewritten estimate treats it as a floor; and it
documented a Type2 stabilization cone as unhandled when the estimate now
folds the cone's worst-axis bulge into the margin the hull is built from, so
a second allowance would double-count. The comment on the m_print arm of the
guard now says what that arm actually does, which is nothing, since the
estimate stopped reading m_print.

It also records why the gate takes "is a tower printed" from
normalize_fdm_2's rule rather than from the estimate: the estimate reports a
tower for a single filament whenever the flush matrix purges, which would
hard-block a plate whose tower normalize_fdm_2 had already cleared, with
nothing drawn on screen to move.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 01:41:42 -04:00
Clifford GarwoodandClaude Opus 5 182dbd55e4 Pin the flush-matrix purge for a single filament
With single_extruder_multi_material and purge_in_prime_tower, the flush
matrix replaces the prime volume and its average is non-zero for one
filament, so the estimate reports a tower. normalize_fdm_2 clears the tower
for that same plate, so none is printed: the estimate answers how big a
tower is, never whether there is one, and a caller reading a non-zero depth
as "a tower is printed" reserves space for, or blocks on, a phantom. The
test asserts that disagreement directly.

Type1 is the exception in the same case, because it decides from its
per-filament purge list and a lone filament is never changed to. The scene
reads the same estimate either way. Type2 is the default for every non-Bambu
printer, and so for every IDEX/IQEX one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 01:41:42 -04:00
Clifford GarwoodandClaude Opus 5 3c31856b85 Bound the second-layer temperature lookup in filament-slot space
nozzle_temperature is variant-expanded, so its length is columns rather than
filament slots. On the dynamic-nozzle path that makes it longer than the
slot count, and an out-of-slot index reached get_filament_config_index() and
came back as filament 0 - the clamp the bounds-checking was meant to remove.
Bound by the slot count instead; on the ordinary path the two are equal and
nothing changes. The is_extruder_used write gains the matching lower-bound
guard.

IMEXHelpers.hpp now states both halves of the rule its call sites follow.
Bound anything derived from the extruder map against the filament slot count
before using it as a filament id, not against the option about to be read.
And a miss is -1, which is a correct tool qualifier but matches no physical
head, so it cannot serve as a skip-the-primary sentinel: which head prints a
filament is answered by the filament and the map, never by a mode role,
since a primary-mode print may use any or all tools, one at a time.

The consequence is recorded there rather than left implicit. The two skip
sites skip nothing for a slot past the end of the map, so a plate with more
slots than nozzles double-writes the primary's pressure advance. It is
narrow and unreported, and a guard there would be a smaller change than
naming a head.

The header also records that RepRapFirmware sends an unqualified pressure
advance as M572 D0, naming drive 0 absolutely rather than the active tool.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 01:32:27 -04:00
Clifford GarwoodandClaude Opus 5 c62385214f Drop two dead lambda captures that only a dev build compiles
CameraPopup and StatusPanel each bind a toggle handler that captures this
and never uses it, inside an #if !BBL_RELEASE_TO_PUBLIC block. CMakeLists
defines that as $<CONFIG:Release>, so the block compiles in every
configuration except Release - and Release is the only one CI builds. Under
-Werror the two captures therefore fail RelWithDebInfo, which is what a
development build uses, while CI never sees them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 01:32:27 -04:00
Clifford GarwoodandClaude Opus 5 b6f690a65c Migrate the IDEX/IQEX config keys testers already have
The parallel printing options shipped to testers as is_ixex and ixex_*,
became is_imex and imex_* when the feature stopped being called iXex, and
the two clearance keys were renamed once more to say what they measure:
nozzle to carriage edge on the collision side, not the carriage's full
width. Nothing translated any of it, so loading an existing printer profile
dropped every one of these values - the keys are unknown and cleared.
is_ixex is the one that matters most, because without it the others migrate
into a feature that stays switched off, leaving settings that look
configured and do nothing.

Per-plate mode is persisted twice and only one path went through
handle_legacy. Plate metadata in a 3MF is matched by exact attribute name
and written with set_key_value, so a project saved between the per-plate
mode landing and the rename loaded every plate back on Primary and sliced
single-carriage with no warning. The loader now accepts the old attribute
name.

ixex_primary_col and ixex_primary_row are dropped rather than mapped: the
primary is a role inside the mode's active-tools string now, not a grid
coordinate, and they were never in an option list, so no saved file carries
them.

The test drives the full era-1 key list and asserts the enum values rather
than non-nullness, since a forward-compatible substitution would otherwise
hide a failed deserialize behind a default. handle_legacy had no test before
this.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 01:32:27 -04:00
Clifford GarwoodandClaude Opus 5 f7a08b0868 Spell the mode loop initializers as std::string
Two range-for loops bound const std::string& to braced lists of string
literals, so each iteration constructed a temporary to bind to. GCC 16
reports it as -Wrange-loop-construct, which upstream's blanket -Werror turns
into a build failure; clang does not report it at all. Spell the
initializers the way the loop above them already does.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 01:32:26 -04:00
Clifford GarwoodandClaude Opus 5 98544fd6bc Bound the parallel pressure-advance lookup to the filament count
Review findings on the preceding commit, plus one defect it should have
caught.

- The IDEX/IQEX pressure-advance loop fed resolve_filament_for_head()'s
  result straight into enable_pressure_advance and pressure_advance. That
  result is bounded by physical_extruder_map, which holds one entry per
  NOZZLE, while both options are indexed per filament SLOT. On a printer
  with more nozzles than the project has filaments the two spaces diverge
  and get_at() clamped the overflow onto filament 0, emitting its pressure
  advance on a secondary carriage. The second-layer temperature loop bounds the
  same lookup, but against nozzle_temperature, which is variant-expanded and so
  is not the slot count either -- it is not the precedent it looks like.
  IMEXHelpers.hpp states the rule
  once, and a test pins the contract that makes the bound necessary:
  resolve_filament_for_head() answers in nozzle space, so a non-negative
  result is not by itself safe to use as a filament id.

- The header claimed every caller renders a -1 tool qualifier as "emit
  none". RepRapFirmware substitutes the historical D0 instead, deliberately
  and with its own comment in GCodeWriter. Say so, rather than leaving a
  contract a future author would code against.

- A cross-reference pointed at a hard-coded line number that the preceding
  commit had itself shifted by nine lines. Name the function instead.

- The multi-color rejection reasons reach the user through Print::validate()
  as raw English, while the returns on either side of them use L(). Wrap
  them and register IMEXHelpers.cpp for extraction. They also still said
  "IMEX", the internal name, so they move to IDEX/IQEX with the rest of the
  user-facing strings rather than shipping the internal one to translators.

- Trim the preceding commit's comments. One block explained the same
  clamping hazard six times; the canonical explanation now lives in
  IMEXHelpers.hpp and the call sites point at it. The mode grid carried
  twelve lines of commentary and no code, most of it archaeology already in
  the commit message, and one claim about the modes editor that was not
  true. The ArrangeJob threading note stays: it documents an invariant that
  cannot be recovered from the code.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 01:32:26 -04:00
Clifford GarwoodandClaude Opus 5 e309029b28 Fix extruder-map bounds, popover lifetime, and arrange thread safety
Review findings on the IDEX/IQEX parallel printing code, all in paths the
feature owns.

- physical_extruder_map lookups used ConfigOptionVector::get_at(), which
  clamps an out-of-range index to values.front() rather than reporting a
  miss. The map holds one entry per nozzle while filament ids index slots,
  and nothing caps the slot count at the nozzle count, so a project authored
  with more filaments than the printer has extruders silently addressed the
  primary's head: pressure advance pinned to the wrong carriage, and
  skip-primary loops suppressing whichever head sat at pem[0]. Bounds-check
  at all four sites and treat the miss as "no mapping" (-1). Covered by a new
  imex_pem_tool_for test; the header note now warns against get_at here.

- IMEXFilamentPickerPopover leaked a top-level window per ghost click:
  wxPopupTransientWindow::Dismiss() only hides, and never reaches OnDismiss().
  Destroy from an OnDismiss() override and dismiss the picker through
  DismissAndNotify(), which is the path a successful pick takes.

- ArrangeJob read PartPlate's IMEX zone cache from the worker thread, where
  a cache miss rebuilds GLModel members with no GL context current while the
  GUI thread may be painting them. Snapshot the zones in prepare(), on the
  main thread, already converted to plate-local coordinates.

- The mode grid anchored its row window to the Primary's gantry row. A window
  as tall as the grid can only start at row 0, so this drew tiles for tools
  that do not exist and hid real ones. Render the whole grid instead; a
  Primary outside it is a data problem the zone layout already reports.

- Build the mode tooltip from one format string rather than two catalog
  fragments concatenated around a runtime value, so translators can move the
  mode name within the sentence, and register IMEXModesCtrl.cpp for string
  extraction.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 01:32:26 -04:00
Clifford Garwood b17414b160 Merge remote-tracking branch 'upstream/main' into integration/upstream-main-sync 2026-09-13 02:21:43 -04:00
Clifford GarwoodandClaude Opus 5 6c67fcfe07 Read IMEX geometry defaults from print_config_def instead of literals
Five sites carried a hardcoded fallback for imex_tools_per_gantry that had
to match the value registered in print_config_def, with nothing enforcing
the agreement, and several explanatory comments miscounted the sites they
described or cited stale line numbers.

Add imex_cfg_int/_float/_bool/_enum<T> to IMEXHelpers, which return the
value registered for the key when it is absent from the config, so the
registration is the single source and there is nothing left to keep in
sync. Route every read of the IMEX geometry keys through them: 32 call
sites across IMEXZones, PartPlate, GCodeViewer and Tab. The only direct
lookup left is the bail in PartPlate::imex_multicolor_block_reason, which
must not default because it reports a routing conflict and a defaulted
grid would produce a false warning.

imex_cfg_enum uses dynamic_cast on both halves rather than the type()
comparison the others use: every ConfigOptionEnum<T> reports coEnum, so a
type() check cannot tell one enum type from another and would cast a
ConfigOptionEnum<OtherEnum> to the requested T. The ConfigOptionPercent :
ConfigOptionFloat inheritance that rules dynamic_cast out for the float
accessor has no analogue for enums.

Correct the comments that prompted this: the cache-key input list in
PartPlate named five inputs for a nine-part key, the ImexMarkerKey note
in GCodeViewer called imex_tool_layout an input only the preview reads
when the plate keys it too, and four file:line citations pointed at the
wrong lines. Values are unchanged at every converted site; cache key
strings keep their existing representation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-04 20:34:04 -04:00
Clifford GarwoodandClaude Opus 5 e45c241466 Address the review on the IMEX parallel printing PR
Resolves all 21 inline comments, plus six changes that altered behaviour for
users not using the feature and seven defects found alongside them.

The reviewer's central point generalised: a pressure-advance change had moved
every RepRapFirmware user onto an unverified command form. Auditing for that
class found five more — 14 config keys leaking into every exported g-code, an
ungated Moonraker sync writing to non-IMEX printers' presets, every slice
eagerly re-rendering all plate thumbnails, a preset delta-encoding regression,
and physical_extruder_map being normalised for printers that read it the other
way.

The structural asks landed as asked: the 392-line bed-zone geometry moved to
libslic3r and is now unit-tested, the preview consumes that same layout instead
of a second copy, nine open-coded mode lookups became one, and the modes editor
moved out of Tab.cpp. Making the geometry testable exposed three further
defects in it, including an aggregated gantry that raised no collision strip.

The worst bug was not in the review: the GUI computed the firmware-managed
slice offset in the plate-list world frame while both consumers subtracted the
plate origin again, so every plate after the first failed to slice with 'part
is off the plate'.

User-facing strings now read IDEX/IQEX, honouring 461c69c83e. Config keys, C++
identifiers and 3MF metadata keys keep the imex_ spelling as on-disk format.

815/815 tests pass in Release.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-04 00:26:21 -04:00
Clifford GarwoodandClaude Opus 5 c09ce3a0d1 Merge upstream main: CLI argument parsing, GUI string fixes, nozzle type undo tracking, warning cleanups
No IMEX code upstream, so nothing in this merge touches the feature. All 21 overlapping
files auto-resolved; verified every upstream addition is present in the merged tree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-04 00:17:36 -04:00
Clifford GarwoodandClaude Opus 5 5eac300d91 Use IDEX/IQEX in the user-facing strings
Closes review comment 10.

`461c69c83e` settled this in April — IMEX internally, IDEX/IQEX as the user-facing label — but the
UI strings were never converted. Every translated string naming the feature now reads IDEX/IQEX:
41 occurrences across the printer and process option labels and tooltips, the modes editor, the
plate mode indicator, the pre-slice warnings, the placement refusals and the slicing errors. The
reviewer listed eight; the rest were in the same class.

Nothing else moves. The config keys keep the `imex_` spelling — `is_imex`, `imex_mode_names`,
`imex_parallel_mode` and the rest are on-disk format in existing printer presets and 3MF projects,
so renaming them would break every profile and project already saved. C++ identifiers, filenames,
comments and test names keep IMEX as well: it stays the internal name of the subsystem, which is
what covers the topology space (one gantry with 2-4 tools, 2x1 and 2x2 grids) that neither acronym
names on its own. Where a tooltip quotes a key, the key spelling is preserved and only the feature
word around it changed.

The `is_imex` tooltip is reworded rather than substituted: it already named the hardware families
parenthetically, so a literal replacement would have said IDEX/IQEX twice in one sentence.

No translation impact — no IMEX string had reached OrcaSlicer.pot or any catalogue, so there is
nothing to migrate. One test asserted on the old error text and now matches the new one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 10:05:37 -04:00
Clifford GarwoodandClaude Opus 5 bd8dfd6250 Cover the IMEX slice offset, the mode G-code placeholders, and the non-IMEX heater guard
Closes review comments 13, 14 and 15, and adds the test for a shipped-profile
regression that nothing guarded.

- 14 and 15: compute_imex_slice_offset had eight tests on the calculation and none
  on the result, which is the whole firmware-managed path. test_imex_slice_offset
  now covers the derivation end (which config produces a non-zero offset, and that
  it is plate-local rather than moving with the plate origin -- the bug that
  shifted every plate after the first) and the consumption end (emitted
  coordinates and first_layer_print_min/max both move by the derived amount).
  The first_layer case also cross-checks the two consumers against each other: the
  declared bounds must keep the same relationship to the emitted toolpaths in both
  frames, which fails if exactly one of them is shifted. It deliberately does not
  pin the size of that gap -- it is 2.225 mm here, set by the wall generator, the
  same with no offset at all, and pinning it would fail on an unrelated change.
- 13: nothing exercised the imex_mode / imex_mode_index / imex_mode_gcode
  placeholders or the {global} flow into machine_start_gcode that their ordering
  exists to guarantee. Seven cases now do, including the ordering itself -- the
  mode script declares a global and machine_start_gcode reads it back, so moving
  the mode processing later leaves the variable undefined and fails the export --
  plus the inert cases (Primary mode, and a printer with the table filled in but
  is_imex off). All matching is whole-line, because the config block the exporter
  appends repeats machine_start_gcode verbatim and would make substring checks
  meaningless.
- New: GCodeWriter passes this->config.is_imex.value into the heater remap, and
  nothing tested that it passes the flag rather than a constant. Hardcode true
  there and the whole suite stays green while fdm_bbl_3dp_002_common, which ships
  physical_extruder_map [1,0], starts sending filament 0's M104/M109 to heater 1.
  The new case runs a two-nozzle non-IMEX printer with that map and asserts each
  filament's temperature reaches only its own tool. It uses idle_temperature via
  ooze prevention rather than nozzle_temperature: keys in
  filament_options_with_variant are re-indexed per filament by variant slot at
  apply time, and this harness pins nozzle_diameter to one value, so every filament
  resolves to the same slot and the temperatures stop telling the heads apart.

Also fixes two weaknesses in tests added earlier in this branch: an assertion that
would have been prefix-satisfied by the very routing it was meant to exclude, and
a whole-file command comparison between two slices, which this slicer's output is
not stable enough to support.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 00:57:14 -04:00
Clifford GarwoodandClaude Opus 5 7c49660c05 Share one routing derivation with the slicer, and stop two GUI paths acting on non-IMEX printers
Closes review comment 20; the other two are non-IMEX leaks found auditing the
branch.

collect_imex_warnings re-derived the active mode, its tools, the primary and the
filament routing that Print::validate also derives, so the warning and the hard
block could drift apart -- and this function had already had one index-confusion
bug, the AFC/MMU wrong-filament names fixed in fbc58d2a1d. Both now read
imex_resolve_routing() and derive nothing themselves. The function stays
file-static: what is left in it is PresetBundle lookups, bed-type resolution and
formatting, none of which can disagree with the slicer about what the plate is
doing, and the index-confusion surface is now library code with tests covering the
AFC manifold in both directions. A latent out-of-bounds read went with it -- the
primary fallback can return -1 and the bounds checks were upper-only, so
filament_presets[-1] was reachable on a profile whose roster names a head absent
from the map.

Slicing eagerly re-rendered every plate thumbnail on the main thread after
switching to Preview, up to two blocking offscreen GL renders per plate on every
slice click. The work was already redundant: select_view_3D("Preview") invalidates
the thumbnails and marks the toolbar dirty, and the next frame force-regenerates
them anyway. It could not have served its stated purpose either, since it ran
immediately after reslice(), which only starts the background slice. Both calls
dropped; the plate badge state is recomputed per frame and is unaffected. The
export_3mf thumbnail log lines are back at info, and the slice-event traces
restored.

The Moonraker device sync wrote physical_extruder_map into the edited printer
preset ungated, so any Klipper machine running a current AFC build had its preset
marked dirty with no user action, and saving persisted the map into every 3MF
after. The map is indexed by logical extruder while the device reports one entry
per lane, and nothing in the lane payload carries the logical slot, so the two
index spaces coincide only when the counts match. Now gated on is_imex, written
only when the counts agree, and only when the value actually differs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 00:56:55 -04:00
Clifford GarwoodandClaude Opus 5 1e608fe24c Have the preview consume the shared zone layout instead of rebuilding it
Closes review comment 17.

GCodeViewer had its own copy of the flip_x/flip_y corner mapping, the active
column/row sets, the physical-to-zone index mapping and the zone pitch -- the same
derivation as the plate's, with nothing keeping the two in step. It now calls
compute_imex_zone_layout() and consumes head_zone_centers. The mirror is expressed
as a reflection about the midpoint of the two zone centres rather than about a
zone-relative strip width, which is algebraically identical for equal-sized zones
and needs no pitch, and the toolhead-box face is chosen by comparing zone centres
instead of physical columns.

The July report of a math error in the visualizer for non-primary heads was this
drift: the sec_box_offset_y else-branch hardcoded -imex_box_wy, which happened to
equal the primary's offset on the rear-* layouts and pointed the wrong way on the
front-* ones. Structurally unreachable now.

Verifying the two sides matched turned up two config defaults that disagreed, both
fixed in their own commits: imex_nozzle_clearance_x/y (the viewer's 30.0f matched
PrintConfig, the zone code's 0.0 did not, and the strip loops are gated on it) and
imex_tools_per_gantry (the library's 2 matched, both GUI paths used 1).

Consuming the shared function meant resolving it per frame, and the sequential-view
marker flag is sticky, so one drag of the slider made every subsequent frame parse
five strings and allocate a dozen containers from inputs that never change. The
resolve now sits behind a cache key mirroring PartPlate::build_imex_cache_key(),
plus the two inputs only the preview reads -- the bed extents and the tool layout.
An idle frame compares scalars and allocates nothing. The toolhead-box mesh, which
was being re-uploaded to the GPU every frame for the same reason, is rebuilt only
when the clearances change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 00:56:55 -04:00
Clifford GarwoodandClaude Opus 5 838c2f2df8 Reset plates to Primary when their mode is deleted
Closes review comment 9.

PartPlate::reset_imex_mode() had no callers, while the PR description said
deleting a mode resets affected plates to Primary. IMEXModesCtrl now exposes an
on_mode_removed callback that TabPrinter::build_fff handles by resetting every
plate whose mode matches the deleted row, under a single undo snapshot, followed
by the same dirty/update sequence the plate's own mode button runs.

Wired to deletion only, on purpose: the name field notifies on every keystroke, so
routing renames through the same path would orphan and reset the plate on the
first character typed. Renames stay covered by the slice-time fallback and its
warning. The callback is copied to a local before notify(), because notify()
reaches load_from_config() -> clear_rows(), which tears down the row the handler
is running inside.

The rest of this file is the modes editor moving out to its own translation unit,
leaving the include and the construction site.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 00:56:55 -04:00
Clifford GarwoodandClaude Opus 5 f75bdd6013 Move the IMEX modes editor into its own file and fix four defects in it
Closes review comments 21, 4 and 12, and the widget half of 19.

- 21: IMEXModesCtrl was 572 lines inside Tab.cpp. It now lives in
  IMEXModesCtrl.{hpp,cpp} next to IMEXFilamentPickerPopover, which was the
  precedent named in the comment. The move itself is exact -- member order,
  comments and every string literal unchanged -- and the class had no file-local
  dependencies in Tab.cpp, only its include list, so the new source states those
  explicitly.
- 4: a mode row with an empty Name was silently dropped on save, tools and G-code
  with it, and matches_config() compared against that same filtered output so the
  preset never went dirty and the row stayed on screen. Rows are now given a
  generated unique name instead of being discarded, and add_row() pre-fills one so
  the common path never produces a blank. Names are deliberately not translated:
  objects store a mode name in imex_parallel_mode and GCode.cpp matches it by
  string, so a localized name would break a project reopened in another language.
- 12: the editor had a third parser that read a bare token and an unknown role
  suffix as Primary, while parse_imex_active_tools reads both as Copy -- so the
  editor and the slicer could read one imex_mode_active_tools string two different
  ways. Deleted; the editor now uses the same two helpers the slicer does.
- 19: tile state was an int shadowing ImexRole, with the role letters duplicated in
  a second switch that wrote the on-disk format. The tile now holds
  optional<ImexRole>, with Inactive spelled as the absence of a role rather than a
  fifth integer, and the letters come from kImexRoleTable.

Four further changes, from testing rather than the review:

- Deleting a mode reported only the row's current name, so renaming a mode and then
  deleting it left every plate using it stranded on a name that no longer exists.
  Both the build-time and current names are now reported, minus any a surviving row
  still carries.
- The instruction text and colour legend were built once in the constructor and
  never rebuilt, so raising gantry count to 2 gave the tiles a Span role the legend
  never explained until the preset was saved and the page reopened. Both are
  rebuilt with the grid, and the per-role detail moved into legend tooltips so the
  panel no longer opens with a paragraph.
- The tile holding Primary is now read-only. Primary is tool 0 and moves only via
  Tool 0 Position; a click could previously demote the only Primary, leaving a mode
  that parses to no primary at all, which degrades the plate to an ordinary
  single-tool print with nothing in the editor showing what is wrong. A mode
  arriving without a Primary keeps every tile live so it can still be repaired.
- Names and G-code were read with ToStdString() (the ANSI codepage on Windows) and
  written with from_u8() (UTF-8). On a non-UTF-8 codepage a name like "Modus A"
  with a diaeresis was stored as invalid UTF-8, came back blank, and was then
  silently renamed by the auto-naming above. Every read is now into_u8() and every
  write from_u8(); EditGCodeDialog was affected in both directions.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 00:56:26 -04:00
Clifford GarwoodandClaude Opus 5 5b30af463d Reduce PartPlate's zone code to a wrapper and key its cache on the tool layout
calc_imex_zones() is now 69 lines: fetch the two edited configs, call
compute_imex_zone_layout(), store the result, and clip each returned rect to the
bed outline to build the GLModels. That last step is the only part that needs GUI
types, which is why it stayed. See the extraction commit for the behaviour-
preservation evidence.

refresh_imex_slice_offset() is deleted along with its call in
update_slice_context(); the offset is derived in the engine now, and it was
computing it in the plate-list world frame, which double-counted the plate origin
for every plate after the first.

Two smaller changes:

- The zone/ghost cache key omitted imex_tool_layout, which decides which physical
  corner tool 0 occupies and therefore moves every zone rectangle, collision strip
  and ghost offset while every other keyed field stays put. A layout change
  produced an identical key. That this currently appears to work is incidental --
  some other path happens to rebuild -- and not something to depend on. Found by
  building the preview's own cache key against this one.
- The tools-per-gantry fallback for a missing key was 1 in two places where
  PrintConfig registers 2 and the zone code uses 2. All four sites now agree; a
  missing key otherwise grouped tools against a grid divided a different way.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 00:56:26 -04:00
Clifford GarwoodandClaude Opus 5 5ac1d05fcb Stop three IMEX changes from altering non-IMEX behaviour
None of these came from the review; they were found auditing the branch for the
same class of leak review comment 6 identified.

physical_extruder_map was normalised through effective_physical_extruder_map on
every Print::apply(), so any printer with more than one nozzle and no authored map
got the identity [0,1,...,n-1] where the single-element {0} default belongs. The
key carries two readings: the IMEX paths index it by logical extruder and need one
entry per extruder, while the inherited BBL paths read it through the clamping
get_at(), for which {0} means "everything is physical 0". Deriving unconditionally
imposed the IMEX reading on profiles that mean the other one, changing the config
block line and the {first_tools} / {first_filaments} / {curr_physical_extruder_id}
placeholders for multi-nozzle non-IMEX printers. Gated on is_imex; every consumer
needing the per-extruder form is already IMEX-gated, and profiles with an authored
map of the right length are unaffected either way.

That gating unmasked a latent out-of-bounds read: WipeTower's M104/M109 emitters
index m_physical_extruder_map by tool with no bounds check, which reads past the
end of the single-element default on a multi-nozzle machine. Upstream's bug, from
the BambuStudio wipe tower sync, previously hidden because the map was being
widened for everyone. Now bounds-checked, falling back to the tool's own index --
the form GCodeProcessor already uses for the same map.

Preset::save() and get_preset_differed_for_save() carried a branch storing the
full vector whenever a child and its parent had different lengths. It was written
against a set_with_nil that threw on mismatched sizes; upstream #13035 replaced
that with a tolerant version that keeps the child vector verbatim and nil-marks
only the overlapping range, and that fix was already in the tree when this branch
was rebased. Left in, it defeated the delta encoding for every user printer preset
whose extruder count differs from its parent's: a 7-extruder profile inheriting a
single-extruder base wrote out all of its per-variant retraction keys as literals,
including ones identical to the parent, pinning them against future vendor updates
while the UI still reported the preset as inheriting. Removed; the two save loops
are now identical to upstream. Note this only affects new saves -- presets already
written keep their frozen values until re-saved.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 00:55:58 -04:00
Clifford GarwoodandClaude Opus 5 a5ba39393e Escape the IMEX plate attributes written into the 3MF
Closes review comment 1.

imex_parallel_mode and imex_head_filament_map were streamed raw into XML attribute
values, while every other free-text attribute in the same writer goes through
xml_escape. Mode names are free text, so "PLA & ABS", a quote or a "<" made the
document malformed. The failure is not a bad value on reload: both load paths for
model_settings.config return false on an expat error, and m_is_bbl_3mf is set
before the second entry loop runs, so the whole project fails to open with
"Archive does not contain a valid model config".

Both attributes now use xml_escape_double_quotes_attribute_value(), which also
emits tab, CR and LF as numeric character references. That matters and plain
xml_escape would not do: XML normalises literal whitespace in attribute values on
read, so a tab in a mode name would come back as a space and silently rename the
mode. The read side needs no change -- it takes expat's already-decoded value with
no second unescape -- so this is a lossless round trip and a file written by the
new code still loads in an older build.

The round-trip test used "copy_mode", which exercised none of this; it now carries
&, <, a quote and a tab, and also pins that ' and > come back unmodified, since
both are legal raw inside a double-quoted value.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 00:55:58 -04:00
Clifford GarwoodandClaude Opus 5 5629dd29e9 Restore the pre-IMEX pressure advance output for non-IMEX printers
Closes review comment 6.

The per-tool pressure advance work changed set_pressure_advance() for users who
are not using the feature. RepRapFirmware lost its D qualifier when no tool index
was supplied: upstream emits M572 D0 S<pa> unconditionally, and a bare M572
applies to whatever tool is currently selected and errors when there is none, so
PA started depending on tool-selection state for every RRF user. The D is back,
defaulting to 0, and D<tool> is reached only from the IMEX paths.

The same rewrite had also changed the comment separator from "<value>; Override"
to "<value> ; Override" on the Klipper, RRF, Marlin 2.x and Marlin Legacy
branches, so every non-IMEX print of those flavors carried a one-byte diff.
Restored. Upstream is internally inconsistent here -- BBL and Repetier do use the
spaced form -- and the point is to match it exactly rather than to tidy it.

Emitted output for all six flavors with no tool index is now byte-identical to
upstream. Verified on a real slice: a Klipper profile emits
"SET_PRESSURE_ADVANCE ADVANCE=0.02; Override pressure advance value", an exact
string match, with no EXTRUDER= qualifier. The tests were pinning the regressed
form and are inverted.

Also records at the imex key registrations why they are kept out of the g-code
config block, matching the house convention at the other banned keys.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 00:55:35 -04:00
Clifford GarwoodandClaude Opus 5 612e0e3932 Fall back to Primary when an IMEX mode does not resolve, and keep imex keys out of the config block
Closes review comment 2, part of 18, and one non-IMEX regression the review did
not cover.

An imex_parallel_mode naming no entry in imex_mode_names still entered the
parallel branches. get_imex_active_tools() returned empty and the else was
skipped, so no head received its 1st-to-2nd layer temperature transition, and
imex_suppresses_bare_toolchange() still dropped the initial T<n> on the
expectation that a mode script would select the tool. validate() did not catch it
because its guard is declared_primary >= 0 and an unresolved mode yields -1. You
reach it by renaming a mode after a plate is set to it, or by opening a 3MF whose
printer preset names its modes differently. m_imex_parallel_mode is no longer
assigned before the lookup; a non-Primary name that matches no row now warns and
re-resolves against the Primary row, which is the fallback the PR description
already claimed. A mode that resolves to an empty tool roster takes the same path,
since the emitted G-code is wrong in the same way.

Warned rather than blocked: opening someone else's 3MF is a legitimate way to get
here and the Primary reading prints correctly, so refusing to slice would turn a
recoverable situation into a dead end. Silent was not an option either, because
the plate keeps showing the stale mode name while drawing no zones.

Separately, the 14 imex config keys all register non-nil defaults, so
append_full_config was emitting "; imex_* = <default>" into every exported
G-code, including on single-nozzle printers with nothing to do with the feature.
They are banned from the dump, matching the treatment already given to the
fast-purge, extruder-change and timelapse keys, so the config block is
byte-identical to the pre-IMEX baseline for the whole shipping fleet. Nothing
reads them back: GCodeProcessor has no imex reference, and the two per-plate keys
round-trip through the 3MF's model_settings.config on an independent path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 00:55:35 -04:00
Clifford GarwoodandClaude Opus 5 3629056831 Derive the IMEX slice offset in the engine
Closes review comment 11, and fixes a worse bug found while doing so.

The offset was only ever pushed from PartPlate::refresh_imex_slice_offset(),
reachable from update_slice_context() and the plater -- both GUI-only, and it
dereferences wxGetApp(). A headless slice therefore kept Vec2d::Zero(), so
orca-slicer --slice on a plate with imex_firmware_managed_zones emitted
slicer-managed coordinates while the firmware applied its own offsets on top.
Print::update_imex_slice_offset() now derives it from the applied config and runs
from process() and export_gcode(), so a CLI slice gets the value a GUI slice does.
It reads m_full_print_config rather than m_config because imex_tool_layout and
imex_carriage_margin are printer-preset options with no member in the static
PrintConfig, and it takes the mode from the same place GCode.cpp resolves it, so
the shift cannot disagree with the mode that is emitted.

The GUI push and Print::set_imex_slice_offset() are deleted rather than kept as an
override, because the two did not agree. calc_imex_zones() divides
get_extents(m_shape), and set_shape translates m_shape by the plate position, so
the pushed offset carried the plate origin -- which translate_to_print_space() and
the writer offset already subtract. Plate 1 sits at the origin and agreed by
accident; every later plate had the origin subtracted twice and was shifted by a
full plate stride. Not silent, either: the displaced geometry fell outside the
printable area, so slicing plate 2 failed validation with "part is off the plate".
Confirmed fixed on hardware profiles -- the same model on plates 1 and 2 now emits
identical extents.

Deleting the push also removes the post-apply ordering constraint that forced the
duplicate call in Plater::priv::update_background_process: the value is computed
at the point of use, and process()/export_gcode() are structurally after apply().

Also routes validate()'s primary-routing check through imex_resolve_routing() so
the hard block and the plater's warning cannot describe a plate differently
(review comment 20), and through find_imex_mode() for the mode lookup (18).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 00:55:35 -04:00
Clifford GarwoodandClaude Opus 5 fcfda6c835 Move the IMEX bed-zone geometry into libslic3r
Closes review comment 16, and fixes three defects the move exposed.

PartPlate::calc_imex_zones() was 392 lines deciding where every zone, collision
strip and safety margin sits, in the GUI layer, with no test coverage. The three
wxGetApp() calls that kept it there were all in its first 25 lines, fetching two
configs. The geometry now lives in compute_imex_zone_layout(); the wrapper fetches
the configs, calls it, and clips the returned rects to the bed outline for the
GLModels, which is the only part needing GUI types. libslic3r gained no wx
dependency: it takes DynamicPrintConfig directly, so the option lookups moved
verbatim rather than through a hand-written value struct that could drift.

The move is otherwise exact -- verified by a line-for-line diff of every
arithmetic expression against the original, and by running 15 scenarios through
the extracted code against hand-derived values. The only deletion is a lambda that
was never called.

Three fixes on top, each of which needed the code to be testable:

- An off-grid or absent Primary left pri_col/pri_row at their (0,0) initialisers
  and built a layout from them, reporting the whole bed as the clear primary zone
  and the whole bed as a blocked mirror zone at once; under
  imex_firmware_managed_zones it shifted the slice by the bed centre. Guarding on
  the resolved primary head covers both routes. Reachable only from a hand-edited
  preset or a 3MF authored against another printer -- the editor pins Primary to
  tool 0 -- but that is the same class the unresolved-mode fallback handles.
- imex_nozzle_clearance_x/y fell back to 0.0 where PrintConfig registers 30.0.
  Both strip loops are gated on the value being positive, so the fallback emitted
  no collision strips at all while the preview still drew 30 mm toolhead boxes.
- The collision-strip loop asked each mirror head for its own grid cell, but an
  aggregated gantry's cell is pinned to the primary's column and expanded into a
  full-width row strip. Where the representative's column differed from the
  primary's, no boundary matched and the plate came back with no strips and no
  margin bands -- an object flush against the shared boundary sliced without a
  warning while the far carriage occupied it. Present since Span aggregation was
  added in 4966d0fae8 and carried out of PartPlate verbatim. The flags now come
  from the painted cells; an exhaustive sweep of the reachable grid, role and
  layout space (1,630,720 configurations) shows the only behaviour change is the
  missing strips appearing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 00:55:04 -04:00
Clifford GarwoodandClaude Opus 5 171a721304 Harden the IMEX helper layer and give it one mode lookup
Closes review comments 3, 5, 7, 8, 18 and 19, plus the library half of 20.
These share a file, so they share a commit; each is independent of the others.

- 3: ::isspace(char) is undefined for bytes above 0x7F because char is signed on
  our targets. Three call sites now go through one strip_whitespace() using an
  unsigned char cast. Line 308 parses imex_head_filament_map straight out of 3MF
  metadata, so a non-ASCII byte reached it without passing through the UI.
- 5: an imex_head_filament_map override past the end of physical_extruder_map now
  falls through to the printer's own routing instead of resolving to a wrong
  filament. Bounded in resolve_filament_for_head, where the slot count is known,
  rather than at the parse site, which has no count to check against; the parse
  site also gains the absolute MAXIMUM_EXTRUDER_NUMBER cap its sibling already had.
- 7: imex_physical_heater_for's !is_imex early return is what keeps a stock BBL
  profile (physical_extruder_map [1,0]) out of the heater remap, and had no test.
  Six cases now cover it, pinning pass-through rather than get_at()'s clamp.
- 8: ImexRole::Span was missing from the imex_head_transform switch, so it warned
  under -Wswitch. Identity is correct, not merely convenient: a Span tool prints
  the primary's own zone through mid-print toolchanges and has no zone to be
  translated into.
- 18: three positionally coupled string vectors were resolved by nine open-coded
  lookups using three incompatible bounds idioms. None read out of bounds, but six
  folded the guard into the match condition, so a ragged row did not stop the scan
  and a later duplicate name could win. struct ImexMode + find_imex_mode() is now
  the only resolution rule: the names array is the roster, first match wins, a
  short sibling pads to empty and sets ragged, not-found is an explicit -1.
- 19: the letters P/C/M/S existed in three independent copies, one of which was the
  writer of the on-disk format. kImexRoleTable is now the single source, read by
  both parsers and the serializer. Adding a role was 14 edit sites with one
  compiler-enforced; it is now the enum, the table entry, and four -Wswitch
  switches. Verified by adding a fifth enumerator and recompiling: exactly four
  warnings, nothing else.
- 20: imex_resolve_routing() extracts the mode/primary/routing chain that
  Print::validate and the plater's warning collector each derived separately.

The three config keys keep their names, types and on-disk representation. This is
a read-side view only; presets and 3MF files are unaffected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 00:55:03 -04:00
Clifford GarwoodandClaude Opus 5 855c1b51ac fix: size the destination row before migrating per-variant values
update_values_from_multi_to_multi_2 iterates the destination PRINTER's variant
list while writing into a row taken from the destination PRINT preset. Those two
lengths are maintained independently -- print_extruder_variant against
printer_extruder_variant -- and Tab::load_current_preset() runs the migration
before the print preset is re-selected for the new printer. Opening a project
saved on a single-variant printer and switching to a seven-variant one therefore
wrote six elements past the end of a one-element vector. The corruption stays
silent until the next allocation, so the abort surfaces somewhere unrelated and
the backtrace points at innocent code.

Size the row to the variant count before indexing it. Every write is then in
range, and the result carries one value per destination variant, which is what
the callers consume. Pad with nil rather than a copied value: set_to_index()
skips nil entries, so a variant the object has no opinion about keeps tracking
the print preset instead of being pinned to another variant's number.

The same shape -- a count from one array indexing another -- appears twice more
in this file. update_values_from_multi_to_multi has three of these writes
protected only by assert(idx < old_count), and NDEBUG is defined for every
non-Debug configuration, so those guards are absent from shipping builds.
update_values_from_single_to_multi has the read half. Both are bounded here;
leaving them would fix one third of one defect.

Source reads are bounded too. is_nil(size_t) indexes values[idx] without
checking, so an index past the end was undefined behaviour on that side as well.

Where the row already matches the variant list -- every case that was not
corrupting the heap -- the resize is a no-op and the output is unchanged.

Fixes #15455

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 01:47:45 -04:00
Clifford Garwood 9abc3bcaf7 Merge upstream main: mixed-filament extruder-count fix, FFmpeg camera view, plugin storage API, warning cleanups 2026-08-30 23:58:48 -04:00
Clifford GarwoodandClaude Opus 5 5c73f0b1c8 refactor(gcode): drop the wipe tower's unreachable preheat rewrite
append_tcr2 scanned the tower's G-code for a "preheat T<n>" comment and rewrote
its S value to the interface temperature. Nothing it could match was ever there.

That comment has exactly one producer, GCodeProcessor's backtrace injector, and
that runs inside run_post_process() -- a pass over the finished, exported file.
append_tcr2 runs while the file is still being generated, so the text it looked
for did not exist yet and could not.

The loop therefore walked every line, matched none, and swapped the string for
an identical copy. Delete it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 10:36:15 -04:00
Clifford GarwoodandClaude Opus 5 1aa9bb8d93 fix(imex): make the plate badge count filaments the way validate does
The badge is meant to predict whether slicing will be refused, and it delegates
to the same helper for that reason. It was feeding that helper a different
filament list. get_extruders(true) resolves a mixed slot into its physical
components -- right for AMS mapping, which has to know what is actually loaded
-- while Print::validate counts the slot itself.

So a plate holding one two-component blend reads as two filaments to the badge
and one to validate. The badge sees two, decides the plate is fine, and stays
silent; the slice is then refused. It also runs the other way: a plate the user
sees as a single colour draws a multi-material warning, because the expansion
made it look like two.

Give get_extruders an expand_mixed flag, defaulted so every existing caller
keeps the resolved list, and have the badge ask for the authored one.

The badge was also only mirroring validate's multi-color rule, not its first
one -- a mixed filament is unsupported in a parallel mode outright. Without it
the badge stays quiet on exactly the plate validate refuses first.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 10:36:15 -04:00
Clifford GarwoodandClaude Opus 5 2473ca7496 docs(imex): record that the primary-routing refusal is a scope choice
The rule's comment claimed the plate "is not printable as configured: the
primary tool executes the toolpaths while the flow and temperatures were
computed for a filament it cannot load". That is not what the emitter does. It
never uses the declared primary -- it re-derives an effective one from the
filament actually in use -- so a plate whose only filament sits on a Span tool
sharing the primary's gantry produces coherent G-code and would print.

The refusal is still correct, but it rests on intent rather than physics: a
parallel mode exists to run carriages in parallel, and a single-colour plate
riding one span lane is not that. Left as a physical-impossibility claim, the
rule reads as a false positive to anyone who checks it against the emitter --
a review already flagged it as one -- and the obvious "fix" is to relax it.

Say which it is, and keep the genuinely-broken case distinct: a filament routed
to a head outside the mode's active tools still yields a stuck-hot nozzle, and
that one is not a matter of taste.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 09:04:50 -04:00
Clifford GarwoodandClaude Opus 5 018091f43b fix(imex): let the mixed-filament refusal outrank the multi-color rule
A mixed filament is unsupported in a parallel mode outright, but the rule saying
so ran third. A plate carrying a blend plus any second filament tripped the
multi-color rule's used > 1 gate first and was told its active tools all sit on
one gantry -- a diagnosis of a multi-color print the user never configured,
whose remedy is to go rework the mode's tool roster. The blend was never
mentioned. Move the check ahead of both rules below it; being unsupported
regardless of routing or topology, it dominates them.

Nothing is masked that leads anywhere else: every branch of
imex_multicolor_block_reason is itself confined to non-primary modes, so the
mixed message's remedy -- switch this plate to Primary -- silences those too.

Say "Mixed filaments", not "Blended". Every other string in the app calls these
mixed, including the button that creates one and the sibling refusal for the
wipe tower filament, so the user had no way to connect the message to the
feature it names.

Three comments in the block were wrong, and two of them were newly wrong. The
routing rule's bounds-check note still said "Blended slots are out of range by
construction, but they never reach here -- the rule above returns first": the
rule above is now the multi-color one, which does not return first for a single
mixed filament, and out-of-range is not guaranteed at all. Mixed slots are kept
at the tail of the filament arrays by convention, not by enforcement --
PresetBundle::set_num_filaments grows filament_is_mixed with resize(), so
raising a printer's extruder count with a blend present lands physical slots
after the mixed one. The scan is position-agnostic and stays correct; only the
stated reason was wrong.

The same discovery makes the empty-routed_list guard live rather than the dead
code it was described as. Print::apply() normalises physical_extruder_map before
validate() runs, so an unauthored map is never the cause -- but a printer with
more filaments than logical extruders leaves the tail slots outside the map, and
raising the extruder count does exactly that.

The new test validates the plate twice. The first pass, with no blend, asserts
the multi-color rule is armed at all; without it the second proves nothing,
because the rule only fires here thanks to a degenerate fixture mode whose two
tools share a gantry. Give that mode a Span tool and the whole test would pass
under either ordering while appearing to guard it. It also pins err.object,
which the mixed path sets and the multi-color path leaves null -- a discriminator
that survives the next wording change. Verified by reverting the order: the test
fails on both the message and the object.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 08:48:14 -04:00
Clifford GarwoodandClaude Opus 5 2e883df7d9 fix(imex): name the physical head in M104/M109 tool indices
M104/M109 address a heater, but every caller of the instance
GCodeWriter::set_temperature overload addresses filaments by logical id, so on a
printer whose physical_extruder_map is not the identity the emitted T named the
wrong head -- or, where the logical id exceeds the head count, no head at all.
With a map of 0,0,0,0,1,2,3 a toolchange to filament 5 emitted "M109 S265 T4"
and "M104 S190 T4 ;cooldown" while the head it meant was T1.

Upstream already treats these commands as physical: the preheat it injects in
GCodeProcessor maps through the same map before emitting, and BBS's own wipe
tower does likewise. Emitting logical is the half that never got the memo.

That mismatch also disabled the cooldown suppression beside the preheat, which
compares the line's T against pem[tool_number] and so never matched a logical
one -- 171 cooldowns survived in a two-head print where none should have. Worse,
it could match the wrong line: a cooldown for filament 1 emitted T1, and a
toolchange to filament 5 gives pem[4] == 1, so a legitimate cooldown for head 0
was deleted because the incoming head happened to be numbered 1.

Translate once, in the instance overload every logical-space caller passes
through. The static overload is already physical-in and is left alone.

Gated on is_imex. physical_extruder_map carries two readings in this tree: the
BBS paths index it by extruder id, the IMEX paths by filament id, and the two
coincide only when the filament and nozzle counts match. Mapping unconditionally
would impose the IMEX reading on profiles that mean the other one --
fdm_bbl_3dp_002_common ships a non-identity [1,0], spared today only because
single_extruder_multi_material suppresses the T qualifier entirely.

The wipe tower's interface-temperature pass has to move with it. It strips the
M109 that post_toolchange emits by searching for that filament's tool index, so
it now searches for the mapped one; left alone it would have stopped matching,
and the surviving blocking M109 would have silently defeated the interface
temperature. Its sibling pass reads WipeTower2 output, which emits no T at all,
and is deliberately unchanged.

The bare T<n> toolchange stays logical -- it selects an AFC lane, not a heater.

Test slices two objects across a head boundary, the only case that reaches this
emission: the same-physical short-circuit in set_extruder suppresses the
cooldown entirely for lane swaps within one head. It scans every M104/M109
rather than matching fixed strings, so it catches any unmapped emission and not
just the two sites changed here. With the mapping neutered it reports 101
offending lines; with it in place, none.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 22:04:23 -04:00
Clifford GarwoodandClaude Opus 5 f0778ef5fa fix(imex): shorten the off-primary message and block blended filaments
The routing error ran to roughly 450 characters and explained the mechanism
before it got to the remedy. It also offered to "edit the mode in Printer
Settings so its Primary tool is one of %3%", which on a plate whose filaments
resolve to no head at all rendered as "one of no configured extruder". Cut it
to the mode, the tool it prints with, where the plate's filaments actually are,
and the two things the user can do about it.

The second msgid that named candidate modes went with it. It could only suggest
a mode whose primary is among the routed heads, and every mode on the printers
this fires for declares 0:P, so it had nothing to offer.

Blended filaments now return before that check rather than falling through it.
A blend is mixed at the nozzle by its component toolheads, and a parallel mode
is already using those toolheads to print copies or mirrors, so the two cannot
run at once regardless of where the components route -- including when a
component sits on the declared primary. Reaching the routing rule would also
have described them wrongly: mixed slots sit past the end of
physical_extruder_map, so they resolve to no head and read as merely unrouted.

Keeps the empty-list guard the shortening first dropped. validate() reads the
raw physical_extruder_map, whose registered default is a single entry, so a
profile that declares IMEX modes without authoring a map leaves every slot past
the first outside it -- and the sentence ended in a dangling "on .".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 22:04:02 -04:00
Clifford GarwoodandClaude Opus 5 b24733cea8 Merge the color mixing subsystem into the IMEX work
Brings the upstream color-mixing feature and its follow-ups onto the branch so the
IMEX placement and primary-routing checks are built and tested against them for the
first time.

Merged clean, no conflicts. Not yet exercised together: a mixed filament is a virtual
slot no nozzle carries, while physical_extruder_map routes logical slots to physical
heads, so the IMEX pem lookups have no defined answer for one. Print::extruders()
lists mixed slots under their own id while tool_ordering.all_extruders() lists them
post-expansion, and the IMEX code reads both.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 09:25:41 -04:00
Clifford GarwoodandClaude Opus 5 008d13a59e fix(imex): keep the placement error across every validation path
Plater::validate_current_plate() runs the same background_process.validate() as
update_background_process(), and on success clears update_apply_result_invalid(false)
and closes the ValidateError notification -- but it never consulted
imex_placement_violation(). Any event reaching it wiped a live IMEX placement error
and re-enabled the Slice button on a plate the slicer still refused; pressing Slice
then hit the check in reslice() and returned early, so the job simply never started.

Reproduce by clicking the bed with the prime tower overlapping a reserved area.
Plater::select_plate_by_hover_id() -> select_plate() calls validate_current_plate()
unconditionally, even when the clicked plate is already current, and deselects as a
side effect -- which makes it look as though deselecting the tower cleared the error.
Escape and clicks off the bed go through selection_changed(), which only renders and
clears nothing.

Extract the violation-to-message mapping into one helper and call it from both paths.
model_fits is set alongside err.string in validate_current_plate, mirroring the
missing-plugin block below it: the slice is already gated by m_apply_invalid, but
leaving m_ready_for_slice true would trap a future consumer that reads it alone.

These are the only two sites that matter. NotificationType::ValidateError has exactly
three references in the tree and update_apply_result_invalid exactly four; the other
slice-ready writers can only touch m_ready_for_slice, never m_apply_invalid, so they
cannot re-enable Slice on their own.

Three adjacent gaps are left alone, all pre-existing: "Slice all" is hard-coded
always-enabled regardless of plate state; a slice-all batch halts silently at a
violating plate because reslice() returns above the line that queues the advance; and
object_list_changed() computes its own can_slice from geometry, harmless only because
the result is ANDed with PartPlate::can_slice().

This is a hole in the shipped tower-zone check rather than a regression from rotating
the tower hull -- it was simply invisible until a tower could be placed in violation.

No automated gate: Plater is GUI-only, the helper is file-local and unlinkable, and
imex_placement_violation() needs a live wxApp and preset bundle. "Both call sites
consult it" is a call-graph property no unit test can express. The grep for a single
imex_placement_violation reference is a future regression tripwire, not evidence this
refactor happened -- it already returned 1 beforehand.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 08:25:25 -04:00
Clifford GarwoodandClaude Opus 5 ba07716ddb Merge upstream main: color mixing feature and related fixes
Brings in 54 upstream commits, the bulk of them the BambuStudio-ported color
mixing / mixed filament subsystem (#15347) plus its follow-ups, along with the
Assimp-backed colored OBJ import, warning-policy build changes, and assorted
profile and localization updates.

Two conflicts, both "each side added at the same point", resolved by keeping
both:

- Print::validate() -- our IMEX multi-color block and upstream's new gradient
  mixed filament warning were inserted at the same spot after the empty
  extruders check. They test unrelated conditions, so both are kept, each with
  its own closing brace.
- tests/libslic3r/test_3mf.cpp -- our three IMEX per-plate round-trip scenarios
  and upstream's mixed-filament round-trip scenario both append to the end of
  the file, and each side added one include. All four scenarios and both
  includes are kept.

Everything else merged cleanly, including GCode.cpp, ToolOrdering.cpp,
PartPlate.cpp and PrintConfig.cpp. Upstream left the is_extruder_used block
untouched, so the IMEX supplement still applies, and estimate_wipe_tower_polygon
is unchanged, so the prime tower hull work is unaffected.

Not addressed here, and worth its own change: a mixed filament is a virtual slot
that no nozzle carries, while physical_extruder_map routes logical slots to
physical heads. Print::extruders() lists mixed slots under their own id whereas
tool_ordering.all_extruders() lists them post-expansion, so the IMEX pem lookups
have no defined answer for a mixed slot. Upstream's own guards reject a mixed
filament where a physical slot is required; IMEX likely wants the same.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 13:07:26 -04:00
Clifford GarwoodandClaude Opus 5 baef398ee3 fix(imex): refuse a plate whose filament cannot reach the mode's primary tool
The IMEX primary tool prints the sliced paths directly, so it can only load a
filament that physical_extruder_map routes to it. The ghost filament picker
enforces that for the secondary tools -- it offers only lanes whose pem entry
equals that head -- but the primary's filament comes from the ordinary object
filament selector, which has no IMEX awareness. Nothing detected the mismatch:
collect_imex_warnings() computes the same condition and discards it into a
display fallback, and the multi-color rule never examines it.

Block it in Print::validate() via the existing imex_primary_tool_for_mode and
imex_primary_logical_from_objects helpers. The message names the declared
primary, the heads the plate's filaments actually live on, and any configured
modes whose primary would work, and carries the object so the notification can
offer a jump to it.

Blocks rather than warns, matching the multi-color rule: the plate is not
printable as configured, and where the routed head is also absent from the
mode's active tools the 1st->2nd layer temperature branch skips it too, leaving
that head at its initial-layer temperature for the whole job.

The multi-color check now runs first. Its constraints -- an MMU manifold sharing
one head, a single-gantry mode -- cannot be fixed by switching mode, so the more
specific error should win rather than be masked by routing advice that leads
straight back to it. The extruders().size() > 1 gate moved onto that call, since
the routing check must also see single-filament plates, which is its common case.

The copy-mode guard-rail test printed on a filament routed off the primary, so
it asserted a plate this rule now refuses; retargeted to a well-formed plate.
Its replacement pins the object's own extruder, because ModelVolume reports its
extruder_id and would otherwise put a primary-routed slot on the plate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 12:58:52 -04:00
Clifford GarwoodandClaude Opus 5 8edf6b9d4f fix(imex): rotate the prime-tower hull before checking it against carriage zones
imex_wipe_tower_hull() took the axis-aligned box estimate_wipe_tower_polygon()
returns and compared it to the IMEX collision zones as-is. The real tower is
rotated about its anchor corner before placement -- first_layer_wipe_tower_corners
builds the box in tower-local coordinates, rotates about the local origin, then
translates by wipe_tower_x/y -- so a rotated tower's true footprint fell outside
the hull and the placement check passed on a tower that intrudes into a
carriage's reserved space.

The gap was documented in place and previously harmless, because
wipe_tower_rotation_angle was read from the project config after it had been
moved to the print preset, so the setting did nothing. Upstream repaired that
read ("Fix prime tower rotation angle setting not working"), which makes the
angle reachable and the stale hull wrong.

Rotation is applied to the hull alone. estimate_wipe_tower_polygon() still
returns an unrotated box and still leaves ArrangePolygon::rotation unset, since
the arranger consumes that field separately.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 01:09:38 -04:00
Clifford GarwoodandClaude Opus 5 90d1481463 Merge upstream main into the IMEX work
Upstream replaced MainFrame's fixed-index TabPosition enum with string-based
page ids ("feat: refactor notebook/tabs to be string based instead of fixed
index based"). The IMEX toolhead-visibility menu item was the only consumer of
that enum left on this branch, so its enable check now compares
m_tabpanel->GetSelectedPageName() against TAB_ID_PREVIEW -- the same form the
neighbouring upstream menu items use.

That mismatch is what broke CI: the branch built on its own, but the merge
commit CI builds no longer had TabPosition declared. No other conflicts.

666/666 tests pass in Release.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 17:34:10 -04:00
Clifford GarwoodandClaude Opus 5 99486a5597 Merge IMEX G-code emission and plate-icon raycaster fixes
- parallel modes now give the printing head its second-layer temperature
  transition; it previously held nozzle_temperature_initial_layer all print
- single-tool primary mode no longer marks a phantom filament slot used, which
  made machine_start_gcode heat an extruder that never prints
- the IMEX mode icon's bed raycaster is re-registered after the icon is
  rebuilt, fixing a use-after-free in the picking pass

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 14:54:35 -04:00
Clifford GarwoodandClaude Opus 5 a5b2600b2c fix(imex): re-register the IMEX mode icon's bed raycaster after rebuilding it
SceneRaycasterItem keeps the MeshRaycaster it was registered with as a raw
pointer, while PickingModel::reset() destroys it through a unique_ptr. Rebuilding
an icon therefore invalidates any registration still referring to it.

refresh_imex_icon(), reached only from Plater::on_config_change when is_imex or
the bed shape changes, rebuilt the IMEX mode icon without touching the
SceneRaycaster. The stale entry survived, and the next picking pass dereferenced
freed memory inside AABBMesh::intersect_ray.

Swap that one registration in place, matching how calc_vertex_for_plate_name()
handles the name-edit icon. Only the mode icon is registered for picking -- the
warning badge beside it is a plain GLModel -- so a single id is affected and the
blanket remove/re-register reload_scene() performs is not needed here.

Crashes were delayed and looked unrelated to the config change, because bed
raycasters are only tested when the camera looks down (SceneRaycaster::hit). The
reported dump landed on File > New Project, whose render ran a picking pass with
a registration that had gone stale earlier.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 12:37:28 -04:00
Clifford GarwoodandClaude Opus 5 ef8d80980d fix(imex): enumerate no secondary carriages in single-tool primary mode
get_imex_active_tools returned every physical head named by the active mode's
tool string, including the one carrying the Primary role. The pressure-advance
and nozzle-temperature sites are already gated on the mode not being primary, so
only the is_extruder_used supplement was exposed.

In primary mode that supplement treated the mode's single declared tool as a
secondary carriage and marked its filament slot used, so machine_start_gcode
emitted a heat command for an extruder that never prints. The phantom slot
appears when the mode's declared tool differs from the head the initial tool
routes to through physical_extruder_map -- on an AFC/MMU layout, printing with a
filament that lives on any head other than the declared one.

Return an empty roster for primary mode, where there are no parallel carriages by
definition. This lives in the enumerator rather than at the call site because the
mode is already resolved and normalized there, and the two guarded callers cannot
reach it in that mode, so their behaviour is unchanged.

Scope: this closes the primary-mode instance. The same phantom slot still occurs
in a parallel mode when the initial tool's head is not the mode's declared
Primary, which turns on which of the two notions of "primary" the three emission
sites should skip. That question is unresolved and deliberately left alone here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 12:37:28 -04:00
Clifford GarwoodandClaude Opus 5 c94e8324d0 fix(imex): give the printing head its second-layer temperature in parallel modes
The IMEX branch of the 1st->2nd layer temperature transition is mutually
exclusive with the standard per-extruder path in its `else`, but it skipped the
head carrying the print's own toolpaths on the premise that "the standard
per-extruder temp path already addresses it". That path is the `else` branch and
never runs for a parallel mode, so the printing head received no transition at
all and held nozzle_temperature_initial_layer for the entire job.

Emit for every carriage the mode drives, the printing one included. The printing
head takes this layer's own filament; the parallel carriages, which carry no
toolpaths of their own, keep resolving through the per-plate head map with pem
inversion as the fallback. The lookup now goes through get_filament_config_index()
like the standard path, since a variant-expanded printer gives a filament its own
column and a raw index would read the wrong one.

Reproduced on a 4-carriage IQEX in copy mode: the only temperature command in the
whole file set the idle secondary carriage to the value it already had, while the
head doing the printing never left its first-layer temperature. The defect is
invisible whenever initial and regular temperatures match, which is why earlier
per-tool validation passed.

Tests cover both gantry counts, since the active set comes from the mode's tool
roster: an IDEX copy mode drives two carriages, an IQEX mode drives four, and the
IQEX case asserts a first-layer filament and a second-layer transition for each of
the four.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 12:37:28 -04:00
Clifford GarwoodandClaude Fable 5 8a5bc75e7b fix(imex): key the resolved tool roster and fw-managed flag in the zone/ghost cache
Review follow-up. Removing pem from the ghost cache key also removed its
accidental role as the key's only printer-identity signal: the resolved
active-tools string (roster + primary) and imex_firmware_managed_zones both
shape the baked zone/ghost set but were never keyed directly, so a printer
swap between presets with matching mode names and topology could leave a
stale ghost set. Key all three in build_imex_cache_key, which also hardens
the zone cache against the same pre-existing gap.

Also from review: the tooltip swatch reuses the pem/map its label already
hoisted (one resolution, not two); the bake constructs ghosts with no color
at all, making update_imex_ghost_colors the sole color author; the headless
!m_plater guard is documented as the wxGetApp sentinel it is.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 16:54:48 -04:00
Clifford GarwoodandClaude Fable 5 28c0c681fe refactor(imex): make render the sole author of ghost RGB; scope cache key to mesh inputs
Follow-up to the live ghost-color fix, addressing review findings: with the
render loop restamping ghost colors every frame, the bake-time resolution in
calc_imex_ghosts was dead code (its RGB was displayed for zero frames), and
the pem + head-filament-map entries in the ghost cache key had become
color-only inputs that forced a full mesh re-bake — including a visible
hitch on every ghost-picker selection — for what is now a pure recolor.

- calc_imex_ghosts bakes an alpha-only placeholder; IMEX_GHOST_ALPHA is
  hoisted to file scope as the single opacity authority (no more reading
  alpha back out of the field the restamp overwrites).
- build_imex_ghost_cache_key drops pem and the head-filament map; the
  forced invalidations in set/reset_imex_head_filament_map go with them.
  Picker selections now recolor live with no rebuild.
- The restamp moves into PartPlate::update_imex_ghost_colors(), beside the
  transform refresh, so PartPlate owns its volumes' colors and the canvas
  calls one hook. Plate-level inputs (pem, override map) are hoisted once
  per frame via a new get_imex_head_filament_color overload that the
  single-head form delegates to, keeping tooltip parity by construction.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 16:39:14 -04:00
Clifford Garwood f41752546c Merge upstream main (multi-nozzle override fix, slice-all toolbar crash guard, filament_colour_type G-code skip) into IMEX branch 2026-08-14 16:00:26 -04:00
Clifford GarwoodandClaude Fable 5 bdbdd8e222 fix(imex): resolve ghost colors live at render time, not at bake time
Ghost GLVolumes baked their filament color once in calc_imex_ghosts and
only rebuilt when build_imex_ghost_cache_key changed. The key covers the
mesh-shaping inputs (mode topology, pem, object set, per-plate head map)
but not filament_colour, and no invalidation hook fires on filament
preset or color changes — so a ghost baked under a transient palette
(late-loading project colors, a subsequently edited filament color)
kept the stale color forever. A failed lookup at bake time baked
GLVolume::UNPRINTABLE_COLOR, which renders as a jet-black ghost while
the hover tooltip — which re-resolves live — shows the correct color.

Re-stamp each ghost's color in _render_imex_ghosts from
get_imex_head_filament_color, the same resolution the tooltip runs, so
the two can never disagree. Resolution is hoisted into a per-head map
so instances sharing a head resolve once per frame; the alpha baked by
calc_imex_ghosts is preserved. The render loop already stamps color per
ghost per frame (set_render_color + model.set_color), so the added cost
is a couple of map lookups. The cache key stays scoped to what it
actually protects: the expensive mesh bake and transforms.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-10 07:45:50 -04:00
Clifford GarwoodandClaude Opus 5 ec309e1cc6 fix(imex): apply the same-physical cool-down skip only on IMEX printers
set_extruder skips the ooze-prevention standby cool-down when the outgoing and
incoming filament route to the same physical extruder. That is an IMEX behaviour --
an AFC/MMU lane swap keeps the same heater selected -- but the check was not gated,
so it ran on every printer.

73 shipping profiles enable ooze_prevention by default (37 Snapmaker, 21 WonderMaker,
9 Flashforge, plus Lulzbot, Prusa, re3D, iQ and the MyToolChanger), and the 150
multi-nozzle machines behind them author no physical_extruder_map. They were spared
only because every one declares a single variant per extruder, so the map came out as
the identity and nothing was ever suppressed. Correctness should not rest on that.

Gate on is_imex. Verified on one fixture with only is_imex differing, with two
filaments mapped to the same physical extruder: 0 cool-downs emitted with it on, 26
with it off.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 12:20:52 -04:00
Clifford GarwoodandClaude Opus 5 df4009e734 fix(imex): size physical_extruder_map from the nozzle count
physical_extruder_map has one entry per logical extruder -- the index space of
nozzle_diameter -- and its consumers size their own arrays from that count. It was
being derived from printer_extruder_id, which is indexed by variant slot: one entry
per extruder+variant pair. An X1 Carbon has one nozzle and printer_extruder_id
{1,1}; an H2D 0.4 has two nozzles and {1,1,2,2,2}. The two spaces coincide only
when every extruder declares a single variant.

The visible effect was on the standby cool-down. set_extruder skips it when the
outgoing and incoming filaments share a physical extruder, and that check is not
gated on IMEX. With the map built from the wrong array, two filaments on a
dual-nozzle machine read as sharing one hotend and the cool-down was dropped --
caught by "Toolchange temperature commands are unchanged when the wipe tower wait
is off", which failed on all five CI platforms with the ;cooldown line missing.

Derive the identity over the nozzle count instead, the same fallback Plater.cpp
already applies where a profile authors no map. A profile counts as authoring one
only when its length matches the nozzle count, so the single-element PrintConfig
default is replaced rather than read as a one-extruder machine. Authored maps pass
through untouched, including the {1,0} numbering permutation the BBL dual-nozzle
profiles ship.

Tests pin the four branches and the length invariant the consumers depend on.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 12:20:52 -04:00
Rodrigo Faselli 1a30c49993 Merge branch 'main' into main 2026-08-07 10:50:35 -03:00
Clifford Garwood 520cf3a415 Merge upstream main: prime tower on non-rectangular beds, toolchange temperature wait, ironing speed override, preset dialog QOL
# Conflicts:
#	src/libslic3r/GCode.cpp
#	src/slic3r/GUI/Tab.cpp
2026-08-06 17:07:02 -04:00
Clifford Garwood 7541d6a1e6 Merge upstream main: printer-specific OrcaFilamentLibrary profiles, convex_hull_2d test replacement
# Conflicts:
#	tests/libslic3r/test_3mf.cpp
2026-08-03 15:17:00 -04:00
Clifford Garwood 612af109f5 Merge upstream main: outer-only brim ears, Hilbert infill smooth factor, ImGui mouse capture and gizmo fixes, Wayland splash and Linux control styling, 32-bit and warning cleanups 2026-08-03 10:58:46 -04:00
Clifford GarwoodandClaude Opus 5 5e38877ba1 refactor(imex): use IMEX consistently in user-facing strings
Labels, tooltips, menu items and dialog text mixed "IDEX/IQEX" with "IMEX"
for the same feature. IMEX is now the user-facing name throughout: IDEX and
IQEX are hardware categories, IMEX is the feature spanning them. The
hardware terms remain only where they help a user tell whether the feature
applies to their printer.

Comments and log messages keep IDEX/IQEX, where the specific carriage
topology is the more precise term.

Also corrects the imex_parallel_mode tooltip, which pointed at a
"Printer -> IDEX/IQEX tab" that does not exist; the options live under
Printer -> Multimaterial -> IMEX Configuration.

None of these strings appear in any .po or the .pot, so no translation is
affected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 08:26:38 -04:00
Clifford GarwoodandClaude Opus 5 3852f4be61 fix(imex): block slicing when the prime tower overlaps a parallel-printing zone
IMEX placement validation only walked model instances. The prime tower is
not a ModelObject, so it could sit in a secondary zone or a carriage
collision strip and slice with no warning -- on mirror mode, a carriage
crash. Span (paired-gantry multicolor) is what made towers reachable in
parallel modes, so this is a gap in that feature, not inherited breakage.

The check now returns a cause instead of a bool so the message can name the
offender, and the tower and per-instance paths share one predicate,
imex_hull_violates_zones(), moved to libslic3r and covered by tests.
Overlap is area-based: a hull flush against a zone boundary is legal, only
a crossing violates. The tests pin that in both directions, since switching
to a touch-based test would silently block placements that work today.

The tower footprint comes from the same estimate the scene draws, so
validation matches what the user sees and drags. Three config reads there
are load-bearing in non-obvious ways and are commented at the point of use.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 08:26:38 -04:00
Clifford Garwood 3315b88012 Merge upstream main: rib wall prime tower, wipe tower sync, multi-extruder config fixes, plugin resolution fixes 2026-07-31 15:09:53 -04:00
Clifford Garwood bbcd2e2a80 Merge upstream main: cyclic print ordering, brim overlap fix, perimeter and support fixes 2026-07-31 01:42:48 -04:00
Clifford GarwoodandClaude Opus 5 4995a59685 fix(tests): restore SCENARIO closing brace lost in upstream merge
The merge of upstream main into the PR branch resolved the conflict in
test_gcodewriter.cpp by keeping both sides but dropping the closing brace
of the set_pressure_advance BBL SCENARIO. That nested the incoming
TEST_CASE inside it and broke the build on every target that compiles the
test suite:

  test_gcodewriter.cpp:1087: error: namespaces can only be defined in
  global or namespace scope

Close the SCENARIO before the TEST_CASE. Both tests are preserved and no
test logic changes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 23:34:13 -04:00
Rodrigo Faselli fccd9d52ce Merge branch 'main' into main 2026-07-25 19:19:07 -03:00
Clifford GarwoodandClaude Opus 4.8 f74b8177af fix(imex): repaint plate-selector icon when a plate's slice-ready state flips
The big upstream merge replaced the old per-idle unconditional plate-selector toolbar
refresh with a dirty flag (Plater::mark_plate_toolbar_image_dirty), which the
geometry-change sites already set. PartPlate::update_slice_result_valid_state flips a
plate's slice-ready / IMEX blocked-plate ("naughty plate") state without any geometry
change and did not set the flag, so after the merge nothing repaints that plate's
thumbnail or warning badge — the old per-idle refresh used to mask it. Mark the toolbar
image dirty when the state actually changes, matching the established pattern.

Cosmetic; no effect on slicing. Surfaced by the merge review of 58b4a68a10.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 19:10:08 -04:00
Clifford Garwood 58b4a68a10 Merge upstream main: Python plugins, Bambu device rework, macOS fixes
Resolved 4 conflicts:
- PrintConfig.hpp / Preset.cpp: upstream restored calib_flowrate_topinfill_special_order
  (removed in the prior merge, now re-added and re-registered); kept it alongside our
  IMEX options (imex_parallel_mode, imex_head_filament_map).
- Plater.cpp: kept our <sstream> include plus upstream's <optional> and plugin includes.
- tests/fff_print/test_gcodewriter.cpp: both sides appended disjoint scenarios after a
  shared base. Reconstructed as upstream's full file (base + its 3 toolchange/H2C
  scenarios + the boost/filesystem include its helper needs) followed by our 10
  set_pressure_advance / set_temperature scenarios. 22 scenarios total, no duplicates.
2026-07-19 18:33:58 -04:00
Clifford Garwood 061190558f Merge upstream main: surface fill order, retract-after-wipe, i18n
Resolved conflicts in PrintConfig.hpp and Preset.cpp. Both were adjacent to upstream's
removal of calib_flowrate_topinfill_special_order (superseded by the new
top/bottom_surface_fill_order options); took upstream's removal from the option class
and the print-options list while keeping our new IMEX options (imex_parallel_mode,
imex_head_filament_map) and the IMEX enums (ImexToolLayout, ImexVizTheme) alongside
upstream's new SurfaceFillOrder enum. The option remains in PrintConfig's legacy
ignore-set so old projects still load.
2026-07-15 01:45:44 -04:00
Clifford GarwoodandClaude Opus 4.8 90e8ff7cee fix(imex): align the preview marker grid with the plate zone grid
Follow-up to the cross-gantry mirror axis change, from an adversarial review of it.

GCodeViewer builds its own copy of the zone grid to place the sequential-preview
carriage markers, and it must reproduce PartPlate::calc_imex_zones exactly or the
markers drift away from the ghosts they are meant to track. It did not, in two ways,
because sizing the grid and placing a cell answer different questions:

  - Sizing: calc_imex_zones counts every Copy/Mirror tool's OWN column, including the
    non-representatives of an aggregated (Span) gantry -- they still donate a column.
    GCodeViewer only ever saw the representative, so on an aggregated gantry it could
    count fewer columns than the plate and lay its markers out against wider strips.

  - Placement: calc_imex_zones PINS an aggregated cell to the primary's column, because
    that row-strip spans the full bed and has no column of its own. GCodeViewer used the
    representative's own column, which put the marker a strip away from the ghost
    whenever the representative was not column-paired with the primary.

Track the two sets separately: grid_tool_ids sizes the grid from own columns, eff_col_of
pins only aggregated tools when placing. Out-of-grid tool indices are deliberately left
unfiltered -- calc_imex_zones drops them while calc_imex_ghosts keeps them, so no policy
here can agree with both, and a comment says so rather than pretending otherwise.

Also:
  - The mirror-axis rule lived in three copies (two PartPlate lambdas plus an inline
    re-derivation here). Hoist it to imex_mirror_axis_for() so the ghosts and the markers
    cannot drift apart, and unit-test it, including degenerate tools_per_gantry.
  - Drop imex_head_transform's mirror_axis default. A defaulted axis silently hands a
    forgetful caller the X reflection, which is wrong for every cross-gantry tool and
    fails silently -- exactly how a stale test kept certifying the old rule.
  - Replace that stale test: it asserted a diagonal mirror "flips X only", the rule this
    work overturned, and stayed green only because of the default.
  - A secondary sharing the primary's gantry now takes the primary's Y box facing. The
    box shows the side a tool could be hit from, and two tools on one beam can only be
    hit by the same other gantry. No-op on the rear-* layouts, where the hardcoded value
    already matched; on front-* layouts it pointed the box away from the only tools that
    could reach it.
  - Correct two comments that described the aggregated X-frame substitution as a
    reflection plane. It is not one: it exists to zero gantry_offset.x, and removing it
    would push aggregated ghosts a column off their strip.

Verified: 385/385 tests; CLI slice of the IMEX regression project is byte-identical to
the previous commit's G-code apart from the timestamp, confirming this is
visualization-only and cannot affect sliced output.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 10:01:09 -04:00
Clifford GarwoodandClaude Opus 4.8 0cb1788b23 fix(tests): use ASCII hyphens in IMEX test names so Windows can run them
catch_discover_tests registers each Catch2 test with ctest by name, and ctest then
re-invokes the binary passing that name back as a -# filter. The IMEX test names
contain em dashes (U+2014). They survive discovery, but on Windows the round trip
through the console codepage mangles them, so the filter matches nothing:

    Filters: "imex_head_transform G-- copy mode is pure translation"
    No test cases matched  ->  No tests ran  ->  exit != 0  ->  ctest: Failed

All 85 IMEX tests were reported as failures on both Windows x64 and arm64 without a
single one of them ever executing. Linux and macOS are UTF-8 end to end and were
unaffected, which is why this went unnoticed since the names were introduced in
77c32a2e15.

These were the only non-ASCII test names in the whole tests/ tree. Renaming them to
plain hyphens costs nothing and keeps the suite portable.

Test names only -- no assertion, no logic, no comment is touched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 10:00:50 -04:00
Clifford Garwood 11b894dc85 Merge upstream main (wipe tower height initialization fix) 2026-07-14 00:57:31 -04:00
Clifford Garwood f850912123 fix(imex): mirror across the gantry-row axis on two-gantry printers
A Mirror tool reflects across the boundary it shares with the primary's zone, and
which boundary that is depends on where the tool sits:

  - Same gantry: the tools are side by side along X, so the shared boundary is
    vertical and the reflection negates X. This is what single-gantry IDEX does,
    and it was the only case the code modelled.
  - Different gantry: the zones are stacked along Y (front strip vs rear strip),
    so the shared boundary is horizontal and the reflection negates Y. The part
    that comes off gantry 1 is a Y-reflection of the tool directly behind it.

imex_head_transform() hardcoded diag(-1, 1, 1) for every mirror, as its own TODO
acknowledged. Lift the axis to a caller-supplied ImexMirrorAxis; PartPlate picks it
from the tool's gantry row. Both reflections keep det = -1, so a mirrored part stays
a true mirror image rather than a 180-degree rotation, which would print the
primary's part merely turned around.

The correct axis removes two workarounds. Both ghost paths special-cased aggregated
mirrors to "drop the X reflection, translate 1:1 and bake the flip into the mesh"
because reflecting X pushed the ghost off-bed as the primary was dragged. With a Y
reflection the X translation is already zero for aggregated tools, so that falls out
for free and the special cases are deleted.

Preview markers follow the same rule, which also fixes two placement bugs:

  - Mirrors reflected across a Copy tool's zone edge, falling back to the primary's
    column when a row had no Copy. In iq-mirror (0:P,1:C,2:M,3:M) the front row has
    no Copy, so t2 and t3 both fell back and computed the identical X — both drawn
    on top of each other in t3's zone. A mirror now reflects within its own zone.
  - The toolhead footprint box flipped to the far side of the nozzle for any mirror
    right of the primary. That only holds for an X-axis mirror, which reverses the
    carriage's orientation; a cross-gantry mirror keeps the X orientation of the
    tool behind it, so its box stays on the same side.

Tests cover the cross-gantry and diagonal cases, that the axis is caller-supplied
rather than inferred from the offset vector, and that both axes are reflections
(det = -1) rather than rotations.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 00:57:31 -04:00
Clifford Garwood 7e272f0303 fix(print): keep derived physical_extruder_map in the original-config snapshot
Print::apply() derives physical_extruder_map from printer_extruder_id and writes
it into new_full_config, but does so after m_ori_full_print_config is snapshotted.
The selector write-back path rebuilds m_full_print_config from that snapshot, so
m_full_print_config kept the unexpanded default while every subsequent apply
re-derived the expanded map. An unchanged config therefore diffed on
physical_extruder_map on every re-apply, and since that key is not handled by
invalidate_state_by_config_options() it fell through to the catch-all and
invalidated every step — forcing a full reslice on each apply.

Mirror the derived map into the snapshot so the two stay consistent.

Caught by the upstream test 'Selector write-back expands migrating filaments and
survives re-apply', which asserts a re-applied unchanged config is not
APPLY_STATUS_INVALIDATED.
2026-07-13 16:22:28 -04:00
Clifford Garwood fdb0e20f36 Merge upstream main (Bambu H2C/A2L multi-nozzle support) into IMEX branch
Conflicts were all co-located additions rather than design collisions:
- GCode.cpp: adopt upstream's toolchange(filament_id, nozzle_id) signature and
  per-variant set_config_index() while keeping the IMEX bare-T<n> suppression;
  rebase the second-layer temperature loop's non-IMEX branch onto upstream's
  get_filament_config_index() resolution.
- Preset.cpp / PresetBundle.cpp / PrintConfig.cpp: keep both sides' option-list
  and enum-map entries.
- GLCanvas3D.cpp: upstream's printable_heights argument plus the IMEX ghost pass.
- PartPlate.cpp: keep <set> (still used).
- test_gcodewriter.cpp / test_3mf.cpp: keep both sides' test cases.
2026-07-13 16:22:22 -04:00
Clifford Garwood 0ba09780d1 Merge upstream main into IMEX parallel printing branch
Resolves two conflicts:
- GLCanvas3D.hpp: keep both the IMEX ghost render declarations and
  upstream's _render_wireframe_overlay().
- test_gcodewriter.cpp: both sides appended test cases to the same
  region; keep upstream's origin/machine-limit tests alongside the
  pressure-advance and temperature scenarios.
2026-07-13 13:17:45 -04:00
Clifford Garwood 17584e2ffb Merge remote-tracking branch 'upstream/main' 2026-06-30 01:55:14 -04:00
Clifford GarwoodandClaude Opus 4.8 4677c79e18 Merge upstream/main into iXex PR branch (#13086)
Catches the iXex/IDEX branch up to upstream main (44 commits). One
content conflict resolved:

- src/libslic3r/Print.hpp: kept upstream's default-initialized
  m_origin {0,0,0} alongside our m_imex_slice_offset member.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 12:02:29 -04:00
Clifford GarwoodandClaude Opus 4.8 c5d0cdbcff Merge upstream/main into iXex PR branch (#13086)
Catches the iXex/IDEX parallel-printing branch up to upstream main
(102 commits). Two content conflicts resolved:

- src/libslic3r/Preset.cpp: s_Preset_printer_options — kept upstream's
  new "use_3mf" key and our iMEX printer-capability/mode keys.
- tests/fff_print/test_gcodewriter.cpp: upstream revived the disabled
  suite (#14196), dropping the obsolete [.]-tagged lift() test and its
  config_lift_unlift.ini; kept their set_speed + z_hop tests and appended
  our 10 per-firmware set_pressure_advance/set_temperature scenarios.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 23:29:35 -04:00
Clifford Garwood 62074b409d fix(gcode): skip ooze prevention for same-physical filament changes
Ooze prevention currently treats every toolchange as a transition between
independent hotends — the OLD filament's extruder cools to a standby
temperature (via standby_temperature_delta or the filament's
idle_temperature) before the change, and the NEW filament's extruder
ramps back up afterward. This is correct for IDEX/toolchanger setups
where the parked nozzle would drip otherwise.

For AFC/MMU lane swaps where the SAME physical extruder stays selected
(only the loaded filament changes), the cool-down → re-heat round trip
is pointless: same nozzle, same heater, just a different filament feeding
it. In-print this costs 30+ seconds per lane swap, and the AFC tip-form
sequence ends up running on a cooling extruder.

Gate the pre_toolchange call on physical_extruder_map: when both the old
and new filament index map to the same physical extruder, skip the
standby cool-down. post_toolchange is left untouched — its M109 to the
new filament's print temp is still emitted, so per-lane temperature
differences (e.g. PLA → PETG on the same AFC manifold) are still handled.

Note on pem sizing: the guard requires physical_extruder_map to be sized
to the filament count for the per-filament lookup to succeed. The option's
registered default is a single-entry [0], which is shorter than the filament
count on any multi-filament setup, so the bounds check fails and ooze runs
as before. The fix fires only on profiles that explicitly author pem to
match filament count (the AFC/MMU/toolchanger configs that actually encode
same-physical routing).

Behavior matrix:

| Config | pem | Ooze behavior |
|---|---|---|
| Single-extruder + SEMM | (any) | `init_ooze_prevention` already disables ooze. No change. |
| Single-extruder, no SEMM | (any) | Single filament, no toolchanges. N/A. |
| IDEX / Toolchanger | `[0,1,…]` per-filament | Cross-physical → ooze runs as before. |
| Vanilla AFC, SEMM=true | (any) | `init_ooze_prevention` disables ooze. No change. |
| Vanilla AFC, SEMM=false | `[0,0,…]` per-filament | Same physical → **ooze SKIPPED** (the fix). |
| Toolchanger + AFC | per-filament | AFC swaps skip, cross-physical swaps run. |
| Default pem `[0]` (1 entry) | shorter than filament count | Bounds check fails for filament index ≥ 1 → ooze runs as before. No-op for profiles that haven't authored a per-filament pem. |
| Empty pem | `[]` | Guard returns false → ooze runs as before. |

The guard depends only on physical_extruder_map; no machine-class check.
Any printer whose pem is sized to filament count and indicates multiple
logical slots routed to the same physical hotend benefits.
2026-06-08 03:13:44 -04:00
Clifford Garwood 896adc7071 Merge: hide secondary g-code preview carriages under firmware-managed-zones
The firmware-managed-zones writer offset puts prim_pos and the preview
toolpaths in a frame shifted relative to the bed bounds the zone math
uses, so secondaries would have rendered off the plate. Skipping the
secondary computation when firmware-managed is on keeps the centered
preview honest.

Addresses Felix14-v2 feedback on PR #13086.
2026-06-08 01:26:33 -04:00
Clifford Garwood e502175030 Sync upstream main
124 upstream commits including CrealityPrint integration (added include
in Plater.cpp alongside the existing IMEXHelpers include), profile fixes
and version bumps (#14084, #14085, Polymaker), CI artifact publishing,
test refactors (arachne walls test added; test_3mf/test_config/
test_gcodewriter content moved/removed upstream — IMEX test file
preserved as it is feedback-only), translations.
2026-06-08 01:26:33 -04:00
Clifford Garwood d0c6e8305d fix(imex): hide secondary carriages in g-code preview when firmware-managed-zones is on
The firmware-managed-zones writer offset centers the slice at bed origin,
so prim_pos and the preview toolpaths sit in a shifted frame relative to
the plate-local bed bounds the secondary-marker zone math uses. Computing
secondaries against unshifted bed bounds puts them off the build plate.

Skip the secondary-marker / toolhead-box computation when the
firmware-managed-zones option is enabled. The firmware physically fans the
centered toolpath out into the zones at print time, so the honest preview
is the single centered toolpath with no secondaries.

Reported by @Felix14-v2 on PR #13086.
2026-06-08 01:24:35 -04:00
Clifford Garwood 92de4e3f87 Merge IMEX g-code preview carriage placement fix
Secondary carriage markers now read the tool layout enum correctly,
aggregate to one active marker per gantry in Span modes, and mirror
across the bed centerline so they render on-bed.
2026-05-29 09:50:25 -04:00
Clifford Garwood ed6c608dc2 Sync upstream main
Pulls latest upstream fixes (Default filament option #13887, SEMM
multi-extruder crash #13896, linux wxstring crash #13925, Polymaker
profiles, et al.).
2026-05-29 09:50:18 -04:00
Clifford Garwood b2f548b098 Merge PR branch to converge histories
Brings the standalone PrintApply dedup-fix commit and the prior upstream
merge into staging. Their content is already present here (the dedup fix
was re-applied during the latest upstream sync), so this only reconciles
the commit ancestry — no tree changes expected.
2026-05-29 09:40:04 -04:00
Clifford Garwood 4a6342d646 fix(imex): correct secondary carriage placement in g-code preview
- Layout enum read via opt<ConfigOptionEnum<ImexToolLayout>>() did a
  dynamic_cast that fails for the ConfigOptionEnumGeneric type enums load
  as from presets, silently defaulting to FrontLeft. This desynced flip_y
  from PartPlate::calc_imex_zones, computing secondaries against the wrong
  primary zone and rendering them off the bed. Use option<>() (type-checked
  static_cast), matching how PartPlate reads the same key.

- Span modes now collapse each non-primary gantry to one active marker via
  group_imex_active_tools_by_gantry() (the same aggregation PartPlate uses
  for zones), since only one tool prints per zone at a time. Previously every
  Copy/Mirror tool got its own marker.

- Aggregated mirror markers reflect across the bed centerline rather than a
  column edge; the aggregated strip spans full-X, so edge reflection would
  push the marker off the bed.
2026-05-29 09:31:15 -04:00
Clifford Garwood 9b3c1b87f4 Sync upstream main
Includes nozzle-diameter guards for printers without nozzle info (#13255,
now landed upstream) and manual-calibration nozzle mismatch fix (#13882).

# Conflicts:
#	src/slic3r/GUI/GCodeViewer.cpp
#	src/slic3r/GUI/GLCanvas3D.hpp
#	src/slic3r/GUI/Tab.cpp
2026-05-29 01:49:42 -04:00
Clifford Garwood 4fd19a5531 fix: drop duplicate filament-extruder reshape in Print::apply
Upstream #13360 moved `m_ori_full_print_config = ...` and
`update_values_to_printer_extruders_for_multiple_filaments(...)` into a
new `if (!extruder_applied)` block. The 3-way merge with our IMEX
`physical_extruder_map` derivation block (sitting between the old and
new positions) silently kept both copies, running the per-filament
reshape twice and miscounting filaments for the multi-temperature
compatibility check. Removed the obsolete second copy at the old
position. Resolves `p1s_multicolor.3mf` regression test failure.
2026-05-25 05:13:44 -04:00
Clifford Garwood d6de031c8a Merge remote-tracking branch 'upstream/main' into merge-test-upstream
# Conflicts:
#	src/slic3r/GUI/GCodeViewer.cpp
#	src/slic3r/GUI/GLCanvas3D.hpp
#	src/slic3r/GUI/Tab.cpp
2026-05-25 03:11:50 -04:00
Clifford Garwood c89de62fc2 Merge feedback into main
Brings in upstream sync + AFC extruder_index integration:
- physical_extruder_map auto-population from AFC lane data
- verified end-to-end against xplorer (7 lanes, pem [0,0,0,0,1,2,3])
2026-05-25 02:46:46 -04:00
Clifford Garwood 0adc6024a4 Merge feature/extruder-mapping into feedback
Auto-populate physical_extruder_map from AFC extruder_index field.
Verified live on xplorer: 7/7 lanes report extruder_index, gate fires.
2026-05-25 02:05:08 -04:00
Clifford Garwood 5fd0f3c5b7 Merge remote-tracking branch 'upstream/main' into feedback 2026-05-17 20:21:23 -04:00
Clifford GarwoodandClaude Opus 4.7 cb66867768 feat(imex): topology-aware Tool 0 Position dropdown
For single-gantry IMEX setups, front/rear is meaningless — only left/right
matters. Collapse the imex_tool_layout dropdown to two items ("Left" / "Right",
mapped to front-left/front-right internally) when imex_gantry_count == 1, and
show all four corner items otherwise. Normalize stored rear-* selections to
their front-* equivalents on the transition so the displayed selection always
matches the persisted value.

Also rename the field label "Tool 0 Corner" → "Tool 0 Position" so it reads
correctly in both single- and dual-gantry contexts, with a tooltip that
explains the IDEX/IQEX distinction.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 13:25:37 -04:00
Clifford Garwood f11011efac Merge branch 'feedback' 2026-05-13 02:05:11 -04:00
Clifford GarwoodandClaude Opus 4.7 ca06900d33 chore(imex): drop "(center-origin slice)" subtitle from firmware-managed zones label
Tooltip already explains the mechanism; the parenthetical clutters the label.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 02:04:45 -04:00
Clifford Garwood de8448f048 Merge remote-tracking branch 'origin/main' 2026-05-13 01:52:29 -04:00
Clifford Garwood ab63789076 Merge remote-tracking branch 'upstream/main' into feedback
# Conflicts:
#	src/libslic3r/GCodeWriter.cpp
#	src/libslic3r/PrintApply.cpp
#	src/slic3r/GUI/Tab.cpp
#	tests/libslic3r/test_config.cpp
2026-05-13 00:57:36 -04:00
Clifford GarwoodandClaude Opus 4.7 48c603faa5 feat(imex): firmware-managed zones — center slice + ghost suppression
Adds the `imex_firmware_managed_zones` printer-config key (default off) for
IDEX/IQEX printers whose firmware applies its own copy/mirror offsets in
non-primary modes (e.g. RepRapFirmware IDEX duplication mode, Flashforge
Creator Pro 2/3 Pro). For these printers the slicer needs to emit a single
centered slice at bed origin and let the firmware fan toolheads out from there;
the previous slicer-managed iMEX rendering would draw a print at the primary
zone's world position (off-bed for the firmware-fan-out paradigm).

When the flag is on and the active mode is non-primary, the slicer subtracts
the primary zone's plate-local center from the gcode emission frame. The
writer offset is augmented but the gcode-processor offset stays at plate_origin
so the gcode-preview visualizer renders the centered slice at the bed center
rather than at the prepare-view zone placement. translate_to_print_space is
augmented too so first_layer_print_min/max placeholders (consumed by user
start_gcode like Felix's M118 header) reflect the centered frame.

Slice handoff lives in PartPlate::refresh_imex_slice_offset, called from both
update_slice_context (plate switch) and Plater::priv::update_background_process
(every-slice path — reslice() goes through here with switch_print=false so the
plate-switch hook alone wouldn't fire on mode toggle).

calc_imex_ghosts early-returns in firmware-managed mode: the existing
imex_head_transform math places ghosts at primary_zone_center + gantry_offset
(slicer-managed semantics), which renders off-bed when the toolpath is being
emitted in a centered frame. Proper firmware-managed ghost rendering (showing
where copies/mirrors will actually print after firmware fan-out) is deferred.

When the flag is off, all the new code paths reduce to no-ops byte-identical to
prior behavior. Layer 1 unit tests in test_imex_helpers cover every gating path
of compute_imex_slice_offset; full ctest suite passes (247/247).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 00:55:45 -04:00
Rodrigo Faselli f0271df707 Merge branch 'main' into main 2026-05-09 11:11:17 -03:00
Clifford Garwood 493a9e96d2 Merge remote-tracking branch 'upstream/main'
# Conflicts:
#	src/libslic3r/Preset.cpp
2026-05-02 01:17:02 -04:00
Clifford GarwoodandClaude Opus 4.7 6c7bd484db fix(imex): guard IMEX zone refresh against headless CLI slicing
`check_outside` (placement check) calls `ensure_imex_zones()` to make sure
zone geometry is current before deciding if an instance is in-bounds. In
the GUI path that's fine, but the CLI / headless 3MF-load path also reaches
this through `PartPlateList::load_from_3mf_structure -> reload_all_objects
-> add_instance -> check_outside`, and CLI mode has no GUI_App initialized.

`build_imex_cache_key` and `calc_imex_zones` both dereference
`wxGetApp().preset_bundle` — without a GUI_App, `wxGetApp()` returns memory
that segfaults on member access, killing the slicer with SIGSEGV before
any G-code is produced.

Latent since 461c69c83e (Apr 9), surfaced now that upstream's main carries
the headless regression-test CI step (#13353) that exercises CLI slicing
on every PR build.

Fix: short-circuit `ensure_imex_zones()` when `m_plater` is null (already
the GUI/CLI marker used by `calc_imex_ghosts`). Also tighten the existing
`build_imex_cache_key` null check to consult `m_plater` first as defence
in depth, so the function stays safe if reached from another headless
caller.

Verified locally against the upstream regression suite — klipper /
p1s_multicolor / toolchanger_4_color all slice cleanly within the
20% baseline tolerance.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 08:14:07 -04:00
Clifford Garwood b452a8a28f Merge remote-tracking branch 'upstream/main' into feedback 2026-05-01 01:30:40 -04:00
Clifford GarwoodandClaude Opus 4.7 1d6fab7a44 fix(imex): pivot Span ghost X-flip on mesh bbox center
The aggregated-mirror ghost was X-flipping about the mesh's local origin,
which shifted the ghost sideways for models whose local origin sits at a
corner (calibration cubes, calicat, most STL imports anchored at the
min corner). Visible as a constant left-X offset between the primary's
position and the ghost's position.

Pivot on `mo->raw_mesh_bounding_box().center()` instead, applied through
the instance transform so rotated objects flip about the rotated bbox
center too. Same correction applied to the live-drag update path.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 01:27:47 -04:00
Clifford GarwoodandClaude Opus 4.7 4966d0fae8 feat(imex): Span tile state for paired-gantry multicolor
Introduce ImexRole::Span as a 5th tile cycle state that declares "this
tool is the multicolor partner of Primary on the same gantry." Encoded
as the `S` role suffix in `imex_mode_active_tools` (e.g. `0:P,1:S,2:M,3:M`).
Disambiguates paired-gantry mc-mirror from 4-independent-copies — both
share the same active_tools shape sans the marker.

Span drives:
- Multicolor block rule: now requires Span on primary's gantry to allow
  multi-color slicing in a parallel mode. Replaces the prior "≥2 tools
  on primary's gantry" check; pre-existing 4-tool multicolor configs
  need T1 flipped to Span.
- Ghost aggregation: one ghost per non-primary gantry when Span is
  present, using the column-paired representative. Aggregated-mirror
  drag tracks primary 1:1 in X (gantries don't share an X rail) with
  X-flip baked into mesh-local frame so geometry still reads as mirrored.
- Zone aggregation: one full-X row strip per non-primary gantry instead
  of per-tool quadrants.
- UI: 5th button in IMEXModesCtrl. Cycle Off→P→C→M→S→Off, only offered
  on multi-gantry printers and only on tiles sharing primary's gantry row.

Single source of pairing truth: group_imex_active_tools_by_gantry in
IMEXHelpers, consumed by ghost factory and zone calculator.

Also fixes the carriage collision strip's X-boundary check, which lacked
the row constraint its Y-boundary counterpart already had — paired-gantry
mc-mirror was drawing a spurious right-edge strip from T3 sitting
diagonally from primary.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 01:16:02 -04:00
Clifford GarwoodandClaude Opus 4.7 a8dbdf90de fix(imex): scope ghost render + picking to the active plate
_render_imex_ghosts and _picking_pass_imex_ghosts both walked every
plate in the partplate list and rendered/hit-tested all ghosts found —
which meant background plates' ghosts bled through into the active
scene whenever GL state was shared (most visibly when entering paint
mode), and clicks could land on a ghost that belonged to a plate the
user wasn't actually looking at.

Switch both paths to read get_curr_plate() and skip the per-plate loop.
Per-plate ghost volumes still live on each PartPlate so 3MF
round-trips work and switching plates picks up the new active plate's
ghosts cleanly; we just don't draw or hit-test the ones whose plate
isn't the active one.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 23:01:15 -04:00
Clifford GarwoodandClaude Opus 4.7 2c604173d3 fix(imex): block multi-color slicing for single-gantry "fake IMEX" modes
Catches the case where a non-primary IMEX mode's active tools all sit on
one gantry — e.g. mode "0:P,1:C" on a 2x2 IQEX where T0 and T1 share
gantry 0. The "Copy"/"Mirror" label is decorative there: nothing actually
parallel-prints, but the user's mode_gcode still fires and emits firmware
setup that doesn't apply, while the slicer treats it as a multi-color
parallel print. Conceptually it's just a regular multi-tool single-gantry
print and belongs in Primary mode.

Changes:
- imex_multicolor_block_reason now collects the set of distinct gantries
  spanned by the active tools and short-circuits with a clear "single
  gantry — not a parallel-print scenario" message before falling through
  to the existing within-gantry-swap check.
- Drops the redundant pre-slice "Multi-material objects detected" soft
  warning from collect_imex_warnings — the slice-time block surfaces a
  more specific message at the right moment, and the soft warning was
  vague handwaving in front of it. Bed-temp + filament-type checks stay.
- New unit test covering the single-gantry block.

Behavior matrix on a 2x2 IQEX with multi-color:
  "0:P,1:C"           single gantry         -> BLOCK (new)
  "0:P,2:C"           dual gantry, 1 each   -> BLOCK (existing within-gantry-swap)
  "0:P,1:C,2:M,3:M"   dual gantry, 2 each   -> ALLOW
  multiple filaments to same physical via pem -> BLOCK (existing MMU sharing)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 22:53:33 -04:00
Clifford GarwoodandClaude Opus 4.7 66891899e9 refactor(imex): drop unused Split mode-type infrastructure
The mode-type tag (imex_mode_types config + imex_mode_type_for helper +
Split sentinel) was added in 0bb1cef as scaffolding for the Split rendering
work that landed in 4370cca and then got reverted in d9be71b. With Phase 2-4
gone, this scaffolding is now unused dead code — and the design we settled
on instead is to leave topology entirely implicit (parsed from active_tools_str)
rather than carrying a per-mode type tag the user would otherwise have to
manage explicitly.

The multi-color slicing block + bare T<n> suppression that were the actual
substance of the safeguards work stay in place:
- imex_multicolor_block_reason still allows multi-color exactly when 2+ tools
  are active on the primary's gantry — Felix's hypothetical IQEX paired-gantry
  case works through this path, no new mode type required.
- Slicer-side: bare T<n> stays suppressed at print-start in IMEX parallel modes;
  mid-print T<n> emits naturally for the legitimate IQEX 4-tool-active scenario.

Removed:
- ConfigOptionStrings imex_mode_types (PrintConfig.hpp/cpp + Preset.cpp key list)
- imex_mode_type_for helper + kImexModeType{Primary,Copy,Mirror,Split} sentinels
- mode_type parameter on imex_multicolor_block_reason and the Split short-circuit
- mode_type plumbing in Print::validate and PartPlate::has_imex_multimaterial_conflict
- Three unit tests for imex_mode_type_for + two Split-specific multicolor block tests

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-28 00:48:39 -04:00
Clifford Garwood d9be71b1ca Revert "feat(imex): Split mode rendering + modes editor Type column"
This reverts commit 4370cca4c9.
2026-04-28 00:20:31 -04:00
Clifford GarwoodandClaude Opus 4.7 4370cca4c9 feat(imex): Split mode rendering + modes editor Type column
Completes the Split-type IMEX mode plumbing started in 0bb1cef. Phase 1
landed the config option, helper, validator, and plater badge updates;
this commit lands the visualization side and the editor surface so users
can actually create and use Split modes.

Zone aggregation (PartPlate::calc_imex_zones)
- When mode_type == "split" and there's row separation between primary
  and secondaries, collapse copy_cells and mirror_cells per-gantry
  (each cell gets primary's column) and force has_col_sep = false.
  make_boxes then takes its full-X-row branch and renders one zone
  covering each non-primary gantry's full Y band, instead of per-tool
  quadrants.

Ghost aggregation (PartPlate::calc_imex_ghosts)
- Pre-scan the active mode's tool list to pick a canonical head per
  non-primary gantry (the tool whose physical column matches primary's;
  fallback to first-seen). For Split modes, the per-head emission loop
  skips non-canonical tools so each non-primary gantry gets exactly one
  aggregate ghost rendered at the canonical's mirrored position.
- update_imex_ghost_transforms unaffected — it iterates the already-
  filtered ghost set.

Modes editor Type column (IMEXModesCtrl in Tab.cpp)
- Each non-primary mode row gains a wxChoice dropdown selecting Copy /
  Mirror / Split. Primary row gets a static "Primary" label.
- Header row picks up a "Type" column header with sized spacer that
  aligns with the dropdown.
- get_mode_data() now returns a fourth tuple element (types vector).
  load_from_config() reads imex_mode_types via imex_mode_type_for so
  legacy presets without the new option still infer types from mode
  names. on_change writes imex_mode_types back to config.
- matches_config(), snapshot_row(), row_differs_from_parent(), and
  reset_row_to_parent() all extended to track type alongside name/
  tools/gcode — per-row reset arrows reflect type-only changes, and
  reset restores the parent's type via imex_mode_type_for fallback.
- Type-choice dropdown fires notify() on change so the dirty/save flow
  catches it like any other row edit.

End-to-end: switching a row to Split type in the modes editor, then
selecting that mode on a plate, allows multicolor slicing (the gantry-
pair check is bypassed in imex_multicolor_block_reason) and the bed
visualization shows a single aggregate zone + one ghost per non-primary
gantry instead of per-tool clutter.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-27 23:17:24 -04:00
Clifford GarwoodandClaude Opus 4.7 0bb1cef78e feat(imex): polish UI per PR feedback + add parallel-mode safeguards
Felix14-v2 PR review feedback (https://github.com/OrcaSlicer/OrcaSlicer/pull/13086#issuecomment-4323696312)
plus the slice-time validation work that follows from his bug list.

UI polish:
- Capitalize "Primary" in display (right-click mode menu, plate tooltip).
  Sentinel stays lowercase for wire compatibility.
- Pre-slice-warnings checkbox now uses Orca's ::CheckBox so it matches the
  green toggle style of the rest of the IDEX/IQEX configuration page.
- DPI-scaled the IMEXModesCtrl (modes editor) sub-panels, button grid,
  text wraps, gcode textarea, and the ghost-tooltip swatch (imgui.scaled).
  Legend swatches sized to body-text height for visual balance.
- Primary mode tool buttons in the modes editor are now disabled (read-
  only): cycling roles on the IMEX-off mode is a no-op and confusing.
- Modes editor sub-panels now explicitly inherit the app's window-default
  dark colour so chromeless ScalableButtons don't render with a visible
  light box around their icons on GTK dark themes.
- Per-mode-line reset arrows in the modes editor: each row gets a small
  reset bitmap that snaps that row's name+tools+gcode triplet back to the
  saved preset's value. matches_config() guard on the page-level reload
  prevents the textbox-being-typed-into from being destroyed mid-keystroke.
- New View menu item "Show IDEX/IQEX Toolhead" — toggles the per-carriage
  footprint boxes during G-code preview playback. Gated to Preview tab +
  IMEX printer; backed by app_config so it persists.

Coordinated config migrations:
- imex_tool_layout and imex_viz_theme migrate from coString to coEnum
  (ImexToolLayout / ImexVizTheme). Existing wire format preserved so
  saved presets and 3MFs deserialize unchanged. Side-benefit: both now
  pick up standard Field rendering and so finally show reset arrows.

Slice-time safeguards (the validation half):
- imex_suppresses_bare_toolchange(parallel_mode, count): suppresses the
  slicer's bare T<n> at print-start in any IMEX parallel mode (the user's
  imex_mode_gcode + machine_start_gcode owns tool activation there). Mid-
  print T<n> emits normally — Print::validate blocks the configurations
  where mid-print T<n> wouldn't make sense. Applied to both code paths
  inside GCode::set_extruder (the long multi-extruder path AND the
  single-extruder path that fires when multiple_extruders=false).
- imex_multicolor_block_reason(): hard-stop validator returning a user-
  facing reason string when the active IMEX configuration can't physically
  support multi-color. Catches IDEX (1 tool/gantry), 2-tool-active IQEX
  (no within-gantry swap topology), and any MMU/AFC lane sharing among
  used filaments. Wired into Print::validate as a slice blocker, and into
  PartPlate::has_imex_multimaterial_conflict so the plater badge agrees
  with the slice block (no more false positives where the badge warns but
  the slice goes through).
- New imex_mode_types config option (parallel array to imex_mode_names)
  and imex_mode_type_for() helper. Mode-type tag drives behaviour: zones,
  ghosts, and validation interpret modes differently per type. Initial
  types: "primary", "copy", "mirror", "split". Split modes are explicitly
  designed for paired-gantry IQEX multi-color and bypass the gantry-pair
  check in imex_multicolor_block_reason (MMU sharing still blocks them).

Test coverage:
- New unit tests cover imex_suppresses_bare_toolchange (4 cases),
  imex_multicolor_block_reason (8 cases including IDEX, IQEX 2-/4-tool-
  active, MMU sharing, missing primary, Split type), and imex_mode_type_for
  (3 cases including legacy fallback). Total: 186 IMEX assertions across
  71 test cases, all passing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-27 22:51:05 -04:00
Clifford GarwoodandClaude Sonnet 4.6 9676f04cbf feat: auto-populate physical_extruder_map from AFC extruder_index
AFC now publishes an extruder_index field per lane. This lets Moonraker
auto-derive physical_extruder_map without user configuration:

- Add extruder_index field to AmsTrayData (-1 = not provided)
- Parse extruder_index from AFC lane JSON using contains() check since
  0 is a valid extruder index (safe_json_int returns 0 for missing fields,
  which would be ambiguous)
- After build_ams_payload, if all trays have extruder_index, derive
  physical_extruder_map and write it to the printer preset config via
  CallAfter so IMEX PA and temperature emission use the correct physical
  extruder qualifier

AFC lanes sharing a carriage all report extruder_index=0; independent
direct-drive tools on separate carriages report their carriage index.
PrintApply.cpp's auto-derive from printer_extruder_id is skipped when
physical_extruder_map has more than 1 element, so the AFC-populated map
takes precedence.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 13:04:28 -04:00
Clifford Garwood b10d6e1b9f Merge remote-tracking branch 'origin/main' 2026-04-25 05:17:47 -04:00
Clifford GarwoodandClaude Opus 4.7 41457635e9 test(imex): extract physical→logical helpers + cover them with unit tests
Three of the four IMEX physical-vs-logical fixes landed earlier on this
branch (fbc58d2a1d, fa048babeb, a38b95bf45, e11e7d46df) used inline
lambdas / direct loops to translate physical extruder indices to logical
filament slots.  No test coverage existed for the specific composition,
even though the underlying primitives (resolve_filament_for_head,
first_filament_for_physical_head) were tested.

Pull two patterns out of Plater.cpp and GCode.cpp into IMEXHelpers as
named helpers, then unit-test them:

  imex_primary_logical_from_objects(used_slots_1b, pem, primary_physical)
    Walks the plate's used filament slots (1-based) and returns the
    first one whose pem entry maps to the primary's physical extruder.
    This is what the warning's `logical_for_primary` now delegates to —
    moves the "look at object assignments, not pem first-routed default"
    behavior introduced in fa048babeb out of the lambda and into a
    separately-testable function.

  imex_secondary_logical_slots(active_physicals, primary_physical,
                               plate_head_filament_map, pem)
    Iterates IMEX active physicals, skips the one matching primary,
    resolves each remainder via resolve_filament_for_head (per-plate
    override + first-routed fallback), deduplicates, drops -1 entries.
    Replaces the inline loop in GCode::_do_export's is_extruder_used
    marking (a38b95bf45 + e11e7d46df).

10 new test cases in test_imex_helpers.cpp cover the cases that
correspond directly to bugs hit:

  imex_primary_logical_from_objects:
    - AFC primary picks the object's slot (the user's specific bug)
    - Multi-color AFC primary returns first input-order match
    - Direct extruder primary unambiguous (no MMU)
    - No object routed to primary returns -1
    - Empty inputs (no objects, empty pem)

  imex_secondary_logical_slots:
    - Copy mode skips primary, falls back to first-routed
    - IQEX 4-mode enumerates all three secondaries
    - Per-plate override wins over first-routed
    - Drops unrouted physicals + deduplicates
    - Only-primary-active returns empty

All [IMEX] + [Variant] + [3mf] regression: 166 assertions / 61 cases.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 05:07:34 -04:00
Clifford GarwoodandClaude Opus 4.7 d41e0d09b4 fix(imex): skip primary in IMEX is_extruder_used marking
a38b95bf45 translated physical -> logical for IMEX active tools but used
resolve_filament_for_head for *all* active tools including the primary.
For the primary, that falls back to first_filament_for_physical_head —
which returns the FIRST logical slot routed to the primary's physical
extruder, not the slot the user actually assigned to the printing object.

On the user's Neo XP 0.6 (pem [0,0,0,0,1,2,3,3]) printing in copy mode
with the object on slot 2 (PLA dark grey):
  - tool_ordering correctly marks slot 2 (object's filament).
  - The IMEX-marking loop then *also* marked slot 0 (ABS) as the primary's
    "first-routed" slot — wrong: slot 0 isn't loaded, slot 2 is.
  - Start-gcode template emitted EXTRUDER=260 EXTRUDER2=235 EXTRUDER4=235;
    the ABS-temp emission for slot 0 was harmless noise in this macro
    design but conceptually bogus.

The primary's filament is already correctly covered by
tool_ordering.all_extruders() — that lists the slots the objects on the
plate are assigned to.  Skip the primary in the IMEX-marking loop using
the same pattern as the IMEX PA emission path at GCode.cpp:3265
(translate initial_extruder_id -> physical via pem, skip that physical).

Result on the user's setup post-fix:
  - is_extruder_used: slot 2 (object), slot 4 (T1's filament in copy).
  - Start-gcode emits EXTRUDER2=235 EXTRUDER4=235 — exactly two temps,
    one per active heater, with no spurious ABS bookkeeping.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 05:07:34 -04:00
Clifford GarwoodandClaude Opus 4.7 2b7f438887 fix(imex): translate physical→logical when marking is_extruder_used for IMEX active tools
is_extruder_used is a logical-filament-slot indexed bool array — start-gcode
templates use it as `is_extruder_used[N]` where N is a logical filament slot
(matches how the rest of the codebase consumes per-filament arrays like
filament_settings_id, nozzle_temperature_initial_layer, etc.).

`tool_ordering.all_extruders()` returns LOGICAL slots and was correctly
marking those. But the IMEX-secondary marking added in 5aa624b025 was
writing PHYSICAL extruder indices straight into the logical array, mixing
index spaces. On any printer with physical_extruder_map size > 1 (MMU/AFC),
this marks the wrong slots and misses the right ones.

Symptom on the user's Neo XP 0.6 (pem [0,0,0,0,1,2,3,3]) in copy mode
[0:P,1:C] with object on slot 2 (PLA):
  - tool_ordering marks slot 2 (correct: object's filament).
  - IMEX active = [0, 1] (physical T0, T1) → mistakenly marks logical
    slots 0 (ABS) and 1 (ASA), neither of which is used.
  - Slot 4 (PLA on physical T1, the actual filament that loads on the
    secondary in copy mode) is NOT marked.
  - Start-gcode template emits EXTRUDER=ABS_temp EXTRUDER1=ASA_temp
    EXTRUDER2=PLA_temp; no EXTRUDER4.
  - PRINT_START macro reads t4=0, skips heating extruder1 — T1 stays
    cold during the print.

Translate physical → logical via resolve_filament_for_head before marking
(per-plate imex_head_filament_map override consulted, with first-routed
fallback when no override is set). This matches what the firmware actually
loads on each carriage during the parallel-mode print, and what the rest
of the IMEX hot path (PA emission, layer-change temperature) already does.

Result on the user's setup post-fix:
  - is_extruder_used[2]=true (object), is_extruder_used[4]=true (T1 in copy).
  - Start-gcode emits 2 temps for the actually-used filaments.
  - extruder1 heats correctly to slot 4's temp before the print begins.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 05:07:34 -04:00
Clifford GarwoodandClaude Opus 4.7 c8e7c5678b fix(imex): primary's filament lookup must come from object assignment, not pem default
The pre-slice warning's primary-tool filament lookup (added in
fbc58d2a1d) used resolve_filament_for_head, which returns the FIRST
slot routed to the primary's physical extruder via pem. That's the
right rule for *secondaries* (no object owns them in copy/mirror mode;
filament comes from the per-plate imex_head_filament_map override) but
wrong for the *primary*: the primary prints the actual objects on the
plate, and the slot it uses is whatever the user assigned to those
objects — not whatever happens to be at the head of the AFC manifold.

Symptom on the user's Neo XP 0.6:
  - pem = [0,0,0,0,1,2,3,3] (slots 0-3 share AFC manifold on physical T0)
  - filaments: slot 0 ABS, slot 2 PLA, slots 4-6 PLA, etc.
  - object assigned to slot 2 (PLA), IMEX mode "copy" (T0 primary, T1 copy)
  - User picks slot 5 PLA for T1 via the IMEX ghost picker (writes a
    per-plate imex_head_filament_map).
  - Warning reads slot 0 (ABS) for primary because that's
    first_filament_for_physical_head(pem, 0). Fires "T0 ABS vs T1 PLA
    type mismatch" even though the actual print uses slot 2 (PLA) for
    the primary — slicer and warning disagree.

Split the lookup:
  logical_for_primary(physical_idx)
    Walks plate->get_extruders(true) (1-based slots used by objects on
    this plate), returns the first slot whose pem entry maps to
    physical_idx. Falls back to first_filament_for_physical_head if no
    object on the plate routes to this physical extruder (defensive).

  logical_for_secondary(physical_idx)
    Unchanged behavior: per-plate imex_head_filament_map override with
    first_filament_for_physical_head fallback.

The user-facing "T%d" labels still display the physical extruder index
(carriage identity); only the filament-info lookups change.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 05:07:34 -04:00
Clifford GarwoodandClaude Opus 4.7 0db99d21dd fix(imex): translate physical→logical when looking up filament info in pre-slice warnings
collect_imex_warnings parses imex_mode_active_tools to get the active tool
indices. Those are PHYSICAL extruder indices (one per carriage). It then
used those same indices to look up filament_presets[tool_idx] and
bed_temps[tool_idx] — but both arrays are indexed by LOGICAL filament slot.

For MMU/AFC layouts where multiple logical slots feed one physical
extruder (e.g. AFC manifold: 4 lanes on physical T0), the warning would
report the wrong filament: a secondary on physical T1 would be named with
filament_presets[1] (= AFC lane 2) instead of the actual filament on T1.

Symptom: in IMEX parallel mode on the user's IQEX-AFC printer (pem
[0,0,0,0,1,2,3]), the multi-extruder warning called the secondary tool
"T1" but reported the filament type for logical slot 1 (an AFC lane),
not the actual filament 4 routed to physical T1.

Translate physical → logical via effective_physical_extruder_map (with
per-plate imex_head_filament_map override) before indexing into
filament_presets and bed_temps. The displayed "T%d" still shows the
PHYSICAL extruder number — that's the carriage identity the user sees
on hardware. Only the filament-info lookup is changed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 05:07:34 -04:00
Clifford GarwoodandClaude Opus 4.7 6e176434d0 docs(imex): document physical-vs-logical index distinction
Three IMEX bugs of the same shape have surfaced over the lifetime of
this feature:
  - Inline pem lookup duplicated at two PA emission sites
  - Pre-slice warnings indexing filament_presets by physical index
  - Ghost color resolution with stale default pem handling

Each was a place where a per-filament array got indexed by what the call
site had on hand (a physical T-number) without translating through
physical_extruder_map. On non-MMU/non-AFC printers the indices coincide
and nothing breaks; on AFC layouts the slicer reads the wrong filament
preset for a carriage with no error or log line.

Add a header comment block to IMEXHelpers.hpp describing the two index
spaces, when each is used, how to translate, and a list of the bugs we
hit so future contributors can recognize the pattern.

The constant kImexPrimaryMode and the helper declarations follow this
block; readers searching for pem helpers will land on the guidance first.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 05:07:34 -04:00
Clifford GarwoodandClaude Opus 4.7 d329fce6b0 Merge branch 'tests/imex-coverage' into feedback
Brings in the IMEX test coverage (PA per-firmware, Temperature per-firmware,
[Variant] expansion, 3MF round-trip, imex_pem_tool_for helper + tests, and
the cherry-picked variant-truncation regression test).

Resolution notes:
- Two GCode.cpp call sites for set_pressure_advance had divergent edits:
    * tests/imex-coverage rewrote them to use the new imex_pem_tool_for
      helper (commit c2492ccc47), eliminating the inline parallel-mode
      check entirely.
    * feedback replaced the literal "primary" with kImexPrimaryMode in the
      same lines (commit 085f5ccec8).
  Resolution: keep the helper-call form. The kImex change is moot on lines
  the helper replaces, and imex_pem_tool_for in IMEXHelpers.cpp is also
  updated to use kImexPrimaryMode for consistency with the rest of the
  codebase.

- Test test_3mf.cpp updated for upstream's load_bbs_3mf signature change
  (PR adds is_orca_3mf out-parameter between is_bbl_3mf and file_version).
  All three call sites in the new IMEX 3MF round-trip tests pass &is_orca
  in addition to &is_bbl.

Full regression post-merge:
  libslic3r:    143 cases / 48,553 assertions  (+10 cases from new tests)
  fff_print:     24 cases /    245 assertions  (+10 cases from new tests)
  sla_print:     21 cases / 14,100 assertions
  libnest2d:     14 cases /    488 assertions
  slic3rutils:    3 cases /      3 assertions

All tests pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 01:17:27 -04:00
Clifford Garwood 357fcb374d Merge remote-tracking branch 'upstream/main' into feedback 2026-04-25 00:46:48 -04:00
Clifford GarwoodandClaude Opus 4.7 212f81446c refactor(imex): convert remaining "primary" literals in GCodeViewer
Follow-up to 085f5ccec8. Self-review of that commit's grep output missed
GCodeViewer.cpp. Three sites in the layer-preview multi-carriage marker
logic still compared against the bare "primary" literal:

  - GCodeViewer.cpp:1538 — process-preset mode default
  - GCodeViewer.cpp:1542 — per-plate mode override gate
  - GCodeViewer.cpp:1551 — secondary marker computation gate

All three now use kImexPrimaryMode. The file already includes
IMEXHelpers.hpp (line 13) so no new include needed.

Verified by grepping the full IMEX-touching set: only IMEXHelpers.hpp
itself (the constant definition) still references the literal string,
which is correct.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 00:42:51 -04:00
Clifford GarwoodandClaude Opus 4.7 7dcb74e504 docs(imex): document inputs of build_imex_cache_key
The IMEX zone cache key drives ghost rebuild and zone-overlay
invalidation. Adding a printer config option that affects zone geometry,
ghost transforms, or collision strips without extending the key produces
a silent staleness bug: the cache thinks the zones are still valid and
ghost meshes / overlays don't refresh after the new option changes.

Document what currently feeds the key and pin the precision convention
(*10 scale on float values for 0.1 mm resolution) so future contributors
know the contract and where to extend it.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 00:32:42 -04:00
Clifford GarwoodandClaude Opus 4.7 4e0a2704b9 refactor(imex): consolidate IMEX active-tools parsing through one helper
GCode.cpp's static get_imex_active_tools() inlined its own "phys[:role]"
tokenizer with subtly different semantics from IMEXHelpers'
parse_imex_active_tools — only the GCode version bounded against
MAXIMUM_EXTRUDER_NUMBER. Three other call sites (PartPlate zones,
GCodeViewer legend, Plater warnings) routed through parse_imex_active_tools
already.

Move the bounds check into parse_imex_active_tools so all consumers get
it consistently, then rewrite get_imex_active_tools to do only the
Print-extraction portion (active mode lookup, tools-string fetch) and
delegate token parsing to the helper. Keeps Print out of IMEXHelpers'
include set.

No behavior change for the non-pathological case (mode strings have always
parsed identically); for indices >= MAXIMUM_EXTRUDER_NUMBER (64) the three
older call sites silently filter them out now where previously they would
have accepted them — this matches what get_imex_active_tools already did.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 00:32:34 -04:00
Clifford GarwoodandClaude Opus 4.7 085f5ccec8 refactor(imex): replace "primary" magic string with kImexPrimaryMode constant
Across 6 files the literal "primary" string was the sentinel for "no IMEX
parallel mode active" — used for short-circuiting in serialization, ghost
visualization, zone calc, popup-menu list construction, the IMEXModesCtrl
non-deletable first row, and several layer-of-export checks. A typo in
any one would silently bypass the guard.

Define kImexPrimaryMode in IMEXHelpers.hpp with a docstring describing
what equality with it means semantically, and route every call site
through it. No behavior change.

Touched: bbs_3mf.cpp, GCode.cpp, PartPlate.cpp, Plater.cpp, Tab.cpp.
The bbs_3mf and Tab files now include IMEXHelpers.hpp; the other three
already did.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 00:32:22 -04:00
Clifford GarwoodandClaude Opus 4.7 efa9d65cb4 fix(imex): code-review nits — null check, cache-key precision, lambda capture
Four quick-fix items surfaced by pre-PR self-review.

GCodeViewer.cpp:1607
  Null-check get_curr_plate() before dereferencing. Other call sites in
  the file already guard; this was the only unguarded one in the IMEX
  layer-preview path. In practice m_plate_list always has a plate, but
  the inconsistency is easy to fix and removes the only ungated deref.

PartPlate.cpp:build_imex_cache_key
  Cache key for IMEX zone geometry truncated nozzle_clearance_x/y to int
  before stringifying — a config change from 30.0 to 30.5 would not
  invalidate the cache. Match the *10 precision pattern already used for
  imex_carriage_margin so 0.1 mm steps invalidate correctly.

Plater.cpp:select_plate_by_hover_id (right-click popup)
  Two issues:
  1. Lambda captured `modes` by reference. PopupMenu() is synchronous
     today so the reference outlived the menu's event handling, but the
     pattern is fragile — anyone refactoring to async Popup() would
     silently dangle. Capture by value.
  2. Used wxID_HIGHEST + i for menu item IDs — standard wx anti-pattern
     because it can collide with other handlers listening in that range.
     Allocate per-item IDs via wxNewId() and look up the chosen mode by
     finding the event ID in a parallel vector. The lookup becomes O(N)
     instead of O(1) but N is small (mode count) and this is clicker
     latency, not a hot path.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 00:07:35 -04:00
Clifford GarwoodandClaude Opus 4.7 7b9070d521 fix(gcode): restore BBL initial-filament PA emission removed in error
af59501f4a ("feat: firmware-agnostic per-tool PA emission for IMEX
parallel modes") inadvertently deleted the BBL-specific PA emission for
initial_non_support_extruder_id while adding IMEX per-tool PA support.
That deletion was scope creep into core BBL functionality and not part
of the IMEX feature.

Restore the original block verbatim. The new IMEX-parallel-modes PA
emission (per-secondary-tool, gated on m_imex_parallel_mode) is left
untouched — that's legitimately IMEX scope. BBL printers in non-IMEX
mode now get back the pre-PR initial-PA behavior.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 00:04:41 -04:00
Clifford GarwoodandClaude Opus 4.7 ee757dac20 test: address self-review findings on [Variant] and [3mf][IMEX] coverage
Self-review found two weaknesses in the preceding test commits:

1) The equal-size [Variant] scenario claimed to distinguish the truncation
   guard's `cur > target` predicate from a regression to `cur >= target`,
   but both paths yield identical child values in practice: when
   extruder_variant names match, set_with_restore's variant_index is fully
   populated (no -1 slots) and the merge path restores every position from
   backup — producing the same {1.5, 2.5} output as the skip path. The
   test passes in both guard states.

   Rewritten to use mismatched variant names between child and parent.
   variant_index then has -1 slots, and set_with_restore overwrites those
   positions with parent values. Now the merge path yields {0.8, 0.8} and
   the skip path yields {1.5, 2.5} — observably different. Verified:
     - `cur >  target` (correct):  4 scenarios pass, 15 assertions
     - `cur >= target` (regressed): equal-size scenario fails with
       "1.5 is within 0.000000001 of 0.80000000000000004"
     - Guard removed entirely: child>parent + stride=2 both fail with
       truncation ("1 == 2" / "2 == 4")

2) The [3mf][IMEX] round-trip only covered a single plate. A plate-
   indexing regression (IMEX metadata landing on the wrong plate, or
   bleeding across plates on reload) would not have been caught.

   Added a multi-plate scenario: two plates with distinct mode and
   head-filament-map values. Asserts both land on their respective
   destination plates after reload. Load-bearing verified:
     - With IMEX serialization intact:    3 scenarios pass, 45 assertions
     - With IMEX serialization disabled:  positive + multi-plate fail
       (both "nullptr != nullptr"); primary-mode passes (expects nullptr)
     - With primary-mode short-circuit removed: primary-mode scenario
       fails ("0x... == nullptr") because primary modes now serialize

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 23:29:01 -04:00
Clifford GarwoodandClaude Opus 4.7 b45d8a5b7b test(gcode): per-firmware coverage for GCodeWriter::set_temperature(tool)
Four scenarios cover the temperature emission surface that IMEX layer-change
handling routes through (Tier 1 of the deferred Target B test plan —
pure-function-only, no fixture).

- Per-flavor command routing: Marlin (M104), RRF (G10 — M104 is deprecated
  on RRF), Mach3/Machinekit (P-prefix for value instead of S).
- Wait handling: Marlin emits M109, MakerWare/Sailfish silently drop wait
  requests (the firmware doesn't support blocking waits), Teacup and RRF
  both emit a separate M116 poll.
- Per-tool qualifier for IMEX secondary carriages: Marlin and Klipper
  emit T<N>, RRF uses P<N> (same P override as its wait poll). This is
  exactly the path that lets IMEX set secondary-tool layer temperatures
  without a tool-change.
- Instance overload's multi-extruder gating: a tool index passed to a
  single-extruder GCodeWriter is discarded (no spurious T0 on
  single-tool printers), but a multiple_extruders writer passes it
  through verbatim.

All 24 assertions in 4 cases pass under [Temperature].

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 23:08:57 -04:00
Clifford GarwoodandClaude Opus 4.7 c2492ccc47 refactor(imex): extract imex_pem_tool_for helper + unit tests
The physical_extruder_map translation used by IMEX per-tool PA emission was
inlined identically at two sites in GCode.cpp (tool-change and second-layer
transition). Extract to IMEXHelpers so the routing rule ("parallel mode AND
populated pem → physical index, else -1") is testable in isolation and the
call sites read as intent rather than re-deriving the conditional.

Production change is behavior-preserving:
- Same predicate (`!mode.empty() && mode != "primary"`)
- Same empty-pem short-circuit returning -1
- Same get_at() dispatch on hit
- Both call sites replaced with a single call

Four unit tests in [IMEX] cover the routing matrix:
  - non-IMEX ("") and primary mode short-circuit
  - parallel mode + empty pem short-circuits (defense-in-depth; get_at would
    throw on empty values otherwise)
  - identity pem (non-MMU IDEX) routes filament to itself
  - MMU collapse routes multiple logical slots to one physical (7-slot profile
    with 4-lane MMU on physical 0 and direct drives on 1/2/3)

All IMEX + Variant regression suites pass post-refactor (133 assertions / 48
cases under libslic3r, 25 assertions / 6 cases under fff_print [PressureAdvance]).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 23:07:23 -04:00
Clifford GarwoodandClaude Opus 4.7 cb15f35444 test(3mf): round-trip coverage for per-plate IMEX state
Validates that imex_parallel_mode and imex_head_filament_map survive a
full store_bbs_3mf → load_bbs_3mf cycle — the same silent-state-loss bug
class that produced the variant-vector truncation regression, applied to
IMEX plate state which rides the same XML metadata path.

- Positive round-trip: a plate with copy_mode + a non-trivial head
  filament map ("1:2,2:3") is saved and reloaded; both options land on
  the destination plate's config with the exact values preserved.
- Guard scope: a plate with mode="primary" and empty head-filament-map
  does NOT emit metadata (per the serializer's short-circuit), and the
  reload leaves both options absent from the destination config. If the
  serializer ever regressed to writing primary-mode plates, the load
  path would surface phantom "primary" strings on plates that shipped
  clean — this catches that.

Both scenarios call set_temporary_dir to point the BBS exporter's backup
scaffolding at a writable per-process temp directory (by default it
resolves under root at runtime, which fails for non-root test
processes).

All 27 assertions in 2 test cases pass under [3mf][IMEX].

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 22:58:19 -04:00
Clifford GarwoodandClaude Opus 4.7 5434a5217c test(gcode): per-firmware coverage for GCodeWriter::set_pressure_advance(tool)
Exercises the IMEX per-tool PA emission surface added in af59501f4a
("feat: firmware-agnostic per-tool PA emission for IMEX parallel modes").
Six scenarios cover the full routing matrix:

- Negative PA returns empty across all flavors (early-exit guard).
- Klipper: bare vs EXTRUDER=extruder vs EXTRUDER=extruderN. Asserts the
  tool=0 case emits the unsuffixed extruder name (first Klipper extruder
  is named "extruder", not "extruder0") — a subtle edge case easy to
  regress.
- RRF: bare vs D0 vs DN. The D0 case matters: passing tool=0 explicitly
  must emit `D0`, not the current-tool fallback.
- Marlin 2.x: bare vs T0 vs TN.
- Marlin Legacy: tool index is silently dropped — verifies the fallback
  branch can't accidentally start emitting T qualifiers on firmware that
  doesn't support them.
- BBL: flag wins over firmware flavor (Marlin 2 flavor + BBL flag emits
  the BBL-specific `M900 K... L1000 M10`) and BBL never emits a per-tool
  qualifier regardless of the tool argument.

All 25 assertions across 6 cases pass under [PressureAdvance].

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 22:53:24 -04:00
Clifford GarwoodandClaude Opus 4.7 af8fe649ef test(preset): expand [Variant] coverage — stride=2, equal-size, non-variant guard
Adds three scenarios alongside the existing child>parent stride=1 regression
test for update_non_diff_values_to_base_config:

- stride=2 child>parent: machine_max_acceleration_x (size 4 vs 2) — confirms
  the truncation guard fires for the (normal,silent)-pair stride=2 path, not
  just stride=1. Catches a regression class the existing test would miss
  because stride=2 routes through normalize_stride2_floats and a different
  set_with_restore call site.

- equal-size (2=2): exercises the path the guard does NOT short-circuit;
  asserts child per-extruder values survive set_with_restore's nil-restore
  merge. Catches any future change that breaks the equal-size merge — the
  fix's `cur > target ? skip` predicate could regress to `cur >= target` and
  silently override child values otherwise.

- non-variant scalar: layer_height in `keys` and `different_keys` but absent
  from printer_options_with_variant_1/_2. Hits the is_scalar() / "nothing to
  do" branch and must remain untouched. Scopes the guard's blast radius.

All four scenarios in the [Variant] tag pass: 15 assertions, 4 test cases.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 22:49:59 -04:00
Clifford GarwoodandClaude Opus 4.7 6ed688dfc5 test(preset): regression test for child>parent variant-vector truncation
Adds a Catch2 scenario that builds a 2-extruder child DynamicPrintConfig
inheriting from a 1-extruder parent, calls update_non_diff_values_to_base_config
through the real printer_options_with_variant_1 / _2 key sets, and asserts
that printer_extruder_id, printer_extruder_variant, and retraction_length
retain their full size after the merge. Covers three distinct
set_with_restore<T> instantiations (Ints, Strings, Floats) and verifies
both size preservation and per-extruder value preservation.

Verified load-bearing: with the guard in update_non_diff_values_to_base_config
temporarily removed, the test fails with "1 == 2" on pe_id.values.size() and
retraction_length.values.size(); with the guard restored, all six assertions
pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 20:42:57 -04:00
Clifford 0c829c4619 Merge branch 'main' into main 2026-04-22 02:21:37 -04:00
Clifford GarwoodandClaude Opus 4.7 135a571379 fix(preset): don't truncate child variant vectors to parent size on load
update_non_diff_values_to_base_config sizes variant_index to the parent's
(inherits-target's) extruder count, and set_with_restore then replaces
the child's vector with a parent-sized one. When the child preset has
more extruders than the parent (e.g. an IDEX preset inheriting from a
single-nozzle base), every key in printer_options_with_variant_1 is
truncated to the parent's size on project reload, destroying per-extruder
data.

Observable symptoms: objects render with the wrong color (often black)
after reopening the project, and the printer preset shows a permanent
dirty-asterisk that no save/reload cycle can clear.

The child's saved value is authoritative for its own extruder count, so
skip the parent-shaped merge for the variant-keyed branch when cur >
target. Confirmed by loading a 2-extruder IDEX preset inheriting from a
single-nozzle base: all 24 variant-keyed options previously truncated
from child_size=2 -> 1 are now preserved at size 2.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 01:20:29 -04:00
Clifford GarwoodandClaude Opus 4.7 9d75ea2ebc fix(imex): clear stale ghost-hover head when ghosts disappear mid-hover
_picking_pass_imex_ghosts is what resets m_hover_ghost_head, but _picking_pass
early-returns (mouse drag, mouse off-canvas, gizmo drag) skip that reset. If
the user switches from an IMEX printer to a non-IMEX one during such a window
the plate clears its ghost volumes while the stale head index survives,
producing an orphan tooltip anchored to nothing.

Validate the hover state against live ghost volumes before rendering the
tooltip and self-heal the indices when they no longer point at anything.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 22:52:03 -04:00
Clifford GarwoodandClaude Opus 4.7 9a87b96ded refactor(imex): Mirror reflects about zone-boundary plane, not primary origin
Replace imex_head_transform's fifth argument (Vec3d primary_origin) with a
Vec2d primary_zone_center and rewrite the Mirror branch as a true reflection
about the plane x = primary_zone_center.x + gantry_offset.x/2. Previous math
flipped about the primary's current origin, which:
  * let the ghost drift out of the target zone as the primary moved, and
  * made mirrored drag motion track 1:1 with the primary instead of reflecting.

The new transform places the ghost at the mirrored position within the target
zone (matching where the mirror tool actually prints) and reflects drag so
primary +X → ghost -X while Y tracks 1:1 — i.e. the ghost stays a true
mirror while the user drags. Off-row Mirror targets (e.g. T3 on a 2x2) still
reflect across the same X-plane as on-row peers.

PartPlate::calc_imex_ghosts and update_imex_ghost_transforms now feed primary_off
(the primary head's zone center) instead of an instance-space Vec3d.

Mirror tests rewritten against the new geometric contract: ghost origin at the
reflected position, primary drag deltas reflected across the zone-boundary plane.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 22:51:56 -04:00
Clifford GarwoodandClaude Opus 4.7 6a3de6a28f feat(imex): effective_physical_extruder_map helper + diagnostic no-filament tooltip
Centralize the project→printer→printer_extruder_id fallback for the physical
extruder map. PrintApply, PartPlate (ghost color + cache key), Plater (tooltip
+ click gate) all previously open-coded the three-step lookup, and each handled
the "pem unset, derive from pei" case slightly differently — an IDEX printer
without an explicit pem could paint an UNPRINTABLE_COLOR ghost even though the
slicer would have derived a valid mapping.

- IMEXHelpers: add effective_physical_extruder_map(explicit_pem, pei) and a
  PresetBundle overload that wraps the project→printer precedence.
- PrintApply: use the helper in place of the inline pei→pem normalization.
- PartPlate / Plater: call the PresetBundle overload at every ghost-color,
  ghost-cache-key, tooltip, and click-gate site.
- Plater::format_imex_ghost_tooltip: when no filament resolves to a head,
  surface an actionable message directing the user to extend the extruder
  count in the Machine tab, instead of the generic "(no filament routed)".
- IMEXFilamentPickerPopover: hold m_pem by value so callers can pass a
  stack-local derived pem without lifetime worries.
- Tests: 5 new cases covering explicit-wins, default-pem fallback, null
  inputs, and the IDEX ghost-color regression that motivated this.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 22:51:31 -04:00
Clifford GarwoodandClaude Opus 4.7 b2128cc330 refactor(imex): route three inline tool-state parsers through parse_imex_active_tools
PartPlate::calc_imex_zones, GCodeViewer::render, and Plater::collect_imex_warnings
each hand-rolled their own "phys:P/C/M" tokenizer with subtly different error
handling. Replace the three inline loops with parse_imex_active_tools +
imex_primary_tool_for_mode so the Primary/Copy/Mirror classification agrees
across zones, the G-code viewer legend, and slice warnings.

No behavior change: the shared helpers preserve the 1=Primary / 2=Copy /
3=Mirror encoding already consumed downstream and continue to accept the
legacy bare-index form as Primary.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 22:49:08 -04:00
Clifford Garwood 77c32a2e15 feat(imex): plate ghost renderer + per-plate filament map
Replaces the plater-icon popover with colored transparent ghost copies
of primary-head instances on the plate, one per secondary active head
under its Copy/Mirror role transform. Left-click on a ghost opens a
compact filament picker popover for the ghost's head (MMU lane override).
Ghosts track the primary through drag/rotate/scale/mirror and invalidate
on mode or pem changes.

Key pieces:
  IMEXHelpers -- imex_head_transform (Primary/Copy/Mirror), shared role
    parser, per-head filament resolution with X-axis Mirror anchor.
  PartPlate -- ghost state, volume rebuild on mode/map/object mutation,
    primary_origin plumbed for Mirror reflection across the primary-row
    gantry plane.
  GLCanvas3D -- ghost rendering with per-head filament color and
    translucent blending; picking routed via volume composite id.
  Plater -- ghost click + tooltip; plater icon left-click always cycles.
  IMEXFilamentPickerPopover -- BitmapComboBox row for one secondary head,
    writes imex_head_filament_map on selection.
  bbs_3mf -- round-trip the per-plate imex_head_filament_map option.
  PrintConfig -- add imex_head_filament_map as a plate option.

MMU/AFC routing for parallel modes relies on the printer profile's
physical_extruder_map (see prior commit for authoring format). Primary-
row heads and their per-plate filament overrides are resolved through
that map, so PA and temperature emission address the correct physical
extruder when multiple logical slots share one carriage.

Tests: IMEXHelpers coverage for Primary/Copy/Mirror transforms
including a 2x2 off-row regression guard for the X-axis reflection fix.
2026-04-21 14:56:22 -04:00
Clifford GarwoodandClaude Sonnet 4.6 0530d6c4da feat: derive physical_extruder_map from printer_extruder_id; use for IMEX PA/temp
Auto-populate physical_extruder_map (0-indexed) from printer_extruder_id
(1-indexed) in Print::apply(). The guard only runs when the map is still
at default size (<= 1 element), so printer profiles that set it explicitly
are untouched.

All IMEX parallel-mode PA and temperature emission now routes tool slot
indices through physical_extruder_map before constructing firmware
extruder qualifiers (EXTRUDER=, M104 T, M572 D). This ensures AFC/MMU
setups where multiple slots share one physical extruder get the correct
qualifier -- e.g. T6 on physical extruder 3 emits EXTRUDER=extruder3
instead of EXTRUDER=extruder6.

Profile authoring for MMU/AFC printers:
  Add physical_extruder_map to the printer profile JSON as a 0-indexed
  string array, one entry per logical filament slot, whose value is the
  physical extruder carrying that slot. The array size must be > 1 for
  the explicit map to override the auto-derive. Example for a 7-slot
  printer with a 4-lane MMU on extruder 0 and three independent direct
  drives on extruders 1/2/3:

    "physical_extruder_map": ["0","0","0","0","1","2","3"]

  Non-MMU printers need no action -- printer_extruder_id already encodes
  the 1:1 mapping and the auto-derive handles it.

Deeper integration (zone validation, collision detection, filament
assignment grouping, Moonraker agent auto-population) is deferred.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 14:56:22 -04:00
Clifford Garwood 481cd7d8e7 Merge remote-tracking branch 'upstream/main' into feedback 2026-04-21 14:56:08 -04:00
Clifford GarwoodandClaude Sonnet 4.6 2cfdddf8b0 fix: crash on Multimaterial tab for all printers (Windows-only)
The "Pre-slice warnings" line in the IDEX/IQEX Configuration section
was a widget-only line (no options) with full_width left at the default
of 0. activate_line() only skips the option_set.front() call when
full_width=1; without it, the code falls through to:

    bool is_legend_line = option_set.front().opt.gui_type == ...

Calling front() on an empty std::vector is undefined behavior. On
Windows/MSVC release builds this dereferences a null pointer and reads
at offset 0x30 (where ConfigOptionDef::gui_type lands), producing an
ACCESS_VIOLATION at 0x30. On Linux/GCC the same UB happens to be
harmless, so the crash is Windows-only and cannot be reproduced on
Linux.

Fix: set line.full_width = 1, matching the pattern used by the "Modes"
(IMEXModesCtrl) line. This takes the early-return widget path in both
append_line and activate_line, bypassing option_set.front() entirely.

Reported by tester: crash on clicking Multimaterial tab with any
printer (K3D VOSTOK confirmed), build af59501f.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 16:45:25 -04:00
Clifford GarwoodandClaude Sonnet 4.6 af59501f4a feat: firmware-agnostic per-tool PA emission for IMEX parallel modes
Extends set_pressure_advance() with an optional tool index (default -1,
preserving existing behavior for all non-IMEX call sites). Per-firmware:
- Klipper: EXTRUDER=extruder[N] when tool >= 0, bare command otherwise
- RRF: M572 D<N> when tool >= 0, bare M572 otherwise (no D0 fallback)
- Marlin 2: M900 K<X> T<N> when tool >= 0, bare M900 otherwise
- Marlin Legacy / fallback: M900 K<X> always

Adds m_imex_parallel_mode to GCode, set once per export from the active
plate mode. PA and layer-transition temperature tool-qualification are
gated on this being a non-primary parallel mode — primary mode prints
use regular tool-change PA exactly like any non-IMEX printer. Secondary
active tools in parallel modes receive explicit per-tool PA at print
start since they never go through a tool-change sequence.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 09:17:39 -04:00
Clifford GarwoodandClaude Sonnet 4.6 84fc851846 feat: add "Don't show again" to IMEX pre-slice warnings
Replaces MessageDialog with RichMessageDialog to show a suppress
checkbox on both slice-plate and slice-all warning paths. Persists
the choice to app_config as imex_pre_slice_warnings=false. Adds a
re-enable toggle in Printer Settings → Multimaterial → IDEX/IQEX
Configuration so the warnings can be restored if suppressed accidentally.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 09:17:29 -04:00
Clifford GarwoodandClaude Sonnet 4.6 857bfbfb82 fix: guard against empty option_set in OG_CustomCtrl widget-only lines
update_visibility() and render() both called front() on an empty vector
when a CtrlLine had no options (pure widget lines). Added an early-return
path in update_visibility() and a null-guard in render().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 09:17:23 -04:00
Clifford GarwoodandClaude Sonnet 4.6 ad0b42fd37 feat: iMEX temperature handling and pre-slice warnings
Temperature emission (GCode.cpp, PrintConfig.cpp, ConfigManipulation.cpp):
- Set temperatures for all active tools in layer_change_gcode for iMEX
  parallel modes (primary + secondary carriages)
- Fix filament temperature commands so Layer 1 temperatures only emit on
  the first layer; subsequent layers use normal layer-change temperatures
- Remove stray temperature commands that fired outside the intended context
- Consolidate iMEX temperature handling into the second-layer transition;
  clean up emission logic and naming throughout

Pre-slice warning dialogs (PartPlate.cpp/hpp, Plater.cpp):
- Collect per-plate IMEX warnings before slicing: multi-material conflict,
  bed temperature mismatch between carriages (>5 °C delta), and filament
  type incompatibility
- Show a dismissible Yes/No dialog from both "Slice Plate" and "Slice All"
  actions; No returns to 3D view, Yes proceeds to slice
- Refresh plate thumbnails after the panel switch so previously-generated
  thumbnails are not left black

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 17:45:11 -04:00
Clifford Garwood 5aa624b025 fix: Mark secondary carriages as used in iMEX parallel modes
In iMEX (IDEX/IQEX) parallel printing modes (copy/mirror), only the
primary extruder generates toolpaths. The firmware duplicates the
primary's movements for secondary carriages, so they don't appear in
tool_ordering.all_extruders().

This caused is_extruder_used[N] to return false for secondary tools
even though they're physically active and moving.

The fix adds logic to parse the active mode's tool assignments from
imex_mode_active_tools config and marks all assigned tools as used.

Changes:
- Added null checks and bounds validation for config options
- Skip empty tool strings to avoid unnecessary parsing
- Reordered bounds checks for defensive programming
- Added clarifying comments for exception handling

This ensures is_extruder_used[N] is true for all tools in a parallel
mode, allowing printer profiles to correctly enable heaters and
emit cleanup G-code for all active carriages.

Fixes: is_extruder_used[1] returns false in copy/mirror modes (#13086)

Related: Comment [28]/[30], Comment [18] (is_extruder_used in G-code header)
2026-04-11 02:00:02 -04:00
Clifford GarwoodandClaude Sonnet 4.6 73b5b750c6 feat: iMEX multi-material warning badge, conflict dialog, and mode row UI
- Add warning badge (obj_warning.svg overlay) to the iMEX plate icon when
  a parallel mode is active alongside multi-material objects on the same plate
- Add has_imex_multimaterial_conflict() using get_extruders(true) so only
  filaments actually used on the plate are checked
- Move multi-material caution dialog from reslice() into on_action_slice_plate /
  on_action_slice_all so it fires exactly once per user action and does not
  disrupt GL thumbnail generation during Slice All
- Fix is_imex missing from p->config init key list so on_config_change()
  diff detection correctly triggers refresh_imex_icons()
- Defer imex_changed handling until after set_bed_shape() so m_shape is current
- Replace plain remove button with ScalableButton (imex_remove.svg) in mode rows
- Add EditGCodeDialog launch button per mode row for placeholder browsing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 00:10:03 -04:00
Clifford GarwoodandClaude Sonnet 4.6 1d68f917b0 feat: add imex_mode/imex_mode_index/imex_mode_gcode placeholders with global var flow
Register imex_mode (string), imex_mode_index (int), and imex_mode_gcode
(string) in OtherSlicingStatesConfigDef so they appear in the placeholder
search UI under Slicing State.

Set all three via placeholder_parser().set() before any script processing
in _do_export(). Process imex_mode_gcode first so {global} declarations
defined there flow forward into machine_start_gcode.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 19:08:57 -04:00
Clifford GarwoodandClaude Sonnet 4.6 9a87363044 feat: 4-tool support, primary row, zone sizing, and stability fixes
IMEXModesCtrl:
- Primary mode is now a non-deletable first row stored as sentinel
  "primary" in imex_mode_names; older configs load cleanly
- New rows default T0 → Primary when no tool assignment is stored
- Filter "primary" from plater popup/cycle list to prevent double entry
- imex_tools_per_gantry cap raised 2 → 4

Zone sizing:
- Zone width/height now based on active tool count only; inactive tools
  donate their bed share to active neighbors (fixes 4-tool layout)
- Active col/row maps (col_to_zone/row_to_zone) applied consistently
  across zone fills, collision strips, and primary zone box

GCodeViewer animation:
- Mirror position formula fixed: left-of-copy reflects across copy
  zone's left edge; right-of-copy reflects across right edge
  (T3 was rendering on top of T1)
- strip_width/row_strip_height use active counts, matching PartPlate

Stability:
- Early return in calc_imex_zones() when tool_states is empty; prevents
  OOB crash on new printer with stale process-preset mode name
- is_imex toggle in on_config_change calls refresh_imex_icons() so the
  plate mode icon appears without requiring a new project

GCode:
- Remove "primary" guard so Primary mode gcode field is emitted at
  start of print

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 19:08:57 -04:00
Clifford GarwoodandClaude Sonnet 4.6 0d73cd13ba refactor: move IDEX/IQEX settings from standalone tab into Multimaterial section
- Remove standalone IDEX/IQEX printer tab
- Add IDEX/IQEX Configuration and Parallel Modes sections to the
  Multimaterial page, gated behind is_imex
- Carriage config options hidden via toggle_options() when is_imex is off
- Custom widgets (layout combo, theme combo, modes ctrl) show/hide accordingly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 19:08:57 -04:00
Clifford GarwoodandClaude Sonnet 4.6 461c69c83e refactor: rename iXex → IMEX throughout; use IDEX/IQEX as user-facing label
- All config keys: ixex_* → imex_* (is_imex, imex_gantry_count, etc.)
- All C++ identifiers: IXexModesCtrl → IMEXModesCtrl, m_ixex_* → m_imex_*, etc.
- 3MF serialization key: ixex_parallel_mode → imex_parallel_mode
- UI strings: iXex → IDEX/IQEX
- SVG icons: plate_ixex_mode*.svg → plate_imex_mode*.svg
- Remove unused ixex_mode.svg

Breaking change for existing printer configs — acceptable pre-merge.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 19:08:57 -04:00
Clifford Garwood 2934b18d2a Merge remote-tracking branch 'upstream/main' 2026-04-09 00:22:06 -04:00
Clifford GarwoodandClaude Sonnet 4.6 f540cf87a8 fix: preserve iXex tool assignments across gantry count changes
- Add all_tool_states map to IXexModesCtrl Row, storing all tool
  assignments including those not currently visible due to grid size
- active_tools_string() now serializes from all_tool_states so
  off-screen assignments survive the round trip through a smaller grid
- Button clicks keep all_tool_states in sync with visible btn_states
- Display anchors to the gantry row containing the Primary assignment
  so reducing gantry count keeps the meaningful row visible rather
  than always defaulting to row 0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 23:35:32 -04:00
Clifford Garwood 48b9e6ab26 Merge branch 'main' into feedback 2026-04-07 23:56:30 -04:00
Clifford GarwoodandClaude Sonnet 4.6 5fa6480f61 feat: per-plate iXex mode selection with undo, dark mode icons, and menu fixes
- Add per-plate iXex mode icon to the plate toolbar (normal, hover, dark, dark-hover SVG variants)
- Left-click cycles through available modes; right-click shows a popup menu with all modes as radio items
- Mode changes are recorded in the undo/redo snapshot system
- Fix double context menu: suppress EVT_GLCANVAS_PLATE_RIGHT_CLICK and EVT_GLCANVAS_RIGHT_CLICK when the iXex icon popup was already shown
- Remove ixex_parallel_mode combo from Print Settings > Other > Special mode (superseded by per-plate icon)
- Remove dead code: refresh_ixex_mode_combo(), m_ixex_mode_combo member, related Tab reload hook
- iXex mode persisted in 3MF project files via existing plate metadata serialization

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 23:25:38 -04:00
Clifford GarwoodandClaude Sonnet 4.6 ec40172019 feat: per-plate iXex mode selection with undo, dark mode icons, and menu fixes
- Add per-plate iXex mode icon to the plate toolbar (normal, hover, dark, dark-hover SVG variants)
- Left-click cycles through available modes; right-click shows a popup menu with all modes as radio items
- Mode changes are recorded in the undo/redo snapshot system
- Fix double context menu: suppress EVT_GLCANVAS_PLATE_RIGHT_CLICK and EVT_GLCANVAS_RIGHT_CLICK when the iXex icon popup was already shown
- Remove ixex_parallel_mode combo from Print Settings > Other > Special mode (superseded by per-plate icon)
- Remove dead code: refresh_ixex_mode_combo(), m_ixex_mode_combo member, related Tab reload hook
- iXex mode persisted in 3MF project files via existing plate metadata serialization

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 23:25:22 -04:00
Clifford f25a433ec7 Merge branch 'OrcaSlicer:main' into main 2026-04-07 22:50:22 -04:00
Clifford Garwood f8565df113 Merge branch 'feedback' into main
Includes two feature sets on top of the base iXex implementation:

Auto-arrange iXex zone constraint:
  Constrains placement to the primary zone when a parallel mode is active.
  Collision strips registered as hard obstacles via m_unselected so the
  NFP placer correctly excludes them.

Nozzle clearance rename + carriage box visualization overhaul:
  ixex_carriage_width_x/y → ixex_nozzle_clearance_x/y (breaking rename).
  Collision strip width now uses the literal clearance value, not half.
  Carriage footprint boxes place the nozzle at the correct physical edge
  for all printer types (IDEX, IQEX 2x2) and modes (copy, mirror).
  Fixed GLModel::reset() bug causing stale mesh on config change.
2026-04-06 15:06:49 -04:00
Clifford GarwoodandClaude Sonnet 4.6 167211c85c refactor: rename ixex_carriage_width → ixex_nozzle_clearance; fix strip math and carriage box visualization
Config key rename (breaking for saved profiles — call out in PR):
  ixex_carriage_width_x/y → ixex_nozzle_clearance_x/y
  Labels updated to "Nozzle Clearance X/Y" with consistent tooltips
  describing the measurement as nozzle-to-collision-side-edge distance.

Strip math fix:
  Previously halved the clearance value (× 0.5) under the assumption
  the nozzle was centered in the carriage. The measurement is now the
  literal nozzle-to-edge distance, so the × 0.5 factor is removed.
  The collision strip width now equals the configured value directly.

Carriage box visualization (GCodeViewer):
  - Add per-carriage box_offset_x/y so the nozzle marker sits at the
    physically correct edge of the footprint box rather than centered.
  - X: zone-based by default (nozzle at inner edge facing bed center).
    Copy secondaries inherit the primary's X orientation (same movement
    direction). Mirror secondaries use the collision-side edge.
  - Y: always row-based regardless of copy/mirror mode. Gantry is always
    behind the nozzle (high-Y); front-row primaries with a back-row
    secondary override to place nozzle at the low-Y edge.
  - Fix stale mesh bug: GLModel::init_from() is a no-op when already
    initialized. Call reset() before init_from() so mesh rebuilds
    correctly when nozzle clearance values change in config.
  - Remove m_ixex_toolhead_box_dims (was the now-unnecessary cache key).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 14:57:57 -04:00
Clifford GarwoodandClaude Sonnet 4.6 a9ad17cea2 fix: route iXex collision zones through m_unselected for effective exclusion
params.excluded_regions flows into libnest2d PlacementConfig.m_excluded_regions
which is declared but never consumed by the NFP placer — the field is dead code.
m_unselected is the working path: it becomes fixeditems that are preloaded as
physical fixed obstacles in the NFP computation, same as the wipe tower.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 14:57:57 -04:00
Clifford GarwoodandClaude Sonnet 4.6 1cacc6d02d fix: exclude iXex collision strips from auto-arrange placement
Auto-arrange now injects the carriage collision zones as hard excluded
regions (is_virt_object) when an iXex parallel mode is active, using the
same BoundingBoxf3 data already stored for rendering and violation checks.
Objects will no longer be placed in the mirror-edge danger strips.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 14:57:57 -04:00
Clifford GarwoodandClaude Sonnet 4.6 b9f7148080 fix: constrain auto-arrange to iXex primary zone when parallel mode is active
Adds std::optional<BoundingBoxf> m_ixex_primary_zone_box to PartPlate,
populated by calc_ixex_zones() alongside the existing secondary/collision
zone geometry. The new ixex_primary_zone() getter calls ensure_ixex_zones()
so callers always get fresh data. ArrangeJob::process() replaces the
full-bed bedpts with the primary zone corners when the getter returns a
value, so auto-arrange no longer drops objects into the bed center.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 14:57:56 -04:00
Clifford GarwoodandClaude Sonnet 4.6 a3cac6c139 fix: remove iXex zone outline borders to eliminate aliasing flicker
The thin rectangular outlines around copy/mirror zones caused visible
aliasing and appeared to flash during interaction. Removed the border
GLModel, its build code in calc_ixex_zones(), the render block, and
the unused border field from IXexTheme.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 14:57:40 -04:00
Clifford GarwoodandClaude Sonnet 4.6 185b7084d2 fix: null m_ixex_mode_combo in TabPrint::clear_pages() to prevent dangling pointer crash
Same class of bug as the TabPrinter fix: clear_pages() destroys all
page widgets but didn't null m_ixex_mode_combo, causing a SIGSEGV in
refresh_ixex_mode_combo() when load_current_preset() ran after a
page rebuild (e.g. triggered by Plater::reset()).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 14:57:39 -04:00
Clifford GarwoodandClaude Sonnet 4.6 f4808ae7ba fix: avoid capturing C array in lambda for ixex_tool_layout combo
Arrays cannot be captured by value in C++ lambdas; replaced with a
static local declared inside the event handler.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 14:57:39 -04:00
Clifford GarwoodandClaude Sonnet 4.6 5297417518 fix: remove phantom phys_row variable reference from iXex button layout comment
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 14:57:39 -04:00
Clifford GarwoodandClaude Sonnet 4.6 a03d2cd566 fix: restore info log level in two commented-out PartPlate log lines
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 14:57:39 -04:00
Clifford GarwoodandClaude Sonnet 4.6 c75231ec03 fix: remove spurious blank lines in PrintConfig.hpp
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 14:57:39 -04:00
Clifford GarwoodandClaude Sonnet 4.6 e786fabb6a fix: restore info log levels in PartPlate accidentally raised to warning
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 14:57:39 -04:00
Clifford Garwood 1e56b65146 feat: Add iXex parallel printing support for IDEX and IQEX printers
Introduces first-class parallel printing (copy/mirror modes) for printers
with multiple independent X-axis carriages. Branded iXex (independent X
extruder), targeting Klipper firmware with a firmware-agnostic design.

- PrintConfig: new printer options declaring iXex capability and geometry
  (is_ixex, ixex_gantry_count, ixex_tools_per_gantry, carriage dims,
  tool layout, and per-mode name/role/gcode arrays)
- Preset: iXex keys registered in printer and process preset option lists
- Tab: IXexModesCtrl visual grid editor in Printer preset tab; mode
  dropdown in Process → Others tab; clear_pages() nulls iXex pointers to
  prevent dangling-pointer crash on preset save
- PartPlate: 2D zone visualization (active/dimmed/dividers) and
  placement-violation detection (has_ixex_placement_violations) that
  blocks slicing when objects fall outside the primary zone
- Plater: violation detection wired into update_background_process so
  the Slice button is disabled with an error notification on violation
- GCode: mode-activation G-code injected before machine_start_gcode
- GCodeViewer: multi-carriage toolhead markers in sequential preview,
  filament legend annotated with active carriage count and mode name
2026-04-06 14:57:39 -04:00
357 changed files with 49234 additions and 76797 deletions
+11 -7
View File
@@ -6,7 +6,7 @@ description: Use when writing, modifying, reviewing or debugging any OrcaSlicer
# OrcaSlicer wxWidgets GUI
This skill is how OrcaSlicer's wxWidgets GUI is written, changed, fixed and reviewed. Its core is
**wxWidgets 3.3.2 API usage** — the documented contracts, the per-platform behaviour and the known
**wxWidgets 3.3.4 API usage** — the documented contracts, the per-platform behaviour and the known
limitations of the wx version Orca pins — layered with the OrcaSlicer conventions, wrappers, custom
widget library and stable component designs a contributor must follow. Every reference file opens
with a numbered **Rules** checklist (what a diff is checked against), followed by sections that give
@@ -15,9 +15,12 @@ wrong → right pairs with the commit that fixed each one.
## Ground truth: the wx tree in deps/
Orca pins **wxWidgets 3.3.2** from the fork `github.com/SoftFever/Orca-deps-wxWidgets` (tag `v3.3.2`,
`deps/wxWidgets/wxWidgets.cmake`), built static (Flatpak: shared). Facts about this build that change
how you read the wx docs:
Orca pins upstream **wxWidgets 3.3.4** (`github.com/wxWidgets/wxWidgets`, tag `v3.3.4`,
`deps/wxWidgets/wxWidgets.cmake`), built static (Flatpak: shared). Two in-tree patches are applied: an
Apple-only sRGB colour-component fix (`0001-macos-use-srgb-colour-components.patch`) and a Windows-only
clang-cl sibling-compiler search fix for the installed `wxWidgetsConfig.cmake`
(`0002-clang-cl-sibling-compiler-prefix.patch`); neither applies on Linux. Facts about this build that
change how you read the wx docs:
- **Linux builds against GTK3** (`DEP_WX_GTK3` defaults ON in `deps/CMakeLists.txt`). Target GTK3 on
both X11 and Wayland; GTK-guarded code must still compile on GTK2, an opt-out build Orca does not ship.
@@ -33,7 +36,7 @@ Look things up in the source the app is built from — it beats memory, and 3.3
WX=$(find -L deps -maxdepth 5 -type d -path '*dep_wxWidgets-prefix/src/dep_wxWidgets' 2>/dev/null | head -1)
# macOS: deps/build/<arch>/dep_wxWidgets-prefix/src/dep_wxWidgets Linux, Windows: deps/<tree>/dep_wxWidgets-prefix/...
# -L follows a worktree's deps/<tree> symlinked to the main checkout. Not a glob: zsh aborts on one that matches nothing.
# If deps are not built: git clone --depth 1 -b v3.3.2 https://github.com/SoftFever/Orca-deps-wxWidgets
# If deps are not built: git clone --depth 1 -b v3.3.4 https://github.com/wxWidgets/wxWidgets
grep -n "CaptureMouse" -A 30 $WX/interface/wx/window.h # documented contract (doxygen source)
grep -rn "@onlyfor\|not implemented" $WX/interface/wx/popupwin.h # documented platform limits
ls $WX/docs/doxygen/overviews/ # eventhandling.h, sizer.h, high_dpi.md, windowdeletion.h, ...
@@ -219,14 +222,15 @@ known class with a pitfall entry and a fixing commit.
any wx key event; Control+click arrives as a right-click.
- **Windows:** `IsDark()` and `wxSYS_COLOUR_*` follow the system app mode, not Orca's theme — use
`dark_mode()`; menu bitmaps follow `check_dark_mode()`; windows are not double-buffered by default in
3.3.2; `ProcessLeftDown` is never called for popups; a popup that must close with the frame calls
3.3.4; `ProcessLeftDown` is never called for popups; a popup that must close with the frame calls
`BindUnfocusEvent()`.
- **Linux GTK3:** dialogs without size hints collapse (only sizer-fitting calls, not `Fit()`, are
replayed at the first `Show()`); command events from a popup's children are not stopped at the popup
(MSW/macOS stop them); chained popups need `transient_for` set to the mapped parent right before
showing; native borders leak through custom widgets (`RemoveButtonBorder`/`RemoveInputBorder`).
- **Wayland:** no global pointer position, no window positioning, `wxClientDC`, `Update()` and `SetIcon`
do nothing, no floating AUI panes, GL is EGL only.
do nothing, AUI floating is disabled (wx 3.3.4 can drag a floating frame via system drag sessions, but
its docking hints still need global positions), GL is EGL only.
## Editing this skill
@@ -59,7 +59,7 @@ Contents: [Rules](#rules) · [wxColour](#wxcolour) · [System colours and appear
**Contract.** Constructors: `()`, `(r, g, b, a = wxALPHA_OPAQUE)`, `(unsigned long|long|int|unsigned int)` ("A
packed RGB value", `interface/wx/colour.h:98-101`), `(const wxString&|const char*|const wchar_t*)`;
`wxColour(bool) = delete` (`include/wx/colour.h:225-240`; `docs/changes.txt:246-248`: code "unintentionally
`wxColour(bool) = delete` (`include/wx/colour.h:225-240`; `docs/changes.txt:278-280`: code "unintentionally
and mistakenly using wxColour ctor from bool … doesn't compile any longer").
| Fact | Cite |
@@ -121,7 +121,7 @@ returned colour is always valid" (`interface/wx/settings.h:44-48`, `:398-407`).
| Method | Contract |
|---|---|
| `IsDark()` | "checks the appearance of the current application and not the other applications on the system, so under MSW … will return false even if dark mode is used system-wide unless the application opted in using dark mode using wxApp::MSWEnableDarkMode()" (`:332-346`). An incompatible 3.3 change (`docs/changes.txt:71-73`). |
| `IsDark()` | "checks the appearance of the current application and not the other applications on the system, so under MSW … will return false even if dark mode is used system-wide unless the application opted in using dark mode using wxApp::MSWEnableDarkMode()" (`:332-346`). An incompatible 3.3 change (`docs/changes.txt:77-79`). |
| `AreAppsDark()` (3.3.0) | system-wide app dark mode "even if it's not enabled for this particular application"; same as `IsDark()` off MSW (`:308-321`). |
| `IsSystemDark()` (3.3.0) | the "Windows mode", which can differ from the "app mode" (`:348-358`). |
| `IsUsingDarkBackground()` | luminance fallback, "generally not very useful to call directly" (`:360-370`). |
@@ -233,25 +233,25 @@ child handlers.
`@onlyfor{wxmsw}`, `interface/wx/app.h:1418-1465`):
- "experimental"; uses "undocumented, and unsupported by Microsoft, functions"; works on Windows 10 later than
v1809 (including LTSC 2019) and all Windows 11; testing before 20H1 (v2004) "has been limited".
- Flags: default follows the system ("dark mode is only used if it is the default mode for the applications on
the current system"); `DarkMode_Always` forces dark. **[source]** `DarkMode_Auto = 0` exists in
`include/wx/msw/app.h:48` although only `DarkMode_Always` is documented.
- Flags: `DarkMode_Auto` (default) follows the system ("dark mode is only used if it is the default mode
for the applications on the current system"); `DarkMode_Always` forces dark, `DarkMode_Never` forces
light. **[source]** the enum also names `DarkMode_Auto = 0` in `include/wx/msw/app.h:46-52`;
`DarkMode_Never` is new in 3.3.4 (documented at `interface/wx/app.h:1450-1454`).
- Returns `true` if enabled, `false` "most likely because the system doesn't support dark mode".
- Alternatives: the `msw.dark-mode` system option (1 = `MSWEnableDarkMode()`, 2 = `DarkMode_Always`, settable by
environment variable from outside the app, `interface/wx/sysopt.h:85-89`), or `SetAppearance(System|Dark)`.
- Known limitations (`interface/wx/app.h:1434-1448`): anything `TaskDialog()`-based has no dark mode —
`wxMessageBox()`, `wxMessageDialog`, `wxRichMessageDialog`, `wxProgressDialog`, simple `wxAboutBox()` (wx suggests
`wxGenericMessageDialog`/`wxGenericProgressDialog`); common-dialog wrappers `wxColourDialog`, `wxFindReplaceDialog`,
`wxFontDialog`, `wxPageSetupDialog`, `wxPrintDialog`; `wxTimePickerCtrl`, `wxDatePickerCtrl`, `wxCalendarCtrl`
stay light; toolbar items with `wxToolBar::SetDropdownMenu()` draw the drop-down "almost invisible".
environment variable from outside the app, `interface/wx/sysopt.h:85-89`), or `SetAppearance(System|Light|Dark)`.
- Known limitations (`interface/wx/app.h:1442-1448`): `wxTimePickerCtrl`, `wxDatePickerCtrl` and
`wxCalendarCtrl` stay light; `wxPrintDialog` on Windows 10 stays light; MDI applications are not
recommended in dark mode. `wxMessageDialog` gained dark mode in 3.3.3 (#26570) and the common dialogs
in 3.3.4 (#26780), so the older "anything `TaskDialog()`-based and the common dialogs stay light" note
no longer applies.
`AppearanceResult wxApp::SetAppearance(Appearance::System|Light|Dark)` (3.3.0, `interface/wx/app.h:1152-1190`):
`AppearanceResult wxApp::SetAppearance(Appearance::System|Light|Dark)` (3.3.0, `interface/wx/app.h:1130-1187`):
GTK/macOS follow the system by default and the call is immediate and "affects all the existing windows as well
as any windows created after this call"; "Under MSW, the default appearance is always light" and an app that
wants to follow the system must call it with `Appearance::System`; "the appearance can be only set before any
windows are created and calling this function too late will return AppearanceResult::CannotChange" (only wxMSW
returns it);
`Failure` e.g. "because `GTK_THEME` is defined".
wants to follow the system must call it with `Appearance::System`; on MSW it maps to `MSWEnableDarkMode()` and
returns `Ok`/`Failure` (`interface/wx/app.h:1166-1169`). `Failure` e.g. "because `GTK_THEME` is defined";
`AppearanceResult::CannotChange` is "currently never returned" (`interface/wx/app.h:1151, 1179-1183`).
`wxDarkModeSettings` (`interface/wx/msw/darkmode.h:30-111`), passed to `MSWEnableDarkMode()`: `GetColour(wxSystemColour)`
(defaults "not documented and are subject to change"; the doc example names `0x202020` as the default background);
@@ -260,34 +260,40 @@ returns it);
which "doesn't look very well in dark mode"; the base returns grey.
**[source] facts the docs do not state:**
- `SetAppearance` on MSW returns `CannotChange` when any TLW exists **or `MSWEnableDarkMode` was already called**
(`gs_appMode != AppMode_Default`); `SetAppearance(Light)` returns `Ok` without doing anything
(`src/msw/darkmode.cpp` `wxApp::SetAppearance`). macOS `System` sets `NSApp.appearance` to
`[NSAppearance currentAppearance]` rather than nil, pinning the current look (`src/osx/cocoa/utils.mm:485-513`);
GTK3 maps to the portal colour-scheme machinery; GTK2 always returns `Failure` (`src/gtk/app.cpp:355-381`).
- `MSWEnableDarkMode` may be called any time, but dark title bars are applied only to TLWs **created** afterwards
- As of 3.3.3 (#26516) and 3.3.4 (#26734), MSW dark mode can be switched at runtime: `SetAppearance()` maps to
`MSWEnableDarkMode()` and returns `Ok`/`Failure`, never `CannotChange` (`src/msw/darkmode.cpp:354-375`).
`MSWEnableDarkMode()` re-applies the mode to an existing window by sending it `WM_SYSCOLORCHANGE` (`:345-349`),
and the window restyles through `wxMSWDarkMode::HasChanged()`/`NotifySysColorChange()`
(`src/msw/window.cpp:5337, 5528-5534`), so a control created before the switch is re-themed too. macOS `System`
sets `NSApp.appearance` to `[NSAppearance currentAppearance]` rather than nil, pinning the current look
(`src/osx/cocoa/utils.mm:485-513`); GTK3 maps to the portal colour-scheme machinery; GTK2 always returns
`Failure` (`src/gtk/app.cpp:355-381`).
- `MSWEnableDarkMode` may be called any time; dark title bars are applied when a TLW is created
(`EnableForTLW` at the end of TLW creation, `src/msw/toplevel.cpp:513`) and controls are dark-enabled at creation
(`MSWCreateControl` → `AllowForWindow` and, for some controls, `SetForegroundColour(LISTBOXTEXT)`,
`src/msw/control.cpp:133-140`). wx cannot flip existing windows — the reason `SetAppearance` refuses late calls.
`src/msw/control.cpp:133-140`).
- wx takes ownership of the settings pointer (`wxDarkModeModule::SetSettings`) — allocate it with `new`.
- `msw.dark-mode` is read in `wxApp::Initialize` (`src/msw/app.cpp:492-494`): a user's `WX_MSW_DARK_MODE`
environment variable enables wx dark mode before Orca's own call.
- wx's owner-drawn menu path keys on `wxMSWDarkMode::IsActive()` (`src/msw/menuitem.cpp` `wxMenuItem::OnDrawItem`,
`GetColourToUse`; `src/msw/menu.cpp`; menu-bar UAH drawing in `src/msw/darkmode.cpp`).
3.3.x dark-mode fix log: 3.3.2 wxMSW (`docs/changes.txt:297-308`: checkbox accessibility in dark mode, rendering of
several controls, toolbar, menus; also "Revert use of WS_EX_COMPOSITED"), 3.3.1 (`:358-372`: wxStaticBitmap-in-notebook
crash, disabled wxButton bitmaps and wxStaticText, notebook high-contrast background, wxDataViewCtrl light-mode
border regression, selected toolbar buttons, wxComboCtrl, wxTE_RICH wxTextCtrl), 3.3.0 (`:385` "Add experimental dark
mode support to wxMSW"); XRC dark colour variants (`:494`).
3.3.x dark-mode fix log: 3.3.4 wxMSW (`docs/changes.txt:349-358`: dynamic dark mode, dark mode for
wxFontPickerCtrl, wxNotebook, wxSpinButton and the common dialogs), 3.3.3 (`:433` "Implement support for
switching between light/dark modes"; `:426` wxMessageDialog, `:429` combo/listbox on Windows 11, `:431`
wxCheckBox label in screen readers), 3.3.2 (`:492-501`: checkbox accessibility in dark mode, rendering of
several controls, toolbar, menus; also "Revert use of WS_EX_COMPOSITED"), 3.3.1 (`:553-565`:
wxStaticBitmap-in-notebook crash, disabled wxButton bitmaps and wxStaticText, notebook high-contrast
background, wxDataViewCtrl light-mode border regression, selected toolbar buttons, wxComboCtrl, wxTE_RICH
wxTextCtrl), 3.3.0 (`:578` "Add experimental dark mode support to wxMSW"); XRC dark colour variants (`:687`).
**OrcaSlicer.** `GUI_App::on_init_inner` (`#ifdef __WINDOWS__`) calls `MSWEnableDarkMode(DarkMode_Auto)` and then
`NppDarkMode::InitDarkMode(init_dark_color_mode, init_sys_menu_enabled)`. wx's call exists only so that wx-drawn
menus get dark borders; NppDarkMode does the theming (title bars, explorer theme, scrollbars, list headers) because
it can switch live and wx cannot. The code comment "Orca: todo switch to native dark mode support in wxWidgets and
remove NppDarkMode" records the intent; the blocker is that a live Preferences toggle would become restart-only
(`SetAppearance` returns `CannotChange` after startup, existing windows cannot be restyled) and TaskDialog/common
dialogs/pickers stay light anyway — which is also why Orca has its own `MsgDialog` family and `ProgressDialog`.
`NppDarkMode::InitDarkMode(init_dark_color_mode, init_sys_menu_enabled)`. wx's call exists so that wx-drawn
menus get dark borders; NppDarkMode does Orca's theming (title bars, explorer theme, scrollbars, list headers).
The code comment "Orca: todo switch to native dark mode support in wxWidgets and remove NppDarkMode" records the
intent. The old blocker — a live Preferences toggle would be restart-only because `SetAppearance` returned
`CannotChange` and existing windows could not be restyled — is gone as of 3.3.3/3.3.4 (above). Orca still keeps
NppDarkMode, and Orca's own `MsgDialog` family and `ProgressDialog` remain its message/progress UI.
**[source]** consequences of `DarkMode_Auto`: wx's internal "dark" stays `AllowDark` and follows the **system app
mode**; NppDarkMode's later `SetPreferredAppMode` call does not touch wx's `gs_appMode`. So with Windows light +
Orca dark, wx's dark machinery (menus, `GetColour()`, `IsDark()`) stays off; with Windows dark + Orca light it is
@@ -312,11 +318,15 @@ on: `GetColour()` returns the dark palette, new native controls are dark-enabled
```
Cite: bf397a0632 (`GUI_App.cpp`, `GUI_App::on_init_inner`).
- **Rule:** Do not call `wxTheApp->SetAppearance(...)` in Orca.
**Why:** MSW returns `CannotChange` (Orca already called `MSWEnableDarkMode`), GTK2 fails, and macOS pins
**Why:** MSW would switch wx's own mode out of step with NppDarkMode and `dark_color_mode` (wx's mode is driven
by `MSWEnableDarkMode(DarkMode_Auto)` and follows the system), GTK2 fails, and macOS pins
`NSApp.appearance` while `GUI_App::dark_mode()` keeps reading the system `AppleInterfaceStyle` — the two would
disagree.
- **Rule:** Do not show wx's TaskDialog-based or common dialogs where dark mode matters; use `MessageDialog` and
friends (`MsgDialog.hpp`, see `references/windows-dialogs.md`).
- **Rule:** Do not show wx's TaskDialog-based or common dialogs; use `MessageDialog` and friends
(`MsgDialog.hpp`, see `references/windows-dialogs.md`).
**Why:** Orca's dialogs are owner-drawn and consistent with the rest of the UI. The old dark-mode gap
closed — `wxMessageDialog` gained dark mode in 3.3.3 (#26570) and the common dialogs in 3.3.4 (#26780) —
but the convention stands.
## Window colours, inheritance and native-control limits
@@ -371,7 +381,7 @@ macOS paints the system grey.
| Port | Behaviour |
|---|---|
| MSW | `wxButton`/`wxToggleButton` `SetBackgroundColour`/`SetForegroundColour` switch the native button to `BS_OWNERDRAW` (`src/msw/anybutton.cpp` `wxAnyButton::MakeOwnerDrawn`, `:1324-1390`) — the colour works but native theming is gone. `wxCheckBox`/`wxRadioButton` foreground makes them owner-drawn when themes are active (`src/msw/control.cpp` `MSWMakeOwnerDrawnIfNecessary`); 3.3.2 fixed checkbox accessibility in that mode (`docs/changes.txt:299-300`). Static-text-like children paint the ancestor brush only if that ancestor's background is inheritable. |
| MSW | `wxButton`/`wxToggleButton` `SetBackgroundColour`/`SetForegroundColour` switch the native button to `BS_OWNERDRAW` (`src/msw/anybutton.cpp` `wxAnyButton::MakeOwnerDrawn`, `:1324-1390`) — the colour works but native theming is gone. `wxCheckBox`/`wxRadioButton` foreground makes them owner-drawn when themes are active (`src/msw/control.cpp` `MSWMakeOwnerDrawnIfNecessary`); 3.3.2 fixed checkbox accessibility in that mode (`docs/changes.txt:492-493`). Static-text-like children paint the ancestor brush only if that ancestor's background is inheritable. |
| macOS | `SetBackgroundColour` reaches the NSView only if it `respondsToSelector:setBackgroundColor:` and the style is not `wxBG_STYLE_TRANSPARENT` (`src/osx/cocoa/window.mm:3514-3531`): **NSButton (`wxButton`) ignores the background colour**. Foreground uses `setTextColor:` when the view has it (`:3885-3887`); stock NSButton has none. `wxStaticText` is an NSTextField with `setDrawsBackground:NO` — its background is never painted (`src/osx/cocoa/stattext.mm:154`). `wxTextCtrl`'s `setEnabled:` resets the text colour: multi-line (`wxNSTextView`) always, to `controlTextColor`/`disabledControlTextColor`; single-line (`wxNSTextField`) when it does not draw its background, to `controlTextColor`/`secondarySelectedControlColor` (`src/osx/cocoa/textctrl.mm`). |
| GTK3 | `SetBackgroundColour/SetForegroundColour/SetFont` install a per-widget CSS provider `*{color:..;background:..;font:..}` at `GTK_STYLE_PROVIDER_PRIORITY_APPLICATION` (`src/gtk/window.cpp` `wxWindowGTK::GTKApplyStyle`); `wxButton`/`wxCheckBox` apply it to their inner label too (`src/gtk/button.cpp:325-335`, `src/gtk/checkbox.cpp:233-237`). User CSS (priority USER) can still override. |
| GTK2 | colours applied with `gtk_widget_modify_style` — pixmap-engine themes may ignore them. |
@@ -106,7 +106,7 @@ The general wx rule is that setters do not send events. The exceptions are the b
| `wxTextEntry::SetValue(s)` | **yes**, one `wxEVT_TEXT`, even when `s` equals the current text | `interface/wx/textentry.h:539-542`; [source] `src/common/textentrycmn.cpp:236-254`, `src/msw/textctrl.cpp:1133-1145` |
| `wxTextEntry::ChangeValue(s)` | no | `interface/wx/textentry.h:177-178` |
| `wxTextEntry::Clear()` | yes (= `SetValue("")`) | `interface/wx/textentry.h:193-194` |
| `wxTextCtrl::SetLabel/SetLabelText` | nothing at all (3.3) | `docs/changes.txt:111-113`; `src/common/textcmn.cpp:934-937` |
| `wxTextCtrl::SetLabel/SetLabelText` | nothing at all (3.3) | `docs/changes.txt:117-119`; `src/common/textcmn.cpp:934-937` |
| `SetSelection/SetStringSelection` on wxChoice, wxComboBox, wxListBox, wxRadioBox | no | `interface/wx/ctrlsub.h:102-103,126` |
| `wxComboBox::SetValue` | `wxEVT_TEXT` if editable; none with `wxCB_READONLY` | `interface/wx/combobox.h:256-272` |
| `wxComboBox::Popup()/Dismiss()` | DROPDOWN/CLOSEUP, except on wxOSX | `interface/wx/combobox.h:279-281,292-294` |
@@ -184,7 +184,7 @@ Pitfalls:
ctrl->GetTextCtrl()->SetLabel(s); // Wrong: no-op
ctrl->GetTextCtrl()->ChangeValue(s); // Right (or SetValue if listeners must react)
```
Cite: `docs/changes.txt:111-113`.
Cite: `docs/changes.txt:117-119`.
- **Rule:** Handle `wxEVT_TEXT_ENTER` without `Skip()` unless you want the default button activated as well.
**Why:** a skipped Enter falls through to the dialog's default button and closes it.
```cpp
@@ -213,7 +213,7 @@ Contract:
verbatim, and `wxControl::EscapeMnemonics()` escapes it (`interface/wx/control.h:174-195, 379-387`). This applies
to `wxStaticText`, `wxCheckBox`, `wxRadioButton`, `wxButton` labels and to book page titles: all `wx*book`
classes interpret mnemonics in page text (`interface/wx/bookctrl.h:141-147`), and since 3.3 wxListbook and
wxChoicebook do too (`docs/changes.txt:128-130`).
wxChoicebook do too (`docs/changes.txt:134-136`).
- `SetLabelMarkup()` needs well-formed markup or the label "won't be shown at all" (returns false, keeps the old
label). A bare `&` is still a mnemonic. Multi-line markup works only on GTK and macOS; the generic version used on
MSW handles single lines (`interface/wx/control.h:338-347`). Quote untrusted text with
@@ -383,10 +383,10 @@ Pitfalls:
- Virtual list (`wxLC_REPORT|wxLC_VIRTUAL`): call `SetItemCount()` and override `OnGetItemText` (optionally
`OnGetItemImage`/`OnGetItemAttr`) (`interface/wx/listctrl.h:128-134`). `EditLabel()` asserts without
`wxLC_EDIT_LABELS` (`docs/changes.txt:68-69`).
`wxLC_EDIT_LABELS` (`docs/changes.txt:74-75`).
- Images: prefer `SetImages(std::vector<wxBitmapBundle>)`; `wxImageList` is discouraged
(`interface/wx/withimages.h:21-40`) and is measured in physical pixels in 3.3 (`docs/changes.txt:85-88`); calling
its methods on an invalid (unsized) list now asserts, i.e. fails silently in Orca (`docs/changes.txt:53-56`).
(`interface/wx/withimages.h:21-40`) and is measured in physical pixels in 3.3 (`docs/changes.txt:91-94`); calling
its methods on an invalid (unsized) list now asserts, i.e. fails silently in Orca (`docs/changes.txt:59-62`).
`Assign*` transfers ownership, `Set*ImageList` does not (`interface/wx/withimages.h:90-110`). Sizing:
`references/dpi-bitmaps-fonts.md`.
- `wxTreeCtrl`: `SelectItem` emits events ([table](#events-from-programmatic-changes)).
@@ -402,7 +402,7 @@ Contract:
selection's blocks).
- `FreezeTo(row, col)` returns false (an assert, silent in Orca) for out-of-range values, merged cells or the native
header (`interface/wx/grid.h:5747-5772`); [source] also when rows/columns were reordered or drag-moving is enabled
(`src/generic/grid.cpp:5742-5750`). In 3.3 it freezes even when the grid is too small (`docs/changes.txt:48-51`).
(`src/generic/grid.cpp:5742-5750`). In 3.3 it freezes even when the grid is too small (`docs/changes.txt:54-57`).
- Editor contract: `EndEdit` must not modify the grid — it stores the value and returns true if it changed;
`ApplyEdit` writes it after `wxEVT_GRID_CELL_CHANGING` was not vetoed (`interface/wx/grid.h:610-638`). Editors,
renderers and attrs are ref-counted and the setters take ownership (`:1263-1305, 1739-1770, 2681-2701`).
@@ -492,7 +492,7 @@ All cites `interface/wx/dataview.h`. MSW uses the generic implementation; wxGTK
Other per-port facts: editing starts on a slow double-click or a platform key — "F2 is typical on Windows, Space
and/or Enter is common elsewhere" (2604-2607, 1915-1918); a custom renderer's `StartDrag()` is "Not yet supported"
(2717-2719); `RenderText()` should be used inside `Render()` so text matches native renderers (2708-2714). Calling
`Collapse()` from an event handler was fixed in 3.3.1 (`docs/changes.txt:346`; the generic re-check is described
`Collapse()` from an event handler was fixed in 3.3.1 (`docs/changes.txt:539`; the generic re-check is described
under [DVC control API](#wxdataviewctrl-control-api)).
OrcaSlicer: `ParamsViewCtrl` (`EditGCodeDialog`) and `DiffViewCtrl` (`UnsavedChangesDialog`) use
@@ -1,6 +1,6 @@
# DPI, bitmaps and fonts
How wx 3.3.2 maps DIP, logical and physical pixels on each platform, how DPI changes reach a window,
How wx 3.3.4 maps DIP, logical and physical pixels on each platform, how DPI changes reach a window,
and how OrcaSlicer sizes layout (`FromDIP`, `em_unit`), rescales (`DPIAware`), rasterizes icons
(`BitmapCache`, `create_scaled_bitmap`, `ScalableBitmap`) and chooses fonts (`Label` table). Read it
for any fixed size, icon, bitmap, image list, font, or `on_dpi_changed` work, and when a bug looks
@@ -488,7 +488,7 @@ handler bound there runs once more after every DPI change.
inherits the DC's scale (`interface/wx/bitmap.h:370-385`).
- `wxBitmap(const wxCursor&)` is invalid on GTK under Wayland (`interface/wx/bitmap.h:388-401`).
- wxMSW `wxBitmap::Create(size, dc)` no longer multiplies by the DC's content scale
(`docs/changes.txt:94-96`).
(`docs/changes.txt:100-102`).
**Offscreen drawing.** "The scaling factor of the bitmap determines the scaling factor used by
this device context" (`interface/wx/dcmemory.h:41-58`); `wxMemoryDC(wxDC*)` does **not** inherit
@@ -740,7 +740,7 @@ are in `references/popups-menus.md`.
in the new code as it doesn't support showing DPI-dependent bitmaps. Please use
wxWithImages::SetImages() instead"; "the size is specified in physical pixels and must correspond
to the size of bitmaps … that will be added". 3.3 made the size physical and makes calls on an
invalid list assert (`docs/changes.txt:53-56, 85-88`) — silently in Orca's assert-free build.
invalid list assert (`docs/changes.txt:59-62, 91-94`) — silently in Orca's assert-free build.
When a list is unavoidable, `wxBitmapBundle::CreateImageList(win, bundles)` builds one at the
consensus size (public but undocumented, [source] `include/wx/bmpbndl.h`).
```cpp
@@ -761,7 +761,7 @@ wxArtProvider::GetBitmapBundle(wxART_WARNING, wxART_OTHER, wxSize(16, 16));
```
**`wxImage`** resizing (`interface/wx/image.h:28-89`): `wxIMAGE_QUALITY_NEAREST` is no longer an
alias of `NORMAL` since 3.3.0 (`docs/changes.txt:98-99`; `wxIMAGE_QUALITY_FAST` is the speed
alias of `NORMAL` since 3.3.0 (`docs/changes.txt:104-105`; `wxIMAGE_QUALITY_FAST` is the speed
synonym). `NORMAL` (default) = bilinear down to an integer multiple, then box average; `HIGH` =
box average when shrinking, bicubic when enlarging; `BILINEAR`, `BICUBIC`, `BOX_AVERAGE` explicit.
High-quality scaling "may not work as expected when using a single mask colour for
@@ -1,6 +1,6 @@
# Events: binding, dispatch, posting and deferred calls
How wx 3.3.2 finds, runs, propagates, queues and drops event handlers, and how OrcaSlicer code
How wx 3.3.4 finds, runs, propagates, queues and drops event handlers, and how OrcaSlicer code
binds, emits and defers. Read it before writing any `Bind`/`Unbind`, `Skip()`, `ProcessEvent`,
`wxPostEvent`/`wxQueueEvent` or `CallAfter`, before defining a custom event, and when debugging a
handler that never runs, runs twice, runs on a dead object, or swallows a widget's own behaviour.
@@ -1,6 +1,6 @@
# Mouse, keyboard and focus
How input reaches wx windows in the wxWidgets 3.3.2 build Orca ships, and the Orca conventions on
How input reaches wx windows in the wxWidgets 3.3.4 build Orca ships, and the Orca conventions on
top: mouse capture, mouse and key events, accelerators and Orca's shortcut registry, focus,
tooltips and cursors. Read it when a widget captures the mouse or tracks hover, when adding or
changing a keyboard shortcut, when touching focus, tooltip or cursor code, and when debugging an
@@ -297,7 +297,7 @@ Per port [source]:
non-enter event while the cached position is invalid is treated as the enter.
- **GTK:** crossing events with a grab/ungrab mode are ignored; outside capture wx re-derives the
window under the pointer on each motion (`src/gtk/window.cpp:2073-2117, 2384-2456`; fixes #24339
and #24931–#24933 are in this release, `docs/changes.txt:537, 547`).
and #24931–#24933 are in this release, `docs/changes.txt:730, 740`).
- **macOS:** each view's `NSTrackingArea` uses `NSTrackingInVisibleRect`
(`src/osx/cocoa/window.mm:3924`) and covers its children, so do not rely on the parent getting
LEAVE when the pointer moves onto a child. `NSMouseMoved` is delivered only to the deepest view
@@ -334,7 +334,7 @@ A composite's hover state must therefore track its children. Orca's `StateHandle
`CS_DBLCLKS`, `src/msw/app.cpp:584`). GTK drops the surplus press before `GDK_2BUTTON_PRESS`
(`src/gtk/window.cpp:1798-1817`), and GTK2 also suppresses triple clicks (`:1818-1829`). On wxOSX the
third press of a triple click is a DOWN again, as on MSW (`src/osx/cocoa/window.mm:4105-4140`;
#25886, `docs/changes.txt:316`). `GetClickCount()` is "implemented only in wxMac and returns -1
#25886, `docs/changes.txt:509`). `GetClickCount()` is "implemented only in wxMac and returns -1
for the other platforms" (`interface/wx/event.h:2965-2974`).
- **Context menu:** "under MSW the context menu event is generated after EVT_RIGHT_UP … but under
GTK … after EVT_RIGHT_DOWN", so a window handling `wxEVT_CONTEXT_MENU` must not handle (or must
@@ -398,7 +398,7 @@ A diagonal trackpad scroll on macOS sends two events, vertical first, then one w
| `ClientToScreen()`, `ScreenToClient()`, `GetScreenRect()` | — | add the top-level window's `gdk_window_get_origin()` (`src/gtk/window.cpp:4630-4698`). Without a global origin the result is only meaningful relative to the same top-level window: fine for deltas and hit tests inside one window, wrong across windows or for absolute placement |
| `wxFindWindowAtPoint(pt)` | deepest window at a screen point; disabled children count, hidden ones are skipped (`interface/wx/utils.h:385-394`) | built on screen rectangles, so unreliable. On GTK it is `wxGenericFindWindowAtPoint`, a walk over every top-level window and child (`src/gtk/utilsgtk.cpp:98-101`, `src/common/utilscmn.cpp:1294-1345`) |
| `wxGetKeyState(key)` | "In wxGTK, this function can be only used with modifier keys … when not using X11 backend" (`interface/wx/utils.h:352-358`) | Ctrl/Alt/Shift and Caps/Num/Scroll Lock only; any other key returns false (`src/unix/utilsx11.cpp:2596-2662`) |
| `WarpPointer()` | Apple's HIG forbids it; on Wayland it works only with a compositor implementing the pointer-warp protocol, and mutter also needs a pressed button (`interface/wx/window.h:3895-3914`; `docs/changes.txt:294`) | — |
| `WarpPointer()` | Apple's HIG forbids it; on Wayland it works only with a compositor implementing the pointer-warp protocol, and mutter also needs a pressed button (`interface/wx/window.h:3895-3914`; `docs/changes.txt:487`) | — |
| `wxUIActionSimulator` | "doesn't work when using Wayland" (`interface/wx/uiaction.h:20`) | — |
| `PopupMenu(x, y)` | — | GTK ≥ 3.22 positions it relative to the window (`gtk_menu_popup_at_rect`, `src/gtk/window.cpp:6520-6565`), so it is safe |
@@ -540,7 +540,7 @@ The full Esc/close path is in `references/windows-dialogs.md`.
layout"; specials are their `WXK_*` (`:1371-1394`). A Cyrillic `ц` gives `'W'`, so Ctrl+letter
shortcuts work across layouts, but an AZERTY key reports its own label (`$` where US has `]`), so
`Ctrl-;`-style punctuation accelerators may be untypeable on some layouts (`:1396-1407`). wxGTK
got the non-Latin mapping in 3.3.0 (#23379, `docs/changes.txt:540`).
got the non-Latin mapping in 3.3.0 (#23379, `docs/changes.txt:733`).
- `wxEVT_CHAR` reflects Shift and the layout. Ctrl+letter gives 1..26 (`WXK_CONTROL_A` …,
`:1409-1421`). Exception: on macOS Cmd+letter's CHAR is the letter itself (`'a'`/`'A'`); only the
physical Control key yields 1..26 (`src/osx/cocoa/window.mm:304-307`).
@@ -629,8 +629,14 @@ Space/Return holds the mouse capture until its KEY_UP reaches the button. On MSW
### Contract
- "An accelerator takes precedence over normal processing" (`interface/wx/accel.h:178`), but it
runs after `wxEVT_CHAR_HOOK` on every port (table above).
- "An accelerator takes precedence over normal processing" (`interface/wx/accel.h:178`). Since 3.3.4 this
holds on every port (wxGTK used to let the focused window handle the key first), unless the focused
window claims the key. The accelerator walk still runs after `wxEVT_CHAR_HOOK` (table above). The new
`wxEVT_ACCELERATOR_KEY` event (`interface/wx/event.h:1740-1801`, `@since 3.3.4`) is sent to the focus
window — and propagated to its parents — before an accelerator is used; it is sent after
`wxEVT_CHAR_HOOK` on most ports and before it on macOS. Its handler can call `UseAccelerator()`, and a
window that must keep the key overrides `wxWindow::ClaimsKeyBeforeAccelerator()`
(`interface/wx/window.h:4554-4583`).
- [source] On GTK and macOS a hit is sent as `wxEVT_MENU` with the entry's id and, if unprocessed,
retried as `wxEVT_BUTTON`; macOS treats the key as consumed either way
(`src/gtk/window.cpp:1340-1366`, `src/osx/window_osx.cpp:2563-2590`). On MSW the `WM_COMMAND` goes to
@@ -654,11 +660,16 @@ Space/Return holds the mouse capture until its KEY_UP reaches the button. On MSW
them in `wxMenuBar::RebuildAccelTable` (`src/msw/menu.cpp:1264-1292`); GTK adds the menu's accel
group to the top-level window in `AttachToFrame` (`src/gtk/menu.cpp:239-247`). A menu shown with
`PopupMenu` treats `"\tCtrl+X"` as display text.
- **MSW:** accelerators are not translated while a `wxTextCtrl`/`wxComboBox`/`wxSpinCtrl` has focus and
the key is a text-editing key: Ctrl+A/C/V/X/Ins/Del/Home/End/Left/Right, Shift+those navigation
keys, bare Del/Home/End, Alt+Backspace (`src/msw/textentry.cpp:1073-1150`). Multi-line controls also
keep Enter (`src/msw/textctrl.cpp:2110-2135`).
- **GTK:** the accelerator walk has no such exemption (`src/gtk/window.cpp:1340-1366`). As menu
- **Text-entry controls claim their editing keys on every port since 3.3.4.** A `wxTextCtrl`, a
non-read-only `wxComboBox` or a `wxSpinCtrl` overrides `ClaimsKeyBeforeAccelerator()` to return
`wxTextEntryBase::IsUsedForEditing()` (`src/common/textcmn.cpp:136-140`;
`src/common/spinctrlcmn.cpp:160-167`; `src/msw/combobox.cpp:389-395`, `src/gtk/combobox.cpp:205-211`):
Alt+Backspace; any printable key with no modifier or Shift (except Delete, so Esc/Enter/Tab stay
usable); Backspace/Delete/Home/End with no modifier or Shift; Ctrl+A/C/V/X; and Insert/Delete/Home/
End/Left/Right with Ctrl or Shift. A matching menu accelerator does not fire while such a control has
focus. The set lives in `IsUsedForEditing` (`src/common/textentrycmn.cpp:441-509`).
- **GTK:** the accelerator walk now consults `ClaimsKeyBeforeAccelerator()` too
(`src/gtk/window.cpp:1524-1539`), so the text-control exemption above applies here since 3.3.4. As menu
accelerators, Shift with non-alphabetic keys does not work, bare arrow keys do not work, and the
listed keys (Tab, the modifiers, locks …) are unsupported (`interface/wx/menuitem.h:562-575`).
- **macOS:** menu-bar items become `NSMenuItem` key equivalents
@@ -69,9 +69,12 @@ Contents: [The stack](#the-stack-orcas-gui-is-built-on) ·
## The stack Orca's GUI is built on
- **wxWidgets 3.3.2, SoftFever fork.** `deps/wxWidgets/wxWidgets.cmake` fetches
`https://github.com/SoftFever/Orca-deps-wxWidgets` at tag `v3.3.2` and builds it static
(`-DwxBUILD_SHARED=OFF`); Flatpak builds build it shared. Linux builds against **GTK3** by default
- **wxWidgets 3.3.4, upstream.** `deps/wxWidgets/wxWidgets.cmake` fetches
`https://github.com/wxWidgets/wxWidgets` at tag `v3.3.4` and builds it static
(`-DwxBUILD_SHARED=OFF`); Flatpak builds build it shared. Two in-tree platform patches apply on
Apple and Windows only (`0001-macos-use-srgb-colour-components.patch`,
`0002-clang-cl-sibling-compiler-prefix.patch`) — `references/platforms.md`. Linux builds against
**GTK3** by default
(`option(DEP_WX_GTK3 "Build wxWidgets against GTK3" ON)` in `deps/CMakeLists.txt`, `SLIC3R_GTK`
default `"3"`, Flatpak uses gtk3). GTK2 exists only as an opt-out (`-DDEP_WX_GTK3=OFF`) and loses
EGL, WebKit2 and DIP pixels. Code guarded for GTK should still compile on GTK2, but GTK3 under X11
@@ -85,11 +88,12 @@ Contents: [The stack](#the-stack-orcas-gui-is-built-on) ·
- **Orca's own widget library.** New UI code largely does not use raw wx controls: the owner-drawn
widgets in `src/slic3r/GUI/Widgets/` (`Button`, `CheckBox`, `ComboBox`, `TextInput`, `SpinInput`,
`SwitchButton`, `RadioGroup`, `Label`, `DialogButtons`, …) replace them. Reasons: native controls
cannot follow Orca's look or its app-level dark-mode toggle; on Windows wx's native dark mode does
not reach anything built on `TaskDialog()` (`wxMessageBox`, `wxMessageDialog`, `wxRichMessageDialog`,
`wxProgressDialog`) nor the wrapped common dialogs (`wxColourDialog`, `wxFontDialog`, …)
(`interface/wx/app.h:1434-1443`), so Orca shows the `MsgDialog` family and its own generic
`Widgets/ProgressDialog` instead; and on GTK the theme's borders bleed through wrapped native
cannot follow Orca's look or its app-level dark-mode toggle; on Windows wx's native dark mode reaches
the `TaskDialog()`-based boxes (`wxMessageBox`, `wxMessageDialog`, `wxRichMessageDialog`,
`wxProgressDialog`) since 3.3.3 and the wrapped common dialogs (`wxColourDialog`, `wxFontDialog`, …)
since 3.3.4, but the date/time/calendar controls, `wxPrintDialog` on Windows 10 and MDI applications
stay light (`interface/wx/app.h:1442-1448`), so Orca shows the `MsgDialog` family and its own generic
`Widgets/ProgressDialog` for a consistent look; and on GTK the theme's borders bleed through wrapped native
controls (the widgets call `RemoveButtonBorder` / `RemoveInputBorder` under `__WXGTK__`).
Plain containers stay raw (`wxPanel`, `wxBoxSizer`, `wxScrolledWindow`).
- **Namespaces.** Most widgets are in the global namespace; a few (`DialogButtons`, `HyperLink`,
@@ -67,9 +67,10 @@ Contents: [Rules](#rules) · [Why the library exists](#why-the-library-exists)
Native controls cannot carry Orca's flat, rounded, palette-coloured look, and they cannot follow all of Orca's
theming:
- On Windows the theme is an app-level setting (Preferences "Enable dark Mode", Windows-only) layered on
`MSWEnableDarkMode`, and wx's MSW dark mode does not reach `TaskDialog()`-based dialogs (`wxMessageBox`,
`wxMessageDialog`, `wxRichMessageDialog`, `wxProgressDialog`), the common dialogs or the date/time pickers
(`interface/wx/app.h:1436-1445`). Hence the `MsgDialog` family (`references/windows-dialogs.md`) and Orca's own
`MSWEnableDarkMode`; wx's MSW dark mode now reaches the `TaskDialog()`-based dialogs (`wxMessageBox`,
`wxMessageDialog`, `wxRichMessageDialog`, `wxProgressDialog`) since 3.3.3 and the common dialogs since 3.3.4,
but the date/time pickers, `wxPrintDialog` on Windows 10 and MDI applications stay light
(`interface/wx/app.h:1442-1448`). Hence the `MsgDialog` family (`references/windows-dialogs.md`) and Orca's own
`ProgressDialog`.
- On macOS and Linux Orca follows the system appearance; native controls are themed by the toolkit, but any
light palette colour set on them still needs the `UpdateDarkUI` pass (`references/colours-dark-mode.md`).
@@ -6,7 +6,7 @@ to write a custom control on the wx side and how to author an Orca widget on the
`StaticBox`/`StateHandler` foundation. Read it before writing or reviewing any `wxEVT_PAINT`
handler, `render`/`doRender` method, `messureSize`, or a new class under `src/slic3r/GUI/Widgets/`.
wx cites are relative to the pinned wx 3.3.2 tree (located as in `SKILL.md` §Ground truth).
wx cites are relative to the pinned wx 3.3.4 tree (located as in `SKILL.md` §Ground truth).
Orca builds wx with `wxBUILD_DEBUG_LEVEL=0` and `libslic3r_gui` with `wxDEBUG_LEVEL=0`: every
wx assert below is compiled out and `wxCHECK*` returns silently, so paint misuse shows up only as
wrong, missing or stale pixels, never as an assert dialog.
@@ -116,7 +116,7 @@ Bind(wxEVT_PAINT, [this](wxPaintEvent&) {
| Situation | MSW | macOS | GTK3 (X11 and Wayland) | GTK2 (opt-out build) |
|---|---|---|---|---|
| Handler creates no `wxPaintDC` | `wxWindowMSW::HandlePaint` sees no DC and lets `DefWindowProc` validate the region: no `WM_PAINT` storm in 3.3.2, but drawing is unclipped and the contract is broken (`src/msw/window.cpp` `HandlePaint`) | no error; the window shows only what the background step left (undetermined under `wxBG_STYLE_PAINT`) | same | same |
| Handler creates no `wxPaintDC` | `wxWindowMSW::HandlePaint` sees no DC and lets `DefWindowProc` validate the region: no `WM_PAINT` storm in 3.3.4, but drawing is unclipped and the contract is broken (`src/msw/window.cpp` `HandlePaint`) | no error; the window shows only what the background step left (undetermined under `wxBG_STYLE_PAINT`) | same | same |
| Handler creates a DC, then `Skip()`s | default `OnPaint` (native painting) runs as well | — | — | — |
| `wxPaintDC` outside a paint event | `wxCHECK_RET(!paintStack.empty(), "wxPaintDC can't be created outside wxEVT_PAINT handler")` (also for a window other than the one being repainted): the DC gets no HDC and every draw call is a silent no-op (`src/msw/dcclient.cpp` `wxPaintDCImpl::wxPaintDCImpl`) | no visible effect | `wxCHECK_RET(cr, "using wxPaintDC without being in a native paint event")`, every draw call is a silent no-op (`src/gtk/dc.cpp:518`) | works (GDK) |
| `wxClientDC` drawing | works on non-composited windows; the doc lists "wxMSW when using double buffering" (`WS_EX_COMPOSITED`, see below) as non-working (`interface/wx/dcclient.h:82-84`), yet `CanBeUsedForDrawing` returns `true` unconditionally (`include/wx/msw/dcclient.h:60`), so it cannot detect that case | never; `CanBeUsedForDrawing` is `false` (`include/wx/osx/dcclient.h:58`) | X11 works; Wayland has no effect, `CanBeUsedForDrawing` is `false` on `wxDisplayWayland` (`src/gtk/dc.cpp` `wxClientDCImpl::CanBeUsedForDrawing`) | works |
@@ -283,8 +283,8 @@ void MyCard::OnPaint(wxPaintEvent&) {
ancestor up to the TLW (`src/msw/window.cpp` `wxWindowMSW::IsDoubleBuffered`);
`SetDoubleBuffered(on)` toggles that style; `MSWDisableComposited()` removes it up the chain.
`WS_EX_COMPOSITED` makes the whole subtree paint bottom-up into one buffer.
- wx 3.3.0 enabled double buffering for all MSW windows (#22851, `docs/changes.txt:557`) and 3.3.2
reverted it (#25808, `docs/changes.txt:308`). In 3.3.2 nothing in wx sets `WS_EX_COMPOSITED`
- wx 3.3.0 enabled double buffering for all MSW windows (#22851, `docs/changes.txt:750`) and 3.3.2
reverted it (#25808, `docs/changes.txt:501`). In 3.3.4 nothing in wx sets `WS_EX_COMPOSITED`
unless the app calls `SetDoubleBuffered(true)`: MSW windows are not double-buffered by default,
exactly as in 3.2.
- macOS: always double-buffered, `IsDoubleBuffered()` returns `true` (`include/wx/osx/window.h:161`);
@@ -439,7 +439,7 @@ Two variants exist:
(`src/msw/gdiimage.cpp` `wxGDIImage::GetScaleFactor`, `src/msw/dcmemory.cpp`; details in
`references/dpi-bitmaps-fonts.md §wxBitmap`), while `GetLogicalSize()` stays the physical size there.
- MSW `wxBitmap::Create(size, dc)` no longer multiplies by the DC content scale; the size is
physical (`docs/changes.txt:94-96`).
physical (`docs/changes.txt:100-102`).
**Usage — portable back buffer.**
```cpp
@@ -556,7 +556,7 @@ already prepared ([Buffered painting](#buffered-painting-and-double-buffering)).
must set the appropriate fonts and colours" (`interface/wx/dc.h:775-780`). `GetFont()` returns `wxNullFont` until
`SetFont()` is called (`interface/wx/dc.h:715-720`).
- wx 3.3 moved the non-drawing members to `wxReadOnlyDC` and several overridable measuring virtuals
now take it (`docs/changes.txt:228-231`); see `references/wx-33-changes.md`.
now take it (`docs/changes.txt:260-263`); see `references/wx-33-changes.md`.
**OrcaSlicer.** Orca widgets measure in `messureSize()`. A `messureSize()` that builds a
`wxClientDC dc(this)` (`Button::messureSize`, `TextInput::messureSize`) measures correctly on every
@@ -1,7 +1,7 @@
# Platforms, the wx build, and platform-specific code
Read this when code has to differ per platform, when a bug shows up on only one OS, toolkit or display
server, or when you need to know how Orca's wxWidgets is built. It covers the wx fork and its build
server, or when you need to know how Orca's wxWidgets is built. It covers the wx source pin and its build
options, platform macros, per-platform summaries that point into the topic files, and where
platform-specific code lives. It also owns runtime X11/Wayland detection, the Wayland gap list, custom
title bars and window decoration, GTK native-chrome removal, and the cross-platform test checklist.
@@ -58,37 +58,46 @@ Contents: [Rules](#rules) · [The wx build Orca uses](#the-wx-build-orca-uses)
## The wx build Orca uses
### Source, pin and local patch
### Source, pin and local patches
- `deps/wxWidgets/wxWidgets.cmake` builds `https://github.com/SoftFever/Orca-deps-wxWidgets` at tag
**`v3.3.2`** (`GIT_SHALLOW ON`, submodules `3rdparty/catch`, `3rdparty/pcre` and `3rdparty/libwebp`
only). The fork carries Orca's build fixes, the clang-cl fix among them; do not duplicate
a fork fix as a local patch under `deps/wxWidgets/` (cc390f11ee removed the local
`0001-Clang-CL-fix.patch` once the fork carried the fix). The fork's clang-cl fix is the MSVC lib-dir
selection in the installed `wxWidgetsConfig.cmake`: it looks for `<prefix>_<arch>_lib` (or `_dll`)
under the consuming compiler's prefix first and then the sibling one (`clang` ↔ `vc`), because cl
and clang-cl share an ABI and either can consume either build
(`build/cmake/wxWidgetsConfig.cmake.in:53-73`).
- **The one local patch** is `deps/wxWidgets/0001-macos-use-srgb-colour-components.patch`, applied
- `deps/wxWidgets/wxWidgets.cmake` builds upstream `https://github.com/wxWidgets/wxWidgets` at tag
**`v3.3.4`** (`GIT_SHALLOW ON`, submodules `3rdparty/catch`, `3rdparty/pcre` and `3rdparty/libwebp`
only). Two in-tree patches under `deps/wxWidgets/` are applied, one per platform, so neither applies
on Linux: `0001-macos-use-srgb-colour-components.patch` on Apple and
`0002-clang-cl-sibling-compiler-prefix.patch` on Windows. The clang-cl patch is the old fork's
clang-cl fix ported back to a local patch: it changes the MSVC lib-dir selection in the installed
`wxWidgetsConfig.cmake` so it looks for `<prefix>_<arch>_lib` (or `_dll`) under the consuming
compiler's prefix first and then the sibling one (`clang` ↔ `vc`), because cl and clang-cl share an
ABI and either can consume either build (`build/cmake/wxWidgetsConfig.cmake.in:53-73`). Because the
pin is upstream, every other build fix must live as a local patch under `deps/wxWidgets/`.
- **The Apple patch** is `deps/wxWidgets/0001-macos-use-srgb-colour-components.patch`, applied
only `if (APPLE)`. The `PATCH_COMMAND` first runs `git checkout -f -- src/osx/cocoa/colour.mm` and
then `git apply`, so the step can run again safely (a7775296b0). The patch makes the wxOSX
`wxColour` component getters (`wxNSColorRefData::Red/Green/Blue/Alpha` and `IsSolid`) convert the
`NSColor` with `[NSColorSpace sRGBColorSpace]` instead of `NSCalibratedRGBColorSpace`, so colours
read back on macOS match their sRGB values (custom-colour accuracy). Colour usage:
`references/colours-dark-mode.md`.
- **The Windows patch** is `deps/wxWidgets/0002-clang-cl-sibling-compiler-prefix.patch`, applied only
`if (WIN32)`. Its `PATCH_COMMAND` first runs
`git checkout -f -- build/cmake/wxWidgetsConfig.cmake.in` and then `git apply`, so the step can run
again safely. It rewrites the `WIN32_MSVC_NAMING` branch of `build/cmake/wxWidgetsConfig.cmake.in`
to try the consuming compiler's prefix first and then the sibling one (`clang` ↔ `vc`) when locating
the installed `<prefix>_<arch>_lib`/`_dll` directory.
- The checked-out source is the tree that every wx citation in this skill refers to:
`deps/build/<arch>/dep_wxWidgets-prefix/src/dep_wxWidgets` on macOS and
`deps/<tree>/dep_wxWidgets-prefix/src/dep_wxWidgets` on Linux and Windows (`deps/build` for a
release build; `build_win.bat` names the others). Locate it with the bash or PowerShell lookup in
`SKILL.md` §Ground truth. On macOS its
`src/osx/cocoa/colour.mm` already has the patch applied.
`src/osx/cocoa/colour.mm` already has the Apple patch applied; on Windows its
`build/cmake/wxWidgetsConfig.cmake.in` already has the clang-cl patch applied.
- **Flatpak builds wx separately.** `deps/CMakeLists.txt` leaves `dep_wxWidgets` out of the deps
target when `FLATPAK` is set. Instead, `scripts/flatpak/com.orcaslicer.OrcaSlicer.yml` has its own
`wxWidgets` module whose config-opts "mirror deps/wxWidgets/wxWidgets.cmake with FLATPAK=ON,
DEP_WX_GTK3=ON": `-DwxBUILD_TOOLKIT=gtk3`, a shared build (`wxBUILD_SHARED=ON`,
`BUILD_SHARED_LIBS=ON`, `d` debug postfix), and `wxUSE_LIBWEBP=sys`, because the builtin webp
libraries are installed only by static builds. It links with lld and pins the fork's tag
`orca-3.3.2` at a fixed commit. Option and version changes must be made in both files.
libraries are installed only by static builds. It links with lld and pins upstream `v3.3.4` at
commit `6a42a4b999b127cf28a8b5fe686da6b8ca93a9b8`. Option and version changes must be made in both
files; it applies neither patch (GTK3, no MSVC).
### Toolkit per platform
@@ -188,7 +197,7 @@ Its `wx/gtk/private*` includes are commented out. The per-port headers outside t
`wx/msw/private.h` (`BitmapComboBox.cpp`, `PresetComboBoxes.cpp`, Windows-guarded) and
`wx/osx/private.h` (`Utils/MacDarkMode.mm`), come with the regular wx install. Private headers are
port-specific and unversioned. Include one only under the same macro wx uses for that port or
feature, and re-check it whenever the fork is bumped.
feature, and re-check it whenever the wx pin is bumped.
### wxInspector
@@ -268,8 +277,8 @@ Each bullet names the mechanism and the file that owns it.
popups with it are dismissed on deactivation, deferred through `CallAfter`. No key dismisses a
popup, and `ProcessLeftDown` is never called. `PopupWindow::BindUnfocusEvent` is MSW-only →
`references/popups-menus.md` §5.
- **Painting:** in 3.3.2 windows are not double-buffered by default (the 3.3.0 global
`WS_EX_COMPOSITED` was reverted, `docs/changes.txt:308`). Custom widgets buffer by hand →
- **Painting:** in 3.3.4 windows are not double-buffered by default (the 3.3.0 global
`WS_EX_COMPOSITED` was reverted, `docs/changes.txt:501`). Custom widgets buffer by hand →
`references/painting-custom-widgets.md`.
- **Building large panels:** every control is a native child window, and outside a sizer pass a move
or resize is immediate, repainting when the window is shown [source: `src/msw/window.cpp:2036`
@@ -284,8 +293,9 @@ Each bullet names the mechanism and the file that owns it.
- **Mouse capture:** `wxEVT_MOUSE_CAPTURE_LOST` and `wxEVT_MOUSE_CAPTURE_CHANGED` are delivered →
`references/mouse-keyboard-focus.md`.
- **Controls:** `wxDataViewCtrl` is the generic implementation (`references/controls-dataview.md`).
TaskDialog-based dialogs and common dialogs stay light in wx dark mode
(`interface/wx/app.h:1434-1448`); Orca's `MsgDialog` family is owner-drawn →
TaskDialog-based dialogs (since 3.3.3) and common dialogs (since 3.3.4) follow wx dark mode; the
date/time/calendar controls, `wxPrintDialog` on Windows 10 and MDI applications stay light
(`interface/wx/app.h:1442-1448`); Orca's `MsgDialog` family is owner-drawn →
`references/windows-dialogs.md`.
- **WebView:** Edge (WebView2). It needs the runtime (checked by `GUI_App::init_webview_runtime`),
creates asynchronously, serves custom schemes as `https://<scheme>.wxsite`, and allows one script
@@ -616,16 +626,16 @@ headers. Rows marked [source] or "protocol" come from the implementation or from
| Area | What happens on Wayland | Cite | Orca handling / owner |
|---|---|---|---|
| Global pointer position | `wxGetMousePosition()` / `wxGetMouseState()` call `gdk_device_get_position` [source], but Wayland gives clients no global pointer position, so the result is not a screen position | `src/gtk/window.cpp` `wxGetMousePosition` | use event coordinates + `ClientToScreen` (`BBLTopbar`); dismiss popups by focus tracking (`SearchDialog::Dismiss`) → `references/mouse-keyboard-focus.md` |
| Top-level position | the compositor places windows, and `SetPosition()` / `Move()` on a TLW have no effect (protocol) | — | `GUI_App::window_pos_restore` restores only size and maximised state; moves go through `gtk_window_begin_move_drag` |
| Top-level position | the compositor places windows, and `SetPosition()` / `Move()` on a TLW have no effect (protocol). 3.3.4 added geometry restore through the Wayland XDG session-management protocol (`SaveGeometry`/`RestoreToGeometry` with the new `SaveString`/`RestoreString` fields) | `interface/wx/toplevel.h:439-455, 474-490` | `GUI_App::window_pos_restore` restores only size and maximised state; moves go through `gtk_window_begin_move_drag` |
| Window icon | `SetIcon()` / `SetIcons()` "doesn't do anything when using Wayland … create a `.desktop` file" | `interface/wx/toplevel.h:517-521, 538-542` | `src/dev-utils/platform/unix/com.orcaslicer.OrcaSlicer.desktop` (`Icon=OrcaSlicer`, `StartupWMClass=orca-slicer`) |
| App id | `wxAppConsole::SetClassName()` is the xdg `app_id` with GTK ≥ 3.24.22 (and the AUMID on Windows); it must be set before any TLW. wx applies it when a TLW is mapped, and only if it is non-empty [source: `wxTopLevelWindowGTK::GTKHandleMapped`] | `interface/wx/app.h:765-812` | Orca calls only `SetAppName(SLIC3R_APP_KEY)`, so GTK's default applies. On Windows, `SetClassName` would also change shell behaviour (MRU, Shift+middle-click) |
| `wxClientDC` | deprecated in 3.3 ("please use wxInfoDC instead for obtaining information", `interface/wx/dcclient.h:43-46`). Drawing through it "simply doesn't have any effect" on GTK3/Wayland or wxOSX. `CanBeUsedForDrawing()` [source]: false on Wayland only for wxGTK (`src/gtk/dc.cpp`), always false on wxOSX, always true on wxMSW (`include/wx/{osx,msw}/dcclient.h`), although its doc also lists "wxMSW when using double buffering" | `interface/wx/dcclient.h:48-53, 80-88` | draw only in `wxPaintDC` after `Refresh()`/`RefreshRect()` → `references/painting-custom-widgets.md` |
| `wxWindow::Update()` | "doesn't do anything in wxGTK port when using Wayland". [source] wx skips the GDK update calls there because they broke later updates (#25036) | `interface/wx/window.h:2405-2407` | never rely on `Update()` to paint synchronously |
| `WarpPointer()` | works only if the compositor implements the pointer-warp protocol; mutter also needs a mouse button held | `interface/wx/window.h:3902-3907`; `docs/changes.txt:294` | Orca never warps the pointer |
| `WarpPointer()` | works only if the compositor implements the pointer-warp protocol; mutter also needs a mouse button held | `interface/wx/window.h:3902-3907`; `docs/changes.txt:487` | Orca never warps the pointer |
| `wxUIActionSimulator` | "currently doesn't work when using Wayland with wxGTK" | `interface/wx/uiaction.h:20` | not used; Orca also builds `wxUSE_XTEST=OFF` |
| `wxBitmap(const wxCursor&)` | creates an invalid bitmap | `interface/wx/bitmap.h:393-395` | — |
| OpenGL | only EGL; `PreferGLX()` has no effect. Without EGL in the build, wxGTK's `wxGLCanvas` shows a fatal message and refuses to work [source: `src/gtk/glcanvas.cpp` `IsAvailable`]. The EGL surface is a subsurface over the canvas, ready only after map and a frame callback [source: `src/unix/glegl.cpp`] | `interface/wx/glcanvas.h:1094-1095` | `CLI::run` forces X11 when wx lacks EGL; overlays are drawn in GL/ImGui, never as wx children over the canvas → `references/webview-gl-aui-media.md` |
| AUI | the doc note "live resize is always used … for wxOSX and wxGTK3 when using Wayland" is obsolete: "As of wxWidgets 3.3.0 this function always returns false", and `wxAUI_MGR_LIVE_RESIZE` is in the default flags. Floating panes need global positions | `interface/wx/aui/framemanager.h:336-345` | `Plater::priv::priv` clears `wxAUI_MGR_ALLOW_FLOATING`; `sanitize_window_layout_for_wayland` strips floating state from the saved layout → `references/webview-gl-aui-media.md` §wxAuiManager |
| AUI | the doc note "live resize is always used … for wxOSX and wxGTK3 when using Wayland" is obsolete: "As of wxWidgets 3.3.0 this function always returns false", and `wxAUI_MGR_LIVE_RESIZE` is in the default flags. AUI's docking-hint logic still needs global positions; 3.3.4 added system drag sessions (`StartDragSession`) so wx can move a pane's floating frame under Wayland anyway | `interface/wx/aui/framemanager.h:336-345`; `src/aui/framemanager.cpp:4574-4599` | `Plater::priv::priv` clears `wxAUI_MGR_ALLOW_FLOATING`; `sanitize_window_layout_for_wayland` strips floating state from the saved layout → `references/webview-gl-aui-media.md` §wxAuiManager |
| Popups | a GTK popup is an `xdg_popup` only for COMBO/DROPDOWN/POPUP_MENU hints, so wx creates popups with `GDK_WINDOW_TYPE_HINT_COMBO` [source]. A chained popup's parent must be the mapped popup, and mapping it with a grab deactivates the toplevel (Orca's comments in `DropDown::mouseMove`, `DropDown::ShouldDismissOnTopWindowDeactivate`) | `src/gtk/popupwin.cpp:110-114` | `DropDown` transient-for chain, `ShouldDismissOnTopWindowDeactivate` → `references/popups-menus.md` |
| Fractional scale | arrives as an integer GDK scale | `docs/doxygen/overviews/high_dpi.md:348-351` | → `references/dpi-bitmaps-fonts.md` |
| Window decorations | some desktop environments draw a title bar on undecorated windows anyway | — | [Undecorated windows](#wayland-undecorated-top-level-windows-splash) |
@@ -636,7 +646,9 @@ were two-finger scrolling (703, 3.1.3), the EGL-based `wxGLCanvas` for Wayland (
support (498, both 3.1.5). New with the upgrade: "Many bug fixes for Wayland-specific problem" (397)
and a `wxMediaCtrl` fix (402) in 3.1.6, GDK errors from `PopupMenu()` avoided (317, 3.1.7),
`wxCURSOR_SIZING` fixed (261, 3.2.0). In 3.3, `WarpPointer()` on supported compositors
(`docs/changes.txt:294`) and the EGL/Wayland high-DPI scale fix (`changes.txt:538`).
(`docs/changes.txt:487`) and the EGL/Wayland high-DPI scale fix (`changes.txt:731`); in 3.3.4, a fix for
the crash on pointer/seat disconnect (`changes.txt:371`), AUI pane dragging via system drag sessions
(`:372`) and window-geometry restore via XDG session management (`:373`).
## Window decoration and custom title bars
@@ -658,7 +670,7 @@ There is no `wxCAPTION` on any platform; each port then needs its own handling.
**Contract** [source + documented]. Since wx **3.3.0**, `wxTopLevelWindowMSW::MSWGetStyle` adds
`WS_CAPTION` whenever any of `wxCAPTION | wxMINIMIZE_BOX | wxMAXIMIZE_BOX | wxCLOSE_BOX` is set
(`src/msw/toplevel.cpp:133-135`). The 3.3.0 wxMSW change list says "Turn wxCAPTION on automatically if
required by other styles (#23575)" (`docs/changes.txt:581`). Commit eefdabcd98 attributes this to
required by other styles (#23575)" (`docs/changes.txt:774`). Commit eefdabcd98 attributes this to
3.3.2, but it is a 3.3.0 change. `SetWindowStyleFlag()` recomputes the native style through
`MSWGetStyle` and turns the bits back on (`src/msw/window.cpp` `wxWindowMSW::MSWUpdateStyle`).
@@ -868,10 +880,11 @@ do both). Cite: 6148ba16b3, 988b500f33.
## wx 3.3 migration notes
Orca moved from **3.1.5** to 3.3.2 in 8248b06337 ("Updated wxWidgets to 3.3.2", #12941; build system
in 2d7e26292b), so the 3.1.6–3.2.0 incompatible changes (`docs/changes_32.txt`) apply as well as
`docs/changes.txt`. The version digest, every migration commit as a rule for new code, and the
post-upgrade regressions to watch are owned by `references/wx-33-changes.md` (§8). The
Orca moved from **3.1.5** to **3.3.2** in 8248b06337 ("Updated wxWidgets to 3.3.2", #12941; build system
in 2d7e26292b); the pin is now upstream **3.3.4** (`deps/wxWidgets/wxWidgets.cmake`, tag `v3.3.4`), so
the 3.1.6–3.2.0 incompatible changes (`docs/changes_32.txt`) apply as well as `docs/changes.txt`. The
version digest, every migration commit as a rule for new code, and the post-upgrade regressions to
watch are owned by `references/wx-33-changes.md` (§10); the 3.3.3 and 3.3.4 changes are its §8–§9. The
platform-specific ones are described in this file: the `MainFrame` `WS_CAPTION` handling
([MSW title bar](#msw-the-mainframe-custom-title-bar)), the macOS `kAEGetURL` re-registration and the
`SidePopup` anchoring ([ifdef landscape](#the-ifdef-landscape)), and the GTK criticals filter
@@ -1,6 +1,6 @@
# Popups, transient windows and menus
How wx 3.3.2 popups and menus behave on each port, and the Orca wrappers built on them: `PopupWindow`,
How wx 3.3.4 popups and menus behave on each port, and the Orca wrappers built on them: `PopupWindow`,
`DropDown`/`ComboBox`, `SidePopup`, the search popups, frameless dialogs used as popups, `MenuFactory`,
`append_menu_item`, `Plater::PopupMenu` and the macOS menubar versus `BBLTopbar`. Read it before you add
or change anything that opens over other UI and must close by itself, or any context menu or menubar item.
@@ -791,7 +791,7 @@ created with `wxBORDER_NONE | wxFRAME_NO_TASKBAR | wxFRAME_FLOAT_ON_PARENT | wxF
`wxWindow`. "The window automatically destroys itself when the user clicks on it or it loses the focus"
(`interface/wx/tipwin.h:13-14`). Since 3.3.2 use `static Ref New(parent, text, maxLength, rectBound)`. The
returned `wxTipWindow::Ref` is "guaranteed to become invalid when the tip window is closed", and the raw
constructor is deprecated (`tipwin.h:27-132`; `docs/changes.txt:286`). `Create` positions the tip at the mouse
constructor is deprecated (`tipwin.h:27-132`; `docs/changes.txt:479`). `Create` positions the tip at the mouse
and pops it up immediately (`src/generic/tipwin.cpp:171-240`); on GTK it also captures the mouse.
`SetBoundingRect` closes the tip when the mouse leaves that rectangle (`tipwin.h:160-172`). Orca:
`Button::OnParentMotion` (MSW-only tooltips for disabled buttons) keeps a `wxTipWindow::Ref`.
@@ -800,11 +800,11 @@ created with `wxBORDER_NONE | wxFRAME_NO_TASKBAR | wxFRAME_FLOAT_ON_PARENT | wxF
`wxRichToolTipPopup` (`src/generic/richtooltipg.cpp`). Re-check it on every wx update.
- **`wxInfoBar`** "calls its parent wxWindow::Layout() method and assumes that it will change the parent
layout": add it to the parent's sizer (`interface/wx/infobar.h:38-44`). It is generic on all ports and native
in wxGTK. Use `ShowMessage(msg, flags)`/`Dismiss()`. 3.3 adds `ShowCheckBox` (`docs/changes.txt:460`).
in wxGTK. Use `ShowMessage(msg, flags)`/`Dismiss()`. 3.3 adds `ShowCheckBox` (`docs/changes.txt:653`).
- **`wxNotificationMessage`** is "not a window", native on MSW, macOS and GTK (`interface/wx/notifmsg.h:12-18`).
MSW recommends `MSWUseToasts()` before the first notification, macOS needs the alert notification style for
actions, and GTK ignores the timeout for warnings and errors (`notifmsg.h:23-38`, 179-182). 3.3 reports the
dismissal reason (`docs/changes.txt:488`). Orca does not use it. Its in-app notifications are the GL-canvas
dismissal reason (`docs/changes.txt:681`). Orca does not use it. Its in-app notifications are the GL-canvas
`NotificationManager`.
## 13. wxMenu and wxMenuItem
@@ -862,7 +862,7 @@ submenu (it leaks), `Destroy(id)` deletes it, and `Remove()` returns ownership (
an item switch it to owner-drawn (`MSWMustUseOwnerDrawn`, `src/msw/menuitem.cpp:1371-1377`). "Owner drawn items
are also incompatible with dark mode support and using them makes the entire menu containing them to use
standard light mode colours even in dark mode." 3.3.2 reworked MSW dark-mode menu rendering
(`docs/changes.txt:305-306`); owner-drawn items still opt out of it.
(`docs/changes.txt:498-499`); owner-drawn items still opt out of it.
**Labels and accelerators** (`menuitem.h:460-490`, 555-570).
- The syntax is `"&Label\tCtrl+X"`; `&&` is a literal ampersand. `CTRL` means Cmd on macOS, and `RAWCTRL` gives
@@ -1,6 +1,6 @@
# Sizers and layout
How wx 3.3.2 computes window sizes and lays out children, and how OrcaSlicer builds layouts on top of
How wx 3.3.4 computes window sizes and lays out children, and how OrcaSlicer builds layouts on top of
that: sizers and flags, best/min size, the fitting functions (`SetSizerAndFit`, `SetSizeHints`, `Fit`,
`Layout`, `FitInside`) per platform, show/hide relayout, Freeze/Thaw, scrolled windows, `wxStaticText`
wrapping, layout on DPI change, and Orca's layout idioms. Read it when building or reviewing any dialog
@@ -604,7 +604,7 @@ Ordinary dialogs and the sidebar use plain `wxScrolledWindow`.
without spaces never wraps with `wxStaticText::Wrap`.
- `wxST_WRAP` (new in 3.3.2): "Wrap label text on multiple lines if necessary, using the available
horizontal space. This style only works when the control is used inside a sizer"
(`interface/wx/stattext.h:46-49`; `docs/changes.txt:280`). It is opt-in; labels without it are
(`interface/wx/stattext.h:46-49`; `docs/changes.txt:473`). It is opt-in; labels without it are
unaffected. It is implemented with `GetMinSizeFromKnownDirection` (`stattextcmn.cpp:285-312`).
**[source]** The initial `CalcMin` still uses the unwrapped best size, so a fitted dialog grows to the
full one-line width and nothing wraps; constrain the width another way (a fixed or min width on the
@@ -44,7 +44,8 @@ Contents: [Rules](#rules) · [Build facts](#build-facts-that-decide-string-behav
12. One complete sentence per msgid, placeholders instead of concatenated fragments, the number
placeholder in both plural forms, `// TRN` for ambiguous strings, a context for homonyms.
→ §Plurals and contexts
13. A wrapper around `wxGetTranslation` returns `wxString` by value. → §Translation
13. A wrapper that converts a translation (`wxString::FromUTF8`, `ToUTF8()`, `format_wxstr`) returns
by value; `wxGetTranslation()` itself returns `const wxString&`. → §Translation
14. User-supplied text (preset, filament, file, printer names) in a mnemonic-interpreting label
goes through `SetLabelText` or `wxControl::EscapeMnemonics`. → §Labels
15. File dialogs: filters from `file_wildcards(FT_*)`, default dir `from_u8(app_config->get_last_dir())`,
@@ -186,11 +187,11 @@ was the splash screen's event filter, fixed in 4088a36095; see `references/threa
**modifies the string**. Use `s[n].GetValue()` or an explicit `int`/`wchar_t` type
(`interface/wx/string.h:171-226`).
- Since 3.3 `wxstr = {"Hello", 2}` is ambiguous (the `string_view` constructor); write
`wxString{"Hello", 2}` (`docs/changes.txt:191-194`).
`wxString{"Hello", 2}` (`docs/changes.txt:223-226`).
- Never pass a `wxString`, `c_str()` or `mb_str()` to a real C vararg function (`printf`); use
`wxString::Format`/`wxPrintf` or convert explicitly (`interface/wx/string.h:262-300`).
- `wxUSE_STL` no longer exists; wx 3.3 re-enables implicit `wxString` → `std::string` only through
`wxUSE_STD_STRING_CONV_IN_WXSTRING=1` (`docs/changes.txt:163-166`). Orca keeps it 0 and adds
`wxUSE_STD_STRING_CONV_IN_WXSTRING=1` (`docs/changes.txt:195-198`). Orca keeps it 0 and adds
`wxNO_UNSAFE_WXSTRING_CONV`; do not enable either conversion, the compile error is the guard.
### Pitfalls
@@ -322,10 +323,13 @@ language's decimal separator; `wxString::ToCDouble`/`FromCDouble` do not.
**wx contract** (`interface/wx/translation.h:577-640`): `wxGetTranslation(string, domain = "",
context = "")` returns the original string when no catalog has it; a non-empty context needs a
matching `msgctxt` in the catalog; the plural overload returns `string` for `n == 1` and `plural`
otherwise when no catalog is found; "This function is thread-safe". Since 3.3 it returns
**`wxString` by value**, not a const reference: "please change the return type of the function to
wxString" (`docs/changes.txt:139-142`). Orca's `I18N::translate` overloads already return by value;
keep any new wrapper that way, a `const wxString&` return now dangles.
otherwise when no catalog is found; "This function is thread-safe". It returns **`const wxString&`**
(`interface/wx/translation.h:615`; `include/wx/translation.h:277-309`). 3.3.2 changed this to a
by-value `wxString` and **3.3.3 reverted it** to the 3.2.x reference, so the reference points at the
catalog's own string or at `wxTranslations::GetUntranslatedString()`, both of which outlive the call.
Forwarding that reference is therefore safe; what must return by value is a wrapper that constructs a
temporary — `wxString::FromUTF8`, `ToUTF8()`, `format_wxstr`. Orca's `I18N::translate` overloads
return `wxString` by value and `translate_utf8`/`_u8L` return `std::string`.
**OrcaSlicer macros** (`src/slic3r/GUI/I18N.hpp`):
@@ -429,7 +433,7 @@ using this class doesn't affect the native controls and dialogs", and on macOS 1
C locale can break the menus (`interface/wx/intl.h:282-292`): native file dialogs, the app menu and
standard buttons follow the system language. `wxLocale::IsAvailable` builds a region tag and asks
`wxUILocale(...).IsSupported()` (`src/common/intl.cpp` `wxLocale::IsAvailable`), which on Unix no
longer falls back to another region of the same language (`docs/changes.txt:75-79`); keep the
longer falls back to another region of the same language (`docs/changes.txt:81-85`); keep the
fallbacks in `load_language`.
- **Rule:** translate at use; never store a translated string in a namespace-scope or function-local
@@ -457,7 +461,7 @@ call `SetLabel(label)`) [source]. `SetLabelMarkup` also treats an
unescaped `&` as a mnemonic, needs `&amp;`/`&lt;` for literal characters, strips the markup where it
is unsupported, and leaves the label unchanged (returns false) when the string is not well-formed
(`interface/wx/control.h:200-360`); user text inside markup must be XML-escaped.
Since 3.3 wxListbook/wxChoicebook also interpret mnemonics in page titles (`docs/changes.txt:128-130`);
Since 3.3 wxListbook/wxChoicebook also interpret mnemonics in page titles (`docs/changes.txt:134-136`);
Orca uses neither, but the rule is the same for every book control.
**OrcaSlicer.** `Label` (`Widgets/Label.cpp`) derives from `wxStaticText`; `Label::SetLabel` stores
@@ -495,7 +499,7 @@ so build it from the wide form (`into_path(w).wstring()`) or stay with boost. At
expands `$VAR`/`%VAR%` inside file names (`interface/wx/filename.h:90-103`); use `MakeAbsolute()` or
explicit `wxPATH_NORM_DOTS | wxPATH_NORM_ABSOLUTE`. 3.3 adds `IsMSWExtendedLengthPath()` for `\\?\`
paths "avoiding the 260 character path length restriction" (`interface/wx/filename.h:1040-1051`,
`docs/changes.txt:551`). `wxFileSystem::FileNameToURL`/`URLToFileName` convert to and from `file:`
`docs/changes.txt:744`). `wxFileSystem::FileNameToURL`/`URLToFileName` convert to and from `file:`
URLs (`interface/wx/filesys.h:96-103, 175-180`).
**`wxStandardPaths`** (`interface/wx/stdpaths.h`): the directories "may or may not exist"
@@ -552,9 +556,9 @@ for (const wxString& p : paths)
| | Behaviour |
|---|---|
| MSW | New-style `IFileDialog`: wx calls `SetDefaultExtension` with the selected filter's first extension and no longer calls `AppendExtension` itself (`src/msw/filedlg.cpp:1644-1656, 1738-1740`; `docs/changes.txt:556`). `SetExtraControlCreator` → XP-style dialog. |
| MSW | New-style `IFileDialog`: wx calls `SetDefaultExtension` with the selected filter's first extension and no longer calls `AppendExtension` itself (`src/msw/filedlg.cpp:1644-1656, 1738-1740`; `docs/changes.txt:749`). `SetExtraControlCreator` → XP-style dialog. |
| macOS | Open dialogs show **no filter choice** and apply all wildcards at once unless `wxSystemOptions::SetOption(wxOSX_FILEDIALOG_ALWAYS_SHOW_TYPES, 1)`, and even then non-matching files are only greyed (`interface/wx/filedlg.h:117-128`). Matching compares the lower-cased last path extension (`wxOpenSavePanelDelegate panel:shouldEnableURL:`, `src/osx/cocoa/filedlg.mm:58-88`) [source]: case-insensitive, and a multi-dot pattern such as `*.gcode.3mf` or `*.zip.amf` never matches by itself. `wxFD_OVERWRITE_PROMPT` is always on (`:172-175`); `wxFD_OPEN` always behaves as `wxFD_FILE_MUST_EXIST` (`:184-189`). The save panel replaces the initial file name's extension with the first one in the wildcard (Orca's comment on `file_wildcards`). The native panel runs `runModal` and does not re-raise the parent dialog afterwards (`src/osx/cocoa/filedlg.mm` `ShowModal`); see `references/windows-dialogs.md` for the deferred re-raise. |
| GTK3 | `GtkFileChooserNative` (portal-capable, e.g. Flatpak) when GTK ≥ 3.20 at runtime and neither `wxFD_PREVIEW` nor an extra control/customize hook is used (`src/gtk/filedlg.cpp:265-274, 437-443`; `src/gtk/dirdlg.cpp:122-130`; `docs/changes.txt:393`). It runs through `gtk_native_dialog_run`: there is no wx window, so size/position calls do nothing (`wxFileDialog::DoSetSize` is empty). Patterns go to `gtk_file_filter_add_pattern` per token and are **case-sensitive** (`src/gtk/filectrl.cpp:169`). The chooser is transient for the parent left after `GetParentForModalDialog`, which replaces a null (or hidden, dying or `wxWS_EX_TRANSIENT`) parent with the active top-level window, else the app's main top window (`src/gtk/filedlg.cpp:206, 223-225`; `src/common/dlgcmn.cpp:180-203` `DoGetParentForDialog`) [source]. `wxFD_PREVIEW` is GTK-only (`interface/wx/filedlg.h:195-197`). |
| GTK3 | `GtkFileChooserNative` (portal-capable, e.g. Flatpak) when GTK ≥ 3.20 at runtime and neither `wxFD_PREVIEW` nor an extra control/customize hook is used (`src/gtk/filedlg.cpp:265-274, 437-443`; `src/gtk/dirdlg.cpp:122-130`; `docs/changes.txt:586`). It runs through `gtk_native_dialog_run`: there is no wx window, so size/position calls do nothing (`wxFileDialog::DoSetSize` is empty). Patterns go to `gtk_file_filter_add_pattern` per token and are **case-sensitive** (`src/gtk/filectrl.cpp:169`). The chooser is transient for the parent left after `GetParentForModalDialog`, which replaces a null (or hidden, dying or `wxWS_EX_TRANSIENT`) parent with the active top-level window, else the app's main top window (`src/gtk/filedlg.cpp:206, 223-225`; `src/common/dlgcmn.cpp:180-203` `DoGetParentForDialog`) [source]. `wxFD_PREVIEW` is GTK-only (`interface/wx/filedlg.h:195-197`). |
| GTK2 (opt-out build) | never uses the native chooser; patterns case-sensitive. |
**OrcaSlicer:**
@@ -650,7 +654,7 @@ dtor, and `!lock` tests `IsOpened()`.
| | Behaviour |
|---|---|
| MSW | `SetData`/`GetData` do not check `Open()` (`src/msw/clipbrd.cpp` `wxClipboard::SetData`), so code that forgets it works here only. `wxTextDataObject::SetData()` size must now include the 2-byte NUL (`docs/changes.txt:62-66`); use `SetText()` or the ctor. |
| MSW | `SetData`/`GetData` do not check `Open()` (`src/msw/clipbrd.cpp` `wxClipboard::SetData`), so code that forgets it works here only. `wxTextDataObject::SetData()` size must now include the 2-byte NUL (`docs/changes.txt:68-72`); use `SetText()` or the ctor. |
| macOS | without `Open()`, `SetData`/`AddData`/`GetData` return false through `wxCHECK_MSG(m_open, …)` without taking the object (`src/osx/carbon/clipbrd.cpp:84-105, 140-147`); a successful write is flushed to the pasteboard immediately (`:110-114`). |
| GTK | same `wxCHECK_MSG(m_open, …)` early return (`src/gtk/clipbrd.cpp:643-647, 775`). `GetData`/`IsSupported` are asynchronous underneath: `wxClipboardSync` spins `YieldFor(wxEVT_CATEGORY_CLIPBOARD)` until GTK answers and forbids re-entrancy (`src/gtk/clipbrd.cpp:68-92`). PRIMARY selection exists. Under Wayland, Wayland MIME types are advertised next to the X11 atoms (`src/gtk/clipbrd.cpp:655-700`). |
@@ -793,8 +797,8 @@ from a non-main thread on wxOSX (`:39-40`); `wxCANCEL_DEFAULT` is ignored on wxO
`wxID_OK/wxID_CANCEL/wxID_YES/wxID_NO/wxID_HELP` (`:269-275`), but **`wxMessageBox()` returns
`wxYES/wxNO/wxCANCEL/wxOK/wxHELP`** (`:308-312`). `wxRichMessageDialog` is native only on MSW and
generic elsewhere (`interface/wx/richmsgdlg.h:18-23`). On MSW every `TaskDialog`-based dialog
(`wxMessageBox`, `wxMessageDialog`, `wxRichMessageDialog`, `wxProgressDialog`) ignores dark mode
(`interface/wx/app.h:1436-1440`).
(`wxMessageBox`, `wxMessageDialog`, `wxRichMessageDialog`, `wxProgressDialog`) has followed dark mode
since 3.3.3 (`interface/wx/app.h:1442-1448`).
**OrcaSlicer.** UI code uses the MsgDialog family (`MessageDialog`, `RichMessageDialog`,
`WarningDialog`, `ErrorDialog`, `InfoDialog`; `references/windows-dialogs.md` §MsgDialog family) for
@@ -848,7 +852,7 @@ Orca does not use `wxConfig`/`wxFileConfig` (so the 3.3 change of the Unix defau
**Contract** (`interface/wx/secretstore.h`): on Unix it needs libsecret and a running secret service, so
always check `IsOk(&errmsg)` (`:193-210, 257`); libsecret is no longer required at run time
(`docs/changes.txt:533`), so a missing library or service shows up only as `IsOk()` false.
(`docs/changes.txt:726`), so a missing library or service shows up only as `IsOk()` false.
`GetDefault()` "may show a dialog to the user under some platforms, so it can take an arbitrarily long
time to return" (`:243-246`). One username per service (`:260-268`).
@@ -7,7 +7,7 @@ yields and nested event loops, progress dialogs, startup, shutdown and exception
screen, and the access rules for `wxGetApp()` and `app_config`. Read it whenever code runs off the
main thread, defers work, starts a timer, yields, shows progress, or runs during startup or shutdown.
wx cites are relative to the pinned wx 3.3.2 tree (located as in `SKILL.md` §Ground truth).
wx cites are relative to the pinned wx 3.3.4 tree (located as in `SKILL.md` §Ground truth).
Orca builds wx with `wxBUILD_DEBUG_LEVEL=0` and `libslic3r_gui` with `wxDEBUG_LEVEL=0`, so `wxASSERT`
is compiled out and `wxCHECK*` returns silently: a timer started off the main thread never fires on macOS,
`Exit()` on a loop that is not the active one is ignored, `Start(0)` on macOS fails — all without
@@ -454,12 +454,12 @@ and aborts the whole app. Wrap the entire body; log and convert to a status the
`wxMessageQueue::Post(T&&)` accepts move-only types (`interface/wx/msgqueue.h:90-97`).
- `wxTHREAD_WAIT_DEFAULT` is now `wxTHREAD_WAIT_BLOCK`; `wxTHREAD_WAIT_YIELD` "is dangerous" and "is
only implemented in wxMSW and simply ignored under the other platforms" (`interface/wx/thread.h:648-675`),
so the 3.3 default change (`docs/changes.txt:39-41`) only affects MSW code calling
so the 3.3 default change (`docs/changes.txt:45-47`) only affects MSW code calling
`Wait()`/`Delete()` from the GUI thread.
- `wxCRITICAL_SECTION(name)` declares a function-static critical section, which is initialised
safely only if the function is first called from the main thread before other threads call it;
otherwise make it a global (`interface/wx/thread.h:1762-1779`). Prefer `std::mutex`.
- `wxSecretStore` documents no thread-affinity notes in 3.3.2 (`interface/wx/secretstore.h`).
- `wxSecretStore` documents no thread-affinity notes in 3.3.4 (`interface/wx/secretstore.h`).
## The Jobs framework
@@ -829,8 +829,8 @@ nothing) (`interface/wx/utils.h:1203-1212, 1243-1248`; see `references/strings-i
**Platforms.** On MSW `wxProgressDialog` is native: a TaskDialog run by a
`wxProgressDialogTaskRunner` **wxThread**, yielding `ALL & ~USER_INPUT` (TIMER and THREAD included)
(`src/msw/progdlg.cpp` `DispatchEvents`) **[source]**; anything based on `TaskDialog()` has no dark
mode (`interface/wx/app.h:1436-1440`). On macOS and GTK it is `wxGenericProgressDialog`, which creates
(`src/msw/progdlg.cpp` `DispatchEvents`) **[source]**; the native `TaskDialog` follows wx dark mode
since 3.3.3 (`interface/wx/app.h:1442-1448`). On macOS and GTK it is `wxGenericProgressDialog`, which creates
a temporary event loop if none is active (`src/generic/progdlgg.cpp:360-366`). On MSW (native
dialog or Orca's generic one) and macOS, `CallAfter` bodies run inside `Update()`/`Pulse()`; on GTK
the masked yield runs no queued event, so they wait until control returns to an event loop
@@ -839,7 +839,7 @@ the masked yield runs no queued event, so they wait until control returns to an
**OrcaSlicer.**
- `Slic3r::GUI::ProgressDialog` (`Widgets/ProgressDialog.hpp`) is a themed fork of
`wxGenericProgressDialog`: same `YieldFor(UI | USER_INPUT)` semantics, temporary event loop when
none is active, generic on every platform (so no TaskDialog dark-mode gap).
none is active, generic on every platform (so it is themed by Orca, not the native TaskDialog).
- `detail::run_off_thread_with_progress` (`PluginsDialog.hpp`, wrapped by
`PluginsDialog::run_with_dialog`/`run_with_dialog_wait`): a heap wx `wxProgressDialog` (the native
TaskDialog on MSW, the generic dialog on macOS and GTK) pulsed by a
@@ -1,7 +1,7 @@
# WebView, OpenGL canvas, AUI docking and media
How wxWebView, wxGLCanvas/wxGLContext, wxAuiManager/wxAuiToolBar and the camera view behave in the
wx 3.3.2 tree Orca builds, and the Orca wrappers built on them: `WebView`, `WebViewHostDialog` and
wx 3.3.4 tree Orca builds, and the Orca wrappers built on them: `WebView`, `WebViewHostDialog` and
the web dialogs/panels, `OpenGLManager`, `GLCanvas3D` and its ImGui layer, the Plater docking
manager, `BBLTopbar`, `wxMediaCtrl3`. Read it before touching any browser view, GL canvas, docked
pane, the top bar or the camera view, or when debugging a blank web page, a black or garbled 3D
@@ -242,7 +242,7 @@ a single message handler and the Chromium and IE backends do not support script
| `Add…` return | `false` if *any* handler already exists, whatever its name (`src/msw/webview_edge.cpp:1635-1645`) | always `true` (`src/osx/webview_webkit.mm:620-629`); a duplicate name makes WebKit raise an NSException that cannot be caught and kills the app ([external]: Apple behaviour, recorded in the `WebView::CreateWebView` comment) | `false` on a duplicate or with no view, **but** `g_signal_connect` runs before registration, so a duplicate add leaves a second connection (`src/gtk/webview_webkit2.cpp:1638-1657`) |
| `Remove…` | clears the single name (`1647-1652`) | removes the native handler (`632-636`) | unregisters but **does not disconnect** the signal (`1659-1664`): Remove + Add delivers every later message **twice** |
| `window.<name>` alias | user script `window.<name> = window.chrome.webview` + `ExecuteScript` (`1030-1046`) | `AddUserScript` + **synchronous** `RunScript` (`626-628`) | `AddUserScript` + **synchronous** `RunScript` (`1650-1654`) |
| Delivery | queued with `AddPendingEvent` (`785-813`); [documented] "Queue Edge wxWebView events" (`docs/changes.txt:525`) | `ProcessWindowEvent` **inside** the `WKScriptMessageHandler` delegate (`1324-1361`) | `HandleWindowEvent` **inside** the GTK signal (`397-409`) |
| Delivery | queued with `AddPendingEvent` (`785-813`); [documented] "Queue Edge wxWebView events" (`docs/changes.txt:718`) | `ProcessWindowEvent` **inside** the `WKScriptMessageHandler` delegate (`1324-1361`) | `HandleWindowEvent` **inside** the GTK signal (`397-409`) |
| Event fields | handler name and event object set | name set, **no event object** | **empty `GetMessageHandler()`, no event object** |
| Body | `get_WebMessageAsJson`, JSON-decoded when it is a string (`798-809`) | NSString as is; dict/array → JSON; NSNumber → `stringValue` (booleans arrive as `1`/`0`); NSNull → `"null"` | object → JSON string, otherwise `ToString` (`64-94`) |
@@ -710,8 +710,8 @@ the environment variable `wx_opengl_egl=0` does the same; it exists only when `w
returns true only when EGL is in use (false when `wxHAS_EGL` is undefined); `GetGLXVersion()` returns 0
when GLX is not in use; both exist only when `wxHAS_GLX` is defined (`interface/wx/glcanvas.h:999-1030`).
`wxGLCanvas::CreateSurface()` was removed "as exposing it doesn't make sense when wxGLCanvas may use
either EGL or GLX" (`docs/changes.txt:250-252`); "Allow using GLX and EGL in same program (#26023)" is
an upstream 3.3.2 wxGTK change (`docs/changes.txt:290-292`).
either EGL or GLX" (`docs/changes.txt:282-284`); "Allow using GLX and EGL in same program (#26023)" is
an upstream 3.3.2 wxGTK change (`docs/changes.txt:485`).
**Source** [source]:
- When both backends are built, **EGL is the default even on X11** unless `PreferGLX()` or the
@@ -727,7 +727,7 @@ an upstream 3.3.2 wxGTK change (`docs/changes.txt:290-292`).
(`src/unix/glegl.cpp:801-860`).
- The EGL-based `wxGLCanvas` for Wayland dates from 3.1.5 (`docs/changes_32.txt:497`), so it predates
Orca's upgrade; its
high-DPI scale under EGL/Wayland was fixed in 3.3.0 (`docs/changes.txt:538`).
high-DPI scale under EGL/Wayland was fixed in 3.3.0 (`docs/changes.txt:731`).
**OrcaSlicer.**
- `deps/wxWidgets/wxWidgets.cmake` passes `-DwxUSE_GLCANVAS_EGL=ON` (also in the Flatpak manifest).
@@ -956,13 +956,13 @@ widgets beside the canvas (a sibling, not a child over it), or in a popup.
`src/aui/framemanager.cpp:910-920`; `OnPaneButton`); `DestroyOnClose` panes are detached and `Destroy()`ed
(`ClosePane`, `1431-1467`). With several handlers bound, each must `Skip()`.
- **3.3 changes.** `wxAUI_MGR_HINT_FADE` left the default flags (`interface/wx/aui/framemanager.h:43-50`;
`docs/changes.txt:132`); `wxAUI_MGR_LIVE_RESIZE` joined them (`interface/wx/aui/framemanager.h:59-62`); `AlwaysUsesLiveResize()` "always
`docs/changes.txt:138`); `wxAUI_MGR_LIVE_RESIZE` joined them (`interface/wx/aui/framemanager.h:59-62`); `AlwaysUsesLiveResize()` "always
returns false" as of 3.3.0 (`interface/wx/aui/framemanager.h:345`; `src/aui/framemanager.cpp:711-714`), non-live resize being
restored (`docs/changes.txt:490`) — the older "live resize is always used on wxOSX and GTK3/Wayland"
restored (`docs/changes.txt:683`) — the older "live resize is always used on wxOSX and GTK3/Wayland"
statement (`interface/wx/aui/framemanager.h:336-340`) is obsolete. Docking hints for floating frames use
`::wxGetMousePosition()` (`src/aui/framemanager.cpp:4139-4148`, `4242-4251`), which Wayland cannot supply.
- `wxAuiNotebook` (not used by Orca): page indices are logical; use `GetPagePosition()` for on-screen
order (`docs/changes.txt:121-126`); the default tab art changed (`35-37`).
order (`docs/changes.txt:128-132`); the default tab art changed (`41-43`).
**OrcaSlicer.** Orca's docks use `AuiMgr` (`AuiMgr.hpp`), a `wxAuiManager` whose `CreateFloatingFrame`
returns a `wxAuiFloatingFrame` that applies `UpdateFrameDarkUI`. `init(window)` calls
@@ -1034,8 +1034,10 @@ Plater (Prepare and Preview); `DesignPanel::m_aui` manages the Design tab's body
```
- **Rule:** Disable floating on Wayland (clear `wxAUI_MGR_ALLOW_FLOATING`, `Floatable(false)`, strip
floating state from saved layouts).
**Why:** AUI's floating and docking-hint logic needs global pointer positions and top-level window
moves (`wxGetMousePosition`, `wxEVT_MOVE`), which Wayland does not provide.
**Why:** AUI's docking-hint logic needs global pointer positions and top-level window moves
(`wxGetMousePosition`, `wxEVT_MOVE`), which Wayland does not provide. 3.3.4 added system drag sessions
so wx can move a pane's floating frame there anyway (`src/aui/framemanager.cpp` `StartDragSession`),
but the drop/docking hints still cannot work, so Orca keeps floating disabled.
## wxAuiToolBar and BBLTopbar
@@ -6,7 +6,7 @@ port; what Orca's `DPIDialog`/`DPIFrame` add; the Orca dialog recipe; and the `M
Read it before writing or reviewing any dialog, frame, close handler, `Destroy()`/`delete`, or code
that keeps a pointer to a window across an event, a `CallAfter` or a modal loop.
wx cites are relative to the pinned wx 3.3.2 tree (located as in `SKILL.md` §Ground truth). wx is
wx cites are relative to the pinned wx 3.3.4 tree (located as in `SKILL.md` §Ground truth). wx is
built with `wxBUILD_DEBUG_LEVEL=0` and `libslic3r_gui` with `wxDEBUG_LEVEL=0`: every wx assert
quoted below is compiled out, so misuse fails silently (dropped call, stuck loop, freed memory), never with an assert dialog. "GTK" below
means wxGTK as Orca builds it on Linux: GTK3 by default (X11 or Wayland); GTK2 is only an opt-out.
@@ -424,7 +424,7 @@ mapped (`src/gtk/toplevel.cpp:1141-1243`).
**Raise.** "only requests the window manager to raise this window… If the window is currently hidden,
this function does *not* show it", top-level windows only (`interface/wx/window.h:3013-3033`); true on all ports
since 3.3 (`docs/changes.txt:144-146`). **[source]** MSW = `::SetForegroundWindow`, subject to the
since 3.3 (`docs/changes.txt:145-147`). **[source]** MSW = `::SetForegroundWindow`, subject to the
foreground lock — Windows may only flash the taskbar button (`src/msw/toplevel.cpp:650-655`); GTK =
`gtk_window_present` only if shown (`src/gtk/toplevel.cpp:1301-1310`; during a deferred X11 first show it
already counts as shown); macOS = `makeKeyAndOrderFront` only if shown (`src/osx/nonownedwnd_osx.cpp:289-295`, `src/osx/cocoa/nonownedwnd.mm:897-899`),
@@ -910,8 +910,9 @@ buttons are chosen — in numeric id order, so `{"Save", "OK"}` makes Save prima
**Rule.** Never `wxMessageBox`/`wxMessageDialog`/`wxRichMessageDialog` once the GUI exists; use the themed
replacements in `src/slic3r/GUI/MsgDialog.hpp` (`Slic3r::GUI`), rooted in `MsgDialog : DPIDialog` (logo on the
left, content on the right, `Button` row underneath, dark-mode and DPI aware). Why: on MSW the TaskDialog-based
native boxes (`wxMessageBox`, `wxMessageDialog`, `wxRichMessageDialog`, `wxProgressDialog`) ignore dark mode
(`interface/wx/app.h:1436-1446`), and native boxes cannot match Orca's look. `wxMessageBox` remains only for
native boxes (`wxMessageBox`, `wxMessageDialog`, `wxRichMessageDialog`, `wxProgressDialog`) now follow dark mode
since 3.3.3, so only the date/time/calendar controls, `wxPrintDialog` on Windows 10 and MDI applications stay
light (`interface/wx/app.h:1442-1448`); native boxes cannot match Orca's look. `wxMessageBox` remains only for
failures before the GUI exists (e.g. `GUI_App::load_language`). Native message-box style limits:
`references/strings-i18n-files.md`. Return-value trap when reading old code: `wxMessageBox()` returns
`wxYES/wxNO/wxCANCEL/wxOK/wxHELP`, while `ShowModal()` returns `wxID_YES/…` (`interface/wx/msgdlg.h:269-276` vs `309-311`).
@@ -1,17 +1,19 @@
# wx 3.1.5 → 3.3.2: changes Orca code can trip on
# wx 3.1.5 → 3.3.4: changes Orca code can trip on
OrcaSlicer moved from wxWidgets **3.1.5** to **3.3.2** (8248b06337, #12941). This file digests the
OrcaSlicer moved from wxWidgets **3.1.5** to **3.3.2** (8248b06337, #12941) and the pin is now upstream
**3.3.4** (`deps/wxWidgets/wxWidgets.cmake`, tag `v3.3.4`). This file digests the
changes between those versions that matter to GUI code — the 3.1.6–3.2.0 incompatible changes, the
3.3 incompatible changes, and the notable 3.3.0/3.3.1/3.3.2 changes — each with what it means for
Orca, plus the migration already done in Orca as rules for new code. Read it when code written
from older wx knowledge behaves oddly, when a wx doc says "since 3.3", or when reviewing code near
the workarounds in §8. Where this file and prior wx knowledge disagree, this file wins.
3.3 incompatible changes, and the notable 3.3.0/3.3.1/3.3.2/3.3.3/3.3.4 changes — each with what it
means for Orca, plus the migration already done in Orca as rules for new code. Read it when code
written from older wx knowledge behaves oddly, when a wx doc says "since 3.3", or when reviewing code
near the workarounds in §10. Where this file and prior wx knowledge disagree, this file wins.
Contents: [Rules](#rules) · [1 Reading the change logs](#1-reading-the-change-logs) ·
[2 3.1.6 → 3.2.0](#2-changes-from-316-to-320) · [3 3.3 behaviour changes](#3-33-behaviour-changes-that-compile) ·
[4 3.3 build changes](#4-33-changes-that-break-the-build) · [5 3.3.0](#5-330-notable-changes) ·
[6 3.3.1](#6-331-notable-fixes) · [7 3.3.2](#7-332-notable-changes) ·
[8 Migration done in Orca](#8-migration-already-done-in-orca)
[8 3.3.3](#8-333-notable-changes) · [9 3.3.4](#9-334-notable-changes) ·
[10 Migration done in Orca](#10-migration-already-done-in-orca)
All `docs/`, `interface/`, `include/`, `src/`, `build/` cites are relative to the pinned wx tree
(located as in `SKILL.md` §Ground truth), except paths
@@ -21,8 +23,8 @@ explicitly called Orca's (`deps/…`, Orca's `src/CMakeLists.txt`) and bare Orca
1. Read every "now asserts" in the change logs as "now silently returns or does nothing" in Orca:
wx asserts are compiled out. Validate arguments yourself. → [§1](#1-reading-the-change-logs)
2. Target 3.3.2 only. Do not add `wxCHECK_VERSION` / `wxVERSION_NUMBER` /
`wxVERSION_EQUAL_OR_GREATER_THAN` branches for older wx. → [§8](#8-migration-already-done-in-orca)
2. Target 3.3.4 only. Do not add `wxCHECK_VERSION` / `wxVERSION_NUMBER` /
`wxVERSION_EQUAL_OR_GREATER_THAN` branches for older wx. → [§10](#10-migration-already-done-in-orca)
3. Mark every override of a wx virtual `override`. 3.2/3.3 changed parameter types (`wxBitmapBundle`,
`wxReadOnlyDC`, `wxWindowBase*`, `wxVersionContext`); a stale signature must fail to compile, not
become a silent overload. → [§4](#4-33-changes-that-break-the-build)
@@ -39,33 +41,36 @@ explicitly called Orca's (`deps/…`, Orca's `src/CMakeLists.txt`) and bare Orca
`WX_GL_SAMPLES`); `wxGLAttributes::Defaults()` no longer includes it. → [§3](#3-33-behaviour-changes-that-compile)
9. Size a `wxImageList` in physical pixels (the bitmaps' `GetSize()`), or use the `wxBitmapBundle`
image APIs. → [§3](#3-33-behaviour-changes-that-compile)
10. Return translated strings as `wxString` by value. → [§3](#3-33-behaviour-changes-that-compile)
10. `wxGetTranslation()` returns `const wxString&` (3.3.2 made it by-value; 3.3.3 reverted that); a
wrapper that converts a translation returns by value. → [§3](#3-33-behaviour-changes-that-compile)
11. Escape `&` in user text used as a control label or book page title. → [§3](#3-33-behaviour-changes-that-compile)
12. `wxDynamicCast` only on a pointer whose static type derives from `wxObject`; use `dynamic_cast`
for mixin interfaces (`wxComboPopup`, `wxItemContainer`, `wxTextEntry`). → [§4](#4-33-changes-that-break-the-build)
13. Build a `wxArrayString` with `Add()` or an initializer list, walk wx lists with
`compatibility_iterator` or range-for, and make a `wxString` the first operand of a mixed
`+` chain. → [§4](#4-33-changes-that-break-the-build)
14. MSW windows are not double-buffered by default in 3.3.2; a custom-painted control buffers itself.
14. MSW windows are not double-buffered by default in 3.3.4; a custom-painted control buffers itself.
→ [§5](#5-330-notable-changes)
15. `wxEVT_DPI_CHANGED` now fires on GTK3 too; such handlers must be correct on GTK, and every handler
you bind calls `Skip()` (`DPIAware`'s own is the one exception). → [§5](#5-330-notable-changes)
16. wx MSW dark mode cannot be switched at runtime; do not replace Orca's NppDarkMode with it without
keeping live theme switching. → [§5](#5-330-notable-changes)
16. wx MSW dark mode became runtime-switchable in 3.3.3/3.3.4 (`SetAppearance`/`MSWEnableDarkMode`
restyle existing windows); Orca still keeps NppDarkMode for its own chrome.
→ [§5](#5-330-notable-changes), [§9](#9-334-notable-changes)
17. After `wxStaticText::SetLabel`, re-wrap with `Wrap(-1); Wrap(w);` (or use `Label`); `wxST_WRAP`
is opt-in and works only inside a sizer. → [§7](#7-332-notable-changes)
18. On Linux/X11 call `wxGLCanvas::PreferGLX()` before any GL use; on Unix set a swap interval explicitly
if frame pacing matters (wx forces 0). → [§7](#7-332-notable-changes)
19. Compare `wxGrid::GetSelectedBlocks()` `begin()` with `end()` before dereferencing.
→ [§8](#8-migration-already-done-in-orca)
→ [§10](#10-migration-already-done-in-orca)
20. Keep the post-upgrade workarounds (MainFrame `WS_CAPTION` masking, macOS deep-link handler,
`MSWEnableDarkMode` ordering, `SidePopup` anchoring, MSW `GLCanvas3D::on_paint` render) when
touching that code. → [§8](#8-migration-already-done-in-orca)
touching that code. → [§10](#10-migration-already-done-in-orca)
## 1 Reading the change logs
**Sources.** `docs/changes.txt` lists changes since 3.2: incompatible behaviour changes :11-146,
build-breaking changes :149-252, 3.3.2 :255-330, 3.3.1 :333-377, 3.3.0 :380-622.
**Sources.** `docs/changes.txt` lists changes since 3.2: incompatible behaviour changes :11-178,
build-breaking changes :181-292, 3.3.4 :295-385, 3.3.3 :388-445, 3.3.2 :448-523, 3.3.1 :526-570,
3.3.0 :573-815.
`docs/changes_32.txt` covers 3.x → 3.2.0: the cumulative "INCOMPATIBLE CHANGES SINCE 3.0.x" list
:9-233, 3.2.0 :235-276, 3.1.7 :279-340, 3.1.6 :343-444. wxQt, wxiOS, wxUniv, wxMotif and wxGTK1
items are left out here.
@@ -73,11 +78,14 @@ items are left out here.
**Coverage gaps.**
- The "since 3.0.x" list in `changes_32.txt` is cumulative over all of 3.1.x. Most of it was already
in force in 3.1.5; §2.1 lists only what was added after 3.1.5, §2.3 the older items worth knowing.
- `changes_32.txt` in this tree stops at 3.2.0. The 3.2.1–3.2.10 maintenance fixes are not listed
- `changes_32.txt` in this tree stops at 3.2.0. The 3.2.1–3.2.12 maintenance fixes are not listed
anywhere in the tree.
- 3.3.0's list is relative to 3.2.8 (`changes.txt:383`). 3.3.2's list is relative to 3.2.10, and the
- 3.3.0's list is relative to 3.2.8 (`changes.txt:576`). 3.3.2's list is relative to 3.2.10, and the
full set of changes since 3.3.1 *also includes* the 3.2.9 and 3.2.10 fixes, which are **not**
listed in this file (`changes.txt:258-259`).
listed in this file (`changes.txt:451-452`). 3.3.3's list is relative to 3.2.11, and its changes
since 3.3.2 *also include* the 3.2.10–3.2.11 fixes (`changes.txt:391-392`); 3.3.4's list is
relative to 3.2.11 too, and its changes since 3.3.3 *also include* the 3.2.11–3.2.12 fixes
(`changes.txt:298-299`).
**"Asserts" mean silence in Orca.** wx is built with `-DwxBUILD_DEBUG_LEVEL=0`
(`deps/wxWidgets/wxWidgets.cmake` → `build/cmake/init.cmake:245-246`), and `libslic3r_gui` gets
@@ -117,7 +125,7 @@ the 3.1.7-specific section.
| `wxSpinCtrl::SetValue(wxString)` sends no events on MSW (as documented) | :129-130 | do not rely on setter events (`controls-dataview.md`) |
| `wxButton::GetBitmap{Current,Disabled,Focus,Pressed}()` return a valid bitmap on MSW only if set | :132-134 | check `IsOk()` |
| `wxFileName::GetVolume()` returns `\\share` for UNC paths and `\\?\Volume{GUID}` for GUID paths | :136-140 | path code comparing volumes |
| `wxBitmapComboBoxBase::SetItemBitmap()` takes `wxBitmapBundle` | :152-153 | the bundle change also retyped `OnAddBitmap` (5f365b5c6b, §8) |
| `wxBitmapComboBoxBase::SetItemBitmap()` takes `wxBitmapBundle` | :152-153 | the bundle change also retyped `OnAddBitmap` (5f365b5c6b, §10) |
| MSW also links `oleacc` (3.1.5 already needed `shlwapi`, `uxtheme`, `version`) | :158-163 | automatic with MSVC |
| Xcode projects drop i386, add arm64 | :205-207 | none (CMake build) |
| `wxImage` ctor from XPM data is `explicit` | :225-226 | write `wxImage(xpm)` |
@@ -136,7 +144,7 @@ the 3.1.7-specific section.
| MSW: TLW resizing on DPI change improved and overridable — a handler that sizes the TLW itself does not `Skip()` (3.2.0) | `changes_32.txt:269`; `interface/wx/event.h` (`wxDPIChangedEvent`) | Orca's `DPIAware` handler does not `Skip()` (`dpi-bitmaps-fonts.md`) |
| `wxUILocale` (3.1.6); `wxLocale::IsAvailable` is now implemented through it | `changes_32.txt:348`; `src/common/intl.cpp:740-781` | see `wxUILocale::IsSupported` in §3 |
| `wxWebView::RunScriptAsync()` (3.1.6) | `changes_32.txt:382` | `webview-gl-aui-media.md` |
| wxOSX: "Allow user input in `wxPopupTransientWindow`" (3.1.7) — the popup's child holds mouse capture while the cursor is outside it and releases it inside, toggled from `OnIdle` [source] | `changes_32.txt:334`; `src/common/popupcmn.cpp:438-471` | how wxOSX transient popups track the mouse; the macOS gap-dismissal worked around in `SidePopup::Popup` is not traced to a specific wx change (§8; `popups-menus.md`) |
| wxOSX: "Allow user input in `wxPopupTransientWindow`" (3.1.7) — the popup's child holds mouse capture while the cursor is outside it and releases it inside, toggled from `OnIdle` [source] | `changes_32.txt:334`; `src/common/popupcmn.cpp:438-471` | how wxOSX transient popups track the mouse; the macOS gap-dismissal worked around in `SidePopup::Popup` is not traced to a specific wx change (§10; `popups-menus.md`) |
| wxGTK: Wayland fixes (3.1.6), no GDK errors from `PopupMenu()` on Wayland (3.1.7), `wxCURSOR_SIZING` on Wayland (3.2.0) | `changes_32.txt:397, 317, 261` | `platforms.md` |
| MSW: all native modal dialogs are app-modal (3.1.6) | `changes_32.txt:409` | `windows-dialogs.md` |
| Also new: `wxKeyEvent::IsAutoRepeat()`, `wxSpinCtrl::GetTextValue()/SetIncrement()`, `wxTopLevelWindow::SetContentProtection()`, `wxEVT_SPLITTER_SASH_POS_RESIZE`, OSX full-screen view options, OSX `wxEVT_CHAR` from `wxDataViewCtrl` | `changes_32.txt:364-436` | available API |
@@ -167,7 +175,7 @@ These were already in force in 3.1.5, so the upgrade did not change them, but co
## 3 3.3 behaviour changes that compile
`changes.txt` "Changes in behaviour not resulting in compilation errors" (:11-146). Each row: the
`changes.txt` "Changes in behaviour not resulting in compilation errors" (:11-178). Each row: the
change, its line, and what it means for Orca.
| Change | Line | Orca relevance |
@@ -177,31 +185,30 @@ change, its line, and what it means for Orca.
| `wxGLCanvas` no longer multisamples by default | :21-23 | pitfall below |
| `wxFileConfig` on Unix defaults to XDG `~/.config/appname.conf` (old file still used; `wxCONFIG_USE_XDG`/`wxCONFIG_USE_HOME`, `MigrateLocalFile()`) | :25-29 | none: Orca uses `AppConfig`, no `wxConfig` (`strings-i18n-files.md`) |
| `wxColourDatabase` uses CSS values; `UseScheme()` restores the old ones (`interface/wx/gdicmn.h:845, 973-996`) | :31-33 | none: stock colours (`*wxGREEN`, …) are fixed RGB (`src/common/gdicmn.cpp:789-830`) and Orca builds colours from hex/RGB. Do not build colours from names |
| `wxAuiNotebook` default art is the new flat art; `wxAuiNativeTabArt` (or `"native"` in XRC) keeps the old look | :35-37 | none: no `wxAuiNotebook` in Orca |
| `wxTHREAD_WAIT_DEFAULT` is `wxTHREAD_WAIT_BLOCK`: `wxThread::Delete()/Wait()` no longer pump events | :39-41 | Orca uses std/boost threads (`wxThread` only for `IsMain()`); a new `wxThread` whose exit needs the main loop would deadlock |
| `wxDocument::OnCloseDocument()` runs once, after the views are destroyed (it used to run twice when the document was closed from the menu); an override must not rely on any view existing | :43-46 | none: no doc/view |
| `wxGrid::FreezeTo()` asserts on out-of-range counts, and freezes even when the grid is too small | :48-51 | silent `false` in Orca; it also refuses reordered or drag-movable rows/columns [source] (`src/generic/grid.cpp:5742-5750`). Clamp arguments (`controls-dataview.md`) |
| Invalid `wxImageList` calls assert | :53-56 | silent in Orca; `wxCHECK` returns (generic `Add` → -1). Create the list with a valid size before use |
| `wxTRANSPARENT_WINDOW` does nothing (`#define wxTRANSPARENT_WINDOW 0`, `include/wx/defs.h:1449`); MSW code that needs it can set `WS_EX_TRANSPARENT` | :58-60 | pitfall below; 026b105dcb |
| MSW `wxTextDataObject::SetData()` size includes the 2-byte NUL (consistent with `GetDataSize()`); an old-style size chops the last character | :62-66 | none: Orca uses the `wxTextDataObject(text)` ctor; use `SetText()`, never `SetData()` |
| `wxListCtrl::EditLabel()` asserts without `wxLC_EDIT_LABELS` | :68-69 | silent no-op in Orca; add the style where editing is intended |
| MSW `wxSystemAppearance::IsDark()` reports the app's own mode; `AreAppsDark()`/`IsSystemDark()` report the OS | :71-73 | pitfall below |
| Unix `wxUILocale::IsSupported()` no longer falls back to another region of the same language; pass just `"fr"` to accept any `fr_XX` | :75-79 | `wxLocale::IsAvailable` builds the region-qualified tag (`GetCanonicalWithRegion()`) and calls `IsSupported()` [source] (`src/common/intl.cpp:740-781`); `GUI_App::load_language` relies on it, so on Linux a language whose canonical locale (e.g. `fr_FR`) is not installed reports unavailable. Keep the fallbacks in `load_language` |
| Deprecated `wxPGCellRenderer::DrawCaptionSelectionRect()` overload not called; override the overload taking `wxWindow*`, or enable 3.0 compatibility | :81-83 | none: no propgrid in Orca's own code |
| `wxImageList` size is in physical pixels | :85-88 | pitfall below |
| Mac `wxWebRequest` no longer uses persistent storage. The entry names `wxWebRequest::EnablePersistentStorage()`; the real API is `wxWebSession::EnablePersistentStorage(bool)` (`interface/wx/webrequest.h:1612-1629`; also `wxWebSessionSync`, :1835), macOS-only, before the first request | :90-92 | none: Orca uses `wxWebRequest` only for image downloads (`wxWebSession::GetDefault().CreateRequest` in `StatusPanel`, `SliceInfoPanel`, `ReleaseNote`, `DeviceErrorDialog`); network and login agents use libcurl (`Slic3r::Http`) |
| MSW `wxBitmap::Create(size, dc)` no longer multiplies by the DC's content scale; the size is physical | :94-96 | remove compensating scaling; use `CreateWithLogicalSize` for logical sizes |
| `wxIMAGE_QUALITY_NEAREST` has a new value and is no longer `wxIMAGE_QUALITY_NORMAL`; `NORMAL` (the `Scale`/`Rescale` default) is bilinear + box average (`interface/wx/image.h:31-67`) | :98-99 | never store or compare the enum numerically; default-quality thumbnails and icons look smoother than under 3.1.5; pass `wxIMAGE_QUALITY_NEAREST` for pixel-exact scaling |
| `wxTextCtrl::{Save,Load}File()` treat `.rtf` as RTF | :101-104 | pass `wxTEXT_TYPE_PLAIN` to keep plain text |
| `wxClientDC`/`wxPaintDC` offset their origin by a `wxFrame` toolbar on every port | :106-109 | none: Orca frames have no native toolbar (`BBLTopbar` is a child `wxAuiToolBar`) |
| `wxTextCtrl::SetLabel()` does nothing and asserts on every port (MSW used to act as `SetValue`) | :111-113 | pitfall below |
| `wxAuiGenericTabArt` subclasses (also via `wxAuiMSWTabArt`) override `DrawPageTab()`/`GetPageTabSize()` instead of `DrawTab()`/`GetTabSize()`; direct `wxAuiTabArt` subclasses still work | :115-120 | none: no Orca tab art |
| `wxAuiNotebook` page index is logical (reorder-independent); `GetPagePosition()` gives the screen position | :122-126 | none; index math under `wxAUI_NB_TAB_MOVE` is what breaks |
| `wxListbook`/`wxChoicebook` interpret mnemonics in page titles "just as the other wx*book classes already did" | :128-130 | Orca uses neither (`BedShapeDialog` uses `wxSimplebook` + a combo); every book interprets `&` (`interface/wx/bookctrl.h:141-147`) → rule 11 |
| `wxAUI_MGR_HINT_FADE` is not in the default `wxAuiManager` style | :132-133 | Orca's `AuiMgr` keeps the default flags (minus `wxAUI_MGR_ALLOW_FLOATING` on Wayland, `AuiMgr::init`), so the docking hint no longer fades; add the flag if wanted |
| `wxPrintDialogData::SetAllPages(false)`/`SetSelection(false)` changed meaning | :135-137 | none |
| `wxGetTranslation()` returns `wxString` by value | :139-142 | pitfall below |
| `wxWindow::Raise()` no longer shows a hidden window on any port | :144-146 | pitfall below; ba867cc534 |
| `wxAuiNotebook` default art is the new flat art; `wxAuiNativeTabArt` (or `"native"` in XRC) keeps the old look | :41-43 | none: no `wxAuiNotebook` in Orca |
| `wxTHREAD_WAIT_DEFAULT` is `wxTHREAD_WAIT_BLOCK`: `wxThread::Delete()/Wait()` no longer pump events | :45-47 | Orca uses std/boost threads (`wxThread` only for `IsMain()`); a new `wxThread` whose exit needs the main loop would deadlock |
| `wxDocument::OnCloseDocument()` runs once, after the views are destroyed (it used to run twice when the document was closed from the menu); an override must not rely on any view existing | :49-52 | none: no doc/view |
| `wxGrid::FreezeTo()` asserts on out-of-range counts, and freezes even when the grid is too small | :54-57 | silent `false` in Orca; it also refuses reordered or drag-movable rows/columns [source] (`src/generic/grid.cpp:5742-5750`). Clamp arguments (`controls-dataview.md`) |
| Invalid `wxImageList` calls assert | :59-62 | silent in Orca; `wxCHECK` returns (generic `Add` → -1). Create the list with a valid size before use |
| `wxTRANSPARENT_WINDOW` does nothing (`#define wxTRANSPARENT_WINDOW 0`, `include/wx/defs.h:1449`); MSW code that needs it can set `WS_EX_TRANSPARENT` | :64-66 | pitfall below; 026b105dcb |
| MSW `wxTextDataObject::SetData()` size includes the 2-byte NUL (consistent with `GetDataSize()`); an old-style size chops the last character | :68-72 | none: Orca uses the `wxTextDataObject(text)` ctor; use `SetText()`, never `SetData()` |
| `wxListCtrl::EditLabel()` asserts without `wxLC_EDIT_LABELS` | :74-75 | silent no-op in Orca; add the style where editing is intended |
| MSW `wxSystemAppearance::IsDark()` reports the app's own mode; `AreAppsDark()`/`IsSystemDark()` report the OS | :77-79 | pitfall below |
| Unix `wxUILocale::IsSupported()` no longer falls back to another region of the same language; pass just `"fr"` to accept any `fr_XX` | :81-85 | `wxLocale::IsAvailable` builds the region-qualified tag (`GetCanonicalWithRegion()`) and calls `IsSupported()` [source] (`src/common/intl.cpp:740-781`); `GUI_App::load_language` relies on it, so on Linux a language whose canonical locale (e.g. `fr_FR`) is not installed reports unavailable. Keep the fallbacks in `load_language` |
| Deprecated `wxPGCellRenderer::DrawCaptionSelectionRect()` overload not called; override the overload taking `wxWindow*`, or enable 3.0 compatibility | :87-89 | none: no propgrid in Orca's own code |
| `wxImageList` size is in physical pixels | :91-94 | pitfall below |
| Mac `wxWebRequest` no longer uses persistent storage. The entry names `wxWebRequest::EnablePersistentStorage()`; the real API is `wxWebSession::EnablePersistentStorage(bool)` (`interface/wx/webrequest.h:1612-1629`; also `wxWebSessionSync`, :1835), macOS-only, before the first request | :96-98 | none: Orca uses `wxWebRequest` only for image downloads (`wxWebSession::GetDefault().CreateRequest` in `StatusPanel`, `SliceInfoPanel`, `ReleaseNote`, `DeviceErrorDialog`); network and login agents use libcurl (`Slic3r::Http`) |
| MSW `wxBitmap::Create(size, dc)` no longer multiplies by the DC's content scale; the size is physical | :100-102 | remove compensating scaling; use `CreateWithLogicalSize` for logical sizes |
| `wxIMAGE_QUALITY_NEAREST` has a new value and is no longer `wxIMAGE_QUALITY_NORMAL`; `NORMAL` (the `Scale`/`Rescale` default) is bilinear + box average (`interface/wx/image.h:31-67`) | :104-105 | never store or compare the enum numerically; default-quality thumbnails and icons look smoother than under 3.1.5; pass `wxIMAGE_QUALITY_NEAREST` for pixel-exact scaling |
| `wxTextCtrl::{Save,Load}File()` treat `.rtf` as RTF | :107-110 | pass `wxTEXT_TYPE_PLAIN` to keep plain text |
| `wxClientDC`/`wxPaintDC` offset their origin by a `wxFrame` toolbar on every port | :112-115 | none: Orca frames have no native toolbar (`BBLTopbar` is a child `wxAuiToolBar`) |
| `wxTextCtrl::SetLabel()` does nothing and asserts on every port (MSW used to act as `SetValue`) | :117-119 | pitfall below |
| `wxAuiGenericTabArt` subclasses (also via `wxAuiMSWTabArt`) override `DrawPageTab()`/`GetPageTabSize()` instead of `DrawTab()`/`GetTabSize()`; direct `wxAuiTabArt` subclasses still work | :121-126 | none: no Orca tab art |
| `wxAuiNotebook` page index is logical (reorder-independent); `GetPagePosition()` gives the screen position | :128-132 | none; index math under `wxAUI_NB_TAB_MOVE` is what breaks |
| `wxListbook`/`wxChoicebook` interpret mnemonics in page titles "just as the other wx*book classes already did" | :134-136 | Orca uses neither (`BedShapeDialog` uses `wxSimplebook` + a combo); every book interprets `&` (`interface/wx/bookctrl.h:141-147`) → rule 11 |
| `wxAUI_MGR_HINT_FADE` is not in the default `wxAuiManager` style | :138-139 | Orca's `AuiMgr` keeps the default flags (minus `wxAUI_MGR_ALLOW_FLOATING` on Wayland, `AuiMgr::init`), so the docking hint no longer fades; add the flag if wanted |
| `wxPrintDialogData::SetAllPages(false)`/`SetSelection(false)` changed meaning | :141-143 | none |
| `wxWindow::Raise()` no longer shows a hidden window on any port | :145-147 | pitfall below; ba867cc534 |
**Pitfalls**
@@ -277,7 +284,7 @@ change, its line, and what it means for Orca.
// Wrong: m_input_ip->GetTextCtrl()->SetLabelText(m_obj->get_dev_ip());
// Right: m_input_ip->GetTextCtrl()->ChangeValue(m_obj->get_dev_ip()); // no wxEVT_TEXT
```
Cite: `docs/changes.txt:111-113`; `controls-dataview.md`.
Cite: `docs/changes.txt:117-119`; `controls-dataview.md`.
- **Rule:** Size a `wxImageList` from the bitmaps' physical size, or use
`SetImages(std::vector<wxBitmapBundle>)`.
@@ -293,18 +300,23 @@ change, its line, and what it means for Orca.
// Right: wxBitmap bmp = create_scaled_bitmap("icon", this, 16);
// m_images = new wxImageList(bmp.GetWidth(), bmp.GetHeight(), false);
```
Cite: `docs/changes.txt:85-88`; `Tab` builds its list from `bmp().GetWidth()/GetHeight()`;
Cite: `docs/changes.txt:91-94`; `Tab` builds its list from `bmp().GetWidth()/GetHeight()`;
`dpi-bitmaps-fonts.md`.
- **Rule:** A function that returns a translation returns `wxString` by value.
**Why:** `wxGetTranslation()` returns by value now (`include/wx/translation.h:278-321`); returning
it as `const wxString&` dangles. Orca's `I18N::translate` overloads and `_L` already return by
value.
- **Rule:** A wrapper that converts a translation returns by value; `wxGetTranslation()` itself
returns `const wxString&`.
**Why:** 3.3.2 changed `wxGetTranslation()` to return `wxString` by value, and **3.3.3 reverted it**
(`interface/wx/translation.h:615`, `include/wx/translation.h:277-309`), so the return type is the
3.2.x `const wxString&` again. That reference points at the catalog's own string or at
`wxTranslations::GetUntranslatedString()`, both of which outlive the call, so forwarding it as a
reference is safe. What still dangles is a temporary you construct: `wxString::FromUTF8`,
`ToUTF8()`, `format_wxstr`. Orca's `I18N::translate` overloads return `wxString` by value and
`translate_utf8`/`_u8L` return `std::string`.
```cpp
// Wrong: const wxString& title() { return _L("Printer"); }
// Right: wxString title() { return _L("Printer"); }
// Wrong: const wxString& path() { return wxString::FromUTF8(raw); } // temporary
// Right: wxString path() { return wxString::FromUTF8(raw); }
```
Cite: `docs/changes.txt:139-142`; `I18N.hpp`.
Cite: §8 (the 3.3.3 revert); `I18N.hpp`.
- **Rule:** Escape `&` in user data (preset, filament, printer, file names) shown as a control label or
book page title.
@@ -314,37 +326,37 @@ change, its line, and what it means for Orca.
// Wrong: book->AddPage(page, preset_name);
// Right: book->AddPage(page, wxControl::EscapeMnemonics(preset_name)); // or label->SetLabelText(name)
```
Cite: `docs/changes.txt:128-130`; `interface/wx/bookctrl.h:141-147`; `controls-dataview.md`.
Cite: `docs/changes.txt:134-136`; `interface/wx/bookctrl.h:141-147`; `controls-dataview.md`.
## 4 3.3 changes that break the build
`changes.txt` "Changes in behaviour which may result in build errors" (:149-252).
`changes.txt` "Changes in behaviour which may result in build errors" (:181-292).
| Change | Line | Orca relevance |
|---|---|---|
| 3.0-deprecated symbols disabled by default (`WXWIN_COMPATIBILITY_3_0=1` at wx build time re-enables them), 2.8-deprecated removed | :152-154 | Orca builds `WXWIN_COMPATIBILITY_3_0 0`, `_3_2 1`: port off 3.0-deprecated API |
| `wxUSE_UNICODE=0` unsupported | :156 | none |
| `wxUSE_STD_CONTAINERS=1` by default ("Container Classes" overview); building wx with 0 keeps the old containers | :158-161 | Orca builds with 1 → pitfall below |
| `wxUSE_STL` gone; implicit `wxString` → `std::[w]string` only with `wxUSE_STD_STRING_CONV_IN_WXSTRING=1` at wx build time | :163-166 | Orca builds 0: convert explicitly (`into_u8`, `ToStdString`, `ToUTF8()`) |
| MSW links `gdiplus.lib`, `msimg32.lib` | :168-172 | automatic with MSVC/wx-config; only static non-MSVC builds add them |
| wxMotif, wxGTK1 removed | :174-175 | none |
| Private containers (e.g. `wxSimpleDataObjectList`) removed; object arrays (`wxImageArray`) compare values in `Index()` | :177-183 | use `std::vector`/`std::list` |
| Operators on wx types are hidden (not global) | :185-189 | pitfall below |
| `wxString` from `std::string_view` makes `wxstr = {"Hello", 2}` ambiguous | :191-194 | write `wxString{"Hello", 2}` |
| Generic `wxSearchCtrl` lost multi-line-only methods | :196-197 | none |
| Wide-filename `wxOnAssert()` overload removed | :199-200 | none |
| 64-bit DLLs carry an `x64` suffix in all build systems | :202-204 | packaging scripts matching DLL names; Orca links wx statically (Flatpak builds it shared in its own manifest) |
| CMake config installs to `lib/cmake/wxWidgets-3.3`; plain `find_package(wxWidgets)` is unaffected, hard-coded paths break | :206-211 | Orca: `find_package(wxWidgets 3.3 CONFIG …)` on Windows/macOS, `wx-config --toolkit=gtk${SLIC3R_GTK}` on Linux (Orca's `src/CMakeLists.txt`) |
| Memory-tracing options removed | :213-216 | use ASan |
| `wxTEST_DIALOG()` needs a trailing `;` | :218-219 | none |
| `wxWindow::GetDefaultBorderForControl()` not virtual | :221-223 | do not override; use `wxBORDER_THEME` |
| GTK `wxDirButton::Create()` lost `wildcard` | :225-226 | none |
| Several virtuals take `wxReadOnlyDC` | :228-231 | pitfall below |
| `wxSizer::Detach()` takes `wxWindowBase*` | :233-236 | only custom sizer subclasses |
| `wx/cursor.h` no longer includes `wx/utils.h` | :238-240 | include `<wx/utils.h>` explicitly (8248b06337 added it to `GLCanvas3D.cpp`) |
| `wxStyledTextCtrl::AddSelection()` returns void | :242-244 | none: `wxUSE_STC=OFF` |
| `wxColour` from `bool` no longer compiles | :246-248 | use the RGB or string ctor explicitly |
| `wxGLCanvas::CreateSurface()` removed from EGL builds | :250-252 | none: wx chooses EGL or GLX itself (§7) |
| 3.0-deprecated symbols disabled by default (`WXWIN_COMPATIBILITY_3_0=1` at wx build time re-enables them), 2.8-deprecated removed | :184-186 | Orca builds `WXWIN_COMPATIBILITY_3_0 0`, `_3_2 1`: port off 3.0-deprecated API |
| `wxUSE_UNICODE=0` unsupported | :188 | none |
| `wxUSE_STD_CONTAINERS=1` by default ("Container Classes" overview); building wx with 0 keeps the old containers | :190-193 | Orca builds with 1 → pitfall below |
| `wxUSE_STL` gone; implicit `wxString` → `std::[w]string` only with `wxUSE_STD_STRING_CONV_IN_WXSTRING=1` at wx build time | :195-198 | Orca builds 0: convert explicitly (`into_u8`, `ToStdString`, `ToUTF8()`) |
| MSW links `gdiplus.lib`, `msimg32.lib` | :200-204 | automatic with MSVC/wx-config; only static non-MSVC builds add them |
| wxMotif, wxGTK1 removed | :206-207 | none |
| Private containers (e.g. `wxSimpleDataObjectList`) removed; object arrays (`wxImageArray`) compare values in `Index()` | :209-215 | use `std::vector`/`std::list` |
| Operators on wx types are hidden (not global) | :217-221 | pitfall below |
| `wxString` from `std::string_view` makes `wxstr = {"Hello", 2}` ambiguous | :223-226 | write `wxString{"Hello", 2}` |
| Generic `wxSearchCtrl` lost multi-line-only methods | :228-229 | none |
| Wide-filename `wxOnAssert()` overload removed | :231-232 | none |
| 64-bit DLLs carry an `x64` suffix in all build systems | :234-236 | packaging scripts matching DLL names; Orca links wx statically (Flatpak builds it shared in its own manifest) |
| CMake config installs to `lib/cmake/wxWidgets-3.3`; plain `find_package(wxWidgets)` is unaffected, hard-coded paths break | :238-243 | Orca: `find_package(wxWidgets 3.3 CONFIG …)` on Windows/macOS, `wx-config --toolkit=gtk${SLIC3R_GTK}` on Linux (Orca's `src/CMakeLists.txt`) |
| Memory-tracing options removed | :245-248 | use ASan |
| `wxTEST_DIALOG()` needs a trailing `;` | :250-251 | none |
| `wxWindow::GetDefaultBorderForControl()` not virtual | :253-255 | do not override; use `wxBORDER_THEME` |
| GTK `wxDirButton::Create()` lost `wildcard` | :257-258 | none |
| Several virtuals take `wxReadOnlyDC` | :260-263 | pitfall below |
| `wxSizer::Detach()` takes `wxWindowBase*` | :265-268 | only custom sizer subclasses |
| `wx/cursor.h` no longer includes `wx/utils.h` | :270-272 | include `<wx/utils.h>` explicitly (8248b06337 added it to `GLCanvas3D.cpp`) |
| `wxStyledTextCtrl::AddSelection()` returns void | :274-276 | none: `wxUSE_STC=OFF` |
| `wxColour` from `bool` no longer compiles | :278-280 | use the RGB or string ctor explicitly |
| `wxGLCanvas::CreateSurface()` removed from EGL builds | :282-284 | none: wx chooses EGL or GLX itself (§7) |
**Pitfalls**
@@ -366,7 +378,7 @@ change, its line, and what it means for Orca.
// Wrong: wxSize GetToolSize(wxDC& dc, wxWindow* w, const wxAuiToolBarItem& it); // never called
// Right: wxSize GetToolSize(wxReadOnlyDC& dc, wxWindow* w, const wxAuiToolBarItem& it) override;
```
Cite: `docs/changes.txt:228-231`; `painting-custom-widgets.md`.
Cite: `docs/changes.txt:260-263`; `painting-custom-widgets.md`.
- **Rule:** With `wxUSE_STD_CONTAINERS=1`, build a `wxArrayString` with `Add()` or an initializer
list, and walk `WX_DECLARE_LIST` lists with `compatibility_iterator` or range-for.
@@ -386,7 +398,7 @@ change, its line, and what it means for Orca.
`wchar_t`, `std::string` or `std::wstring`.
**Why:** wx operators are hidden friends now, found only by argument-dependent lookup on a wx type;
"preventing them from implicitly being used with types convertible to wx types"
(`changes.txt:185-189`). `char + std::wstring + …` compiled under 3.1.5 through the global
(`changes.txt:217-221`). `char + std::wstring + …` compiled under 3.1.5 through the global
`operator+(char, const wxString&)`, converting the `std::wstring` implicitly; under 3.3 that
operator is a hidden friend (`include/wx/string.h:2150`) and no operand is a `wxString`, so it
does not.
@@ -413,9 +425,9 @@ change, its line, and what it means for Orca.
## 5 3.3.0 notable changes
`changes.txt:380-592` (relative to 3.2.8).
`changes.txt:573-785` (relative to 3.2.8).
**Major changes** (:385-394)
**Major changes** (:578-587)
| Change | Orca relevance |
|---|---|
@@ -429,7 +441,7 @@ change, its line, and what it means for Orca.
| Native GTK file dialogs when possible (#24486, #25104) | the portal dialog is used only with GTK ≥ 3.20 at runtime, without `wxFD_PREVIEW` and without an extra control [source] (`src/gtk/filedlg.cpp:265-273, 438-443`); Orca's `CheckboxFileDialog` (`SetExtraControlCreator`, `GUI_Utils.hpp`) therefore gets the non-native GTK dialog |
| Native `wxTextCtrl` contents / RTF (#24626, #24912) | available (`GetRTFValue`, `SearchText`) |
**All** (:398-442)
**All** (:591-635)
| Change | Orca relevance |
|---|---|
@@ -439,7 +451,7 @@ change, its line, and what it means for Orca.
| Environment variables use UTF-8 (#25101) | check round-tripping of non-ASCII paths through `wxGetEnv`/`wxSetEnv` |
| Improved locale matching (#24855); thread-safe `wxPlatformInfo::Get()` (#25459); `wxXmlParseError` from `wxXmlDocument::Load()` (#24215); customisable error exit code (#24770) | available |
**All (GUI)** (:444-500)
**All (GUI)** (:637-693)
| Change | Orca relevance |
|---|---|
@@ -452,17 +464,17 @@ change, its line, and what it means for Orca.
| Non-live resize restored in wxAUI and `wxSplitterWindow` (#24193) | `wxAUI_MGR_LIVE_RESIZE` is in `wxAUI_MGR_DEFAULT` since 3.3.0 (`interface/wx/aui/framemanager.h:59-66`). The style table's "always enabled in wxGTK3 and wxOSX ports as non-live resizing is not implemented in them" (:199-206) is as stale as the `AlwaysUsesLiveResize()` note: that function "always returns false" as of 3.3.0 (:345; `src/aui/framemanager.cpp:711-714`), and the flag decides on every port [source] (`HasLiveResize`, :716-719). See `webview-gl-aui-media.md` §AUI docking |
| `wxInfoBar::ShowCheckBox()` (#25394); printing multiple page ranges (#25030); `wxGrid::CopySelection()` (#24124); new default flat AUI tab art (#25316); `wxApp::SetAppearance()` (#24461) | available |
**All (WebView)** (:510-525) — `wxWebViewConfiguration` + `GetNativeConfiguration()`, `SetProxy()`,
**All (WebView)** (:703-718) — `wxWebViewConfiguration` + `GetNativeConfiguration()`, `SetProxy()`,
`ShowDevTools()`, `EnablePersistentStorage()`, `EnableBrowserAcceleratorKeys()`, clearing browsing
data, advanced requests, child-window handling, `IsTargetMainFrame()`, Edge user agent settable after
creation, and **Edge events queued** (#22744, #19075): Edge handlers run later than on the other
backends. Edge posts its events with `AddPendingEvent` (script messages: `src/msw/webview_edge.cpp:811`)
except the vetoable ones, `wxEVT_WEBVIEW_NAVIGATING` (:599) and `wxEVT_WEBVIEW_NEWWINDOW` with its
`NEWWINDOW_FEATURES` follow-up (:723, :737), which stay synchronous; WebKit and
backends. Edge posts its events with `AddPendingEvent` (script messages: `src/msw/webview_edge.cpp:872`)
except the vetoable ones, `wxEVT_WEBVIEW_NAVIGATING` (:655-662) and `wxEVT_WEBVIEW_NEWWINDOW` with its
`NEWWINDOW_FEATURES` follow-up (:783-785, :796-798), which stay synchronous; WebKit and
WebKit2GTK deliver script messages synchronously (`src/osx/webview_webkit.mm:1360`,
`src/gtk/webview_webkit2.cpp:408`) [source]. `webview-gl-aui-media.md` owns the details.
**wxGTK** (:527-547)
**wxGTK** (:720-740)
| Change | Orca relevance |
|---|---|
@@ -477,46 +489,53 @@ WebKit2GTK deliver script messages synchronously (`src/osx/webview_webkit.mm:136
| `wxDC::DrawRoundedRectangle()` radius limited to half the smaller side (#24327) | the clamp is in the GTK2 GDK DC (`src/gtk/dcclient.cpp:874-875`, built only in the GTK2 opt-out, `GTK2_LOWLEVEL_SRC` in `build/files`) and also in the common `wxGraphicsPathData::AddRoundedRectangle` (`src/common/graphcmn.cpp:439-440`, absent in 3.1.5) that `wxGraphicsContext::DrawRoundedRectangle` uses, so every `wxGCDC` clamps: GTK3 (Cairo) and macOS window DCs, and Orca's memory-DC + `wxGCDC` paint paths on all ports [source]. A radius larger than half the smaller side is now clamped instead of drawing overlapping arcs |
| Read-only `wxBitmapComboBox` height fixed (#25468) | `Slic3r::GUI::BitmapComboBox` on GTK |
**wxMSW** (:549-583)
**wxMSW** (:742-776)
| Change | Orca relevance |
|---|---|
| "Enable double buffering for all windows" (#22851) | **reverted in 3.3.2** (#25808) → pitfall below |
| `wxOverlay` reimplemented with layered windows (#23261) | none: no `wxOverlay` |
| `wxBG_STYLE_TRANSPARENT` implemented (#23412) | as `WS_EX_TRANSPARENT` on non-TLW children [source] (`src/msw/window.cpp:1581-1582`); set before `Create()` (`painting-custom-widgets.md`) |
| `wxCAPTION` turned on when min/max/close boxes are set (#23575) | `WS_CAPTION` is added to the style (`src/msw/toplevel.cpp:132-135`); origin of the MainFrame workaround (§8) |
| `wxCAPTION` turned on when min/max/close boxes are set (#23575) | `WS_CAPTION` is added to the style (`src/msw/toplevel.cpp:132-135`); origin of the MainFrame workaround (§10) |
| `wxButton` default size larger in high DPI (#25297) | raw `wxButton` layouts may grow; Orca dialogs use `Button`/`DialogButtons` |
| Markup in `wxStaticText` (#25000); `wxHyperlinkCtrl` colour changeable (#23549) | `SetLabelMarkup` now works on MSW (single line) |
| Extended-length paths (#25033); `wxFileDialog` no unwanted extension (#24949); UTF-8 build fixes (#23313); non-BMP strings (#25128) | available |
| `wxDisplay` invalidated on display change (#25396); TLW with one child resized on DPI change (#22983); Aero-snapped geometry saved | multi-monitor geometry |
| Modern default `wxTreeCtrl` look (#23844); RTL fixes for `wxOverlay`/`wxScrolled` (#25413) and GDI+ (#25431); `wxBitmap::UseAlpha()` returns `bool` (#23919) | available |
**wxOSX** (:585-592) — `wxEventLoop::OnExit()` is always called (#25409); TLW cursor setting fixed
**wxOSX** (:778-785) — `wxEventLoop::OnExit()` is always called (#25409); TLW cursor setting fixed
(#25131); Cmd-C no longer activates a "Close" button (#25346); `wxCursor` loadable from resources
(#24374); `wxUIActionSimulator` works (#23692), usable for GUI tests.
**Pitfalls**
- **Rule:** Do not replace Orca's MSW dark mode with wx's (`SetAppearance`/`MSWEnableDarkMode`)
unless live theme switching is kept.
**Why:** wx's MSW dark mode cannot change once any top-level window exists or a mode was chosen:
`SetAppearance` returns `CannotChange` (`src/msw/darkmode.cpp:263-270`;
`interface/wx/app.h:1166-1173`). TaskDialog-based dialogs (`wxMessageDialog`, `wxProgressDialog`,
simple `wxAboutBox`), the common dialogs (colour, find/replace, font, page setup, print) and the
date/time/calendar controls stay light (`interface/wx/app.h:1434-1448`). Orca switches themes at
runtime, which is why 8248b06337 kept NppDarkMode and only informs wx with
`MSWEnableDarkMode(DarkMode_Auto)` (§8).
- **Rule:** Orca keeps its own Windows theming (`NppDarkMode`) and only informs wx with
`MSWEnableDarkMode(DarkMode_Auto)`; do not replace it with wx's dark mode without accounting for the
chrome NppDarkMode themes itself.
**Why:** when 8248b06337 landed (3.3.2), wx's MSW dark mode could not be changed once a top-level
window existed — `SetAppearance` returned `CannotChange` — so a live Preferences toggle would have
been restart-only. That blocker is gone: 3.3.3 (#26516) and 3.3.4 (#26734) made the mode switchable
at runtime. `MSWEnableDarkMode()` re-applies the mode to an existing window (sends it
`WM_SYSCOLORCHANGE`, `src/msw/darkmode.cpp:345-349`), controls restyle through
`wxMSWDarkMode::HasChanged()`/`NotifySysColorChange()` (`src/msw/window.cpp:5337, 5528-5534`), and
`SetAppearance()` returns only `Ok`/`Failure`; `AppearanceResult::CannotChange` is no longer
returned (`src/msw/darkmode.cpp:289-375`; `interface/wx/app.h:1151, 1179-1183`). The remaining
documented limits are the date/time/calendar controls, `wxPrintDialog` on Windows 10 and MDI
applications (`interface/wx/app.h:1442-1448`); `wxMessageDialog` (3.3.3, #26570) and the common
dialogs (3.3.4, #26780) now do support dark mode. Orca still keeps NppDarkMode because it themes
Orca's own chrome (title bars, explorer theme, scrollbars, list headers) and drives the live
theme-switch path (§10).
Cite: 8248b06337 (PR text); `colours-dark-mode.md`.
- **Rule:** On MSW, a custom-painted control buffers its own drawing (`wxAutoBufferedPaintDC`/
`wxBufferedPaintDC` with `wxBG_STYLE_PAINT`, or the Orca memory-DC + `wxGCDC` path), or calls
`SetDoubleBuffered(true)` after creation.
**Why:** 3.3.0's global `WS_EX_COMPOSITED` was reverted in 3.3.2 (`changes.txt:308`). In 3.3.2
**Why:** 3.3.0's global `WS_EX_COMPOSITED` was reverted in 3.3.2 (`changes.txt:501`). In 3.3.4
nothing sets it except an explicit `SetDoubleBuffered(true)` [source] (`src/msw/window.cpp:4704-4721`;
the only other use is `MSWDisableComposited`, :1643-1652), so MSW windows are not double-buffered by
default, exactly as in 3.1.5. `wxAutoBufferedPaintDC` is a `wxBufferedPaintDC` on MSW at compile
time (`include/wx/dcbuffer.h:18-23, 215-221`). Nothing tuned against 3.3.0/3.3.1 applies.
Cite: `docs/changes.txt:308, 557`; `painting-custom-widgets.md`.
Cite: `docs/changes.txt:501, 750`; `painting-custom-widgets.md`.
- **Rule:** A `wxEVT_DPI_CHANGED` handler must be correct on GTK3, and every handler you bind — on a
child, a control or a `DPIDialog`/`DPIFrame` — calls `Skip()`.
@@ -529,11 +548,11 @@ WebKit2GTK deliver script messages synchronously (`src/osx/webview_webkit.mm:136
ctor starts at 1 because `get_dpi_for_window` returns 96 on Linux). Double scaling when a window moves between monitors of
different scale is a risk that has not been verified at runtime; test DPI changes on GTK when
touching `DPIAware::rescale` paths.
Cite: `docs/changes.txt:541`; `dpi-bitmaps-fonts.md` §wxEVT_DPI_CHANGED.
Cite: `docs/changes.txt:734`; `dpi-bitmaps-fonts.md` §wxEVT_DPI_CHANGED.
## 6 3.3.1 notable fixes
`changes.txt:333-377`.
`changes.txt:526-570`.
| Port | Change | Orca relevance |
|---|---|---|
@@ -547,7 +566,7 @@ WebKit2GTK deliver script messages synchronously (`src/osx/webview_webkit.mm:136
## 7 3.3.2 notable changes
`changes.txt:255-318`, relative to 3.2.10 (see §1 for the unlisted 3.2.9/3.2.10 fixes).
`changes.txt:448-511`, relative to 3.2.10 (see §1 for the unlisted 3.2.9/3.2.10 fixes).
| Port | Change | Orca relevance |
|---|---|---|
@@ -589,7 +608,7 @@ WebKit2GTK deliver script messages synchronously (`src/osx/webview_webkit.mm:136
// Wrong: m_static_valid->SetLabel(info_line); m_static_valid->Wrap(FromDIP(300));
// Right: m_static_valid->SetLabel(info_line); m_static_valid->Wrap(-1); m_static_valid->Wrap(FromDIP(300));
```
Cite: `docs/changes.txt:280`; `sizers-layout.md` §wxStaticText wrapping.
Cite: `docs/changes.txt:473`; `sizers-layout.md` §wxStaticText wrapping.
- **Rule:** On Linux/X11, call `wxGLCanvas::PreferGLX()` before any GL use, attribute objects
included; set a swap interval explicitly if the canvas needs VSync.
@@ -605,7 +624,7 @@ WebKit2GTK deliver script messages synchronously (`src/osx/webview_webkit.mm:136
```cpp
// Right (if frame pacing is wanted): canvas->SetSwapInterval(1); // before the first SwapBuffers
```
Cite: `docs/changes.txt:275-276, 292`; `webview-gl-aui-media.md` §EGL vs GLX.
Cite: `docs/changes.txt:468-469, 485`; `webview-gl-aui-media.md` §EGL vs GLX.
- **Rule:** Do not count clicks from `wxEVT_LEFT_DCLICK` alone on macOS; handle `DOWN` and `DCLICK`
as on MSW.
@@ -613,13 +632,68 @@ WebKit2GTK deliver script messages synchronously (`src/osx/webview_webkit.mm:136
(left and right buttons), so a triple click gives `DOWN, DCLICK, DOWN` as on MSW; previously every
click with `clickCount > 1` was a `DCLICK` [source] (`src/osx/cocoa/window.mm:4103-4150`). Per-platform click-count ifdefs
written for 3.1.5 need rechecking.
Cite: `docs/changes.txt:316`; `mouse-keyboard-focus.md`.
Cite: `docs/changes.txt:509`; `mouse-keyboard-focus.md`.
## 8 Migration already done in Orca
## 8 3.3.3 notable changes
The upgrade landed as 8248b06337 ("Updated wxWidgets to 3.3.2", #12941; build system 2d7e26292b).
Its PR kept Orca's own MSW dark mode to avoid broader changes and because wx's needs an app restart to
switch. Each row is a rule for new code; the owning file has the detail.
`changes.txt:388-445`, relative to 3.2.11 (see §1 for the unlisted 3.2.10/3.2.11 fixes).
| Port | Change | Orca relevance |
|---|---|---|
| All | `wxMoveToTrash()` (#26256); `wxSizer::DetachItem()` (#26512); truncated reads are errors in `wxDataInputStream` (#26600); `wxGetOsDescription()` (#26231); C++20 `<=>` for `wxString` (#26306) | available; Orca detaches windows and has its own file deletion |
| All | **`wxGetTranslation()` returns `const wxString&` again**, reverting 3.3.2's by-value change | the by-value rule is retired → §3 pitfall |
| GUI | `wxWebView::Print()` print settings (#26239) and `PrintToPDF()` (#26583); `wxWebViewConfiguration::DisableGPUAcceleration()` (#26626) | `webview-gl-aui-media.md` |
| GUI | `wxGrid::GetFrozen{Row,Col}LabelWindow()` (#26617); `wxSVGFileDC` accessibility (#26379) and `GetSVGDocument()` (#26344) | `controls-dataview.md`; `wxSVGFileDC` available |
| wxGTK | Fix regression when using `wxImageList` in high DPI (#26349) | the physical-pixel image-list rule is unchanged (§3) |
| wxGTK | Fix `wxStaticText` label wrapping | the base `Wrap()` cache is unchanged (§7); only the GTK label wrapping was fixed |
| wxGTK | Assert when the clipboard has non-ASCII targets fixed (#26263); GTK-without-Wayland build fixed (#26269); high-DPI bitmaps in `wxAboutBox` (#22192) | clipboard |
| wxMSW | **Light/dark mode switching implemented** (#26516) | see §5: this removes the old "cannot switch at runtime" blocker |
| wxMSW | `wxMessageDialog` dark mode (#26570); combo/listbox dark mode on Windows 11 (#26535); `wxCheckBox` label in screen readers (#26270); `wxPD_CAN_SKIP` regression (#26287); Direct2D text regression (#26259) | dark-mode and accessibility fixes |
| wxMSW | Stylus events (#26223); colour emoji via Direct2D (#26657) | available |
| wxOSX | Data-view icons under macOS 26 (#26374); `wxOverlay` phantoms in Mission Control (#26380); `wxEVT_DATAVIEW_ITEM_EDITING_DONE` value set (#26367); Xcode project for macOS 27 (#26578) | native look and data view |
## 9 3.3.4 notable changes
`changes.txt:295-385`, relative to 3.2.11 (see §1 for the unlisted 3.2.11/3.2.12 fixes).
| Port | Change | Orca relevance |
|---|---|---|
| All | `wxString` usable with `{fmt}`/`std::format()` (#27180); `wxPATH_RMDIR_PARENTS`; `fsync()` in `wxTempFile` (#27001); `wxDateTime::GetAsDOS()` after 2038 (#4393); TCP `wxIPC` fixes (#24858) | available |
| GUI | **Accelerators take priority over the focused window in all ports** (#22630), with the new `wxEVT_ACCELERATOR_KEY` event and the `wxWindow::ClaimsKeyBeforeAccelerator()` override that text controls use to reserve their editing keys (`interface/wx/event.h:1799`, `interface/wx/window.h:4580`) | pitfall below; `mouse-keyboard-focus.md` |
| GUI | wxGrid: `wxGridCellChoiceEditor` UI fixes (#12094), best size for auto-wrapped rows (#14072), `MakeCellVisible()` with variable row heights (#4371), editor-change crash (#21986) | `controls-dataview.md`; the choice-editor `Combo()` cast rule is unchanged |
| GUI | AUI: no spurious sash jump on click (#4547); an added pane's best size is respected (#4404) | Plater docking |
| GUI | `wxListCtrl` (#15411) and `wxStatusBar` (#24305) accessibility; `wxAuiNotebook` selection after a page removal (#9920); `wxPreviewCanvas` crash (#9822); generic `wxCalendarCtrl` background colour (#21885); LunaSVG with filters (#27176) | available; no `wxAuiNotebook` in Orca |
| wxMSW | **Dark mode can be enabled dynamically** (#26734); dark mode for `wxFontPickerCtrl` (#26667), `wxNotebook` (#26694, #26695), `wxSpinButton` (#26682, #26683) and the common dialogs (#26780) | §5 pitfall; `colours-dark-mode.md` |
| wxMSW | `wxRenameFile()` no longer copies file attributes (`changes.txt:345-347`, incompatible); Esc closes dialogs used by IME/auto-completion (#16059, #27145); IME handling in wxGrid improved (#24558); `wxPersistent` controls DPI-aware (#18752); non-BMP character input (#27016) | file dialogs, keyboard and IME |
| wxGTK | Wayland: crash on pointer/seat disconnect fixed (#27069); **AUI panes draggable via system drag sessions** (#26969); **window geometry restorable** via XDG session management (#26324); overlay-scrollbar client size (#26889) | the Wayland gaps are partly narrowed; `webview-gl-aui-media.md` §AUI |
| wxOSX | `wxEVT_CHAR` key codes fixed for events with Ctrl (#26810); context-menu accessibility (#13010); custom controls in sandboxed `wxFileDialog` (#26908) | `mouse-keyboard-focus.md`; macOS keys |
**Pitfalls**
- **Rule:** On every port an accelerator now runs before the focused window handles the key, unless
that window claims the key; a custom control that must keep a key over an accelerator overrides
`wxWindow::ClaimsKeyBeforeAccelerator()`, or handles `wxEVT_ACCELERATOR_KEY` and calls
`UseAccelerator()`.
**Why:** before 3.3.4 only wxGTK let the focused window handle the key first; the other ports
already gave accelerators priority. 3.3.4 made every port consistent and added the
`wxEVT_ACCELERATOR_KEY` event, sent after `wxEVT_CHAR_HOOK` (before it on macOS) to the focus
window and propagated to its parents (`interface/wx/event.h:1740-1801`;
`interface/wx/window.h:4554-4583`). Text controls, `wxComboBox` and `wxSpinCtrl` reserve their
editing keys by overriding `ClaimsKeyBeforeAccelerator()` and returning
`wxTextEntryBase::IsUsedForEditing()` (`src/common/textcmn.cpp:136-140`;
`src/common/textentrycmn.cpp:441-509`; `src/common/spinctrlcmn.cpp:160-167`;
`src/msw/combobox.cpp:389-395`, `src/gtk/combobox.cpp:205-211`), and the GTK accelerator walk now
consults it (`src/gtk/window.cpp:1524-1539`). Orca's topbar menus are popups whose accelerators are
display-only and Orca dispatches its own shortcuts, so few sites are affected; the macOS
`ObjectList` `wxAcceleratorTable` and the wxInspector table are the live ones.
Cite: `docs/changes.txt:321`; `mouse-keyboard-focus.md` §Accelerators.
## 10 Migration already done in Orca
The upgrade landed as 8248b06337 ("Updated wxWidgets to 3.3.2", #12941; build system 2d7e26292b) and
the pin later advanced to upstream 3.3.4. Its PR kept Orca's own MSW dark mode to avoid broader
changes; wx could then only switch at app start, so a live Orca toggle needed NppDarkMode. Each row is
a rule for new code; the owning file has the detail.
| Commit | Rule for new code | Where | Owner |
|---|---|---|---|
+3 -6
View File
@@ -707,12 +707,9 @@ jobs:
max_releases: 1 # optional, if there are more releases than this matching the asset_name, the oldest ones are going to be deleted
- name: Deploy Ubuntu release
if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' && ! env.ACT && github.ref == 'refs/heads/main' && runner.os == 'Linux' && !vars.SELF_HOSTED && inputs.arch != 'aarch64' }}
uses: rickstaa/action-create-tag@v1
with:
tag: "nightly-builds"
tag_exists_error: false
force_push_tag: true
message: "nightly-builds"
run: |
git -c user.name="${GITHUB_ACTOR}" -c user.email="${GITHUB_ACTOR}@users.noreply.github.com" tag -f -a nightly-builds "${GITHUB_SHA}" -m nightly-builds
git push -f origin refs/tags/nightly-builds
- name: Deploy Ubuntu OrcaSlicer_profile_validator release
if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' && ! env.ACT && github.ref == 'refs/heads/main' && runner.os == 'Linux' && !vars.SELF_HOSTED && inputs.arch != 'aarch64' }}
+2 -2
View File
@@ -156,7 +156,7 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
libopengl0 libglu1-mesa libgl1 libegl1 libwebkit2gtk-4.1-0
libopengl0 libgl1 libegl1 libwebkit2gtk-4.1-0
- uses: actions/setup-python@v6
with:
@@ -224,7 +224,7 @@ jobs:
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
xvfb xdotool imagemagick openbox mesa-utils \
libopengl0 libglu1-mesa libgl1 libegl1 libwebkit2gtk-4.1-0
libopengl0 libgl1 libegl1 libwebkit2gtk-4.1-0
- name: Run the parity harness
run: |
+2
View File
@@ -70,6 +70,8 @@ If you come across any of these in search results, please <b>report them</b> as
Supports a broad range of printers: Bambu Lab, Prusa, Creality, Voron, and more.
- **[Belt Printer Support](https://www.orcaslicer.com/wiki/belt_printing)**
Slice for belt / conveyor (infinite-Z) printers, with belt-aware supports and a tilted-bed preview. Contributed by [Joseph Robertson (@HarrierPigeon)](https://github.com/HarrierPigeon).
- **[IDEX/IQEX Parallel Printing Support](https://www.orcaslicer.com/wiki/idex_iqex_parallel_printing)**
Print copies or mirror images of a part on every carriage of an IDEX or IQEX printer at once, with the mode chosen per plate and nozzle clearance zones shown on the bed. Contributed by [Clifford (@cgarwood82)](https://github.com/cgarwood82).
- Additional features can be found in the [change notes](https://github.com/OrcaSlicer/OrcaSlicer/releases/).
# Wiki
-359
View File
@@ -1,359 +0,0 @@
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
# PrusaSlicer specifics:
# This file is backported from CMake 3.15 distribution to behave uniformly
# across all versions of CMake. It explicitly adds GLEW_STATIC compile
# definition to static targets which is needed to prevent link errors.
#[=======================================================================[.rst:
FindGLEW
--------
Find the OpenGL Extension Wrangler Library (GLEW)
Input Variables
^^^^^^^^^^^^^^^
The following variables may be set to influence this module’s behavior:
``GLEW_USE_STATIC_LIBS``
to find and create :prop_tgt:`IMPORTED` target for static linkage.
``GLEW_VERBOSE``
to output a detailed log of this module.
Imported Targets
^^^^^^^^^^^^^^^^
This module defines the following :ref:`Imported Targets <Imported Targets>`:
``GLEW::glew``
The GLEW shared library.
``GLEW::glew_s``
The GLEW static library, if ``GLEW_USE_STATIC_LIBS`` is set to ``TRUE``.
``GLEW::GLEW``
Duplicates either ``GLEW::glew`` or ``GLEW::glew_s`` based on availability.
Result Variables
^^^^^^^^^^^^^^^^
This module defines the following variables:
``GLEW_INCLUDE_DIRS``
include directories for GLEW
``GLEW_LIBRARIES``
libraries to link against GLEW
``GLEW_SHARED_LIBRARIES``
libraries to link against shared GLEW
``GLEW_STATIC_LIBRARIES``
libraries to link against static GLEW
``GLEW_FOUND``
true if GLEW has been found and can be used
``GLEW_VERSION``
GLEW version
``GLEW_VERSION_MAJOR``
GLEW major version
``GLEW_VERSION_MINOR``
GLEW minor version
``GLEW_VERSION_MICRO``
GLEW micro version
#]=======================================================================]
include(FindPackageHandleStandardArgs)
if(APPLE)
find_package(OpenGL QUIET)
if(OpenGL_FOUND)
if(GLEW_VERBOSE)
message(STATUS "FindGLEW: Found OpenGL Framework.")
message(STATUS "FindGLEW: OPENGL_LIBRARIES: ${OPENGL_LIBRARIES}")
endif()
else()
if(GLEW_VERBOSE)
message(STATUS "FindGLEW: could not find GLEW library.")
endif()
return()
endif()
endif()
function(__glew_set_find_library_suffix shared_or_static)
if((UNIX AND NOT APPLE) AND "${shared_or_static}" MATCHES "SHARED")
set(CMAKE_FIND_LIBRARY_SUFFIXES ".so")
elseif((UNIX AND NOT APPLE) AND "${shared_or_static}" MATCHES "STATIC")
set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
elseif(APPLE AND "${shared_or_static}" MATCHES "SHARED")
set(CMAKE_FIND_LIBRARY_SUFFIXES ".dylib;.so")
elseif(APPLE AND "${shared_or_static}" MATCHES "STATIC")
set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
elseif(WIN32 AND "${shared_or_static}" MATCHES "SHARED")
set(CMAKE_FIND_LIBRARY_SUFFIXES ".lib")
elseif(WIN32 AND "${shared_or_static}" MATCHES "STATIC")
set(CMAKE_FIND_LIBRARY_SUFFIXES ".lib;.a;.dll.a")
endif()
set(CMAKE_FIND_LIBRARY_SUFFIXES "${CMAKE_FIND_LIBRARY_SUFFIXES}" PARENT_SCOPE)
if(GLEW_VERBOSE)
message(STATUS "FindGLEW: CMAKE_FIND_LIBRARY_SUFFIXES for ${shared_or_static}: ${CMAKE_FIND_LIBRARY_SUFFIXES}")
endif()
endfunction()
if(GLEW_VERBOSE)
if(DEFINED GLEW_USE_STATIC_LIBS)
message(STATUS "FindGLEW: GLEW_USE_STATIC_LIBS: ${GLEW_USE_STATIC_LIBS}.")
else()
message(STATUS "FindGLEW: GLEW_USE_STATIC_LIBS is undefined. Treated as FALSE.")
endif()
endif()
find_path(GLEW_INCLUDE_DIR GL/glew.h)
mark_as_advanced(GLEW_INCLUDE_DIR)
set(GLEW_INCLUDE_DIRS ${GLEW_INCLUDE_DIR})
if(GLEW_VERBOSE)
message(STATUS "FindGLEW: GLEW_INCLUDE_DIR: ${GLEW_INCLUDE_DIR}")
message(STATUS "FindGLEW: GLEW_INCLUDE_DIRS: ${GLEW_INCLUDE_DIRS}")
endif()
if("${CMAKE_GENERATOR_PLATFORM}" MATCHES "x64" OR "${CMAKE_GENERATOR}" MATCHES "Win64")
set(_arch "x64")
elseif("${CMAKE_GENERATOR_PLATFORM}" MATCHES "ARM64")
set(_arch "x64") # GLEW ships one header set; ARM64 uses the x64 import path
else()
set(_arch "Win32")
endif()
set(__GLEW_CURRENT_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES})
__glew_set_find_library_suffix(SHARED)
find_library(GLEW_SHARED_LIBRARY_RELEASE
NAMES GLEW glew glew32
PATH_SUFFIXES lib lib64 libx32 lib/Release/${_arch}
PATHS ENV GLEW_ROOT)
find_library(GLEW_SHARED_LIBRARY_DEBUG
NAMES GLEWd glewd glew32d
PATH_SUFFIXES lib lib64
PATHS ENV GLEW_ROOT)
__glew_set_find_library_suffix(STATIC)
find_library(GLEW_STATIC_LIBRARY_RELEASE
NAMES GLEW glew glew32s
PATH_SUFFIXES lib lib64 libx32 lib/Release/${_arch}
PATHS ENV GLEW_ROOT)
find_library(GLEW_STATIC_LIBRARY_DEBUG
NAMES GLEWds GLEWd glewd glewds glew32ds
PATH_SUFFIXES lib lib64
PATHS ENV GLEW_ROOT)
set(CMAKE_FIND_LIBRARY_SUFFIXES ${__GLEW_CURRENT_FIND_LIBRARY_SUFFIXES})
unset(__GLEW_CURRENT_FIND_LIBRARY_SUFFIXES)
include(SelectLibraryConfigurations)
select_library_configurations(GLEW_SHARED)
select_library_configurations(GLEW_STATIC)
if(NOT GLEW_USE_STATIC_LIBS)
set(GLEW_LIBRARIES ${GLEW_SHARED_LIBRARY})
else()
set(GLEW_LIBRARIES ${GLEW_STATIC_LIBRARY})
endif()
if(GLEW_VERBOSE)
message(STATUS "FindGLEW: GLEW_SHARED_LIBRARY_RELEASE: ${GLEW_SHARED_LIBRARY_RELEASE}")
message(STATUS "FindGLEW: GLEW_STATIC_LIBRARY_RELEASE: ${GLEW_STATIC_LIBRARY_RELEASE}")
message(STATUS "FindGLEW: GLEW_SHARED_LIBRARY_DEBUG: ${GLEW_SHARED_LIBRARY_DEBUG}")
message(STATUS "FindGLEW: GLEW_STATIC_LIBRARY_DEBUG: ${GLEW_STATIC_LIBRARY_DEBUG}")
message(STATUS "FindGLEW: GLEW_SHARED_LIBRARY: ${GLEW_SHARED_LIBRARY}")
message(STATUS "FindGLEW: GLEW_STATIC_LIBRARY: ${GLEW_STATIC_LIBRARY}")
message(STATUS "FindGLEW: GLEW_LIBRARIES: ${GLEW_LIBRARIES}")
endif()
# Read version from GL/glew.h file
if(EXISTS "${GLEW_INCLUDE_DIR}/GL/glew.h")
file(STRINGS "${GLEW_INCLUDE_DIR}/GL/glew.h" _contents REGEX "^VERSION_.+ [0-9]+")
if(_contents)
string(REGEX REPLACE ".*VERSION_MAJOR[ \t]+([0-9]+).*" "\\1" GLEW_VERSION_MAJOR "${_contents}")
string(REGEX REPLACE ".*VERSION_MINOR[ \t]+([0-9]+).*" "\\1" GLEW_VERSION_MINOR "${_contents}")
string(REGEX REPLACE ".*VERSION_MICRO[ \t]+([0-9]+).*" "\\1" GLEW_VERSION_MICRO "${_contents}")
set(GLEW_VERSION "${GLEW_VERSION_MAJOR}.${GLEW_VERSION_MINOR}.${GLEW_VERSION_MICRO}")
endif()
endif()
if(GLEW_VERBOSE)
message(STATUS "FindGLEW: GLEW_VERSION_MAJOR: ${GLEW_VERSION_MAJOR}")
message(STATUS "FindGLEW: GLEW_VERSION_MINOR: ${GLEW_VERSION_MINOR}")
message(STATUS "FindGLEW: GLEW_VERSION_MICRO: ${GLEW_VERSION_MICRO}")
message(STATUS "FindGLEW: GLEW_VERSION: ${GLEW_VERSION}")
endif()
find_package_handle_standard_args(GLEW
REQUIRED_VARS GLEW_INCLUDE_DIRS GLEW_LIBRARIES
VERSION_VAR GLEW_VERSION)
if(NOT GLEW_FOUND)
if(GLEW_VERBOSE)
message(STATUS "FindGLEW: could not find GLEW library.")
endif()
return()
endif()
if(NOT TARGET GLEW::glew AND NOT GLEW_USE_STATIC_LIBS)
if(GLEW_VERBOSE)
message(STATUS "FindGLEW: Creating GLEW::glew imported target.")
endif()
add_library(GLEW::glew UNKNOWN IMPORTED)
set_target_properties(GLEW::glew
PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${GLEW_INCLUDE_DIRS}")
if(APPLE)
if(CMAKE_VERSION VERSION_GREATER_EQUAL "4.0")
set_target_properties(GLEW::glew
PROPERTIES INTERFACE_LINK_LIBRARIES "-framework OpenGL")
else()
set_target_properties(GLEW::glew
PROPERTIES INTERFACE_LINK_LIBRARIES OpenGL::GL)
endif()
endif()
if(GLEW_SHARED_LIBRARY_RELEASE)
set_property(TARGET GLEW::glew
APPEND
PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
set_target_properties(GLEW::glew
PROPERTIES IMPORTED_LOCATION_RELEASE "${GLEW_SHARED_LIBRARY_RELEASE}")
endif()
if(GLEW_SHARED_LIBRARY_DEBUG)
set_property(TARGET GLEW::glew
APPEND
PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
set_target_properties(GLEW::glew
PROPERTIES IMPORTED_LOCATION_DEBUG "${GLEW_SHARED_LIBRARY_DEBUG}")
endif()
elseif(NOT TARGET GLEW::glew_s AND GLEW_USE_STATIC_LIBS)
if(GLEW_VERBOSE)
message(STATUS "FindGLEW: Creating GLEW::glew_s imported target.")
endif()
add_library(GLEW::glew_s UNKNOWN IMPORTED)
set_target_properties(GLEW::glew_s
PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${GLEW_INCLUDE_DIRS}")
set_target_properties(GLEW::glew_s PROPERTIES INTERFACE_COMPILE_DEFINITIONS GLEW_STATIC)
if(APPLE)
if(CMAKE_VERSION VERSION_GREATER_EQUAL "4.0")
set_target_properties(GLEW::glew_s
PROPERTIES INTERFACE_LINK_LIBRARIES "-framework OpenGL")
else()
set_target_properties(GLEW::glew_s
PROPERTIES INTERFACE_LINK_LIBRARIES OpenGL::GL)
endif()
endif()
if(GLEW_STATIC_LIBRARY_RELEASE)
set_property(TARGET GLEW::glew_s
APPEND
PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
set_target_properties(GLEW::glew_s
PROPERTIES IMPORTED_LOCATION_RELEASE "${GLEW_STATIC_LIBRARY_RELEASE}")
endif()
if(GLEW_STATIC_LIBRARY_DEBUG)
set_property(TARGET GLEW::glew_s
APPEND
PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
set_target_properties(GLEW::glew_s
PROPERTIES IMPORTED_LOCATION_DEBUG "${GLEW_STATIC_LIBRARY_DEBUG}")
endif()
endif()
if(NOT TARGET GLEW::GLEW)
if(GLEW_VERBOSE)
message(STATUS "FindGLEW: Creating GLEW::GLEW imported target.")
endif()
add_library(GLEW::GLEW UNKNOWN IMPORTED)
set_target_properties(GLEW::GLEW
PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${GLEW_INCLUDE_DIRS}")
if(APPLE)
if(CMAKE_VERSION VERSION_GREATER_EQUAL "4.0")
set_target_properties(GLEW::GLEW
PROPERTIES INTERFACE_LINK_LIBRARIES "-framework OpenGL")
else()
set_target_properties(GLEW::GLEW
PROPERTIES INTERFACE_LINK_LIBRARIES OpenGL::GL)
endif()
endif()
if(TARGET GLEW::glew)
if(GLEW_SHARED_LIBRARY_RELEASE)
set_property(TARGET GLEW::GLEW
APPEND
PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
set_target_properties(GLEW::GLEW
PROPERTIES IMPORTED_LOCATION_RELEASE "${GLEW_SHARED_LIBRARY_RELEASE}")
endif()
if(GLEW_SHARED_LIBRARY_DEBUG)
set_property(TARGET GLEW::GLEW
APPEND
PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
set_target_properties(GLEW::GLEW
PROPERTIES IMPORTED_LOCATION_DEBUG "${GLEW_SHARED_LIBRARY_DEBUG}")
endif()
elseif(TARGET GLEW::glew_s)
if(GLEW_STATIC_LIBRARY_RELEASE)
set_property(TARGET GLEW::GLEW
APPEND
PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
set_target_properties(GLEW::GLEW
PROPERTIES IMPORTED_LOCATION_RELEASE "${GLEW_STATIC_LIBRARY_RELEASE}"
INTERFACE_COMPILE_DEFINITIONS GLEW_STATIC)
endif()
if(GLEW_STATIC_LIBRARY_DEBUG AND GLEW_USE_STATIC_LIBS)
set_property(TARGET GLEW::GLEW
APPEND
PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
set_target_properties(GLEW::GLEW
PROPERTIES IMPORTED_LOCATION_DEBUG "${GLEW_STATIC_LIBRARY_DEBUG}"
INTERFACE_COMPILE_DEFINITIONS GLEW_STATIC)
endif()
elseif(GLEW_VERBOSE)
message(WARNING "FindGLEW: no `GLEW::glew` or `GLEW::glew_s` target was created. Something went wrong in FindGLEW target creation.")
endif()
endif()
-3
View File
@@ -379,10 +379,8 @@ include(Boost/Boost.cmake)
include(Cereal/Cereal.cmake)
include(Qhull/Qhull.cmake)
include(GLEW/GLEW.cmake)
include(GLFW/GLFW.cmake)
include(OpenCSG/OpenCSG.cmake)
set(SLVS_PKG "")
if (SLIC3R_CAD)
include(SLVS/SLVS.cmake)
@@ -478,7 +476,6 @@ set(_dep_list
dep_Draco
dep_NLopt
dep_OpenVDB
dep_OpenCSG
${SLVS_PKG}
dep_OpenCV
dep_Eigen
-2
View File
@@ -1,6 +1,4 @@
orcaslicer_add_cmake_project(EXPAT
# GIT_REPOSITORY https://github.com/nigels-com/glew.git
# GIT_TAG 3a8eff7 # 2.1.0
SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/expat
)
-14
View File
@@ -1,14 +0,0 @@
# We have to check for OpenGL to compile GLEW
set(OpenGL_GL_PREFERENCE "LEGACY") # to prevent a nasty warning by cmake
find_package(OpenGL QUIET REQUIRED)
orcaslicer_add_cmake_project(
GLEW
SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/glew
CMAKE_ARGS
-DGLEW_USE_EGL=OFF
)
if (MSVC)
add_debug_dep(dep_GLEW)
endif ()
-44
View File
@@ -1,44 +0,0 @@
cmake_minimum_required(VERSION 3.0)
project(GLEW)
find_package(OpenGL REQUIRED)
# Allow parent project to control EGL usage.
# Default to OFF since OrcaSlicer forces GDK_BACKEND=x11 (using GLX contexts).
# GLEW must use glXGetProcAddressARB (GLX) to match wxWidgets GL canvas.
# Using EGL function loading with GLX contexts causes rendering failures.
option(GLEW_USE_EGL "Use EGL instead of GLX for OpenGL function loading" OFF)
if(GLEW_USE_EGL)
message(STATUS "Building GLEW with EGL support")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DGLEW_EGL")
else()
message(STATUS "Building GLEW with GLX support")
endif()
add_library(GLEW src/glew.c)
target_include_directories(GLEW PRIVATE include/)
target_link_libraries(GLEW PUBLIC OpenGL::GL)
if (NOT BUILD_SHARED_LIBS)
target_compile_definitions(GLEW PUBLIC GLEW_STATIC)
endif ()
include(GNUInstallDirs)
install(
FILES
${PROJECT_SOURCE_DIR}/include/GL/glew.h
${PROJECT_SOURCE_DIR}/include/GL/wglew.h
${PROJECT_SOURCE_DIR}/include/GL/glxew.h
${PROJECT_SOURCE_DIR}/include/GL/eglew.h
DESTINATION
${CMAKE_INSTALL_INCLUDEDIR}/GL
)
install(TARGETS GLEW GLEW
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
-73
View File
@@ -1,73 +0,0 @@
The OpenGL Extension Wrangler Library
Copyright (C) 2002-2007, Milan Ikits <milan ikits[]ieee org>
Copyright (C) 2002-2007, Marcelo E. Magallon <mmagallo[]debian org>
Copyright (C) 2002, Lev Povalahev
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* The name of the author may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.
Mesa 3-D graphics library
Version: 7.0
Copyright (C) 1999-2007 Brian Paul All Rights Reserved.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
BRIAN PAUL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Copyright (c) 2007 The Khronos Group Inc.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and/or associated documentation files (the
"Materials"), to deal in the Materials without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Materials, and to
permit persons to whom the Materials are furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Materials.
THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
-251
View File
@@ -1,251 +0,0 @@
# GLEW - The OpenGL Extension Wrangler Library
The OpenGL Extension Wrangler Library (GLEW) is a cross-platform open-source C/C++ extension loading library. GLEW provides efficient run-time mechanisms for determining which OpenGL extensions are supported on the target platform. OpenGL core and extension functionality is exposed in a single header file. GLEW has been tested on a variety of operating systems, including Windows, Linux, Mac OS X, FreeBSD, Irix, and Solaris.
![](http://glew.sourceforge.net/glew.png)
http://glew.sourceforge.net/
https://github.com/nigels-com/glew
[![Build Status](https://travis-ci.org/nigels-com/glew.svg?branch=master)](https://travis-ci.org/nigels-com/glew)
[![Gitter](https://badges.gitter.im/nigels-com/glew.svg)](https://gitter.im/nigels-com/glew?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[![Download](https://img.shields.io/sourceforge/dm/glew.svg)](https://sourceforge.net/projects/glew/files/latest/download)
## Table of Contents
* [Downloads](#downloads)
* [Recent snapshots](#recent-snapshots)
* [Build](#build)
* [Linux and Mac](#linux-and-mac)
* [Using GNU Make](#using-gnu-make)
* [Install build tools](#install-build-tools)
* [Build](#build-1)
* [Linux EGL](#linux-egl)
* [Linux mingw-w64](#linux-mingw-w64)
* [Using cmake](#using-cmake)
* [Install build tools](#install-build-tools-1)
* [Build](#build-2)
* [Windows](#windows)
* [Visual Studio](#visual-studio)
* [MSYS/Mingw](#msysmingw)
* [MSYS2/Mingw-w64](#msys2mingw-w64)
* [glewinfo](#glewinfo)
* [Code Generation](#code-generation)
* [Authors](#authors)
* [Contributions](#contributions)
* [Copyright and Licensing](#copyright-and-licensing)
## Downloads
Current release is [2.1.0](https://sourceforge.net/projects/glew/files/glew/2.1.0/).
[(Change Log)](http://glew.sourceforge.net/log.html)
Sources available as
[ZIP](https://sourceforge.net/projects/glew/files/glew/2.1.0/glew-2.1.0.zip/download) or
[TGZ](https://sourceforge.net/projects/glew/files/glew/2.1.0/glew-2.1.0.tgz/download).
Windows binaries for [32-bit and 64-bit](https://sourceforge.net/projects/glew/files/glew/2.1.0/glew-2.1.0-win32.zip/download).
### Recent snapshots
Snapshots may contain new features, bug-fixes or new OpenGL extensions ahead of tested, official releases.
[glew-20200115.tgz](https://sourceforge.net/projects/glew/files/glew/snapshots/glew-20200115.tgz/download) *GLEW 2.2.0 RC3: fixes*
[glew-20190928.tgz](https://sourceforge.net/projects/glew/files/glew/snapshots/glew-20190928.tgz/download) *GLEW 2.2.0 RC2: New extensions, bug fixes*
## Build
It is highly recommended to build from a tgz or zip release snapshot.
The code generation workflow is a complex brew of gnu make, perl and python, that works best on Linux or Mac.
The code generation is known to work on Windows using [MSYS2](https://www.msys2.org/).
For most end-users of GLEW the official releases are the best choice, with first class support.
### Linux and Mac
#### Using GNU Make
GNU make is the primary build system for GLEW, historically.
It includes targets for building the sources and headers, for maintenance purposes.
##### Install build tools
Debian/Ubuntu/Mint: `$ sudo apt-get install build-essential libxmu-dev libxi-dev libgl-dev`
RedHat/CentOS/Fedora: `$ sudo yum install libXmu-devel libXi-devel libGL-devel`
FreeBSD: `# pkg install xorg lang/gcc git cmake gmake bash python perl5`
##### Build
$ make
$ sudo make install
$ make clean
Targets: `all, glew.lib (sub-targets: glew.lib.shared, glew.lib.static), glew.bin, clean, install, uninstall`
Variables: `SYSTEM=linux-clang, GLEW_DEST=/usr/local, STRIP=`
_Note: you may need to call `make` in the **auto** folder first_
##### Linux EGL
$ sudo apt install libegl1-mesa-dev
$ make SYSTEM=linux-egl
##### Linux mingw-w64
$ sudo apt install mingw-w64
$ make SYSTEM=linux-mingw32
$ make SYSTEM=linux-mingw64
#### Using cmake
The cmake build is mostly contributer maintained.
Due to the multitude of use cases this is maintained on a _best effort_ basis.
Pull requests are welcome.
*CMake 2.8.12 or higher is required.*
##### Install build tools
Debian/Ubuntu/Mint: `$ sudo apt-get install build-essential libxmu-dev libxi-dev libgl-dev cmake git`
RedHat/CentOS/Fedora: `$ sudo yum install libXmu-devel libXi-devel libGL-devel cmake git`
##### Build
$ cd build
$ cmake ./cmake
$ make -j4
| Target | Description |
| ---------- | ----------- |
| glew | Build the glew shared library. |
| glew_s | Build the glew static library. |
| glewinfo | Build the `glewinfo` executable (requires `BUILD_UTILS` to be `ON`). |
| visualinfo | Build the `visualinfo` executable (requires `BUILD_UTILS` to be `ON`). |
| install | Install all enabled targets into `CMAKE_INSTALL_PREFIX`. |
| clean | Clean up build artifacts. |
| all | Build all enabled targets (default target). |
| Variables | Description |
| --------------- | ----------- |
| BUILD_UTILS | Build the `glewinfo` and `visualinfo` executables. |
| GLEW_REGAL | Build in Regal mode. |
| BUILD_FRAMEWORK | Build as MacOSX Framework. Setting `CMAKE_INSTALL_PREFIX` to `/Library/Frameworks` is recommended. |
### Windows
#### Visual Studio
Use the provided Visual Studio project file in build/vc15/
Projects for vc6, vc10, vc12 and vc14 are also provided
#### MSYS/Mingw
Available from [Mingw](http://www.mingw.org/)
Requirements: bash, make, gcc
$ mingw32-make
$ mingw32-make install
$ mingw32-make install.all
Alternative toolchain: `SYSTEM=mingw-win32`
#### MSYS2/Mingw-w64
Available from [Msys2](http://msys2.github.io/) and/or [Mingw-w64](http://mingw-w64.org/)
Requirements: bash, make, gcc
$ pacman -S gcc make mingw-w64-i686-gcc mingw-w64-x86_64-gcc
$ make
$ make install
$ make install.all
Alternative toolchain: `SYSTEM=msys, SYSTEM=msys-win32, SYSTEM=msys-win64`
## glewinfo
`glewinfo` is a command-line tool useful for inspecting the capabilities of an
OpenGL implementation and GLEW support for that. Please include `glewinfo.txt`
with bug reports, as appropriate.
---------------------------
GLEW Extension Info
---------------------------
GLEW version 2.0.0
Reporting capabilities of pixelformat 3
Running on a Intel(R) HD Graphics 3000 from Intel
OpenGL version 3.1.0 - Build 9.17.10.4229 is supported
GL_VERSION_1_1: OK
---------------
GL_VERSION_1_2: OK
---------------
glCopyTexSubImage3D: OK
glDrawRangeElements: OK
glTexImage3D: OK
glTexSubImage3D: OK
...
## Code Generation
A Unix or Mac environment is needed for building GLEW from scratch to
include new extensions, or customize the code generation. The extension
data is regenerated from the top level source directory with:
make extensions
An alternative to generating the GLEW sources from scratch is to
download a pre-generated (unsupported) snapshot:
https://sourceforge.net/projects/glew/files/glew/snapshots/
## Authors
GLEW is currently maintained by [Nigel Stewart](https://github.com/nigels-com)
with bug fixes, new OpenGL extension support and new releases.
GLEW was developed by [Milan Ikits](http://www.cs.utah.edu/~ikits/)
and [Marcelo Magallon](http://wwwvis.informatik.uni-stuttgart.de/~magallon/).
Aaron Lefohn, Joe Kniss, and Chris Wyman were the first users and also
assisted with the design and debugging process.
The acronym GLEW originates from Aaron Lefohn.
Pasi K&auml;rkk&auml;inen identified and fixed several problems with
GLX and SDL. Nate Robins created the `wglinfo` utility, to
which modifications were made by Michael Wimmer.
## Contributions
GLEW welcomes community contributions. Typically these are co-ordinated
via [Issues](https://github.com/nigels-com/glew/issues) or
[Pull Requests](https://github.com/nigels-com/glew/pulls) in the
GitHub web interface.
Be sure to mention platform and compiler toolchain details when filing
a bug report. The output of `glewinfo` can be quite useful for discussion
also.
Generally GLEW is usually released once a year, around the time of the Siggraph
computer graphics conference. If you're not using the current release
version of GLEW, be sure to check if the issue or bug is fixed there.
## Copyright and Licensing
GLEW is originally derived from the EXTGL project by Lev Povalahev.
The source code is licensed under the
[Modified BSD License](http://glew.sourceforge.net/glew.txt), the
[Mesa 3-D License](http://glew.sourceforge.net/mesa.txt) (MIT) and the
[Khronos License](http://glew.sourceforge.net/khronos.txt) (MIT).
The automatic code generation scripts are released under the
[GNU GPL](http://glew.sourceforge.net/gpl.txt).
-1
View File
@@ -1 +0,0 @@
2.2.0
-3051
View File
File diff suppressed because it is too large Load Diff
-26427
View File
File diff suppressed because it is too large Load Diff
-1831
View File
File diff suppressed because it is too large Load Diff
-1468
View File
File diff suppressed because it is too large Load Diff
-31949
View File
File diff suppressed because it is too large Load Diff
-101
View File
@@ -1,101 +0,0 @@
cmake_minimum_required(VERSION 3.0)
project(OpenCSG)
if (NOT BUILD_SHARED_LIBS)
set(GLEW_USE_STATIC_LIBS ON)
elseif (MSVC)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
endif()
find_package(OpenGL REQUIRED)
set(GLEW_VERBOSE ON)
find_package(GLEW 1.13.0 REQUIRED)
set(_srcfiles
src/area.cpp
src/batch.cpp
src/context.cpp
src/channelManager.cpp
src/frameBufferObject.cpp
src/frameBufferObjectExt.cpp
src/occlusionQuery.cpp
src/opencsgRender.cpp
src/openglHelper.cpp
src/pBufferTexture.cpp
src/primitive.cpp
src/primitiveHelper.cpp
src/renderGoldfeather.cpp
src/renderSCS.cpp
src/scissorMemo.cpp
src/settings.cpp
src/stencilManager.cpp
RenderTexture/RenderTexture.cpp
include/opencsg.h
src/opencsgConfig.h
src/area.h
src/batch.h
src/context.h
src/channelManager.h
src/frameBufferObject.h
src/frameBufferObjectExt.h
src/occlusionQuery.h
src/offscreenBuffer.h
src/opencsgRender.h
src/openglHelper.h
src/pBufferTexture.h
src/primitiveHelper.h
src/scissorMemo.h
src/settings.h
src/stencilManager.h
)
add_library(opencsg ${_srcfiles})
target_include_directories(opencsg PUBLIC $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>)
target_include_directories(opencsg PUBLIC $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>)
target_link_libraries(opencsg PRIVATE GLEW::GLEW OpenGL::GL)
include(CMakePackageConfigHelpers)
include(GNUInstallDirs)
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
VERSION 1.4.2
COMPATIBILITY AnyNewerVersion
)
install(TARGETS opencsg
EXPORT ${PROJECT_NAME}Targets
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
export(EXPORT ${PROJECT_NAME}Targets
FILE "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
NAMESPACE ${PROJECT_NAME}:: )
set(ConfigPackageLocation ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
install(EXPORT ${PROJECT_NAME}Targets
FILE
"${PROJECT_NAME}Config.cmake"
NAMESPACE
${PROJECT_NAME}::
DESTINATION
${ConfigPackageLocation}
)
install(
FILES
${PROJECT_SOURCE_DIR}/include/opencsg.h
DESTINATION
${CMAKE_INSTALL_INCLUDEDIR}/opencsg
)
install(
FILES
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
DESTINATION
${ConfigPackageLocation}
)
-17
View File
@@ -1,17 +0,0 @@
orcaslicer_add_cmake_project(OpenCSG
# GIT_REPOSITORY https://github.com/floriankirsch/OpenCSG.git
# GIT_TAG 83e274457b46c9ad11a4ee599203250b1618f3b9 #v1.4.2
URL https://github.com/floriankirsch/OpenCSG/archive/refs/tags/opencsg-1-4-2-release.zip
URL_HASH SHA256=51afe0db79af8386e2027d56d685177135581e0ee82ade9d7f2caff8deab5ec5
PATCH_COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_LIST_DIR}/CMakeLists.txt.in ./CMakeLists.txt
DEPENDS dep_GLEW
)
if (TARGET ${ZLIB_PKG})
add_dependencies(dep_OpenCSG ${ZLIB_PKG})
endif()
if (MSVC)
add_debug_dep(dep_OpenCSG)
endif ()
+29
View File
@@ -44,6 +44,18 @@ else()
if(APPLE)
set(_conf_cmd export MACOSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET} && ./Configure -mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET})
else()
# A static library that is embedded into a shared object must not export
# its symbols. On Linux the running process also loads the system OpenSSL
# 3.x (WebKitGTK/gnutls pull in libcrypto.so.3), and CPython's _ssl and
# _hashlib are dlopened (RTLD_LOCAL) DSOs that each embed this OpenSSL.
# With default visibility their unversioned OpenSSL references are
# preempted by that global 3.x copy, mixing the 1.1.1 and 3.x ABIs and
# corrupting the heap (ssl.create_default_context() aborts). Hidden
# visibility makes each embedded copy self-contained. Linux-only: macOS
# binds dylibs with a two-level namespace (no interposition) and ships no
# OpenSSL, and Windows has no equivalent flag and no system OpenSSL to
# collide with.
set(_openssl_extra_cflags -fvisibility=hidden)
set(_conf_cmd env "CC=${CMAKE_C_COMPILER}" "LDFLAGS=${CMAKE_EXE_LINKER_FLAGS}" "./config")
endif()
set(_cross_comp_prefix_line "")
@@ -102,3 +114,20 @@ ExternalProject_Add_Step(dep_OpenSSL install_cmake_files
COMMAND ${CMAKE_COMMAND} -E copy_directory openssl "${DESTDIR}${CMAKE_INSTALL_LIBDIR}/cmake/openssl"
WORKING_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}"
)
if (NOT WIN32 AND NOT APPLE)
# OpenSSL's object rules do not depend on CFLAGS, so reconfiguring it (for
# example to add -fvisibility=hidden) relinks the archives from stale
# objects instead of recompiling them, and the change silently has no
# effect. Drop the objects whenever this recipe changes so the next build
# actually recompiles them.
ExternalProject_Get_Property(dep_OpenSSL SOURCE_DIR)
ExternalProject_Add_Step(dep_OpenSSL clean_objects
DEPENDEES configure
DEPENDERS build
COMMAND make clean
WORKING_DIRECTORY "${SOURCE_DIR}"
DEPENDS "${CMAKE_CURRENT_LIST_FILE}"
COMMENT "OpenSSL: cleaning objects after a recipe change"
)
endif ()
+24
View File
@@ -299,3 +299,27 @@ endif()
if(TARGET dep_ZLIB)
add_dependencies(dep_python3 dep_ZLIB)
endif()
if (NOT WIN32 AND NOT APPLE)
# CPython's Makefile rules for _ssl and _hashlib depend only on their own
# sources, not on the OpenSSL archives, so a rebuilt OpenSSL does not make
# them relink and they keep the previous symbols. On an incremental tree,
# drop the built modules and relink them against the current OpenSSL; a
# fresh build is left alone (its PGO target builds them). "make" alone is a
# no-op once PGO has run, so sharedmods is invoked explicitly.
ExternalProject_Get_Property(dep_python3 SOURCE_DIR)
file(GLOB _python_ssl_modules
"${SOURCE_DIR}/Modules/_ssl*.so"
"${SOURCE_DIR}/Modules/_hashlib*.so")
if (_python_ssl_modules)
ExternalProject_Add_Step(dep_python3 relink_ssl_extensions
DEPENDEES configure
DEPENDERS build
COMMAND sh -c "rm -f '${SOURCE_DIR}'/Modules/_ssl*.so '${SOURCE_DIR}'/Modules/_hashlib*.so && make -j${NPROC} sharedmods"
WORKING_DIRECTORY "${SOURCE_DIR}"
COMMENT "CPython: relinking _ssl/_hashlib against the current OpenSSL"
DEPENDS "${CMAKE_CURRENT_LIST_FILE}"
"${CMAKE_CURRENT_LIST_DIR}/../OpenSSL/OpenSSL.cmake"
)
endif ()
endif ()
@@ -0,0 +1,38 @@
diff --git a/build/cmake/wxWidgetsConfig.cmake.in b/build/cmake/wxWidgetsConfig.cmake.in
index 2f3a735d920c..2593c045b89b 100644
--- a/build/cmake/wxWidgetsConfig.cmake.in
+++ b/build/cmake/wxWidgetsConfig.cmake.in
@@ -50,12 +50,28 @@ if(WIN32_MSVC_NAMING)
set(wxARCH_SUFFIX "_${wxPLATFORM_ARCH}")
endif()
- # use wxWidgets_USE_STATIC to force static libraries, otherwise shared is searched first
- if(NOT wxWidgets_USE_STATIC AND EXISTS "${CMAKE_CURRENT_LIST_DIR}/${wxCOMPILER_PREFIX}${wxARCH_SUFFIX}_dll/@PROJECT_NAME@Targets.cmake")
- set(wxPLATFORM_LIB_DIR "/${wxCOMPILER_PREFIX}${wxARCH_SUFFIX}_dll")
- elseif(EXISTS "${CMAKE_CURRENT_LIST_DIR}/${wxCOMPILER_PREFIX}${wxARCH_SUFFIX}_lib/@PROJECT_NAME@Targets.cmake")
- set(wxPLATFORM_LIB_DIR "/${wxCOMPILER_PREFIX}${wxARCH_SUFFIX}_lib")
+ # wxCOMPILER_PREFIX names the consuming compiler; the installed layout is named
+ # after the compiler that built wxWidgets. cl and clang-cl share an ABI, so
+ # either can consume either build.
+ set(wxCOMPILER_PREFIXES "${wxCOMPILER_PREFIX}")
+ if(wxCOMPILER_PREFIX STREQUAL "clang")
+ list(APPEND wxCOMPILER_PREFIXES "vc")
+ elseif(wxCOMPILER_PREFIX STREQUAL "vc")
+ list(APPEND wxCOMPILER_PREFIXES "clang")
endif()
+
+ # use wxWidgets_USE_STATIC to force static libraries, otherwise shared is searched first
+ foreach(wxPREFIX IN LISTS wxCOMPILER_PREFIXES)
+ if(NOT wxWidgets_USE_STATIC AND EXISTS "${CMAKE_CURRENT_LIST_DIR}/${wxPREFIX}${wxARCH_SUFFIX}_dll/@PROJECT_NAME@Targets.cmake")
+ set(wxPLATFORM_LIB_DIR "/${wxPREFIX}${wxARCH_SUFFIX}_dll")
+ break()
+ elseif(EXISTS "${CMAKE_CURRENT_LIST_DIR}/${wxPREFIX}${wxARCH_SUFFIX}_lib/@PROJECT_NAME@Targets.cmake")
+ set(wxPLATFORM_LIB_DIR "/${wxPREFIX}${wxARCH_SUFFIX}_lib")
+ break()
+ endif()
+ endforeach()
+ unset(wxPREFIX)
+ unset(wxCOMPILER_PREFIXES)
endif()
include("${CMAKE_CURRENT_LIST_DIR}${wxPLATFORM_LIB_DIR}/@PROJECT_NAME@Targets.cmake")
+8 -2
View File
@@ -28,12 +28,18 @@ if (APPLE)
COMMAND ${GIT_EXECUTABLE} apply --verbose
${CMAKE_CURRENT_LIST_DIR}/0001-macos-use-srgb-colour-components.patch
)
elseif (WIN32)
set(_wx_patch_command
${GIT_EXECUTABLE} checkout -f -- build/cmake/wxWidgetsConfig.cmake.in
COMMAND ${GIT_EXECUTABLE} apply --verbose
${CMAKE_CURRENT_LIST_DIR}/0002-clang-cl-sibling-compiler-prefix.patch
)
endif ()
orcaslicer_add_cmake_project(
wxWidgets
GIT_REPOSITORY "https://github.com/SoftFever/Orca-deps-wxWidgets"
GIT_TAG v3.3.2
GIT_REPOSITORY "https://github.com/wxWidgets/wxWidgets"
GIT_TAG v3.3.4
GIT_SHALLOW ON
GIT_SUBMODULES 3rdparty/catch 3rdparty/pcre 3rdparty/libwebp
PATCH_COMMAND ${_wx_patch_command}
+1 -1
View File
@@ -191,7 +191,7 @@ public:
tail->next = std::move(p);
tail = new_tail;
}
data_cond.notify_one();
disrupt_wait_for_data();
}
void wait_and_pop(T& value)
+221
View File
@@ -0,0 +1,221 @@
# Adaptive TPMS infill — High Level Design
## Purpose and scope
`tpms_adaptive` grades the sparse infill of the Gyroid, TPMS-D and TPMS-FK
patterns inside the object: the cells grow continuously from the surface
towards the center of the object. `distance_warp`, `smooth_blend` and
`stepped_shells` follow the distance to the nearest surface, including the top
and bottom, like concentric shells; `lobes` follows the whole 3D shape towards
the center of each lobe of the object; `normal_z`, `normal_y` and `normal_x`
follow each section of the object normal to that axis, so the grading does not
change along the axis, as suits a profile extruded along it.
`sparse_infill_density` is the density at the surface, `tpms_interior_density`
the density at the center, and `tpms_adaptive_gradient` picks how the density
goes from one to the other. Only internal sparse infill is graded; the Gyroid
Z-buckling optimization does not apply to it.
The design has two parts: a field built once per object, and a pattern made
from it, warped around the center of each lobe of a body so that its cell size
follows the field, or, in the modes following the distance to the surface,
split into shells or blended between densities.
## Radial field
`TpmsRadialField` gives every point of an object the center of its lobe and a
radial coordinate: 0 at the center, 1 at the surface along the ray from the
center. `PrintObject::prepare_tpms_radial_fields()` builds it in
`bridge_over_infill()`, next to the adaptive cubic octree, because the anchoring
infill generated there has to match the printed infill. A field is built for
every mode a region uses, and is shared by the regions using that mode: the
field depends on the geometry only, the densities are applied per region in the
fill. An object thinner than the grid cells has no body in the field; no field
is kept then, and the infill falls back to the regular pattern. In the modes
following the distance to the surface, the field also gives the depth of every
point (see below).
A regular 3D grid of cubic cells is rasterized from the `lslices` of the layers,
so the field follows what is printed: negative volumes, the union of
overlapping parts and holes are taken into account, and the mesh does not need
to be closed. A padding node around the grid is always outside. The grid is
capped at about a million nodes, with cells no smaller than 0.5 mm.
- Bodies are the connected inside nodes. Each is graded on its own, so separate
parts of one object each get their own sparse center.
- A body is split into lobes around the local maxima of the depth, by an exact
Euclidean distance transform (Felzenszwalb and Huttenlocher, one pass per
axis). Two maxima are in separate lobes when the depth along the segment
between them drops below 0.8 of the shallower one, like at the neck between
two united spheres; maxima shallower than 0.3 of the deepest one are ignored.
A maximum joins the first lobe whose first maximum it sees without a neck.
The lobes are made one at a time, the remaining maxima tested against the
first one in parallel, as a plate has a whole plane of them.
Where the depth ties along a line or a plane, as in a tall box, the lobe's
center is the node nearest to the middle of the tied nodes, so the center is
in the middle of the height and not a column.
- A point belongs to the lobe it is nearest to relative to their depths, so the
side between two lobes is nearer to the smaller one. Near that side, within a
tenth of that relative distance, the patterns of the lobes morph into each
other, so the lines stay continuous. Every lobe in that range takes part, up
to four, so the morph is also continuous where three or four lobes meet.
- The reach of a lobe is the distance from its center to the first exit along
24 x 48 latitude-longitude directions, smoothed twice over neighbouring
directions in log space. Towards a neighbouring lobe it stops at twice the
distance to the side between them, so that side is graded half way, as deep
as a neck is, rather than as sparse as the center or as dense as the surface.
Only the lobes whose centers are near enough to be nearer at the current
distance are compared along a ray, so many lobes, as in a perforated plate,
stay cheap.
The radial coordinate of a point is its distance to the center over the reach
in its direction. Behind a gap, as across the hole
of a ring, the radial coordinate is above 1 and the infill keeps the surface
density.
- Every outside node belongs to its nearest body, so points near a surface find
their body without a search. With a single body, all nodes belong to it.
In the 2D modes, every plane of nodes normal to the axis is a field of its own:
the distance transform skips the axis, bodies, lobes and the nearest body are
found within the plane, and the reach is sampled on a circle of 48 directions.
A point is looked up in the two planes around it, the weights of their lobes
interpolated along the axis, so the grading does not step between planes; a
plane without a body uses the nearest one that has one. The planes are a cell
apart, not a layer: where the sections change abruptly, as at a step, the
patterns of the two planes morph into each other over that cell.
A distance to the nearest surface would be the obvious field, but no smooth map
follows it. By the divergence theorem, the mean scale of a map over a body is
fixed by its values on the surface: a map that keeps the full density along the
whole surface, as the distance would ask under the top and bottom, has the mean
density of the uniform infill, the sparser core being paid for by lines crowding
along the walls. The layers of a plate at different depths would also need
different line spacings in the same directions, which no continuous map allows
without shearing across the plate. Following the distance needs changes of the
topology of the lattice (see below). The radial coordinate instead grades what a
single map can: towards one point.
## Warped pattern
The pattern is evaluated on warped coordinates:
TPMS(f_surface * m(t) * (p - center))
where `m` scales the pattern around the center of the lobe: its frequency is
`m + t * m'` along the ray and `m` across it. `m(t)` is the mean of the target
scale over the ball of radius `t`, `3 / t^3 * integral of s^2 * target(s) ds`, so
the mean of the three, and with it the density, follows the gradient. The cells
are round at the center; near the surface they are flattened, with the lines
running parallel to it. Beyond the surface the target is the surface scale, so
the warp extends continuously outside.
In the 2D modes only the coordinates within the plane are warped, and `m(t)` is
the mean over the disc, `2 / t^2 * integral of s * target(s) ds`. Along the axis
the pattern keeps the interior frequency: scaling it with `m` would shear the
pattern by the distance along the axis times the gradient of `m`, without bound
on a long object. The cells are round at the center and stretched along the
axis near the surface. With Normal Z the layers are graded exactly, since
the lines of a layer follow its in-plane frequencies; normal to X or Y, the
layers near the sides are as dense as the larger of the two frequencies in the
layer, which is the surface one.
Evaluating a TPMS at a frequency that varies with the position without such a
map distorts it wherever the frequency changes, because the phase also changes
with the gradient of the frequency times the distance from the origin. Fitting a
smooth map to a varying isotropic scale in the least-squares sense (a Poisson
problem per axis) cannot grade strongly: its divergence is the target scale plus
a harmonic function pinned by the surface, which keeps the scale in the core
near two thirds of the surface one. Following a distance exactly needs the
lattice to change its topology, by blending lattices of different densities or
filling shells of equal distance with them, as the modes following the distance
to the surface do.
The target scale at depth `d = 1 - t`, with `S` the surface and `I` the interior
frequency, both from each pattern's own density calibration:
| Gradient | Scale |
|-------------|------------------------|
| Linear | `1 + (I / S - 1) * d` |
| Quadratic | `1 + (I / S - 1) * d^2`|
| Exponential | `(I / S)^d` |
With a denser surface, quadratic keeps the surface density deepest and
exponential drops fastest. A denser interior works the same way.
The zero level is extracted with marching squares like the regular TPMS-FK, on
a sampling grid fixed in the fill frame like the optimized Gyroid, so that every
region of a layer connects its lines the same way at the saddles of the pattern.
Loops narrower than two lines (shorter than `2 * PI * spacing`) are dropped, as
they would print as blobs. The fill works in a frame rotated by the infill
angle, so the radial field is looked up at the point rotated back into the
object frame, and the center rotated into the fill frame. Both use the middle of
the layer.
## Modes following the distance to the surface
`distance_warp`, `smooth_blend` and `stepped_shells` grade by the distance to
the nearest surface, including the top and bottom, as concentric shells do. The
depth of a point is that distance over the distance of the deepest point of its
body, from 0 at the surface to 1; the field keeps it for every node and
interpolates it between them. A tall box so keeps its whole axis as sparse as
its center, and a plate is graded through its thickness.
No single smooth pattern follows that depth without distortion (see above), so
the three modes trade differently:
- Distance warp keeps the lobes and the warp of Lobes, but its radial profile
comes from the depth. For every direction of a lobe, the mean depth over the
ball along the ray is sampled at 33 radii up to the reach, then smoothed over
the neighbouring directions like the reach. The radial coordinate is the one
of the linear profile with the same mean depth, `t = 4/3 * (1 - mean depth)`,
so with a linear gradient the mean cell size follows the depth exactly, and
with the others approximately. In a sphere or a cube, where the depth falls
linearly along every ray, it is Lobes. Elsewhere the profile changes with the
direction, and the warp shears where neighbouring directions differ, as in
plates and long bodies; right under the top of a long body the cells are
sparser within the layer, the warp moving their density into the height.
The profiles are smoothed over the directions like the reach, as sharper
ones shear the pattern across the layer, which adds lines. A long body is so
graded partly along its length, between Lobes and the distance.
- Smooth blend evaluates the regular patterns of the two levels around the
target of every point and blends them by a smoothstep over the whole gap
between the levels, here at most 2.5 times apart. The density follows the
depth without steps, but where two lattices blend, part of their lines run
along the blend, so fewer levels print fewer extra lines. From 25% to 5%,
three levels print about 0.45 of the uniform infill in a deep core whose
levels alone would print 0.3; levels 1.5 times apart print 0.6 to 0.7, and a
single blend from the surface to the interior 0.6.
- Stepped shells split each region of a layer into shells and fill every shell
with the regular pattern at its density. The densities are levels from the
surface to the interior density at most 1.5 times apart, five from 25% to 5%,
and a point takes the level nearest to its target on that geometric scale.
The shells are traced by marching squares of the continuous level over the
layer on a 0.5 mm grid fixed in the object, so every region of a layer gets
the same shells, and clipped to the region. Each shell is shrunk by half a
line, like a filled region, and its regular filler connects its lines along
that boundary, so the connections of two neighbouring shells lie side by side
instead of on top of each other. The pattern is never
distorted, but its lines end at every shell, and thin parts get thin shells.
The connections add lines: in the core of a 60 mm cube, about a third more
than the target.
Stepped shells and Smooth blend need neither lobes nor reaches, which are not
built for them.
## Constraints
- With `tpms_adaptive` disabled, or for other patterns, the fill parameters are reset
to their defaults, so they neither change the infill nor split fill batches.
- `Layer::get_sparse_infill_max_void_area()` uses the sparser of the two
densities, as the voids at the center are that large.
- At a sparse infill density of 100% the sparse infill is turned into solid
infill, so there is nothing to grade: the options are hidden and no field is
built.
- The adaptive options invalidate `posPrepareInfill`, which rebuilds the field
and the anchoring infill.
- An elongated body without a neck has one center, so its far ends are graded
as the outer part of the body, and the warp shears where the reach changes
quickly with the direction. A concave body, like an L, may be split into lobes
where its maxima cannot see each other in a straight line.
- Across a ray, the scale is the mean of the gradient from the center, so the
layers right under the top and above the bottom are sparser than the surface
density in their middle, and a plate is graded from its middle outwards rather
than through its thickness.
+172
View File
@@ -0,0 +1,172 @@
# Center of mass markers — High Level Design
## Purpose and scope
The "Center of mass" item of the canvas toolbar menu, in the bottom left corner of the 3D view, marks
where the mass of the plate, of each object instance and of each body of an assembly is centered,
in Prepare and in Preview. It helps judge how parts will rest on the plate, for instance whether a
tall or leaning part could tip. It is a view setting: it changes nothing in the model, the slice or
the project file, and it does not reach plate thumbnails. The choice is kept in the app config as
`show_center_of_mass`, off by default. The assembly view and the Design tab have no markers.
Three kinds of marker share one shape, a sphere whose octants alternate between two colors:
- each plate, black and white, for everything on it;
- each object instance, light blue and white;
- each body of an assembly, red and yellow;
- in Preview, the supports and raft of each object instance, green and black.
A click on a marker opens a box beside it with the weight and volume of what it stands for, where its
center lies in that thing's bounding box and the size of the box, and its moments of inertia about
axes through the center parallel to x, y and z.
An assembly is an object of several parts or with negative volumes. Its bodies are the connected
solids its parts make once united, the bodies the separated infills option centers its infill on
(see separated-infills.md): parts that overlap or touch are one body, parts apart are separate
bodies. An object of one body, and every object of a single part, has no body markers, as its object
marker says it all.
Mass is volume times density, the `filament_density` of the filament that prints it, or 1.245 g/cm³
(`DEFAULT_FILAMENT_DENSITY`) for a filament without one. Prepare has the model only, so it takes each
part as a solid of the density of its filament, the part's own or else its object's. It reads each
filament's density from the filament's selected preset, edits not yet saved included, as slicing does:
the plater's own config holds the values of the filament edited last only. Preview has
what will be printed, so its markers come from the toolpaths, whose mass depends on walls, infill
and flow as well. There the solid markers are for what is printed up to the top layer the layer
slider shows: for the plate with brim, raft and supports, where the weight rests at that point of
the print; for an object or a body, its own extrusions. Each has a faded twin for the same at the
end of the print, so the slider shows the weight moving toward where it ends, and at the top layer
the two meet. In Prepare the plate has the model alone, as brim, raft and supports exist only once
sliced.
## Prepare: from the meshes
An object of one part takes the mass properties of its mesh at unit density, times its density, from
`its_mass_properties()`, which handles a mesh in a single pass. Each triangle and a fixed vertex of
the mesh span a tetrahedron whose signed volume is `V = a · (b × c) / 6`, with `a`, `b`, `c` taken
relative to that vertex. By the divergence theorem these volumes add up to the volume of a closed
mesh, their volume-weighted centroids to its center of mass, and their second moments
`V (a aᵀ + b bᵀ + c cᵀ + s sᵀ) / 20`, with `s = a + b + c`, to its own. Shells facing inward, such as
a cavity, subtract themselves, and flipping every triangle changes nothing. The sums are kept in
double precision and relative to a vertex of the mesh rather than the origin, which keeps them exact
for meshes far from it. The result keeps the spread of the mass about its center,
`(x - c)(x - c)ᵀ` averaged over the mass, from which the moments of inertia follow.
The CGAL routines that look alike do not compute this. `CGAL::centroid` weighs tetrahedra by their
unsigned volume, so it fails on cavities and on any shell that is not star-shaped from the fan's
apex; over triangles it returns the centroid of the surface, and over points the average of the
vertices, which depends on the tessellation. `CGAL::barycenter` with the signed volumes as weights
gives the same answer, but only after copying every tetrahedron into a vector of weighted points,
and takes two and a half times as long.
A mesh's result is in its own coordinates. Each `GLVolume` maps it to the world with its world
matrix `M`, and weighs it by the volume times the absolute determinant of that matrix: a center of
mass moves with any affine map, and the spread becomes `L S Lᵀ` for the linear part `L` of `M`, so no
mesh is ever transformed. The `GLVolume`'s matrices, rather than the
model's, let the markers follow an object while it is dragged, before the model is updated. Results
are cached by `ModelVolume` id; a `ModelVolume` takes a new id whenever its mesh changes, which is
the rule `reload_scene()` relies on to rebuild a `GLVolume`'s geometry, so a cached result never
outlives its mesh.
An assembly's parts overlap or touch, which the mesh formula would count twice, so `solid_bodies()`
slices them instead, in the object's coordinates. It cuts the height into 500 slabs, 100 while a part
is dragged, slices every part and negative volume at the middle of each slab, unites the parts and
cuts the negative volumes away, and links the islands of neighboring slabs that overlap into bodies
with `connected_bodies()`. Each island adds a prism of the slab's thickness at the density: its
area, and its first and second moments of area, from the same sums over the outline as the area,
with the slab's height for z. Where parts of different densities overlap, the later volume of the object counts, as
slicing clips every part by the parts after it; each part then weighs the region it prints, which is
credited to the island holding it. Each body also keeps the convex hull of its islands and the height
they span, whose corners, once transformed, give its bounding box, tight while the instance turns
about z only. The object is the sum of its bodies, its box that of its parts, as the object's size
shows it, and each plate the sum of the object instances `PartPlateList::find_instance()` puts on it,
so that an instance on no plate counts in none. The bodies are cached by `ModelObject` id with the volumes, types, densities and
transformations they were sliced from.
## Preview: from the toolpaths
`GCodeProcessor` sums the masses while it processes the G-code, in the same pass that builds the
moves, and leaves them in `GCodeProcessorResult`; `GCodeViewer` keeps a copy of them when it loads a
result. Nothing is stored per move.
Each extrusion weighs the volume of filament its E extrudes times the density of the filament that
extrudes it, so a print of several materials weighs each as it is. Flow ratio, line widths, ironing
and purging into infill all count
as printed. Its mass spreads evenly along the segment the bead's center runs, half the layer height
below the nozzle, in the frame of the stored moves: plate offset added, Z offset removed. Such a
segment from `a` to `b` adds `m (a + b) / 2` to the moments and `m (a² + a b + b²) / 3` to the second
moments along each axis, and its box widened by half the bead's height to the bounding box; not by
half its width, which the processor only estimates, so that a box runs along the walls' center lines. Arcs are already split into segments by the processor. Walls, infill, top and bottom surfaces, ironing
and gap fill make the parts. The brim and the support roles, the raft among them, count only in what
the plate prints. The skirt, the prime tower and custom G-code count nowhere.
The plate takes every extrusion, so it needs nothing more. The objects and bodies need the sliced
objects, which the G-code does not describe, so the G-code export hands the processor a locator
built from the `Print`; G-code opened from a file, or from a project sliced earlier, has no `Print`
behind it, and so shows the plate alone. Object labels would not do: profiles turn the four kinds
Orca writes on and off in every combination, and none of them tells the bodies apart.
The locator numbers the object instances and, for each assembly, the bodies of every instance. It
takes the bodies `PrintObject::prepare_infill()` found for separated infills, or, when that option
did not need them, links the islands (`Layer::lslices`) of neighboring layers into bodies with the
same `connected_bodies()`. For each extrusion of a part, it finds the first layer printed at or above
its height, as spiral vase rises through each layer, and the island holding it with an
`IslandLocator`, the one `solid_bodies()` credits its regions with: by the island's box, widened by
1 mm for walls reaching past it, with a polygon test only where boxes overlap, and the nearest
outline where none holds the point. The boxes of one layer's islands say nothing of the other
instances, so an instance whose widened box reaches another's, as copies placed side by side do,
tests the outlines alone, and outside them the nearest outline of all such instances wins. The island
gives both the instance and the body. The island found last is tried first, as extrusions mostly follow each other on one
island. Brim, raft and supports lie outside the islands. The brim counts in the plate only; a support
or raft extrusion goes to the instance whose footprint, the box of its widened islands, holds it, the
one whose center is nearest among several, or else the nearest footprint, as supports stand below and
around their object.
Each mass holds, for each layer id, the running total of what is printed up to that layer, the last
of which is the faded marker's, so the solid marker for any slider position is a single lookup. The layer ids are those
the moves carry, which are also the layers of libvgcode and of the slider; in a print by object they
follow the order of printing, so the solid markers show the objects printed so far as they are.
## Drawing
`smooth_sphere()` with a resolution divisible by four leaves every triangle within one octant, so it
splits into two models drawn with the `gouraud_light` shader in each kind's two colors. The radius is
9 pixels for the plate, 7 for the objects, 6 for the supports and 5 for the bodies, scaled like the canvas toolbar for the
display's DPI and kept constant on screen through the camera's inverse zoom. They are drawn in that
order, so that markers at one place show as rings. The faded markers are the same spheres at 40%
opacity, drawn before all the solid ones, which show over them where both meet.
The centers usually lie inside the objects, so the markers are drawn without the depth test and show
through the objects and anything in front of them. Back face culling keeps the far half of a sphere
from covering the near one. They are drawn after the ambient occlusion pass, which would otherwise
darken them as the surface behind them, and before FXAA, which smooths their edges.
The markers are part of the cached scene, so toggling them, or changing a filament's density while
they are shown, marks the scene dirty, and moving the layer slider redraws the scene with the solid
markers where they belong. In Prepare they are hidden while any gizmo other than Move, Rotate, Scale
and Lay on face is open, since the others work on the surface a marker would cover, and a hidden
object has no markers.
## Details
The markers drawn last are kept, and a left click is tested against them before it selects: each
center and a point a radius to its right are projected to the screen, and the click hits a marker
within that distance. The solid markers are tested before the faded ones and the smaller kinds
before the larger, the order in which they cover each other. A hit opens the details of that marker
and keeps the click from changing the selection; a click anywhere else closes them. The box is an
ImGui window beside the marker, redrawn with the overlay from the markers of the last scene, so it
follows a dragged object, and in Preview the layer slider. It closes when its marker is gone, or when
the number of markers of its kind changes, as then it may stand for something else.
Its title says what the marker stands for: the plate, an object, an assembly or a part, the body of
an assembly. The G-code export lists the object instances for the processor, marking assemblies, as
it hands it the locator.
Each marker carries its sums: mass, volume, first moments and the second moments about the origin
along each axis, `Σ m x²`, `Σ m y²` and `Σ m z²`, which add up from parts to objects to plates. The
moment of inertia about the axis through the center parallel to x is then
`m (σy² + σz²)`, with `σ² = Σ m x² / m - c²` along each axis, and likewise for y and z. Masses are
kept in mg, volume times density in g/cm³, and shown in g, volumes in cm³ and moments of inertia in
g·mm². In Preview the box tells the finished print from what is printed up to the layer shown, the
two weighing differently, and both are placed in the bounding box of everything the marker holds
by the end.
+2 -2
View File
@@ -18,8 +18,8 @@ each body on its own (see Octree infill).
## Bodies
`PrintObject::prepare_infill()` groups the islands of every layer (`lslices`)
into 3D connected bodies before bridges are detected, so bridge anchors and
printed infill share one origin. Islands on adjacent layers belong to one body
into 3D connected bodies with `connected_bodies()` before bridges are detected,
so bridge anchors and printed infill share one origin. Islands on adjacent layers belong to one body
when their slices overlap. Parts that touch or overlap form one body. Separate
parts, disconnected islands of one mesh, and interleaved parts that never touch,
such as chain links, each form their own. Every island stores the index of its
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+2
View File
@@ -164,6 +164,7 @@ src/slic3r/GUI/OptionsGroup.cpp
src/slic3r/GUI/PrintOptionsDialog.cpp
src/slic3r/GUI/SafetyOptionsDialog.cpp
src/slic3r/GUI/ParamsPanel.cpp
src/slic3r/GUI/IMEXModesCtrl.cpp
src/slic3r/GUI/PartPlate.cpp
src/slic3r/GUI/Plater.cpp
src/slic3r/GUI/Preferences.cpp
@@ -212,6 +213,7 @@ src/libslic3r/ExtrusionEntity.cpp
src/libslic3r/Flow.cpp
src/libslic3r/Format/AMF.cpp
src/libslic3r/miniz_extension.cpp
src/libslic3r/IMEXHelpers.cpp
src/libslic3r/Preset.cpp
src/libslic3r/Print.cpp
src/libslic3r/PrintBase.cpp
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M5.5,14.5c-1.105,0-2-3.686-2-7s.895-7,2-7" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/><line x1="8.67" y1="4.67" x2="14.33" y2="10.33" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round"/><line x1="14.33" y1="4.67" x2="8.67" y2="10.33" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round"/><path d="M3.5,13.7c-.294.511-.636.8-1,.8-1.1,0-2-3.686-2-7s.9-7,2-7c.365,0,.707.293,1,.805" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><path d="M6.727,4.5c.334-2.208,1-4,1.773-4,.354,0,.686.378.974,1" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><path d="M9.474,13.5c-.288.622-.62,1-.974,1-.77,0-1.439-1.792-1.773-4" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/></svg>

After

Width:  |  Height:  |  Size: 931 B

+1 -3
View File
@@ -1,3 +1 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.2992 1.42627C10.7774 1.42627 8.73315 3.47056 8.73315 5.99231C8.73315 8.51406 10.7774 10.5583 13.2992 10.5583C13.3609 10.5583 13.4223 10.5571 13.4834 10.5547C15.9197 10.4581 17.8652 8.45238 17.8652 5.99231C17.8652 3.47056 15.8209 1.42627 13.2992 1.42627ZM15.6185 11.0536C17.5346 10.1741 18.8652 8.23861 18.8652 5.99231C18.8652 2.91827 16.3732 0.42627 13.2992 0.42627C11.5793 0.42627 10.0417 1.2063 9.02067 2.43188H2.31853C1.93193 2.43188 1.61853 2.74528 1.61853 3.13188V12.4712C1.61853 12.6313 1.6723 12.7789 1.76278 12.8968C1.76671 13.0046 1.79621 13.1144 1.85576 13.2185L3.48408 16.0649C3.60875 16.2829 3.8406 16.4173 4.09168 16.4173H6.75859L7.97903 19.1586C8.22556 19.7123 9.01148 19.7123 9.25801 19.1586L10.4784 16.4173H13.1454C13.3965 16.4173 13.6283 16.2829 13.753 16.0649L15.3813 13.2185C15.4409 13.1144 15.4704 13.0046 15.4743 12.8968C15.5648 12.7789 15.6185 12.6313 15.6185 12.4712V11.0536ZM13.4429 11.5565C13.8469 11.5463 14.2403 11.493 14.6185 11.4011V12.1709H2.61853V3.43188H8.35573C7.95792 4.19837 7.73315 5.06909 7.73315 5.99231C7.73315 9.06635 10.2252 11.5583 13.2992 11.5583C13.3472 11.5583 13.3951 11.5577 13.4429 11.5565ZM2.98076 13.1712H14.2563L12.9714 15.4173H9.85194L9.84578 15.4173H7.39126L7.3851 15.4173H4.26569L2.98076 13.1712ZM9.38381 16.4173L8.61852 18.1363L7.85322 16.4173H9.38381ZM12.2021 3.36422C12.3973 3.55948 12.3973 3.87606 12.2021 4.07133L11.743 4.53041H16.0625C16.3386 4.53041 16.5625 4.75426 16.5625 5.03041C16.5625 5.30655 16.3386 5.53041 16.0625 5.53041H10.7773C10.2428 5.53041 9.97507 4.88412 10.353 4.50614L11.495 3.36422C11.6902 3.16896 12.0068 3.16896 12.2021 3.36422ZM14.3963 7.91305C14.2011 8.10831 14.2011 8.42489 14.3963 8.62015C14.5916 8.81542 14.9082 8.81542 15.1034 8.62015L16.2453 7.47823C16.6233 7.10026 16.3556 6.45397 15.8211 6.45397L10.5359 6.45397C10.2597 6.45397 10.0359 6.67783 10.0359 6.95397C10.0359 7.23011 10.2597 7.45397 10.5359 7.45397L14.8554 7.45397L14.3963 7.91305Z" fill="#262E30"/>
</svg>
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M3.5,11.5v1l2,2,2-2v-1" style="fill:none; stroke:#949494; stroke-linecap:square; stroke-linejoin:round;"/><circle cx="10.5" cy="4.5" r="4" style="fill:none; stroke:#009688; stroke-linecap:round; stroke-linejoin:round;"/><polyline points="11.5 2.5 12.5 3.5 8.5 3.5" style="fill:none; stroke:#009688; stroke-linecap:round; stroke-linejoin:round;"/><polyline points="12.5 5.5 8.5 5.5 9.5 6.5" style="fill:none; stroke:#009688; stroke-linecap:square; stroke-linejoin:round;"/><path d="M5.5.5H1.5C.95.5.5.95.5,1.5v9c0,.55.45,1,1,1h8c.55,0,1-.45,1-1" style="fill:none; stroke:#949494; stroke-linecap:square; stroke-linejoin:round;"/></svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 747 B

+1 -10
View File
@@ -1,10 +1 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="1.73486" y="1" width="10.53" height="5.12169" fill="#C2C2C2"/>
<rect x="1.73486" y="1" width="10.53" height="0.497608" fill="#858585"/>
<rect x="1.73486" y="2.70703" width="10.53" height="0.497608" fill="#858585"/>
<rect x="1.73486" y="4.41602" width="10.53" height="0.497608" fill="#858585"/>
<rect x="1.73486" y="6.12305" width="10.53" height="0.497608" fill="#858585"/>
<path d="M6.10107 11.9961H7.77047L7.30675 12.9991H6.61117L6.10107 11.9961Z" fill="#8C8C8C"/>
<rect x="4.1748" y="6.62109" width="5.65026" height="5.3765" fill="#646464"/>
<rect x="6.61475" y="6.62109" width="0.642075" height="5.35947" fill="#1D1D1D"/>
</svg>
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M3.5,3.5h8M3.5,5.5h8M3.5,1.5h8M6.5,5.5v7l1,1,1-1v-7" style="fill:none; stroke:#949494; stroke-linecap:square; stroke-linejoin:round;"/></svg>

Before

Width:  |  Height:  |  Size: 737 B

After

Width:  |  Height:  |  Size: 255 B

@@ -0,0 +1,7 @@
<svg width="43" height="43" xmlns="http://www.w3.org/2000/svg">
<rect fill="#e9e9e9" rx="3.5" height="40" width="40" y="1.5" x="1.5"/>
<path stroke-width="1.0" fill="#545454" d="m38,2a3,3 0 0 1 3,3l0,33a3,3 0 0 1 -3,3l-33,0a3,3 0 0 1 -3,-3l0,-33a3,3 0 0 1 3,-3l33,0m0,-1l-33,0a4,4 0 0 0 -4,4l0,33a4,4 0 0 0 4,4l33,0a4,4 0 0 0 4,-4l0,-33a4,4 0 0 0 -4,-4z"/>
<path stroke-linecap="round" stroke-width="0.8" stroke="#545454" fill="#545454" d="m21.5275,7.14657l0,28.59908"/>
<path stroke-width="1.0" stroke="#545454" fill-opacity="0" fill="#ffffff" fill-rule="nonzero" d="m14.5112,25.5943l-0.632,0l0.632,-1.1115l-0.0305,-1.3114l-0.6445,-1.103l-0.7745,-0.632l-0.3502,-0.7142l-0.9981,-1.8354l1.4221,0.9295l1.6267,0.1116l0.8272,0.5112l0.9667,-0.4926l0.2509,-1.3664l-1.0317,-0.5763l-0.9945,-1.5429l0.1999,-0.7236l-1.1015,-1.0425l0.4275,-1.608l-0.7064,-0.0651l-0.9388,0.7157l-1.8125,0.1674l-1.71896,0.9102l-0.81298,1.0906l-0.77183,2.014l-0.22958,2.4972l0.25693,2.9999l0.89251,0.5883l0.21446,1.5893l-0.6172,0l-0.66057,1.2606l-0.51219,1.9929l8.95941,0l-0.5289,-1.9929l-0.8089,-1.2606l0,0z"/>
<path stroke-dasharray="1,2" stroke-linecap="round" stroke-width="1.0" stroke="#545454" fill-opacity="0" fill="#ffffff" fill-rule="nonzero" d="m34.77509,25.5943l-0.632,0l0.632,-1.1115l-0.0305,-1.3114l-0.6445,-1.103l-0.7745,-0.632l-0.3502,-0.7142l-0.9981,-1.8354l1.4221,0.9295l1.6267,0.1116l0.8272,0.5112l0.9667,-0.4926l0.2509,-1.3664l-1.0317,-0.5763l-0.9945,-1.5429l0.1999,-0.7236l-1.1015,-1.0425l0.4275,-1.608l-0.7064,-0.0651l-0.9388,0.7157l-1.8125,0.1674l-1.719,0.9102l-0.8129,1.0906l-0.7719,2.014l-0.2296,2.4972l0.257,2.9999l0.8925,0.5883l0.2145,1.5893l-0.6173,0l-0.6605,1.2606l-0.5122,1.9929l8.9594,0l-0.5289,-1.9929l-0.8089,-1.2606l0,0z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1,7 @@
<svg width="43" height="43" xmlns="http://www.w3.org/2000/svg">
<rect fill="#54545a" rx="3.5" height="40" width="40" y="1.5" x="1.5"/>
<path stroke-width="1.0" fill="#b3b3b3" d="m38,2a3,3 0 0 1 3,3l0,33a3,3 0 0 1 -3,3l-33,0a3,3 0 0 1 -3,-3l0,-33a3,3 0 0 1 3,-3l33,0m0,-1l-33,0a4,4 0 0 0 -4,4l0,33a4,4 0 0 0 4,4l33,0a4,4 0 0 0 4,-4l0,-33a4,4 0 0 0 -4,-4z"/>
<path stroke-linecap="round" stroke-width="0.8" stroke="#c4c4c4" fill="#c4c4c4" d="m21.5275,7.14657l0,28.59908"/>
<path stroke-width="1.0" stroke="#c4c4c4" fill-opacity="0" fill="#ffffff" fill-rule="nonzero" d="m14.5112,25.5943l-0.632,0l0.632,-1.1115l-0.0305,-1.3114l-0.6445,-1.103l-0.7745,-0.632l-0.3502,-0.7142l-0.9981,-1.8354l1.4221,0.9295l1.6267,0.1116l0.8272,0.5112l0.9667,-0.4926l0.2509,-1.3664l-1.0317,-0.5763l-0.9945,-1.5429l0.1999,-0.7236l-1.1015,-1.0425l0.4275,-1.608l-0.7064,-0.0651l-0.9388,0.7157l-1.8125,0.1674l-1.71896,0.9102l-0.81298,1.0906l-0.77183,2.014l-0.22958,2.4972l0.25693,2.9999l0.89251,0.5883l0.21446,1.5893l-0.6172,0l-0.66057,1.2606l-0.51219,1.9929l8.95941,0l-0.5289,-1.9929l-0.8089,-1.2606l0,0z"/>
<path stroke-dasharray="1,2" stroke-linecap="round" stroke-width="1.0" stroke="#c4c4c4" fill-opacity="0" fill="#ffffff" fill-rule="nonzero" d="m34.77509,25.5943l-0.632,0l0.632,-1.1115l-0.0305,-1.3114l-0.6445,-1.103l-0.7745,-0.632l-0.3502,-0.7142l-0.9981,-1.8354l1.4221,0.9295l1.6267,0.1116l0.8272,0.5112l0.9667,-0.4926l0.2509,-1.3664l-1.0317,-0.5763l-0.9945,-1.5429l0.1999,-0.7236l-1.1015,-1.0425l0.4275,-1.608l-0.7064,-0.0651l-0.9388,0.7157l-1.8125,0.1674l-1.719,0.9102l-0.8129,1.0906l-0.7719,2.014l-0.2296,2.4972l0.257,2.9999l0.8925,0.5883l0.2145,1.5893l-0.6173,0l-0.6605,1.2606l-0.5122,1.9929l8.9594,0l-0.5289,-1.9929l-0.8089,-1.2606l0,0z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1,7 @@
<svg width="43" height="43" xmlns="http://www.w3.org/2000/svg">
<rect fill="#e9e9e9" rx="3.5" height="40" width="40" x="1.5" y="1.5"/>
<path stroke-width="1.0" fill="#009688" d="m38,2a3,3 0 0 1 3,3l0,33a3,3 0 0 1 -3,3l-33,0a3,3 0 0 1 -3,-3l0,-33a3,3 0 0 1 3,-3l33,0m0,-1l-33,0a4,4 0 0 0 -4,4l0,33a4,4 0 0 0 4,4l33,0a4,4 0 0 0 4,-4l0,-33a4,4 0 0 0 -4,-4z"/>
<path stroke-linecap="round" stroke-width="0.8" stroke="#009688" fill="#009688" d="m21.5275,7.14657l0,28.59908"/>
<path stroke-width="1.0" stroke="#009688" fill-opacity="0" fill="#ffffff" fill-rule="nonzero" d="m14.5112,25.5943l-0.632,0l0.632,-1.1115l-0.0305,-1.3114l-0.6445,-1.103l-0.7745,-0.632l-0.3502,-0.7142l-0.9981,-1.8354l1.4221,0.9295l1.6267,0.1116l0.8272,0.5112l0.9667,-0.4926l0.2509,-1.3664l-1.0317,-0.5763l-0.9945,-1.5429l0.1999,-0.7236l-1.1015,-1.0425l0.4275,-1.608l-0.7064,-0.0651l-0.9388,0.7157l-1.8125,0.1674l-1.71896,0.9102l-0.81298,1.0906l-0.77183,2.014l-0.22958,2.4972l0.25693,2.9999l0.89251,0.5883l0.21446,1.5893l-0.6172,0l-0.66057,1.2606l-0.51219,1.9929l8.95941,0l-0.5289,-1.9929l-0.8089,-1.2606l0,0z"/>
<path stroke-dasharray="1,2" stroke-linecap="round" stroke-width="1.0" stroke="#009688" fill-opacity="0" fill="#ffffff" fill-rule="nonzero" d="m34.77509,25.5943l-0.632,0l0.632,-1.1115l-0.0305,-1.3114l-0.6445,-1.103l-0.7745,-0.632l-0.3502,-0.7142l-0.9981,-1.8354l1.4221,0.9295l1.6267,0.1116l0.8272,0.5112l0.9667,-0.4926l0.2509,-1.3664l-1.0317,-0.5763l-0.9945,-1.5429l0.1999,-0.7236l-1.1015,-1.0425l0.4275,-1.608l-0.7064,-0.0651l-0.9388,0.7157l-1.8125,0.1674l-1.719,0.9102l-0.8129,1.0906l-0.7719,2.014l-0.2296,2.4972l0.257,2.9999l0.8925,0.5883l0.2145,1.5893l-0.6173,0l-0.6605,1.2606l-0.5122,1.9929l8.9594,0l-0.5289,-1.9929l-0.8089,-1.2606l0,0z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1,7 @@
<svg width="43" height="43" xmlns="http://www.w3.org/2000/svg">
<rect fill="#54545a" rx="3.5" height="40" width="40" y="1.5" x="1.5"/>
<path stroke-width="1.0" fill="#009688" d="m38,2a3,3 0 0 1 3,3l0,33a3,3 0 0 1 -3,3l-33,0a3,3 0 0 1 -3,-3l0,-33a3,3 0 0 1 3,-3l33,0m0,-1l-33,0a4,4 0 0 0 -4,4l0,33a4,4 0 0 0 4,4l33,0a4,4 0 0 0 4,-4l0,-33a4,4 0 0 0 -4,-4z"/>
<path stroke-linecap="round" stroke-width="0.8" stroke="#009688" fill="#009688" d="m21.5275,7.14657l0,28.59908"/>
<path stroke-width="1.0" stroke="#009688" fill-opacity="0" fill="#ffffff" fill-rule="nonzero" d="m14.5112,25.5943l-0.632,0l0.632,-1.1115l-0.0305,-1.3114l-0.6445,-1.103l-0.7745,-0.632l-0.3502,-0.7142l-0.9981,-1.8354l1.4221,0.9295l1.6267,0.1116l0.8272,0.5112l0.9667,-0.4926l0.2509,-1.3664l-1.0317,-0.5763l-0.9945,-1.5429l0.1999,-0.7236l-1.1015,-1.0425l0.4275,-1.608l-0.7064,-0.0651l-0.9388,0.7157l-1.8125,0.1674l-1.71896,0.9102l-0.81298,1.0906l-0.77183,2.014l-0.22958,2.4972l0.25693,2.9999l0.89251,0.5883l0.21446,1.5893l-0.6172,0l-0.66057,1.2606l-0.51219,1.9929l8.95941,0l-0.5289,-1.9929l-0.8089,-1.2606l0,0z"/>
<path stroke-dasharray="1,2" stroke-linecap="round" stroke-width="1.0" stroke="#009688" fill-opacity="0" fill="#ffffff" fill-rule="nonzero" d="m34.77509,25.5943l-0.632,0l0.632,-1.1115l-0.0305,-1.3114l-0.6445,-1.103l-0.7745,-0.632l-0.3502,-0.7142l-0.9981,-1.8354l1.4221,0.9295l1.6267,0.1116l0.8272,0.5112l0.9667,-0.4926l0.2509,-1.3664l-1.0317,-0.5763l-0.9945,-1.5429l0.1999,-0.7236l-1.1015,-1.0425l0.4275,-1.608l-0.7064,-0.0651l-0.9388,0.7157l-1.8125,0.1674l-1.719,0.9102l-0.8129,1.0906l-0.7719,2.014l-0.2296,2.4972l0.257,2.9999l0.8925,0.5883l0.2145,1.5893l-0.6173,0l-0.6605,1.2606l-0.5122,1.9929l8.9594,0l-0.5289,-1.9929l-0.8089,-1.2606l0,0z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1,10 @@
<svg width="43" height="43" xmlns="http://www.w3.org/2000/svg">
<rect fill="#e9e9e9" rx="3.5" height="40" width="40" y="1.5" x="1.5"/>
<path stroke-width="1.0" fill="#545454" d="m38,2a3,3 0 0 1 3,3l0,33a3,3 0 0 1 -3,3l-33,0a3,3 0 0 1 -3,-3l0,-33a3,3 0 0 1 3,-3l33,0m0,-1l-33,0a4,4 0 0 0 -4,4l0,33a4,4 0 0 0 4,4l33,0a4,4 0 0 0 4,-4l0,-33a4,4 0 0 0 -4,-4z"/>
<path stroke-linecap="round" stroke-width="0.8" stroke="#545454" fill="#545454" d="m21.5275,5.5l0,31.9"/>
<path stroke-linecap="round" stroke-width="0.8" stroke="#545454" fill="#545454" d="m6.7,21.575891l29.2,0"/>
<path stroke-dasharray="1,2" stroke-linecap="round" stroke-width="1" stroke="#545454" fill-opacity="0" fill="#ffffff" fill-rule="nonzero" d="m28.4888,25.5943l0.632,0l-0.632,-1.1115l0.0305,-1.3114l0.6445,-1.103l0.7745,-0.632l0.3502,-0.7142l0.9981,-1.8354l-1.4221,0.9295l-1.6267,0.1116l-0.8272,0.5112l-0.9667,-0.4926l-0.2509,-1.3664l1.0317,-0.5763l0.9945,-1.5429l-0.1999,-0.7236l1.1015,-1.0425l-0.4275,-1.608l0.7064,-0.0651l0.9388,0.7157l1.8125,0.1674l1.719,0.9102l0.8129,1.0906l0.7719,2.014l0.2296,2.4972l-0.257,2.9999l-0.8925,0.5883l-0.2145,1.5893l0.6173,0l0.6605,1.2606l0.5122,1.9929l-8.9594,0l0.5289,-1.9929l0.8089,-1.2606l0,0z" transform="translate(11.75,11.75) scale(0.7) translate(-31.15,-20.94)"/>
<path stroke-dasharray="1,2" stroke-linecap="round" stroke-width="1" stroke="#545454" fill-opacity="0" fill="#ffffff" fill-rule="nonzero" d="m28.4888,25.5943l0.632,0l-0.632,-1.1115l0.0305,-1.3114l0.6445,-1.103l0.7745,-0.632l0.3502,-0.7142l0.9981,-1.8354l-1.4221,0.9295l-1.6267,0.1116l-0.8272,0.5112l-0.9667,-0.4926l-0.2509,-1.3664l1.0317,-0.5763l0.9945,-1.5429l-0.1999,-0.7236l1.1015,-1.0425l-0.4275,-1.608l0.7064,-0.0651l0.9388,0.7157l1.8125,0.1674l1.719,0.9102l0.8129,1.0906l0.7719,2.014l0.2296,2.4972l-0.257,2.9999l-0.8925,0.5883l-0.2145,1.5893l0.6173,0l0.6605,1.2606l0.5122,1.9929l-8.9594,0l0.5289,-1.9929l0.8089,-1.2606l0,0z" transform="translate(31.25,11.75) scale(-0.7,0.7) translate(-31.15,-20.94)"/>
<path stroke-width="1" stroke="#545454" fill-opacity="0" fill="#ffffff" fill-rule="nonzero" d="m14.5112,25.5943l-0.632,0l0.632,-1.1115l-0.0305,-1.3114l-0.6445,-1.103l-0.7745,-0.632l-0.3502,-0.7142l-0.9981,-1.8354l1.4221,0.9295l1.6267,0.1116l0.8272,0.5112l0.9667,-0.4926l0.2509,-1.3664l-1.0317,-0.5763l-0.9945,-1.5429l0.1999,-0.7236l-1.1015,-1.0425l0.4275,-1.608l-0.7064,-0.0651l-0.9388,0.7157l-1.8125,0.1674l-1.71896,0.9102l-0.81298,1.0906l-0.77183,2.014l-0.22958,2.4972l0.25693,2.9999l0.89251,0.5883l0.21446,1.5893l-0.6172,0l-0.66057,1.2606l-0.51219,1.9929l8.95941,0l-0.5289,-1.9929l-0.8089,-1.2606l0,0z" transform="translate(11.75,31.25) scale(0.7) translate(-11.85,-20.93)"/>
<path stroke-dasharray="1,2" stroke-linecap="round" stroke-width="1" stroke="#545454" fill-opacity="0" fill="#ffffff" fill-rule="nonzero" d="m28.4888,25.5943l0.632,0l-0.632,-1.1115l0.0305,-1.3114l0.6445,-1.103l0.7745,-0.632l0.3502,-0.7142l0.9981,-1.8354l-1.4221,0.9295l-1.6267,0.1116l-0.8272,0.5112l-0.9667,-0.4926l-0.2509,-1.3664l1.0317,-0.5763l0.9945,-1.5429l-0.1999,-0.7236l1.1015,-1.0425l-0.4275,-1.608l0.7064,-0.0651l0.9388,0.7157l1.8125,0.1674l1.719,0.9102l0.8129,1.0906l0.7719,2.014l0.2296,2.4972l-0.257,2.9999l-0.8925,0.5883l-0.2145,1.5893l0.6173,0l0.6605,1.2606l0.5122,1.9929l-8.9594,0l0.5289,-1.9929l0.8089,-1.2606l0,0z" transform="translate(31.25,31.25) scale(0.7) translate(-31.15,-20.94)"/>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

@@ -0,0 +1,10 @@
<svg width="43" height="43" xmlns="http://www.w3.org/2000/svg">
<rect fill="#54545a" rx="3.5" height="40" width="40" y="1.5" x="1.5"/>
<path stroke-width="1.0" fill="#b3b3b3" d="m38,2a3,3 0 0 1 3,3l0,33a3,3 0 0 1 -3,3l-33,0a3,3 0 0 1 -3,-3l0,-33a3,3 0 0 1 3,-3l33,0m0,-1l-33,0a4,4 0 0 0 -4,4l0,33a4,4 0 0 0 4,4l33,0a4,4 0 0 0 4,-4l0,-33a4,4 0 0 0 -4,-4z"/>
<path stroke-linecap="round" stroke-width="0.8" stroke="#c4c4c4" fill="#c4c4c4" d="m21.5275,5.5l0,31.9"/>
<path stroke-linecap="round" stroke-width="0.8" stroke="#c4c4c4" fill="#c4c4c4" d="m6.7,21.575891l29.2,0"/>
<path stroke-dasharray="1,2" stroke-linecap="round" stroke-width="1" stroke="#c4c4c4" fill-opacity="0" fill="#ffffff" fill-rule="nonzero" d="m28.4888,25.5943l0.632,0l-0.632,-1.1115l0.0305,-1.3114l0.6445,-1.103l0.7745,-0.632l0.3502,-0.7142l0.9981,-1.8354l-1.4221,0.9295l-1.6267,0.1116l-0.8272,0.5112l-0.9667,-0.4926l-0.2509,-1.3664l1.0317,-0.5763l0.9945,-1.5429l-0.1999,-0.7236l1.1015,-1.0425l-0.4275,-1.608l0.7064,-0.0651l0.9388,0.7157l1.8125,0.1674l1.719,0.9102l0.8129,1.0906l0.7719,2.014l0.2296,2.4972l-0.257,2.9999l-0.8925,0.5883l-0.2145,1.5893l0.6173,0l0.6605,1.2606l0.5122,1.9929l-8.9594,0l0.5289,-1.9929l0.8089,-1.2606l0,0z" transform="translate(11.75,11.75) scale(0.7) translate(-31.15,-20.94)"/>
<path stroke-dasharray="1,2" stroke-linecap="round" stroke-width="1" stroke="#c4c4c4" fill-opacity="0" fill="#ffffff" fill-rule="nonzero" d="m28.4888,25.5943l0.632,0l-0.632,-1.1115l0.0305,-1.3114l0.6445,-1.103l0.7745,-0.632l0.3502,-0.7142l0.9981,-1.8354l-1.4221,0.9295l-1.6267,0.1116l-0.8272,0.5112l-0.9667,-0.4926l-0.2509,-1.3664l1.0317,-0.5763l0.9945,-1.5429l-0.1999,-0.7236l1.1015,-1.0425l-0.4275,-1.608l0.7064,-0.0651l0.9388,0.7157l1.8125,0.1674l1.719,0.9102l0.8129,1.0906l0.7719,2.014l0.2296,2.4972l-0.257,2.9999l-0.8925,0.5883l-0.2145,1.5893l0.6173,0l0.6605,1.2606l0.5122,1.9929l-8.9594,0l0.5289,-1.9929l0.8089,-1.2606l0,0z" transform="translate(31.25,11.75) scale(-0.7,0.7) translate(-31.15,-20.94)"/>
<path stroke-width="1" stroke="#c4c4c4" fill-opacity="0" fill="#ffffff" fill-rule="nonzero" d="m14.5112,25.5943l-0.632,0l0.632,-1.1115l-0.0305,-1.3114l-0.6445,-1.103l-0.7745,-0.632l-0.3502,-0.7142l-0.9981,-1.8354l1.4221,0.9295l1.6267,0.1116l0.8272,0.5112l0.9667,-0.4926l0.2509,-1.3664l-1.0317,-0.5763l-0.9945,-1.5429l0.1999,-0.7236l-1.1015,-1.0425l0.4275,-1.608l-0.7064,-0.0651l-0.9388,0.7157l-1.8125,0.1674l-1.71896,0.9102l-0.81298,1.0906l-0.77183,2.014l-0.22958,2.4972l0.25693,2.9999l0.89251,0.5883l0.21446,1.5893l-0.6172,0l-0.66057,1.2606l-0.51219,1.9929l8.95941,0l-0.5289,-1.9929l-0.8089,-1.2606l0,0z" transform="translate(11.75,31.25) scale(0.7) translate(-11.85,-20.93)"/>
<path stroke-dasharray="1,2" stroke-linecap="round" stroke-width="1" stroke="#c4c4c4" fill-opacity="0" fill="#ffffff" fill-rule="nonzero" d="m28.4888,25.5943l0.632,0l-0.632,-1.1115l0.0305,-1.3114l0.6445,-1.103l0.7745,-0.632l0.3502,-0.7142l0.9981,-1.8354l-1.4221,0.9295l-1.6267,0.1116l-0.8272,0.5112l-0.9667,-0.4926l-0.2509,-1.3664l1.0317,-0.5763l0.9945,-1.5429l-0.1999,-0.7236l1.1015,-1.0425l-0.4275,-1.608l0.7064,-0.0651l0.9388,0.7157l1.8125,0.1674l1.719,0.9102l0.8129,1.0906l0.7719,2.014l0.2296,2.4972l-0.257,2.9999l-0.8925,0.5883l-0.2145,1.5893l0.6173,0l0.6605,1.2606l0.5122,1.9929l-8.9594,0l0.5289,-1.9929l0.8089,-1.2606l0,0z" transform="translate(31.25,31.25) scale(0.7) translate(-31.15,-20.94)"/>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

@@ -0,0 +1,10 @@
<svg width="43" height="43" xmlns="http://www.w3.org/2000/svg">
<rect fill="#e9e9e9" rx="3.5" height="40" width="40" y="1.5" x="1.5"/>
<path stroke-width="1.0" fill="#009688" d="m38,2a3,3 0 0 1 3,3l0,33a3,3 0 0 1 -3,3l-33,0a3,3 0 0 1 -3,-3l0,-33a3,3 0 0 1 3,-3l33,0m0,-1l-33,0a4,4 0 0 0 -4,4l0,33a4,4 0 0 0 4,4l33,0a4,4 0 0 0 4,-4l0,-33a4,4 0 0 0 -4,-4z"/>
<path stroke-linecap="round" stroke-width="0.8" stroke="#009688" fill="#009688" d="m21.5275,5.5l0,31.9"/>
<path stroke-linecap="round" stroke-width="0.8" stroke="#009688" fill="#009688" d="m6.7,21.575891l29.2,0"/>
<path stroke-dasharray="1,2" stroke-linecap="round" stroke-width="1" stroke="#009688" fill-opacity="0" fill="#ffffff" fill-rule="nonzero" d="m28.4888,25.5943l0.632,0l-0.632,-1.1115l0.0305,-1.3114l0.6445,-1.103l0.7745,-0.632l0.3502,-0.7142l0.9981,-1.8354l-1.4221,0.9295l-1.6267,0.1116l-0.8272,0.5112l-0.9667,-0.4926l-0.2509,-1.3664l1.0317,-0.5763l0.9945,-1.5429l-0.1999,-0.7236l1.1015,-1.0425l-0.4275,-1.608l0.7064,-0.0651l0.9388,0.7157l1.8125,0.1674l1.719,0.9102l0.8129,1.0906l0.7719,2.014l0.2296,2.4972l-0.257,2.9999l-0.8925,0.5883l-0.2145,1.5893l0.6173,0l0.6605,1.2606l0.5122,1.9929l-8.9594,0l0.5289,-1.9929l0.8089,-1.2606l0,0z" transform="translate(11.75,11.75) scale(0.7) translate(-31.15,-20.94)"/>
<path stroke-dasharray="1,2" stroke-linecap="round" stroke-width="1" stroke="#009688" fill-opacity="0" fill="#ffffff" fill-rule="nonzero" d="m28.4888,25.5943l0.632,0l-0.632,-1.1115l0.0305,-1.3114l0.6445,-1.103l0.7745,-0.632l0.3502,-0.7142l0.9981,-1.8354l-1.4221,0.9295l-1.6267,0.1116l-0.8272,0.5112l-0.9667,-0.4926l-0.2509,-1.3664l1.0317,-0.5763l0.9945,-1.5429l-0.1999,-0.7236l1.1015,-1.0425l-0.4275,-1.608l0.7064,-0.0651l0.9388,0.7157l1.8125,0.1674l1.719,0.9102l0.8129,1.0906l0.7719,2.014l0.2296,2.4972l-0.257,2.9999l-0.8925,0.5883l-0.2145,1.5893l0.6173,0l0.6605,1.2606l0.5122,1.9929l-8.9594,0l0.5289,-1.9929l0.8089,-1.2606l0,0z" transform="translate(31.25,11.75) scale(-0.7,0.7) translate(-31.15,-20.94)"/>
<path stroke-width="1" stroke="#009688" fill-opacity="0" fill="#ffffff" fill-rule="nonzero" d="m14.5112,25.5943l-0.632,0l0.632,-1.1115l-0.0305,-1.3114l-0.6445,-1.103l-0.7745,-0.632l-0.3502,-0.7142l-0.9981,-1.8354l1.4221,0.9295l1.6267,0.1116l0.8272,0.5112l0.9667,-0.4926l0.2509,-1.3664l-1.0317,-0.5763l-0.9945,-1.5429l0.1999,-0.7236l-1.1015,-1.0425l0.4275,-1.608l-0.7064,-0.0651l-0.9388,0.7157l-1.8125,0.1674l-1.71896,0.9102l-0.81298,1.0906l-0.77183,2.014l-0.22958,2.4972l0.25693,2.9999l0.89251,0.5883l0.21446,1.5893l-0.6172,0l-0.66057,1.2606l-0.51219,1.9929l8.95941,0l-0.5289,-1.9929l-0.8089,-1.2606l0,0z" transform="translate(11.75,31.25) scale(0.7) translate(-11.85,-20.93)"/>
<path stroke-dasharray="1,2" stroke-linecap="round" stroke-width="1" stroke="#009688" fill-opacity="0" fill="#ffffff" fill-rule="nonzero" d="m28.4888,25.5943l0.632,0l-0.632,-1.1115l0.0305,-1.3114l0.6445,-1.103l0.7745,-0.632l0.3502,-0.7142l0.9981,-1.8354l-1.4221,0.9295l-1.6267,0.1116l-0.8272,0.5112l-0.9667,-0.4926l-0.2509,-1.3664l1.0317,-0.5763l0.9945,-1.5429l-0.1999,-0.7236l1.1015,-1.0425l-0.4275,-1.608l0.7064,-0.0651l0.9388,0.7157l1.8125,0.1674l1.719,0.9102l0.8129,1.0906l0.7719,2.014l0.2296,2.4972l-0.257,2.9999l-0.8925,0.5883l-0.2145,1.5893l0.6173,0l0.6605,1.2606l0.5122,1.9929l-8.9594,0l0.5289,-1.9929l0.8089,-1.2606l0,0z" transform="translate(31.25,31.25) scale(0.7) translate(-31.15,-20.94)"/>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

@@ -0,0 +1,10 @@
<svg width="43" height="43" xmlns="http://www.w3.org/2000/svg">
<rect fill="#54545a" rx="3.5" height="40" width="40" y="1.5" x="1.5"/>
<path stroke-width="1.0" fill="#009688" d="m38,2a3,3 0 0 1 3,3l0,33a3,3 0 0 1 -3,3l-33,0a3,3 0 0 1 -3,-3l0,-33a3,3 0 0 1 3,-3l33,0m0,-1l-33,0a4,4 0 0 0 -4,4l0,33a4,4 0 0 0 4,4l33,0a4,4 0 0 0 4,-4l0,-33a4,4 0 0 0 -4,-4z"/>
<path stroke-linecap="round" stroke-width="0.8" stroke="#009688" fill="#009688" d="m21.5275,5.5l0,31.9"/>
<path stroke-linecap="round" stroke-width="0.8" stroke="#009688" fill="#009688" d="m6.7,21.575891l29.2,0"/>
<path stroke-dasharray="1,2" stroke-linecap="round" stroke-width="1" stroke="#009688" fill-opacity="0" fill="#ffffff" fill-rule="nonzero" d="m28.4888,25.5943l0.632,0l-0.632,-1.1115l0.0305,-1.3114l0.6445,-1.103l0.7745,-0.632l0.3502,-0.7142l0.9981,-1.8354l-1.4221,0.9295l-1.6267,0.1116l-0.8272,0.5112l-0.9667,-0.4926l-0.2509,-1.3664l1.0317,-0.5763l0.9945,-1.5429l-0.1999,-0.7236l1.1015,-1.0425l-0.4275,-1.608l0.7064,-0.0651l0.9388,0.7157l1.8125,0.1674l1.719,0.9102l0.8129,1.0906l0.7719,2.014l0.2296,2.4972l-0.257,2.9999l-0.8925,0.5883l-0.2145,1.5893l0.6173,0l0.6605,1.2606l0.5122,1.9929l-8.9594,0l0.5289,-1.9929l0.8089,-1.2606l0,0z" transform="translate(11.75,11.75) scale(0.7) translate(-31.15,-20.94)"/>
<path stroke-dasharray="1,2" stroke-linecap="round" stroke-width="1" stroke="#009688" fill-opacity="0" fill="#ffffff" fill-rule="nonzero" d="m28.4888,25.5943l0.632,0l-0.632,-1.1115l0.0305,-1.3114l0.6445,-1.103l0.7745,-0.632l0.3502,-0.7142l0.9981,-1.8354l-1.4221,0.9295l-1.6267,0.1116l-0.8272,0.5112l-0.9667,-0.4926l-0.2509,-1.3664l1.0317,-0.5763l0.9945,-1.5429l-0.1999,-0.7236l1.1015,-1.0425l-0.4275,-1.608l0.7064,-0.0651l0.9388,0.7157l1.8125,0.1674l1.719,0.9102l0.8129,1.0906l0.7719,2.014l0.2296,2.4972l-0.257,2.9999l-0.8925,0.5883l-0.2145,1.5893l0.6173,0l0.6605,1.2606l0.5122,1.9929l-8.9594,0l0.5289,-1.9929l0.8089,-1.2606l0,0z" transform="translate(31.25,11.75) scale(-0.7,0.7) translate(-31.15,-20.94)"/>
<path stroke-width="1" stroke="#009688" fill-opacity="0" fill="#ffffff" fill-rule="nonzero" d="m14.5112,25.5943l-0.632,0l0.632,-1.1115l-0.0305,-1.3114l-0.6445,-1.103l-0.7745,-0.632l-0.3502,-0.7142l-0.9981,-1.8354l1.4221,0.9295l1.6267,0.1116l0.8272,0.5112l0.9667,-0.4926l0.2509,-1.3664l-1.0317,-0.5763l-0.9945,-1.5429l0.1999,-0.7236l-1.1015,-1.0425l0.4275,-1.608l-0.7064,-0.0651l-0.9388,0.7157l-1.8125,0.1674l-1.71896,0.9102l-0.81298,1.0906l-0.77183,2.014l-0.22958,2.4972l0.25693,2.9999l0.89251,0.5883l0.21446,1.5893l-0.6172,0l-0.66057,1.2606l-0.51219,1.9929l8.95941,0l-0.5289,-1.9929l-0.8089,-1.2606l0,0z" transform="translate(11.75,31.25) scale(0.7) translate(-11.85,-20.93)"/>
<path stroke-dasharray="1,2" stroke-linecap="round" stroke-width="1" stroke="#009688" fill-opacity="0" fill="#ffffff" fill-rule="nonzero" d="m28.4888,25.5943l0.632,0l-0.632,-1.1115l0.0305,-1.3114l0.6445,-1.103l0.7745,-0.632l0.3502,-0.7142l0.9981,-1.8354l-1.4221,0.9295l-1.6267,0.1116l-0.8272,0.5112l-0.9667,-0.4926l-0.2509,-1.3664l1.0317,-0.5763l0.9945,-1.5429l-0.1999,-0.7236l1.1015,-1.0425l-0.4275,-1.608l0.7064,-0.0651l0.9388,0.7157l1.8125,0.1674l1.719,0.9102l0.8129,1.0906l0.7719,2.014l0.2296,2.4972l-0.257,2.9999l-0.8925,0.5883l-0.2145,1.5893l0.6173,0l0.6605,1.2606l0.5122,1.9929l-8.9594,0l0.5289,-1.9929l0.8089,-1.2606l0,0z" transform="translate(31.25,31.25) scale(0.7) translate(-31.15,-20.94)"/>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

@@ -0,0 +1,7 @@
<svg width="43" height="43" xmlns="http://www.w3.org/2000/svg">
<rect fill="#e9e9e9" rx="3.5" height="40" width="40" y="1.5" x="1.5"/>
<path stroke-width="1.0" fill="#545454" d="m38,2a3,3 0 0 1 3,3l0,33a3,3 0 0 1 -3,3l-33,0a3,3 0 0 1 -3,-3l0,-33a3,3 0 0 1 3,-3l33,0m0,-1l-33,0a4,4 0 0 0 -4,4l0,33a4,4 0 0 0 4,4l33,0a4,4 0 0 0 4,-4l0,-33a4,4 0 0 0 -4,-4z"/>
<path stroke-linecap="round" stroke-width="0.8" stroke="#545454" fill="#545454" d="m21.5275,7.14657l0,28.59908"/>
<path stroke-width="1.0" stroke="#545454" fill-opacity="0" fill="#ffffff" fill-rule="nonzero" d="m14.5112,25.5943l-0.632,0l0.632,-1.1115l-0.0305,-1.3114l-0.6445,-1.103l-0.7745,-0.632l-0.3502,-0.7142l-0.9981,-1.8354l1.4221,0.9295l1.6267,0.1116l0.8272,0.5112l0.9667,-0.4926l0.2509,-1.3664l-1.0317,-0.5763l-0.9945,-1.5429l0.1999,-0.7236l-1.1015,-1.0425l0.4275,-1.608l-0.7064,-0.0651l-0.9388,0.7157l-1.8125,0.1674l-1.71896,0.9102l-0.81298,1.0906l-0.77183,2.014l-0.22958,2.4972l0.25693,2.9999l0.89251,0.5883l0.21446,1.5893l-0.6172,0l-0.66057,1.2606l-0.51219,1.9929l8.95941,0l-0.5289,-1.9929l-0.8089,-1.2606l0,0z"/>
<path stroke-dasharray="1,2" stroke-linecap="round" stroke-width="1.0" stroke="#545454" fill-opacity="0" fill="#ffffff" fill-rule="nonzero" d="m28.4888,25.5943l0.632,0l-0.632,-1.1115l0.0305,-1.3114l0.6445,-1.103l0.7745,-0.632l0.3502,-0.7142l0.9981,-1.8354l-1.4221,0.9295l-1.6267,0.1116l-0.8272,0.5112l-0.9667,-0.4926l-0.2509,-1.3664l1.0317,-0.5763l0.9945,-1.5429l-0.1999,-0.7236l1.1015,-1.0425l-0.4275,-1.608l0.7064,-0.0651l0.9388,0.7157l1.8125,0.1674l1.719,0.9102l0.8129,1.0906l0.7719,2.014l0.2296,2.4972l-0.257,2.9999l-0.8925,0.5883l-0.2145,1.5893l0.6173,0l0.6605,1.2606l0.5122,1.9929l-8.9594,0l0.5289,-1.9929l0.8089,-1.2606l0,0z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1,7 @@
<svg width="43" height="43" xmlns="http://www.w3.org/2000/svg">
<rect fill="#54545a" rx="3.5" height="40" width="40" y="1.5" x="1.5"/>
<path stroke-width="1.0" fill="#b3b3b3" d="m38,2a3,3 0 0 1 3,3l0,33a3,3 0 0 1 -3,3l-33,0a3,3 0 0 1 -3,-3l0,-33a3,3 0 0 1 3,-3l33,0m0,-1l-33,0a4,4 0 0 0 -4,4l0,33a4,4 0 0 0 4,4l33,0a4,4 0 0 0 4,-4l0,-33a4,4 0 0 0 -4,-4z"/>
<path stroke-linecap="round" stroke-width="0.8" stroke="#c4c4c4" fill="#c4c4c4" d="m21.5275,7.14657l0,28.59908"/>
<path stroke-width="1.0" stroke="#c4c4c4" fill-opacity="0" fill="#ffffff" fill-rule="nonzero" d="m14.5112,25.5943l-0.632,0l0.632,-1.1115l-0.0305,-1.3114l-0.6445,-1.103l-0.7745,-0.632l-0.3502,-0.7142l-0.9981,-1.8354l1.4221,0.9295l1.6267,0.1116l0.8272,0.5112l0.9667,-0.4926l0.2509,-1.3664l-1.0317,-0.5763l-0.9945,-1.5429l0.1999,-0.7236l-1.1015,-1.0425l0.4275,-1.608l-0.7064,-0.0651l-0.9388,0.7157l-1.8125,0.1674l-1.71896,0.9102l-0.81298,1.0906l-0.77183,2.014l-0.22958,2.4972l0.25693,2.9999l0.89251,0.5883l0.21446,1.5893l-0.6172,0l-0.66057,1.2606l-0.51219,1.9929l8.95941,0l-0.5289,-1.9929l-0.8089,-1.2606l0,0z"/>
<path stroke-dasharray="1,2" stroke-linecap="round" stroke-width="1.0" stroke="#c4c4c4" fill-opacity="0" fill="#ffffff" fill-rule="nonzero" d="m28.4888,25.5943l0.632,0l-0.632,-1.1115l0.0305,-1.3114l0.6445,-1.103l0.7745,-0.632l0.3502,-0.7142l0.9981,-1.8354l-1.4221,0.9295l-1.6267,0.1116l-0.8272,0.5112l-0.9667,-0.4926l-0.2509,-1.3664l1.0317,-0.5763l0.9945,-1.5429l-0.1999,-0.7236l1.1015,-1.0425l-0.4275,-1.608l0.7064,-0.0651l0.9388,0.7157l1.8125,0.1674l1.719,0.9102l0.8129,1.0906l0.7719,2.014l0.2296,2.4972l-0.257,2.9999l-0.8925,0.5883l-0.2145,1.5893l0.6173,0l0.6605,1.2606l0.5122,1.9929l-8.9594,0l0.5289,-1.9929l0.8089,-1.2606l0,0z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1,7 @@
<svg width="43" height="43" xmlns="http://www.w3.org/2000/svg">
<rect fill="#e9e9e9" rx="3.5" height="40" width="40" y="1.5" x="1.5"/>
<path stroke-width="1.0" fill="#009688" d="m38,2a3,3 0 0 1 3,3l0,33a3,3 0 0 1 -3,3l-33,0a3,3 0 0 1 -3,-3l0,-33a3,3 0 0 1 3,-3l33,0m0,-1l-33,0a4,4 0 0 0 -4,4l0,33a4,4 0 0 0 4,4l33,0a4,4 0 0 0 4,-4l0,-33a4,4 0 0 0 -4,-4z"/>
<path stroke-linecap="round" stroke-width="0.8" stroke="#009688" fill="#009688" d="m21.5275,7.14657l0,28.59908"/>
<path stroke-width="1.0" stroke="#009688" fill-opacity="0" fill="#ffffff" fill-rule="nonzero" d="m14.5112,25.5943l-0.632,0l0.632,-1.1115l-0.0305,-1.3114l-0.6445,-1.103l-0.7745,-0.632l-0.3502,-0.7142l-0.9981,-1.8354l1.4221,0.9295l1.6267,0.1116l0.8272,0.5112l0.9667,-0.4926l0.2509,-1.3664l-1.0317,-0.5763l-0.9945,-1.5429l0.1999,-0.7236l-1.1015,-1.0425l0.4275,-1.608l-0.7064,-0.0651l-0.9388,0.7157l-1.8125,0.1674l-1.71896,0.9102l-0.81298,1.0906l-0.77183,2.014l-0.22958,2.4972l0.25693,2.9999l0.89251,0.5883l0.21446,1.5893l-0.6172,0l-0.66057,1.2606l-0.51219,1.9929l8.95941,0l-0.5289,-1.9929l-0.8089,-1.2606l0,0z"/>
<path stroke-dasharray="1,2" stroke-linecap="round" stroke-width="1.0" stroke="#009688" fill-opacity="0" fill="#ffffff" fill-rule="nonzero" d="m28.4888,25.5943l0.632,0l-0.632,-1.1115l0.0305,-1.3114l0.6445,-1.103l0.7745,-0.632l0.3502,-0.7142l0.9981,-1.8354l-1.4221,0.9295l-1.6267,0.1116l-0.8272,0.5112l-0.9667,-0.4926l-0.2509,-1.3664l1.0317,-0.5763l0.9945,-1.5429l-0.1999,-0.7236l1.1015,-1.0425l-0.4275,-1.608l0.7064,-0.0651l0.9388,0.7157l1.8125,0.1674l1.719,0.9102l0.8129,1.0906l0.7719,2.014l0.2296,2.4972l-0.257,2.9999l-0.8925,0.5883l-0.2145,1.5893l0.6173,0l0.6605,1.2606l0.5122,1.9929l-8.9594,0l0.5289,-1.9929l0.8089,-1.2606l0,0z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1,7 @@
<svg width="43" height="43" xmlns="http://www.w3.org/2000/svg">
<rect fill="#54545a" rx="3.5" height="40" width="40" y="1.5" x="1.5"/>
<path stroke-width="1.0" fill="#009688" d="m38,2a3,3 0 0 1 3,3l0,33a3,3 0 0 1 -3,3l-33,0a3,3 0 0 1 -3,-3l0,-33a3,3 0 0 1 3,-3l33,0m0,-1l-33,0a4,4 0 0 0 -4,4l0,33a4,4 0 0 0 4,4l33,0a4,4 0 0 0 4,-4l0,-33a4,4 0 0 0 -4,-4z"/>
<path stroke-linecap="round" stroke-width="0.8" stroke="#009688" fill="#009688" d="m21.5275,7.14657l0,28.59908"/>
<path stroke-width="1.0" stroke="#009688" fill-opacity="0" fill="#ffffff" fill-rule="nonzero" d="m14.5112,25.5943l-0.632,0l0.632,-1.1115l-0.0305,-1.3114l-0.6445,-1.103l-0.7745,-0.632l-0.3502,-0.7142l-0.9981,-1.8354l1.4221,0.9295l1.6267,0.1116l0.8272,0.5112l0.9667,-0.4926l0.2509,-1.3664l-1.0317,-0.5763l-0.9945,-1.5429l0.1999,-0.7236l-1.1015,-1.0425l0.4275,-1.608l-0.7064,-0.0651l-0.9388,0.7157l-1.8125,0.1674l-1.71896,0.9102l-0.81298,1.0906l-0.77183,2.014l-0.22958,2.4972l0.25693,2.9999l0.89251,0.5883l0.21446,1.5893l-0.6172,0l-0.66057,1.2606l-0.51219,1.9929l8.95941,0l-0.5289,-1.9929l-0.8089,-1.2606l0,0z"/>
<path stroke-dasharray="1,2" stroke-linecap="round" stroke-width="1.0" stroke="#009688" fill-opacity="0" fill="#ffffff" fill-rule="nonzero" d="m28.4888,25.5943l0.632,0l-0.632,-1.1115l0.0305,-1.3114l0.6445,-1.103l0.7745,-0.632l0.3502,-0.7142l0.9981,-1.8354l-1.4221,0.9295l-1.6267,0.1116l-0.8272,0.5112l-0.9667,-0.4926l-0.2509,-1.3664l1.0317,-0.5763l0.9945,-1.5429l-0.1999,-0.7236l1.1015,-1.0425l-0.4275,-1.608l0.7064,-0.0651l0.9388,0.7157l1.8125,0.1674l1.719,0.9102l0.8129,1.0906l0.7719,2.014l0.2296,2.4972l-0.257,2.9999l-0.8925,0.5883l-0.2145,1.5893l0.6173,0l0.6605,1.2606l0.5122,1.9929l-8.9594,0l0.5289,-1.9929l0.8089,-1.2606l0,0z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1,10 @@
<svg width="43" height="43" xmlns="http://www.w3.org/2000/svg">
<rect fill="#e9e9e9" rx="3.5" height="40" width="40" x="1.5" y="1.5"/>
<path fill="#545454" d="m38,2a3,3 0 0 1 3,3l0,33a3,3 0 0 1 -3,3l-33,0a3,3 0 0 1 -3,-3l0,-33a3,3 0 0 1 3,-3l33,0m0,-1l-33,0a4,4 0 0 0 -4,4l0,33a4,4 0 0 0 4,4l33,0a4,4 0 0 0 4,-4l0,-33a4,4 0 0 0 -4,-4z"/>
<path stroke="#545454" fill="#ffffff" fill-opacity="0" fill-rule="nonzero" stroke-width="1.0" d="m27.20643,29.4739l-1.21802,0l1.21802,-2.14213l-0.05879,-2.52738l-1.2421,-2.12576l-1.49265,-1.21801l-0.67492,-1.37644l-1.92358,-3.53726l2.74073,1.79137l3.13505,0.21508l1.59421,0.98521l1.86307,-0.94935l0.48354,-2.63339l-1.98833,-1.11067l-1.91665,-2.97354l0.38526,-1.39456l-2.12286,-2.00915l0.82389,-3.09901l-1.3614,-0.12546l-1.80929,1.37933l-3.49313,0.32262l-3.31285,1.75417l-1.56681,2.10186l-1.48751,3.88146l-0.44245,4.81271l0.49516,5.78154l1.72009,1.13379l0.41331,3.06297l-1.1895,0l-1.27306,2.42948l-0.98712,3.8408l17.26695,0l-1.01931,-3.8408l-1.55895,-2.42948l0,0z"/>
<path stroke="#545454" stroke-dasharray="2,2" d="m23.61156,6.63793l-1.16183,2.2557l-1.89806,0.27415l-2.4104,1.37699l-1.46486,2.1687l-1.05714,3.57465l-0.28913,4.51495l0.30214,6.3633" fill-opacity="0" stroke-width="1.0" fill="none"/>
<line stroke="#545454" stroke-dasharray="2,2" y2="32.58856" x2="28.96602" y1="32.58856" x1="13.5093" fill-opacity="0" stroke-width="1.0" fill="none"/>
<line stroke="#545454" y2="29.47069" x2="25.98988" y1="29.47069" x1="16.06028" fill-opacity="0" stroke-width="1.0" fill="none"/>
<path stroke="#545454" stroke-dasharray="2,2" d="m27.94317,10.33418l-2.21519,0.02258l-0.32541,0.65079l2.39086,0.92312" fill-opacity="0" stroke-width="1.0" fill="none"/>
<path stroke="#545454" stroke-dasharray="2,2" d="m31.36607,16.48593l-1.47286,-0.34296l0.38557,-0.77114" stroke-width="1.0" fill-opacity="0" fill="none"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

@@ -0,0 +1,10 @@
<svg width="43" height="43" xmlns="http://www.w3.org/2000/svg">
<rect fill="#54545a" rx="3.5" height="40" width="40" x="1.5" y="1.5"/>
<path fill="#b3b3b3" d="m38,2a3,3 0 0 1 3,3l0,33a3,3 0 0 1 -3,3l-33,0a3,3 0 0 1 -3,-3l0,-33a3,3 0 0 1 3,-3l33,0m0,-1l-33,0a4,4 0 0 0 -4,4l0,33a4,4 0 0 0 4,4l33,0a4,4 0 0 0 4,-4l0,-33a4,4 0 0 0 -4,-4z"/>
<path stroke="#c4c4c4" fill="#ffffff" fill-opacity="0" fill-rule="nonzero" stroke-width="1.0" d="m27.20643,29.4739l-1.21802,0l1.21802,-2.14213l-0.05879,-2.52738l-1.2421,-2.12576l-1.49265,-1.21801l-0.67492,-1.37644l-1.92358,-3.53726l2.74073,1.79137l3.13505,0.21508l1.59421,0.98521l1.86307,-0.94935l0.48354,-2.63339l-1.98833,-1.11067l-1.91665,-2.97354l0.38526,-1.39456l-2.12286,-2.00915l0.82389,-3.09901l-1.3614,-0.12546l-1.80929,1.37933l-3.49313,0.32262l-3.31285,1.75417l-1.56681,2.10186l-1.48751,3.88146l-0.44245,4.81271l0.49516,5.78154l1.72009,1.13379l0.41331,3.06297l-1.1895,0l-1.27306,2.42948l-0.98712,3.8408l17.26695,0l-1.01931,-3.8408l-1.55895,-2.42948l0,0z"/>
<path stroke="#c4c4c4" stroke-dasharray="2,2" d="m23.61156,6.63793l-1.16183,2.2557l-1.89806,0.27415l-2.4104,1.37699l-1.46486,2.1687l-1.05714,3.57465l-0.28913,4.51495l0.30214,6.3633" fill-opacity="0" stroke-width="1.0" fill="none"/>
<line stroke="#c4c4c4" stroke-dasharray="2,2" y2="32.58856" x2="28.96602" y1="32.58856" x1="13.5093" fill-opacity="0" stroke-width="1.0" fill="none"/>
<line stroke="#c4c4c4" y2="29.47069" x2="25.98988" y1="29.47069" x1="16.06028" fill-opacity="0" stroke-width="1.0" fill="none"/>
<path stroke="#c4c4c4" stroke-dasharray="2,2" d="m27.94317,10.33418l-2.21519,0.02258l-0.32541,0.65079l2.39086,0.92312" fill-opacity="0" stroke-width="1.0" fill="none"/>
<path stroke="#c4c4c4" stroke-dasharray="2,2" d="m31.36607,16.48593l-1.47286,-0.34296l0.38557,-0.77114" stroke-width="1.0" fill-opacity="0" fill="none"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

@@ -0,0 +1,10 @@
<svg width="43" height="43" xmlns="http://www.w3.org/2000/svg">
<rect fill="#e9e9e9" rx="3.5" height="40" width="40" x="1.5" y="1.5"/>
<path fill="#009688" d="m38,2a3,3 0 0 1 3,3l0,33a3,3 0 0 1 -3,3l-33,0a3,3 0 0 1 -3,-3l0,-33a3,3 0 0 1 3,-3l33,0m0,-1l-33,0a4,4 0 0 0 -4,4l0,33a4,4 0 0 0 4,4l33,0a4,4 0 0 0 4,-4l0,-33a4,4 0 0 0 -4,-4z"/>
<path stroke="#009688" fill="#ffffff" fill-opacity="0" fill-rule="nonzero" stroke-width="1.0" d="m27.20643,29.4739l-1.21802,0l1.21802,-2.14213l-0.05879,-2.52738l-1.2421,-2.12576l-1.49265,-1.21801l-0.67492,-1.37644l-1.92358,-3.53726l2.74073,1.79137l3.13505,0.21508l1.59421,0.98521l1.86307,-0.94935l0.48354,-2.63339l-1.98833,-1.11067l-1.91665,-2.97354l0.38526,-1.39456l-2.12286,-2.00915l0.82389,-3.09901l-1.3614,-0.12546l-1.80929,1.37933l-3.49313,0.32262l-3.31285,1.75417l-1.56681,2.10186l-1.48751,3.88146l-0.44245,4.81271l0.49516,5.78154l1.72009,1.13379l0.41331,3.06297l-1.1895,0l-1.27306,2.42948l-0.98712,3.8408l17.26695,0l-1.01931,-3.8408l-1.55895,-2.42948l0,0z"/>
<path stroke="#009688" stroke-dasharray="2,2" d="m23.61156,6.63793l-1.16183,2.2557l-1.89806,0.27415l-2.4104,1.37699l-1.46486,2.1687l-1.05714,3.57465l-0.28913,4.51495l0.30214,6.3633" fill-opacity="0" stroke-width="1.0" fill="none"/>
<line stroke="#009688" stroke-dasharray="2,2" y2="32.58856" x2="28.96602" y1="32.58856" x1="13.5093" fill-opacity="0" stroke-width="1.0" fill="none"/>
<line stroke="#009688" y2="29.47069" x2="25.98988" y1="29.47069" x1="16.06028" fill-opacity="0" stroke-width="1.0" fill="none"/>
<path stroke="#009688" stroke-dasharray="2,2" d="m27.94317,10.33418l-2.21519,0.02258l-0.32541,0.65079l2.39086,0.92312" fill-opacity="0" stroke-width="1.0" fill="none"/>
<path stroke="#009688" stroke-dasharray="2,2" d="m31.36607,16.48593l-1.47286,-0.34296l0.38557,-0.77114" stroke-width="1.0" fill-opacity="0" fill="none"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

@@ -0,0 +1,10 @@
<svg width="43" height="43" xmlns="http://www.w3.org/2000/svg">
<rect fill="#54545a" rx="3.5" height="40" width="40" x="1.5" y="1.5"/>
<path fill="#009688" d="m38,2a3,3 0 0 1 3,3l0,33a3,3 0 0 1 -3,3l-33,0a3,3 0 0 1 -3,-3l0,-33a3,3 0 0 1 3,-3l33,0m0,-1l-33,0a4,4 0 0 0 -4,4l0,33a4,4 0 0 0 4,4l33,0a4,4 0 0 0 4,-4l0,-33a4,4 0 0 0 -4,-4z"/>
<path stroke="#009688" fill="#ffffff" fill-opacity="0" fill-rule="nonzero" stroke-width="1.0" d="m27.20643,29.4739l-1.21802,0l1.21802,-2.14213l-0.05879,-2.52738l-1.2421,-2.12576l-1.49265,-1.21801l-0.67492,-1.37644l-1.92358,-3.53726l2.74073,1.79137l3.13505,0.21508l1.59421,0.98521l1.86307,-0.94935l0.48354,-2.63339l-1.98833,-1.11067l-1.91665,-2.97354l0.38526,-1.39456l-2.12286,-2.00915l0.82389,-3.09901l-1.3614,-0.12546l-1.80929,1.37933l-3.49313,0.32262l-3.31285,1.75417l-1.56681,2.10186l-1.48751,3.88146l-0.44245,4.81271l0.49516,5.78154l1.72009,1.13379l0.41331,3.06297l-1.1895,0l-1.27306,2.42948l-0.98712,3.8408l17.26695,0l-1.01931,-3.8408l-1.55895,-2.42948l0,0z"/>
<path stroke="#009688" stroke-dasharray="2,2" d="m23.61156,6.63793l-1.16183,2.2557l-1.89806,0.27415l-2.4104,1.37699l-1.46486,2.1687l-1.05714,3.57465l-0.28913,4.51495l0.30214,6.3633" fill-opacity="0" stroke-width="1.0" fill="none"/>
<line stroke="#009688" stroke-dasharray="2,2" y2="32.58856" x2="28.96602" y1="32.58856" x1="13.5093" fill-opacity="0" stroke-width="1.0" fill="none"/>
<line stroke="#009688" y2="29.47069" x2="25.98988" y1="29.47069" x1="16.06028" fill-opacity="0" stroke-width="1.0" fill="none"/>
<path stroke="#009688" stroke-dasharray="2,2" d="m27.94317,10.33418l-2.21519,0.02258l-0.32541,0.65079l2.39086,0.92312" fill-opacity="0" stroke-width="1.0" fill="none"/>
<path stroke="#009688" stroke-dasharray="2,2" d="m31.36607,16.48593l-1.47286,-0.34296l0.38557,-0.77114" stroke-width="1.0" fill-opacity="0" fill="none"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

+41 -1
View File
@@ -1,6 +1,6 @@
{
"name": "Anker",
"version": "02.04.00.05",
"version": "02.04.00.06",
"force_update": "0",
"description": "Anker configurations",
"machine_model_list": [
@@ -304,6 +304,26 @@
"name": "Generic PLA Silk @Anker 0.25 nozzle",
"sub_path": "filament/Generic PLA Silk @Anker 0.25 nozzle.json"
},
{
"name": "AnkerMake PLA+ Basic @Anker base",
"sub_path": "filament/AnkerMake PLA+ Basic @Anker base.json"
},
{
"name": "AnkerMake PLA+ Glitter @Anker base",
"sub_path": "filament/AnkerMake PLA+ Glitter @Anker base.json"
},
{
"name": "AnkerMake PLA+ Matte @Anker base",
"sub_path": "filament/AnkerMake PLA+ Matte @Anker base.json"
},
{
"name": "AnkerMake PLA+ Metallic @Anker base",
"sub_path": "filament/AnkerMake PLA+ Metallic @Anker base.json"
},
{
"name": "AnkerMake PLA+ Silk @Anker base",
"sub_path": "filament/AnkerMake PLA+ Silk @Anker base.json"
},
{
"name": "Generic PLA+ @Anker",
"sub_path": "filament/Generic PLA+ @Anker.json"
@@ -327,6 +347,26 @@
{
"name": "Generic TPU @Anker",
"sub_path": "filament/Generic TPU @Anker.json"
},
{
"name": "AnkerMake PLA+ Basic @Anker",
"sub_path": "filament/AnkerMake PLA+ Basic @Anker.json"
},
{
"name": "AnkerMake PLA+ Glitter @Anker",
"sub_path": "filament/AnkerMake PLA+ Glitter @Anker.json"
},
{
"name": "AnkerMake PLA+ Matte @Anker",
"sub_path": "filament/AnkerMake PLA+ Matte @Anker.json"
},
{
"name": "AnkerMake PLA+ Metallic @Anker",
"sub_path": "filament/AnkerMake PLA+ Metallic @Anker.json"
},
{
"name": "AnkerMake PLA+ Silk @Anker",
"sub_path": "filament/AnkerMake PLA+ Silk @Anker.json"
}
],
"machine_list": [
@@ -0,0 +1,50 @@
{
"type": "filament",
"name": "AnkerMake PLA+ Basic @Anker base",
"inherits": "Generic PLA+ @Anker base",
"from": "system",
"filament_id": "OFUuZlQL",
"instantiation": "false",
"filament_vendor": [
"AnkerMake"
],
"nozzle_temperature": [
"200"
],
"nozzle_temperature_initial_layer": [
"230"
],
"hot_plate_temp": [
"65"
],
"textured_plate_temp": [
"65"
],
"hot_plate_temp_initial_layer": [
"65"
],
"textured_plate_temp_initial_layer": [
"65"
],
"filament_flow_ratio": [
"1"
],
"close_fan_the_first_x_layers": [
"3"
],
"fan_cooling_layer_time": [
"60"
],
"slow_down_layer_time": [
"8"
],
"slow_down_min_speed": [
"30"
],
"default_filament_colour": [
"#FFFFFF"
],
"filament_max_volumetric_speed": [
"22"
]
}
@@ -0,0 +1,16 @@
{
"type": "filament",
"name": "AnkerMake PLA+ Basic @Anker",
"inherits": "AnkerMake PLA+ Basic @Anker base",
"from": "system",
"setting_id": "EfM5rmn4w6pMBajC",
"instantiation": "true",
"compatible_printers": [
"Anker M5 0.4 nozzle",
"Anker M5 0.6 nozzle",
"Anker M5 All-Metal 0.4 nozzle",
"Anker M5 All-Metal 0.6 nozzle",
"Anker M5C 0.4 nozzle",
"Anker M5C 0.6 nozzle"
]
}
@@ -0,0 +1,50 @@
{
"type": "filament",
"name": "AnkerMake PLA+ Glitter @Anker base",
"inherits": "Generic PLA+ @Anker base",
"from": "system",
"filament_id": "OFxCURdg",
"instantiation": "false",
"filament_vendor": [
"AnkerMake"
],
"nozzle_temperature": [
"200"
],
"nozzle_temperature_initial_layer": [
"230"
],
"hot_plate_temp": [
"65"
],
"textured_plate_temp": [
"65"
],
"hot_plate_temp_initial_layer": [
"65"
],
"textured_plate_temp_initial_layer": [
"65"
],
"filament_flow_ratio": [
"1"
],
"close_fan_the_first_x_layers": [
"3"
],
"fan_cooling_layer_time": [
"60"
],
"slow_down_layer_time": [
"6"
],
"slow_down_min_speed": [
"30"
],
"default_filament_colour": [
"#00AD66"
],
"filament_max_volumetric_speed": [
"22"
]
}
@@ -0,0 +1,16 @@
{
"type": "filament",
"name": "AnkerMake PLA+ Glitter @Anker",
"inherits": "AnkerMake PLA+ Glitter @Anker base",
"from": "system",
"setting_id": "blGHp0htgHEj7khE",
"instantiation": "true",
"compatible_printers": [
"Anker M5 0.4 nozzle",
"Anker M5 0.6 nozzle",
"Anker M5 All-Metal 0.4 nozzle",
"Anker M5 All-Metal 0.6 nozzle",
"Anker M5C 0.4 nozzle",
"Anker M5C 0.6 nozzle"
]
}
@@ -0,0 +1,50 @@
{
"type": "filament",
"name": "AnkerMake PLA+ Matte @Anker base",
"inherits": "Generic PLA+ @Anker base",
"from": "system",
"filament_id": "OFKDOyNK",
"instantiation": "false",
"filament_vendor": [
"AnkerMake"
],
"nozzle_temperature": [
"210"
],
"nozzle_temperature_initial_layer": [
"230"
],
"hot_plate_temp": [
"65"
],
"textured_plate_temp": [
"65"
],
"hot_plate_temp_initial_layer": [
"65"
],
"textured_plate_temp_initial_layer": [
"65"
],
"filament_flow_ratio": [
"1"
],
"close_fan_the_first_x_layers": [
"3"
],
"fan_cooling_layer_time": [
"60"
],
"slow_down_layer_time": [
"6"
],
"slow_down_min_speed": [
"30"
],
"default_filament_colour": [
"#FFFFFF"
],
"filament_max_volumetric_speed": [
"22"
]
}
@@ -0,0 +1,13 @@
{
"type": "filament",
"name": "AnkerMake PLA+ Matte @Anker",
"inherits": "AnkerMake PLA+ Matte @Anker base",
"from": "system",
"setting_id": "tPgwC1eEXyWPOn9S",
"instantiation": "true",
"compatible_printers": [
"Anker M5 0.4 nozzle",
"Anker M5 All-Metal 0.4 nozzle",
"Anker M5C 0.4 nozzle"
]
}
@@ -0,0 +1,56 @@
{
"type": "filament",
"name": "AnkerMake PLA+ Metallic @Anker base",
"inherits": "Generic PLA+ @Anker base",
"from": "system",
"filament_id": "OFBfbmtb",
"instantiation": "false",
"filament_vendor": [
"AnkerMake"
],
"nozzle_temperature": [
"200"
],
"textured_plate_temp": [
"60"
],
"textured_plate_temp_initial_layer": [
"60"
],
"filament_flow_ratio": [
"1"
],
"close_fan_the_first_x_layers": [
"3"
],
"fan_cooling_layer_time": [
"60"
],
"slow_down_layer_time": [
"6"
],
"slow_down_min_speed": [
"30"
],
"overhang_fan_speed": [
"50"
],
"reduce_fan_stop_start_freq": [
"0"
],
"filament_retraction_length": [
"1"
],
"filament_retraction_speed": [
"50"
],
"filament_deretraction_speed": [
"50"
],
"default_filament_colour": [
"#ACAC8A"
],
"filament_max_volumetric_speed": [
"20"
]
}
@@ -0,0 +1,13 @@
{
"type": "filament",
"name": "AnkerMake PLA+ Metallic @Anker",
"inherits": "AnkerMake PLA+ Metallic @Anker base",
"from": "system",
"setting_id": "jlbAMNNaEacdUASZ",
"instantiation": "true",
"compatible_printers": [
"Anker M5 0.4 nozzle",
"Anker M5 All-Metal 0.4 nozzle",
"Anker M5C 0.4 nozzle"
]
}
@@ -0,0 +1,62 @@
{
"type": "filament",
"name": "AnkerMake PLA+ Silk @Anker base",
"inherits": "Generic PLA+ @Anker base",
"from": "system",
"filament_id": "OFkxTGQc",
"instantiation": "false",
"filament_vendor": [
"AnkerMake"
],
"nozzle_temperature": [
"210"
],
"nozzle_temperature_initial_layer": [
"230"
],
"hot_plate_temp": [
"65"
],
"textured_plate_temp": [
"65"
],
"hot_plate_temp_initial_layer": [
"65"
],
"textured_plate_temp_initial_layer": [
"65"
],
"filament_flow_ratio": [
"1"
],
"close_fan_the_first_x_layers": [
"3"
],
"fan_cooling_layer_time": [
"60"
],
"slow_down_layer_time": [
"2"
],
"slow_down_min_speed": [
"20"
],
"filament_retraction_length": [
"0.5"
],
"filament_retraction_speed": [
"20"
],
"filament_wipe": [
"0"
],
"default_filament_colour": [
"#99ADA3"
],
"filament_deretraction_speed": [
"20"
],
"filament_max_volumetric_speed": [
"13"
]
}
@@ -0,0 +1,15 @@
{
"type": "filament",
"name": "AnkerMake PLA+ Silk @Anker",
"inherits": "AnkerMake PLA+ Silk @Anker base",
"from": "system",
"setting_id": "eVM9Ckwjl5vEvEKN",
"instantiation": "true",
"compatible_printers": [
"Anker M5 0.4 nozzle",
"Anker M5 All-Metal 0.4 nozzle",
"Anker M5 All-Metal 0.6 nozzle",
"Anker M5C 0.4 nozzle",
"Anker M5C 0.6 nozzle"
]
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "Anycubic",
"version": "02.04.00.08",
"version": "02.04.00.09",
"force_update": "0",
"description": "Anycubic configurations",
"machine_model_list": [
@@ -110,7 +110,7 @@
"default_filament_profile": [
"Generic PLA @Anycubic"
],
"machine_start_gcode": "G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstops\nG0 Z0.2 F1800 ; move nozzle to print position\nG92 E0 ; specify current extruder position as zero\nG1 Y10 X180 E50 F1200 ; extrude a line in front of the printer\nG92 E0 ; specify current extruder position as zero\nG0 Z20 F6000 ; move head up\nG1 E-7 F2400 ; retract\nG04 S2 ; wait 2s\nG0 X0 F6000 ; wipe from oozed filament\nG1 E-1 F2400 ; undo some of the retraction to avoid oozing\nG1 F6000 ; set travel speed to move to start printing point\nM117",
"machine_start_gcode": "G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstops\nG0 Z0.2 F1800 ; move nozzle to print position\nG92 E0 ; specify current extruder position as zero\nM109 S[nozzle_temperature_initial_layer] ; wait for nozzle temp to stabilize\nG1 Y10 X180 E50 F1200 ; extrude a line in front of the printer\nG92 E0 ; specify current extruder position as zero\nG0 Z20 F6000 ; move head up\nG1 E-7 F2400 ; retract\nG04 S2 ; wait 2s\nG0 X0 F6000 ; wipe from oozed filament\nG1 E-1 F2400 ; undo some of the retraction to avoid oozing\nG1 F6000 ; set travel speed to move to start printing point\nM117",
"machine_end_gcode": "G4 ; wait\nG92 E0\nG1{if max_layer_z < printable_height} Z{z_offset+min(max_layer_z+30, printable_height)}{endif} ; move print head up\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200 F3000 ; home X axis\nM84 ; disable motors",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",

Some files were not shown because too many files have changed in this diff Show More