Compare commits

...
Author SHA1 Message Date
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 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
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
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
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
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
TheLegendTubaGuy a3005a96ac Merge branch 'main' into fix/16194-bbl-infill-retraction 2026-10-09 02:33:14 -05: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
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
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
209 changed files with 20276 additions and 68182 deletions
+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 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 - 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' }} 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 run: |
with: 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
tag: "nightly-builds" git push -f origin refs/tags/nightly-builds
tag_exists_error: false
force_push_tag: true
message: "nightly-builds"
- name: Deploy Ubuntu OrcaSlicer_profile_validator release - 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' }} 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: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install -y --no-install-recommends \ 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 - uses: actions/setup-python@v6
with: with:
@@ -224,7 +224,7 @@ jobs:
sudo apt-get update sudo apt-get update
sudo apt-get install -y --no-install-recommends \ sudo apt-get install -y --no-install-recommends \
xvfb xdotool imagemagick openbox mesa-utils \ 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 - name: Run the parity harness
run: | 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. Supports a broad range of printers: Bambu Lab, Prusa, Creality, Voron, and more.
- **[Belt Printer Support](https://www.orcaslicer.com/wiki/belt_printing)** - **[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). 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/). - Additional features can be found in the [change notes](https://github.com/OrcaSlicer/OrcaSlicer/releases/).
# Wiki # 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(Cereal/Cereal.cmake)
include(Qhull/Qhull.cmake) include(Qhull/Qhull.cmake)
include(GLEW/GLEW.cmake)
include(GLFW/GLFW.cmake) include(GLFW/GLFW.cmake)
include(OpenCSG/OpenCSG.cmake)
set(SLVS_PKG "") set(SLVS_PKG "")
if (SLIC3R_CAD) if (SLIC3R_CAD)
include(SLVS/SLVS.cmake) include(SLVS/SLVS.cmake)
@@ -478,7 +476,6 @@ set(_dep_list
dep_Draco dep_Draco
dep_NLopt dep_NLopt
dep_OpenVDB dep_OpenVDB
dep_OpenCSG
${SLVS_PKG} ${SLVS_PKG}
dep_OpenCV dep_OpenCV
dep_Eigen dep_Eigen
-2
View File
@@ -1,6 +1,4 @@
orcaslicer_add_cmake_project(EXPAT 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 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 ()
+1 -1
View File
@@ -191,7 +191,7 @@ public:
tail->next = std::move(p); tail->next = std::move(p);
tail = new_tail; tail = new_tail;
} }
data_cond.notify_one(); disrupt_wait_for_data();
} }
void wait_and_pop(T& value) void wait_and_pop(T& value)
+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 ## Bodies
`PrintObject::prepare_infill()` groups the islands of every layer (`lslices`) `PrintObject::prepare_infill()` groups the islands of every layer (`lslices`)
into 3D connected bodies before bridges are detected, so bridge anchors and into 3D connected bodies with `connected_bodies()` before bridges are detected,
printed infill share one origin. Islands on adjacent layers belong to one body 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 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, 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 such as chain links, each form their own. Every island stores the index of its
+2
View File
@@ -164,6 +164,7 @@ src/slic3r/GUI/OptionsGroup.cpp
src/slic3r/GUI/PrintOptionsDialog.cpp src/slic3r/GUI/PrintOptionsDialog.cpp
src/slic3r/GUI/SafetyOptionsDialog.cpp src/slic3r/GUI/SafetyOptionsDialog.cpp
src/slic3r/GUI/ParamsPanel.cpp src/slic3r/GUI/ParamsPanel.cpp
src/slic3r/GUI/IMEXModesCtrl.cpp
src/slic3r/GUI/PartPlate.cpp src/slic3r/GUI/PartPlate.cpp
src/slic3r/GUI/Plater.cpp src/slic3r/GUI/Plater.cpp
src/slic3r/GUI/Preferences.cpp src/slic3r/GUI/Preferences.cpp
@@ -212,6 +213,7 @@ src/libslic3r/ExtrusionEntity.cpp
src/libslic3r/Flow.cpp src/libslic3r/Flow.cpp
src/libslic3r/Format/AMF.cpp src/libslic3r/Format/AMF.cpp
src/libslic3r/miniz_extension.cpp src/libslic3r/miniz_extension.cpp
src/libslic3r/IMEXHelpers.cpp
src/libslic3r/Preset.cpp src/libslic3r/Preset.cpp
src/libslic3r/Print.cpp src/libslic3r/Print.cpp
src/libslic3r/PrintBase.cpp src/libslic3r/PrintBase.cpp
+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"> <?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>
<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>

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"> <?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>
<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>

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

+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "Bambulab", "name": "Bambulab",
"url": "http://www.bambulab.com/Parameters/vendor/BBL.json", "url": "http://www.bambulab.com/Parameters/vendor/BBL.json",
"version": "02.08.00.12", "version": "02.08.00.13",
"force_update": "0", "force_update": "0",
"description": "the initial version of BBL configurations", "description": "the initial version of BBL configurations",
"machine_model_list": [ "machine_model_list": [
@@ -94,7 +94,7 @@
"prime_tower_flat_ironing": "0", "prime_tower_flat_ironing": "0",
"raft_layers": "0", "raft_layers": "0",
"reduce_crossing_wall": "0", "reduce_crossing_wall": "0",
"reduce_infill_retraction_mode": "Auto", "reduce_infill_retraction": "1",
"resolution": "0.012", "resolution": "0.012",
"scarf_angle_threshold": "155", "scarf_angle_threshold": "155",
"seam_position": "aligned", "seam_position": "aligned",
+149 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "Vivedino", "name": "Vivedino",
"version": "02.04.00.02", "version": "02.04.00.13",
"force_update": "0", "force_update": "0",
"description": "Vivedino configurations", "description": "Vivedino configurations",
"machine_model_list": [ "machine_model_list": [
@@ -11,6 +11,22 @@
{ {
"name": "Troodon 2.0 - RRF", "name": "Troodon 2.0 - RRF",
"sub_path": "machine/Troodon2RRF.json" "sub_path": "machine/Troodon2RRF.json"
},
{
"name": "Xplorer Dual Gantry",
"sub_path": "machine/XplorerDualGantry.json"
},
{
"name": "Xplorer IDEX",
"sub_path": "machine/XplorerIDEX.json"
},
{
"name": "Xplorer IQEX",
"sub_path": "machine/XplorerIQEX.json"
},
{
"name": "Xplorer Single",
"sub_path": "machine/XplorerSingle.json"
} }
], ],
"process_list": [ "process_list": [
@@ -45,6 +61,86 @@
{ {
"name": "0.28mm Extra Draft @Troodon2", "name": "0.28mm Extra Draft @Troodon2",
"sub_path": "process/0.28mm Extra Draft @Troodon2.json" "sub_path": "process/0.28mm Extra Draft @Troodon2.json"
},
{
"name": "fdm_process_xplorer_common",
"sub_path": "process/fdm_process_xplorer_common.json"
},
{
"name": "0.04mm Ultra Fine @Xplorer 0.4",
"sub_path": "process/0.04mm Ultra Fine @Xplorer 0.4.json"
},
{
"name": "0.08mm Extra Fine @Xplorer 0.4",
"sub_path": "process/0.08mm Extra Fine @Xplorer 0.4.json"
},
{
"name": "0.12mm Fine @Xplorer 0.4",
"sub_path": "process/0.12mm Fine @Xplorer 0.4.json"
},
{
"name": "0.16mm Optimal @Xplorer 0.4",
"sub_path": "process/0.16mm Optimal @Xplorer 0.4.json"
},
{
"name": "0.20mm Standard @Xplorer 0.4",
"sub_path": "process/0.20mm Standard @Xplorer 0.4.json"
},
{
"name": "0.28mm Draft @Xplorer 0.4",
"sub_path": "process/0.28mm Draft @Xplorer 0.4.json"
},
{
"name": "0.32mm Extra Draft @Xplorer 0.4",
"sub_path": "process/0.32mm Extra Draft @Xplorer 0.4.json"
},
{
"name": "fdm_process_xplorer_common_0_6",
"sub_path": "process/fdm_process_xplorer_common_0_6.json"
},
{
"name": "fdm_process_xplorer_common_0_8",
"sub_path": "process/fdm_process_xplorer_common_0_8.json"
},
{
"name": "0.18mm Fine @Xplorer 0.6",
"sub_path": "process/0.18mm Fine @Xplorer 0.6.json"
},
{
"name": "0.24mm Optimal @Xplorer 0.6",
"sub_path": "process/0.24mm Optimal @Xplorer 0.6.json"
},
{
"name": "0.30mm Standard @Xplorer 0.6",
"sub_path": "process/0.30mm Standard @Xplorer 0.6.json"
},
{
"name": "0.36mm Draft @Xplorer 0.6",
"sub_path": "process/0.36mm Draft @Xplorer 0.6.json"
},
{
"name": "0.42mm Extra Draft @Xplorer 0.6",
"sub_path": "process/0.42mm Extra Draft @Xplorer 0.6.json"
},
{
"name": "0.24mm Fine @Xplorer 0.8",
"sub_path": "process/0.24mm Fine @Xplorer 0.8.json"
},
{
"name": "0.32mm Optimal @Xplorer 0.8",
"sub_path": "process/0.32mm Optimal @Xplorer 0.8.json"
},
{
"name": "0.40mm Standard @Xplorer 0.8",
"sub_path": "process/0.40mm Standard @Xplorer 0.8.json"
},
{
"name": "0.48mm Draft @Xplorer 0.8",
"sub_path": "process/0.48mm Draft @Xplorer 0.8.json"
},
{
"name": "0.56mm Extra Draft @Xplorer 0.8",
"sub_path": "process/0.56mm Extra Draft @Xplorer 0.8.json"
} }
], ],
"filament_list": [], "filament_list": [],
@@ -65,9 +161,61 @@
"name": "Troodon 2.0 Klipper 0.4 nozzle", "name": "Troodon 2.0 Klipper 0.4 nozzle",
"sub_path": "machine/Troodon 2.0 Klipper 0.4 nozzle.json" "sub_path": "machine/Troodon 2.0 Klipper 0.4 nozzle.json"
}, },
{
"name": "Xplorer common",
"sub_path": "machine/Xplorer common.json"
},
{ {
"name": "Troodon 2.0 RRF 0.4 nozzle", "name": "Troodon 2.0 RRF 0.4 nozzle",
"sub_path": "machine/Troodon 2.0 RRF 0.4 nozzle.json" "sub_path": "machine/Troodon 2.0 RRF 0.4 nozzle.json"
},
{
"name": "Xplorer Dual Gantry 0.4 nozzle",
"sub_path": "machine/Xplorer Dual Gantry 0.4 nozzle.json"
},
{
"name": "Xplorer Dual Gantry 0.6 nozzle",
"sub_path": "machine/Xplorer Dual Gantry 0.6 nozzle.json"
},
{
"name": "Xplorer Dual Gantry 0.8 nozzle",
"sub_path": "machine/Xplorer Dual Gantry 0.8 nozzle.json"
},
{
"name": "Xplorer IDEX 0.4 nozzle",
"sub_path": "machine/Xplorer IDEX 0.4 nozzle.json"
},
{
"name": "Xplorer IDEX 0.6 nozzle",
"sub_path": "machine/Xplorer IDEX 0.6 nozzle.json"
},
{
"name": "Xplorer IDEX 0.8 nozzle",
"sub_path": "machine/Xplorer IDEX 0.8 nozzle.json"
},
{
"name": "Xplorer IQEX 0.4 nozzle",
"sub_path": "machine/Xplorer IQEX 0.4 nozzle.json"
},
{
"name": "Xplorer IQEX 0.6 nozzle",
"sub_path": "machine/Xplorer IQEX 0.6 nozzle.json"
},
{
"name": "Xplorer IQEX 0.8 nozzle",
"sub_path": "machine/Xplorer IQEX 0.8 nozzle.json"
},
{
"name": "Xplorer Single 0.4 nozzle",
"sub_path": "machine/Xplorer Single 0.4 nozzle.json"
},
{
"name": "Xplorer Single 0.6 nozzle",
"sub_path": "machine/Xplorer Single 0.6 nozzle.json"
},
{
"name": "Xplorer Single 0.8 nozzle",
"sub_path": "machine/Xplorer Single 0.8 nozzle.json"
} }
] ]
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Vivedino Xplorer Dual Gantry build plate. Dashed red outlines mark real reach
limits; the viewBox matches printable_area so the texture is not stretched.
Print zones are drawn by the slicer per plate, not by this texture. -->
<svg
width="400mm"
height="342.5mm"
viewBox="0 0 400 342.5"
version="1.1"
id="svg1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<g
id="layer1">
<rect
style="fill:none;stroke:#ff0e0e;stroke-width:1;stroke-dasharray:1, 2;stroke-dashoffset:0;stroke-opacity:1"
id="bound-rear"
width="400"
height="26"
x="0"
y="0" />
<rect
style="fill:none;stroke:#ff0e0e;stroke-width:1;stroke-dasharray:1, 2;stroke-dashoffset:0;stroke-opacity:1"
id="bound-front"
width="400"
height="77.5"
x="0"
y="265" />
<g
id="text3-6"
style="opacity:1">
<path
style="font-weight:bold;font-size:21.1667px;font-family:Recharge;-inkscape-font-specification:'Recharge Bold';text-align:center;baseline-shift:baseline;text-anchor:middle;display:inline;overflow:visible;vector-effect:none;fill:#ff0000;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="M 22.60772,9.2135632 H 19.221048 V 5.4247238 h -3.831173 v 3.7888394 h -2.582337 v 0.1693336 l 1.058335,2.7516712 h 1.524002 v 8.699514 h 3.831173 v -8.699514 h 3.386672 z m 1.227659,1.1006688 v 6.096009 c 0,2.878672 1.375835,4.423841 4.762507,4.423841 h 8.805348 c 3.407838,0 4.762507,-1.545169 4.762507,-4.423841 v -6.096009 c 0,-3.0903387 -1.354669,-4.8895082 -4.762507,-4.8895082 h -8.805348 c -3.386672,0 -4.762507,1.7991695 -4.762507,4.8895082 z m 4.191006,0.656167 c 0,-1.2700017 0.592668,-1.820336 1.566336,-1.820336 h 6.582844 c 1.227669,0 1.799169,0.6138344 1.799169,1.98967 v 3.915839 c 0,1.333503 -0.5715,1.905003 -1.799169,1.905003 h -6.582844 c -1.227668,0 -1.566336,-0.719667 -1.566336,-2.159003 z"
id="path25" />
<path
style="font-weight:bold;font-size:21.1667px;font-family:Recharge;-inkscape-font-specification:'Recharge Bold';text-align:center;baseline-shift:baseline;text-anchor:middle;display:inline;overflow:visible;vector-effect:none;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="m 14.890625,4.9238281 v 3.7890625 h -2.583984 v 0.7636719 l 1.216797,3.1582035 h 1.367187 v 8.699218 h 4.830078 v -8.699218 h 3.386719 V 8.7128906 H 19.720703 V 4.9238281 Z m 13.707031,0 c -1.781877,0 -3.138417,0.4891086 -4.019531,1.4492188 -0.881114,0.9601101 -1.242188,2.322036 -1.242188,3.9414061 v 6.095703 c 0,1.518993 0.370925,2.78417 1.261719,3.650391 0.890794,0.86622 2.230623,1.273437 4,1.273437 h 8.804688 c 1.779488,0 3.121173,-0.408321 4.009765,-1.27539 0.888593,-0.86707 1.253907,-2.13067 1.253907,-3.648438 v -6.095703 c 0,-1.6182286 -0.355554,-2.9804801 -1.234375,-3.9414061 -0.878822,-0.960926 -2.237387,-1.4492188 -4.029297,-1.4492188 z m -12.707031,1 h 2.830078 v 3.7890625 h 3.386719 v 1.9218754 h -3.386719 v 8.699218 H 15.890625 V 11.634766 H 14.208984 L 13.46875,9.7128906 h 2.421875 z m 12.707031,0 h 8.804688 c 1.615927,0 2.640543,0.4137566 3.291015,1.125 0.650473,0.7112434 0.972657,1.793515 0.972657,3.2656249 v 6.095703 c 0,1.360905 -0.312424,2.308411 -0.953125,2.933594 -0.640702,0.625183 -1.682198,0.990234 -3.310547,0.990234 h -8.804688 c -1.617294,0 -2.658943,-0.364202 -3.302734,-0.990234 -0.643792,-0.626032 -0.958984,-1.573915 -0.958985,-2.933594 v -6.095703 c 0,-1.4709684 0.325044,-2.5535656 0.978516,-3.2656249 0.653472,-0.7120593 1.678409,-1.125 3.283203,-1.125 z m 0.994141,2.7265625 c -0.581037,0 -1.137457,0.1802682 -1.517578,0.5976563 -0.380122,0.4173881 -0.546875,1.0098771 -0.546875,1.7226561 v 3.830078 c 0,0.758884 0.06928,1.390241 0.404297,1.896485 0.335014,0.506243 0.948621,0.763671 1.660156,0.763671 h 6.583984 c 0.691421,0 1.302744,-0.170803 1.720703,-0.607421 0.417959,-0.436618 0.578125,-1.060649 0.578125,-1.798828 v -3.914063 c 0,-0.757148 -0.157083,-1.3938421 -0.570312,-1.8476562 C 37.491068,8.8391546 36.872949,8.6503906 36.175781,8.6503906 Z m 0,1 h 6.583984 c 0.530501,0 0.810796,0.1173912 0.990235,0.3144532 0.179438,0.1970622 0.308593,0.5570942 0.308593,1.1757812 v 3.914063 c 0,0.595323 -0.126073,0.924914 -0.300781,1.107421 -0.174708,0.182508 -0.461798,0.298828 -0.998047,0.298828 h -6.583984 c -0.516133,0 -0.684935,-0.102981 -0.826172,-0.316406 -0.141237,-0.213424 -0.238281,-0.663298 -0.238281,-1.34375 v -3.830078 c 0,-0.557223 0.12748,-0.875502 0.287109,-1.0507811 0.15963,-0.1752795 0.384713,-0.2695313 0.777344,-0.2695313 z"
id="path26" />
</g>
<g
transform="translate(-356.014,288.234)">
<g
id="text3-8"
style="opacity:1">
<path
style="font-weight:bold;font-size:21.1667px;font-family:Recharge;-inkscape-font-specification:'Recharge Bold';text-align:center;baseline-shift:baseline;text-anchor:middle;display:inline;overflow:visible;vector-effect:none;fill:#ff0000;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="m 378.62465,9.2135632 h -3.38667 V 5.4247238 h -3.83117 v 3.7888394 h -2.58234 v 0.1693336 l 1.05834,2.7516712 h 1.524 v 8.699514 h 3.83117 v -8.699514 h 3.38667 z m 3.70417,-0.0635 c 0.74083,0 0.97367,0.2751672 0.97367,1.0795018 v 10.604517 h 4.191 V 9.2770633 c 0,-2.7940045 -0.762,-3.8523395 -3.70417,-3.8523395 h -4.65667 v 0.1693336 l 1.37583,3.5560056 z"
id="path23" />
<path
style="font-weight:bold;font-size:21.1667px;font-family:Recharge;-inkscape-font-specification:'Recharge Bold';text-align:center;baseline-shift:baseline;text-anchor:middle;display:inline;overflow:visible;vector-effect:none;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="m 370.90625,4.9238281 v 3.7890625 h -2.58203 v 0.7636719 l 1.21484,3.1582035 h 1.36719 v 8.699218 h 4.83203 V 12.634766 H 379.125 V 8.7128906 h -3.38672 V 4.9238281 Z m 7.72656,0 V 5.6875 l 1.53321,3.9628906 h 2.1621 c 0.30851,0 0.37476,0.050297 0.39258,0.070312 0.0178,0.020016 0.082,0.1558452 0.082,0.5097654 v 11.103515 h 5.19141 V 9.2773438 c 0,-1.4424568 -0.18358,-2.5460127 -0.87891,-3.3105469 -0.69533,-0.7645343 -1.79513,-1.0429688 -3.32617,-1.0429688 z m -6.72656,1 h 2.83203 v 3.7890625 h 3.38672 v 1.9218754 h -3.38672 v 8.699218 h -2.83203 v -8.699218 h -1.67969 l -0.74023,-1.9218754 h 2.41992 z m 7.89062,0 h 3.99219 c 1.41114,0 2.16473,0.2517094 2.58594,0.7148438 0.42121,0.4631344 0.61914,1.2871241 0.61914,2.6386719 V 20.333984 h -3.19141 V 10.230469 c 0,-0.4504146 -0.0521,-0.85713 -0.33593,-1.1757815 -0.2838,-0.3186515 -0.70635,-0.4042969 -1.13868,-0.4042969 h -1.47656 z"
id="path24" />
</g>
</g>
<g
transform="translate(0.020506,27.6309)">
<g
id="text6-8"
style="opacity:1">
<path
style="font-weight:bold;font-size:21.1667px;font-family:Recharge;-inkscape-font-specification:'Recharge Bold';text-align:center;baseline-shift:baseline;text-anchor:middle;display:inline;overflow:visible;vector-effect:none;fill:#ff0000;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="m 198.87817,149.53483 h 2.20134 v -4.826 h 4.80484 v -2.18017 h -4.80484 v -4.82601 h -2.20134 v 4.82601 h -4.80484 v 2.18017 h 4.80484 z"
id="path17" />
<path
style="font-weight:bold;font-size:21.1667px;font-family:Recharge;-inkscape-font-specification:'Recharge Bold';text-align:center;baseline-shift:baseline;text-anchor:middle;display:inline;overflow:visible;vector-effect:none;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="m 198.37891,137.20312 v 4.82618 h -4.80469 v 3.17968 h 4.80469 v 4.82618 h 3.20117 v -4.82618 h 4.80469 v -3.17968 h -4.80469 v -4.82618 z m 1,1 h 1.20117 v 4.82618 h 4.80469 v 1.17968 h -4.80469 v 4.82618 h -1.20117 v -4.82618 h -4.80469 v -1.17968 h 4.80469 z"
id="path18" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.9 KiB

@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Vivedino Xplorer IDEX build plate. Dashed red outlines mark real reach
limits; the viewBox matches printable_area so the texture is not stretched.
Print zones are drawn by the slicer per plate, not by this texture. -->
<svg
width="400mm"
height="400mm"
viewBox="0 0 400 400"
version="1.1"
id="svg1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<g
id="layer1">
<rect
style="fill:none;stroke:#ff0e0e;stroke-width:1;stroke-dasharray:1, 2;stroke-dashoffset:0;stroke-opacity:1"
id="bound-left"
width="66"
height="400"
x="0"
y="0" />
<rect
style="fill:none;stroke:#ff0e0e;stroke-width:1;stroke-dasharray:1, 2;stroke-dashoffset:0;stroke-opacity:1"
id="bound-right"
width="68"
height="400"
x="332"
y="0" />
<g
transform="translate(0,345.734)">
<g
id="text3-6"
style="opacity:1">
<path
style="font-weight:bold;font-size:21.1667px;font-family:Recharge;-inkscape-font-specification:'Recharge Bold';text-align:center;baseline-shift:baseline;text-anchor:middle;display:inline;overflow:visible;vector-effect:none;fill:#ff0000;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="M 22.60772,9.2135632 H 19.221048 V 5.4247238 h -3.831173 v 3.7888394 h -2.582337 v 0.1693336 l 1.058335,2.7516712 h 1.524002 v 8.699514 h 3.831173 v -8.699514 h 3.386672 z m 1.227659,1.1006688 v 6.096009 c 0,2.878672 1.375835,4.423841 4.762507,4.423841 h 8.805348 c 3.407838,0 4.762507,-1.545169 4.762507,-4.423841 v -6.096009 c 0,-3.0903387 -1.354669,-4.8895082 -4.762507,-4.8895082 h -8.805348 c -3.386672,0 -4.762507,1.7991695 -4.762507,4.8895082 z m 4.191006,0.656167 c 0,-1.2700017 0.592668,-1.820336 1.566336,-1.820336 h 6.582844 c 1.227669,0 1.799169,0.6138344 1.799169,1.98967 v 3.915839 c 0,1.333503 -0.5715,1.905003 -1.799169,1.905003 h -6.582844 c -1.227668,0 -1.566336,-0.719667 -1.566336,-2.159003 z"
id="path25" />
<path
style="font-weight:bold;font-size:21.1667px;font-family:Recharge;-inkscape-font-specification:'Recharge Bold';text-align:center;baseline-shift:baseline;text-anchor:middle;display:inline;overflow:visible;vector-effect:none;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="m 14.890625,4.9238281 v 3.7890625 h -2.583984 v 0.7636719 l 1.216797,3.1582035 h 1.367187 v 8.699218 h 4.830078 v -8.699218 h 3.386719 V 8.7128906 H 19.720703 V 4.9238281 Z m 13.707031,0 c -1.781877,0 -3.138417,0.4891086 -4.019531,1.4492188 -0.881114,0.9601101 -1.242188,2.322036 -1.242188,3.9414061 v 6.095703 c 0,1.518993 0.370925,2.78417 1.261719,3.650391 0.890794,0.86622 2.230623,1.273437 4,1.273437 h 8.804688 c 1.779488,0 3.121173,-0.408321 4.009765,-1.27539 0.888593,-0.86707 1.253907,-2.13067 1.253907,-3.648438 v -6.095703 c 0,-1.6182286 -0.355554,-2.9804801 -1.234375,-3.9414061 -0.878822,-0.960926 -2.237387,-1.4492188 -4.029297,-1.4492188 z m -12.707031,1 h 2.830078 v 3.7890625 h 3.386719 v 1.9218754 h -3.386719 v 8.699218 H 15.890625 V 11.634766 H 14.208984 L 13.46875,9.7128906 h 2.421875 z m 12.707031,0 h 8.804688 c 1.615927,0 2.640543,0.4137566 3.291015,1.125 0.650473,0.7112434 0.972657,1.793515 0.972657,3.2656249 v 6.095703 c 0,1.360905 -0.312424,2.308411 -0.953125,2.933594 -0.640702,0.625183 -1.682198,0.990234 -3.310547,0.990234 h -8.804688 c -1.617294,0 -2.658943,-0.364202 -3.302734,-0.990234 -0.643792,-0.626032 -0.958984,-1.573915 -0.958985,-2.933594 v -6.095703 c 0,-1.4709684 0.325044,-2.5535656 0.978516,-3.2656249 0.653472,-0.7120593 1.678409,-1.125 3.283203,-1.125 z m 0.994141,2.7265625 c -0.581037,0 -1.137457,0.1802682 -1.517578,0.5976563 -0.380122,0.4173881 -0.546875,1.0098771 -0.546875,1.7226561 v 3.830078 c 0,0.758884 0.06928,1.390241 0.404297,1.896485 0.335014,0.506243 0.948621,0.763671 1.660156,0.763671 h 6.583984 c 0.691421,0 1.302744,-0.170803 1.720703,-0.607421 0.417959,-0.436618 0.578125,-1.060649 0.578125,-1.798828 v -3.914063 c 0,-0.757148 -0.157083,-1.3938421 -0.570312,-1.8476562 C 37.491068,8.8391546 36.872949,8.6503906 36.175781,8.6503906 Z m 0,1 h 6.583984 c 0.530501,0 0.810796,0.1173912 0.990235,0.3144532 0.179438,0.1970622 0.308593,0.5570942 0.308593,1.1757812 v 3.914063 c 0,0.595323 -0.126073,0.924914 -0.300781,1.107421 -0.174708,0.182508 -0.461798,0.298828 -0.998047,0.298828 h -6.583984 c -0.516133,0 -0.684935,-0.102981 -0.826172,-0.316406 -0.141237,-0.213424 -0.238281,-0.663298 -0.238281,-1.34375 v -3.830078 c 0,-0.557223 0.12748,-0.875502 0.287109,-1.0507811 0.15963,-0.1752795 0.384713,-0.2695313 0.777344,-0.2695313 z"
id="path26" />
</g>
</g>
<g
transform="translate(0,345.734)">
<g
id="text3-8"
style="opacity:1">
<path
style="font-weight:bold;font-size:21.1667px;font-family:Recharge;-inkscape-font-specification:'Recharge Bold';text-align:center;baseline-shift:baseline;text-anchor:middle;display:inline;overflow:visible;vector-effect:none;fill:#ff0000;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="m 378.62465,9.2135632 h -3.38667 V 5.4247238 h -3.83117 v 3.7888394 h -2.58234 v 0.1693336 l 1.05834,2.7516712 h 1.524 v 8.699514 h 3.83117 v -8.699514 h 3.38667 z m 3.70417,-0.0635 c 0.74083,0 0.97367,0.2751672 0.97367,1.0795018 v 10.604517 h 4.191 V 9.2770633 c 0,-2.7940045 -0.762,-3.8523395 -3.70417,-3.8523395 h -4.65667 v 0.1693336 l 1.37583,3.5560056 z"
id="path23" />
<path
style="font-weight:bold;font-size:21.1667px;font-family:Recharge;-inkscape-font-specification:'Recharge Bold';text-align:center;baseline-shift:baseline;text-anchor:middle;display:inline;overflow:visible;vector-effect:none;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="m 370.90625,4.9238281 v 3.7890625 h -2.58203 v 0.7636719 l 1.21484,3.1582035 h 1.36719 v 8.699218 h 4.83203 V 12.634766 H 379.125 V 8.7128906 h -3.38672 V 4.9238281 Z m 7.72656,0 V 5.6875 l 1.53321,3.9628906 h 2.1621 c 0.30851,0 0.37476,0.050297 0.39258,0.070312 0.0178,0.020016 0.082,0.1558452 0.082,0.5097654 v 11.103515 h 5.19141 V 9.2773438 c 0,-1.4424568 -0.18358,-2.5460127 -0.87891,-3.3105469 -0.69533,-0.7645343 -1.79513,-1.0429688 -3.32617,-1.0429688 z m -6.72656,1 h 2.83203 v 3.7890625 h 3.38672 v 1.9218754 h -3.38672 v 8.699218 h -2.83203 v -8.699218 h -1.67969 l -0.74023,-1.9218754 h 2.41992 z m 7.89062,0 h 3.99219 c 1.41114,0 2.16473,0.2517094 2.58594,0.7148438 0.42121,0.4631344 0.61914,1.2871241 0.61914,2.6386719 V 20.333984 h -3.19141 V 10.230469 c 0,-0.4504146 -0.0521,-0.85713 -0.33593,-1.1757815 -0.2838,-0.3186515 -0.70635,-0.4042969 -1.13868,-0.4042969 h -1.47656 z"
id="path24" />
</g>
</g>
<g
transform="translate(0.020506,56.3809)">
<g
id="text6-8"
style="opacity:1">
<path
style="font-weight:bold;font-size:21.1667px;font-family:Recharge;-inkscape-font-specification:'Recharge Bold';text-align:center;baseline-shift:baseline;text-anchor:middle;display:inline;overflow:visible;vector-effect:none;fill:#ff0000;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="m 198.87817,149.53483 h 2.20134 v -4.826 h 4.80484 v -2.18017 h -4.80484 v -4.82601 h -2.20134 v 4.82601 h -4.80484 v 2.18017 h 4.80484 z"
id="path17" />
<path
style="font-weight:bold;font-size:21.1667px;font-family:Recharge;-inkscape-font-specification:'Recharge Bold';text-align:center;baseline-shift:baseline;text-anchor:middle;display:inline;overflow:visible;vector-effect:none;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="m 198.37891,137.20312 v 4.82618 h -4.80469 v 3.17968 h 4.80469 v 4.82618 h 3.20117 v -4.82618 h 4.80469 v -3.17968 h -4.80469 v -4.82618 z m 1,1 h 1.20117 v 4.82618 h 4.80469 v 1.17968 h -4.80469 v 4.82618 h -1.20117 v -4.82618 h -4.80469 v -1.17968 h 4.80469 z"
id="path18" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.0 KiB

