Compare commits

...
Author SHA1 Message Date
SoftFever 9f34f37c27 Merge branch 'main' into feature/slice-sweep-full-placeholder-coverage 2026-09-29 01:57:28 +08:00
SoftFever 9d8257d2eb Support extruder variants on every printer (#15964)
# Description

Extruder variants (Standard, High Flow, extra high flow) now work on any
printer. Any vendor profile can declare them, and a multi-variant
filament picks up the right variant on every printer. In the sidebar,
users can switch the printer variant and set the nozzle volume type of
each extruder on multi-extruder printers. This also fixes a later
filament printing at the first filament's temperature on a P1S or X1C
with a High Flow nozzle. The profile checks now reject variant arrays of
the wrong size and outdated variant strings. Every shipped vendor
profile passes them, and the orca-profiles skill documents the rules.

Slicing output changes only where the wrong variant was used before. 
# Screenshots/Recordings/Graphs
<img width="393" height="218" alt="Screenshot 2026-09-28 at 11 28 07 PM"
src="https://github.com/user-attachments/assets/8bee4b0e-c38c-4039-8c11-096ace6fbad0"
/>
<img width="448" height="267" alt="Screenshot 2026-09-28 at 11 28 37 PM"
src="https://github.com/user-attachments/assets/e767cd97-a5d0-4728-b010-c8ea2bc94ca7"
/>
<img width="746" height="603" alt="Screenshot 2026-09-28 at 11 28 54 PM"
src="https://github.com/user-attachments/assets/23c8af3b-c679-46e5-9fd0-2e43df0449b3"
/>




https://github.com/user-attachments/assets/10d75d72-86a3-42e8-8a95-b1627bd58e91



## 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-09-29 01:57:01 +08:00
SoftFever 185cfe4323 Slice-validate every custom G-code and filename_format in system profiles 2026-09-29 01:53:30 +08:00
SoftFever 78fb4f767a Fix the Wanhao France D12 template custom G-code failing to parse 2026-09-29 01:53:30 +08:00
SoftFever 05da6bbcc8 Remove unreachable ACE block from the Kobra X filament change G-code
The block ran only when flush_length_4 is -1392 and read ace_t_box_vector / ace_t_slot_vector, which only Anycubic's own slicer defines. It emitted comments only, so the printed G-code is unchanged.
2026-09-29 01:53:30 +08:00
Ian Chua 0a55e6ef83 fix: expose initial_no_support_extruder to the filename format template (#15950) 2026-09-29 01:52:25 +08:00
1846407e93 Add Precise Seam placement feature (#12974)
Co-authored-by: Ioannis Giannakas <59056762+igiannakas@users.noreply.github.com>
Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
Co-authored-by: Ian Bassi <ian.bassi@outlook.com>
2026-09-28 14:40:55 -03:00
SoftFever 52679ea1bc Bump the version of every vendor bundle this branch changes 2026-09-28 23:48:22 +08:00
SoftFever d48e63b5e3 Merge branch 'main' into feature/add-multi-variant 2026-09-28 23:30:14 +08:00
SoftFever 7a2daaa351 Make all vendor profiles pass the updated profile checks 2026-09-28 23:02:04 +08:00
Ian Chua 08f086daf3 fix: tolerate unsupported OFL publisher branches (pre 2.5.x) (#15962) 2026-09-28 20:40:24 +08:00
Ian Chua 293aa3e0ed fix: OFL workflow checkpoint and clear ordering (#15960)
# Description

<!--
> Please provide a summary of the changes made in this PR. Include
details such as:
  > * What issue does this PR address or fix?
  > * What new features or enhancements does this PR introduce?
> * Are there any breaking changes or dependencies that need to be
considered?
-->
This PR re-orders the OFL OTA auto-publish workflow by using successful
cron runs as checkpoints, marking and waiting for every dispatched
profile publisher to finish, and clearing the pending queue once
centrally only after all publishers succeed.

# 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-09-28 20:00:04 +08:00
Ian Chua faeb84da72 fix: change checkpointing to use last cronjob instead 2026-09-28 19:59:01 +08:00
anjisandyw4z c30c9beb09 Fix clipped tall models in G-code preview for gcode files (#15360)
Fix clipped tall models in G-code preview

Co-authored-by: yw4z <ywsyildiz@gmail.com>
2026-09-28 14:58:24 +03:00
Ian Chua 77f8c64d37 fix: OFL workflow checkpoint and clear ordering 2026-09-28 19:53:23 +08:00
SoftFever 00a2c5a087 update profile checks 2026-09-28 19:08:35 +08:00
SoftFever 71bb400e46 Add multi-variant support to the app 2026-09-28 19:08:35 +08:00
SoftFever 2ccabb5695 add multi variant to orca-profiles skill
add symbol link .agents
2026-09-28 19:08:35 +08:00
Ian Chua 576cce2f72 test: bump elegoo profile to test OFL OTA E2E [TO BE REVERTED] (#15954)
Merged by /bot merge on behalf of @peachismomo (id 52488812).
Grants: resources/profiles/OrcaFilamentLibrary/filament/Elegoo, resources/profiles/OrcaFilamentLibrary.json, resources/profiles/Elegoo, resources/profiles/Elegoo.json
Head: d4a18c633e
2026-09-28 10:47:41 +00:00
Kenneth Rapleeandyw4z f3a8f711fd Catch Standard_Failure before std::exception (OCCT >= 8) (#15826)
Co-authored-by: yw4z <ywsyildiz@gmail.com>
2026-09-28 13:44:32 +03:00
Kenneth Rapleeandyw4z 00bde9265b Add missing OCCT (8.x) header includes (#15825)
* Add missing TopTools includes to GeometryEngine.cpp

* Add missing TDF_LabelSequence include in STEP.cpp

---------

Co-authored-by: yw4z <ywsyildiz@gmail.com>
2026-09-28 13:42:25 +03:00
Ioannis Giannakas 7707487252 Fix overhang slowdown and fan applied to whole walls ahead of an overhang (#15945) 2026-09-28 10:59:39 +01:00
Hugo Costa 3df9c215f8 Blocks: branded filaments, naming/loading fixes, RF50 bed type and other fixes. (#15804)
# Description

<!--
> Please provide a summary of the changes made in this PR. Include
details such as:
  > * What issue does this PR address or fix?
  > * What new features or enhancements does this PR introduce?
> * Are there any breaking changes or dependencies that need to be
considered?
-->

This PR fixes some problems with the current profiles for Blocks
printers

- **Fix**: start gcode for the Blocks 0.6mm RF50 printer where the start
gcode command had no newline separation on two commands so they were
concatenated and would make printing fail.
- **Added**: multi material plates selection for the RF50 printers,
right now only textured PEI and smooth high temp plates are available,
but in the future more to come.
- **Naming consistancy** for Blocks Pro S100 processes and filaments,
target labels used bare `@Blocks` while RD50 and RF50 used
`@Blocks_RD50` and `@Blocks_RF50`. Renamed Pro S100 to
`@Blocks_Pro_S100` to match the structure.
- **Filament restructure**: replaced Blocks tuned-generic filaments
(e.g. `Generic PLA @Blocks`) with real Blocks-branded filaments. One for
each printer model. Every material keeps the same coverage we already
had.
- **Fix**: `sparse_infill_pattern` was misspelled `sparse_infill_patter`
on 13 `@Blocks_RF50` process presets. Harmless due to inheritances but
fixed non the less.
- **Fix**: `Blocks RD50 V2 0.4 nozzle.json` was missing
`printer_variant`.
- **Normalize** all Blocks profiles to standard formatting (tab
indentation, key order) and bumped `Blocks.json` version.
- **RF50 retraction length** increased on all RF50 machines.


## Tests

- [x] `orca_profile_tool.py check --vendor Blocks` - clean
- [x] `orca_profile_tool.py check` - clean
- [x] `normalize` / `generate-id` / `update-index --dry-run` — fully
settled, no pending changes
- [x] `check_profile.sh --vendor Blocks` → `profile_tool` check — passes
- [ ] `validate_system` / `validate_slice` /
`validate_filament_subtypes` / `validate_custom` —
could not run locally (dev host's glibc 2.36 is older than the nightly
validator binary's
      required 2.38); needs CI or a compatible host
- [x] In-app confirmation that the Blocks bundle loads
 
<!--
> 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-09-28 17:16:55 +08:00
HanifKoh cda1588578 Draw the Toolpaths Top-Down When the Camera Looks Down on the Print (#15883)
The segments come in print order, bottom layer first, which seen from above is back to
front: every hidden fragment is shaded before the one that covers it, and on an integrated
GPU that overdraw is most of the frame. Drawing the instances last to first whenever the
camera looks down lets the depth test reject the hidden fragments instead. Side views and
views from below keep the print order, and the shadow-caster pass is unchanged.
2026-09-28 15:36:08 +08:00
Ian Chua ec0d8c225f fix: slicing lifecycle event naming on cancellation (#15889)
# Description

<!--
> Please provide a summary of the changes made in this PR. Include
details such as:
  > * What issue does this PR address or fix?
  > * What new features or enhancements does this PR introduce?
> * Are there any breaking changes or dependencies that need to be
considered?
-->

Avoid calling Print::output_filename() when a slice is canceled or
fails, since unresolved filename placeholders can throw before G-code
export completes.
Instead, we should use the model name in ctx.name and the stable model
ID in ctx.id, consistently across slice and G-code export events.

# 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)
Fix #15885
2026-09-28 12:20:08 +08:00
Ian Chua 3040ebaac1 Merge branch 'main' into fix/slicing-evt-name 2026-09-28 12:19:59 +08:00
Kris Austin 5298e49dd2 fix: Klipper/Moonraker upload errors show a raw Python traceback (#14841) 2026-09-27 19:08:27 -03:00
Kenneth Rapleeandyw4z 4964f49765 Fix size_t/%d format mismatch in MsgDialog::add_button (#15852)
Fix size_t/%d mismatch in MsgDialog button keys

`m_buttons.size()` is a `size_t`, which does not match the `%d` conversion in
printf-style variadics. Build the key with `std::to_string` instead; exact
for any size_t, no behavior change for realistic counts.

Co-authored-by: yw4z <ywsyildiz@gmail.com>
2026-09-27 19:21:03 +03:00
Chris Bennight ea471dc82d Fix model initialization without a wx application (#15866)
Guard the smooth-normals preference read when CLI thumbnail generation initializes geometry without a wx application. Use the existing flat-normal path in that case and preserve GUI preferences.
2026-09-27 18:53:11 +03:00
Kiss Lorand bd65cce3f4 Unify GUI scroll rates (#15902)
Use DPI-aware 20 DIP vertical scrolling for general-purpose GUI
scroll areas.

Keep list-based views aligned to their item height so one wheel increment follows the visible row rhythm.

This makes Preferences and other dialogs scroll consistently across
Windows DPI settings.
2026-09-27 16:58:10 +03:00
Ian Chua d347a80ef8 Merge branch 'main' into fix/slicing-evt-name 2026-09-27 17:57:40 +08:00
peachismomo e340a13c18 fix: use current print for slicing lifecycle identity 2026-09-27 17:53:41 +08:00
peachismomo 9e16cdb23b fix: comment above reset_export 2026-09-27 17:29:25 +08:00
peachismomo 58842bab05 test: test for SliceStarted ensuring stable model ID is presetn when model name is absent 2026-09-27 17:29:01 +08:00
peachismomo 04204ec0d3 test: cover slicing lifecycle event context 2026-09-27 17:25:56 +08:00
Ian Chua 9ed459e132 fix: slicing event name and ID 2026-09-25 13:25:45 +08:00
2968 changed files with 32349 additions and 13752 deletions
Symlink
+1
View File
@@ -0,0 +1 @@
.claude
+146 -84
View File
@@ -1,78 +1,127 @@
---
name: orca-profiles
description: Use when creating, modifying, reviewing or debugging OrcaSlicer FFF system profiles under resources/profiles, including printer/vendor/nozzle/material additions, bundle indexes and versions, preset renames, setting_id and filament_id. Also use for missing presets or vendors, ignored profile settings, ambiguous AMS filament matches, and failures from orca_profile_tool.py, check_profile.sh/.bat, OrcaSlicer_profile_validator or the Check profiles CI job.
description: Use when creating, modifying, reviewing or debugging OrcaSlicer FFF system profiles under resources/profiles, including printer/vendor/nozzle/material additions, bundle indexes and versions, preset renames, setting_id and filament_id, and moving settings that sibling presets repeat onto shared bases after fix-variant or while drafting. Also use for missing presets or vendors, ignored profile settings, ambiguous AMS filament matches, and failures from orca_profile_tool.py, check_profile.sh/.bat, OrcaSlicer_profile_validator or the Check profiles CI job.
---
# OrcaSlicer system profiles
A bundle is `resources/profiles/<Vendor>.json` plus `<Vendor>/`. The vendor id is the
filename stem, not the index's display `name`. The index is the loader's only entry point:
unindexed presets never load. `OrcaFilamentLibrary` is the shared filament bundle;
`blacklist.json` is data, not a bundle.
This skill describes how OrcaSlicer system profiles are drafted and shaped: the rules, equations and
patterns a profile follows. Use it to draft new profiles, modify existing ones, fix profile issues and
review profile changes.
## Choose the reference for the task
A bundle is the index `resources/profiles/<Vendor>.json` plus the folder `<Vendor>/`. The vendor id is
the filename stem (`BBL`), not the index's display `name` (`Bambulab`). The index is the loader's only
entry point: an unindexed preset never loads. `OrcaFilamentLibrary` is the shared filament bundle,
loaded first; `blacklist.json` is data, not a bundle.
Read the relevant reference before editing; load others only when the task crosses those areas.
Paths below are relative to this skill. Commands run from the repository root.
## References
Read the reference for the task before editing; load others only when the task crosses into them.
Paths below are relative to this skill. Commands run from the repository root; on Windows use `py -3`
for `python3`.
| Task | Read |
| --- | --- |
| Add or tune a filament, brand or material; fix compatibility / alias shadowing | [filament-profiles.md](references/filament-profiles.md) |
| Add a printer or nozzle; change models, variants, assets or extruder vectors | [machine-profiles.md](references/machine-profiles.md) |
| Add or tune a filament, brand or material; fix compatibility, alias shadowing or overlapping coverage | [filament-profiles.md](references/filament-profiles.md) |
| Add a printer or nozzle; change models, variants, assets or per-extruder vectors | [machine-profiles.md](references/machine-profiles.md) |
| Add or tune extruder variants (`extruder_type` Direct Drive / Bowden × nozzle volume type Standard / High Flow / TPU High Flow / E3D High Flow / Extra High Flow variants) on a printer, process or filament | [extruder-variants.md](references/extruder-variants.md) |
| Add a quality tier or tune a process | [process-profiles.md](references/process-profiles.md) |
| Create a vendor bundle; diagnose loading or inheritance; migrate preset names | [vendor-bundle.md](references/vendor-bundle.md) |
| Name a preset; check what a name must equal | [naming.md](references/naming.md) |
| Draft several presets, or clean up after `fix-variant`: which base each shared value belongs on, when a new base pays off, proving nothing loads differently | [shared-bases.md](references/shared-bases.md) |
| Name a preset; check what a name must equal; base names, uniqueness, filenames | [naming.md](references/naming.md) |
| Create a vendor bundle; index, `version`, `inherits`, `include`; migrate preset names; diagnose why a bundle fails to load | [vendor-bundle.md](references/vendor-bundle.md) |
| Change ids; diagnose AMS identity | [ids.md](references/ids.md), then `docs/HLSD/filament_id.md` for identity changes |
| Review a profile diff | [review-checklist.md](references/review-checklist.md) |
| Run checks, interpret failures, test another tree or verify in the app | [validation.md](references/validation.md) |
| Review a profile diff | [review-checklist.md](references/review-checklist.md) |
## Golden rules
## Rules
1. **Bump every changed bundle's `version`**, including `OrcaFilamentLibrary.json` when affected.
Increment the last component; carry `.99` into the third component (`02.04.00.99` →
`02.04.01.00`). The updater requires a strictly newer version. CI does not check this.
2. **Register every preset, bases included, parents before children.** `update-index` generates
the four `*_list` arrays; `check` requires its output. Index names must equal file `name` fields.
3. **Generate ids; never invent or copy them.** Keep existing ids during ordinary tuning. New
presets normally omit them until `generate-id`; bases must have no `setting_id`.
BBL's authoritative `setting_id` and a wrongly inherited `filament_id` need the explicit
handling in [ids.md](references/ids.md).
4. **Load failures can discard a whole vendor bundle.** Broken `inherits`, missing indexed files,
duplicate names, invalid model/variant references and unresolved filament ids affect more than
the edited preset. Inheritance stays within a bundle, except filaments may inherit the library.
5. **Preserve shipped selectable names.** Renaming, deleting or changing `instantiation` from
`"true"` to `"false"` needs `renamed_from` on a selectable successor. It is a `;`-separated string;
update in-tree references too. See [migration rules](references/vendor-bundle.md#renamed_from).
6. **Compatibility uses exact printer variant names.** Every instantiated non-library filament
needs a non-empty `compatible_printers` in its own file. Library fallbacks may omit it;
library printer-specific tunes use a non-empty list. One variant may be claimed by only one
profile per filament product (`filament_id`); an overlap is resolved by moving the variant to the
most specific preset, which is preferred over deleting a profile. See
[one variant, one profile](references/filament-profiles.md#overlapping-coverage-one-variant-one-profile-per-product).
7. **Preset values are strings or arrays of strings.** Use `"instantiation": "false"`, not `false`.
Model `nozzle_diameter` is a `;`-separated string; machine `nozzle_diameter` is an array.
Wrong types can abort loading; see [failure scopes](references/vendor-bundle.md#failure-modes-ranked-by-blast-radius).
8. **Verify setting keys against the code.** Unknown keys are silently discarded. Check
`PrintConfig.cpp` definitions and `PrintConfigDef::handle_legacy`; neighbours can contain dead
keys. `normalize` removes known obsolete keys, but does not detect arbitrary misspellings.
9. **Run the full profile checks before reporting completion.** A vendor-scoped pass is only a
development loop. Review also covers version bumps, assets, non-default processes and hardware
tuning that CI cannot establish.
10. **One all-printer preset per product; color is a runtime property, never a preset.** Never ship
presets that differ only by color — CI accepts them, so this is a review call. See
[color is a runtime property](references/filament-profiles.md#color-is-a-runtime-property).
1. **Bump the `version` of every bundle you change**, `OrcaFilamentLibrary.json` included when affected.
Increment the last component and carry `.99` into the third (`02.04.00.99` → `02.04.01.00`). The
updater installs only a strictly newer version, and CI does not check the bump.
2. **Register every preset, bases included, parents before children.** `update-index` writes the four
`*_list` arrays from the files on disk; `check` fails unless the index equals its output. Each index
entry's `name` must equal the file's `name`.
3. **Generate ids; never invent or copy them.** Keep existing ids during ordinary tuning. New presets
normally omit them until `generate-id`; bases carry no `setting_id`. BBL's own `setting_id`s and a wrongly
inherited `filament_id` need the explicit handling in [ids.md](references/ids.md).
4. **A name is an identity; preserve shipped selectable names.** Every reference (`inherits`,
`compatible_printers`, `default_*`, `printer_model`) is the exact, case-sensitive `name`. Renaming or
deleting a shipped selectable preset, or flipping its `instantiation` from `"true"` to `"false"`,
needs `renamed_from` (a `;`-separated string) on a selectable successor
([migration rules](references/vendor-bundle.md#renamed_from)); update in-tree references too.
5. **Values are strings or arrays of strings.** `"instantiation": "false"`, never `false`. A
`machine_model`'s `nozzle_diameter` is a `;`-separated string; a `machine`'s is an array. Custom
G-code is one string. Wrong types can abort loading of the bundle or of every vendor
([failure scopes](references/vendor-bundle.md#failure-scopes)).
6. **Unknown keys are dropped silently.** Confirm every new key exists in
`src/libslic3r/PrintConfig.cpp`; a key a neighbouring file writes is no evidence it exists. `check` rejects,
and `normalize` removes, known obsolete keys, but neither detects an arbitrary misspelling. A key
missing from the definitions may be a legacy name the loader still renames
(`tool_change_gcode` → `change_filament_gcode`) or whose value it rewrites (`DirectDrive` →
`Direct Drive`); check `PrintConfigDef::handle_legacy` before removing one, and write the current name
in new edits.
7. **Write overrides only.** Inherit the bundle's bases and restate just what differs; follow the
bundle's existing layering and style, except that a new filament prefers the library's bases. A
value that every preset of a group shares goes on the group's base
([shared bases](references/shared-bases.md)).
8. **One load error can discard a whole vendor bundle**: an unresolved `inherits`, a missing indexed
file, two selectable presets with one name, an unknown `printer_model` or `printer_variant`, a
filament with no resolvable `filament_id`, `nil` in a non-nullable key. `inherits` and `include`
resolve only inside the bundle, except that filaments may inherit from OrcaFilamentLibrary.
9. **Filament compatibility names exact printer variants.** Every instantiated filament outside the
library writes a non-empty `compatible_printers` in its own file. Library fallbacks may omit it;
library printer-specific tunes use a non-empty list. One variant may be claimed by only one preset
per filament product (`filament_id`); an overlap is resolved by moving the variant to the most
specific preset, which is preferred over deleting a preset
([one variant, one profile](references/filament-profiles.md#overlapping-coverage-one-variant-one-profile-per-product)).
10. **One all-printer preset per product; colour is a runtime property, never a preset.** Never ship
presets that differ only by colour. CI accepts them, so this is a review call
([colour](references/filament-profiles.md#colour-is-a-runtime-property)).
11. **Write a variant key at full width or not at all.** A key in the four variant sets holds exactly
`N` values in the selectable preset that writes it, `N = S × k` in the
[sizing equation](references/extruder-variants.md#sizing-equation): one per variant, a (normal,
silent) pair per variant for the `machine_max_*` limits; one value is not "the same for every
variant". Profiles must be correct as written: `check` judges each selectable preset by the
equation, never by what the loader pads or cuts, and `check --strict` also holds what a preset
inherits to its own width, as BBL writes it. A base is never judged on its own: its array widths
count, under `--strict`, where they reach a preset, while the id and layout rules judge the
composed preset, inherited values included, without it. Declare the variant layout on a
multi-extruder printer whose extruders need different values
([widths](references/extruder-variants.md#widths)).
12. **Run the full checks before reporting completion.** A `--vendor` run is only a development loop.
Review also covers version bumps, assets, non-default processes and hardware tuning, which CI cannot
establish.
## Names
| Type | Shape | What the loader uses |
| --- | --- | --- |
| `machine_model` | `<Model>` (`Bambu Lab X1 Carbon`) | the exact string, named by each variant's `printer_model`; also the `<Model>_cover.png` stem |
| `machine` | `<Model> <nozzle> nozzle` | the exact string, named by `compatible_printers`; `printer_variant` holds the nozzle token (`0.4`, [rules](references/machine-profiles.md#printer_model-and-printer_variant)) |
| `process` | `<lh>mm <Quality> @<target>` | the exact string when referenced or selected; `@<target>` is a label, compatibility comes from the preset's list or condition |
| `filament` | `<Product> @<target>` | text before the first `@` is the alias (shadowing, `filament_id`); the rest is a label, with reserved targets `@base` and `@System` |
The shapes are convention; `check` enforces only uniqueness. Per-type conventions, base names and
filename rules are in [naming.md](references/naming.md).
## Creating or modifying a profile
1. **Inspect the diff and neighbouring presets.** Read their `name`, parent chain and children;
edits to a base or a leaf with descendants propagate. Match the bundle's structure and write
1. **Inspect the diff and the neighbouring presets.** Read their `name`, parent chain and children:
edits to a base, or to a leaf that others inherit, propagate. Match the bundle's structure and write
only overrides. New files use tab indentation, LF and a trailing newline; preserve unrelated
formatting in existing files. Match filename case exactly and use cross-platform names.
2. **Author explicit metadata.** Set `type` yourself, especially for `machine` vs `machine_model`.
Use `"from": "system"` and string `instantiation` on config presets. Omit ids on new presets
unless [ids.md](references/ids.md) requires special handling; retain them on existing ones.
Complete compatibility, defaults, assets and any rename migration using the task reference.
3. **Bump the version**, then run the authoring commands in order for each affected bundle:
formatting in existing files. Match filename case exactly and use
[cross-platform names](references/naming.md#filenames-and-paths).
2. **Author explicit metadata.** Set `type` yourself (`machine` vs `machine_model` especially), and use
`"from": "system"` and a string `instantiation` on config presets. Omit ids on new presets unless
[ids.md](references/ids.md) requires special handling; retain them on existing ones. Complete
compatibility, defaults, assets and any rename migration using the task reference.
3. **Put shared values on shared bases** when drafting several presets, and after `fix-variant`, which
widens an array in every preset that writes it and moves nothing. Each value goes on the base of the
level that determines it, a new base only where it pays for itself, and a restructure must leave
every selectable preset loading what it loaded: `snapshot` before the edit, `compare` after it
([shared-bases.md](references/shared-bases.md)).
4. **Bump the version**, then run the authoring commands in order for each affected bundle, reading
every diff and resolving every error before moving on:
```bash
python3 scripts/orca_profile_tool.py normalize --vendor "<Vendor>"
@@ -81,46 +130,59 @@ Paths below are relative to this skill. Commands run from the repository root.
python3 scripts/orca_profile_tool.py check
```
Writing commands support `--dry-run`. Inspect their diffs: `normalize` changes content and can
reformat entire files. Stop and resolve command errors before proceeding.
**Do not use `trim` in this workflow:** it can delete newly authored, unindexed profiles.
Do not use `normalize --force` for routine edits.
4. **Validate:**
Writing commands accept `--dry-run`. `normalize` changes content and can reformat entire files;
rerun `update-index` after any change to `inherits` or `include`, since it orders by them.
**Do not use `trim` in this workflow:** it deletes unindexed files, including one you just added. Do
not use `normalize --force` for routine edits. An error in a bundle you did not touch predates your
change: confirm it on a clean checkout and report it rather than fixing it in the same change.
5. **Validate:**
```bash
./scripts/check_profile.sh --vendor "<Vendor>" # development loop
./scripts/check_profile.sh # full tree before the PR
```
On Windows use `py -3` instead of `python3`, and `scripts\check_profile.bat -Vendor "<Vendor>"`
/ `scripts\check_profile.bat`. Logs land in a per-user cache dir (see
[validation.md](references/validation.md)).
Id checks remain tree-wide under `--vendor`; filament-only bundles skip the default slice check.
See [validation.md](references/validation.md) for flags, coverage and error remedies.
5. **Verify the changed behavior.** Slice newly added non-default processes explicitly, and
[test in the app](references/validation.md#testing-in-the-app) for selection or UI behavior.
Report checks actually run, failures/skips and any hardware tuning still unverified.
On Windows use `scripts\check_profile.bat -Vendor "<Vendor>"` / `scripts\check_profile.bat`. Logs
land in a per-user cache dir ([validation.md](references/validation.md)). Id checks stay tree-wide
under `--vendor`, and filament-only bundles skip the default slice check. Under `--vendor` read only
`profile_tool` and `validate_slice`: the other three checks fail on library presets that name other
vendors' printers ([why](references/validation.md#the-five-checks)).
6. **Verify the changed behaviour.** Slice newly added non-default processes and filaments
[explicitly](references/validation.md#checking-a-copy-of-the-tree), and
[test in the app](references/validation.md#testing-in-the-app) for selection or UI behaviour. Report
the checks actually run, their failures and skips, and any hardware tuning still unverified.
## Symptom → first reference
## Symptom → first look
| Symptom | Start here |
| --- | --- |
| A vendor disappears | Loader log / `validate_system`; [bundle failure scopes](references/vendor-bundle.md#failure-modes-ranked-by-blast-radius) |
| A setting has no effect | Key spelling/type, `handle_legacy`, or a config key placed on a `machine_model` |
| A preset exists but is not selectable | Index registration, `instantiation`, installation and compatibility |
| A filament is missing, duplicated, or matches the wrong spool | [Compatibility and alias shadowing](references/filament-profiles.md#compatible_printers); [ids](references/ids.md) |
| Presets differ only by color, or an all-printer library preset lacks `@System` | [Color is a runtime property](references/filament-profiles.md#color-is-a-runtime-property) |
| A bed temperature is ignored | [Plate-specific temperature keys](references/filament-profiles.md#bed-temperature-is-twelve-keys-not-one) |
| A change is absent from the running app | Version bump and [installed profile location](references/validation.md#testing-in-the-app) |
| A check fails | [Error → remedy](references/validation.md#error--remedy) |
| A vendor disappears | the app's log or the `validate_system` log; [failure scopes](references/vendor-bundle.md#failure-scopes) |
| A setting has no effect | key spelling or a legacy name (`PrintConfigDef::handle_legacy`), value type, or a config key placed on a `machine_model` |
| A preset exists but is not selectable | index registration, `instantiation`, whether it is installed (chosen in the setup wizard, or listed in the model's `default_materials`), compatibility |
| A filament is missing, duplicated, or matches the wrong spool | [compatibility and alias shadowing](references/filament-profiles.md#compatible_printers), [ids](references/ids.md) |
| Presets differ only by colour, or an all-printer library preset lacks `@System` | [colour is a runtime property](references/filament-profiles.md#colour-is-a-runtime-property) |
| High Flow (or a second extruder) slices with Standard (or extruder 1) values; a variant switch is missing; a variant's tuned values never arrive | [extruder variants](references/extruder-variants.md#variant-strings), [widths](references/extruder-variants.md#widths), [variant names](references/validation.md#variant-names) |
| Values land on the wrong extruder or mode after a variant was added | [inserting a variant](references/extruder-variants.md#adding-a-variant-inserts-its-values-at-its-variant-index), [padding and composition](references/extruder-variants.md#padding-truncation-and-composition) |
| A resolved value matches neither the file nor its `inherits` parent; an array is not the width the file wrote, or a child got only a base's first value | [composition order and `include`](references/vendor-bundle.md#inherits-and-include) |
| A bed temperature is ignored | [the twelve plate keys](references/filament-profiles.md#bed-temperature-is-twelve-keys-not-one) |
| A change is absent from the running app | version bump and [installed profile location](references/validation.md#testing-in-the-app) |
| A check fails | [error → remedy](references/validation.md#error--remedy) |
## Source of truth
When guidance and behavior disagree, inspect the current checkout:
`scripts/orca_profile_tool.py` for tooling and flags; `src/libslic3r/Preset*.cpp` for loading and
compatibility; `src/libslic3r/PrintConfig.cpp` for setting types and legacy handling;
`src/dev-utils/OrcaSlicer_profile_validator.cpp` and `.github/workflows/check_profiles.yml` for
validation coverage. `docs/HLSD/filament_id.md` defines filament identity. The
[profile development guide](https://github.com/OrcaSlicer/OrcaSlicer_WIKI/blob/main/developer_reference/how_to_create_profiles.md)
When this skill and the checkout disagree, the checkout wins: `scripts/orca_profile_tool.py` for the
tool and its flags; `src/libslic3r/Preset*.cpp` for loading and compatibility;
`src/libslic3r/PrintConfig.cpp` for keys, types, nullable options, the variant key sets and legacy
handling; `src/dev-utils/OrcaSlicer_profile_validator.cpp` and `.github/workflows/check_profiles.yml`
for validation coverage; `docs/HLSD/filament_id.md` for filament identity. The wiki's
[profile guide](https://github.com/OrcaSlicer/OrcaSlicer_WIKI/blob/main/developer_reference/how_to_create_profiles.md)
is a tutorial; confirm loader and CLI details against these sources.
## Editing this skill
The skill describes what a profile must be, not what the shipped tree currently is. State rules,
equations, patterns and profile shapes; never inventories of shipped defects, counts, dated
measurements or lists of which vendors do what. Test each sentence: if editing the profiles alone,
with the engine and tool unchanged, could make it false, state the rule behind it or give a generic
example instead. Example files named as models to copy, and commit hashes cited as the reason for a
rule, are fine.
@@ -0,0 +1,548 @@
# Extruder variants
Use this when a printer's hotend or extruder can be in more than one hardware configuration that
needs different settings: a Standard and a High Flow nozzle, a TPU High Flow, E3D High Flow or Extra High Flow nozzle, or
a Direct Drive and a Bowden extruder on one machine. Variants let one printer, process and filament preset carry a
separate value per configuration; the user picks the configuration in the sidebar, and slicing uses
the matching values. They are not for nozzle **diameter**: that stays one `machine` preset per
`printer_variant`.
## Variant strings
- A **variant string** is `"<extruder_type> <nozzle volume type>"`: the extruder's `extruder_type`
value, a space, and its nozzle volume type (the project's `nozzle_volume_type`, seeded by the
printer preset's `default_nozzle_volume_type`), e.g. `"Direct Drive High Flow"`.
- A **variant** is one entry of a preset's variant list, named by its variant string (plus an extruder
id for printer and process lists). A variant-aware key holds **one value per variant** (a
(normal, silent) pair for the `machine_max_*` limits), and each preset declares its variants in
that list. Slicing picks, for each extruder, the variant whose
variant string equals the current `extruder_type` + nozzle volume type.
- Matching is an **exact string compare** of the whole string (plus the extruder id for printer and
process lists) against the preset's own resolved list, in any order. With no match the **first
variant** is used, silently: variant index 0 for printer and process keys (extruder 1's first
variant, whichever extruder asks), the filament's own first variant for filament keys. Printer and
process keys are matched only on a printer with several extruders or whose
`extruder_variant_list` offers several variant strings (`support_different_extruders`); on any
other printer their variant index 0 is read whatever it names. Nothing rejects a profile for a
variant mismatch; mistakes surface only as wrong values in the G-code.
- Variant index and array length follow [Widths](#widths).
The complete enum is `s_keys_map_ExtruderType` and `s_keys_map_NozzleVolumeType` in
`src/libslic3r/PrintConfig.cpp` (`grep -A5 s_keys_map_NozzleVolumeType` there to confirm):
| Part | Values |
| --- | --- |
| Extruder type | `Direct Drive`, `Bowden` |
| Nozzle volume type | `Standard`, `High Flow`, `TPU High Flow`, `E3D High Flow`, `Extra High Flow` (`Hybrid` exists but is runtime-only) |
So the ten legal variant strings are the two extruder types × the five writable nozzle volume types,
and this table is the whole test of legality: a string's presence in a shipped profile is no evidence
for it. `Hybrid` (an extruder with several sub-nozzles) is never a variant: a filament on it reads the
variant of its own nozzle volume type from the project's `filament_volume_map`, printer and process
keys get one variant per nozzle volume type the extruder holds (`extruder_nozzle_stats`), and any
other lookup reads `Standard`. Never write it in a variant string. The same per-filament and
per-type reading applies on any extruder once `extruder_nozzle_stats` lists more nozzle volume
types than there are extruders.
Every other string (a nozzle volume type name from another slicer, a typo, a variant copied from a
shipped profile) is a **dead variant**: nothing selects it, and since lookup is by string it does not
shift the variants beside it; it still counts toward the variant length when arrays are sized.
`orca_profile_tool.py check` reports it as an error in every bundle, BBL included
([variant names](validation.md#variant-names)). Legacy names are errors too: in the four variant
lists, `default_nozzle_volume_type` and `nozzle_volume_type`, the loader still rewrites `Normal` →
`Standard` and `Big Traffic` → `High Flow`, so a ported `Direct Drive Normal` variant would load, but
`check` rejects the spelling and names the enum name to write. `DirectDrive` is rewritten only in
`extruder_type`, so `DirectDrive Standard` in a variant list is dead. Write the enum names;
`normalize` does not convert them.
### A nozzle the enum does not name
The nozzle volume types are fixed by the engine, and a profile cannot add one. A nozzle the table does
not name needs the type added in code first, which is outside profile work; until then any string
for it is a dead variant. Once the engine has the type, its variant string is the enum name after
the extruder type, e.g. `"Direct Drive <name>"`, and `check` accepts it with no tool change, since it
reads the enum from `PrintConfig.cpp`. Existing arrays are unaffected
([slice time](#slice-time-and-existing-users)).
## When to use variants
| Hardware | Do |
| --- | --- |
| One extruder, one nozzle type | Nothing. Without a variant list the printer has one variant ([variant length](#widths)) and no printer-key lookup takes place; a filament with several variants still gets the one for the printer's variant string. |
| Bowden-only printer | Nothing either. A list-less printer matches no string, so every printer-key lookup reads variant index 0 whether that variant is called `"Bowden Standard"` or `"Direct Drive Standard"`; naming it is cosmetic while it is the printer's only variant. A multi-extruder Bowden printer that declares the layout writes `"Bowden Standard"` variants: `"Direct Drive Standard"` entries match none of its extruders, so every extruder reads variant index 0 (and `check` reports them under [Printer rule 3](#printer-machine)). A filament with a `"Bowden Standard"` variant does get that variant there. |
| Nozzle types the user swaps (Standard / High Flow / TPU High Flow / E3D High Flow / Extra High Flow) | The printer lists them as variants; tune the keys that really differ per nozzle volume type. |
| Extruders of different types on one machine | One `extruder_type` per extruder, each extruder listing its own variants. |
| Several extruders that need different values in a variant key (retraction, z-hop, `nozzle_volume`, the `machine_max_*` limits) | Declare the layout even with a single nozzle volume type: `extruder_variant_list` with one `"<type> Standard"` per extruder, and the flattened pair. Without it the arrays still hold one value per extruder ([variant length](#widths)), but the loader keeps only extruder 1's value of a list-less printer's arrays, so every extruder prints with it. |
A preset without variant keys keeps working on a variant printer: its single variant is applied to
every extruder. So adding variants to a printer does not break existing processes or library
filaments; it only makes per-variant tuning possible.
## Printer (`machine`)
```json
"extruder_type": ["Direct Drive", "Direct Drive"],
"extruder_variant_list": ["Direct Drive Standard,Direct Drive High Flow",
"Direct Drive Standard,Direct Drive High Flow,Direct Drive TPU High Flow"],
"printer_extruder_id": ["1", "1", "2", "2", "2"],
"printer_extruder_variant": ["Direct Drive Standard", "Direct Drive High Flow",
"Direct Drive Standard", "Direct Drive High Flow", "Direct Drive TPU High Flow"],
"default_nozzle_volume_type": ["Standard", "Standard"]
```
Rules:
1. `extruder_variant_list` has **one entry per extruder**; each entry is the `,`-joined variants that
extruder supports. It is the per-extruder menu the sidebar offers. It is in no
[variant set](#the-four-key-sets), so the variant-length resize leaves it alone. Extruder 1's
first variant, variant index 0 of the flattened pair, is the fallback of every extruder whose
variant is missing when the arrays are collapsed for slicing
([slice time](#slice-time-and-existing-users)); it is not necessarily the configuration the user
sees, which is `default_nozzle_volume_type` (rule 4).
2. `printer_extruder_variant` is that list **flattened** extruder-major, one entry per variant, and
`printer_extruder_id` gives each entry its 1-based extruder. These two size and address every
variant. Write all three keys and keep them in agreement. At load with
`single_extruder_multi_material` off, and in the app when the printer tab loads a printer with a
different number of extruders, the pair is rebuilt from `extruder_variant_list` (one
`Direct Drive Standard` per extruder when the list is absent) and the variant arrays are resized to
the rebuilt pair, padded with their first value or cut. The resize skips the `machine_max_*` limits
and `hotend_heating_rate` / `hotend_cooling_rate`: they keep their width, and an extruder beyond it
reads their first value, so extruder 2 and up of a list-less printer take extruder 1's normal limit
as their silent one too. With the three in agreement that changes nothing; a pair written without
the list is replaced. A listed variant the pair lacks is a menu choice that reads variant index 0.
- The pair without `extruder_variant_list` slices, but the sidebar offers no variant switch and
the app cannot add variants to a list-less process: nothing is lost while every extruder
has exactly one variant, and every further variant is unreachable.
- A missing or one-value `printer_extruder_id` is extruder 1 at every index
([the id trap](#padding-truncation-and-composition)) unless the load-time rebuild above
replaces the pair (`single_extruder_multi_material` off).
- `check` reports against this rule ([variant arrays](validation.md#variant-arrays)). Errors: a
pair that is not the flattening, an id array that does not give each entry its extruder, a list
of more than one variant without the pair, and a pair without the list that puts several variants
on one extruder. A pair without the list and one variant per extruder is a warning where the
load-time rebuild would replace it (`single_extruder_multi_material` off, and a pair other than
one `Direct Drive Standard` per extruder), and passes otherwise.
3. Every variant string in an entry must start with that extruder's `extruder_type`.
4. `default_nozzle_volume_type` has one value per extruder and must name a nozzle volume type that
extruder's variants list; it seeds the sidebar. The live choice is `nozzle_volume_type` in the
project config, never a preset key.
5. Size every array by the set its key belongs to (the three sets are listed in full in
[The four key sets](#the-four-key-sets); the lengths are worked through in the
[sizing equation](#sizing-equation)): exactly the length below in each selectable preset that
writes it, or leave the key out and the preset takes what reaches it, the default or its base's
array. One value is no
shorthand for "the same for every variant"; write the value for every variant:
| Set | Length | Keys |
| --- | --- | --- |
| `printer_extruder_options` | extruders (`E`) | the 8 per-extruder keys outside the variant scheme (`extruder_type`, `nozzle_diameter`, `default_nozzle_volume_type`, …) |
| `printer_options_with_variant_1` | variant length (`S`) | the full list below; not guessable from names |
| `printer_options_with_variant_2` | 2 × variant length | the 16 `machine_max_*` limits, at [stride 2](#widths): a (normal, silent) pair per variant |
Put the variant layout on the shared base of all printers that share the hardware, and let the
nozzle-diameter siblings inherit it, restating only the variant arrays whose values change. `check`
does not judge a base on its own; its arrays count where they reach a selectable preset, under
`check --strict`. The loader stores a base at its **own** `printer_extruder_variant`, one variant
when it writes none whatever its extruder count, and cuts a wider array to its first values before
any child inherits it; so a multi-extruder base whose extruders need different values declares the
layout itself ([composition](#padding-truncation-and-composition)). When a variant array can move from
the presets to a base is in [shared-bases.md](shared-bases.md#variant-arrays-on-a-base).
## Process
1. `print_extruder_variant` + `print_extruder_id` list the (extruder id, variant string) pairs of the
process's variants. A variant is found by that pair, never by position: what is **required** is
that every pair a compatible printer (by list or condition) can select is present, in any order. A
missing pair reads the process's variant index 0 for that extruder, an extra pair is a variant
nothing selects, and neither is reported. The exception is a single-extruder printer whose
`extruder_variant_list` offers one variant string: no pair is matched there and variant index 0
is read, so a process shared with such a printer lists that printer's pair first. **Mirroring** the printer's `printer_extruder_variant` +
`printer_extruder_id` entry for entry is the convention; follow it, so the arrays compare by eye,
but a different order with every pair present is a nit, not a defect. A process shared by printers
whose pairs differ falls back to variant index 0 on the pairs it lacks; give each layout its own base.
2. Every key in `print_options_with_variant` ([the full list](#the-four-key-sets), which is not
"every speed") has exactly one value per variant, or is left out. `print_extruder_id` needs its
value per variant too: one value pads to extruder 1 everywhere. `check` holds a
`print_extruder_id` that reaches the preset, written or inherited, to one entry per variant on any
printer, without `--strict`, and warns when it is absent and a variant repeats.
3. Only add process variants if speeds or accelerations really differ per nozzle volume type or
extruder. Otherwise omit the variant keys: a one-variant process needs no list, because match and
no-match both read variant index 0, and that variant is copied to every extruder at slice time.
4. Put the lists on the process base for that printer layout, so leaves stay small.
## Filament
```json
"filament_extruder_variant": ["Direct Drive Standard", "Direct Drive High Flow"],
"filament_max_volumetric_speed": ["21", "29"],
"filament_flow_ratio": ["0.98", "0.98"],
"filament_retraction_length": ["nil", "0.4"]
```
1. `filament_extruder_variant` lists variants **without extruder ids**: a filament's High Flow variant
is used on whichever extruder is in High Flow. Its entries must be distinct, since the lookup
returns the first equal string and a repeated entry is a variant nothing selects. There is no
filament id key: `filament_extruder_id` exists only as a G-code placeholder, not as a filament
option (its option and its set entry are commented out in `PrintConfig.cpp`), so a filament file that writes it loses the key as unknown.
`filament_extruder_compatibility` is unrelated to variants (it says which extruders the filament
may be loaded into).
2. Every key in `filament_options_with_variant` ([the full list](#the-four-key-sets)) that reaches the
preset, whether written, included or inherited, is resized to its variant count: write it at exactly
that width, or leave it out. Keys outside the set
(`filament_type`, plate temperatures, `fan_max_speed`, `slow_down_min_speed`, `pressure_advance`,
…) are never addressed by variant index; the preset contributes their first value however wide a
file writes them.
3. Cover every variant the material is meant to print on across its `compatible_printers`. Leave out
a variant deliberately when the material should not be tuned for it (e.g. a TPU High Flow variant
only on TPU filaments); an extruder reporting that variant string then reads the first variant.
4. Order the variants like the printer's, Standard first, so the first-variant fallback is the
conservative one.
5. Tune what really differs: `filament_max_volumetric_speed` is the usual difference between nozzle
volume types, then flow ratio, temperature and retraction. Use measured values; never copy the
Standard value into the High Flow variant and call it tuned.
6. `nil` is legal per variant in the nullable override keys, occupies one entry like any value, and
keeps the printer's value for that variant only.
7. Declare a multi-variant list with the arrays it sizes: on the filament, in its own file or in a
template it pulls in with [`include`](vendor-bundle.md#inherits-and-include). The filament may be
compatible with printers that list fewer variants, or none: each printer takes the filament's variant
for its own variant string, else the filament's first variant. A one-variant list on a shared base
is harmless, because one variant is the width a list-less preset has anyway; and a list-less base is
stored at that width, so a wider array on it reaches its children as its first value only.
A key such a base writes reaches a multi-variant child as one value, which the loader spreads over
the child's variants; `check --strict` reports it at the child, which restates it at its own width.
## Widths
Every variant key is a flat array addressed by **variant index**: index `n` belongs to entry `n` of
the preset's own variant list (`printer_extruder_variant`, `print_extruder_variant` or
`filament_extruder_variant`). The index is found by exact compare of the string
`"<extruder_type> <nozzle volume type>"` (plus the 1-based extruder id for printer and process lists),
and the value is read at `index × stride`:
| Stride | Keys | Layout |
| --- | --- | --- |
| 1 | `printer_options_with_variant_1`, `print_options_with_variant`, `filament_options_with_variant` | `[variant 0, variant 1, …]` |
| 2 | `printer_options_with_variant_2` (the `machine_max_*` limits) | `[variant 0 normal, variant 0 silent, variant 1 normal, variant 1 silent, …]` |
So a variant array is `variant length × stride` long. The **variant length** is the number of
entries in the preset's variant list, counted on the preset's config **after** `include` and
`inherits` are applied, dead variants included. An inherited array arrives already resized to the
base's own variant length, an included one at the width its file wrote
([composition](#padding-truncation-and-composition)):
| Preset | Variant length |
| --- | --- |
| `machine` | `len(printer_extruder_variant)`; without it, the variants `extruder_variant_list` offers; without both, **one per extruder** (`len(nozzle_diameter)`), since the list's default is one `Direct Drive Standard` per extruder. `extruders_count` is a printer-tab field, not a preset key. The loader honours that default only halfway for a system preset: it sizes the composed preset by the one-entry default `printer_extruder_variant`, cutting every variant array to its first value, and only then (with `single_extruder_multi_material` off) rebuilds the pair to one variant per extruder ([Printer rule 2](#printer-machine)) and pads the arrays with that value. The rule's width is still one per extruder; to give extruders different values, declare the layout. |
| `process` | `len(print_extruder_variant)`; without one, 1 |
| `filament` | `len(filament_extruder_variant)`; without one, 1 |
The per-extruder keys in `printer_extruder_options` ([listed with the sets](#the-four-key-sets)) are
outside this scheme and stay one value per extruder. Keys outside [the four sets](#the-four-key-sets)
are never variant-resized or addressed by variant index, however wide a shipped file writes them; a
per-extruder vector holds `len(nozzle_diameter)` values, and a shorter one acts as padded with its
first value ([machine-profiles.md](machine-profiles.md#multi-extruder-idex-and-tool-changers)).
### Sizing equation
For a key in one of the four variant sets:
```
E = extruders = len(nozzle_diameter) = len(extruder_type)
= len(default_nozzle_volume_type) = len(extruder_variant_list)
V_i = variants listed for extruder i = ","-separated entries of extruder_variant_list[i],
each "<extruder_type[i]> <nozzle volume type>"
S = variant length = V_1 + V_2 + … + V_E
= len(printer_extruder_variant) = len(printer_extruder_id)
k = stride = 2 for printer_options_with_variant_2, else 1
N = values the key holds, one per variant:
machine (printer_options_with_variant_1, _2) = S × k
process (print_options_with_variant) = len(print_extruder_variant) = len(print_extruder_id)
filament (filament_options_with_variant) = len(filament_extruder_variant)
values[s × k + m] = variant index s, mode m (m = 0 normal, m = 1 silent; only m = 0 at stride 1)
variant index s = (printer_extruder_id[s], printer_extruder_variant[s])
```
`printer_extruder_variant` is not sized by the equation; it defines `S`: it is `extruder_variant_list`
flattened extruder by extruder, and `printer_extruder_id[s]` is the extruder that index `s` came from.
A process that mirrors the printer's pairs, as [Process rule 1](#process) asks, has `N = S`; a
filament lists each variant string it is tuned for once, with no extruder id, so its `N` is its own
and independent of any one printer: it serves every printer in its `compatible_printers`, and a
variant string no extruder of a printer reports is simply never read there (a two-extruder printer
with `S = 7` serves a filament whose `N` is 3, and the filament keeps its 3 on a printer that offers
two variant strings). The
pair is what the loader reads, so the equality with the sum holds when the three keys agree, as
[Printer rule 2](#printer-machine) requires.
A selectable preset that writes a variant key writes it at its own `N`; any other width, one value
included, is an error. A preset that leaves a key out takes what reaches it, the default or an array
it inherits or includes, which the loader resizes to the preset's `N`; `check --strict` holds that
array to the preset's `N` too. A base is not judged on its own: its arrays count only where they reach
a preset that does not override them. BBL is the model for strict: every printer-specific machine,
process and filament declares its layout and restates every variant key at its own `N`, even where all
the values are the same, keys Orca added to the sets included.
Without the lists, `extruder_variant_list` defaults to one
`Direct Drive Standard` per extruder, so a machine has `V_i = 1` and `S = E`, and a process or filament
has `N = 1`; the loader cuts a list-less machine to its first variant and, with
`single_extruder_multi_material` off, widens it again with that value ([variant length](#widths)). The per-extruder keys outside the sets
(`printer_extruder_options` plus `extruder_offset` and `extruder_colour`) and `extruder_variant_list`
itself hold `E` values.
### Sizing examples
**One extruder, two nozzle volume types**: `E = 1`, `V_1 = 2`, so `S = 2`; stride-1 keys hold 2
values, `machine_max_*` hold 4:
```json
"nozzle_diameter": ["0.4"],
"extruder_type": ["Direct Drive"],
"extruder_variant_list": ["Direct Drive Standard,Direct Drive High Flow"],
"printer_extruder_id": ["1", "1"],
"printer_extruder_variant": ["Direct Drive Standard", "Direct Drive High Flow"],
"default_nozzle_volume_type": ["Standard"],
"retraction_length": ["0.8", "1.0"],
"machine_max_speed_x": ["500", "200", "600", "250"]
```
The matching process lists the same two pairs (`print_extruder_id` `["1", "1"]`,
`print_extruder_variant` as above) and holds 2 values per key, e.g. `outer_wall_speed`
`["200", "260"]`; a filament for it lists `["Direct Drive Standard", "Direct Drive High Flow"]` and
holds 2 values per key, e.g. `filament_max_volumetric_speed` `["16", "24"]`.
**Four extruders, one nozzle volume type each** (a tool changer): `E = 4`, every `V_i = 1`, so
`S = 4`; stride-1 keys hold 4 values, `machine_max_*` hold 8:
```json
"nozzle_diameter": ["0.4", "0.4", "0.6", "0.4"],
"extruder_type": ["Direct Drive", "Direct Drive", "Direct Drive", "Direct Drive"],
"extruder_variant_list": ["Direct Drive Standard", "Direct Drive Standard",
"Direct Drive Standard", "Direct Drive Standard"],
"printer_extruder_id": ["1", "2", "3", "4"],
"printer_extruder_variant": ["Direct Drive Standard", "Direct Drive Standard",
"Direct Drive Standard", "Direct Drive Standard"],
"default_nozzle_volume_type": ["Standard", "Standard", "Standard", "Standard"],
"retraction_length": ["0.8", "0.8", "1.2", "0.8"],
"machine_max_speed_x": ["500", "200", "500", "200", "500", "200", "500", "200"]
```
The (normal, silent) pair is repeated per extruder: `["500", "200"]` would be width 2 against
`S × k = 8` (the loader pads it to `500, 200, 500, 500, …`) and `["500"]` width 1; both are errors. The
process mirrors the four pairs (`print_extruder_id` `["1", "2", "3", "4"]`) with 4 values per key, or
omits the variant keys altogether when nothing differs per extruder (then one value per key). A
filament for it lists only `["Direct Drive Standard"]`: one entry, so one value per key. Drop the
layout from this printer and the widths stay the same, since the default list gives `S = E = 4`; but
the loader then keeps only the first variant ([variant length](#widths)): `retraction_length`
becomes `0.8` on every extruder and extruder 3 loses its `1.2`.
### Adding a variant inserts its values at its variant index
Indexes run extruder-major: extruder 1's variants in `extruder_variant_list` order, then extruder
2's. A new variant's values go in at its index, not at the end. Giving extruder 1 of a two-extruder
printer a High Flow option, when only extruder 2 had one:
| Key | Before | After |
| --- | --- | --- |
| `extruder_variant_list` | `["Direct Drive Standard", "Direct Drive Standard,Direct Drive High Flow"]` | `["Direct Drive Standard,Direct Drive High Flow", "Direct Drive Standard,Direct Drive High Flow"]` |
| `printer_extruder_id` | `["1", "2", "2"]` | `["1", "1", "2", "2"]` |
| `printer_extruder_variant` | `[Standard, Standard, High Flow]` | `[Standard, High Flow, Standard, High Flow]` (full strings in the file) |
| `retraction_length` (stride 1) | `["0.8", "1.0", "1.2"]` | `["0.8", "?", "1.0", "1.2"]`: one value at index 1 |
| `machine_max_speed_x` (stride 2) | `["500", "200", "600", "250", "700", "300"]` | `["500", "200", "?", "?", "600", "250", "700", "300"]`: a (normal, silent) pair at position 2 |
| `print_extruder_id` / `print_extruder_variant` / `outer_wall_speed` | mirror the printer | the same insertion at index 1 |
| `filament_extruder_variant` `[Standard, High Flow]` | — | unchanged: filament variants carry no extruder id, so the existing High Flow variant now serves both extruders |
Every `?` is a measured value for that nozzle, on the machine limits as much as on retraction.
Removing or renaming a variant shifts the later values the same way in reverse; a variant string that
no longer matches the enum is simply a variant nothing selects.
### Padding, truncation and composition
**Every key in the set widens, whether or not a file restates it.** At load each variant key of the
composed config is resized to the length of the preset's own `*_extruder_variant` (the one-entry
default when it writes none) × stride: a short array is **padded by repeating its first value**, a
long one is truncated to its first values, without a word from the loader or the validator. The rule
does not follow from this padding: a file writes a variant key at **exactly `variant length × stride`**
or not at all. Any other length, one value included (which the loader spreads over every variant, at
stride 2 over normal *and* silent alike), is a mistake the loader hides and
`orca_profile_tool.py check` reports as an error in the selectable preset that writes it, even when
every value is the same; `check --strict` also reports an array that reaches a selectable preset at
another width
([variant arrays](validation.md#variant-arrays)).
The loader sizes a list-less preset of any type to one variant at this step, a list-less machine
included: a two-extruder machine without a layout that writes `retraction_length` `["0.8", "0.9"]`, the
width the equation asks for, stores `["0.8"]`, which the pair rebuild and the slice-time collapse hand
to both extruders. A 3-value array on a preset of variant length 2 keeps its first two; at length 4 it
becomes `[a, b, c, a]`.
Composition hands down widths in two ways:
- **`inherits` hands down the parent's resized arrays.** A base is stored after its own resize, at
the length of its own `*_extruder_variant` (one variant for a base of any type that writes none,
whatever its extruder count), so an array wider than the base's list is cut to its first values
before any child sees it, and a child that adds variants gets those first values padded. Widen an
array only on a preset whose own resolved list already has the entries. `check` judges selectable
presets only, at the width each file wrote: an array a base's own resize cuts is not seen (a review
item), and an inherited array of another width than a child's list is left to the loader's resize
unless `check --strict`, which asks the child to restate it at its own width.
- **`include` hands down the template's diff at its pre-resize width.** The template contributes every
key where its composed config differs from the built-in defaults, taken before its own resize, so the
arrays it writes arrive at the width its file wrote, and the includer's own list sizes them. A key the
template sets to the built-in default is not passed on
([`include`](vendor-bundle.md#inherits-and-include)).
The id keys are the trap in this padding: `printer_extruder_id` and `print_extruder_id` are members
with default `[1]`, so a missing or one-value id array beside a longer variant list is padded to
extruder 1 at every index. That is right on a single-extruder printer and wrong on a multi-extruder
one, where every variant is then addressed as extruder 1's. A machine escapes it only where the
load-time pair rebuild of [Printer rule 2](#printer-machine) runs (`single_extruder_multi_material`
off); a process's `print_extruder_id` is never rebuilt at load. `check`
holds an id array that reaches a selectable preset, written or inherited, to one entry per variant on
any printer; `fix-variant` never pads an id array or a variant list, since those address the
variants rather than fill them.
Consequences:
- A base that gains a variant silently pads every descendant that restates a variant array at the old
width, and a short `machine_max_*` array copies variant 0's *normal* limit into the silent entries
too; a descendant that restates nothing inherits the widened array and needs no edit. Extend, in one
change: the printer base and each nozzle-diameter sibling that restates a variant array, the process
bases that mirror the printer's variants, and the filaments that should cover the variant.
- A one-value override is reported, and the loader spreads it over **all** variants, overwriting the
ones that should differ.
- A process or filament that omits the new variant is not padded; the variant resolves to its index 0.
### Slice time and existing users
**Slice time collapses variants to extruders.** When printer, process and filaments are combined on a
printer with several extruders or several variant strings, each printer and process variant key is
re-gathered to one entry per extruder (× stride) in extruder order, using each extruder's live nozzle
volume type, or to one entry per nozzle volume type for an extruder that holds several (Hybrid, or
`extruder_nozzle_stats` listing more types than there are extruders); on any other printer they are
not re-gathered and variant index 0 is read. Filament keys are re-gathered to one entry per filament,
on a printer with a single variant too once a filament has several variants, and under a dynamic
nozzle map to one entry per variant each filament prints through. Custom G-code and the
`machine_max_*` limits therefore index by extruder or filament, never by variant index; variant order
matters only inside the preset. An extruder with no matching variant reads variant index 0, extruder
1's first variant, whichever extruder it is; under layered nozzle grouping, `get_config_index_base`
reads the collapsed arrays and falls back to the same extruder's first entry instead.
**Existing user presets and projects follow the variant string, not the position.** A user preset
stores every variant array it changed (nullable keys as per-variant diffs, `nil` where equal to the
parent). On load each parent variant takes the child's value for the variant with the same extruder id
and variant string; variants the parent gained keep the parent's value, and a child whose arrays the
parent cannot map keeps its own. Per-object process overrides are remapped when a printer change
alters the extruder count or the length of `printer_extruder_variant`, by variant string alone (no
extruder id; of several matching values the smallest wins), and a single-value override applies to
every variant. Adding or reordering variants in a shipped preset is therefore safe for existing
users; renaming a variant, or moving it to another extruder id, loses their values for it.
**A new nozzle volume type changes no array.** Adding one to the code widens nothing until a profile
lists the new variant string; until then every existing array keeps its length and meaning.
## The four key sets
Membership is literal (four `std::set<std::string>` initializers in `src/libslic3r/PrintConfig.cpp`)
and **not guessable from names**: `ironing_speed`, `skirt_speed`, `wipe_speed`, `scarf_joint_speed`,
`small_support_perimeter_speed` and `wipe_tower_max_purge_speed` are process speeds outside the set,
while every process `*_acceleration` and `*_jerk` key is inside, and so are
`small_perimeter_threshold`, `top_solid_infill_flow_ratio` and `slowdown_for_curled_perimeters`;
`filament_flush_temp` is in and `filament_flush_temp_fast` out; `use_firmware_retraction` is out and
`travel_slope` and `retract_lift_enforce` in. The three variant-list keys and the two id
keys are members of their own set (the list sizes itself, a no-op; the id keys are padded like any
other member, [the id trap](#padding-truncation-and-composition)), while `extruder_variant_list` is in
no set: the variant-length resize leaves it alone, and only the pair rebuild of
[Printer rule 2](#printer-machine) pads it. The per-extruder `printer_extruder_options` is listed
last for contrast; it is not a variant set.
`check` and `fix-variant` read the four sets from `PrintConfig.cpp` on every run, so they follow the
engine. The lists below are from the 2026-09-25 checkout; regenerate them from the repository root
before relying on them (the recipe strips comments, since an initializer can carry a commented-out entry):
```bash
python3 - <<'EOF'
import re
src = open('src/libslic3r/PrintConfig.cpp', encoding='utf-8', errors='replace').read()
for name in ['printer_options_with_variant_1', 'printer_options_with_variant_2',
'print_options_with_variant', 'filament_options_with_variant',
'printer_extruder_options']:
body = re.search(r'std::set<std::string>\s+' + name + r'\s*=\s*\{(.*?)\};', src, re.S).group(1)
body = re.sub(r'/\*.*?\*/', '', body, flags=re.S)
body = re.sub(r'//[^\n]*', '', body)
print(name, sorted(set(re.findall(r'"([^"]+)"', body))))
EOF
```
**`printer_options_with_variant_1`**, machine, stride 1 (27): `deretraction_speed`, `hotend_cooling_rate`, `hotend_heating_rate`, `long_retractions_when_cut`, `nozzle_flush_dataset`, `nozzle_type`, `nozzle_volume`, `printer_extruder_id`, `printer_extruder_variant`, `retract_after_wipe`, `retract_before_wipe`, `retract_length_toolchange`, `retract_lift_above`, `retract_lift_below`, `retract_lift_enforce`, `retract_restart_extra`, `retract_restart_extra_toolchange`, `retract_when_changing_layer`, `retraction_distances_when_cut`, `retraction_length`, `retraction_minimum_travel`, `retraction_speed`, `travel_slope`, `wipe`, `wipe_distance`, `z_hop`, `z_hop_types`
**`printer_options_with_variant_2`**, machine, stride 2 (16): `machine_max_acceleration_e`, `machine_max_acceleration_extruding`, `machine_max_acceleration_retracting`, `machine_max_acceleration_travel`, `machine_max_acceleration_x`, `machine_max_acceleration_y`, `machine_max_acceleration_z`, `machine_max_jerk_e`, `machine_max_jerk_x`, `machine_max_jerk_y`, `machine_max_jerk_z`, `machine_max_junction_deviation`, `machine_max_speed_e`, `machine_max_speed_x`, `machine_max_speed_y`, `machine_max_speed_z`
**`print_options_with_variant`**, process, stride 1 (45): `bridge_acceleration`, `bridge_speed`, `default_acceleration`, `default_jerk`, `default_junction_deviation`, `enable_overhang_speed`, `gap_infill_speed`, `infill_jerk`, `initial_layer_acceleration`, `initial_layer_infill_speed`, `initial_layer_jerk`, `initial_layer_speed`, `initial_layer_travel_acceleration`, `initial_layer_travel_jerk`, `initial_layer_travel_speed`, `inner_wall_acceleration`, `inner_wall_jerk`, `inner_wall_speed`, `internal_bridge_speed`, `internal_solid_infill_acceleration`, `internal_solid_infill_speed`, `outer_wall_acceleration`, `outer_wall_jerk`, `outer_wall_speed`, `overhang_1_4_speed`, `overhang_2_4_speed`, `overhang_3_4_speed`, `overhang_4_4_speed`, `print_extruder_id`, `print_extruder_variant`, `slowdown_for_curled_perimeters`, `small_perimeter_speed`, `small_perimeter_threshold`, `sparse_infill_acceleration`, `sparse_infill_speed`, `support_interface_speed`, `support_speed`, `top_solid_infill_flow_ratio`, `top_surface_acceleration`, `top_surface_jerk`, `top_surface_speed`, `travel_acceleration`, `travel_jerk`, `travel_speed`, `travel_speed_z`
**`filament_options_with_variant`**, filament, stride 1 (48): `activate_air_filtration`, `activate_air_filtration_during_print`, `activate_air_filtration_on_completion`, `complete_print_exhaust_fan_speed`, `during_print_exhaust_fan_speed`, `filament_adaptive_volumetric_speed`, `filament_cooling_before_tower`, `filament_deretraction_speed`, `filament_extruder_variant`, `filament_flow_ratio`, `filament_flush_temp`, `filament_flush_volumetric_speed`, `filament_ironing_flow`, `filament_ironing_inset`, `filament_ironing_spacing`, `filament_ironing_speed`, `filament_long_retractions_when_cut`, `filament_max_volumetric_speed`, `filament_pre_cooling_temperature`, `filament_pre_cooling_temperature_nc`, `filament_preheat_temperature_delta`, `filament_ramming_travel_time`, `filament_ramming_travel_time_nc`, `filament_ramming_volumetric_speed`, `filament_ramming_volumetric_speed_nc`, `filament_retract_after_wipe`, `filament_retract_before_wipe`, `filament_retract_length_nc`, `filament_retract_length_toolchange`, `filament_retract_lift_above`, `filament_retract_lift_below`, `filament_retract_lift_enforce`, `filament_retract_restart_extra`, `filament_retract_restart_extra_toolchange`, `filament_retract_when_changing_layer`, `filament_retraction_distances_when_cut`, `filament_retraction_length`, `filament_retraction_minimum_travel`, `filament_retraction_speed`, `filament_wipe`, `filament_wipe_distance`, `filament_z_hop`, `filament_z_hop_types`, `long_retractions_when_ec`, `nozzle_temperature`, `nozzle_temperature_initial_layer`, `retraction_distances_when_ec`, `volumetric_speed_coefficients`
**`printer_extruder_options`**, machine, one value per extruder, not a variant set (8):
`default_nozzle_volume_type`, `extruder_max_nozzle_count`, `extruder_printable_area`,
`extruder_printable_height`, `extruder_type`, `max_layer_height`, `min_layer_height`,
`nozzle_diameter`. These are never addressed by variant index; `extruder_offset`, `extruder_colour` and
`extruder_variant_list` are per extruder too. A shorter array acts as padded with its first value, and
entries beyond the extruder count are never read
([per-extruder vectors](machine-profiles.md#multi-extruder-idex-and-tool-changers)).
## Checking and testing
Checklist for a new variant profile set:
1. Decide the variants per extruder from the real hardware; pick legal strings only.
2. Printer base: `extruder_type`, `extruder_variant_list`, flattened `printer_extruder_variant` +
`printer_extruder_id`, `default_nozzle_volume_type`; every variant array at variant length, every
`machine_max_*` at 2 × variant length as (normal, silent) pairs, even where the values are the
same, values in variant order.
3. A process base per variant layout, or no variant keys at all.
4. Filaments for the printer: a variant list covering the intended variants, every variant key at that
width, measured values per nozzle volume type.
5. Run the usual authoring commands and full checks. `check` holds each array of steps 2–4 to the
width of the selectable preset that writes it, `check --strict` also to every selectable preset it
reaches, and
the printer's layout keys through every selectable preset
([variant arrays](validation.md#variant-arrays)); `check_variant_names` holds every variant string,
`extruder_type`, `nozzle_volume_type` and `default_nozzle_volume_type` to the enums
([variant names](validation.md#variant-names)). The choice of variants, the variant order and
the measured values are not checked.
6. In the app, for each nozzle volume type in the sidebar combo: slice and confirm the G-code uses that
variant's values (e.g. volumetric speed limit, retraction). `validate_slice` only slices the default
nozzle volume type.
To review rather than author, run the same list against the diff. `check` catches a wrong array length,
a layout key out of step, and a variant string the enums cannot build. The failures no check catches: a
new variant appended instead of inserted at its index, an array widened on a base whose list is
shorter (cut before any child inherits it), a process lacking a pair its printer can select, and a High
Flow variant copied from Standard.
### UI facts to design around
- The single-extruder sidebar shows a **nozzle volume type combo** (tooltip `Flow`, in place of the
nozzle-diameter selector) only when `extruder_variant_list` offers more than one distinct variant
string (`support_different_extruders`); four extruders listing `Direct Drive Standard` each show
none.
- The two-extruder sidebar's per-extruder nozzle volume type combos are shown for BBL printers only.
Another vendor's multi-extruder printer falls back to the single-extruder layout: one combo (for the
first extruder) when the variants differ, otherwise the nozzle-diameter selector, so the other
extruders' nozzle volume type stays at `default_nozzle_volume_type`.
- High Flow is hidden from the combo when `printer_variant` is `0.2` or the printer model is
`Bambu Lab X1E`, and E3D High Flow unless `printer_variant` is `0.4` or `0.6`. A variant listed on
another nozzle diameter is never selectable there.
- The filament tab shows a variant switch built from the filament's own `filament_extruder_variant`;
a multi-variant filament whose tab shows none did not resolve its variant list through `include` or
`inherits`. The printer and process tabs show one entry per extruder instead, labelled with that
extruder's live nozzle volume type (two for Hybrid), and only on two-extruder printers whose
`extruder_variant_list` offers more than one variant string; elsewhere they edit the variant of the
nozzle volume type selected in the sidebar (an X1C shows no switch).
### Worked examples in the tree
`BBL/machine/fdm_bbl_3dp_001_common.json` (one extruder), `fdm_bbl_3dp_002_common.json` (two
extruders), `Bambu Lab X1 Carbon 0.4 nozzle.json` (Standard + High Flow), `Bambu Lab H2D 0.4
nozzle.json` (extruders with different variant sets), `Bambu Lab X2D 0.4 nozzle.json` (Direct Drive +
Bowden), with their `@BBL` processes and filaments. The H2D and X2D examples also carry
`E3D High Flow` variants. BBL's multi-variant filament lists come from `fdm_filament_template_*`
presets that the printer-specific filaments pull in with `include`, not from their `inherits` chain.
@@ -1,8 +1,8 @@
# Filament profiles and OrcaFilamentLibrary
`OrcaFilamentLibrary` is the filament-only bundle the loader reads **first**; its config map
becomes the base bundle, so any vendor may inherit a library preset by name. It is the only cross-bundle
parent — vendor-to-vendor inheritance always fails.
`OrcaFilamentLibrary` is the filament-only bundle the loader reads **first**, so any vendor's filament may
inherit a library preset by name. It is the only cross-bundle parent: vendor-to-vendor inheritance
always fails.
## Where a filament goes
@@ -10,63 +10,65 @@ parent — vendor-to-vendor inheritance always fails.
| --- | --- |
| Generic material for all printers | `OrcaFilamentLibrary/filament/Generic <mat> @System.json` |
| A brand's product, all printers | `OrcaFilamentLibrary/filament/<Brand>/` |
| A brand's tune for one printer | `OrcaFilamentLibrary/filament/<Brand>/<PrinterVendor>/` — recommended; `<PrinterVendor>/filament/<Brand>/` also works |
| A printer vendor's tune of a generic or its own product | `<Vendor>/filament/` |
| A brand's tune for one printer vendor | `OrcaFilamentLibrary/filament/<Brand>/<PrinterVendor>/` (recommended); `<PrinterVendor>/filament/<Brand>/` also works |
| A printer vendor's tune of a generic, or its own product | `<PrinterVendor>/filament/` |
Both locations for the last-but-one row are supported: `OrcaFilamentLibrary/filament/<Brand>/<PrinterVendor>/<Name>.json`
Both locations in the third row are supported: `OrcaFilamentLibrary/filament/<Brand>/<PrinterVendor>/<Name>.json`
(the shape the wiki shows) and `<PrinterVendor>/filament/<Brand>/`. The library path is the one a
filament vendor should contribute to — `OrcaFilamentLibrary/filament/<Brand>/` is the brand's own
folder, while a printer vendor's folder belongs to that printer vendor. Brand tunes do ship under
printer vendors' folders today (Polymaker and SUNLU among others).
filament brand should contribute to: `OrcaFilamentLibrary/filament/<Brand>/` is the brand's own folder,
while a printer vendor's folder belongs to that printer vendor.
Library layout: `filament/base/fdm_filament_*.json` type roots, root-level `Generic <mat> @System.json`
generics, and one subfolder per brand, which may nest printer-specific tunes one level deeper. Adding
a brand means adding a folder here; the folder name is a directory label only — `filament_vendor` inside the JSON is the real vendor string.
Library layout: `filament/base/fdm_filament_*.json` material roots, root-level
`Generic <mat> @System.json` generics, and one subfolder per brand, which may nest printer-specific
tunes one level deeper. Adding a brand means adding a folder here; the folder name is a directory label
only, and `filament_vendor` inside the JSON is the real vendor string.
## The three-part shape
```jsonc
// Fiberon PA6-CF @base.json — the product root, holds identity + material values
// OrcaFilamentLibrary/filament/Polymaker/Fiberon PA6-CF @base.json — the product root: identity + material values
{ "type": "filament", "name": "Fiberon PA6-CF @base", "from": "system",
"instantiation": "false", "inherits": "fdm_filament_pa",
"filament_id": "OFkOviHk", // generated here; variants inherit it
"filament_id": "OFkOviHk", // minted here by generate-id; every child inherits it
"filament_vendor": ["Polymaker"], "filament_type": ["PA6-CF"], /* … */ }
// Fiberon PA6-CF @System.json — the selectable shim, 7 keys
// OrcaFilamentLibrary/filament/Polymaker/Fiberon PA6-CF @System.json — the selectable all-printer shim, 7 keys
{ "type": "filament", "name": "Fiberon PA6-CF @System", "from": "system",
"instantiation": "true", "inherits": "Fiberon PA6-CF @base",
"setting_id": "…", "compatible_printers": [] }
// <PrinterVendor>/filament/Polymaker/Fiberon PA6-CF @BBL X1C.json — a printer tune
{ … "inherits": "Fiberon PA6-CF @base", "filament_max_volumetric_speed": ["14"],
// BBL/filament/Polymaker/Fiberon PA6-CF @BBL X1C.json — a printer tune (BBL keeps its own copy of the @base)
{ …, "inherits": "Fiberon PA6-CF @base", "filament_max_volumetric_speed": ["14"],
"compatible_printers": ["Bambu Lab X1 Carbon 0.4 nozzle", …] }
```
- `@base` is the convention for a root. A base carries **no** `setting_id`, no `compatible_printers`, no
`filament_settings_id`. Only the `setting_id` half is enforced, and nothing violates it; the other two
are unchecked and plenty of bases still carry them. Do not copy that from a neighbouring file.
- Every `@System` must be `"instantiation": "true"`. DREMC ships `@System` presets set to `"false"`,
which therefore ship but can never be selected; no check catches it.
- A duplicated brand `@base` across bundles is normal and intentional (`Fiberon PA6-CF @base` exists in
both the library and BBL with the same id, differing only in MVS) — bases never enter the preset
collection, so there is no duplicate-name error.
- You may inherit from an instantiated preset as well as from a base; it is common.
- `@base` is the convention for a root; a root is really `instantiation: "false"`. A base carries
**no** `setting_id`, no `compatible_printers` and no `filament_settings_id`. Only the `setting_id`
half is enforced; the other two are unchecked, so a neighbouring base that carries them is no model.
- Every `@System` shim must be `"instantiation": "true"`; one set to `"false"` would ship but could
never be selected, and no check catches it. The shim exists only for products in the library.
- `filament_cost`, `filament_density`, `filament_type` and `filament_vendor` belong on the root and
should not appear in a printer tune.
- A brand `@base` duplicated across bundles is legal (a vendor bundle may keep its own copy of a
library product root, with the same id): bases never become selectable presets and the
duplicate-name error covers only those, so there is none.
- You may inherit from an instantiated preset as well as from a base.
## Color is a runtime property
## Colour is a runtime property
`filament_id` identifies a product, not a color; filament sync/AMS reads the color from the spool at
runtime. A product ships one all-printer preset and the color is chosen at runtime — never a sibling
preset that differs only by color. A material family (PLA vs PLA Matte vs PLA Silk) is a new product; a
color is not. A printer tune keeps the product alias and does not multiply per color either.
`filament_id` identifies a product, not a colour; filament sync and AMS read the colour from the spool
at runtime. A product ships one all-printer preset and the colour is chosen at runtime, never a sibling
preset that differs only by colour. A material family (PLA vs PLA Matte vs PLA Silk) is a new product;
a colour is not. A printer tune keeps the product alias and does not multiply per colour either.
CI does not catch this — per-color presets pass `check` — so it is a review call.
CI does not catch this (per-colour presets pass `check`), so it is a review call.
## The two most common contributions
**A printer vendor tuning a generic.** Keep the `Generic X` base name so the alias shadows the library
preset on your printers, inherit `Generic X @System`, declare **no** `filament_id` (inheriting the
library's is correct — the product really is the library's generic), and give it a non-empty
`compatible_printers` in its own body:
**A printer vendor tuning a generic.** Keep the `Generic X` alias so it shadows the library preset on
your printers, inherit `Generic X @System`, declare **no** `filament_id` (inheriting the library's is
correct: the product really is the library's generic), and give it a non-empty `compatible_printers` in
its own file:
```jsonc
// <Vendor>/filament/Generic PETG @Acme One 0.4 nozzle.json
@@ -76,13 +78,14 @@ library's is correct — the product really is the library's generic), and give
"compatible_printers": ["Acme One 0.4 nozzle"] }
```
**A printer vendor's own branded product.** Give it a `@base` root so `generate-id` can mint the id (see
[ids.md](ids.md) — inheriting `Generic X @System` directly makes the id unfixable by the tool), then one
instantiated leaf per printer in the same bundle. No `@System` shim: that is only for a product entering
OrcaFilamentLibrary.
**A printer vendor's own branded product.** Give it a `@base` root on a material base so `generate-id`
can mint the id, then one instantiated leaf per printer in the same bundle. Inheriting
`Generic X @System` directly gives the product the generic's id, which the tool cannot fix
([ids.md](ids.md#what-generate-id-does-and-does-not-fix)). No `@System` shim: that is only for a product
entering OrcaFilamentLibrary.
```jsonc
// <Vendor>/filament/Acme Aura PETG @base.json — instantiation false, no setting_id
// <Vendor>/filament/Acme Aura PETG @base.json — instantiation false, no setting_id
{ "type": "filament", "name": "Acme Aura PETG @base", "from": "system",
"instantiation": "false", "inherits": "fdm_filament_pet",
"filament_vendor": ["Acme"], "filament_type": ["PETG"] } // filament_id minted here
@@ -94,174 +97,190 @@ OrcaFilamentLibrary.
"compatible_printers": ["Acme One 0.4 nozzle"] }
```
Omit `filament_settings_id` from new presets — it is runtime bookkeeping the app rewrites to the preset
Omit `filament_settings_id` from new presets: it is runtime bookkeeping the app rewrites to the preset
name.
To offer either kind by default, add its name to each model's `default_materials`; put it first in the
machine's `default_filament_profile` only if it should be the preselected filament
([machine keys](machine-profiles.md#other-keys)).
## `compatible_printers`
- **Library fallbacks:** empty `[]` or absent, so they are offered on all printers except where
[alias shadowing](#alias-shadowing) supplies a printer-specific tune.
- **Library printer-specific tunes:** non-empty, listing exact printer **variant** names. These can
- **Library fallbacks** (`@System`): empty `[]` or absent, so they are offered on all printers except
where [alias shadowing](#alias-shadowing) supplies a printer-specific tune.
- **Library printer-specific tunes**: non-empty, listing exact printer **variant** names. These
supersede a same-alias fallback just like a tune in a printer vendor's bundle.
- **Instantiated filaments in every other vendor:** non-empty, listing exact printer **variant** names.
Enforced twice but not identically: the C++ `has_errors` reads the *flattened* config, so an inherited list satisfies it,
while the Python check reads the file's **own** key. Write the list in the file itself. This is the
most common filament CI failure.
- Emptying it to "make it apply everywhere" fails that check *and* creates a duplicate-`filament_id`
collision against the library generic on every printer.
- Copying a base's full printer list onto a nozzle-specific variant produces duplicate combobox entries —
a real shipped bug twice over.
- **Instantiated filaments in every other vendor**: non-empty, listing exact printer **variant** names.
Enforced twice, but not identically: `validate_system` reads the resolved config, so an inherited list
satisfies it, while `check` reads the file's **own** key. Write the list in the file itself. This is
the most common filament CI failure.
- Emptying it to "make it apply everywhere" fails that check *and* collides with the library generic's
`filament_id` on every printer.
- Copying a base's full printer list onto a nozzle-specific tune produces duplicate combobox entries: a
real shipped bug twice over.
## Overlapping coverage: one variant, one profile per product
`filament_id` is the **product** key, not the preset key — every variant of one product shares it
(`<filament_vendor>/<filament_type>/<name-before-@>`). So if one printer variant appears in the
`compatible_printers` of two presets of that product, the slicer cannot tell them apart at AMS match time.
The C++ validator reports `Ambiguous AMS filament match: N presets share filament_id "X" … printer "Y"`.
`orca_profile_tool.py check` does **not** see it and passes. Resolve the overlap by **specificity**: keep
the variant on the most specific profile and remove it from every more general one. Deleting a profile is
the least preferred fix — moving coverage keeps the tune that users rely on.
`filament_id` is the **product** key, not the preset key: every preset of one product shares it
(`<filament_vendor>/<filament_type>/<alias>`). So if one printer variant appears in the
`compatible_printers` of two presets of that product, the slicer cannot tell them apart at AMS match
time. `validate_system` reports `Ambiguous AMS filament match: N filament presets share filament_id "X"
and are all compatible with printer "Y"`; `orca_profile_tool.py check` does **not** see it and passes.
Resolve the overlap by **specificity**: keep the variant on the most specific profile and remove it from
every more general one. Deleting a profile is the least preferred fix: moving coverage keeps the tune
that users rely on.
Judge specificity from the profile's `compatible_printers` — how many variants it actually covers — and
use the name only as a secondary, easily-vague hint; decide by the lists, with a best judgement call on
the name. Naming conventions differ by vendor: BBL's is the reference (`@<Vendor> <Model>` for a whole
Judge specificity from the profile's `compatible_printers` (how many variants it actually covers) and
use the name only as a secondary, often vague hint; decide by the lists, with a judgement call on the
name. Naming conventions differ by vendor: BBL's is the reference (`@<Vendor> <Model>` for a whole
model, `@<Vendor> <Model> <nozzle> nozzle` for one variant, `@<Vendor>` for a vendor-wide generic), but
others vary (`@<printer model>`, a printer serial, or Creality's `@<Model>-all`). A name never overrides
the list — see [preset naming](naming.md) for the shapes.
the list; see [preset naming](naming.md#filament) for the shapes.
Specificity, most to least:
1. **Variant-specialized** — lists a single printer variant (BBL-style
`... @<Vendor> <Model> <nozzle> nozzle`).
2. **Model-specialized** — lists the variants of one printer model (BBL-style `... @<Vendor> <Model>`).
It should cover every variant of its model, not only the nozzle it was authored for.
3. **Family / series** — lists variants spanning a printer family or series.
4. **Generic / catch-all** — vendor-wide, covering many unrelated models (often the bare
1. **Variant-specialized**: lists a single printer variant (BBL-style
`… @<Vendor> <Model> <nozzle> nozzle`).
2. **Model-specialized**: lists the variants of one printer model (BBL-style `… @<Vendor> <Model>`). It
should cover every variant of its model, not only the nozzle it was authored for.
3. **Family / series**: lists variants spanning a printer family or series.
4. **Generic / catch-all**: vendor-wide, covering many unrelated models (often the bare
`Generic <mat> @<Vendor>`).
Rules:
- A model-specialized profile is extended to **all** variants of its model, and each variant it thereby
starts covering is removed from the family and generic profiles that also listed it — including variants
that had no overlap before. Apply it per nozzle, not just 0.4.
starts covering is removed from the family and generic profiles that also listed it, including
variants that had no overlap before. Apply it per nozzle, not just 0.4.
- Apply it **per product**: trim only the material that has a specialized profile from the generic; a
material whose product has no specialized profile keeps the variant in the generic.
- Never strip coverage a variant has nowhere else to get. If a variant has no variant-level specialized
profile, the next level down keeps it; when the model has specialized profiles, the model-level one wins
over the family/generic.
- Moving coverage is preferred over deleting. If a profile must be deleted, remove the more general
one, not the specialized profile that carries the tune.
- After moving coverage, repoint the affected `default_filament_profile` (machine) and clean the model's
`default_materials`: they should name the most specific profile that covers the variant, and should not
keep generic entries that no longer cover the model. This rule applies equally when adding or fixing
defaults.
profile, the next level down keeps it; when the model has specialized profiles, the model-level one
wins over the family and generic ones.
- Moving coverage is preferred over deleting. If a profile must be deleted, remove the more general one,
not the specialized profile that carries the tune.
- After moving coverage, repoint the affected machine's `default_filament_profile` and clean the model's
`default_materials`: they should name the most specific profile that covers the variant, and should
not keep generic entries that no longer cover the model. This rule applies equally when adding or
fixing defaults.
Multiple profiles of one product with **disjoint** `compatible_printers` is the intended end state.
Several profiles of one product with **disjoint** `compatible_printers` is the intended end state.
Adding coverage to the specialized profile and removing it from the generic is the preferred direction.
**Detection caveat:** `orca_profile_tool.py check` is blind to this; only the C++ validator behind the full
`./scripts/check_profile.sh` reports it (`validate_system`). Always confirm with that, not the vendor-scoped
loop.
**Detection caveat:** `orca_profile_tool.py check` is blind to this; only the validator behind the full
`./scripts/check_profile.sh` reports it (`validate_system`). Always confirm with that, not the
vendor-scoped loop.
## Alias shadowing
A printer-specific filament in either the library or a vendor bundle supersedes the library fallback
on the printers it lists. The matching key is the **alias**: the preset name up to the **first** `@`,
right-trimmed (no `@` → the whole name). So
`QIDI ABS-GF@Q2-Series` aliases to `QIDI ABS-GF`.
A printer-specific filament in either the library or a vendor bundle supersedes the library fallback on
the printers it lists. The matching key is the **alias**: the preset name up to the **first** `@`,
right-trimmed (no `@` → the whole name). So `QIDI ABS-GF@Q2-Series` aliases to `QIDI ABS-GF`.
A library preset with an empty `compatible_printers` collects, into `m_excluded_from`, every printer named
by any same-alias preset that *has* a non-empty list, and is then hidden on those printers.
A library preset with an empty `compatible_printers` is hidden on every printer that a same-alias preset
lists in a non-empty `compatible_printers`, whether that preset is in the library or in a vendor bundle
(a printer matches by its own name or its parent's).
Two consequences:
- **Only an unrestricted library fallback can be shadowed.** Two printer-specific presets sharing
an alias do not exclude each other — overlapping lists for the same product trip the
duplicate-`filament_id` check instead.
- This is why adding `Generic PLA @<printer>` to a vendor silently removes the library `Generic PLA`
from that printer. Intended — and the reason a vendor tuning a generic must **keep the `Generic X`
base name**.
- **Only an unrestricted library fallback can be shadowed.** Two printer-specific presets sharing an
alias do not hide each other; overlapping lists for the same product trip the ambiguous-match error
above instead.
- This is why adding `Generic PLA @<printer>` to a vendor silently removes the library
`Generic PLA @System` from that printer. That is intended, and the reason a vendor tuning a generic
must **keep the `Generic X` alias**.
The literal spelling `Generic <mat> @System` is load-bearing beyond shadowing: `find_preset2` rewrites an
unresolved name containing "Generic" into that form and retries against the library, which is how 3MF
and project recovery works.
The literal spelling `Generic <mat> @System` is load-bearing beyond shadowing: when a user preset, an
imported preset or a 3MF project names a parent that no longer resolves and contains `Generic`, the
loader rewrites the name into `Generic <mat> @System` and retries. Only the library ships those names,
so keep them.
## `filament_id`, `filament_vendor`, `filament_type`
`filament_id` is minted from the triple `(filament_vendor, filament_type, name-before-first-@)`.
`filament_vendor` and `filament_type` are therefore **identity, not decoration** — editing either
re-mints the id. Read `docs/HLSD/filament_id.md` before changing any of them, and see
[ids.md](ids.md) for the tooling.
`filament_id` is minted from the triple `(filament_vendor, filament_type, alias)`. `filament_vendor` and
`filament_type` are therefore **identity, not decoration**: editing either, or the alias, re-mints the
id. Read `docs/HLSD/filament_id.md` before changing any of them, and see [ids.md](ids.md) for the
tooling.
A filament with no resolvable `filament_id` anywhere in its `inherits` chain is a **hard load error** that
discards the vendor bundle. The id inherits across bundles, so a vendor's `Generic ABS @X` inheriting
`Generic ABS @System` gets the library's id for free; a vendor's own product must resolve its own.
A filament with no resolvable `filament_id` anywhere in its `inherits` chain is a **hard load error**
that discards the vendor bundle. The id inherits across bundles, so a vendor's `Generic ABS @X`
inheriting `Generic ABS @System` gets the library's id for free; a vendor's own product must resolve its
own.
- `filament_type` **must be a JSON array** — the one vector key the Python check enforces. A scalar
`"PP"` once hung the filament/printer selection UI.
- `filament_type` **must be a JSON array**: the one vector key `check` rejects as a scalar outright. A
scalar `"PP"` once hung the filament and printer selection UI.
- It is an **open** enum: an unlisted value is accepted silently and falls back to 190–300 °C defaults
and adhesion 1.0. Off-list values do ship. Prefer a value from `MaterialType::all()` in
and adhesion 1.0. Prefer a value from `MaterialType::all()` in
`src/libslic3r/MaterialType.cpp`, or add a row there.
- Generics use `filament_vendor: ["Generic"]`, which `fdm_filament_common` already defaults to.
## `"nil"`
Legal in any key whose `ConfigOptionDef` is `nullable`. In a filament preset that is most of the
`filament_*` family, plus `long_retractions_when_ec` and `retraction_distances_when_ec`. About half are
the extruder overrides (`filament_retraction_length`, `filament_z_hop`, `filament_wipe`,
`filament_retract_*`, `filament_retraction_speed`, `filament_deretraction_speed`,
`filament_retraction_minimum_travel`, `filament_wipe_distance`, `filament_long_retractions_when_cut`,
`filament_retraction_distances_when_cut`, …), where `nil` means *keep the printer/extruder's own value*.
The rest are ordinary nullable options (`filament_flow_ratio`, `filament_flush_temp`,
`filament_adaptive_volumetric_speed`, …) where it means *unset*.
`"nil"` is legal only in an option defined as nullable (`add_nullable`, or `nullable = true`, in
`src/libslic3r/PrintConfig.cpp`). Anywhere else it fails the file, and with it the **whole bundle**
(`Failed loading configuration file`, after `Deserializing nil into a non-nullable object` or
`Invalid value provided for parameter <key>: nil`). To leave a non-nullable key unset, omit it; do not
write `nil`.
Anywhere else it throws `Deserializing nil into a non-nullable object`. To not set a non-nullable key,
omit it — do not write `nil`.
In filament presets a minority of the `filament_*` keys are nullable, plus `long_retractions_when_ec`
and `retraction_distances_when_ec`. About half of them are the extruder overrides (`filament_retraction_length`,
`filament_z_hop`, `filament_wipe`, `filament_retract_*`, `filament_retraction_speed`,
`filament_deretraction_speed`, `filament_retraction_minimum_travel`, `filament_wipe_distance`,
`filament_long_retractions_when_cut`, `filament_retraction_distances_when_cut`, …), where `nil` means
*keep the printer's or extruder's own value*. The rest are ordinary nullable options
(`filament_flow_ratio`, `filament_flush_temp`, `filament_adaptive_volumetric_speed`, …), where it means
*unset*. Check the option's definition before writing `nil` anywhere else.
## What to review per nozzle
## Tuning per nozzle and per variant
Across `@X` / `@X 0.N nozzle` sibling pairs the keys that differ, most often first, are
Between a product's `@X` and `@X 0.N nozzle` tunes the keys that usually differ, most often first, are
`filament_max_volumetric_speed`, `filament_retraction_length`, `slow_down_min_speed`,
`filament_flow_ratio`, `slow_down_layer_time`, `nozzle_temperature` and `pressure_advance`.
`filament_cost`, `filament_density`, `filament_type` and `filament_vendor` belong on the `@base` and
should not appear in a printer tune.
`filament_flow_ratio`, `slow_down_layer_time`, `nozzle_temperature` and `pressure_advance` (switched on
by `enable_pressure_advance`).
Use measured values for the material, hotend, extruder and nozzle combination. Neither maximum
volumetric speed nor pressure advance has a universal nozzle-only lookup table. When cloning a
0.4 preset for a 0.2 nozzle, explicitly revisit flow limits; do not infer a pressure-advance value
or a required direction of change from diameter alone.
volumetric speed nor pressure advance has a universal nozzle-only lookup table. When cloning a 0.4
preset for a 0.2 nozzle, explicitly revisit flow limits; do not infer a pressure-advance value, or a
required direction of change, from diameter alone.
## Style
Overrides, not full copies: a typical instantiated filament preset carries around a dozen non-meta keys,
and a library leaf two or three. Presets that restate fifty-plus keys from their parent do still ship —
Phrozen's single filament preset is that style — but they are the pattern to move away from, not to
copy. Commit `6943b6ddc3` is the stated model (flip true bases to `instantiation: "false"`, strip
`compatible_printers`/`setting_id`/`filament_settings_id`, add `renamed_from` on the survivor).
Prefer the library's `fdm_filament_*` bases over a vendor-local copy. Phrozen's local
`fdm_filament_common` has drifted from the library's.
Canonical key order, written by `orca_profile_tool.py normalize` when it rewrites a file: `type`, `name`,
`renamed_from`, `inherits`, `from`, `setting_id`, `filament_id`, `instantiation`, then everything else in
the order you wrote it. Not enforced — a file that leads with `compatible_printers` passes `check`.
**Every vector-typed (`co*s`) key must be a JSON array.** Only `filament_type` is an outright error, but
`normalize` silently arrayifies five more (`filament_cost`, `filament_density`,
`temperature_vitrification`, `filament_max_volumetric_speed`, `filament_vendor`) and `check` fails when
it would. Every other vector key is on you — including `filament_start_gcode`, `filament_end_gcode`,
`filament_extruder_variant`, `compatible_printers` and the plate temperatures.
On a printer with extruder variants, a filament tunes these per variant too:
`filament_max_volumetric_speed`, `filament_flow_ratio`, `nozzle_temperature` and the retraction
overrides carry one value per variant of `filament_extruder_variant` (Standard, High Flow, …). The
exact key set is [`filament_options_with_variant`](extruder-variants.md#the-four-key-sets);
`slow_down_min_speed` and `pressure_advance` are not in it. Keep every such array at exactly that width, even where the
setting does not differ per variant, and measure the High Flow variant rather than copying Standard
([extruder-variants.md](extruder-variants.md#filament)).
## Bed temperature is twelve keys, not one
There is no single "bed temperature". Which plate key applies depends on `curr_bed_type`, whose six
selectable values (`btPC`, `btEP`, `btPEI`, `btPTE`, `btPCT`, `btSuperTack`; `btDefault` maps to no key)
`get_bed_temp_key()` turns into `cool_plate_temp`, `eng_plate_temp`, `hot_plate_temp`,
`textured_plate_temp`, `textured_cool_plate_temp` and `supertack_plate_temp` — each with an
`*_initial_layer` twin.
There is no single "bed temperature". The plate type selected for the printer (`Cool Plate`,
`Engineering Plate`, `High Temp Plate`, `Textured PEI Plate`, `Textured Cool Plate`, `Supertack Plate`)
picks one of six keys, each with an `_initial_layer` twin: `cool_plate_temp`, `eng_plate_temp`,
`hot_plate_temp`, `textured_plate_temp`, `textured_cool_plate_temp` and `supertack_plate_temp`.
`textured_cool_plate_temp` is the one most often forgotten. A printer with `support_multi_bed_types` off
hides the selector, and the printer preset's
`default_bed_type` decides which plate is selected for it, but `curr_bed_type` can still hold a stale
value carried over from another printer — so set every plate the printer plausibly has, as the sibling
presets in the bundle do.
`textured_cool_plate_temp` is the one most often forgotten. A non-BBL printer with
`support_multi_bed_types` off hides the plate selector and uses the printer preset's `default_bed_type`
(High Temp Plate, `hot_plate_temp`, when unset or invalid), but a loaded project or a CLI config can
still carry another plate. So set every plate the printer plausibly has, as the sibling presets in the
bundle do.
## Style
- Overrides, not full copies: an instantiated filament preset carries around a dozen non-meta keys,
and a library `@System` shim two or three. A preset that restates fifty-plus keys from its parent is
the pattern to move away from, not to copy. Commit `6943b6ddc3` is the stated model for converting such presets: flip true
bases to `instantiation: "false"`, strip their `compatible_printers`, `setting_id` and
`filament_settings_id`, and add `renamed_from` on the surviving selectable preset. A value every
printer tune of a product shares goes on the product's `@base`
([shared bases](shared-bases.md#levels)).
- Prefer the library's `fdm_filament_*` bases over a vendor-local copy; for a new preset, even in a
bundle whose older presets use one: a local copy drifts from the library's.
- Canonical key order, written by `orca_profile_tool.py normalize` when it rewrites a file: `type`,
`name`, `renamed_from`, `inherits`, `from`, `setting_id`, `filament_id`, `instantiation`, then
everything else in the order you wrote it. Not enforced on its own: a file that leads with
`compatible_printers` passes `check`.
- **Every vector-typed (`co…s`) key must be a JSON array.** Only a scalar `filament_type` is an outright
error; `normalize` silently arrayifies five more (`filament_cost`, `filament_density`,
`temperature_vitrification`, `filament_max_volumetric_speed`, `filament_vendor`), and `check` fails
when it would. Every other vector key is on you, including `filament_start_gcode`,
`filament_end_gcode`, `filament_extruder_variant`, `compatible_printers` and the plate temperatures.
+76 -69
View File
@@ -1,76 +1,78 @@
# `setting_id` and `filament_id`
Orca-generated ids are deterministic hashes of identity. **Never invent an id or copy a sibling's
`setting_id`.** Use `scripts/orca_profile_tool.py`; the two special cases are
`setting_id`.**
Use `scripts/orca_profile_tool.py`; the two special cases are
[a wrongly inherited filament id](#what-generate-id-does-and-does-not-fix) and
[BBL's authoritative setting ids](#bbls-exception-precisely).
`docs/HLSD/filament_id.md` is the authoritative design document for `filament_id` — the id landscape, the
checks CI runs, and the Bambu catalog map. This page is the tooling half.
`docs/HLSD/filament_id.md` is the authoritative design document for `filament_id`: the id landscape,
the checks CI runs, and the Bambu catalog map. This page is the tooling half.
| | `setting_id` | `filament_id` |
| --- | --- | --- |
| Identifies | one selectable preset | one filament **product** |
| Key hashed | `<vendor folder>/<type>/<name>` | `filament_product/<filament_vendor>/<filament_type>/<name-before-@>` |
| Shape | 16 base62 chars | `OF` + 6 base62 chars |
| Key hashed | `<vendor folder>/<type>/<name>` | `filament_product/<filament_vendor>/<filament_type>/<alias>`, using the resolved (inherited) first values and the name up to the first `@`, right-trimmed |
| Shape | 16 base62 characters | `OF` + 6 base62 characters |
| Required on | every `instantiation: "true"` preset | every **instantiated** filament, own or inherited |
| Forbidden on | bases (`instantiation != "true"`) | — (a base is exactly where it belongs) |
| Scope | globally unique across the tree | shared by every variant of the product, in every bundle |
| Forbidden on | bases (`instantiation` not `"true"`) | — (a product's root base is exactly where it belongs) |
| Scope | unique across the whole tree | shared by every preset of the product, in every bundle |
`<type>` is `machine` / `process` / `filament` — the vendor is the **folder** name (`BBL`), not the
display name (`Bambulab`). Renaming a preset changes its `setting_id`; renaming a filament, or editing
its `filament_vendor` or `filament_type`, also changes its `filament_id`.
`<type>` is `machine`, `process` or `filament`, and the vendor is the **folder** name (`BBL`), not the
display name (`Bambulab`). Renaming a preset changes its `setting_id`; renaming a filament's alias, or
editing its `filament_vendor` or `filament_type`, also changes its `filament_id`, and the old id is not
forwarded.
## The tool
Use `scripts/orca_profile_tool.py` with a subcommand:
`scripts/orca_profile_tool.py` takes a subcommand:
| Command | Does |
| --- | --- |
| `check` | everything CI's `profile_tool` step runs — see [validation.md](validation.md) |
| `check` | everything CI's `profile_tool` step runs; see [validation.md](validation.md#orca_profile_toolpy-check) |
| `generate-id` | writes `setting_id` and `filament_id` |
| `normalize` | rewrites profile files into their canonical shape |
| `trim` | deletes profile files no `<vendor>.json` list references |
| `trim` | deletes profile files no `<Vendor>.json` list references |
| `update-index` | rebuilds the `*_list` sections from the files on disk |
The order after adding, renaming or deleting files — each step feeds the next, so it is not
interchangeable — is `normalize` → `update-index` → `generate-id` → `check`.
The [authoring workflow](../SKILL.md#creating-or-modifying-a-profile) has the commands.
The order after adding, renaming or deleting files is `normalize` → `update-index` → `generate-id` →
`check`. Each step feeds the next, so it is not interchangeable. The
[authoring workflow](../SKILL.md#creating-or-modifying-a-profile) has the commands.
> **`trim` deletes.** It removes every profile file the index does not list — including the one you just
> added and have not registered yet. Register first, or skip `trim` entirely; it is a cleanup sweep, not
> **`trim` deletes.** It removes every profile file the index does not list, including the one you just
> added and have not registered yet. Register first, or skip `trim` entirely: it is a cleanup sweep, not
> part of landing a profile. Preview with `--dry-run`.
**Register, then mint.** The `filament_id` pass reads `<Vendor>.json`'s `filament_list`, not the
filesystem (the `setting_id` pass walks the filesystem, so a bundle whose index has not landed yet is
still assignable). A new filament file is therefore invisible to `generate-id`'s filament_id pass until
it is registered — its `setting_id` is written regardless.
still assignable). A new filament file is therefore invisible to `generate-id`'s `filament_id` pass
until it is registered; its `setting_id` is written regardless.
- `--dry-run` works on every writing command (`generate-id`, `normalize`, `trim`, `update-index`)
and writes nothing.
- `--filament-id` / `--setting-id` narrow `generate-id`; they exclude each other, and passing neither
writes both.
- `--vendor` is repeatable and narrows **only what is written** — the id is a function of the triple
- `--dry-run` works on every writing command (`generate-id`, `normalize`, `trim`, `update-index`) and
writes nothing.
- `--filament-id` / `--setting-id` narrow `generate-id` to one pass; they exclude each other, and
passing neither writes both.
- `--vendor` is repeatable and narrows **only what is written**: an id is a function of its own key
alone, so a narrowed run writes exactly what a full run would. An unknown vendor exits 1 before any
write. `--vendor` on `check` narrows the per-vendor checks only; the `setting_id` and `filament_id`
passes stay tree-wide.
- `--profiles DIR` points any command at another tree — see
- `--profiles DIR` points any command at another tree; see
[Checking a copy of the tree](validation.md#checking-a-copy-of-the-tree).
- `--profile-type` narrows `normalize`, `trim` and `update-index` to `machine_model`, `process`,
`filament` or `machine`.
- Exit codes: 0 clean, 1 errors found (`generate-id` still writes what it could), 2 argparse misuse.
- Output is ANSI-coloured; searching for the literal `[ERROR]` still works.
`generate-id` is **idempotent and byte-preserving** — BOM and CRLF kept, one key line touched per pass.
A legitimate `generate-id` diff is one or two changed lines per file: a new instantiated filament gets
both a `filament_id` and a `setting_id`, and a BBL file with a misspelled `settings_id` has that line
dropped and its value restored under the right key. `normalize` is the opposite by design — it rewrites
whole files into canonical shape — which is why `check` demands it already be a no-op. Some bundles have
CRLF committed (OrcaFilamentLibrary, Anycubic and RH3D among them), so a `normalize` pass there rewrites
every line — read the diff before committing it.
`generate-id` is **idempotent and byte-preserving**: BOM and CRLF are kept, and each pass touches only
its one key line. A legitimate `generate-id` diff is one or two changed lines per file: a new instantiated
filament gets both a `filament_id` and a `setting_id`. `normalize` is the opposite by design (it
rewrites whole files into canonical shape), which is why `check` demands it already be a no-op. A file
committed with CRLF line endings changes on every line under `normalize`; read the diff before
committing it.
On a clean tree `check` and `generate-id --dry-run` both exit 0 with zero findings. That is the
baseline to restore before opening a PR.
Exit 1 from `generate-id` does not mean nothing was written: it writes every id it can and reports the
rest, so read the diff before rerunning. On a clean tree `check` and `generate-id --dry-run` both exit 0
with zero findings; that is the baseline to restore before opening a PR.
## What `generate-id` does and does not fix
@@ -78,35 +80,37 @@ Writes:
- a `setting_id` into any instantiated preset that lacks one, or whose value does not match the formula;
- strips a `setting_id` from a base;
- deletes the misspelled `settings_id` key;
- deletes the misspelled key `settings_id`, moving its value to `setting_id` only on an instantiated BBL
preset that lacks one (everywhere else the old value is discarded and a fresh id minted);
- a `filament_id` into the id-less **root(s)** of an instantiated filament that resolves none;
- rewrites a **declared** `filament_id` that is not the mint of its own triple.
Refuses to write (reports only): a base62 collision between two products, an empty `filament_vendor` or
`filament_type`, a broken `inherits` chain, roots of one filament resolving divergent `(vendor, type)`
pairs.
`filament_type`, a broken `inherits` chain, and roots of one filament resolving different
`(filament_vendor, filament_type)` pairs.
**Does not fix: a preset that *inherits* a wrong `filament_id`.** This is check 2b, and it is the trap
most likely to bite. It happens when a branded filament inherits a generic for its settings:
**Does not fix: a preset that *inherits* a wrong `filament_id`.** This is check 2b (the label `docs/HLSD/filament_id.md` and the tool use), and it is the trap most likely to bite.
It happens when a branded filament inherits a generic for its settings:
```jsonc
{ "name": "Phrozen Aura PETG @Phrozen Arco 0.4 nozzle",
"inherits": "Generic PETG @System" } // resolves the OFL generic's id — wrong product
"inherits": "Generic PETG @System" } // resolves the library generic's id: wrong product
```
The preset resolves *an* id, so `generate-id` neither inserts nor rewrites, and `check` fails with
The preset resolves *an* id, so `generate-id` neither inserts nor rewrites one, and `check` fails with
`inherits filament_id "X" but its own triple "V/T/N" mints "Y"`.
Two fixes, in order of preference:
1. **Give the product a `@base` root** inheriting a material base (`fdm_filament_pet`,
`fdm_filament_pla`, …). No `fdm_filament_*` base carries a `filament_id`, so the filament now resolves
none and `generate-id` mints it for you. This is also the shape the rest of the tree uses.
2. **Declare the tool-computed key on the preset itself.** Use the expected value reported by `check`
or compute it with the function below; this is not a manually chosen id. Make sure the preset
resolves the right `filament_vendor` and `filament_type` first — with
neither set, the triple resolves through the generic parent and the branded product is minted
under vendor `Generic`. If you need the id before the file exists:
`fdm_filament_pla`, …) with its own `filament_vendor` and `filament_type`. No `fdm_filament_*` base
carries a `filament_id`, so the filament now resolves none and `generate-id` mints it on the root.
This is the product-root shape ([the three-part shape](filament-profiles.md#the-three-part-shape)).
2. **Declare the tool-computed id on the preset itself.** Use the expected value `check` reports, or
compute it with the function below; this is not a manually chosen id. First make sure the preset
resolves the right `filament_vendor` and `filament_type`: with neither set, the triple resolves
through the generic parent and the branded product is minted under vendor `Generic`. If you need the
id before the file exists:
```bash
python3 -c "import sys; sys.path.insert(0,'scripts'); from orca_profile_tool import generate_filament_id as g; print(g('Polymaker','PLA','PolyLite PLA'))"
@@ -114,36 +118,39 @@ Two fixes, in order of preference:
```
The quoting works unchanged in cmd and PowerShell; only swap `python3` for `py -3`.
`generate_preset_setting_id('<vendor folder>', '<type>', '<name>')` is the `setting_id` equivalent.
The `setting_id` equivalent is `generate_preset_setting_id('<vendor folder>', '<type>', '<name>')`.
A vendor's tune of a generic that keeps the `Generic X` alias is not this case: inheriting the
generic's id is correct there, because the product really is the library generic
([filament-profiles.md](filament-profiles.md#the-two-most-common-contributions)).
## BBL's exception, precisely
`RESERVED_VENDORS = {"BBL"}` covers **`setting_id` assignment only**, keyed on the *folder* name:
The exception covers **`setting_id` assignment only**, keyed on the *folder* name `BBL`:
- The tool never mints or replaces a BBL `setting_id`. A new instantiated BBL preset with no
`setting_id` therefore **cannot be fixed by the tool**, yet the presence rule still applies to it —
carry over Bambu's authoritative id by hand.
- The tool never mints or replaces a `setting_id` in `BBL/`: those are Bambu's own ids. A new
instantiated BBL preset with no `setting_id` therefore **cannot be fixed by the tool**, yet the
presence rule still applies to it: carry over Bambu's authoritative id by hand.
- BBL is not exempt from anything else: bases still get their `setting_id` stripped, ids must still be
globally unique, and BBL `filament_id`s are minted like everyone else's — every one of them is an
`OF*`.
unique across the tree, and BBL `filament_id`s are minted like everyone else's, as `OF…` ids.
## Ids other systems compose
No id from another system is the mint of a triple, so `check` rejects it like any other bad id — same
error, same remedy, whoever wrote it. Three such spaces exist near the tree; recognise them so you do
not copy one into a profile:
No id from another system is the mint of a triple, so `check` rejects one used as a `filament_id` like
any other bad id: same error, same remedy, whoever wrote it. Three such spaces exist near the tree;
recognise them so you do not copy one into a profile:
- **Bambu's `GF*` catalog** — external and opaque, correlated to Orca's ids by the generated
`resources/printers/bambu_filament_ids.json`. `GF` is a *prefix*, not a spelling the tree avoids: most
BBL `setting_id`s start with `G`, and `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_*`** — composed at runtime by the box (`QD_<series>_<vendor>_<typeidx>`), not a preset id.
- **`P` + 7 hex, and `"null"`** — what `CreatePresetsDialog.cpp` gives a *user*-created filament.
- **Bambu's `GF…` catalog**: external and opaque, correlated to Orca's ids by the generated
`resources/printers/bambu_filament_ids.json`. `blacklist.json` and
`BBL/filament/filaments_color_codes.json` reference Bambu catalog ids by design. The rule is about
`filament_id` and nothing else: every BBL `setting_id` starts with `G`, and that is Bambu's own
preset id, not a leaked catalog id.
- **Qidi's `QD_…`**: composed at runtime by the printer's filament box
(`QD_<series>_<vendor>_<typeidx>`), not a preset id.
- **`P` + 7 hex digits, and `"null"`**: what the app gives a *user*-created filament.
## Tests
`python3 -m unittest discover -s scripts/tests -t scripts` (`py -3 -m …` on Windows). Note the
`-t scripts` argument; without it the imports fail. CI runs them as the first, non-`continue-on-error`
step of the profile job — see [validation.md](validation.md#ci).
`python3 -m unittest discover -s scripts/tests -t scripts` (`py -3 -m …` on Windows) runs the tool's
unit tests. Note the `-t scripts` argument; without it the imports fail. CI runs them as the first,
non-`continue-on-error` step of the profile job; see [validation.md](validation.md#ci).
@@ -1,23 +1,23 @@
# Printer models and variants
Both live in `resources/profiles/<Vendor>/machine/*.json`; models go in `machine_model_list`, variants
and shared bases in `machine_list`. Every one of them is registered. Some vendors (Elegoo, Eryone,
InfiMech, FlyingBear) nest a further subfolder under `machine/`, so recurse rather than globbing
`machine/*.json`.
Both live in `resources/profiles/<Vendor>/machine/`; models go in `machine_model_list`, variants and
shared bases in `machine_list`. Every one of them is registered. A bundle may nest further subfolders
under `machine/`, so recurse rather than globbing `machine/*.json`.
## A `machine_model` is not a config preset
## `machine_model`: a record, not a config preset
It is parsed by a hand-written key switch, and only these keys are stored (`version` and `url` are
matched and discarded):
The loader reads a fixed set of keys from a `machine_model` and stores only these (`version` and `url`
are recognised and discarded):
`name`, `model_id`, `nozzle_diameter`, `machine_tech`, `family`, `bed_model`, `bed_texture`,
`hotend_model`, `default_materials`, `not_support_bed_type`, `image_bed_type`,
`bottom_texture_end_name`, `bottom_texture_rect`, `bottom_texture_rect_longer`, `middle_texture_rect`,
`use_double_extruder_default_texture`.
**Everything else is silently dropped.** Only `name` and `nozzle_diameter` are required. Dead keys ship
on real models today — `url`, `default_bed_type`, even a `desciption` typo — so a neighbour carrying a
key is no evidence it does anything. Printer config options belong on the `machine` preset, never here.
**Everything else is silently dropped**, a printer config key such as `default_bed_type` or a
misspelling included, so a neighbour carrying a key is no evidence it does anything. Printer config options belong on the `machine` preset, never here. The loader drops a model
silently if its index entry has no name or its `nozzle_diameter` yields no sizes; `check` also requires
the file's own `name`.
```json
{
@@ -36,28 +36,28 @@ key is no evidence it does anything. Printer config options belong on the `machi
| Field | Notes |
| --- | --- |
| identity | **the `name` of the `machine_model_list` entry**, which is what a variant's `printer_model` must equal. `check_name_consistency` forces it to equal the file's `name`, so they coincide. |
| `model_id` | a *separate* cloud/device printer type. Optional, and not required to be unique. Not the model's identity. Changing it changes device matching. |
| `machine_tech` | only `starts_with("SL")` means SLA; everything else is FFF. Write `FFF`; a few models write `FGF`, which is a label with no effect. |
| `nozzle_diameter` | `;`-separated string, one token per available size. Order is free (Qidi writes `0.4;0.2;0.6;0.8` to put the default first). This list is the authoritative set of legal `printer_variant` values. |
| `default_materials` | `;`-separated filament **preset names**. Used to preselect in the wizard *and* by `PresetBundle::load_installed_filaments` to auto-install a printer's filaments on first run, so a dangling entry costs a real user a filament. Not `,`; case-sensitive (`@System`). `check` fails on a dangling name here or in `default_filament_profile`. |
| identity | **the `name` of the `machine_model_list` entry**, which is what a variant's `printer_model` must equal. `check` forces it to equal the file's `name`, so they coincide. |
| `model_id` | a *separate* cloud/device printer type. Optional, and not required to be unique. Not the model's identity; changing it changes device matching. |
| `machine_tech` | only a value starting with `SL` means SLA; everything else is FFF. Write `FFF`; `FGF` behaves as FFF. |
| `nozzle_diameter` | `;`-separated string, one token per available size. Order is free (`0.4;0.2;0.6;0.8` puts the default first). This list is the authoritative set of legal `printer_variant` values. |
| `default_materials` | `;`-separated filament **preset names**, not `,`; case-sensitive (`@System`); order is ignored. Used to preselect filaments in the setup wizard *and* to install a printer's filaments on first run, so a dangling entry costs a real user a filament. Every name must exist (`check` fails on a name matching no filament file, here or in `default_filament_profile`), and every variant of the model needs at least one entry compatible with it (`validate_system`). |
| `family` | a wizard grouping label only; give every model one. |
### Assets
`bed_model`, `bed_texture` and `hotend_model` are paths relative to the **vendor folder** (by id).
Majority convention: `<Model>_buildplate_model.stl` and `<Model>_buildplate_texture.svg`. An empty string
is the legal "none", and is the norm for `hotend_model`.
`bed_model`, `bed_texture` and `hotend_model` are paths relative to the **vendor folder** (named by the
vendor id). Convention: `<Model>_buildplate_model.stl` and `<Model>_buildplate_texture.svg`. An
empty string is the legal "none", and is the norm for `hotend_model`.
**Nothing checks that the file exists.** A missing `hotend_model` falls back to
`resources/profiles/hotend.stl`; a missing `bed_model`/`bed_texture` just renders nothing. Broken
references already ship. Verify by hand.
**Nothing checks that the files exist.** A missing `hotend_model` falls back to
`resources/profiles/hotend.stl`; a missing `bed_model` makes the bed render as a generic custom bed, and a missing
`bed_texture` renders no texture.
Verify by hand, in exact case.
Every model also has a `<Model>_cover.png` in the vendor folder — treat it as required, not optional.
240×240 is the cap `scripts/optimize_cover_images.py` enforces and the size most covers already use.
A missing cover degrades to a placeholder in both the wizard and the sidebar.
Every model also has a `<Model>_cover.png` in the vendor folder; treat it as required, not optional.
240×240 is the cap `scripts/optimize_cover_images.py` enforces. A missing cover degrades to a placeholder in both the wizard and the sidebar.
## The `machine` variant
## `machine`: the variant
```json
{
@@ -79,116 +79,154 @@ A missing cover degrades to a placeholder in both the wizard and the sidebar.
Minimum viable key set: `type`, `name`, `from`, `instantiation`, `setting_id`, `inherits`,
`printer_model`, `printer_variant`, `nozzle_diameter`, `printable_area`, `printable_height`,
`default_print_profile`. The four keys without which the preset will not load at all are `name`,
`instantiation`, `printer_model` and `printer_variant`; `default_filament_profile` is an array
(`["Generic PLA @System"]`) and the model's `default_materials` a `;`-separated string. Unlike a
`machine_model`, a `machine` **is** config-loaded, so a key belonging to another preset type is a
reported error (a misspelled key is still silent).
`default_print_profile`. `default_filament_profile` is optional; when written it
is an array (`["Generic PLA @System"]`), while the model's `default_materials` is a `;`-separated
string. Unlike a `machine_model`, a `machine` **is** a config preset, so a key belonging to another
preset type is a reported error and is removed; a misspelled key is still dropped silently.
### `printer_variant` — three hard rules
### `printer_model` and `printer_variant`
1. Non-empty, and an exact member of the model's `;`-separated `nozzle_diameter` list.
2. `printer_model` non-empty and naming a model of this vendor.
3. In validation mode, for instantiated presets only: split `printer_variant` on `+`, each token must
start with a number (a trailing non-numeric suffix such as `HF` is ignored), and the resulting **set**
must equal `set(nozzle_diameter)`.
1. `printer_model` is non-empty and names a model of this bundle exactly.
2. `printer_variant` is non-empty and an exact token of that model's `;`-separated `nozzle_diameter`
list.
3. For instantiated presets, when validating: split `printer_variant` on `+`; each token must start
with a number (a trailing non-numeric suffix such as `HF` is ignored), and the resulting **set** must
equal the set of `nozzle_diameter` values.
Rules 1 and 2 are loader-enforced — failing either drops the preset *and* the whole bundle. Rule 3 only
raises a validation error: the preset still loads, but the validator exits non-zero.
Rules 1 and 2 are loader-enforced: failing either discards the whole bundle. Rule 3 only raises a
validation error: the preset still loads, but the validator exits non-zero.
`nozzle_diameter` lists one entry **per physical nozzle**; `printer_variant` lists the **distinct**
diameters joined with `+`. Snapmaker U1 is the worked case: `["0.4","0.4","0.6","0.6"]` against
`"0.4+0.6"` — it passes because the comparison is on sets.
`nozzle_diameter` lists one entry **per extruder**; `printer_variant` lists the **distinct** diameters
joined with `+`: `["0.4","0.4","0.6","0.6"]` against `"0.4+0.6"` passes because the comparison is on
sets.
The conventional values are `0.2`, `0.25`, `0.4`, `0.5`, `0.6`, `0.8` and `1.0`. Suffixed forms
(`0.4HF`, `0.6HF`, `0.8HF`, `0.4HS`) are Flashforge-only and the `+` form is rare. A variant is **not**
required to be unique within a model — Volumic ships `EXO42 IDRE`, `… COPY MODE` and `… MIRROR MODE` all
at `0.4` under the one model `EXO42 IDRE`.
Write `printer_variant` as a bare diameter matching the model's list, with no unit. The conventional
values are `0.2`, `0.25`, `0.4`, `0.5`, `0.6`, `0.8` and `1.0`; a suffixed form (`0.4HF`, `0.4HS`) or
the `+` form is legal under rule 3. A `printer_variant` is **not** required to be unique within a
model: an IDEX model's normal, `COPY MODE` and `MIRROR MODE` presets can all be `0.4`.
The converse is **unchecked**: a nozzle size in the model's list with no matching variant is offered in
the wizard and resolves to nothing. `Wanhao France`'s `D12 500 PRO M2 DIRECT` ships that bug today.
the wizard and resolves to nothing. A variant whose `printer_model` names a sibling model by mistake
leaves its own model's size in exactly that state.
### Other fields worth knowing
### Other keys
- `default_print_profile` is a **scalar**, matched by exact preset name. Not a `;` list. The named
- `default_print_profile` is a **scalar**, matched by exact preset name; not a `;` list. The named
process must be compatible with this printer through its resolved list or condition.
`validate_slice` attempts to select it and rejects generic Default fallbacks, but compatibility
updates can choose another compatible preset. Check the exact default reference yourself.
- `default_filament_profile` is an **array**, one name per element.
- `printable_area` is an array of `"XxY"` strings — four points for a rectangle, one per segment for a
delta or circular bed.
- `gcode_flavor` is usually set once in the base; `klipper`, `marlin`, `marlin2` and `reprapfirmware`
cover nearly every shipped printer.
- `printer_settings_id` is junk — most files carrying it disagree with their own name. Do not copy it
when cloning a bundle.
- `min_layer_height` / `max_layer_height` are **machine** keys (per extruder), never process keys.
updates can choose another compatible preset, and `check` does not resolve the name. Check the exact
default reference yourself.
- `default_filament_profile` is an **array**, one name per element. Entry 0 is the filament preselected
when the printer is chosen; entry *i* is the preferred replacement when filament *i* is incompatible,
and any listed name outranks an unlisted one. The validator checks every entry. The list of a
printer's filaments is the model's `default_materials`: a new filament goes into `default_materials`;
put it first in `default_filament_profile` only if it should become the preselected one.
- `printable_area` is an array of `"XxY"` strings: four points for a rectangle; a delta or other circular bed
is a polygon with one point per segment.
- A non-BBL printer shows the plate selector only with `support_multi_bed_types` `"1"`; otherwise it
uses its `default_bed_type` (a plate name such as `"Textured PEI Plate"`; High Temp Plate when unset).
Filaments still set every plate
([twelve keys](filament-profiles.md#bed-temperature-is-twelve-keys-not-one)).
- `gcode_flavor` is usually set once in the base; the common values are `klipper`, `marlin`, `marlin2`
and `reprapfirmware`.
- `printer_settings_id` does nothing in a preset file: the app replaces it with the selected preset's
name before slicing. Omit it, and do not copy it when cloning a bundle.
- `min_layer_height` / `max_layer_height` are **machine** keys (one per extruder), never process keys.
## Bases
Nearly every machine-bearing vendor registers a base literally named `fdm_machine_common`, and Klipper
vendors add `fdm_klipper_common` on top of it. Two levels is the usual depth.
The conventional machine root is a base named `fdm_machine_common`, with `fdm_klipper_common` on top
of it for Klipper printers. Which values a hardware
family or model base holds, and when adding one pays off, is in [shared-bases.md](shared-bases.md).
**There is no leading-underscore convention for bases.**
## Adding a printer to an existing bundle
1. Choose the names first — model, variant(s), process(es); everything else references them.
1. Choose the names first: model, variant(s), process(es); everything else references them
([naming.md](naming.md)).
2. Add the model (`machine_model_list`) and one `machine` variant per nozzle; the minimum key sets are
above. Bed assets and `<Model>_cover.png` go directly in `<Vendor>/`.
3. Add at least one process per variant naming it in `compatible_printers`
([process-profiles.md](process-profiles.md#adding-a-quality-tier-or-a-nozzles-processes)).
4. Register everything (or run `update-index`), bump the version, run the id tool, validate.
4. Add the variants to the filaments they should offer, and set the model's `default_materials` so every
variant has a compatible entry.
5. Register everything (`update-index`), bump the version, run the id tool, validate: the
[authoring workflow](../SKILL.md#creating-or-modifying-a-profile).
## Adding a nozzle variant
1. Extend the model's `nozzle_diameter` (`"0.4"` → `"0.4;0.6"`).
2. Add the variant preset. Either inherit the shared base (the usual choice) or the 0.4 sibling (Elegoo,
BBL, Prusa and Qidi do this — smaller diff, but the sibling's edits now reach this file too).
3. Override what actually changes with nozzle: `nozzle_diameter`, `printer_variant`,
`default_print_profile`, `default_filament_profile`, `min_layer_height`/`max_layer_height`, and
2. Add the variant preset. Either inherit the shared base (the usual choice), or the 0.4 sibling
(a smaller diff, but the sibling's edits now reach this file too). Follow the bundle.
3. Override what actually changes with the nozzle: `nozzle_diameter`, `printer_variant`,
`default_print_profile`, `default_filament_profile`, `min_layer_height` / `max_layer_height`, and
retraction if the vendor tunes it.
4. Add at least one process for the new nozzle — see [process-profiles.md](process-profiles.md).
4. Add at least one process for the new nozzle (see [process-profiles.md](process-profiles.md)), and
extend the filaments' `compatible_printers` so at least one `default_materials` entry covers the new
variant.
5. Register both, bump the version, run the id tool, validate.
## Multi-extruder, IDEX and tool-changers
## Multi-extruder, IDEX and tool changers
Per-extruder vectors are **silently resized** to the nozzle count, with no error. Padding repeats the
**first** value, not the last — `["0.4","0.6"]` on a 4-nozzle machine becomes `0.4, 0.6, 0.4, 0.4`.
Longer vectors are truncated.
Per-extruder vectors hold one value per extruder (`len(nozzle_diameter)`), and a wrong length raises no
error: a short vector acts as padded with its **first** value, not the last (`extruder_offset`
`["0x0","50x0"]` on a 4-extruder machine reads as `0x0, 50x0, 0x0, 0x0`), and entries beyond the
extruder count are never read.
Note the two sizing families: the plain per-extruder keys (`extruder_offset`, `extruder_colour`,
`extruder_printable_height`, `min_layer_height`, `max_layer_height`, `nozzle_diameter`) are sized to the
extruder count, while `printer_options_with_variant_1` (`retraction_length`, `z_hop`, `wipe`,
`nozzle_type`, the rest of the retraction family) is sized to `printer_extruder_variant` instead.
Note the two sizing families. The plain per-extruder keys (`printer_extruder_options`: `extruder_type`,
`nozzle_diameter`, `default_nozzle_volume_type`, `extruder_printable_height`, `min_layer_height`,
`max_layer_height`, …, given in full with the [key sets](extruder-variants.md#the-four-key-sets), plus
`extruder_offset` and `extruder_colour`) hold one value per extruder. The variant sets
(`retraction_length`, `z_hop`, `wipe`, `nozzle_type`, the `machine_max_*` limits at stride 2;
[the full lists](extruder-variants.md#the-four-key-sets)) are sized to the variant length:
`len(printer_extruder_variant)`, or one variant per extruder when the resolved preset writes no layout,
since `extruder_variant_list` defaults to one `Direct Drive Standard` per extruder
([widths](extruder-variants.md#widths)). `extruders_count` is a printer-tab field, not a preset key; the
loader drops it.
- Give **one entry per extruder** for ordinary per-extruder vectors such as `extruder_offset`,
`extruder_colour`, `min_layer_height` and `max_layer_height`; size the variant-dependent family
to `printer_extruder_variant` instead.
A single `["0x0"]` `extruder_offset` on a dual or multi-tool machine — which already ships — pads every
toolhead to the same offset, so the offset never applies.
- Overriding `nozzle_diameter` to a different count without re-stating every per-extruder vector is the
other half of the trap — `Snapmaker U1 (0.4+0.6 nozzle)` inherits 5-entry vectors against 4 nozzles.
`extruder_colour`, `min_layer_height` and `max_layer_height`. Size the variant sets to the variant
length × stride, one value per variant (per extruder when there is no layout; a (normal, silent)
pair for the `machine_max_*` limits), even where the values are the same; the loader keeps only the first value of a list-less printer's variant arrays, so declare
the layout when the extruders differ. A single `["0x0"]` `extruder_offset` on a dual or
multi-extruder machine pads every extruder to the same offset, so the offset never applies.
- Overriding `nozzle_diameter` to a different count without restating every per-extruder vector is the
other half of the trap: a 4-extruder preset on a 5-extruder base inherits 5-entry vectors against 4
extruders.
The reverse is silent too: a base is stored resized to its own `printer_extruder_variant` (one variant
when it writes none, whatever its extruder count), so a wider variant array on it reaches the children
as its first value padded ([composition](extruder-variants.md#padding-truncation-and-composition)).
`check` does not judge the base on its own; where its extruders need different values, declare the
layout on the base.
Copy targets: `Custom/machine/fdm_toolchanger_common.json` + `Custom/machine/MyToolChanger 0.4
nozzle.json` (a clean minimal variant on a base that gives every vector five entries), and
`Ratrig/machine/RatRig V-Core 4 IDEX 300 0.4 nozzle.json` for IDEX. The BBL extruder-variant machinery
(`extruder_variant_list`, `printer_extruder_id`, `default_nozzle_volume_type`) is used by a handful of
vendors — do not copy it into a new bundle (`nozzle_volume_type` itself is not a machine-preset key).
Structure to copy: `Custom/machine/fdm_toolchanger_common.json` + `Custom/machine/MyToolChanger 0.4
nozzle.json` (a minimal variant on a base that gives the per-extruder vectors five entries), and
`Ratrig/machine/RatRig V-Core 4 IDEX 300 0.4 nozzle.json` for IDEX. Take the structure from them and
the widths from the [sizing equation](extruder-variants.md#sizing-equation). Both are list-less, so
every variant array holds one value per extruder and the loader keeps only the first: fine while every
extruder shares the same retraction and limits. Add the extruder-variant layout
(`extruder_variant_list`, `printer_extruder_variant` / `printer_extruder_id`,
`default_nozzle_volume_type`) when the hardware has swappable nozzle volume types or mixed extruder
types, or as soon as one extruder needs its own value in a variant key; how to author it, and the
matching process and filament variants, is in [extruder-variants.md](extruder-variants.md).
(`nozzle_volume_type` itself is not a machine-preset key.)
## Custom G-code
The keys are `machine_start_gcode`, `machine_end_gcode`, `change_filament_gcode`,
`machine_pause_gcode`, `before_layer_change_gcode` and `layer_change_gcode`. Both a single string with
embedded `\n` and a JSON array of lines are legal and both are in use — do not convert one into the
other. Conditionals are `{if …}` / `{elsif …}` / `{else}` / `{endif}`; `{elsif}` is rare but real (Qidi's
`layer_change_gcode` uses it).
`machine_pause_gcode`, `before_layer_change_gcode` and `layer_change_gcode`. Each is one string with
embedded `\n`. Never split G-code into a JSON array of lines: the loader joins array elements with `,`
into a single line (a one-element array is equivalent to the string): a two-element
`machine_start_gcode` becomes one line, `PRINT_START …,SET_PRESSURE_ADVANCE ADVANCE=0.046`.
Conditionals are `{if …}` / `{elsif …}` / `{else}` / `{endif}`.
Placeholder errors only surface when the config is actually expanded, which means `validate_slice`:
Placeholder errors only surface when the G-code is actually expanded, which means `validate_slice`:
```bash
./scripts/check_profile.sh --vendor "<Vendor>" validate_slice
# Windows: scripts\check_profile.bat -Vendor "<Vendor>" validate_slice
```
What the sweep covers is in [validation.md](validation.md#validate_slice); no `CP TOOLCHANGE START` in
the output means `change_filament_gcode` never expanded.
What the sweep covers is in [validation.md](validation.md#validate_slice); a printer whose output has no
`CP TOOLCHANGE START` fails it, because its `change_filament_gcode` never expanded.
@@ -1,90 +1,94 @@
# Preset naming
A preset's `name` is the loader's key, not decoration. The index registers it; `inherits`,
`compatible_printers` and the `default_*` keys reference it by the exact string; `renamed_from` depends
on it; and `setting_id` / `filament_id` hash it (see [ids.md](ids.md)). Two presets of one type in a
bundle may not share a name (`check_preset_name_uniqueness`). Treat a name change as an identity change,
not a relabel.
Naming is convention only where the loader does not parse it. What the loader actually acts on:
| Type | Shape | Acted on |
| --- | --- | --- |
| `machine_model` | `<Model>` | the exact string, named by a variant's `printer_model` |
| `machine` | `<Model> <nozzle> nozzle` | the exact string, named by `compatible_printers`; `printer_variant` must equal a nozzle diameter |
| `process` | `<lh>mm <Quality> @<target>` | the exact string when referenced or selected; the `@<target>` half is a label |
| `filament` | `<Product> @<target>` | text before the first `@` is the **alias**, used for shadowing; the rest is a label |
A preset's `name` is its identity, not decoration. The index registers it; `inherits`,
`compatible_printers`, `printer_model` and the `default_*` keys reference it by the exact,
case-sensitive string; `renamed_from` migrates it; `setting_id` and `filament_id` hash it
([ids.md](ids.md)). Treat a name change as an identity change that needs
[migration](vendor-bundle.md#renamed_from), not a relabel. Which part of each name the loader acts on
is the table in [SKILL.md](../SKILL.md#names); this page holds the conventions.
## `machine_model`
`<Model>` — the vendor-prefixed model name (`Bambu Lab X1 Carbon`, `Creality K1`, `Prusa CORE One`). A
variant names it verbatim in `printer_model`; a mismatch makes the variant invalid. `check_name_consistency`
forces the index entry to equal the file's `name`, and the variant's `printer_model` targets this string
([A `machine_model` is not a config preset](machine-profiles.md#a-machine_model-is-not-a-config-preset)).
It is also the `<Model>_cover.png` and bed-asset stem.
`<Model>`, vendor-prefixed: `Bambu Lab X1 Carbon`, `Creality K1`, `Prusa CORE One`. Each variant's
`printer_model` names it verbatim (a mismatch discards the bundle), and its index entry equals the
file's `name`. It is also the stem of `<Model>_cover.png` and, by convention, of the bed assets
(`<Model>_buildplate_model.stl`).
## `machine` (variant)
`<Model> <nozzle> nozzle` is near-universal (`Bambu Lab X1 Carbon 0.4 nozzle`). `printer_variant` holds
the bare nozzle (`0.4`) and must be an exact member of the model's `nozzle_diameter` list — the hard
rules are in [The `machine` variant](machine-profiles.md#the-machine-variant). Casing varies
(`nozzle` / `Nozzle`): match the bundle, not this page. A variant that is not nozzle-specific (a special
toolhead, a multi-material build) may drop the suffix — still an exact reference. Bases are named
`fdm_machine_common` / `fdm_<vendor>_common`.
`<Model> <nozzle> nozzle` is near-universal (`Bambu Lab X1 Carbon 0.4 nozzle`). Casing varies
(`nozzle` / `Nozzle`): match the bundle, not this page. A variant that is not nozzle-specific (a
special toolhead, a multi-material build, IDEX copy and mirror modes such as
`<Model> COPY MODE (0.4 nozzle)`) may drop or reshape the suffix; it is still an exact reference. `printer_variant`
holds the nozzle token: `0.4`, a suffixed `0.4HF`, or `0.4+0.6` for mixed nozzles
([rules](machine-profiles.md#printer_model-and-printer_variant)).
## `process`
`<layer height>mm <quality> @<target>` — [process-profiles.md](process-profiles.md#naming) has the
quality ladder and the `fdm_process_*` base names. The quality label stays before `@` and the printer
target after it: a printer model in the quality slot leaves the tier undescribed. The `@<target>` is a
human label, not a reference: it usually does not equal a real variant, and compatibility comes from the
resolved `compatible_printers` list or condition.
`<layer height>mm <quality> @<target>`. The quality word stays before `@` and the printer target after
it: a printer model in the quality position leaves the tier undescribed. The `@<target>` is a label,
and need not equal any variant name; compatibility comes from `compatible_printers` or the
condition. The quality ladder and per-nozzle labels are in
[process-profiles.md](process-profiles.md#naming).
## `filament`
`<Product> @<target>`. The product half is what `filament_id` hashes and what survives as the **alias** up
to the first `@`; the target half is a label except for reserved forms:
`<Product> @<target>`. The product half, up to the first `@` and right-trimmed, is the **alias**:
shadowing matches on it and `filament_id` hashes it. The target half is a label, except for the
reserved forms:
- `@base` — a non-instantiated product root. `@base` is convention; a base is really identified by
`instantiation: "false"` and no `setting_id` ([the three-part shape](filament-profiles.md#the-three-part-shape)).
- `@System` — the OrcaFilamentLibrary selectable shim, and the convention for an all-printer product
(`<Product> @System`, empty `compatible_printers`); not enforced, so a deviation is worth a review
comment. The literal `Generic <mat> @System` is load-bearing for 3MF/project recovery, beyond the
alias rule ([alias shadowing](filament-profiles.md#alias-shadowing)).
- `@<Vendor>`, `@<Vendor> <Model>`, `@<Vendor> <Model> <nozzle> nozzle` — printer tunes, BBL's shape.
Other vendors differ (a bare model, a printer serial, Creality's `@<Model>-all`). Specificity is judged
from `compatible_printers`, not the name
- `@base`: a non-instantiated product root. Convention only; a base is really
`instantiation: "false"` without `setting_id`
([the three-part shape](filament-profiles.md#the-three-part-shape)).
- `@System`: the OrcaFilamentLibrary selectable shim, and the convention for an all-printer product
(`<Product> @System`, empty `compatible_printers`). Not enforced, so a deviation is worth a review
comment. The literal `Generic <mat> @System` is also load-bearing for project recovery
([alias shadowing](filament-profiles.md#alias-shadowing)).
- Printer tunes. BBL's shape is the reference: `@<Vendor>` (vendor-wide), `@<Vendor> <Model>` (one
model), `@<Vendor> <Model> <nozzle> nozzle` (one variant). Other vendors differ: a bare model
(`QIDI ABS-GF@Q2-Series`), a printer serial, Creality's `@<Model>-all`. Judge specificity from
`compatible_printers`, never from the name
([one variant, one profile](filament-profiles.md#overlapping-coverage-one-variant-one-profile-per-product)).
- Color is not part of the product name: `<Product> <Color>` presets are not authored; the color is
chosen at runtime
([color is a runtime property](filament-profiles.md#color-is-a-runtime-property)).
- No colour in the product name: `<Product> <Colour>` presets are not authored; colour is chosen at
runtime ([colour](filament-profiles.md#colour-is-a-runtime-property)).
## Checking names
Check every newly added profile's `name` against its type and role: model, selectable preset or base.
Apply the same checks to an intentional name change. The human-readable naming shapes are not enforced
by `check`: inspect the added or renamed profiles in the diff, using neighbouring names as context and
following the bundle's established style where the type-specific conventions allow variation.
For bases (`instantiation: "false"`), use the type-specific conventions:
## Bases
| Type | Base names |
| --- | --- |
| `machine` | `fdm_machine_common`, `fdm_<vendor>_common`, or an established machine-family base name |
| `process` | `fdm_process_*`, including shared roots and per-layer-height / per-nozzle bases such as `fdm_process_single_0.20` |
| `filament` | `fdm_filament_*` material roots or `<Product> @base` product roots |
| `machine` | `fdm_machine_common`, `fdm_<vendor>_common`, `fdm_klipper_common`, or an established machine-family base |
| `process` | `fdm_process_*`: shared roots and per-layer-height or per-nozzle bases such as `fdm_process_single_0.20` or `fdm_process_<vendor>_<lh>_nozzle_<n>` |
| `filament` | `fdm_filament_*` material roots, `<Product> @base` product roots |
Shared base names across bundles are intentional, including product roots such as `Fiberon PA6-CF @base`.
Investigate a newly authored base that retains an unrelated selectable preset's name from a copy.
There is no leading-underscore convention. Base names repeat across bundles by design:
every bundle may have its own `fdm_process_common`, and a product root such as `Fiberon PA6-CF @base`
can exist in both the library and a vendor. Investigate a newly authored base that kept an unrelated
selectable preset's name from a copy.
**Name uniqueness is checked by CI.** `check_preset_name_uniqueness` checks type + name within each
bundle. `check_machine_model_name_uniqueness` checks model names across the entire tree, even with
`--vendor`: `Preset::get_printer_type` matches `printer_model` against all vendors' models and returns
the first match, so a duplicate makes lookup depend on vendor order. Both run as part of
`python3 scripts/orca_profile_tool.py check`.
## Uniqueness
## Not the same as the filename
- Type + name is unique within a bundle, indexed or not; `check` enforces it.
- `machine_model` names are unique across the whole tree; `check` enforces it even with `--vendor`.
Printer-type lookup matches `printer_model` against every vendor's models and takes the first, so a
duplicate makes it depend on vendor order.
- At load, two selectable presets with one name discard the bundle, and a duplicate across vendors
is a validator error. Two bases with one name, or a base and a selectable preset, load silently and
the first in the index wins; an unindexed twin is therefore one `sub_path` edit away from becoming
the parent every child resolves to.
The loader keys off `name`, and a filename that disagrees usually still loads. Index `name` must equal the
file's `name`, and the filename should match `sub_path`; a mismatch that differs only in case breaks
another platform ([cross-platform paths](validation.md#cross-platform-paths)).
## Filenames and paths
The loader keys off `name`, and a filename that disagrees usually still loads, but keep the filename
equal to the `name` and to the index `sub_path`. Match the exact case of every `sub_path` and asset
filename: Linux filesystems distinguish case even when a macOS or Windows checkout does not, and
preset-name references are case-sensitive on every platform. Avoid Windows-invalid characters
(`< > : " | ? *`), reserved device names such as `CON` and `NUL` (with any extension), and trailing
spaces or dots in a path component; a space right before `.json` is not a trailing space.
## Checking names
Check the `name` of every newly added profile, and every intentional rename, against its type and role
(model, selectable preset or base). `check` does not enforce the shapes on this page: inspect the
added or renamed presets in the diff, use neighbouring names as context, and follow the bundle's
established style where the conventions allow variation. Preserve shipped names during ordinary
tuning; renaming a shipped selectable preset needs `renamed_from`.
@@ -1,14 +1,15 @@
# Process profiles
Processes live in `resources/profiles/<Vendor>/process/` — selectable leaves and shared bases alike, and
Processes live in `resources/profiles/<Vendor>/process/`, selectable leaves and shared bases alike, and
every one of them is registered in `process_list`. There are no global processes shared across vendors.
## Naming
`"<layer height>mm <quality> @<target>"` — near-universal, so match it.
`"<layer height>mm <quality> @<target>"` is near-universal, so match it: the quality word before `@`,
the printer label after it ([naming.md](naming.md#process)).
Follow the bundle's existing quality vocabulary. BBL's common ladder relates the quality word to
the layer-height / nozzle ratio; it is a naming convention, not a loader constraint:
Follow the bundle's existing quality vocabulary. BBL's common ladder relates the quality word to the
layer height / nozzle ratio; it is a naming convention, not a loader constraint:
| Quality | Ratio | 0.2 nozzle | 0.4 | 0.6 | 0.8 |
| --- | --- | --- | --- | --- | --- |
@@ -20,49 +21,50 @@ the layer-height / nozzle ratio; it is a naming convention, not a loader constra
| Extra Draft | 0.7× | 0.14 | 0.28 | 0.42 | 0.56 |
This is the `fdm_process_single_<lh>_nozzle_<n>` ladder; 0.4 is commonly the unsuffixed nozzle default.
Match neighbouring names rather than renaming shipped tiers to fit the table.
Newer BBL printers add High Quality, Balanced Quality and Strength tiers. Match neighbouring names rather
than renaming shipped tiers to fit the table. On a model with several nozzles, processes for the other
nozzles usually carry the nozzle in the label (`0.30mm Standard @BBL X1C 0.6 nozzle`); follow the bundle.
The `@target` is a human label, not a reference: most do not equal any real printer variant name.
The `@target` is a human label, not a reference: it need not equal any printer variant name.
Compatibility comes from the resolved list or condition, not this label.
## Shape
A selectable leaf's only truly universal keys are `type`, `setting_id`, `name` and `instantiation`;
`inherits` and `from` are near-universal — plus compatibility. No slicing key is universal; even
`layer_height` is more often inherited than restated. A base has `type`, `name`, `instantiation`, almost
always `from`, and **no** `setting_id`.
A selectable leaf has `type`, `setting_id`, `name` and `instantiation`, normally `inherits` and
`from`, plus compatibility; its slicing keys, `layer_height` included, normally come from its bases. A
base has `type`, `name`, `instantiation`, `from`, and **no** `setting_id`.
**Target shape: a 7-key leaf.** `OrcaArena` is the cleanest model —
**Target shape: a 7-key leaf.** `OrcaArena` is the cleanest model:
`fdm_process_common` → `fdm_process_arena_common` → `fdm_process_arena_<lh>_nozzle_<n>` → leaf, where the
leaf carries only `type`, `name`, `inherits`, `from`, `setting_id`, `instantiation`,
leaf carries only `type`, `name`, `inherits`, `from`, `setting_id`, `instantiation` and
`compatible_printers`, and the per-nozzle base holds the layer height and all eight line widths.
[shared-bases.md](shared-bases.md#levels) says which level each process setting belongs to.
BBL, WonderMaker and Z-Bolt are uniform in *layering* — every leaf inherits a base, names its printers
directly and holds no layer height of its own — but not in key count. Imitate BBL's layering, not its
content: its leaves carry doubled `print_extruder_variant` arrays that no single-variant vendor needs.
BBL's *layering* is a model too (every leaf inherits a base, names its printers directly and holds no
layer height of its own), but not its content: its leaves carry multi-variant `print_extruder_variant`
arrays that no single-variant vendor needs ([extruder-variants.md](extruder-variants.md#process)).
Nearly every vendor ships its own `fdm_process_common` as the inherits-less root. Those files are not
identical; copying another vendor's version into a new bundle is normal.
A bundle has its own `fdm_process_common` as the inherits-less root, since a process inherits only
inside its bundle; starting a new bundle's from another vendor's copy is fine.
Beware leaf-inherits-leaf: Prusa chains several levels deep through sibling leaves, and Elegoo and
Flashforge do it too, so editing one selectable process silently changes others. Check a leaf's children
before editing it.
Beware leaf-inherits-leaf: a bundle may chain selectable processes several levels deep, so editing one
silently changes others. Check a leaf's children before editing it.
## Compatibility
Most leaves set `compatible_printers` directly; some inherit it from a base, and Prusa's fall through to
A leaf sets `compatible_printers` directly, inherits it from a base, or falls through to
`compatible_printers_condition`. After resolving `inherits`, **every selectable process has one or the
other** — that is the invariant to review against. Unlike filaments, inheriting `compatible_printers` is
other**: that is the invariant to review against. Unlike filaments, inheriting `compatible_printers` is
legitimate for a process, and no check enforces its presence.
- A non-empty `compatible_printers` makes `compatible_printers_condition` **dead code**. Use one or
the other.
- A condition that fails to parse means *compatible with everything* — a warning, not an error. A typo
- A non-empty `compatible_printers` makes `compatible_printers_condition` **dead**. Use one or the
other.
- A condition that fails to parse means *compatible with everything*: a warning, not an error. A typo
widens compatibility instead of narrowing it.
- Matching is `boost::regex` **`regex_match`** — a full-string match, which is why every shipped
condition wraps its keyword in `.*`. Because it is boost rather than `std`, `.` also spans the newlines
inside `printer_notes`.
- A `printer_notes` keyword that prefixes another model's keyword matches both. Prusa guards it:
- A regex in a condition must match the **whole** string, so wrap the keyword in `.*`; `.` also spans
the newlines inside `printer_notes`.
- A `printer_notes` keyword that prefixes another model's keyword matches both. Guard it with a
character class after the keyword, and combine terms with `and`:
```
printer_notes=~/.*PRINTER_MODEL_COREONE[^_a-zA-Z0-9].*/ and nozzle_diameter[0]==0.4 and printer_notes=~/.*HF_NOZZLE.*/
@@ -70,76 +72,84 @@ legitimate for a process, and no check enforces its presence.
The `[^_a-zA-Z0-9]` exists because `PRINTER_MODEL_COREONE_L` also contains `PRINTER_MODEL_COREONE`.
`compatible_printers` is almost always one element. A leaf listing a whole model family is where a newly
added printer is usually forgotten.
A leaf listing a whole model family is where a newly added printer is usually forgotten.
## What to review per nozzle
## Values to review per nozzle
| Key group | Review |
| --- | --- |
| `line_width` and per-region widths | resolved widths suit the nozzle and layer height |
| `layer_height`, `initial_layer_print_height` | within the printer's limits |
| `layer_height`, `initial_layer_print_height` | within the printer's `min_layer_height` / `max_layer_height` |
| print speeds | consistent with flow limits and hardware tuning |
| shell layers, wall loops, accelerations, support Z distances | preserve the intended thickness, motion and support behavior |
| shell layers, wall loops, accelerations, support Z distances | preserve the intended thickness, motion and support behaviour |
**A common starting pattern is nozzle + 0.02 mm**: 0.22 / 0.42 / 0.62 / 0.82 / 1.02. In that pattern, at 0.4,
`inner_wall_line_width`, `sparse_infill_line_width`, `skin_infill_line_width` and
**A common starting pattern is line width = nozzle + 0.02 mm**: 0.22 / 0.42 / 0.62 / 0.82 / 1.02. In
that pattern, at 0.4, `inner_wall_line_width`, `sparse_infill_line_width`, `skin_infill_line_width` and
`skeleton_infill_line_width` widen to 0.45 and `initial_layer_line_width` to 0.5; at 0.2,
`initial_layer_line_width` widens to 0.25. Also derived, and easily missed:
`ironing_inset = line_width / 2` (0.11 / 0.21 / 0.31 / 0.41).
These are examples, not required values; preserve intentional vendor tuning and percentage/automatic
widths, and validate their resolved values.
`ironing_inset = line_width / 2` (0.11 / 0.21 / 0.31 / 0.41). These are examples, not required values;
preserve intentional vendor tuning and percentage or automatic widths, and validate their resolved
values.
`min_layer_height` and `max_layer_height` are machine keys — no process file sets them.
`min_layer_height` and `max_layer_height` are machine keys; no process file sets them.
## Slice-time content checks
### Slicing limits
`Print::validate()` enforces four rules at slice time:
Slicing rejects a process that breaks one of these (the message in italics):
1. `initial_layer_print_height` ≤ min `nozzle_diameter`
2. `layer_height` ≤ min `nozzle_diameter` — *"Layer height cannot exceed nozzle diameter."*
3. `line_width` and the seven per-region widths (inner/outer wall, sparse infill, internal solid infill,
top surface, skin, skeleton) > `layer_height` — *"Line width too small"*. `support_line_width` only
when the object has support or a raft; `initial_layer_line_width` is never checked.
4. every width ≤ 5 × max `nozzle_diameter` — *"Line width too large"*
1. `initial_layer_print_height` ≤ the smallest `nozzle_diameter` (with a raft, the nozzle of the raft's
first-layer extruder).
2. `layer_height` ≤ the smallest `nozzle_diameter`: *"Layer height cannot exceed nozzle diameter."*
3. `line_width` and the seven per-region widths (inner and outer wall, sparse infill, internal solid
infill, top surface, skin, skeleton) > `layer_height`: *"Line width too small"*.
`support_line_width` is checked only when the object has support or a raft;
`initial_layer_line_width` is never checked. A width that resolves to 0 (automatic) is skipped.
4. Every width ≤ 5 × the largest `nozzle_diameter`: *"Line width too large"*.
Two further rules cover `bridge_line_width` (≤ nozzle diameter; > `layer_height` unless `thick_bridges`
and `thick_internal_bridges` are both on). The sweep starts from printer defaults rather than
enumerating every process. **A new non-default process gets no dedicated slice coverage in CI.**
Two further rules cover `bridge_line_width`: it must not exceed the nozzle diameter, and must exceed
`layer_height` unless `thick_bridges` and `thick_internal_bridges` are both on. The slice sweep starts
from printer defaults rather than enumerating every process: **a new non-default process gets no
dedicated slice coverage in CI.**
## What CI checks on a process
Structure, not content: `process_list` name consistency **and** index coverage the other way, two files
claiming one process name, the `extruder_clearance_radius` / `extruder_clearance_max_radius` conflict
pair, duplicate JSON keys, a file `normalize` would rewrite, and the five `setting_id` rules (the fifth
rejects the misspelled key `settings_id`). `compatible_printers` presence is checked for **filaments
only**.
pair, duplicate JSON keys, a file `normalize` would rewrite, the five `setting_id` rules (present on
selectable presets, absent from bases, equal to the formula outside `BBL/`, unique across the tree, and
no misspelled key `settings_id`), and the variant arrays: every array of `print_options_with_variant`
exactly `variant length × stride` wide in each selectable process that writes it, and a written
`print_extruder_id` one entry per variant
([variant arrays](validation.md#variant-arrays)). `compatible_printers` presence is checked for
**filaments only**.
Note the C++ loader derives a missing `setting_id` on the fly, so the validator will not fail a process
without one — only `orca_profile_tool.py check` catches it. Running the validator alone gives a false
all-clear.
The loader derives a missing `setting_id` on the fly, so the validator accepts a process without one;
only `orca_profile_tool.py check` catches it.
Running the validator alone gives a false all-clear.
## Silent failures specific to processes
- **Unknown or misspelled keys are discarded with no error and no warning.** They ship all over the
process tree, both plain typos (`inital_layer_height`, `tree_support_bramch_diameter_angle`,
`sparse_infill_patter`) and keys copied from other slicers that Orca never defined.
- Keys on the tool's `OBSOLETE_KEYS` list (`adaptive_layer_height`, `overhang_totally_speed`, …) are
rejected by `check`'s normalization pass across preset types; `normalize` removes them.
The additional per-key obsolete warnings read `filament/` only.
- A dangling `compatible_printers` inside an `instantiation: "false"` base is invisible to
`check_preset_references`: a base never becomes a `Preset` at all (its config goes into `config_maps`
and the loader returns early), so it is in no collection for the check to walk.
- Orphan bases that nothing inherits are scattered through the tree — usually the leftover of a
half-finished nozzle addition.
- **Unknown or misspelled keys are discarded with no error and no warning**, both plain typos
(`inital_layer_height`, `tree_support_bramch_diameter_angle`, `sparse_infill_patter`) and keys
copied from other slicers that Orca never defined.
- Keys on the tool's obsolete list (`adaptive_layer_height`, `overhang_totally_speed`, …) are rejected
by `check`'s normalization pass across preset types; `normalize` removes them. The additional per-key
obsolete warnings read `filament/` only.
- A dangling `compatible_printers` inside an `instantiation: "false"` base is reported only through a
selectable child that inherits it unchanged; it goes unreported when every child overrides the list,
or when the base has no instantiated children.
- Nothing flags an orphan base that nothing inherits, usually the leftover of a half-finished nozzle
addition.
## Adding a quality tier or a nozzle's processes
1. Choose the layer height and quality label using the vendor's existing ladder.
2. If the vendor has per-nozzle bases, add one (`fdm_process_<vendor>_<lh>_nozzle_<n>`) with the layer
1. Choose the layer height and quality label using the bundle's existing ladder.
2. If the bundle has per-nozzle bases, add one (`fdm_process_<vendor>_<lh>_nozzle_<n>`) with the layer
height, nozzle-appropriate line widths, `initial_layer_print_height` and `ironing_inset`.
3. Add the leaf: 7 keys, `compatible_printers` naming the exact printer variant(s).
4. Register both in `process_list`, parent first. Bump the version, run the id tool, validate.
5. Slice this process explicitly with its intended printer; the sweep gives non-default tiers no
dedicated coverage. If it is a printer's `default_print_profile`, verify the exact name and
resolved compatibility too — the sweep may fall back or select another compatible process.
4. Register both in `process_list`, parent first, bump the version, run the id tool and validate: the
[authoring workflow](../SKILL.md#creating-or-modifying-a-profile).
5. Slice this process explicitly with its intended printer
([on a copy of the tree](validation.md#checking-a-copy-of-the-tree)); the sweep gives non-default
tiers no dedicated coverage. If it is a printer's `default_print_profile`, verify the exact name and
resolved compatibility too: the sweep may fall back or select another compatible process.
@@ -1,37 +1,43 @@
# Reviewing a profile change
Start with delivery, identity and backward compatibility, then check the affected preset types.
The table highlights gaps that need human review. What CI *does* run:
[validation.md](validation.md).
Run `./scripts/check_profile.sh` on the applied diff first ([validation.md](validation.md) says what CI
runs), then work through the items below: delivery, identity and backward compatibility first, then the
affected preset types. The table lists the gaps CI cannot see, so only a reviewer catches them.
| Not checked by CI | Consequence |
| --- | --- |
| The `version` bump | The change never reaches an upgrading user |
| A misspelled setting key | Setting silently has no effect |
| The `version` bump | The change never reaches an upgrading user; an absent `version` hides the vendor from the setup wizard |
| A misspelled setting key | The setting silently has no effect |
| A filename Windows cannot check out, or one that differs from its `sub_path` only in case | Works on the author's machine, breaks the bundle on another platform |
| `bed_model` / `bed_texture` / `hotend_model` pointing at a missing asset | Bed renders as Custom, hotend falls back to the generic model |
| `bed_model` / `bed_texture` / `hotend_model` / cover pointing at a missing asset | A missing bed model renders a generic custom bed and a missing texture renders none, the hotend falls back to the generic model, the cover shows a placeholder |
| A nozzle size in a model's list with no matching variant | The size is offered and resolves to nothing |
| A non-default process | `validate_slice` gives non-default quality tiers no dedicated coverage |
| Whether the intended default survived compatibility selection | The sweep can select a different compatible preset |
| A dangling `compatible_printers` inside an `instantiation: "false"` base | A base never becomes a `Preset`, so the reference check never sees it (a bad `inherits` in a base *is* caught) |
| A dangling `compatible_printers` inside a base whose children all override it (or that has no instantiated children) | The reference check walks resolved selectable presets, so it reports a base's list only through a child that inherits it unchanged (a bad `inherits` in a base *is* caught) |
| A base nothing inherits | Dead weight, usually the leftover of an unfinished nozzle addition |
| A `renamed_from` whose old name is still a live preset | The redirect is inert while a live preset carries that name |
| A preset differentiated only by color, or an all-printer library preset without `@System` | Per-color presets split one product across ids and the selector fills with near-duplicates; CI stays green |
| Per-extruder vector length on a multi-nozzle printer | Silently padded (with the **first** value) or truncated |
| A name that ignores its type's convention — a model in a `process` quality slot, or an unrelated target label left in a copied preset | The selector misrepresents the preset's quality or intended printer |
| A preset differentiated only by colour, or an all-printer library preset without `@System` | Per-colour presets split one product across several ids and the selector fills with near-duplicates; CI stays green |
| Plate temperatures for plates the printer has | The user's plate reads an unset or inherited temperature |
| Per-extruder vector length on a multi-extruder printer | Silently padded (with the **first** value) or truncated |
| A new variant appended instead of inserted at its variant index, a variant array widened on a base with a shorter list, or a variant a filament/process lacks | Values shift onto the wrong extruder, are cut before any child inherits them, or resolve to the first variant: High Flow silently gets Standard values |
| A name that ignores its type's convention: a printer model in a `process` quality position, or an unrelated target label or base name left in a copied preset | The selector misrepresents the preset's quality or intended printer |
| Values: temperatures, speeds, widths, pressure advance | A wrong value prints wrong while CI stays green |
## 1. Was the vendor `version` bumped?
For **every** bundle whose folder the diff touches, `resources/profiles/<Vendor>.json` must have its
`version` incremented — last component, carrying `.99` into the third component. A library change
means bumping `OrcaFilamentLibrary.json`.
`version` incremented: last component, carrying `.99` into the third component. A library change means
bumping `OrcaFilamentLibrary.json`.
*Why:* nothing in CI checks it, and `PresetUpdater` reinstalls only when `vendor_ver < resource_ver` —
without a bump the change reaches neither an upgrading user nor the author's own running app.
*Why:* nothing in CI checks it, and the app reinstalls a bundled profile set only when its version is
newer than the installed one: without a bump the change reaches neither an upgrading user nor the
author's own running app. Without any `version` the vendor vanishes from the setup wizard, and neither
`check` nor the validator reports it.
## 2. Was the index rebuilt, and does the diff contain only this change?
`check` now fails on an unregistered file, on an index `update-index` would reorder, and on a file
`normalize` would rewrite — so a PR that skipped them arrives red, and you do not have to spot the
`check` fails on an unregistered file, on an index `update-index` would reorder, and on a file
`normalize` would rewrite, so a PR that skipped them arrives red and you do not have to spot the
omission yourself. Three things are still yours:
- **The index diff belongs to this change.** `update-index` rewrites whole `*_list` sections. If the
@@ -41,11 +47,15 @@ omission yourself. Three things are still yours:
registration; `validate_custom` detects the break only for names covered by released fixtures.
- **`normalize` edits content, not just layout.** It drops `version` and `is_custom_defined` from preset
files, removes obsolete keys, deletes six print-speed keys from filament profiles, and resolves
`extruder_clearance_radius` against `extruder_clearance_max_radius` by keeping the larger.
Check that the keys it removed were meant to go.
`extruder_clearance_radius` against `extruder_clearance_max_radius` by keeping the larger
([what normalize changes](validation.md#normalize-and-update-index-are-part-of-the-check)). Check that
the keys it removed were meant to go.
Obsolete keys fail `check`'s normalization pass and should be removed with `normalize`.
`check` also reports per-key obsolete warnings for filament profiles in the selected vendors.
Index order is dependency order, not alphabetical: parents and include templates before the presets
that use them, then, in name order, the entries that neither depend on nor are depended on by another
entry of their own list (such as a leaf filament whose only parent is in the library), and any entry on
a dependency cycle. Judge a hand-placed entry only by
`update-index --dry-run`: if it reports nothing to rebuild, the position is not a finding.
*Why:* the index is the loader's only entry point. Out-of-order entries fail with `can not find inherits`
and take the whole vendor bundle down; an unindexed file gets reviewed, merged and never loads.
@@ -55,8 +65,8 @@ and take the whole vendor bundle down; an unindexed file gets reviewed, merged a
No hand-typed or copied `setting_id` / `filament_id`. Instantiated presets have a `setting_id`; bases do
not. `check` enforces all of that; what it cannot tell you is whether the identity *should* have moved.
A rewritten or removed `filament_id` means a product's identity moved — a rename, or an edited
`filament_vendor` / `filament_type` — and the old id is not forwarded anywhere. Confirm that was
A rewritten or removed `filament_id` means a product's identity moved (a renamed alias, or an edited
`filament_vendor` / `filament_type`), and the old id is not forwarded anywhere. Confirm that was
intended, and that a new id is not a rename in disguise.
*Why:* a duplicate `filament_id` on one printer makes AMS spool matching a coin toss; a copied
@@ -64,136 +74,204 @@ intended, and that a new id is not a rename in disguise.
## 4. Does anything disappear for existing users?
A rename, a deletion, or a flip of `"instantiation": "true"` → `"false"` on a shipped preset removes the
name from the preset collection. It needs `renamed_from` on a successor — and only one preset may claim a
given old name. The claimed old name must **not** still be a live preset; the redirect is inert if it is.
A rename, a deletion, or a flip of `"instantiation": "true"` → `"false"` on a shipped selectable preset
removes the name from the preset collection. It needs `renamed_from` on a selectable successor
([renamed_from](vendor-bundle.md#renamed_from)), and only one preset may claim a given old name. The
claimed old name must **not** still be a live preset; the redirect is inert if it is.
*Why:* user presets inheriting it die with `can not find parent <name> for config <file>!`; 3MF-embedded
presets are dropped with no error at all. Commit `33923464ae` reverted exactly this for Cubicon;
`6943b6ddc3` redid it correctly. CI's `validate_custom` catches the shipped-name case — but not an inert
`renamed_from`.
`6943b6ddc3` redid it correctly with `renamed_from`. CI's `validate_custom` catches the shipped-name
case, but not an inert `renamed_from`.
## 5. Is `compatible_printers` right?
Exact printer **variant** names, non-empty on every instantiated filament outside OrcaFilamentLibrary
and written in the preset's own file — golden rule 6, with the flattened-vs-own-key trap in
[filament-profiles.md](filament-profiles.md#compatible_printers). Watch for a nozzle-specific variant that
inherited or copied the base's full printer list, and for two presets of one product with overlapping
lists — duplicate combobox entries and an ambiguous AMS match.
Exact printer **variant** names, non-empty on every instantiated filament outside OrcaFilamentLibrary,
and written in the preset's own file ([SKILL.md rule 9](../SKILL.md#rules); the resolved-vs-own-key trap
is in [filament-profiles.md](filament-profiles.md#compatible_printers)). A `machine_model` name instead
of a variant name is the usual mistake: `check` passes it, the validator reports
`references unknown compatible_printers`. Watch for a nozzle-specific tune that inherited or copied the
base's full printer list, and for two presets of one product with overlapping lists: duplicate combobox
entries and an ambiguous AMS match.
Two presets of one product (`filament_id`) must not share a variant. Resolve it by specificity: move the
variant to the most specific preset and remove it from the more general ones — preferred over deleting a
profile. Then repoint the machine's `default_filament_profile` and the model's `default_materials` at the
profile that now covers it. See
variant to the most specific preset and remove it from the more general ones, which is preferred over
deleting a profile. Then repoint the machine's `default_filament_profile` and the model's
`default_materials` at the profile that now covers it. See
[one variant, one profile](filament-profiles.md#overlapping-coverage-one-variant-one-profile-per-product).
*Why:* real shipped bugs twice (`b7b3418baf` "showing up everywhere", `ff83aa41ef` duplicate Flashforge
entries). The Python `check` passes on an overlap; only the full `check_profile.sh` (`validate_system`)
reports `Ambiguous AMS filament match`.
*Why:* real shipped bugs twice (`b7b3418baf` filaments "showing up everywhere", `ff83aa41ef` duplicate
Flashforge entries). The Python `check` passes on an overlap; only the full `check_profile.sh`
(`validate_system`) reports `Ambiguous AMS filament match`.
## 6. Model ↔ variant ↔ process consistency
## 6. One product, one all-printer preset; colour is not a preset
- New nozzle size → the model's `nozzle_diameter` list extended, a variant with a matching
`printer_variant`, and at least one process listing that variant.
- `default_print_profile` is one exact name (not a `;` list), and that process's resolved
compatibility list or condition includes this printer.
No presets that differ only by colour: `filament_id` identifies a product, and the colour comes from the
spool at runtime. An all-printer library product is a `<Product> @System` shim with an empty
`compatible_printers` ([colour](filament-profiles.md#colour-is-a-runtime-property)).
*Why:* per-colour presets pass every check, so this is a review call.
## 7. Model ↔ variant ↔ process consistency
- A new nozzle size → the model's `nozzle_diameter` list extended, a variant with a matching
`printer_variant`, and at least one process listing that variant. Every size in the model's list has a
variant (unchecked).
- `default_print_profile` is one exact name (not a `;` list), and that process's resolved compatibility
list or condition includes this printer.
- `default_filament_profile` is an array of names that exist.
- Each variant of the model has at least one compatible entry in the model's `default_materials`.
*Why:* an unlisted `printer_variant` is a hard bundle-load failure. Default process selection is
weaker: the sweep attempts the named default, then updates compatibility and rejects generic Default
fallbacks. Another compatible process can conceal a bad reference, so inspect it even after a pass.
*Why:* an unlisted `printer_variant` is a hard bundle-load failure. Default process selection is weaker:
the sweep attempts the named default, then updates compatibility and rejects generic Default fallbacks,
so another compatible process can conceal a bad reference. Inspect it even after a pass.
## 7. Types and spellings
## 8. Types and spellings
Every value a string or an array of strings; `filament_type` an array; `instantiation` the string
`"true"`/`"false"` — golden rule 7. Check index metadata and model `nozzle_diameter` especially;
wrong types there can abort loading for **every** vendor.
`"true"` / `"false"`; custom G-code one string, never an array of lines ([SKILL.md rule 5](../SKILL.md#rules)).
Check index metadata and model `nozzle_diameter` especially: wrong types there can abort loading for
**every** vendor.
The part only a reviewer can do: check new setting keys against `src/libslic3r/PrintConfig.cpp`. A
misspelled key is silently discarded (rule 8), the single most common way a profile edit does nothing
while CI stays green.
misspelled key is silently discarded ([rule 6](../SKILL.md#rules)), the single most common way a profile
edit does nothing while CI stays green.
## 8. Blast radius of a base edit
## 9. Blast radius of a base edit
A change to `fdm_*_common.json` reaches every child at once. Ask which presets it touches — several
reverts in this repo are exactly this (`41d1b0d3c8`, `dc491166a8`). Also check whether the edited leaf has
children of its own: Prusa, Flashforge and Elegoo all chain leaf-inherits-leaf several levels deep.
A change to `fdm_*_common.json` or any other base reaches every child at once. Ask which presets it
touches: several reverts in this repo are exactly this (`41d1b0d3c8`, `dc491166a8`). Also check whether
the edited leaf has children of its own: a bundle may chain leaf-inherits-leaf several levels deep. A
newly added base that nothing inherits is dead weight, and a dangling
`compatible_printers` inside a base is reported only through a child that inherits it unchanged. A diff
that only moves values between presets and bases must leave every selectable preset loading what it
loaded: ask for the `compare` result ([shared bases](shared-bases.md#nothing-loads-differently)), and
check each new base against the [balance rules](shared-bases.md#balance).
## 9. Do the numbers make sense for the nozzle?
## 10. Do the numbers make sense for the nozzle and material?
Check resolved widths and layer heights against the nozzle, and flow limits / pressure advance
against the actual hardware and material. The patterns in [process-profiles.md](process-profiles.md)
are examples, not mandatory values; [filament-profiles.md](filament-profiles.md) explains what to
revisit for a nozzle change. A cloned preset's unchanged MVS needs particular scrutiny.
Check resolved widths and layer heights against the nozzle, temperatures against the material (PLA
values under an ASA name print wrong), and flow limits / pressure advance against the actual hardware
and material. The patterns in [process-profiles.md](process-profiles.md#values-to-review-per-nozzle)
are examples, not mandatory values; [filament-profiles.md](filament-profiles.md#tuning-per-nozzle-and-per-variant)
explains what to revisit for a nozzle change. A cloned preset's unchanged volumetric speed needs
particular scrutiny.
Settings tuned for real hardware cannot be verified by reading the diff. Say so rather than approving
numbers nobody measured.
## 10. Asset references (not checked anywhere)
## 11. Plate temperatures
A filament sets the plate temperature for every plate the printer plausibly has, as its siblings do;
`textured_cool_plate_temp` is the one most often forgotten
([twelve keys](filament-profiles.md#bed-temperature-is-twelve-keys-not-one)).
## 12. Asset references (not checked anywhere)
`bed_model`, `bed_texture`, `hotend_model` and `<Model>_cover.png` exist under
`resources/profiles/<vendor folder>/`. Broken references already ship; nothing checks them.
`resources/profiles/<vendor folder>/`, in exact case. Nothing checks them.
## 11. `default_materials` (checked by CI)
## 13. `default_materials` and `default_filament_profile` (checked by CI)
`check` fails on a `default_materials` / `default_filament_profile` name that resolves to no system
filament, so a dangling entry no longer reaches review. When compatibility moves between profiles of a
product, the machine's `default_filament_profile` and the model's `default_materials` must be repointed at
the most specific profile that still covers the variant, dropping generic entries that no longer apply —
the same [specificity rule](filament-profiles.md#overlapping-coverage-one-variant-one-profile-per-product)
applies when adding or fixing defaults. Scope the run while working on one vendor:
`check` fails on a `default_materials` / `default_filament_profile` name that matches no filament file,
and `validate_system` on a variant with no compatible system filament in `default_materials`, so a
dangling entry no longer reaches review. When compatibility moves between profiles of a product, the
machine's `default_filament_profile` and the model's `default_materials` must be repointed at the most
specific profile that still covers the variant, dropping generic entries that no longer apply; the same
[specificity rule](filament-profiles.md#overlapping-coverage-one-variant-one-profile-per-product) applies
when adding or fixing defaults. Scope the run while working on one vendor:
```bash
python3 scripts/orca_profile_tool.py check --vendor "<Vendor>" # py -3 on Windows
```
## 12. Per-extruder vector lengths (not checked)
## 14. Per-extruder vectors (not checked) and variant arrays (widths and layout checked)
One entry per extruder for the plain per-extruder vectors; the `printer_options_with_variant_1` keys are
sized to `printer_extruder_variant` instead. A wrong length is silently padded — repeating the **first**
value, not the last — or truncated. The two sizing families and the worked cases are in
One entry per extruder for the plain per-extruder vectors; the variant sets are sized to the variant
length, `len(printer_extruder_variant)` or one per extruder when the resolved preset has no layout (a
per-extruder difference in those keys still needs the layout, since the loader keeps only the
first value of a list-less printer's arrays). A wrong length is silently padded, repeating the **first**
value, not the last, or truncated. The two sizing families and the worked cases are in
[machine-profiles.md](machine-profiles.md#multi-extruder-idex-and-tool-changers).
## 13. Non-default processes get no slice coverage
`check` reports a variant array of any type that is not exactly the `variant length × stride` width
of the selectable preset that writes it, one value included, as an error whatever the values;
`check --strict` also reports one that reaches a selectable preset at another width. A base is not
judged on its own. On a machine it
also reports layout keys that disagree: `printer_extruder_variant` / `printer_extruder_id` not the
flattening of `extruder_variant_list`, a variant without its extruder's `extruder_type` prefix, a
`default_nozzle_volume_type` the extruder does not list, a list of several variants without the pair, a
pair without the list that puts several variants on one extruder. On a machine or a process, an id
array, written or inherited, whose length differs from its variant list is an error; the id and
layout rules judge the composed preset without `--strict`. Two layout findings are warnings: a
pair without the list that `single_extruder_multi_material` off would replace at load, and a process
without `print_extruder_id` whose variants repeat. The full rules are in
[variant arrays](validation.md#variant-arrays). `check_variant_names` separately holds every variant
string, `extruder_type`, `nozzle_volume_type` and `default_nozzle_volume_type` to the engine's enums
in every bundle, BBL included, failing on a dead variant, a legacy spelling and a variant list that
names one variant twice ([variant names](validation.md#variant-names)).
On a multi-variant printer, still check by hand:
- that a new variant's values sit at its variant index (machine limits as a (normal, silent) pair at
`2 × index`) in every file of the chain that restates the key, `include` templates included;
- that each process lists every (extruder id, variant) pair its printers can select;
- that no array was widened on a base with a shorter variant list: the loader cuts it before the
children inherit it ([composition](extruder-variants.md#padding-truncation-and-composition)), and
`check`, even `--strict`, composes without that cut, so it passes;
- that only keys in [the four sets](extruder-variants.md#the-four-key-sets) carry per-variant values;
- that the High Flow variants carry measured values rather than copies
([checking and testing](extruder-variants.md#checking-and-testing)).
## 15. Non-default processes get no slice coverage
`validate_slice` starts from printer defaults; it does not enumerate every process. Slice a new or
changed non-default tier explicitly with its intended printer.
## 14. Housekeeping worth a nit, not a block
`"from"` other than `"system"` (the preset-bundle loader ignores it, though the CLI's config-file loader
rejects anything but `system`/`user`/`User`), `printer_settings_id` copied from another
vendor, and a filename that disagrees with the preset's `name` (common; the loader keys off `name`).
## 15. Cross-platform filenames and paths (not checked)
Check for Windows-invalid characters, reserved device names, trailing path-component spaces/dots,
and case mismatches in `sub_path` or asset paths. See [cross-platform paths](validation.md#cross-platform-paths).
changed non-default tier explicitly with its intended printer
([on a copy of the tree](validation.md#checking-a-copy-of-the-tree)).
## 16. Do the preset names follow the conventions?
Check **every newly added profile and intentional name change**, including models and bases,
against [the naming conventions](naming.md#checking-names). Preserve shipped names during
ordinary tuning; renaming a shipped selectable preset requires the migration in item 4.
Check **every newly added profile and intentional name change**, including models and bases, against
[the naming conventions](naming.md#checking-names): no printer model in a process quality position, no
target label or base name left over from a copied preset, the bundle's established style. Preserve
shipped names during ordinary tuning; renaming a shipped selectable preset requires the migration in
item 4.
*Why:* CI checks name uniqueness, but does not enforce the naming conventions. Catch naming
mistakes before the names ship and existing projects depend on them.
*Why:* CI checks name uniqueness, but does not enforce the naming conventions. Catch naming mistakes
before the names ship and existing projects depend on them.
## 17. Cross-platform filenames and paths (not checked)
Check for Windows-invalid characters, reserved device names, trailing path-component spaces or dots,
and case mismatches between `sub_path` or asset references and the files on disk. See
[filenames and paths](naming.md#filenames-and-paths).
## 18. Housekeeping worth a nit, not a block
`"from"` other than `"system"` (the bundle loader ignores it, though loading the file as a CLI config
rejects anything but `system` / `user` / `User`), `printer_settings_id` copied from another vendor,
redundant overrides that restate the parent's value, siblings that each repeat a value their base could
hold ([shared bases](shared-bases.md)), and a filename that disagrees with the preset's
`name` (the loader keys off `name`).
---
## Reporting the review
A finding is: **one defect**, its file, what breaks at runtime or in CI, and the fix. Split independent
defects into separate findings even when they live in one file — five id problems in one bullet get one
fix and four survivors.
A finding is **one defect**: its file (or quoted lines), what breaks at runtime or in CI, and the fix.
Split independent defects into separate findings even when they live in one file: five id problems in
one bullet get one fix and four survivors. Say which findings `check` or the validator reports and which
only a reader catches: a missing version bump, a misspelled key and wrong temperatures pass CI, so a
contributor who only reruns the tools fixes what CI flags and resubmits the rest.
Severity discriminates only if it is earned:
| Severity | Means |
| --- | --- |
| blocker | the bundle fails to load, or a preset is unreachable at runtime |
| major | CI fails, or existing users lose a preset |
| minor | wrong-but-working: dead keys, `from`, naming, redundant overrides |
| major | CI fails, existing users lose a preset, or a value prints wrong while CI stays green (PLA temperatures under an ASA name; a misspelled key whose intended value differs from the inherited one) |
| minor | wrong but working: redundant or dead keys that change nothing, `from`, naming |
Compute every number and id (`orca_profile_tool.py`, a scripted count) or omit it — one invented count
makes a reader stop trusting the right ones. Report a command's result only if you ran it.
Compute every number and id (`orca_profile_tool.py`, a scripted count) or omit it: one invented count
makes a reader stop trusting the right ones. Report a command's result only if you ran it. End with a
verdict: can it merge as it stands?
@@ -0,0 +1,254 @@
# Shared bases
Use this when drafting several presets at once, after `fix-variant`, or when sibling presets repeat the
same values. Each value is written once, on the base of the group it is true for, and a selectable
preset holds its identity and what makes it different. The work has two halves: choosing the groups,
which is judgment, and moving the values, which must leave every preset loading exactly what it loaded
before.
## When
- **Drafting** a printer family, a quality ladder or a product line: place each setting at its
[level](#levels) before writing any preset, then write the presets as overrides
([Rule 7](../SKILL.md#rules)).
- **After `fix-variant`.** It resizes each array in the selectable preset that writes it and never
moves or deletes a value ([variant arrays](validation.md#variant-arrays)). A family whose presets all
wrote one value now repeats the widened array in every preset, and a preset that restated what it
inherits now restates it wider.
- **Converting full copies**: a machine that inherits nothing, or a filament that restates fifty-plus
keys, is the style to move away from ([filament style](filament-profiles.md#style)).
Out of scope:
- **BBL.** Its profiles are synced from BambuStudio: a restructure is overwritten by the next sync and
makes every later sync diff unreadable. Fix BBL values in the file that holds them.
- **OrcaFilamentLibrary bases for one vendor's values.** A library base reaches every bundle's
filaments. Put a vendor's shared values on a base in its own bundle (a product `@base`, or a vendor
base that inherits the library's), and change a library base only for a value true of every filament
below it in every bundle.
- **Families the change does not touch.** Restructure the presets you are already changing; a
bundle-wide pass is a change of its own. Commit a restructure without any value change, so `compare`
alone verifies it.
## Nothing loads differently
A restructure changes where values are written, never what a selectable preset loads. Presets keep
their `name` and `setting_id`, and user presets and projects refer to a system preset by name and store
their own changes against what it loads, so an unchanged load changes nothing for users. Prove it with
the bundled helper, from the repository root (every subcommand takes `--profiles <dir>` to work on a
copy of the tree):
```bash
python3 .claude/skills/orca-profiles/scripts/shared_settings.py snapshot <before.json> # before any edit
python3 .claude/skills/orca-profiles/scripts/shared_settings.py compare <before.json> # after: "0 difference(s)", exit 0
```
`snapshot` records every selectable preset of every bundle as the loader stores it: the parent's stored
config, each `include` at the width its file wrote, the preset's own keys, then every variant array
resized to the preset's own variant list ([composition](vendor-bundle.md#inherits-and-include)). It
models the step `check` does not see: a base is stored after its own resize, so an array wider than a
base's list reaches its children cut. It also leaves out what the loader reads from each file and never
hands down (`name`, `type`, `from`, `instantiation`, `inherits`, `include`, `setting_id`,
`renamed_from`, `description`, `version`, `url`, `is_custom_defined`), so those keys never move to a
base. Nor does it record `print_settings_id`, `printer_settings_id` or `filament_settings_id`: the app
replaces them with the selected presets' names before slicing, so a file's value never counts. Delete
them from the presets you restructure rather than moving them to a base.
It does not know the built-in defaults, so `compare` lists a key written on one side only (no file of
the preset's chain writes it on the other) apart from the differences, and exits 1 for either. A
difference is a value the edit changed: undo it, or make it a separate, deliberate change. A one-sided
key is no change only when its written value is the option's default in `PrintConfig.cpp`: confirm
each, as when a preset that loaded the default by leaving a key out must now write it
([Balance 5](#balance)). A value equal to the default needs writing nowhere when no base above writes
another: delete it from the presets rather than moving it, and confirm the one-sided keys.
## Levels
A base stands for a level of the vendor's catalogue, and a setting lives at the level that determines
it. The test for a shared value: if it had to change for one preset of the group, should it change for
all of them? If yes, it is the group's and goes on the group's base. A value that is only equal today
(two unrelated printers with the same acceleration) stays in each preset: a base built on coincidence
is later edited for one preset and silently changes the others. For a default with exceptions
([Balance 5](#balance)), ask the question of the presets that inherit the default.
The tables give each setting's usual level; the test decides for a given bundle: where each toolhead
(Bowden or Direct Drive) has its own default filament, `default_filament_profile` follows the
toolhead, not the nozzle. Levels run
coarse to fine, and a chain need not visit every level: each preset or base inherits the next coarser
level that has a base.
| Machine level | Base | Settings it determines |
| --- | --- | --- |
| Vendor | `fdm_machine_common`, `fdm_<vendor>_common` | the vendor's defaults for every printer |
| Firmware | `fdm_klipper_common`, `fdm_marlin_common` | `gcode_flavor`, G-code that calls the firmware's macros (layer change, pause, filament change), `host_type`, `print_host`, the thumbnail format |
| Hardware family: models that share a frame, motion system, toolhead or extruder layout | `fdm_<vendor>_<family>_common` (`fdm_qidi_x3_common`, `fdm_machine_eryone_ER20_common`) | the `machine_max_*` limits, `extruder_clearance_*`, the toolhead's retraction where every nozzle shares it, `z_hop` and wipe, fitted hardware such as `auxiliary_fan`, the extruder count and per-extruder vectors, the variant layout ([Printer rule 2](extruder-variants.md#printer-machine)) |
| Model: one `machine_model`, which for an IDEX printer includes its mode | the default-nozzle preset where the bundle hangs its other nozzle presets off it; otherwise `fdm_<vendor>_<model>_common` | `printable_area`, `printable_height`, `bed_exclude_area`, the model's start G-code, a COPY or MIRROR mode's settings |
| Nozzle: the selectable preset | none | `printer_model` and `printer_variant`, which name the preset's model and nozzle and stay in every preset as the tree writes them; `nozzle_diameter`, `min_layer_height`, `max_layer_height`, `default_print_profile`, `default_filament_profile`, retraction the vendor tunes per nozzle |
A family may split once more, into toolhead or revision groups that exist only within it:
`fdm_<vendor>_<family>_common` → `fdm_<vendor>_<family>_mk1_common`. A split that crosses another axis is not a
level: when every controller comes with every toolhead, a toolhead base under each controller base
repeats the same values in each ([Balance 6](#balance)).
| Process level | Base | Settings it determines |
| --- | --- | --- |
| Vendor | `fdm_process_common`, `fdm_process_<vendor>_common` | strategy: seam, wall order, infill and support patterns |
| Printer family or variant layout | `fdm_process_<vendor>_<family>_common` (`fdm_process_arena_common`, BBL's `fdm_process_dual_common`) | speeds, accelerations and jerk of that motion system; the variant layout ([Process rule 4](extruder-variants.md#process)) |
| Layer height × nozzle | `fdm_process_<vendor>_<lh>_nozzle_<n>` (BBL's `fdm_process_single_0.20`) | `layer_height`, line widths, shell layers, speeds scaled to the layer |
| Quality × printer: the selectable preset | none | `compatible_printers`, and what is unique to that combination |
| Filament level | Base | Settings it determines |
| --- | --- | --- |
| Material | `fdm_filament_<material>` in OrcaFilamentLibrary, shared by every bundle | material defaults |
| Product | `<Product> @base` | `filament_id` (minted here, [ids](ids.md)), `filament_vendor`, `filament_type`, density, cost, the product's temperatures and cooling |
| Printer or nozzle tune: the selectable preset | none | `compatible_printers` (always in its own file, [Rule 9](../SKILL.md#rules)), volumetric speed, flow ratio, pressure advance and retraction measured on that printer |
**Equal where they must differ is a copy.** A key that follows a finer level, such as the layer-height
limits and line widths that follow the nozzle diameter or `printable_area` that follows the bed, never
moves above that level. When presets that differ in it carry the same value, the value was copied: leave
it in the presets and report it (the [worked example](#worked-example-an-idex-family) has one).
## Balance
1. **One group, one base; use the existing one first.** A base is the home of the presets below it,
whatever its name. In a bundle whose presets are all one family, the vendor base is the family base,
so the family's values go there. In a bundle that hangs the other nozzles off the default-nozzle preset, that preset is the model's home.
Never create a base whose presets are exactly its parent's. Where two existing bases already serve
the same presets (a copied `fdm_machine_common` above the vendor's own base), the finer one is the
home, and merging the pair is a change of its own. Moving a key into an existing home adds no file.
A base value that no preset below it loads is dead: replace it with the group's value when there is
one; otherwise leave it and report it, since a future preset would inherit it.
2. **A new base must stand for a level and pay for itself.** Its file costs five metadata keys and an
index entry, so create it only when it takes `k` keys off `n` selectable presets with
`(n − 1) × k > 6`, where a custom G-code value counts as one key per G-code line: what it removes
must outnumber what it adds. A model with two nozzles that share three short keys keeps them in both.
3. **No ad-hoc bases:** never a base for presets that merely agree (the ones with 0.8 mm retraction),
and never a base with one preset below it.
4. **Keep every selectable preset within four ancestors**, selectable parents included. When a level would push a preset past that, fold it into the level above or leave
its keys in the presets.
5. **A default with exceptions.** A value that only some presets below a shared base load moves to that
base, whichever axis it follows, when two conditions hold. More presets load it than any other value
(on a tie the key stays in the presets), and `w − a > 1`, where `w` presets drop their copy and `a`
presets that take the key from the base and load another value, the built-in default included, must
now write theirs. Presets that write another value keep it and are unaffected. The base then holds
the group's default, and the exceptions stay visible in their own files.
6. **One chain; the other axes stay in the presets.** `inherits` follows one axis. When presets vary
along several (bed size × controller × toolhead), first fill the existing homes by
rules 1 and 5. Then give new bases to the axis whose bases pay most: sum rule 2's count over its
bases, less the keys a re-parented preset must now write because it no longer inherits them from its
old parent; on a tie, follow the layering the bundle already has. Leave the other axes' keys in the
presets, and never repeat one axis's bases under each group of another. Outside BBL, whose synced
presets need theirs, add no `include` template for a second axis: the loader reads `include` only
since #15869 (2026-09-25), and an app that predates it ignores the key, so the template's settings
never reach the preset.
Name a new base after its level ([base names](naming.md#bases)). The name must be unique in its bundle
and must not equal a selectable preset's: two such presets load silently and the first in the index wins
([uniqueness](naming.md#uniqueness)).
## Restated values
`candidates` lists every key a file writes that it would load unchanged without writing it. Delete it
when the value is what the presets below the base that supplies it share: more of them load it,
written or inherited, than any other value. A family that restates machine limits, clearances and
G-code every other printer of the bundle loads from `fdm_klipper_common` drops its copies. When most
presets below that base load another value, the match is a coincidence: keep the key, and move it to
the level of the presets that share it. Keys of the nozzle level stay in the preset in
either case.
After a restructure the report still lists keys that are right where they are: the nozzle-level keys
each preset keeps, and arrays a list-less multi-extruder base writes at its presets' width for
`check --strict`.
## Variant arrays on a base
- The loader stores a base with its variant arrays resized to the base's own variant list, one variant
when it writes none, so an array on a narrower base reaches its presets as its first value, padded.
Move a variant array to a base only when the base declares the presets' variant list (and, for a
machine or process, their ids), moving the layout keys with it as
[Printer rule 2](extruder-variants.md#printer-machine) and [Process rule 4](extruder-variants.md#process)
ask; or when the presets load its first value anyway: every value is equal, or the presets are
list-less machines, which the loader cuts to one variant. `compare` catches a cut.
- Write the array on the base at the width of the presets it serves, their `N`
([sizing equation](extruder-variants.md#sizing-equation)), so `check --strict` judges the right width
where it reaches them. When the presets below a base need different widths (single- and
dual-extruder models on one base), leave the array in the presets, or on bases that each serve one
width.
- On a list-less multi-extruder family base, declare the extruder count: `nozzle_diameter` and the other
per-extruder vectors at one entry per extruder. The base then has its presets' width, and
`fix-variant --strict` writes an array that reaches the presets at another width into the base once,
instead of into every preset.
- Deleting a restated variant array leaves the preset on the inherited array. Plain `check` accepts
that; `check --strict` reports it when the inherited width differs, which is why a bundle held to
`--strict` restates the array at each preset's width.
## Procedure
1. **Snapshot** the tree before any edit, `fix-variant` included. After `fix-variant`, run `compare`: a
difference is a value its padding changed (it repeats the last value, the loader the first). Set that
value deliberately, then snapshot again as the baseline for the restructure.
2. **List the candidates:**
```bash
python3 .claude/skills/orca-profiles/scripts/shared_settings.py candidates --vendor "<Vendor>" --type machine
python3 .claude/skills/orca-profiles/scripts/shared_settings.py candidates --vendor "<Vendor>" --type machine --group-by printer_model
```
It prints the [restated values](#restated-values), then, per base, the keys every selectable preset
below it loads with one value and how many of those presets write it themselves, and under
`default with exceptions` the values that pass [Balance 5](#balance), with `w` and `a`. With
`--group-by <key>` it groups the selectable presets by that key's value instead and names each
group's nearest common base, where a new base would go: `printer_model` for models, `gcode_flavor`
for firmware, `extruder_type` or `default_filament_profile` for toolheads, `filament_id` for
filament products, `layer_height` for processes. The report is evidence, not a plan: it cannot tell
a shared value from a coincidence or a copy.
3. **Decide each key** by [Levels](#levels), [Balance](#balance) and
[Restated values](#restated-values): delete the restatements of shared values, move group values up
to the group's home, and create only the bases that pay.
4. **Edit.** Each new base gets `"type"`, `"name"`, `"from": "system"`, `"instantiation": "false"`, no
`setting_id`, and `inherits` set to the presets' old parent. Point the presets' `inherits` at it and
delete the moved keys from them. Bump the version, then run `normalize`, `update-index` (it orders
parents first) and `generate-id --dry-run`, which must write nothing: bases take no id and presets
keep theirs.
5. **In a bundle held to `--strict`, run `fix-variant --strict` now**, so it writes into the new bases.
6. **Verify.** `compare` prints `0 difference(s)`, and every one-sided key it lists is a default.
`check` reports no error it did not report before, and neither does `check --strict` where the bundle
passes it. Then run the [authoring checks](../SKILL.md#creating-or-modifying-a-profile).
7. **Report** each base added (name, level, presets below it, keys it holds), the keys left in presets
and why, the copies found, and the `compare` result.
## Worked example: an IDEX family
A Klipper bundle's IDEX family has 36 selectable machines (bed size 300, 400 or 500 × normal, COPY or
MIRROR mode × 0.4, 0.5, 0.6 or 0.8 nozzle) that all inherit `fdm_klipper_common` directly and write 49
keys each. `fix-variant` has widened their retraction arrays to two values and their machine limits
to four.
- **Restated.** All 36 restate 10 values that every other printer of the bundle loads from
`fdm_klipper_common`: three machine limits, the three clearances, wipe, `retract_before_wipe`, and the
layer-change and pause G-code. Delete them.
- **Family.** All 36 load one value for 24 more keys: the other machine limits, `extruder_offset`,
retraction, `z_hop`, `single_extruder_multi_material`, `manual_filament_change`, the remaining G-code
except the start G-code, and thumbnails. A new family base, `fdm_<vendor>_<family>_idex_common`,
holds them, plus `nozzle_diameter` `["0.4", "0.4"]` for the extruder count: at least
`(36 − 1) × 24 = 840`.
- **Model.** Each `printer_model` (a bed size in one mode) has four nozzle presets that share
`printable_area`, `printable_height` and a three-line `machine_start_gcode`: `(4 − 1) × 5 = 15`, so
one base per model, nine in all. The family does not hang its other nozzles off a default-nozzle
preset, so the model level here is a base.
- **A coincidence.** The twelve 500 presets' `printable_height` 500 equals `fdm_klipper_common`'s, but
every preset of the bundle writes its own height and 300 is the most common. The 500 is the base's
leftover, not a shared value, so it stays on the model bases.
- **A copy.** In COPY and MIRROR mode, every nozzle of a model carries the 0.4 nozzle's
`min_layer_height`, `max_layer_height` and `retract_lift_below`: 0.06, 0.3 and 0.2 on the 0.8 nozzle,
where normal mode has 0.12, 0.5 and 0.3. `candidates --group-by printer_model` lists the first and
last as shared by the model, and `max_layer_height` as restated from `fdm_klipper_common`, whose value
is also 0.3. They follow the nozzle, so they stay in the presets and are reported for tuning.
- **Nozzle.** Each preset keeps `printer_model`, `printer_variant`, `nozzle_diameter`,
`min_layer_height`, `max_layer_height` and `retract_lift_below` beside its metadata.
- **Result.** 36 full presets become 36 short ones on 10 new bases. `compare` reports 0 differences,
`check --vendor "<Vendor>"` passes as before, and `generate-id --dry-run` writes nothing.
`check --strict` reports more errors than before, because the deleted variant arrays now reach the
presets at `fdm_klipper_common`'s one value. `fix-variant --strict` writes those arrays into the
family base alone, after which `check --strict` passes for the family and `compare` still reports 0
differences.
@@ -1,8 +1,8 @@
# Validating profiles
```bash
./scripts/check_profile.sh # everything CI runs
./scripts/check_profile.sh --vendor "<Vendor>" # fast loop
./scripts/check_profile.sh # everything CI runs
./scripts/check_profile.sh --vendor "<Vendor>" # development loop
./scripts/check_profile.sh profile_tool validate_slice # named checks only
```
@@ -12,153 +12,316 @@ scripts\check_profile.bat -Vendor "<Vendor>"
scripts\check_profile.bat profile_tool validate_slice
```
`check_profile.bat` is a shim around `check_profile.ps1` — same checks, same order, same logs;
the flags take PowerShell spellings (`-Vendor`, `-ProfilesDir`, `-Validator`, `-Download`, `-Refresh`,
`-WorkDir`, `-LogLevel`) and positional check names are unchanged. `-p`, `-v` and `-l` are aliases, so
`-v Elegoo -l 2` reads the same on both platforms. It passes `-ExecutionPolicy Bypass` because a
`check_profile.bat` is a shim around `check_profile.ps1`: same checks, same order, same logs. Its flags
take PowerShell spellings (`-Vendor`, `-ProfilesDir`, `-Validator`, `-Download`, `-Refresh`,
`-WorkDir`, `-LogLevel`), and positional check names are unchanged. `-p`, `-v` and `-l` are aliases,
so `-v Elegoo -l 2` reads the same on both platforms. It passes `-ExecutionPolicy Bypass` because a
default Windows client refuses to run a checked-out `.ps1` at all. The `.ps1` finds Python itself,
probing `py -3`, then `python`, then `python3`; run the tool by hand with `py -3` for the same reason.
Every check in the run happens even after an earlier one fails; the script exits non-zero if any did, and writes
`logs/<check>.log` plus, on failure, `pr_comment.md` under a per-user cache dir — the same report CI posts on the PR.
That dir is `~/Library/Caches/orca-profile-check` on macOS, `${XDG_CACHE_HOME:-~/.cache}/orca-profile-check` on Linux
and `%LOCALAPPDATA%\orca-profile-check` on Windows; it is named apart from OrcaSlicer's own per-user dirs and sits
outside the checkout, so every worktree shares one copy. `--work-dir` / `-WorkDir` overrides it. A stale `.lock`
there after a crash must be removed by hand.
Every check runs even after an earlier one fails; the script exits non-zero if any failed, and writes
`logs/<check>.log` plus, on failure, `pr_comment.md` (the same report CI posts on the PR) under a
per-user cache dir:
| Platform | Cache dir |
| --- | --- |
| macOS | `~/Library/Caches/orca-profile-check` |
| Linux | `${XDG_CACHE_HOME:-~/.cache}/orca-profile-check` |
| Windows | `%LOCALAPPDATA%\orca-profile-check` |
It is named apart from OrcaSlicer's own per-user dirs and sits outside the checkout, so every worktree
shares one copy and each run overwrites its `logs/`. `--work-dir` / `-WorkDir` overrides it.
When other worktrees or agents may run checks too, pass `--work-dir <a dir of your own>` from the start
and capture the console output yourself: the shared `logs/` can belong to another run by the time you
read them. With `--work-dir`, also pass `--validator` pointing at the cached nightly, so the new dir
does not download it again:
| Platform | Cached validator |
| --- | --- |
| Linux | `<cache dir>/validator/OrcaSlicer_profile_validator` |
| macOS | `<cache dir>/validator/OrcaSlicer_profile_validator.app/Contents/MacOS/OrcaSlicer_profile_validator` |
| Windows | `<cache dir>\validator\OrcaSlicer_profile_validator.exe` |
Copying the cached `profile-fixtures/` into the new dir reuses the fixture archives; the fixture
`manifest.json` is still downloaded on every run, so `validate_custom` needs the network either way.
`another run is using <dir>` means a live run holds `<dir>/.lock`: leave it and use your own
`--work-dir`. Only a `.lock` with no `check_profile` process alive is a crash leftover; delete it by
hand.
## The five checks
| Check | Command it runs | Catches |
| --- | --- | --- |
| `profile_tool` | `python3 scripts/orca_profile_tool.py check` | index coverage **both ways**, preset-name collisions, files `normalize`/`update-index` would still rewrite, duplicate JSON keys, filament `compatible_printers`, `filament_type` array, conflict keys, id length, **all `setting_id` and `filament_id` rules** |
| `validate_system` | `validator -p resources/profiles -l 2` | load errors, missing filament `compatible_printers`, dangling `inherits`/`compatible_*`, duplicate `filament_id` per printer |
| `validate_slice` | `validator -p … -s -l 2` | custom G-code expansion, unresolvable printer defaults |
| `validate_filament_subtypes` | `validator -p … -l 2 -f` | nothing extra — see below |
| `validate_custom` | `validator -p <tree+fixture> -l 2` | a shipped preset name that a past release offered no longer resolving |
| `profile_tool` | `python3 scripts/orca_profile_tool.py check` | index coverage **both ways**, preset-name collisions, files `normalize` / `update-index` would still rewrite, duplicate JSON keys, filament `compatible_printers`, `filament_type` array, conflict keys, variant strings and array widths and layout keys, dangling `default_materials`, id length, **all `setting_id` and `filament_id` rules** ([below](#orca_profile_toolpy-check)) |
| `validate_system` | `validator -p resources/profiles -l 2` | load errors, missing filament `compatible_printers`, dangling `inherits` / `compatible_*`, duplicate `filament_id` per printer (`Ambiguous AMS filament match`), printer defaults that name no compatible system filament |
| `validate_slice` | `validator -p … -s -l 2` | custom G-code expansion and unresolvable printer defaults, by slicing |
| `validate_filament_subtypes` | `validator -p … -l 2 -f` | nothing extra; see below |
| `validate_custom` | `validator -p <tree + fixture> -l 2` | a shipped preset name that a past release offered no longer resolving |
**`-f` is a no-op.** It is declared `po::bool_switch()->default_value(true)`, so the duplicate-`filament_id`
check runs whether or not you pass it — `validate_system` already fails on duplicates. The binary's own
`--help` ("Off unless this flag is present") does not reflect that default.
**`-f` is a no-op.** It defaults to on, so the duplicate-`filament_id` check runs whether or not you
pass it, and `validate_system` already fails on duplicates. The binary's own `--help` ("Off unless this
flag is present") does not reflect that default.
### `validate_custom` — the backward-compatibility gate
**A `--vendor` run reads differently from CI.** The validator's `-v` loads that vendor plus
OrcaFilamentLibrary and nothing else, so every library tune whose `compatible_printers` names another
vendor's printers fails `validate_system`, `validate_filament_subtypes` and each `validate_custom`
fixture with thousands of `references unknown compatible_printers "Bambu Lab …"` lines. Under
`--vendor`, `profile_tool` and `validate_slice` are the meaningful results; for the other three, filter
the log for your vendor's files and treat only those lines as findings. The unscoped run is the CI
result; run it before the PR.
Downloads one fixture archive per past release (v1.9.0 onwards) of *generated mock* user presets —
a `<vendor>_<preset>_orca_test` copy of every system preset that
release shipped, cut with the validator's own `-g 1` mode — unpacks each over a copy of the current tree
and loads it. Each entry holds only `inherits` plus a canned diff, so the one failure it adds over
`validate_system` is a shipped preset name disappearing. (The whole current tree sits under each fixture,
so every `validate_system` error fails it too.) This is what makes a rename or an
`instantiation` flip a CI failure rather than just a user complaint, and the reason `renamed_from` is
mandatory.
### `validate_custom`: the backward-compatibility gate
It downloads one fixture archive per past release (v1.9.0 onwards) of *generated mock* user presets: a
`<vendor>_<preset>_orca_test` copy of every system preset that release shipped, cut with the
validator's own `-g 1` mode. It unpacks each over a copy of the current tree and loads it. Each entry
holds only `inherits` plus a canned diff, so the one failure it adds over `validate_system` is a shipped
preset name disappearing. This is what makes a rename, a deletion or an `instantiation` flip a CI
failure rather than just a user complaint, and the reason `renamed_from` is mandatory.
The whole current tree sits under each fixture, so every `validate_system` error fails
`validate_custom` too: fix `validate_system` first. Under `--vendor` it copies only the top-level index
files, `<Vendor>/` and `OrcaFilamentLibrary/`, and picks fixtures by the index's display `name`
(`Bambulab` for `BBL`), not the file stem; fixture presets without that prefix are covered only by an
unscoped run, and it warns `validate_custom checked nothing` when none match.
### `validate_slice`
Slices a two-colour cube on every instantiable printer in the tree, sequentially, forcing the prime tower.
It selects `default_print_profile` and the first `default_filament_profile`, then updates compatibility;
that update can select a different compatible preset. Confirm the intended defaults yourself rather
than treating a passing sweep as proof that those exact presets were sliced.
A printer fails if it cannot be selected, falls back to a Default preset, throws, produces no g-code, or
emits no `CP TOOLCHANGE START`. It cannot be scoped to a filament-only vendor
(`No instantiable printer presets found for vendor OrcaFilamentLibrary`); `check_profile.sh` records it
as SKIP for a vendor with no `machine/` folder.
It slices a two-colour cube on every instantiable printer in the tree, sequentially, forcing the prime
tower. It selects `default_print_profile` and the first `default_filament_profile`, then updates
compatibility; that update can select a different compatible preset. Confirm the intended defaults
yourself rather than treating a passing sweep as proof that those exact presets were sliced.
A printer fails if it cannot be selected, falls back to a Default preset, throws, produces no G-code,
or emits no `CP TOOLCHANGE START` (`change_filament_gcode` never expanded). Non-default processes and
filaments get no dedicated coverage; [slice them on a copy](#checking-a-copy-of-the-tree). In the
default set, a bundle without a `machine/` folder is recorded as SKIP; naming `validate_slice`
explicitly for it fails (`No instantiable printer presets found for vendor OrcaFilamentLibrary`). The
validator logs `[error]` lines that do not fail a check (such as `could not found extruder_type`); only
each check's PASS or FAIL counts.
## `orca_profile_tool.py check`
`check` is one subcommand of the tool that also owns
`generate-id`, `normalize`, `trim` and `update-index`; see [ids.md](ids.md) for the writing half.
`check` is one subcommand of the tool that also owns `fix-variant`, `generate-id`, `normalize`,
`trim` and `update-index`; [ids.md](ids.md#the-tool) has the writing half.
| Per vendor | Catches |
| --- | --- |
| `check_preset_name_uniqueness` | two files in one bundle claiming one type + name — indexed or not |
| `check_index_coverage` | a file on disk that no `*_list` references (**an error, not a warning**) |
| `check_name_consistency` | an index entry whose `name` disagrees with the file, or whose `sub_path` is missing |
| `check_normalized` | a file `normalize` would rewrite, and an index `update-index` would rebuild |
| `check_filament_compatible_printers` | an instantiated non-library filament with no `compatible_printers` of its own |
| `check_conflict_keys` | `extruder_clearance_radius` alongside `extruder_clearance_max_radius` |
| `check_vector_type_keys` | a vector option written as a scalar (`"filament_type": "PLA"`) |
| `check_filament_id_length` | a declared `filament_id` longer than 8 characters |
| `check_machine_default_materials` | every `default_materials` / `default_filament_profile` name resolves |
| `check_obsolete_keys` | per-key warnings for ignored options; **filament files only** |
| Catches | Scope | Function in the tool |
| --- | --- | --- |
| two files in one bundle claiming one type + name, indexed or not | per vendor | `check_preset_name_uniqueness` |
| a file on disk that no `*_list` references (**an error, not a warning**) | per vendor | `check_index_coverage` |
| an index entry whose `name` disagrees with the file, or whose `sub_path` is missing | per vendor | `check_name_consistency` |
| a file `normalize` would rewrite, an index `update-index` would rebuild | per vendor | `check_normalized` |
| duplicate JSON keys in a file | every file read | the JSON loader |
| an instantiated non-library filament with no non-empty `compatible_printers` of its own | per vendor | `check_filament_compatible_printers` |
| `extruder_clearance_radius` alongside `extruder_clearance_max_radius` | per vendor | `check_conflict_keys` |
| a scalar `filament_type` (`"filament_type": "PLA"`); the five other filament vectors `normalize` arrayifies surface as `normalize would convert <field> to an array` | per vendor | `check_vector_type_keys`, `check_normalized` |
| a variant string the two enums cannot build (a dead variant, a legacy spelling included), a variant list naming one variant twice, an `extruder_type`, `nozzle_volume_type` or `default_nozzle_volume_type` that is not an enum name ([variant names](#variant-names)) | per vendor | `check_variant_names` |
| a variant array not exactly `variant length × stride` wide in a selectable preset that writes it (with `--strict`, also in one it reaches); machine variant layout keys that disagree; a process id array that does not pair each variant ([variant arrays](#variant-arrays)) | per vendor | `check_variant_arrays` |
| a declared `filament_id` longer than 8 characters | per vendor | `check_filament_id_length` |
| a `default_materials` name, or a `default_filament_profile` name, matching no filament file ([below](#default-material-references)) | per vendor | `check_machine_default_materials` |
| per-key warnings for ignored options, **filament files only** | per vendor | `check_obsolete_keys` |
| `setting_id` uniqueness, every `filament_id` rule, and `machine_model` names duplicated across bundles | **tree-wide, ignoring `--vendor` entirely** | `check_setting_id_uniqueness`, `check_filament_ids`, `check_machine_model_name_uniqueness` |
Tree-wide, **ignoring `--vendor` entirely**: `check_setting_id_uniqueness` and `check_filament_ids`. So a
vendor-scoped run can and does fail on another vendor's files — and it saves seconds, not minutes.
Because the id and model-name checks stay tree-wide, a vendor-scoped run can and does fail on another
vendor's files, and it saves seconds, not minutes.
Unscoped, the per-vendor pass covers every bundle. The only exclusion is the stray `user/` directory
(see below); `OrcaFilamentLibrary` is held to the same rules as any vendor, its sole exemption being
that a library filament may leave `compatible_printers` empty — exactly what
`check_filament_compatible_printers` allows. `check_normalized` covers every bundle with an index.
(below); OrcaFilamentLibrary is held to the same rules as any vendor, its sole exemption being that a
library filament may leave `compatible_printers` empty. `check_normalized` covers every bundle with an
index.
Notes that matter:
- Exit codes: **0** clean, **1** errors found, **2** argparse misuse. Warnings never change the exit code.
- A nonexistent `--vendor` is a hard error — `[ERROR] unknown vendor "<V>" in <dir>`, exit 1.
- `--vendor ""` means all vendors; `check_profile.sh` relies on that. `--vendor` is repeatable.
- A **stray directory** under `resources/profiles/` still gets counted as a vendor by the per-vendor pass
and warned about (`No profiles found for vendor: <dir> at …/<dir>.json`, and the "Checked vendors" count
goes up by one). The one exception is `user/`, the validator's data dir, which an unscoped `check`
skips by name; `--vendor user` still checks and warns about it. Warnings never change the exit code.
`normalize`, `trim` and `update-index` ignore strays too — they define a bundle as *a directory with a
- Exit codes: **0** clean, **1** errors found, **2** argparse misuse. Warnings never change the exit
code.
- A nonexistent `--vendor` is a hard error: `[ERROR] unknown vendor "<V>" in <dir>`, exit 1.
- `--vendor ""` means all vendors; `check_profile.sh` relies on that. `--vendor` is repeatable
(`check --vendor A --vendor OrcaFilamentLibrary`); `check_profile.sh` takes one.
- A **stray directory** under `resources/profiles/` still gets counted as a vendor by the per-vendor
pass and warned about (`No profiles found for vendor: <dir> at …/<dir>.json`, and the "Checked
vendors" count goes up by one): usually an emptied folder, or a `user/` left by a direct validator
run. An unscoped `check` skips `user/` by name; `--vendor user` still checks and warns about it.
`normalize`, `trim` and `update-index` ignore strays too: they define a bundle as *a directory with a
matching index file*.
- Each remedy is printed once for the whole run, not once per file, as a `[WARNING]` under the errors
("2 unreferenced file(s) above: delete them, or run … update-index"). Read those lines: they name the
(`2 unreferenced file(s) above: delete them, or run … update-index`). Read those lines: they name the
command that fixes the batch.
- The trailing summary always suggests `normalize`. That is right for the shape errors and misleading for
everything else — an id error needs `generate-id`, a dangling `default_materials` needs a human.
- When there are errors or warnings, the trailing summary suggests `normalize`. That is right for the
shape errors and misleading for everything else: an id error needs `generate-id`, a dangling
`default_materials` needs a human.
- Other options: `--dry-run` on every writing command, `--profiles DIR` to point any command at another
tree, `--profile-type` to narrow `normalize`, `trim` and `update-index` to one type
([ids.md](ids.md#the-tool)).
- `resources/profiles/check_unused_setting_id.py` is a legacy BBL-only diagnostic, not part of
profile CI. Use `orca_profile_tool.py check` for current id validation.
### Obsolete-key diagnostics
### Obsolete keys
`check` always reports per-key warnings for obsolete options in filament profiles.
The normalization check also rejects obsolete keys across preset types; `normalize` removes them.
`check` always reports per-key warnings for obsolete options in filament profiles. Its normalization
check also rejects obsolete keys across all preset types (`normalize would remove <key>`); `normalize`
removes them.
### Default-material references
The materials check finds `default_materials` / `default_filament_profile` entries naming a preset
that does not exist. The three authoring errors it surfaces are `,` instead of `;`, wrong case
(`@system`), and a whole `;`-joined string stuffed into one array element.
The materials check finds `default_materials` / `default_filament_profile` entries naming a preset that
does not exist. It reads each `machine/` file's own key (a model's `default_materials`, a variant's
`default_filament_profile`; a file that writes both is checked on `default_materials` only) and accepts
any `name` found in the vendor's or OrcaFilamentLibrary's `filament/` files, bases and unindexed files
included. The three authoring errors it surfaces are `,` instead of `;`, wrong case (`@system`), and a
whole `;`-joined string stuffed into one array element. Only the validator (`validate_system`) requires
an instantiated system filament that is compatible with each variant.
### Variant arrays
`check_variant_arrays` composes every selectable preset the loader's way (the parent, then each
`include` in order, then the file's own keys; a filament's `inherits` may fall through to
OrcaFilamentLibrary) and holds each key of [the four variant sets](extruder-variants.md#the-four-key-sets)
that the preset writes itself to exactly its `variant length × stride`
([widths](extruder-variants.md#widths)). The variant length is the length of the composed preset's
own `*_extruder_variant` list; without one, a machine's is the number of variants its
`extruder_variant_list` offers, else its extruder count (the list's default is one
`Direct Drive Standard` per extruder), and a process's or filament's is 1. Any other width is an
error, one value included and even when every value is the same. A key the preset does not write
takes what reaches it, the default or an array it inherits or includes, which the loader resizes; it
is not checked. A base is not judged on its own: what it writes counts only where it reaches a preset
that does not override it.
`check --strict` also holds every selectable preset to its own width for each key that reaches it,
so a preset whose variants differ from those of the file its array comes from restates the array
(the error names that file). That is BBL's practice and the target for new printer-specific presets;
CI runs `check` without `--strict`.
An id array that reaches a selectable preset, `printer_extruder_id` or `print_extruder_id`, written or
inherited, must have one entry per entry of its variant list on any printer. This rule and the
machine layout rules below judge the composed preset without `--strict`, whichever file writes the
keys. Beside a written variant list this rule reports it instead of the width
rule, so it is reported once. A process that
lists variants without `print_extruder_id` gets a **warning** when a variant repeats (every entry then
reads as extruder 1, so the repeated variant is unreachable), nothing otherwise.
On a machine the layout keys are held to [Printer rules 1–4](extruder-variants.md#printer-machine),
every failure an error unless marked:
- With `extruder_variant_list` written: the list has one entry per extruder, as many as
`nozzle_diameter`; every variant starts with its extruder's `extruder_type`;
`default_nozzle_volume_type` names a nozzle volume type that extruder lists;
`printer_extruder_variant` is the list flattened extruder-major and `printer_extruder_id` gives each
entry its 1-based extruder (an id array left out reads as extruder 1 everywhere, which passes when
those are the flattening's ids). Without the pair, the list may offer one variant in total.
- With the pair written and no `extruder_variant_list`: one variant per extruder at most. A pair that
`single_extruder_multi_material` off would replace with the default at load is a **warning**; a pair
the rebuild would leave as it is passes.
It does **not** see a base's own resize: it composes at the width each file wrote, so an array wider
than a base's list, which the loader cuts before any child inherits it, passes even with `--strict`
([composition](extruder-variants.md#padding-truncation-and-composition)). Nor does it see per-extruder
vectors outside the sets (`extruder_offset`, `printer_extruder_options`, …), which no variant list
sizes. What a variant holds (a High Flow variant copied from Standard, a variant inserted at the wrong
index) is review work
([item 14](review-checklist.md#14-per-extruder-vectors-not-checked-and-variant-arrays-widths-and-layout-checked));
whether it is a name the engine can select at all is `check_variant_names`'.
`python3 scripts/orca_profile_tool.py fix-variant` resizes every array the width rule rejects in the
selectable preset that writes it, leaves bases alone and adds no key: extra values are dropped, missing ones
repeat the last value (the last normal/silent pair at stride 2; a lone value fills normal and silent
alike). It leaves the variant lists and id arrays to you, since they address the variants rather than
fill them. `fix-variant --strict` then also writes each key that reaches a selectable preset at
another width: into the most general file on the way down to the preset whose own width is the
preset's and whose selectable presets taking it all need that width, else into the preset itself.
Presets of every bundle count towards that agreement; `--vendor` limits the files written and
`--dry-run` previews. The loader pads with the first value where `fix-variant` repeats the last, so a
padded array need not load as before, and
trimming deletes values: when the extra values were meant as per-extruder or per-variant values,
declare the variant layout instead ([Printer rule 2](extruder-variants.md#printer-machine)) and keep
them. `fix-variant` moves no value, so a family whose presets all wrote one value repeats the widened
array in each; put it on the family's base afterwards ([shared bases](shared-bases.md)).
### Variant names
`check_variant_names` reads the four list keys plus `extruder_type`, `nozzle_volume_type` and
`default_nozzle_volume_type` of every preset the bundle's index references, bases included, and holds
each entry to the names the engine's two enum maps define (`s_keys_map_ExtruderType`,
`s_keys_map_NozzleVolumeType`, read from `PrintConfig.cpp` on every run). The bundle's own files are
judged, not the composed config: a bad string is the writing file's error, once. Every finding is an
error, and no bundle is exempt: BBL, whose bundle is imported from BambuStudio, is held to OrcaSlicer's
enums like any other.
- A variant string outside `<extruder type> <nozzle volume type>` is a **dead variant**: it still
counts toward the variant length the arrays are sized by, so the values written for it silently never
reach the G-code. `Hybrid` too, which is runtime-only, and an empty entry. A name BambuStudio's enum
has and OrcaSlicer's lacks is dead here as well, and passes with no tool change once the engine gains
that nozzle volume type.
- A legacy name the loader still rewrites in these keys (`Normal` → `Standard`, `Big Traffic` →
`High Flow`) is an error that names the enum name to write; the profile has to spell the enum
name. `DirectDrive` is only rewritten in `extruder_type`, so a variant string carrying it is dead.
- A variant list naming one variant twice is an error: the lookup returns the first equal string, so
the repeat is unreachable and its value sits at an index no extruder reads. A filament list takes
strings, `extruder_variant_list` takes them per extruder, and a process takes `(extruder id, variant)`
pairs — one string on two extruders is two pairs, not a repeat.
- An `extruder_type`, `nozzle_volume_type` or `default_nozzle_volume_type` value that is not an enum
name is an error: they are enum options, so an unknown value fails the validator's load of the
whole bundle, while the app silently loads the option's default instead. A legacy spelling
(`DirectDrive`, `Normal`, `Big Traffic`) and `Hybrid`, an enum value no profile writes, are errors
too.
The variant *order*, the choice of variants, and the values themselves are not checked.
### `normalize` and `update-index` are part of the check
`check` fails when either command would still change something, so they are not optional polish — the
`check` fails when either command would still change something, so they are not optional polish: the
file that gets reviewed has to be the file that ships. What `normalize` changes is narrow and fixed:
adds a missing `type`, deletes a `version` or `is_custom_defined` key from a *preset* file, deletes six
print-speed keys from filament profiles (`initial_layer_print_speed`, `outer_wall_speed`,
`inner_wall_speed`, `infill_speed`, `top_surface_speed`, `travel_speed`), deletes the
obsolete keys in `PrintConfigDef::handle_legacy`'s `ignore` set across preset types, resolves the
`extruder_clearance_*` conflict pair by keeping the larger, arrayifies five filament options besides
`filament_type`, and hoists `type`, `name`, `renamed_from`, `inherits`, `from`, `setting_id`,
`filament_id`, `instantiation` to the front. A file it changes is then rewritten whole — tab-indented,
LF, one trailing newline, keys reordered.
- adds a missing `type`;
- deletes a `version` or `is_custom_defined` key from a *preset* file;
- deletes six print-speed keys from filament profiles (`initial_layer_print_speed`, `outer_wall_speed`,
`inner_wall_speed`, `infill_speed`, `top_surface_speed`, `travel_speed`);
- deletes the obsolete keys the loader ignores (the `ignore` set in `PrintConfigDef::handle_legacy`),
across preset types;
- resolves the `extruder_clearance_*` conflict pair by keeping the larger;
- arrayifies six filament options (`filament_type`, `filament_cost`, `filament_density`,
`temperature_vitrification`, `filament_max_volumetric_speed`, `filament_vendor`);
- hoists `type`, `name`, `renamed_from`, `inherits`, `from`, `setting_id`, `filament_id`,
`instantiation` to the front.
A file it changes is then rewritten whole: tab-indented, LF, one trailing newline, keys reordered. A
file committed with CRLF line endings therefore changes on every line; read the diff before committing
it.
**Set `type` explicitly when authoring.** For a file in `machine/` without it, normalization guesses
`machine` only if its name contains `nozzle`, otherwise `machine_model`. That heuristic cannot
reliably classify shared machine bases or unusually named variants.
`machine` only if its name contains `nozzle` (case-insensitive), otherwise `machine_model`. That
heuristic cannot reliably classify shared machine bases or unusually named variants.
The Python obsolete-key set is checked against the C++ source by a unit test. Active options
and legacy aliases that the loader migrates (such as `extruder_type` and
`extruder_clearance_max_radius`) are preserved.
The tool's obsolete-key set is checked against the loader's ignore list by a unit test. Active options
are preserved, including live keys whose *values* the loader rewrites (`extruder_type`: `DirectDrive` →
`Direct Drive`; the variant-string keys: `Normal` / `Big Traffic` → `Standard` / `High Flow`), and so are
legacy key names the loader migrates (such as `extruder_clearance_max_radius`).
Two things it therefore does **not** enforce:
- **Formatting and key order on their own.** A file with none of those problems is skipped entirely, so
4-space indent, a missing trailing newline, and a file that leads with `compatible_printers` all pass
`check`. They stay latent until something else trips `normalize` and the whole file reformats inside an
unrelated diff. (`normalize --force` rewrites every file, which is not something to run on a shipped
bundle.)
`check`. They stay latent until something else trips `normalize` and the whole file reformats inside
an unrelated diff. (`normalize --force` rewrites every file; do not run it on a shipped bundle.)
- **A misspelled setting key.** `inital_layer_height` and `sparse_infill_densiti` pass `check` cleanly.
Verify new keys against `PrintConfig.cpp` and `PrintConfigDef::handle_legacy`.
Verify new keys against `src/libslic3r/PrintConfig.cpp` and the loader's legacy handling
(`PrintConfigDef::handle_legacy`: renamed keys, rewritten values and ignored keys).
`check` does not catch a dangling `default_print_profile` either; check that name by hand.
## The validator binary
Built from `src/dev-utils/OrcaSlicer_profile_validator.cpp` (`-DORCA_TOOLS=ON`).
Both scripts find a local build under `build*/` — `check_profile.sh` tries Release, RelWithDebInfo, then
Debug, and `check_profile.ps1` adds MinSizeRel — else they download the nightly into the
`validator` subdirectory of the per-user cache dir (see above). Pass `--download` / `-Download` to
match CI exactly, since a stale local build is used silently. Windows looks for
Built from `src/dev-utils/OrcaSlicer_profile_validator.cpp` with `-DORCA_TOOLS=ON`. Both scripts use a
local build under `build*/` when one exists, else they download the nightly into the `validator`
subdirectory of the cache dir. `check_profile.sh` searches Release, then RelWithDebInfo, then Debug
(each under `build*/src/<config>` and `build*/*/src/<config>`), then single-config `build*/src`, and
prefers a host-architecture build tree; `check_profile.ps1` tries Release, RelWithDebInfo, MinSizeRel,
then Debug. A stale local build is used silently; `--download` / `-Download` skips local builds and uses the
nightly. The download and the fixtures stay cached until `--refresh` / `-Refresh`, so
`--download --refresh` (`-Download -Refresh`) matches CI exactly. Windows looks for
`OrcaSlicer_profile_validator.exe`.
If your build lives somewhere else entirely, point at it with `--validator` / `-Validator`, or set
If your build lives somewhere else, point at it with `--validator` / `-Validator`, or set
`ORCA_PROFILE_VALIDATOR` (`$env:ORCA_PROFILE_VALIDATOR` in PowerShell).
| Flag | Meaning |
@@ -167,11 +330,12 @@ If your build lives somewhere else entirely, point at it with `--validator` / `-
| `-l <n>` | log level; CI uses 2 |
| `-v <Vendor>` | load only that vendor **plus** OrcaFilamentLibrary |
| `-s` | slice sweep |
| `-o <dir>` | with `-s`, save each printer's G-code there |
| `-f` | no-op (see above) |
| `-g 1` | regenerate user-preset fixtures; takes a value, and wipes the user preset dir first |
On ARM64 Linux the nightly is x86-64 only — the script warns and downloads anyway, producing a binary
that will not run. Build it locally instead.
On ARM64 Linux the nightly is x86-64 only: the script warns and downloads anyway, producing a binary
that will not run. Build it locally with `-DORCA_TOOLS=ON` and pass `--validator`.
Running the validator directly uses the profile tree as its data directory and can create `user/`
there. Prefer the wrappers, which stash existing user presets and restore them afterward. After a
@@ -185,16 +349,27 @@ Use `--profiles DIR` on the Python tool and `-p DIR` on the validator. The wrapp
```bash
./scripts/check_profile.sh --profiles "<tree>"
# Windows: scripts\check_profile.bat -ProfilesDir "<tree>"
```
On Windows use `scripts\check_profile.bat -ProfilesDir "<tree>"`.
To slice a process or filament that is not a printer's default, or to read the G-code, work on a copy:
copy `resources/profiles` and `resources/info` into one scratch dir (the validator reads `info/` next to
the tree), point the printer's `default_print_profile` or first `default_filament_profile` at the preset
in the copy, and run the validator with `-o`:
```bash
<validator> -p <scratch>/profiles -v "<Vendor>" -s -o <gcode dir>
```
Each printer's G-code is saved as `<vendor name>__<printer>.gcode`; its embedded config
(`; print_settings_id = …`, `; filament_settings_id = …`) shows what was actually sliced.
## Testing in the app
Editing this checkout's `resources/profiles` does not update a separately installed application.
Test with a build using the edited resources and a bumped bundle version; the updater installs newer
bundles under `<data_dir>/system/`, and the preset cache also depends on the bundle version.
Use Help ▸ Show Configuration Folder to locate the active data directory:
Editing this checkout's `resources/profiles` does not update a separately installed application. Test
with a build using the edited resources and a bumped bundle version: the updater installs newer bundles
under `<data_dir>/system/`, and the preset cache also depends on the bundle version. Use Help ▸ Show
Configuration Folder to locate the active data directory:
| Platform | Default data directory |
| --- | --- |
@@ -202,58 +377,71 @@ Use Help ▸ Show Configuration Folder to locate the active data directory:
| Linux | `$XDG_CONFIG_HOME/OrcaSlicer`, or `~/.config/OrcaSlicer` when unset |
| Windows | `%APPDATA%\OrcaSlicer` |
A portable `data_dir` next to the executable takes precedence. Use a separate test configuration
for a clean-install check; preserve the normal configuration and user presets.
## Cross-platform paths
Match the exact case of each `sub_path` and asset filename; Linux filesystems commonly distinguish
case even when a macOS or Windows checkout does not. Preset-name references are case-sensitive
on every platform. Avoid Windows-invalid characters (`< > : " | ? *`), reserved device names
such as `CON` / `NUL` (including with extensions), and trailing spaces or dots in path components.
Keep stems tidy too, but a space immediately before `.json` is not a trailing path-component space.
A portable `data_dir` next to the executable takes precedence. Use a separate test configuration for a
clean-install check; preserve the normal configuration and user presets.
## Error → remedy
| Message | Fix |
| --- | --- |
| `can not find inherits <parent> for <preset>` | parent missing, unregistered, or listed **after** the child |
| `can not find filament_id for <name>` | nothing in the chain declares one — run `generate-id` |
| `can not find parent <name> for config <user preset>!` | a shipped name disappeared — add `renamed_from` |
| `Missing instantiation attribute for <name>` | key absent **or** not the string `"true"`/`"false"` |
| `can not find inherits <parent> for <preset>` | parent missing, unregistered, misspelled, or listed **after** the child |
| `can not find include` | the template is misspelled, registered after the includer, or selectable |
| `can not find filament_id for <name>` | nothing in the chain declares one: run `generate-id` |
| `can not find parent <name> for config <user preset>!` | a shipped name disappeared: add `renamed_from` |
| `Failed loading configuration file <file>` | that file could not be loaded and the whole bundle was discarded: a JSON error, or a value its option cannot take, such as `nil` in a non-nullable key (`Invalid value provided for parameter <key>: nil`, `Deserializing nil into a non-nullable object`); the lines above it name the cause |
| `Missing instantiation attribute for <name>` | key absent **or** not the string `"true"` / `"false"` |
| `contains incorrect keys: <keys>, which were removed` | a key valid for a different preset type |
| `defines invalid printer variant "<v>"` | not in the model's `nozzle_diameter` list |
| `has printer_variant "<v>" that does not match its nozzle_diameter` | the set comparison in [machine-profiles.md](machine-profiles.md) |
| `references unknown compatible_printers "<p>"` | the printer was renamed or deleted; fix the reference |
| `defines invalid printer variant "<v>"` | not a token of the model's `nozzle_diameter` list |
| `has printer_variant "<v>" that does not match its nozzle_diameter` | [the set comparison](machine-profiles.md#printer_model-and-printer_variant) |
| `references unknown compatible_printers "<p>"` | the printer was renamed or deleted, or a `machine_model` name was used instead of a variant name: fix the reference. Under `--vendor`, usually another vendor's printer ([why](#the-five-checks)) |
| `references renamed compatible_printers "<old>" (now "<new>")` | in-tree references must name the current preset; `renamed_from` does not excuse them |
| `Filament preset "<f>" is missing compatible_printers setting` | non-library filaments need a non-empty list in their **own** file — the flattened-vs-own-key trap is in [filament-profiles.md](filament-profiles.md#compatible_printers) |
| `Ambiguous AMS filament match: N presets share filament_id "X" … printer "Y"` | make the lists disjoint by [specificity](filament-profiles.md#overlapping-coverage-one-variant-one-profile-per-product) — the specialized profile keeps the variant, the general ones drop it; prefer this over deleting a profile — or fix an `inherits` pointing at another material's `@base`. `orca_profile_tool.py check` does not catch this; only `validate_system` here does |
| `Layer height cannot exceed nozzle diameter.` / `Line width too small` | `Print::validate()` flow rules |
| `Filament preset "<f>" is missing compatible_printers setting` | non-library filaments need a non-empty list in their **own** file; the resolved-vs-own-key trap is in [filament-profiles.md](filament-profiles.md#compatible_printers) |
| `Ambiguous AMS filament match: N filament presets share filament_id "X" and are all compatible with printer "Y"` | make the lists disjoint by [specificity](filament-profiles.md#overlapping-coverage-one-variant-one-profile-per-product): the specialized profile keeps the variant, the general ones drop it; prefer this over deleting a profile. Or fix an `inherits` pointing at another product's `@base`. `orca_profile_tool.py check` does not catch this; only `validate_system` does |
| `Layer height cannot exceed nozzle diameter.` / `Line width too small` / `Line width too large` | the [slicing limits](process-profiles.md#values-to-review-per-nozzle) |
| `[ERROR] … no <V>.json list references it, so it never loads` | `update-index`, or delete the file |
| `[ERROR] … references it and it declares no profile type` | set the correct `type` explicitly, then `normalize` and `update-index` |
| `[ERROR] … no <V>.json list references it and it declares no profile type` | set the correct `type` explicitly, then `normalize` and `update-index` |
| `[ERROR] … normalize would <change>` / `<V>.json: update-index would rebuild <lists>` | run that command and commit the result |
| `[ERROR] <V> has N <type> profiles named "<name>"` | identify the intended preset and remove or rename the duplicate; use `trim --dry-run` only for deliberate unindexed-file cleanup |
| `[ERROR] … must not have a setting_id` / `is missing a setting_id` | `generate-id --setting-id` |
| `inherits filament_id "X" but its own triple … mints "Y"` | `generate-id` will **not** fix this — see [ids.md](ids.md) |
| `vendor <V>'s config version: <s> invalid` | the `version` string is not Semver-parseable |
| `[json.exception.type_error.302] type must be string` | locate the non-string value in the index or model; see [failure scopes](vendor-bundle.md#failure-modes-ranked-by-blast-radius) |
| `Printer "<p>" fell back to a default preset` | final process or filament selection is a generic Default preset; check named defaults, visibility and available compatible presets. An incompatible default may instead be replaced without this error |
| `Printer "<p>" sliced but the filament change never fired` | `change_filament_gcode` never expanded |
| `Duplicate key error in <file>: Duplicate key detected: <key>` | a key written twice in one file; keep the intended one |
| `… must not have a setting_id` / `… is missing a setting_id` / `setting_id "X" in <file> does not match the expected "Y" …` | `generate-id --setting-id` (by hand in `BBL/`, see [ids.md](ids.md#bbls-exception-precisely)) |
| `filament_id "X" declared by … does not match the mint of its triple …` | `generate-id --filament-id`; if the id should not be declared here at all, remove it so the preset inherits its root's |
| `inherits filament_id "X" but its own triple "V/T/N" mints "Y"` | `generate-id` will **not** fix this; see [ids.md](ids.md#what-generate-id-does-and-does-not-fix) |
| `"<key>" has N values for variant length S at stride k, which takes M` (with ` (no <list key>, so …)` after `S`, naming where `S` came from, when the preset writes no list, and ` (it comes from <file>)` at the end under `--strict` for an array the preset does not write) | exactly `S × k` values in variant order, or leave the key out ([widths](extruder-variants.md#widths)); on a list-less multi-extruder printer whose extruders differ, declare the layout; `fix-variant` cuts or pads to that width ([variant arrays](#variant-arrays)); an `S` you did not expect means the variant list did not resolve through `include` or `inherits` |
| `printer_extruder_variant […] is not extruder_variant_list flattened extruder-major […]` / `printer_extruder_id […] does not give each entry of printer_extruder_variant its 1-based extruder […]` / `printer_extruder_id has N entries for the M entries of printer_extruder_variant` | write the pair as the flattening of the list, ids in step ([Printer rule 2](extruder-variants.md#printer-machine)) |
| `extruder_variant_list has N entries for M extruder(s)` / `extruder_variant_list entry i "…" holds a variant that does not start with extruder i's extruder_type` / `default_nozzle_volume_type "…" is not a nozzle volume type extruder i lists` | [Printer rules 1, 3 and 4](extruder-variants.md#printer-machine) |
| `extruder_variant_list offers N variants but the preset writes no printer_extruder_variant/printer_extruder_id` / `printer_extruder_variant lists several variants for extruder N but the preset writes no extruder_variant_list` / `[WARNING] … with single_extruder_multi_material off the loader replaces printer_extruder_variant …` | write all three layout keys ([Printer rule 2](extruder-variants.md#printer-machine)) |
| `print_extruder_id has N entries for the M entries of print_extruder_variant` / `[WARNING] … print_extruder_variant repeats a variant but print_extruder_id is absent` | one id per variant entry, mirroring the printer's pairs ([Process rule 1](extruder-variants.md#process)) |
| `<key> <where> holds "…", which no extruder can select: "…" is not a nozzle volume type the enum has (…)` / `… it does not start with an extruder type the enum has (…)` / `… is empty` / `… Hybrid names the sub-nozzles of one hybrid extruder at runtime` | write a legal variant string, `<extruder type> <nozzle volume type>` from the two enums ([variant names](#variant-names), [variant strings](extruder-variants.md#variant-strings)); in every bundle, BBL included |
| `… holds the legacy variant "…"` / `extruder_type spells the legacy name "…"` / `nozzle_volume_type spells the legacy name "…"` / `default_nozzle_volume_type spells the legacy name "…"` | write the enum name the message gives: the loader still rewrites the legacy one, but `check` rejects it |
| `<key> lists "…" N times` / `print_extruder_variant lists the pair (extruder N, "…") N times` | drop the repeat and its value from every variant array: the lookup returns the first equal string |
| `extruder_type "…" is not one of (…)` / `nozzle_volume_type "…" is not one of (…)` / `default_nozzle_volume_type "…" is not one of (…)` / `… names "Hybrid", which the engine computes for a hybrid extruder at runtime` | they are enum options, so an unknown value fails the validator's load of the whole bundle and silently becomes the default in the app; use `s_keys_map_ExtruderType` / `s_keys_map_NozzleVolumeType`, `Hybrid` excepted ([variant strings](extruder-variants.md#variant-strings)) |
| `[WARNING] No profiles found for vendor: <dir>` | a directory with no matching index (an emptied folder, or a `user/` left by a direct validator run); remove it |
| `… has no compatible system filament in its model's "default_materials"` | add a system filament preset compatible with that variant to the model's `default_materials` |
| `… names the unknown system filament "<n>" in its "default_materials"` / `… "default_filament_profile"` | name an existing system (not user, not base) filament exactly; `;` separators, exact case |
| `Missing filament profile: '<n>' referenced in <file>` | the same, caught by `check`; usual causes are `,` instead of `;`, wrong case (`@system`), or a `;`-joined list packed into one array element |
| `machine_model name "<n>" is declared by N bundles` | model names are unique across the tree; rename the new model |
| `vendor <V>'s config version: <s> invalid` | the `version` string does not parse; write `MM.mm.pp.bb` |
| `[json.exception.type_error.302] type must be string` | locate the non-string value in the index or a model; see [failure scopes](vendor-bundle.md#failure-scopes) |
| `Printer "<p>" fell back to a default preset` | the final process or filament selection is a generic Default preset: check the named defaults, their visibility and that compatible presets exist. An incompatible default may instead be replaced without this error |
| `Printer "<p>" sliced but the filament change never fired (no CP TOOLCHANGE START)` | `change_filament_gcode` never expanded |
## CI
`.github/workflows/check_profiles.yml`, job **"Check profiles"**, on `pull_request` into `main` or
`release/*`, paths `resources/profiles/**`, `resources/printers/**`, `scripts/**` and the workflow itself.
There is no push trigger — a direct push to main runs no profile validation.
`.github/workflows/check_profiles.yml`, job **"Check profiles"**, runs on `pull_request` into `main` or
`release/*` touching `resources/profiles/**`, `resources/printers/**`, `scripts/**`,
`src/libslic3r/PrintConfig.cpp` (where the tool reads the variant key sets) or the workflow itself.
There is no push trigger: a direct push to main runs no profile validation.
The job opens with `python3 -m unittest discover -s scripts/tests -t scripts`, the tool's own unit
tests. That step is deliberately **not** `continue-on-error`: a broken tool makes everything it then says
about the profiles worthless. Every check after it is `continue-on-error` with a final gate, so one run
reports all five results. On failure a second workflow posts or replaces a single PR comment marked
`<!-- profile-validation-comment -->`, with each failing log truncated to 30 KB; it deletes the comment
once the run is green.
The job opens with `python3 -m unittest discover -s scripts/tests -t scripts`, the tool's own unit tests
(run them locally after changing `scripts/`, with `py -3` on Windows, and keep `-t scripts` or the
imports fail). That step is deliberately **not** `continue-on-error`: a broken tool makes everything it
then says about the profiles worthless. Every check after it is `continue-on-error` with a final gate,
so one run reports all five results. On failure a second workflow posts or replaces a single PR comment
marked `<!-- profile-validation-comment -->`, holding the start of each failing log (30 KB per check,
12 KB per `validate_custom` fixture; reproduce locally for the full list); it deletes the comment once
the run is green.
The job name is also the required check for the delegated-merge bot, which lets a vendor maintainer
self-merge a `resources/profiles/<Their vendor>/` PR with no human review — so whatever CI does not check
is what ships unreviewed. Its denied patterns refuse `^scripts/` and any `.py`, so a PR that touches the
tooling always needs a maintainer.
self-merge a PR limited to their own `resources/profiles/<Vendor>/` folder with no human review, so
whatever CI does not check is what ships unreviewed. Its denied patterns refuse `^scripts/` and any
`.py`, so a PR that touches the tooling always needs a maintainer.
@@ -1,7 +1,7 @@
# The vendor bundle and the loader
# Vendor bundles
A bundle is `resources/profiles/<Vendor>.json` (the index) plus `resources/profiles/<Vendor>/`.
The **vendor id is the filename stem**, not the `name` inside — several differ (`BBL.json` is named
A bundle is `resources/profiles/<Vendor>.json` (the index) plus `resources/profiles/<Vendor>/`. The
**vendor id is the filename stem**, not the `name` inside; the two may differ (`BBL.json` is named
"Bambulab"). Asset paths and the `setting_id` formula use the id; the `validate_custom` fixture prefix
uses the `name`.
@@ -13,16 +13,16 @@ uses the `name`.
"version": "02.04.00.03",
"force_update": "0",
"description": "Phrozen configurations",
"machine_model_list": [ { "name": "...", "sub_path": "machine/....json" } ],
"machine_list": [ ... ],
"process_list": [ ... ],
"filament_list": [ ... ]
"machine_model_list": [ { "name": "Phrozen Arco", "sub_path": "machine/Phrozen Arco.json" } ],
"machine_list": [ … ],
"process_list": [ … ],
"filament_list": [ … ]
}
```
The loader reads `name`, `version`, `url` and the four `*_list` arrays.
`description` is only logged. `force_update` is read by `PresetUpdater`, never by the loader.
`sub_path` is relative to the **vendor folder**.
The loader reads `name`, `version`, `url` and the four `*_list` arrays. `description` is only logged;
`force_update` is read by the profile updater, never by the loader. `sub_path` is relative to the
**vendor folder**.
| List | Holds |
| --- | --- |
@@ -35,141 +35,180 @@ The loader reads `name`, `version`, `url` and the four `*_list` arrays.
1. **Everything is registered, bases included.** Every preset file on disk has exactly one entry in the
matching list, and no unindexed preset file is left in the tree.
2. **Parents before children.** `inherits` resolves against a per-kind map filled as the list is walked
(`configs.clear()` then process, filaments, printers). A parent listed after its child produces
`can not find inherits <parent> for <child>` and the bundle is discarded.
3. **The index entry's `name` must equal the `name` inside the sub_path file.** `check_name_consistency`
walks the index looking for the files; `check_index_coverage` walks the files looking for them in the
index. The `renamed_from` escape hatch `check_name_consistency`'s docstring promises is commented out.
2. **Parents before children, includes before includers.** The lists load processes first, then
filaments, then printers, each in index order, and `inherits` and `include` resolve only against
presets of that type already loaded from it. A parent
listed after its child produces `can not find inherits <parent> for <child>` and the bundle is
discarded; an include listed after its includer is `can not find include`, a counted error that
leaves the includer without those keys.
3. **The index entry's `name` equals the `name` inside the `sub_path` file.** `renamed_from` does not
excuse a mismatch.
All three are `check` errors now, and `update-index` writes an index that satisfies all three from the
files on disk — including the parents-first ordering, by topological sort. Hand-editing the index is
All three are `check` errors, and `update-index` writes an index that satisfies all three from the
files on disk, including the dependency ordering (parents and templates before the presets that use
them, then, in name order, entries that neither depend on nor are depended on by another entry of their own
list, and any entry on a dependency cycle). Hand-editing the index is
fine for a one-line addition, but the committed result must equal what `update-index` writes, because
`check` compares them.
The loader itself reports none of this: an unregistered file, or an entry with a typo'd key
(`"subpath"`), is silently dropped. (A typo'd `sub_path` is a `check` error naming the entry.)
The loader itself reports none of this: an unregistered file, or an entry with a misspelled key
(`"subpath"`), is silently dropped. (A misspelled `sub_path` value is a `check` error naming the entry.)
`BBL/cli_config.json` and `BBL/filament/filaments_color_codes.json` are auxiliary data loaded by path,
not presets. The tool's `NON_PROFILE_FILES` excludes these basenames from preset maintenance.
`BBL/cli_config.json` and, in `BBL/filament/`, `filaments_color_codes.json`, `filament_id_map.json`,
`filament_name_map.json` and `support_recommended_params.json` are auxiliary data files read by path,
not presets. The last three carry a `type` key and look like presets; the tool excludes all five from
preset maintenance.
## `version`
Parsed by a four-component Semver where the 4th is folded in as `patch = patch*100 + value`. Write it
zero-padded, `MM.mm.pp.bb`; a couple of bundles drop a component or the padding, but do not imitate them.
Four components, `MM.mm.pp.bb`, compared as a version number in which the fourth is folded into the
third (`patch × 100 + build`). Write all four components, zero-padded.
- **Bump the version for every bundle the PR touches.** `PresetUpdater` installs bundled resources
only when their version is newer than the installed version; the `.opc` preset cache is also
versioned. Nothing in profile CI checks the bump.
- **Keep the last component ≤ 99.** `02.04.00.100` and `02.04.01.00` both parse to `2.4.100`. A bundle
that reaches `.99` carries into the third component (`02.03.02.99` → `02.03.03.00`).
- An **absent** version is worse than a stale one: the validator still passes, but `Semver::valid()`
excludes `0.0.0`, so the vendor is dropped from the configuration wizard entirely and the preset cache
is disabled for it. An *unparseable* version is not silent — it throws and discards the whole bundle
(see the failure table below).
- **Bump the version for every bundle the change touches.** The app installs bundled profiles only
when their version is newer than the installed one, and the `.opc` preset cache is also keyed on
the version. Nothing in profile CI checks the bump.
- **Keep the last component ≤ 99.** `02.04.00.100` and `02.04.01.00` both read as
`2.4.100`. A bundle that
reaches `.99` carries into the third component (`02.03.02.99` → `02.03.03.00`).
- An **absent** version is worse than a stale one: it reads as `0.0.0`, which is not a valid version.
`check` and the validator still pass, but the vendor is dropped from the setup wizard entirely and gets no preset cache. Confirm the key exists. An
*unparseable* version is not silent: it discards the whole bundle (`vendor <V>'s config version: <s>
invalid`).
## Common preset keys
| Key | Value |
| --- | --- |
| `type` | `machine_model` / `machine` / `process` / `filament` |
| `name` | the preset name; the filename is *not* authoritative |
| `inherits` | the parent's exact `name` — no path, no `.json` |
| `type` | `machine_model`, `machine`, `process` or `filament` |
| `name` | the preset name, the identity every reference uses; the filename is *not* authoritative |
| `inherits` | the parent's exact `name`: no path, no `.json` |
| `include` | a template's exact `name`, or an array of them, layered under this preset's own keys ([below](#inherits-and-include)) |
| `instantiation` | the **string** `"true"` (selectable) or `"false"` (base) |
| `from` | `"system"` by convention; the vendor loader never reads it |
| `setting_id` | required on instantiated presets, forbidden on bases — generated |
| `renamed_from` | `;`-separated list of old names this preset supersedes |
| `from` | `"system"` for shipped presets |
| `setting_id` | generated; required on instantiated presets, forbidden on bases |
| `renamed_from` | `;`-separated old names this preset supersedes ([below](#renamed_from)) |
These are config-preset keys; `machine_model` records have their own
[schema](machine-profiles.md#a-machine_model-is-not-a-config-preset). Keep `from` as `"system"`
for shipped presets. The vendor loader ignores it, but the CLI config-file loader accepts only
`system`, `user` or `User` and handles their inheritance differently.
[key set](machine-profiles.md#machine_model-a-record-not-a-config-preset). Keep `from` as `"system"`:
the bundle loader ignores it, but loading the file as a CLI config accepts only `system`, `user` or
`User` and handles their inheritance differently.
`instantiation` is the one metadata key that is hard-gated: a missing key or any value other than the
strings `"true"`/`"false"` is an error (`Missing instantiation attribute for <name>`). A JSON boolean
`true` fails harder — it throws inside `load_from_json` and takes the **whole vendor bundle** down.
`instantiation` is the one metadata key the validator gates: a missing key or any value other than the
strings `"true"` / `"false"` is a counted error (`Missing instantiation attribute for <name>`) that fails
the validator, though the preset still loads and is treated as selectable. A file with no
`instantiation` whose name contains `gcode`, or that has no `name`, silently becomes an include-only
template. A JSON boolean `true` fails harder: it takes the **whole vendor bundle** down.
### `inherits`
## `inherits` and `include`
Resolution is an exact-name lookup **within the same bundle**, plus one exception: filaments may inherit
from `OrcaFilamentLibrary`, which is loaded first and becomes the base bundle. Vendor-to-vendor
inheritance always fails. You can inherit from an instantiated preset as well as from a base; it is
common.
`inherits` resolves by exact name **within the same bundle**, plus one exception: filaments may inherit
from OrcaFilamentLibrary, which is loaded first. Vendor-to-vendor inheritance always fails, and an
unresolved `inherits` discards the bundle. You can inherit from an instantiated preset as well as from a
base.
### `renamed_from`
`"include": ["<name>", …]` (or one bare name) pulls in `instantiation: "false"` presets of the same type
from the same bundle (never the library), registered before the includer. It shares a block of keys
between presets that do not share a parent: a variant layout, a G-code template. Only `"false"` presets
can be included, so a name that resolves to nothing (misspelled, registered after the includer, or a
selectable preset) is a counted error (`can not find include`) and the preset loads without it.
**How a preset's config is composed:** start from the parent's stored config (a root starts from the
built-in defaults), apply each preset named in `include` in the order listed, then the preset's own
keys. Later layers win, so precedence is own keys > later includes > earlier includes > the `inherits`
chain. Only then is every variant key of the composed config resized to its variant length
([widths](extruder-variants.md#widths)), and keys of another preset type removed. The two routes hand
down different widths:
- **`inherits` hands down the resized config.** A base is stored *after* its own resize, at the length
of its own `*_extruder_variant` (one variant for a base of any type that writes none, whatever its
extruder count). A child therefore inherits the base's arrays at the base's width: an array wider than
that is cut to its first values before any child sees it, and a child that adds variants gets those
first values padded. So widen an array only on a preset whose own variant list already has the
entries; a wide array on a narrow base is silently lost at load, and `check`, which composes at the
width each file wrote and judges selectable presets only, misses that cut.
- **`include` hands down the template's diff, at its pre-resize width.** An included preset contributes
every key where its own composed config (its parent, its own includes and its own keys) differs from
the built-in defaults, taken *before* its resize. So keys the template inherits are passed on too,
arrays it writes arrive at the width its file wrote, and a key it sets to the built-in default value
is not passed on at all, so it cannot override what the includer inherited. Resizing happens on the
includer, not on the template.
## `renamed_from`
One JSON string, `;`-separated for several old names.
- Write `"A;B"`, never `"A ; B"` — an unquoted item keeps its trailing space and can never match.
- Write `"A;B"`, never `"A ; B"`: a space after a `;` is skipped, but a space before it stays part of
the name (`"A "`), which can never match.
- When `renamed_from` is **absent** and the name contains `@`, the loader auto-adds the `@`-removed form
(`X @Y` → `X Y`) as a rename alias. Declaring an explicit `renamed_from` **suppresses** that, so a
preset that needs both the `@`-removed form and a real old name must list both. No shipped profile
currently does, which means any preset that gained a `renamed_from` quietly lost its `X Y` alias.
preset that needs both the `@`-removed form and a real old name must list both; a preset that gains
a `renamed_from` without it quietly loses its `X Y` alias.
- It rescues names stored **outside** the tree: user presets and 3MF projects. It does **not** rescue
in-tree `inherits` (exact lookup), it does **not** satisfy `check_name_consistency`, the validator
reports an in-tree reference that only resolves through it (`references renamed compatible_printers
"OLD" (now "NEW")`), and `machine_model` records never read it at all.
- Only one preset may claim a given old name — two that do is a counted error
in-tree `inherits` (exact lookup), it does **not** satisfy the index-name rule, the validator reports
an in-tree reference that only resolves through it (`references renamed compatible_printers "OLD"
(now "NEW")`), and `machine_model` records never read it at all.
- Only one preset may claim a given old name; two that do is a counted error
(`… was marked as renamed from "Y" … as well`). But the redirect is **inert while a live preset still
carries that name**, and nothing checks *that*; Z-Bolt ships a folder of such dead entries.
carries that name**, and nothing checks *that*, so a neighbour's `renamed_from` is no model.
## Failure modes, ranked by blast radius
## Failure scopes
| Scope | Cause |
| --- | --- |
| **All vendors, zero system profiles** | a non-string `version`, `name` or `url` at the top level of a vendor index (`"version": 2`), or non-string `nozzle_diameter` on a model — `nlohmann::type_error` escapes the per-vendor `std::runtime_error` catch |
| **The whole vendor bundle** | unparseable `version`; index JSON parse error; a `sub_path` file missing or unparseable; unresolvable `inherits`; duplicate preset name within the vendor; empty/unknown `printer_model` or `printer_variant`; a filament resolving no `filament_id` |
| **One preset** | `instantiation` missing or a wrong string; keys belonging to another preset type (`contains incorrect keys: …, which were removed`); a non-string inside a `*_list` entry (`invalid value type for <key>`) |
| **Logged, not counted** | a raw JSON number in a preset — `invalid json type for <key>`, the value is dropped and the exit code stays 0 |
| **Nothing reported by the loader** | unregistered file; misspelled setting key; missing bed/hotend asset. Only the first of those is a `check` error; the other two reach users |
| **Every vendor except OrcaFilamentLibrary, and all user presets** | a non-string where the index or a `machine_model` expects a string (`"version": 2` at the top level of an index, a numeric `name` or `url`, a non-string `nozzle_diameter` or other model key): `[json.exception.type_error.302] type must be string`, and the validator reports `Validation failed` |
| **The whole vendor bundle** | index JSON parse error; unparseable `version`; a listed file missing or unparseable; a value its option cannot take, such as `nil` in a non-nullable key (`Failed loading configuration file`); unresolved `inherits`; two selectable presets with one name; empty or unknown `printer_model` / `printer_variant`; a filament resolving no `filament_id`; a JSON boolean `instantiation` |
| **A counted error; the preset still loads** | `instantiation` missing or not `"true"` / `"false"`; keys belonging to another preset type (`contains incorrect keys: …, which were removed`); a non-string inside a `*_list` entry (`invalid value type for <key>`); an `include` naming nothing usable (`can not find include`, loads without it) |
| **The rest of the file, logged only** | an array with a non-string element (`[0.4]`, `invalid json array`): that key and every key after it in the file are dropped, and no error is counted |
| **One value, logged only** | a raw JSON number in a preset (`invalid json type for <key>`): the value is dropped and the exit code stays 0 |
| **Nothing reported by the loader** | unregistered file; two bases with one name, or a base and a selectable preset with one name (the first in the index wins); misspelled setting key; missing bed, hotend or cover asset. `check` catches the first two; the others reach users |
Deleting a file the index still lists surfaces as a *parse error* on line 1, not "file not found" — the
loader `ifstream`s the missing path and nlohmann reports `unexpected end of input`.
Deleting a file the index still lists surfaces as a *parse error* on line 1 (`unexpected end of input`), not "file
not found".
Preset names are a **single global namespace across every vendor**: a duplicate within one vendor is a
hard bundle failure, a duplicate across vendors is reported as `Found duplicated preset: <name> in
vendor: <vendor>` and still counts as an error. `check_preset_name_uniqueness` catches the within-bundle
case earlier and more precisely — including an *unindexed* twin, which is one `sub_path` edit away from
silently becoming the parent every child resolves to (`std::map::emplace` keeps the first insertion, so
index order decides). Base names, by contrast, repeat across bundles by design: `fdm_process_common`
exists in nearly all of them.
Selectable preset names are a **single namespace across every vendor**: a duplicate within one vendor
is a hard bundle failure, and a duplicate across vendors is reported as `Found duplicated preset: <name>
in vendor: <vendor>` and still counts as an error. `check` catches the within-bundle case earlier and
more precisely, bases included, and including an *unindexed* twin, which is one `sub_path` edit away
from silently becoming the parent every child resolves to (the first registered preset of a name wins,
so index order decides). Base names, by contrast, repeat across bundles by design:
every bundle may have its own `fdm_process_common` ([uniqueness](naming.md#uniqueness)).
## Starting a whole new vendor bundle
Nothing generates one; copy the smallest bundle that resembles the hardware. **`Voxelab` or `M3D`** are
the minimal shape — a shared machine base, the model, one variant, a shared process base, two
processes, and an empty `filament_list` that takes the library generics. Do *not* start from `Phrozen`:
it carries local `fdm_filament_*` copies that have drifted from the library, and a filament preset that
restates most of its parent — the style this skill advises against.
Nothing generates one; copy the smallest bundle that resembles the hardware. **`Voxelab`** is the
minimal shape: a shared machine base, the model, one variant, a shared process base, two processes, and
an empty `filament_list`, so the printer takes the library generics. Do *not* start from a bundle that
carries local `fdm_filament_*` copies, which drift from the library, or filament presets that restate
most of their parent, the style this skill advises against.
Write the machine files **last**, so you only visit them once:
1. **Choose the names first** — model, variant(s), process(es). Everything else references them.
1. **Choose the names first**: model, variant(s), process(es). Everything else references them
([naming.md](naming.md)).
2. `resources/profiles/<Vendor>.json`: `name`, `version` (`01.00.00.00`), `force_update: "0"`,
`description`, and all four `*_list` arrays (an empty `filament_list` is fine).
3. The shared bases — `<Vendor>/machine/fdm_machine_common.json` and
`<Vendor>/process/fdm_process_common.json`, both `"instantiation": "false"` with no `setting_id`.
For a Klipper printer add your own `<Vendor>/machine/fdm_klipper_common.json` inheriting the machine
`description`, and all four `*_list` arrays (empty is fine: `update-index` fills them once the files
exist, so this step only needs the bundle metadata to be right).
3. The shared bases: `<Vendor>/machine/fdm_machine_common.json` and
`<Vendor>/process/fdm_process_common.json`, both `"instantiation": "false"` with no `setting_id`. For
a Klipper printer add your own `<Vendor>/machine/fdm_klipper_common.json` inheriting the machine
base; there is no shared one, because a `machine` preset can only inherit inside its own bundle.
4. One selectable process per variant, each naming its variant in `compatible_printers`.
5. Bed assets and `<Model>_cover.png`, all directly in `<Vendor>/`. None of them is needed for the
bundle to load, and nothing in CI checks them — but the bed files are inert unless the `machine_model`
names them in `bed_model` / `bed_texture`, and the cover is found by convention as
bundle to load, and nothing in CI checks them; but the bed files are inert unless the
`machine_model` names them in `bed_model` / `bed_texture`, and the cover is found by convention as
`<the name you gave the model in machine_model_list>_cover.png`.
6. The `machine_model` record and the `machine` variants, now that every value they reference exists —
6. The `machine_model` record and the `machine` variants, now that every value they reference exists;
the minimum key sets and the `default_*` shapes are in
[machine-profiles.md](machine-profiles.md#the-machine-variant).
7. Run the tool and validate — follow
[machine-profiles.md](machine-profiles.md#machine-the-variant).
7. Run the tool and validate: follow
[Creating or modifying a profile](../SKILL.md#creating-or-modifying-a-profile). `generate-id` is not
optional for a new bundle: the validator loads presets that have no `setting_id`, but `check` fails
every one of them. `update-index` will fill the four `*_list` arrays for you once the files exist, so
step 2 only needs the bundle metadata to be right.
every one of them.
## `resources/profiles_template/`
A separate tree (`Template.json` + `Template/`) holding filament and process templates. It is **not** a
scaffold for shipped profiles — `CreatePresetsDialog.cpp` reads it for the in-app "create a custom
printer/filament" wizard, so editing it changes what users get when they create a custom preset.
`check_profile.sh`'s validator checks default to `resources/profiles` (redirectable with `-p`), and so
does `orca_profile_tool.py` (redirectable with `--profiles`);
neither covers this tree.
scaffold for shipped profiles: the app's "create a custom printer / filament" dialog reads it, so
editing it changes what users get when they create a custom preset. `check_profile.sh`'s validator
checks default to `resources/profiles` (redirectable with `-p`), and so does `orca_profile_tool.py`
(redirectable with `--profiles`); neither covers this tree.
@@ -0,0 +1,241 @@
#!/usr/bin/env python3
"""Find settings to move onto shared bases, and prove a move changed nothing.
snapshot OUT.json write every selectable preset's config as the loader stores it
compare BEFORE.json report every value that differs from the snapshot; exit 1 if any
candidates --vendor V restated values; per base, the settings its presets all share and the
defaults with exceptions that would pay
Every subcommand takes --profiles DIR (default resources/profiles).
Configs are composed the loader's way: the parent's stored config, then each include at
the width its file wrote, then the preset's own keys, and every variant key resized to the
preset's own variant list (one variant without one), padded with its first value or cut.
A base is stored after that resize, so a variant array wider than a base's list reaches its
children cut. Not modelled: the built-in defaults. A key no file in a preset's chain writes
loads its default, so compare reports a key written on one side only separately: it is no
change when the written value is the option's default in PrintConfig.cpp. Nor is it modelled
that an include template does not pass on a key equal to the default. Reads
scripts/orca_profile_tool.py.
"""
import argparse
import json
import os
import sys
from collections import Counter, defaultdict
# The profile tool lives in <repo>/scripts; this file in <repo>/.claude/skills/orca-profiles/scripts.
sys.path[:0] = [os.path.join(os.getcwd(), "scripts"),
os.path.join(os.path.dirname(os.path.abspath(__file__)), *[os.pardir] * 4, "scripts")]
import orca_profile_tool as tool # noqa: E402
TYPES = ("machine", "process", "filament")
# Keys the loader reads from each file as metadata; neither inherits nor include passes them on.
PER_FILE = {"type", "name", "from", "instantiation", "setting_id", "renamed_from", "description",
"inherits", "include", "version", "url", "is_custom_defined"}
# Keys the app replaces with the selected presets' names before slicing: a file's value never counts.
REPLACED = {"print_settings_id", "printer_settings_id", "filament_settings_id"}
# What a restructure changes by design, or what never reaches a slice.
MOVED_BY_DESIGN = {"inherits", "include"} | REPLACED
# Keys that stay in their own file: metadata, identity, and each preset's compatibility.
NEVER_SHARED = PER_FILE | REPLACED | {"filament_id", "compatible_printers", "compatible_prints",
"printer_variant", "printer_model"}
class Tree:
def __init__(self, profiles_dir):
self.dir = profiles_dir
self.scheme = tool._variant_scheme()
self.vendors = tool.list_vendor_names(profiles_dir)
self.bundles = {v: tool.load_vendor_configs(profiles_dir, v) for v in self.vendors}
self.cache = {}
def lookup(self, vendor, ptype, name, in_ofl=False):
"""(vendor the name resolves in, (rel, data)); filaments fall back to the library."""
if not in_ofl and name in self.bundles[vendor][ptype]:
return vendor, self.bundles[vendor][ptype][name]
if ptype == "filament" and tool.OFL in self.bundles and name in self.bundles[tool.OFL][ptype]:
return tool.OFL, self.bundles[tool.OFL][ptype][name]
return None, None
def composed(self, vendor, ptype, name, drop=None, seen=frozenset()):
"""Config before the preset's own resize: parent stored, includes, own keys."""
owner, found = self.lookup(vendor, ptype, name, vendor == tool.OFL)
if found is None or (owner, name) in seen:
return {}
seen = seen | {(owner, name)}
data = found[1]
config = {}
if data.get("inherits"):
config.update(self.stored(owner, ptype, data["inherits"], seen))
include = data.get("include") or []
for included in [include] if isinstance(include, str) else include:
if included in self.bundles[owner][ptype]:
config.update(self.composed(owner, ptype, included, seen=seen))
config = {k: v for k, v in config.items() if k not in PER_FILE}
config.update((k, v) for k, v in data.items() if k != drop)
return config
def stored(self, vendor, ptype, name, seen=frozenset()):
key = (vendor, ptype, name)
if key not in self.cache:
self.cache[key] = self.resize(ptype, self.composed(vendor, ptype, name, seen=seen))
return self.cache[key]
def resize(self, ptype, config):
list_key, strides = self.scheme[ptype]
length = len(tool._as_list(config[list_key])) if list_key in config else 1
out = dict(config)
for key, stride in strides.items():
if key in out:
values = tool._as_list(out[key])
need = length * stride
out[key] = values[:need] + values[:1] * (need - len(values))
return out
def presets(self, vendors=None, ptypes=TYPES):
for vendor in vendors or self.vendors:
for ptype in ptypes:
for name, (rel, data) in sorted(self.bundles[vendor][ptype].items()):
yield vendor, ptype, name, rel, data
def snapshot(tree):
return {f"{vendor}/{ptype}/{name}": {k: v for k, v in tree.stored(vendor, ptype, name).items()
if k not in MOVED_BY_DESIGN}
for vendor, ptype, name, _rel, data in tree.presets()
if data.get("instantiation") == "true"}
def compare(before, after):
changed, one_sided = 0, []
for preset in sorted(before.keys() | after.keys()):
old, new = before.get(preset), after.get(preset)
if old is None or new is None:
print(f"{preset}: {'added' if old is None else 'removed'}")
changed += 1
continue
for key in sorted(old.keys() | new.keys()):
if key not in old or key not in new:
one_sided.append(f"{preset}: {key} "
f"{json.dumps(old[key]) if key in old else '(built-in default)'} -> "
f"{json.dumps(new[key]) if key in new else '(built-in default)'}")
elif old[key] != new[key]:
print(f"{preset}: {key} {json.dumps(old[key])} -> {json.dumps(new[key])}")
changed += 1
for line in one_sided:
print(line)
print(f"{changed} difference(s)")
if one_sided:
print(f"{len(one_sided)} key(s) written on one side only: each is a difference unless the "
f"written value is the option's default in src/libslic3r/PrintConfig.cpp")
return changed + len(one_sided)
def candidates(tree, vendor, ptypes, group_by):
for ptype in ptypes:
entries = {name: data for _v, _t, name, _rel, data in tree.presets([vendor], (ptype,))}
selectable = [n for n, d in entries.items() if d.get("instantiation") == "true"]
stored = {n: tree.stored(vendor, ptype, n) for n in entries}
list_key = tree.scheme[ptype][0]
# Restated: a key a file writes that it would inherit unchanged without writing it.
restated = {}
for name, data in entries.items():
restated[name] = sorted(
k for k in data if k not in NEVER_SHARED and k != list_key
and (data.get("inherits") or data.get("include"))
and tree.resize(ptype, tree.composed(vendor, ptype, name, drop=k)).get(k) == stored[name][k])
if restated[name]:
print(f"{vendor}/{ptype} {name}: restates what it inherits: {', '.join(restated[name])}")
# Groups: every preset with selectable presets below it, or the --group-by values.
chain = {n: [] for n in selectable}
for name in selectable:
node = entries[name].get("inherits")
while node in entries and node not in chain[name]:
chain[name].append(node)
node = entries[node].get("inherits")
groups = defaultdict(list)
for name in selectable:
if group_by:
groups[json.dumps(stored[name].get(group_by))].append(name)
else:
for base in chain[name]:
groups[base].append(name)
printed = {}
for label, members in sorted(groups.items(), key=lambda g: -len(g[1])):
if len(members) < 2 or label == "null":
continue
if frozenset(members) in printed:
print(f"\n{vendor}/{ptype} {label}: the same presets as {printed[frozenset(members)]}")
continue
common = [b for b in chain[members[0]] if all(b in chain[m] for m in members[1:])]
home = common[0] if group_by and common else None if group_by else label
def below(m):
"""m and the files between it and the group's home."""
return [m] + chain[m][:chain[m].index(home)] if home in chain[m] else [m]
shared, defaults = [], []
for key in sorted(set().union(*(entries[m].keys() for m in members)) - NEVER_SHARED):
loaded = [json.dumps(stored[m].get(key)) for m in members]
counts = Counter(loaded).most_common(2)
if len(counts) == 1:
writers = sum(key in entries[m] and key not in restated[m] for m in members)
if writers >= 2:
shared.append(f"{key} ({writers} write it)")
continue
(top, held), (_, runner_up) = counts
if held == runner_up or top == "null" or home is None:
continue
# Balance 5: w presets drop their copy; a presets that take the key from the home
# (no file on their way to it writes it) and load another value must write theirs.
w = sum(key in entries[m] and v == top for m, v in zip(members, loaded))
a = sum(v != top and not any(key in entries[f] for f in below(m))
for m, v in zip(members, loaded))
if w - a > 1:
shown = top if len(top) <= 40 else top[:37] + "..."
defaults.append(f"{key} = {shown}: {held} load it, {w} write it, "
f"{a} would have to write their own")
if shared or defaults:
where = f"; nearest common base {common[0]}" if group_by and common else ""
title = f"{group_by} = {label}" if group_by else label
print(f"\n{vendor}/{ptype} {title}: {len(members)} presets{where}")
printed[frozenset(members)] = title
for line in shared:
print(f" {line}")
if defaults:
print(" default with exceptions:")
for line in defaults:
print(f" {line}")
def main():
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
profiles = argparse.ArgumentParser(add_help=False)
profiles.add_argument("--profiles", default=os.path.join("resources", "profiles"),
help="profiles directory (default: resources/profiles)")
sub = parser.add_subparsers(dest="command", required=True)
sub.add_parser("snapshot", parents=[profiles]).add_argument("out")
sub.add_parser("compare", parents=[profiles]).add_argument("before")
cand = sub.add_parser("candidates", parents=[profiles])
cand.add_argument("--vendor", required=True)
cand.add_argument("--type", choices=TYPES, action="append")
cand.add_argument("--group-by", help="group selectable presets by this key's value instead of by "
"base: printer_model, gcode_flavor, extruder_type, filament_id, layer_height, ...")
args = parser.parse_args()
tree = Tree(args.profiles)
if args.command == "snapshot":
presets = snapshot(tree)
with open(args.out, "w", encoding="utf-8") as f:
json.dump(presets, f, sort_keys=True)
print(f"{len(presets)} selectable presets written to {args.out}")
elif args.command == "compare":
with open(args.before, encoding="utf-8") as f:
sys.exit(1 if compare(json.load(f), snapshot(tree)) else 0)
else:
candidates(tree, args.vendor, args.type or TYPES, args.group_by)
if __name__ == "__main__":
main()
+5 -3
View File
@@ -183,9 +183,11 @@ jobs:
os: ${{ vars.SELF_HOSTED && 'orca-macos-arm64' || 'macos-14' }}
artifact: ${{ github.sha }}-tests-macos-arm64
test-dir: build/arm64/tests
# Slice a two-colour cube through every shipped printer so all custom g-code
# (change_filament_gcode, machine start/end, etc.) is expanded - catches
# slicing regressions the static profile checks and unit tests can't see.
# Slice a two-colour cube through every shipped printer, and through every
# system process/filament whose templates no printer's own slice reaches, so
# every custom g-code and filename_format shipped is expanded (names in {if}
# branches not taken included) - catches slicing regressions the static
# profile checks and unit tests can't see.
# Profile-only PRs are covered by check_profiles.yml's nightly binary; this
# covers src/engine PRs with the PR-built binary.
slice_check_linux:
+7 -2
View File
@@ -14,6 +14,9 @@ on:
# this workflow.
- 'resources/printers/**'
- 'scripts/**'
# orca_profile_tool.py reads the variant key sets from PrintConfig.cpp, and its
# tests the obsolete keys, so a PR changing either must be checked against the profiles.
- 'src/libslic3r/PrintConfig.cpp'
- ".github/workflows/check_profiles.yml"
workflow_dispatch:
@@ -71,8 +74,10 @@ jobs:
set +e
./OrcaSlicer_profile_validator -p ${{ github.workspace }}/resources/profiles -l 2 2>&1 | tee ${{ runner.temp }}/validate_system.log
exit ${PIPESTATUS[0]}
# Slice a two-colour cube through every printer so all custom g-code (incl. change_filament_gcode)
# is expanded - catches undefined-placeholder / invalid-flow bugs the static checks above cannot see.
# Slice a two-colour cube through every printer, and through every system process/filament whose
# templates no printer's own slice reaches, so every custom g-code and filename_format shipped is
# expanded (names in {if} branches not taken included) - catches undefined-placeholder /
# invalid-flow bugs the static checks above cannot see.
- name: validate slice (expand custom g-code)
id: validate_slice
continue-on-error: true
+105 -45
View File
@@ -1,5 +1,7 @@
name: Daily OFL OTA Update
run-name: Daily OFL OTA Update [OFL barrier]
# This workflow is intended for creating and publishing the OrcaFilamentLibrary (OFL) OPC package to
# https://github.com/OrcaSlicer/orcaslicer-profiles, which generates an OTA update.
# This cronjob runs daily at 00:00 UTC every day and scans main plus every release/vX.Y.Z branch for
@@ -12,9 +14,9 @@ name: Daily OFL OTA Update
# vendor-dispatch path is also what makes post_merge_profiles.yml call the OTA auto-publish API after
# uploading - see post_merge_profiles.yml for both sides of that contract.
#
# At the start of each run, the pending-publish table is cleared up to a captured
# timestamp (POST /api/v1/ota/ofl/pending/clear?timestamp=...). Changes merged after
# that timestamp remain pending for the next run.
# Each run captures a timestamp, dispatches the needed OFL publishers, waits for
# all of them to finish, then clears the pending-publish table once. Changes merged
# after that timestamp remain pending for the next run.
on:
schedule:
@@ -34,32 +36,14 @@ jobs:
if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' }}
runs-on: ubuntu-24.04
steps:
- name: Capture start timestamp and clear OFL pending queue
- name: Capture start timestamp
id: start
shell: bash
env:
OTA_API_BASE_URL: ${{ vars.OTA_API_BASE_URL }}
OTA_API_KEY: ${{ secrets.OFL_OTA_PUBLISH_KEY }}
run: |
set -euo pipefail
[ -n "$OTA_API_BASE_URL" ] || { echo "::error::vars.OTA_API_BASE_URL is not set"; exit 1; }
[ -n "$OTA_API_KEY" ] || { echo "::error::secrets.OFL_OTA_PUBLISH_KEY is not set"; exit 1; }
timestamp="$(date -u +%s)"
echo "timestamp=$timestamp" >> "$GITHUB_OUTPUT"
resp_file="$RUNNER_TEMP/ota-pending-clear-response.json"
status="$(curl -sS -o "$resp_file" -w '%{http_code}' -X POST \
"${OTA_API_BASE_URL%/}/api/v1/ota/ofl/pending/clear?timestamp=$timestamp" \
-H "Authorization: Bearer $OTA_API_KEY")"
body="$(cat "$resp_file")"
echo "$body"
if [ "$status" != "200" ]; then
echo "::error::OTA pending-clear call failed with HTTP $status"
exit 1
fi
- name: Checkout repository
uses: actions/checkout@v7
with:
@@ -84,27 +68,21 @@ jobs:
| grep -E '^(main|release/v[0-9]+\.[0-9]+\.[0-9]+)$' | sort -u
)
# The cron run is the checkpoint: a successful run means every
# dispatched branch publisher completed and the pending queue was
# cleared. Manual or push-triggered post_merge_profiles runs are not
# checkpoints for this scan.
successful_cron_runs="$(gh api --method GET \
"repos/${{ github.repository }}/actions/workflows/ofl-ota-cronjob.yml/runs" \
-f status=success -f branch=main -f per_page=100 --paginate \
--jq '.workflow_runs[] | select((.display_title // "") | contains("[OFL barrier]"))')"
since="$(jq -rs 'sort_by(.run_started_at) | last.run_started_at // empty' <<< "$successful_cron_runs")"
for branch in "${branches[@]}"; do
echo "::group::$branch"
# post_merge_profiles.yml's own run history, not this workflow's: this
# workflow only ever runs against main (schedule, or workflow_dispatch
# --ref main), so its head branch never varies - filtering ITS history
# by $branch would never match anything except main. post_merge_profiles.yml
# genuinely runs per-branch (this dispatch below sets --ref "$branch"),
# so its history is the real per-branch checkpoint. It also means a
# failed publish naturally gets retried tomorrow: the checkpoint only
# advances on a run that actually succeeded.
# --method GET is required, not cosmetic: gh api defaults to POST
# whenever -f fields are present unless a method is given
# explicitly, and POST on this list-runs endpoint 404s - confirmed
# on real Actions infrastructure, not just reasoned about.
since="$(gh api --method GET "repos/${{ github.repository }}/actions/workflows/post_merge_profiles.yml/runs" \
-f status=success -f branch="$branch" -f per_page=1 \
--jq '.workflow_runs[0].run_started_at // empty')"
if [ -z "$since" ]; then
echo "No prior successful run for $branch; checking OFL changes up to $SCAN_UNTIL."
echo "No prior successful OFL cron run; checking $branch through $SCAN_UNTIL."
changed_files="$(git log --until="$SCAN_UNTIL" --name-only --pretty=format: "origin/$branch" -- \
resources/profiles/OrcaFilamentLibrary resources/profiles/OrcaFilamentLibrary.json \
| sed '/^$/d')"
@@ -124,16 +102,98 @@ jobs:
fi
if [ "$changed" = true ]; then
# Tolerate a per-branch failure (e.g. a pre-existing release branch
# whose post_merge_profiles.yml predates the vendor/auto_publish
# inputs) rather than aborting the whole scan under set -e.
if ! gh workflow run post_merge_profiles.yml \
dispatch_id="${GITHUB_RUN_ID}-${branch//\//-}"
# Record successful dispatches for the barrier step below.
# Branches whose workflow predates workflow_dispatch are skipped
# with a warning, as they were before the barrier was added.
if gh workflow run post_merge_profiles.yml \
--repo "${{ github.repository }}" \
--ref "$branch" \
-f vendor="$VENDOR" -f auto_publish=true; then
echo "::warning::failed to dispatch post_merge_profiles.yml for $branch - its post_merge_profiles.yml at this ref may predate the vendor/auto_publish inputs"
-f vendor="$VENDOR" -f auto_publish=true \
-f ofl_cron_dispatch_id="$dispatch_id"; then
printf '%s\t%s\n' "$branch" "$dispatch_id" >> "$RUNNER_TEMP/ofl-dispatches.tsv"
else
echo "::warning::skipping $branch because post_merge_profiles.yml could not be dispatched at that ref"
fi
fi
echo "::endgroup::"
done
- name: Wait for OFL publishers
id: wait
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DISPATCHES_FILE: ${{ runner.temp }}/ofl-dispatches.tsv
run: |
set -euo pipefail
if [ ! -s "$DISPATCHES_FILE" ]; then
echo "No OFL publisher workflows were dispatched; pending queue will not be cleared."
echo "publishers_dispatched=false" >> "$GITHUB_OUTPUT"
exit 0
fi
: > "$RUNNER_TEMP/ofl-run-ids.tsv"
while IFS=$'\t' read -r branch dispatch_id; do
[ -n "$branch" ] || continue
echo "Waiting for OFL publisher on $branch ($dispatch_id)"
run_id=""
for _ in {1..120}; do
runs_json="$(gh api --method GET \
"repos/${{ github.repository }}/actions/workflows/post_merge_profiles.yml/runs" \
-f branch="$branch" -f event=workflow_dispatch -f per_page=100)"
run_id="$(jq -r --arg marker "[OFL cron $dispatch_id]" \
'[.workflow_runs[] | select((.display_title // "") | contains($marker))] \
| sort_by(.created_at) | last | .id // empty' <<< "$runs_json")"
[ -n "$run_id" ] && break
sleep 5
done
if [ -z "$run_id" ]; then
echo "::error::could not find dispatched post_merge_profiles run for $branch ($dispatch_id)"
exit 1
fi
printf '%s\t%s\n' "$branch" "$run_id" >> "$RUNNER_TEMP/ofl-run-ids.tsv"
done < "$DISPATCHES_FILE"
all_success=true
while IFS=$'\t' read -r branch run_id; do
[ -n "$run_id" ] || continue
echo "Watching OFL publisher run $run_id for $branch"
if ! gh run watch "$run_id" --repo "${{ github.repository }}" --exit-status; then
all_success=false
fi
done < "$RUNNER_TEMP/ofl-run-ids.tsv"
if [ "$all_success" != true ]; then
echo "::error::one or more OFL publisher workflows failed; pending queue will not be cleared"
exit 1
fi
echo "publishers_dispatched=true" >> "$GITHUB_OUTPUT"
- name: Clear OFL pending queue
if: steps.wait.outputs.publishers_dispatched == 'true'
shell: bash
env:
OTA_API_BASE_URL: ${{ vars.OTA_API_BASE_URL }}
OTA_API_KEY: ${{ secrets.OFL_OTA_PUBLISH_KEY }}
TIMESTAMP: ${{ steps.start.outputs.timestamp }}
run: |
set -euo pipefail
[ -n "$OTA_API_BASE_URL" ] || { echo "::error::vars.OTA_API_BASE_URL is not set"; exit 1; }
[ -n "$OTA_API_KEY" ] || { echo "::error::secrets.OFL_OTA_PUBLISH_KEY is not set"; exit 1; }
resp_file="$RUNNER_TEMP/ota-pending-clear-response.json"
status="$(curl -sS -o "$resp_file" -w '%{http_code}' -X POST \
"${OTA_API_BASE_URL%/}/api/v1/ota/ofl/pending/clear?timestamp=$TIMESTAMP" \
-H "Authorization: Bearer $OTA_API_KEY")"
body="$(cat "$resp_file")"
echo "$body"
if [ "$status" != "200" ]; then
echo "::error::OTA pending-clear call failed with HTTP $status"
exit 1
fi
+15
View File
@@ -1,5 +1,14 @@
name: Post-merge profiles
run-name: >-
Post-merge profiles${{
inputs.ofl_cron_dispatch_id != '' &&
inputs.vendor == 'OrcaFilamentLibrary' &&
(inputs.auto_publish == true || inputs.auto_publish == 'true') &&
format(' [OFL cron {0}]', inputs.ofl_cron_dispatch_id) ||
''
}}
# Push-triggered counterpart to check_profiles.yml (which only gates PRs). When a
# profile change lands on main or a release branch, rebuild the affected vendors'
# binary preset caches (<vendor>.opc) and publish each as a versioned ZIP asset on
@@ -59,6 +68,12 @@ on:
required: false
type: boolean
default: false
ofl_cron_dispatch_id:
description: >-
Unique marker supplied by the trusted OFL daily cron so it can find
and wait for this dispatched workflow run.
required: false
type: string
permissions:
contents: read
+239
View File
@@ -0,0 +1,239 @@
# Precise Seam — High Level Design
## Purpose and scope
Precise Seam places the seam where a helper volume intersects the external
wall. The user attaches a mesh to an object as a Precise Seam modifier, and on
every layer the seam placer reads the modifier's slice to decide where the seam
of each external perimeter may, must or must not go. The same mesh keeps
working after the model changes, so the seam does not have to be repainted
after every design revision, and a swept helper body can guide the seam along
any path.
The modifier is non-printing geometry. It does not take part in slicing, region
assignment, filament selection or brim adhesion. It affects only seam
placement, which runs during G-code export.
## Volume types and priority
Precise Seam adds six `ModelVolumeType` values after `SUPPORT_ENFORCER`. The
strong types come first and the weak types follow. `is_precise_seam()`,
`is_precise_seam_strong()` and `is_precise_seam_weak()` are range checks that
depend on this order.
| Type | Group | Effect on the perimeter |
| --- | --- | --- |
| `PRECISE_SEAM_CENTER` | strong | seam at the arc-length midpoint of the intersection |
| `PRECISE_SEAM_LEFT` | strong | seam at the first point of the intersection |
| `PRECISE_SEAM_RIGHT` | strong | seam at the last point of the intersection |
| `PRECISE_SEAM_ENFORCED` | weak | intersection marked as enforced |
| `PRECISE_SEAM_BLOCKED` | weak | intersection marked as blocked |
| `PRECISE_SEAM_NEUTRAL` | weak | intersection reset to neutral |
A strong modifier fixes one point. A weak modifier only changes the
enforced/blocked type of seam candidates, and the configured seam position then
chooses among them. First and last are taken along the perimeter made
counter-clockwise seen from above. On an outer wall seen from outside, Left is
the left end of the intersection. On the wall of a hole seen from inside the
hole, the two ends are swapped.
The order of volumes in the object is the priority order, highest first.
`ModelObject::sort_volumes()` keeps every strong modifier before every weak one
and preserves the user's order within each group. The object list lets the user
drag a modifier only within its own group. A type change that crosses a group
boundary moves the volume to the end of its new group, where it has the lowest
priority. Strong modifiers are tried in this order, and the first one that
yields a seam on a perimeter wins. Weak modifiers are applied from the lowest
priority to the highest, so the highest one overwrites any overlapping zone.
## Model storage and 3MF compatibility
Projects must stay readable by earlier releases, and the modifier must not
change a print there. Both 3MF writers therefore store a Precise Seam volume as
an ordinary parameter modifier: `modifier_part` in the Bambu-format part
subtype, and `ParameterModifier` together with the legacy `modifier` flag in
the Prusa-format volume metadata. The seam mode is written separately under
`precise_seam_type`, using the names from `ModelVolume::type_to_string()`
(`precise_seam_center` and so on).
On load, the mode applies after all other volume metadata, regardless of XML
key order, and only when the base type is a modifier. Missing or unknown modes
leave an ordinary modifier. Seam metadata on any other base type is ignored.
Files that stored the seam mode directly as the volume type still load.
A Precise Seam volume keeps any per-volume settings it had as a part or
modifier, but they are inactive and the object list shows no settings item for
it. The writers prefix these keys with `precise_seam_config:`, so an earlier
reader drops them as unknown options. The volume therefore loads there as a
modifier without settings and has no effect on the print. The current reader
restores the keys only when the volume ends up as a Precise Seam type, so the
settings return when the user changes the type back. Configuration values are
XML-escaped in both writers, for every volume type.
## Print invalidation
`Print::apply()` compares the Precise Seam volumes of each object by type, ID
and transformation. Adding, removing, moving, reordering or retyping one
cancels background processing and invalidates only `psGCodeExport`; the sliced
layers are kept. `model_volume_list_update_supports_and_seams()` then brings
the support and Precise Seam volumes of the print's model copy in line with the
new model in one pass. A volume may switch between the two families, since
neither affects slicing. A conversion to or from a part or ordinary modifier
changes the solid and modifier volume lists and reslices as before.
## Modifier slices
`SeamPlacer::init()` collects the Precise Seam volumes of each object once:
strong ones in priority order and weak ones reversed. It slices each volume
separately with `PrintObject::slice_single_volume()`, which shares
`slice_modifier_volumes()` with support blockers and enforcers but does not
merge volumes, so each keeps its own priority. The result is cached per volume
and indexed by object layer; `Layer::id()` includes raft layers, which are
subtracted. Seam candidates are then gathered in parallel over the layers and
read the cache without locking.
Objects without Precise Seam volumes follow the unchanged seam placement path.
For objects that have them, perimeter extraction also removes consecutive
duplicate points and the repeated closing point of each extrusion loop.
Zero-length edges at path junctions would otherwise prevent point insertion
there. Distinct visits to one point of a self-touching contour are kept.
## Finding the wall segment
The seam placer works on the external perimeter loops of each layer, both
outer contours and holes, each made counter-clockwise. For every modifier
polygon on the layer that overlaps the perimeter's bounding box, the region
enclosed by the perimeter is clipped against the modifier polygon. The boundary
of each intersection polygon alternates between runs that follow the perimeter
and runs that follow the modifier outline. The wall segment is the longest
continuous run of intersection vertices that lie on the perimeter, measured in
vertices.
The fast path first finds an intersection vertex that exactly matches a
perimeter vertex. It then walks forward and backward, expecting the adjacent
perimeter vertex and falling back to projection when Clipper has merged or
split collinear edges. A vertex counts as on the perimeter when its projection
is within about 1.6 nm, which covers Clipper's rounding. If no vertex matches
exactly, or every vertex lies on the perimeter, the general path projects all
vertices. When every vertex is on the perimeter, the edge midpoints are checked
instead: a modifier chord can join two perimeter vertices directly, and the
chords split the vertex ring into runs. If no edge leaves the perimeter, the
perimeter lies entirely inside the modifier.
`Polygon::point_projection()` optionally reports the edge that holds the
projection, and every point of the segment keeps the index of its perimeter
edge. New points are inserted on that edge. A point within 1 µm of an existing
vertex snaps to that vertex instead.
## Strong modifiers
For a strong modifier, the target is the first point, the last point or the
arc-length midpoint of the segment. The midpoint is projected back onto the
original perimeter, because Clipper may have merged several perimeter edges
into one segment edge. The target is inserted into the perimeter, and a helper
point is inserted 1 µm before and after it. Strong modifiers are tried in
priority order, the first valid intersection decides the seam, and weak
modifiers are not processed for that perimeter.
When candidates are built, the inserted point is the only enforced candidate
and becomes the central enforcer; every other candidate is blocked. The seam
position modes then pick that point: Aligned and Aligned Back prefer the central
enforcer, while Back, Random and Nearest rank enforced candidates above blocked
ones. Alignment and random placement can still move the final position along an
edge. After alignment, `restore_precise_seam_positions()` writes the exact point
and its index back into every perimeter that has a strong seam. Inner walls take
their seam from the external seam as usual, including staggering.
## Weak modifiers
Weak modifiers produce one segment per intersection polygon, so one modifier can
mark several zones on one perimeter. All segment boundaries are inserted into
the perimeter in order of decreasing arc length. Each insertion then leaves the
indices of the pending, shorter ones unchanged; a point on the closing edge is
appended rather than inserted at index zero. A helper point is added 1 µm
outside each boundary. Random placement picks a position along the edge that
follows a candidate. These helpers keep that edge 1 µm long at each boundary, so
a zone cannot extend or intrude further than that. Boundaries that coincide
share their helper points.
The zone types are then resolved in priority order, and the edges of enforced
zones are subdivided into steps of at most
`SeamPlacer::enforcer_oversampling_distance` (0.2 mm). The middle candidate of
the longest enforced patch is therefore close to the geometric middle of the
zone. That patch is measured in candidates, across the closing edge, regardless
of where the contour starts; the same rule applies to painted seams.
Candidates first receive their type from seam painting. The weak zones then
overwrite it, lowest priority first. Blocked and Enforced zones therefore take
precedence over painting, and Neutral clears painting inside its zone.
## Unsupported geometry and warnings
Some modifier shapes cannot be resolved to one seam or one zone per crossing.
They are detected cheaply and reported rather than guessed:
- A strong modifier that crosses a perimeter in more than one place uses only
its first valid segment. The other crossings are ignored.
- A modifier that crosses the whole region enclosed by the perimeter is
detected when the modifier outline minus that region leaves more than one
piece, none of them a hole. Its intersection holds two wall runs, and only
one of them is used.
- A modifier whose slice has a hole on a layer, found as a clockwise polygon in
the flattened slice, is skipped on that layer. The flattened slice no longer
records which hole belongs to which contour.
- A perimeter that lies entirely inside a modifier is ignored by that modifier.
The conditions are atomic flags shared by all layers and objects. After all
objects are processed, `SeamPlacer::init()` issues at most one non-critical
warning with the ID `SlicingPreciseSeamWarning`. The warning is a single line
that lists every cause found, because the export warnings dialog shows only the
first line of each warning. Repeated warning events replace this notification
instead of appending text to it.
## User interface
- *Add Precise Seam* in the object menu creates a Center modifier from a
primitive or a loaded mesh. Text and SVG volumes cannot become Precise Seam
modifiers: the menu does not offer them, and `ObjectList::set_volume_type()`
refuses the change.
- *Change Type* has a single *Precise Seam* entry. It converts other volumes to
Center and keeps the mode of volumes that are already Precise Seam. The
*Precise Seam Type* submenu appears only when every selected item is a
Precise Seam volume, including settings rows that resolve to one. It sets the
chosen mode on all selected volumes.
- Each mode has its own icon in the object list and its own color in the 3D
view, at 60% opacity: warm oranges for the strong modes, and green, red and
gray for Enforced, Blocked and Neutral.
- Object list drops map visible rows to volume indices while skipping hidden
cut connectors, and they refresh the row-to-volume map of the object.
- Precise Seam volumes have no filament, block pasting into SLA, and are exposed
to Python plugins as `ModelVolumeType` values plus the `is_precise_seam*()`
methods.
## Implementation and verification
- [PreciseSeam.cpp](../../src/libslic3r/GCode/PreciseSeam.cpp) implements segment
detection, point insertion, weak-zone resolution and position restoration.
[SeamPlacer.cpp](../../src/libslic3r/GCode/SeamPlacer.cpp) integrates it into
candidate gathering and issues the warning.
- [Model.hpp](../../src/libslic3r/Model.hpp) defines the types and their order,
[PrintApply.cpp](../../src/libslic3r/PrintApply.cpp) handles invalidation, and
[PrintObjectSlice.cpp](../../src/libslic3r/PrintObjectSlice.cpp) slices the
modifiers. [bbs_3mf.cpp](../../src/libslic3r/Format/bbs_3mf.cpp) and
[3mf.cpp](../../src/libslic3r/Format/3mf.cpp) store them.
- [GUI_Factories.cpp](../../src/slic3r/GUI/GUI_Factories.cpp) and
[GUI_ObjectList.cpp](../../src/slic3r/GUI/GUI_ObjectList.cpp) provide the menus,
type changes and ordering.
- [Precise Seam tests](../../tests/fff_print/test_precise_seam.cpp) cover the
strong positions, including a midpoint on an existing vertex or the closing
edge. They also cover shared and coincident weak boundaries, every warning,
and the priority order.
- [Seam placer tests](../../tests/fff_print/test_seam_placer.cpp) cover
enforced-patch selection independent of the contour start, fully painted
contours, duplicate removal, and `Print::apply()` synchronization through
type changes and restored model snapshots.
- [3MF tests](../../tests/libslic3r/test_precise_seam_3mf.cpp) cover the round
trip of every mode and of inactive settings, attribute escaping, and which
metadata combinations restore a seam mode.
[Plugin tests](../../tests/slic3rutils/test_precise_seam_plugin.cpp) cover the
Python bindings.
+1
View File
@@ -213,6 +213,7 @@ src/slic3r/Utils/Process.cpp
src/libslic3r/GCode.cpp
src/libslic3r/GCodeWriter.cpp
src/libslic3r/GCode/ToolOrdering.cpp
src/libslic3r/GCode/SeamPlacer.cpp
src/libslic3r/ExtrusionEntity.cpp
src/libslic3r/Flow.cpp
src/libslic3r/Format/AMF.cpp
+19
View File
@@ -27357,3 +27357,22 @@ msgstr ""
#~ msgid "Right click to reset value to system default."
#~ msgstr "Правая кнопка мыши - сброс значения до системного по умолчанию."
# AI Translated
msgid "Precise Seam"
msgstr "Точный шов"
msgid "multiple intersections with a perimeter detected"
msgstr "обнаружены множественные пересечения с периметром"
msgid "modifier fully crosses the printable perimeter"
msgstr "модификатор пересекает печатаемый периметр насквозь"
msgid "modifier shape is not solid (has holes inside) and was ignored"
msgstr "форма модификатора не сплошная (имеет отверстия) и была проигнорирована"
msgid "perimeter is fully contained inside modifier and was ignored"
msgstr "периметр полностью содержится внутри модификатора и был проигнорирован"
msgid "Seam placement may differ from expected."
msgstr "Размещение шва может отличаться от ожидаемого."
@@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<!-- Рамка -->
<path d="M0.5,13.5L0.5,1.5C0.5,0.951,0.951,0.5,1.5,0.5L13.5,0.5C14.049,0.5,14.5,0.951,14.5,1.5L14.5,13.5C14.5,14.049,14.049,14.5,13.5,14.5L1.5,14.5C0.951,14.5,0.5,14.049,0.5,13.5Z" style="fill:none;stroke:#949494;stroke-width:1px"/>
<!-- Центральные пунктирные линии -->
<rect x="7" y="1" width="1" height="1" style="fill:#009688"/>
<rect x="7" y="3" width="1" height="1" style="fill:#009688"/>
<rect x="7" y="5" width="1" height="1" style="fill:#009688"/>
<rect x="7" y="7" width="1" height="1" style="fill:#009688"/>
<rect x="7" y="9" width="1" height="1" style="fill:#009688"/>
<rect x="7" y="11" width="1" height="1" style="fill:#009688"/>
<rect x="7" y="13" width="1" height="1" style="fill:#009688"/>
<!-- Правая стрелка -->
<path d="M9.5,7.5L12.504,4.504L12.5,10.5L9.5,7.5Z" style="fill:#949494;stroke:#949494;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5"/>
<!-- Левая стрелка -->
<path d="M5.501928,7.5L2.497928,4.504L2.501928,10.5L5.501928,7.5Z" style="fill:#949494;stroke:#949494;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,28 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<!-- Рамка -->
<path d="M0.5,13.5L0.5,1.5C0.5,0.951,0.951,0.5,1.5,0.5L13.5,0.5C14.049,0.5,14.5,0.951,14.5,1.5L14.5,13.5C14.5,14.049,14.049,14.5,13.5,14.5L1.5,14.5C0.951,14.5,0.5,14.049,0.5,13.5Z" style="fill:none;stroke:#949494;stroke-width:1px"/>
<!-- Левые пунктирные линии -->
<rect x="2" y="1" width="1" height="1" style="fill:#009688"/>
<rect x="2" y="3" width="1" height="1" style="fill:#009688"/>
<rect x="2" y="5" width="1" height="1" style="fill:#009688"/>
<rect x="2" y="7" width="1" height="1" style="fill:#009688"/>
<rect x="2" y="9" width="1" height="1" style="fill:#009688"/>
<rect x="2" y="11" width="1" height="1" style="fill:#009688"/>
<rect x="2" y="13" width="1" height="1" style="fill:#009688"/>
<!-- Правые пунктирные линии -->
<rect x="12" y="1" width="1" height="1" style="fill:#009688"/>
<rect x="12" y="3" width="1" height="1" style="fill:#009688"/>
<rect x="12" y="5" width="1" height="1" style="fill:#009688"/>
<rect x="12" y="7" width="1" height="1" style="fill:#009688"/>
<rect x="12" y="9" width="1" height="1" style="fill:#009688"/>
<rect x="12" y="11" width="1" height="1" style="fill:#009688"/>
<rect x="12" y="13" width="1" height="1" style="fill:#009688"/>
<!-- Круг (blocked seam marker) -->
<circle cx="7.5" cy="7.5" r="3.5" style="fill:#B16F6D"/>
<!-- Горизонтальная линия (минус) -->
<rect x="5" y="7" width="5" height="1" style="fill:#fff"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

@@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<!-- Рамка -->
<path d="M0.5,13.5L0.5,1.5C0.5,0.951,0.951,0.5,1.5,0.5L13.5,0.5C14.049,0.5,14.5,0.951,14.5,1.5L14.5,13.5C14.5,14.049,14.049,14.5,13.5,14.5L1.5,14.5C0.951,14.5,0.5,14.049,0.5,13.5Z" style="fill:none;stroke:#949494;stroke-width:1px"/>
<!-- Центральные пунктирные линии -->
<rect x="7" y="1" width="1" height="1" style="fill:#009688"/>
<rect x="7" y="3" width="1" height="1" style="fill:#009688"/>
<rect x="7" y="5" width="1" height="1" style="fill:#009688"/>
<rect x="7" y="7" width="1" height="1" style="fill:#009688"/>
<rect x="7" y="9" width="1" height="1" style="fill:#009688"/>
<rect x="7" y="11" width="1" height="1" style="fill:#009688"/>
<rect x="7" y="13" width="1" height="1" style="fill:#009688"/>
<!-- Правая стрелка -->
<path d="M9.5,7.5L12.504,4.504L12.5,10.5L9.5,7.5Z" style="fill:#949494;stroke:#949494;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5"/>
<!-- Левая стрелка -->
<path d="M5.501928,7.5L2.497928,4.504L2.501928,10.5L5.501928,7.5Z" style="fill:#949494;stroke:#949494;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,29 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<!-- Рамка -->
<path d="M0.5,13.5L0.5,1.5C0.5,0.951,0.951,0.5,1.5,0.5L13.5,0.5C14.049,0.5,14.5,0.951,14.5,1.5L14.5,13.5C14.5,14.049,14.049,14.5,13.5,14.5L1.5,14.5C0.951,14.5,0.5,14.049,0.5,13.5Z" style="fill:none;stroke:#949494;stroke-width:1px"/>
<!-- Левые пунктирные линии -->
<rect x="2" y="1" width="1" height="1" style="fill:#009688"/>
<rect x="2" y="3" width="1" height="1" style="fill:#009688"/>
<rect x="2" y="5" width="1" height="1" style="fill:#009688"/>
<rect x="2" y="7" width="1" height="1" style="fill:#009688"/>
<rect x="2" y="9" width="1" height="1" style="fill:#009688"/>
<rect x="2" y="11" width="1" height="1" style="fill:#009688"/>
<rect x="2" y="13" width="1" height="1" style="fill:#009688"/>
<!-- Правые пунктирные линии -->
<rect x="12" y="1" width="1" height="1" style="fill:#009688"/>
<rect x="12" y="3" width="1" height="1" style="fill:#009688"/>
<rect x="12" y="5" width="1" height="1" style="fill:#009688"/>
<rect x="12" y="7" width="1" height="1" style="fill:#009688"/>
<rect x="12" y="9" width="1" height="1" style="fill:#009688"/>
<rect x="12" y="11" width="1" height="1" style="fill:#009688"/>
<rect x="12" y="13" width="1" height="1" style="fill:#009688"/>
<!-- Круг (enforced seam marker) -->
<circle cx="7.5" cy="7.5" r="3.5" style="fill:#009688"/>
<!-- Крест внутри круга -->
<rect x="7" y="5" width="1" height="5" style="fill:#fff"/>
<rect x="5" y="7" width="5" height="1" style="fill:#fff"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

@@ -0,0 +1,16 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<!-- Рамка -->
<path d="M0.5,13.5L0.5,1.5C0.5,0.951,0.951,0.5,1.5,0.5L13.5,0.5C14.049,0.5,14.5,0.951,14.5,1.5L14.5,13.5C14.5,14.049,14.049,14.5,13.5,14.5L1.5,14.5C0.951,14.5,0.5,14.049,0.5,13.5Z" style="fill:none;stroke:#949494;stroke-width:1px"/>
<!-- Центральные пунктирные линии -->
<rect x="7" y="1" width="1" height="1" style="fill:#009688"/>
<rect x="7" y="3" width="1" height="1" style="fill:#009688"/>
<rect x="7" y="5" width="1" height="1" style="fill:#009688"/>
<rect x="7" y="7" width="1" height="1" style="fill:#009688"/>
<rect x="7" y="9" width="1" height="1" style="fill:#009688"/>
<rect x="7" y="11" width="1" height="1" style="fill:#009688"/>
<rect x="7" y="13" width="1" height="1" style="fill:#009688"/>
<!-- Правая стрелка -->
<path d="M9.5,7.5L12.504,4.504L12.5,10.5L9.5,7.5Z" style="fill:#949494;stroke:#949494;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,25 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<!-- Рамка -->
<path d="M0.5,13.5L0.5,1.5C0.5,0.951,0.951,0.5,1.5,0.5L13.5,0.5C14.049,0.5,14.5,0.951,14.5,1.5L14.5,13.5C14.5,14.049,14.049,14.5,13.5,14.5L1.5,14.5C0.951,14.5,0.5,14.049,0.5,13.5Z" style="fill:none;stroke:#949494;stroke-width:1px"/>
<!-- Левые пунктирные линии -->
<rect x="2" y="1" width="1" height="1" style="fill:#009688"/>
<rect x="2" y="3" width="1" height="1" style="fill:#009688"/>
<rect x="2" y="5" width="1" height="1" style="fill:#009688"/>
<rect x="2" y="7" width="1" height="1" style="fill:#009688"/>
<rect x="2" y="9" width="1" height="1" style="fill:#009688"/>
<rect x="2" y="11" width="1" height="1" style="fill:#009688"/>
<rect x="2" y="13" width="1" height="1" style="fill:#009688"/>
<!-- Правые пунктирные линии -->
<rect x="12" y="1" width="1" height="1" style="fill:#009688"/>
<rect x="12" y="3" width="1" height="1" style="fill:#009688"/>
<rect x="12" y="5" width="1" height="1" style="fill:#009688"/>
<rect x="12" y="7" width="1" height="1" style="fill:#009688"/>
<rect x="12" y="9" width="1" height="1" style="fill:#009688"/>
<rect x="12" y="11" width="1" height="1" style="fill:#009688"/>
<rect x="12" y="13" width="1" height="1" style="fill:#009688"/>
<!-- Круг (neutral seam marker) -->
<circle cx="7.5" cy="7.5" r="3.5" style="fill:#949494"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -0,0 +1,16 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<!-- Рамка -->
<path d="M0.5,13.5L0.5,1.5C0.5,0.951,0.951,0.5,1.5,0.5L13.5,0.5C14.049,0.5,14.5,0.951,14.5,1.5L14.5,13.5C14.5,14.049,14.049,14.5,13.5,14.5L1.5,14.5C0.951,14.5,0.5,14.049,0.5,13.5Z" style="fill:none;stroke:#949494;stroke-width:1px"/>
<!-- Центральные пунктирные линии -->
<rect x="7" y="1" width="1" height="1" style="fill:#009688"/>
<rect x="7" y="3" width="1" height="1" style="fill:#009688"/>
<rect x="7" y="5" width="1" height="1" style="fill:#009688"/>
<rect x="7" y="7" width="1" height="1" style="fill:#009688"/>
<rect x="7" y="9" width="1" height="1" style="fill:#009688"/>
<rect x="7" y="11" width="1" height="1" style="fill:#009688"/>
<rect x="7" y="13" width="1" height="1" style="fill:#009688"/>
<!-- Левая стрелка -->
<path d="M5.501928,7.5L2.497928,4.504L2.501928,10.5L5.501928,7.5Z" style="fill:#949494;stroke:#949494;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,25 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<!-- Рамка -->
<path d="M0.5,13.5L0.5,1.5C0.5,0.951,0.951,0.5,1.5,0.5L13.5,0.5C14.049,0.5,14.5,0.951,14.5,1.5L14.5,13.5C14.5,14.049,14.049,14.5,13.5,14.5L1.5,14.5C0.951,14.5,0.5,14.049,0.5,13.5Z" style="fill:none;stroke:#949494;stroke-width:1px"/>
<!-- Левые пунктирные линии -->
<rect x="2" y="1" width="1" height="1" style="fill:#009688"/>
<rect x="2" y="3" width="1" height="1" style="fill:#009688"/>
<rect x="2" y="5" width="1" height="1" style="fill:#009688"/>
<rect x="2" y="7" width="1" height="1" style="fill:#009688"/>
<rect x="2" y="9" width="1" height="1" style="fill:#009688"/>
<rect x="2" y="11" width="1" height="1" style="fill:#009688"/>
<rect x="2" y="13" width="1" height="1" style="fill:#009688"/>
<!-- Правые пунктирные линии -->
<rect x="12" y="1" width="1" height="1" style="fill:#009688"/>
<rect x="12" y="3" width="1" height="1" style="fill:#009688"/>
<rect x="12" y="5" width="1" height="1" style="fill:#009688"/>
<rect x="12" y="7" width="1" height="1" style="fill:#009688"/>
<rect x="12" y="9" width="1" height="1" style="fill:#009688"/>
<rect x="12" y="11" width="1" height="1" style="fill:#009688"/>
<rect x="12" y="13" width="1" height="1" style="fill:#009688"/>
<!-- Галочка (checkmark) -->
<path d="M5.086213,7.556L6.953213,9.867L10.420213,4.444" style="fill:none;stroke:#949494;stroke-width:1.41px;stroke-linecap:round;stroke-linejoin:round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "Afinia",
"version": "02.04.00.05",
"version": "02.04.00.06",
"force_update": "0",
"description": "Afinia configurations",
"machine_model_list": [
@@ -1,11 +1,11 @@
{
"type": "filament",
"filament_id": "OFLfywkp",
"setting_id": "wAJTMxtCY7EoavRi",
"name": "Afinia ABS+@HS",
"from": "system",
"instantiation": "true",
"inherits": "fdm_filament_abs",
"from": "system",
"setting_id": "wAJTMxtCY7EoavRi",
"filament_id": "OFLfywkp",
"instantiation": "true",
"filament_flow_ratio": [
"0.95"
],
@@ -1,11 +1,11 @@
{
"type": "filament",
"filament_id": "OFV5wEMe",
"setting_id": "qCDnb2iBaz4hd4vX",
"name": "Afinia ABS@HS",
"from": "system",
"instantiation": "true",
"inherits": "fdm_filament_abs",
"from": "system",
"setting_id": "qCDnb2iBaz4hd4vX",
"filament_id": "OFV5wEMe",
"instantiation": "true",
"filament_flow_ratio": [
"0.95"
],
@@ -1,11 +1,11 @@
{
"type": "filament",
"filament_id": "OF9HCdyQ",
"setting_id": "N3sCgjdjvp6FTtw9",
"name": "Afinia PLA@HS",
"from": "system",
"instantiation": "true",
"inherits": "fdm_filament_pla",
"from": "system",
"setting_id": "N3sCgjdjvp6FTtw9",
"filament_id": "OF9HCdyQ",
"instantiation": "true",
"filament_cost": [
"24.99"
],
@@ -1,11 +1,11 @@
{
"type": "filament",
"filament_id": "OFXi59OX",
"setting_id": "OxIiEYjbhEvSykaQ",
"name": "Afinia Value ABS@HS",
"from": "system",
"instantiation": "true",
"inherits": "fdm_filament_abs",
"from": "system",
"setting_id": "OxIiEYjbhEvSykaQ",
"filament_id": "OFXi59OX",
"instantiation": "true",
"filament_flow_ratio": [
"0.95"
],
@@ -1,11 +1,11 @@
{
"type": "filament",
"filament_id": "OFgHh8ly",
"setting_id": "BASsUdyvElEVJ9AA",
"name": "Afinia Value PLA@HS",
"from": "system",
"instantiation": "true",
"inherits": "fdm_filament_pla",
"from": "system",
"setting_id": "BASsUdyvElEVJ9AA",
"filament_id": "OFgHh8ly",
"instantiation": "true",
"filament_cost": [
"24.99"
],
@@ -1,10 +1,10 @@
{
"type": "machine",
"setting_id": "iwkXfmEyz9RLZTvh",
"name": "Afinia H+1(HS) 0.4 nozzle",
"from": "system",
"instantiation": "true",
"inherits": "fdm_afinia_common",
"from": "system",
"setting_id": "iwkXfmEyz9RLZTvh",
"instantiation": "true",
"printer_model": "Afinia H+1(HS)",
"default_print_profile": "0.20mm Standard @Afinia H+1(HS)",
"nozzle_diameter": [
@@ -1,10 +1,10 @@
{
"type": "machine",
"setting_id": "vngsrxKvEIsmr8tQ",
"name": "Afinia H+1(HS) 0.6 nozzle",
"from": "system",
"instantiation": "true",
"inherits": "Afinia H+1(HS) 0.4 nozzle",
"from": "system",
"setting_id": "vngsrxKvEIsmr8tQ",
"instantiation": "true",
"printer_model": "Afinia H+1(HS)",
"default_print_profile": "0.30mm Strength @Afinia H+1(HS) 0.6 nozzle",
"nozzle_diameter": [
@@ -1,10 +1,10 @@
{
"type": "process",
"setting_id": "FueWYijuvLNWzNmL",
"name": "0.12mm Fine @Afinia H+1(HS)",
"from": "system",
"instantiation": "true",
"inherits": "fdm_process_afinia_HS_common",
"from": "system",
"setting_id": "FueWYijuvLNWzNmL",
"instantiation": "true",
"layer_height": "0.12",
"bottom_shell_layers": "5",
"elefant_foot_compensation": "0.15",
@@ -1,10 +1,10 @@
{
"type": "process",
"setting_id": "Ntf0ZMcETTPz7EY1",
"name": "0.16mm Optimal @Afinia H+1(HS)",
"from": "system",
"instantiation": "true",
"inherits": "fdm_process_afinia_HS_common",
"from": "system",
"setting_id": "Ntf0ZMcETTPz7EY1",
"instantiation": "true",
"layer_height": "0.16",
"elefant_foot_compensation": "0.15",
"bottom_shell_layers": "4",
@@ -1,9 +1,9 @@
{
"type": "process",
"setting_id": "Lxid5Z2e0fvVDMMm",
"name": "0.20mm Standard @Afinia H+1(HS)",
"from": "system",
"inherits": "fdm_process_afinia_HS_common",
"from": "system",
"setting_id": "Lxid5Z2e0fvVDMMm",
"instantiation": "true",
"elefant_foot_compensation": "0.15",
"top_shell_thickness": "1.0",
@@ -1,10 +1,10 @@
{
"type": "process",
"setting_id": "APeSqnRZh6I9OTFr",
"name": "0.24mm Draft @Afinia H+1(HS)",
"from": "system",
"instantiation": "true",
"inherits": "fdm_process_afinia_HS_common",
"from": "system",
"setting_id": "APeSqnRZh6I9OTFr",
"instantiation": "true",
"layer_height": "0.24",
"elefant_foot_compensation": "0.15",
"top_surface_line_width": "0.45",
@@ -1,10 +1,10 @@
{
"type": "process",
"setting_id": "VZ3NOVzdVWqZ37Ge",
"name": "0.28mm Extra Draft @Afinia H+1(HS)",
"from": "system",
"instantiation": "true",
"inherits": "fdm_process_afinia_HS_common",
"from": "system",
"setting_id": "VZ3NOVzdVWqZ37Ge",
"instantiation": "true",
"layer_height": "0.28",
"elefant_foot_compensation": "0.15",
"top_surface_line_width": "0.45",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "Anker",
"version": "02.04.00.04",
"version": "02.04.00.05",
"force_update": "0",
"description": "Anker configurations",
"machine_model_list": [
@@ -1,8 +1,8 @@
{
"type": "filament",
"name": "Generic ABS @Anker 0.2 nozzle",
"inherits": "Generic ABS @Anker base",
"renamed_from": "Anker Generic ABS 0.2 nozzle",
"inherits": "Generic ABS @Anker base",
"from": "system",
"setting_id": "iOCt7x95XbWYMe5q",
"instantiation": "true",
@@ -1,8 +1,8 @@
{
"type": "filament",
"name": "Generic ABS @Anker 0.25 nozzle",
"inherits": "Generic ABS @Anker base",
"renamed_from": "Anker Generic ABS 0.25 nozzle",
"inherits": "Generic ABS @Anker base",
"from": "system",
"setting_id": "7Q2pr70SpSjK7v7Y",
"instantiation": "true",
@@ -1,8 +1,8 @@
{
"type": "filament",
"name": "Generic ABS @Anker",
"inherits": "Generic ABS @Anker base",
"renamed_from": "Anker Generic ABS",
"inherits": "Generic ABS @Anker base",
"from": "system",
"setting_id": "x3IBYtgKBx6Mszzk",
"instantiation": "true",
@@ -1,8 +1,8 @@
{
"type": "filament",
"name": "Generic ASA @Anker 0.2 nozzle",
"inherits": "Generic ASA @Anker base",
"renamed_from": "Anker Generic ASA 0.2 nozzle",
"inherits": "Generic ASA @Anker base",
"from": "system",
"setting_id": "lTR8QLbuttyVs5UW",
"instantiation": "true",
@@ -1,8 +1,8 @@
{
"type": "filament",
"name": "Generic ASA @Anker 0.25 nozzle",
"inherits": "Generic ASA @Anker base",
"renamed_from": "Anker Generic ASA 0.25 nozzle",
"inherits": "Generic ASA @Anker base",
"from": "system",
"setting_id": "FU4BiMydEyo7zxq5",
"instantiation": "true",
@@ -1,8 +1,8 @@
{
"type": "filament",
"name": "Generic ASA @Anker",
"inherits": "Generic ASA @Anker base",
"renamed_from": "Anker Generic ASA",
"inherits": "Generic ASA @Anker base",
"from": "system",
"setting_id": "aDADuxiEKt2dVKOJ",
"instantiation": "true",
@@ -1,8 +1,8 @@
{
"type": "filament",
"name": "Generic PA @Anker 0.2 nozzle",
"inherits": "Generic PA @Anker base",
"renamed_from": "Anker Generic PA 0.2 nozzle",
"inherits": "Generic PA @Anker base",
"from": "system",
"setting_id": "tYAT7xnNFD6gY84A",
"instantiation": "true",
@@ -1,8 +1,8 @@
{
"type": "filament",
"name": "Generic PA @Anker 0.25 nozzle",
"inherits": "Generic PA @Anker base",
"renamed_from": "Anker Generic PA 0.25 nozzle",
"inherits": "Generic PA @Anker base",
"from": "system",
"setting_id": "Zlv65wLA94PbevRG",
"instantiation": "true",
@@ -1,8 +1,8 @@
{
"type": "filament",
"name": "Generic PA @Anker",
"inherits": "Generic PA @Anker base",
"renamed_from": "Anker Generic PA",
"inherits": "Generic PA @Anker base",
"from": "system",
"setting_id": "sLWVtBxpAC53eUwo",
"instantiation": "true",
@@ -1,8 +1,8 @@
{
"type": "filament",
"name": "Generic PA-CF @Anker",
"inherits": "Generic PA-CF @Anker base",
"renamed_from": "Anker Generic PA-CF",
"inherits": "Generic PA-CF @Anker base",
"from": "system",
"setting_id": "K4LcpR0zBp5klSpb",
"instantiation": "true",
@@ -1,8 +1,8 @@
{
"type": "filament",
"name": "Generic PC @Anker 0.2 nozzle",
"inherits": "Generic PC @Anker base",
"renamed_from": "Anker Generic PC 0.2 nozzle",
"inherits": "Generic PC @Anker base",
"from": "system",
"setting_id": "2sSOgctcnVh9bGbQ",
"instantiation": "true",
@@ -1,8 +1,8 @@
{
"type": "filament",
"name": "Generic PC @Anker 0.25 nozzle",
"inherits": "Generic PC @Anker base",
"renamed_from": "Anker Generic PC 0.25 nozzle",
"inherits": "Generic PC @Anker base",
"from": "system",
"setting_id": "h7IRuq5uekjNw4vg",
"instantiation": "true",
@@ -1,8 +1,8 @@
{
"type": "filament",
"name": "Generic PC @Anker",
"inherits": "Generic PC @Anker base",
"renamed_from": "Anker Generic PC",
"inherits": "Generic PC @Anker base",
"from": "system",
"setting_id": "ffufXVvW95sunH1B",
"instantiation": "true",
@@ -1,8 +1,8 @@
{
"type": "filament",
"name": "Generic PETG @Anker 0.2 nozzle",
"inherits": "Generic PETG @Anker base",
"renamed_from": "Anker Generic PETG 0.2 nozzle",
"inherits": "Generic PETG @Anker base",
"from": "system",
"setting_id": "67ZHuiXPyCP6Tdtr",
"instantiation": "true",
@@ -1,8 +1,8 @@
{
"type": "filament",
"name": "Generic PETG @Anker 0.25 nozzle",
"inherits": "Generic PETG @Anker base",
"renamed_from": "Anker Generic PETG 0.25 nozzle",
"inherits": "Generic PETG @Anker base",
"from": "system",
"setting_id": "aLuRALDt0Otbd2So",
"instantiation": "true",
@@ -1,8 +1,8 @@
{
"type": "filament",
"name": "Generic PETG @Anker",
"inherits": "Generic PETG @Anker base",
"renamed_from": "Anker Generic PETG",
"inherits": "Generic PETG @Anker base",
"from": "system",
"setting_id": "hvV0rmnzRHJk9sMz",
"instantiation": "true",
@@ -1,8 +1,8 @@
{
"type": "filament",
"name": "Generic PETG-CF @Anker",
"inherits": "Generic PETG-CF @Anker base",
"renamed_from": "Anker Generic PETG-CF",
"inherits": "Generic PETG-CF @Anker base",
"from": "system",
"setting_id": "vCAuQeQLoXUk28ph",
"instantiation": "true",
@@ -1,8 +1,8 @@
{
"type": "filament",
"name": "Generic PLA @Anker 0.2 nozzle",
"inherits": "Generic PLA @Anker base",
"renamed_from": "Anker Generic PLA 0.2 nozzle",
"inherits": "Generic PLA @Anker base",
"from": "system",
"setting_id": "rCvmCzPgi4KA6nmI",
"instantiation": "true",
@@ -1,8 +1,8 @@
{
"type": "filament",
"name": "Generic PLA @Anker 0.25 nozzle",
"inherits": "Generic PLA @Anker base",
"renamed_from": "Anker Generic PLA 0.25 nozzle",
"inherits": "Generic PLA @Anker base",
"from": "system",
"setting_id": "C0hNEbEueKrcYojS",
"instantiation": "true",
@@ -1,8 +1,8 @@
{
"type": "filament",
"name": "Generic PLA @Anker",
"inherits": "Generic PLA @Anker base",
"renamed_from": "Anker Generic PLA",
"inherits": "Generic PLA @Anker base",
"from": "system",
"setting_id": "GfVhcxRmt6mHSemI",
"instantiation": "true",
@@ -1,8 +1,8 @@
{
"type": "filament",
"name": "Generic PLA Silk @Anker 0.2 nozzle",
"inherits": "Generic PLA Silk @Anker base",
"renamed_from": "Anker Generic PLA Silk 0.2 nozzle",
"inherits": "Generic PLA Silk @Anker base",
"from": "system",
"setting_id": "NWGiPWYmLVktDMJt",
"instantiation": "true",
@@ -1,8 +1,8 @@
{
"type": "filament",
"name": "Generic PLA Silk @Anker 0.25 nozzle",
"inherits": "Generic PLA Silk @Anker base",
"renamed_from": "Anker Generic PLA Silk 0.25 nozzle",
"inherits": "Generic PLA Silk @Anker base",
"from": "system",
"setting_id": "4thKBCnYgDtnAtF5",
"instantiation": "true",
@@ -1,8 +1,8 @@
{
"type": "filament",
"name": "Generic PLA Silk @Anker",
"inherits": "Generic PLA Silk @Anker base",
"renamed_from": "Anker Generic PLA Silk",
"inherits": "Generic PLA Silk @Anker base",
"from": "system",
"setting_id": "URRIeNXIiIPjxbKL",
"instantiation": "true",
@@ -1,8 +1,8 @@
{
"type": "filament",
"name": "Generic PLA+ @Anker 0.2 nozzle",
"inherits": "Generic PLA+ @Anker base",
"renamed_from": "Anker Generic PLA+ 0.2 nozzle",
"inherits": "Generic PLA+ @Anker base",
"from": "system",
"setting_id": "5m3xLxdReqLlE2Jp",
"instantiation": "true",
@@ -1,8 +1,8 @@
{
"type": "filament",
"name": "Generic PLA+ @Anker 0.25 nozzle",
"inherits": "Generic PLA+ @Anker base",
"renamed_from": "Anker Generic PLA+ 0.25 nozzle",
"inherits": "Generic PLA+ @Anker base",
"from": "system",
"setting_id": "wwkWVDsg6SoeCS0F",
"instantiation": "true",
@@ -1,8 +1,8 @@
{
"type": "filament",
"name": "Generic PLA+ @Anker",
"inherits": "Generic PLA+ @Anker base",
"renamed_from": "Anker Generic PLA+",
"inherits": "Generic PLA+ @Anker base",
"from": "system",
"setting_id": "MDb4CH02FxbgPNjS",
"instantiation": "true",
@@ -1,8 +1,8 @@
{
"type": "filament",
"name": "Generic PLA-CF @Anker",
"inherits": "Generic PLA-CF @Anker base",
"renamed_from": "Anker Generic PLA-CF",
"inherits": "Generic PLA-CF @Anker base",
"from": "system",
"setting_id": "5AdjHUxRi8LCU4E9",
"instantiation": "true",
@@ -1,8 +1,8 @@
{
"type": "filament",
"name": "Generic PVA @Anker",
"inherits": "Generic PVA @Anker base",
"renamed_from": "Anker Generic PVA",
"inherits": "Generic PVA @Anker base",
"from": "system",
"setting_id": "HXERDaP3AgzWtmiu",
"instantiation": "true",
@@ -1,8 +1,8 @@
{
"type": "filament",
"name": "Generic TPU @Anker",
"inherits": "Generic TPU @Anker base",
"renamed_from": "Anker Generic TPU",
"inherits": "Generic TPU @Anker base",
"from": "system",
"setting_id": "5YigkBBSP2RM1MbK",
"instantiation": "true",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "Anycubic",
"version": "02.04.00.07",
"version": "02.04.00.08",
"force_update": "0",
"description": "Anycubic configurations",
"machine_model_list": [
@@ -1,8 +1,8 @@
{
"type": "filament",
"name": "Generic ABS @Anycubic",
"inherits": "fdm_filament_abs",
"renamed_from": "Anycubic Generic ABS",
"inherits": "fdm_filament_abs",
"from": "system",
"setting_id": "jKvjWU6rcHvWblWC",
"filament_id": "OFY9muEs",
@@ -1,8 +1,8 @@
{
"type": "filament",
"name": "Generic ASA @Anycubic",
"inherits": "fdm_filament_asa",
"renamed_from": "Anycubic Generic ASA",
"inherits": "fdm_filament_asa",
"from": "system",
"setting_id": "o2Cr8cBtE4OKsTqm",
"filament_id": "OFLPAxz3",
@@ -1,8 +1,8 @@
{
"type": "filament",
"name": "Generic PA @Anycubic",
"inherits": "fdm_filament_pa",
"renamed_from": "Anycubic Generic PA",
"inherits": "fdm_filament_pa",
"from": "system",
"setting_id": "5MJsYpJIimshIDZm",
"filament_id": "OFg8ndtj",
@@ -1,8 +1,8 @@
{
"type": "filament",
"name": "Generic PA-CF @Anycubic",
"inherits": "fdm_filament_pa",
"renamed_from": "Anycubic Generic PA-CF",
"inherits": "fdm_filament_pa",
"from": "system",
"setting_id": "j1uioy0lCnm4AKrm",
"filament_id": "OFQLcbps",
@@ -1,8 +1,8 @@
{
"type": "filament",
"name": "Generic PC @Anycubic",
"inherits": "fdm_filament_pc",
"renamed_from": "Anycubic Generic PC",
"inherits": "fdm_filament_pc",
"from": "system",
"setting_id": "3jP8aWkWw2dT2fNM",
"filament_id": "OFLakOUI",
@@ -1,8 +1,8 @@
{
"type": "filament",
"name": "Generic PETG @Anycubic",
"inherits": "fdm_filament_pet",
"renamed_from": "Anycubic Generic PETG",
"inherits": "fdm_filament_pet",
"from": "system",
"setting_id": "ddafY4oOvEc6L6Ej",
"filament_id": "OFYPdQJh",
@@ -1,8 +1,8 @@
{
"type": "filament",
"name": "Generic PLA @Anycubic",
"inherits": "fdm_filament_pla",
"renamed_from": "Anycubic Generic PLA",
"inherits": "fdm_filament_pla",
"from": "system",
"setting_id": "qoYfeBQCSt15O9ye",
"filament_id": "OFDSrzZ8",
@@ -1,8 +1,8 @@
{
"type": "filament",
"name": "Generic PLA-CF @Anycubic",
"inherits": "fdm_filament_pla",
"renamed_from": "Anycubic Generic PLA-CF",
"inherits": "fdm_filament_pla",
"from": "system",
"setting_id": "mLENUT39vBcQiLpm",
"filament_id": "OFWbdGsC",
@@ -1,8 +1,8 @@
{
"type": "filament",
"name": "Generic PVA @Anycubic",
"inherits": "fdm_filament_pva",
"renamed_from": "Anycubic Generic PVA",
"inherits": "fdm_filament_pva",
"from": "system",
"setting_id": "ae5ljmDsW7uauLz8",
"filament_id": "OFDvXujf",
@@ -1,8 +1,8 @@
{
"type": "filament",
"name": "Generic TPU @Anycubic",
"inherits": "fdm_filament_tpu",
"renamed_from": "Anycubic Generic TPU",
"inherits": "fdm_filament_tpu",
"from": "system",
"setting_id": "z0FSikhD8ZHv2UYt",
"filament_id": "OFgbpcy9",
@@ -104,79 +104,64 @@
"machine_end_gcode": "M400\nG92 E0 ; zero the extruder\nG1 E-2 F3600\n{if max_layer_z < max_print_height-1}G1 Z{max_layer_z+2} F900 ; Move print head further up{endif}\nG1 X420 Y420 F12000 ; present print\nM140 S0 ; turn off heatbed\nM104 S0 ; turn off temperature\nM107;turn off fan\nM84; disable motors\n; disable stepper motors",
"machine_load_filament_time": "75",
"machine_max_acceleration_e": [
"10000",
"10000",
"10000"
],
"machine_max_acceleration_extruding": [
"10000",
"10000",
"10000"
],
"machine_max_acceleration_retracting": [
"10000",
"10000",
"10000"
],
"machine_max_acceleration_travel": [
"10000",
"10000",
"10000"
],
"machine_max_acceleration_x": [
"10000",
"10000",
"10000"
],
"machine_max_acceleration_y": [
"10000",
"10000",
"10000"
],
"machine_max_acceleration_z": [
"3000",
"3000",
"3000"
],
"machine_max_jerk_e": [
"20",
"20",
"20"
],
"machine_max_jerk_x": [
"20",
"20",
"20"
],
"machine_max_jerk_y": [
"20",
"20",
"20"
],
"machine_max_jerk_z": [
"20",
"20",
"20"
],
"machine_max_speed_e": [
"600",
"300",
"780"
"300"
],
"machine_max_speed_x": [
"600",
"300",
"780"
"300"
],
"machine_max_speed_y": [
"600",
"300",
"780"
"300"
],
"machine_max_speed_z": [
"20",
"10",
"26"
"10"
],
"machine_min_extruding_rate": [
"0",
@@ -77,79 +77,64 @@
"machine_end_gcode": "G92 E0\nG1 E-2 F3600\n{if max_layer_z < max_print_height-1}G1 Z{max_layer_z+2} F900 ; Move print head further up{endif}\nG1 X250 Y220 F12000 ; present print\nM140 S0 ; turn off heatbed\nM104 S0 ; turn off temperature\nM107;turn off fan\nM84; disable motors\n; disable stepper motors\n\n",
"machine_load_filament_time": "42",
"machine_max_acceleration_e": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_extruding": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_retracting": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_travel": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_x": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_y": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_z": [
"1000",
"1000",
"1000"
],
"machine_max_jerk_e": [
"20",
"20",
"20"
],
"machine_max_jerk_x": [
"20",
"20",
"20"
],
"machine_max_jerk_y": [
"20",
"20",
"20"
],
"machine_max_jerk_z": [
"20",
"20",
"20"
],
"machine_max_speed_e": [
"600",
"300",
"780"
"300"
],
"machine_max_speed_x": [
"600",
"300",
"780"
"300"
],
"machine_max_speed_y": [
"600",
"300",
"780"
"300"
],
"machine_max_speed_z": [
"15",
"10",
"26"
"10"
],
"machine_min_extruding_rate": [
"0",
@@ -73,79 +73,64 @@
"machine_end_gcode": "M400\nG92 E0 ; zero the extruder\nG1 E-2 F3600\n{if max_layer_z < max_print_height-1}G1 Z{max_layer_z+2} F900 ; Move print head further up{endif}\nG1 X250 Y220 F12000 ; present print\nM140 S0 ; turn off heatbed\nM104 S0 ; turn off temperature\nM107;turn off fan\nM84; disable motors\n; disable stepper motors",
"machine_load_filament_time": "42",
"machine_max_acceleration_e": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_extruding": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_retracting": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_travel": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_x": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_y": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_z": [
"1000",
"1000",
"1000"
],
"machine_max_jerk_e": [
"20",
"20",
"20"
],
"machine_max_jerk_x": [
"20",
"20",
"20"
],
"machine_max_jerk_y": [
"20",
"20",
"20"
],
"machine_max_jerk_z": [
"20",
"20",
"20"
],
"machine_max_speed_e": [
"600",
"300",
"780"
"300"
],
"machine_max_speed_x": [
"600",
"300",
"780"
"300"
],
"machine_max_speed_y": [
"600",
"300",
"780"
"300"
],
"machine_max_speed_z": [
"15",
"10",
"26"
"10"
],
"machine_min_extruding_rate": [
"0",
@@ -73,79 +73,64 @@
"machine_end_gcode": "M400 ; wait for buffer to clear\nG92 E0 ; zero the extruder\nG1 E-1 F3600 ; retract\n{if max_layer_z < max_print_height-1}G1 Z{max_layer_z+2} F600 ; Move print head further up{endif}\nG1 X250 Y220 F12000 ; present print\nM140 S0 ; turn off heatbed\nM104 S0 ; turn off temperature\nM107;turn off fan\nM84; disable motors\n; disable stepper motors\n\n",
"machine_load_filament_time": "42",
"machine_max_acceleration_e": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_extruding": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_retracting": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_travel": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_x": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_y": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_z": [
"1000",
"1000",
"1000"
],
"machine_max_jerk_e": [
"20",
"20",
"20"
],
"machine_max_jerk_x": [
"20",
"20",
"20"
],
"machine_max_jerk_y": [
"20",
"20",
"20"
],
"machine_max_jerk_z": [
"20",
"20",
"20"
],
"machine_max_speed_e": [
"600",
"300",
"780"
"300"
],
"machine_max_speed_x": [
"600",
"300",
"780"
"300"
],
"machine_max_speed_y": [
"600",
"300",
"780"
"300"
],
"machine_max_speed_z": [
"15",
"10",
"26"
"10"
],
"machine_min_extruding_rate": [
"0",
@@ -77,79 +77,64 @@
"machine_end_gcode": "M400 ; wait for buffer to clear\nG92 E0 ; zero the extruder\nG1 E-2 F3000 ; retract\n{if max_layer_z < max_print_height-1}G1 Z{max_layer_z+2} F600 ; Move print head further up{endif}\nG1 X250 Y220 F12000 ; present print\nM140 S0 ; turn off heatbed\nM104 S0 ; turn off temperature\nM107;turn off fan\nM84; disable motors\n; disable stepper motors\n\n",
"machine_load_filament_time": "42",
"machine_max_acceleration_e": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_extruding": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_retracting": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_travel": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_x": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_y": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_z": [
"1000",
"1000",
"1000"
],
"machine_max_jerk_e": [
"20",
"20",
"20"
],
"machine_max_jerk_x": [
"20",
"20",
"20"
],
"machine_max_jerk_y": [
"20",
"20",
"20"
],
"machine_max_jerk_z": [
"20",
"20",
"20"
],
"machine_max_speed_e": [
"600",
"300",
"780"
"300"
],
"machine_max_speed_x": [
"600",
"300",
"780"
"300"
],
"machine_max_speed_y": [
"600",
"300",
"780"
"300"
],
"machine_max_speed_z": [
"15",
"10",
"26"
"10"
],
"machine_min_extruding_rate": [
"0",
@@ -1,12 +1,12 @@
{
"type": "machine_model",
"machine_tech": "FFF",
"family": "Anycubic",
"name": "Anycubic Kobra 3 V2",
"model_id": "Anycubic Kobra 3 V2",
"nozzle_diameter": "0.4;0.2;0.6;0.8",
"bed_model": "Anycubic Kobra 3 V2_buildplate_model.stl",
"bed_texture": "Anycubic Kobra 3 V2_buildplate_texture.svg",
"hotend_model": "",
"default_materials": "Anycubic PLA @Anycubic Kobra 3 V2 0.2 nozzle;Anycubic PLA @Anycubic Kobra 3 V2 0.4 nozzle;Anycubic PLA @Anycubic Kobra 3 V2 0.6 nozzle;Anycubic PLA @Anycubic Kobra 3 V2 0.8 nozzle"
"type": "machine_model",
"name": "Anycubic Kobra 3 V2",
"machine_tech": "FFF",
"family": "Anycubic",
"model_id": "Anycubic Kobra 3 V2",
"nozzle_diameter": "0.4;0.2;0.6;0.8",
"bed_model": "Anycubic Kobra 3 V2_buildplate_model.stl",
"bed_texture": "Anycubic Kobra 3 V2_buildplate_texture.svg",
"hotend_model": "",
"default_materials": "Anycubic PLA @Anycubic Kobra 3 V2 0.2 nozzle;Anycubic PLA @Anycubic Kobra 3 V2 0.4 nozzle;Anycubic PLA @Anycubic Kobra 3 V2 0.6 nozzle;Anycubic PLA @Anycubic Kobra 3 V2 0.8 nozzle"
}
@@ -73,79 +73,64 @@
"machine_end_gcode": "M400\nM140 S0 ; turn off heatbed\nM104 S0 ; turn off temperature\nM141 S0 ; turn off box temperature\nM106 P1 S0 ; turn off fan\nM106 P2 S0\nM106 P3 S0\nM84; disable motors \n; disable stepper motors",
"machine_load_filament_time": "0",
"machine_max_acceleration_e": [
"5000",
"5000",
"5000"
],
"machine_max_acceleration_extruding": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_retracting": [
"5000",
"5000",
"5000"
],
"machine_max_acceleration_travel": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_x": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_y": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_z": [
"400",
"400",
"400"
],
"machine_max_jerk_e": [
"1",
"1",
"1"
],
"machine_max_jerk_x": [
"15",
"15",
"15"
],
"machine_max_jerk_y": [
"15",
"15",
"15"
],
"machine_max_jerk_z": [
"1",
"1",
"1"
],
"machine_max_speed_e": [
"80",
"40",
"104"
"40"
],
"machine_max_speed_x": [
"600",
"300",
"780"
"300"
],
"machine_max_speed_y": [
"600",
"300",
"780"
"300"
],
"machine_max_speed_z": [
"10",
"5",
"13"
"5"
],
"machine_min_extruding_rate": [
"0",
@@ -73,79 +73,64 @@
"machine_end_gcode": "M400\nM140 S0 ; turn off heatbed\nM104 S0 ; turn off temperature\nM141 S0 ; turn off box temperature\nM106 P1 S0 ; turn off fan\nM106 P2 S0\nM106 P3 S0\nM84; disable motors \n; disable stepper motors",
"machine_load_filament_time": "0",
"machine_max_acceleration_e": [
"5000",
"5000",
"5000"
],
"machine_max_acceleration_extruding": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_retracting": [
"5000",
"5000",
"5000"
],
"machine_max_acceleration_travel": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_x": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_y": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_z": [
"400",
"400",
"400"
],
"machine_max_jerk_e": [
"1",
"1",
"1"
],
"machine_max_jerk_x": [
"15",
"15",
"15"
],
"machine_max_jerk_y": [
"15",
"15",
"15"
],
"machine_max_jerk_z": [
"1",
"1",
"1"
],
"machine_max_speed_e": [
"80",
"40",
"104"
"40"
],
"machine_max_speed_x": [
"600",
"300",
"780"
"300"
],
"machine_max_speed_y": [
"600",
"300",
"780"
"300"
],
"machine_max_speed_z": [
"10",
"5",
"13"
"5"
],
"machine_min_extruding_rate": [
"0",
@@ -73,79 +73,64 @@
"machine_end_gcode": "M400\nM140 S0 ; turn off heatbed\nM104 S0 ; turn off temperature\nM141 S0 ; turn off box temperature\nM106 P1 S0 ; turn off fan\nM106 P2 S0\nM106 P3 S0\nM84; disable motors \n; disable stepper motors",
"machine_load_filament_time": "0",
"machine_max_acceleration_e": [
"5000",
"5000",
"5000"
],
"machine_max_acceleration_extruding": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_retracting": [
"5000",
"5000",
"5000"
],
"machine_max_acceleration_travel": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_x": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_y": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_z": [
"400",
"400",
"400"
],
"machine_max_jerk_e": [
"1",
"1",
"1"
],
"machine_max_jerk_x": [
"15",
"15",
"15"
],
"machine_max_jerk_y": [
"15",
"15",
"15"
],
"machine_max_jerk_z": [
"1",
"1",
"1"
],
"machine_max_speed_e": [
"80",
"40",
"104"
"40"
],
"machine_max_speed_x": [
"600",
"300",
"780"
"300"
],
"machine_max_speed_y": [
"600",
"300",
"780"
"300"
],
"machine_max_speed_z": [
"10",
"5",
"13"
"5"
],
"machine_min_extruding_rate": [
"0",
@@ -73,79 +73,64 @@
"machine_end_gcode": "M400\nM140 S0 ; turn off heatbed\nM104 S0 ; turn off temperature\nM141 S0 ; turn off box temperature\nM106 P1 S0 ; turn off fan\nM106 P2 S0\nM106 P3 S0\nM84; disable motors \n; disable stepper motors",
"machine_load_filament_time": "0",
"machine_max_acceleration_e": [
"5000",
"5000",
"5000"
],
"machine_max_acceleration_extruding": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_retracting": [
"5000",
"5000",
"5000"
],
"machine_max_acceleration_travel": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_x": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_y": [
"20000",
"20000",
"20000"
],
"machine_max_acceleration_z": [
"400",
"400",
"400"
],
"machine_max_jerk_e": [
"1",
"1",
"1"
],
"machine_max_jerk_x": [
"15",
"15",
"15"
],
"machine_max_jerk_y": [
"15",
"15",
"15"
],
"machine_max_jerk_z": [
"1",
"1",
"1"
],
"machine_max_speed_e": [
"80",
"40",
"104"
"40"
],
"machine_max_speed_x": [
"600",
"300",
"780"
"300"
],
"machine_max_speed_y": [
"600",
"300",
"780"
"300"
],
"machine_max_speed_z": [
"10",
"5",
"13"
"5"
],
"machine_min_extruding_rate": [
"0",
@@ -1,12 +1,12 @@
{
"type": "machine_model",
"machine_tech": "FFF",
"family": "Anycubic",
"name": "Anycubic Kobra S1 Max",
"model_id": "Anycubic Kobra S1 Max",
"nozzle_diameter": "0.4;0.25;0.6;0.8",
"bed_model": "Anycubic Kobra S1 Max_buildplate_model.stl",
"bed_texture": "Anycubic Kobra S1 Max_buildplate_texture.svg",
"hotend_model": "",
"default_materials": "Anycubic PLA @Anycubic Kobra S1 Max 0.4 nozzle;Anycubic PLA @Anycubic Kobra S1 Max 0.25 nozzle;Anycubic PLA @Anycubic Kobra S1 Max 0.6 nozzle;Anycubic PLA @Anycubic Kobra S1 Max 0.8 nozzle"
"type": "machine_model",
"name": "Anycubic Kobra S1 Max",
"machine_tech": "FFF",
"family": "Anycubic",
"model_id": "Anycubic Kobra S1 Max",
"nozzle_diameter": "0.4;0.25;0.6;0.8",
"bed_model": "Anycubic Kobra S1 Max_buildplate_model.stl",
"bed_texture": "Anycubic Kobra S1 Max_buildplate_texture.svg",
"hotend_model": "",
"default_materials": "Anycubic PLA @Anycubic Kobra S1 Max 0.4 nozzle;Anycubic PLA @Anycubic Kobra S1 Max 0.25 nozzle;Anycubic PLA @Anycubic Kobra S1 Max 0.6 nozzle;Anycubic PLA @Anycubic Kobra S1 Max 0.8 nozzle"
}
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "Artillery",
"version": "02.04.00.05",
"version": "02.04.00.06",
"force_update": "0",
"description": "Artillery configurations",
"machine_model_list": [
@@ -1,12 +1,12 @@
{
"type": "filament",
"filament_id": "OFY9muEs",
"setting_id": "DNgTEZpEz8bJoobJ",
"name": "Generic ABS @Artillery",
"from": "system",
"instantiation": "true",
"inherits": "fdm_filament_abs",
"renamed_from": "Artillery Generic ABS",
"inherits": "fdm_filament_abs",
"from": "system",
"setting_id": "DNgTEZpEz8bJoobJ",
"filament_id": "OFY9muEs",
"instantiation": "true",
"filament_flow_ratio": [
"0.926"
],
@@ -1,12 +1,12 @@
{
"type": "filament",
"filament_id": "OFLPAxz3",
"setting_id": "xWHuScm0fPJCqjVx",
"name": "Generic ASA @Artillery",
"from": "system",
"instantiation": "true",
"inherits": "fdm_filament_asa",
"renamed_from": "Artillery Generic ASA",
"inherits": "fdm_filament_asa",
"from": "system",
"setting_id": "xWHuScm0fPJCqjVx",
"filament_id": "OFLPAxz3",
"instantiation": "true",
"filament_flow_ratio": [
"0.93"
],
@@ -1,12 +1,12 @@
{
"type": "filament",
"filament_id": "OFYPdQJh",
"setting_id": "6wZKfV2XvuJSUSwl",
"name": "Generic PETG @Artillery",
"from": "system",
"instantiation": "true",
"inherits": "fdm_filament_pet",
"renamed_from": "Artillery Generic PETG",
"inherits": "fdm_filament_pet",
"from": "system",
"setting_id": "6wZKfV2XvuJSUSwl",
"filament_id": "OFYPdQJh",
"instantiation": "true",
"reduce_fan_stop_start_freq": [
"1"
],
@@ -1,12 +1,12 @@
{
"type": "filament",
"filament_id": "OFDSrzZ8",
"setting_id": "T0sW3uMBr94Y1X1G",
"name": "Generic PLA @Artillery",
"from": "system",
"instantiation": "true",
"inherits": "fdm_filament_pla",
"renamed_from": "Artillery Generic PLA",
"inherits": "fdm_filament_pla",
"from": "system",
"setting_id": "T0sW3uMBr94Y1X1G",
"filament_id": "OFDSrzZ8",
"instantiation": "true",
"filament_flow_ratio": [
"0.98"
],
@@ -1,12 +1,12 @@
{
"type": "filament",
"filament_id": "OFWbdGsC",
"setting_id": "Gxafdb0zYnf9kPwa",
"name": "Generic PLA-CF @Artillery",
"from": "system",
"instantiation": "true",
"inherits": "fdm_filament_pla",
"renamed_from": "Artillery Generic PLA-CF",
"inherits": "fdm_filament_pla",
"from": "system",
"setting_id": "Gxafdb0zYnf9kPwa",
"filament_id": "OFWbdGsC",
"instantiation": "true",
"filament_flow_ratio": [
"0.95"
],

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