# Validating profiles ```bash ./scripts/check_profile.sh # everything CI runs ./scripts/check_profile.sh --vendor "" # development loop ./scripts/check_profile.sh profile_tool validate_slice # named checks only ``` ```bat scripts\check_profile.bat :: the same three, on Windows scripts\check_profile.bat -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. 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 runs even after an earlier one fails; the script exits non-zero if any failed, and writes `logs/.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 ` 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 | `/validator/OrcaSlicer_profile_validator` | | macOS | `/validator/OrcaSlicer_profile_validator.app/Contents/MacOS/OrcaSlicer_profile_validator` | | Windows | `\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 ` means a live run holds `/.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, 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 -l 2` | a shipped preset name that a past release offered no longer resolving | **`-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. **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. ### `validate_custom`: the backward-compatibility gate It downloads one fixture archive per past release (v1.9.0 onwards) of *generated mock* user presets: a `__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, `/` 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` 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 `fix-variant`, `generate-id`, `normalize`, `trim` and `update-index`; [ids.md](ids.md#the-tool) has the writing half. | 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 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` | 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 (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 "" in `, 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: at …/.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 command that fixes the batch. - 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 keys `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 `); `normalize` removes them. ### Default-material references 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 ` ` 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 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 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` (case-insensitive), otherwise `machine_model`. That heuristic cannot reliably classify shared machine bases or unusually named variants. 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; 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 `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` 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/` and `build*/*/src/`), 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, point at it with `--validator` / `-Validator`, or set `ORCA_PROFILE_VALIDATOR` (`$env:ORCA_PROFILE_VALIDATOR` in PowerShell). | Flag | Meaning | | --- | --- | | `-p ` | profile tree (also becomes the data dir) | | `-l ` | log level; CI uses 2 | | `-v ` | load only that vendor **plus** OrcaFilamentLibrary | | `-s` | slice sweep | | `-o ` | 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 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 direct run, inspect `user/` and remove only empty directories created by that run; fixtures or pre-existing user files may be present. ## Checking a copy of the tree Use `--profiles DIR` on the Python tool and `-p DIR` on the validator. The wrappers' `--profiles` / `-ProfilesDir` passes the tree to both, so one run validates a copy fully: ```bash ./scripts/check_profile.sh --profiles "" # Windows: scripts\check_profile.bat -ProfilesDir "" ``` 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 -p /profiles -v "" -s -o ``` Each printer's G-code is saved as `__.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 `/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 | | --- | --- | | macOS | `~/Library/Application Support/OrcaSlicer` | | 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. ## Error → remedy | Message | Fix | | --- | --- | | `can not find inherits for ` | 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 ` | nothing in the chain declares one: run `generate-id` | | `can not find parent for config !` | a shipped name disappeared: add `renamed_from` | | `Failed loading configuration 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 : nil`, `Deserializing nil into a non-nullable object`); the lines above it name the cause | | `Missing instantiation attribute for ` | key absent **or** not the string `"true"` / `"false"` | | `contains incorrect keys: , which were removed` | a key valid for a different preset type | | `defines invalid printer variant ""` | not a token of the model's `nozzle_diameter` list | | `has printer_variant "" that does not match its nozzle_diameter` | [the set comparison](machine-profiles.md#printer_model-and-printer_variant) | | `references unknown compatible_printers "

"` | 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 "" (now "")` | in-tree references must name the current preset; `renamed_from` does not excuse them | | `Filament preset "" 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 .json list references it, so it never loads` | `update-index`, or delete the file | | `[ERROR] … no .json list references it and it declares no profile type` | set the correct `type` explicitly, then `normalize` and `update-index` | | `[ERROR] … normalize would ` / `.json: update-index would rebuild ` | run that command and commit the result | | `[ERROR] has N profiles named ""` | identify the intended preset and remove or rename the duplicate; use `trim --dry-run` only for deliberate unindexed-file cleanup | | `Duplicate key error in : Duplicate key detected: ` | 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 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) | | `"" has N values for variant length S at stride k, which takes M` (with ` (no , so …)` after `S`, naming where `S` came from, when the preset writes no list, and ` (it comes from )` 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)) | | ` 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, ` ` 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 | | ` 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:

` | 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 "" in its "default_materials"` / `… "default_filament_profile"` | name an existing system (not user, not base) filament exactly; `;` separators, exact case | | `Missing filament profile: '' referenced in ` | 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 "" is declared by N bundles` | model names are unique across the tree; rename the new model | | `vendor 's config version: 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 "

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

" 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"**, 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 (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 ``, 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 PR limited to their own `resources/profiles//` 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.