@@ -0,0 +1,154 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Vivedino Xplorer IQEX build plate. Dashed red outlines mark real reach
limits; the viewBox matches printable_area so the texture is not stretched.
Print zones are drawn by the slicer per plate, not by this texture. -->
<svg
width="400mm"
height="342.5mm"
viewBox="0 0 400 342.5"
version="1.1"
id="svg1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<g
id="layer1">
<rect
style="fill:none;stroke:#ff0e0e;stroke-width:1;stroke-dasharray:1, 2;stroke-dashoffset:0;stroke-opacity:1"
id="bound-left"
width="66"
height="342.5"
x="0"
y="0" />
<rect
style="fill:none;stroke:#ff0e0e;stroke-width:1;stroke-dasharray:1, 2;stroke-dashoffset:0;stroke-opacity:1"
id="bound-right"
width="68"
height="342.5"
x="332"
y="0" />
<rect
style="fill:none;stroke:#ff0e0e;stroke-width:1;stroke-dasharray:1, 2;stroke-dashoffset:0;stroke-opacity:1"
id="bound-rear"
width="400"
height="26"
x="0"
y="0" />
<rect
style="fill:none;stroke:#ff0e0e;stroke-width:1;stroke-dasharray:1, 2;stroke-dashoffset:0;stroke-opacity:1"
id="bound-front"
width="400"
height="77.5"
x="0"
y="265" />
<g
id="text3-6"
style="opacity:1">
<path
style="font-weight:bold;font-size:21.1667px;font-family:Recharge;-inkscape-font-specification:'Recharge Bold';text-align:center;baseline-shift:baseline;text-anchor:middle;display:inline;overflow:visible;vector-effect:none;fill:#ff0000;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="M 22.60772,9.2135632 H 19.221048 V 5.4247238 h -3.831173 v 3.7888394 h -2.582337 v 0.1693336 l 1.058335,2.7516712 h 1.524002 v 8.699514 h 3.831173 v -8.699514 h 3.386672 z m 1.227659,1.1006688 v 6.096009 c 0,2.878672 1.375835,4.423841 4.762507,4.423841 h 8.805348 c 3.407838,0 4.762507,-1.545169 4.762507,-4.423841 v -6.096009 c 0,-3.0903387 -1.354669,-4.8895082 -4.762507,-4.8895082 h -8.805348 c -3.386672,0 -4.762507,1.7991695 -4.762507,4.8895082 z m 4.191006,0.656167 c 0,-1.2700017 0.592668,-1.820336 1.566336,-1.820336 h 6.582844 c 1.227669,0 1.799169,0.6138344 1.799169,1.98967 v 3.915839 c 0,1.333503 -0.5715,1.905003 -1.799169,1.905003 h -6.582844 c -1.227668,0 -1.566336,-0.719667 -1.566336,-2.159003 z"
id="path25" />
<path
style="font-weight:bold;font-size:21.1667px;font-family:Recharge;-inkscape-font-specification:'Recharge Bold';text-align:center;baseline-shift:baseline;text-anchor:middle;display:inline;overflow:visible;vector-effect:none;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="m 14.890625,4.9238281 v 3.7890625 h -2.583984 v 0.7636719 l 1.216797,3.1582035 h 1.367187 v 8.699218 h 4.830078 v -8.699218 h 3.386719 V 8.7128906 H 19.720703 V 4.9238281 Z m 13.707031,0 c -1.781877,0 -3.138417,0.4891086 -4.019531,1.4492188 -0.881114,0.9601101 -1.242188,2.322036 -1.242188,3.9414061 v 6.095703 c 0,1.518993 0.370925,2.78417 1.261719,3.650391 0.890794,0.86622 2.230623,1.273437 4,1.273437 h 8.804688 c 1.779488,0 3.121173,-0.408321 4.009765,-1.27539 0.888593,-0.86707 1.253907,-2.13067 1.253907,-3.648438 v -6.095703 c 0,-1.6182286 -0.355554,-2.9804801 -1.234375,-3.9414061 -0.878822,-0.960926 -2.237387,-1.4492188 -4.029297,-1.4492188 z m -12.707031,1 h 2.830078 v 3.7890625 h 3.386719 v 1.9218754 h -3.386719 v 8.699218 H 15.890625 V 11.634766 H 14.208984 L 13.46875,9.7128906 h 2.421875 z m 12.707031,0 h 8.804688 c 1.615927,0 2.640543,0.4137566 3.291015,1.125 0.650473,0.7112434 0.972657,1.793515 0.972657,3.2656249 v 6.095703 c 0,1.360905 -0.312424,2.308411 -0.953125,2.933594 -0.640702,0.625183 -1.682198,0.990234 -3.310547,0.990234 h -8.804688 c -1.617294,0 -2.658943,-0.364202 -3.302734,-0.990234 -0.643792,-0.626032 -0.958984,-1.573915 -0.958985,-2.933594 v -6.095703 c 0,-1.4709684 0.325044,-2.5535656 0.978516,-3.2656249 0.653472,-0.7120593 1.678409,-1.125 3.283203,-1.125 z m 0.994141,2.7265625 c -0.581037,0 -1.137457,0.1802682 -1.517578,0.5976563 -0.380122,0.4173881 -0.546875,1.0098771 -0.546875,1.7226561 v 3.830078 c 0,0.758884 0.06928,1.390241 0.404297,1.896485 0.335014,0.506243 0.948621,0.763671 1.660156,0.763671 h 6.583984 c 0.691421,0 1.302744,-0.170803 1.720703,-0.607421 0.417959,-0.436618 0.578125,-1.060649 0.578125,-1.798828 v -3.914063 c 0,-0.757148 -0.157083,-1.3938421 -0.570312,-1.8476562 C 37.491068,8.8391546 36.872949,8.6503906 36.175781,8.6503906 Z m 0,1 h 6.583984 c 0.530501,0 0.810796,0.1173912 0.990235,0.3144532 0.179438,0.1970622 0.308593,0.5570942 0.308593,1.1757812 v 3.914063 c 0,0.595323 -0.126073,0.924914 -0.300781,1.107421 -0.174708,0.182508 -0.461798,0.298828 -0.998047,0.298828 h -6.583984 c -0.516133,0 -0.684935,-0.102981 -0.826172,-0.316406 -0.141237,-0.213424 -0.238281,-0.663298 -0.238281,-1.34375 v -3.830078 c 0,-0.557223 0.12748,-0.875502 0.287109,-1.0507811 0.15963,-0.1752795 0.384713,-0.2695313 0.777344,-0.2695313 z"
id="path26" />
</g>
<g
id="text3-8"
style="opacity:1">
<path
style="font-weight:bold;font-size:21.1667px;font-family:Recharge;-inkscape-font-specification:'Recharge Bold';text-align:center;baseline-shift:baseline;text-anchor:middle;display:inline;overflow:visible;vector-effect:none;fill:#ff0000;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="m 378.62465,9.2135632 h -3.38667 V 5.4247238 h -3.83117 v 3.7888394 h -2.58234 v 0.1693336 l 1.05834,2.7516712 h 1.524 v 8.699514 h 3.83117 v -8.699514 h 3.38667 z m 3.70417,-0.0635 c 0.74083,0 0.97367,0.2751672 0.97367,1.0795018 v 10.604517 h 4.191 V 9.2770633 c 0,-2.7940045 -0.762,-3.8523395 -3.70417,-3.8523395 h -4.65667 v 0.1693336 l 1.37583,3.5560056 z"
id="path23" />
<path
style="font-weight:bold;font-size:21.1667px;font-family:Recharge;-inkscape-font-specification:'Recharge Bold';text-align:center;baseline-shift:baseline;text-anchor:middle;display:inline;overflow:visible;vector-effect:none;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="m 370.90625,4.9238281 v 3.7890625 h -2.58203 v 0.7636719 l 1.21484,3.1582035 h 1.36719 v 8.699218 h 4.83203 V 12.634766 H 379.125 V 8.7128906 h -3.38672 V 4.9238281 Z m 7.72656,0 V 5.6875 l 1.53321,3.9628906 h 2.1621 c 0.30851,0 0.37476,0.050297 0.39258,0.070312 0.0178,0.020016 0.082,0.1558452 0.082,0.5097654 v 11.103515 h 5.19141 V 9.2773438 c 0,-1.4424568 -0.18358,-2.5460127 -0.87891,-3.3105469 -0.69533,-0.7645343 -1.79513,-1.0429688 -3.32617,-1.0429688 z m -6.72656,1 h 2.83203 v 3.7890625 h 3.38672 v 1.9218754 h -3.38672 v 8.699218 h -2.83203 v -8.699218 h -1.67969 l -0.74023,-1.9218754 h 2.41992 z m 7.89062,0 h 3.99219 c 1.41114,0 2.16473,0.2517094 2.58594,0.7148438 0.42121,0.4631344 0.61914,1.2871241 0.61914,2.6386719 V 20.333984 h -3.19141 V 10.230469 c 0,-0.4504146 -0.0521,-0.85713 -0.33593,-1.1757815 -0.2838,-0.3186515 -0.70635,-0.4042969 -1.13868,-0.4042969 h -1.47656 z"
id="path24" />
</g>
<g
id="text3"
style="opacity:1">
<path
style="font-weight:bold;font-size:21.1667px;font-family:Recharge;-inkscape-font-specification:'Recharge Bold';text-align:center;baseline-shift:baseline;text-anchor:middle;display:inline;overflow:visible;vector-effect:none;fill:#ff0000;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="m 22.61014,297.44787 h -3.386672 v -3.78884 h -3.831173 v 3.78884 h -2.582337 v 0.16933 l 1.058335,2.75168 h 1.524002 v 8.69951 h 3.831173 v -8.69951 h 3.386672 z m 18.944187,7.81051 h -8.001013 c -1.227669,0 -2.286004,0.0212 -4.064006,0.127 v -0.14816 c 0.952501,-0.23284 1.735669,-0.46567 3.810006,-1.143 l 5.969009,-1.92617 c 1.98967,-0.63501 2.455337,-1.35467 2.455337,-3.47134 v -1.54517 c 0,-2.09551 -0.952501,-3.49251 -3.661839,-3.49251 H 23.753132 v 0.16934 l 1.312335,3.429 h 11.408852 c 0.698501,0 0.867834,0.27517 0.867834,0.78317 v 0.5715 c 0,0.55033 -0.211667,0.8255 -0.825501,1.03717 l -11.408851,3.89467 c -0.783168,0.27517 -1.016002,0.61384 -1.016002,1.56634 v 3.19617 c 0,0.55033 0.254,0.762 0.592668,0.762 h 16.86986 z"
id="path29" />
<path
style="font-weight:bold;font-size:21.1667px;font-family:Recharge;-inkscape-font-specification:'Recharge Bold';text-align:center;baseline-shift:baseline;text-anchor:middle;display:inline;overflow:visible;vector-effect:none;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="m 14.892578,293.1582 v 3.78907 h -2.582031 v 0.76367 l 1.214844,3.1582 h 1.367187 v 8.69922 h 4.830078 v -8.69922 h 3.386719 v -3.92187 h -3.386719 v -3.78907 z m 8.361328,0 v 0.76172 l 1.466797,3.83789 h 11.753906 c 0.283594,0 0.342775,0.0558 0.337891,0.0508 -0.0049,-0.005 0.0293,0.034 0.0293,0.23243 v 0.57031 c 0,0.21106 -0.03003,0.28002 -0.07422,0.33594 -0.04419,0.0559 -0.153527,0.13867 -0.414062,0.22851 l -11.410157,3.89649 h -0.002 c -0.437489,0.15371 -0.814289,0.36283 -1.052734,0.72656 -0.238445,0.36373 -0.296875,0.79355 -0.296875,1.31054 v 3.19727 c 0,0.35209 0.0826,0.67409 0.292969,0.91602 0.210369,0.24192 0.525326,0.3457 0.798828,0.3457 h 17.371093 v -4.81055 h -8.501953 c -0.243624,0 -0.570664,0.0232 -0.808593,0.0254 0.305142,-0.0978 0.326312,-0.0879 0.708984,-0.21289 l 5.966797,-1.92578 c 1.036192,-0.3307 1.774785,-0.71448 2.232422,-1.37109 0.457636,-0.65661 0.572265,-1.48117 0.572265,-2.57617 v -1.54493 c 0,-1.12351 -0.260337,-2.14285 -0.953125,-2.87695 -0.692788,-0.7341 -1.768378,-1.11719 -3.208984,-1.11719 z m -7.361328,1 h 2.830078 v 3.78907 h 3.386719 v 1.92187 h -3.386719 v 8.69922 h -2.830078 v -8.69922 h -1.68164 l -0.738282,-1.92187 h 2.419922 z m 8.523438,0 H 38.0625 c 1.268732,0 2.021625,0.31641 2.482422,0.80469 0.460797,0.48828 0.679687,1.21746 0.679687,2.18945 v 1.54493 c 0,1.02166 -0.119963,1.61276 -0.392578,2.0039 -0.272614,0.39114 -0.761365,0.68593 -1.714844,0.99024 h -0.002 l -5.970703,1.92773 c -2.071176,0.6763 -2.834211,0.90322 -3.773437,1.13281 l -0.38086,0.0918 v 1.07227 l 0.529297,-0.0312 c 1.770212,-0.10534 2.811291,-0.12696 4.033203,-0.12696 h 7.501953 v 2.81055 H 24.683594 c -0.06517,0 -0.04362,-4.6e-4 -0.04492,-0.002 -0.0013,-0.002 -0.04687,-0.0615 -0.04687,-0.25977 v -3.19727 c 0,-0.4355 0.05905,-0.64919 0.132812,-0.76171 0.07376,-0.11253 0.20315,-0.21058 0.548828,-0.33204 l 11.40625,-3.89257 v -0.002 c 0.353299,-0.12183 0.657948,-0.28056 0.873047,-0.55273 0.215099,-0.27217 0.289063,-0.61776 0.289063,-0.95703 v -0.57031 c 0,-0.30956 -0.05028,-0.66208 -0.314453,-0.93165 -0.264176,-0.26956 -0.637827,-0.35156 -1.052735,-0.35156 h -11.06446 z"
id="path30" />
</g>
<g
id="text3-1"
style="opacity:1">
<path
style="font-weight:bold;font-size:21.1667px;font-family:Recharge;-inkscape-font-specification:'Recharge Bold';text-align:center;baseline-shift:baseline;text-anchor:middle;display:inline;overflow:visible;vector-effect:none;fill:#ff0000;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="m 368.78211,297.97579 h -3.38667 v -3.78883 h -3.83117 v 3.78883 h -2.58234 v 0.16934 l 1.05833,2.75167 h 1.52401 v 8.69951 h 3.83117 v -8.69951 h 3.38667 z m 13.52551,-0.27516 c 0.61384,0 0.889,0.254 0.889,0.762 v 0.74083 c 0,0.5715 -0.254,0.91017 -0.91016,0.91017 h -10.11769 v 0.16934 l 0.99484,2.70933 h 9.05935 c 0.6985,0 1.03716,0.29634 1.03716,0.9525 v 0.76201 c 0,0.71966 -0.29633,1.05833 -1.16416,1.05833 h -11.74752 v 3.83117 h 13.03868 c 3.00568,0 4.10634,-1.50283 4.10634,-4.064 v -1.16417 c 0,-1.3335 -0.6985,-2.30717 -1.56633,-2.73051 0.635,-0.46566 1.22767,-1.22766 1.22767,-2.56117 v -1.29117 c 0,-2.159 -1.03717,-3.59833 -3.89468,-3.59833 h -13.50435 v 0.16933 l 1.35467,3.34434 z"
id="path27" />
<path
style="font-weight:bold;font-size:21.1667px;font-family:Recharge;-inkscape-font-specification:'Recharge Bold';text-align:center;baseline-shift:baseline;text-anchor:middle;display:inline;overflow:visible;vector-effect:none;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="m 361.06445,293.6875 v 3.78906 h -2.58203 v 0.76172 l 1.21485,3.1582 h 1.36718 v 8.69922 h 4.83008 v -8.69922 h 3.38672 v -3.91992 h -3.38672 v -3.78906 z m 8.19141,0 v 0.76562 l 1.51758,3.74805 h 11.53515 c 0.23796,0 0.328,0.051 0.34571,0.0664 0.0177,0.0154 0.043,0.0316 0.043,0.19531 v 0.74023 c 0,0.21168 -0.0464,0.29622 -0.0762,0.32813 -0.0297,0.0319 -0.0919,0.082 -0.33398,0.082 h -10.61914 v 0.75781 l 1.14648,3.1211 h 9.40821 c 0.27854,0 0.40023,0.0587 0.44531,0.0996 0.0451,0.0409 0.0918,0.11145 0.0918,0.35351 v 0.76172 c 0,0.2912 -0.0525,0.39055 -0.10157,0.4375 -0.0491,0.047 -0.19362,0.12109 -0.5625,0.12109 h -12.24804 v 4.83008 h 13.53906 c 1.58617,0 2.77621,-0.40668 3.54492,-1.22851 0.76871,-0.82184 1.0625,-1.98178 1.0625,-3.33399 v -1.16601 c 0,-1.24687 -0.61226,-2.15723 -1.39258,-2.75782 0.56011,-0.56303 1.05274,-1.33541 1.05274,-2.5332 v -1.29101 c 0,-1.15957 -0.2836,-2.20926 -1.01758,-2.95899 -0.73398,-0.74972 -1.86425,-1.13867 -3.37695,-1.13867 z m -7.19141,1 h 2.83008 v 3.78906 h 3.38672 v 1.91992 h -3.38672 v 8.69922 h -2.83008 v -8.69922 h -1.68164 l -0.73828,-1.91992 h 2.41992 z m 8.36524,0 h 12.83008 c 1.3448,0 2.16312,0.3282 2.6621,0.83789 0.49899,0.50969 0.73243,1.26033 0.73243,2.25977 v 1.29101 c 0,1.18536 -0.47856,1.75863 -1.02344,2.1582 l -0.66797,0.49024 0.74414,0.36328 c 0.69133,0.33724 1.28711,1.1204 1.28711,2.2793 v 1.16601 c 0,1.20896 -0.25798,2.08052 -0.79102,2.65039 -0.53303,0.56988 -1.39689,0.91211 -2.8164,0.91211 h -12.53906 v -2.83008 h 11.24804 c 0.49896,0 0.93787,-0.0961 1.25391,-0.39843 0.31604,-0.3023 0.41016,-0.7317 0.41016,-1.16016 v -0.76172 c 0,-0.4141 -0.12105,-0.82246 -0.41993,-1.09375 -0.29887,-0.27129 -0.69723,-0.35937 -1.11718,-0.35937 h -8.71094 l -0.68945,-1.87891 h 9.46484 c 0.4141,0 0.80511,-0.12009 1.06641,-0.40039 0.26129,-0.2803 0.34375,-0.64994 0.34375,-1.00977 v -0.74023 c 0,-0.34427 -0.11339,-0.71067 -0.38672,-0.94922 -0.27333,-0.23854 -0.62607,-0.3125 -1.00196,-0.3125 h -10.86132 z"
id="path28" />
</g>
<g
id="text6-84"
style="opacity:1">
<path
style="font-weight:bold;font-size:21.1667px;font-family:Recharge;-inkscape-font-specification:'Recharge Bold';text-align:center;baseline-shift:baseline;text-anchor:middle;display:inline;overflow:visible;vector-effect:none;fill:#ff0000;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="m 198.87817,19.323245 h 2.20134 v -4.826008 h 4.80484 v -2.18017 h -4.80484 V 7.4910596 h -2.20134 v 4.8260074 h -4.80484 v 2.18017 h 4.80484 z"
id="path13" />
<path
style="font-weight:bold;font-size:21.1667px;font-family:Recharge;-inkscape-font-specification:'Recharge Bold';text-align:center;baseline-shift:baseline;text-anchor:middle;display:inline;overflow:visible;vector-effect:none;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="m 198.37891,6.9902344 v 4.8261716 h -4.80469 v 3.181641 h 4.80469 v 4.826172 h 3.20117 v -4.826172 h 4.80469 v -3.181641 h -4.80469 V 6.9902344 Z m 1,1 h 1.20117 v 4.8261716 h 4.80469 v 1.181641 h -4.80469 v 4.826172 h -1.20117 v -4.826172 h -4.80469 v -1.181641 h 4.80469 z"
id="path14" />
</g>
<g
id="text6"
style="opacity:1">
<path
style="font-weight:bold;font-size:21.1667px;font-family:Recharge;-inkscape-font-specification:'Recharge Bold';text-align:center;baseline-shift:baseline;text-anchor:middle;display:inline;overflow:visible;vector-effect:none;fill:#ff0000;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="m 28.866225,149.41589 h 2.201337 v -4.82601 h 4.804841 v -2.18017 h -4.804841 v -4.826 h -2.201337 v 4.826 h -4.804841 v 2.18017 h 4.804841 z"
id="path21" />
<path
style="font-weight:bold;font-size:21.1667px;font-family:Recharge;-inkscape-font-specification:'Recharge Bold';text-align:center;baseline-shift:baseline;text-anchor:middle;display:inline;overflow:visible;vector-effect:none;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="m 28.367187,137.08398 v 4.82618 h -4.80664 v 3.17968 h 4.80664 v 4.82618 h 3.201172 v -4.82618 h 4.804688 v -3.17968 h -4.804688 v -4.82618 z m 1,1 h 1.201172 v 4.82618 h 4.804688 v 1.17968 h -4.804688 v 4.82618 h -1.201172 v -4.82618 h -4.80664 v -1.17968 h 4.80664 z"
id="path22" />
</g>
<g
id="text6-8"
style="opacity:1">
<path
style="font-weight:bold;font-size:21.1667px;font-family:Recharge;-inkscape-font-specification:'Recharge Bold';text-align:center;baseline-shift:baseline;text-anchor:middle;display:inline;overflow:visible;vector-effect:none;fill:#ff0000;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="m 198.87817,149.53483 h 2.20134 v -4.826 h 4.80484 v -2.18017 h -4.80484 v -4.82601 h -2.20134 v 4.82601 h -4.80484 v 2.18017 h 4.80484 z"
id="path17" />
<path
style="font-weight:bold;font-size:21.1667px;font-family:Recharge;-inkscape-font-specification:'Recharge Bold';text-align:center;baseline-shift:baseline;text-anchor:middle;display:inline;overflow:visible;vector-effect:none;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="m 198.37891,137.20312 v 4.82618 h -4.80469 v 3.17968 h 4.80469 v 4.82618 h 3.20117 v -4.82618 h 4.80469 v -3.17968 h -4.80469 v -4.82618 z m 1,1 h 1.20117 v 4.82618 h 4.80469 v 1.17968 h -4.80469 v 4.82618 h -1.20117 v -4.82618 h -4.80469 v -1.17968 h 4.80469 z"
id="path18" />
</g>
<g
id="text6-3"
style="opacity:1">
<path
style="font-weight:bold;font-size:21.1667px;font-family:Recharge;-inkscape-font-specification:'Recharge Bold';text-align:center;baseline-shift:baseline;text-anchor:middle;display:inline;overflow:visible;vector-effect:none;fill:#ff0000;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="m 368.87506,149.45341 h 2.20133 v -4.82601 h 4.80484 v -2.18017 h -4.80484 v -4.826 h -2.20133 v 4.826 h -4.80484 v 2.18017 h 4.80484 z"
id="path19" />
<path
style="font-weight:bold;font-size:21.1667px;font-family:Recharge;-inkscape-font-specification:'Recharge Bold';text-align:center;baseline-shift:baseline;text-anchor:middle;display:inline;overflow:visible;vector-effect:none;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="m 368.375,137.12109 v 4.82618 h -4.80469 v 3.17968 h 4.80469 v 4.82617 h 3.20117 v -4.82617 h 4.80469 v -3.17968 h -4.80469 v -4.82618 z m 1,1 h 1.20117 v 4.82618 h 4.80469 v 1.17968 h -4.80469 v 4.82617 H 369.375 v -4.82617 h -4.80469 v -1.17968 h 4.80469 z"
id="path20" />
</g>
<g
id="text6-2"
style="opacity:1">
<path
style="font-weight:bold;font-size:21.1667px;font-family:Recharge;-inkscape-font-specification:'Recharge Bold';text-align:center;baseline-shift:baseline;text-anchor:middle;display:inline;overflow:visible;vector-effect:none;fill:#ff0000;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="m 198.8614,307.41443 h 2.20134 v -4.82601 h 4.80484 v -2.18017 h -4.80484 v -4.82601 h -2.20134 v 4.82601 h -4.80484 v 2.18017 h 4.80484 z"
id="path15" />
<path
style="font-weight:bold;font-size:21.1667px;font-family:Recharge;-inkscape-font-specification:'Recharge Bold';text-align:center;baseline-shift:baseline;text-anchor:middle;display:inline;overflow:visible;vector-effect:none;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="m 198.36133,295.08203 v 4.82617 h -4.80469 v 3.17969 h 4.80469 v 4.82617 h 3.20117 v -4.82617 h 4.80469 v -3.17969 h -4.80469 v -4.82617 z m 1,1 h 1.20117 v 4.82617 h 4.80469 v 1.17969 h -4.80469 v 4.82617 h -1.20117 v -4.82617 h -4.80469 v -1.17969 h 4.80469 z"
id="path16" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 20 KiB

