Merge branch 'main' into cad-mainline

This commit is contained in:
SoftFever
2026-09-18 14:01:23 +08:00
committed by GitHub
2596 changed files with 133083 additions and 118866 deletions
+84 -105
View File
@@ -9,7 +9,7 @@ OrcaFilamentLibrary (OFL), Qidi, or Snapmaker bundle. The granularity is the nam
not the brand behind it: `AAA PLA Lite` and `AAA PLA Pro` are two filaments with two ids, not
variants of one.
**How it is generated:** an id is computed, never invented. `scripts/orca_id_tool.py`
**How it is generated:** an id is computed, never invented. `scripts/orca_profile_tool.py`
mints it as a deterministic hash of the product's identity — the triple
`(filament_vendor, filament_type, filament name)`, where the filament name is the preset name
with its `@...` variant suffix stripped — producing an 8-character `OF*` code that is the
@@ -34,23 +34,19 @@ This page is the rule for authoring `filament_id` in system profiles
> [!IMPORTANT]
> **Never write a `filament_id` value by hand.** A new filament gets its id from
> `python scripts/orca_id_tool.py --generate`; one already in the tree has one — inherit it.
> `python scripts/orca_profile_tool.py generate-id`; one already in the tree has one — inherit it.
## The design, in two pieces
## The design
Because several consumers match **globally by id alone, first hit wins** (see the next
section), any two materials sharing one id feed wrong data somewhere — a wrong tray name, a
wrong support-material flag, a wrong nozzle grouping — and inside one printer a duplicated id
makes AMS spool matching a coin toss. Hand-written ids produce such collisions constantly, so
the system is built to make them impossible:
1. **Deterministic minting.** An id is a pure hash of the product's identity — no registry to
maintain, no next-free-number ceremony, no way for two concurrent PRs to race for the same
number, and no way to get it wrong by hand, because you never write it by hand.
2. **A sanctioned snapshot.** The complete id landscape derived from the tree must equal
`scripts/filament_id_snapshot.json` exactly, so every change to ids, claims (which bundles
ship which id, and for which filament), or product identity surfaces as a reviewable diff to
one file — the maintainer gate.
the system is built to make them impossible: an id is a pure hash of the product's identity —
no registry to maintain, no next-free-number ceremony, no way for two concurrent PRs to race
for the same number, and no way to get it wrong by hand, because you never write it by hand.
CI holds every id in the tree to that rule, so the profiles themselves are the whole record of
which products exist and which bundles ship them.
## Who consumes the id
@@ -160,8 +156,8 @@ key needed). Tuning a generic material → **join the OrcaFilamentLibrary filame
different product by rule 5, so it then needs its own id.
5. **Ids follow the product identity.** The id is a pure function of the product triple
`(filament_vendor, filament_type, filament name)`, so correcting any of them re-mints the id
**by design**, applied by `--generate` (preview with `--dry-run`, confine with `--vendor`) and
gated by the `--update-snapshot` diff; the exact sequence is in the FAQ. Nothing forwards
**by design**, applied by `generate-id` (preview with `--dry-run`, confine with `--vendor`);
the exact sequence is in the FAQ. Nothing forwards
the old value, so anything outside the tree that stored it — a device tray, a calibration
record, a saved project — falls back to matching by filament type until the user re-selects
the filament. Re-mint deliberately, and only to fix a genuinely wrong identity.
@@ -170,7 +166,7 @@ key needed). Tuning a generic material → **join the OrcaFilamentLibrary filame
## Minting — nobody invents ids
New ids are deterministic, computed exactly like the `setting_id` precedent
(the `setting_id` half of `scripts/orca_id_tool.py`):
(the `setting_id` half of `scripts/orca_profile_tool.py generate-id`):
```text
FILAMENT_ID_NAMESPACE = uuid5(setting-id NAMESPACE, "filament_id")
@@ -196,13 +192,13 @@ Snapmaker bundles alike; the OFL generic `Generic/PLA/Generic PLA` mints `OFDSrz
by 35 bundles — most by independent declarations converging on the same mint, the rest
purely through inheritance from the OFL preset.
Nothing but the triple feeds the mint — not the rest of the tree, not the snapshot, not what
another preset of the product happens to carry. Determined triple, determined id: one product
Nothing but the triple feeds the mint — not the rest of the tree, not what another preset of
the product happens to carry. Determined triple, determined id: one product
carries one id and there is no second acceptable value for it, so any other value on a preset
is a mismatch `--check` reports and `--generate` pulls back. Two *different* products whose
is a mismatch `check` reports and `generate-id` pulls back. Two *different* products whose
triples mint the same base62 value would be a collision (a roughly 36-bit id space against a
few thousand products); nothing salts past it: `--check` reports it naming both products,
`--generate` refuses to write it, and the remedy is a rename so their triples differ. Where
few thousand products); nothing salts past it: `check` reports it naming both products,
`generate-id` refuses to write it, and the remedy is a rename so their triples differ. Where
two presets of one product would be AMS-ambiguous on a printer, the fix is likewise in the
profiles — make their `compatible_printers` disjoint (structure rule 3), retire the redundant
preset, or, if they really are different products, give them different names so their triples
@@ -212,80 +208,76 @@ Workflow for a new filament:
```bash
# 1. Author the filament with NO filament_id key anywhere.
python scripts/orca_id_tool.py --dry-run # 2. preview the ids — writes nothing
python scripts/orca_id_tool.py --generate # 3. apply them to the profile file(s)
python scripts/orca_id_tool.py --update-snapshot # 4. record the new claims in the snapshot
python scripts/orca_id_tool.py --check # 5. validate the filament_id state
python scripts/orca_extra_profile_check.py # 6. ...and everything else CI checks
# 7. Commit the profile edits together with scripts/filament_id_snapshot.json, for review.
python scripts/orca_profile_tool.py generate-id --dry-run # 2. preview the ids — writes nothing
python scripts/orca_profile_tool.py generate-id # 3. apply them to the profile file(s)
python scripts/orca_profile_tool.py check # 4. validate — everything CI checks
```
`--generate` makes every filament's id equal the mint of its own
`generate-id` makes every filament's id equal the mint of its own
`(filament_vendor, filament_type, filament name)` triple: it inserts one where an instantiated
filament resolves none, and re-derives one that does not match. A preset that *inherits* a
mismatching id is the one case left to the author — check 3b names it, and the fix is to inherit
mismatching id is the one case left to the author — check 2b names it, and the fix is to inherit
a preset of the same filament or to give the preset its own key. A declaration is left alone
exactly when it already equals the one id its triple mints, and a collision (check 3d) is
exactly when it already equals the one id its triple mints, and a collision (check 2d) is
reported and left unwritten. The same run assigns
`generate_preset_setting_id(vendor, type, name)` to every instantiated filament, process
and machine preset of every vendor except BBL, which keeps its authoritative `G*` ids, strips
`setting_id` from base profiles, and fixes the misspelled `settings_id` key — dropped, or, for
BBL, whose ids have no formula to fall back on, restored under the correct name. It is idempotent and
byte-preserving (indentation, BOM, and line endings intact, every edited file re-parsed to fail
loudly), and a no-op on a tree that already passes `scripts/orca_extra_profile_check.py` — the
check CI runs over both id kinds, of which `--check` is the `filament_id` half.
loudly), and a no-op on a tree that already passes `check`.
- `--filament-id` limits the run to `filament_id`.
- `--setting-id` limits the run to `setting_id`. The two exclude each other; pass neither to
write both.
- `--vendor VENDOR` confines the run to that bundle; repeatable. The id is a function of the
triple alone, so a narrowed run writes exactly what a full one would; `--check` reports
triple alone, so a narrowed run writes exactly what a full one would; `check` reports
whatever it left outside.
- `--dry-run` reports what `--generate` would do and writes nothing; with no mode of its own it
implies `--generate`, so `--dry-run --vendor <Vendor>` previews just that bundle.
- `--dry-run` reports what the run would do and writes nothing, so
`generate-id --dry-run --vendor <Vendor>` previews just that bundle.
- `--profiles DIR` points the tooling at a different profile tree (default
`resources/profiles`). `--check` and `--update-snapshot` read and write the sanctioned state of
the tree they are given, so pointing them elsewhere needs `--snapshot PATH` for that tree too —
`scripts/filament_id_snapshot.json` describes `resources/profiles` and no other tree.
`resources/profiles`).
**Identity fixes need no separate mode.** `--generate` re-derives an id that no longer matches its
triple exactly the way it fills in a missing one, so a rename or a `filament_vendor` /
`filament_type` correction is just: fix the config, run `--generate` (confine it with `--vendor`,
preview it with `--dry-run`), then `--update-snapshot` and review the diff.
The tool's other commands maintain the tree around the ids: `fix` normalises profile files,
`trim` drops files no `<vendor>.json` list references, and `update-index` rebuilds those lists.
They do not touch ids; `--help` documents them.
**Identity fixes need no separate command.** `generate-id` re-derives an id that no longer matches
its triple exactly the way it fills in a missing one, so a rename or a `filament_vendor` /
`filament_type` correction is just: fix the config and run `generate-id` (confine it with
`--vendor`, preview it with `--dry-run`).
If you skip the tooling, CI fails and prints the remedy: the expected id for your filament and
the instruction to run `python scripts/orca_id_tool.py --generate`; once the id is minted, the
snapshot checks likewise point at `--update-snapshot` and tell you to commit the resulting
diff.
the instruction to run `python scripts/orca_profile_tool.py generate-id`.
## Reserved namespaces — never mint or hand-write into
## Ids other systems compose
A **reserved namespace** is an id space no system profile may declare, because an external
catalog or a device protocol owns the values. None of them has an owning vendor: there is no
bundle — not even the one whose printers use the catalog — that may write one into a profile.
Every filament profile carries a minted id, with no exceptions and no spellings held back for
anyone. There is therefore no reserved namespace to respect and no bundle that owns one: an id
some other system composes for its own purposes is simply not the mint of a triple, so it
cannot be a system profile's `filament_id`, and the format check rejects it for that reason
alone — same error, same remedy, whoever wrote it.
| Space | Status | Rule |
| --- | --- | --- |
| `GF*` | Bambu AMS/RFID catalog | declarable by **nobody**, BBL included: Bambu's own ids live in the generated catalog map, never in a profile |
| `QD_*` | Qidi device protocol | declarable by **nobody**, Qidi included: the box composes these ids at runtime and they are not preset ids |
| `P` + 7 hex chars (case-insensitive), `"null"` | user-created custom filaments (`CreatePresetsDialog.cpp`) | never appears in system profiles |
Three such spaces exist around us, and are worth recognising so nobody mistakes one for an id
to copy into a profile:
The two device namespaces, in detail:
- **Bambu (`GF*`).** Bambu's device/RFID/cloud catalog is external and opaque, which is a
reason to keep it out of the profiles rather than to let one bundle own it. Every BBL filament
mints an `OF` id from its triple like every other vendor's, and the correspondence to Bambu's
catalog ids lives in one generated file the app applies at the printer boundary — the next
section. Nothing under `resources/profiles/**` carries a `GF*` id today and nothing can be
exempted, so a `GF*` id appearing anywhere in the tree is a mistake, whoever wrote it.
- **Qidi (`QD_*`).** `QD_*` is a device-*protocol* namespace, not a preset id space: the
Qidi box path composes `QD_<series>_<vendor>_<typeidx>` ids at runtime (slot vendor and
type indices reported by the device, the series digit inferred client-side from the printer
model/name). Qidi presets carry ordinary minted `OF*` ids (generics share the OFL ids), so
a composed id matches no preset and the slot falls back to filament type; translating it to
the filament's id belongs in `QidiPrinterAgent`. The alternative — treating per-series
protocol ids as preset ids — would put one product under five ids (`QIDI PLA Rapido` would
be `QD_0_1_1` through `QD_4_1_1`), exactly the fragmentation the mint rule removes.
- **Bambu's `GF*` catalog.** Bambu's device / RFID / cloud catalog is external and opaque. Every
BBL filament mints an `OF` id from its triple like every other vendor's, and the
correspondence to Bambu's catalog ids lives in one generated file the app applies at the
printer boundary — the next section. Note that `GF` is a *prefix*, not a namespace the tree
avoids: BBL's authoritative `setting_id` values include `GF`-prefixed ones, and
`resources/profiles/blacklist.json` and `BBL/filament/filaments_color_codes.json` both
reference Bambu catalog ids by design. The rule is about `filament_id` and nothing else.
- **Qidi's `QD_*` protocol ids.** The Qidi box composes `QD_<series>_<vendor>_<typeidx>` at
runtime (slot vendor and type indices reported by the device, the series digit inferred
client-side from the printer model/name). Qidi presets carry ordinary minted `OF*` ids
(generics share the OFL ids), so a composed id matches no preset and the slot falls back to
filament type; translating it to the filament's id belongs in `QidiPrinterAgent`. Treating
per-series protocol ids as preset ids would put one product under five ids
(`QIDI PLA Rapido` would be `QD_0_1_1` through `QD_4_1_1`) — exactly the fragmentation the
mint rule removes.
- **`P` + 7 hex chars, and `"null"`.** What `CreatePresetsDialog.cpp` gives a filament a *user*
creates. Those are user presets, not system profiles, and the two never meet in the tree.
## The Bambu catalog map
@@ -337,7 +329,7 @@ OrcaFilamentLibrary. **135 is the number to expect at every regeneration** — 1
one-off size of the transition and stopped being computable from the tree once the BBL bundle
was re-minted, so do not "fix" the report to print it.
**Check 6** lives in `check_filament_ids`, so profile CI runs it alongside the other five. It
**Check 4** lives in `check_filament_ids`, so profile CI runs it alongside the other three. It
holds the file to its contract: it parses, carries `source` / `bambustudio_commit` /
`generated`, keys only `OF`-format ids, maps each Bambu id at most once, and — for every row
whose key the tree actually claims — agrees with the tree on that id's `(vendor, type, name)`
@@ -426,23 +418,14 @@ map would silently reproduce the bug.
## How CI enforces this
Profile CI (`check_profiles.yml`) runs `check_filament_ids()` tree-wide via
`scripts/orca_extra_profile_check.py`. Its ground truth is
**`scripts/filament_id_snapshot.json` — the sanctioned state**: the id state derived from the
tree must equal the snapshot exactly, in both directions. Any change to the id landscape
therefore surfaces as a diff to that file, and **that snapshot diff is what maintainers review
and gate in a PR**. Never edit the snapshot by hand — `--update-snapshot` regenerates it
deterministically (running it twice changes nothing). The snapshot holds one map, `ids`: each
entry is the product the id is minted from (`filament_vendor`, `filament_type`, `name`) and the
`filaments` claiming it (`Vendor/Filament`), and it sanctions *state*, never exceptions: no check
consults it to excuse a preset from a rule, and there is no grandfather list of any kind.
`scripts/orca_profile_tool.py check`. Every check judges the tree against the rules on this
page and nothing else — there is no recorded id state to match and no grandfather list of any
kind.
The checks, in brief:
- **Format** — every id occurring in the tree is `OF` + 6 base62 chars. No exceptions: not a
snapshot entry, not BBL.
- **Snapshot equality** — tree claims == snapshot claims **and** each id's declared triple ==
its snapshot entry, both directions: any `filament_vendor`/`filament_type`/name change
surfaces as a snapshot diff.
- **Format** — every id occurring in the tree is `OF` + 6 base62 chars. No exceptions, not
even BBL.
- **Identity** — the id is a function of the triple alone. A declared `OF*` id must equal the
one id its declarer's own triple mints, with no second acceptable value; the id an
instantiated preset *inherits* must equal the mint of *its* own triple, however it inherits
@@ -450,8 +433,6 @@ The checks, in brief:
system filament must resolve an effective id at all (recall: an id-less one is a hard load
error in C++ that discards the whole vendor bundle); and no two products mint one id (a
base62 collision, resolved by renaming one of them). The errors print the expected id.
- **Reserved namespaces** — `GF*`, `QD_*`, `P<7-hex>` or `"null"` claimed by any vendor,
BBL and Qidi included.
- **Triple integrity** — every declarer must resolve a non-empty `filament_vendor` and
`filament_type` (generics use `"Generic"`), and all declarers of one filament within a
bundle must agree on the triple.
@@ -461,16 +442,14 @@ The checks, in brief:
tree claims. See [The Bambu catalog map](#the-bambu-catalog-map); the remedy is always to
regenerate, never to hand-edit.
A profile that declares a **reserved-namespace** id — `GF*`, `QD_*` or `P<7-hex>`, whatever
its vendor — cannot pass the format check, so `--update-snapshot` refuses to sanction it
rather than hide the mistake until CI. For a Bambu-cataloged product, the catalog map is where
the correspondence belongs. Any other new sharing via a *declared* id is caught by the identity
check; sharing through inheritance carries no declaration to check and surfaces only as a new
claim in the snapshot diff — which is exactly why that diff is the gate.
A profile that declares an id no triple mints — a Bambu catalog id, a composed Qidi one, a
hand-typed value, whatever its vendor — fails the format check. For a Bambu-cataloged product
the catalog map is where the correspondence belongs. Two products sharing one id are caught by
the identity check whether the id is declared or inherited.
`orca_extra_profile_check.py` separately holds every declared id to the AMS 8-character limit,
tree-wide and for every vendor alike, scoped to the presets a vendor's index actually
references (a file the index never loads cannot break AMS matching).
The same `check` run holds every declared id to the AMS 8-character limit, tree-wide and for
every vendor alike, scoped to the presets a vendor's index actually references (a file the index
never loads cannot break AMS matching).
Complementing the Python checks, CI also runs the C++ profile validator with `-f`
(`check_filament_subtypes`): it loads the bundle exactly as the app does and flags any printer
@@ -488,21 +467,21 @@ ambiguity check behind structure rule 3.
`Generic PLA` base name, set `compatible_printers`; no id key needed.
- **A branded filament that borrows a generic's settings?** Fine — inherit `Generic X @System`
(or any real filament) for the settings and declare the id of your own filament; run
`python scripts/orca_id_tool.py --generate` to mint it. Inheritance never changes the id.
`python scripts/orca_profile_tool.py generate-id` to mint it. Inheritance never changes the id.
- **I need to fix a filament's `filament_vendor` or `filament_type`.** Fix the config, run
`--generate --vendor <Vendor>` (preview with `--dry-run`), then `--update-snapshot`, and commit
the profile and snapshot diffs together. The id re-derives from the corrected identity, and
`generate-id --vendor <Vendor>` (preview with `--dry-run`), and commit the result. The id
re-derives from the corrected identity, and
nothing forwards the old value, so a tray or record still holding it falls back to matching by
filament type.
- **I need to rename a filament.** Rename the presets (adding `renamed_from`, which keeps the
preset *name* resolving), then `--generate --vendor <Vendor>` (preview with `--dry-run`), then
`--update-snapshot`. The id follows the new filament name; as with any identity fix, the old id
preset *name* resolving), then `generate-id --vendor <Vendor>` (preview with `--dry-run`). The
id follows the new filament name; as with any identity fix, the old id
is not forwarded.
- **Can I reuse a `QD_*` id for a Qidi profile?** No — nobody can. It is the device protocol's
own id space: the box composes those values at runtime and no preset carries one. Author
Qidi filaments like any other vendor's.
- **CI says my filament needs an id.** Run `python scripts/orca_id_tool.py --generate`, then
`--update-snapshot`, and commit both diffs. Do not type an id by hand.
- **Can I reuse a `QD_*` id for a Qidi profile?** No — it is not a mint, so it is not a
`filament_id`. Those values are composed by the box at runtime, and no preset carries one.
Author Qidi filaments like any other vendor's.
- **CI says my filament needs an id.** Run `python scripts/orca_profile_tool.py generate-id` and
commit the result. Do not type an id by hand.
For general profile authoring, see the profile development guide on the
[OrcaSlicer wiki](https://www.orcaslicer.com/wiki).
+170
View File
@@ -0,0 +1,170 @@
# Wipe inward — High Level Design
## Purpose and scope
Wipe inward reduces reheating of fresh plastic and visible seam artifacts by
moving the hot nozzle toward adjacent printed material during the external-wall
wipe. Wipe marks are especially visible at layer heights below 0.1 mm.
The option applies only to wipes after external walls, including walls around
holes. It does not offset wipes after inner walls, infill or supports. For an
outer contour the move is inward; for a hole it is away from the hole, toward
the surrounding material. The path must remain supported by material that is
already present when the wipe executes.
The operation belongs to G-code generation. It uses extrusion paths, their actual
widths and their print order. Changing its settings invalidates G-code export
while preserving the sliced geometry.
## Settings and eligibility
`wipe_inward` defaults to disabled and requires Wipe while retracting to be
enabled for the active filament. `wipe_inward_distance` defaults to 50% of the
actual external-wall extrusion width; it also accepts an absolute distance in
millimeters. Using the path width makes Auto width and Arachne's variable widths
meaningful. The effective offset is limited by that width and the spacing to the
adjacent wall. A zero distance disables the offset.
Only external perimeters with a suitable, previously printed inner perimeter
are eligible. A configured wall count alone cannot establish eligibility:
the local geometry may contain fewer walls, and walls scheduled later do not
provide support. Outer/Inner wall order therefore normally retains the regular
wipe path.
Retraction and pressure advance calibrations disable inward wiping so it cannot
mask the behavior being measured. The calibration settings turn it off, and
G-code generation enforces this even if a profile or object override enables it.
## Path selection and support
The planner identifies an adjacent inner perimeter on the material side of the
outgoing wall. Contour winding and the distinction between outer contours and
holes establish a preferred direction; local printed geometry resolves ambiguous
or self-touching contours.
Candidate paths offset or translate the portion needed for the configured wipe
distance. A wide seam gap can prevent a supported forward path; following the
incoming printed wall backwards is also a candidate. If translating that wall
cannot provide a complete wipe around a curve, the planner tries an offset of
the reversed wall. Direction checks allow coordinate-rounding error at a
perpendicular entry, while rejecting actual backtracking. The planner checks the
complete executable path, including its connector from the nozzle position,
against the current and earlier printed perimeters. Nearby endpoints alone do
not establish support across a gap.
Each region accumulates its printed perimeter prefix once, in extrusion order.
Every entity contributes its geometry only after it is printed, and the prefix
is discarded when the region ends. This collection is skipped when inward wiping
is disabled or its configured distance is zero. A mixed inner-wall loop remains
an eligible target even when its first path is an overhang: ordinary inner-wall
paths elsewhere in the loop identify it. Likewise, an external loop with an
overhanging start remains eligible when other segments identify the external
wall. It is available for support checks but is not an inner-wall target.
Candidate-specific support filtering and AABB trees are built only for eligible
external loops, then reused across their candidate paths.
Material-side validation applies with or without a seam gap. Along each
candidate, local wall normals point toward the adjacent printed inner wall;
samples on the opposite side are rejected even when they remain close enough
to the external wall to pass the support check. This uses the open wall geometry
without treating it as a closed polygon. Full paths at a zero-gap seam also
retain clearance from the external wall after their initial connector. At a
clipped corner, another branch can be closer than the requested offset, so
material-side and support checks apply without that additional clearance rule.
An accepted candidate replaces the stored wipe path as a whole. A short direct
inward move is also eligible when longer candidates fail validation. It may
waive full wall clearance, but must pass the material-side check. Its initial
direction is checked from the actual nozzle position after any loop pre-move;
the original wall endpoint is retained separately for intersection checks. It takes
priority over the alternate offset when the preferred and translated paths
are unusable. A longer reversed path may replace the selected candidate only
when its distance to the target inner wall is no worse within tolerance.
## Fallback to the regular wipe
The original wipe path is retained when:
- No suitable adjacent inner wall has already been printed near the seam. This
includes single-wall areas, locally missing inner walls and normally Outer/Inner
wall order. A distant wall or a wall on the air side does not qualify.
- The requested or available offset, or the configured wipe distance, is zero
or too small at the geometry's coordinate precision.
- Degenerate geometry prevents construction of a usable candidate, or all
candidates fail the checks for printed support, direction, wall clearance or
the connector from the actual nozzle position. This can occur at tight corners,
narrow features or seam gaps.
Corners and seam gaps do not automatically trigger fallback: an offset,
translated, reversed or short direct inward path may still be valid. The regular
wipe is retained only when no candidate is accepted.
Fallback uses the path and retraction rules for `wipe_inward` disabled.
Wipe while retracting must still be enabled for a wipe to occur; `wipe_on_loops`
remains controlled by its own setting.
## Interaction with Wipe on loop
`wipe_on_loops` is an independent option that makes a short move before leaving
an external loop. It can operate with `wipe_inward` disabled. When both options
are enabled, its destination is the starting position for the inward wipe.
The loop move samples the outgoing and incoming paths by distance across path
boundaries. The sampling distance is bounded by the nozzle diameter and one
quarter of the total path length. It samples the outgoing path at up to 20% of
the nozzle diameter and rotates that point around the seam through one third
of the material-side corner angle. For a closed square outer contour, this
produces a move of 20% of the nozzle diameter at 30 degrees into the corner.
Coincident samples or degenerate angles suppress the move.
The nozzle position stored by G-code generation must match the emitted loop
move. Both travel planning and wipe execution depend on this position, including
when Wipe inward is disabled.
With a seam gap, a loop move may advance past the inward offset's original entry.
If that alone makes the connector backtrack, the entry advances to the nozzle's
projection on the offset. The planner extends the source as needed to preserve
the configured wipe length and validates the new connector and complete path.
Joins that already backtrack across the seam gap are not adjusted this way.
## Execution and retraction
The stored wipe path uses a sentinel first point. Execution starts from the
actual nozzle position and proceeds to the second stored point. Path selection,
support validation and wipe-length calculation must all use this same executable
geometry, especially after a Wipe on loop move.
An accepted inward path executes at the end of the external loop, after any
Wipe on loop move, without retracting filament. It consumes the stored path and
updates the nozzle position before travel planning. A short travel to the next
wall cannot discard this wipe or force a retraction or Z-hop. Subsequent travel
uses the normal minimum-travel threshold and retraction/lift settings from the
new position. The regular wipe, including fallback, remains deferred until a
normal retraction uses it.
Retraction is divided into portions before, during and after wiping. The amount
that can be retracted during the wipe depends on its executable length, wipe
speed and the active filament's retraction speed. Fractional retraction speeds
are retained in this calculation. For a 2 mm wipe at 100 mm/s and a retraction
speed of 25.5 mm/s, the wipe can retract 0.51 mm. With a total retraction of 0.8 mm
and both before/after percentages set to zero, the remaining 0.29 mm is retracted
before wiping. This split applies to regular deferred wipes, including fallback;
an accepted inward wipe executes separately without retraction.
## Implementation and verification
- [GCode.cpp](../../src/libslic3r/GCode.cpp) integrates path selection, nozzle
position and retraction; [Print.cpp](../../src/libslic3r/Print.cpp) controls
invalidation, and [PrintConfig.cpp](../../src/libslic3r/PrintConfig.cpp) defines
the settings.
- [WipePathHelpers](../../src/libslic3r/GCode/WipePathHelpers.hpp) implements path
sampling, offset selection and support checks.
- [Geometry tests](../../tests/libslic3r/test_wipe_path.cpp) cover support,
degenerate paths, contour and hole orientations, and exact loop-move geometry
across path subdivisions.
- [FFF tests](../../tests/fff_print/test_wipe.cpp) cover emitted trajectories,
fallback, minimum-travel retraction and Z-hop rules, and export invalidation.
With Wipe inward disabled, they check the loop move's direction and magnitude
for Classic and Arachne, the subsequent wipe's start and length, and fractional
retraction splitting in absolute and relative E modes.
Loop-move checks use reserved role/wipe markers and extrusion state, and run
with human-readable G-code comments both enabled and disabled.