@@ -0,0 +1,261 @@
{
"type": "machine",
"name": "Xplorer Dual Gantry 0.4 nozzle",
"inherits": "Xplorer common",
"from": "system",
"setting_id": "DgYk8X6L1R0MKcfg",
"instantiation": "true",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\n{if layer_num != 0}\nG92 E0\n{endif}\n{if layer_num <= 0}\nG91\nG1 Z+10 F1200\nG90\n{endif}",
"change_filament_gcode": ";CHANGE_FILAMENT - Start\n;Layer#=[layer_num] - Z=[layer_z]\n{if layer_z >first_layer_height}\nG91\nG1 Z+2 F1200\nG90\n{endif}\n{if layer_num != -1 || next_extruder != 0}\n Tool{next_extruder}\n{endif}\n; <----\nG1 Z[layer_z] F1200\n{if layer_num == -1 || layer_num == 0}\n M109 S[first_layer_temperature[next_extruder]] T[next_extruder]\n{else}\n M104 S[temperature[next_extruder]] T[next_extruder]\n{endif}\n; ---->\nM83\n;Layer#=[layer_num] - Z=[layer_z]\nLOG_EXTR\n;CHANGE_FILAMENT - End",
"default_nozzle_volume_type": [
"High Flow",
"High Flow"
],
"default_print_profile": "0.20mm Standard @Xplorer 0.4",
"deretraction_speed": [
"120",
"120"
],
"extruder_colour": [
"#FCE94F",
"#729FCF"
],
"extruder_offset": [
"0x0",
"0x0"
],
"extruder_type": [
"Direct Drive",
"Direct Drive"
],
"extruder_variant_list": [
"Direct Drive High Flow",
"Direct Drive High Flow"
],
"imex_gantry_count": "2",
"imex_mode_active_tools": [
"0:P",
"0:P,1:C",
"0:P,1:M"
],
"imex_mode_gcodes": [
"SET_PRINT_MODE MODE=PRIMARY",
"SET_PRINT_MODE MODE=COPY",
"SET_PRINT_MODE MODE=MIRROR"
],
"imex_mode_names": [
"primary",
"copy",
"mirror"
],
"imex_tool_layout": "rear-left",
"imex_tools_per_gantry": "1",
"is_imex": "1",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]\nLOG_RESUME\nLOG_EXTR",
"long_retractions_when_cut": [
"0",
"0"
],
"machine_max_acceleration_e": [
"6000",
"5000",
"6000",
"5000"
],
"machine_max_acceleration_extruding": [
"5000",
"2500",
"5000",
"2500"
],
"machine_max_acceleration_retracting": [
"5000",
"5000",
"5000",
"5000"
],
"machine_max_acceleration_travel": [
"5000",
"2500",
"5000",
"2500"
],
"machine_max_acceleration_x": [
"5000",
"2500",
"5000",
"2500"
],
"machine_max_acceleration_y": [
"5000",
"2500",
"5000",
"2500"
],
"machine_max_acceleration_z": [
"100",
"50",
"100",
"50"
],
"machine_max_jerk_e": [
"2.5",
"1.25",
"2.5",
"1.25"
],
"machine_max_jerk_x": [
"5",
"2.5",
"5",
"2.5"
],
"machine_max_jerk_y": [
"5",
"2.5",
"5",
"2.5"
],
"machine_max_jerk_z": [
"0.4",
"0.2",
"0.4",
"0.2"
],
"machine_max_junction_deviation": [
"0",
"0",
"0",
"0"
],
"machine_max_speed_e": [
"120",
"60",
"120",
"60"
],
"machine_max_speed_x": [
"300",
"150",
"300",
"150"
],
"machine_max_speed_y": [
"300",
"150",
"300",
"150"
],
"machine_max_speed_z": [
"40",
"20",
"40",
"20"
],
"machine_start_gcode": "M104 S{first_layer_temperature[0]} T0\nM104 S{first_layer_temperature[1]} T1\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S{first_layer_temperature[0]} T0\nM109 S{first_layer_temperature[1]} T1\nstart_print\nG1 F5000\n{if first_layer_temperature[1] !=0 }\nTool1\nbrush_Tool1\n{endif}\n{if first_layer_temperature[0] !=0 }\nTool0\nbrush_Tool0\n{endif}\n{if first_layer_temperature[1] !=0 }\nTool1\nG1 Y280 X10.0 Z0.38 F5000.0 \nG1 Y100 X10.0 Z0.38 F1500.0 E15 \nG1 Y100 X10.4 Z0.38 F5000.0 \nG1 Y280 X10.4 Z0.38 F1500.0 E30\nG92 E0\nG1 Z2 F2000\n{endif}\nG1 F5000\n{if first_layer_temperature[0] !=0 }\nTool0\nG1 Y280 X11.0 Z0.38 F5000.0 \nG1 Y100 X11.0 Z0.38 F1500.0 E15 \nG1 Y100 X11.4 Z0.38 F5000.0 \nG1 Y280 X11.4 Z0.38 F1500.0 E30\nG92 E0\nG1 Z2 F2000\n{endif}\nG1 F5000\nM400\nSKEW_PROFILE LOAD=SKEW1\nG28 X\nG28 Y\nTool{initial_extruder}\nSTART_PRINTING_MODE\n\n",
"max_layer_height": [
"0.32",
"0.32"
],
"min_layer_height": [
"0.04",
"0.04"
],
"nozzle_diameter": [
"0.4",
"0.4"
],
"nozzle_type": [
"hardened_steel",
"hardened_steel"
],
"nozzle_volume": [
"0",
"0"
],
"printable_area": [
"10x0",
"410x0",
"410x342.5",
"10x342.5"
],
"printable_height": "401",
"printer_extruder_id": [
"1",
"2"
],
"printer_extruder_variant": [
"Direct Drive High Flow",
"Direct Drive High Flow"
],
"printer_model": "Xplorer Dual Gantry",
"printer_variant": "0.4",
"retract_before_wipe": [
"70%",
"70%"
],
"retract_length_toolchange": [
"2",
"2"
],
"retract_lift_above": [
"0.2",
"0.2"
],
"retract_lift_below": [
"400",
"400"
],
"retract_lift_enforce": [
"All Surfaces",
"All Surfaces"
],
"retract_restart_extra": [
"0",
"0"
],
"retract_restart_extra_toolchange": [
"0",
"0"
],
"retract_when_changing_layer": [
"1",
"1"
],
"retraction_distances_when_cut": [
"18",
"18"
],
"retraction_length": [
"1.35",
"1.35"
],
"retraction_minimum_travel": [
"2",
"2"
],
"retraction_speed": [
"120",
"120"
],
"travel_slope": [
"1",
"1"
],
"wipe": [
"1",
"1"
],
"wipe_distance": [
"1",
"1"
],
"z_hop": [
"1",
"1"
],
"z_hop_types": [
"Normal Lift",
"Normal Lift"
]
}
@@ -0,0 +1,261 @@
{
"type": "machine",
"name": "Xplorer Dual Gantry 0.6 nozzle",
"inherits": "Xplorer common",
"from": "system",
"setting_id": "M1ZyjMxGz0TpRDcv",
"instantiation": "true",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\n{if layer_num != 0}\nG92 E0\n{endif}\n{if layer_num <= 0}\nG91\nG1 Z+10 F1200\nG90\n{endif}",
"change_filament_gcode": ";CHANGE_FILAMENT - Start\n;Layer#=[layer_num] - Z=[layer_z]\n{if layer_z >first_layer_height}\nG91\nG1 Z+2 F1200\nG90\n{endif}\n{if layer_num != -1 || next_extruder != 0}\n Tool{next_extruder}\n{endif}\n; <----\nG1 Z[layer_z] F1200\n{if layer_num == -1 || layer_num == 0}\n M109 S[first_layer_temperature[next_extruder]] T[next_extruder]\n{else}\n M104 S[temperature[next_extruder]] T[next_extruder]\n{endif}\n; ---->\nM83\n;Layer#=[layer_num] - Z=[layer_z]\nLOG_EXTR\n;CHANGE_FILAMENT - End",
"default_nozzle_volume_type": [
"High Flow",
"High Flow"
],
"default_print_profile": "0.30mm Standard @Xplorer 0.6",
"deretraction_speed": [
"120",
"120"
],
"extruder_colour": [
"#FCE94F",
"#729FCF"
],
"extruder_offset": [
"0x0",
"0x0"
],
"extruder_type": [
"Direct Drive",
"Direct Drive"
],
"extruder_variant_list": [
"Direct Drive High Flow",
"Direct Drive High Flow"
],
"imex_gantry_count": "2",
"imex_mode_active_tools": [
"0:P",
"0:P,1:C",
"0:P,1:M"
],
"imex_mode_gcodes": [
"SET_PRINT_MODE MODE=PRIMARY",
"SET_PRINT_MODE MODE=COPY",
"SET_PRINT_MODE MODE=MIRROR"
],
"imex_mode_names": [
"primary",
"copy",
"mirror"
],
"imex_tool_layout": "rear-left",
"imex_tools_per_gantry": "1",
"is_imex": "1",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]\nLOG_RESUME\nLOG_EXTR",
"long_retractions_when_cut": [
"0",
"0"
],
"machine_max_acceleration_e": [
"6000",
"5000",
"6000",
"5000"
],
"machine_max_acceleration_extruding": [
"5000",
"2500",
"5000",
"2500"
],
"machine_max_acceleration_retracting": [
"5000",
"5000",
"5000",
"5000"
],
"machine_max_acceleration_travel": [
"5000",
"2500",
"5000",
"2500"
],
"machine_max_acceleration_x": [
"5000",
"2500",
"5000",
"2500"
],
"machine_max_acceleration_y": [
"5000",
"2500",
"5000",
"2500"
],
"machine_max_acceleration_z": [
"100",
"50",
"100",
"50"
],
"machine_max_jerk_e": [
"2.5",
"1.25",
"2.5",
"1.25"
],
"machine_max_jerk_x": [
"5",
"2.5",
"5",
"2.5"
],
"machine_max_jerk_y": [
"5",
"2.5",
"5",
"2.5"
],
"machine_max_jerk_z": [
"0.4",
"0.2",
"0.4",
"0.2"
],
"machine_max_junction_deviation": [
"0",
"0",
"0",
"0"
],
"machine_max_speed_e": [
"120",
"60",
"120",
"60"
],
"machine_max_speed_x": [
"300",
"150",
"300",
"150"
],
"machine_max_speed_y": [
"300",
"150",
"300",
"150"
],
"machine_max_speed_z": [
"40",
"20",
"40",
"20"
],
"machine_start_gcode": "M104 S{first_layer_temperature[0]} T0\nM104 S{first_layer_temperature[1]} T1\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S{first_layer_temperature[0]} T0\nM109 S{first_layer_temperature[1]} T1\nstart_print\nG1 F5000\n{if first_layer_temperature[1] !=0 }\nTool1\nbrush_Tool1\n{endif}\n{if first_layer_temperature[0] !=0 }\nTool0\nbrush_Tool0\n{endif}\n{if first_layer_temperature[1] !=0 }\nTool1\nG1 Y280 X10.0 Z0.38 F5000.0 \nG1 Y100 X10.0 Z0.38 F1500.0 E15 \nG1 Y100 X10.4 Z0.38 F5000.0 \nG1 Y280 X10.4 Z0.38 F1500.0 E30\nG92 E0\nG1 Z2 F2000\n{endif}\nG1 F5000\n{if first_layer_temperature[0] !=0 }\nTool0\nG1 Y280 X11.0 Z0.38 F5000.0 \nG1 Y100 X11.0 Z0.38 F1500.0 E15 \nG1 Y100 X11.4 Z0.38 F5000.0 \nG1 Y280 X11.4 Z0.38 F1500.0 E30\nG92 E0\nG1 Z2 F2000\n{endif}\nG1 F5000\nM400\nSKEW_PROFILE LOAD=SKEW1\nG28 X\nG28 Y\nTool{initial_extruder}\nSTART_PRINTING_MODE\n\n",
"max_layer_height": [
"0.45",
"0.45"
],
"min_layer_height": [
"0.12",
"0.12"
],
"nozzle_diameter": [
"0.6",
"0.6"
],
"nozzle_type": [
"hardened_steel",
"hardened_steel"
],
"nozzle_volume": [
"0",
"0"
],
"printable_area": [
"10x0",
"410x0",
"410x342.5",
"10x342.5"
],
"printable_height": "401",
"printer_extruder_id": [
"1",
"2"
],
"printer_extruder_variant": [
"Direct Drive High Flow",
"Direct Drive High Flow"
],
"printer_model": "Xplorer Dual Gantry",
"printer_variant": "0.6",
"retract_before_wipe": [
"70%",
"70%"
],
"retract_length_toolchange": [
"2",
"2"
],
"retract_lift_above": [
"0.2",
"0.2"
],
"retract_lift_below": [
"400",
"400"
],
"retract_lift_enforce": [
"All Surfaces",
"All Surfaces"
],
"retract_restart_extra": [
"0",
"0"
],
"retract_restart_extra_toolchange": [
"0",
"0"
],
"retract_when_changing_layer": [
"1",
"1"
],
"retraction_distances_when_cut": [
"18",
"18"
],
"retraction_length": [
"1.35",
"1.35"
],
"retraction_minimum_travel": [
"2",
"2"
],
"retraction_speed": [
"120",
"120"
],
"travel_slope": [
"1",
"1"
],
"wipe": [
"1",
"1"
],
"wipe_distance": [
"1",
"1"
],
"z_hop": [
"1",
"1"
],
"z_hop_types": [
"Normal Lift",
"Normal Lift"
]
}
@@ -0,0 +1,261 @@
{
"type": "machine",
"name": "Xplorer Dual Gantry 0.8 nozzle",
"inherits": "Xplorer common",
"from": "system",
"setting_id": "fWRcMcf9H5NP6zrX",
"instantiation": "true",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\n{if layer_num != 0}\nG92 E0\n{endif}\n{if layer_num <= 0}\nG91\nG1 Z+10 F1200\nG90\n{endif}",
"change_filament_gcode": ";CHANGE_FILAMENT - Start\n;Layer#=[layer_num] - Z=[layer_z]\n{if layer_z >first_layer_height}\nG91\nG1 Z+2 F1200\nG90\n{endif}\n{if layer_num != -1 || next_extruder != 0}\n Tool{next_extruder}\n{endif}\n; <----\nG1 Z[layer_z] F1200\n{if layer_num == -1 || layer_num == 0}\n M109 S[first_layer_temperature[next_extruder]] T[next_extruder]\n{else}\n M104 S[temperature[next_extruder]] T[next_extruder]\n{endif}\n; ---->\nM83\n;Layer#=[layer_num] - Z=[layer_z]\nLOG_EXTR\n;CHANGE_FILAMENT - End",
"default_nozzle_volume_type": [
"High Flow",
"High Flow"
],
"default_print_profile": "0.40mm Standard @Xplorer 0.8",
"deretraction_speed": [
"120",
"120"
],
"extruder_colour": [
"#FCE94F",
"#729FCF"
],
"extruder_offset": [
"0x0",
"0x0"
],
"extruder_type": [
"Direct Drive",
"Direct Drive"
],
"extruder_variant_list": [
"Direct Drive High Flow",
"Direct Drive High Flow"
],
"imex_gantry_count": "2",
"imex_mode_active_tools": [
"0:P",
"0:P,1:C",
"0:P,1:M"
],
"imex_mode_gcodes": [
"SET_PRINT_MODE MODE=PRIMARY",
"SET_PRINT_MODE MODE=COPY",
"SET_PRINT_MODE MODE=MIRROR"
],
"imex_mode_names": [
"primary",
"copy",
"mirror"
],
"imex_tool_layout": "rear-left",
"imex_tools_per_gantry": "1",
"is_imex": "1",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]\nLOG_RESUME\nLOG_EXTR",
"long_retractions_when_cut": [
"0",
"0"
],
"machine_max_acceleration_e": [
"6000",
"5000",
"6000",
"5000"
],
"machine_max_acceleration_extruding": [
"5000",
"2500",
"5000",
"2500"
],
"machine_max_acceleration_retracting": [
"5000",
"5000",
"5000",
"5000"
],
"machine_max_acceleration_travel": [
"5000",
"2500",
"5000",
"2500"
],
"machine_max_acceleration_x": [
"5000",
"2500",
"5000",
"2500"
],
"machine_max_acceleration_y": [
"5000",
"2500",
"5000",
"2500"
],
"machine_max_acceleration_z": [
"100",
"50",
"100",
"50"
],
"machine_max_jerk_e": [
"2.5",
"1.25",
"2.5",
"1.25"
],
"machine_max_jerk_x": [
"5",
"2.5",
"5",
"2.5"
],
"machine_max_jerk_y": [
"5",
"2.5",
"5",
"2.5"
],
"machine_max_jerk_z": [
"0.4",
"0.2",
"0.4",
"0.2"
],
"machine_max_junction_deviation": [
"0",
"0",
"0",
"0"
],
"machine_max_speed_e": [
"120",
"60",
"120",
"60"
],
"machine_max_speed_x": [
"300",
"150",
"300",
"150"
],
"machine_max_speed_y": [
"300",
"150",
"300",
"150"
],
"machine_max_speed_z": [
"40",
"20",
"40",
"20"
],
"machine_start_gcode": "M104 S{first_layer_temperature[0]} T0\nM104 S{first_layer_temperature[1]} T1\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S{first_layer_temperature[0]} T0\nM109 S{first_layer_temperature[1]} T1\nstart_print\nG1 F5000\n{if first_layer_temperature[1] !=0 }\nTool1\nbrush_Tool1\n{endif}\n{if first_layer_temperature[0] !=0 }\nTool0\nbrush_Tool0\n{endif}\n{if first_layer_temperature[1] !=0 }\nTool1\nG1 Y280 X10.0 Z0.38 F5000.0 \nG1 Y100 X10.0 Z0.38 F1500.0 E15 \nG1 Y100 X10.4 Z0.38 F5000.0 \nG1 Y280 X10.4 Z0.38 F1500.0 E30\nG92 E0\nG1 Z2 F2000\n{endif}\nG1 F5000\n{if first_layer_temperature[0] !=0 }\nTool0\nG1 Y280 X11.0 Z0.38 F5000.0 \nG1 Y100 X11.0 Z0.38 F1500.0 E15 \nG1 Y100 X11.4 Z0.38 F5000.0 \nG1 Y280 X11.4 Z0.38 F1500.0 E30\nG92 E0\nG1 Z2 F2000\n{endif}\nG1 F5000\nM400\nSKEW_PROFILE LOAD=SKEW1\nG28 X\nG28 Y\nTool{initial_extruder}\nSTART_PRINTING_MODE\n\n",
"max_layer_height": [
"0.64",
"0.64"
],
"min_layer_height": [
"0.2",
"0.2"
],
"nozzle_diameter": [
"0.8",
"0.8"
],
"nozzle_type": [
"hardened_steel",
"hardened_steel"
],
"nozzle_volume": [
"0",
"0"
],
"printable_area": [
"10x0",
"410x0",
"410x342.5",
"10x342.5"
],
"printable_height": "401",
"printer_extruder_id": [
"1",
"2"
],
"printer_extruder_variant": [
"Direct Drive High Flow",
"Direct Drive High Flow"
],
"printer_model": "Xplorer Dual Gantry",
"printer_variant": "0.8",
"retract_before_wipe": [
"70%",
"70%"
],
"retract_length_toolchange": [
"2",
"2"
],
"retract_lift_above": [
"0.2",
"0.2"
],
"retract_lift_below": [
"400",
"400"
],
"retract_lift_enforce": [
"All Surfaces",
"All Surfaces"
],
"retract_restart_extra": [
"0",
"0"
],
"retract_restart_extra_toolchange": [
"0",
"0"
],
"retract_when_changing_layer": [
"1",
"1"
],
"retraction_distances_when_cut": [
"18",
"18"
],
"retraction_length": [
"1.35",
"1.35"
],
"retraction_minimum_travel": [
"2",
"2"
],
"retraction_speed": [
"120",
"120"
],
"travel_slope": [
"1",
"1"
],
"wipe": [
"1",
"1"
],
"wipe_distance": [
"1",
"1"
],
"z_hop": [
"1",
"1"
],
"z_hop_types": [
"Normal Lift",
"Normal Lift"
]
}
@@ -0,0 +1,254 @@
{
"type": "machine",
"name": "Xplorer IDEX 0.4 nozzle",
"inherits": "Xplorer common",
"from": "system",
"setting_id": "lOZ2qJY7SsNRW03z",
"instantiation": "true",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\n{if layer_num != 0}\nG92 E0\n{endif}\n{if layer_num <= 0}\nG91\nG1 Z+10 F1200\nG90\n{endif}",
"change_filament_gcode": ";CHANGE_FILAMENT - Start\n;Layer#=[layer_num] - Z=[layer_z]\n{if layer_z >first_layer_height}\nG91\nG1 Z+2 F1200\nG90\n{endif}\n{if layer_num != -1 || next_extruder != 0}\n Tool{next_extruder}\n{endif}\n; <----\nG1 Z[layer_z] F1200\n{if layer_num == -1 || layer_num == 0}\n M109 S[first_layer_temperature[next_extruder]] T[next_extruder]\n{else}\n M104 S[temperature[next_extruder]] T[next_extruder]\n{endif}\n; ---->\nM83\n;Layer#=[layer_num] - Z=[layer_z]\nLOG_EXTR\n;CHANGE_FILAMENT - End",
"default_nozzle_volume_type": [
"High Flow",
"High Flow"
],
"default_print_profile": "0.20mm Standard @Xplorer 0.4",
"deretraction_speed": [
"120",
"120"
],
"extruder_colour": [
"#FCE94F",
"#729FCF"
],
"extruder_offset": [
"0x0",
"0x0"
],
"extruder_type": [
"Direct Drive",
"Direct Drive"
],
"extruder_variant_list": [
"Direct Drive High Flow",
"Direct Drive High Flow"
],
"imex_mode_active_tools": [
"0:P",
"0:P,1:C",
"0:P,1:M"
],
"imex_mode_gcodes": [
"SET_PRINT_MODE MODE=PRIMARY",
"SET_PRINT_MODE MODE=COPY",
"SET_PRINT_MODE MODE=MIRROR"
],
"imex_mode_names": [
"primary",
"copy",
"mirror"
],
"imex_nozzle_clearance_x": "40",
"imex_tool_layout": "front-left",
"is_imex": "1",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]\nLOG_RESUME\nLOG_EXTR",
"long_retractions_when_cut": [
"0",
"0"
],
"machine_max_acceleration_e": [
"6000",
"5000",
"6000",
"5000"
],
"machine_max_acceleration_extruding": [
"5000",
"2500",
"5000",
"2500"
],
"machine_max_acceleration_retracting": [
"5000",
"5000",
"5000",
"5000"
],
"machine_max_acceleration_travel": [
"5000",
"2500",
"5000",
"2500"
],
"machine_max_acceleration_x": [
"5000",
"2500",
"5000",
"2500"
],
"machine_max_acceleration_y": [
"5000",
"2500",
"5000",
"2500"
],
"machine_max_acceleration_z": [
"100",
"50",
"100",
"50"
],
"machine_max_jerk_e": [
"2.5",
"1.25",
"2.5",
"1.25"
],
"machine_max_jerk_x": [
"5",
"2.5",
"5",
"2.5"
],
"machine_max_jerk_y": [
"5",
"2.5",
"5",
"2.5"
],
"machine_max_jerk_z": [
"0.4",
"0.2",
"0.4",
"0.2"
],
"machine_max_junction_deviation": [
"0",
"0",
"0",
"0"
],
"machine_max_speed_e": [
"120",
"60",
"120",
"60"
],
"machine_max_speed_x": [
"300",
"150",
"300",
"150"
],
"machine_max_speed_y": [
"300",
"150",
"300",
"150"
],
"machine_max_speed_z": [
"40",
"20",
"40",
"20"
],
"machine_start_gcode": "M104 S{first_layer_temperature[0]} T0\nM104 S{first_layer_temperature[1]} T1\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S{first_layer_temperature[0]} T0\nM109 S{first_layer_temperature[1]} T1\nstart_print\nG1 F5000\n{if first_layer_temperature[1] !=0 }\nTool1\nbrush_Tool1\n{endif}\n{if first_layer_temperature[0] !=0 }\nTool0\nbrush_Tool0\n{endif}\n{if first_layer_temperature[1] !=0 }\nTool1\nG1 X280.1 Y3.0 Z0.38 F5000.0 \nG1 X100.1 Y3.0 Z0.38 F1500.0 E15 \nG1 X100.1 Y3.4 Z0.38 F5000.0 \nG1 X280.1 Y3.4 Z0.38 F1500.0 E30\nG92 E0\nG1 Z2 F2000\n{endif}\n{if first_layer_temperature[0] !=0 }\nTool0\nG1 X100.1 Y2.0 Z0.38 F5000.0 \nG1 X280.1 Y2.0 Z0.38 F1500.0 E15 \nG1 X280.1 Y2.4 Z0.38 F5000.0 \nG1 X100.1 Y2.4 Z0.38 F1500.0 E30\nG92 E0\nG1 Z2 F2000\n{endif}\nM400\nSKEW_PROFILE LOAD=SKEW1\nG28 X\nTool{initial_extruder}\nSTART_PRINTING_MODE\n\n",
"max_layer_height": [
"0.32",
"0.32"
],
"min_layer_height": [
"0.04",
"0.04"
],
"nozzle_diameter": [
"0.4",
"0.4"
],
"nozzle_type": [
"hardened_steel",
"hardened_steel"
],
"nozzle_volume": [
"0",
"0"
],
"printable_height": "401",
"printer_extruder_id": [
"1",
"2"
],
"printer_extruder_variant": [
"Direct Drive High Flow",
"Direct Drive High Flow"
],
"printer_model": "Xplorer IDEX",
"printer_variant": "0.4",
"retract_before_wipe": [
"70%",
"70%"
],
"retract_length_toolchange": [
"2",
"2"
],
"retract_lift_above": [
"0.2",
"0.2"
],
"retract_lift_below": [
"400",
"400"
],
"retract_lift_enforce": [
"All Surfaces",
"All Surfaces"
],
"retract_restart_extra": [
"0",
"0"
],
"retract_restart_extra_toolchange": [
"0",
"0"
],
"retract_when_changing_layer": [
"1",
"1"
],
"retraction_distances_when_cut": [
"18",
"18"
],
"retraction_length": [
"1.35",
"1.35"
],
"retraction_minimum_travel": [
"2",
"2"
],
"retraction_speed": [
"120",
"120"
],
"travel_slope": [
"1",
"1"
],
"wipe": [
"1",
"1"
],
"wipe_distance": [
"1",
"1"
],
"z_hop": [
"1",
"1"
],
"z_hop_types": [
"Normal Lift",
"Normal Lift"
]
}
@@ -0,0 +1,254 @@
{
"type": "machine",
"name": "Xplorer IDEX 0.6 nozzle",
"inherits": "Xplorer common",
"from": "system",
"setting_id": "S4ZLGZuPtR3yC4IZ",
"instantiation": "true",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\n{if layer_num != 0}\nG92 E0\n{endif}\n{if layer_num <= 0}\nG91\nG1 Z+10 F1200\nG90\n{endif}",
"change_filament_gcode": ";CHANGE_FILAMENT - Start\n;Layer#=[layer_num] - Z=[layer_z]\n{if layer_z >first_layer_height}\nG91\nG1 Z+2 F1200\nG90\n{endif}\n{if layer_num != -1 || next_extruder != 0}\n Tool{next_extruder}\n{endif}\n; <----\nG1 Z[layer_z] F1200\n{if layer_num == -1 || layer_num == 0}\n M109 S[first_layer_temperature[next_extruder]] T[next_extruder]\n{else}\n M104 S[temperature[next_extruder]] T[next_extruder]\n{endif}\n; ---->\nM83\n;Layer#=[layer_num] - Z=[layer_z]\nLOG_EXTR\n;CHANGE_FILAMENT - End",
"default_nozzle_volume_type": [
"High Flow",
"High Flow"
],
"default_print_profile": "0.30mm Standard @Xplorer 0.6",
"deretraction_speed": [
"120",
"120"
],
"extruder_colour": [
"#FCE94F",
"#729FCF"
],
"extruder_offset": [
"0x0",
"0x0"
],
"extruder_type": [
"Direct Drive",
"Direct Drive"
],
"extruder_variant_list": [
"Direct Drive High Flow",
"Direct Drive High Flow"
],
"imex_mode_active_tools": [
"0:P",
"0:P,1:C",
"0:P,1:M"
],
"imex_mode_gcodes": [
"SET_PRINT_MODE MODE=PRIMARY",
"SET_PRINT_MODE MODE=COPY",
"SET_PRINT_MODE MODE=MIRROR"
],
"imex_mode_names": [
"primary",
"copy",
"mirror"
],
"imex_nozzle_clearance_x": "40",
"imex_tool_layout": "front-left",
"is_imex": "1",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]\nLOG_RESUME\nLOG_EXTR",
"long_retractions_when_cut": [
"0",
"0"
],
"machine_max_acceleration_e": [
"6000",
"5000",
"6000",
"5000"
],
"machine_max_acceleration_extruding": [
"5000",
"2500",
"5000",
"2500"
],
"machine_max_acceleration_retracting": [
"5000",
"5000",
"5000",
"5000"
],
"machine_max_acceleration_travel": [
"5000",
"2500",
"5000",
"2500"
],
"machine_max_acceleration_x": [
"5000",
"2500",
"5000",
"2500"
],
"machine_max_acceleration_y": [
"5000",
"2500",
"5000",
"2500"
],
"machine_max_acceleration_z": [
"100",
"50",
"100",
"50"
],
"machine_max_jerk_e": [
"2.5",
"1.25",
"2.5",
"1.25"
],
"machine_max_jerk_x": [
"5",
"2.5",
"5",
"2.5"
],
"machine_max_jerk_y": [
"5",
"2.5",
"5",
"2.5"
],
"machine_max_jerk_z": [
"0.4",
"0.2",
"0.4",
"0.2"
],
"machine_max_junction_deviation": [
"0",
"0",
"0",
"0"
],
"machine_max_speed_e": [
"120",
"60",
"120",
"60"
],
"machine_max_speed_x": [
"300",
"150",
"300",
"150"
],
"machine_max_speed_y": [
"300",
"150",
"300",
"150"
],
"machine_max_speed_z": [
"40",
"20",
"40",
"20"
],
"machine_start_gcode": "M104 S{first_layer_temperature[0]} T0\nM104 S{first_layer_temperature[1]} T1\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S{first_layer_temperature[0]} T0\nM109 S{first_layer_temperature[1]} T1\nstart_print\nG1 F5000\n{if first_layer_temperature[1] !=0 }\nTool1\nbrush_Tool1\n{endif}\n{if first_layer_temperature[0] !=0 }\nTool0\nbrush_Tool0\n{endif}\n{if first_layer_temperature[1] !=0 }\nTool1\nG1 X280.1 Y3.0 Z0.38 F5000.0 \nG1 X100.1 Y3.0 Z0.38 F1500.0 E15 \nG1 X100.1 Y3.4 Z0.38 F5000.0 \nG1 X280.1 Y3.4 Z0.38 F1500.0 E30\nG92 E0\nG1 Z2 F2000\n{endif}\n{if first_layer_temperature[0] !=0 }\nTool0\nG1 X100.1 Y2.0 Z0.38 F5000.0 \nG1 X280.1 Y2.0 Z0.38 F1500.0 E15 \nG1 X280.1 Y2.4 Z0.38 F5000.0 \nG1 X100.1 Y2.4 Z0.38 F1500.0 E30\nG92 E0\nG1 Z2 F2000\n{endif}\nM400\nSKEW_PROFILE LOAD=SKEW1\nG28 X\nTool{initial_extruder}\nSTART_PRINTING_MODE\n\n",
"max_layer_height": [
"0.45",
"0.45"
],
"min_layer_height": [
"0.12",
"0.12"
],
"nozzle_diameter": [
"0.6",
"0.6"
],
"nozzle_type": [
"hardened_steel",
"hardened_steel"
],
"nozzle_volume": [
"0",
"0"
],
"printable_height": "401",
"printer_extruder_id": [
"1",
"2"
],
"printer_extruder_variant": [
"Direct Drive High Flow",
"Direct Drive High Flow"
],
"printer_model": "Xplorer IDEX",
"printer_variant": "0.6",
"retract_before_wipe": [
"70%",
"70%"
],
"retract_length_toolchange": [
"2",
"2"
],
"retract_lift_above": [
"0.2",
"0.2"
],
"retract_lift_below": [
"400",
"400"
],
"retract_lift_enforce": [
"All Surfaces",
"All Surfaces"
],
"retract_restart_extra": [
"0",
"0"
],
"retract_restart_extra_toolchange": [
"0",
"0"
],
"retract_when_changing_layer": [
"1",
"1"
],
"retraction_distances_when_cut": [
"18",
"18"
],
"retraction_length": [
"1.35",
"1.35"
],
"retraction_minimum_travel": [
"2",
"2"
],
"retraction_speed": [
"120",
"120"
],
"travel_slope": [
"1",
"1"
],
"wipe": [
"1",
"1"
],
"wipe_distance": [
"1",
"1"
],
"z_hop": [
"1",
"1"
],
"z_hop_types": [
"Normal Lift",
"Normal Lift"
]
}
@@ -0,0 +1,254 @@
{
"type": "machine",
"name": "Xplorer IDEX 0.8 nozzle",
"inherits": "Xplorer common",
"from": "system",
"setting_id": "U50ftmONKAXI4OQ9",
"instantiation": "true",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\n{if layer_num != 0}\nG92 E0\n{endif}\n{if layer_num <= 0}\nG91\nG1 Z+10 F1200\nG90\n{endif}",
"change_filament_gcode": ";CHANGE_FILAMENT - Start\n;Layer#=[layer_num] - Z=[layer_z]\n{if layer_z >first_layer_height}\nG91\nG1 Z+2 F1200\nG90\n{endif}\n{if layer_num != -1 || next_extruder != 0}\n Tool{next_extruder}\n{endif}\n; <----\nG1 Z[layer_z] F1200\n{if layer_num == -1 || layer_num == 0}\n M109 S[first_layer_temperature[next_extruder]] T[next_extruder]\n{else}\n M104 S[temperature[next_extruder]] T[next_extruder]\n{endif}\n; ---->\nM83\n;Layer#=[layer_num] - Z=[layer_z]\nLOG_EXTR\n;CHANGE_FILAMENT - End",
"default_nozzle_volume_type": [
"High Flow",
"High Flow"
],
"default_print_profile": "0.40mm Standard @Xplorer 0.8",
"deretraction_speed": [
"120",
"120"
],
"extruder_colour": [
"#FCE94F",
"#729FCF"
],
"extruder_offset": [
"0x0",
"0x0"
],
"extruder_type": [
"Direct Drive",
"Direct Drive"
],
"extruder_variant_list": [
"Direct Drive High Flow",
"Direct Drive High Flow"
],
"imex_mode_active_tools": [
"0:P",
"0:P,1:C",
"0:P,1:M"
],
"imex_mode_gcodes": [
"SET_PRINT_MODE MODE=PRIMARY",
"SET_PRINT_MODE MODE=COPY",
"SET_PRINT_MODE MODE=MIRROR"
],
"imex_mode_names": [
"primary",
"copy",
"mirror"
],
"imex_nozzle_clearance_x": "40",
"imex_tool_layout": "front-left",
"is_imex": "1",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]\nLOG_RESUME\nLOG_EXTR",
"long_retractions_when_cut": [
"0",
"0"
],
"machine_max_acceleration_e": [
"6000",
"5000",
"6000",
"5000"
],
"machine_max_acceleration_extruding": [
"5000",
"2500",
"5000",
"2500"
],
"machine_max_acceleration_retracting": [
"5000",
"5000",
"5000",
"5000"
],
"machine_max_acceleration_travel": [
"5000",
"2500",
"5000",
"2500"
],
"machine_max_acceleration_x": [
"5000",
"2500",
"5000",
"2500"
],
"machine_max_acceleration_y": [
"5000",
"2500",
"5000",
"2500"
],
"machine_max_acceleration_z": [
"100",
"50",
"100",
"50"
],
"machine_max_jerk_e": [
"2.5",
"1.25",
"2.5",
"1.25"
],
"machine_max_jerk_x": [
"5",
"2.5",
"5",
"2.5"
],
"machine_max_jerk_y": [
"5",
"2.5",
"5",
"2.5"
],
"machine_max_jerk_z": [
"0.4",
"0.2",
"0.4",
"0.2"
],
"machine_max_junction_deviation": [
"0",
"0",
"0",
"0"
],
"machine_max_speed_e": [
"120",
"60",
"120",
"60"
],
"machine_max_speed_x": [
"300",
"150",
"300",
"150"
],
"machine_max_speed_y": [
"300",
"150",
"300",
"150"
],
"machine_max_speed_z": [
"40",
"20",
"40",
"20"
],
"machine_start_gcode": "M104 S{first_layer_temperature[0]} T0\nM104 S{first_layer_temperature[1]} T1\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S{first_layer_temperature[0]} T0\nM109 S{first_layer_temperature[1]} T1\nstart_print\nG1 F5000\n{if first_layer_temperature[1] !=0 }\nTool1\nbrush_Tool1\n{endif}\n{if first_layer_temperature[0] !=0 }\nTool0\nbrush_Tool0\n{endif}\n{if first_layer_temperature[1] !=0 }\nTool1\nG1 X280.1 Y3.0 Z0.38 F5000.0 \nG1 X100.1 Y3.0 Z0.38 F1500.0 E15 \nG1 X100.1 Y3.4 Z0.38 F5000.0 \nG1 X280.1 Y3.4 Z0.38 F1500.0 E30\nG92 E0\nG1 Z2 F2000\n{endif}\n{if first_layer_temperature[0] !=0 }\nTool0\nG1 X100.1 Y2.0 Z0.38 F5000.0 \nG1 X280.1 Y2.0 Z0.38 F1500.0 E15 \nG1 X280.1 Y2.4 Z0.38 F5000.0 \nG1 X100.1 Y2.4 Z0.38 F1500.0 E30\nG92 E0\nG1 Z2 F2000\n{endif}\nM400\nSKEW_PROFILE LOAD=SKEW1\nG28 X\nTool{initial_extruder}\nSTART_PRINTING_MODE\n\n",
"max_layer_height": [
"0.64",
"0.64"
],
"min_layer_height": [
"0.2",
"0.2"
],
"nozzle_diameter": [
"0.8",
"0.8"
],
"nozzle_type": [
"hardened_steel",
"hardened_steel"
],
"nozzle_volume": [
"0",
"0"
],
"printable_height": "401",
"printer_extruder_id": [
"1",
"2"
],
"printer_extruder_variant": [
"Direct Drive High Flow",
"Direct Drive High Flow"
],
"printer_model": "Xplorer IDEX",
"printer_variant": "0.8",
"retract_before_wipe": [
"70%",
"70%"
],
"retract_length_toolchange": [
"2",
"2"
],
"retract_lift_above": [
"0.2",
"0.2"
],
"retract_lift_below": [
"400",
"400"
],
"retract_lift_enforce": [
"All Surfaces",
"All Surfaces"
],
"retract_restart_extra": [
"0",
"0"
],
"retract_restart_extra_toolchange": [
"0",
"0"
],
"retract_when_changing_layer": [
"1",
"1"
],
"retraction_distances_when_cut": [
"18",
"18"
],
"retraction_length": [
"1.35",
"1.35"
],
"retraction_minimum_travel": [
"2",
"2"
],
"retraction_speed": [
"120",
"120"
],
"travel_slope": [
"1",
"1"
],
"wipe": [
"1",
"1"
],
"wipe_distance": [
"1",
"1"
],
"z_hop": [
"1",
"1"
],
"z_hop_types": [
"Normal Lift",
"Normal Lift"
]
}
@@ -0,0 +1,399 @@
{
"type": "machine",
"name": "Xplorer IQEX 0.4 nozzle",
"inherits": "Xplorer common",
"from": "system",
"setting_id": "fbUJR4RMoMczxSkH",
"instantiation": "true",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\n{if layer_num != 0}\nG92 E0\n{endif}\n{if layer_num <= 0}\nG91\nG1 Z+10 F1200\nG90\n{endif}",
"change_filament_gcode": ";CHANGE_FILAMENT - Start\n;Layer#=[layer_num] - Z=[layer_z]\n{if layer_z >first_layer_height}\nG91\nG1 Z+2 F1200\nG90\n{endif}\n{if layer_num != -1 || next_extruder != 0}\n Tool{next_extruder}\n{endif}\n; <----\nG1 Z[layer_z] F1200\n{if layer_num == -1 || layer_num == 0}\n M109 S[first_layer_temperature[next_extruder]] T[next_extruder]\n{else}\n M104 S[temperature[next_extruder]] T[next_extruder]\n{endif}\n; ---->\nM83\n;Layer#=[layer_num] - Z=[layer_z]\nLOG_EXTR\n;CHANGE_FILAMENT - End",
"default_nozzle_volume_type": [
"High Flow",
"High Flow",
"High Flow",
"High Flow"
],
"default_print_profile": "0.20mm Standard @Xplorer 0.4",
"deretraction_speed": [
"120",
"120",
"120",
"120"
],
"extruder_colour": [
"#FCE94F",
"#729FCF",
"#AD7FA8",
"#F57900"
],
"extruder_offset": [
"0x0",
"0x0",
"0x0",
"0x0"
],
"extruder_type": [
"Direct Drive",
"Direct Drive",
"Direct Drive",
"Direct Drive"
],
"extruder_variant_list": [
"Direct Drive High Flow",
"Direct Drive High Flow",
"Direct Drive High Flow",
"Direct Drive High Flow"
],
"imex_gantry_count": "2",
"imex_mode_active_tools": [
"0:P",
"0:P,1:C",
"0:P,1:M",
"0:P,1:C,2:C,3:C",
"0:P,1:C,2:M,3:M",
"0:P,1:S,2:C,3:C",
"0:P,1:S,2:M,3:M"
],
"imex_mode_gcodes": [
"SET_PRINT_MODE MODE=PRIMARY",
"SET_PRINT_MODE MODE=COPY01",
"SET_PRINT_MODE MODE=MIRROR01",
"SET_PRINT_MODE MODE=COPY0123",
"SET_PRINT_MODE MODE=MIRROR0123",
"SET_PRINT_MODE MODE=MULTICOLOR_COPY01",
"SET_PRINT_MODE MODE=MULTICOLOR_MIRROR01"
],
"imex_mode_names": [
"primary",
"copy",
"mirror",
"iq-copy",
"iq-mirror",
"mc-copy",
"mc-mirror"
],
"imex_nozzle_clearance_x": "40",
"imex_tool_layout": "rear-left",
"is_imex": "1",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]\nLOG_RESUME\nLOG_EXTR",
"long_retractions_when_cut": [
"0",
"0",
"0",
"0"
],
"machine_max_acceleration_e": [
"6000",
"5000",
"6000",
"5000",
"6000",
"5000",
"6000",
"5000"
],
"machine_max_acceleration_extruding": [
"5000",
"2500",
"5000",
"2500",
"5000",
"2500",
"5000",
"2500"
],
"machine_max_acceleration_retracting": [
"5000",
"5000",
"5000",
"5000",
"5000",
"5000",
"5000",
"5000"
],
"machine_max_acceleration_travel": [
"5000",
"2500",
"5000",
"2500",
"5000",
"2500",
"5000",
"2500"
],
"machine_max_acceleration_x": [
"5000",
"2500",
"5000",
"2500",
"5000",
"2500",
"5000",
"2500"
],
"machine_max_acceleration_y": [
"5000",
"2500",
"5000",
"2500",
"5000",
"2500",
"5000",
"2500"
],
"machine_max_acceleration_z": [
"100",
"50",
"100",
"50",
"100",
"50",
"100",
"50"
],
"machine_max_jerk_e": [
"2.5",
"1.25",
"2.5",
"1.25",
"2.5",
"1.25",
"2.5",
"1.25"
],
"machine_max_jerk_x": [
"5",
"2.5",
"5",
"2.5",
"5",
"2.5",
"5",
"2.5"
],
"machine_max_jerk_y": [
"5",
"2.5",
"5",
"2.5",
"5",
"2.5",
"5",
"2.5"
],
"machine_max_jerk_z": [
"0.4",
"0.2",
"0.4",
"0.2",
"0.4",
"0.2",
"0.4",
"0.2"
],
"machine_max_junction_deviation": [
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0"
],
"machine_max_speed_e": [
"120",
"60",
"120",
"60",
"120",
"60",
"120",
"60"
],
"machine_max_speed_x": [
"300",
"150",
"300",
"150",
"300",
"150",
"300",
"150"
],
"machine_max_speed_y": [
"300",
"150",
"300",
"150",
"300",
"150",
"300",
"150"
],
"machine_max_speed_z": [
"40",
"20",
"40",
"20",
"40",
"20",
"40",
"20"
],
"machine_start_gcode": "M104 S{first_layer_temperature[0]} T0\nM104 S{first_layer_temperature[1]} T1\nM104 S{first_layer_temperature[2]} T2\nM104 S{first_layer_temperature[3]} T3\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S{first_layer_temperature[0]} T0\nM109 S{first_layer_temperature[1]} T1\nM109 S{first_layer_temperature[2]} T2\nM109 S{first_layer_temperature[3]} T3\nstart_print\nG1 F5000\n{if first_layer_temperature[3] !=0 }\nTool3\nbrush_Tool3\n{endif}\n{if first_layer_temperature[2] !=0 }\nTool2\nbrush_Tool2\n{endif}\n{if first_layer_temperature[1] !=0 }\nTool1\nbrush_Tool1\n{endif}\n{if first_layer_temperature[0] !=0 }\nTool0\nbrush_Tool0\n{endif}\n{if first_layer_temperature[3] !=0 }\nTool3\nG1 Y280 X407.0 Z0.38 F5000.0 \nG1 Y175 X407.0 Z0.38 F1500.0 E15 \nG1 Y175 X407.4 Z0.38 F5000.0 \nG1 Y280 X407.4 Z0.38 F1500.0 E30\nG92 E0\nG1 Z2 F2000\n{endif}\n{if first_layer_temperature[2] !=0 }\nTool2\nG1 Y280 X12.0 Z0.38 F5000.0 \nG1 Y175 X12.0 Z0.38 F1500.0 E15 \nG1 Y175 X12.4 Z0.38 F5000.0 \nG1 Y280 X12.4 Z0.38 F1500.0 E30\nG92 E0\nG1 Z2 F2000\n{endif}\n{if first_layer_temperature[1] !=0 }\nTool1\nG1 Y280 X408.0 Z0.38 F5000.0 \nG1 Y175 X408.0 Z0.38 F1500.0 E15 \nG1 Y175 X408.4 Z0.38 F5000.0 \nG1 Y280 X408.4 Z0.38 F1500.0 E30\nG92 E0\nG1 Z2 F2000\n{endif}\nG1 F5000\n{if first_layer_temperature[0] !=0 }\nTool0\nG1 Y280 X11.0 Z0.38 F5000.0 \nG1 Y175 X11.0 Z0.38 F1500.0 E15 \nG1 Y175 X11.4 Z0.38 F5000.0 \nG1 Y280 X11.4 Z0.38 F1500.0 E30\nG92 E0\nG1 Z2 F2000\n{endif}\nG1 F5000\nM400\nSKEW_PROFILE LOAD=SKEW1\nG28 X\nG28 Y\nTool{initial_extruder}\nSTART_PRINTING_MODE\n\n",
"max_layer_height": [
"0.32",
"0.32",
"0.32",
"0.32"
],
"min_layer_height": [
"0.04",
"0.04",
"0.04",
"0.04"
],
"nozzle_diameter": [
"0.4",
"0.4",
"0.4",
"0.4"
],
"nozzle_type": [
"hardened_steel",
"hardened_steel",
"hardened_steel",
"hardened_steel"
],
"nozzle_volume": [
"0",
"0",
"0",
"0"
],
"printable_area": [
"10x0",
"410x0",
"410x342.5",
"10x342.5"
],
"printable_height": "401",
"printer_extruder_id": [
"1",
"2",
"3",
"4"
],
"printer_extruder_variant": [
"Direct Drive High Flow",
"Direct Drive High Flow",
"Direct Drive High Flow",
"Direct Drive High Flow"
],
"printer_model": "Xplorer IQEX",
"printer_variant": "0.4",
"retract_before_wipe": [
"70%",
"70%",
"70%",
"70%"
],
"retract_length_toolchange": [
"2",
"2",
"2",
"2"
],
"retract_lift_above": [
"0.2",
"0.2",
"0.2",
"0.2"
],
"retract_lift_below": [
"400",
"400",
"400",
"400"
],
"retract_lift_enforce": [
"All Surfaces",
"All Surfaces",
"All Surfaces",
"All Surfaces"
],
"retract_restart_extra": [
"0",
"0",
"0",
"0"
],
"retract_restart_extra_toolchange": [
"0",
"0",
"0",
"0"
],
"retract_when_changing_layer": [
"1",
"1",
"1",
"1"
],
"retraction_distances_when_cut": [
"18",
"18",
"18",
"18"
],
"retraction_length": [
"1.35",
"1.35",
"1.35",
"1.35"
],
"retraction_minimum_travel": [
"2",
"2",
"2",
"2"
],
"retraction_speed": [
"120",
"120",
"120",
"120"
],
"travel_slope": [
"1",
"1",
"1",
"1"
],
"wipe": [
"1",
"1",
"1",
"1"
],
"wipe_distance": [
"1",
"1",
"1",
"1"
],
"z_hop": [
"1",
"1",
"1",
"1"
],
"z_hop_types": [
"Normal Lift",
"Normal Lift",
"Normal Lift",
"Normal Lift"
]
}
@@ -0,0 +1,399 @@
{
"type": "machine",
"name": "Xplorer IQEX 0.6 nozzle",
"inherits": "Xplorer common",
"from": "system",
"setting_id": "hOYAHBik6rxU1B0d",
"instantiation": "true",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\n{if layer_num != 0}\nG92 E0\n{endif}\n{if layer_num <= 0}\nG91\nG1 Z+10 F1200\nG90\n{endif}",
"change_filament_gcode": ";CHANGE_FILAMENT - Start\n;Layer#=[layer_num] - Z=[layer_z]\n{if layer_z >first_layer_height}\nG91\nG1 Z+2 F1200\nG90\n{endif}\n{if layer_num != -1 || next_extruder != 0}\n Tool{next_extruder}\n{endif}\n; <----\nG1 Z[layer_z] F1200\n{if layer_num == -1 || layer_num == 0}\n M109 S[first_layer_temperature[next_extruder]] T[next_extruder]\n{else}\n M104 S[temperature[next_extruder]] T[next_extruder]\n{endif}\n; ---->\nM83\n;Layer#=[layer_num] - Z=[layer_z]\nLOG_EXTR\n;CHANGE_FILAMENT - End",
"default_nozzle_volume_type": [
"High Flow",
"High Flow",
"High Flow",
"High Flow"
],
"default_print_profile": "0.30mm Standard @Xplorer 0.6",
"deretraction_speed": [
"120",
"120",
"120",
"120"
],
"extruder_colour": [
"#FCE94F",
"#729FCF",
"#AD7FA8",
"#F57900"
],
"extruder_offset": [
"0x0",
"0x0",
"0x0",
"0x0"
],
"extruder_type": [
"Direct Drive",
"Direct Drive",
"Direct Drive",
"Direct Drive"
],
"extruder_variant_list": [
"Direct Drive High Flow",
"Direct Drive High Flow",
"Direct Drive High Flow",
"Direct Drive High Flow"
],
"imex_gantry_count": "2",
"imex_mode_active_tools": [
"0:P",
"0:P,1:C",
"0:P,1:M",
"0:P,1:C,2:C,3:C",
"0:P,1:C,2:M,3:M",
"0:P,1:S,2:C,3:C",
"0:P,1:S,2:M,3:M"
],
"imex_mode_gcodes": [
"SET_PRINT_MODE MODE=PRIMARY",
"SET_PRINT_MODE MODE=COPY01",
"SET_PRINT_MODE MODE=MIRROR01",
"SET_PRINT_MODE MODE=COPY0123",
"SET_PRINT_MODE MODE=MIRROR0123",
"SET_PRINT_MODE MODE=MULTICOLOR_COPY01",
"SET_PRINT_MODE MODE=MULTICOLOR_MIRROR01"
],
"imex_mode_names": [
"primary",
"copy",
"mirror",
"iq-copy",
"iq-mirror",
"mc-copy",
"mc-mirror"
],
"imex_nozzle_clearance_x": "40",
"imex_tool_layout": "rear-left",
"is_imex": "1",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]\nLOG_RESUME\nLOG_EXTR",
"long_retractions_when_cut": [
"0",
"0",
"0",
"0"
],
"machine_max_acceleration_e": [
"6000",
"5000",
"6000",
"5000",
"6000",
"5000",
"6000",
"5000"
],
"machine_max_acceleration_extruding": [
"5000",
"2500",
"5000",
"2500",
"5000",
"2500",
"5000",
"2500"
],
"machine_max_acceleration_retracting": [
"5000",
"5000",
"5000",
"5000",
"5000",
"5000",
"5000",
"5000"
],
"machine_max_acceleration_travel": [
"5000",
"2500",
"5000",
"2500",
"5000",
"2500",
"5000",
"2500"
],
"machine_max_acceleration_x": [
"5000",
"2500",
"5000",
"2500",
"5000",
"2500",
"5000",
"2500"
],
"machine_max_acceleration_y": [
"5000",
"2500",
"5000",
"2500",
"5000",
"2500",
"5000",
"2500"
],
"machine_max_acceleration_z": [
"100",
"50",
"100",
"50",
"100",
"50",
"100",
"50"
],
"machine_max_jerk_e": [
"2.5",
"1.25",
"2.5",
"1.25",
"2.5",
"1.25",
"2.5",
"1.25"
],
"machine_max_jerk_x": [
"5",
"2.5",
"5",
"2.5",
"5",
"2.5",
"5",
"2.5"
],
"machine_max_jerk_y": [
"5",
"2.5",
"5",
"2.5",
"5",
"2.5",
"5",
"2.5"
],
"machine_max_jerk_z": [
"0.4",
"0.2",
"0.4",
"0.2",
"0.4",
"0.2",
"0.4",
"0.2"
],
"machine_max_junction_deviation": [
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0"
],
"machine_max_speed_e": [
"120",
"60",
"120",
"60",
"120",
"60",
"120",
"60"
],
"machine_max_speed_x": [
"300",
"150",
"300",
"150",
"300",
"150",
"300",
"150"
],
"machine_max_speed_y": [
"300",
"150",
"300",
"150",
"300",
"150",
"300",
"150"
],
"machine_max_speed_z": [
"40",
"20",
"40",
"20",
"40",
"20",
"40",
"20"
],
"machine_start_gcode": "M104 S{first_layer_temperature[0]} T0\nM104 S{first_layer_temperature[1]} T1\nM104 S{first_layer_temperature[2]} T2\nM104 S{first_layer_temperature[3]} T3\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S{first_layer_temperature[0]} T0\nM109 S{first_layer_temperature[1]} T1\nM109 S{first_layer_temperature[2]} T2\nM109 S{first_layer_temperature[3]} T3\nstart_print\nG1 F5000\n{if first_layer_temperature[3] !=0 }\nTool3\nbrush_Tool3\n{endif}\n{if first_layer_temperature[2] !=0 }\nTool2\nbrush_Tool2\n{endif}\n{if first_layer_temperature[1] !=0 }\nTool1\nbrush_Tool1\n{endif}\n{if first_layer_temperature[0] !=0 }\nTool0\nbrush_Tool0\n{endif}\n{if first_layer_temperature[3] !=0 }\nTool3\nG1 Y280 X407.0 Z0.38 F5000.0 \nG1 Y175 X407.0 Z0.38 F1500.0 E15 \nG1 Y175 X407.4 Z0.38 F5000.0 \nG1 Y280 X407.4 Z0.38 F1500.0 E30\nG92 E0\nG1 Z2 F2000\n{endif}\n{if first_layer_temperature[2] !=0 }\nTool2\nG1 Y280 X12.0 Z0.38 F5000.0 \nG1 Y175 X12.0 Z0.38 F1500.0 E15 \nG1 Y175 X12.4 Z0.38 F5000.0 \nG1 Y280 X12.4 Z0.38 F1500.0 E30\nG92 E0\nG1 Z2 F2000\n{endif}\n{if first_layer_temperature[1] !=0 }\nTool1\nG1 Y280 X408.0 Z0.38 F5000.0 \nG1 Y175 X408.0 Z0.38 F1500.0 E15 \nG1 Y175 X408.4 Z0.38 F5000.0 \nG1 Y280 X408.4 Z0.38 F1500.0 E30\nG92 E0\nG1 Z2 F2000\n{endif}\nG1 F5000\n{if first_layer_temperature[0] !=0 }\nTool0\nG1 Y280 X11.0 Z0.38 F5000.0 \nG1 Y175 X11.0 Z0.38 F1500.0 E15 \nG1 Y175 X11.4 Z0.38 F5000.0 \nG1 Y280 X11.4 Z0.38 F1500.0 E30\nG92 E0\nG1 Z2 F2000\n{endif}\nG1 F5000\nM400\nSKEW_PROFILE LOAD=SKEW1\nG28 X\nG28 Y\nTool{initial_extruder}\nSTART_PRINTING_MODE\n\n",
"max_layer_height": [
"0.45",
"0.45",
"0.45",
"0.45"
],
"min_layer_height": [
"0.12",
"0.12",
"0.12",
"0.12"
],
"nozzle_diameter": [
"0.6",
"0.6",
"0.6",
"0.6"
],
"nozzle_type": [
"hardened_steel",
"hardened_steel",
"hardened_steel",
"hardened_steel"
],
"nozzle_volume": [
"0",
"0",
"0",
"0"
],
"printable_area": [
"10x0",
"410x0",
"410x342.5",
"10x342.5"
],
"printable_height": "401",
"printer_extruder_id": [
"1",
"2",
"3",
"4"
],
"printer_extruder_variant": [
"Direct Drive High Flow",
"Direct Drive High Flow",
"Direct Drive High Flow",
"Direct Drive High Flow"
],
"printer_model": "Xplorer IQEX",
"printer_variant": "0.6",
"retract_before_wipe": [
"70%",
"70%",
"70%",
"70%"
],
"retract_length_toolchange": [
"2",
"2",
"2",
"2"
],
"retract_lift_above": [
"0.2",
"0.2",
"0.2",
"0.2"
],
"retract_lift_below": [
"400",
"400",
"400",
"400"
],
"retract_lift_enforce": [
"All Surfaces",
"All Surfaces",
"All Surfaces",
"All Surfaces"
],
"retract_restart_extra": [
"0",
"0",
"0",
"0"
],
"retract_restart_extra_toolchange": [
"0",
"0",
"0",
"0"
],
"retract_when_changing_layer": [
"1",
"1",
"1",
"1"
],
"retraction_distances_when_cut": [
"18",
"18",
"18",
"18"
],
"retraction_length": [
"1.35",
"1.35",
"1.35",
"1.35"
],
"retraction_minimum_travel": [
"2",
"2",
"2",
"2"
],
"retraction_speed": [
"120",
"120",
"120",
"120"
],
"travel_slope": [
"1",
"1",
"1",
"1"
],
"wipe": [
"1",
"1",
"1",
"1"
],
"wipe_distance": [
"1",
"1",
"1",
"1"
],
"z_hop": [
"1",
"1",
"1",
"1"
],
"z_hop_types": [
"Normal Lift",
"Normal Lift",
"Normal Lift",
"Normal Lift"
]
}
@@ -0,0 +1,399 @@
{
"type": "machine",
"name": "Xplorer IQEX 0.8 nozzle",
"inherits": "Xplorer common",
"from": "system",
"setting_id": "e048HgBDJy6XnaaO",
"instantiation": "true",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\n{if layer_num != 0}\nG92 E0\n{endif}\n{if layer_num <= 0}\nG91\nG1 Z+10 F1200\nG90\n{endif}",
"change_filament_gcode": ";CHANGE_FILAMENT - Start\n;Layer#=[layer_num] - Z=[layer_z]\n{if layer_z >first_layer_height}\nG91\nG1 Z+2 F1200\nG90\n{endif}\n{if layer_num != -1 || next_extruder != 0}\n Tool{next_extruder}\n{endif}\n; <----\nG1 Z[layer_z] F1200\n{if layer_num == -1 || layer_num == 0}\n M109 S[first_layer_temperature[next_extruder]] T[next_extruder]\n{else}\n M104 S[temperature[next_extruder]] T[next_extruder]\n{endif}\n; ---->\nM83\n;Layer#=[layer_num] - Z=[layer_z]\nLOG_EXTR\n;CHANGE_FILAMENT - End",
"default_nozzle_volume_type": [
"High Flow",
"High Flow",
"High Flow",
"High Flow"
],
"default_print_profile": "0.40mm Standard @Xplorer 0.8",
"deretraction_speed": [
"120",
"120",
"120",
"120"
],
"extruder_colour": [
"#FCE94F",
"#729FCF",
"#AD7FA8",
"#F57900"
],
"extruder_offset": [
"0x0",
"0x0",
"0x0",
"0x0"
],
"extruder_type": [
"Direct Drive",
"Direct Drive",
"Direct Drive",
"Direct Drive"
],
"extruder_variant_list": [
"Direct Drive High Flow",
"Direct Drive High Flow",
"Direct Drive High Flow",
"Direct Drive High Flow"
],
"imex_gantry_count": "2",
"imex_mode_active_tools": [
"0:P",
"0:P,1:C",
"0:P,1:M",
"0:P,1:C,2:C,3:C",
"0:P,1:C,2:M,3:M",
"0:P,1:S,2:C,3:C",
"0:P,1:S,2:M,3:M"
],
"imex_mode_gcodes": [
"SET_PRINT_MODE MODE=PRIMARY",
"SET_PRINT_MODE MODE=COPY01",
"SET_PRINT_MODE MODE=MIRROR01",
"SET_PRINT_MODE MODE=COPY0123",
"SET_PRINT_MODE MODE=MIRROR0123",
"SET_PRINT_MODE MODE=MULTICOLOR_COPY01",
"SET_PRINT_MODE MODE=MULTICOLOR_MIRROR01"
],
"imex_mode_names": [
"primary",
"copy",
"mirror",
"iq-copy",
"iq-mirror",
"mc-copy",
"mc-mirror"
],
"imex_nozzle_clearance_x": "40",
"imex_tool_layout": "rear-left",
"is_imex": "1",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]\nLOG_RESUME\nLOG_EXTR",
"long_retractions_when_cut": [
"0",
"0",
"0",
"0"
],
"machine_max_acceleration_e": [
"6000",
"5000",
"6000",
"5000",
"6000",
"5000",
"6000",
"5000"
],
"machine_max_acceleration_extruding": [
"5000",
"2500",
"5000",
"2500",
"5000",
"2500",
"5000",
"2500"
],
"machine_max_acceleration_retracting": [
"5000",
"5000",
"5000",
"5000",
"5000",
"5000",
"5000",
"5000"
],
"machine_max_acceleration_travel": [
"5000",
"2500",
"5000",
"2500",
"5000",
"2500",
"5000",
"2500"
],
"machine_max_acceleration_x": [
"5000",
"2500",
"5000",
"2500",
"5000",
"2500",
"5000",
"2500"
],
"machine_max_acceleration_y": [
"5000",
"2500",
"5000",
"2500",
"5000",
"2500",
"5000",
"2500"
],
"machine_max_acceleration_z": [
"100",
"50",
"100",
"50",
"100",
"50",
"100",
"50"
],
"machine_max_jerk_e": [
"2.5",
"1.25",
"2.5",
"1.25",
"2.5",
"1.25",
"2.5",
"1.25"
],
"machine_max_jerk_x": [
"5",
"2.5",
"5",
"2.5",
"5",
"2.5",
"5",
"2.5"
],
"machine_max_jerk_y": [
"5",
"2.5",
"5",
"2.5",
"5",
"2.5",
"5",
"2.5"
],
"machine_max_jerk_z": [
"0.4",
"0.2",
"0.4",
"0.2",
"0.4",
"0.2",
"0.4",
"0.2"
],
"machine_max_junction_deviation": [
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0"
],
"machine_max_speed_e": [
"120",
"60",
"120",
"60",
"120",
"60",
"120",
"60"
],
"machine_max_speed_x": [
"300",
"150",
"300",
"150",
"300",
"150",
"300",
"150"
],
"machine_max_speed_y": [
"300",
"150",
"300",
"150",
"300",
"150",
"300",
"150"
],
"machine_max_speed_z": [
"40",
"20",
"40",
"20",
"40",
"20",
"40",
"20"
],
"machine_start_gcode": "M104 S{first_layer_temperature[0]} T0\nM104 S{first_layer_temperature[1]} T1\nM104 S{first_layer_temperature[2]} T2\nM104 S{first_layer_temperature[3]} T3\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S{first_layer_temperature[0]} T0\nM109 S{first_layer_temperature[1]} T1\nM109 S{first_layer_temperature[2]} T2\nM109 S{first_layer_temperature[3]} T3\nstart_print\nG1 F5000\n{if first_layer_temperature[3] !=0 }\nTool3\nbrush_Tool3\n{endif}\n{if first_layer_temperature[2] !=0 }\nTool2\nbrush_Tool2\n{endif}\n{if first_layer_temperature[1] !=0 }\nTool1\nbrush_Tool1\n{endif}\n{if first_layer_temperature[0] !=0 }\nTool0\nbrush_Tool0\n{endif}\n{if first_layer_temperature[3] !=0 }\nTool3\nG1 Y280 X407.0 Z0.38 F5000.0 \nG1 Y175 X407.0 Z0.38 F1500.0 E15 \nG1 Y175 X407.4 Z0.38 F5000.0 \nG1 Y280 X407.4 Z0.38 F1500.0 E30\nG92 E0\nG1 Z2 F2000\n{endif}\n{if first_layer_temperature[2] !=0 }\nTool2\nG1 Y280 X12.0 Z0.38 F5000.0 \nG1 Y175 X12.0 Z0.38 F1500.0 E15 \nG1 Y175 X12.4 Z0.38 F5000.0 \nG1 Y280 X12.4 Z0.38 F1500.0 E30\nG92 E0\nG1 Z2 F2000\n{endif}\n{if first_layer_temperature[1] !=0 }\nTool1\nG1 Y280 X408.0 Z0.38 F5000.0 \nG1 Y175 X408.0 Z0.38 F1500.0 E15 \nG1 Y175 X408.4 Z0.38 F5000.0 \nG1 Y280 X408.4 Z0.38 F1500.0 E30\nG92 E0\nG1 Z2 F2000\n{endif}\nG1 F5000\n{if first_layer_temperature[0] !=0 }\nTool0\nG1 Y280 X11.0 Z0.38 F5000.0 \nG1 Y175 X11.0 Z0.38 F1500.0 E15 \nG1 Y175 X11.4 Z0.38 F5000.0 \nG1 Y280 X11.4 Z0.38 F1500.0 E30\nG92 E0\nG1 Z2 F2000\n{endif}\nG1 F5000\nM400\nSKEW_PROFILE LOAD=SKEW1\nG28 X\nG28 Y\nTool{initial_extruder}\nSTART_PRINTING_MODE\n\n",
"max_layer_height": [
"0.64",
"0.64",
"0.64",
"0.64"
],
"min_layer_height": [
"0.2",
"0.2",
"0.2",
"0.2"
],
"nozzle_diameter": [
"0.8",
"0.8",
"0.8",
"0.8"
],
"nozzle_type": [
"hardened_steel",
"hardened_steel",
"hardened_steel",
"hardened_steel"
],
"nozzle_volume": [
"0",
"0",
"0",
"0"
],
"printable_area": [
"10x0",
"410x0",
"410x342.5",
"10x342.5"
],
"printable_height": "401",
"printer_extruder_id": [
"1",
"2",
"3",
"4"
],
"printer_extruder_variant": [
"Direct Drive High Flow",
"Direct Drive High Flow",
"Direct Drive High Flow",
"Direct Drive High Flow"
],
"printer_model": "Xplorer IQEX",
"printer_variant": "0.8",
"retract_before_wipe": [
"70%",
"70%",
"70%",
"70%"
],
"retract_length_toolchange": [
"2",
"2",
"2",
"2"
],
"retract_lift_above": [
"0.2",
"0.2",
"0.2",
"0.2"
],
"retract_lift_below": [
"400",
"400",
"400",
"400"
],
"retract_lift_enforce": [
"All Surfaces",
"All Surfaces",
"All Surfaces",
"All Surfaces"
],
"retract_restart_extra": [
"0",
"0",
"0",
"0"
],
"retract_restart_extra_toolchange": [
"0",
"0",
"0",
"0"
],
"retract_when_changing_layer": [
"1",
"1",
"1",
"1"
],
"retraction_distances_when_cut": [
"18",
"18",
"18",
"18"
],
"retraction_length": [
"1.35",
"1.35",
"1.35",
"1.35"
],
"retraction_minimum_travel": [
"2",
"2",
"2",
"2"
],
"retraction_speed": [
"120",
"120",
"120",
"120"
],
"travel_slope": [
"1",
"1",
"1",
"1"
],
"wipe": [
"1",
"1",
"1",
"1"
],
"wipe_distance": [
"1",
"1",
"1",
"1"
],
"z_hop": [
"1",
"1",
"1",
"1"
],
"z_hop_types": [
"Normal Lift",
"Normal Lift",
"Normal Lift",
"Normal Lift"
]
}
@@ -0,0 +1,167 @@
{
"type": "machine",
"name": "Xplorer Single 0.4 nozzle",
"inherits": "Xplorer common",
"from": "system",
"setting_id": "P4sDniCHVUne9JJF",
"instantiation": "true",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\n{if layer_num != 0}\nG92 E0\n{endif}",
"change_filament_gcode": "M600",
"default_nozzle_volume_type": [
"High Flow"
],
"default_print_profile": "0.20mm Standard @Xplorer 0.4",
"deretraction_speed": [
"120"
],
"extruder_colour": [
"#FCE94F"
],
"extruder_offset": [
"0x0"
],
"extruder_type": [
"Direct Drive"
],
"extruder_variant_list": [
"Direct Drive High Flow"
],
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]\nLOG_RESUME\nLOG_EXTR",
"long_retractions_when_cut": [
"0"
],
"machine_max_acceleration_e": [
"6000",
"5000"
],
"machine_max_acceleration_extruding": [
"5000",
"2500"
],
"machine_max_acceleration_retracting": [
"5000",
"5000"
],
"machine_max_acceleration_travel": [
"5000",
"2500"
],
"machine_max_acceleration_x": [
"5000",
"2500"
],
"machine_max_acceleration_y": [
"5000",
"2500"
],
"machine_max_acceleration_z": [
"100",
"50"
],
"machine_max_jerk_e": [
"2.5",
"1.25"
],
"machine_max_jerk_x": [
"5",
"2.5"
],
"machine_max_jerk_y": [
"5",
"2.5"
],
"machine_max_jerk_z": [
"0.4",
"0.2"
],
"machine_max_junction_deviation": [
"0",
"0"
],
"machine_max_speed_e": [
"120",
"60"
],
"machine_max_speed_x": [
"300",
"150"
],
"machine_max_speed_y": [
"300",
"150"
],
"machine_max_speed_z": [
"40",
"20"
],
"machine_start_gcode": "M104 S[first_layer_temperature] T[initial_tool]\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nG4 P10000 ; wait for temp stabilisation\nstart_print \nTool[initial_tool]\nG90\nM109 S[first_layer_temperature] T[initial_tool]\nBrush_Tool[initial_tool]\nG1 Z5.0 F3000 \nG1 X100 Y1.0 Z0.38 F5000.0 \nG1 X280 Y1.0 Z0.38 F1500.0 E15 \nG1 X280 Y1.4 Z0.38 F5000.0 \nG1 X100 Y1.4 Z0.38 F1500.0 E30\nG92 E0\nG1 Z5.0 F3000\nSKEW_PROFILE LOAD=SKEW1\n",
"max_layer_height": [
"0.32"
],
"min_layer_height": [
"0.04"
],
"nozzle_diameter": [
"0.4"
],
"printable_height": "401",
"printer_extruder_id": [
"1"
],
"printer_extruder_variant": [
"Direct Drive High Flow"
],
"printer_model": "Xplorer Single",
"printer_variant": "0.4",
"retract_before_wipe": [
"70%"
],
"retract_length_toolchange": [
"2"
],
"retract_lift_above": [
"0.2"
],
"retract_lift_below": [
"400"
],
"retract_lift_enforce": [
"All Surfaces"
],
"retract_restart_extra": [
"0"
],
"retract_restart_extra_toolchange": [
"0"
],
"retract_when_changing_layer": [
"1"
],
"retraction_distances_when_cut": [
"18"
],
"retraction_length": [
"1.35"
],
"retraction_minimum_travel": [
"2"
],
"retraction_speed": [
"120"
],
"travel_slope": [
"1"
],
"wipe": [
"1"
],
"wipe_distance": [
"1"
],
"z_hop": [
"1"
],
"z_hop_types": [
"Normal Lift"
]
}
@@ -0,0 +1,167 @@
{
"type": "machine",
"name": "Xplorer Single 0.6 nozzle",
"inherits": "Xplorer common",
"from": "system",
"setting_id": "AN4SjJRQBVxztQoT",
"instantiation": "true",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\n{if layer_num != 0}\nG92 E0\n{endif}",
"change_filament_gcode": "M600",
"default_nozzle_volume_type": [
"High Flow"
],
"default_print_profile": "0.30mm Standard @Xplorer 0.6",
"deretraction_speed": [
"120"
],
"extruder_colour": [
"#FCE94F"
],
"extruder_offset": [
"0x0"
],
"extruder_type": [
"Direct Drive"
],
"extruder_variant_list": [
"Direct Drive High Flow"
],
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]\nLOG_RESUME\nLOG_EXTR",
"long_retractions_when_cut": [
"0"
],
"machine_max_acceleration_e": [
"6000",
"5000"
],
"machine_max_acceleration_extruding": [
"5000",
"2500"
],
"machine_max_acceleration_retracting": [
"5000",
"5000"
],
"machine_max_acceleration_travel": [
"5000",
"2500"
],
"machine_max_acceleration_x": [
"5000",
"2500"
],
"machine_max_acceleration_y": [
"5000",
"2500"
],
"machine_max_acceleration_z": [
"100",
"50"
],
"machine_max_jerk_e": [
"2.5",
"1.25"
],
"machine_max_jerk_x": [
"5",
"2.5"
],
"machine_max_jerk_y": [
"5",
"2.5"
],
"machine_max_jerk_z": [
"0.4",
"0.2"
],
"machine_max_junction_deviation": [
"0",
"0"
],
"machine_max_speed_e": [
"120",
"60"
],
"machine_max_speed_x": [
"300",
"150"
],
"machine_max_speed_y": [
"300",
"150"
],
"machine_max_speed_z": [
"40",
"20"
],
"machine_start_gcode": "M104 S[first_layer_temperature] T[initial_tool]\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nG4 P10000 ; wait for temp stabilisation\nstart_print \nTool[initial_tool]\nG90\nM109 S[first_layer_temperature] T[initial_tool]\nBrush_Tool[initial_tool]\nG1 Z5.0 F3000 \nG1 X100 Y1.0 Z0.38 F5000.0 \nG1 X280 Y1.0 Z0.38 F1500.0 E15 \nG1 X280 Y1.4 Z0.38 F5000.0 \nG1 X100 Y1.4 Z0.38 F1500.0 E30\nG92 E0\nG1 Z5.0 F3000\nSKEW_PROFILE LOAD=SKEW1\n",
"max_layer_height": [
"0.45"
],
"min_layer_height": [
"0.12"
],
"nozzle_diameter": [
"0.6"
],
"printable_height": "401",
"printer_extruder_id": [
"1"
],
"printer_extruder_variant": [
"Direct Drive High Flow"
],
"printer_model": "Xplorer Single",
"printer_variant": "0.6",
"retract_before_wipe": [
"70%"
],
"retract_length_toolchange": [
"2"
],
"retract_lift_above": [
"0.2"
],
"retract_lift_below": [
"400"
],
"retract_lift_enforce": [
"All Surfaces"
],
"retract_restart_extra": [
"0"
],
"retract_restart_extra_toolchange": [
"0"
],
"retract_when_changing_layer": [
"1"
],
"retraction_distances_when_cut": [
"18"
],
"retraction_length": [
"1.35"
],
"retraction_minimum_travel": [
"2"
],
"retraction_speed": [
"120"
],
"travel_slope": [
"1"
],
"wipe": [
"1"
],
"wipe_distance": [
"1"
],
"z_hop": [
"1"
],
"z_hop_types": [
"Normal Lift"
]
}
@@ -0,0 +1,167 @@
{
"type": "machine",
"name": "Xplorer Single 0.8 nozzle",
"inherits": "Xplorer common",
"from": "system",
"setting_id": "rGCuBhLqBfV0zFLJ",
"instantiation": "true",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\n{if layer_num != 0}\nG92 E0\n{endif}",
"change_filament_gcode": "M600",
"default_nozzle_volume_type": [
"High Flow"
],
"default_print_profile": "0.40mm Standard @Xplorer 0.8",
"deretraction_speed": [
"120"
],
"extruder_colour": [
"#FCE94F"
],
"extruder_offset": [
"0x0"
],
"extruder_type": [
"Direct Drive"
],
"extruder_variant_list": [
"Direct Drive High Flow"
],
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]\nLOG_RESUME\nLOG_EXTR",
"long_retractions_when_cut": [
"0"
],
"machine_max_acceleration_e": [
"6000",
"5000"
],
"machine_max_acceleration_extruding": [
"5000",
"2500"
],
"machine_max_acceleration_retracting": [
"5000",
"5000"
],
"machine_max_acceleration_travel": [
"5000",
"2500"
],
"machine_max_acceleration_x": [
"5000",
"2500"
],
"machine_max_acceleration_y": [
"5000",
"2500"
],
"machine_max_acceleration_z": [
"100",
"50"
],
"machine_max_jerk_e": [
"2.5",
"1.25"
],
"machine_max_jerk_x": [
"5",
"2.5"
],
"machine_max_jerk_y": [
"5",
"2.5"
],
"machine_max_jerk_z": [
"0.4",
"0.2"
],
"machine_max_junction_deviation": [
"0",
"0"
],
"machine_max_speed_e": [
"120",
"60"
],
"machine_max_speed_x": [
"300",
"150"
],
"machine_max_speed_y": [
"300",
"150"
],
"machine_max_speed_z": [
"40",
"20"
],
"machine_start_gcode": "M104 S[first_layer_temperature] T[initial_tool]\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nG4 P10000 ; wait for temp stabilisation\nstart_print \nTool[initial_tool]\nG90\nM109 S[first_layer_temperature] T[initial_tool]\nBrush_Tool[initial_tool]\nG1 Z5.0 F3000 \nG1 X100 Y1.0 Z0.38 F5000.0 \nG1 X280 Y1.0 Z0.38 F1500.0 E15 \nG1 X280 Y1.4 Z0.38 F5000.0 \nG1 X100 Y1.4 Z0.38 F1500.0 E30\nG92 E0\nG1 Z5.0 F3000\nSKEW_PROFILE LOAD=SKEW1\n",
"max_layer_height": [
"0.64"
],
"min_layer_height": [
"0.2"
],
"nozzle_diameter": [
"0.8"
],
"printable_height": "401",
"printer_extruder_id": [
"1"
],
"printer_extruder_variant": [
"Direct Drive High Flow"
],
"printer_model": "Xplorer Single",
"printer_variant": "0.8",
"retract_before_wipe": [
"70%"
],
"retract_length_toolchange": [
"2"
],
"retract_lift_above": [
"0.2"
],
"retract_lift_below": [
"400"
],
"retract_lift_enforce": [
"All Surfaces"
],
"retract_restart_extra": [
"0"
],
"retract_restart_extra_toolchange": [
"0"
],
"retract_when_changing_layer": [
"1"
],
"retraction_distances_when_cut": [
"18"
],
"retraction_length": [
"1.35"
],
"retraction_minimum_travel": [
"2"
],
"retraction_speed": [
"120"
],
"travel_slope": [
"1"
],
"wipe": [
"1"
],
"wipe_distance": [
"1"
],
"z_hop": [
"1"
],
"z_hop_types": [
"Normal Lift"
]
}
@@ -0,0 +1,51 @@
{
"type": "machine",
"name": "Xplorer common",
"inherits": "fdm_klipper_common",
"from": "system",
"instantiation": "false",
"default_filament_profile": [
"Generic PLA @System"
],
"adaptive_bed_mesh_margin": "0",
"bed_mesh_max": "99999,99999",
"bed_mesh_min": "-99999,-99999",
"bed_mesh_probe_distance": "50,50",
"best_object_pos": "0.5,0.5",
"disable_m73": "0",
"emit_machine_limits_to_gcode": "0",
"enable_long_retraction_when_cut": "0",
"extruder_clearance_height_to_rod": "43",
"extruder_clearance_radius": "70",
"fan_kickstart": "0",
"fan_speedup_overhangs": "1",
"fan_speedup_time": "0",
"head_wrap_detect_zone": [],
"machine_end_gcode": "end_print",
"max_resonance_avoidance_speed": "120",
"min_resonance_avoidance_speed": "70",
"nozzle_height": "2.5",
"nozzle_hrc": "0",
"nozzle_type": "hardened_steel",
"nozzle_volume": "0",
"pellet_modded_printer": "0",
"preferred_orientation": "0",
"printable_area": [
"10x0",
"410x0",
"410x400",
"10x400"
],
"purge_in_prime_tower": "1",
"resonance_avoidance": "0",
"single_extruder_multi_material": "0",
"support_air_filtration": "1",
"support_chamber_temp_control": "1",
"support_multi_bed_types": "0",
"thumbnails": "48x48/PNG, 300x300/PNG",
"thumbnails_format": "PNG",
"time_cost": "0",
"use_firmware_retraction": "0",
"use_relative_e_distances": "1",
"z_offset": "0"
}
@@ -0,0 +1,12 @@
{
"type": "machine_model",
"name": "Xplorer Dual Gantry",
"model_id": "XplorerDualGantry",
"nozzle_diameter": "0.4;0.6;0.8",
"machine_tech": "FFF",
"family": "Vivedino",
"bed_model": "Xplorer_buildplate_model.stl",
"bed_texture": "Xplorer_bed_texture_dualgantry.svg",
"hotend_model": "",
"default_materials": "Generic ABS @System;Generic PLA @System;Generic PLA-CF @System;Generic PETG @System;Generic TPU @System;Generic ASA @System;Generic PC @System;Generic PVA @System;Generic PA @System;Generic PA-CF @System"
}
@@ -0,0 +1,12 @@
{
"type": "machine_model",
"name": "Xplorer IDEX",
"model_id": "XplorerIDEX",
"nozzle_diameter": "0.4;0.6;0.8",
"machine_tech": "FFF",
"family": "Vivedino",
"bed_model": "Xplorer_buildplate_model.stl",
"bed_texture": "Xplorer_bed_texture_idex.svg",
"hotend_model": "",
"default_materials": "Generic ABS @System;Generic PLA @System;Generic PLA-CF @System;Generic PETG @System;Generic TPU @System;Generic ASA @System;Generic PC @System;Generic PVA @System;Generic PA @System;Generic PA-CF @System"
}
@@ -0,0 +1,12 @@
{
"type": "machine_model",
"name": "Xplorer IQEX",
"model_id": "XplorerIQEX",
"nozzle_diameter": "0.4;0.6;0.8",
"machine_tech": "FFF",
"family": "Vivedino",
"bed_model": "Xplorer_buildplate_model.stl",
"bed_texture": "Xplorer_bed_texture_iqex.svg",
"hotend_model": "",
"default_materials": "Generic ABS @System;Generic PLA @System;Generic PLA-CF @System;Generic PETG @System;Generic TPU @System;Generic ASA @System;Generic PC @System;Generic PVA @System;Generic PA @System;Generic PA-CF @System"
}
@@ -0,0 +1,12 @@
{
"type": "machine_model",
"name": "Xplorer Single",
"model_id": "XplorerSingle",
"nozzle_diameter": "0.4;0.6;0.8",
"machine_tech": "FFF",
"family": "Vivedino",
"bed_model": "Xplorer_buildplate_model.stl",
"bed_texture": "",
"hotend_model": "",
"default_materials": "Generic ABS @System;Generic PLA @System;Generic PLA-CF @System;Generic PETG @System;Generic TPU @System;Generic ASA @System;Generic PC @System;Generic PVA @System;Generic PA @System;Generic PA-CF @System"
}
@@ -0,0 +1,28 @@
{
"type": "process",
"name": "0.04mm Ultra Fine @Xplorer 0.4",
"inherits": "fdm_process_xplorer_common",
"from": "system",
"setting_id": "7SZOHFM1nfZsGYHe",
"instantiation": "true",
"bottom_shell_layers": "7",
"initial_layer_print_height": "0.3",
"inner_wall_speed": "190",
"internal_solid_infill_acceleration": "100%",
"min_skirt_length": "0",
"outer_wall_speed": "130",
"skirt_distance": "3",
"skirt_loops": "1",
"slowdown_for_curled_perimeters": "1",
"small_perimeter_speed": "50%",
"sparse_infill_acceleration": "100%",
"top_shell_layers": "9",
"top_solid_infill_flow_ratio": "1",
"layer_height": "0.04",
"compatible_printers": [
"Xplorer Single 0.4 nozzle",
"Xplorer IDEX 0.4 nozzle",
"Xplorer Dual Gantry 0.4 nozzle",
"Xplorer IQEX 0.4 nozzle"
]
}
@@ -0,0 +1,28 @@
{
"type": "process",
"name": "0.08mm Extra Fine @Xplorer 0.4",
"inherits": "fdm_process_xplorer_common",
"from": "system",
"setting_id": "OvD7mwbTHKOUsgiT",
"instantiation": "true",
"bottom_shell_layers": "4",
"initial_layer_print_height": "0.3",
"inner_wall_speed": "190",
"internal_solid_infill_acceleration": "100%",
"min_skirt_length": "0",
"outer_wall_speed": "130",
"skirt_distance": "3",
"skirt_loops": "1",
"slowdown_for_curled_perimeters": "1",
"small_perimeter_speed": "50%",
"sparse_infill_acceleration": "100%",
"top_shell_layers": "5",
"top_solid_infill_flow_ratio": "0.87",
"layer_height": "0.08",
"compatible_printers": [
"Xplorer Single 0.4 nozzle",
"Xplorer IDEX 0.4 nozzle",
"Xplorer Dual Gantry 0.4 nozzle",
"Xplorer IQEX 0.4 nozzle"
]
}
@@ -0,0 +1,28 @@
{
"type": "process",
"name": "0.12mm Fine @Xplorer 0.4",
"inherits": "fdm_process_xplorer_common",
"from": "system",
"setting_id": "9iNid6X4xIPh267R",
"instantiation": "true",
"bottom_shell_layers": "4",
"initial_layer_print_height": "0.3",
"inner_wall_speed": "190",
"internal_solid_infill_acceleration": "100%",
"min_skirt_length": "0",
"outer_wall_speed": "130",
"skirt_distance": "3",
"skirt_loops": "1",
"slowdown_for_curled_perimeters": "1",
"small_perimeter_speed": "50%",
"sparse_infill_acceleration": "100%",
"top_shell_layers": "5",
"top_solid_infill_flow_ratio": "0.87",
"layer_height": "0.12",
"compatible_printers": [
"Xplorer Single 0.4 nozzle",
"Xplorer IDEX 0.4 nozzle",
"Xplorer Dual Gantry 0.4 nozzle",
"Xplorer IQEX 0.4 nozzle"
]
}
@@ -0,0 +1,28 @@
{
"type": "process",
"name": "0.16mm Optimal @Xplorer 0.4",
"inherits": "fdm_process_xplorer_common",
"from": "system",
"setting_id": "6eUjAhfHVuxRSCQr",
"instantiation": "true",
"bottom_shell_layers": "4",
"initial_layer_print_height": "0.3",
"inner_wall_speed": "190",
"internal_solid_infill_acceleration": "100%",
"min_skirt_length": "0",
"outer_wall_speed": "130",
"skirt_distance": "3",
"skirt_loops": "1",
"slowdown_for_curled_perimeters": "1",
"small_perimeter_speed": "50%",
"sparse_infill_acceleration": "100%",
"top_shell_layers": "5",
"top_solid_infill_flow_ratio": "0.87",
"layer_height": "0.16",
"compatible_printers": [
"Xplorer Single 0.4 nozzle",
"Xplorer IDEX 0.4 nozzle",
"Xplorer Dual Gantry 0.4 nozzle",
"Xplorer IQEX 0.4 nozzle"
]
}
@@ -0,0 +1,28 @@
{
"type": "process",
"name": "0.18mm Fine @Xplorer 0.6",
"inherits": "fdm_process_xplorer_common_0_6",
"from": "system",
"setting_id": "tQHKzSbBNyoPQkup",
"instantiation": "true",
"bottom_shell_layers": "4",
"initial_layer_print_height": "0.3",
"inner_wall_speed": "190",
"internal_solid_infill_acceleration": "100%",
"min_skirt_length": "0",
"outer_wall_speed": "130",
"skirt_distance": "3",
"skirt_loops": "1",
"slowdown_for_curled_perimeters": "1",
"small_perimeter_speed": "50%",
"sparse_infill_acceleration": "100%",
"top_shell_layers": "5",
"top_solid_infill_flow_ratio": "0.87",
"layer_height": "0.18",
"compatible_printers": [
"Xplorer Single 0.6 nozzle",
"Xplorer IDEX 0.6 nozzle",
"Xplorer Dual Gantry 0.6 nozzle",
"Xplorer IQEX 0.6 nozzle"
]
}
@@ -0,0 +1,26 @@
{
"type": "process",
"name": "0.20mm Standard @Xplorer 0.4",
"inherits": "fdm_process_xplorer_common",
"from": "system",
"setting_id": "CiJAkwpq5pR6MfYD",
"instantiation": "true",
"initial_layer_print_height": "0.3",
"inner_wall_speed": "190",
"internal_solid_infill_acceleration": "100%",
"min_skirt_length": "10",
"outer_wall_speed": "130",
"skirt_loops": "2",
"slowdown_for_curled_perimeters": "1",
"small_perimeter_speed": "50%",
"sparse_infill_acceleration": "100%",
"top_shell_layers": "4",
"top_solid_infill_flow_ratio": "0.87",
"layer_height": "0.20",
"compatible_printers": [
"Xplorer Single 0.4 nozzle",
"Xplorer IDEX 0.4 nozzle",
"Xplorer Dual Gantry 0.4 nozzle",
"Xplorer IQEX 0.4 nozzle"
]
}
@@ -0,0 +1,26 @@
{
"type": "process",
"name": "0.24mm Fine @Xplorer 0.8",
"inherits": "fdm_process_xplorer_common_0_8",
"from": "system",
"setting_id": "yv9TMduh7bgD1IjO",
"instantiation": "true",
"initial_layer_print_height": "0.3",
"inner_wall_speed": "190",
"internal_solid_infill_acceleration": "100%",
"min_skirt_length": "10",
"outer_wall_speed": "130",
"skirt_loops": "2",
"slowdown_for_curled_perimeters": "1",
"small_perimeter_speed": "50%",
"sparse_infill_acceleration": "100%",
"top_shell_layers": "4",
"top_solid_infill_flow_ratio": "0.87",
"layer_height": "0.24",
"compatible_printers": [
"Xplorer Single 0.8 nozzle",
"Xplorer IDEX 0.8 nozzle",
"Xplorer Dual Gantry 0.8 nozzle",
"Xplorer IQEX 0.8 nozzle"
]
}
@@ -0,0 +1,26 @@
{
"type": "process",
"name": "0.24mm Optimal @Xplorer 0.6",
"inherits": "fdm_process_xplorer_common_0_6",
"from": "system",
"setting_id": "WKjOeNEZndeHrbQw",
"instantiation": "true",
"initial_layer_print_height": "0.3",
"inner_wall_speed": "190",
"internal_solid_infill_acceleration": "100%",
"min_skirt_length": "10",
"outer_wall_speed": "130",
"skirt_loops": "2",
"slowdown_for_curled_perimeters": "1",
"small_perimeter_speed": "50%",
"sparse_infill_acceleration": "100%",
"top_shell_layers": "4",
"top_solid_infill_flow_ratio": "0.87",
"layer_height": "0.24",
"compatible_printers": [
"Xplorer Single 0.6 nozzle",
"Xplorer IDEX 0.6 nozzle",
"Xplorer Dual Gantry 0.6 nozzle",
"Xplorer IQEX 0.6 nozzle"
]
}
@@ -0,0 +1,26 @@
{
"type": "process",
"name": "0.28mm Draft @Xplorer 0.4",
"inherits": "fdm_process_xplorer_common",
"from": "system",
"setting_id": "zjvcZ59oVnh73fij",
"instantiation": "true",
"initial_layer_print_height": "0.32",
"inner_wall_speed": "190",
"internal_solid_infill_acceleration": "100%",
"min_skirt_length": "0",
"outer_wall_speed": "130",
"skirt_loops": "1",
"slowdown_for_curled_perimeters": "0",
"small_perimeter_speed": "50%",
"sparse_infill_acceleration": "100%",
"top_shell_layers": "4",
"top_solid_infill_flow_ratio": "0.87",
"layer_height": "0.28",
"compatible_printers": [
"Xplorer Single 0.4 nozzle",
"Xplorer IDEX 0.4 nozzle",
"Xplorer Dual Gantry 0.4 nozzle",
"Xplorer IQEX 0.4 nozzle"
]
}
@@ -0,0 +1,26 @@
{
"type": "process",
"name": "0.30mm Standard @Xplorer 0.6",
"inherits": "fdm_process_xplorer_common_0_6",
"from": "system",
"setting_id": "1NEAZrryKhhZNWoB",
"instantiation": "true",
"initial_layer_print_height": "0.32",
"inner_wall_speed": "190",
"internal_solid_infill_acceleration": "100%",
"min_skirt_length": "0",
"outer_wall_speed": "130",
"skirt_loops": "1",
"slowdown_for_curled_perimeters": "0",
"small_perimeter_speed": "50%",
"sparse_infill_acceleration": "100%",
"top_shell_layers": "4",
"top_solid_infill_flow_ratio": "0.87",
"layer_height": "0.30",
"compatible_printers": [
"Xplorer Single 0.6 nozzle",
"Xplorer IDEX 0.6 nozzle",
"Xplorer Dual Gantry 0.6 nozzle",
"Xplorer IQEX 0.6 nozzle"
]
}
@@ -0,0 +1,27 @@
{
"type": "process",
"name": "0.32mm Extra Draft @Xplorer 0.4",
"inherits": "fdm_process_xplorer_common",
"from": "system",
"setting_id": "IrvXoa4zZ1b2Y5cX",
"instantiation": "true",
"initial_layer_print_height": "0.32",
"inner_wall_speed": "190",
"internal_solid_infill_acceleration": "100%",
"min_skirt_length": "0",
"outer_wall_speed": "130",
"skirt_loops": "1",
"slowdown_for_curled_perimeters": "0",
"small_perimeter_speed": "50%",
"sparse_infill_acceleration": "100%",
"top_shell_layers": "4",
"top_solid_infill_flow_ratio": "0.87",
"top_surface_line_width": "0.45",
"layer_height": "0.32",
"compatible_printers": [
"Xplorer Single 0.4 nozzle",
"Xplorer IDEX 0.4 nozzle",
"Xplorer Dual Gantry 0.4 nozzle",
"Xplorer IQEX 0.4 nozzle"
]
}
@@ -0,0 +1,26 @@
{
"type": "process",
"name": "0.32mm Optimal @Xplorer 0.8",
"inherits": "fdm_process_xplorer_common_0_8",
"from": "system",
"setting_id": "N3z9IBRaaeXwVLbl",
"instantiation": "true",
"initial_layer_print_height": "0.32",
"inner_wall_speed": "190",
"internal_solid_infill_acceleration": "100%",
"min_skirt_length": "0",
"outer_wall_speed": "130",
"skirt_loops": "1",
"slowdown_for_curled_perimeters": "0",
"small_perimeter_speed": "50%",
"sparse_infill_acceleration": "100%",
"top_shell_layers": "4",
"top_solid_infill_flow_ratio": "0.87",
"layer_height": "0.32",
"compatible_printers": [
"Xplorer Single 0.8 nozzle",
"Xplorer IDEX 0.8 nozzle",
"Xplorer Dual Gantry 0.8 nozzle",
"Xplorer IQEX 0.8 nozzle"
]
}
@@ -0,0 +1,26 @@
{
"type": "process",
"name": "0.36mm Draft @Xplorer 0.6",
"inherits": "fdm_process_xplorer_common_0_6",
"from": "system",
"setting_id": "uv3jUBdJRkJ4dWVd",
"instantiation": "true",
"initial_layer_print_height": "0.32",
"inner_wall_speed": "190",
"internal_solid_infill_acceleration": "100%",
"min_skirt_length": "0",
"outer_wall_speed": "130",
"skirt_loops": "1",
"slowdown_for_curled_perimeters": "0",
"small_perimeter_speed": "50%",
"sparse_infill_acceleration": "100%",
"top_shell_layers": "4",
"top_solid_infill_flow_ratio": "0.87",
"layer_height": "0.36",
"compatible_printers": [
"Xplorer Single 0.6 nozzle",
"Xplorer IDEX 0.6 nozzle",
"Xplorer Dual Gantry 0.6 nozzle",
"Xplorer IQEX 0.6 nozzle"
]
}
@@ -0,0 +1,26 @@
{
"type": "process",
"name": "0.40mm Standard @Xplorer 0.8",
"inherits": "fdm_process_xplorer_common_0_8",
"from": "system",
"setting_id": "ZP9WXfiKDvSAjqU2",
"instantiation": "true",
"initial_layer_print_height": "0.32",
"inner_wall_speed": "190",
"internal_solid_infill_acceleration": "100%",
"min_skirt_length": "0",
"outer_wall_speed": "130",
"skirt_loops": "1",
"slowdown_for_curled_perimeters": "0",
"small_perimeter_speed": "50%",
"sparse_infill_acceleration": "100%",
"top_shell_layers": "4",
"top_solid_infill_flow_ratio": "0.87",
"layer_height": "0.40",
"compatible_printers": [
"Xplorer Single 0.8 nozzle",
"Xplorer IDEX 0.8 nozzle",
"Xplorer Dual Gantry 0.8 nozzle",
"Xplorer IQEX 0.8 nozzle"
]
}
@@ -0,0 +1,26 @@
{
"type": "process",
"name": "0.42mm Extra Draft @Xplorer 0.6",
"inherits": "fdm_process_xplorer_common_0_6",
"from": "system",
"setting_id": "JMbldCAibS7WnG1D",
"instantiation": "true",
"initial_layer_print_height": "0.32",
"inner_wall_speed": "190",
"internal_solid_infill_acceleration": "100%",
"min_skirt_length": "0",
"outer_wall_speed": "130",
"skirt_loops": "1",
"slowdown_for_curled_perimeters": "0",
"small_perimeter_speed": "50%",
"sparse_infill_acceleration": "100%",
"top_shell_layers": "4",
"top_solid_infill_flow_ratio": "0.87",
"layer_height": "0.42",
"compatible_printers": [
"Xplorer Single 0.6 nozzle",
"Xplorer IDEX 0.6 nozzle",
"Xplorer Dual Gantry 0.6 nozzle",
"Xplorer IQEX 0.6 nozzle"
]
}
@@ -0,0 +1,26 @@
{
"type": "process",
"name": "0.48mm Draft @Xplorer 0.8",
"inherits": "fdm_process_xplorer_common_0_8",
"from": "system",
"setting_id": "OhseWRzfSjom4gC9",
"instantiation": "true",
"initial_layer_print_height": "0.32",
"inner_wall_speed": "190",
"internal_solid_infill_acceleration": "100%",
"min_skirt_length": "0",
"outer_wall_speed": "130",
"skirt_loops": "1",
"slowdown_for_curled_perimeters": "0",
"small_perimeter_speed": "50%",
"sparse_infill_acceleration": "100%",
"top_shell_layers": "4",
"top_solid_infill_flow_ratio": "0.87",
"layer_height": "0.48",
"compatible_printers": [
"Xplorer Single 0.8 nozzle",
"Xplorer IDEX 0.8 nozzle",
"Xplorer Dual Gantry 0.8 nozzle",
"Xplorer IQEX 0.8 nozzle"
]
}
@@ -0,0 +1,26 @@
{
"type": "process",
"name": "0.56mm Extra Draft @Xplorer 0.8",
"inherits": "fdm_process_xplorer_common_0_8",
"from": "system",
"setting_id": "EXEeYQRkvcwYeLDA",
"instantiation": "true",
"initial_layer_print_height": "0.32",
"inner_wall_speed": "190",
"internal_solid_infill_acceleration": "100%",
"min_skirt_length": "0",
"outer_wall_speed": "130",
"skirt_loops": "1",
"slowdown_for_curled_perimeters": "0",
"small_perimeter_speed": "50%",
"sparse_infill_acceleration": "100%",
"top_shell_layers": "4",
"top_solid_infill_flow_ratio": "0.87",
"layer_height": "0.56",
"compatible_printers": [
"Xplorer Single 0.8 nozzle",
"Xplorer IDEX 0.8 nozzle",
"Xplorer Dual Gantry 0.8 nozzle",
"Xplorer IQEX 0.8 nozzle"
]
}
@@ -0,0 +1,265 @@
{
"type": "process",
"name": "fdm_process_xplorer_common",
"inherits": "fdm_process_klipper_common",
"from": "system",
"instantiation": "false",
"accel_to_decel_enable": "1",
"accel_to_decel_factor": "50%",
"align_infill_direction_to_model": "0",
"alternate_extra_wall": "0",
"bottom_shell_thickness": "1",
"bottom_solid_infill_flow_ratio": "1",
"bottom_surface_density": "100%",
"bottom_surface_pattern": "monotonicline",
"bridge_acceleration": "1500",
"bridge_angle": "0",
"bridge_density": "100%",
"bridge_flow": "1.5",
"brim_ears_detection_length": "1",
"brim_ears_max_angle": "125",
"brim_type": "no_brim",
"calib_flowrate_topinfill_special_order": "0",
"counterbore_hole_bridging": "none",
"default_acceleration": "5000",
"default_jerk": "5",
"default_junction_deviation": "0",
"detect_narrow_internal_solid_infill": "0",
"dont_filter_internal_bridges": "limited",
"elefant_foot_compensation": "0.1",
"elefant_foot_compensation_layers": "2",
"enable_extra_bridge_layer": "disabled",
"enable_overhang_speed": "1",
"enable_prime_tower": "0",
"enforce_support_layers": "0",
"ensure_vertical_shell_thickness": "ensure_moderate",
"extra_perimeters_on_overhangs": "1",
"extra_solid_infills": "",
"extrusion_rate_smoothing_external_perimeter_only": "0",
"fill_multiline": "1",
"filter_out_gap_fill": "2",
"flush_into_infill": "0",
"flush_into_objects": "0",
"flush_into_support": "1",
"fuzzy_skin": "none",
"fuzzy_skin_first_layer": "0",
"fuzzy_skin_mode": "displacement",
"fuzzy_skin_noise_type": "classic",
"fuzzy_skin_octaves": "4",
"fuzzy_skin_persistence": "0.5",
"fuzzy_skin_point_distance": "0.8",
"fuzzy_skin_scale": "1",
"fuzzy_skin_thickness": "0.3",
"gap_fill_target": "nowhere",
"gap_infill_speed": "10",
"gcode_add_line_number": "0",
"gcode_comments": "0",
"gcode_label_objects": "1",
"hole_to_polyhole": "0",
"hole_to_polyhole_threshold": "0.01",
"hole_to_polyhole_twisted": "1",
"independent_support_layer_height": "1",
"infill_anchor": "10",
"infill_anchor_max": "10",
"infill_combination_max_layer_height": "100%",
"infill_jerk": "0",
"infill_lock_depth": "1",
"infill_overhang_angle": "60",
"infill_shift_step": "0.4",
"infill_wall_overlap": "20%",
"initial_layer_acceleration": "1000",
"initial_layer_jerk": "0",
"initial_layer_line_width": "120%",
"initial_layer_min_bead_width": "85%",
"initial_layer_travel_speed": "200",
"inner_wall_acceleration": "3000",
"inner_wall_jerk": "0",
"inner_wall_line_width": "110%",
"interlocking_beam": "0",
"interlocking_beam_layer_count": "2",
"interlocking_beam_width": "0.8",
"interlocking_boundary_avoidance": "2",
"interlocking_depth": "2",
"interlocking_orientation": "22.5",
"internal_bridge_angle": "0",
"internal_bridge_density": "100%",
"internal_bridge_flow": "1.5",
"internal_bridge_speed": "150%",
"internal_solid_infill_line_width": "120%",
"internal_solid_infill_pattern": "monotonicline",
"ironing_angle": "-1",
"ironing_inset": "0",
"ironing_pattern": "rectilinear",
"is_infill_first": "0",
"lateral_lattice_angle_1": "-45",
"lateral_lattice_angle_2": "45",
"line_width": "110%",
"make_overhang_printable": "0",
"make_overhang_printable_angle": "55",
"make_overhang_printable_hole_size": "0",
"max_bridge_length": "10",
"max_volumetric_extrusion_rate_slope": "0",
"max_volumetric_extrusion_rate_slope_segment_length": "1",
"min_bead_width": "85%",
"min_feature_size": "25%",
"min_length_factor": "0.5",
"min_width_top_surface": "0",
"mmu_segmented_region_interlocking_depth": "0",
"mmu_segmented_region_max_width": "0",
"notes": "",
"only_one_wall_first_layer": "0",
"only_one_wall_top": "1",
"ooze_prevention": "0",
"outer_wall_acceleration": "1000",
"outer_wall_jerk": "0",
"outer_wall_line_width": "100%",
"overhang_1_4_speed": "100",
"overhang_reverse": "0",
"overhang_reverse_internal_only": "0",
"overhang_reverse_threshold": "50%",
"post_process": [],
"precise_outer_wall": "1",
"precise_z_height": "0",
"preheat_steps": "1",
"preheat_time": "10",
"prime_tower_brim_width": "10",
"prime_tower_width": "30",
"prime_volume": "10",
"print_flow_ratio": "1",
"print_order": "default",
"raft_contact_distance": "0.1",
"raft_expansion": "1.5",
"raft_first_layer_density": "90%",
"raft_first_layer_expansion": "2",
"reduce_crossing_wall": "1",
"role_based_wipe_speed": "1",
"scarf_angle_threshold": "155",
"scarf_joint_flow_ratio": "1",
"scarf_joint_speed": "100%",
"scarf_overhang_threshold": "40%",
"seam_gap": "10%",
"seam_slope_conditional": "0",
"seam_slope_entire_loop": "0",
"seam_slope_inner_walls": "0",
"seam_slope_min_length": "20",
"seam_slope_start_height": "0",
"seam_slope_steps": "10",
"seam_slope_type": "none",
"single_extruder_multi_material_priming": "0",
"single_loop_draft_shield": "0",
"skeleton_infill_density": "25%",
"skeleton_infill_line_width": "100%",
"skin_infill_density": "25%",
"skin_infill_depth": "2",
"skin_infill_line_width": "100%",
"skirt_speed": "50",
"skirt_start_angle": "-135",
"skirt_type": "combined",
"slice_closing_radius": "0.049",
"slicing_mode": "regular",
"slow_down_layers": "0",
"small_area_infill_flow_compensation": "1",
"small_area_infill_flow_compensation_model": [
"0,0",
"\n0.2,0.4444",
"\n0.4,0.6145",
"\n0.6,0.7059",
"\n0.8,0.7619",
"\n1.5,0.8571",
"\n2,0.8889",
"\n3,0.9231",
"\n5,0.9520",
"\n10,1"
],
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_rotate_template": "45,135",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "110%",
"sparse_infill_pattern": "gyroid",
"sparse_infill_rotate_template": "",
"spiral_finishing_flow_ratio": "0",
"spiral_mode_max_xy_smoothing": "200%",
"spiral_mode_smooth": "0",
"spiral_starting_flow_ratio": "0",
"staggered_inner_seams": "1",
"support_angle": "0",
"support_bottom_interface_spacing": "0.5",
"support_bottom_z_distance": "0.2",
"support_critical_regions_only": "0",
"support_expansion": "0",
"support_interface_not_for_body": "1",
"support_interface_pattern": "auto",
"support_ironing": "0",
"support_ironing_flow": "10%",
"support_ironing_pattern": "rectilinear",
"support_ironing_spacing": "0.1",
"support_line_width": "100%",
"support_object_first_layer_gap": "0.2",
"support_on_build_plate_only": "1",
"support_remove_small_overhang": "1",
"support_threshold_overlap": "50%",
"support_type": "tree(auto)",
"symmetric_infill_y_axis": "0",
"thick_bridges": "1",
"thick_internal_bridges": "1",
"timelapse_type": "0",
"top_bottom_infill_wall_overlap": "15%",
"top_surface_acceleration": "1000",
"top_surface_density": "100%",
"top_surface_jerk": "0",
"top_surface_line_width": "80%",
"top_surface_speed": "50",
"travel_acceleration": "5000",
"travel_jerk": "0",
"travel_speed": "250",
"travel_speed_z": "0",
"tree_support_adaptive_layer_height": "1",
"tree_support_angle_slow": "25",
"tree_support_auto_brim": "1",
"tree_support_branch_angle_organic": "40",
"tree_support_branch_diameter": "5",
"tree_support_branch_diameter_angle": "5",
"tree_support_branch_diameter_organic": "2",
"tree_support_branch_distance": "5",
"tree_support_branch_distance_organic": "1",
"tree_support_brim_width": "3",
"tree_support_tip_diameter": "0.8",
"tree_support_top_rate": "30%",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_generator": "arachne",
"wall_sequence": "inner wall/outer wall",
"wall_transition_angle": "10",
"wall_transition_filter_deviation": "25%",
"wall_transition_length": "100%",
"wipe_before_external_loop": "0",
"wipe_on_loops": "1",
"wipe_speed": "80%",
"wipe_tower_bridging": "10",
"wipe_tower_cone_angle": "10",
"wipe_tower_extra_flow": "100%",
"wipe_tower_extra_rib_length": "0",
"wipe_tower_extra_spacing": "100%",
"wipe_tower_filament": "0",
"wipe_tower_fillet_wall": "1",
"wipe_tower_max_purge_speed": "90",
"wipe_tower_rib_width": "8",
"wipe_tower_rotation_angle": "0",
"wipe_tower_wall_type": "rectangle",
"wiping_volumes_extruders": [
"70",
"70",
"70",
"70",
"70",
"70",
"70",
"70",
"70",
"70"
],
"xy_hole_compensation": "0.05"
}
@@ -0,0 +1,8 @@
{
"type": "process",
"name": "fdm_process_xplorer_common_0_6",
"inherits": "fdm_process_xplorer_common",
"from": "system",
"instantiation": "false",
"initial_layer_print_height": "0.3"
}
@@ -0,0 +1,8 @@
{
"type": "process",
"name": "fdm_process_xplorer_common_0_8",
"inherits": "fdm_process_xplorer_common",
"from": "system",
"instantiation": "false",
"initial_layer_print_height": "0.4"
}
@@ -29,13 +29,11 @@ uniform vec3 palette_lab[64];
uniform vec3 palette_rgb[64]; uniform vec3 palette_rgb[64];
uniform int palette_count; uniform int palette_count;
uniform bool pure_only; // match against single filaments only (flat-colour image) uniform bool pure_only; // match against single filaments only (flat-colour image)
// How each entry prints. Every entry names a single filament: a mix is given its own mixed filament // The entry's two filaments, equal for a single filament - only so a mix can be told apart. An entry's
// slot, whose components the slicer alternates per print layer, so the fragment just looks that slot's // palette_rgb is already the colour it prints in (for a mix, its mixed filament slot's).
// colour up.
uniform int palette_a[64]; uniform int palette_a[64];
uniform int palette_b[64]; uniform int palette_b[64];
uniform vec3 filament_rgb[16]; uniform float prefer_pure_de; // PREFER_PURE_DE: how much better than a single filament a mix must be
uniform int filament_count;
uniform sampler2D color_tex; // the layer's colour image, sampled at the same uv as the height uniform sampler2D color_tex; // the layer's colour image, sampled at the same uv as the height
uniform bool has_color_tex; uniform bool has_color_tex;
uniform bool volume_mirrored; uniform bool volume_mirrored;
@@ -210,31 +208,16 @@ int nearest_palette_entry(vec3 rgb)
best = i; best = i;
} }
} }
// The same bias make_palette_quantizer() applies (PREFER_PURE_DE = 10): a mix is an interleave, so // The same bias make_palette_quantizer() applies: a mix is an interleave, so it is only worth taking
// it is only worth taking when it beats the nearest single filament by a visible step. Without it // when it beats the nearest single filament by a visible step - otherwise the preview shows mixes
// this picked a mix for almost every fragment - with four filaments the palette is 4 pure entries // where the bake prints a single filament. Compared on the distances rather than their squares, so
// against 30 mixes - while the bake picked a single filament for most of them, so the preview // the margin means the same thing as it does on the CPU (up to CIE76 against CIEDE2000, the
// interleaved the whole wall where the bake interleaves only patches. Compared on the distances // approximation already noted above).
// rather than their squares, so the threshold means the same thing as it does on the CPU (up to if (best_pure >= 0 && palette_a[best] != palette_b[best] && sqrt(bd_pure) - sqrt(bd) < prefer_pure_de)
// CIE76 against CIEDE2000, the approximation already noted above).
if (best_pure >= 0 && palette_a[best] != palette_b[best] && sqrt(bd_pure) - sqrt(bd) < 10.0)
best = best_pure; best = best_pure;
return best; return best;
} }
// One 2x2 Bayer cell, {0, 2; 3, 1}, for x and y in {0, 1}.
// The colour the printer lays down at world point `pos` for palette entry `index`. Every entry names a
// single filament: a mix is given its own mixed filament slot, whose components the slicer alternates
// per print layer, so there is nothing left to interleave here.
vec3 printed_color(int index)
{
int a = palette_a[index];
if (a < 0 || a >= filament_count)
return palette_rgb[index]; // no filament to resolve to: the entry's own colour
return filament_rgb[a];
}
void main() void main()
{ {
if (any(lessThan(clipping_planes_dots, ZERO))) if (any(lessThan(clipping_planes_dots, ZERO)))
@@ -354,16 +337,11 @@ void main()
NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0); NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0);
intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
// Diffuse albedo: the image's colour at this fragment, snapped to the nearest printable colour - // Diffuse albedo: the image's colour at this fragment, snapped to the nearest printable colour.
// and, where that is a mix, the filament the interleave puts here, so the pattern that prints shows.
// Only the albedo - the specular term (intensity.y) stays white - so a coloured fragment reads as // Only the albedo - the specular term (intensity.y) stays white - so a coloured fragment reads as
// the same material under the same light, and the relief this preview exists to show is unaffected. // the same material under the same light, and the relief this preview exists to show is unaffected.
vec3 albedo = uniform_color.rgb; vec3 albedo = uniform_color.rgb;
if (palette_count > 0 && has_color_tex && have_uv && weight > 0.0) if (palette_count > 0 && has_color_tex && have_uv && weight > 0.0)
// tex_pos, not world_pos: the bake resolves the interleave in the bake frame (world albedo = palette_rgb[nearest_palette_entry(texture2D(color_tex, color_uv).rgb)];
// orientation and scale about the volume's origin, see texture_displacement_bake_frame()), so
// measuring z from the bed instead shifted the band phase by the volume origin's height - a
// different filament in the same place than the bake produces.
albedo = printed_color(nearest_palette_entry(texture2D(color_tex, color_uv).rgb));
gl_FragColor = vec4(vec3(intensity.y) + albedo * intensity.x, uniform_color.a); gl_FragColor = vec4(vec3(intensity.y) + albedo * intensity.x, uniform_color.a);
} }
@@ -88,13 +88,11 @@ uniform vec3 palette_lab[64];
uniform vec3 palette_rgb[64]; uniform vec3 palette_rgb[64];
uniform int palette_count; uniform int palette_count;
uniform bool pure_only; // match against single filaments only (flat-colour image) uniform bool pure_only; // match against single filaments only (flat-colour image)
// How each entry prints. Every entry names a single filament: a mix is given its own mixed filament // The entry's two filaments, equal for a single filament - only so a mix can be told apart. An entry's
// slot, whose components the slicer alternates per print layer, so the fragment just looks that slot's // palette_rgb is already the colour it prints in (for a mix, its mixed filament slot's).
// colour up.
uniform int palette_a[64]; uniform int palette_a[64];
uniform int palette_b[64]; uniform int palette_b[64];
uniform vec3 filament_rgb[16]; uniform float prefer_pure_de; // PREFER_PURE_DE: how much better than a single filament a mix must be
uniform int filament_count;
uniform sampler2D color_tex; // the layer's colour image, sampled at the same uv as the height uniform sampler2D color_tex; // the layer's colour image, sampled at the same uv as the height
uniform bool has_color_tex; uniform bool has_color_tex;
uniform bool volume_mirrored; uniform bool volume_mirrored;
@@ -276,31 +274,16 @@ int nearest_palette_entry(vec3 rgb)
best = i; best = i;
} }
} }
// The same bias make_palette_quantizer() applies (PREFER_PURE_DE = 10): a mix is an interleave, so // The same bias make_palette_quantizer() applies: a mix is an interleave, so it is only worth taking
// it is only worth taking when it beats the nearest single filament by a visible step. Without it // when it beats the nearest single filament by a visible step - otherwise the preview shows mixes
// this picked a mix for almost every fragment - with four filaments the palette is 4 pure entries // where the bake prints a single filament. Compared on the distances rather than their squares, so
// against 30 mixes - while the bake picked a single filament for most of them, so the preview // the margin means the same thing as it does on the CPU (up to CIE76 against CIEDE2000, the
// interleaved the whole wall where the bake interleaves only patches. Compared on the distances // approximation already noted above).
// rather than their squares, so the threshold means the same thing as it does on the CPU (up to if (best_pure >= 0 && palette_a[best] != palette_b[best] && sqrt(bd_pure) - sqrt(bd) < prefer_pure_de)
// CIE76 against CIEDE2000, the approximation already noted above).
if (best_pure >= 0 && palette_a[best] != palette_b[best] && sqrt(bd_pure) - sqrt(bd) < 10.0)
best = best_pure; best = best_pure;
return best; return best;
} }
// One 2x2 Bayer cell, {0, 2; 3, 1}, for x and y in {0, 1}.
// The colour the printer lays down at world point `pos` for palette entry `index`. Every entry names a
// single filament: a mix is given its own mixed filament slot, whose components the slicer alternates
// per print layer, so there is nothing left to interleave here.
vec3 printed_color(int index)
{
int a = palette_a[index];
if (a < 0 || a >= filament_count)
return palette_rgb[index]; // no filament to resolve to: the entry's own colour
return filament_rgb[a];
}
void main() void main()
{ {
if (any(lessThan(clipping_planes_dots, ZERO))) if (any(lessThan(clipping_planes_dots, ZERO)))
@@ -442,16 +425,11 @@ void main()
NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0); NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0);
intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
// Diffuse albedo: the image's colour at this fragment, snapped to the nearest printable colour - // Diffuse albedo: the image's colour at this fragment, snapped to the nearest printable colour.
// and, where that is a mix, the filament the interleave puts here, so the pattern that prints shows.
// Only the albedo - the specular term (intensity.y) stays white - so a coloured fragment reads as // Only the albedo - the specular term (intensity.y) stays white - so a coloured fragment reads as
// the same material under the same light, and the relief this preview exists to show is unaffected. // the same material under the same light, and the relief this preview exists to show is unaffected.
vec3 albedo = uniform_color.rgb; vec3 albedo = uniform_color.rgb;
if (palette_count > 0 && has_color_tex && have_uv && weight > 0.0) if (palette_count > 0 && has_color_tex && have_uv && weight > 0.0)
// tex_pos, not world_pos: the bake resolves the interleave in the bake frame (world albedo = palette_rgb[nearest_palette_entry(texture(color_tex, color_uv).rgb)];
// orientation and scale about the volume's origin, see texture_displacement_bake_frame()), so
// measuring z from the bed instead shifted the band phase by the volume origin's height - a
// different filament in the same place than the bake produces.
albedo = printed_color(nearest_palette_entry(texture(color_tex, color_uv).rgb));
out_color = vec4(vec3(intensity.y) + albedo * intensity.x, uniform_color.a); out_color = vec4(vec3(intensity.y) + albedo * intensity.x, uniform_color.a);
} }
-1
View File
@@ -2,5 +2,4 @@
#add_subdirectory(openvdb) #add_subdirectory(openvdb)
# add_subdirectory(meshboolean) # add_subdirectory(meshboolean)
add_subdirectory(its_neighbor_index) add_subdirectory(its_neighbor_index)
# add_subdirectory(opencsg)
#add_subdirectory(aabb-evaluation) #add_subdirectory(aabb-evaluation)
-30
View File
@@ -1,30 +0,0 @@
cmake_minimum_required(VERSION 3.0)
project(OpenCSG-example)
add_executable(opencsg_example WIN32
main.cpp
Engine.hpp Engine.cpp
ShaderCSGDisplay.hpp ShaderCSGDisplay.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../src/slic3r/GUI/Jobs/Job.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../src/slic3r/GUI/ProgressStatusBar.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../src/slic3r/GUI/I18N.hpp
${CMAKE_CURRENT_SOURCE_DIR}/../../src/slic3r/GUI/I18N.cpp)
find_package(wxWidgets 3.1 REQUIRED COMPONENTS core base gl html)
find_package(OpenGL REQUIRED)
find_package(GLEW REQUIRED)
find_package(OpenCSG REQUIRED)
include(${wxWidgets_USE_FILE})
target_link_libraries(opencsg_example libslic3r)
target_include_directories(opencsg_example PRIVATE ${wxWidgets_INCLUDE_DIRS})
target_compile_definitions(opencsg_example PRIVATE ${wxWidgets_DEFINITIONS})
slic3r_remap_configs(OpenCSG::opencsg RelWithDebInfo Release)
target_link_libraries(opencsg_example ${wxWidgets_LIBRARIES}
OpenCSG::opencsg
GLEW::GLEW
OpenGL::GL
#-lXrandr -lXext -lX11
)
-495
View File
@@ -1,495 +0,0 @@
#include "Engine.hpp"
#include <libslic3r/Utils.hpp>
#include <libslic3r/SLAPrint.hpp>
#include <GL/glew.h>
#include <boost/log/trivial.hpp>
#ifndef NDEBUG
#define HAS_GLSAFE
#endif
#ifdef HAS_GLSAFE
extern void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name);
inline void glAssertRecentCall() { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); }
#define glsafe(cmd) do { cmd; glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false)
#define glcheck() do { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false)
void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name)
{
GLenum err = glGetError();
if (err == GL_NO_ERROR)
return;
const char *sErr = 0;
switch (err) {
case GL_INVALID_ENUM: sErr = "Invalid Enum"; break;
case GL_INVALID_VALUE: sErr = "Invalid Value"; break;
// be aware that GL_INVALID_OPERATION is generated if glGetError is executed between the execution of glBegin and the corresponding execution of glEnd
case GL_INVALID_OPERATION: sErr = "Invalid Operation"; break;
case GL_STACK_OVERFLOW: sErr = "Stack Overflow"; break;
case GL_STACK_UNDERFLOW: sErr = "Stack Underflow"; break;
case GL_OUT_OF_MEMORY: sErr = "Out Of Memory"; break;
default: sErr = "Unknown"; break;
}
BOOST_LOG_TRIVIAL(error) << "OpenGL error in " << file_name << ":" << line << ", function " << function_name << "() : " << (int)err << " - " << sErr;
assert(false);
}
#else
inline void glAssertRecentCall() { }
#define glsafe(cmd) cmd
#define glcheck()
#endif
namespace Slic3r { namespace GL {
Scene::Scene() = default;
Scene::~Scene() = default;
void CSGDisplay::render_scene()
{
GLfloat color[] = {1.f, 1.f, 0.f, 0.f};
glsafe(::glColor4fv(color));
if (m_csgsettings.is_enabled()) {
OpenCSG::render(m_scene_cache.primitives_csg);
glDepthFunc(GL_EQUAL);
}
for (auto& p : m_scene_cache.primitives_csg) p->render();
if (m_csgsettings.is_enabled()) glDepthFunc(GL_LESS);
for (auto& p : m_scene_cache.primitives_free) p->render();
glFlush();
}
void Scene::set_print(std::unique_ptr<SLAPrint> &&print)
{
m_print = std::move(print);
// Notify displays
call(&Listener::on_scene_updated, m_listeners, *this);
}
BoundingBoxf3 Scene::get_bounding_box() const
{
return m_print->model().bounding_box();
}
void CSGDisplay::SceneCache::clear()
{
primitives_csg.clear();
primitives_free.clear();
primitives.clear();
}
std::shared_ptr<Primitive> CSGDisplay::SceneCache::add_mesh(const TriangleMesh &mesh)
{
auto p = std::make_shared<Primitive>();
p->load_mesh(mesh);
primitives.emplace_back(p);
primitives_free.emplace_back(p.get());
return p;
}
std::shared_ptr<Primitive> CSGDisplay::SceneCache::add_mesh(const TriangleMesh &mesh,
OpenCSG::Operation o,
unsigned c)
{
auto p = std::make_shared<Primitive>(o, c);
p->load_mesh(mesh);
primitives.emplace_back(p);
primitives_csg.emplace_back(p.get());
return p;
}
void IndexedVertexArray::push_geometry(float x, float y, float z, float nx, float ny, float nz)
{
assert(this->vertices_and_normals_interleaved_VBO_id == 0);
if (this->vertices_and_normals_interleaved_VBO_id != 0)
return;
if (this->vertices_and_normals_interleaved.size() + 6 > this->vertices_and_normals_interleaved.capacity())
this->vertices_and_normals_interleaved.reserve(next_highest_power_of_2(this->vertices_and_normals_interleaved.size() + 6));
this->vertices_and_normals_interleaved.emplace_back(nx);
this->vertices_and_normals_interleaved.emplace_back(ny);
this->vertices_and_normals_interleaved.emplace_back(nz);
this->vertices_and_normals_interleaved.emplace_back(x);
this->vertices_and_normals_interleaved.emplace_back(y);
this->vertices_and_normals_interleaved.emplace_back(z);
this->vertices_and_normals_interleaved_size = this->vertices_and_normals_interleaved.size();
}
void IndexedVertexArray::push_triangle(int idx1, int idx2, int idx3) {
assert(this->vertices_and_normals_interleaved_VBO_id == 0);
if (this->vertices_and_normals_interleaved_VBO_id != 0)
return;
if (this->triangle_indices.size() + 3 > this->vertices_and_normals_interleaved.capacity())
this->triangle_indices.reserve(next_highest_power_of_2(this->triangle_indices.size() + 3));
this->triangle_indices.emplace_back(idx1);
this->triangle_indices.emplace_back(idx2);
this->triangle_indices.emplace_back(idx3);
this->triangle_indices_size = this->triangle_indices.size();
}
void IndexedVertexArray::load_mesh(const TriangleMesh &mesh)
{
assert(triangle_indices.empty() && vertices_and_normals_interleaved_size == 0);
assert(quad_indices.empty() && triangle_indices_size == 0);
assert(vertices_and_normals_interleaved.size() % 6 == 0 && quad_indices_size == vertices_and_normals_interleaved.size());
this->vertices_and_normals_interleaved.reserve(this->vertices_and_normals_interleaved.size() + 3 * 3 * 2 * mesh.facets_count());
int vertices_count = 0;
for (size_t i = 0; i < mesh.facets_count(); ++i) {
const stl_facet &facet = mesh.stl.facet_start[i];
for (int j = 0; j < 3; ++j)
this->push_geometry(facet.vertex[j](0), facet.vertex[j](1), facet.vertex[j](2), facet.normal(0), facet.normal(1), facet.normal(2));
this->push_triangle(vertices_count, vertices_count + 1, vertices_count + 2);
vertices_count += 3;
}
}
void IndexedVertexArray::finalize_geometry()
{
assert(this->vertices_and_normals_interleaved_VBO_id == 0);
assert(this->triangle_indices_VBO_id == 0);
assert(this->quad_indices_VBO_id == 0);
if (!this->vertices_and_normals_interleaved.empty()) {
glsafe(
::glGenBuffers(1, &this->vertices_and_normals_interleaved_VBO_id));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER,
this->vertices_and_normals_interleaved_VBO_id));
glsafe(
::glBufferData(GL_ARRAY_BUFFER,
GLsizeiptr(
this->vertices_and_normals_interleaved.size() *
4),
this->vertices_and_normals_interleaved.data(),
GL_STATIC_DRAW));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
this->vertices_and_normals_interleaved.clear();
}
if (!this->triangle_indices.empty()) {
glsafe(::glGenBuffers(1, &this->triangle_indices_VBO_id));
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,
this->triangle_indices_VBO_id));
glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER,
GLsizeiptr(this->triangle_indices.size() * 4),
this->triangle_indices.data(), GL_STATIC_DRAW));
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
this->triangle_indices.clear();
}
if (!this->quad_indices.empty()) {
glsafe(::glGenBuffers(1, &this->quad_indices_VBO_id));
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,
this->quad_indices_VBO_id));
glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER,
GLsizeiptr(this->quad_indices.size() * 4),
this->quad_indices.data(), GL_STATIC_DRAW));
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
this->quad_indices.clear();
}
}
void IndexedVertexArray::release_geometry()
{
if (this->vertices_and_normals_interleaved_VBO_id) {
glsafe(
::glDeleteBuffers(1,
&this->vertices_and_normals_interleaved_VBO_id));
this->vertices_and_normals_interleaved_VBO_id = 0;
}
if (this->triangle_indices_VBO_id) {
glsafe(::glDeleteBuffers(1, &this->triangle_indices_VBO_id));
this->triangle_indices_VBO_id = 0;
}
if (this->quad_indices_VBO_id) {
glsafe(::glDeleteBuffers(1, &this->quad_indices_VBO_id));
this->quad_indices_VBO_id = 0;
}
this->clear();
}
void IndexedVertexArray::render() const
{
assert(this->vertices_and_normals_interleaved_VBO_id != 0);
assert(this->triangle_indices_VBO_id != 0 ||
this->quad_indices_VBO_id != 0);
glsafe(::glBindBuffer(GL_ARRAY_BUFFER,
this->vertices_and_normals_interleaved_VBO_id));
glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float),
reinterpret_cast<const void *>(3 * sizeof(float))));
glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), nullptr));
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
glsafe(::glEnableClientState(GL_NORMAL_ARRAY));
// Render using the Vertex Buffer Objects.
if (this->triangle_indices_size > 0) {
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,
this->triangle_indices_VBO_id));
glsafe(::glDrawElements(GL_TRIANGLES,
GLsizei(this->triangle_indices_size),
GL_UNSIGNED_INT, nullptr));
glsafe(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
}
if (this->quad_indices_size > 0) {
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,
this->quad_indices_VBO_id));
glsafe(::glDrawElements(GL_QUADS, GLsizei(this->quad_indices_size),
GL_UNSIGNED_INT, nullptr));
glsafe(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
}
glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
glsafe(::glDisableClientState(GL_NORMAL_ARRAY));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
}
void IndexedVertexArray::clear() {
this->vertices_and_normals_interleaved.clear();
this->triangle_indices.clear();
this->quad_indices.clear();
vertices_and_normals_interleaved_size = 0;
triangle_indices_size = 0;
quad_indices_size = 0;
}
void IndexedVertexArray::shrink_to_fit() {
this->vertices_and_normals_interleaved.shrink_to_fit();
this->triangle_indices.shrink_to_fit();
this->quad_indices.shrink_to_fit();
}
void Volume::render()
{
glsafe(::glPushMatrix());
glsafe(::glMultMatrixd(m_trafo.get_matrix().data()));
m_geom.render();
glsafe(::glPopMatrix());
}
void Display::clear_screen()
{
glViewport(0, 0, GLsizei(m_size.x()), GLsizei(m_size.y()));
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
}
Display::~Display()
{
OpenCSG::freeResources();
}
void Display::set_active(long width, long height)
{
if (!m_initialized) {
glewInit();
m_initialized = true;
}
// gray background
glClearColor(0.9f, 0.9f, 0.9f, 1.0f);
// Enable two OpenGL lights
GLfloat light_diffuse[] = { 1.0f, 1.0f, 0.0f, 1.0f}; // White diffuse light
GLfloat light_position0[] = {-1.0f, -1.0f, -1.0f, 0.0f}; // Infinite light location
GLfloat light_position1[] = { 1.0f, 1.0f, 1.0f, 0.0f}; // Infinite light location
glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
glLightfv(GL_LIGHT0, GL_POSITION, light_position0);
glEnable(GL_LIGHT0);
glLightfv(GL_LIGHT1, GL_DIFFUSE, light_diffuse);
glLightfv(GL_LIGHT1, GL_POSITION, light_position1);
glEnable(GL_LIGHT1);
glEnable(GL_LIGHTING);
glEnable(GL_NORMALIZE);
// Use depth buffering for hidden surface elimination
glEnable(GL_DEPTH_TEST);
glEnable(GL_STENCIL_TEST);
set_screen_size(width, height);
}
void Display::set_screen_size(long width, long height)
{
if (m_size.x() != width || m_size.y() != height)
m_camera->set_screen(width, height);
m_size = {width, height};
}
void Display::repaint()
{
clear_screen();
m_camera->view();
render_scene();
m_fps_counter.update();
swap_buffers();
}
void Controller::on_scene_updated(const Scene &scene)
{
const SLAPrint *print = scene.get_print();
if (!print) return;
auto bb = scene.get_bounding_box();
double d = std::max(std::max(bb.size().x(), bb.size().y()), bb.size().z());
m_wheel_pos = long(2 * d);
call_cameras(&Camera::set_zoom, m_wheel_pos);
call(&Display::on_scene_updated, m_displays, scene);
}
void Controller::on_scroll(long v, long d, MouseInput::WheelAxis /*wa*/)
{
m_wheel_pos += v / d;
call_cameras(&Camera::set_zoom, m_wheel_pos);
call(&Display::repaint, m_displays);
}
void Controller::on_moved_to(long x, long y)
{
if (m_left_btn) {
call_cameras(&Camera::rotate, (Vec2i32{x, y} - m_mouse_pos).cast<float>());
call(&Display::repaint, m_displays);
}
m_mouse_pos = {x, y};
}
void CSGDisplay::apply_csgsettings(const CSGSettings &settings)
{
using namespace OpenCSG;
bool needupdate = m_csgsettings.get_convexity() != settings.get_convexity();
m_csgsettings = settings;
setOption(AlgorithmSetting, m_csgsettings.get_algo());
setOption(DepthComplexitySetting, m_csgsettings.get_depth_algo());
setOption(DepthBoundsOptimization, m_csgsettings.get_optimization());
if (needupdate) {
for (OpenCSG::Primitive * p : m_scene_cache.primitives_csg)
if (p->getConvexity() > 1)
p->setConvexity(m_csgsettings.get_convexity());
}
}
void CSGDisplay::on_scene_updated(const Scene &scene)
{
const SLAPrint *print = scene.get_print();
if (!print) return;
m_scene_cache.clear();
for (const SLAPrintObject *po : print->objects()) {
const ModelObject *mo = po->model_object();
TriangleMesh msh = mo->raw_mesh();
sla::DrainHoles holedata = mo->sla_drain_holes;
for (const ModelInstance *mi : mo->instances) {
TriangleMesh mshinst = msh;
auto interior = po->hollowed_interior_mesh();
interior.transform(po->trafo().inverse());
mshinst.merge(interior);
mi->transform_mesh(&mshinst);
auto bb = mshinst.bounding_box();
auto center = bb.center().cast<float>();
mshinst.translate(-center);
m_scene_cache.add_mesh(mshinst, OpenCSG::Intersection,
m_csgsettings.get_convexity());
}
for (const sla::DrainHole &holept : holedata) {
TriangleMesh holemesh = sla::to_triangle_mesh(holept.to_mesh());
m_scene_cache.add_mesh(holemesh, OpenCSG::Subtraction, 1);
}
}
repaint();
}
void Camera::view()
{
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(0.0, m_zoom, 0.0, /* eye is at (0,zoom,0) */
m_referene.x(), m_referene.y(), m_referene.z(),
0.0, 0.0, 1.0); /* up is in positive Y direction */
// TODO Could have been set in prevoius gluLookAt in first argument
glRotatef(m_rot.y(), 1.0, 0.0, 0.0);
glRotatef(m_rot.x(), 0.0, 0.0, 1.0);
if (m_clip_z > 0.) {
GLdouble plane[] = {0., 0., 1., m_clip_z};
glClipPlane(GL_CLIP_PLANE0, plane);
glEnable(GL_CLIP_PLANE0);
} else {
glDisable(GL_CLIP_PLANE0);
}
}
void PerspectiveCamera::set_screen(long width, long height)
{
// Setup the view of the CSG shape
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(45.0, width / double(height), .1, 200.0);
glMatrixMode(GL_MODELVIEW);
}
bool enable_multisampling(bool e)
{
if (!e) { glDisable(GL_MULTISAMPLE); return false; }
GLint is_ms_context;
glGetIntegerv(GL_SAMPLE_BUFFERS, &is_ms_context);
if (is_ms_context) { glEnable(GL_MULTISAMPLE); return true; }
else return false;
}
MouseInput::Listener::~Listener() = default;
void FpsCounter::update()
{
++m_frames;
TimePoint msec = Clock::now();
double seconds_window = to_sec(msec - m_window);
m_fps = 0.5 * m_fps + 0.5 * (m_frames / seconds_window);
if (to_sec(msec - m_last) >= m_resolution) {
m_last = msec;
for (auto &l : m_listeners) l(m_fps);
}
if (seconds_window >= m_window_size) {
m_frames = 0;
m_window = msec;
}
}
}} // namespace Slic3r::GL
-488
View File
@@ -1,488 +0,0 @@
#ifndef SLIC3R_OCSG_EXMP_ENGINE_HPP
#define SLIC3R_OCSG_EXMP_ENGINE_HPP
#include <vector>
#include <memory>
#include <chrono>
#include <libslic3r/Geometry.hpp>
#include <libslic3r/Model.hpp>
#include <libslic3r/TriangleMesh.hpp>
#include <libslic3r/SLA/Hollowing.hpp>
#include <opencsg/opencsg.h>
namespace Slic3r {
class SLAPrint;
namespace GL {
template<class T, class A = std::allocator<T>> using vector = std::vector<T, A>;
// remove empty weak pointers from a vector
template<class L> inline void cleanup(vector<std::weak_ptr<L>> &listeners) {
auto it = std::remove_if(listeners.begin(), listeners.end(),
[](auto &l) { return !l.lock(); });
listeners.erase(it, listeners.end());
}
// Call a class method on each element of a vector of objects (weak pointers)
// of the same type.
template<class F, class L, class...Args>
inline void call(F &&f, vector<std::weak_ptr<L>> &listeners, Args&&... args) {
for (auto &l : listeners)
if (auto p = l.lock()) ((p.get())->*f)(std::forward<Args>(args)...);
}
// A representation of a mouse input for the engine.
class MouseInput
{
public:
enum WheelAxis { waVertical, waHorizontal };
// Interface to implement if an object wants to receive notifications
// about mouse events.
class Listener {
public:
virtual ~Listener();
virtual void on_left_click_down() {}
virtual void on_left_click_up() {}
virtual void on_right_click_down() {}
virtual void on_right_click_up() {}
virtual void on_double_click() {}
virtual void on_scroll(long /*v*/, long /*delta*/, WheelAxis ) {}
virtual void on_moved_to(long /*x*/, long /*y*/) {}
};
private:
vector<std::weak_ptr<Listener>> m_listeners;
public:
virtual ~MouseInput() = default;
virtual void left_click_down()
{
call(&Listener::on_left_click_down, m_listeners);
}
virtual void left_click_up()
{
call(&Listener::on_left_click_up, m_listeners);
}
virtual void right_click_down()
{
call(&Listener::on_right_click_down, m_listeners);
}
virtual void right_click_up()
{
call(&Listener::on_right_click_up, m_listeners);
}
virtual void double_click()
{
call(&Listener::on_double_click, m_listeners);
}
virtual void scroll(long v, long d, WheelAxis wa)
{
call(&Listener::on_scroll, m_listeners, v, d, wa);
}
virtual void move_to(long x, long y)
{
call(&Listener::on_moved_to, m_listeners, x, y);
}
void add_listener(std::shared_ptr<Listener> listener)
{
m_listeners.emplace_back(listener);
cleanup(m_listeners);
}
};
// This is a stripped down version of Slic3r::IndexedVertexArray
class IndexedVertexArray {
public:
~IndexedVertexArray() { release_geometry(); }
// Vertices and their normals, interleaved to be used by void
// glInterleavedArrays(GL_N3F_V3F, 0, x)
vector<float> vertices_and_normals_interleaved;
vector<int> triangle_indices;
vector<int> quad_indices;
// When the geometry data is loaded into the graphics card as Vertex
// Buffer Objects, the above mentioned std::vectors are cleared and the
// following variables keep their original length.
size_t vertices_and_normals_interleaved_size{ 0 };
size_t triangle_indices_size{ 0 };
size_t quad_indices_size{ 0 };
// IDs of the Vertex Array Objects, into which the geometry has been loaded.
// Zero if the VBOs are not sent to GPU yet.
unsigned int vertices_and_normals_interleaved_VBO_id{ 0 };
unsigned int triangle_indices_VBO_id{ 0 };
unsigned int quad_indices_VBO_id{ 0 };
void push_geometry(float x, float y, float z, float nx, float ny, float nz);
inline void push_geometry(
double x, double y, double z, double nx, double ny, double nz)
{
push_geometry(float(x), float(y), float(z), float(nx), float(ny), float(nz));
}
inline void push_geometry(const Vec3d &p, const Vec3d &n)
{
push_geometry(p(0), p(1), p(2), n(0), n(1), n(2));
}
void push_triangle(int idx1, int idx2, int idx3);
void load_mesh(const TriangleMesh &mesh);
inline bool has_VBOs() const
{
return vertices_and_normals_interleaved_VBO_id != 0;
}
// Finalize the initialization of the geometry & indices,
// upload the geometry and indices to OpenGL VBO objects
// and shrink the allocated data, possibly relasing it if it has been
// loaded into the VBOs.
void finalize_geometry();
// Release the geometry data, release OpenGL VBOs.
void release_geometry();
void render() const;
// Is there any geometry data stored?
bool empty() const { return vertices_and_normals_interleaved_size == 0; }
void clear();
// Shrink the internal storage to tighly fit the data stored.
void shrink_to_fit();
};
// Try to enable or disable multisampling.
bool enable_multisampling(bool e = true);
class Volume {
IndexedVertexArray m_geom;
Geometry::Transformation m_trafo;
public:
void render();
void translation(const Vec3d &offset) { m_trafo.set_offset(offset); }
void rotation(const Vec3d &rot) { m_trafo.set_rotation(rot); }
void scale(const Vec3d &scaleing) { m_trafo.set_scaling_factor(scaleing); }
void scale(double s) { scale({s, s, s}); }
inline void load_mesh(const TriangleMesh &mesh)
{
m_geom.load_mesh(mesh);
m_geom.finalize_geometry();
}
};
// A primitive that can be used with OpenCSG rendering algorithms.
// Does a similar job to GLVolume.
class Primitive : public Volume, public OpenCSG::Primitive
{
public:
using OpenCSG::Primitive::Primitive;
Primitive() : OpenCSG::Primitive(OpenCSG::Intersection, 1) {}
void render() override { Volume::render(); }
};
// A simple representation of a camera in a 3D scene
class Camera {
protected:
Vec2f m_rot = {0., 0.};
Vec3d m_referene = {0., 0., 0.};
double m_zoom = 0.;
double m_clip_z = 0.;
public:
virtual ~Camera() = default;
virtual void view();
virtual void set_screen(long width, long height) = 0;
void set_rotation(const Vec2f &rotation) { m_rot = rotation; }
void rotate(const Vec2f &rotation) { m_rot += rotation; }
void set_zoom(double z) { m_zoom = z; }
void set_reference_point(const Vec3d &p) { m_referene = p; }
void set_clip_z(double z) { m_clip_z = z; }
};
// Reset a camera object
inline void reset(Camera &cam)
{
cam.set_rotation({0., 0.});
cam.set_zoom(0.);
cam.set_reference_point({0., 0., 0.});
cam.set_clip_z(0.);
}
// Specialization of a camera which shows in perspective projection
class PerspectiveCamera: public Camera {
public:
void set_screen(long width, long height) override;
};
// A simple counter of FPS. Subscribed objects will receive updates of the
// current fps.
class FpsCounter {
vector<std::function<void(double)>> m_listeners;
using Clock = std::chrono::high_resolution_clock;
using Duration = Clock::duration;
using TimePoint = Clock::time_point;
int m_frames = 0;
TimePoint m_last = Clock::now(), m_window = m_last;
double m_resolution = 0.1, m_window_size = 1.0;
double m_fps = 0.;
static double to_sec(Duration d)
{
return d.count() * double(Duration::period::num) / Duration::period::den;
}
public:
void update();
void add_listener(std::function<void(double)> lst)
{
m_listeners.emplace_back(lst);
}
void clear_listeners() { m_listeners = {}; }
void set_notification_interval(double seconds);
void set_measure_window_size(double seconds);
double get_notification_interval() const { return m_resolution; }
double get_mesure_window_size() const { return m_window_size; }
};
// Collection of the used OpenCSG library settings.
class CSGSettings {
public:
static const constexpr unsigned DEFAULT_CONVEXITY = 10;
private:
OpenCSG::Algorithm m_csgalg = OpenCSG::Algorithm::Automatic;
OpenCSG::DepthComplexityAlgorithm m_depth_algo = OpenCSG::NoDepthComplexitySampling;
OpenCSG::Optimization m_optim = OpenCSG::OptimizationDefault;
bool m_enable = true;
unsigned int m_convexity = DEFAULT_CONVEXITY;
public:
int get_algo() const { return int(m_csgalg); }
void set_algo(int alg)
{
if (alg < OpenCSG::Algorithm::AlgorithmUnused)
m_csgalg = OpenCSG::Algorithm(alg);
}
int get_depth_algo() const { return int(m_depth_algo); }
void set_depth_algo(int alg)
{
if (alg < OpenCSG::DepthComplexityAlgorithmUnused)
m_depth_algo = OpenCSG::DepthComplexityAlgorithm(alg);
}
int get_optimization() const { return int(m_optim); }
void set_optimization(int o)
{
if (o < OpenCSG::Optimization::OptimizationUnused)
m_optim = OpenCSG::Optimization(o);
}
void enable_csg(bool en = true) { m_enable = en; }
bool is_enabled() const { return m_enable; }
unsigned get_convexity() const { return m_convexity; }
void set_convexity(unsigned c) { m_convexity = c; }
};
// The scene is a wrapper around SLAPrint which holds the data to be visualized.
class Scene
{
std::unique_ptr<SLAPrint> m_print;
public:
// Subscribers will be notified if the model is changed. This might be a
// display which will have to load the meshes and repaint itself when
// the scene data changes.
// eg. We load a new 3mf through the UI, this will notify the controller
// associated with the scene and all the displays that the controller is
// connected with.
class Listener {
public:
virtual ~Listener() = default;
virtual void on_scene_updated(const Scene &scene) = 0;
};
Scene();
~Scene();
void set_print(std::unique_ptr<SLAPrint> &&print);
const SLAPrint * get_print() const { return m_print.get(); }
BoundingBoxf3 get_bounding_box() const;
void add_listener(std::shared_ptr<Listener> listener)
{
m_listeners.emplace_back(listener);
cleanup(m_listeners);
}
private:
vector<std::weak_ptr<Listener>> m_listeners;
};
// The basic Display. This is almost just an interface but will do all the
// initialization and show the fps values. Overriding the render_scene is
// needed to show the scene content. The specific method of displaying the
// scene is up the particular implementation (OpenCSG or other screen space
// boolean algorithms)
class Display : public Scene::Listener
{
protected:
Vec2i32 m_size;
bool m_initialized = false;
std::shared_ptr<Camera> m_camera;
FpsCounter m_fps_counter;
public:
explicit Display(std::shared_ptr<Camera> camera = nullptr)
: m_camera(camera ? camera : std::make_shared<PerspectiveCamera>())
{}
~Display() override;
std::shared_ptr<const Camera> get_camera() const { return m_camera; }
std::shared_ptr<Camera> get_camera() { return m_camera; }
void set_camera(std::shared_ptr<Camera> cam) { m_camera = cam; }
virtual void swap_buffers() = 0;
virtual void set_active(long width, long height);
virtual void set_screen_size(long width, long height);
Vec2i32 get_screen_size() const { return m_size; }
virtual void repaint();
bool is_initialized() const { return m_initialized; }
virtual void clear_screen();
virtual void render_scene() {}
template<class _FpsCounter> void set_fps_counter(_FpsCounter &&fpsc)
{
m_fps_counter = std::forward<_FpsCounter>(fpsc);
}
const FpsCounter &get_fps_counter() const { return m_fps_counter; }
FpsCounter &get_fps_counter() { return m_fps_counter; }
};
// Special dispaly using OpenCSG for rendering the scene.
class CSGDisplay : public Display {
protected:
CSGSettings m_csgsettings;
// Cache the renderable primitives. These will be fetched when the scene
// is modified.
struct SceneCache {
vector<std::shared_ptr<Primitive>> primitives;
vector<Primitive *> primitives_free;
vector<OpenCSG::Primitive *> primitives_csg;
void clear();
std::shared_ptr<Primitive> add_mesh(const TriangleMesh &mesh);
std::shared_ptr<Primitive> add_mesh(const TriangleMesh &mesh,
OpenCSG::Operation op,
unsigned covexity);
} m_scene_cache;
public:
// Receive or apply the new settings.
const CSGSettings & get_csgsettings() const { return m_csgsettings; }
void apply_csgsettings(const CSGSettings &settings);
void render_scene() override;
void on_scene_updated(const Scene &scene) override;
};
// The controller is a hub which dispatches mouse events to the connected
// displays. It keeps track of the mouse wheel position, the states whether
// the mouse is being held, dragged, etc... All the connected displays will
// mirror the camera movement (if there is more than one display).
class Controller : public std::enable_shared_from_this<Controller>,
public MouseInput::Listener,
public Scene::Listener
{
long m_wheel_pos = 0;
Vec2i32 m_mouse_pos, m_mouse_pos_rprev, m_mouse_pos_lprev;
bool m_left_btn = false, m_right_btn = false;
std::shared_ptr<Scene> m_scene;
vector<std::weak_ptr<Display>> m_displays;
// Call a method of Camera on all the cameras of the attached displays
template<class F, class...Args>
void call_cameras(F &&f, Args&&... args) {
for (std::weak_ptr<Display> &l : m_displays)
if (auto disp = l.lock()) if (auto cam = disp->get_camera())
(cam.get()->*f)(std::forward<Args>(args)...);
}
public:
// Set the scene that will be controlled.
void set_scene(std::shared_ptr<Scene> scene)
{
m_scene = scene;
m_scene->add_listener(shared_from_this());
}
const Scene * get_scene() const { return m_scene.get(); }
void add_display(std::shared_ptr<Display> disp)
{
m_displays.emplace_back(disp);
cleanup(m_displays);
}
void remove_displays() { m_displays = {}; }
void on_scene_updated(const Scene &scene) override;
void on_left_click_down() override { m_left_btn = true; }
void on_left_click_up() override { m_left_btn = false; }
void on_right_click_down() override { m_right_btn = true; }
void on_right_click_up() override { m_right_btn = false; }
void on_scroll(long v, long d, MouseInput::WheelAxis wa) override;
void on_moved_to(long x, long y) override;
void move_clip_plane(double z) { call_cameras(&Camera::set_clip_z, z); }
};
}} // namespace Slic3r::GL
#endif // SLIC3R_OCSG_EXMP_ENGINE_HPP
-63
View File
@@ -1,63 +0,0 @@
#include "ShaderCSGDisplay.hpp"
#include "libslic3r/SLAPrint.hpp"
#include <GL/glew.h>
namespace Slic3r { namespace GL {
void ShaderCSGDisplay::add_mesh(const TriangleMesh &mesh)
{
auto v = std::make_shared<CSGVolume>();
v->load_mesh(mesh);
m_volumes.emplace_back(v);
}
void ShaderCSGDisplay::render_scene()
{
GLfloat color[] = {1.f, 1.f, 0.f, 0.f};
glColor4fv(color);
glDepthFunc(GL_LESS);
for (auto &v : m_volumes) v->render();
glFlush();
}
void ShaderCSGDisplay::on_scene_updated(const Scene &scene)
{
// TriangleMesh mesh = print->objects().front()->hollowed_interior_mesh();
// Look at CSGDisplay::on_scene_updated to see how its done there.
const SLAPrint *print = scene.get_print();
if (!print) return;
m_volumes.clear();
for (const SLAPrintObject *po : print->objects()) {
const ModelObject *mo = po->model_object();
TriangleMesh msh = mo->raw_mesh();
sla::DrainHoles holedata = mo->sla_drain_holes;
for (const ModelInstance *mi : mo->instances) {
TriangleMesh mshinst = msh;
auto interior = po->hollowed_interior_mesh();
interior.transform(po->trafo().inverse());
mshinst.merge(interior);
mi->transform_mesh(&mshinst);
auto bb = mshinst.bounding_box();
auto center = bb.center().cast<float>();
mshinst.translate(-center);
add_mesh(mshinst);
}
for (const sla::DrainHole &holept : holedata)
add_mesh(sla::to_triangle_mesh(holept.to_mesh()));
}
repaint();
}
}} // namespace Slic3r::GL
-27
View File
@@ -1,27 +0,0 @@
#ifndef SHADERCSGDISPLAY_HPP
#define SHADERCSGDISPLAY_HPP
#include "Engine.hpp"
namespace Slic3r { namespace GL {
class CSGVolume: public Volume
{
// Extend...
};
class ShaderCSGDisplay: public Display {
protected:
vector<std::shared_ptr<CSGVolume>> m_volumes;
void add_mesh(const TriangleMesh &mesh);
public:
void render_scene() override;
void on_scene_updated(const Scene &scene) override;
};
}}
#endif // SHADERCSGDISPLAY_HPP
-734
View File
@@ -1,734 +0,0 @@
#include <iostream>
#include <utility>
#include <memory>
#include "Engine.hpp"
#include "ShaderCSGDisplay.hpp"
#include <GL/glew.h>
#include <opencsg/opencsg.h>
// For compilers that support precompilation, includes "wx/wx.h".
#include <wx/wxprec.h>
#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif
#include <wx/slider.h>
#include <wx/tglbtn.h>
#include <wx/combobox.h>
#include <wx/spinctrl.h>
#include <wx/msgdlg.h>
#include <wx/glcanvas.h>
#include <wx/cmdline.h>
#include "libslic3r/Model.hpp"
#include "libslic3r/Format/3mf.hpp"
#include "libslic3r/SLAPrint.hpp"
#include "slic3r/GUI/Jobs/Job.hpp"
#include "slic3r/GUI/ProgressStatusBar.hpp"
using namespace Slic3r::GL;
class Renderer {
protected:
wxGLCanvas *m_canvas;
std::shared_ptr<wxGLContext> m_context;
public:
Renderer(wxGLCanvas *c): m_canvas{c} {
auto ctx = new wxGLContext(m_canvas);
if (!ctx || !ctx->IsOK()) {
wxMessageBox("Could not create OpenGL context.", "Error",
wxOK | wxICON_ERROR);
return;
}
m_context.reset(ctx);
}
wxGLContext * context() { return m_context.get(); }
const wxGLContext * context() const { return m_context.get(); }
};
// Tell the CSGDisplay how to swap buffers and set the gl context.
class OCSGRenderer: public Renderer, public Slic3r::GL::CSGDisplay {
public:
OCSGRenderer(wxGLCanvas *c): Renderer{c} {}
void set_active(long w, long h) override
{
m_canvas->SetCurrent(*m_context);
Slic3r::GL::Display::set_active(w, h);
}
void swap_buffers() override { m_canvas->SwapBuffers(); }
};
// Tell the CSGDisplay how to swap buffers and set the gl context.
class ShaderCSGRenderer : public Renderer, public Slic3r::GL::ShaderCSGDisplay {
public:
ShaderCSGRenderer(wxGLCanvas *c): Renderer{c} {}
void set_active(long w, long h) override
{
m_canvas->SetCurrent(*m_context);
Slic3r::GL::Display::set_active(w, h);
}
void swap_buffers() override { m_canvas->SwapBuffers(); }
};
// The opengl rendering facility. Here we implement the rendering objects.
class Canvas: public wxGLCanvas
{
// One display is active at a time, the OCSGRenderer by default.
std::shared_ptr<Slic3r::GL::Display> m_display;
public:
template<class...Args>
Canvas(Args &&...args): wxGLCanvas(std::forward<Args>(args)...) {}
std::shared_ptr<Slic3r::GL::Display> get_display() const { return m_display; }
void set_display(std::shared_ptr<Slic3r::GL::Display> d) { m_display = d; }
};
// Enumerate possible mouse events, we will record them.
enum EEvents { LCLK_U, RCLK_U, LCLK_D, RCLK_D, DDCLK, SCRL, MV };
struct Event
{
EEvents type;
long a, b;
Event(EEvents t, long x = 0, long y = 0) : type{t}, a{x}, b{y} {}
};
// Create a special mouse input adapter, which can store (record) the received
// mouse signals into a file and play back the stored events later.
class RecorderMouseInput: public MouseInput {
std::vector<Event> m_events;
bool m_recording = false, m_playing = false;
public:
void left_click_down() override
{
if (m_recording) m_events.emplace_back(LCLK_D);
if (!m_playing) MouseInput::left_click_down();
}
void left_click_up() override
{
if (m_recording) m_events.emplace_back(LCLK_U);
if (!m_playing) MouseInput::left_click_up();
}
void right_click_down() override
{
if (m_recording) m_events.emplace_back(RCLK_D);
if (!m_playing) MouseInput::right_click_down();
}
void right_click_up() override
{
if (m_recording) m_events.emplace_back(RCLK_U);
if (!m_playing) MouseInput::right_click_up();
}
void double_click() override
{
if (m_recording) m_events.emplace_back(DDCLK);
if (!m_playing) MouseInput::double_click();
}
void scroll(long v, long d, WheelAxis wa) override
{
if (m_recording) m_events.emplace_back(SCRL, v, d);
if (!m_playing) MouseInput::scroll(v, d, wa);
}
void move_to(long x, long y) override
{
if (m_recording) m_events.emplace_back(MV, x, y);
if (!m_playing) MouseInput::move_to(x, y);
}
void save(std::ostream &stream)
{
for (const Event &evt : m_events)
stream << evt.type << " " << evt.a << " " << evt.b << std::endl;
}
void load(std::istream &stream)
{
m_events.clear();
while (stream.good()) {
int type; long a, b;
stream >> type >> a >> b;
m_events.emplace_back(EEvents(type), a, b);
}
}
void record(bool r) { m_recording = r; if (r) m_events.clear(); }
void play()
{
m_playing = true;
for (const Event &evt : m_events) {
switch (evt.type) {
case LCLK_U: MouseInput::left_click_up(); break;
case LCLK_D: MouseInput::left_click_down(); break;
case RCLK_U: MouseInput::right_click_up(); break;
case RCLK_D: MouseInput::right_click_down(); break;
case DDCLK: MouseInput::double_click(); break;
case SCRL: MouseInput::scroll(evt.a, evt.b, WheelAxis::waVertical); break;
case MV: MouseInput::move_to(evt.a, evt.b); break;
}
wxTheApp->Yield();
if (!m_playing)
break;
}
m_playing = false;
}
void stop() { m_playing = false; }
bool is_playing() const { return m_playing; }
};
// The top level frame of the application.
class MyFrame: public wxFrame
{
// Instantiate the 3D engine.
std::shared_ptr<Scene> m_scene; // Model
std::shared_ptr<Canvas> m_canvas; // Views store
std::shared_ptr<OCSGRenderer> m_ocsgdisplay; // View
std::shared_ptr<ShaderCSGRenderer> m_shadercsg_display; // Another view
std::shared_ptr<Controller> m_ctl; // Controller
// Add a status bar with progress indication.
std::shared_ptr<Slic3r::GUI::ProgressStatusBar> m_stbar;
RecorderMouseInput m_mouse;
// When loading a Model from 3mf and preparing it, we use a separate thread.
class SLAJob: public Slic3r::GUI::Job {
MyFrame *m_parent;
std::unique_ptr<Slic3r::SLAPrint> m_print;
std::string m_fname;
public:
SLAJob(MyFrame *frame, const std::string &fname)
: Slic3r::GUI::Job{frame->m_stbar}
, m_parent{frame}
, m_fname{fname}
{}
// Runs in separate thread
void process() override;
const std::string & get_project_fname() const { return m_fname; }
protected:
// Runs in the UI thread.
void finalize() override
{
m_parent->m_scene->set_print(std::move(m_print));
m_parent->m_stbar->set_status_text(
wxString::Format("Model %s loaded.", m_fname));
}
};
std::unique_ptr<SLAJob> m_ui_job;
// To keep track of the running average of measured fps values.
double m_fps_avg = 0.;
// We need the record button across methods
wxToggleButton *m_record_btn;
wxComboBox * m_alg_select;
wxComboBox * m_depth_select;
wxComboBox * m_optimization_select;
wxSpinCtrl * m_convexity_spin;
wxToggleButton *m_csg_toggle;
wxToggleButton *m_ms_toggle;
wxStaticText *m_fpstext;
CSGSettings m_csg_settings;
void read_csg_settings(const wxCmdLineParser &parser);
void set_renderer_algorithm(const wxString &alg);
void activate_canvas_display();
public:
MyFrame(const wxString & title,
const wxPoint & pos,
const wxSize & size,
const wxCmdLineParser &parser);
// Grab a 3mf and load (hollow it out) within the UI job.
void load_model(const std::string &fname) {
m_ui_job = std::make_unique<SLAJob>(this, fname);
m_ui_job->start();
}
// Load a previously stored mouse event log and play it back.
void play_back_mouse(const std::string &events_fname)
{
std::fstream stream(events_fname, std::fstream::in);
if (stream.good()) {
std::string model_name;
std::getline(stream, model_name);
load_model(model_name);
while (!m_ui_job->is_finalized())
wxTheApp->Yield();;
int w, h;
stream >> w >> h;
SetSize(w, h);
m_mouse.load(stream);
if (m_record_btn) m_record_btn->Disable();
m_mouse.play();
}
}
Canvas * canvas() { return m_canvas.get(); }
const Canvas * canvas() const { return m_canvas.get(); }
// Bind the canvas mouse events to a class implementing MouseInput interface
void bind_canvas_events(MouseInput &msinput);
double get_fps_average() const { return m_fps_avg; }
};
// Possible OpenCSG configuration values. Will be used on the command line and
// on the UI widgets.
static const std::vector<wxString> CSG_ALGS = {"Auto", "Goldfeather", "SCS", "EnricoShader"};
static const std::vector<wxString> CSG_DEPTH = {"Off", "OcclusionQuery", "On"};
static const std::vector<wxString> CSG_OPT = { "Default", "ForceOn", "On", "Off" };
inline long get_idx(const wxString &a, const std::vector<wxString> &v)
{
auto it = std::find(v.begin(), v.end(), a.ToStdString());
return it - v.begin();
};
class App : public wxApp {
MyFrame *m_frame = nullptr;
wxString m_fname;
public:
bool OnInit() override {
wxCmdLineParser parser(argc, argv);
parser.AddOption("p", "play", "play back file", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL);
parser.AddOption("a", "algorithm", "OpenCSG algorithm [Auto|Goldfeather|SCS]", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL);
parser.AddOption("d", "depth", "OpenCSG depth strategy [Off|OcclusionQuery|On]", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL);
parser.AddOption("o", "optimization", "OpenCSG optimization strategy [Default|ForceOn|On|Off]", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL);
parser.AddOption("c", "convexity", "OpenCSG convexity parameter for generic meshes", wxCMD_LINE_VAL_NUMBER, wxCMD_LINE_PARAM_OPTIONAL);
parser.AddSwitch("", "disable-csg", "Disable csg rendering", wxCMD_LINE_PARAM_OPTIONAL);
parser.Parse();
bool is_play = parser.Found("play", &m_fname);
m_frame = new MyFrame("OrcaSlicer OpenCSG Demo", wxDefaultPosition, wxSize(1024, 768), parser);
if (is_play) {
Bind(wxEVT_IDLE, &App::Play, this);
m_frame->Show( true );
} else m_frame->Show( true );
return true;
}
void Play(wxIdleEvent &) {
Unbind(wxEVT_IDLE, &App::Play, this);
m_frame->play_back_mouse(m_fname.ToStdString());
m_frame->Destroy();
}
};
wxIMPLEMENT_APP(App);
void MyFrame::read_csg_settings(const wxCmdLineParser &parser)
{
wxString alg;
parser.Found("algorithm", &alg);
wxString depth;
parser.Found("depth", &depth);
wxString opt;
parser.Found("optimization", &opt);
long convexity = 1;
parser.Found("convexity", &convexity);
bool csg_off = parser.Found("disable-csg");
if (auto a = get_idx(alg, CSG_ALGS) < OpenCSG::AlgorithmUnused)
m_csg_settings.set_algo(OpenCSG::Algorithm(a));
if (auto a = get_idx(depth, CSG_DEPTH) < OpenCSG::DepthComplexityAlgorithmUnused)
m_csg_settings.set_depth_algo(OpenCSG::DepthComplexityAlgorithm(a));
if (auto a = get_idx(opt, CSG_OPT) < OpenCSG::OptimizationUnused)
m_csg_settings.set_optimization(OpenCSG::Optimization(a));
m_csg_settings.set_convexity(unsigned(convexity));
m_csg_settings.enable_csg(!csg_off);
if (m_ocsgdisplay) m_ocsgdisplay->apply_csgsettings(m_csg_settings);
}
void MyFrame::set_renderer_algorithm(const wxString &alg)
{
long alg_idx = get_idx(alg, CSG_ALGS);
if (alg_idx < 0 || alg_idx >= long(CSG_ALGS.size())) return;
// If there is a valid display in place, save its camera.
auto cam = m_canvas->get_display() ?
m_canvas->get_display()->get_camera() : nullptr;
if (alg == "EnricoShader") {
m_alg_select->SetSelection(int(alg_idx));
m_depth_select->Disable();
m_optimization_select->Disable();
m_csg_toggle->Disable();
m_ocsgdisplay.reset();
canvas()->set_display(nullptr);
m_shadercsg_display = std::make_shared<ShaderCSGRenderer>(canvas());
canvas()->set_display(m_shadercsg_display);
} else {
if (m_csg_settings.get_algo() > 0) m_depth_select->Enable(true);
m_alg_select->SetSelection(m_csg_settings.get_algo());
m_depth_select->SetSelection(m_csg_settings.get_depth_algo());
m_optimization_select->SetSelection(m_csg_settings.get_optimization());
m_convexity_spin->SetValue(int(m_csg_settings.get_convexity()));
m_csg_toggle->SetValue(m_csg_settings.is_enabled());
m_optimization_select->Enable();
m_csg_toggle->Enable();
m_shadercsg_display.reset();
canvas()->set_display(nullptr);
m_ocsgdisplay = std::make_shared<OCSGRenderer>(canvas());
m_ocsgdisplay->apply_csgsettings(m_csg_settings);
canvas()->set_display(m_ocsgdisplay);
}
if (cam)
m_canvas->get_display()->set_camera(cam);
m_ctl->remove_displays();
m_ctl->add_display(m_canvas->get_display());
m_canvas->get_display()->get_fps_counter().add_listener([this](double fps) {
m_fpstext->SetLabel(wxString::Format("fps: %.2f", fps));
m_fps_avg = 0.9 * m_fps_avg + 0.1 * fps;
});
if (IsShown()) {
activate_canvas_display();
m_canvas->get_display()->on_scene_updated(*m_scene);
}
}
void MyFrame::activate_canvas_display()
{
const wxSize ClientSize = m_canvas->GetClientSize();
m_canvas->get_display()->set_active(ClientSize.x, ClientSize.y);
enable_multisampling(m_ms_toggle->GetValue());
m_canvas->Bind(wxEVT_PAINT, [this](wxPaintEvent &) {
// This is required even though dc is not used otherwise.
wxPaintDC dc(m_canvas.get());
const wxSize csize = m_canvas->GetClientSize();
m_canvas->get_display()->set_screen_size(csize.x, csize.y);
m_canvas->get_display()->repaint();
});
m_canvas->Bind(wxEVT_SIZE, [this](wxSizeEvent &) {
const wxSize csize = m_canvas->GetClientSize();
m_canvas->get_display()->set_screen_size(csize.x, csize.y);
m_canvas->get_display()->repaint();
});
// Do the repaint continuously
m_canvas->Bind(wxEVT_IDLE, [this](wxIdleEvent &evt) {
m_canvas->get_display()->repaint();
evt.RequestMore();
});
bind_canvas_events(m_mouse);
}
MyFrame::MyFrame(const wxString &title, const wxPoint &pos, const wxSize &size,
const wxCmdLineParser &parser):
wxFrame(nullptr, wxID_ANY, title, pos, size)
{
wxMenu *menuFile = new wxMenu;
menuFile->Append(wxID_OPEN);
menuFile->Append(wxID_EXIT);
wxMenuBar *menuBar = new wxMenuBar;
menuBar->Append( menuFile, "&File" );
SetMenuBar( menuBar );
m_stbar = std::make_shared<Slic3r::GUI::ProgressStatusBar>(this);
m_stbar->embed(this);
SetStatusText( "Welcome to wxWidgets!" );
int attribList[] =
{WX_GL_RGBA, WX_GL_DOUBLEBUFFER,
// RGB channels each should be allocated with 8 bit depth. One
// should almost certainly get these bit depths by default.
WX_GL_MIN_RED, 8, WX_GL_MIN_GREEN, 8, WX_GL_MIN_BLUE, 8,
// Requesting an 8 bit alpha channel. Interestingly, the NVIDIA
// drivers would most likely work with some alpha plane, but
// glReadPixels would not return the alpha channel on NVIDIA if
// not requested when the GL context is created.
WX_GL_MIN_ALPHA, 8, WX_GL_DEPTH_SIZE, 8, WX_GL_STENCIL_SIZE, 8,
WX_GL_SAMPLE_BUFFERS, GL_TRUE, WX_GL_SAMPLES, 4, 0};
m_scene = std::make_shared<Scene>();
m_ctl = std::make_shared<Controller>();
m_ctl->set_scene(m_scene);
m_canvas = std::make_shared<Canvas>(this, wxID_ANY, attribList,
wxDefaultPosition, wxDefaultSize,
wxWANTS_CHARS | wxFULL_REPAINT_ON_RESIZE);
read_csg_settings(parser);
wxPanel *control_panel = new wxPanel(this);
auto controlsizer = new wxBoxSizer(wxHORIZONTAL);
auto slider_sizer = new wxBoxSizer(wxVERTICAL);
auto console_sizer = new wxBoxSizer(wxVERTICAL);
auto slider = new wxSlider(control_panel, wxID_ANY, 0, 0, 100,
wxDefaultPosition, wxDefaultSize,
wxSL_VERTICAL);
slider_sizer->Add(slider, 1, wxEXPAND);
m_ms_toggle = new wxToggleButton(control_panel, wxID_ANY, "Multisampling");
console_sizer->Add(m_ms_toggle, 0, wxALL | wxEXPAND, 5);
m_csg_toggle = new wxToggleButton(control_panel, wxID_ANY, "CSG");
m_csg_toggle->SetValue(true);
console_sizer->Add(m_csg_toggle, 0, wxALL | wxEXPAND, 5);
auto add_combobox = [control_panel, console_sizer]
(const wxString &label, const std::vector<wxString> &list)
{
auto widget = new wxComboBox(control_panel, wxID_ANY, list[0],
wxDefaultPosition, wxDefaultSize,
int(list.size()), list.data());
auto sz = new wxBoxSizer(wxHORIZONTAL);
sz->Add(new wxStaticText(control_panel, wxID_ANY, label), 0,
wxALL | wxALIGN_CENTER, 5);
sz->Add(widget, 1, wxALL | wxEXPAND, 5);
console_sizer->Add(sz, 0, wxEXPAND);
return widget;
};
auto add_spinctl = [control_panel, console_sizer]
(const wxString &label, int initial, int min, int max)
{
auto widget = new wxSpinCtrl(
control_panel, wxID_ANY,
wxString::Format("%d", initial),
wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, min, max,
initial);
auto sz = new wxBoxSizer(wxHORIZONTAL);
sz->Add(new wxStaticText(control_panel, wxID_ANY, label), 0,
wxALL | wxALIGN_CENTER, 5);
sz->Add(widget, 1, wxALL | wxEXPAND, 5);
console_sizer->Add(sz, 0, wxEXPAND);
return widget;
};
m_convexity_spin = add_spinctl("Convexity", CSGSettings::DEFAULT_CONVEXITY, 0, 100);
m_alg_select = add_combobox("Algorithm", CSG_ALGS);
m_depth_select = add_combobox("Depth Complexity", CSG_DEPTH);
m_optimization_select = add_combobox("Optimization", CSG_OPT);
m_fpstext = new wxStaticText(control_panel, wxID_ANY, "");
console_sizer->Add(m_fpstext, 0, wxALL, 5);
m_record_btn = new wxToggleButton(control_panel, wxID_ANY, "Record");
console_sizer->Add(m_record_btn, 0, wxALL | wxEXPAND, 5);
controlsizer->Add(slider_sizer, 0, wxEXPAND);
controlsizer->Add(console_sizer, 1, wxEXPAND);
control_panel->SetSizer(controlsizer);
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(m_canvas.get(), 1, wxEXPAND);
sizer->Add(control_panel, 0, wxEXPAND);
SetSizer(sizer);
wxString alg;
if (!parser.Found("algorithm", &alg)) alg = "Auto";
set_renderer_algorithm(alg);
Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent &evt){
if (m_canvas) RemoveChild(m_canvas.get());
m_canvas.reset();
if (!m_mouse.is_playing()) evt.Skip();
else m_mouse.stop();
});
Bind(wxEVT_MENU, [this](wxCommandEvent &) {
wxFileDialog dlg(this, "Select project file", wxEmptyString,
wxEmptyString, "*.3mf", wxFD_OPEN|wxFD_FILE_MUST_EXIST);
if (dlg.ShowModal() == wxID_OK) load_model(dlg.GetPath().ToStdString());
}, wxID_OPEN);
Bind(wxEVT_MENU, [this](wxCommandEvent &) { Close(true); }, wxID_EXIT);
Bind(wxEVT_SHOW, [this](wxShowEvent &) {
activate_canvas_display();
});
Bind(wxEVT_SLIDER, [this, slider](wxCommandEvent &) {
m_ctl->move_clip_plane(double(slider->GetValue()));
});
m_ms_toggle->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent &){
enable_multisampling(m_ms_toggle->GetValue());
m_canvas->get_display()->repaint();
});
m_csg_toggle->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent &){
CSGSettings stt = m_ocsgdisplay->get_csgsettings();
stt.enable_csg(m_csg_toggle->GetValue());
m_ocsgdisplay->apply_csgsettings(stt);
});
m_alg_select->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) {
wxString alg = m_alg_select->GetValue();
int sel = m_alg_select->GetSelection();
m_csg_settings.set_algo(sel);
set_renderer_algorithm(alg);
});
m_depth_select->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) {
int sel = m_depth_select->GetSelection();
m_csg_settings.set_depth_algo(sel);
if (m_ocsgdisplay) m_ocsgdisplay->apply_csgsettings(m_csg_settings);
});
m_optimization_select->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) {
int sel = m_optimization_select->GetSelection();
m_csg_settings.set_optimization(sel);
if (m_ocsgdisplay) m_ocsgdisplay->apply_csgsettings(m_csg_settings);
});
m_convexity_spin->Bind(wxEVT_SPINCTRL, [this](wxSpinEvent &) {
int c = m_convexity_spin->GetValue();
if (c > 0) {
m_csg_settings.set_convexity(unsigned(c));
if (m_ocsgdisplay) m_ocsgdisplay->apply_csgsettings(m_csg_settings);
}
});
m_record_btn->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent &) {
if (!m_ui_job) {
m_stbar->set_status_text("No project loaded!");
return;
}
if (m_record_btn->GetValue()) {
if (auto c = m_canvas->get_display()->get_camera()) reset(*c);
m_ctl->on_scene_updated(*m_scene);
m_mouse.record(true);
} else {
m_mouse.record(false);
wxFileDialog dlg(this, "Select output file",
wxEmptyString, wxEmptyString, "*.events",
wxFD_SAVE|wxFD_OVERWRITE_PROMPT);
if (dlg.ShowModal() == wxID_OK) {
std::fstream stream(dlg.GetPath().ToStdString(),
std::fstream::out);
if (stream.good()) {
stream << m_ui_job->get_project_fname() << "\n";
wxSize winsize = GetSize();
stream << winsize.x << " " << winsize.y << "\n";
m_mouse.save(stream);
}
}
}
});
}
void MyFrame::bind_canvas_events(MouseInput &ms)
{
m_canvas->Bind(wxEVT_MOUSEWHEEL, [&ms](wxMouseEvent &evt) {
ms.scroll(evt.GetWheelRotation(), evt.GetWheelDelta(),
evt.GetWheelAxis() == wxMOUSE_WHEEL_VERTICAL ?
Slic3r::GL::MouseInput::waVertical :
Slic3r::GL::MouseInput::waHorizontal);
});
m_canvas->Bind(wxEVT_MOTION, [&ms](wxMouseEvent &evt) {
ms.move_to(evt.GetPosition().x, evt.GetPosition().y);
});
m_canvas->Bind(wxEVT_RIGHT_DOWN, [&ms](wxMouseEvent & /*evt*/) {
ms.right_click_down();
});
m_canvas->Bind(wxEVT_RIGHT_UP, [&ms](wxMouseEvent & /*evt*/) {
ms.right_click_up();
});
m_canvas->Bind(wxEVT_LEFT_DOWN, [&ms](wxMouseEvent & /*evt*/) {
ms.left_click_down();
});
m_canvas->Bind(wxEVT_LEFT_UP, [&ms](wxMouseEvent & /*evt*/) {
ms.left_click_up();
});
ms.add_listener(m_ctl);
}
void MyFrame::SLAJob::process()
{
using SlStatus = Slic3r::PrintBase::SlicingStatus;
Slic3r::DynamicPrintConfig cfg;
auto model = Slic3r::Model::read_from_file(m_fname, &cfg);
m_print = std::make_unique<Slic3r::SLAPrint>();
m_print->apply(model, cfg);
Slic3r::PrintBase::TaskParams params;
params.to_object_step = Slic3r::slaposHollowing;
m_print->set_task(params);
m_print->set_status_callback([this](const SlStatus &status) {
update_status(status.percent, status.text);
});
try {
m_print->process();
} catch(std::exception &e) {
update_status(0, wxString("Exception during processing: ") + e.what());
}
}
//int main() {}
-4
View File
@@ -23,15 +23,11 @@ RUN apt-get update && apt-get install -y \
libcairo2-dev \ libcairo2-dev \
libcurl4-openssl-dev \ libcurl4-openssl-dev \
libdbus-1-dev \ libdbus-1-dev \
libglew-dev \
libglu1-mesa-dev \
libglu1-mesa-dev \
libgstreamer1.0-dev \ libgstreamer1.0-dev \
libgstreamerd-3-dev \ libgstreamerd-3-dev \
libgstreamer-plugins-base1.0-dev \ libgstreamer-plugins-base1.0-dev \
libgstreamer-plugins-good1.0-dev \ libgstreamer-plugins-good1.0-dev \
libgtk-3-dev \ libgtk-3-dev \
libgtk-3-dev \
libsecret-1-dev \ libsecret-1-dev \
libsoup2.4-dev \ libsoup2.4-dev \
libssl3 \ libssl3 \
-2
View File
@@ -31,8 +31,6 @@ RUN apt-get update && apt-get install -y \
libcairo2-dev \ libcairo2-dev \
libcurl4-openssl-dev \ libcurl4-openssl-dev \
libdbus-1-dev \ libdbus-1-dev \
libglew-dev \
libglu1-mesa-dev \
libgstreamer1.0-dev \ libgstreamer1.0-dev \
libgstreamerd-3-dev \ libgstreamerd-3-dev \
libgstreamer-plugins-base1.0-dev \ libgstreamer-plugins-base1.0-dev \
@@ -55,21 +55,6 @@ modules:
url: https://gstreamer.freedesktop.org/src/gst-plugins-good/gst-plugins-good-1.22.8.tar.xz url: https://gstreamer.freedesktop.org/src/gst-plugins-good/gst-plugins-good-1.22.8.tar.xz
sha256: e305b9f07f52743ca481da0a4e0c76c35efd60adaf1b0694eb3bb021e2137e39 sha256: e305b9f07f52743ca481da0a4e0c76c35efd60adaf1b0694eb3bb021e2137e39
- name: glu
build-options:
cxxflags: -Wno-register
config-opts:
- --disable-static
sources:
- type: archive
url: https://ftp.osuosl.org/pub/blfs/conglomeration/glu/glu-9.0.2.tar.xz
sha256: 6e7280ff585c6a1d9dfcdf2fca489251634b3377bfc33c29e4002466a38d02d4
cleanup:
- /include
- /lib/*.a
- /lib/*.la
- /lib/pkgconfig
- name: kde-extra-cmake-modules - name: kde-extra-cmake-modules
buildsystem: cmake-ninja buildsystem: cmake-ninja
sources: sources:
@@ -217,12 +202,6 @@ modules:
sha256: a133ddc3d3c66143eba9035621db8e0bcf34dba1ee9514a9e23e96afd39fd57a sha256: a133ddc3d3c66143eba9035621db8e0bcf34dba1ee9514a9e23e96afd39fd57a
dest: external-packages/GLFW dest: external-packages/GLFW
# OpenCSG 1.4.2
- type: file
url: https://github.com/floriankirsch/OpenCSG/archive/refs/tags/opencsg-1-4-2-release.zip
sha256: 51afe0db79af8386e2027d56d685177135581e0ee82ade9d7f2caff8deab5ec5
dest: external-packages/OpenCSG
# SolveSpace libslvs (2D sketch constraint solver, Design tab) # SolveSpace libslvs (2D sketch constraint solver, Design tab)
- type: file - type: file
url: https://github.com/JacobStoren/SolveSpaceLib/archive/4d8704523e4bf212fadf5189f92484244f670fea.zip url: https://github.com/JacobStoren/SolveSpaceLib/archive/4d8704523e4bf212fadf5189f92484244f670fea.zip

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